Files
pmedawala 4403c71b96 sound: tegra-safety-audio: Fix CERT C issues
Fix the following CIDs:

CID 689175
CID 689167
CID 689171

Bug 3959323

Change-Id: I86ebee06eb0f06f8f8424fc62b94a079b8e9fe80
Signed-off-by: pmedawala <pmedawala@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3006590
Reviewed-by: Uday Gupta <udayg@nvidia.com>
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
2023-11-01 08:58:38 -07:00

834 lines
20 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#define pr_fmt(msg) "Safety I2S: " msg
#include <nvidia/conftest.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include "tegra_i2s.h"
struct safety_audio_priv {
struct snd_card *card;
};
static int safety_i2s_trigger(struct snd_pcm_substream *substream, int cmd);
static const char * const clk_names[] = {
"pll_a_out0",
"i2s7", "i2s7_clk_parent", "i2s7_ext_audio_sync",
"i2s7_audio_sync", "i2s7_sync_input",
"i2s8", "i2s8_clk_parent", "i2s8_ext_audio_sync",
"i2s8_audio_sync", "i2s8_sync_input",
};
static const char * const reset_names[] = {
"i2s7_reset", "i2s8_reset"
};
//TODO: Either encapsulate it or allocate dynamically
static struct i2s_dev i2s[NUM_SAFETY_I2S_INST];
static unsigned int enabled_i2s_mask[NUM_SAFETY_I2S_INST];
static struct safety_audio_priv *priv;
static const struct snd_pcm_hardware t234_pcm_hardware = {
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 16,
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_INTERLEAVED,
.formats = SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.period_bytes_min = PAGE_SIZE * 16,
.period_bytes_max = PAGE_SIZE * 32,
.periods_min = 1,
.periods_max = 4,
.fifo_size = 256,
.buffer_bytes_max = PAGE_SIZE * 128,
};
static const struct i2s_config i2s_defaults = {
.srate = 48000,
.channels = 8,
.fsync_width = 255,
.bclk_ratio = 1,
.pcm_mask_bits = 0,
.highz_ctrl = 0,
.bit_size = 32,
.total_slots = 8
};
struct i2s_dev *safety_i2s_get_priv(void)
{
return i2s;
}
static int loopback_control_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uc)
{
int id = (int)kctl->private_value;
int enable = uc->value.integer.value[0];
return i2s_set_loopback(id, enable);
}
static int loopback_control_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uc)
{
return 0;
}
static int alwayson_control_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uc)
{
int rx = (int)kctl->private_value & 1;
uint32_t id = (uint32_t)kctl->private_value >> 1;
int enable = uc->value.integer.value[0];
struct i2s_config *i2s_config = &i2s[id].config;
if (rx)
i2s_config->rx_always_on = enable;
else
i2s_config->tx_always_on = enable;
if (enable && rx)
i2s_enable_rx(id);
if (enable && !rx)
i2s_enable_tx(id);
return 0;
}
static int alwayson_control_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uc)
{
int rx = (int)kctl->private_value & 1;
uint32_t id = (uint32_t)kctl->private_value >> 1;
struct i2s_config *i2s_config = &i2s[id].config;
if (rx)
return i2s_config->rx_always_on;
return i2s_config->tx_always_on;
}
static int snd_myctl_mono_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static const struct snd_kcontrol_new controls_i2s7[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "I2S7-RX ON",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.put = alwayson_control_put,
.get = alwayson_control_get,
.info = snd_myctl_mono_info,
.private_value = 0
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "I2S7-TX ON",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.put = alwayson_control_put,
.get = alwayson_control_get,
.info = snd_myctl_mono_info,
.private_value = 1
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "I2S7 Loopback",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.put = loopback_control_put,
.get = loopback_control_get,
.info = snd_myctl_mono_info,
.private_value = 0
}
};
static const struct snd_kcontrol_new controls_i2s8[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "I2S8 Loopback",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.put = loopback_control_put,
.get = loopback_control_get,
.info = snd_myctl_mono_info,
.private_value = 1
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "I2S8-RX ON",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.put = alwayson_control_put,
.get = alwayson_control_get,
.info = snd_myctl_mono_info,
.private_value = 2
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "I2S8-TX ON",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.put = alwayson_control_put,
.get = alwayson_control_get,
.info = snd_myctl_mono_info,
.private_value = 3
}
};
static int safety_i2s_add_kcontrols(struct snd_card *card, int id)
{
int num_of_controls, i = 0, ret = 0;
if (id == 0) {
num_of_controls = ARRAY_SIZE(controls_i2s7);
for (i = 0; i < num_of_controls; i++)
ret = snd_ctl_add(card, snd_ctl_new1(&controls_i2s7[i], i2s));
} else if (id == 1) {
num_of_controls = ARRAY_SIZE(controls_i2s8);
for (i = 0; i < num_of_controls; i++)
ret = snd_ctl_add(card, snd_ctl_new1(&controls_i2s8[i], i2s));
}
return ret;
}
static int prealloc_dma_buff(struct snd_pcm *pcm, unsigned int stream, size_t size)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buff = &substream->dma_buffer;
buff->area = dma_alloc_coherent(pcm->card->dev, size,
&buff->addr, GFP_KERNEL);
if (!buff->area)
return -ENOMEM;
buff->private_data = NULL;
buff->dev.type = SNDRV_DMA_TYPE_DEV;
buff->dev.dev = pcm->card->dev;
buff->bytes = size;
return 0;
}
static int setup_plls(struct device *dev)
{
/* Do nothing for now */
return 0;
}
static int i2s_reset_init_and_deassert(struct device *dev, unsigned int id)
{
struct reset_control *reset;
int ret;
reset = of_reset_control_get(dev->of_node, reset_names[id]);
if (IS_ERR(reset)) {
pr_alert("No reset information found in DT, skipping...");
return PTR_ERR(reset);
}
i2s[id].reset = reset;
pr_alert("Clearing reset for i2s%d... ", id + 7);
ret = reset_control_deassert(reset);
if (ret)
pr_alert("Failed!\n");
else
pr_alert("Success!\n");
return ret;
}
static int i2s_clock_init(struct device *dev, unsigned int id)
{
i2s[id].audio_sync_input = devm_clk_get(dev,
clk_names[id * CLK_NUM_ENTRIES + CLK_AUDIO_INPUT_SYNC]);
if (IS_ERR(i2s[id].audio_sync_input)) {
pr_alert("Could not get audio_sync_input clock from DT\n");
return -EINVAL;
}
i2s[id].audio_sync = devm_clk_get(dev,
clk_names[id * CLK_NUM_ENTRIES + CLK_AUDIO_SYNC]);
if (IS_ERR(i2s[id].audio_sync)) {
pr_alert("Could not get audio_sync clock from DT\n");
return -EINVAL;
}
i2s[id].i2s_sync = devm_clk_get(dev,
clk_names[id * CLK_NUM_ENTRIES + CLK_I2S_SYNC]);
if (IS_ERR(i2s[id].i2s_sync)) {
pr_alert("Could not get i2s_sync clock from DT\n");
return -EINVAL;
}
i2s[id].clk_i2s = devm_clk_get(dev,
clk_names[id * CLK_NUM_ENTRIES + CLK_I2S]);
if (IS_ERR(i2s[id].clk_i2s)) {
pr_alert("Could not get clk_i2s clock from DT\n");
return -EINVAL;
}
i2s[id].clk_i2s_src = devm_clk_get(dev,
clk_names[id * CLK_NUM_ENTRIES + CLK_I2S_SOURCE]);
if (IS_ERR(i2s[id].clk_i2s_src)) {
pr_alert("Could not get clk_i2s_src clock from DT\n");
return -EINVAL;
}
return 0;
}
static int32_t
i2s_get_mode(const void *mode, uint32_t *tx_mode, uint32_t *data_offset)
{
uint32_t i;
struct {
char *name;
uint32_t mode;
uint32_t data_offset;
} i2s_mode[] = {
{"dsp_a", 1, 1},
{"dsp_b", 1, 0},
{"i2s", 0, 1},
};
for (i = 0; i < ARRAY_SIZE(i2s_mode); i++) {
if (strcmp(mode, i2s_mode[i].name) == 0) {
*tx_mode = i2s_mode[i].mode;
*data_offset = i2s_mode[i].data_offset;
return 0;
}
}
return -1;
}
#ifdef SAFETY_I2S_DEBUG
static void dump_config(struct i2s_config *config)
{
#define dump(x) pr_alert("%s = %u\n", #x, x)
dump(config->mode);
dump(config->clock_mode);
dump(config->clock_polarity);
dump(config->edge_ctrl);
dump(config->total_slots);
dump(config->bclk);
dump(config->bit_size);
dump(config->channels);
dump(config->offset);
dump(config->tx_mask);
dump(config->rx_mask);
dump(config->srate);
dump(config->bclk_ratio);
dump(config->fsync_width);
dump(config->pcm_mask_bits);
dump(config->highz_ctrl);
dump(config->clock_trim);
}
#endif
static int i2s_parse_dt(struct device *dev, unsigned int id)
{
char name[5];
struct device_node *i2s_node;
struct i2s_config *i2s_config;
const void *prop;
int ret = 0;
ret = sprintf(name, I2S_DT_NODE, I2S_NODE_START_INDEX + id);
if (ret < 0)
return -EINVAL;
i2s_config = &i2s[id].config;
memcpy(i2s_config, &i2s_defaults, sizeof(i2s_config[0]));
i2s_config->clock_polarity = 1;
i2s_node = of_get_child_by_name(dev->of_node, name);
if (i2s_node == NULL) {
pr_alert("Invalid device tree node\n");
return -EINVAL;
}
if (of_find_property(i2s_node, "frame-slave", NULL))
i2s_config->clock_mode = 1;
prop = of_get_property(i2s_node, "format", NULL);
if (prop != NULL) {
i2s_get_mode(prop, &i2s_config->mode, &i2s_config->offset);
if (strcmp("i2s", prop) == 0)
i2s_config->clock_polarity = 0;
}
if (of_find_property(i2s_node, "bitclock-inversion", NULL))
i2s_config->edge_ctrl = 1;
if (of_find_property(i2s_node, "frame-inversion", NULL))
i2s_config->clock_polarity = !i2s_config->clock_polarity;
prop = of_get_property(i2s_node, "tx-mask", NULL);
if (prop != NULL)
i2s_config->tx_mask = be32_to_cpup(prop);
prop = of_get_property(i2s_node, "rx-mask", NULL);
if (prop != NULL)
i2s_config->rx_mask = be32_to_cpup(prop);
prop = of_get_property(i2s_node, "clk-trim", NULL);
if (prop != NULL)
i2s_config->clock_trim = be32_to_cpup(prop);
prop = of_get_property(i2s_node, "fsync-width", NULL);
if (prop != NULL)
i2s_config->fsync_width = be32_to_cpup(prop);
prop = of_get_property(i2s_node, "srate", NULL);
if (prop != NULL)
i2s_config->srate = be32_to_cpup(prop);
prop = of_get_property(i2s_node, "num-channel", NULL);
if (prop != NULL)
i2s_config->channels = be32_to_cpup(prop);
prop = of_get_property(i2s_node, "bit-format", NULL);
if (prop != NULL)
i2s_config->bit_size = be32_to_cpup(prop);
#ifdef SAFETY_I2S_DEBUG
dump_config(i2s_config);
#endif
return 0;
}
static int is_supported_rate(int rate)
{
return 1;
}
static int i2s_set_rate(unsigned int id, int rate)
{
unsigned long i2s_clk_freq;
int err;
if (!is_supported_rate(rate))
return -EINVAL;
i2s_clk_freq = i2s[id].config.channels * i2s[id].config.srate *
i2s[id].config.bit_size * i2s[id].config.bclk_ratio;
if (i2s[id].config.clock_mode == I2S_MASTER) {
err = clk_set_parent(i2s[id].audio_sync, i2s[id].i2s_sync);
if (err != 0)
return -EINVAL;
err = clk_set_parent(i2s[id].clk_i2s, i2s[id].clk_i2s_src);
if (err != 0)
return -EINVAL;
err = clk_set_rate(i2s[id].clk_i2s, i2s_clk_freq);
if (err != 0)
return -EINVAL;
err = clk_prepare_enable(i2s[id].clk_i2s);
if (err != 0)
return -EINVAL;
}
err = clk_set_rate(i2s[id].audio_sync_input, i2s_clk_freq);
if (err != 0)
return -EINVAL;
return 0;
}
static void i2s_setup(unsigned int id)
{
i2s_set_rate(id, i2s[id].config.srate);
i2s_configure(id, &i2s[id].config);
}
static void safety_i2s_start(struct snd_pcm_substream *substream)
{
struct dma_data *data = substream->private_data;
unsigned int id = data->req_sel - 1;
int rx = (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? 1 : 0;
if (rx)
i2s_enable_rx(id);
else
i2s_enable_tx(id);
data->triggered = 1;
}
static void safety_i2s_stop(struct snd_pcm_substream *substream)
{
struct dma_data *data = substream->private_data;
unsigned int id = data->req_sel - 1;
int rx = (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ? 1 : 0;
if (rx)
i2s_disable_rx(id);
else
i2s_disable_tx(id);
data->triggered = 0;
}
static int gpcdma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct dma_slave_config slave_config;
struct dma_data *dma_data = substream->private_data;
struct dma_chan *chan;
int ret;
chan = snd_dmaengine_pcm_get_chan(substream);
ret = snd_hwparams_to_dma_slave_config(substream, params,
&slave_config);
if (ret) {
pr_alert("gpcdma hw params failed, err = %d\n", ret);
return ret;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
slave_config.dst_addr_width = (dma_data->width == 16) ?
DMA_SLAVE_BUSWIDTH_2_BYTES :
DMA_SLAVE_BUSWIDTH_4_BYTES;
slave_config.dst_addr = dma_data->addr;
/* MC burst should be multiple of this for proper
* stopping of GPCDMA during CYCLIC transfer.
* Currently, GPCDMA configures the MC burst
* to 2 words unless MMIO supports 64 so we just match it.
*/
slave_config.dst_maxburst = 2;
} else {
slave_config.src_addr_width = (dma_data->width == 16) ?
DMA_SLAVE_BUSWIDTH_2_BYTES :
DMA_SLAVE_BUSWIDTH_4_BYTES;
slave_config.src_addr = dma_data->addr;
slave_config.src_maxburst = 2;
}
#if defined(NV_DMA_SLAVE_CONFIG_STRUCT_HAS_SLAVE_ID) /* Linux v5.17 */
//TODO: Read from DT later on
slave_config.slave_id = dma_data->req_sel;
#endif
ret = dmaengine_slave_config(chan, &slave_config);
if (ret < 0) {
pr_alert("dma slave config failed, err = %d\n", ret);
return ret;
}
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
return 0;
}
static int i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
return 0;
}
static int safety_i2s_probe(struct device *dev, unsigned int id)
{
i2s_parse_dt(dev, id);
setup_plls(dev);
i2s_reset_init_and_deassert(dev, id);
i2s_clock_init(dev, id);
i2s_setup(id);
return 0;
}
static int safety_i2s_open(struct snd_pcm_substream *substream)
{
struct i2s_dev *i2s = snd_pcm_substream_chip(substream);
struct dma_data *dma_data =
(substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
&i2s->capture_data : &i2s->playback_data;
struct snd_pcm_runtime *runtime = substream->runtime;
struct dma_chan *chan;
int ret;
/* overwrite pcm->private_data and
* maintain only substram specific data
*/
substream->private_data = dma_data;
runtime->hw.info = t234_pcm_hardware.info;
runtime->hw.rates = t234_pcm_hardware.rates;
runtime->hw.rate_min = t234_pcm_hardware.rate_min;
runtime->hw.rate_max = t234_pcm_hardware.rate_max;
runtime->hw.formats = t234_pcm_hardware.formats;
runtime->hw.period_bytes_min = t234_pcm_hardware.period_bytes_min;
runtime->hw.period_bytes_max = t234_pcm_hardware.period_bytes_max;
runtime->hw.periods_min = t234_pcm_hardware.periods_min;
runtime->hw.periods_max = t234_pcm_hardware.periods_max;
runtime->hw.channels_min = t234_pcm_hardware.channels_min;
runtime->hw.channels_max = t234_pcm_hardware.channels_max;
runtime->hw.buffer_bytes_max = t234_pcm_hardware.buffer_bytes_max;
runtime->hw.fifo_size = t234_pcm_hardware.fifo_size;
//TODO: Support buffer size update from device tree
ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 0x8);
if (ret) {
pr_alert("Failed to set constraint %d\n", ret);
return ret;
}
chan = dma_request_slave_channel(substream->pcm->card->dev,
dma_data->dma_chan_name);
if (!chan) {
pr_alert("failed to allocate dma channel\n");
return -ENODEV;
}
ret = snd_dmaengine_pcm_open(substream, chan);
if (ret) {
pr_alert("failed to open dmaengine\n");
return ret;
}
return 0;
}
static int safety_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
int ret;
/* configure hardware according to the following params
* channels, bits per sample, samples per second, period size
* and num periods
*/
ret = gpcdma_hw_params(substream, params);
if (!ret)
ret = i2s_hw_params(substream, params);
return ret;
}
static int safety_i2s_prepare(struct snd_pcm_substream *substream)
{
return 0;
}
static int safety_i2s_close(struct snd_pcm_substream *substream)
{
struct dma_data *data = substream->private_data;
if (data->triggered) {
safety_i2s_trigger(substream, SNDRV_PCM_TRIGGER_STOP);
data->triggered = 0;
}
snd_dmaengine_pcm_close_release_chan(substream);
return 0;
}
static int safety_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
{
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_dmaengine_pcm_trigger(substream, cmd);
safety_i2s_start(substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
safety_i2s_stop(substream);
snd_dmaengine_pcm_trigger(substream, cmd);
break;
default:
return -EINVAL;
}
return 0;
}
static struct snd_pcm_ops playback_ops = {
.open = safety_i2s_open,
.close = safety_i2s_close,
.hw_params = safety_i2s_hw_params,
.prepare = safety_i2s_prepare,
.pointer = snd_dmaengine_pcm_pointer,
.trigger = safety_i2s_trigger
};
static const struct of_device_id match_table[] = {
{ .compatible = "nvidia,tegra234-safety-audio" },
{}
};
static unsigned int parse_enabled_i2s_mask(struct device *dev)
{
unsigned int num_enabled = 0;
int i;
i = of_property_read_variable_u32_array(dev->of_node,
"enabled-i2s-mask", enabled_i2s_mask,
NUM_SAFETY_I2S_INST, 0);
WARN_ON(i != NUM_SAFETY_I2S_INST);
for (i = 0; i < NUM_SAFETY_I2S_INST; i++)
num_enabled += !!(enabled_i2s_mask[i]);
return num_enabled;
}
static int t234_safety_audio_probe(struct platform_device *pdev)
{
struct snd_card *card;
int ret, pcm_instance = 0;
unsigned int i = 0;
struct snd_pcm *pcm;
char name[5] = {0};
if (parse_enabled_i2s_mask(&pdev->dev) == 0) {
pr_err("No safety-i2s interfaces are available on this board\n");
return -ENODEV;
}
ret = snd_card_new(&pdev->dev, -1, "Safety I2S sound card",
THIS_MODULE, sizeof(*priv), &card);
if (ret < 0)
return ret;
priv = card->private_data;
priv->card = card;
for (i = 0; i < NUM_SAFETY_I2S_INST; i++) {
struct resource *r;
size_t buffer_size = t234_pcm_hardware.buffer_bytes_max;
void __iomem *base;
if (!enabled_i2s_mask[i])
continue;
if ((sprintf(name, I2S_DT_NODE, I2S_NODE_START_INDEX + i)) < 0)
return -EINVAL;
ret = snd_pcm_new(card, name, pcm_instance++, 1, 1, &pcm);
if (ret < 0) {
pr_alert("Could not register i2s pcm, ret: %d\n", ret);
return ret;
}
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &playback_ops);
base = devm_platform_get_and_ioremap_resource(pdev, i, &r);
if (IS_ERR(base)) {
pr_alert("could not remap base\n");
return -EINVAL;
}
i2s[i].base = base;
safety_i2s_probe(&pdev->dev, i);
i2s[i].capture_data.addr = r->start + 0x20;
//TODO: Read from DT later on
i2s[i].capture_data.size = buffer_size;
i2s[i].capture_data.width = i2s[i].config.bit_size;
i2s[i].capture_data.req_sel = i + 1;
i2s[i].capture_data.dma_chan_name =
(i ? "i2s8-rx" : "i2s7-rx");
i2s[i].playback_data.addr = r->start + 0xa0;
//TODO: Read from DT later on
i2s[i].playback_data.size = buffer_size;
i2s[i].playback_data.width = i2s[i].config.bit_size;
i2s[i].playback_data.req_sel = i + 1;
i2s[i].playback_data.dma_chan_name =
(i ? "i2s8-tx" : "i2s7-tx");
pcm->private_data = &i2s[i];
//TODO: check the return value
prealloc_dma_buff(pcm, SNDRV_PCM_STREAM_PLAYBACK, buffer_size);
prealloc_dma_buff(pcm, SNDRV_PCM_STREAM_CAPTURE, buffer_size);
safety_i2s_add_kcontrols(card, i);
}
ret = snd_card_register(card);
if (ret < 0)
pr_alert("Error registering I2S card, ret = %d\n", ret);
else
pr_alert("Sound card registered successfully\n");
return ret;
}
static int t234_safety_audio_remove(struct platform_device *pdev)
{
int i;
for (i = 0; i < NUM_SAFETY_I2S_INST; i++) {
if (!enabled_i2s_mask[i])
continue;
i2s_disable(i);
}
snd_card_free(priv->card);
return 0;
}
static struct platform_driver t234_safety_audio_driver = {
.probe = t234_safety_audio_probe,
.remove = t234_safety_audio_remove,
.driver = {
.name = "tegra234-safety-audio",
.owner = THIS_MODULE,
.of_match_table = match_table,
},
};
MODULE_DEVICE_TABLE(of, match_table);
MODULE_LICENSE("GPL");
module_platform_driver(t234_safety_audio_driver);