// 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 #include #include #include #include #include #include #include #include #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);