On 03.08.2020 11:18, Codrin Ciubotariu wrote: > The new SPDIF TX controller is a serial port compliant with the IEC- > 60958 standard. It also supports programmable User Data and Channel > Status fields. > > This IP is embedded in Microchip's sama7g5 SoC. > > Signed-off-by: Codrin Ciubotariu <codrin.ciubotariu@xxxxxxxxxxxxx> > --- > > Changes in v2, v3: > - none; > > sound/soc/atmel/Kconfig | 12 + > sound/soc/atmel/Makefile | 2 + > sound/soc/atmel/mchp-spdiftx.c | 864 +++++++++++++++++++++++++++++++++ > 3 files changed, 878 insertions(+) > create mode 100644 sound/soc/atmel/mchp-spdiftx.c > > diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig > index 71f2d42188c4..93beb7d670a3 100644 > --- a/sound/soc/atmel/Kconfig > +++ b/sound/soc/atmel/Kconfig > @@ -132,4 +132,16 @@ config SND_MCHP_SOC_I2S_MCC > and supports a Time Division Multiplexed (TDM) interface with > external multi-channel audio codecs. > > +config SND_MCHP_SOC_SPDIFTX > + tristate "Microchip ASoC driver for boards using S/PDIF TX" > + depends on OF && (ARCH_AT91 || COMPILE_TEST) > + select SND_SOC_GENERIC_DMAENGINE_PCM > + select REGMAP_MMIO > + help > + Say Y or M if you want to add support for Microchip S/PDIF TX ASoc > + driver on the following Microchip platforms: > + - sama7g5 > + > + This S/PDIF TX driver is compliant with IEC-60958 standard and > + includes programable User Data and Channel Status fields. > endif > diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile > index c7d2989791be..3fd89a0063df 100644 > --- a/sound/soc/atmel/Makefile > +++ b/sound/soc/atmel/Makefile > @@ -5,6 +5,7 @@ snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o > snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o > snd-soc-atmel-i2s-objs := atmel-i2s.o > snd-soc-mchp-i2s-mcc-objs := mchp-i2s-mcc.o > +snd-soc-mchp-spdiftx-objs := mchp-spdiftx.o > > # pdc and dma need to both be built-in if any user of > # ssc is built-in. > @@ -17,6 +18,7 @@ endif > obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o > obj-$(CONFIG_SND_ATMEL_SOC_I2S) += snd-soc-atmel-i2s.o > obj-$(CONFIG_SND_MCHP_SOC_I2S_MCC) += snd-soc-mchp-i2s-mcc.o > +obj-$(CONFIG_SND_MCHP_SOC_SPDIFTX) += snd-soc-mchp-spdiftx.o > > # AT91 Machine Support > snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o > diff --git a/sound/soc/atmel/mchp-spdiftx.c b/sound/soc/atmel/mchp-spdiftx.c > new file mode 100644 > index 000000000000..738f6788212e > --- /dev/null > +++ b/sound/soc/atmel/mchp-spdiftx.c > @@ -0,0 +1,864 @@ > +// SPDX-License-Identifier: GPL-2.0 > +// > +// Driver for Microchip S/PDIF TX Controller > +// > +// Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries > +// > +// Author: Codrin Ciubotariu <codrin.ciubotariu@xxxxxxxxxxxxx> > + > +#include <linux/clk.h> > +#include <linux/io.h> > +#include <linux/module.h> > +#include <linux/spinlock.h> > + > +#include <sound/asoundef.h> > +#include <sound/dmaengine_pcm.h> > +#include <sound/pcm_params.h> > +#include <sound/soc.h> > + > +/* > + * ---- S/PDIF Transmitter Controller Register map ---- > + */ > +#define SPDIFTX_CR 0x00 /* Control Register */ > +#define SPDIFTX_MR 0x04 /* Mode Register */ This register is read/write either in atomic and non-atomic contextes but not protected everywhere the same way. > +#define SPDIFTX_CDR 0x0C /* Common Data Register */ > + > +#define SPDIFTX_IER 0x14 /* Interrupt Enable Register */ > +#define SPDIFTX_IDR 0x18 /* Interrupt Disable Register */ > +#define SPDIFTX_IMR 0x1C /* Interrupt Mask Register */ > +#define SPDIFTX_ISR 0x20 /* Interrupt Status Register */ > + > +#define SPDIFTX_CH1UD(reg) (0x50 + (reg) * 4) /* User Data 1 Register x */ > +#define SPDIFTX_CH1S(reg) (0x80 + (reg) * 4) /* Channel Status 1 Register x */ > + > +#define SPDIFTX_VERSION 0xF0 > + > +/* > + * ---- Control Register (Write-only) ---- > + */ > +#define SPDIFTX_CR_SWRST BIT(0) /* Software Reset */ > +#define SPDIFTX_CR_FCLR BIT(1) /* FIFO clear */ > + > +/* > + * ---- Mode Register (Read/Write) ---- > + */ > +/* Transmit Enable */ > +#define SPDIFTX_MR_TXEN_MASK GENMASK(0, 0) > +#define SPDIFTX_MR_TXEN_DISABLE (0 << 0) > +#define SPDIFTX_MR_TXEN_ENABLE (1 << 0) > + > +/* Multichannel Transfer */ > +#define SPDIFTX_MR_MULTICH_MASK GENAMSK(1, 1) > +#define SPDIFTX_MR_MULTICH_MONO (0 << 1) > +#define SPDIFTX_MR_MULTICH_DUAL (1 << 1) > + > +/* Data Word Endian Mode */ > +#define SPDIFTX_MR_ENDIAN_MASK GENMASK(2, 2) > +#define SPDIFTX_MR_ENDIAN_LITTLE (0 << 2) > +#define SPDIFTX_MR_ENDIAN_BIG (1 << 2) > + > +/* Data Justification */ > +#define SPDIFTX_MR_JUSTIFY_MASK GENMASK(3, 3) > +#define SPDIFTX_MR_JUSTIFY_LSB (0 << 3) > +#define SPDIFTX_MR_JUSTIFY_MSB (1 << 3) > + > +/* Common Audio Register Transfer Mode */ > +#define SPDIFTX_MR_CMODE_MASK GENMASK(5, 4) > +#define SPDIFTX_MR_CMODE_INDEX_ACCESS (0 << 4) > +#define SPDIFTX_MR_CMODE_TOGGLE_ACCESS (1 << 4) > +#define SPDIFTX_MR_CMODE_INTERLVD_ACCESS (2 << 4) > + > +/* Valid Bits per Sample */ > +#define SPDIFTX_MR_VBPS_MASK GENMASK(13, 8) > +#define SPDIFTX_MR_VBPS(bps) (((bps) << 8) & SPDIFTX_MR_VBPS_MASK) > + > +/* Chunk Size */ > +#define SPDIFTX_MR_CHUNK_MASK GENMASK(19, 16) > +#define SPDIFTX_MR_CHUNK(size) (((size) << 16) & SPDIFTX_MR_CHUNK_MASK) > + > +/* Validity Bits for Channels 1 and 2 */ > +#define SPDIFTX_MR_VALID1 BIT(24) > +#define SPDIFTX_MR_VALID2 BIT(25) > + > +/* Disable Null Frame on underrrun */ > +#define SPDIFTX_MR_DNFR_MASK GENMASK(27, 27) > +#define SPDIFTX_MR_DNFR_INVALID (0 << 27) > +#define SPDIFTX_MR_DNFR_VALID (1 << 27) > + > +/* Bytes per Sample */ > +#define SPDIFTX_MR_BPS_MASK GENMASK(29, 28) > +#define SPDIFTX_MR_BPS(bytes) \ > + ((((bytes) - 1) << 28) & SPDIFTX_MR_BPS_MASK) > + > +/* > + * ---- Interrupt Enable/Disable/Mask/Status Register (Write/Read-only) ---- > + */ > +#define SPDIFTX_IR_TXRDY BIT(0) > +#define SPDIFTX_IR_TXEMPTY BIT(1) > +#define SPDIFTX_IR_TXFULL BIT(2) > +#define SPDIFTX_IR_TXCHUNK BIT(3) > +#define SPDIFTX_IR_TXUDR BIT(4) > +#define SPDIFTX_IR_TXOVR BIT(5) > +#define SPDIFTX_IR_CSRDY BIT(6) > +#define SPDIFTX_IR_UDRDY BIT(7) > +#define SPDIFTX_IR_TXRDYCH(ch) BIT((ch) + 8) > +#define SPDIFTX_IR_SECE BIT(10) > +#define SPDIFTX_IR_TXUDRCH(ch) BIT((ch) + 11) > +#define SPDIFTX_IR_BEND BIT(13) > + > +static bool mchp_spdiftx_readable_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case SPDIFTX_MR: > + case SPDIFTX_IMR: > + case SPDIFTX_ISR: > + case SPDIFTX_CH1UD(0): > + case SPDIFTX_CH1UD(1): > + case SPDIFTX_CH1UD(2): > + case SPDIFTX_CH1UD(3): > + case SPDIFTX_CH1UD(4): > + case SPDIFTX_CH1UD(5): > + case SPDIFTX_CH1S(0): > + case SPDIFTX_CH1S(1): > + case SPDIFTX_CH1S(2): > + case SPDIFTX_CH1S(3): > + case SPDIFTX_CH1S(4): > + case SPDIFTX_CH1S(5): > + return true; > + default: > + return false; > + } > +} > + > +static bool mchp_spdiftx_writeable_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case SPDIFTX_CR: > + case SPDIFTX_MR: > + case SPDIFTX_CDR: > + case SPDIFTX_IER: > + case SPDIFTX_IDR: > + case SPDIFTX_CH1UD(0): > + case SPDIFTX_CH1UD(1): > + case SPDIFTX_CH1UD(2): > + case SPDIFTX_CH1UD(3): > + case SPDIFTX_CH1UD(4): > + case SPDIFTX_CH1UD(5): > + case SPDIFTX_CH1S(0): > + case SPDIFTX_CH1S(1): > + case SPDIFTX_CH1S(2): > + case SPDIFTX_CH1S(3): > + case SPDIFTX_CH1S(4): > + case SPDIFTX_CH1S(5): > + return true; > + default: > + return false; > + } > +} > + > +static bool mchp_spdiftx_precious_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case SPDIFTX_CDR: > + case SPDIFTX_ISR: > + return true; > + default: > + return false; > + } > +} > + > +static const struct regmap_config mchp_spdiftx_regmap_config = { > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > + .max_register = SPDIFTX_VERSION, > + .readable_reg = mchp_spdiftx_readable_reg, > + .writeable_reg = mchp_spdiftx_writeable_reg, > + .precious_reg = mchp_spdiftx_precious_reg, > +}; > + > +#define SPDIFTX_GCLK_RATIO 128 > + > +#define SPDIFTX_CS_BITS 192 > +#define SPDIFTX_UD_BITS 192 > + > +struct mchp_spdiftx_mixer_control { > + unsigned char ch_stat[SPDIFTX_CS_BITS / 8]; > + unsigned char user_data[SPDIFTX_UD_BITS / 8]; > + spinlock_t lock; > +}; > + > +struct mchp_spdiftx_dev { > + struct mchp_spdiftx_mixer_control control; > + struct snd_dmaengine_dai_dma_data playback; > + struct device *dev; > + struct regmap *regmap; > + struct clk *pclk; > + struct clk *gclk; > + unsigned int fmt; > + const struct mchp_i2s_caps *caps; > + int gclk_enabled:1; > +}; > + > +static inline int mchp_spdiftx_is_running(struct mchp_spdiftx_dev *dev) > +{ > + u32 mr; > + > + regmap_read(dev->regmap, SPDIFTX_MR, &mr); > + return !!(mr & SPDIFTX_MR_TXEN_ENABLE); > +} > + > +static void mchp_spdiftx_channel_status_write(struct mchp_spdiftx_dev *dev) > +{ > + struct mchp_spdiftx_mixer_control *ctrl = &dev->control; > + u32 val; > + int i; > + > + for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat) / 4; i++) { > + val = (ctrl->ch_stat[(i * 4) + 0] << 0) | > + (ctrl->ch_stat[(i * 4) + 1] << 8) | > + (ctrl->ch_stat[(i * 4) + 2] << 16) | > + (ctrl->ch_stat[(i * 4) + 3] << 24); > + > + regmap_write(dev->regmap, SPDIFTX_CH1S(i), val); > + } > +} > + > +static void mchp_spdiftx_user_data_write(struct mchp_spdiftx_dev *dev) > +{ > + struct mchp_spdiftx_mixer_control *ctrl = &dev->control; > + u32 val; > + int i; > + > + for (i = 0; i < ARRAY_SIZE(ctrl->user_data) / 4; i++) { > + val = (ctrl->user_data[(i * 4) + 0] << 0) | > + (ctrl->user_data[(i * 4) + 1] << 8) | > + (ctrl->user_data[(i * 4) + 2] << 16) | > + (ctrl->user_data[(i * 4) + 3] << 24); > + > + regmap_write(dev->regmap, SPDIFTX_CH1UD(i), val); > + } > +} > + > +static irqreturn_t mchp_spdiftx_interrupt(int irq, void *dev_id) > +{ > + struct mchp_spdiftx_dev *dev = dev_id; > + struct mchp_spdiftx_mixer_control *ctrl = &dev->control; > + u32 sr, imr, pending, idr = 0; > + > + regmap_read(dev->regmap, SPDIFTX_ISR, &sr); > + regmap_read(dev->regmap, SPDIFTX_IMR, &imr); > + pending = sr & imr; > + > + if (!pending) > + return IRQ_NONE; > + > + if (pending & SPDIFTX_IR_TXUDR) { > + dev_warn(dev->dev, "underflow detected\n"); > + idr |= SPDIFTX_IR_TXUDR; > + } > + > + if (pending & SPDIFTX_IR_TXOVR) { > + dev_warn(dev->dev, "overflow detected\n"); > + idr |= SPDIFTX_IR_TXOVR; > + } > + > + if (pending & SPDIFTX_IR_UDRDY) { > + spin_lock(&ctrl->lock); > + mchp_spdiftx_user_data_write(dev); > + spin_unlock(&ctrl->lock); > + idr |= SPDIFTX_IR_UDRDY; > + } > + > + if (pending & SPDIFTX_IR_CSRDY) { > + spin_lock(&ctrl->lock); > + mchp_spdiftx_channel_status_write(dev); > + spin_unlock(&ctrl->lock); > + idr |= SPDIFTX_IR_CSRDY; > + } > + > + regmap_write(dev->regmap, SPDIFTX_IDR, idr); > + > + return IRQ_HANDLED; > +} > + > +static int mchp_spdiftx_dai_startup(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + int err; > + > + err = clk_prepare_enable(dev->pclk); > + if (err) { > + dev_err(dev->dev, > + "failed to enable the peripheral clock: %d\n", err); > + return err; > + } > + > + /* Software reset the IP */ > + regmap_write(dev->regmap, SPDIFTX_CR, > + SPDIFTX_CR_SWRST | SPDIFTX_CR_FCLR); > + > + return 0; > +} > + > +static void mchp_spdiftx_dai_shutdown(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + > + /* Disable interrupts */ > + regmap_write(dev->regmap, SPDIFTX_IDR, 0xffffffff); > + > + clk_disable_unprepare(dev->pclk); > +} > + > +static int mchp_spdiftx_trigger(struct snd_pcm_substream *substream, int cmd, > + struct snd_soc_dai *dai) > +{ > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + struct mchp_spdiftx_mixer_control *ctrl = &dev->control; > + u32 mr; > + int running; > + int ret; > + > + /* do not start/stop while channel status or user data is updated */ > + spin_lock(&ctrl->lock); > + regmap_read(dev->regmap, SPDIFTX_MR, &mr); Here, atomic, for instance. > + running = !!(mr & SPDIFTX_MR_TXEN_ENABLE); > + > + switch (cmd) { > + case SNDRV_PCM_TRIGGER_START: > + case SNDRV_PCM_TRIGGER_RESUME: > + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: > + if (!running) { > + mr &= ~SPDIFTX_MR_TXEN_MASK; > + mr |= SPDIFTX_MR_TXEN_ENABLE; > + } > + break; > + case SNDRV_PCM_TRIGGER_STOP: > + case SNDRV_PCM_TRIGGER_SUSPEND: > + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: > + if (running) { > + mr &= ~SPDIFTX_MR_TXEN_MASK; > + mr |= SPDIFTX_MR_TXEN_DISABLE; > + } > + break; > + default: > + spin_unlock(&ctrl->lock); > + return -EINVAL; > + } > + > + ret = regmap_write(dev->regmap, SPDIFTX_MR, mr); > + spin_unlock(&ctrl->lock); > + if (ret) { > + dev_err(dev->dev, "unable to disable TX: %d\n", ret); > + return ret; > + } > + > + return 0; > +} > + > +static int mchp_spdiftx_hw_params(struct snd_pcm_substream *substream, > + struct snd_pcm_hw_params *params, > + struct snd_soc_dai *dai) > +{ > + unsigned long flags; > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + struct mchp_spdiftx_mixer_control *ctrl = &dev->control; > + u32 mr; > + unsigned int bps = params_physical_width(params) / 8; > + int ret; > + > + dev_dbg(dev->dev, "%s() rate=%u format=%#x width=%u channels=%u\n", > + __func__, params_rate(params), params_format(params), > + params_width(params), params_channels(params)); > + > + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { > + dev_err(dev->dev, "Capture is not supported\n"); > + return -EINVAL; > + } > + > + regmap_read(dev->regmap, SPDIFTX_MR, &mr); Here non-atomic. > + > + if (mr & SPDIFTX_MR_TXEN_ENABLE) { > + dev_err(dev->dev, "PCM already running\n"); > + return -EBUSY; > + } > + > + /* Defaults: Toggle mode, justify to LSB, chunksize 1 */ > + mr = SPDIFTX_MR_CMODE_TOGGLE_ACCESS | SPDIFTX_MR_JUSTIFY_LSB; > + dev->playback.maxburst = 1; > + switch (params_channels(params)) { > + case 1: > + mr |= SPDIFTX_MR_MULTICH_MONO; > + break; > + case 2: > + mr |= SPDIFTX_MR_MULTICH_DUAL; > + if (bps > 2) > + dev->playback.maxburst = 2; > + break; > + default: > + dev_err(dev->dev, "unsupported number of channels: %d\n", > + params_channels(params)); > + return -EINVAL; > + } > + mr |= SPDIFTX_MR_CHUNK(dev->playback.maxburst); > + > + switch (params_format(params)) { > + case SNDRV_PCM_FORMAT_S8: > + mr |= SPDIFTX_MR_VBPS(8); > + break; > + case SNDRV_PCM_FORMAT_S16_BE: > + mr |= SPDIFTX_MR_ENDIAN_BIG; > + fallthrough; > + case SNDRV_PCM_FORMAT_S16_LE: > + mr |= SPDIFTX_MR_VBPS(16); > + break; > + case SNDRV_PCM_FORMAT_S18_3BE: > + mr |= SPDIFTX_MR_ENDIAN_BIG; > + fallthrough; > + case SNDRV_PCM_FORMAT_S18_3LE: > + mr |= SPDIFTX_MR_VBPS(18); > + break; > + case SNDRV_PCM_FORMAT_S20_3BE: > + mr |= SPDIFTX_MR_ENDIAN_BIG; > + fallthrough; > + case SNDRV_PCM_FORMAT_S20_3LE: > + mr |= SPDIFTX_MR_VBPS(20); > + break; > + case SNDRV_PCM_FORMAT_S24_3BE: > + mr |= SPDIFTX_MR_ENDIAN_BIG; > + fallthrough; > + case SNDRV_PCM_FORMAT_S24_3LE: > + mr |= SPDIFTX_MR_VBPS(24); > + break; > + case SNDRV_PCM_FORMAT_S24_BE: > + mr |= SPDIFTX_MR_ENDIAN_BIG; > + fallthrough; > + case SNDRV_PCM_FORMAT_S24_LE: > + mr |= SPDIFTX_MR_VBPS(24); > + break; > + case SNDRV_PCM_FORMAT_S32_BE: > + mr |= SPDIFTX_MR_ENDIAN_BIG; > + fallthrough; > + case SNDRV_PCM_FORMAT_S32_LE: > + mr |= SPDIFTX_MR_VBPS(32); > + break; > + default: > + dev_err(dev->dev, "unsupported PCM format: %d\n", > + params_format(params)); > + return -EINVAL; > + } > + > + mr |= SPDIFTX_MR_BPS(bps); > + > + spin_lock_irqsave(&ctrl->lock, flags); > + ctrl->ch_stat[3] &= ~IEC958_AES3_CON_FS; > + switch (params_rate(params)) { > + case 22050: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_22050; > + break; > + case 24000: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_24000; > + break; > + case 32000: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_32000; > + break; > + case 44100: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_44100; > + break; > + case 48000: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_48000; > + break; > + case 88200: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_88200; > + break; > + case 96000: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_96000; > + break; > + case 176400: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_176400; > + break; > + case 192000: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_192000; > + break; > + case 8000: > + case 11025: > + case 16000: > + case 64000: > + ctrl->ch_stat[3] |= IEC958_AES3_CON_FS_NOTID; > + break; > + default: > + dev_err(dev->dev, "unsupported sample frequency: %u\n", > + params_rate(params)); > + spin_unlock_irqrestore(&ctrl->lock, flags); > + return -EINVAL; > + } > + mchp_spdiftx_channel_status_write(dev); > + spin_unlock_irqrestore(&ctrl->lock, flags); > + mr |= SPDIFTX_MR_VALID1 | SPDIFTX_MR_VALID2; > + > + if (dev->gclk_enabled) { > + clk_disable_unprepare(dev->gclk); > + dev->gclk_enabled = 0; > + } > + ret = clk_set_rate(dev->gclk, params_rate(params) * > + SPDIFTX_GCLK_RATIO); > + if (ret) { > + dev_err(dev->dev, > + "unable to change gclk rate to: rate %u * ratio %u\n", > + params_rate(params), SPDIFTX_GCLK_RATIO); > + return ret; > + } > + ret = clk_prepare_enable(dev->gclk); > + if (ret) { > + dev_err(dev->dev, "unable to enable gclk: %d\n", ret); > + return ret; > + } > + dev->gclk_enabled = 1; > + dev_dbg(dev->dev, "%s(): GCLK set to %d\n", __func__, > + params_rate(params) * SPDIFTX_GCLK_RATIO); > + > + /* Enable interrupts */ > + regmap_write(dev->regmap, SPDIFTX_IER, > + SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR); > + > + regmap_write(dev->regmap, SPDIFTX_MR, mr); Same here. > + > + return 0; > +} > + > +static int mchp_spdiftx_hw_free(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + > + regmap_write(dev->regmap, SPDIFTX_IDR, > + SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR); > + if (dev->gclk_enabled) { > + clk_disable_unprepare(dev->gclk); > + dev->gclk_enabled = 0; > + } > + > + return regmap_write(dev->regmap, SPDIFTX_CR, > + SPDIFTX_CR_SWRST | SPDIFTX_CR_FCLR); > +} > + > + > +static const struct snd_soc_dai_ops mchp_spdiftx_dai_ops = { > + .startup = mchp_spdiftx_dai_startup, > + .shutdown = mchp_spdiftx_dai_shutdown, > + .trigger = mchp_spdiftx_trigger, > + .hw_params = mchp_spdiftx_hw_params, > + .hw_free = mchp_spdiftx_hw_free, > +}; > + > +#define MCHP_SPDIFTX_RATES SNDRV_PCM_RATE_8000_192000 > + > +#define MCHP_SPDIFTX_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ > + SNDRV_PCM_FMTBIT_S16_LE | \ > + SNDRV_PCM_FMTBIT_U16_BE | \ > + SNDRV_PCM_FMTBIT_S18_3LE | \ > + SNDRV_PCM_FMTBIT_S18_3BE | \ > + SNDRV_PCM_FMTBIT_S20_3LE | \ > + SNDRV_PCM_FMTBIT_S20_3BE | \ > + SNDRV_PCM_FMTBIT_S24_3LE | \ > + SNDRV_PCM_FMTBIT_S24_3BE | \ > + SNDRV_PCM_FMTBIT_S24_LE | \ > + SNDRV_PCM_FMTBIT_S24_BE | \ > + SNDRV_PCM_FMTBIT_S32_LE | \ > + SNDRV_PCM_FMTBIT_S32_BE \ > + ) > + > +static int mchp_spdiftx_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 mchp_spdiftx_cs_get(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *uvalue) > +{ > + unsigned long flags; > + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + struct mchp_spdiftx_mixer_control *ctrl = &dev->control; > + > + spin_lock_irqsave(&ctrl->lock, flags); > + memcpy(uvalue->value.iec958.status, ctrl->ch_stat, > + sizeof(ctrl->ch_stat)); > + spin_unlock_irqrestore(&ctrl->lock, flags); > + > + return 0; > +} > + > +static int mchp_spdiftx_cs_put(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *uvalue) > +{ > + unsigned long flags; > + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + struct mchp_spdiftx_mixer_control *ctrl = &dev->control; > + int changed = 0; > + int i; > + > + spin_lock_irqsave(&ctrl->lock, flags); > + for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat); i++) { > + if (ctrl->ch_stat[i] != uvalue->value.iec958.status[i]) > + changed = 1; > + ctrl->ch_stat[i] = uvalue->value.iec958.status[i]; > + } > + > + if (changed) { > + /* don't enable IP while we copy the channel status */ > + if (mchp_spdiftx_is_running(dev)) { > + /* > + * if SPDIF is running, wait for interrupt to write > + * channel status > + */ > + regmap_write(dev->regmap, SPDIFTX_IER, > + SPDIFTX_IR_CSRDY); > + } else { > + mchp_spdiftx_channel_status_write(dev); > + } > + } > + spin_unlock_irqrestore(&ctrl->lock, flags); > + > + return changed; > +} > + > +static int mchp_spdiftx_cs_mask(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *uvalue) > +{ > + memset(uvalue->value.iec958.status, 0xff, > + sizeof(uvalue->value.iec958.status)); > + > + return 0; > +} > + > +static int mchp_spdiftx_subcode_get(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *uvalue) > +{ > + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + struct mchp_spdiftx_mixer_control *ctrl = &dev->control; > + unsigned long flags; > + > + spin_lock_irqsave(&ctrl->lock, flags); > + memcpy(uvalue->value.iec958.subcode, ctrl->user_data, > + sizeof(ctrl->user_data)); > + spin_unlock_irqrestore(&ctrl->lock, flags); > + > + return 0; > +} > + > +static int mchp_spdiftx_subcode_put(struct snd_kcontrol *kcontrol, > + struct snd_ctl_elem_value *uvalue) > +{ > + unsigned long flags; > + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + struct mchp_spdiftx_mixer_control *ctrl = &dev->control; > + int changed = 0; > + int i; > + > + spin_lock_irqsave(&ctrl->lock, flags); > + for (i = 0; i < ARRAY_SIZE(ctrl->user_data); i++) { > + if (ctrl->user_data[i] != uvalue->value.iec958.subcode[i]) > + changed = 1; > + > + ctrl->user_data[i] = uvalue->value.iec958.subcode[i]; > + } > + if (changed) { > + if (mchp_spdiftx_is_running(dev)) { > + /* > + * if SPDIF is running, wait for interrupt to write > + * user data > + */ > + regmap_write(dev->regmap, SPDIFTX_IER, > + SPDIFTX_IR_UDRDY); > + } else { > + mchp_spdiftx_user_data_write(dev); > + } > + } > + spin_unlock_irqrestore(&ctrl->lock, flags); > + > + return changed; > +} > + > +static struct snd_kcontrol_new mchp_spdiftx_ctrls[] = { > + /* Channel status controller */ > + { > + .iface = SNDRV_CTL_ELEM_IFACE_PCM, > + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), > + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | > + SNDRV_CTL_ELEM_ACCESS_VOLATILE, > + .info = mchp_spdiftx_info, > + .get = mchp_spdiftx_cs_get, > + .put = mchp_spdiftx_cs_put, > + }, > + { > + .iface = SNDRV_CTL_ELEM_IFACE_PCM, > + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), > + .access = SNDRV_CTL_ELEM_ACCESS_READ, > + SNDRV_CTL_ELEM_ACCESS_VOLATILE, > + .info = mchp_spdiftx_info, > + .get = mchp_spdiftx_cs_mask, > + }, > + /* User bits controller */ > + { > + .iface = SNDRV_CTL_ELEM_IFACE_PCM, > + .name = "IEC958 Subcode Playback Default", > + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, > + .info = mchp_spdiftx_info, > + .get = mchp_spdiftx_subcode_get, > + .put = mchp_spdiftx_subcode_put, > + }, > +}; > + > +static int mchp_spdiftx_dai_probe(struct snd_soc_dai *dai) > +{ > + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); > + > + snd_soc_dai_init_dma_data(dai, &dev->playback, NULL); > + > + /* Add controls */ > + snd_soc_add_dai_controls(dai, mchp_spdiftx_ctrls, > + ARRAY_SIZE(mchp_spdiftx_ctrls)); > + > + return 0; > +} > + > +static struct snd_soc_dai_driver mchp_spdiftx_dai = { > + .name = "mchp-spdiftx", > + .probe = mchp_spdiftx_dai_probe, > + .playback = { > + .stream_name = "S/PDIF TX Playback", > + .channels_min = 1, > + .channels_max = 2, > + .rates = MCHP_SPDIFTX_RATES, > + .formats = MCHP_SPDIFTX_FORMATS, > + }, > + .ops = &mchp_spdiftx_dai_ops, > +}; > + > +static const struct snd_soc_component_driver mchp_spdiftx_component = { > + .name = "mchp-spdiftx", > +}; > + > +static const struct of_device_id mchp_spdiftx_dt_ids[] = { > + { > + .compatible = "microchip,sama7g5-spdiftx", > + }, > + { /* sentinel */ } > +}; > + > +MODULE_DEVICE_TABLE(of, mchp_spdiftx_dt_ids); > +static int mchp_spdiftx_probe(struct platform_device *pdev) > +{ > + struct device_node *np = pdev->dev.of_node; > + const struct of_device_id *match; > + struct mchp_spdiftx_dev *dev; > + struct resource *mem; > + struct regmap *regmap; > + void __iomem *base; > + struct mchp_spdiftx_mixer_control *ctrl; > + int irq; > + int err; > + > + /* Get memory for driver data. */ > + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); > + if (!dev) > + return -ENOMEM; > + > + /* Get hardware capabilities. */ > + match = of_match_node(mchp_spdiftx_dt_ids, np); > + if (match) > + dev->caps = match->data; > + > + /* Map I/O registers. */ > + base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); > + if (IS_ERR(base)) > + return PTR_ERR(base); > + > + regmap = devm_regmap_init_mmio(&pdev->dev, base, > + &mchp_spdiftx_regmap_config); > + if (IS_ERR(regmap)) > + return PTR_ERR(regmap); > + > + /* Request IRQ */ > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) > + return irq; > + > + err = devm_request_irq(&pdev->dev, irq, mchp_spdiftx_interrupt, 0, > + dev_name(&pdev->dev), dev); > + if (err) > + return err; > + > + /* Get the peripheral clock */ > + dev->pclk = devm_clk_get(&pdev->dev, "pclk"); > + if (IS_ERR(dev->pclk)) { > + err = PTR_ERR(dev->pclk); > + dev_err(&pdev->dev, > + "failed to get the peripheral clock: %d\n", err); > + return err; > + } > + > + /* Get the generic clock */ > + dev->gclk = devm_clk_get(&pdev->dev, "gclk"); > + if (IS_ERR(dev->gclk)) { > + err = PTR_ERR(dev->gclk); > + dev_err(&pdev->dev, > + "failed to get the PMC generic clock: %d\n", err); > + return err; > + } > + > + ctrl = &dev->control; > + spin_lock_init(&ctrl->lock); > + > + /* Init channel status */ > + ctrl->ch_stat[0] = IEC958_AES0_CON_NOT_COPYRIGHT | > + IEC958_AES0_CON_EMPHASIS_NONE; > + > + dev->dev = &pdev->dev; > + dev->regmap = regmap; > + platform_set_drvdata(pdev, dev); > + > + dev->playback.addr = (dma_addr_t)mem->start + SPDIFTX_CDR; > + dev->playback.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; > + > + err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); > + if (err) { > + dev_err(&pdev->dev, "failed to register PMC: %d\n", err); > + return err; > + } > + > + err = devm_snd_soc_register_component(&pdev->dev, > + &mchp_spdiftx_component, > + &mchp_spdiftx_dai, 1); > + if (err) { > + dev_err(&pdev->dev, "failed to register component: %d\n", err); > + return err; > + } > + > + return 0; > +} > + > +static struct platform_driver mchp_spdiftx_driver = { > + .probe = mchp_spdiftx_probe, > + .driver = { > + .name = "mchp_spdiftx", > + .of_match_table = of_match_ptr(mchp_spdiftx_dt_ids), > + }, > +}; > + > +module_platform_driver(mchp_spdiftx_driver); > + > +MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@xxxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Microchip S/PDIF TX Controller Driver"); > +MODULE_LICENSE("GPL v2"); >