Hi Miquel On 30. 08. 19 23:06, Miquel Raynal wrote: > This IP is very simple so this driver manage both the DAI and the PCM > streams, hence the presence of both components in this driver. > > There are plenty available interruptions when capturing or playing > back audio that can be triggered but the only one that fits the ALSA > sound system is the XFER_DONE which is used to bound sound > periods. Other interrupts are masked. Please note that capture and > playback are not possible at the same time though. > > Capture seems to work (at least it creates a file with something > inside) but I have no capture mechanism on the board to actually test > that it works correctly. > > Signed-off-by: Miquel Raynal <miquel.raynal@xxxxxxxxxxx> > --- > > Hello, > > This is my first contribution in the sound subsystem, I hope I've > understood the core but I might be entirely wrong as well, so please > do not hesitate to be critical on my choices. > > Thanks, > Miquèl > > sound/soc/xilinx/Kconfig | 7 + > sound/soc/xilinx/Makefile | 2 + > sound/soc/xilinx/xlnx-logicpd-i2s.c | 468 ++++++++++++++++++++++++++++ What IP is this? https://www.xilinx.com/products/intellectual-property/audio-i2s.html https://github.com/Xilinx/linux-xlnx/blob/master/sound/soc/xilinx/xlnx_i2s.c Anyway I am adding Praveen and Maruthi to take a look. Thanks, Michal > 3 files changed, 477 insertions(+) > create mode 100644 sound/soc/xilinx/xlnx-logicpd-i2s.c > > diff --git a/sound/soc/xilinx/Kconfig b/sound/soc/xilinx/Kconfig > index 47f606b924e4..b62cae6750b9 100644 > --- a/sound/soc/xilinx/Kconfig > +++ b/sound/soc/xilinx/Kconfig > @@ -7,6 +7,13 @@ config SND_SOC_XILINX_I2S > PCM data. In receiver mode, IP receives PCM audio and > encapsulates PCM in AES format and sends AES data. > > +config SND_SOC_XILINX_LOGICPD_I2S > + tristate "Audio support for the Xilinx logicPD I2S" > + help > + Select this option to enable Xilinx logicPD I2S slave > + transceiver. This enables I2S playback and capture using > + Xilinx/logicPD IP. > + > config SND_SOC_XILINX_AUDIO_FORMATTER > tristate "Audio support for the the Xilinx audio formatter" > help > diff --git a/sound/soc/xilinx/Makefile b/sound/soc/xilinx/Makefile > index d79fd38b094b..d127c30f8fe2 100644 > --- a/sound/soc/xilinx/Makefile > +++ b/sound/soc/xilinx/Makefile > @@ -1,5 +1,7 @@ > snd-soc-xlnx-i2s-objs := xlnx_i2s.o > obj-$(CONFIG_SND_SOC_XILINX_I2S) += snd-soc-xlnx-i2s.o > +snd-soc-xlnx-logicpd-i2s-objs := xlnx-logicpd-i2s.o > +obj-$(CONFIG_SND_SOC_XILINX_LOGICPD_I2S) += snd-soc-xlnx-logicpd-i2s.o > snd-soc-xlnx-formatter-pcm-objs := xlnx_formatter_pcm.o > obj-$(CONFIG_SND_SOC_XILINX_AUDIO_FORMATTER) += snd-soc-xlnx-formatter-pcm.o > snd-soc-xlnx-spdif-objs := xlnx_spdif.o > diff --git a/sound/soc/xilinx/xlnx-logicpd-i2s.c b/sound/soc/xilinx/xlnx-logicpd-i2s.c > new file mode 100644 > index 000000000000..325a5bb6978a > --- /dev/null > +++ b/sound/soc/xilinx/xlnx-logicpd-i2s.c > @@ -0,0 +1,468 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Xilinx logicPD logiI2S - I2S slave transceiver v2 support > + * > + * Copyright (C) 2019 Bootlin > + * > + * Author: Miquel Raynal <miquel.raynal@xxxxxxxxxxx> > + */ > + > +#include <linux/dma-mapping.h> > +#include <linux/io.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/of_platform.h> > +#include <linux/platform_device.h> > +#include <sound/dmaengine_pcm.h> > +#include <sound/pcm_params.h> > +#include <sound/soc.h> > + > +#define DRV_NAME "xlnx_logicpd_i2s" > + > +#define IP_VERSION 0x0 > +#define PATCH_LEVEL(reg) (((reg) & GENMASK(4, 0)) + 'a') > +#define MINOR_REV(reg) (((reg) & GENMASK(10, 5)) >> 5) > +#define MAJOR_REV(reg) (((reg) & GENMASK(16, 11)) >> 11) > +#define LICENSE_TYPE(reg) (((reg) & GENMASK(18, 17)) >> 17) > +#define CONTROL_REG(s) ((s) == SNDRV_PCM_STREAM_PLAYBACK ? 0x4 : 0x24) > +#define ENGINE_EN BIT(0) > +#define XFER_DONE BIT(1) > +#define BUFF_BASE_ADDR_REG(s) ((s) == SNDRV_PCM_STREAM_PLAYBACK ? 0x8 : 0x28) > +#define BUFF_LEN_REG(s) ((s) == SNDRV_PCM_STREAM_PLAYBACK ? 0xC : 0x2C) > +#define FIFO_STAT_REG(s) ((s) == SNDRV_PCM_STREAM_PLAYBACK ? 0x10 : 0x30) > +#define INTR_MASK_REG(s) ((s) == SNDRV_PCM_STREAM_PLAYBACK ? 0x14 : 0x34) > +#define XFER_DONE_INTR BIT(31) > +#define INTR_STAT_REG(s) ((s) == SNDRV_PCM_STREAM_PLAYBACK ? 0x18 : 0x38) > +#define FIFO_COUNT(reg) ((reg) >> 20) > + > +#define BYTES_TO_WORDS(n) ((n) / 4) > + > +/* Arbitrarily chosen period size */ > +#define PCM_PERIOD_WORDS SZ_8K > +#define PCM_PERIOD_BYTES (PCM_PERIOD_WORDS * 4) > +/* This is the actual maximum size that can actually be moved in one chunk */ > +#define PCM_BUF_WORDS (SZ_64K - 1) > +#define PCM_BUF_BYTES (PCM_BUF_WORDS * 4) > + > +struct xlnx_logicpd_i2s; > + > +/** > + * struct xlnx_logicpd_stream - Internal stream representation > + * > + * @i2s: Chip data > + * @substream: Core substream structure > + * @period_idx: Index of the period within the circular buffer > + */ > +struct xlnx_logicpd_stream { > + struct xlnx_logicpd_i2s *i2s; > + struct snd_pcm_substream *substream; > + unsigned int period_idx; > +}; > + > +/** > + * struct xlnx_logicpd_i2s - Chip structure > + * > + * @base: Registers base address > + * @streams: Playback and capture streams in an array > + */ > +struct xlnx_logicpd_i2s { > + void __iomem *base; > + struct xlnx_logicpd_stream streams[2]; > +}; > + > +static struct xlnx_logicpd_i2s *substream_to_cpu_dai_chip(struct snd_pcm_substream *substream) > +{ > + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); > + > + return snd_soc_dai_get_drvdata(rtd->cpu_dai); > +} > + > +/* PCM methods */ > + > +static const struct snd_pcm_hardware xlnx_logicpd_pcm_hardware = { > + .info = SNDRV_PCM_INFO_MMAP | > + SNDRV_PCM_INFO_MMAP_VALID | > + SNDRV_PCM_INFO_INTERLEAVED | > + SNDRV_PCM_INFO_HALF_DUPLEX, > + .formats = SNDRV_PCM_FMTBIT_S16_LE, > + .rates = SNDRV_PCM_RATE_8000_192000, > + .rate_min = 8000, > + .rate_max = 192000, > + .channels_min = 2, > + .channels_max = 2, > + .period_bytes_min = 0, > + .period_bytes_max = PCM_PERIOD_BYTES, > + .periods_min = 0, > + .periods_max = -1, > + .buffer_bytes_max = PCM_BUF_BYTES, > +}; > + > +static int xlnx_logicpd_pcm_open(struct snd_pcm_substream *substream) > +{ > + struct xlnx_logicpd_i2s *i2s = substream_to_cpu_dai_chip(substream); > + unsigned int dir = substream->stream; > + > + snd_soc_set_runtime_hwparams(substream, &xlnx_logicpd_pcm_hardware); > + > + i2s->streams[dir].substream = substream; > + > + return 0; > +} > + > +static int xlnx_logicpd_pcm_close(struct snd_pcm_substream *substream) > +{ > + struct xlnx_logicpd_i2s *i2s = substream_to_cpu_dai_chip(substream); > + unsigned int dir = substream->stream; > + > + i2s->streams[dir].substream = NULL; > + > + return 0; > +} > + > +static int xlnx_logicpd_pcm_hw_params(struct snd_pcm_substream *substream, > + struct snd_pcm_hw_params *params) > +{ > + struct snd_pcm_runtime *runtime = substream->runtime; > + > + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); > + runtime->dma_bytes = params_buffer_bytes(params); > + > + return 0; > +} > + > +static snd_pcm_uframes_t xlnx_logicpd_pcm_pointer(struct snd_pcm_substream *substream) > +{ > + struct xlnx_logicpd_i2s *i2s = substream_to_cpu_dai_chip(substream); > + struct snd_pcm_runtime *runtime = substream->runtime; > + unsigned int period_sz = snd_pcm_lib_period_bytes(substream); > + unsigned int dir = substream->stream; > + > + return bytes_to_frames(runtime, > + i2s->streams[dir].period_idx * period_sz); > +} > + > +static int xlnx_logicpd_pcm_mmap(struct snd_pcm_substream *substream, > + struct vm_area_struct *vma) > +{ > + return remap_pfn_range(vma, vma->vm_start, > + substream->dma_buffer.addr >> PAGE_SHIFT, > + vma->vm_end - vma->vm_start, vma->vm_page_prot); > +} > + > +static const struct snd_pcm_ops xlnx_logicpd_pcm_ops = { > + .open = xlnx_logicpd_pcm_open, > + .close = xlnx_logicpd_pcm_close, > + .ioctl = snd_pcm_lib_ioctl, > + .hw_params = xlnx_logicpd_pcm_hw_params, > + .pointer = xlnx_logicpd_pcm_pointer, > + .mmap = xlnx_logicpd_pcm_mmap, > +}; > + > +static int xlnx_logicpd_pcm_new(struct snd_soc_pcm_runtime *rtd) > +{ > + struct snd_pcm *pcm = rtd->pcm; > + struct snd_pcm_substream *substream; > + struct snd_dma_buffer *buf; > + int dir; > + > + for (dir = SNDRV_PCM_STREAM_PLAYBACK; > + dir <= SNDRV_PCM_STREAM_CAPTURE; dir++) { > + substream = pcm->streams[dir].substream; > + if (!substream) > + continue; > + > + buf = &substream->dma_buffer; > + buf->area = dma_alloc_coherent(pcm->card->dev, PCM_BUF_BYTES, > + &buf->addr, GFP_KERNEL); > + buf->bytes = PCM_BUF_BYTES; > + if (!buf->area) > + return -ENOMEM; > + } > + > + return 0; > +} > + > +static void xlnx_logicpd_pcm_free(struct snd_pcm *pcm) > +{ > + struct snd_pcm_substream *substream; > + struct snd_dma_buffer *buf; > + int dir; > + > + for (dir = SNDRV_PCM_STREAM_PLAYBACK; > + dir <= SNDRV_PCM_STREAM_CAPTURE; dir++) { > + substream = pcm->streams[dir].substream; > + if (!substream) > + continue; > + > + buf = &substream->dma_buffer; > + if (!buf->area) > + continue; > + > + dma_free_coherent(pcm->card->dev, buf->bytes, > + buf->area, buf->addr); > + buf->area = NULL; > + } > +} > + > +static const struct snd_soc_component_driver xlnx_logicpd_pcm_component = { > + .name = "xlnx-logicp-pcm", > + .ops = &xlnx_logicpd_pcm_ops, > + .pcm_new = xlnx_logicpd_pcm_new, > + .pcm_free = xlnx_logicpd_pcm_free, > +}; > + > +/* DAI methods */ > + > +static void xlnx_logicpd_dai_int_en(struct xlnx_logicpd_i2s *i2s, int dir) > +{ > + u32 reg; > + > + reg = readl_relaxed(i2s->base + INTR_MASK_REG(dir)); > + reg &= ~XFER_DONE_INTR; > + writel(reg, i2s->base + INTR_MASK_REG(dir)); > +} > + > +static void xlnx_logicpd_dai_int_dis(struct xlnx_logicpd_i2s *i2s, int dir) > +{ > + u32 reg; > + > + reg = readl_relaxed(i2s->base + INTR_MASK_REG(dir)); > + reg |= XFER_DONE_INTR; > + writel_relaxed(reg, i2s->base + INTR_MASK_REG(dir)); > +} > + > +static irqreturn_t xlnx_logicpd_dai_isr(int irq, void *dev_id) > +{ > + struct xlnx_logicpd_stream *stream = dev_id; > + struct xlnx_logicpd_i2s *i2s = stream->i2s; > + struct snd_pcm_substream *substream = stream->substream; > + unsigned int period_sz = snd_pcm_lib_period_bytes(substream); > + unsigned int buf_sz = snd_pcm_lib_buffer_bytes(substream); > + dma_addr_t buf_addr = substream->dma_buffer.addr; > + unsigned int dir = substream->stream; > + u32 reg; > + > + /* Reading INTR_STAT deasserts the host interrupt */ > + reg = readl_relaxed(i2s->base + INTR_STAT_REG(dir)); > + > + /* > + * When the XFER_DONE interrupt is triggered, it means the period has > + * been entirely shifted into the FIFO. At this point, we can move the > + * buffer pointer to the next period and ask to transfer another chunk > + * of data. Whenever the FIFO will be at its "almost full" state (4096 > + * words minus the threshold of 100 words) the internal DMA engine will > + * automatically restart shifting data to the FIFO until its full state. > + * Hence, the host has up to 3996 words (in our case, 3996 frames) to > + * serve the interrupt before an underrun that would happen, at eg. > + * 44100Hz, after 90ms. > + */ > + if (reg & XFER_DONE_INTR) { > + unsigned int offset_in_buf = ++stream->period_idx * period_sz; > + > + if (offset_in_buf >= buf_sz) { > + stream->period_idx = 0; > + offset_in_buf = stream->period_idx * period_sz; > + } > + > + /* Move on to the next period in the overall buffer */ > + writel_relaxed(buf_addr + offset_in_buf, > + i2s->base + BUFF_BASE_ADDR_REG(dir)); > + /* The last period might be smaller, update length if needed */ > + period_sz = min(period_sz, buf_sz - offset_in_buf); > + writel_relaxed(BYTES_TO_WORDS(period_sz), > + i2s->base + BUFF_LEN_REG(dir)); > + > + /* Inform the PCM middle-layer */ > + snd_pcm_period_elapsed(substream); > + } > + > + return IRQ_HANDLED; > +} > + > +static int xlnx_logicpd_dai_trigger(struct snd_pcm_substream *substream, > + int cmd, struct snd_soc_dai *dai) > +{ > + struct xlnx_logicpd_i2s *i2s = snd_soc_dai_get_drvdata(dai); > + unsigned int period_sz = snd_pcm_lib_period_bytes(substream); > + dma_addr_t buf_addr = substream->dma_buffer.addr; > + unsigned int dir = substream->stream; > + > + switch (cmd) { > + case SNDRV_PCM_TRIGGER_START: > + i2s->streams[dir].period_idx = 0; > + /* Disable the other engine if enabled */ > + if (readl(i2s->base + CONTROL_REG(!dir)) & ENGINE_EN) > + writel(0, i2s->base + CONTROL_REG(!dir)); > + /* Enable the desired engine */ > + writel_relaxed(ENGINE_EN, i2s->base + CONTROL_REG(dir)); > + /* Set the buffer start address */ > + writel_relaxed(buf_addr, i2s->base + BUFF_BASE_ADDR_REG(dir)); > + /* Enable the XFER_DONE IRQ, signaling the end of the period */ > + xlnx_logicpd_dai_int_en(i2s, dir); > + /* Actually start the internal DMA engine */ > + writel(BYTES_TO_WORDS(period_sz), > + i2s->base + BUFF_LEN_REG(dir)); > + break; > + case SNDRV_PCM_TRIGGER_STOP: > + /* Disable the interrupts */ > + xlnx_logicpd_dai_int_dis(i2s, dir); > + /* Ensure the host IRQ is deasserted */ > + readl_relaxed(i2s->base + INTR_STAT_REG(dir)); > + break; > + case SNDRV_PCM_TRIGGER_RESUME: > + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: > + case SNDRV_PCM_TRIGGER_SUSPEND: > + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: > + default: > + return -EINVAL; > + } > + > + return 0; > +} > + > +static const struct snd_soc_dai_ops xlnx_logicpd_dai_ops = { > + .trigger = xlnx_logicpd_dai_trigger, > +}; > + > +static int xlnx_logicpd_dai_probe(struct snd_soc_dai *dai) > +{ > + struct xlnx_logicpd_i2s *i2s = snd_soc_dai_get_drvdata(dai); > + unsigned int dir; > + > + for (dir = SNDRV_PCM_STREAM_PLAYBACK; > + dir <= SNDRV_PCM_STREAM_CAPTURE; dir++) { > + i2s->streams[dir].i2s = i2s; > + > + /* Reset the transmitter/receiver engine */ > + writel_relaxed(0, i2s->base + CONTROL_REG(dir)); > + /* Mask all interrupts */ > + writel_relaxed(GENMASK(31, 0), i2s->base + INTR_MASK_REG(dir)); > + } > + > + return 0; > +} > + > +struct snd_soc_dai_driver xlnx_logicpd_dai = { > + .name = "xylinx-logicpd-dai", > + .probe = xlnx_logicpd_dai_probe, > + .capture = { > + .stream_name = "Capture", > + .channels_min = 2, > + .channels_max = 2, > + .rates = SNDRV_PCM_RATE_8000_192000, > + .rate_min = 8000, > + .rate_max = 192000, > + .formats = SNDRV_PCM_FMTBIT_S16_LE, > + }, > + .playback = { > + .stream_name = "Playback", > + .channels_min = 2, > + .channels_max = 2, > + .rates = SNDRV_PCM_RATE_8000_192000, > + .rate_min = 8000, > + .rate_max = 192000, > + .formats = SNDRV_PCM_FMTBIT_S16_LE, > + }, > + .symmetric_rates = 1, > + .ops = &xlnx_logicpd_dai_ops, > +}; > + > +static const struct snd_soc_component_driver xlnx_logicpd_i2s_component = { > + .name = DRV_NAME, > + .ops = &xlnx_logicpd_pcm_ops, > +}; > + > +static const struct of_device_id xlnx_logicpd_i2s_of_match[] = { > + { > + .compatible = "xlnx,logicpd-i2s-dai", > + }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, xlnx_logicpd_i2s_of_match); > + > +static int xlnx_logicpd_i2s_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct xlnx_logicpd_i2s *i2s; > + struct xlnx_logicpd_stream *stream; > + int tx_irq, rx_irq, ret; > + u32 reg; > + > + i2s = devm_kzalloc(dev, sizeof(*i2s), GFP_KERNEL); > + if (!i2s) > + return -ENOMEM; > + > + dev_set_drvdata(dev, i2s); > + > + i2s->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(i2s->base)) > + return PTR_ERR(i2s->base); > + > + stream = &i2s->streams[SNDRV_PCM_STREAM_PLAYBACK]; > + tx_irq = platform_get_irq_byname(pdev, "tx"); > + if (tx_irq > 0) { > + ret = devm_request_irq(dev, tx_irq, xlnx_logicpd_dai_isr, > + 0, "logicpd-i2s-tx", stream); > + if (ret) > + return ret; > + } else { > + dev_err(dev, "TX IRQ not available (%d), disabling playback\n", > + tx_irq); > + tx_irq = 0; > + } > + > + stream = &i2s->streams[SNDRV_PCM_STREAM_CAPTURE]; > + rx_irq = platform_get_irq_byname(pdev, "rx"); > + if (rx_irq > 0) { > + ret = devm_request_irq(dev, rx_irq, xlnx_logicpd_dai_isr, > + 0, "logicpd-i2s-rx", stream); > + if (ret) > + return ret; > + } else { > + dev_err(dev, "RX IRQ not available (%d), disabling capture\n", > + rx_irq); > + rx_irq = 0; > + } > + > + if (!tx_irq && !rx_irq) > + return -EINVAL; > + > + ret = devm_snd_soc_register_component(dev, &xlnx_logicpd_pcm_component, > + NULL, 0); > + if (ret) { > + dev_err(dev, "cannot register PCM component (%d)\n", ret); > + return ret; > + } > + > + ret = devm_snd_soc_register_component(dev, &xlnx_logicpd_i2s_component, > + &xlnx_logicpd_dai, 1); > + if (ret) { > + dev_err(dev, "cannot register I2S component (%d)\n", ret); > + return ret; > + } > + > + reg = readl_relaxed(i2s->base + IP_VERSION); > + dev_info(dev, "%s DAI version %u.%u.%c (license: %s) registered\n", > + xlnx_logicpd_dai.name, > + (unsigned int)MAJOR_REV(reg), > + (unsigned int)MINOR_REV(reg), > + (char)PATCH_LEVEL(reg), > + LICENSE_TYPE(reg) == 0 ? "source" : > + (LICENSE_TYPE(reg) == 1 ? "eval" : "release")); > + > + return ret; > +} > + > +static struct platform_driver xlnx_logicpd_i2s_driver = { > + .driver = { > + .name = DRV_NAME, > + .of_match_table = xlnx_logicpd_i2s_of_match, > + }, > + .probe = xlnx_logicpd_i2s_probe, > +}; > + > +module_platform_driver(xlnx_logicpd_i2s_driver); > + > +MODULE_AUTHOR("Miquel Raynal <miquel.raynal@xxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Xilinx logicPD I2S module"); > +MODULE_LICENSE("GPL v2"); >