diff options
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/codecs/tlv320aic31xx.c | 2 | ||||
-rw-r--r-- | sound/soc/codecs/wm_adsp.c | 6 | ||||
-rw-r--r-- | sound/soc/soc-topology.c | 29 | ||||
-rw-r--r-- | sound/soc/stm/Kconfig | 29 | ||||
-rw-r--r-- | sound/soc/stm/Makefile | 12 | ||||
-rw-r--r-- | sound/soc/stm/stm32_i2s.c | 946 | ||||
-rw-r--r-- | sound/soc/stm/stm32_sai.c | 15 | ||||
-rw-r--r-- | sound/soc/stm/stm32_sai.h | 73 | ||||
-rw-r--r-- | sound/soc/stm/stm32_sai_sub.c | 143 | ||||
-rw-r--r-- | sound/soc/stm/stm32_spdifrx.c | 998 | ||||
-rw-r--r-- | sound/soc/sunxi/sun4i-codec.c | 63 | ||||
-rw-r--r-- | sound/soc/sunxi/sun8i-codec-analog.c | 145 |
12 files changed, 2396 insertions, 65 deletions
diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index f8a90ba8cd71..d7d03c92cb8a 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -1210,7 +1210,7 @@ static const struct snd_soc_dai_ops aic31xx_dai_ops = { static struct snd_soc_dai_driver dac31xx_dai_driver[] = { { - .name = "tlv32dac31xx-hifi", + .name = "tlv320dac31xx-hifi", .playback = { .stream_name = "Playback", .channels_min = 2, diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 20695b691aff..65c059b5ffd7 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -482,8 +482,6 @@ struct wm_coeff_ctl_ops { struct snd_ctl_elem_value *ucontrol); int (*xput)(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); - int (*xinfo)(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo); }; struct wm_coeff_ctl { @@ -1890,7 +1888,7 @@ static void *wm_adsp_read_algs(struct wm_adsp *dsp, size_t n_algs, } if (be32_to_cpu(val) != 0xbedead) - adsp_warn(dsp, "Algorithm list end %x 0x%x != 0xbeadead\n", + adsp_warn(dsp, "Algorithm list end %x 0x%x != 0xbedead\n", pos + len, be32_to_cpu(val)); alg = kzalloc(len * 2, GFP_KERNEL | GFP_DMA); @@ -2654,7 +2652,7 @@ int wm_adsp2_preloader_put(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; char preload[32]; - snprintf(preload, ARRAY_SIZE(preload), "DSP%d Preload", mc->shift); + snprintf(preload, ARRAY_SIZE(preload), "DSP%u Preload", mc->shift); dsp->preloaded = ucontrol->value.integer.value[0]; diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index dd3a391476ae..dd471d2c0266 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -352,6 +352,17 @@ static int soc_tplg_widget_load(struct soc_tplg *tplg, return 0; } +/* optionally pass new dynamic widget to component driver. This is mainly for + * external widgets where we can assign private data/ops */ +static int soc_tplg_widget_ready(struct soc_tplg *tplg, + struct snd_soc_dapm_widget *w, struct snd_soc_tplg_dapm_widget *tplg_w) +{ + if (tplg->comp && tplg->ops && tplg->ops->widget_ready) + return tplg->ops->widget_ready(tplg->comp, w, tplg_w); + + return 0; +} + /* pass DAI configurations to component driver for extra initialization */ static int soc_tplg_dai_load(struct soc_tplg *tplg, struct snd_soc_dai_driver *dai_drv) @@ -1160,7 +1171,8 @@ static int soc_tplg_dapm_graph_elems_load(struct soc_tplg *tplg, return -EINVAL; } - dev_dbg(tplg->dev, "ASoC: adding %d DAPM routes\n", count); + dev_dbg(tplg->dev, "ASoC: adding %d DAPM routes for index %d\n", count, + hdr->index); for (i = 0; i < count; i++) { elem = (struct snd_soc_tplg_dapm_graph_elem *)tplg->pos; @@ -1473,6 +1485,7 @@ static int soc_tplg_dapm_widget_create(struct soc_tplg *tplg, if (template.id < 0) return template.id; + /* strings are allocated here, but used and freed by the widget */ template.name = kstrdup(w->name, GFP_KERNEL); if (!template.name) return -ENOMEM; @@ -1585,11 +1598,17 @@ widget: widget->dobj.widget.kcontrol_type = kcontrol_type; widget->dobj.ops = tplg->ops; widget->dobj.index = tplg->index; - kfree(template.sname); - kfree(template.name); list_add(&widget->dobj.list, &tplg->comp->dobj_list); + + ret = soc_tplg_widget_ready(tplg, widget, w); + if (ret < 0) + goto ready_err; + return 0; +ready_err: + snd_soc_tplg_widget_remove(widget); + snd_soc_dapm_free_widget(widget); hdr_err: kfree(template.sname); err: @@ -1636,7 +1655,7 @@ static int soc_tplg_dapm_complete(struct soc_tplg *tplg) */ if (!card || !card->instantiated) { dev_warn(tplg->dev, "ASoC: Parent card not yet available," - "Do not add new widgets now\n"); + " widget card binding deferred\n"); return 0; } @@ -2371,7 +2390,7 @@ static int soc_tplg_load_header(struct soc_tplg *tplg, /* check for matching ID */ if (hdr->index != tplg->req_index && - hdr->index != SND_SOC_TPLG_INDEX_ALL) + tplg->req_index != SND_SOC_TPLG_INDEX_ALL) return 0; tplg->index = hdr->index; diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig index 972970f0890a..3398e6c57f37 100644 --- a/sound/soc/stm/Kconfig +++ b/sound/soc/stm/Kconfig @@ -1,8 +1,31 @@ -menuconfig SND_SOC_STM32 - tristate "STMicroelectronics STM32 SOC audio support" +menu "STMicroelectronics STM32 SOC audio support" + +config SND_SOC_STM32_SAI + tristate "STM32 SAI interface (Serial Audio Interface) support" depends on ARCH_STM32 || COMPILE_TEST depends on SND_SOC select SND_SOC_GENERIC_DMAENGINE_PCM select REGMAP_MMIO help - Say Y if you want to enable ASoC-support for STM32 + Say Y if you want to enable SAI for STM32 + +config SND_SOC_STM32_I2S + tristate "STM32 I2S interface (SPI/I2S block) support" + depends on ARCH_STM32 || COMPILE_TEST + depends on SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y if you want to enable I2S for STM32 + +config SND_SOC_STM32_SPDIFRX + tristate "STM32 S/PDIF receiver (SPDIFRX) support" + depends on ARCH_STM32 || COMPILE_TEST + depends on SND_SOC + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + select SND_SOC_SPDIF + help + Say Y if you want to enable S/PDIF capture for STM32 + +endmenu diff --git a/sound/soc/stm/Makefile b/sound/soc/stm/Makefile index e466a4759698..4ed22e648a9a 100644 --- a/sound/soc/stm/Makefile +++ b/sound/soc/stm/Makefile @@ -1,6 +1,14 @@ # SAI snd-soc-stm32-sai-sub-objs := stm32_sai_sub.o -obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai-sub.o +obj-$(CONFIG_SND_SOC_STM32_SAI) += snd-soc-stm32-sai-sub.o snd-soc-stm32-sai-objs := stm32_sai.o -obj-$(CONFIG_SND_SOC_STM32) += snd-soc-stm32-sai.o +obj-$(CONFIG_SND_SOC_STM32_SAI) += snd-soc-stm32-sai.o + +# I2S +snd-soc-stm32-i2s-objs := stm32_i2s.o +obj-$(CONFIG_SND_SOC_STM32_I2S) += snd-soc-stm32-i2s.o + +# SPDIFRX +snd-soc-stm32-spdifrx-objs := stm32_spdifrx.o +obj-$(CONFIG_SND_SOC_STM32_SPDIFRX) += snd-soc-stm32-spdifrx.o diff --git a/sound/soc/stm/stm32_i2s.c b/sound/soc/stm/stm32_i2s.c new file mode 100644 index 000000000000..8052629a89df --- /dev/null +++ b/sound/soc/stm/stm32_i2s.c @@ -0,0 +1,946 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (I2S) driver. + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#define STM32_I2S_CR1_REG 0x0 +#define STM32_I2S_CFG1_REG 0x08 +#define STM32_I2S_CFG2_REG 0x0C +#define STM32_I2S_IER_REG 0x10 +#define STM32_I2S_SR_REG 0x14 +#define STM32_I2S_IFCR_REG 0x18 +#define STM32_I2S_TXDR_REG 0X20 +#define STM32_I2S_RXDR_REG 0x30 +#define STM32_I2S_CGFR_REG 0X50 + +/* Bit definition for SPI2S_CR1 register */ +#define I2S_CR1_SPE BIT(0) +#define I2S_CR1_CSTART BIT(9) +#define I2S_CR1_CSUSP BIT(10) +#define I2S_CR1_HDDIR BIT(11) +#define I2S_CR1_SSI BIT(12) +#define I2S_CR1_CRC33_17 BIT(13) +#define I2S_CR1_RCRCI BIT(14) +#define I2S_CR1_TCRCI BIT(15) + +/* Bit definition for SPI_CFG2 register */ +#define I2S_CFG2_IOSWP_SHIFT 15 +#define I2S_CFG2_IOSWP BIT(I2S_CFG2_IOSWP_SHIFT) +#define I2S_CFG2_LSBFRST BIT(23) +#define I2S_CFG2_AFCNTR BIT(31) + +/* Bit definition for SPI_CFG1 register */ +#define I2S_CFG1_FTHVL_SHIFT 5 +#define I2S_CFG1_FTHVL_MASK GENMASK(8, I2S_CFG1_FTHVL_SHIFT) +#define I2S_CFG1_FTHVL_SET(x) ((x) << I2S_CFG1_FTHVL_SHIFT) + +#define I2S_CFG1_TXDMAEN BIT(15) +#define I2S_CFG1_RXDMAEN BIT(14) + +/* Bit definition for SPI2S_IER register */ +#define I2S_IER_RXPIE BIT(0) +#define I2S_IER_TXPIE BIT(1) +#define I2S_IER_DPXPIE BIT(2) +#define I2S_IER_EOTIE BIT(3) +#define I2S_IER_TXTFIE BIT(4) +#define I2S_IER_UDRIE BIT(5) +#define I2S_IER_OVRIE BIT(6) +#define I2S_IER_CRCEIE BIT(7) +#define I2S_IER_TIFREIE BIT(8) +#define I2S_IER_MODFIE BIT(9) +#define I2S_IER_TSERFIE BIT(10) + +/* Bit definition for SPI2S_SR register */ +#define I2S_SR_RXP BIT(0) +#define I2S_SR_TXP BIT(1) +#define I2S_SR_DPXP BIT(2) +#define I2S_SR_EOT BIT(3) +#define I2S_SR_TXTF BIT(4) +#define I2S_SR_UDR BIT(5) +#define I2S_SR_OVR BIT(6) +#define I2S_SR_CRCERR BIT(7) +#define I2S_SR_TIFRE BIT(8) +#define I2S_SR_MODF BIT(9) +#define I2S_SR_TSERF BIT(10) +#define I2S_SR_SUSP BIT(11) +#define I2S_SR_TXC BIT(12) +#define I2S_SR_RXPLVL GENMASK(14, 13) +#define I2S_SR_RXWNE BIT(15) + +#define I2S_SR_MASK GENMASK(15, 0) + +/* Bit definition for SPI_IFCR register */ +#define I2S_IFCR_EOTC BIT(3) +#define I2S_IFCR_TXTFC BIT(4) +#define I2S_IFCR_UDRC BIT(5) +#define I2S_IFCR_OVRC BIT(6) +#define I2S_IFCR_CRCEC BIT(7) +#define I2S_IFCR_TIFREC BIT(8) +#define I2S_IFCR_MODFC BIT(9) +#define I2S_IFCR_TSERFC BIT(10) +#define I2S_IFCR_SUSPC BIT(11) + +#define I2S_IFCR_MASK GENMASK(11, 3) + +/* Bit definition for SPI_I2SCGFR register */ +#define I2S_CGFR_I2SMOD BIT(0) + +#define I2S_CGFR_I2SCFG_SHIFT 1 +#define I2S_CGFR_I2SCFG_MASK GENMASK(3, I2S_CGFR_I2SCFG_SHIFT) +#define I2S_CGFR_I2SCFG_SET(x) ((x) << I2S_CGFR_I2SCFG_SHIFT) + +#define I2S_CGFR_I2SSTD_SHIFT 4 +#define I2S_CGFR_I2SSTD_MASK GENMASK(5, I2S_CGFR_I2SSTD_SHIFT) +#define I2S_CGFR_I2SSTD_SET(x) ((x) << I2S_CGFR_I2SSTD_SHIFT) + +#define I2S_CGFR_PCMSYNC BIT(7) + +#define I2S_CGFR_DATLEN_SHIFT 8 +#define I2S_CGFR_DATLEN_MASK GENMASK(9, I2S_CGFR_DATLEN_SHIFT) +#define I2S_CGFR_DATLEN_SET(x) ((x) << I2S_CGFR_DATLEN_SHIFT) + +#define I2S_CGFR_CHLEN_SHIFT 10 +#define I2S_CGFR_CHLEN BIT(I2S_CGFR_CHLEN_SHIFT) +#define I2S_CGFR_CKPOL BIT(11) +#define I2S_CGFR_FIXCH BIT(12) +#define I2S_CGFR_WSINV BIT(13) +#define I2S_CGFR_DATFMT BIT(14) + +#define I2S_CGFR_I2SDIV_SHIFT 16 +#define I2S_CGFR_I2SDIV_BIT_H 23 +#define I2S_CGFR_I2SDIV_MASK GENMASK(I2S_CGFR_I2SDIV_BIT_H,\ + I2S_CGFR_I2SDIV_SHIFT) +#define I2S_CGFR_I2SDIV_SET(x) ((x) << I2S_CGFR_I2SDIV_SHIFT) +#define I2S_CGFR_I2SDIV_MAX ((1 << (I2S_CGFR_I2SDIV_BIT_H -\ + I2S_CGFR_I2SDIV_SHIFT)) - 1) + +#define I2S_CGFR_ODD_SHIFT 24 +#define I2S_CGFR_ODD BIT(I2S_CGFR_ODD_SHIFT) +#define I2S_CGFR_MCKOE BIT(25) + +enum i2s_master_mode { + I2S_MS_NOT_SET, + I2S_MS_MASTER, + I2S_MS_SLAVE, +}; + +enum i2s_mode { + I2S_I2SMOD_TX_SLAVE, + I2S_I2SMOD_RX_SLAVE, + I2S_I2SMOD_TX_MASTER, + I2S_I2SMOD_RX_MASTER, + I2S_I2SMOD_FD_SLAVE, + I2S_I2SMOD_FD_MASTER, +}; + +enum i2s_fifo_th { + I2S_FIFO_TH_NONE, + I2S_FIFO_TH_ONE_QUARTER, + I2S_FIFO_TH_HALF, + I2S_FIFO_TH_THREE_QUARTER, + I2S_FIFO_TH_FULL, +}; + +enum i2s_std { + I2S_STD_I2S, + I2S_STD_LEFT_J, + I2S_STD_RIGHT_J, + I2S_STD_DSP, +}; + +enum i2s_datlen { + I2S_I2SMOD_DATLEN_16, + I2S_I2SMOD_DATLEN_24, + I2S_I2SMOD_DATLEN_32, +}; + +#define STM32_I2S_DAI_NAME_SIZE 20 +#define STM32_I2S_FIFO_SIZE 16 + +#define STM32_I2S_IS_MASTER(x) ((x)->ms_flg == I2S_MS_MASTER) +#define STM32_I2S_IS_SLAVE(x) ((x)->ms_flg == I2S_MS_SLAVE) + +/** + * @regmap_conf: I2S register map configuration pointer + * @egmap: I2S register map pointer + * @pdev: device data pointer + * @dai_drv: DAI driver pointer + * @dma_data_tx: dma configuration data for tx channel + * @dma_data_rx: dma configuration data for tx channel + * @substream: PCM substream data pointer + * @i2sclk: kernel clock feeding the I2S clock generator + * @pclk: peripheral clock driving bus interface + * @x8kclk: I2S parent clock for sampling frequencies multiple of 8kHz + * @x11kclk: I2S parent clock for sampling frequencies multiple of 11kHz + * @base: mmio register base virtual address + * @phys_addr: I2S registers physical base address + * @lock_fd: lock to manage race conditions in full duplex mode + * @dais_name: DAI name + * @mclk_rate: master clock frequency (Hz) + * @fmt: DAI protocol + * @refcount: keep count of opened streams on I2S + * @ms_flg: master mode flag. + */ +struct stm32_i2s_data { + const struct regmap_config *regmap_conf; + struct regmap *regmap; + struct platform_device *pdev; + struct snd_soc_dai_driver *dai_drv; + struct snd_dmaengine_dai_dma_data dma_data_tx; + struct snd_dmaengine_dai_dma_data dma_data_rx; + struct snd_pcm_substream *substream; + struct clk *i2sclk; + struct clk *pclk; + struct clk *x8kclk; + struct clk *x11kclk; + void __iomem *base; + dma_addr_t phys_addr; + spinlock_t lock_fd; /* Manage race conditions for full duplex */ + char dais_name[STM32_I2S_DAI_NAME_SIZE]; + unsigned int mclk_rate; + unsigned int fmt; + int refcount; + int ms_flg; +}; + +static irqreturn_t stm32_i2s_isr(int irq, void *devid) +{ + struct stm32_i2s_data *i2s = (struct stm32_i2s_data *)devid; + struct platform_device *pdev = i2s->pdev; + u32 sr, ier; + unsigned long flags; + int err = 0; + + regmap_read(i2s->regmap, STM32_I2S_SR_REG, &sr); + regmap_read(i2s->regmap, STM32_I2S_IER_REG, &ier); + + flags = sr & ier; + if (!flags) { + dev_dbg(&pdev->dev, "Spurious IRQ sr=0x%08x, ier=0x%08x\n", + sr, ier); + return IRQ_NONE; + } + + regmap_update_bits(i2s->regmap, STM32_I2S_IFCR_REG, + I2S_IFCR_MASK, flags); + + if (flags & I2S_SR_OVR) { + dev_dbg(&pdev->dev, "Overrun\n"); + err = 1; + } + + if (flags & I2S_SR_UDR) { + dev_dbg(&pdev->dev, "Underrun\n"); + err = 1; + } + + if (flags & I2S_SR_TIFRE) + dev_dbg(&pdev->dev, "Frame error\n"); + + if (err) + snd_pcm_stop_xrun(i2s->substream); + + return IRQ_HANDLED; +} + +static bool stm32_i2s_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_I2S_CR1_REG: + case STM32_I2S_CFG1_REG: + case STM32_I2S_CFG2_REG: + case STM32_I2S_IER_REG: + case STM32_I2S_SR_REG: + case STM32_I2S_IFCR_REG: + case STM32_I2S_TXDR_REG: + case STM32_I2S_RXDR_REG: + case STM32_I2S_CGFR_REG: + return true; + default: + return false; + } +} + +static bool stm32_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_I2S_TXDR_REG: + case STM32_I2S_RXDR_REG: + return true; + default: + return false; + } +} + +static bool stm32_i2s_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_I2S_CR1_REG: + case STM32_I2S_CFG1_REG: + case STM32_I2S_CFG2_REG: + case STM32_I2S_IER_REG: + case STM32_I2S_IFCR_REG: + case STM32_I2S_TXDR_REG: + case STM32_I2S_CGFR_REG: + return true; + default: + return false; + } +} + +static int stm32_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + u32 cgfr; + u32 cgfr_mask = I2S_CGFR_I2SSTD_MASK | I2S_CGFR_CKPOL | + I2S_CGFR_WSINV | I2S_CGFR_I2SCFG_MASK; + + dev_dbg(cpu_dai->dev, "fmt %x\n", fmt); + + /* + * winv = 0 : default behavior (high/low) for all standards + * ckpol = 0 for all standards. + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_I2S); + break; + case SND_SOC_DAIFMT_MSB: + cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_LEFT_J); + break; + case SND_SOC_DAIFMT_LSB: + cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_RIGHT_J); + break; + case SND_SOC_DAIFMT_DSP_A: + cgfr = I2S_CGFR_I2SSTD_SET(I2S_STD_DSP); + break; + /* DSP_B not mapped on I2S PCM long format. 1 bit offset does not fit */ + default: + dev_err(cpu_dai->dev, "Unsupported protocol %#x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + /* DAI clock strobing */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + cgfr |= I2S_CGFR_CKPOL; + break; + case SND_SOC_DAIFMT_NB_IF: + cgfr |= I2S_CGFR_WSINV; + break; + case SND_SOC_DAIFMT_IB_IF: + cgfr |= I2S_CGFR_CKPOL; + cgfr |= I2S_CGFR_WSINV; + break; + default: + dev_err(cpu_dai->dev, "Unsupported strobing %#x\n", + fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->ms_flg = I2S_MS_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->ms_flg = I2S_MS_MASTER; + break; + default: + dev_err(cpu_dai->dev, "Unsupported mode %#x\n", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + i2s->fmt = fmt; + return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + cgfr_mask, cgfr); +} + +static int stm32_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + + dev_dbg(cpu_dai->dev, "I2S MCLK frequency is %uHz\n", freq); + + if ((dir == SND_SOC_CLOCK_OUT) && STM32_I2S_IS_MASTER(i2s)) { + i2s->mclk_rate = freq; + + /* Enable master clock if master mode and mclk-fs are set */ + return regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + I2S_CGFR_MCKOE, I2S_CGFR_MCKOE); + } + + return 0; +} + +static int stm32_i2s_configure_clock(struct snd_soc_dai *cpu_dai, + struct snd_pcm_hw_params *params) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + unsigned long i2s_clock_rate; + unsigned int tmp, div, real_div, nb_bits, frame_len; + unsigned int rate = params_rate(params); + int ret; + u32 cgfr, cgfr_mask; + bool odd; + + if (!(rate % 11025)) + clk_set_parent(i2s->i2sclk, i2s->x11kclk); + else + clk_set_parent(i2s->i2sclk, i2s->x8kclk); + i2s_clock_rate = clk_get_rate(i2s->i2sclk); + + /* + * mckl = mclk_ratio x ws + * i2s mode : mclk_ratio = 256 + * dsp mode : mclk_ratio = 128 + * + * mclk on + * i2s mode : div = i2s_clk / (mclk_ratio * ws) + * dsp mode : div = i2s_clk / (mclk_ratio * ws) + * mclk off + * i2s mode : div = i2s_clk / (nb_bits x ws) + * dsp mode : div = i2s_clk / (nb_bits x ws) + */ + if (i2s->mclk_rate) { + tmp = DIV_ROUND_CLOSEST(i2s_clock_rate, i2s->mclk_rate); + } else { + frame_len = 32; + if ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) == + SND_SOC_DAIFMT_DSP_A) + frame_len = 16; + + /* master clock not enabled */ + ret = regmap_read(i2s->regmap, STM32_I2S_CGFR_REG, &cgfr); + if (ret < 0) + return ret; + + nb_bits = frame_len * ((cgfr & I2S_CGFR_CHLEN) + 1); + tmp = DIV_ROUND_CLOSEST(i2s_clock_rate, (nb_bits * rate)); + } + + /* Check the parity of the divider */ + odd = tmp & 0x1; + + /* Compute the div prescaler */ + div = tmp >> 1; + + cgfr = I2S_CGFR_I2SDIV_SET(div) | (odd << I2S_CGFR_ODD_SHIFT); + cgfr_mask = I2S_CGFR_I2SDIV_MASK | I2S_CGFR_ODD; + + real_div = ((2 * div) + odd); + dev_dbg(cpu_dai->dev, "I2S clk: %ld, SCLK: %d\n", + i2s_clock_rate, rate); + dev_dbg(cpu_dai->dev, "Divider: 2*%d(div)+%d(odd) = %d\n", + div, odd, real_div); + + if (((div == 1) && odd) || (div > I2S_CGFR_I2SDIV_MAX)) { + dev_err(cpu_dai->dev, "Wrong divider setting\n"); + return -EINVAL; + } + + if (!div && !odd) + dev_warn(cpu_dai->dev, "real divider forced to 1\n"); + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + cgfr_mask, cgfr); + if (ret < 0) + return ret; + + /* Set bitclock and frameclock to their inactive state */ + return regmap_update_bits(i2s->regmap, STM32_I2S_CFG2_REG, + I2S_CFG2_AFCNTR, I2S_CFG2_AFCNTR); +} + +static int stm32_i2s_configure(struct snd_soc_dai *cpu_dai, + struct snd_pcm_hw_params *params, + struct snd_pcm_substream *substream) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + int format = params_width(params); + u32 cfgr, cfgr_mask, cfg1, cfg1_mask; + unsigned int fthlv; + int ret; + + if ((params_channels(params) == 1) && + ((i2s->fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_DSP_A)) { + dev_err(cpu_dai->dev, "Mono mode supported only by DSP_A\n"); + return -EINVAL; + } + + switch (format) { + case 16: + cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_16); + cfgr_mask = I2S_CGFR_DATLEN_MASK; + break; + case 32: + cfgr = I2S_CGFR_DATLEN_SET(I2S_I2SMOD_DATLEN_32) | + I2S_CGFR_CHLEN; + cfgr_mask = I2S_CGFR_DATLEN_MASK | I2S_CGFR_CHLEN; + break; + default: + dev_err(cpu_dai->dev, "Unexpected format %d", format); + return -EINVAL; + } + + if (STM32_I2S_IS_SLAVE(i2s)) { + cfgr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_FD_SLAVE); + + /* As data length is either 16 or 32 bits, fixch always set */ + cfgr |= I2S_CGFR_FIXCH; + cfgr_mask |= I2S_CGFR_FIXCH; + } else { + cfgr |= I2S_CGFR_I2SCFG_SET(I2S_I2SMOD_FD_MASTER); + } + cfgr_mask |= I2S_CGFR_I2SCFG_MASK; + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + cfgr_mask, cfgr); + if (ret < 0) + return ret; + + cfg1 = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN; + cfg1_mask = cfg1; + + fthlv = STM32_I2S_FIFO_SIZE * I2S_FIFO_TH_ONE_QUARTER / 4; + cfg1 |= I2S_CFG1_FTHVL_SET(fthlv - 1); + cfg1_mask |= I2S_CFG1_FTHVL_MASK; + + return regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, + cfg1_mask, cfg1); +} + +static int stm32_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + + i2s->substream = substream; + + spin_lock(&i2s->lock_fd); + i2s->refcount++; + spin_unlock(&i2s->lock_fd); + + return regmap_update_bits(i2s->regmap, STM32_I2S_IFCR_REG, + I2S_IFCR_MASK, I2S_IFCR_MASK); +} + +static int stm32_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + ret = stm32_i2s_configure(cpu_dai, params, substream); + if (ret < 0) { + dev_err(cpu_dai->dev, "Configuration returned error %d\n", ret); + return ret; + } + + if (STM32_I2S_IS_MASTER(i2s)) + ret = stm32_i2s_configure_clock(cpu_dai, params); + + return ret; +} + +static int stm32_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + bool playback_flg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + u32 cfg1_mask, ier; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* Enable i2s */ + dev_dbg(cpu_dai->dev, "start I2S\n"); + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG, + I2S_CR1_SPE, I2S_CR1_SPE); + if (ret < 0) { + dev_err(cpu_dai->dev, "Error %d enabling I2S\n", ret); + return ret; + } + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG, + I2S_CR1_CSTART, I2S_CR1_CSTART); + if (ret < 0) { + dev_err(cpu_dai->dev, "Error %d starting I2S\n", ret); + return ret; + } + + regmap_update_bits(i2s->regmap, STM32_I2S_IFCR_REG, + I2S_IFCR_MASK, I2S_IFCR_MASK); + + if (playback_flg) { + ier = I2S_IER_UDRIE; + } else { + ier = I2S_IER_OVRIE; + + spin_lock(&i2s->lock_fd); + if (i2s->refcount == 1) + /* dummy write to trigger capture */ + regmap_write(i2s->regmap, + STM32_I2S_TXDR_REG, 0); + spin_unlock(&i2s->lock_fd); + } + + if (STM32_I2S_IS_SLAVE(i2s)) + ier |= I2S_IER_TIFREIE; + + regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG, ier, ier); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (playback_flg) + regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG, + I2S_IER_UDRIE, + (unsigned int)~I2S_IER_UDRIE); + else + regmap_update_bits(i2s->regmap, STM32_I2S_IER_REG, + I2S_IER_OVRIE, + (unsigned int)~I2S_IER_OVRIE); + + spin_lock(&i2s->lock_fd); + i2s->refcount--; + if (i2s->refcount) { + spin_unlock(&i2s->lock_fd); + break; + } + spin_unlock(&i2s->lock_fd); + + dev_dbg(cpu_dai->dev, "stop I2S\n"); + + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CR1_REG, + I2S_CR1_SPE, 0); + if (ret < 0) { + dev_err(cpu_dai->dev, "Error %d disabling I2S\n", ret); + return ret; + } + + cfg1_mask = I2S_CFG1_RXDMAEN | I2S_CFG1_TXDMAEN; + regmap_update_bits(i2s->regmap, STM32_I2S_CFG1_REG, + cfg1_mask, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void stm32_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = snd_soc_dai_get_drvdata(cpu_dai); + + i2s->substream = NULL; + + regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + I2S_CGFR_MCKOE, (unsigned int)~I2S_CGFR_MCKOE); +} + +static int stm32_i2s_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct stm32_i2s_data *i2s = dev_get_drvdata(cpu_dai->dev); + struct snd_dmaengine_dai_dma_data *dma_data_tx = &i2s->dma_data_tx; + struct snd_dmaengine_dai_dma_data *dma_data_rx = &i2s->dma_data_rx; + + /* Buswidth will be set by framework */ + dma_data_tx->addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + dma_data_tx->addr = (dma_addr_t)(i2s->phys_addr) + STM32_I2S_TXDR_REG; + dma_data_tx->maxburst = 1; + dma_data_rx->addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + dma_data_rx->addr = (dma_addr_t)(i2s->phys_addr) + STM32_I2S_RXDR_REG; + dma_data_rx->maxburst = 1; + + snd_soc_dai_init_dma_data(cpu_dai, dma_data_tx, dma_data_rx); + + return 0; +} + +static const struct regmap_config stm32_h7_i2s_regmap_conf = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = STM32_I2S_CGFR_REG, + .readable_reg = stm32_i2s_readable_reg, + .volatile_reg = stm32_i2s_volatile_reg, + .writeable_reg = stm32_i2s_writeable_reg, + .fast_io = true, +}; + +static const struct snd_soc_dai_ops stm32_i2s_pcm_dai_ops = { + .set_sysclk = stm32_i2s_set_sysclk, + .set_fmt = stm32_i2s_set_dai_fmt, + .startup = stm32_i2s_startup, + .hw_params = stm32_i2s_hw_params, + .trigger = stm32_i2s_trigger, + .shutdown = stm32_i2s_shutdown, +}; + +static const struct snd_pcm_hardware stm32_i2s_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, + .buffer_bytes_max = 8 * PAGE_SIZE, + .period_bytes_max = 2048, + .periods_min = 2, + .periods_max = 8, +}; + +static const struct snd_dmaengine_pcm_config stm32_i2s_pcm_config = { + .pcm_hardware = &stm32_i2s_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .prealloc_buffer_size = PAGE_SIZE * 8, +}; + +static const struct snd_soc_component_driver stm32_i2s_component = { + .name = "stm32-i2s", +}; + +static void stm32_i2s_dai_init(struct snd_soc_pcm_stream *stream, + char *stream_name) +{ + stream->stream_name = stream_name; + stream->channels_min = 1; + stream->channels_max = 2; + stream->rates = SNDRV_PCM_RATE_8000_192000; + stream->formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE; +} + +static int stm32_i2s_dais_init(struct platform_device *pdev, + struct stm32_i2s_data *i2s) +{ + struct snd_soc_dai_driver *dai_ptr; + + dai_ptr = devm_kzalloc(&pdev->dev, sizeof(struct snd_soc_dai_driver), + GFP_KERNEL); + if (!dai_ptr) + return -ENOMEM; + + snprintf(i2s->dais_name, STM32_I2S_DAI_NAME_SIZE, + "%s", dev_name(&pdev->dev)); + + dai_ptr->probe = stm32_i2s_dai_probe; + dai_ptr->ops = &stm32_i2s_pcm_dai_ops; + dai_ptr->name = i2s->dais_name; + dai_ptr->id = 1; + stm32_i2s_dai_init(&dai_ptr->playback, "playback"); + stm32_i2s_dai_init(&dai_ptr->capture, "capture"); + i2s->dai_drv = dai_ptr; + + return 0; +} + +static const struct of_device_id stm32_i2s_ids[] = { + { + .compatible = "st,stm32h7-i2s", + .data = &stm32_h7_i2s_regmap_conf + }, + {}, +}; + +static int stm32_i2s_parse_dt(struct platform_device *pdev, + struct stm32_i2s_data *i2s) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; + struct reset_control *rst; + struct resource *res; + int irq, ret; + + if (!np) + return -ENODEV; + + of_id = of_match_device(stm32_i2s_ids, &pdev->dev); + if (of_id) + i2s->regmap_conf = (const struct regmap_config *)of_id->data; + else + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + i2s->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(i2s->base)) + return PTR_ERR(i2s->base); + + i2s->phys_addr = res->start; + + /* Get clocks */ + i2s->pclk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(i2s->pclk)) { + dev_err(&pdev->dev, "Could not get pclk\n"); + return PTR_ERR(i2s->pclk); + } + + i2s->i2sclk = devm_clk_get(&pdev->dev, "i2sclk"); + if (IS_ERR(i2s->i2sclk)) { + dev_err(&pdev->dev, "Could not get i2sclk\n"); + return PTR_ERR(i2s->i2sclk); + } + + i2s->x8kclk = devm_clk_get(&pdev->dev, "x8k"); + if (IS_ERR(i2s->x8kclk)) { + dev_err(&pdev->dev, "missing x8k parent clock\n"); + return PTR_ERR(i2s->x8kclk); + } + + i2s->x11kclk = devm_clk_get(&pdev->dev, "x11k"); + if (IS_ERR(i2s->x11kclk)) { + dev_err(&pdev->dev, "missing x11k parent clock\n"); + return PTR_ERR(i2s->x11kclk); + } + + /* Get irqs */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return -ENOENT; + } + + ret = devm_request_irq(&pdev->dev, irq, stm32_i2s_isr, IRQF_ONESHOT, + dev_name(&pdev->dev), i2s); + if (ret) { + dev_err(&pdev->dev, "irq request returned %d\n", ret); + return ret; + } + + /* Reset */ + rst = devm_reset_control_get(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + return 0; +} + +static int stm32_i2s_probe(struct platform_device *pdev) +{ + struct stm32_i2s_data *i2s; + int ret; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + ret = stm32_i2s_parse_dt(pdev, i2s); + if (ret) + return ret; + + i2s->pdev = pdev; + i2s->ms_flg = I2S_MS_NOT_SET; + spin_lock_init(&i2s->lock_fd); + platform_set_drvdata(pdev, i2s); + + ret = stm32_i2s_dais_init(pdev, i2s); + if (ret) + return ret; + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->base, + i2s->regmap_conf); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(i2s->regmap); + } + + ret = clk_prepare_enable(i2s->pclk); + if (ret) { + dev_err(&pdev->dev, "Enable pclk failed: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(i2s->i2sclk); + if (ret) { + dev_err(&pdev->dev, "Enable i2sclk failed: %d\n", ret); + goto err_pclk_disable; + } + + ret = devm_snd_soc_register_component(&pdev->dev, &stm32_i2s_component, + i2s->dai_drv, 1); + if (ret) + goto err_clocks_disable; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &stm32_i2s_pcm_config, 0); + if (ret) + goto err_clocks_disable; + + /* Set SPI/I2S in i2s mode */ + ret = regmap_update_bits(i2s->regmap, STM32_I2S_CGFR_REG, + I2S_CGFR_I2SMOD, I2S_CGFR_I2SMOD); + if (ret) + goto err_clocks_disable; + + return ret; + +err_clocks_disable: + clk_disable_unprepare(i2s->i2sclk); +err_pclk_disable: + clk_disable_unprepare(i2s->pclk); + + return ret; +} + +static int stm32_i2s_remove(struct platform_device *pdev) +{ + struct stm32_i2s_data *i2s = platform_get_drvdata(pdev); + + clk_disable_unprepare(i2s->i2sclk); + clk_disable_unprepare(i2s->pclk); + + return 0; +} + +MODULE_DEVICE_TABLE(of, stm32_i2s_ids); + +static struct platform_driver stm32_i2s_driver = { + .driver = { + .name = "st,stm32-i2s", + .of_match_table = stm32_i2s_ids, + }, + .probe = stm32_i2s_probe, + .remove = stm32_i2s_remove, +}; + +module_platform_driver(stm32_i2s_driver); + +MODULE_DESCRIPTION("STM32 Soc i2s Interface"); +MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>"); +MODULE_ALIAS("platform:stm32-i2s"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/stm/stm32_sai.c b/sound/soc/stm/stm32_sai.c index 2a27a26bf7a1..f7713314913b 100644 --- a/sound/soc/stm/stm32_sai.c +++ b/sound/soc/stm/stm32_sai.c @@ -27,8 +27,17 @@ #include "stm32_sai.h" +static const struct stm32_sai_conf stm32_sai_conf_f4 = { + .version = SAI_STM32F4, +}; + +static const struct stm32_sai_conf stm32_sai_conf_h7 = { + .version = SAI_STM32H7, +}; + static const struct of_device_id stm32_sai_ids[] = { - { .compatible = "st,stm32f4-sai", .data = (void *)SAI_STM32F4 }, + { .compatible = "st,stm32f4-sai", .data = (void *)&stm32_sai_conf_f4 }, + { .compatible = "st,stm32h7-sai", .data = (void *)&stm32_sai_conf_h7 }, {} }; @@ -52,7 +61,7 @@ static int stm32_sai_probe(struct platform_device *pdev) of_id = of_match_device(stm32_sai_ids, &pdev->dev); if (of_id) - sai->version = (enum stm32_sai_version)of_id->data; + sai->conf = (struct stm32_sai_conf *)of_id->data; else return -EINVAL; @@ -110,6 +119,6 @@ static struct platform_driver stm32_sai_driver = { module_platform_driver(stm32_sai_driver); MODULE_DESCRIPTION("STM32 Soc SAI Interface"); -MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>"); +MODULE_AUTHOR("Olivier Moysan <olivier.moysan@st.com>"); MODULE_ALIAS("platform:st,stm32-sai"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/stm/stm32_sai.h b/sound/soc/stm/stm32_sai.h index a801fda5066f..889974dc62d9 100644 --- a/sound/soc/stm/stm32_sai.h +++ b/sound/soc/stm/stm32_sai.h @@ -31,6 +31,10 @@ #define STM_SAI_CLRFR_REGX 0x18 #define STM_SAI_DR_REGX 0x1C +/* Sub-block A registers, relative to sub-block A address */ +#define STM_SAI_PDMCR_REGX 0x40 +#define STM_SAI_PDMLY_REGX 0x44 + /******************** Bit definition for SAI_GCR register *******************/ #define SAI_GCR_SYNCIN_SHIFT 0 #define SAI_GCR_SYNCIN_MASK GENMASK(1, SAI_GCR_SYNCIN_SHIFT) @@ -75,10 +79,11 @@ #define SAI_XCR1_NODIV BIT(SAI_XCR1_NODIV_SHIFT) #define SAI_XCR1_MCKDIV_SHIFT 20 -#define SAI_XCR1_MCKDIV_WIDTH 4 -#define SAI_XCR1_MCKDIV_MASK GENMASK(24, SAI_XCR1_MCKDIV_SHIFT) +#define SAI_XCR1_MCKDIV_WIDTH(x) (((x) == SAI_STM32F4) ? 4 : 6) +#define SAI_XCR1_MCKDIV_MASK(x) GENMASK((SAI_XCR1_MCKDIV_SHIFT + (x) - 1),\ + SAI_XCR1_MCKDIV_SHIFT) #define SAI_XCR1_MCKDIV_SET(x) ((x) << SAI_XCR1_MCKDIV_SHIFT) -#define SAI_XCR1_MCKDIV_MAX ((1 << SAI_XCR1_MCKDIV_WIDTH) - 1) +#define SAI_XCR1_MCKDIV_MAX(x) ((1 << SAI_XCR1_MCKDIV_WIDTH(x)) - 1) #define SAI_XCR1_OSR_SHIFT 26 #define SAI_XCR1_OSR BIT(SAI_XCR1_OSR_SHIFT) @@ -125,7 +130,6 @@ #define SAI_XFRCR_FSOFF BIT(SAI_XFRCR_FSOFF_SHIFT) /****************** Bit definition for SAI_XSLOTR register ******************/ - #define SAI_XSLOTR_FBOFF_SHIFT 0 #define SAI_XSLOTR_FBOFF_MASK GENMASK(4, SAI_XSLOTR_FBOFF_SHIFT) #define SAI_XSLOTR_FBOFF_SET(x) ((x) << SAI_XSLOTR_FBOFF_SHIFT) @@ -179,8 +183,65 @@ #define SAI_XCLRFR_SHIFT 0 #define SAI_XCLRFR_MASK GENMASK(6, SAI_XCLRFR_SHIFT) +/****************** Bit definition for SAI_PDMCR register ******************/ +#define SAI_PDMCR_PDMEN BIT(0) + +#define SAI_PDMCR_MICNBR_SHIFT 4 +#define SAI_PDMCR_MICNBR_MASK GENMASK(5, SAI_PDMCR_MICNBR_SHIFT) +#define SAI_PDMCR_MICNBR_SET(x) ((x) << SAI_PDMCR_MICNBR_SHIFT) + +#define SAI_PDMCR_CKEN1 BIT(8) +#define SAI_PDMCR_CKEN2 BIT(9) +#define SAI_PDMCR_CKEN3 BIT(10) +#define SAI_PDMCR_CKEN4 BIT(11) + +/****************** Bit definition for (SAI_PDMDLY register ****************/ +#define SAI_PDMDLY_1L_SHIFT 0 +#define SAI_PDMDLY_1L_MASK GENMASK(2, SAI_PDMDLY_1L_SHIFT) +#define SAI_PDMDLY_1L_WIDTH 3 + +#define SAI_PDMDLY_1R_SHIFT 4 +#define SAI_PDMDLY_1R_MASK GENMASK(6, SAI_PDMDLY_1R_SHIFT) +#define SAI_PDMDLY_1R_WIDTH 3 + +#define SAI_PDMDLY_2L_SHIFT 8 +#define SAI_PDMDLY_2L_MASK GENMASK(10, SAI_PDMDLY_2L_SHIFT) +#define SAI_PDMDLY_2L_WIDTH 3 + +#define SAI_PDMDLY_2R_SHIFT 12 +#define SAI_PDMDLY_2R_MASK GENMASK(14, SAI_PDMDLY_2R_SHIFT) +#define SAI_PDMDLY_2R_WIDTH 3 + +#define SAI_PDMDLY_3L_SHIFT 16 +#define SAI_PDMDLY_3L_MASK GENMASK(18, SAI_PDMDLY_3L_SHIFT) +#define SAI_PDMDLY_3L_WIDTH 3 + +#define SAI_PDMDLY_3R_SHIFT 20 +#define SAI_PDMDLY_3R_MASK GENMASK(22, SAI_PDMDLY_3R_SHIFT) +#define SAI_PDMDLY_3R_WIDTH 3 + +#define SAI_PDMDLY_4L_SHIFT 24 +#define SAI_PDMDLY_4L_MASK GENMASK(26, SAI_PDMDLY_4L_SHIFT) +#define SAI_PDMDLY_4L_WIDTH 3 + +#define SAI_PDMDLY_4R_SHIFT 28 +#define SAI_PDMDLY_4R_MASK GENMASK(30, SAI_PDMDLY_4R_SHIFT) +#define SAI_PDMDLY_4R_WIDTH 3 + +#define STM_SAI_IS_F4(ip) ((ip)->conf->version == SAI_STM32F4) +#define STM_SAI_IS_H7(ip) ((ip)->conf->version == SAI_STM32H7) + enum stm32_sai_version { - SAI_STM32F4 + SAI_STM32F4, + SAI_STM32H7 +}; + +/** + * struct stm32_sai_conf - SAI configuration + * @version: SAI version + */ +struct stm32_sai_conf { + int version; }; /** @@ -195,6 +256,6 @@ struct stm32_sai_data { struct platform_device *pdev; struct clk *clk_x8k; struct clk *clk_x11k; - int version; + struct stm32_sai_conf *conf; int irq; }; diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index ae4706ca265b..90d439613899 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -51,12 +51,15 @@ #define STM_SAI_A_ID 0x0 #define STM_SAI_B_ID 0x1 +#define STM_SAI_IS_SUB_A(x) ((x)->id == STM_SAI_A_ID) +#define STM_SAI_IS_SUB_B(x) ((x)->id == STM_SAI_B_ID) #define STM_SAI_BLOCK_NAME(x) (((x)->id == STM_SAI_A_ID) ? "A" : "B") /** * struct stm32_sai_sub_data - private data of SAI sub block (block A or B) * @pdev: device data pointer * @regmap: SAI register map pointer + * @regmap_config: SAI sub block register map configuration pointer * @dma_params: dma configuration data for rx or tx channel * @cpu_dai_drv: DAI driver data pointer * @cpu_dai: DAI runtime data pointer @@ -79,6 +82,7 @@ struct stm32_sai_sub_data { struct platform_device *pdev; struct regmap *regmap; + const struct regmap_config *regmap_config; struct snd_dmaengine_dai_dma_data dma_params; struct snd_soc_dai_driver *cpu_dai_drv; struct snd_soc_dai *cpu_dai; @@ -118,6 +122,8 @@ static bool stm32_sai_sub_readable_reg(struct device *dev, unsigned int reg) case STM_SAI_SR_REGX: case STM_SAI_CLRFR_REGX: case STM_SAI_DR_REGX: + case STM_SAI_PDMCR_REGX: + case STM_SAI_PDMLY_REGX: return true; default: return false; @@ -145,13 +151,15 @@ static bool stm32_sai_sub_writeable_reg(struct device *dev, unsigned int reg) case STM_SAI_SR_REGX: case STM_SAI_CLRFR_REGX: case STM_SAI_DR_REGX: + case STM_SAI_PDMCR_REGX: + case STM_SAI_PDMLY_REGX: return true; default: return false; } } -static const struct regmap_config stm32_sai_sub_regmap_config = { +static const struct regmap_config stm32_sai_sub_regmap_config_f4 = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, @@ -162,6 +170,17 @@ static const struct regmap_config stm32_sai_sub_regmap_config = { .fast_io = true, }; +static const struct regmap_config stm32_sai_sub_regmap_config_h7 = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = STM_SAI_PDMLY_REGX, + .readable_reg = stm32_sai_sub_readable_reg, + .volatile_reg = stm32_sai_sub_volatile_reg, + .writeable_reg = stm32_sai_sub_writeable_reg, + .fast_io = true, +}; + static irqreturn_t stm32_sai_isr(int irq, void *devid) { struct stm32_sai_sub_data *sai = (struct stm32_sai_sub_data *)devid; @@ -181,29 +200,29 @@ static irqreturn_t stm32_sai_isr(int irq, void *devid) SAI_XCLRFR_MASK); if (flags & SAI_XIMR_OVRUDRIE) { - dev_err(&pdev->dev, "IT %s\n", + dev_err(&pdev->dev, "IRQ %s\n", STM_SAI_IS_PLAYBACK(sai) ? "underrun" : "overrun"); status = SNDRV_PCM_STATE_XRUN; } if (flags & SAI_XIMR_MUTEDETIE) - dev_dbg(&pdev->dev, "IT mute detected\n"); + dev_dbg(&pdev->dev, "IRQ mute detected\n"); if (flags & SAI_XIMR_WCKCFGIE) { - dev_err(&pdev->dev, "IT wrong clock configuration\n"); + dev_err(&pdev->dev, "IRQ wrong clock configuration\n"); status = SNDRV_PCM_STATE_DISCONNECTED; } if (flags & SAI_XIMR_CNRDYIE) - dev_warn(&pdev->dev, "IT Codec not ready\n"); + dev_err(&pdev->dev, "IRQ Codec not ready\n"); if (flags & SAI_XIMR_AFSDETIE) { - dev_warn(&pdev->dev, "IT Anticipated frame synchro\n"); + dev_err(&pdev->dev, "IRQ Anticipated frame synchro\n"); status = SNDRV_PCM_STATE_XRUN; } if (flags & SAI_XIMR_LFSDETIE) { - dev_warn(&pdev->dev, "IT Late frame synchro\n"); + dev_err(&pdev->dev, "IRQ Late frame synchro\n"); status = SNDRV_PCM_STATE_XRUN; } @@ -220,8 +239,15 @@ static int stm32_sai_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); + int ret; if ((dir == SND_SOC_CLOCK_OUT) && sai->master) { + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, + SAI_XCR1_NODIV, + (unsigned int)~SAI_XCR1_NODIV); + if (ret < 0) + return ret; + sai->mclk_rate = freq; dev_dbg(cpu_dai->dev, "SAI MCLK frequency is %uHz\n", freq); } @@ -235,7 +261,7 @@ static int stm32_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); int slotr, slotr_mask, slot_size; - dev_dbg(cpu_dai->dev, "masks tx/rx:%#x/%#x, slots:%d, width:%d\n", + dev_dbg(cpu_dai->dev, "Masks tx/rx:%#x/%#x, slots:%d, width:%d\n", tx_mask, rx_mask, slots, slot_width); switch (slot_width) { @@ -356,6 +382,10 @@ static int stm32_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) } cr1_mask |= SAI_XCR1_SLAVE; + /* do not generate master by default */ + cr1 |= SAI_XCR1_NODIV; + cr1_mask |= SAI_XCR1_NODIV; + ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, cr1_mask, cr1); if (ret < 0) { dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); @@ -377,7 +407,7 @@ static int stm32_sai_startup(struct snd_pcm_substream *substream, ret = clk_prepare_enable(sai->sai_ck); if (ret < 0) { - dev_err(cpu_dai->dev, "failed to enable clock: %d\n", ret); + dev_err(cpu_dai->dev, "Failed to enable clock: %d\n", ret); return ret; } @@ -497,7 +527,7 @@ static int stm32_sai_set_slots(struct snd_soc_dai *cpu_dai) SAI_XSLOTR_SLOTEN_SET(sai->slot_mask)); } - dev_dbg(cpu_dai->dev, "slots %d, slot width %d\n", + dev_dbg(cpu_dai->dev, "Slots %d, slot width %d\n", sai->slots, sai->slot_width); return 0; @@ -521,7 +551,7 @@ static void stm32_sai_set_frame(struct snd_soc_dai *cpu_dai) frcr |= SAI_XFRCR_FSALL_SET((fs_active - 1)); frcr_mask = SAI_XFRCR_FRL_MASK | SAI_XFRCR_FSALL_MASK; - dev_dbg(cpu_dai->dev, "frame length %d, frame active %d\n", + dev_dbg(cpu_dai->dev, "Frame length %d, frame active %d\n", sai->fs_length, fs_active); regmap_update_bits(sai->regmap, STM_SAI_FRCR_REGX, frcr_mask, frcr); @@ -540,7 +570,8 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, { struct stm32_sai_sub_data *sai = snd_soc_dai_get_drvdata(cpu_dai); int cr1, mask, div = 0; - int sai_clk_rate, ret; + int sai_clk_rate, mclk_ratio, den, ret; + int version = sai->pdata->conf->version; if (!sai->mclk_rate) { dev_err(cpu_dai->dev, "Mclk rate is null\n"); @@ -553,21 +584,53 @@ static int stm32_sai_configure_clock(struct snd_soc_dai *cpu_dai, clk_set_parent(sai->sai_ck, sai->pdata->clk_x8k); sai_clk_rate = clk_get_rate(sai->sai_ck); - /* - * mclk_rate = 256 * fs - * MCKDIV = 0 if sai_ck < 3/2 * mclk_rate - * MCKDIV = sai_ck / (2 * mclk_rate) otherwise - */ - if (2 * sai_clk_rate >= 3 * sai->mclk_rate) - div = DIV_ROUND_CLOSEST(sai_clk_rate, 2 * sai->mclk_rate); - - if (div > SAI_XCR1_MCKDIV_MAX) { + if (STM_SAI_IS_F4(sai->pdata)) { + /* + * mclk_rate = 256 * fs + * MCKDIV = 0 if sai_ck < 3/2 * mclk_rate + * MCKDIV = sai_ck / (2 * mclk_rate) otherwise + */ + if (2 * sai_clk_rate >= 3 * sai->mclk_rate) + div = DIV_ROUND_CLOSEST(sai_clk_rate, + 2 * sai->mclk_rate); + } else { + /* + * TDM mode : + * mclk on + * MCKDIV = sai_ck / (ws x 256) (NOMCK=0. OSR=0) + * MCKDIV = sai_ck / (ws x 512) (NOMCK=0. OSR=1) + * mclk off + * MCKDIV = sai_ck / (frl x ws) (NOMCK=1) + * Note: NOMCK/NODIV correspond to same bit. + */ + if (sai->mclk_rate) { + mclk_ratio = sai->mclk_rate / params_rate(params); + if (mclk_ratio != 256) { + if (mclk_ratio == 512) { + mask = SAI_XCR1_OSR; + cr1 = SAI_XCR1_OSR; + } else { + dev_err(cpu_dai->dev, + "Wrong mclk ratio %d\n", + mclk_ratio); + return -EINVAL; + } + } + div = DIV_ROUND_CLOSEST(sai_clk_rate, sai->mclk_rate); + } else { + /* mclk-fs not set, master clock not active. NOMCK=1 */ + den = sai->fs_length * params_rate(params); + div = DIV_ROUND_CLOSEST(sai_clk_rate, den); + } + } + + if (div > SAI_XCR1_MCKDIV_MAX(version)) { dev_err(cpu_dai->dev, "Divider %d out of range\n", div); return -EINVAL; } dev_dbg(cpu_dai->dev, "SAI clock %d, divider %d\n", sai_clk_rate, div); - mask = SAI_XCR1_MCKDIV_MASK; + mask = SAI_XCR1_MCKDIV_MASK(SAI_XCR1_MCKDIV_WIDTH(version)); cr1 = SAI_XCR1_MCKDIV_SET(div); ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, mask, cr1); if (ret < 0) { @@ -629,12 +692,12 @@ static int stm32_sai_trigger(struct snd_pcm_substream *substream, int cmd, dev_dbg(cpu_dai->dev, "Disable DMA and SAI\n"); regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, - SAI_XCR1_DMAEN, - (unsigned int)~SAI_XCR1_DMAEN); + SAI_XCR1_SAIEN, + (unsigned int)~SAI_XCR1_SAIEN); ret = regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, - SAI_XCR1_SAIEN, - (unsigned int)~SAI_XCR1_SAIEN); + SAI_XCR1_DMAEN, + (unsigned int)~SAI_XCR1_DMAEN); if (ret < 0) dev_err(cpu_dai->dev, "Failed to update CR1 register\n"); break; @@ -652,6 +715,9 @@ static void stm32_sai_shutdown(struct snd_pcm_substream *substream, regmap_update_bits(sai->regmap, STM_SAI_IMR_REGX, SAI_XIMR_MASK, 0); + regmap_update_bits(sai->regmap, STM_SAI_CR1_REGX, SAI_XCR1_NODIV, + SAI_XCR1_NODIV); + clk_disable_unprepare(sai->sai_ck); sai->substream = NULL; } @@ -761,16 +827,23 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, return -ENODEV; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - dev_err(&pdev->dev, "res %pr\n", res); - base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(base)) return PTR_ERR(base); sai->phys_addr = res->start; - sai->regmap = devm_regmap_init_mmio(&pdev->dev, base, - &stm32_sai_sub_regmap_config); + + sai->regmap_config = &stm32_sai_sub_regmap_config_f4; + /* Note: PDM registers not available for H7 sub-block B */ + if (STM_SAI_IS_H7(sai->pdata) && STM_SAI_IS_SUB_A(sai)) + sai->regmap_config = &stm32_sai_sub_regmap_config_h7; + + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "sai_ck", + base, sai->regmap_config); + if (IS_ERR(sai->regmap)) { + dev_err(&pdev->dev, "Failed to initialize MMIO\n"); + return PTR_ERR(sai->regmap); + } /* Get direction property */ if (of_property_match_string(np, "dma-names", "tx") >= 0) { @@ -784,7 +857,7 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, sai->sai_ck = devm_clk_get(&pdev->dev, "sai_ck"); if (IS_ERR(sai->sai_ck)) { - dev_err(&pdev->dev, "missing kernel clock sai_ck\n"); + dev_err(&pdev->dev, "Missing kernel clock sai_ck\n"); return PTR_ERR(sai->sai_ck); } @@ -849,7 +922,7 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, sai->pdata->irq, stm32_sai_isr, IRQF_SHARED, dev_name(&pdev->dev), sai); if (ret) { - dev_err(&pdev->dev, "irq request returned %d\n", ret); + dev_err(&pdev->dev, "IRQ request returned %d\n", ret); return ret; } @@ -861,7 +934,7 @@ static int stm32_sai_sub_probe(struct platform_device *pdev) ret = devm_snd_dmaengine_pcm_register(&pdev->dev, &stm32_sai_pcm_config, 0); if (ret) { - dev_err(&pdev->dev, "could not register pcm dma\n"); + dev_err(&pdev->dev, "Could not register pcm dma\n"); return ret; } @@ -879,6 +952,6 @@ static struct platform_driver stm32_sai_sub_driver = { module_platform_driver(stm32_sai_sub_driver); MODULE_DESCRIPTION("STM32 Soc SAI sub-block Interface"); -MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>"); +MODULE_AUTHOR("Olivier Moysan <olivier.moysan@st.com>"); MODULE_ALIAS("platform:st,stm32-sai-sub"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c new file mode 100644 index 000000000000..4e4250bdb75a --- /dev/null +++ b/sound/soc/stm/stm32_spdifrx.c @@ -0,0 +1,998 @@ +/* + * STM32 ALSA SoC Digital Audio Interface (SPDIF-rx) driver. + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Olivier Moysan <olivier.moysan@st.com> for STMicroelectronics. + * + * License terms: GPL V2.0. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + */ + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +/* SPDIF-rx Register Map */ +#define STM32_SPDIFRX_CR 0x00 +#define STM32_SPDIFRX_IMR 0x04 +#define STM32_SPDIFRX_SR 0x08 +#define STM32_SPDIFRX_IFCR 0x0C +#define STM32_SPDIFRX_DR 0x10 +#define STM32_SPDIFRX_CSR 0x14 +#define STM32_SPDIFRX_DIR 0x18 + +/* Bit definition for SPDIF_CR register */ +#define SPDIFRX_CR_SPDIFEN_SHIFT 0 +#define SPDIFRX_CR_SPDIFEN_MASK GENMASK(1, SPDIFRX_CR_SPDIFEN_SHIFT) +#define SPDIFRX_CR_SPDIFENSET(x) ((x) << SPDIFRX_CR_SPDIFEN_SHIFT) + +#define SPDIFRX_CR_RXDMAEN BIT(2) +#define SPDIFRX_CR_RXSTEO BIT(3) + +#define SPDIFRX_CR_DRFMT_SHIFT 4 +#define SPDIFRX_CR_DRFMT_MASK GENMASK(5, SPDIFRX_CR_DRFMT_SHIFT) +#define SPDIFRX_CR_DRFMTSET(x) ((x) << SPDIFRX_CR_DRFMT_SHIFT) + +#define SPDIFRX_CR_PMSK BIT(6) +#define SPDIFRX_CR_VMSK BIT(7) +#define SPDIFRX_CR_CUMSK BIT(8) +#define SPDIFRX_CR_PTMSK BIT(9) +#define SPDIFRX_CR_CBDMAEN BIT(10) +#define SPDIFRX_CR_CHSEL_SHIFT 11 +#define SPDIFRX_CR_CHSEL BIT(SPDIFRX_CR_CHSEL_SHIFT) + +#define SPDIFRX_CR_NBTR_SHIFT 12 +#define SPDIFRX_CR_NBTR_MASK GENMASK(13, SPDIFRX_CR_NBTR_SHIFT) +#define SPDIFRX_CR_NBTRSET(x) ((x) << SPDIFRX_CR_NBTR_SHIFT) + +#define SPDIFRX_CR_WFA BIT(14) + +#define SPDIFRX_CR_INSEL_SHIFT 16 +#define SPDIFRX_CR_INSEL_MASK GENMASK(18, PDIFRX_CR_INSEL_SHIFT) +#define SPDIFRX_CR_INSELSET(x) ((x) << SPDIFRX_CR_INSEL_SHIFT) + +#define SPDIFRX_CR_CKSEN_SHIFT 20 +#define SPDIFRX_CR_CKSEN BIT(20) +#define SPDIFRX_CR_CKSBKPEN BIT(21) + +/* Bit definition for SPDIFRX_IMR register */ +#define SPDIFRX_IMR_RXNEI BIT(0) +#define SPDIFRX_IMR_CSRNEIE BIT(1) +#define SPDIFRX_IMR_PERRIE BIT(2) +#define SPDIFRX_IMR_OVRIE BIT(3) +#define SPDIFRX_IMR_SBLKIE BIT(4) +#define SPDIFRX_IMR_SYNCDIE BIT(5) +#define SPDIFRX_IMR_IFEIE BIT(6) + +#define SPDIFRX_XIMR_MASK GENMASK(6, 0) + +/* Bit definition for SPDIFRX_SR register */ +#define SPDIFRX_SR_RXNE BIT(0) +#define SPDIFRX_SR_CSRNE BIT(1) +#define SPDIFRX_SR_PERR BIT(2) +#define SPDIFRX_SR_OVR BIT(3) +#define SPDIFRX_SR_SBD BIT(4) +#define SPDIFRX_SR_SYNCD BIT(5) +#define SPDIFRX_SR_FERR BIT(6) +#define SPDIFRX_SR_SERR BIT(7) +#define SPDIFRX_SR_TERR BIT(8) + +#define SPDIFRX_SR_WIDTH5_SHIFT 16 +#define SPDIFRX_SR_WIDTH5_MASK GENMASK(30, PDIFRX_SR_WIDTH5_SHIFT) +#define SPDIFRX_SR_WIDTH5SET(x) ((x) << SPDIFRX_SR_WIDTH5_SHIFT) + +/* Bit definition for SPDIFRX_IFCR register */ +#define SPDIFRX_IFCR_PERRCF BIT(2) +#define SPDIFRX_IFCR_OVRCF BIT(3) +#define SPDIFRX_IFCR_SBDCF BIT(4) +#define SPDIFRX_IFCR_SYNCDCF BIT(5) + +#define SPDIFRX_XIFCR_MASK GENMASK(5, 2) + +/* Bit definition for SPDIFRX_DR register (DRFMT = 0b00) */ +#define SPDIFRX_DR0_DR_SHIFT 0 +#define SPDIFRX_DR0_DR_MASK GENMASK(23, SPDIFRX_DR0_DR_SHIFT) +#define SPDIFRX_DR0_DRSET(x) ((x) << SPDIFRX_DR0_DR_SHIFT) + +#define SPDIFRX_DR0_PE BIT(24) + +#define SPDIFRX_DR0_V BIT(25) +#define SPDIFRX_DR0_U BIT(26) +#define SPDIFRX_DR0_C BIT(27) + +#define SPDIFRX_DR0_PT_SHIFT 28 +#define SPDIFRX_DR0_PT_MASK GENMASK(29, SPDIFRX_DR0_PT_SHIFT) +#define SPDIFRX_DR0_PTSET(x) ((x) << SPDIFRX_DR0_PT_SHIFT) + +/* Bit definition for SPDIFRX_DR register (DRFMT = 0b01) */ +#define SPDIFRX_DR1_PE BIT(0) +#define SPDIFRX_DR1_V BIT(1) +#define SPDIFRX_DR1_U BIT(2) +#define SPDIFRX_DR1_C BIT(3) + +#define SPDIFRX_DR1_PT_SHIFT 4 +#define SPDIFRX_DR1_PT_MASK GENMASK(5, SPDIFRX_DR1_PT_SHIFT) +#define SPDIFRX_DR1_PTSET(x) ((x) << SPDIFRX_DR1_PT_SHIFT) + +#define SPDIFRX_DR1_DR_SHIFT 8 +#define SPDIFRX_DR1_DR_MASK GENMASK(31, SPDIFRX_DR1_DR_SHIFT) +#define SPDIFRX_DR1_DRSET(x) ((x) << SPDIFRX_DR1_DR_SHIFT) + +/* Bit definition for SPDIFRX_DR register (DRFMT = 0b10) */ +#define SPDIFRX_DR1_DRNL1_SHIFT 0 +#define SPDIFRX_DR1_DRNL1_MASK GENMASK(15, SPDIFRX_DR1_DRNL1_SHIFT) +#define SPDIFRX_DR1_DRNL1SET(x) ((x) << SPDIFRX_DR1_DRNL1_SHIFT) + +#define SPDIFRX_DR1_DRNL2_SHIFT 16 +#define SPDIFRX_DR1_DRNL2_MASK GENMASK(31, SPDIFRX_DR1_DRNL2_SHIFT) +#define SPDIFRX_DR1_DRNL2SET(x) ((x) << SPDIFRX_DR1_DRNL2_SHIFT) + +/* Bit definition for SPDIFRX_CSR register */ +#define SPDIFRX_CSR_USR_SHIFT 0 +#define SPDIFRX_CSR_USR_MASK GENMASK(15, SPDIFRX_CSR_USR_SHIFT) +#define SPDIFRX_CSR_USRGET(x) (((x) & SPDIFRX_CSR_USR_MASK)\ + >> SPDIFRX_CSR_USR_SHIFT) + +#define SPDIFRX_CSR_CS_SHIFT 16 +#define SPDIFRX_CSR_CS_MASK GENMASK(23, SPDIFRX_CSR_CS_SHIFT) +#define SPDIFRX_CSR_CSGET(x) (((x) & SPDIFRX_CSR_CS_MASK)\ + >> SPDIFRX_CSR_CS_SHIFT) + +#define SPDIFRX_CSR_SOB BIT(24) + +/* Bit definition for SPDIFRX_DIR register */ +#define SPDIFRX_DIR_THI_SHIFT 0 +#define SPDIFRX_DIR_THI_MASK GENMASK(12, SPDIFRX_DIR_THI_SHIFT) +#define SPDIFRX_DIR_THI_SET(x) ((x) << SPDIFRX_DIR_THI_SHIFT) + +#define SPDIFRX_DIR_TLO_SHIFT 16 +#define SPDIFRX_DIR_TLO_MASK GENMASK(28, SPDIFRX_DIR_TLO_SHIFT) +#define SPDIFRX_DIR_TLO_SET(x) ((x) << SPDIFRX_DIR_TLO_SHIFT) + +#define SPDIFRX_SPDIFEN_DISABLE 0x0 +#define SPDIFRX_SPDIFEN_SYNC 0x1 +#define SPDIFRX_SPDIFEN_ENABLE 0x3 + +#define SPDIFRX_IN1 0x1 +#define SPDIFRX_IN2 0x2 +#define SPDIFRX_IN3 0x3 +#define SPDIFRX_IN4 0x4 +#define SPDIFRX_IN5 0x5 +#define SPDIFRX_IN6 0x6 +#define SPDIFRX_IN7 0x7 +#define SPDIFRX_IN8 0x8 + +#define SPDIFRX_NBTR_NONE 0x0 +#define SPDIFRX_NBTR_3 0x1 +#define SPDIFRX_NBTR_15 0x2 +#define SPDIFRX_NBTR_63 0x3 + +#define SPDIFRX_DRFMT_RIGHT 0x0 +#define SPDIFRX_DRFMT_LEFT 0x1 +#define SPDIFRX_DRFMT_PACKED 0x2 + +/* 192 CS bits in S/PDIF frame. i.e 24 CS bytes */ +#define SPDIFRX_CS_BYTES_NB 24 +#define SPDIFRX_UB_BYTES_NB 48 + +/* + * CSR register is retrieved as a 32 bits word + * It contains 1 channel status byte and 2 user data bytes + * 2 S/PDIF frames are acquired to get all CS/UB bits + */ +#define SPDIFRX_CSR_BUF_LENGTH (SPDIFRX_CS_BYTES_NB * 4 * 2) + +/** + * struct stm32_spdifrx_data - private data of SPDIFRX + * @pdev: device data pointer + * @base: mmio register base virtual address + * @regmap: SPDIFRX register map pointer + * @regmap_conf: SPDIFRX register map configuration pointer + * @cs_completion: channel status retrieving completion + * @kclk: kernel clock feeding the SPDIFRX clock generator + * @dma_params: dma configuration data for rx channel + * @substream: PCM substream data pointer + * @dmab: dma buffer info pointer + * @ctrl_chan: dma channel for S/PDIF control bits + * @desc:dma async transaction descriptor + * @slave_config: dma slave channel runtime config pointer + * @phys_addr: SPDIFRX registers physical base address + * @lock: synchronization enabling lock + * @cs: channel status buffer + * @ub: user data buffer + * @irq: SPDIFRX interrupt line + * @refcount: keep count of opened DMA channels + */ +struct stm32_spdifrx_data { + struct platform_device *pdev; + void __iomem *base; + struct regmap *regmap; + const struct regmap_config *regmap_conf; + struct completion cs_completion; + struct clk *kclk; + struct snd_dmaengine_dai_dma_data dma_params; + struct snd_pcm_substream *substream; + struct snd_dma_buffer *dmab; + struct dma_chan *ctrl_chan; + struct dma_async_tx_descriptor *desc; + struct dma_slave_config slave_config; + dma_addr_t phys_addr; + spinlock_t lock; /* Sync enabling lock */ + unsigned char cs[SPDIFRX_CS_BYTES_NB]; + unsigned char ub[SPDIFRX_UB_BYTES_NB]; + int irq; + int refcount; +}; + +static void stm32_spdifrx_dma_complete(void *data) +{ + struct stm32_spdifrx_data *spdifrx = (struct stm32_spdifrx_data *)data; + struct platform_device *pdev = spdifrx->pdev; + u32 *p_start = (u32 *)spdifrx->dmab->area; + u32 *p_end = p_start + (2 * SPDIFRX_CS_BYTES_NB) - 1; + u32 *ptr = p_start; + u16 *ub_ptr = (short *)spdifrx->ub; + int i = 0; + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_CBDMAEN, + (unsigned int)~SPDIFRX_CR_CBDMAEN); + + if (!spdifrx->dmab->area) + return; + + while (ptr <= p_end) { + if (*ptr & SPDIFRX_CSR_SOB) + break; + ptr++; + } + + if (ptr > p_end) { + dev_err(&pdev->dev, "Start of S/PDIF block not found\n"); + return; + } + + while (i < SPDIFRX_CS_BYTES_NB) { + spdifrx->cs[i] = (unsigned char)SPDIFRX_CSR_CSGET(*ptr); + *ub_ptr++ = SPDIFRX_CSR_USRGET(*ptr++); + if (ptr > p_end) { + dev_err(&pdev->dev, "Failed to get channel status\n"); + return; + } + i++; + } + + complete(&spdifrx->cs_completion); +} + +static int stm32_spdifrx_dma_ctrl_start(struct stm32_spdifrx_data *spdifrx) +{ + dma_cookie_t cookie; + int err; + + spdifrx->desc = dmaengine_prep_slave_single(spdifrx->ctrl_chan, + spdifrx->dmab->addr, + SPDIFRX_CSR_BUF_LENGTH, + DMA_DEV_TO_MEM, + DMA_CTRL_ACK); + if (!spdifrx->desc) + return -EINVAL; + + spdifrx->desc->callback = stm32_spdifrx_dma_complete; + spdifrx->desc->callback_param = spdifrx; + cookie = dmaengine_submit(spdifrx->desc); + err = dma_submit_error(cookie); + if (err) + return -EINVAL; + + dma_async_issue_pending(spdifrx->ctrl_chan); + + return 0; +} + +static void stm32_spdifrx_dma_ctrl_stop(struct stm32_spdifrx_data *spdifrx) +{ + dmaengine_terminate_async(spdifrx->ctrl_chan); +} + +static int stm32_spdifrx_start_sync(struct stm32_spdifrx_data *spdifrx) +{ + int cr, cr_mask, imr, ret; + + /* Enable IRQs */ + imr = SPDIFRX_IMR_IFEIE | SPDIFRX_IMR_SYNCDIE | SPDIFRX_IMR_PERRIE; + ret = regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IMR, imr, imr); + if (ret) + return ret; + + spin_lock(&spdifrx->lock); + + spdifrx->refcount++; + + regmap_read(spdifrx->regmap, STM32_SPDIFRX_CR, &cr); + + if (!(cr & SPDIFRX_CR_SPDIFEN_MASK)) { + /* + * Start sync if SPDIFRX is still in idle state. + * SPDIFRX reception enabled when sync done + */ + dev_dbg(&spdifrx->pdev->dev, "start synchronization\n"); + + /* + * SPDIFRX configuration: + * Wait for activity before starting sync process. This avoid + * to issue sync errors when spdif signal is missing on input. + * Preamble, CS, user, validity and parity error bits not copied + * to DR register. + */ + cr = SPDIFRX_CR_WFA | SPDIFRX_CR_PMSK | SPDIFRX_CR_VMSK | + SPDIFRX_CR_CUMSK | SPDIFRX_CR_PTMSK | SPDIFRX_CR_RXSTEO; + cr_mask = cr; + + cr |= SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_SYNC); + cr_mask |= SPDIFRX_CR_SPDIFEN_MASK; + ret = regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + cr_mask, cr); + if (ret < 0) + dev_err(&spdifrx->pdev->dev, + "Failed to start synchronization\n"); + } + + spin_unlock(&spdifrx->lock); + + return ret; +} + +static void stm32_spdifrx_stop(struct stm32_spdifrx_data *spdifrx) +{ + int cr, cr_mask, reg; + + spin_lock(&spdifrx->lock); + + if (--spdifrx->refcount) { + spin_unlock(&spdifrx->lock); + return; + } + + cr = SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_DISABLE); + cr_mask = SPDIFRX_CR_SPDIFEN_MASK | SPDIFRX_CR_RXDMAEN; + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, cr_mask, cr); + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IMR, + SPDIFRX_XIMR_MASK, 0); + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IFCR, + SPDIFRX_XIFCR_MASK, SPDIFRX_XIFCR_MASK); + + /* dummy read to clear CSRNE and RXNE in status register */ + regmap_read(spdifrx->regmap, STM32_SPDIFRX_DR, ®); + regmap_read(spdifrx->regmap, STM32_SPDIFRX_CSR, ®); + + spin_unlock(&spdifrx->lock); +} + +static int stm32_spdifrx_dma_ctrl_register(struct device *dev, + struct stm32_spdifrx_data *spdifrx) +{ + int ret; + + spdifrx->dmab = devm_kzalloc(dev, sizeof(struct snd_dma_buffer), + GFP_KERNEL); + if (!spdifrx->dmab) + return -ENOMEM; + + spdifrx->dmab->dev.type = SNDRV_DMA_TYPE_DEV_IRAM; + spdifrx->dmab->dev.dev = dev; + ret = snd_dma_alloc_pages(spdifrx->dmab->dev.type, dev, + SPDIFRX_CSR_BUF_LENGTH, spdifrx->dmab); + if (ret < 0) { + dev_err(dev, "snd_dma_alloc_pages returned error %d\n", ret); + return ret; + } + + spdifrx->ctrl_chan = dma_request_chan(dev, "rx-ctrl"); + if (!spdifrx->ctrl_chan) { + dev_err(dev, "dma_request_slave_channel failed\n"); + return -EINVAL; + } + + spdifrx->slave_config.direction = DMA_DEV_TO_MEM; + spdifrx->slave_config.src_addr = (dma_addr_t)(spdifrx->phys_addr + + STM32_SPDIFRX_CSR); + spdifrx->slave_config.dst_addr = spdifrx->dmab->addr; + spdifrx->slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdifrx->slave_config.src_maxburst = 1; + + ret = dmaengine_slave_config(spdifrx->ctrl_chan, + &spdifrx->slave_config); + if (ret < 0) { + dev_err(dev, "dmaengine_slave_config returned error %d\n", ret); + dma_release_channel(spdifrx->ctrl_chan); + spdifrx->ctrl_chan = NULL; + } + + return ret; +}; + +static const char * const spdifrx_enum_input[] = { + "in0", "in1", "in2", "in3" +}; + +/* By default CS bits are retrieved from channel A */ +static const char * const spdifrx_enum_cs_channel[] = { + "A", "B" +}; + +static SOC_ENUM_SINGLE_DECL(ctrl_enum_input, + STM32_SPDIFRX_CR, SPDIFRX_CR_INSEL_SHIFT, + spdifrx_enum_input); + +static SOC_ENUM_SINGLE_DECL(ctrl_enum_cs_channel, + STM32_SPDIFRX_CR, SPDIFRX_CR_CHSEL_SHIFT, + spdifrx_enum_cs_channel); + +static int stm32_spdifrx_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +static int stm32_spdifrx_ub_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +static int stm32_spdifrx_get_ctrl_data(struct stm32_spdifrx_data *spdifrx) +{ + int ret = 0; + + memset(spdifrx->cs, 0, SPDIFRX_CS_BYTES_NB); + memset(spdifrx->ub, 0, SPDIFRX_UB_BYTES_NB); + + ret = stm32_spdifrx_dma_ctrl_start(spdifrx); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(spdifrx->kclk); + if (ret) { + dev_err(&spdifrx->pdev->dev, "Enable kclk failed: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_CBDMAEN, SPDIFRX_CR_CBDMAEN); + if (ret < 0) + goto end; + + ret = stm32_spdifrx_start_sync(spdifrx); + if (ret < 0) + goto end; + + if (wait_for_completion_interruptible_timeout(&spdifrx->cs_completion, + msecs_to_jiffies(100)) + <= 0) { + dev_err(&spdifrx->pdev->dev, "Failed to get control data\n"); + ret = -EAGAIN; + } + + stm32_spdifrx_stop(spdifrx); + stm32_spdifrx_dma_ctrl_stop(spdifrx); + +end: + clk_disable_unprepare(spdifrx->kclk); + + return ret; +} + +static int stm32_spdifrx_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + + stm32_spdifrx_get_ctrl_data(spdifrx); + + ucontrol->value.iec958.status[0] = spdifrx->cs[0]; + ucontrol->value.iec958.status[1] = spdifrx->cs[1]; + ucontrol->value.iec958.status[2] = spdifrx->cs[2]; + ucontrol->value.iec958.status[3] = spdifrx->cs[3]; + ucontrol->value.iec958.status[4] = spdifrx->cs[4]; + + return 0; +} + +static int stm32_spdif_user_bits_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + + stm32_spdifrx_get_ctrl_data(spdifrx); + + ucontrol->value.iec958.status[0] = spdifrx->ub[0]; + ucontrol->value.iec958.status[1] = spdifrx->ub[1]; + ucontrol->value.iec958.status[2] = spdifrx->ub[2]; + ucontrol->value.iec958.status[3] = spdifrx->ub[3]; + ucontrol->value.iec958.status[4] = spdifrx->ub[4]; + + return 0; +} + +static struct snd_kcontrol_new stm32_spdifrx_iec_ctrls[] = { + /* Channel status control */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = stm32_spdifrx_info, + .get = stm32_spdifrx_capture_get, + }, + /* User bits control */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 User Bit Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = stm32_spdifrx_ub_info, + .get = stm32_spdif_user_bits_get, + }, +}; + +static struct snd_kcontrol_new stm32_spdifrx_ctrls[] = { + SOC_ENUM("SPDIFRX input", ctrl_enum_input), + SOC_ENUM("SPDIFRX CS channel", ctrl_enum_cs_channel), +}; + +static int stm32_spdifrx_dai_register_ctrls(struct snd_soc_dai *cpu_dai) +{ + int ret; + + ret = snd_soc_add_dai_controls(cpu_dai, stm32_spdifrx_iec_ctrls, + ARRAY_SIZE(stm32_spdifrx_iec_ctrls)); + if (ret < 0) + return ret; + + return snd_soc_add_component_controls(cpu_dai->component, + stm32_spdifrx_ctrls, + ARRAY_SIZE(stm32_spdifrx_ctrls)); +} + +static int stm32_spdifrx_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = dev_get_drvdata(cpu_dai->dev); + + spdifrx->dma_params.addr = (dma_addr_t)(spdifrx->phys_addr + + STM32_SPDIFRX_DR); + spdifrx->dma_params.maxburst = 1; + + snd_soc_dai_init_dma_data(cpu_dai, NULL, &spdifrx->dma_params); + + return stm32_spdifrx_dai_register_ctrls(cpu_dai); +} + +static bool stm32_spdifrx_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_SPDIFRX_CR: + case STM32_SPDIFRX_IMR: + case STM32_SPDIFRX_SR: + case STM32_SPDIFRX_IFCR: + case STM32_SPDIFRX_DR: + case STM32_SPDIFRX_CSR: + case STM32_SPDIFRX_DIR: + return true; + default: + return false; + } +} + +static bool stm32_spdifrx_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg == STM32_SPDIFRX_DR) + return true; + + return false; +} + +static bool stm32_spdifrx_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case STM32_SPDIFRX_CR: + case STM32_SPDIFRX_IMR: + case STM32_SPDIFRX_IFCR: + return true; + default: + return false; + } +} + +static const struct regmap_config stm32_h7_spdifrx_regmap_conf = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = STM32_SPDIFRX_DIR, + .readable_reg = stm32_spdifrx_readable_reg, + .volatile_reg = stm32_spdifrx_volatile_reg, + .writeable_reg = stm32_spdifrx_writeable_reg, + .fast_io = true, +}; + +static irqreturn_t stm32_spdifrx_isr(int irq, void *devid) +{ + struct stm32_spdifrx_data *spdifrx = (struct stm32_spdifrx_data *)devid; + struct snd_pcm_substream *substream = spdifrx->substream; + struct platform_device *pdev = spdifrx->pdev; + unsigned int cr, mask, sr, imr; + unsigned int flags; + int err = 0, err_xrun = 0; + + regmap_read(spdifrx->regmap, STM32_SPDIFRX_SR, &sr); + regmap_read(spdifrx->regmap, STM32_SPDIFRX_IMR, &imr); + + mask = imr & SPDIFRX_XIMR_MASK; + /* SERR, TERR, FERR IRQs are generated if IFEIE is set */ + if (mask & SPDIFRX_IMR_IFEIE) + mask |= (SPDIFRX_IMR_IFEIE << 1) | (SPDIFRX_IMR_IFEIE << 2); + + flags = sr & mask; + if (!flags) { + dev_err(&pdev->dev, "Unexpected IRQ. rflags=%#x, imr=%#x\n", + sr, imr); + return IRQ_NONE; + } + + /* Clear IRQs */ + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IFCR, + SPDIFRX_XIFCR_MASK, flags); + + if (flags & SPDIFRX_SR_PERR) { + dev_dbg(&pdev->dev, "Parity error\n"); + err_xrun = 1; + } + + if (flags & SPDIFRX_SR_OVR) { + dev_dbg(&pdev->dev, "Overrun error\n"); + err_xrun = 1; + } + + if (flags & SPDIFRX_SR_SBD) + dev_dbg(&pdev->dev, "Synchronization block detected\n"); + + if (flags & SPDIFRX_SR_SYNCD) { + dev_dbg(&pdev->dev, "Synchronization done\n"); + + /* Enable spdifrx */ + cr = SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_ENABLE); + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_SPDIFEN_MASK, cr); + } + + if (flags & SPDIFRX_SR_FERR) { + dev_dbg(&pdev->dev, "Frame error\n"); + err = 1; + } + + if (flags & SPDIFRX_SR_SERR) { + dev_dbg(&pdev->dev, "Synchronization error\n"); + err = 1; + } + + if (flags & SPDIFRX_SR_TERR) { + dev_dbg(&pdev->dev, "Timeout error\n"); + err = 1; + } + + if (err) { + /* SPDIFRX in STATE_STOP. Disable SPDIFRX to clear errors */ + cr = SPDIFRX_CR_SPDIFENSET(SPDIFRX_SPDIFEN_DISABLE); + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_SPDIFEN_MASK, cr); + + if (substream) + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + + return IRQ_HANDLED; + } + + if (err_xrun && substream) + snd_pcm_stop_xrun(substream); + + return IRQ_HANDLED; +} + +static int stm32_spdifrx_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + int ret; + + spdifrx->substream = substream; + + ret = clk_prepare_enable(spdifrx->kclk); + if (ret) + dev_err(&spdifrx->pdev->dev, "Enable kclk failed: %d\n", ret); + + return ret; +} + +static int stm32_spdifrx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + int data_size = params_width(params); + int fmt; + + switch (data_size) { + case 16: + fmt = SPDIFRX_DRFMT_PACKED; + spdifrx->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 32: + fmt = SPDIFRX_DRFMT_LEFT; + spdifrx->dma_params.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + dev_err(&spdifrx->pdev->dev, "Unexpected data format\n"); + return -EINVAL; + } + + snd_soc_dai_init_dma_data(cpu_dai, NULL, &spdifrx->dma_params); + + return regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_DRFMT_MASK, + SPDIFRX_CR_DRFMTSET(fmt)); +} + +static int stm32_spdifrx_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_IMR, + SPDIFRX_IMR_OVRIE, SPDIFRX_IMR_OVRIE); + + regmap_update_bits(spdifrx->regmap, STM32_SPDIFRX_CR, + SPDIFRX_CR_RXDMAEN, SPDIFRX_CR_RXDMAEN); + + ret = stm32_spdifrx_start_sync(spdifrx); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + stm32_spdifrx_stop(spdifrx); + break; + default: + return -EINVAL; + } + + return ret; +} + +static void stm32_spdifrx_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct stm32_spdifrx_data *spdifrx = snd_soc_dai_get_drvdata(cpu_dai); + + spdifrx->substream = NULL; + clk_disable_unprepare(spdifrx->kclk); +} + +static const struct snd_soc_dai_ops stm32_spdifrx_pcm_dai_ops = { + .startup = stm32_spdifrx_startup, + .hw_params = stm32_spdifrx_hw_params, + .trigger = stm32_spdifrx_trigger, + .shutdown = stm32_spdifrx_shutdown, +}; + +static struct snd_soc_dai_driver stm32_spdifrx_dai[] = { + { + .name = "spdifrx-capture-cpu-dai", + .probe = stm32_spdifrx_dai_probe, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &stm32_spdifrx_pcm_dai_ops, + } +}; + +static const struct snd_pcm_hardware stm32_spdifrx_pcm_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP, + .buffer_bytes_max = 8 * PAGE_SIZE, + .period_bytes_max = 2048, /* MDMA constraint */ + .periods_min = 2, + .periods_max = 8, +}; + +static const struct snd_soc_component_driver stm32_spdifrx_component = { + .name = "stm32-spdifrx", +}; + +static const struct snd_dmaengine_pcm_config stm32_spdifrx_pcm_config = { + .pcm_hardware = &stm32_spdifrx_pcm_hw, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +}; + +static const struct of_device_id stm32_spdifrx_ids[] = { + { + .compatible = "st,stm32h7-spdifrx", + .data = &stm32_h7_spdifrx_regmap_conf + }, + {} +}; + +static int stm_spdifrx_parse_of(struct platform_device *pdev, + struct stm32_spdifrx_data *spdifrx) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *of_id; + struct resource *res; + + if (!np) + return -ENODEV; + + of_id = of_match_device(stm32_spdifrx_ids, &pdev->dev); + if (of_id) + spdifrx->regmap_conf = + (const struct regmap_config *)of_id->data; + else + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spdifrx->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(spdifrx->base)) + return PTR_ERR(spdifrx->base); + + spdifrx->phys_addr = res->start; + + spdifrx->kclk = devm_clk_get(&pdev->dev, "kclk"); + if (IS_ERR(spdifrx->kclk)) { + dev_err(&pdev->dev, "Could not get kclk\n"); + return PTR_ERR(spdifrx->kclk); + } + + spdifrx->irq = platform_get_irq(pdev, 0); + if (spdifrx->irq < 0) { + dev_err(&pdev->dev, "No irq for node %s\n", pdev->name); + return spdifrx->irq; + } + + return 0; +} + +static int stm32_spdifrx_probe(struct platform_device *pdev) +{ + struct stm32_spdifrx_data *spdifrx; + struct reset_control *rst; + const struct snd_dmaengine_pcm_config *pcm_config = NULL; + int ret; + + spdifrx = devm_kzalloc(&pdev->dev, sizeof(*spdifrx), GFP_KERNEL); + if (!spdifrx) + return -ENOMEM; + + spdifrx->pdev = pdev; + init_completion(&spdifrx->cs_completion); + spin_lock_init(&spdifrx->lock); + + platform_set_drvdata(pdev, spdifrx); + + ret = stm_spdifrx_parse_of(pdev, spdifrx); + if (ret) + return ret; + + spdifrx->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "kclk", + spdifrx->base, + spdifrx->regmap_conf); + if (IS_ERR(spdifrx->regmap)) { + dev_err(&pdev->dev, "Regmap init failed\n"); + return PTR_ERR(spdifrx->regmap); + } + + ret = devm_request_irq(&pdev->dev, spdifrx->irq, stm32_spdifrx_isr, 0, + dev_name(&pdev->dev), spdifrx); + if (ret) { + dev_err(&pdev->dev, "IRQ request returned %d\n", ret); + return ret; + } + + rst = devm_reset_control_get(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &stm32_spdifrx_component, + stm32_spdifrx_dai, + ARRAY_SIZE(stm32_spdifrx_dai)); + if (ret) + return ret; + + ret = stm32_spdifrx_dma_ctrl_register(&pdev->dev, spdifrx); + if (ret) + goto error; + + pcm_config = &stm32_spdifrx_pcm_config; + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, pcm_config, 0); + if (ret) { + dev_err(&pdev->dev, "PCM DMA register returned %d\n", ret); + goto error; + } + + return 0; + +error: + if (spdifrx->ctrl_chan) + dma_release_channel(spdifrx->ctrl_chan); + if (spdifrx->dmab) + snd_dma_free_pages(spdifrx->dmab); + + return ret; +} + +static int stm32_spdifrx_remove(struct platform_device *pdev) +{ + struct stm32_spdifrx_data *spdifrx = platform_get_drvdata(pdev); + + if (spdifrx->ctrl_chan) + dma_release_channel(spdifrx->ctrl_chan); + + if (spdifrx->dmab) + snd_dma_free_pages(spdifrx->dmab); + + return 0; +} + +MODULE_DEVICE_TABLE(of, stm32_spdifrx_ids); + +static struct platform_driver stm32_spdifrx_driver = { + .driver = { + .name = "st,stm32-spdifrx", + .of_match_table = stm32_spdifrx_ids, + }, + .probe = stm32_spdifrx_probe, + .remove = stm32_spdifrx_remove, +}; + +module_platform_driver(stm32_spdifrx_driver); + +MODULE_DESCRIPTION("STM32 Soc spdifrx Interface"); +MODULE_AUTHOR("Olivier Moysan, <olivier.moysan@st.com>"); +MODULE_ALIAS("platform:stm32-spdifrx"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c index c3aab10fa085..150069987c0c 100644 --- a/sound/soc/sunxi/sun4i-codec.c +++ b/sound/soc/sunxi/sun4i-codec.c @@ -1339,6 +1339,44 @@ static struct snd_soc_card *sun8i_h3_codec_create_card(struct device *dev) return card; }; +static struct snd_soc_card *sun8i_v3s_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + aux_dev.codec_of_node = of_parse_phandle(dev->of_node, + "allwinner,codec-analog-controls", + 0); + if (!aux_dev.codec_of_node) { + dev_err(dev, "Can't find analog controls for codec.\n"); + return ERR_PTR(-EINVAL); + }; + + card->dai_link = sun4i_codec_create_link(dev, &card->num_links); + if (!card->dai_link) + return ERR_PTR(-ENOMEM); + + card->dev = dev; + card->name = "V3s Audio Codec"; + card->dapm_widgets = sun6i_codec_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); + card->dapm_routes = sun8i_codec_card_routes; + card->num_dapm_routes = ARRAY_SIZE(sun8i_codec_card_routes); + card->aux_dev = &aux_dev; + card->num_aux_devs = 1; + card->fully_routed = true; + + ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing"); + if (ret) + dev_warn(dev, "failed to parse audio-routing: %d\n", ret); + + return card; +}; + static const struct regmap_config sun4i_codec_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -1374,6 +1412,13 @@ static const struct regmap_config sun8i_h3_codec_regmap_config = { .max_register = SUN8I_H3_CODEC_ADC_DBG, }; +static const struct regmap_config sun8i_v3s_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN8I_H3_CODEC_ADC_DBG, +}; + struct sun4i_codec_quirks { const struct regmap_config *regmap_config; const struct snd_soc_codec_driver *codec; @@ -1437,6 +1482,20 @@ static const struct sun4i_codec_quirks sun8i_h3_codec_quirks = { .has_reset = true, }; +static const struct sun4i_codec_quirks sun8i_v3s_codec_quirks = { + .regmap_config = &sun8i_v3s_codec_regmap_config, + /* + * TODO The codec structure should be split out, like + * H3, when adding digital audio processing support. + */ + .codec = &sun8i_a23_codec_codec, + .create_card = sun8i_v3s_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, + .has_reset = true, +}; + static const struct of_device_id sun4i_codec_of_match[] = { { .compatible = "allwinner,sun4i-a10-codec", @@ -1458,6 +1517,10 @@ static const struct of_device_id sun4i_codec_of_match[] = { .compatible = "allwinner,sun8i-h3-codec", .data = &sun8i_h3_codec_quirks, }, + { + .compatible = "allwinner,sun8i-v3s-codec", + .data = &sun8i_v3s_codec_quirks, + }, {} }; MODULE_DEVICE_TABLE(of, sun4i_codec_of_match); diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c index 6c17c99c2c8d..485e79f292c4 100644 --- a/sound/soc/sunxi/sun8i-codec-analog.c +++ b/sound/soc/sunxi/sun8i-codec-analog.c @@ -219,6 +219,22 @@ static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = { SUN8I_ADDA_LOMIXSC_MIC2, 1, 0), }; +/* mixer controls */ +static const struct snd_kcontrol_new sun8i_v3s_codec_mixer_controls[] = { + SOC_DAPM_DOUBLE_R("DAC Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_DACL, 1, 0), + SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_DACR, 1, 0), + SOC_DAPM_DOUBLE_R("Mic1 Playback Switch", + SUN8I_ADDA_LOMIXSC, + SUN8I_ADDA_ROMIXSC, + SUN8I_ADDA_LOMIXSC_MIC1, 1, 0), +}; + /* ADC mixer controls */ static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = { SOC_DAPM_DOUBLE_R("Mixer Capture Switch", @@ -243,6 +259,22 @@ static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = { SUN8I_ADDA_LADCMIXSC_MIC2, 1, 0), }; +/* ADC mixer controls */ +static const struct snd_kcontrol_new sun8i_v3s_codec_adc_mixer_controls[] = { + SOC_DAPM_DOUBLE_R("Mixer Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0), + SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0), + SOC_DAPM_DOUBLE_R("Mic1 Capture Switch", + SUN8I_ADDA_LADCMIXSC, + SUN8I_ADDA_RADCMIXSC, + SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0), +}; + /* volume / mute controls */ static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale, -450, 150, 0); @@ -289,16 +321,12 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { /* Microphone input */ SND_SOC_DAPM_INPUT("MIC1"), - /* Microphone Bias */ - SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, - SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN, - 0, NULL, 0), - /* Mic input path */ SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0), +}; - /* Mixers */ +static const struct snd_soc_dapm_widget sun8i_codec_mixer_widgets[] = { SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, sun8i_codec_mixer_controls, @@ -317,10 +345,31 @@ static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), }; +static const struct snd_soc_dapm_widget sun8i_v3s_codec_mixer_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, + sun8i_v3s_codec_mixer_controls, + ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC, + SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, + sun8i_v3s_codec_mixer_controls, + ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), + SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN, + SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, + sun8i_v3s_codec_adc_mixer_controls, + ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), + SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN, + SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, + sun8i_v3s_codec_adc_mixer_controls, + ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), +}; + static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = { /* Microphone Routes */ { "Mic1 Amplifier", NULL, "MIC1"}, +}; +static const struct snd_soc_dapm_route sun8i_codec_mixer_routes[] = { /* Left Mixer Routes */ { "Left Mixer", "DAC Playback Switch", "Left DAC" }, { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" }, @@ -453,6 +502,27 @@ static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt) return 0; } +/* mbias specific widget */ +static const struct snd_soc_dapm_widget sun8i_codec_mbias_widgets[] = { + SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, + SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN, + 0, NULL, 0), +}; + +static int sun8i_codec_add_mbias(struct snd_soc_component *cmpnt) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mbias_widgets, + ARRAY_SIZE(sun8i_codec_mbias_widgets)); + if (ret) + dev_err(dev, "Failed to add MBIAS DAPM widgets: %d\n", ret); + + return ret; +} + /* hmic specific widget */ static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = { SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, @@ -679,6 +749,7 @@ struct sun8i_codec_analog_quirks { bool has_hmic; bool has_linein; bool has_lineout; + bool has_mbias; bool has_mic2; }; @@ -686,15 +757,64 @@ static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = { .has_headphone = true, .has_hmic = true, .has_linein = true, + .has_mbias = true, .has_mic2 = true, }; static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = { .has_linein = true, .has_lineout = true, + .has_mbias = true, .has_mic2 = true, }; +static int sun8i_codec_analog_add_mixer(struct snd_soc_component *cmpnt, + const struct sun8i_codec_analog_quirks *quirks) +{ + struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); + struct device *dev = cmpnt->dev; + int ret; + + if (!quirks->has_mic2 && !quirks->has_linein) { + /* + * Apply the special widget set which has uses a control + * without MIC2 and Line In, for SoCs without these. + * TODO: not all special cases are supported now, this case + * is present because it's the case of V3s. + */ + ret = snd_soc_dapm_new_controls(dapm, + sun8i_v3s_codec_mixer_widgets, + ARRAY_SIZE(sun8i_v3s_codec_mixer_widgets)); + if (ret) { + dev_err(dev, "Failed to add V3s Mixer DAPM widgets: %d\n", ret); + return ret; + } + } else { + /* Apply the generic mixer widget set. */ + ret = snd_soc_dapm_new_controls(dapm, + sun8i_codec_mixer_widgets, + ARRAY_SIZE(sun8i_codec_mixer_widgets)); + if (ret) { + dev_err(dev, "Failed to add Mixer DAPM widgets: %d\n", ret); + return ret; + } + } + + ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mixer_routes, + ARRAY_SIZE(sun8i_codec_mixer_routes)); + if (ret) { + dev_err(dev, "Failed to add Mixer DAPM routes: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct sun8i_codec_analog_quirks sun8i_v3s_quirks = { + .has_headphone = true, + .has_hmic = true, +}; + static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) { struct device *dev = cmpnt->dev; @@ -709,6 +829,9 @@ static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) quirks = of_device_get_match_data(dev); /* Add controls, widgets, and routes for individual features */ + ret = sun8i_codec_analog_add_mixer(cmpnt, quirks); + if (ret) + return ret; if (quirks->has_headphone) { ret = sun8i_codec_add_headphone(cmpnt); @@ -734,6 +857,12 @@ static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) return ret; } + if (quirks->has_mbias) { + ret = sun8i_codec_add_mbias(cmpnt); + if (ret) + return ret; + } + if (quirks->has_mic2) { ret = sun8i_codec_add_mic2(cmpnt); if (ret) @@ -762,6 +891,10 @@ static const struct of_device_id sun8i_codec_analog_of_match[] = { .compatible = "allwinner,sun8i-h3-codec-analog", .data = &sun8i_h3_quirks, }, + { + .compatible = "allwinner,sun8i-v3s-codec-analog", + .data = &sun8i_v3s_quirks, + }, {} }; MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match); |