diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /sound/soc/atmel | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/soc/atmel')
-rw-r--r-- | sound/soc/atmel/Kconfig | 160 | ||||
-rw-r--r-- | sound/soc/atmel/Makefile | 40 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-classd.c | 637 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-classd.h | 121 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-i2s.c | 743 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-pcm-dma.c | 120 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-pcm-pdc.c | 401 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-pcm.h | 91 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-pdmic.c | 713 | ||||
-rw-r--r-- | sound/soc/atmel/atmel-pdmic.h | 81 | ||||
-rw-r--r-- | sound/soc/atmel/atmel_ssc_dai.c | 927 | ||||
-rw-r--r-- | sound/soc/atmel/atmel_ssc_dai.h | 111 | ||||
-rw-r--r-- | sound/soc/atmel/atmel_wm8904.c | 201 | ||||
-rw-r--r-- | sound/soc/atmel/mchp-i2s-mcc.c | 991 | ||||
-rw-r--r-- | sound/soc/atmel/mchp-spdifrx.c | 1060 | ||||
-rw-r--r-- | sound/soc/atmel/mchp-spdiftx.c | 862 | ||||
-rw-r--r-- | sound/soc/atmel/mikroe-proto.c | 175 | ||||
-rw-r--r-- | sound/soc/atmel/sam9g20_wm8731.c | 276 | ||||
-rw-r--r-- | sound/soc/atmel/sam9x5_wm8731.c | 210 | ||||
-rw-r--r-- | sound/soc/atmel/tse850-pcm5142.c | 454 |
20 files changed, 8374 insertions, 0 deletions
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig new file mode 100644 index 000000000..89210048e --- /dev/null +++ b/sound/soc/atmel/Kconfig @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_ATMEL_SOC + tristate "SoC Audio for the Atmel System-on-Chip" + depends on HAS_IOMEM + help + Say Y or M if you want to add support for codecs attached to + the ATMEL SSC interface. You will also need + to select the audio interfaces to support below. + +if SND_ATMEL_SOC + +config SND_ATMEL_SOC_PDC + bool + +config SND_ATMEL_SOC_DMA + bool + select SND_SOC_GENERIC_DMAENGINE_PCM + +config SND_ATMEL_SOC_SSC + tristate + select SND_ATMEL_SOC_DMA + select SND_ATMEL_SOC_PDC + +config SND_ATMEL_SOC_SSC_PDC + tristate "SoC PCM DAI support for AT91 SSC controller using PDC" + depends on ATMEL_SSC + select SND_ATMEL_SOC_PDC + select SND_ATMEL_SOC_SSC + help + Say Y or M if you want to add support for Atmel SSC interface + in PDC mode configured using audio-graph-card in device-tree. + +config SND_ATMEL_SOC_SSC_DMA + tristate "SoC PCM DAI support for AT91 SSC controller using DMA" + depends on ATMEL_SSC + select SND_ATMEL_SOC_DMA + select SND_ATMEL_SOC_SSC + help + Say Y or M if you want to add support for Atmel SSC interface + in DMA mode configured using audio-graph-card in device-tree. + +config SND_AT91_SOC_SAM9G20_WM8731 + tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board" + depends on ARCH_AT91 || COMPILE_TEST + depends on ATMEL_SSC && SND_SOC_I2C_AND_SPI + select SND_ATMEL_SOC_SSC_PDC + select SND_SOC_WM8731 + help + Say Y if you want to add support for SoC audio on WM8731-based + AT91sam9g20 evaluation board. + +config SND_ATMEL_SOC_WM8904 + tristate "Atmel ASoC driver for boards using WM8904 codec" + depends on ARCH_AT91 || COMPILE_TEST + depends on ATMEL_SSC && I2C + select SND_ATMEL_SOC_SSC_DMA + select SND_SOC_WM8904 + help + Say Y if you want to add support for Atmel ASoC driver for boards using + WM8904 codec. + +config SND_AT91_SOC_SAM9X5_WM8731 + tristate "SoC Audio support for WM8731-based at91sam9x5 board" + depends on ARCH_AT91 || COMPILE_TEST + depends on ATMEL_SSC && SND_SOC_I2C_AND_SPI + select SND_ATMEL_SOC_SSC_DMA + select SND_SOC_WM8731 + help + Say Y if you want to add support for audio SoC on an + at91sam9x5 based board that is using WM8731 codec. + +config SND_ATMEL_SOC_CLASSD + tristate "Atmel ASoC driver for boards using CLASSD" + depends on ARCH_AT91 || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y if you want to add support for Atmel ASoC driver for boards using + CLASSD. + +config SND_ATMEL_SOC_PDMIC + tristate "Atmel ASoC driver for boards using PDMIC" + depends on OF && (ARCH_AT91 || COMPILE_TEST) + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y if you want to add support for Atmel ASoC driver for boards using + PDMIC. + +config SND_ATMEL_SOC_TSE850_PCM5142 + tristate "ASoC driver for the Axentia TSE-850" + depends on ARCH_AT91 && OF + depends on ATMEL_SSC && I2C + select SND_ATMEL_SOC_SSC_DMA + select SND_SOC_PCM512x_I2C + help + Say Y if you want to add support for the ASoC driver for the + Axentia TSE-850 with a PCM5142 codec. + +config SND_ATMEL_SOC_I2S + tristate "Atmel ASoC driver for boards using I2S" + 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 Atmel ASoc driver for boards + using I2S. + +config SND_SOC_MIKROE_PROTO + tristate "Support for Mikroe-PROTO board" + depends on OF + depends on SND_SOC_I2C_AND_SPI + select SND_SOC_WM8731 + help + Say Y or M if you want to add support for MikroElektronika PROTO Audio + Board. This board contains the WM8731 codec, which can be configured + using I2C over SDA (MPU Data Input) and SCL (MPU Clock Input) pins. + Both playback and capture are supported. + +config SND_MCHP_SOC_I2S_MCC + tristate "Microchip ASoC driver for boards using I2S MCC" + 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 I2S Multi-Channel ASoC + driver on the following Microchip platforms: + - sam9x60 + + The I2SMCC complies with the Inter-IC Sound (I2S) bus specification + 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. + +config SND_MCHP_SOC_SPDIFRX + tristate "Microchip ASoC driver for boards using S/PDIF RX" + depends on OF && (ARCH_AT91 || COMPILE_TEST) + depends on COMMON_CLK + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for Microchip S/PDIF RX ASoc + driver on the following Microchip platforms: + - sama7g5 + + This S/PDIF RX 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 new file mode 100644 index 000000000..016188397 --- /dev/null +++ b/sound/soc/atmel/Makefile @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0 +# AT91 Platform Support +snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o +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 +snd-soc-mchp-spdifrx-objs := mchp-spdifrx.o + +# pdc and dma need to both be built-in if any user of +# ssc is built-in. +ifdef CONFIG_SND_ATMEL_SOC_PDC +obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel-pcm-pdc.o +endif +ifdef CONFIG_SND_ATMEL_SOC_DMA +obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel-pcm-dma.o +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 +obj-$(CONFIG_SND_MCHP_SOC_SPDIFRX) += snd-soc-mchp-spdifrx.o + +# AT91 Machine Support +snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o +snd-atmel-soc-wm8904-objs := atmel_wm8904.o +snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o +snd-atmel-soc-classd-objs := atmel-classd.o +snd-atmel-soc-pdmic-objs := atmel-pdmic.o +snd-atmel-soc-tse850-pcm5142-objs := tse850-pcm5142.o +snd-soc-mikroe-proto-objs := mikroe-proto.o + +obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o +obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o +obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o +obj-$(CONFIG_SND_ATMEL_SOC_CLASSD) += snd-atmel-soc-classd.o +obj-$(CONFIG_SND_ATMEL_SOC_PDMIC) += snd-atmel-soc-pdmic.o +obj-$(CONFIG_SND_ATMEL_SOC_TSE850_PCM5142) += snd-atmel-soc-tse850-pcm5142.o +obj-$(CONFIG_SND_SOC_MIKROE_PROTO) += snd-soc-mikroe-proto.o diff --git a/sound/soc/atmel/atmel-classd.c b/sound/soc/atmel/atmel-classd.c new file mode 100644 index 000000000..f91a0e728 --- /dev/null +++ b/sound/soc/atmel/atmel-classd.c @@ -0,0 +1,637 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Atmel ALSA SoC Audio Class D Amplifier (CLASSD) driver + * + * Copyright (C) 2015 Atmel + * + * Author: Songjun Wu <songjun.wu@atmel.com> + */ + +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include "atmel-classd.h" + +struct atmel_classd_pdata { + bool non_overlap_enable; + int non_overlap_time; + int pwm_type; + const char *card_name; +}; + +struct atmel_classd { + dma_addr_t phy_base; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct device *dev; + int irq; + const struct atmel_classd_pdata *pdata; +}; + +#ifdef CONFIG_OF +static const struct of_device_id atmel_classd_of_match[] = { + { + .compatible = "atmel,sama5d2-classd", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, atmel_classd_of_match); + +static struct atmel_classd_pdata *atmel_classd_dt_init(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct atmel_classd_pdata *pdata; + const char *pwm_type; + int ret; + + if (!np) { + dev_err(dev, "device node not found\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_string(np, "atmel,pwm-type", &pwm_type); + if ((ret == 0) && (strcmp(pwm_type, "diff") == 0)) + pdata->pwm_type = CLASSD_MR_PWMTYP_DIFF; + else + pdata->pwm_type = CLASSD_MR_PWMTYP_SINGLE; + + ret = of_property_read_u32(np, + "atmel,non-overlap-time", &pdata->non_overlap_time); + if (ret) + pdata->non_overlap_enable = false; + else + pdata->non_overlap_enable = true; + + ret = of_property_read_string(np, "atmel,model", &pdata->card_name); + if (ret) + pdata->card_name = "CLASSD"; + + return pdata; +} +#else +static inline struct atmel_classd_pdata * +atmel_classd_dt_init(struct device *dev) +{ + return ERR_PTR(-EINVAL); +} +#endif + +#define ATMEL_CLASSD_RATES (SNDRV_PCM_RATE_8000 \ + | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 \ + | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 \ + | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 \ + | SNDRV_PCM_RATE_96000) + +static const struct snd_pcm_hardware atmel_classd_hw = { + .info = SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_RESUME + | SNDRV_PCM_INFO_PAUSE, + .formats = (SNDRV_PCM_FMTBIT_S16_LE), + .rates = ATMEL_CLASSD_RATES, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 256, +}; + +#define ATMEL_CLASSD_PREALLOC_BUF_SIZE (64 * 1024) + +/* cpu dai component */ +static int atmel_classd_cpu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + int err; + + regmap_write(dd->regmap, CLASSD_THR, 0x0); + + err = clk_prepare_enable(dd->pclk); + if (err) + return err; + err = clk_prepare_enable(dd->gclk); + if (err) { + clk_disable_unprepare(dd->pclk); + return err; + } + return 0; +} + +/* platform */ +static int +atmel_classd_platform_configure_dma(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct dma_slave_config *slave_config) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + + if (params_physical_width(params) != 16) { + dev_err(dd->dev, + "only supports 16-bit audio data\n"); + return -EINVAL; + } + + if (params_channels(params) == 1) + slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + else + slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + slave_config->direction = DMA_MEM_TO_DEV; + slave_config->dst_addr = dd->phy_base + CLASSD_THR; + slave_config->dst_maxburst = 1; + slave_config->src_maxburst = 1; + slave_config->device_fc = false; + + return 0; +} + +static const struct snd_dmaengine_pcm_config +atmel_classd_dmaengine_pcm_config = { + .prepare_slave_config = atmel_classd_platform_configure_dma, + .pcm_hardware = &atmel_classd_hw, + .prealloc_buffer_size = ATMEL_CLASSD_PREALLOC_BUF_SIZE, +}; + +/* codec */ +static const char * const mono_mode_text[] = { + "mix", "sat", "left", "right" +}; + +static SOC_ENUM_SINGLE_DECL(classd_mono_mode_enum, + CLASSD_INTPMR, CLASSD_INTPMR_MONO_MODE_SHIFT, + mono_mode_text); + +static const char * const eqcfg_text[] = { + "Treble-12dB", "Treble-6dB", + "Medium-8dB", "Medium-3dB", + "Bass-12dB", "Bass-6dB", + "0 dB", + "Bass+6dB", "Bass+12dB", + "Medium+3dB", "Medium+8dB", + "Treble+6dB", "Treble+12dB", +}; + +static const unsigned int eqcfg_value[] = { + CLASSD_INTPMR_EQCFG_T_CUT_12, CLASSD_INTPMR_EQCFG_T_CUT_6, + CLASSD_INTPMR_EQCFG_M_CUT_8, CLASSD_INTPMR_EQCFG_M_CUT_3, + CLASSD_INTPMR_EQCFG_B_CUT_12, CLASSD_INTPMR_EQCFG_B_CUT_6, + CLASSD_INTPMR_EQCFG_FLAT, + CLASSD_INTPMR_EQCFG_B_BOOST_6, CLASSD_INTPMR_EQCFG_B_BOOST_12, + CLASSD_INTPMR_EQCFG_M_BOOST_3, CLASSD_INTPMR_EQCFG_M_BOOST_8, + CLASSD_INTPMR_EQCFG_T_BOOST_6, CLASSD_INTPMR_EQCFG_T_BOOST_12, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL(classd_eqcfg_enum, + CLASSD_INTPMR, CLASSD_INTPMR_EQCFG_SHIFT, 0xf, + eqcfg_text, eqcfg_value); + +static const DECLARE_TLV_DB_SCALE(classd_digital_tlv, -7800, 100, 1); + +static const struct snd_kcontrol_new atmel_classd_snd_controls[] = { +SOC_DOUBLE_TLV("Playback Volume", CLASSD_INTPMR, + CLASSD_INTPMR_ATTL_SHIFT, CLASSD_INTPMR_ATTR_SHIFT, + 78, 1, classd_digital_tlv), + +SOC_SINGLE("Deemphasis Switch", CLASSD_INTPMR, + CLASSD_INTPMR_DEEMP_SHIFT, 1, 0), + +SOC_SINGLE("Mono Switch", CLASSD_INTPMR, CLASSD_INTPMR_MONO_SHIFT, 1, 0), + +SOC_SINGLE("Swap Switch", CLASSD_INTPMR, CLASSD_INTPMR_SWAP_SHIFT, 1, 0), + +SOC_ENUM("Mono Mode", classd_mono_mode_enum), + +SOC_ENUM("EQ", classd_eqcfg_enum), +}; + +static const char * const pwm_type[] = { + "Single ended", "Differential" +}; + +static int atmel_classd_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(component); + struct atmel_classd *dd = snd_soc_card_get_drvdata(card); + const struct atmel_classd_pdata *pdata = dd->pdata; + u32 mask, val; + + mask = CLASSD_MR_PWMTYP_MASK; + val = pdata->pwm_type << CLASSD_MR_PWMTYP_SHIFT; + + mask |= CLASSD_MR_NON_OVERLAP_MASK; + if (pdata->non_overlap_enable) { + val |= (CLASSD_MR_NON_OVERLAP_EN + << CLASSD_MR_NON_OVERLAP_SHIFT); + + mask |= CLASSD_MR_NOVR_VAL_MASK; + switch (pdata->non_overlap_time) { + case 5: + val |= (CLASSD_MR_NOVR_VAL_5NS + << CLASSD_MR_NOVR_VAL_SHIFT); + break; + case 10: + val |= (CLASSD_MR_NOVR_VAL_10NS + << CLASSD_MR_NOVR_VAL_SHIFT); + break; + case 15: + val |= (CLASSD_MR_NOVR_VAL_15NS + << CLASSD_MR_NOVR_VAL_SHIFT); + break; + case 20: + val |= (CLASSD_MR_NOVR_VAL_20NS + << CLASSD_MR_NOVR_VAL_SHIFT); + break; + default: + val |= (CLASSD_MR_NOVR_VAL_10NS + << CLASSD_MR_NOVR_VAL_SHIFT); + dev_warn(component->dev, + "non-overlapping value %d is invalid, the default value 10 is specified\n", + pdata->non_overlap_time); + break; + } + } + + snd_soc_component_update_bits(component, CLASSD_MR, mask, val); + + dev_info(component->dev, + "PWM modulation type is %s, non-overlapping is %s\n", + pwm_type[pdata->pwm_type], + pdata->non_overlap_enable?"enabled":"disabled"); + + return 0; +} + +static int atmel_classd_component_resume(struct snd_soc_component *component) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(component); + struct atmel_classd *dd = snd_soc_card_get_drvdata(card); + + return regcache_sync(dd->regmap); +} + +static int atmel_classd_cpu_dai_mute_stream(struct snd_soc_dai *cpu_dai, + int mute, int direction) +{ + struct snd_soc_component *component = cpu_dai->component; + u32 mask, val; + + mask = CLASSD_MR_LMUTE_MASK | CLASSD_MR_RMUTE_MASK; + + if (mute) + val = mask; + else + val = 0; + + snd_soc_component_update_bits(component, CLASSD_MR, mask, val); + + return 0; +} + +#define CLASSD_GCLK_RATE_11M2896_MPY_8 (112896 * 100 * 8) +#define CLASSD_GCLK_RATE_12M288_MPY_8 (12288 * 1000 * 8) + +static struct { + int rate; + int sample_rate; + int dsp_clk; + unsigned long gclk_rate; +} const sample_rates[] = { + { 8000, CLASSD_INTPMR_FRAME_8K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 16000, CLASSD_INTPMR_FRAME_16K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 32000, CLASSD_INTPMR_FRAME_32K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 48000, CLASSD_INTPMR_FRAME_48K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 96000, CLASSD_INTPMR_FRAME_96K, + CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_GCLK_RATE_12M288_MPY_8 }, + { 22050, CLASSD_INTPMR_FRAME_22K, + CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_GCLK_RATE_11M2896_MPY_8 }, + { 44100, CLASSD_INTPMR_FRAME_44K, + CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_GCLK_RATE_11M2896_MPY_8 }, + { 88200, CLASSD_INTPMR_FRAME_88K, + CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_GCLK_RATE_11M2896_MPY_8 }, +}; + +static int +atmel_classd_cpu_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = cpu_dai->component; + int fs; + int i, best, best_val, cur_val, ret; + u32 mask, val; + + fs = params_rate(params); + + best = 0; + best_val = abs(fs - sample_rates[0].rate); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + /* Closest match */ + cur_val = abs(fs - sample_rates[i].rate); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + + dev_dbg(component->dev, + "Selected SAMPLE_RATE of %dHz, GCLK_RATE of %ldHz\n", + sample_rates[best].rate, sample_rates[best].gclk_rate); + + clk_disable_unprepare(dd->gclk); + + ret = clk_set_rate(dd->gclk, sample_rates[best].gclk_rate); + if (ret) + return ret; + + mask = CLASSD_INTPMR_DSP_CLK_FREQ_MASK | CLASSD_INTPMR_FRAME_MASK; + val = (sample_rates[best].dsp_clk << CLASSD_INTPMR_DSP_CLK_FREQ_SHIFT) + | (sample_rates[best].sample_rate << CLASSD_INTPMR_FRAME_SHIFT); + + snd_soc_component_update_bits(component, CLASSD_INTPMR, mask, val); + + return clk_prepare_enable(dd->gclk); +} + +static void +atmel_classd_cpu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card); + + clk_disable_unprepare(dd->gclk); +} + +static int atmel_classd_cpu_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_component *component = cpu_dai->component; + + snd_soc_component_update_bits(component, CLASSD_MR, + CLASSD_MR_LEN_MASK | CLASSD_MR_REN_MASK, + (CLASSD_MR_LEN_DIS << CLASSD_MR_LEN_SHIFT) + |(CLASSD_MR_REN_DIS << CLASSD_MR_REN_SHIFT)); + + return 0; +} + +static int atmel_classd_cpu_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_component *component = cpu_dai->component; + u32 mask, val; + + mask = CLASSD_MR_LEN_MASK | CLASSD_MR_REN_MASK; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = mask; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = (CLASSD_MR_LEN_DIS << CLASSD_MR_LEN_SHIFT) + | (CLASSD_MR_REN_DIS << CLASSD_MR_REN_SHIFT); + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, CLASSD_MR, mask, val); + + return 0; +} + +static const struct snd_soc_dai_ops atmel_classd_cpu_dai_ops = { + .startup = atmel_classd_cpu_dai_startup, + .shutdown = atmel_classd_cpu_dai_shutdown, + .mute_stream = atmel_classd_cpu_dai_mute_stream, + .hw_params = atmel_classd_cpu_dai_hw_params, + .prepare = atmel_classd_cpu_dai_prepare, + .trigger = atmel_classd_cpu_dai_trigger, + .no_capture_mute = 1, +}; + +static struct snd_soc_dai_driver atmel_classd_cpu_dai = { + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_CLASSD_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &atmel_classd_cpu_dai_ops, +}; + +static const struct snd_soc_component_driver atmel_classd_cpu_dai_component = { + .name = "atmel-classd", + .probe = atmel_classd_component_probe, + .resume = atmel_classd_component_resume, + .controls = atmel_classd_snd_controls, + .num_controls = ARRAY_SIZE(atmel_classd_snd_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, +}; + +/* ASoC sound card */ +static int atmel_classd_asoc_card_init(struct device *dev, + struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link; + struct atmel_classd *dd = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link_component *comp; + + dai_link = devm_kzalloc(dev, sizeof(*dai_link), GFP_KERNEL); + if (!dai_link) + return -ENOMEM; + + comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL); + if (!comp) + return -ENOMEM; + + dai_link->cpus = &comp[0]; + dai_link->codecs = &comp[1]; + dai_link->platforms = &comp[2]; + + dai_link->num_cpus = 1; + dai_link->num_codecs = 1; + dai_link->num_platforms = 1; + + dai_link->name = "CLASSD"; + dai_link->stream_name = "CLASSD PCM"; + dai_link->codecs->dai_name = "snd-soc-dummy-dai"; + dai_link->cpus->dai_name = dev_name(dev); + dai_link->codecs->name = "snd-soc-dummy"; + dai_link->platforms->name = dev_name(dev); + + card->dai_link = dai_link; + card->num_links = 1; + card->name = dd->pdata->card_name; + card->dev = dev; + + return 0; +}; + +/* regmap configuration */ +static const struct reg_default atmel_classd_reg_defaults[] = { + { CLASSD_INTPMR, 0x00301212 }, +}; + +#define ATMEL_CLASSD_REG_MAX 0xE4 +static const struct regmap_config atmel_classd_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ATMEL_CLASSD_REG_MAX, + + .cache_type = REGCACHE_FLAT, + .reg_defaults = atmel_classd_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(atmel_classd_reg_defaults), +}; + +static int atmel_classd_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct atmel_classd *dd; + struct resource *res; + void __iomem *io_base; + const struct atmel_classd_pdata *pdata; + struct snd_soc_card *card; + int ret; + + pdata = dev_get_platdata(dev); + if (!pdata) { + pdata = atmel_classd_dt_init(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } + + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) + return -ENOMEM; + + dd->pdata = pdata; + + dd->irq = platform_get_irq(pdev, 0); + if (dd->irq < 0) + return dd->irq; + + dd->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(dd->pclk)) { + ret = PTR_ERR(dd->pclk); + dev_err(dev, "failed to get peripheral clock: %d\n", ret); + return ret; + } + + dd->gclk = devm_clk_get(dev, "gclk"); + if (IS_ERR(dd->gclk)) { + ret = PTR_ERR(dd->gclk); + dev_err(dev, "failed to get GCK clock: %d\n", ret); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + dd->phy_base = res->start; + dd->dev = dev; + + dd->regmap = devm_regmap_init_mmio(dev, io_base, + &atmel_classd_regmap_config); + if (IS_ERR(dd->regmap)) { + ret = PTR_ERR(dd->regmap); + dev_err(dev, "failed to init register map: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_component(dev, + &atmel_classd_cpu_dai_component, + &atmel_classd_cpu_dai, 1); + if (ret) { + dev_err(dev, "could not register CPU DAI: %d\n", ret); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(dev, + &atmel_classd_dmaengine_pcm_config, + 0); + if (ret) { + dev_err(dev, "could not register platform: %d\n", ret); + return ret; + } + + /* register sound card */ + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) { + ret = -ENOMEM; + goto unregister_codec; + } + + snd_soc_card_set_drvdata(card, dd); + + ret = atmel_classd_asoc_card_init(dev, card); + if (ret) { + dev_err(dev, "failed to init sound card\n"); + goto unregister_codec; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret) { + dev_err(dev, "failed to register sound card: %d\n", ret); + goto unregister_codec; + } + + return 0; + +unregister_codec: + return ret; +} + +static int atmel_classd_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver atmel_classd_driver = { + .driver = { + .name = "atmel-classd", + .of_match_table = of_match_ptr(atmel_classd_of_match), + .pm = &snd_soc_pm_ops, + }, + .probe = atmel_classd_probe, + .remove = atmel_classd_remove, +}; +module_platform_driver(atmel_classd_driver); + +MODULE_DESCRIPTION("Atmel ClassD driver under ALSA SoC architecture"); +MODULE_AUTHOR("Songjun Wu <songjun.wu@atmel.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/atmel-classd.h b/sound/soc/atmel/atmel-classd.h new file mode 100644 index 000000000..0f2e25aeb --- /dev/null +++ b/sound/soc/atmel/atmel-classd.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ATMEL_CLASSD_H_ +#define __ATMEL_CLASSD_H_ + +#define CLASSD_CR 0x00000000 +#define CLASSD_CR_RESET 0x1 + +#define CLASSD_MR 0x00000004 + +#define CLASSD_MR_LEN_DIS 0x0 +#define CLASSD_MR_LEN_EN 0x1 +#define CLASSD_MR_LEN_MASK (0x1 << 0) +#define CLASSD_MR_LEN_SHIFT (0) + +#define CLASSD_MR_LMUTE_DIS 0x0 +#define CLASSD_MR_LMUTE_EN 0x1 +#define CLASSD_MR_LMUTE_SHIFT (0x1) +#define CLASSD_MR_LMUTE_MASK (0x1 << 1) + +#define CLASSD_MR_REN_DIS 0x0 +#define CLASSD_MR_REN_EN 0x1 +#define CLASSD_MR_REN_MASK (0x1 << 4) +#define CLASSD_MR_REN_SHIFT (4) + +#define CLASSD_MR_RMUTE_DIS 0x0 +#define CLASSD_MR_RMUTE_EN 0x1 +#define CLASSD_MR_RMUTE_SHIFT (0x5) +#define CLASSD_MR_RMUTE_MASK (0x1 << 5) + +#define CLASSD_MR_PWMTYP_SINGLE 0x0 +#define CLASSD_MR_PWMTYP_DIFF 0x1 +#define CLASSD_MR_PWMTYP_MASK (0x1 << 8) +#define CLASSD_MR_PWMTYP_SHIFT (8) + +#define CLASSD_MR_NON_OVERLAP_DIS 0x0 +#define CLASSD_MR_NON_OVERLAP_EN 0x1 +#define CLASSD_MR_NON_OVERLAP_MASK (0x1 << 16) +#define CLASSD_MR_NON_OVERLAP_SHIFT (16) + +#define CLASSD_MR_NOVR_VAL_5NS 0x0 +#define CLASSD_MR_NOVR_VAL_10NS 0x1 +#define CLASSD_MR_NOVR_VAL_15NS 0x2 +#define CLASSD_MR_NOVR_VAL_20NS 0x3 +#define CLASSD_MR_NOVR_VAL_MASK (0x3 << 20) +#define CLASSD_MR_NOVR_VAL_SHIFT (20) + +#define CLASSD_INTPMR 0x00000008 + +#define CLASSD_INTPMR_ATTL_MASK (0x3f << 0) +#define CLASSD_INTPMR_ATTL_SHIFT (0) +#define CLASSD_INTPMR_ATTR_MASK (0x3f << 8) +#define CLASSD_INTPMR_ATTR_SHIFT (8) + +#define CLASSD_INTPMR_DSP_CLK_FREQ_12M288 0x0 +#define CLASSD_INTPMR_DSP_CLK_FREQ_11M2896 0x1 +#define CLASSD_INTPMR_DSP_CLK_FREQ_MASK (0x1 << 16) +#define CLASSD_INTPMR_DSP_CLK_FREQ_SHIFT (16) + +#define CLASSD_INTPMR_DEEMP_DIS 0x0 +#define CLASSD_INTPMR_DEEMP_EN 0x1 +#define CLASSD_INTPMR_DEEMP_MASK (0x1 << 18) +#define CLASSD_INTPMR_DEEMP_SHIFT (18) + +#define CLASSD_INTPMR_SWAP_LEFT_ON_LSB 0x0 +#define CLASSD_INTPMR_SWAP_RIGHT_ON_LSB 0x1 +#define CLASSD_INTPMR_SWAP_MASK (0x1 << 19) +#define CLASSD_INTPMR_SWAP_SHIFT (19) + +#define CLASSD_INTPMR_FRAME_8K 0x0 +#define CLASSD_INTPMR_FRAME_16K 0x1 +#define CLASSD_INTPMR_FRAME_32K 0x2 +#define CLASSD_INTPMR_FRAME_48K 0x3 +#define CLASSD_INTPMR_FRAME_96K 0x4 +#define CLASSD_INTPMR_FRAME_22K 0x5 +#define CLASSD_INTPMR_FRAME_44K 0x6 +#define CLASSD_INTPMR_FRAME_88K 0x7 +#define CLASSD_INTPMR_FRAME_MASK (0x7 << 20) +#define CLASSD_INTPMR_FRAME_SHIFT (20) + +#define CLASSD_INTPMR_EQCFG_FLAT 0x0 +#define CLASSD_INTPMR_EQCFG_B_BOOST_12 0x1 +#define CLASSD_INTPMR_EQCFG_B_BOOST_6 0x2 +#define CLASSD_INTPMR_EQCFG_B_CUT_12 0x3 +#define CLASSD_INTPMR_EQCFG_B_CUT_6 0x4 +#define CLASSD_INTPMR_EQCFG_M_BOOST_3 0x5 +#define CLASSD_INTPMR_EQCFG_M_BOOST_8 0x6 +#define CLASSD_INTPMR_EQCFG_M_CUT_3 0x7 +#define CLASSD_INTPMR_EQCFG_M_CUT_8 0x8 +#define CLASSD_INTPMR_EQCFG_T_BOOST_12 0x9 +#define CLASSD_INTPMR_EQCFG_T_BOOST_6 0xa +#define CLASSD_INTPMR_EQCFG_T_CUT_12 0xb +#define CLASSD_INTPMR_EQCFG_T_CUT_6 0xc +#define CLASSD_INTPMR_EQCFG_SHIFT (24) + +#define CLASSD_INTPMR_MONO_DIS 0x0 +#define CLASSD_INTPMR_MONO_EN 0x1 +#define CLASSD_INTPMR_MONO_MASK (0x1 << 28) +#define CLASSD_INTPMR_MONO_SHIFT (28) + +#define CLASSD_INTPMR_MONO_MODE_MIX 0x0 +#define CLASSD_INTPMR_MONO_MODE_SAT 0x1 +#define CLASSD_INTPMR_MONO_MODE_LEFT 0x2 +#define CLASSD_INTPMR_MONO_MODE_RIGHT 0x3 +#define CLASSD_INTPMR_MONO_MODE_MASK (0x3 << 29) +#define CLASSD_INTPMR_MONO_MODE_SHIFT (29) + +#define CLASSD_INTSR 0x0000000c + +#define CLASSD_THR 0x00000010 + +#define CLASSD_IER 0x00000014 + +#define CLASSD_IDR 0x00000018 + +#define CLASSD_IMR 0x0000001c + +#define CLASSD_ISR 0x00000020 + +#define CLASSD_WPMR 0x000000e4 + +#endif diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c new file mode 100644 index 000000000..0341b3119 --- /dev/null +++ b/sound/soc/atmel/atmel-i2s.c @@ -0,0 +1,743 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Atmel I2S controller + * + * Copyright (C) 2015 Atmel Corporation + * + * Author: Cyrille Pitchen <cyrille.pitchen@atmel.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#define ATMEL_I2SC_MAX_TDM_CHANNELS 8 + +/* + * ---- I2S Controller Register map ---- + */ +#define ATMEL_I2SC_CR 0x0000 /* Control Register */ +#define ATMEL_I2SC_MR 0x0004 /* Mode Register */ +#define ATMEL_I2SC_SR 0x0008 /* Status Register */ +#define ATMEL_I2SC_SCR 0x000c /* Status Clear Register */ +#define ATMEL_I2SC_SSR 0x0010 /* Status Set Register */ +#define ATMEL_I2SC_IER 0x0014 /* Interrupt Enable Register */ +#define ATMEL_I2SC_IDR 0x0018 /* Interrupt Disable Register */ +#define ATMEL_I2SC_IMR 0x001c /* Interrupt Mask Register */ +#define ATMEL_I2SC_RHR 0x0020 /* Receiver Holding Register */ +#define ATMEL_I2SC_THR 0x0024 /* Transmitter Holding Register */ +#define ATMEL_I2SC_VERSION 0x0028 /* Version Register */ + +/* + * ---- Control Register (Write-only) ---- + */ +#define ATMEL_I2SC_CR_RXEN BIT(0) /* Receiver Enable */ +#define ATMEL_I2SC_CR_RXDIS BIT(1) /* Receiver Disable */ +#define ATMEL_I2SC_CR_CKEN BIT(2) /* Clock Enable */ +#define ATMEL_I2SC_CR_CKDIS BIT(3) /* Clock Disable */ +#define ATMEL_I2SC_CR_TXEN BIT(4) /* Transmitter Enable */ +#define ATMEL_I2SC_CR_TXDIS BIT(5) /* Transmitter Disable */ +#define ATMEL_I2SC_CR_SWRST BIT(7) /* Software Reset */ + +/* + * ---- Mode Register (Read/Write) ---- + */ +#define ATMEL_I2SC_MR_MODE_MASK GENMASK(0, 0) +#define ATMEL_I2SC_MR_MODE_SLAVE (0 << 0) +#define ATMEL_I2SC_MR_MODE_MASTER (1 << 0) + +#define ATMEL_I2SC_MR_DATALENGTH_MASK GENMASK(4, 2) +#define ATMEL_I2SC_MR_DATALENGTH_32_BITS (0 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_24_BITS (1 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_20_BITS (2 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_18_BITS (3 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_16_BITS (4 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_16_BITS_COMPACT (5 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_8_BITS (6 << 2) +#define ATMEL_I2SC_MR_DATALENGTH_8_BITS_COMPACT (7 << 2) + +#define ATMEL_I2SC_MR_FORMAT_MASK GENMASK(7, 6) +#define ATMEL_I2SC_MR_FORMAT_I2S (0 << 6) +#define ATMEL_I2SC_MR_FORMAT_LJ (1 << 6) /* Left Justified */ +#define ATMEL_I2SC_MR_FORMAT_TDM (2 << 6) +#define ATMEL_I2SC_MR_FORMAT_TDMLJ (3 << 6) + +/* Left audio samples duplicated to right audio channel */ +#define ATMEL_I2SC_MR_RXMONO BIT(8) + +/* Receiver uses one DMA channel ... */ +#define ATMEL_I2SC_MR_RXDMA_MASK GENMASK(9, 9) +#define ATMEL_I2SC_MR_RXDMA_SINGLE (0 << 9) /* for all audio channels */ +#define ATMEL_I2SC_MR_RXDMA_MULTIPLE (1 << 9) /* per audio channel */ + +/* I2SDO output of I2SC is internally connected to I2SDI input */ +#define ATMEL_I2SC_MR_RXLOOP BIT(10) + +/* Left audio samples duplicated to right audio channel */ +#define ATMEL_I2SC_MR_TXMONO BIT(12) + +/* Transmitter uses one DMA channel ... */ +#define ATMEL_I2SC_MR_TXDMA_MASK GENMASK(13, 13) +#define ATMEL_I2SC_MR_TXDMA_SINGLE (0 << 13) /* for all audio channels */ +#define ATMEL_I2SC_MR_TXDME_MULTIPLE (1 << 13) /* per audio channel */ + +/* x sample transmitted when underrun */ +#define ATMEL_I2SC_MR_TXSAME_MASK GENMASK(14, 14) +#define ATMEL_I2SC_MR_TXSAME_ZERO (0 << 14) /* Zero sample */ +#define ATMEL_I2SC_MR_TXSAME_PREVIOUS (1 << 14) /* Previous sample */ + +/* Audio Clock to I2SC Master Clock ratio */ +#define ATMEL_I2SC_MR_IMCKDIV_MASK GENMASK(21, 16) +#define ATMEL_I2SC_MR_IMCKDIV(div) \ + (((div) << 16) & ATMEL_I2SC_MR_IMCKDIV_MASK) + +/* Master Clock to fs ratio */ +#define ATMEL_I2SC_MR_IMCKFS_MASK GENMASK(29, 24) +#define ATMEL_I2SC_MR_IMCKFS(fs) \ + (((fs) << 24) & ATMEL_I2SC_MR_IMCKFS_MASK) + +/* Master Clock mode */ +#define ATMEL_I2SC_MR_IMCKMODE_MASK GENMASK(30, 30) +/* 0: No master clock generated (selected clock drives I2SCK pin) */ +#define ATMEL_I2SC_MR_IMCKMODE_I2SCK (0 << 30) +/* 1: master clock generated (internally generated clock drives I2SMCK pin) */ +#define ATMEL_I2SC_MR_IMCKMODE_I2SMCK (1 << 30) + +/* Slot Width */ +/* 0: slot is 32 bits wide for DATALENGTH = 18/20/24 bits. */ +/* 1: slot is 24 bits wide for DATALENGTH = 18/20/24 bits. */ +#define ATMEL_I2SC_MR_IWS BIT(31) + +/* + * ---- Status Registers ---- + */ +#define ATMEL_I2SC_SR_RXEN BIT(0) /* Receiver Enabled */ +#define ATMEL_I2SC_SR_RXRDY BIT(1) /* Receive Ready */ +#define ATMEL_I2SC_SR_RXOR BIT(2) /* Receive Overrun */ + +#define ATMEL_I2SC_SR_TXEN BIT(4) /* Transmitter Enabled */ +#define ATMEL_I2SC_SR_TXRDY BIT(5) /* Transmit Ready */ +#define ATMEL_I2SC_SR_TXUR BIT(6) /* Transmit Underrun */ + +/* Receive Overrun Channel */ +#define ATMEL_I2SC_SR_RXORCH_MASK GENMASK(15, 8) +#define ATMEL_I2SC_SR_RXORCH(ch) (1 << (((ch) & 0x7) + 8)) + +/* Transmit Underrun Channel */ +#define ATMEL_I2SC_SR_TXURCH_MASK GENMASK(27, 20) +#define ATMEL_I2SC_SR_TXURCH(ch) (1 << (((ch) & 0x7) + 20)) + +/* + * ---- Interrupt Enable/Disable/Mask Registers ---- + */ +#define ATMEL_I2SC_INT_RXRDY ATMEL_I2SC_SR_RXRDY +#define ATMEL_I2SC_INT_RXOR ATMEL_I2SC_SR_RXOR +#define ATMEL_I2SC_INT_TXRDY ATMEL_I2SC_SR_TXRDY +#define ATMEL_I2SC_INT_TXUR ATMEL_I2SC_SR_TXUR + +static const struct regmap_config atmel_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ATMEL_I2SC_VERSION, +}; + +struct atmel_i2s_gck_param { + int fs; + unsigned long mck; + int imckdiv; + int imckfs; +}; + +#define I2S_MCK_12M288 12288000UL +#define I2S_MCK_11M2896 11289600UL +#define I2S_MCK_6M144 6144000UL + +/* mck = (32 * (imckfs+1) / (imckdiv+1)) * fs */ +static const struct atmel_i2s_gck_param gck_params[] = { + /* mck = 6.144Mhz */ + { 8000, I2S_MCK_6M144, 1, 47}, /* mck = 768 fs */ + + /* mck = 12.288MHz */ + { 16000, I2S_MCK_12M288, 1, 47}, /* mck = 768 fs */ + { 24000, I2S_MCK_12M288, 3, 63}, /* mck = 512 fs */ + { 32000, I2S_MCK_12M288, 3, 47}, /* mck = 384 fs */ + { 48000, I2S_MCK_12M288, 7, 63}, /* mck = 256 fs */ + { 64000, I2S_MCK_12M288, 7, 47}, /* mck = 192 fs */ + { 96000, I2S_MCK_12M288, 7, 31}, /* mck = 128 fs */ + {192000, I2S_MCK_12M288, 7, 15}, /* mck = 64 fs */ + + /* mck = 11.2896MHz */ + { 11025, I2S_MCK_11M2896, 1, 63}, /* mck = 1024 fs */ + { 22050, I2S_MCK_11M2896, 3, 63}, /* mck = 512 fs */ + { 44100, I2S_MCK_11M2896, 7, 63}, /* mck = 256 fs */ + { 88200, I2S_MCK_11M2896, 7, 31}, /* mck = 128 fs */ + {176400, I2S_MCK_11M2896, 7, 15}, /* mck = 64 fs */ +}; + +struct atmel_i2s_dev; + +struct atmel_i2s_caps { + int (*mck_init)(struct atmel_i2s_dev *, struct device_node *np); +}; + +struct atmel_i2s_dev { + struct device *dev; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct snd_dmaengine_dai_dma_data playback; + struct snd_dmaengine_dai_dma_data capture; + unsigned int fmt; + const struct atmel_i2s_gck_param *gck_param; + const struct atmel_i2s_caps *caps; + int clk_use_no; +}; + +static irqreturn_t atmel_i2s_interrupt(int irq, void *dev_id) +{ + struct atmel_i2s_dev *dev = dev_id; + unsigned int sr, imr, pending, ch, mask; + irqreturn_t ret = IRQ_NONE; + + regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr); + regmap_read(dev->regmap, ATMEL_I2SC_IMR, &imr); + pending = sr & imr; + + if (!pending) + return IRQ_NONE; + + if (pending & ATMEL_I2SC_INT_RXOR) { + mask = ATMEL_I2SC_SR_RXOR; + + for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) { + if (sr & ATMEL_I2SC_SR_RXORCH(ch)) { + mask |= ATMEL_I2SC_SR_RXORCH(ch); + dev_err(dev->dev, + "RX overrun on channel %d\n", ch); + } + } + regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask); + ret = IRQ_HANDLED; + } + + if (pending & ATMEL_I2SC_INT_TXUR) { + mask = ATMEL_I2SC_SR_TXUR; + + for (ch = 0; ch < ATMEL_I2SC_MAX_TDM_CHANNELS; ++ch) { + if (sr & ATMEL_I2SC_SR_TXURCH(ch)) { + mask |= ATMEL_I2SC_SR_TXURCH(ch); + dev_err(dev->dev, + "TX underrun on channel %d\n", ch); + } + } + regmap_write(dev->regmap, ATMEL_I2SC_SCR, mask); + ret = IRQ_HANDLED; + } + + return ret; +} + +#define ATMEL_I2S_RATES SNDRV_PCM_RATE_8000_192000 + +#define ATMEL_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int atmel_i2s_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev->fmt = fmt; + return 0; +} + +static int atmel_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int rhr, sr = 0; + + if (is_playback) { + regmap_read(dev->regmap, ATMEL_I2SC_SR, &sr); + if (sr & ATMEL_I2SC_SR_RXRDY) { + /* + * The RX Ready flag should not be set. However if here, + * we flush (read) the Receive Holding Register to start + * from a clean state. + */ + dev_dbg(dev->dev, "RXRDY is set\n"); + regmap_read(dev->regmap, ATMEL_I2SC_RHR, &rhr); + } + } + + return 0; +} + +static int atmel_i2s_get_gck_param(struct atmel_i2s_dev *dev, int fs) +{ + int i, best; + + if (!dev->gclk) { + dev_err(dev->dev, "cannot generate the I2S Master Clock\n"); + return -EINVAL; + } + + /* + * Find the best possible settings to generate the I2S Master Clock + * from the PLL Audio. + */ + dev->gck_param = NULL; + best = INT_MAX; + for (i = 0; i < ARRAY_SIZE(gck_params); ++i) { + const struct atmel_i2s_gck_param *gck_param = &gck_params[i]; + int val = abs(fs - gck_param->fs); + + if (val < best) { + best = val; + dev->gck_param = gck_param; + } + } + + return 0; +} + +static int atmel_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + unsigned int mr = 0, mr_mask; + int ret; + + mr_mask = ATMEL_I2SC_MR_FORMAT_MASK | ATMEL_I2SC_MR_MODE_MASK | + ATMEL_I2SC_MR_DATALENGTH_MASK; + if (is_playback) + mr_mask |= ATMEL_I2SC_MR_TXMONO; + else + mr_mask |= ATMEL_I2SC_MR_RXMONO; + + switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mr |= ATMEL_I2SC_MR_FORMAT_I2S; + break; + + default: + dev_err(dev->dev, "unsupported bus format\n"); + return -EINVAL; + } + + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* codec is slave, so cpu is master */ + mr |= ATMEL_I2SC_MR_MODE_MASTER; + ret = atmel_i2s_get_gck_param(dev, params_rate(params)); + if (ret) + return ret; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is master, so cpu is slave */ + mr |= ATMEL_I2SC_MR_MODE_SLAVE; + dev->gck_param = NULL; + break; + + default: + dev_err(dev->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + switch (params_channels(params)) { + case 1: + if (is_playback) + mr |= ATMEL_I2SC_MR_TXMONO; + else + mr |= ATMEL_I2SC_MR_RXMONO; + break; + case 2: + break; + default: + dev_err(dev->dev, "unsupported number of audio channels\n"); + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mr |= ATMEL_I2SC_MR_DATALENGTH_8_BITS; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_16_BITS; + break; + + case SNDRV_PCM_FORMAT_S18_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_18_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S20_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_20_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S24_3LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS | ATMEL_I2SC_MR_IWS; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_24_BITS; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + mr |= ATMEL_I2SC_MR_DATALENGTH_32_BITS; + break; + + default: + dev_err(dev->dev, "unsupported size/endianness for audio samples\n"); + return -EINVAL; + } + + return regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr); +} + +static int atmel_i2s_switch_mck_generator(struct atmel_i2s_dev *dev, + bool enabled) +{ + unsigned int mr, mr_mask; + unsigned long gclk_rate; + int ret; + + mr = 0; + mr_mask = (ATMEL_I2SC_MR_IMCKDIV_MASK | + ATMEL_I2SC_MR_IMCKFS_MASK | + ATMEL_I2SC_MR_IMCKMODE_MASK); + + if (!enabled) { + /* Disable the I2S Master Clock generator. */ + ret = regmap_write(dev->regmap, ATMEL_I2SC_CR, + ATMEL_I2SC_CR_CKDIS); + if (ret) + return ret; + + /* Reset the I2S Master Clock generator settings. */ + ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, + mr_mask, mr); + if (ret) + return ret; + + /* Disable/unprepare the PMC generated clock. */ + clk_disable_unprepare(dev->gclk); + + return 0; + } + + if (!dev->gck_param) + return -EINVAL; + + gclk_rate = dev->gck_param->mck * (dev->gck_param->imckdiv + 1); + + ret = clk_set_rate(dev->gclk, gclk_rate); + if (ret) + return ret; + + ret = clk_prepare_enable(dev->gclk); + if (ret) + return ret; + + /* Update the Mode Register to generate the I2S Master Clock. */ + mr |= ATMEL_I2SC_MR_IMCKDIV(dev->gck_param->imckdiv); + mr |= ATMEL_I2SC_MR_IMCKFS(dev->gck_param->imckfs); + mr |= ATMEL_I2SC_MR_IMCKMODE_I2SMCK; + ret = regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr); + if (ret) + return ret; + + /* Finally enable the I2S Master Clock generator. */ + return regmap_write(dev->regmap, ATMEL_I2SC_CR, + ATMEL_I2SC_CR_CKEN); +} + +static int atmel_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + bool is_master, mck_enabled; + unsigned int cr, mr; + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + cr = is_playback ? ATMEL_I2SC_CR_TXEN : ATMEL_I2SC_CR_RXEN; + mck_enabled = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + cr = is_playback ? ATMEL_I2SC_CR_TXDIS : ATMEL_I2SC_CR_RXDIS; + mck_enabled = false; + break; + default: + return -EINVAL; + } + + /* Read the Mode Register to retrieve the master/slave state. */ + err = regmap_read(dev->regmap, ATMEL_I2SC_MR, &mr); + if (err) + return err; + is_master = (mr & ATMEL_I2SC_MR_MODE_MASK) == ATMEL_I2SC_MR_MODE_MASTER; + + /* If master starts, enable the audio clock. */ + if (is_master && mck_enabled) { + if (!dev->clk_use_no) { + err = atmel_i2s_switch_mck_generator(dev, true); + if (err) + return err; + } + dev->clk_use_no++; + } + + err = regmap_write(dev->regmap, ATMEL_I2SC_CR, cr); + if (err) + return err; + + /* If master stops, disable the audio clock. */ + if (is_master && !mck_enabled) { + if (dev->clk_use_no == 1) { + err = atmel_i2s_switch_mck_generator(dev, false); + if (err) + return err; + } + dev->clk_use_no--; + } + + return err; +} + +static const struct snd_soc_dai_ops atmel_i2s_dai_ops = { + .prepare = atmel_i2s_prepare, + .trigger = atmel_i2s_trigger, + .hw_params = atmel_i2s_hw_params, + .set_fmt = atmel_i2s_set_dai_fmt, +}; + +static int atmel_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &dev->playback, &dev->capture); + return 0; +} + +static struct snd_soc_dai_driver atmel_i2s_dai = { + .probe = atmel_i2s_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_I2S_RATES, + .formats = ATMEL_I2S_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = ATMEL_I2S_RATES, + .formats = ATMEL_I2S_FORMATS, + }, + .ops = &atmel_i2s_dai_ops, + .symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver atmel_i2s_component = { + .name = "atmel-i2s", +}; + +static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev, + struct device_node *np) +{ + struct clk *muxclk; + int err; + + if (!dev->gclk) + return 0; + + /* muxclk is optional, so we return error for probe defer only */ + muxclk = devm_clk_get(dev->dev, "muxclk"); + if (IS_ERR(muxclk)) { + err = PTR_ERR(muxclk); + if (err == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(dev->dev, + "failed to get the I2S clock control: %d\n", err); + return 0; + } + + return clk_set_parent(muxclk, dev->gclk); +} + +static const struct atmel_i2s_caps atmel_i2s_sama5d2_caps = { + .mck_init = atmel_i2s_sama5d2_mck_init, +}; + +static const struct of_device_id atmel_i2s_dt_ids[] = { + { + .compatible = "atmel,sama5d2-i2s", + .data = (void *)&atmel_i2s_sama5d2_caps, + }, + + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_i2s_dt_ids); + +static int atmel_i2s_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + struct atmel_i2s_dev *dev; + struct resource *mem; + struct regmap *regmap; + void __iomem *base; + int irq; + int err = -ENXIO; + unsigned int pcm_flags = 0; + unsigned int version; + + /* 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(atmel_i2s_dt_ids, np); + if (match) + dev->caps = match->data; + + /* Map I/O registers. */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(&pdev->dev, base, + &atmel_i2s_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, atmel_i2s_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 audio clock to generate the I2S Master Clock (I2S_MCK) */ + dev->gclk = devm_clk_get(&pdev->dev, "gclk"); + if (IS_ERR(dev->gclk)) { + if (PTR_ERR(dev->gclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + /* Master Mode not supported */ + dev->gclk = NULL; + } + dev->dev = &pdev->dev; + dev->regmap = regmap; + platform_set_drvdata(pdev, dev); + + /* Do hardware specific settings to initialize I2S_MCK generator */ + if (dev->caps && dev->caps->mck_init) { + err = dev->caps->mck_init(dev, np); + if (err) + return err; + } + + /* Enable the peripheral clock. */ + err = clk_prepare_enable(dev->pclk); + if (err) + return err; + + /* Get IP version. */ + regmap_read(dev->regmap, ATMEL_I2SC_VERSION, &version); + dev_info(&pdev->dev, "hw version: %#x\n", version); + + /* Enable error interrupts. */ + regmap_write(dev->regmap, ATMEL_I2SC_IER, + ATMEL_I2SC_INT_RXOR | ATMEL_I2SC_INT_TXUR); + + err = devm_snd_soc_register_component(&pdev->dev, + &atmel_i2s_component, + &atmel_i2s_dai, 1); + if (err) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + /* Prepare DMA config. */ + dev->playback.addr = (dma_addr_t)mem->start + ATMEL_I2SC_THR; + dev->playback.maxburst = 1; + dev->capture.addr = (dma_addr_t)mem->start + ATMEL_I2SC_RHR; + dev->capture.maxburst = 1; + + if (of_property_match_string(np, "dma-names", "rx-tx") == 0) + pcm_flags |= SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX; + err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, pcm_flags); + if (err) { + dev_err(&pdev->dev, "failed to register PCM: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + return 0; +} + +static int atmel_i2s_remove(struct platform_device *pdev) +{ + struct atmel_i2s_dev *dev = platform_get_drvdata(pdev); + + clk_disable_unprepare(dev->pclk); + + return 0; +} + +static struct platform_driver atmel_i2s_driver = { + .driver = { + .name = "atmel_i2s", + .of_match_table = of_match_ptr(atmel_i2s_dt_ids), + }, + .probe = atmel_i2s_probe, + .remove = atmel_i2s_remove, +}; +module_platform_driver(atmel_i2s_driver); + +MODULE_DESCRIPTION("Atmel I2S Controller driver"); +MODULE_AUTHOR("Cyrille Pitchen <cyrille.pitchen@atmel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/atmel/atmel-pcm-dma.c b/sound/soc/atmel/atmel-pcm-dma.c new file mode 100644 index 000000000..96a8c7dba --- /dev/null +++ b/sound/soc/atmel/atmel-pcm-dma.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * atmel-pcm-dma.c -- ALSA PCM DMA support for the Atmel SoC. + * + * Copyright (C) 2012 Atmel + * + * Author: Bo Shen <voice.shen@atmel.com> + * + * Based on atmel-pcm by: + * Sedji Gaouaou <sedji.gaouaou@atmel.com> + * Copyright 2008 Atmel + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/atmel-ssc.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "atmel-pcm.h" + +/*--------------------------------------------------------------------------*\ + * Hardware definition +\*--------------------------------------------------------------------------*/ +static const struct snd_pcm_hardware atmel_pcm_dma_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE, + .period_bytes_min = 256, /* lighting DMA overhead */ + .period_bytes_max = 2 * 0xffff, /* if 2 bytes format */ + .periods_min = 8, + .periods_max = 1024, /* no limit */ + .buffer_bytes_max = 512 * 1024, +}; + +/* + * atmel_pcm_dma_irq: SSC interrupt handler for DMAENGINE enabled SSC + * + * We use DMAENGINE to send/receive data to/from SSC so this ISR is only to + * check if any overrun occured. + */ +static void atmel_pcm_dma_irq(u32 ssc_sr, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_pcm_dma_params *prtd; + + prtd = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + + if (ssc_sr & prtd->mask->ssc_error) { + if (snd_pcm_running(substream)) + pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x)\n", + substream->stream == SNDRV_PCM_STREAM_PLAYBACK + ? "underrun" : "overrun", prtd->name, + ssc_sr); + + /* stop RX and capture: will be enabled again at restart */ + ssc_writex(prtd->ssc->regs, SSC_CR, prtd->mask->ssc_disable); + snd_pcm_stop_xrun(substream); + + /* now drain RHR and read status to remove xrun condition */ + ssc_readx(prtd->ssc->regs, SSC_RHR); + ssc_readx(prtd->ssc->regs, SSC_SR); + } +} + +static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_pcm_dma_params *prtd; + struct ssc_device *ssc; + int ret; + + prtd = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + ssc = prtd->ssc; + + ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config); + if (ret) { + pr_err("atmel-pcm: hwparams to dma slave configure failed\n"); + return ret; + } + + slave_config->dst_addr = ssc->phybase + SSC_THR; + slave_config->dst_maxburst = 1; + + slave_config->src_addr = ssc->phybase + SSC_RHR; + slave_config->src_maxburst = 1; + + prtd->dma_intr_handler = atmel_pcm_dma_irq; + + return 0; +} + +static const struct snd_dmaengine_pcm_config atmel_dmaengine_pcm_config = { + .prepare_slave_config = atmel_pcm_configure_dma, + .pcm_hardware = &atmel_pcm_dma_hardware, + .prealloc_buffer_size = 64 * 1024, +}; + +int atmel_pcm_dma_platform_register(struct device *dev) +{ + return devm_snd_dmaengine_pcm_register(dev, + &atmel_dmaengine_pcm_config, 0); +} +EXPORT_SYMBOL(atmel_pcm_dma_platform_register); + +MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); +MODULE_DESCRIPTION("Atmel DMA based PCM module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c new file mode 100644 index 000000000..704f70001 --- /dev/null +++ b/sound/soc/atmel/atmel-pcm-pdc.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC. + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com> + * + * Based on at91-pcm. by: + * Frank Mandarino <fmandarino@endrelia.com> + * Copyright 2006 Endrelia Technologies Inc. + * + * Based on pxa2xx-pcm.c by: + * + * Author: Nicolas Pitre + * Created: Nov 30, 2004 + * Copyright: (C) 2004 MontaVista Software, Inc. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/atmel_pdc.h> +#include <linux/atmel-ssc.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "atmel-pcm.h" + + +static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = ATMEL_SSC_DMABUF_SIZE; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%zu\n", + (void *)buf->area, (void *)(long)buf->addr, size); + + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static int atmel_pcm_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static int atmel_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + pr_debug("atmel-pcm: allocating PCM playback DMA buffer\n"); + ret = atmel_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n"); + ret = atmel_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +static void atmel_pcm_free(struct snd_soc_component *component, + struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +/*--------------------------------------------------------------------------*\ + * Hardware definition +\*--------------------------------------------------------------------------*/ +/* TODO: These values were taken from the AT91 platform driver, check + * them against real values for AT32 + */ +static const struct snd_pcm_hardware atmel_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE, +}; + + +/*--------------------------------------------------------------------------*\ + * Data types +\*--------------------------------------------------------------------------*/ +struct atmel_runtime_data { + struct atmel_pcm_dma_params *params; + dma_addr_t dma_buffer; /* physical address of dma buffer */ + dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */ + size_t period_size; + + dma_addr_t period_ptr; /* physical address of next period */ +}; + +/*--------------------------------------------------------------------------*\ + * ISR +\*--------------------------------------------------------------------------*/ +static void atmel_pcm_dma_irq(u32 ssc_sr, + struct snd_pcm_substream *substream) +{ + struct atmel_runtime_data *prtd = substream->runtime->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + static int count; + + count++; + + if (ssc_sr & params->mask->ssc_endbuf) { + pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n", + substream->stream == SNDRV_PCM_STREAM_PLAYBACK + ? "underrun" : "overrun", + params->name, ssc_sr, count); + + /* re-start the PDC */ + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + prtd->period_ptr += prtd->period_size; + if (prtd->period_ptr >= prtd->dma_buffer_end) + prtd->period_ptr = prtd->dma_buffer; + + ssc_writex(params->ssc->regs, params->pdc->xpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xcr, + prtd->period_size / params->pdc_xfer_size); + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_enable); + } + + if (ssc_sr & params->mask->ssc_endx) { + /* Load the PDC next pointer and counter registers */ + prtd->period_ptr += prtd->period_size; + if (prtd->period_ptr >= prtd->dma_buffer_end) + prtd->period_ptr = prtd->dma_buffer; + + ssc_writex(params->ssc->regs, params->pdc->xnpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xncr, + prtd->period_size / params->pdc_xfer_size); + } + + snd_pcm_period_elapsed(substream); +} + + +/*--------------------------------------------------------------------------*\ + * PCM operations +\*--------------------------------------------------------------------------*/ +static int atmel_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct atmel_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + + /* this may get called several times by oss emulation + * with different params */ + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + prtd->params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + prtd->params->dma_intr_handler = atmel_pcm_dma_irq; + + prtd->dma_buffer = runtime->dma_addr; + prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes; + prtd->period_size = params_period_bytes(params); + + pr_debug("atmel-pcm: " + "hw_params: DMA for %s initialized " + "(dma_bytes=%zu, period_size=%zu)\n", + prtd->params->name, + runtime->dma_bytes, + prtd->period_size); + return 0; +} + +static int atmel_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct atmel_runtime_data *prtd = substream->runtime->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + + if (params != NULL) { + ssc_writex(params->ssc->regs, SSC_PDC_PTCR, + params->mask->pdc_disable); + prtd->params->dma_intr_handler = NULL; + } + + return 0; +} + +static int atmel_pcm_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct atmel_runtime_data *prtd = substream->runtime->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + + ssc_writex(params->ssc->regs, SSC_IDR, + params->mask->ssc_endx | params->mask->ssc_endbuf); + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + return 0; +} + +static int atmel_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *rtd = substream->runtime; + struct atmel_runtime_data *prtd = rtd->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + int ret = 0; + + pr_debug("atmel-pcm:buffer_size = %ld," + "dma_area = %p, dma_bytes = %zu\n", + rtd->buffer_size, rtd->dma_area, rtd->dma_bytes); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->period_ptr = prtd->dma_buffer; + + ssc_writex(params->ssc->regs, params->pdc->xpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xcr, + prtd->period_size / params->pdc_xfer_size); + + prtd->period_ptr += prtd->period_size; + ssc_writex(params->ssc->regs, params->pdc->xnpr, + prtd->period_ptr); + ssc_writex(params->ssc->regs, params->pdc->xncr, + prtd->period_size / params->pdc_xfer_size); + + pr_debug("atmel-pcm: trigger: " + "period_ptr=%lx, xpr=%u, " + "xcr=%u, xnpr=%u, xncr=%u\n", + (unsigned long)prtd->period_ptr, + ssc_readx(params->ssc->regs, params->pdc->xpr), + ssc_readx(params->ssc->regs, params->pdc->xcr), + ssc_readx(params->ssc->regs, params->pdc->xnpr), + ssc_readx(params->ssc->regs, params->pdc->xncr)); + + ssc_writex(params->ssc->regs, SSC_IER, + params->mask->ssc_endx | params->mask->ssc_endbuf); + ssc_writex(params->ssc->regs, SSC_PDC_PTCR, + params->mask->pdc_enable); + + pr_debug("sr=%u imr=%u\n", + ssc_readx(params->ssc->regs, SSC_SR), + ssc_readx(params->ssc->regs, SSC_IER)); + break; /* SNDRV_PCM_TRIGGER_START */ + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_disable); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, + params->mask->pdc_enable); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t atmel_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct atmel_runtime_data *prtd = runtime->private_data; + struct atmel_pcm_dma_params *params = prtd->params; + dma_addr_t ptr; + snd_pcm_uframes_t x; + + ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr); + x = bytes_to_frames(runtime, ptr - prtd->dma_buffer); + + if (x == runtime->buffer_size) + x = 0; + + return x; +} + +static int atmel_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct atmel_runtime_data *prtd; + int ret = 0; + + snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware); + + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL); + if (prtd == NULL) { + ret = -ENOMEM; + goto out; + } + runtime->private_data = prtd; + + out: + return ret; +} + +static int atmel_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct atmel_runtime_data *prtd = substream->runtime->private_data; + + kfree(prtd); + return 0; +} + +static const struct snd_soc_component_driver atmel_soc_platform = { + .open = atmel_pcm_open, + .close = atmel_pcm_close, + .hw_params = atmel_pcm_hw_params, + .hw_free = atmel_pcm_hw_free, + .prepare = atmel_pcm_prepare, + .trigger = atmel_pcm_trigger, + .pointer = atmel_pcm_pointer, + .mmap = atmel_pcm_mmap, + .pcm_construct = atmel_pcm_new, + .pcm_destruct = atmel_pcm_free, +}; + +int atmel_pcm_pdc_platform_register(struct device *dev) +{ + return devm_snd_soc_register_component(dev, &atmel_soc_platform, + NULL, 0); +} +EXPORT_SYMBOL(atmel_pcm_pdc_platform_register); + +MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>"); +MODULE_DESCRIPTION("Atmel PCM module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/atmel-pcm.h b/sound/soc/atmel/atmel-pcm.h new file mode 100644 index 000000000..2e648748e --- /dev/null +++ b/sound/soc/atmel/atmel-pcm.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * at91-pcm.h - ALSA PCM interface for the Atmel AT91 SoC. + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com> + * + * Based on at91-pcm. by: + * Frank Mandarino <fmandarino@endrelia.com> + * Copyright 2006 Endrelia Technologies Inc. + * + * Based on pxa2xx-pcm.c by: + * + * Author: Nicolas Pitre + * Created: Nov 30, 2004 + * Copyright: (C) 2004 MontaVista Software, Inc. + */ + +#ifndef _ATMEL_PCM_H +#define _ATMEL_PCM_H + +#include <linux/atmel-ssc.h> + +#define ATMEL_SSC_DMABUF_SIZE (64 * 1024) + +/* + * Registers and status bits that are required by the PCM driver. + */ +struct atmel_pdc_regs { + unsigned int xpr; /* PDC recv/trans pointer */ + unsigned int xcr; /* PDC recv/trans counter */ + unsigned int xnpr; /* PDC next recv/trans pointer */ + unsigned int xncr; /* PDC next recv/trans counter */ + unsigned int ptcr; /* PDC transfer control */ +}; + +struct atmel_ssc_mask { + u32 ssc_enable; /* SSC recv/trans enable */ + u32 ssc_disable; /* SSC recv/trans disable */ + u32 ssc_error; /* SSC error conditions */ + u32 ssc_endx; /* SSC ENDTX or ENDRX */ + u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */ + u32 pdc_enable; /* PDC recv/trans enable */ + u32 pdc_disable; /* PDC recv/trans disable */ +}; + +/* + * This structure, shared between the PCM driver and the interface, + * contains all information required by the PCM driver to perform the + * PDC DMA operation. All fields except dma_intr_handler() are initialized + * by the interface. The dma_intr_handler() pointer is set by the PCM + * driver and called by the interface SSC interrupt handler if it is + * non-NULL. + */ +struct atmel_pcm_dma_params { + char *name; /* stream identifier */ + int pdc_xfer_size; /* PDC counter increment in bytes */ + struct ssc_device *ssc; /* SSC device for stream */ + struct atmel_pdc_regs *pdc; /* PDC receive or transmit registers */ + struct atmel_ssc_mask *mask; /* SSC & PDC status bits */ + struct snd_pcm_substream *substream; + void (*dma_intr_handler)(u32, struct snd_pcm_substream *); +}; + +/* + * SSC register access (since ssc_writel() / ssc_readl() require literal name) + */ +#define ssc_readx(base, reg) (__raw_readl((base) + (reg))) +#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg)) + +#if IS_ENABLED(CONFIG_SND_ATMEL_SOC_PDC) +int atmel_pcm_pdc_platform_register(struct device *dev); +#else +static inline int atmel_pcm_pdc_platform_register(struct device *dev) +{ + return 0; +} +#endif + +#if IS_ENABLED(CONFIG_SND_ATMEL_SOC_DMA) +int atmel_pcm_dma_platform_register(struct device *dev); +#else +static inline int atmel_pcm_dma_platform_register(struct device *dev) +{ + return 0; +} +#endif + +#endif /* _ATMEL_PCM_H */ diff --git a/sound/soc/atmel/atmel-pdmic.c b/sound/soc/atmel/atmel-pdmic.c new file mode 100644 index 000000000..049383e54 --- /dev/null +++ b/sound/soc/atmel/atmel-pdmic.c @@ -0,0 +1,713 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Atmel PDMIC driver + * + * Copyright (C) 2015 Atmel + * + * Author: Songjun Wu <songjun.wu@atmel.com> + */ + +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include "atmel-pdmic.h" + +struct atmel_pdmic_pdata { + u32 mic_min_freq; + u32 mic_max_freq; + s32 mic_offset; + const char *card_name; +}; + +struct atmel_pdmic { + dma_addr_t phy_base; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct device *dev; + int irq; + struct snd_pcm_substream *substream; + const struct atmel_pdmic_pdata *pdata; +}; + +static const struct of_device_id atmel_pdmic_of_match[] = { + { + .compatible = "atmel,sama5d2-pdmic", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, atmel_pdmic_of_match); + +#define PDMIC_OFFSET_MAX_VAL S16_MAX +#define PDMIC_OFFSET_MIN_VAL S16_MIN + +static struct atmel_pdmic_pdata *atmel_pdmic_dt_init(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct atmel_pdmic_pdata *pdata; + + if (!np) { + dev_err(dev, "device node not found\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + if (of_property_read_string(np, "atmel,model", &pdata->card_name)) + pdata->card_name = "PDMIC"; + + if (of_property_read_u32(np, "atmel,mic-min-freq", + &pdata->mic_min_freq)) { + dev_err(dev, "failed to get mic-min-freq\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_u32(np, "atmel,mic-max-freq", + &pdata->mic_max_freq)) { + dev_err(dev, "failed to get mic-max-freq\n"); + return ERR_PTR(-EINVAL); + } + + if (pdata->mic_max_freq < pdata->mic_min_freq) { + dev_err(dev, + "mic-max-freq should not be less than mic-min-freq\n"); + return ERR_PTR(-EINVAL); + } + + if (of_property_read_s32(np, "atmel,mic-offset", &pdata->mic_offset)) + pdata->mic_offset = 0; + + if (pdata->mic_offset > PDMIC_OFFSET_MAX_VAL) { + dev_warn(dev, + "mic-offset value %d is larger than the max value %d, the max value is specified\n", + pdata->mic_offset, PDMIC_OFFSET_MAX_VAL); + pdata->mic_offset = PDMIC_OFFSET_MAX_VAL; + } else if (pdata->mic_offset < PDMIC_OFFSET_MIN_VAL) { + dev_warn(dev, + "mic-offset value %d is less than the min value %d, the min value is specified\n", + pdata->mic_offset, PDMIC_OFFSET_MIN_VAL); + pdata->mic_offset = PDMIC_OFFSET_MIN_VAL; + } + + return pdata; +} + +/* cpu dai component */ +static int atmel_pdmic_cpu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + int ret; + + ret = clk_prepare_enable(dd->gclk); + if (ret) + return ret; + + ret = clk_prepare_enable(dd->pclk); + if (ret) { + clk_disable_unprepare(dd->gclk); + return ret; + } + + /* Clear all bits in the Control Register(PDMIC_CR) */ + regmap_write(dd->regmap, PDMIC_CR, 0); + + dd->substream = substream; + + /* Enable the overrun error interrupt */ + regmap_write(dd->regmap, PDMIC_IER, PDMIC_IER_OVRE); + + return 0; +} + +static void atmel_pdmic_cpu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + + /* Disable the overrun error interrupt */ + regmap_write(dd->regmap, PDMIC_IDR, PDMIC_IDR_OVRE); + + clk_disable_unprepare(dd->gclk); + clk_disable_unprepare(dd->pclk); +} + +static int atmel_pdmic_cpu_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = cpu_dai->component; + u32 val; + int ret; + + /* Clean the PDMIC Converted Data Register */ + ret = regmap_read(dd->regmap, PDMIC_CDR, &val); + if (ret < 0) + return 0; + + ret = snd_soc_component_update_bits(component, PDMIC_CR, + PDMIC_CR_ENPDM_MASK, + PDMIC_CR_ENPDM_DIS << + PDMIC_CR_ENPDM_SHIFT); + if (ret < 0) + return ret; + + return 0; +} + +#define ATMEL_PDMIC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) + +/* platform */ +#define ATMEL_PDMIC_MAX_BUF_SIZE (64 * 1024) +#define ATMEL_PDMIC_PREALLOC_BUF_SIZE ATMEL_PDMIC_MAX_BUF_SIZE + +static const struct snd_pcm_hardware atmel_pdmic_hw = { + .info = SNDRV_PCM_INFO_MMAP + | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_INTERLEAVED + | SNDRV_PCM_INFO_RESUME + | SNDRV_PCM_INFO_PAUSE, + .formats = ATMEL_PDMIC_FORMATS, + .buffer_bytes_max = ATMEL_PDMIC_MAX_BUF_SIZE, + .period_bytes_min = 256, + .period_bytes_max = 32 * 1024, + .periods_min = 2, + .periods_max = 256, +}; + +static int +atmel_pdmic_platform_configure_dma(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct dma_slave_config *slave_config) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + int ret; + + ret = snd_hwparams_to_dma_slave_config(substream, params, + slave_config); + if (ret) { + dev_err(dd->dev, + "hw params to dma slave configure failed\n"); + return ret; + } + + slave_config->src_addr = dd->phy_base + PDMIC_CDR; + slave_config->src_maxburst = 1; + slave_config->dst_maxburst = 1; + + return 0; +} + +static const struct snd_dmaengine_pcm_config +atmel_pdmic_dmaengine_pcm_config = { + .prepare_slave_config = atmel_pdmic_platform_configure_dma, + .pcm_hardware = &atmel_pdmic_hw, + .prealloc_buffer_size = ATMEL_PDMIC_PREALLOC_BUF_SIZE, +}; + +/* codec */ +/* Mic Gain = dgain * 2^(-scale) */ +struct mic_gain { + unsigned int dgain; + unsigned int scale; +}; + +/* range from -90 dB to 90 dB */ +static const struct mic_gain mic_gain_table[] = { +{ 1, 15}, { 1, 14}, /* -90, -84 dB */ +{ 3, 15}, { 1, 13}, { 3, 14}, { 1, 12}, /* -81, -78, -75, -72 dB */ +{ 5, 14}, { 13, 15}, /* -70, -68 dB */ +{ 9, 14}, { 21, 15}, { 23, 15}, { 13, 14}, /* -65 ~ -62 dB */ +{ 29, 15}, { 33, 15}, { 37, 15}, { 41, 15}, /* -61 ~ -58 dB */ +{ 23, 14}, { 13, 13}, { 58, 15}, { 65, 15}, /* -57 ~ -54 dB */ +{ 73, 15}, { 41, 14}, { 23, 13}, { 13, 12}, /* -53 ~ -50 dB */ +{ 29, 13}, { 65, 14}, { 73, 14}, { 41, 13}, /* -49 ~ -46 dB */ +{ 23, 12}, { 207, 15}, { 29, 12}, { 65, 13}, /* -45 ~ -42 dB */ +{ 73, 13}, { 41, 12}, { 23, 11}, { 413, 15}, /* -41 ~ -38 dB */ +{ 463, 15}, { 519, 15}, { 583, 15}, { 327, 14}, /* -37 ~ -34 dB */ +{ 367, 14}, { 823, 15}, { 231, 13}, { 1036, 15}, /* -33 ~ -30 dB */ +{ 1163, 15}, { 1305, 15}, { 183, 12}, { 1642, 15}, /* -29 ~ -26 dB */ +{ 1843, 15}, { 2068, 15}, { 145, 11}, { 2603, 15}, /* -25 ~ -22 dB */ +{ 365, 12}, { 3277, 15}, { 3677, 15}, { 4125, 15}, /* -21 ~ -18 dB */ +{ 4629, 15}, { 5193, 15}, { 5827, 15}, { 3269, 14}, /* -17 ~ -14 dB */ +{ 917, 12}, { 8231, 15}, { 9235, 15}, { 5181, 14}, /* -13 ~ -10 dB */ +{11627, 15}, {13045, 15}, {14637, 15}, {16423, 15}, /* -9 ~ -6 dB */ +{18427, 15}, {20675, 15}, { 5799, 13}, {26029, 15}, /* -5 ~ -2 dB */ +{ 7301, 13}, { 1, 0}, {18383, 14}, {10313, 13}, /* -1 ~ 2 dB */ +{23143, 14}, {25967, 14}, {29135, 14}, {16345, 13}, /* 3 ~ 6 dB */ +{ 4585, 11}, {20577, 13}, { 1443, 9}, {25905, 13}, /* 7 ~ 10 dB */ +{14533, 12}, { 8153, 11}, { 2287, 9}, {20529, 12}, /* 11 ~ 14 dB */ +{11517, 11}, { 6461, 10}, {28997, 12}, { 4067, 9}, /* 15 ~ 18 dB */ +{18253, 11}, { 10, 0}, {22979, 11}, {25783, 11}, /* 19 ~ 22 dB */ +{28929, 11}, {32459, 11}, { 9105, 9}, {20431, 10}, /* 23 ~ 26 dB */ +{22925, 10}, {12861, 9}, { 7215, 8}, {16191, 9}, /* 27 ~ 30 dB */ +{ 9083, 8}, {20383, 9}, {11435, 8}, { 6145, 7}, /* 31 ~ 34 dB */ +{ 3599, 6}, {32305, 9}, {18123, 8}, {20335, 8}, /* 35 ~ 38 dB */ +{ 713, 3}, { 100, 0}, { 7181, 6}, { 8057, 6}, /* 39 ~ 42 dB */ +{ 565, 2}, {20287, 7}, {11381, 6}, {25539, 7}, /* 43 ~ 46 dB */ +{ 1791, 3}, { 4019, 4}, { 9019, 5}, {20239, 6}, /* 47 ~ 50 dB */ +{ 5677, 4}, {25479, 6}, { 7147, 4}, { 8019, 4}, /* 51 ~ 54 dB */ +{17995, 5}, {20191, 5}, {11327, 4}, {12709, 4}, /* 55 ~ 58 dB */ +{ 3565, 2}, { 1000, 0}, { 1122, 0}, { 1259, 0}, /* 59 ~ 62 dB */ +{ 2825, 1}, {12679, 3}, { 7113, 2}, { 7981, 2}, /* 63 ~ 66 dB */ +{ 8955, 2}, {20095, 3}, {22547, 3}, {12649, 2}, /* 67 ~ 70 dB */ +{28385, 3}, { 3981, 0}, {17867, 2}, {20047, 2}, /* 71 ~ 74 dB */ +{11247, 1}, {12619, 1}, {14159, 1}, {31773, 2}, /* 75 ~ 78 dB */ +{17825, 1}, {10000, 0}, {11220, 0}, {12589, 0}, /* 79 ~ 82 dB */ +{28251, 1}, {15849, 0}, {17783, 0}, {19953, 0}, /* 83 ~ 86 dB */ +{22387, 0}, {25119, 0}, {28184, 0}, {31623, 0}, /* 87 ~ 90 dB */ +}; + +static const DECLARE_TLV_DB_RANGE(mic_gain_tlv, + 0, 1, TLV_DB_SCALE_ITEM(-9000, 600, 0), + 2, 5, TLV_DB_SCALE_ITEM(-8100, 300, 0), + 6, 7, TLV_DB_SCALE_ITEM(-7000, 200, 0), + 8, ARRAY_SIZE(mic_gain_table)-1, TLV_DB_SCALE_ITEM(-6500, 100, 0), +); + +static int pdmic_get_mic_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + unsigned int dgain_val, scale_val; + int i; + + dgain_val = (snd_soc_component_read(component, PDMIC_DSPR1) & PDMIC_DSPR1_DGAIN_MASK) + >> PDMIC_DSPR1_DGAIN_SHIFT; + + scale_val = (snd_soc_component_read(component, PDMIC_DSPR0) & PDMIC_DSPR0_SCALE_MASK) + >> PDMIC_DSPR0_SCALE_SHIFT; + + for (i = 0; i < ARRAY_SIZE(mic_gain_table); i++) { + if ((mic_gain_table[i].dgain == dgain_val) && + (mic_gain_table[i].scale == scale_val)) + ucontrol->value.integer.value[0] = i; + } + + return 0; +} + +static int pdmic_put_mic_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + int max = mc->max; + unsigned int val; + int ret; + + val = ucontrol->value.integer.value[0]; + + if (val > max) + return -EINVAL; + + ret = snd_soc_component_update_bits(component, PDMIC_DSPR1, PDMIC_DSPR1_DGAIN_MASK, + mic_gain_table[val].dgain << PDMIC_DSPR1_DGAIN_SHIFT); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, PDMIC_DSPR0, PDMIC_DSPR0_SCALE_MASK, + mic_gain_table[val].scale << PDMIC_DSPR0_SCALE_SHIFT); + if (ret < 0) + return ret; + + return 0; +} + +static const struct snd_kcontrol_new atmel_pdmic_snd_controls[] = { +SOC_SINGLE_EXT_TLV("Mic Capture Volume", PDMIC_DSPR1, PDMIC_DSPR1_DGAIN_SHIFT, + ARRAY_SIZE(mic_gain_table)-1, 0, + pdmic_get_mic_volsw, pdmic_put_mic_volsw, mic_gain_tlv), + +SOC_SINGLE("High Pass Filter Switch", PDMIC_DSPR0, + PDMIC_DSPR0_HPFBYP_SHIFT, 1, 1), + +SOC_SINGLE("SINCC Filter Switch", PDMIC_DSPR0, PDMIC_DSPR0_SINBYP_SHIFT, 1, 1), +}; + +static int atmel_pdmic_component_probe(struct snd_soc_component *component) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(component); + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(card); + + snd_soc_component_update_bits(component, PDMIC_DSPR1, PDMIC_DSPR1_OFFSET_MASK, + (u32)(dd->pdata->mic_offset << PDMIC_DSPR1_OFFSET_SHIFT)); + + return 0; +} + +#define PDMIC_MR_PRESCAL_MAX_VAL 127 + +static int +atmel_pdmic_cpu_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_component *component = cpu_dai->component; + unsigned int rate_min = substream->runtime->hw.rate_min; + unsigned int rate_max = substream->runtime->hw.rate_max; + int fs = params_rate(params); + int bits = params_width(params); + unsigned long pclk_rate, gclk_rate; + unsigned int f_pdmic; + u32 mr_val, dspr0_val, pclk_prescal, gclk_prescal; + + if (params_channels(params) != 1) { + dev_err(component->dev, + "only supports one channel\n"); + return -EINVAL; + } + + if ((fs < rate_min) || (fs > rate_max)) { + dev_err(component->dev, + "sample rate is %dHz, min rate is %dHz, max rate is %dHz\n", + fs, rate_min, rate_max); + + return -EINVAL; + } + + switch (bits) { + case 16: + dspr0_val = (PDMIC_DSPR0_SIZE_16_BITS + << PDMIC_DSPR0_SIZE_SHIFT); + break; + case 32: + dspr0_val = (PDMIC_DSPR0_SIZE_32_BITS + << PDMIC_DSPR0_SIZE_SHIFT); + break; + default: + return -EINVAL; + } + + if ((fs << 7) > (rate_max << 6)) { + f_pdmic = fs << 6; + dspr0_val |= PDMIC_DSPR0_OSR_64 << PDMIC_DSPR0_OSR_SHIFT; + } else { + f_pdmic = fs << 7; + dspr0_val |= PDMIC_DSPR0_OSR_128 << PDMIC_DSPR0_OSR_SHIFT; + } + + pclk_rate = clk_get_rate(dd->pclk); + gclk_rate = clk_get_rate(dd->gclk); + + /* PRESCAL = SELCK/(2*f_pdmic) - 1*/ + pclk_prescal = (u32)(pclk_rate/(f_pdmic << 1)) - 1; + gclk_prescal = (u32)(gclk_rate/(f_pdmic << 1)) - 1; + + if ((pclk_prescal > PDMIC_MR_PRESCAL_MAX_VAL) || + (gclk_rate/((gclk_prescal + 1) << 1) < + pclk_rate/((pclk_prescal + 1) << 1))) { + mr_val = gclk_prescal << PDMIC_MR_PRESCAL_SHIFT; + mr_val |= PDMIC_MR_CLKS_GCK << PDMIC_MR_CLKS_SHIFT; + } else { + mr_val = pclk_prescal << PDMIC_MR_PRESCAL_SHIFT; + mr_val |= PDMIC_MR_CLKS_PCK << PDMIC_MR_CLKS_SHIFT; + } + + snd_soc_component_update_bits(component, PDMIC_MR, + PDMIC_MR_PRESCAL_MASK | PDMIC_MR_CLKS_MASK, mr_val); + + snd_soc_component_update_bits(component, PDMIC_DSPR0, + PDMIC_DSPR0_OSR_MASK | PDMIC_DSPR0_SIZE_MASK, dspr0_val); + + return 0; +} + +static int atmel_pdmic_cpu_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_component *component = cpu_dai->component; + u32 val; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = PDMIC_CR_ENPDM_EN << PDMIC_CR_ENPDM_SHIFT; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val = PDMIC_CR_ENPDM_DIS << PDMIC_CR_ENPDM_SHIFT; + break; + default: + return -EINVAL; + } + + snd_soc_component_update_bits(component, PDMIC_CR, PDMIC_CR_ENPDM_MASK, val); + + return 0; +} + +static const struct snd_soc_dai_ops atmel_pdmic_cpu_dai_ops = { + .startup = atmel_pdmic_cpu_dai_startup, + .shutdown = atmel_pdmic_cpu_dai_shutdown, + .prepare = atmel_pdmic_cpu_dai_prepare, + .hw_params = atmel_pdmic_cpu_dai_hw_params, + .trigger = atmel_pdmic_cpu_dai_trigger, +}; + + +static struct snd_soc_dai_driver atmel_pdmic_cpu_dai = { + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = ATMEL_PDMIC_FORMATS, + }, + .ops = &atmel_pdmic_cpu_dai_ops, +}; + +static const struct snd_soc_component_driver atmel_pdmic_cpu_dai_component = { + .name = "atmel-pdmic", + .probe = atmel_pdmic_component_probe, + .controls = atmel_pdmic_snd_controls, + .num_controls = ARRAY_SIZE(atmel_pdmic_snd_controls), + .idle_bias_on = 1, + .use_pmdown_time = 1, +}; + +/* ASoC sound card */ +static int atmel_pdmic_asoc_card_init(struct device *dev, + struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link; + struct atmel_pdmic *dd = snd_soc_card_get_drvdata(card); + struct snd_soc_dai_link_component *comp; + + dai_link = devm_kzalloc(dev, sizeof(*dai_link), GFP_KERNEL); + if (!dai_link) + return -ENOMEM; + + comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL); + if (!comp) + return -ENOMEM; + + dai_link->cpus = &comp[0]; + dai_link->codecs = &comp[1]; + dai_link->platforms = &comp[2]; + + dai_link->num_cpus = 1; + dai_link->num_codecs = 1; + dai_link->num_platforms = 1; + + dai_link->name = "PDMIC"; + dai_link->stream_name = "PDMIC PCM"; + dai_link->codecs->dai_name = "snd-soc-dummy-dai"; + dai_link->cpus->dai_name = dev_name(dev); + dai_link->codecs->name = "snd-soc-dummy"; + dai_link->platforms->name = dev_name(dev); + + card->dai_link = dai_link; + card->num_links = 1; + card->name = dd->pdata->card_name; + card->dev = dev; + + return 0; +} + +static void atmel_pdmic_get_sample_rate(struct atmel_pdmic *dd, + unsigned int *rate_min, unsigned int *rate_max) +{ + u32 mic_min_freq = dd->pdata->mic_min_freq; + u32 mic_max_freq = dd->pdata->mic_max_freq; + u32 clk_max_rate = (u32)(clk_get_rate(dd->pclk) >> 1); + u32 clk_min_rate = (u32)(clk_get_rate(dd->gclk) >> 8); + + if (mic_max_freq > clk_max_rate) + mic_max_freq = clk_max_rate; + + if (mic_min_freq < clk_min_rate) + mic_min_freq = clk_min_rate; + + *rate_min = DIV_ROUND_CLOSEST(mic_min_freq, 128); + *rate_max = mic_max_freq >> 6; +} + +/* PDMIC interrupt handler */ +static irqreturn_t atmel_pdmic_interrupt(int irq, void *dev_id) +{ + struct atmel_pdmic *dd = (struct atmel_pdmic *)dev_id; + u32 pdmic_isr; + irqreturn_t ret = IRQ_NONE; + + regmap_read(dd->regmap, PDMIC_ISR, &pdmic_isr); + + if (pdmic_isr & PDMIC_ISR_OVRE) { + regmap_update_bits(dd->regmap, PDMIC_CR, PDMIC_CR_ENPDM_MASK, + PDMIC_CR_ENPDM_DIS << PDMIC_CR_ENPDM_SHIFT); + + snd_pcm_stop_xrun(dd->substream); + + ret = IRQ_HANDLED; + } + + return ret; +} + +/* regmap configuration */ +#define ATMEL_PDMIC_REG_MAX 0x124 +static const struct regmap_config atmel_pdmic_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ATMEL_PDMIC_REG_MAX, +}; + +static int atmel_pdmic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct atmel_pdmic *dd; + struct resource *res; + void __iomem *io_base; + const struct atmel_pdmic_pdata *pdata; + struct snd_soc_card *card; + unsigned int rate_min, rate_max; + int ret; + + pdata = atmel_pdmic_dt_init(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) + return -ENOMEM; + + dd->pdata = pdata; + dd->dev = dev; + + dd->irq = platform_get_irq(pdev, 0); + if (dd->irq < 0) + return dd->irq; + + dd->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(dd->pclk)) { + ret = PTR_ERR(dd->pclk); + dev_err(dev, "failed to get peripheral clock: %d\n", ret); + return ret; + } + + dd->gclk = devm_clk_get(dev, "gclk"); + if (IS_ERR(dd->gclk)) { + ret = PTR_ERR(dd->gclk); + dev_err(dev, "failed to get GCK: %d\n", ret); + return ret; + } + + /* The gclk clock frequency must always be three times + * lower than the pclk clock frequency + */ + ret = clk_set_rate(dd->gclk, clk_get_rate(dd->pclk)/3); + if (ret) { + dev_err(dev, "failed to set GCK clock rate: %d\n", ret); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + dd->phy_base = res->start; + + dd->regmap = devm_regmap_init_mmio(dev, io_base, + &atmel_pdmic_regmap_config); + if (IS_ERR(dd->regmap)) { + ret = PTR_ERR(dd->regmap); + dev_err(dev, "failed to init register map: %d\n", ret); + return ret; + } + + ret = devm_request_irq(dev, dd->irq, atmel_pdmic_interrupt, 0, + "PDMIC", (void *)dd); + if (ret < 0) { + dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", + dd->irq, ret); + return ret; + } + + /* Get the minimal and maximal sample rate that the microphone supports */ + atmel_pdmic_get_sample_rate(dd, &rate_min, &rate_max); + + /* register cpu dai */ + atmel_pdmic_cpu_dai.capture.rate_min = rate_min; + atmel_pdmic_cpu_dai.capture.rate_max = rate_max; + ret = devm_snd_soc_register_component(dev, + &atmel_pdmic_cpu_dai_component, + &atmel_pdmic_cpu_dai, 1); + if (ret) { + dev_err(dev, "could not register CPU DAI: %d\n", ret); + return ret; + } + + /* register platform */ + ret = devm_snd_dmaengine_pcm_register(dev, + &atmel_pdmic_dmaengine_pcm_config, + 0); + if (ret) { + dev_err(dev, "could not register platform: %d\n", ret); + return ret; + } + + /* register sound card */ + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) { + ret = -ENOMEM; + goto unregister_codec; + } + + snd_soc_card_set_drvdata(card, dd); + + ret = atmel_pdmic_asoc_card_init(dev, card); + if (ret) { + dev_err(dev, "failed to init sound card: %d\n", ret); + goto unregister_codec; + } + + ret = devm_snd_soc_register_card(dev, card); + if (ret) { + dev_err(dev, "failed to register sound card: %d\n", ret); + goto unregister_codec; + } + + return 0; + +unregister_codec: + return ret; +} + +static int atmel_pdmic_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver atmel_pdmic_driver = { + .driver = { + .name = "atmel-pdmic", + .of_match_table = of_match_ptr(atmel_pdmic_of_match), + .pm = &snd_soc_pm_ops, + }, + .probe = atmel_pdmic_probe, + .remove = atmel_pdmic_remove, +}; +module_platform_driver(atmel_pdmic_driver); + +MODULE_DESCRIPTION("Atmel PDMIC driver under ALSA SoC architecture"); +MODULE_AUTHOR("Songjun Wu <songjun.wu@atmel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/atmel/atmel-pdmic.h b/sound/soc/atmel/atmel-pdmic.h new file mode 100644 index 000000000..1dd351871 --- /dev/null +++ b/sound/soc/atmel/atmel-pdmic.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ATMEL_PDMIC_H_ +#define __ATMEL_PDMIC_H_ + +#include <linux/bitops.h> + +#define PDMIC_CR 0x00000000 + +#define PDMIC_CR_SWRST 0x1 +#define PDMIC_CR_SWRST_MASK BIT(0) +#define PDMIC_CR_SWRST_SHIFT (0) + +#define PDMIC_CR_ENPDM_DIS 0x0 +#define PDMIC_CR_ENPDM_EN 0x1 +#define PDMIC_CR_ENPDM_MASK BIT(4) +#define PDMIC_CR_ENPDM_SHIFT (4) + +#define PDMIC_MR 0x00000004 + +#define PDMIC_MR_CLKS_PCK 0x0 +#define PDMIC_MR_CLKS_GCK 0x1 +#define PDMIC_MR_CLKS_MASK BIT(4) +#define PDMIC_MR_CLKS_SHIFT (4) + +#define PDMIC_MR_PRESCAL_MASK GENMASK(14, 8) +#define PDMIC_MR_PRESCAL_SHIFT (8) + +#define PDMIC_CDR 0x00000014 + +#define PDMIC_IER 0x00000018 +#define PDMIC_IER_OVRE BIT(25) + +#define PDMIC_IDR 0x0000001c +#define PDMIC_IDR_OVRE BIT(25) + +#define PDMIC_IMR 0x00000020 + +#define PDMIC_ISR 0x00000024 +#define PDMIC_ISR_OVRE BIT(25) + +#define PDMIC_DSPR0 0x00000058 + +#define PDMIC_DSPR0_HPFBYP_DIS 0x1 +#define PDMIC_DSPR0_HPFBYP_EN 0x0 +#define PDMIC_DSPR0_HPFBYP_MASK BIT(1) +#define PDMIC_DSPR0_HPFBYP_SHIFT (1) + +#define PDMIC_DSPR0_SINBYP_DIS 0x1 +#define PDMIC_DSPR0_SINBYP_EN 0x0 +#define PDMIC_DSPR0_SINBYP_MASK BIT(2) +#define PDMIC_DSPR0_SINBYP_SHIFT (2) + +#define PDMIC_DSPR0_SIZE_16_BITS 0x0 +#define PDMIC_DSPR0_SIZE_32_BITS 0x1 +#define PDMIC_DSPR0_SIZE_MASK BIT(3) +#define PDMIC_DSPR0_SIZE_SHIFT (3) + +#define PDMIC_DSPR0_OSR_128 0x0 +#define PDMIC_DSPR0_OSR_64 0x1 +#define PDMIC_DSPR0_OSR_MASK GENMASK(6, 4) +#define PDMIC_DSPR0_OSR_SHIFT (4) + +#define PDMIC_DSPR0_SCALE_MASK GENMASK(11, 8) +#define PDMIC_DSPR0_SCALE_SHIFT (8) + +#define PDMIC_DSPR0_SHIFT_MASK GENMASK(15, 12) +#define PDMIC_DSPR0_SHIFT_SHIFT (12) + +#define PDMIC_DSPR1 0x0000005c + +#define PDMIC_DSPR1_DGAIN_MASK GENMASK(14, 0) +#define PDMIC_DSPR1_DGAIN_SHIFT (0) + +#define PDMIC_DSPR1_OFFSET_MASK GENMASK(31, 16) +#define PDMIC_DSPR1_OFFSET_SHIFT (16) + +#define PDMIC_WPMR 0x000000e4 + +#define PDMIC_WPSR 0x000000e8 + +#endif diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c new file mode 100644 index 000000000..97533412c --- /dev/null +++ b/sound/soc/atmel/atmel_ssc_dai.c @@ -0,0 +1,927 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * atmel_ssc_dai.c -- ALSA SoC ATMEL SSC Audio Layer Platform driver + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Author: Sedji Gaouaou <sedji.gaouaou@atmel.com> + * ATMEL CORP. + * + * Based on at91-ssc.c by + * Frank Mandarino <fmandarino@endrelia.com> + * Based on pxa2xx Platform drivers by + * Liam Girdwood <lrg@slimlogic.co.uk> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/atmel_pdc.h> + +#include <linux/atmel-ssc.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "atmel-pcm.h" +#include "atmel_ssc_dai.h" + + +#define NUM_SSC_DEVICES 3 + +/* + * SSC PDC registers required by the PCM DMA engine. + */ +static struct atmel_pdc_regs pdc_tx_reg = { + .xpr = ATMEL_PDC_TPR, + .xcr = ATMEL_PDC_TCR, + .xnpr = ATMEL_PDC_TNPR, + .xncr = ATMEL_PDC_TNCR, +}; + +static struct atmel_pdc_regs pdc_rx_reg = { + .xpr = ATMEL_PDC_RPR, + .xcr = ATMEL_PDC_RCR, + .xnpr = ATMEL_PDC_RNPR, + .xncr = ATMEL_PDC_RNCR, +}; + +/* + * SSC & PDC status bits for transmit and receive. + */ +static struct atmel_ssc_mask ssc_tx_mask = { + .ssc_enable = SSC_BIT(CR_TXEN), + .ssc_disable = SSC_BIT(CR_TXDIS), + .ssc_endx = SSC_BIT(SR_ENDTX), + .ssc_endbuf = SSC_BIT(SR_TXBUFE), + .ssc_error = SSC_BIT(SR_OVRUN), + .pdc_enable = ATMEL_PDC_TXTEN, + .pdc_disable = ATMEL_PDC_TXTDIS, +}; + +static struct atmel_ssc_mask ssc_rx_mask = { + .ssc_enable = SSC_BIT(CR_RXEN), + .ssc_disable = SSC_BIT(CR_RXDIS), + .ssc_endx = SSC_BIT(SR_ENDRX), + .ssc_endbuf = SSC_BIT(SR_RXBUFF), + .ssc_error = SSC_BIT(SR_OVRUN), + .pdc_enable = ATMEL_PDC_RXTEN, + .pdc_disable = ATMEL_PDC_RXTDIS, +}; + + +/* + * DMA parameters. + */ +static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = { + {{ + .name = "SSC0 PCM out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC0 PCM in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + } }, + {{ + .name = "SSC1 PCM out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC1 PCM in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + } }, + {{ + .name = "SSC2 PCM out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC2 PCM in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + } }, +}; + + +static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = { + { + .name = "ssc0", + .dir_mask = SSC_DIR_MASK_UNUSED, + .initialized = 0, + }, + { + .name = "ssc1", + .dir_mask = SSC_DIR_MASK_UNUSED, + .initialized = 0, + }, + { + .name = "ssc2", + .dir_mask = SSC_DIR_MASK_UNUSED, + .initialized = 0, + }, +}; + + +/* + * SSC interrupt handler. Passes PDC interrupts to the DMA + * interrupt handler in the PCM driver. + */ +static irqreturn_t atmel_ssc_interrupt(int irq, void *dev_id) +{ + struct atmel_ssc_info *ssc_p = dev_id; + struct atmel_pcm_dma_params *dma_params; + u32 ssc_sr; + u32 ssc_substream_mask; + int i; + + ssc_sr = (unsigned long)ssc_readl(ssc_p->ssc->regs, SR) + & (unsigned long)ssc_readl(ssc_p->ssc->regs, IMR); + + /* + * Loop through the substreams attached to this SSC. If + * a DMA-related interrupt occurred on that substream, call + * the DMA interrupt handler function, if one has been + * registered in the dma_params structure by the PCM driver. + */ + for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) { + dma_params = ssc_p->dma_params[i]; + + if ((dma_params != NULL) && + (dma_params->dma_intr_handler != NULL)) { + ssc_substream_mask = (dma_params->mask->ssc_endx | + dma_params->mask->ssc_endbuf); + if (ssc_sr & ssc_substream_mask) { + dma_params->dma_intr_handler(ssc_sr, + dma_params-> + substream); + } + } + } + + return IRQ_HANDLED; +} + +/* + * When the bit clock is input, limit the maximum rate according to the + * Serial Clock Ratio Considerations section from the SSC documentation: + * + * The Transmitter and the Receiver can be programmed to operate + * with the clock signals provided on either the TK or RK pins. + * This allows the SSC to support many slave-mode data transfers. + * In this case, the maximum clock speed allowed on the RK pin is: + * - Peripheral clock divided by 2 if Receiver Frame Synchro is input + * - Peripheral clock divided by 3 if Receiver Frame Synchro is output + * In addition, the maximum clock speed allowed on the TK pin is: + * - Peripheral clock divided by 6 if Transmit Frame Synchro is input + * - Peripheral clock divided by 2 if Transmit Frame Synchro is output + * + * When the bit clock is output, limit the rate according to the + * SSC divider restrictions. + */ +static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct atmel_ssc_info *ssc_p = rule->private; + struct ssc_device *ssc = ssc_p->ssc; + struct snd_interval *i = hw_param_interval(params, rule->var); + struct snd_interval t; + struct snd_ratnum r = { + .den_min = 1, + .den_max = 4095, + .den_step = 1, + }; + unsigned int num = 0, den = 0; + int frame_size; + int mck_div = 2; + int ret; + + frame_size = snd_soc_params_to_frame_size(params); + if (frame_size < 0) + return frame_size; + + switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFS: + if ((ssc_p->dir_mask & SSC_DIR_MASK_CAPTURE) + && ssc->clk_from_rk_pin) + /* Receiver Frame Synchro (i.e. capture) + * is output (format is _CFS) and the RK pin + * is used for input (format is _CBM_). + */ + mck_div = 3; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + if ((ssc_p->dir_mask & SSC_DIR_MASK_PLAYBACK) + && !ssc->clk_from_rk_pin) + /* Transmit Frame Synchro (i.e. playback) + * is input (format is _CFM) and the TK pin + * is used for input (format _CBM_ but not + * using the RK pin). + */ + mck_div = 6; + break; + } + + switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + r.num = ssc_p->mck_rate / mck_div / frame_size; + + ret = snd_interval_ratnum(i, 1, &r, &num, &den); + if (ret >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) { + params->rate_num = num; + params->rate_den = den; + } + break; + + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBM_CFM: + t.min = 8000; + t.max = ssc_p->mck_rate / mck_div / frame_size; + t.openmin = t.openmax = 0; + t.integer = 0; + ret = snd_interval_refine(i, &t); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/*-------------------------------------------------------------------------*\ + * DAI functions +\*-------------------------------------------------------------------------*/ +/* + * Startup. Only that one substream allowed in each direction. + */ +static int atmel_ssc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + struct atmel_pcm_dma_params *dma_params; + int dir, dir_mask; + int ret; + + pr_debug("atmel_ssc_startup: SSC_SR=0x%x\n", + ssc_readl(ssc_p->ssc->regs, SR)); + + /* Enable PMC peripheral clock for this SSC */ + pr_debug("atmel_ssc_dai: Starting clock\n"); + ret = clk_enable(ssc_p->ssc->clk); + if (ret) + return ret; + + ssc_p->mck_rate = clk_get_rate(ssc_p->ssc->clk); + + /* Reset the SSC unless initialized to keep it in a clean state */ + if (!ssc_p->initialized) + ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST)); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dir = 0; + dir_mask = SSC_DIR_MASK_PLAYBACK; + } else { + dir = 1; + dir_mask = SSC_DIR_MASK_CAPTURE; + } + + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + atmel_ssc_hw_rule_rate, + ssc_p, + SNDRV_PCM_HW_PARAM_FRAME_BITS, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret < 0) { + dev_err(dai->dev, "Failed to specify rate rule: %d\n", ret); + return ret; + } + + dma_params = &ssc_dma_params[pdev->id][dir]; + dma_params->ssc = ssc_p->ssc; + dma_params->substream = substream; + + ssc_p->dma_params[dir] = dma_params; + + snd_soc_dai_set_dma_data(dai, substream, dma_params); + + if (ssc_p->dir_mask & dir_mask) + return -EBUSY; + + ssc_p->dir_mask |= dir_mask; + + return 0; +} + +/* + * Shutdown. Clear DMA parameters and shutdown the SSC if there + * are no other substreams open. + */ +static void atmel_ssc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + struct atmel_pcm_dma_params *dma_params; + int dir, dir_mask; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = 0; + else + dir = 1; + + dma_params = ssc_p->dma_params[dir]; + + if (dma_params != NULL) { + dma_params->ssc = NULL; + dma_params->substream = NULL; + ssc_p->dma_params[dir] = NULL; + } + + dir_mask = 1 << dir; + + ssc_p->dir_mask &= ~dir_mask; + if (!ssc_p->dir_mask) { + if (ssc_p->initialized) { + free_irq(ssc_p->ssc->irq, ssc_p); + ssc_p->initialized = 0; + } + + /* Reset the SSC */ + ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST)); + /* Clear the SSC dividers */ + ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0; + ssc_p->forced_divider = 0; + } + + /* Shutdown the SSC clock. */ + pr_debug("atmel_ssc_dai: Stopping clock\n"); + clk_disable(ssc_p->ssc->clk); +} + + +/* + * Record the DAI format for use in hw_params(). + */ +static int atmel_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct platform_device *pdev = to_platform_device(cpu_dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + + ssc_p->daifmt = fmt; + return 0; +} + +/* + * Record SSC clock dividers for use in hw_params(). + */ +static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct platform_device *pdev = to_platform_device(cpu_dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + + switch (div_id) { + case ATMEL_SSC_CMR_DIV: + /* + * The same master clock divider is used for both + * transmit and receive, so if a value has already + * been set, it must match this value. + */ + if (ssc_p->dir_mask != + (SSC_DIR_MASK_PLAYBACK | SSC_DIR_MASK_CAPTURE)) + ssc_p->cmr_div = div; + else if (ssc_p->cmr_div == 0) + ssc_p->cmr_div = div; + else + if (div != ssc_p->cmr_div) + return -EBUSY; + ssc_p->forced_divider |= BIT(ATMEL_SSC_CMR_DIV); + break; + + case ATMEL_SSC_TCMR_PERIOD: + ssc_p->tcmr_period = div; + ssc_p->forced_divider |= BIT(ATMEL_SSC_TCMR_PERIOD); + break; + + case ATMEL_SSC_RCMR_PERIOD: + ssc_p->rcmr_period = div; + ssc_p->forced_divider |= BIT(ATMEL_SSC_RCMR_PERIOD); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* Is the cpu-dai master of the frame clock? */ +static int atmel_ssc_cfs(struct atmel_ssc_info *ssc_p) +{ + switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFS: + return 1; + } + return 0; +} + +/* Is the cpu-dai master of the bit clock? */ +static int atmel_ssc_cbs(struct atmel_ssc_info *ssc_p) +{ + switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBS_CFS: + return 1; + } + return 0; +} + +/* + * Configure the SSC. + */ +static int atmel_ssc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + int id = pdev->id; + struct atmel_ssc_info *ssc_p = &ssc_info[id]; + struct ssc_device *ssc = ssc_p->ssc; + struct atmel_pcm_dma_params *dma_params; + int dir, channels, bits; + u32 tfmr, rfmr, tcmr, rcmr; + int ret; + int fslen, fslen_ext, fs_osync, fs_edge; + u32 cmr_div; + u32 tcmr_period; + u32 rcmr_period; + + /* + * Currently, there is only one set of dma params for + * each direction. If more are added, this code will + * have to be changed to select the proper set. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = 0; + else + dir = 1; + + /* + * If the cpu dai should provide BCLK, but noone has provided the + * divider needed for that to work, fall back to something sensible. + */ + cmr_div = ssc_p->cmr_div; + if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_CMR_DIV)) && + atmel_ssc_cbs(ssc_p)) { + int bclk_rate = snd_soc_params_to_bclk(params); + + if (bclk_rate < 0) { + dev_err(dai->dev, "unable to calculate cmr_div: %d\n", + bclk_rate); + return bclk_rate; + } + + cmr_div = DIV_ROUND_CLOSEST(ssc_p->mck_rate, 2 * bclk_rate); + } + + /* + * If the cpu dai should provide LRCLK, but noone has provided the + * dividers needed for that to work, fall back to something sensible. + */ + tcmr_period = ssc_p->tcmr_period; + rcmr_period = ssc_p->rcmr_period; + if (atmel_ssc_cfs(ssc_p)) { + int frame_size = snd_soc_params_to_frame_size(params); + + if (frame_size < 0) { + dev_err(dai->dev, + "unable to calculate tx/rx cmr_period: %d\n", + frame_size); + return frame_size; + } + + if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_TCMR_PERIOD))) + tcmr_period = frame_size / 2 - 1; + if (!(ssc_p->forced_divider & BIT(ATMEL_SSC_RCMR_PERIOD))) + rcmr_period = frame_size / 2 - 1; + } + + dma_params = ssc_p->dma_params[dir]; + + channels = params_channels(params); + + /* + * Determine sample size in bits and the PDC increment. + */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + bits = 8; + dma_params->pdc_xfer_size = 1; + break; + case SNDRV_PCM_FORMAT_S16_LE: + bits = 16; + dma_params->pdc_xfer_size = 2; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bits = 24; + dma_params->pdc_xfer_size = 4; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bits = 32; + dma_params->pdc_xfer_size = 4; + break; + default: + printk(KERN_WARNING "atmel_ssc_dai: unsupported PCM format"); + return -EINVAL; + } + + /* + * Compute SSC register settings. + */ + + fslen_ext = (bits - 1) / 16; + fslen = (bits - 1) % 16; + + switch (ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) { + + case SND_SOC_DAIFMT_LEFT_J: + fs_osync = SSC_FSOS_POSITIVE; + fs_edge = SSC_START_RISING_RF; + + rcmr = SSC_BF(RCMR_STTDLY, 0); + tcmr = SSC_BF(TCMR_STTDLY, 0); + + break; + + case SND_SOC_DAIFMT_I2S: + fs_osync = SSC_FSOS_NEGATIVE; + fs_edge = SSC_START_FALLING_RF; + + rcmr = SSC_BF(RCMR_STTDLY, 1); + tcmr = SSC_BF(TCMR_STTDLY, 1); + + break; + + case SND_SOC_DAIFMT_DSP_A: + /* + * DSP/PCM Mode A format + * + * Data is transferred on first BCLK after LRC pulse rising + * edge.If stereo, the right channel data is contiguous with + * the left channel data. + */ + fs_osync = SSC_FSOS_POSITIVE; + fs_edge = SSC_START_RISING_RF; + fslen = fslen_ext = 0; + + rcmr = SSC_BF(RCMR_STTDLY, 1); + tcmr = SSC_BF(TCMR_STTDLY, 1); + + break; + + default: + printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n", + ssc_p->daifmt); + return -EINVAL; + } + + if (!atmel_ssc_cfs(ssc_p)) { + fslen = fslen_ext = 0; + rcmr_period = tcmr_period = 0; + fs_osync = SSC_FSOS_NONE; + } + + rcmr |= SSC_BF(RCMR_START, fs_edge); + tcmr |= SSC_BF(TCMR_START, fs_edge); + + if (atmel_ssc_cbs(ssc_p)) { + /* + * SSC provides BCLK + * + * The SSC transmit and receive clocks are generated from the + * MCK divider, and the BCLK signal is output + * on the SSC TK line. + */ + rcmr |= SSC_BF(RCMR_CKS, SSC_CKS_DIV) + | SSC_BF(RCMR_CKO, SSC_CKO_NONE); + + tcmr |= SSC_BF(TCMR_CKS, SSC_CKS_DIV) + | SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS); + } else { + rcmr |= SSC_BF(RCMR_CKS, ssc->clk_from_rk_pin ? + SSC_CKS_PIN : SSC_CKS_CLOCK) + | SSC_BF(RCMR_CKO, SSC_CKO_NONE); + + tcmr |= SSC_BF(TCMR_CKS, ssc->clk_from_rk_pin ? + SSC_CKS_CLOCK : SSC_CKS_PIN) + | SSC_BF(TCMR_CKO, SSC_CKO_NONE); + } + + rcmr |= SSC_BF(RCMR_PERIOD, rcmr_period) + | SSC_BF(RCMR_CKI, SSC_CKI_RISING); + + tcmr |= SSC_BF(TCMR_PERIOD, tcmr_period) + | SSC_BF(TCMR_CKI, SSC_CKI_FALLING); + + rfmr = SSC_BF(RFMR_FSLEN_EXT, fslen_ext) + | SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(RFMR_FSOS, fs_osync) + | SSC_BF(RFMR_FSLEN, fslen) + | SSC_BF(RFMR_DATNB, (channels - 1)) + | SSC_BIT(RFMR_MSBF) + | SSC_BF(RFMR_LOOP, 0) + | SSC_BF(RFMR_DATLEN, (bits - 1)); + + tfmr = SSC_BF(TFMR_FSLEN_EXT, fslen_ext) + | SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE) + | SSC_BF(TFMR_FSDEN, 0) + | SSC_BF(TFMR_FSOS, fs_osync) + | SSC_BF(TFMR_FSLEN, fslen) + | SSC_BF(TFMR_DATNB, (channels - 1)) + | SSC_BIT(TFMR_MSBF) + | SSC_BF(TFMR_DATDEF, 0) + | SSC_BF(TFMR_DATLEN, (bits - 1)); + + if (fslen_ext && !ssc->pdata->has_fslen_ext) { + dev_err(dai->dev, "sample size %d is too large for SSC device\n", + bits); + return -EINVAL; + } + + pr_debug("atmel_ssc_hw_params: " + "RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n", + rcmr, rfmr, tcmr, tfmr); + + if (!ssc_p->initialized) { + if (!ssc_p->ssc->pdata->use_dma) { + ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0); + + ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0); + ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0); + } + + ret = request_irq(ssc_p->ssc->irq, atmel_ssc_interrupt, 0, + ssc_p->name, ssc_p); + if (ret < 0) { + printk(KERN_WARNING + "atmel_ssc_dai: request_irq failure\n"); + pr_debug("Atmel_ssc_dai: Stopping clock\n"); + clk_disable(ssc_p->ssc->clk); + return ret; + } + + ssc_p->initialized = 1; + } + + /* set SSC clock mode register */ + ssc_writel(ssc_p->ssc->regs, CMR, cmr_div); + + /* set receive clock mode and format */ + ssc_writel(ssc_p->ssc->regs, RCMR, rcmr); + ssc_writel(ssc_p->ssc->regs, RFMR, rfmr); + + /* set transmit clock mode and format */ + ssc_writel(ssc_p->ssc->regs, TCMR, tcmr); + ssc_writel(ssc_p->ssc->regs, TFMR, tfmr); + + pr_debug("atmel_ssc_dai,hw_params: SSC initialized\n"); + return 0; +} + + +static int atmel_ssc_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + struct atmel_pcm_dma_params *dma_params; + int dir; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = 0; + else + dir = 1; + + dma_params = ssc_p->dma_params[dir]; + + ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable); + ssc_writel(ssc_p->ssc->regs, IDR, dma_params->mask->ssc_error); + + pr_debug("%s enabled SSC_SR=0x%08x\n", + dir ? "receive" : "transmit", + ssc_readl(ssc_p->ssc->regs, SR)); + return 0; +} + +static int atmel_ssc_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct platform_device *pdev = to_platform_device(dai->dev); + struct atmel_ssc_info *ssc_p = &ssc_info[pdev->id]; + struct atmel_pcm_dma_params *dma_params; + int dir; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = 0; + else + dir = 1; + + dma_params = ssc_p->dma_params[dir]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable); + break; + default: + ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable); + break; + } + + return 0; +} + +#ifdef CONFIG_PM +static int atmel_ssc_suspend(struct snd_soc_component *component) +{ + struct atmel_ssc_info *ssc_p; + struct platform_device *pdev = to_platform_device(component->dev); + + if (!snd_soc_component_active(component)) + return 0; + + ssc_p = &ssc_info[pdev->id]; + + /* Save the status register before disabling transmit and receive */ + ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR); + ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS)); + + /* Save the current interrupt mask, then disable unmasked interrupts */ + ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR); + ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr); + + ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR); + ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR); + ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR); + ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR); + ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR); + + return 0; +} + +static int atmel_ssc_resume(struct snd_soc_component *component) +{ + struct atmel_ssc_info *ssc_p; + struct platform_device *pdev = to_platform_device(component->dev); + u32 cr; + + if (!snd_soc_component_active(component)) + return 0; + + ssc_p = &ssc_info[pdev->id]; + + /* restore SSC register settings */ + ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr); + ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr); + ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr); + ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr); + ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr); + + /* re-enable interrupts */ + ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr); + + /* Re-enable receive and transmit as appropriate */ + cr = 0; + cr |= + (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0; + cr |= + (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0; + ssc_writel(ssc_p->ssc->regs, CR, cr); + + return 0; +} +#else /* CONFIG_PM */ +# define atmel_ssc_suspend NULL +# define atmel_ssc_resume NULL +#endif /* CONFIG_PM */ + +#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops atmel_ssc_dai_ops = { + .startup = atmel_ssc_startup, + .shutdown = atmel_ssc_shutdown, + .prepare = atmel_ssc_prepare, + .trigger = atmel_ssc_trigger, + .hw_params = atmel_ssc_hw_params, + .set_fmt = atmel_ssc_set_dai_fmt, + .set_clkdiv = atmel_ssc_set_dai_clkdiv, +}; + +static struct snd_soc_dai_driver atmel_ssc_dai = { + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 384000, + .formats = ATMEL_SSC_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 384000, + .formats = ATMEL_SSC_FORMATS,}, + .ops = &atmel_ssc_dai_ops, +}; + +static const struct snd_soc_component_driver atmel_ssc_component = { + .name = "atmel-ssc", + .suspend = atmel_ssc_suspend, + .resume = atmel_ssc_resume, +}; + +static int asoc_ssc_init(struct device *dev) +{ + struct ssc_device *ssc = dev_get_drvdata(dev); + int ret; + + ret = devm_snd_soc_register_component(dev, &atmel_ssc_component, + &atmel_ssc_dai, 1); + if (ret) { + dev_err(dev, "Could not register DAI: %d\n", ret); + return ret; + } + + if (ssc->pdata->use_dma) + ret = atmel_pcm_dma_platform_register(dev); + else + ret = atmel_pcm_pdc_platform_register(dev); + + if (ret) { + dev_err(dev, "Could not register PCM: %d\n", ret); + return ret; + } + + return 0; +} + +/** + * atmel_ssc_set_audio - Allocate the specified SSC for audio use. + * @ssc_id: SSD ID in [0, NUM_SSC_DEVICES[ + */ +int atmel_ssc_set_audio(int ssc_id) +{ + struct ssc_device *ssc; + int ret; + + /* If we can grab the SSC briefly to parent the DAI device off it */ + ssc = ssc_request(ssc_id); + if (IS_ERR(ssc)) { + pr_err("Unable to parent ASoC SSC DAI on SSC: %ld\n", + PTR_ERR(ssc)); + return PTR_ERR(ssc); + } else { + ssc_info[ssc_id].ssc = ssc; + } + + ret = asoc_ssc_init(&ssc->pdev->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(atmel_ssc_set_audio); + +void atmel_ssc_put_audio(int ssc_id) +{ + struct ssc_device *ssc = ssc_info[ssc_id].ssc; + + ssc_free(ssc); +} +EXPORT_SYMBOL_GPL(atmel_ssc_put_audio); + +/* Module information */ +MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com"); +MODULE_DESCRIPTION("ATMEL SSC ASoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/atmel_ssc_dai.h b/sound/soc/atmel/atmel_ssc_dai.h new file mode 100644 index 000000000..3470b966e --- /dev/null +++ b/sound/soc/atmel/atmel_ssc_dai.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * atmel_ssc_dai.h - ALSA SSC interface for the Atmel SoC + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Author: Sedji Gaouaou <sedji.gaouaou@atmel.com> + * ATMEL CORP. + * + * Based on at91-ssc.c by + * Frank Mandarino <fmandarino@endrelia.com> + * Based on pxa2xx Platform drivers by + * Liam Girdwood <lrg@slimlogic.co.uk> + */ + +#ifndef _ATMEL_SSC_DAI_H +#define _ATMEL_SSC_DAI_H + +#include <linux/types.h> +#include <linux/atmel-ssc.h> + +#include "atmel-pcm.h" + +/* SSC system clock ids */ +#define ATMEL_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */ + +/* SSC divider ids */ +#define ATMEL_SSC_CMR_DIV 0 /* MCK divider for BCLK */ +#define ATMEL_SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */ +#define ATMEL_SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */ +/* + * SSC direction masks + */ +#define SSC_DIR_MASK_UNUSED 0 +#define SSC_DIR_MASK_PLAYBACK 1 +#define SSC_DIR_MASK_CAPTURE 2 + +/* + * SSC register values that Atmel left out of <linux/atmel-ssc.h>. These + * are expected to be used with SSC_BF + */ +/* START bit field values */ +#define SSC_START_CONTINUOUS 0 +#define SSC_START_TX_RX 1 +#define SSC_START_LOW_RF 2 +#define SSC_START_HIGH_RF 3 +#define SSC_START_FALLING_RF 4 +#define SSC_START_RISING_RF 5 +#define SSC_START_LEVEL_RF 6 +#define SSC_START_EDGE_RF 7 +#define SSS_START_COMPARE_0 8 + +/* CKI bit field values */ +#define SSC_CKI_FALLING 0 +#define SSC_CKI_RISING 1 + +/* CKO bit field values */ +#define SSC_CKO_NONE 0 +#define SSC_CKO_CONTINUOUS 1 +#define SSC_CKO_TRANSFER 2 + +/* CKS bit field values */ +#define SSC_CKS_DIV 0 +#define SSC_CKS_CLOCK 1 +#define SSC_CKS_PIN 2 + +/* FSEDGE bit field values */ +#define SSC_FSEDGE_POSITIVE 0 +#define SSC_FSEDGE_NEGATIVE 1 + +/* FSOS bit field values */ +#define SSC_FSOS_NONE 0 +#define SSC_FSOS_NEGATIVE 1 +#define SSC_FSOS_POSITIVE 2 +#define SSC_FSOS_LOW 3 +#define SSC_FSOS_HIGH 4 +#define SSC_FSOS_TOGGLE 5 + +#define START_DELAY 1 + +struct atmel_ssc_state { + u32 ssc_cmr; + u32 ssc_rcmr; + u32 ssc_rfmr; + u32 ssc_tcmr; + u32 ssc_tfmr; + u32 ssc_sr; + u32 ssc_imr; +}; + + +struct atmel_ssc_info { + char *name; + struct ssc_device *ssc; + unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */ + unsigned short initialized; /* true if SSC has been initialized */ + unsigned short daifmt; + unsigned short cmr_div; + unsigned short tcmr_period; + unsigned short rcmr_period; + unsigned int forced_divider; + struct atmel_pcm_dma_params *dma_params[2]; + struct atmel_ssc_state ssc_state; + unsigned long mck_rate; +}; + +int atmel_ssc_set_audio(int ssc_id); +void atmel_ssc_put_audio(int ssc_id); + +#endif /* _AT91_SSC_DAI_H */ diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c new file mode 100644 index 000000000..9e237580a --- /dev/null +++ b/sound/soc/atmel/atmel_wm8904.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * atmel_wm8904 - Atmel ASoC driver for boards with WM8904 codec. + * + * Copyright (C) 2012 Atmel + * + * Author: Bo Shen <voice.shen@atmel.com> + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <sound/soc.h> + +#include "../codecs/wm8904.h" +#include "atmel_ssc_dai.h" + +static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK, + 32768, params_rate(params) * 256); + if (ret < 0) { + pr_err("%s - failed to set wm8904 codec PLL.", __func__); + return ret; + } + + /* + * As here wm8904 use FLL output as its system clock + * so calling set_sysclk won't care freq parameter + * then we pass 0 + */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + pr_err("%s -failed to set wm8904 SYSCLK\n", __func__); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops atmel_asoc_wm8904_ops = { + .hw_params = atmel_asoc_wm8904_hw_params, +}; + +SND_SOC_DAILINK_DEFS(pcm, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8904-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = { + .name = "WM8904", + .stream_name = "WM8904 PCM", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ops = &atmel_asoc_wm8904_ops, + SND_SOC_DAILINK_REG(pcm), +}; + +static struct snd_soc_card atmel_asoc_wm8904_card = { + .name = "atmel_asoc_wm8904", + .owner = THIS_MODULE, + .dai_link = &atmel_asoc_wm8904_dailink, + .num_links = 1, + .dapm_widgets = atmel_asoc_wm8904_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets), + .fully_routed = true, +}; + +static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card = &atmel_asoc_wm8904_card; + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int ret; + + if (!np) { + dev_err(&pdev->dev, "only device tree supported\n"); + return -EINVAL; + } + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { + dev_err(&pdev->dev, "failed to parse card name\n"); + return ret; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio routing\n"); + return ret; + } + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "failed to get dai and pcm info\n"); + ret = -EINVAL; + return ret; + } + dailink->cpus->of_node = cpu_np; + dailink->platforms->of_node = cpu_np; + of_node_put(cpu_np); + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "failed to get codec info\n"); + ret = -EINVAL; + return ret; + } + dailink->codecs->of_node = codec_np; + of_node_put(codec_np); + + return 0; +} + +static int atmel_asoc_wm8904_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &atmel_asoc_wm8904_card; + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int id, ret; + + card->dev = &pdev->dev; + ret = atmel_asoc_wm8904_dt_init(pdev); + if (ret) { + dev_err(&pdev->dev, "failed to init dt info\n"); + return ret; + } + + id = of_alias_get_id((struct device_node *)dailink->cpus->of_node, "ssc"); + ret = atmel_ssc_set_audio(id); + if (ret != 0) { + dev_err(&pdev->dev, "failed to set SSC %d for audio\n", id); + return ret; + } + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed\n"); + goto err_set_audio; + } + + return 0; + +err_set_audio: + atmel_ssc_put_audio(id); + return ret; +} + +static int atmel_asoc_wm8904_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; + int id; + + id = of_alias_get_id((struct device_node *)dailink->cpus->of_node, "ssc"); + + snd_soc_unregister_card(card); + atmel_ssc_put_audio(id); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id atmel_asoc_wm8904_dt_ids[] = { + { .compatible = "atmel,asoc-wm8904", }, + { } +}; +MODULE_DEVICE_TABLE(of, atmel_asoc_wm8904_dt_ids); +#endif + +static struct platform_driver atmel_asoc_wm8904_driver = { + .driver = { + .name = "atmel-wm8904-audio", + .of_match_table = of_match_ptr(atmel_asoc_wm8904_dt_ids), + .pm = &snd_soc_pm_ops, + }, + .probe = atmel_asoc_wm8904_probe, + .remove = atmel_asoc_wm8904_remove, +}; + +module_platform_driver(atmel_asoc_wm8904_driver); + +/* Module information */ +MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); +MODULE_DESCRIPTION("ALSA SoC machine driver for Atmel EK with WM8904 codec"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c new file mode 100644 index 000000000..04acc18f2 --- /dev/null +++ b/sound/soc/atmel/mchp-i2s-mcc.c @@ -0,0 +1,991 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Driver for Microchip I2S Multi-channel controller +// +// Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries +// +// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com> + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/lcm.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +/* + * ---- I2S Controller Register map ---- + */ +#define MCHP_I2SMCC_CR 0x0000 /* Control Register */ +#define MCHP_I2SMCC_MRA 0x0004 /* Mode Register A */ +#define MCHP_I2SMCC_MRB 0x0008 /* Mode Register B */ +#define MCHP_I2SMCC_SR 0x000C /* Status Register */ +#define MCHP_I2SMCC_IERA 0x0010 /* Interrupt Enable Register A */ +#define MCHP_I2SMCC_IDRA 0x0014 /* Interrupt Disable Register A */ +#define MCHP_I2SMCC_IMRA 0x0018 /* Interrupt Mask Register A */ +#define MCHP_I2SMCC_ISRA 0X001C /* Interrupt Status Register A */ + +#define MCHP_I2SMCC_IERB 0x0020 /* Interrupt Enable Register B */ +#define MCHP_I2SMCC_IDRB 0x0024 /* Interrupt Disable Register B */ +#define MCHP_I2SMCC_IMRB 0x0028 /* Interrupt Mask Register B */ +#define MCHP_I2SMCC_ISRB 0X002C /* Interrupt Status Register B */ + +#define MCHP_I2SMCC_RHR 0x0030 /* Receiver Holding Register */ +#define MCHP_I2SMCC_THR 0x0034 /* Transmitter Holding Register */ + +#define MCHP_I2SMCC_RHL0R 0x0040 /* Receiver Holding Left 0 Register */ +#define MCHP_I2SMCC_RHR0R 0x0044 /* Receiver Holding Right 0 Register */ + +#define MCHP_I2SMCC_RHL1R 0x0048 /* Receiver Holding Left 1 Register */ +#define MCHP_I2SMCC_RHR1R 0x004C /* Receiver Holding Right 1 Register */ + +#define MCHP_I2SMCC_RHL2R 0x0050 /* Receiver Holding Left 2 Register */ +#define MCHP_I2SMCC_RHR2R 0x0054 /* Receiver Holding Right 2 Register */ + +#define MCHP_I2SMCC_RHL3R 0x0058 /* Receiver Holding Left 3 Register */ +#define MCHP_I2SMCC_RHR3R 0x005C /* Receiver Holding Right 3 Register */ + +#define MCHP_I2SMCC_THL0R 0x0060 /* Transmitter Holding Left 0 Register */ +#define MCHP_I2SMCC_THR0R 0x0064 /* Transmitter Holding Right 0 Register */ + +#define MCHP_I2SMCC_THL1R 0x0068 /* Transmitter Holding Left 1 Register */ +#define MCHP_I2SMCC_THR1R 0x006C /* Transmitter Holding Right 1 Register */ + +#define MCHP_I2SMCC_THL2R 0x0070 /* Transmitter Holding Left 2 Register */ +#define MCHP_I2SMCC_THR2R 0x0074 /* Transmitter Holding Right 2 Register */ + +#define MCHP_I2SMCC_THL3R 0x0078 /* Transmitter Holding Left 3 Register */ +#define MCHP_I2SMCC_THR3R 0x007C /* Transmitter Holding Right 3 Register */ + +#define MCHP_I2SMCC_VERSION 0x00FC /* Version Register */ + +/* + * ---- Control Register (Write-only) ---- + */ +#define MCHP_I2SMCC_CR_RXEN BIT(0) /* Receiver Enable */ +#define MCHP_I2SMCC_CR_RXDIS BIT(1) /* Receiver Disable */ +#define MCHP_I2SMCC_CR_CKEN BIT(2) /* Clock Enable */ +#define MCHP_I2SMCC_CR_CKDIS BIT(3) /* Clock Disable */ +#define MCHP_I2SMCC_CR_TXEN BIT(4) /* Transmitter Enable */ +#define MCHP_I2SMCC_CR_TXDIS BIT(5) /* Transmitter Disable */ +#define MCHP_I2SMCC_CR_SWRST BIT(7) /* Software Reset */ + +/* + * ---- Mode Register A (Read/Write) ---- + */ +#define MCHP_I2SMCC_MRA_MODE_MASK GENMASK(0, 0) +#define MCHP_I2SMCC_MRA_MODE_SLAVE (0 << 0) +#define MCHP_I2SMCC_MRA_MODE_MASTER (1 << 0) + +#define MCHP_I2SMCC_MRA_DATALENGTH_MASK GENMASK(3, 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_32_BITS (0 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_24_BITS (1 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_20_BITS (2 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_18_BITS (3 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_16_BITS (4 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_16_BITS_COMPACT (5 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_8_BITS (6 << 1) +#define MCHP_I2SMCC_MRA_DATALENGTH_8_BITS_COMPACT (7 << 1) + +#define MCHP_I2SMCC_MRA_WIRECFG_MASK GENMASK(5, 4) +#define MCHP_I2SMCC_MRA_WIRECFG_I2S_1_TDM_0 (0 << 4) +#define MCHP_I2SMCC_MRA_WIRECFG_I2S_2_TDM_1 (1 << 4) +#define MCHP_I2SMCC_MRA_WIRECFG_I2S_4_TDM_2 (2 << 4) +#define MCHP_I2SMCC_MRA_WIRECFG_TDM_3 (3 << 4) + +#define MCHP_I2SMCC_MRA_FORMAT_MASK GENMASK(7, 6) +#define MCHP_I2SMCC_MRA_FORMAT_I2S (0 << 6) +#define MCHP_I2SMCC_MRA_FORMAT_LJ (1 << 6) /* Left Justified */ +#define MCHP_I2SMCC_MRA_FORMAT_TDM (2 << 6) +#define MCHP_I2SMCC_MRA_FORMAT_TDMLJ (3 << 6) + +/* Transmitter uses one DMA channel ... */ +/* Left audio samples duplicated to right audio channel */ +#define MCHP_I2SMCC_MRA_RXMONO BIT(8) + +/* I2SDO output of I2SC is internally connected to I2SDI input */ +#define MCHP_I2SMCC_MRA_RXLOOP BIT(9) + +/* Receiver uses one DMA channel ... */ +/* Left audio samples duplicated to right audio channel */ +#define MCHP_I2SMCC_MRA_TXMONO BIT(10) + +/* x sample transmitted when underrun */ +#define MCHP_I2SMCC_MRA_TXSAME_ZERO (0 << 11) /* Zero sample */ +#define MCHP_I2SMCC_MRA_TXSAME_PREVIOUS (1 << 11) /* Previous sample */ + +/* select between peripheral clock and generated clock */ +#define MCHP_I2SMCC_MRA_SRCCLK_PCLK (0 << 12) +#define MCHP_I2SMCC_MRA_SRCCLK_GCLK (1 << 12) + +/* Number of TDM Channels - 1 */ +#define MCHP_I2SMCC_MRA_NBCHAN_MASK GENMASK(15, 13) +#define MCHP_I2SMCC_MRA_NBCHAN(ch) \ + ((((ch) - 1) << 13) & MCHP_I2SMCC_MRA_NBCHAN_MASK) + +/* Selected Clock to I2SMCC Master Clock ratio */ +#define MCHP_I2SMCC_MRA_IMCKDIV_MASK GENMASK(21, 16) +#define MCHP_I2SMCC_MRA_IMCKDIV(div) \ + (((div) << 16) & MCHP_I2SMCC_MRA_IMCKDIV_MASK) + +/* TDM Frame Synchronization */ +#define MCHP_I2SMCC_MRA_TDMFS_MASK GENMASK(23, 22) +#define MCHP_I2SMCC_MRA_TDMFS_SLOT (0 << 22) +#define MCHP_I2SMCC_MRA_TDMFS_HALF (1 << 22) +#define MCHP_I2SMCC_MRA_TDMFS_BIT (2 << 22) + +/* Selected Clock to I2SMC Serial Clock ratio */ +#define MCHP_I2SMCC_MRA_ISCKDIV_MASK GENMASK(29, 24) +#define MCHP_I2SMCC_MRA_ISCKDIV(div) \ + (((div) << 24) & MCHP_I2SMCC_MRA_ISCKDIV_MASK) + +/* Master Clock mode */ +#define MCHP_I2SMCC_MRA_IMCKMODE_MASK GENMASK(30, 30) +/* 0: No master clock generated*/ +#define MCHP_I2SMCC_MRA_IMCKMODE_NONE (0 << 30) +/* 1: master clock generated (internally generated clock drives I2SMCK pin) */ +#define MCHP_I2SMCC_MRA_IMCKMODE_GEN (1 << 30) + +/* Slot Width */ +/* 0: slot is 32 bits wide for DATALENGTH = 18/20/24 bits. */ +/* 1: slot is 24 bits wide for DATALENGTH = 18/20/24 bits. */ +#define MCHP_I2SMCC_MRA_IWS BIT(31) + +/* + * ---- Mode Register B (Read/Write) ---- + */ +/* all enabled I2S left channels are filled first, then I2S right channels */ +#define MCHP_I2SMCC_MRB_CRAMODE_LEFT_FIRST (0 << 0) +/* + * an enabled I2S left channel is filled, then the corresponding right + * channel, until all channels are filled + */ +#define MCHP_I2SMCC_MRB_CRAMODE_REGULAR (1 << 0) + +#define MCHP_I2SMCC_MRB_FIFOEN BIT(1) + +#define MCHP_I2SMCC_MRB_DMACHUNK_MASK GENMASK(9, 8) +#define MCHP_I2SMCC_MRB_DMACHUNK(no_words) \ + (((fls(no_words) - 1) << 8) & MCHP_I2SMCC_MRB_DMACHUNK_MASK) + +#define MCHP_I2SMCC_MRB_CLKSEL_MASK GENMASK(16, 16) +#define MCHP_I2SMCC_MRB_CLKSEL_EXT (0 << 16) +#define MCHP_I2SMCC_MRB_CLKSEL_INT (1 << 16) + +/* + * ---- Status Registers (Read-only) ---- + */ +#define MCHP_I2SMCC_SR_RXEN BIT(0) /* Receiver Enabled */ +#define MCHP_I2SMCC_SR_TXEN BIT(4) /* Transmitter Enabled */ + +/* + * ---- Interrupt Enable/Disable/Mask/Status Registers A ---- + */ +#define MCHP_I2SMCC_INT_TXRDY_MASK(ch) GENMASK((ch) - 1, 0) +#define MCHP_I2SMCC_INT_TXRDYCH(ch) BIT(ch) +#define MCHP_I2SMCC_INT_TXUNF_MASK(ch) GENMASK((ch) + 7, 8) +#define MCHP_I2SMCC_INT_TXUNFCH(ch) BIT((ch) + 8) +#define MCHP_I2SMCC_INT_RXRDY_MASK(ch) GENMASK((ch) + 15, 16) +#define MCHP_I2SMCC_INT_RXRDYCH(ch) BIT((ch) + 16) +#define MCHP_I2SMCC_INT_RXOVF_MASK(ch) GENMASK((ch) + 23, 24) +#define MCHP_I2SMCC_INT_RXOVFCH(ch) BIT((ch) + 24) + +/* + * ---- Interrupt Enable/Disable/Mask/Status Registers B ---- + */ +#define MCHP_I2SMCC_INT_WERR BIT(0) +#define MCHP_I2SMCC_INT_TXFFRDY BIT(8) +#define MCHP_I2SMCC_INT_TXFFEMP BIT(9) +#define MCHP_I2SMCC_INT_RXFFRDY BIT(12) +#define MCHP_I2SMCC_INT_RXFFFUL BIT(13) + +/* + * ---- Version Register (Read-only) ---- + */ +#define MCHP_I2SMCC_VERSION_MASK GENMASK(11, 0) + +#define MCHP_I2SMCC_MAX_CHANNELS 8 +#define MCHP_I2MCC_TDM_SLOT_WIDTH 32 + +static const struct regmap_config mchp_i2s_mcc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = MCHP_I2SMCC_VERSION, +}; + +struct mchp_i2s_mcc_dev { + struct wait_queue_head wq_txrdy; + struct wait_queue_head wq_rxrdy; + struct device *dev; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + struct snd_dmaengine_dai_dma_data playback; + struct snd_dmaengine_dai_dma_data capture; + unsigned int fmt; + unsigned int sysclk; + unsigned int frame_length; + int tdm_slots; + int channels; + unsigned int gclk_use:1; + unsigned int gclk_running:1; + unsigned int tx_rdy:1; + unsigned int rx_rdy:1; +}; + +static irqreturn_t mchp_i2s_mcc_interrupt(int irq, void *dev_id) +{ + struct mchp_i2s_mcc_dev *dev = dev_id; + u32 sra, imra, srb, imrb, pendinga, pendingb, idra = 0; + irqreturn_t ret = IRQ_NONE; + + regmap_read(dev->regmap, MCHP_I2SMCC_IMRA, &imra); + regmap_read(dev->regmap, MCHP_I2SMCC_ISRA, &sra); + pendinga = imra & sra; + + regmap_read(dev->regmap, MCHP_I2SMCC_IMRB, &imrb); + regmap_read(dev->regmap, MCHP_I2SMCC_ISRB, &srb); + pendingb = imrb & srb; + + if (!pendinga && !pendingb) + return IRQ_NONE; + + /* + * Tx/Rx ready interrupts are enabled when stopping only, to assure + * availability and to disable clocks if necessary + */ + idra |= pendinga & (MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels) | + MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)); + if (idra) + ret = IRQ_HANDLED; + + if ((imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) && + (imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) == + (idra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels))) { + dev->tx_rdy = 1; + wake_up_interruptible(&dev->wq_txrdy); + } + if ((imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) && + (imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) == + (idra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels))) { + dev->rx_rdy = 1; + wake_up_interruptible(&dev->wq_rxrdy); + } + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, idra); + + return ret; +} + +static int mchp_i2s_mcc_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s() clk_id=%d freq=%u dir=%d\n", + __func__, clk_id, freq, dir); + + /* We do not need SYSCLK */ + if (dir == SND_SOC_CLOCK_IN) + return 0; + + dev->sysclk = freq; + + return 0; +} + +static int mchp_i2s_mcc_set_bclk_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s() ratio=%u\n", __func__, ratio); + + dev->frame_length = ratio; + + return 0; +} + +static int mchp_i2s_mcc_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, "%s() fmt=%#x\n", __func__, fmt); + + /* We don't support any kind of clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) + return -EINVAL; + + /* We can't generate only FSYNC */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFS) + return -EINVAL; + + /* We can only reconfigure the IP when it's stopped */ + if (fmt & SND_SOC_DAIFMT_CONT) + return -EINVAL; + + dev->fmt = fmt; + + return 0; +} + +static int mchp_i2s_mcc_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dev->dev, + "%s() tx_mask=0x%08x rx_mask=0x%08x slots=%d width=%d\n", + __func__, tx_mask, rx_mask, slots, slot_width); + + if (slots < 0 || slots > MCHP_I2SMCC_MAX_CHANNELS || + slot_width != MCHP_I2MCC_TDM_SLOT_WIDTH) + return -EINVAL; + + if (slots) { + /* We do not support daisy chain */ + if (rx_mask != GENMASK(slots - 1, 0) || + rx_mask != tx_mask) + return -EINVAL; + } + + dev->tdm_slots = slots; + dev->frame_length = slots * MCHP_I2MCC_TDM_SLOT_WIDTH; + + return 0; +} + +static int mchp_i2s_mcc_clk_get_rate_diff(struct clk *clk, + unsigned long rate, + struct clk **best_clk, + unsigned long *best_rate, + unsigned long *best_diff_rate) +{ + long round_rate; + unsigned int diff_rate; + + round_rate = clk_round_rate(clk, rate); + if (round_rate < 0) + return (int)round_rate; + + diff_rate = abs(rate - round_rate); + if (diff_rate < *best_diff_rate) { + *best_clk = clk; + *best_diff_rate = diff_rate; + *best_rate = rate; + } + + return 0; +} + +static int mchp_i2s_mcc_config_divs(struct mchp_i2s_mcc_dev *dev, + unsigned int bclk, unsigned int *mra, + unsigned long *best_rate) +{ + unsigned long clk_rate; + unsigned long lcm_rate; + unsigned long best_diff_rate = ~0; + unsigned int sysclk; + struct clk *best_clk = NULL; + int ret; + + /* For code simplification */ + if (!dev->sysclk) + sysclk = bclk; + else + sysclk = dev->sysclk; + + /* + * MCLK is Selected CLK / (2 * IMCKDIV), + * BCLK is Selected CLK / (2 * ISCKDIV); + * if IMCKDIV or ISCKDIV are 0, MCLK or BCLK = Selected CLK + */ + lcm_rate = lcm(sysclk, bclk); + if ((lcm_rate / sysclk % 2 == 1 && lcm_rate / sysclk > 2) || + (lcm_rate / bclk % 2 == 1 && lcm_rate / bclk > 2)) + lcm_rate *= 2; + + for (clk_rate = lcm_rate; + (clk_rate == sysclk || clk_rate / (sysclk * 2) <= GENMASK(5, 0)) && + (clk_rate == bclk || clk_rate / (bclk * 2) <= GENMASK(5, 0)); + clk_rate += lcm_rate) { + ret = mchp_i2s_mcc_clk_get_rate_diff(dev->gclk, clk_rate, + &best_clk, best_rate, + &best_diff_rate); + if (ret) { + dev_err(dev->dev, "gclk error for rate %lu: %d", + clk_rate, ret); + } else { + if (!best_diff_rate) { + dev_dbg(dev->dev, "found perfect rate on gclk: %lu\n", + clk_rate); + break; + } + } + + ret = mchp_i2s_mcc_clk_get_rate_diff(dev->pclk, clk_rate, + &best_clk, best_rate, + &best_diff_rate); + if (ret) { + dev_err(dev->dev, "pclk error for rate %lu: %d", + clk_rate, ret); + } else { + if (!best_diff_rate) { + dev_dbg(dev->dev, "found perfect rate on pclk: %lu\n", + clk_rate); + break; + } + } + } + + /* check if clocks returned only errors */ + if (!best_clk) { + dev_err(dev->dev, "unable to change rate to clocks\n"); + return -EINVAL; + } + + dev_dbg(dev->dev, "source CLK is %s with rate %lu, diff %lu\n", + best_clk == dev->pclk ? "pclk" : "gclk", + *best_rate, best_diff_rate); + + /* Configure divisors */ + if (dev->sysclk) + *mra |= MCHP_I2SMCC_MRA_IMCKDIV(*best_rate / (2 * sysclk)); + *mra |= MCHP_I2SMCC_MRA_ISCKDIV(*best_rate / (2 * bclk)); + + if (best_clk == dev->gclk) + *mra |= MCHP_I2SMCC_MRA_SRCCLK_GCLK; + else + *mra |= MCHP_I2SMCC_MRA_SRCCLK_PCLK; + + return 0; +} + +static int mchp_i2s_mcc_is_running(struct mchp_i2s_mcc_dev *dev) +{ + u32 sr; + + regmap_read(dev->regmap, MCHP_I2SMCC_SR, &sr); + return !!(sr & (MCHP_I2SMCC_SR_TXEN | MCHP_I2SMCC_SR_RXEN)); +} + +static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + unsigned long rate = 0; + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + u32 mra = 0; + u32 mrb = 0; + unsigned int channels = params_channels(params); + unsigned int frame_length = dev->frame_length; + unsigned int bclk_rate; + int set_divs = 0; + int ret; + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + 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)); + + switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + if (dev->tdm_slots) { + dev_err(dev->dev, "I2S with TDM is not supported\n"); + return -EINVAL; + } + mra |= MCHP_I2SMCC_MRA_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + if (dev->tdm_slots) { + dev_err(dev->dev, "Left-Justified with TDM is not supported\n"); + return -EINVAL; + } + mra |= MCHP_I2SMCC_MRA_FORMAT_LJ; + break; + case SND_SOC_DAIFMT_DSP_A: + mra |= MCHP_I2SMCC_MRA_FORMAT_TDM; + break; + default: + dev_err(dev->dev, "unsupported bus format\n"); + return -EINVAL; + } + + switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* cpu is BCLK and LRC master */ + mra |= MCHP_I2SMCC_MRA_MODE_MASTER; + if (dev->sysclk) + mra |= MCHP_I2SMCC_MRA_IMCKMODE_GEN; + set_divs = 1; + break; + case SND_SOC_DAIFMT_CBS_CFM: + /* cpu is BCLK master */ + mrb |= MCHP_I2SMCC_MRB_CLKSEL_INT; + set_divs = 1; + fallthrough; + case SND_SOC_DAIFMT_CBM_CFM: + /* cpu is slave */ + mra |= MCHP_I2SMCC_MRA_MODE_SLAVE; + if (dev->sysclk) + dev_warn(dev->dev, "Unable to generate MCLK in Slave mode\n"); + break; + default: + dev_err(dev->dev, "unsupported master/slave mode\n"); + return -EINVAL; + } + + if (dev->fmt & (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J)) { + switch (channels) { + case 1: + if (is_playback) + mra |= MCHP_I2SMCC_MRA_TXMONO; + else + mra |= MCHP_I2SMCC_MRA_RXMONO; + break; + case 2: + break; + default: + dev_err(dev->dev, "unsupported number of audio channels\n"); + return -EINVAL; + } + + if (!frame_length) + frame_length = 2 * params_physical_width(params); + } else if (dev->fmt & SND_SOC_DAIFMT_DSP_A) { + if (dev->tdm_slots) { + if (channels % 2 && channels * 2 <= dev->tdm_slots) { + /* + * Duplicate data for even-numbered channels + * to odd-numbered channels + */ + if (is_playback) + mra |= MCHP_I2SMCC_MRA_TXMONO; + else + mra |= MCHP_I2SMCC_MRA_RXMONO; + } + channels = dev->tdm_slots; + } + + mra |= MCHP_I2SMCC_MRA_NBCHAN(channels); + if (!frame_length) + frame_length = channels * MCHP_I2MCC_TDM_SLOT_WIDTH; + } + + /* + * We must have the same burst size configured + * in the DMA transfer and in out IP + */ + mrb |= MCHP_I2SMCC_MRB_DMACHUNK(channels); + if (is_playback) + dev->playback.maxburst = 1 << (fls(channels) - 1); + else + dev->capture.maxburst = 1 << (fls(channels) - 1); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_8_BITS; + break; + case SNDRV_PCM_FORMAT_S16_LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_16_BITS; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_18_BITS | + MCHP_I2SMCC_MRA_IWS; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_20_BITS | + MCHP_I2SMCC_MRA_IWS; + break; + case SNDRV_PCM_FORMAT_S24_3LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_24_BITS | + MCHP_I2SMCC_MRA_IWS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_24_BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + mra |= MCHP_I2SMCC_MRA_DATALENGTH_32_BITS; + break; + default: + dev_err(dev->dev, "unsupported size/endianness for audio samples\n"); + return -EINVAL; + } + + if (set_divs) { + bclk_rate = frame_length * params_rate(params); + ret = mchp_i2s_mcc_config_divs(dev, bclk_rate, &mra, + &rate); + if (ret) { + dev_err(dev->dev, + "unable to configure the divisors: %d\n", ret); + return ret; + } + } + + /* + * If we are already running, the wanted setup must be + * the same with the one that's currently ongoing + */ + if (mchp_i2s_mcc_is_running(dev)) { + u32 mra_cur; + u32 mrb_cur; + + regmap_read(dev->regmap, MCHP_I2SMCC_MRA, &mra_cur); + regmap_read(dev->regmap, MCHP_I2SMCC_MRB, &mrb_cur); + if (mra != mra_cur || mrb != mrb_cur) + return -EINVAL; + + return 0; + } + + if (mra & MCHP_I2SMCC_MRA_SRCCLK_GCLK && !dev->gclk_use) { + /* set the rate */ + ret = clk_set_rate(dev->gclk, rate); + if (ret) { + dev_err(dev->dev, + "unable to set rate %lu to GCLK: %d\n", + rate, ret); + return ret; + } + + ret = clk_prepare(dev->gclk); + if (ret < 0) { + dev_err(dev->dev, "unable to prepare GCLK: %d\n", ret); + return ret; + } + dev->gclk_use = 1; + } + + /* Save the number of channels to know what interrupts to enable */ + dev->channels = channels; + + ret = regmap_write(dev->regmap, MCHP_I2SMCC_MRA, mra); + if (ret < 0) { + if (dev->gclk_use) { + clk_unprepare(dev->gclk); + dev->gclk_use = 0; + } + return ret; + } + return regmap_write(dev->regmap, MCHP_I2SMCC_MRB, mrb); +} + +static int mchp_i2s_mcc_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + long err; + + if (is_playback) { + err = wait_event_interruptible_timeout(dev->wq_txrdy, + dev->tx_rdy, + msecs_to_jiffies(500)); + if (err == 0) { + dev_warn_once(dev->dev, + "Timeout waiting for Tx ready\n"); + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, + MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)); + dev->tx_rdy = 1; + } + } else { + err = wait_event_interruptible_timeout(dev->wq_rxrdy, + dev->rx_rdy, + msecs_to_jiffies(500)); + if (err == 0) { + dev_warn_once(dev->dev, + "Timeout waiting for Rx ready\n"); + regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, + MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)); + dev->rx_rdy = 1; + } + } + + if (!mchp_i2s_mcc_is_running(dev)) { + regmap_write(dev->regmap, MCHP_I2SMCC_CR, MCHP_I2SMCC_CR_CKDIS); + + if (dev->gclk_running) { + clk_disable(dev->gclk); + dev->gclk_running = 0; + } + if (dev->gclk_use) { + clk_unprepare(dev->gclk); + dev->gclk_use = 0; + } + } + + return 0; +} + +static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + u32 cr = 0; + u32 iera = 0; + u32 sr; + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (is_playback) + cr = MCHP_I2SMCC_CR_TXEN | MCHP_I2SMCC_CR_CKEN; + else + cr = MCHP_I2SMCC_CR_RXEN | MCHP_I2SMCC_CR_CKEN; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_read(dev->regmap, MCHP_I2SMCC_SR, &sr); + if (is_playback && (sr & MCHP_I2SMCC_SR_TXEN)) { + cr = MCHP_I2SMCC_CR_TXDIS; + dev->tx_rdy = 0; + /* + * Enable Tx Ready interrupts on all channels + * to assure all data is sent + */ + iera = MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels); + } else if (!is_playback && (sr & MCHP_I2SMCC_SR_RXEN)) { + cr = MCHP_I2SMCC_CR_RXDIS; + dev->rx_rdy = 0; + /* + * Enable Rx Ready interrupts on all channels + * to assure all data is received + */ + iera = MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels); + } + break; + default: + return -EINVAL; + } + + if ((cr & MCHP_I2SMCC_CR_CKEN) && dev->gclk_use && + !dev->gclk_running) { + err = clk_enable(dev->gclk); + if (err) { + dev_err_once(dev->dev, "failed to enable GCLK: %d\n", + err); + } else { + dev->gclk_running = 1; + } + } + + regmap_write(dev->regmap, MCHP_I2SMCC_IERA, iera); + regmap_write(dev->regmap, MCHP_I2SMCC_CR, cr); + + return 0; +} + +static int mchp_i2s_mcc_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + /* Software reset the IP if it's not running */ + if (!mchp_i2s_mcc_is_running(dev)) { + return regmap_write(dev->regmap, MCHP_I2SMCC_CR, + MCHP_I2SMCC_CR_SWRST); + } + + return 0; +} + +static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = { + .set_sysclk = mchp_i2s_mcc_set_sysclk, + .set_bclk_ratio = mchp_i2s_mcc_set_bclk_ratio, + .startup = mchp_i2s_mcc_startup, + .trigger = mchp_i2s_mcc_trigger, + .hw_params = mchp_i2s_mcc_hw_params, + .hw_free = mchp_i2s_mcc_hw_free, + .set_fmt = mchp_i2s_mcc_set_dai_fmt, + .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot, +}; + +static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai) +{ + struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai); + + init_waitqueue_head(&dev->wq_txrdy); + init_waitqueue_head(&dev->wq_rxrdy); + dev->tx_rdy = 1; + dev->rx_rdy = 1; + + snd_soc_dai_init_dma_data(dai, &dev->playback, &dev->capture); + + return 0; +} + +#define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000 + +#define MCHP_I2SMCC_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver mchp_i2s_mcc_dai = { + .probe = mchp_i2s_mcc_dai_probe, + .playback = { + .stream_name = "I2SMCC-Playback", + .channels_min = 1, + .channels_max = 8, + .rates = MCHP_I2SMCC_RATES, + .formats = MCHP_I2SMCC_FORMATS, + }, + .capture = { + .stream_name = "I2SMCC-Capture", + .channels_min = 1, + .channels_max = 8, + .rates = MCHP_I2SMCC_RATES, + .formats = MCHP_I2SMCC_FORMATS, + }, + .ops = &mchp_i2s_mcc_dai_ops, + .symmetric_rates = 1, + .symmetric_samplebits = 1, + .symmetric_channels = 1, +}; + +static const struct snd_soc_component_driver mchp_i2s_mcc_component = { + .name = "mchp-i2s-mcc", +}; + +#ifdef CONFIG_OF +static const struct of_device_id mchp_i2s_mcc_dt_ids[] = { + { + .compatible = "microchip,sam9x60-i2smcc", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mchp_i2s_mcc_dt_ids); +#endif + +static int mchp_i2s_mcc_probe(struct platform_device *pdev) +{ + struct mchp_i2s_mcc_dev *dev; + struct resource *mem; + struct regmap *regmap; + void __iomem *base; + u32 version; + int irq; + int err; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap = devm_regmap_init_mmio(&pdev->dev, base, + &mchp_i2s_mcc_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_irq(&pdev->dev, irq, mchp_i2s_mcc_interrupt, 0, + dev_name(&pdev->dev), dev); + if (err) + return err; + + 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 optional generated clock */ + dev->gclk = devm_clk_get(&pdev->dev, "gclk"); + if (IS_ERR(dev->gclk)) { + if (PTR_ERR(dev->gclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_warn(&pdev->dev, + "generated clock not found: %d\n", err); + dev->gclk = NULL; + } + + dev->dev = &pdev->dev; + dev->regmap = regmap; + platform_set_drvdata(pdev, dev); + + err = clk_prepare_enable(dev->pclk); + if (err) { + dev_err(&pdev->dev, + "failed to enable the peripheral clock: %d\n", err); + return err; + } + + err = devm_snd_soc_register_component(&pdev->dev, + &mchp_i2s_mcc_component, + &mchp_i2s_mcc_dai, 1); + if (err) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + dev->playback.addr = (dma_addr_t)mem->start + MCHP_I2SMCC_THR; + dev->capture.addr = (dma_addr_t)mem->start + MCHP_I2SMCC_RHR; + + err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (err) { + dev_err(&pdev->dev, "failed to register PCM: %d\n", err); + clk_disable_unprepare(dev->pclk); + return err; + } + + /* Get IP version. */ + regmap_read(dev->regmap, MCHP_I2SMCC_VERSION, &version); + dev_info(&pdev->dev, "hw version: %#lx\n", + version & MCHP_I2SMCC_VERSION_MASK); + + return 0; +} + +static int mchp_i2s_mcc_remove(struct platform_device *pdev) +{ + struct mchp_i2s_mcc_dev *dev = platform_get_drvdata(pdev); + + clk_disable_unprepare(dev->pclk); + + return 0; +} + +static struct platform_driver mchp_i2s_mcc_driver = { + .driver = { + .name = "mchp_i2s_mcc", + .of_match_table = of_match_ptr(mchp_i2s_mcc_dt_ids), + }, + .probe = mchp_i2s_mcc_probe, + .remove = mchp_i2s_mcc_remove, +}; +module_platform_driver(mchp_i2s_mcc_driver); + +MODULE_DESCRIPTION("Microchip I2S Multi-Channel Controller driver"); +MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@microchip.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/atmel/mchp-spdifrx.c b/sound/soc/atmel/mchp-spdifrx.c new file mode 100644 index 000000000..39a3c2a33 --- /dev/null +++ b/sound/soc/atmel/mchp-spdifrx.c @@ -0,0 +1,1060 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Driver for Microchip S/PDIF RX Controller +// +// Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries +// +// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com> + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/spinlock.h> + +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +/* + * ---- S/PDIF Receiver Controller Register map ---- + */ +#define SPDIFRX_CR 0x00 /* Control Register */ +#define SPDIFRX_MR 0x04 /* Mode Register */ + +#define SPDIFRX_IER 0x10 /* Interrupt Enable Register */ +#define SPDIFRX_IDR 0x14 /* Interrupt Disable Register */ +#define SPDIFRX_IMR 0x18 /* Interrupt Mask Register */ +#define SPDIFRX_ISR 0x1c /* Interrupt Status Register */ +#define SPDIFRX_RSR 0x20 /* Status Register */ +#define SPDIFRX_RHR 0x24 /* Holding Register */ + +#define SPDIFRX_CHSR(channel, reg) \ + (0x30 + (channel) * 0x30 + (reg) * 4) /* Channel x Status Registers */ + +#define SPDIFRX_CHUD(channel, reg) \ + (0x48 + (channel) * 0x30 + (reg) * 4) /* Channel x User Data Registers */ + +#define SPDIFRX_WPMR 0xE4 /* Write Protection Mode Register */ +#define SPDIFRX_WPSR 0xE8 /* Write Protection Status Register */ + +#define SPDIFRX_VERSION 0xFC /* Version Register */ + +/* + * ---- Control Register (Write-only) ---- + */ +#define SPDIFRX_CR_SWRST BIT(0) /* Software Reset */ + +/* + * ---- Mode Register (Read/Write) ---- + */ +/* Receive Enable */ +#define SPDIFRX_MR_RXEN_MASK GENMASK(0, 0) +#define SPDIFRX_MR_RXEN_DISABLE (0 << 0) /* SPDIF Receiver Disabled */ +#define SPDIFRX_MR_RXEN_ENABLE (1 << 0) /* SPDIF Receiver Enabled */ + +/* Validity Bit Mode */ +#define SPDIFRX_MR_VBMODE_MASK GENAMSK(1, 1) +#define SPDIFRX_MR_VBMODE_ALWAYS_LOAD \ + (0 << 1) /* Load sample regardless of validity bit value */ +#define SPDIFRX_MR_VBMODE_DISCARD_IF_VB1 \ + (1 << 1) /* Load sample only if validity bit is 0 */ + +/* Data Word Endian Mode */ +#define SPDIFRX_MR_ENDIAN_MASK GENMASK(2, 2) +#define SPDIFRX_MR_ENDIAN_LITTLE (0 << 2) /* Little Endian Mode */ +#define SPDIFRX_MR_ENDIAN_BIG (1 << 2) /* Big Endian Mode */ + +/* Parity Bit Mode */ +#define SPDIFRX_MR_PBMODE_MASK GENMASK(3, 3) +#define SPDIFRX_MR_PBMODE_PARCHECK (0 << 3) /* Parity Check Enabled */ +#define SPDIFRX_MR_PBMODE_NOPARCHECK (1 << 3) /* Parity Check Disabled */ + +/* Sample Data Width */ +#define SPDIFRX_MR_DATAWIDTH_MASK GENMASK(5, 4) +#define SPDIFRX_MR_DATAWIDTH(width) \ + (((6 - (width) / 4) << 4) & SPDIFRX_MR_DATAWIDTH_MASK) + +/* Packed Data Mode in Receive Holding Register */ +#define SPDIFRX_MR_PACK_MASK GENMASK(7, 7) +#define SPDIFRX_MR_PACK_DISABLED (0 << 7) +#define SPDIFRX_MR_PACK_ENABLED (1 << 7) + +/* Start of Block Bit Mode */ +#define SPDIFRX_MR_SBMODE_MASK GENMASK(8, 8) +#define SPDIFRX_MR_SBMODE_ALWAYS_LOAD (0 << 8) +#define SPDIFRX_MR_SBMODE_DISCARD (1 << 8) + +/* Consecutive Preamble Error Threshold Automatic Restart */ +#define SPDIFRX_MR_AUTORST_MASK GENMASK(24, 24) +#define SPDIFRX_MR_AUTORST_NOACTION (0 << 24) +#define SPDIFRX_MR_AUTORST_UNLOCK_ON_PRE_ERR (1 << 24) + +/* + * ---- Interrupt Enable/Disable/Mask/Status Register (Write/Read-only) ---- + */ +#define SPDIFRX_IR_RXRDY BIT(0) +#define SPDIFRX_IR_LOCKED BIT(1) +#define SPDIFRX_IR_LOSS BIT(2) +#define SPDIFRX_IR_BLOCKEND BIT(3) +#define SPDIFRX_IR_SFE BIT(4) +#define SPDIFRX_IR_PAR_ERR BIT(5) +#define SPDIFRX_IR_OVERRUN BIT(6) +#define SPDIFRX_IR_RXFULL BIT(7) +#define SPDIFRX_IR_CSC(ch) BIT((ch) + 8) +#define SPDIFRX_IR_SECE BIT(10) +#define SPDIFRX_IR_BLOCKST BIT(11) +#define SPDIFRX_IR_NRZ_ERR BIT(12) +#define SPDIFRX_IR_PRE_ERR BIT(13) +#define SPDIFRX_IR_CP_ERR BIT(14) + +/* + * ---- Receiver Status Register (Read/Write) ---- + */ +/* Enable Status */ +#define SPDIFRX_RSR_ULOCK BIT(0) +#define SPDIFRX_RSR_BADF BIT(1) +#define SPDIFRX_RSR_LOWF BIT(2) +#define SPDIFRX_RSR_NOSIGNAL BIT(3) +#define SPDIFRX_RSR_IFS_MASK GENMASK(27, 16) +#define SPDIFRX_RSR_IFS(reg) \ + (((reg) & SPDIFRX_RSR_IFS_MASK) >> 16) + +/* + * ---- Version Register (Read-only) ---- + */ +#define SPDIFRX_VERSION_MASK GENMASK(11, 0) +#define SPDIFRX_VERSION_MFN_MASK GENMASK(18, 16) +#define SPDIFRX_VERSION_MFN(reg) (((reg) & SPDIFRX_VERSION_MFN_MASK) >> 16) + +static bool mchp_spdifrx_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SPDIFRX_MR: + case SPDIFRX_IMR: + case SPDIFRX_ISR: + case SPDIFRX_RSR: + case SPDIFRX_CHSR(0, 0): + case SPDIFRX_CHSR(0, 1): + case SPDIFRX_CHSR(0, 2): + case SPDIFRX_CHSR(0, 3): + case SPDIFRX_CHSR(0, 4): + case SPDIFRX_CHSR(0, 5): + case SPDIFRX_CHUD(0, 0): + case SPDIFRX_CHUD(0, 1): + case SPDIFRX_CHUD(0, 2): + case SPDIFRX_CHUD(0, 3): + case SPDIFRX_CHUD(0, 4): + case SPDIFRX_CHUD(0, 5): + case SPDIFRX_CHSR(1, 0): + case SPDIFRX_CHSR(1, 1): + case SPDIFRX_CHSR(1, 2): + case SPDIFRX_CHSR(1, 3): + case SPDIFRX_CHSR(1, 4): + case SPDIFRX_CHSR(1, 5): + case SPDIFRX_CHUD(1, 0): + case SPDIFRX_CHUD(1, 1): + case SPDIFRX_CHUD(1, 2): + case SPDIFRX_CHUD(1, 3): + case SPDIFRX_CHUD(1, 4): + case SPDIFRX_CHUD(1, 5): + case SPDIFRX_WPMR: + case SPDIFRX_WPSR: + case SPDIFRX_VERSION: + return true; + default: + return false; + } +} + +static bool mchp_spdifrx_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SPDIFRX_CR: + case SPDIFRX_MR: + case SPDIFRX_IER: + case SPDIFRX_IDR: + case SPDIFRX_WPMR: + return true; + default: + return false; + } +} + +static bool mchp_spdifrx_precious_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SPDIFRX_ISR: + case SPDIFRX_RHR: + return true; + default: + return false; + } +} + +static const struct regmap_config mchp_spdifrx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SPDIFRX_VERSION, + .readable_reg = mchp_spdifrx_readable_reg, + .writeable_reg = mchp_spdifrx_writeable_reg, + .precious_reg = mchp_spdifrx_precious_reg, +}; + +#define SPDIFRX_GCLK_RATIO_MIN (12 * 64) + +#define SPDIFRX_CS_BITS 192 +#define SPDIFRX_UD_BITS 192 + +#define SPDIFRX_CHANNELS 2 + +struct mchp_spdifrx_ch_stat { + unsigned char data[SPDIFRX_CS_BITS / 8]; + struct completion done; +}; + +struct mchp_spdifrx_user_data { + unsigned char data[SPDIFRX_UD_BITS / 8]; + struct completion done; +}; + +struct mchp_spdifrx_mixer_control { + struct mchp_spdifrx_ch_stat ch_stat[SPDIFRX_CHANNELS]; + struct mchp_spdifrx_user_data user_data[SPDIFRX_CHANNELS]; + bool ulock; + bool badf; + bool signal; +}; + +struct mchp_spdifrx_dev { + struct snd_dmaengine_dai_dma_data capture; + struct mchp_spdifrx_mixer_control control; + struct mutex mlock; + struct device *dev; + struct regmap *regmap; + struct clk *pclk; + struct clk *gclk; + unsigned int fmt; + unsigned int trigger_enabled; + unsigned int gclk_enabled:1; +}; + +static void mchp_spdifrx_channel_status_read(struct mchp_spdifrx_dev *dev, + int channel) +{ + struct mchp_spdifrx_mixer_control *ctrl = &dev->control; + u8 *ch_stat = &ctrl->ch_stat[channel].data[0]; + u32 val; + int i; + + for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat[channel].data) / 4; i++) { + regmap_read(dev->regmap, SPDIFRX_CHSR(channel, i), &val); + *ch_stat++ = val & 0xFF; + *ch_stat++ = (val >> 8) & 0xFF; + *ch_stat++ = (val >> 16) & 0xFF; + *ch_stat++ = (val >> 24) & 0xFF; + } +} + +static void mchp_spdifrx_channel_user_data_read(struct mchp_spdifrx_dev *dev, + int channel) +{ + struct mchp_spdifrx_mixer_control *ctrl = &dev->control; + u8 *user_data = &ctrl->user_data[channel].data[0]; + u32 val; + int i; + + for (i = 0; i < ARRAY_SIZE(ctrl->user_data[channel].data) / 4; i++) { + regmap_read(dev->regmap, SPDIFRX_CHUD(channel, i), &val); + *user_data++ = val & 0xFF; + *user_data++ = (val >> 8) & 0xFF; + *user_data++ = (val >> 16) & 0xFF; + *user_data++ = (val >> 24) & 0xFF; + } +} + +static irqreturn_t mchp_spdif_interrupt(int irq, void *dev_id) +{ + struct mchp_spdifrx_dev *dev = dev_id; + struct mchp_spdifrx_mixer_control *ctrl = &dev->control; + u32 sr, imr, pending; + irqreturn_t ret = IRQ_NONE; + int ch; + + regmap_read(dev->regmap, SPDIFRX_ISR, &sr); + regmap_read(dev->regmap, SPDIFRX_IMR, &imr); + pending = sr & imr; + dev_dbg(dev->dev, "ISR: %#x, IMR: %#x, pending: %#x\n", sr, imr, + pending); + + if (!pending) + return IRQ_NONE; + + if (pending & SPDIFRX_IR_BLOCKEND) { + for (ch = 0; ch < SPDIFRX_CHANNELS; ch++) { + mchp_spdifrx_channel_user_data_read(dev, ch); + complete(&ctrl->user_data[ch].done); + } + regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_BLOCKEND); + ret = IRQ_HANDLED; + } + + for (ch = 0; ch < SPDIFRX_CHANNELS; ch++) { + if (pending & SPDIFRX_IR_CSC(ch)) { + mchp_spdifrx_channel_status_read(dev, ch); + complete(&ctrl->ch_stat[ch].done); + regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_CSC(ch)); + ret = IRQ_HANDLED; + } + } + + if (pending & SPDIFRX_IR_OVERRUN) { + dev_warn(dev->dev, "Overrun detected\n"); + ret = IRQ_HANDLED; + } + + return ret; +} + +static int mchp_spdifrx_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + mutex_lock(&dev->mlock); + /* Enable overrun interrupts */ + regmap_write(dev->regmap, SPDIFRX_IER, SPDIFRX_IR_OVERRUN); + + /* Enable receiver. */ + regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK, + SPDIFRX_MR_RXEN_ENABLE); + dev->trigger_enabled = true; + mutex_unlock(&dev->mlock); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + mutex_lock(&dev->mlock); + /* Disable overrun interrupts */ + regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_OVERRUN); + + /* Disable receiver. */ + regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK, + SPDIFRX_MR_RXEN_DISABLE); + dev->trigger_enabled = false; + mutex_unlock(&dev->mlock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int mchp_spdifrx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + u32 mr = 0; + 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_PLAYBACK) { + dev_err(dev->dev, "Playback is not supported\n"); + return -EINVAL; + } + + if (params_channels(params) != SPDIFRX_CHANNELS) { + dev_err(dev->dev, "unsupported number of channels: %d\n", + params_channels(params)); + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_BE: + case SNDRV_PCM_FORMAT_S20_3BE: + case SNDRV_PCM_FORMAT_S24_3BE: + case SNDRV_PCM_FORMAT_S24_BE: + mr |= SPDIFRX_MR_ENDIAN_BIG; + fallthrough; + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S24_LE: + mr |= SPDIFRX_MR_DATAWIDTH(params_width(params)); + break; + default: + dev_err(dev->dev, "unsupported PCM format: %d\n", + params_format(params)); + return -EINVAL; + } + + mutex_lock(&dev->mlock); + if (dev->trigger_enabled) { + dev_err(dev->dev, "PCM already running\n"); + ret = -EBUSY; + goto unlock; + } + + if (dev->gclk_enabled) { + clk_disable_unprepare(dev->gclk); + dev->gclk_enabled = 0; + } + ret = clk_set_min_rate(dev->gclk, params_rate(params) * + SPDIFRX_GCLK_RATIO_MIN + 1); + if (ret) { + dev_err(dev->dev, + "unable to set gclk min rate: rate %u * ratio %u + 1\n", + params_rate(params), SPDIFRX_GCLK_RATIO_MIN); + goto unlock; + } + ret = clk_prepare_enable(dev->gclk); + if (ret) { + dev_err(dev->dev, "unable to enable gclk: %d\n", ret); + goto unlock; + } + dev->gclk_enabled = 1; + + dev_dbg(dev->dev, "GCLK range min set to %d\n", + params_rate(params) * SPDIFRX_GCLK_RATIO_MIN + 1); + + ret = regmap_write(dev->regmap, SPDIFRX_MR, mr); + +unlock: + mutex_unlock(&dev->mlock); + + return ret; +} + +static int mchp_spdifrx_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + + mutex_lock(&dev->mlock); + if (dev->gclk_enabled) { + clk_disable_unprepare(dev->gclk); + dev->gclk_enabled = 0; + } + mutex_unlock(&dev->mlock); + return 0; +} + +static const struct snd_soc_dai_ops mchp_spdifrx_dai_ops = { + .trigger = mchp_spdifrx_trigger, + .hw_params = mchp_spdifrx_hw_params, + .hw_free = mchp_spdifrx_hw_free, +}; + +#define MCHP_SPDIF_RATES SNDRV_PCM_RATE_8000_192000 + +#define MCHP_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_U16_BE | \ + 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 \ + ) + +static int mchp_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 mchp_spdifrx_cs_get(struct mchp_spdifrx_dev *dev, + int channel, + struct snd_ctl_elem_value *uvalue) +{ + struct mchp_spdifrx_mixer_control *ctrl = &dev->control; + struct mchp_spdifrx_ch_stat *ch_stat = &ctrl->ch_stat[channel]; + int ret = 0; + + mutex_lock(&dev->mlock); + + /* + * We may reach this point with both clocks enabled but the receiver + * still disabled. To void waiting for completion and return with + * timeout check the dev->trigger_enabled. + * + * To retrieve data: + * - if the receiver is enabled CSC IRQ will update the data in software + * caches (ch_stat->data) + * - otherwise we just update it here the software caches with latest + * available information and return it; in this case we don't need + * spin locking as the IRQ is disabled and will not be raised from + * anywhere else. + */ + + if (dev->trigger_enabled) { + reinit_completion(&ch_stat->done); + regmap_write(dev->regmap, SPDIFRX_IER, SPDIFRX_IR_CSC(channel)); + /* Check for new data available */ + ret = wait_for_completion_interruptible_timeout(&ch_stat->done, + msecs_to_jiffies(100)); + /* Valid stream might not be present */ + if (ret <= 0) { + dev_dbg(dev->dev, "channel status for channel %d timeout\n", + channel); + regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_CSC(channel)); + ret = ret ? : -ETIMEDOUT; + goto unlock; + } else { + ret = 0; + } + } else { + /* Update software cache with latest channel status. */ + mchp_spdifrx_channel_status_read(dev, channel); + } + + memcpy(uvalue->value.iec958.status, ch_stat->data, + sizeof(ch_stat->data)); + +unlock: + mutex_unlock(&dev->mlock); + return ret; +} + +static int mchp_spdifrx_cs1_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + + return mchp_spdifrx_cs_get(dev, 0, uvalue); +} + +static int mchp_spdifrx_cs2_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + + return mchp_spdifrx_cs_get(dev, 1, uvalue); +} + +static int mchp_spdifrx_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_spdifrx_subcode_ch_get(struct mchp_spdifrx_dev *dev, + int channel, + struct snd_ctl_elem_value *uvalue) +{ + struct mchp_spdifrx_mixer_control *ctrl = &dev->control; + struct mchp_spdifrx_user_data *user_data = &ctrl->user_data[channel]; + int ret = 0; + + mutex_lock(&dev->mlock); + + /* + * We may reach this point with both clocks enabled but the receiver + * still disabled. To void waiting for completion to just timeout we + * check here the dev->trigger_enabled flag. + * + * To retrieve data: + * - if the receiver is enabled we need to wait for blockend IRQ to read + * data to and update it for us in software caches + * - otherwise reading the SPDIFRX_CHUD() registers is enough. + */ + + if (dev->trigger_enabled) { + reinit_completion(&user_data->done); + regmap_write(dev->regmap, SPDIFRX_IER, SPDIFRX_IR_BLOCKEND); + ret = wait_for_completion_interruptible_timeout(&user_data->done, + msecs_to_jiffies(100)); + /* Valid stream might not be present. */ + if (ret <= 0) { + dev_dbg(dev->dev, "user data for channel %d timeout\n", + channel); + regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_BLOCKEND); + ret = ret ? : -ETIMEDOUT; + goto unlock; + } else { + ret = 0; + } + } else { + /* Update software cache with last available data. */ + mchp_spdifrx_channel_user_data_read(dev, channel); + } + + memcpy(uvalue->value.iec958.subcode, user_data->data, + sizeof(user_data->data)); + +unlock: + mutex_unlock(&dev->mlock); + return ret; +} + +static int mchp_spdifrx_subcode_ch1_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + + return mchp_spdifrx_subcode_ch_get(dev, 0, uvalue); +} + +static int mchp_spdifrx_subcode_ch2_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + + return mchp_spdifrx_subcode_ch_get(dev, 1, uvalue); +} + +static int mchp_spdifrx_boolean_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +static int mchp_spdifrx_ulock_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + struct mchp_spdifrx_mixer_control *ctrl = &dev->control; + u32 val; + bool ulock_old = ctrl->ulock; + + mutex_lock(&dev->mlock); + + /* + * The RSR.ULOCK has wrong value if both pclk and gclk are enabled + * and the receiver is disabled. Thus we take into account the + * dev->trigger_enabled here to return a real status. + */ + if (dev->trigger_enabled) { + regmap_read(dev->regmap, SPDIFRX_RSR, &val); + ctrl->ulock = !(val & SPDIFRX_RSR_ULOCK); + } else { + ctrl->ulock = 0; + } + + uvalue->value.integer.value[0] = ctrl->ulock; + + mutex_unlock(&dev->mlock); + + return ulock_old != ctrl->ulock; +} + +static int mchp_spdifrx_badf_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + struct mchp_spdifrx_mixer_control *ctrl = &dev->control; + u32 val; + bool badf_old = ctrl->badf; + + mutex_lock(&dev->mlock); + + /* + * The RSR.ULOCK has wrong value if both pclk and gclk are enabled + * and the receiver is disabled. Thus we take into account the + * dev->trigger_enabled here to return a real status. + */ + if (dev->trigger_enabled) { + regmap_read(dev->regmap, SPDIFRX_RSR, &val); + ctrl->badf = !!(val & SPDIFRX_RSR_BADF); + } else { + ctrl->badf = 0; + } + + mutex_unlock(&dev->mlock); + + uvalue->value.integer.value[0] = ctrl->badf; + + return badf_old != ctrl->badf; +} + +static int mchp_spdifrx_signal_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + struct mchp_spdifrx_mixer_control *ctrl = &dev->control; + u32 val = ~0U, loops = 10; + int ret; + bool signal_old = ctrl->signal; + + mutex_lock(&dev->mlock); + + /* + * To get the signal we need to have receiver enabled. This + * could be enabled also from trigger() function thus we need to + * take care of not disabling the receiver when it runs. + */ + if (!dev->trigger_enabled) { + ret = clk_prepare_enable(dev->gclk); + if (ret) + goto unlock; + + regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK, + SPDIFRX_MR_RXEN_ENABLE); + + /* Wait for RSR.ULOCK bit. */ + while (--loops) { + regmap_read(dev->regmap, SPDIFRX_RSR, &val); + if (!(val & SPDIFRX_RSR_ULOCK)) + break; + usleep_range(100, 150); + } + + regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK, + SPDIFRX_MR_RXEN_DISABLE); + + clk_disable_unprepare(dev->gclk); + } else { + regmap_read(dev->regmap, SPDIFRX_RSR, &val); + } + +unlock: + mutex_unlock(&dev->mlock); + + if (!(val & SPDIFRX_RSR_ULOCK)) + ctrl->signal = !(val & SPDIFRX_RSR_NOSIGNAL); + else + ctrl->signal = 0; + uvalue->value.integer.value[0] = ctrl->signal; + + return signal_old != ctrl->signal; +} + +static int mchp_spdifrx_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 192000; + + return 0; +} + +static int mchp_spdifrx_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + u32 val; + int rate; + + mutex_lock(&dev->mlock); + + /* + * The RSR.ULOCK has wrong value if both pclk and gclk are enabled + * and the receiver is disabled. Thus we take into account the + * dev->trigger_enabled here to return a real status. + */ + if (dev->trigger_enabled) { + regmap_read(dev->regmap, SPDIFRX_RSR, &val); + /* If the receiver is not locked, ISF data is invalid. */ + if (val & SPDIFRX_RSR_ULOCK || !(val & SPDIFRX_RSR_IFS_MASK)) { + ucontrol->value.integer.value[0] = 0; + goto unlock; + } + } else { + /* Reveicer is not locked, IFS data is invalid. */ + ucontrol->value.integer.value[0] = 0; + goto unlock; + } + + rate = clk_get_rate(dev->gclk); + + ucontrol->value.integer.value[0] = rate / (32 * SPDIFRX_RSR_IFS(val)); + +unlock: + mutex_unlock(&dev->mlock); + return 0; +} + +static struct snd_kcontrol_new mchp_spdifrx_ctrls[] = { + /* Channel status controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT) + " Channel 1", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mchp_spdifrx_info, + .get = mchp_spdifrx_cs1_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT) + " Channel 2", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mchp_spdifrx_info, + .get = mchp_spdifrx_cs2_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = mchp_spdifrx_info, + .get = mchp_spdifrx_cs_mask, + }, + /* User bits controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Subcode Capture Default Channel 1", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mchp_spdifrx_info, + .get = mchp_spdifrx_subcode_ch1_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Subcode Capture Default Channel 2", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mchp_spdifrx_info, + .get = mchp_spdifrx_subcode_ch2_get, + }, + /* Lock status */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Unlocked", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mchp_spdifrx_boolean_info, + .get = mchp_spdifrx_ulock_get, + }, + /* Bad format */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE)"Bad Format", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mchp_spdifrx_boolean_info, + .get = mchp_spdifrx_badf_get, + }, + /* Signal */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Signal", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mchp_spdifrx_boolean_info, + .get = mchp_spdifrx_signal_get, + }, + /* Sampling rate */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mchp_spdifrx_rate_info, + .get = mchp_spdifrx_rate_get, + }, +}; + +static int mchp_spdifrx_dai_probe(struct snd_soc_dai *dai) +{ + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + struct mchp_spdifrx_mixer_control *ctrl = &dev->control; + int ch; + 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; + } + + snd_soc_dai_init_dma_data(dai, NULL, &dev->capture); + + /* Software reset the IP */ + regmap_write(dev->regmap, SPDIFRX_CR, SPDIFRX_CR_SWRST); + + /* Default configuration */ + regmap_write(dev->regmap, SPDIFRX_MR, + SPDIFRX_MR_VBMODE_DISCARD_IF_VB1 | + SPDIFRX_MR_SBMODE_DISCARD | + SPDIFRX_MR_AUTORST_NOACTION | + SPDIFRX_MR_PACK_DISABLED); + + for (ch = 0; ch < SPDIFRX_CHANNELS; ch++) { + init_completion(&ctrl->ch_stat[ch].done); + init_completion(&ctrl->user_data[ch].done); + } + + /* Add controls */ + snd_soc_add_dai_controls(dai, mchp_spdifrx_ctrls, + ARRAY_SIZE(mchp_spdifrx_ctrls)); + + return 0; +} + +static int mchp_spdifrx_dai_remove(struct snd_soc_dai *dai) +{ + struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai); + + /* Disable interrupts */ + regmap_write(dev->regmap, SPDIFRX_IDR, GENMASK(14, 0)); + + clk_disable_unprepare(dev->pclk); + + return 0; +} + +static struct snd_soc_dai_driver mchp_spdifrx_dai = { + .name = "mchp-spdifrx", + .probe = mchp_spdifrx_dai_probe, + .remove = mchp_spdifrx_dai_remove, + .capture = { + .stream_name = "S/PDIF Capture", + .channels_min = SPDIFRX_CHANNELS, + .channels_max = SPDIFRX_CHANNELS, + .rates = MCHP_SPDIF_RATES, + .formats = MCHP_SPDIF_FORMATS, + }, + .ops = &mchp_spdifrx_dai_ops, +}; + +static const struct snd_soc_component_driver mchp_spdifrx_component = { + .name = "mchp-spdifrx", +}; + +static const struct of_device_id mchp_spdifrx_dt_ids[] = { + { + .compatible = "microchip,sama7g5-spdifrx", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mchp_spdifrx_dt_ids); + +static int mchp_spdifrx_probe(struct platform_device *pdev) +{ + struct mchp_spdifrx_dev *dev; + struct resource *mem; + struct regmap *regmap; + void __iomem *base; + int irq; + int err; + u32 vers; + + /* Get memory for driver data. */ + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* 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_spdifrx_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_spdif_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 generated 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 generated clock: %d\n", err); + return err; + } + + /* + * Signal control need a valid rate on gclk. hw_params() configures + * it propertly but requesting signal before any hw_params() has been + * called lead to invalid value returned for signal. Thus, configure + * gclk at a valid rate, here, in initialization, to simplify the + * control path. + */ + clk_set_min_rate(dev->gclk, 48000 * SPDIFRX_GCLK_RATIO_MIN + 1); + + mutex_init(&dev->mlock); + + dev->dev = &pdev->dev; + dev->regmap = regmap; + platform_set_drvdata(pdev, dev); + + dev->capture.addr = (dma_addr_t)mem->start + SPDIFRX_RHR; + dev->capture.maxburst = 1; + + 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_spdifrx_component, + &mchp_spdifrx_dai, 1); + if (err) { + dev_err(&pdev->dev, "fail to register dai\n"); + return err; + } + + regmap_read(regmap, SPDIFRX_VERSION, &vers); + dev_info(&pdev->dev, "hw version: %#lx\n", vers & SPDIFRX_VERSION_MASK); + + return 0; +} + +static struct platform_driver mchp_spdifrx_driver = { + .probe = mchp_spdifrx_probe, + .driver = { + .name = "mchp_spdifrx", + .of_match_table = of_match_ptr(mchp_spdifrx_dt_ids), + }, +}; + +module_platform_driver(mchp_spdifrx_driver); + +MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@microchip.com>"); +MODULE_DESCRIPTION("Microchip S/PDIF RX Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/atmel/mchp-spdiftx.c b/sound/soc/atmel/mchp-spdiftx.c new file mode 100644 index 000000000..bcca1cf3c --- /dev/null +++ b/sound/soc/atmel/mchp-spdiftx.c @@ -0,0 +1,862 @@ +// 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@microchip.com> + +#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 */ +#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 underrun */ +#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; /* exclusive access to control data */ +}; + +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; + unsigned 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); + + /* 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); +} + +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); + 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); + + 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); + + 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); + + 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); + int ret; + + snd_soc_dai_init_dma_data(dai, &dev->playback, NULL); + + ret = clk_prepare_enable(dev->pclk); + if (ret) { + dev_err(dev->dev, + "failed to enable the peripheral clock: %d\n", ret); + return ret; + } + + /* Add controls */ + snd_soc_add_dai_controls(dai, mchp_spdiftx_ctrls, + ARRAY_SIZE(mchp_spdiftx_ctrls)); + + return 0; +} + +static int mchp_spdiftx_dai_remove(struct snd_soc_dai *dai) +{ + struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(dev->pclk); + + return 0; +} + +static struct snd_soc_dai_driver mchp_spdiftx_dai = { + .name = "mchp-spdiftx", + .probe = mchp_spdiftx_dai_probe, + .remove = mchp_spdiftx_dai_remove, + .playback = { + .stream_name = "S/PDIF 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 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; + + /* 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@microchip.com>"); +MODULE_DESCRIPTION("Microchip S/PDIF TX Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/atmel/mikroe-proto.c b/sound/soc/atmel/mikroe-proto.c new file mode 100644 index 000000000..f9a85fd01 --- /dev/null +++ b/sound/soc/atmel/mikroe-proto.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ASoC driver for PROTO AudioCODEC (with a WM8731) + * + * Author: Florian Meier, <koalo@koalo.de> + * Copyright 2013 + */ + +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include "../codecs/wm8731.h" + +#define XTAL_RATE 12288000 /* This is fixed on this board */ + +static int snd_proto_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + + /* Set proto sysclk */ + int ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, + XTAL_RATE, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "Failed to set WM8731 SYSCLK: %d\n", + ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_dapm_widget snd_proto_widget[] = { + SND_SOC_DAPM_MIC("Microphone Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_soc_dapm_route snd_proto_route[] = { + /* speaker connected to LHPOUT/RHPOUT */ + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + /* mic is connected to Mic Jack, with WM8731 Mic Bias */ + {"MICIN", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Microphone Jack"}, +}; + +/* audio machine driver */ +static struct snd_soc_card snd_proto = { + .name = "snd_mikroe_proto", + .owner = THIS_MODULE, + .dapm_widgets = snd_proto_widget, + .num_dapm_widgets = ARRAY_SIZE(snd_proto_widget), + .dapm_routes = snd_proto_route, + .num_dapm_routes = ARRAY_SIZE(snd_proto_route), +}; + +static int snd_proto_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *dai; + struct snd_soc_dai_link_component *comp; + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct device_node *bitclkmaster = NULL; + struct device_node *framemaster = NULL; + unsigned int dai_fmt; + int ret = 0; + + if (!np) { + dev_err(&pdev->dev, "No device node supplied\n"); + return -EINVAL; + } + + snd_proto.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&snd_proto, "model"); + if (ret) + return ret; + + dai = devm_kzalloc(&pdev->dev, sizeof(*dai), GFP_KERNEL); + if (!dai) + return -ENOMEM; + + /* for cpus/codecs/platforms */ + comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL); + if (!comp) + return -ENOMEM; + + snd_proto.dai_link = dai; + snd_proto.num_links = 1; + + dai->cpus = &comp[0]; + dai->num_cpus = 1; + dai->codecs = &comp[1]; + dai->num_codecs = 1; + dai->platforms = &comp[2]; + dai->num_platforms = 1; + + dai->name = "WM8731"; + dai->stream_name = "WM8731 HiFi"; + dai->codecs->dai_name = "wm8731-hifi"; + dai->init = &snd_proto_init; + + codec_np = of_parse_phandle(np, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "audio-codec node missing\n"); + return -EINVAL; + } + dai->codecs->of_node = codec_np; + + cpu_np = of_parse_phandle(np, "i2s-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "i2s-controller missing\n"); + return -EINVAL; + } + dai->cpus->of_node = cpu_np; + dai->platforms->of_node = cpu_np; + + dai_fmt = snd_soc_of_parse_daifmt(np, NULL, + &bitclkmaster, &framemaster); + if (bitclkmaster != framemaster) { + dev_err(&pdev->dev, "Must be the same bitclock and frame master\n"); + return -EINVAL; + } + if (bitclkmaster) { + dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + if (codec_np == bitclkmaster) + dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + else + dai_fmt |= SND_SOC_DAIFMT_CBS_CFS; + } + of_node_put(bitclkmaster); + of_node_put(framemaster); + dai->dai_fmt = dai_fmt; + + of_node_put(codec_np); + of_node_put(cpu_np); + + ret = snd_soc_register_card(&snd_proto); + if (ret && ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "snd_soc_register_card() failed: %d\n", ret); + + return ret; +} + +static int snd_proto_remove(struct platform_device *pdev) +{ + return snd_soc_unregister_card(&snd_proto); +} + +static const struct of_device_id snd_proto_of_match[] = { + { .compatible = "mikroe,mikroe-proto", }, + {}, +}; +MODULE_DEVICE_TABLE(of, snd_proto_of_match); + +static struct platform_driver snd_proto_driver = { + .driver = { + .name = "snd-mikroe-proto", + .of_match_table = snd_proto_of_match, + }, + .probe = snd_proto_probe, + .remove = snd_proto_remove, +}; + +module_platform_driver(snd_proto_driver); + +MODULE_AUTHOR("Florian Meier"); +MODULE_DESCRIPTION("ASoC Driver for PROTO board (WM8731)"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c new file mode 100644 index 000000000..8a55d59a6 --- /dev/null +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * sam9g20_wm8731 -- SoC audio for AT91SAM9G20-based + * ATMEL AT91SAM9G20ek board. + * + * Copyright (C) 2005 SAN People + * Copyright (C) 2008 Atmel + * + * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com> + * + * Based on ati_b1_wm8731.c by: + * Frank Mandarino <fmandarino@endrelia.com> + * Copyright 2006 Endrelia Technologies Inc. + * Based on corgi.c by: + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/of.h> + +#include <linux/atmel-ssc.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "../codecs/wm8731.h" +#include "atmel-pcm.h" +#include "atmel_ssc_dai.h" + +#define MCLK_RATE 12000000 + +/* + * As shipped the board does not have inputs. However, it is relatively + * straightforward to modify the board to hook them up so support is left + * in the driver. + */ +#undef ENABLE_MIC_INPUT + +static struct clk *mclk; + +static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + static int mclk_on; + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + if (!mclk_on) + ret = clk_enable(mclk); + if (ret == 0) + mclk_on = 1; + break; + + case SND_SOC_BIAS_OFF: + case SND_SOC_BIAS_STANDBY: + if (mclk_on) + clk_disable(mclk); + mclk_on = 0; + break; + } + + return ret; +} + +static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_soc_dapm_route intercon[] = { + + /* speaker connected to LHPOUT/RHPOUT */ + {"Ext Spk", NULL, "LHPOUT"}, + {"Ext Spk", NULL, "RHPOUT"}, + + /* mic is connected to Mic Jack, with WM8731 Mic Bias */ + {"MICIN", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Int Mic"}, +}; + +/* + * Logic for a wm8731 as connected on a at91sam9g20ek board. + */ +static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct device *dev = rtd->dev; + int ret; + + dev_dbg(dev, "%s called\n", __func__); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_MCLK, + MCLK_RATE, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret); + return ret; + } + +#ifndef ENABLE_MIC_INPUT + snd_soc_dapm_nc_pin(&rtd->card->dapm, "Int Mic"); +#endif + + return 0; +} + +SND_SOC_DAILINK_DEFS(pcm, + DAILINK_COMP_ARRAY(COMP_CPU("at91rm9200_ssc.0")), + DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.0-001b", "wm8731-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("at91rm9200_ssc.0"))); + +static struct snd_soc_dai_link at91sam9g20ek_dai = { + .name = "WM8731", + .stream_name = "WM8731 PCM", + .init = at91sam9g20ek_wm8731_init, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM, + SND_SOC_DAILINK_REG(pcm), +}; + +static struct snd_soc_card snd_soc_at91sam9g20ek = { + .name = "AT91SAMG20-EK", + .owner = THIS_MODULE, + .dai_link = &at91sam9g20ek_dai, + .num_links = 1, + .set_bias_level = at91sam9g20ek_set_bias_level, + + .dapm_widgets = at91sam9g20ek_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(at91sam9g20ek_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .fully_routed = true, +}; + +static int at91sam9g20ek_audio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct clk *pllb; + struct snd_soc_card *card = &snd_soc_at91sam9g20ek; + int ret; + + if (!np) { + return -ENODEV; + } + + ret = atmel_ssc_set_audio(0); + if (ret) { + dev_err(&pdev->dev, "ssc channel is not valid\n"); + return -EINVAL; + } + + /* + * Codec MCLK is supplied by PCK0 - set it up. + */ + mclk = clk_get(NULL, "pck0"); + if (IS_ERR(mclk)) { + dev_err(&pdev->dev, "Failed to get MCLK\n"); + ret = PTR_ERR(mclk); + goto err; + } + + pllb = clk_get(NULL, "pllb"); + if (IS_ERR(pllb)) { + dev_err(&pdev->dev, "Failed to get PLLB\n"); + ret = PTR_ERR(pllb); + goto err_mclk; + } + ret = clk_set_parent(mclk, pllb); + clk_put(pllb); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to set MCLK parent\n"); + goto err_mclk; + } + + clk_set_rate(mclk, MCLK_RATE); + + card->dev = &pdev->dev; + + /* Parse device node info */ + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, + "atmel,audio-routing"); + if (ret) + goto err; + + /* Parse codec info */ + at91sam9g20ek_dai.codecs->name = NULL; + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "codec info missing\n"); + return -EINVAL; + } + at91sam9g20ek_dai.codecs->of_node = codec_np; + + /* Parse dai and platform info */ + at91sam9g20ek_dai.cpus->dai_name = NULL; + at91sam9g20ek_dai.platforms->name = NULL; + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "dai and pcm info missing\n"); + of_node_put(codec_np); + return -EINVAL; + } + at91sam9g20ek_dai.cpus->of_node = cpu_np; + at91sam9g20ek_dai.platforms->of_node = cpu_np; + + of_node_put(codec_np); + of_node_put(cpu_np); + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed\n"); + } + + return ret; + +err_mclk: + clk_put(mclk); + mclk = NULL; +err: + atmel_ssc_put_audio(0); + return ret; +} + +static int at91sam9g20ek_audio_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + clk_disable(mclk); + mclk = NULL; + snd_soc_unregister_card(card); + atmel_ssc_put_audio(0); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id at91sam9g20ek_wm8731_dt_ids[] = { + { .compatible = "atmel,at91sam9g20ek-wm8731-audio", }, + { } +}; +MODULE_DEVICE_TABLE(of, at91sam9g20ek_wm8731_dt_ids); +#endif + +static struct platform_driver at91sam9g20ek_audio_driver = { + .driver = { + .name = "at91sam9g20ek-audio", + .of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids), + }, + .probe = at91sam9g20ek_audio_probe, + .remove = at91sam9g20ek_audio_remove, +}; + +module_platform_driver(at91sam9g20ek_audio_driver); + +/* Module information */ +MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>"); +MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731"); +MODULE_ALIAS("platform:at91sam9g20ek-audio"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/atmel/sam9x5_wm8731.c b/sound/soc/atmel/sam9x5_wm8731.c new file mode 100644 index 000000000..529604a06 --- /dev/null +++ b/sound/soc/atmel/sam9x5_wm8731.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * sam9x5_wm8731 -- SoC audio for AT91SAM9X5-based boards + * that are using WM8731 as codec. + * + * Copyright (C) 2011 Atmel, + * Nicolas Ferre <nicolas.ferre@atmel.com> + * + * Copyright (C) 2013 Paratronic, + * Richard Genoud <richard.genoud@gmail.com> + * + * Based on sam9g20_wm8731.c by: + * Sedji Gaouaou <sedji.gaouaou@atmel.com> + */ +#include <linux/of.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/device.h> + +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <sound/soc-dapm.h> + +#include "../codecs/wm8731.h" +#include "atmel_ssc_dai.h" + + +#define MCLK_RATE 12288000 + +#define DRV_NAME "sam9x5-snd-wm8731" + +struct sam9x5_drvdata { + int ssc_id; +}; + +/* + * Logic for a wm8731 as connected on a at91sam9x5ek based board. + */ +static int sam9x5_wm8731_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct device *dev = rtd->dev; + int ret; + + dev_dbg(dev, "%s called\n", __func__); + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, + MCLK_RATE, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret); + return ret; + } + + return 0; +} + +/* + * Audio paths on at91sam9x5ek board: + * + * |A| ------------> | | ---R----> Headphone Jack + * |T| <----\ | WM | ---L--/ + * |9| ---> CLK <--> | 8731 | <--R----- Line In Jack + * |1| <------------ | | <--L--/ + */ +static const struct snd_soc_dapm_widget sam9x5_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), +}; + +static int sam9x5_wm8731_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_card *card; + struct snd_soc_dai_link *dai; + struct sam9x5_drvdata *priv; + struct snd_soc_dai_link_component *comp; + int ret; + + if (!np) { + dev_err(&pdev->dev, "No device node supplied\n"); + return -EINVAL; + } + + card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL); + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + dai = devm_kzalloc(&pdev->dev, sizeof(*dai), GFP_KERNEL); + comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL); + if (!dai || !card || !priv || !comp) { + ret = -ENOMEM; + goto out; + } + + snd_soc_card_set_drvdata(card, priv); + + card->dev = &pdev->dev; + card->owner = THIS_MODULE; + card->dai_link = dai; + card->num_links = 1; + card->dapm_widgets = sam9x5_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sam9x5_dapm_widgets); + + dai->cpus = &comp[0]; + dai->num_cpus = 1; + dai->codecs = &comp[1]; + dai->num_codecs = 1; + dai->platforms = &comp[2]; + dai->num_platforms = 1; + + dai->name = "WM8731"; + dai->stream_name = "WM8731 PCM"; + dai->codecs->dai_name = "wm8731-hifi"; + dai->init = sam9x5_wm8731_init; + dai->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM; + + ret = snd_soc_of_parse_card_name(card, "atmel,model"); + if (ret) { + dev_err(&pdev->dev, "atmel,model node missing\n"); + goto out; + } + + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); + if (ret) { + dev_err(&pdev->dev, "atmel,audio-routing node missing\n"); + goto out; + } + + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "atmel,audio-codec node missing\n"); + ret = -EINVAL; + goto out; + } + + dai->codecs->of_node = codec_np; + + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "atmel,ssc-controller node missing\n"); + ret = -EINVAL; + goto out_put_codec_np; + } + dai->cpus->of_node = cpu_np; + dai->platforms->of_node = cpu_np; + + priv->ssc_id = of_alias_get_id(cpu_np, "ssc"); + + ret = atmel_ssc_set_audio(priv->ssc_id); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to set SSC %d for audio: %d\n", + ret, priv->ssc_id); + goto out_put_cpu_np; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) { + dev_err(&pdev->dev, "Platform device allocation failed\n"); + goto out_put_audio; + } + + dev_dbg(&pdev->dev, "%s ok\n", __func__); + + goto out_put_cpu_np; + +out_put_audio: + atmel_ssc_put_audio(priv->ssc_id); +out_put_cpu_np: + of_node_put(cpu_np); +out_put_codec_np: + of_node_put(codec_np); +out: + return ret; +} + +static int sam9x5_wm8731_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct sam9x5_drvdata *priv = card->drvdata; + + atmel_ssc_put_audio(priv->ssc_id); + + return 0; +} + +static const struct of_device_id sam9x5_wm8731_of_match[] = { + { .compatible = "atmel,sam9x5-wm8731-audio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sam9x5_wm8731_of_match); + +static struct platform_driver sam9x5_wm8731_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(sam9x5_wm8731_of_match), + }, + .probe = sam9x5_wm8731_driver_probe, + .remove = sam9x5_wm8731_driver_remove, +}; +module_platform_driver(sam9x5_wm8731_driver); + +/* Module information */ +MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>"); +MODULE_AUTHOR("Richard Genoud <richard.genoud@gmail.com>"); +MODULE_DESCRIPTION("ALSA SoC machine driver for AT91SAM9x5 - WM8731"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/atmel/tse850-pcm5142.c b/sound/soc/atmel/tse850-pcm5142.c new file mode 100644 index 000000000..50c3dc693 --- /dev/null +++ b/sound/soc/atmel/tse850-pcm5142.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// TSE-850 audio - ASoC driver for the Axentia TSE-850 with a PCM5142 codec +// +// Copyright (C) 2016 Axentia Technologies AB +// +// Author: Peter Rosin <peda@axentia.se> +// +// loop1 relays +// IN1 +---o +------------+ o---+ OUT1 +// \ / +// + + +// | / | +// +--o +--. | +// | add | | +// | V | +// | .---. | +// DAC +----------->|Sum|---+ +// | '---' | +// | | +// + + +// +// IN2 +---o--+------------+--o---+ OUT2 +// loop2 relays +// +// The 'loop1' gpio pin controls two relays, which are either in loop +// position, meaning that input and output are directly connected, or +// they are in mixer position, meaning that the signal is passed through +// the 'Sum' mixer. Similarly for 'loop2'. +// +// In the above, the 'loop1' relays are inactive, thus feeding IN1 to the +// mixer (if 'add' is active) and feeding the mixer output to OUT1. The +// 'loop2' relays are active, short-cutting the TSE-850 from channel 2. +// IN1, IN2, OUT1 and OUT2 are TSE-850 connectors and DAC is the PCB name +// of the (filtered) output from the PCM5142 codec. + +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> + +#include <sound/soc.h> +#include <sound/pcm_params.h> + +struct tse850_priv { + struct gpio_desc *add; + struct gpio_desc *loop1; + struct gpio_desc *loop2; + + struct regulator *ana; + + int add_cache; + int loop1_cache; + int loop2_cache; +}; + +static int tse850_get_mux1(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = tse850->loop1_cache; + + return 0; +} + +static int tse850_put_mux1(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kctrl->private_value; + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val >= e->items) + return -EINVAL; + + gpiod_set_value_cansleep(tse850->loop1, val); + tse850->loop1_cache = val; + + return snd_soc_dapm_put_enum_double(kctrl, ucontrol); +} + +static int tse850_get_mux2(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = tse850->loop2_cache; + + return 0; +} + +static int tse850_put_mux2(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kctrl->private_value; + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val >= e->items) + return -EINVAL; + + gpiod_set_value_cansleep(tse850->loop2, val); + tse850->loop2_cache = val; + + return snd_soc_dapm_put_enum_double(kctrl, ucontrol); +} + +static int tse850_get_mix(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = tse850->add_cache; + + return 0; +} + +static int tse850_put_mix(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + int connect = !!ucontrol->value.integer.value[0]; + + if (tse850->add_cache == connect) + return 0; + + /* + * Hmmm, this gpiod_set_value_cansleep call should probably happen + * inside snd_soc_dapm_mixer_update_power in the loop. + */ + gpiod_set_value_cansleep(tse850->add, connect); + tse850->add_cache = connect; + + snd_soc_dapm_mixer_update_power(dapm, kctrl, connect, NULL); + return 1; +} + +static int tse850_get_ana(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + int ret; + + ret = regulator_get_voltage(tse850->ana); + if (ret < 0) + return ret; + + /* + * Map regulator output values like so: + * -11.5V to "Low" (enum 0) + * 11.5V-12.5V to "12V" (enum 1) + * 12.5V-13.5V to "13V" (enum 2) + * ... + * 18.5V-19.5V to "19V" (enum 8) + * 19.5V- to "20V" (enum 9) + */ + if (ret < 11000000) + ret = 11000000; + else if (ret > 20000000) + ret = 20000000; + ret -= 11000000; + ret = (ret + 500000) / 1000000; + + ucontrol->value.enumerated.item[0] = ret; + + return 0; +} + +static int tse850_put_ana(struct snd_kcontrol *kctrl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kctrl); + struct snd_soc_card *card = dapm->card; + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kctrl->private_value; + unsigned int uV = ucontrol->value.enumerated.item[0]; + int ret; + + if (uV >= e->items) + return -EINVAL; + + /* + * Map enum zero (Low) to 2 volts on the regulator, do this since + * the ana regulator is supplied by the system 12V voltage and + * requesting anything below the system voltage causes the system + * voltage to be passed through the regulator. Also, the ana + * regulator induces noise when requesting voltages near the + * system voltage. So, by mapping Low to 2V, that noise is + * eliminated when all that is needed is 12V (the system voltage). + */ + if (uV) + uV = 11000000 + (1000000 * uV); + else + uV = 2000000; + + ret = regulator_set_voltage(tse850->ana, uV, uV); + if (ret < 0) + return ret; + + return snd_soc_dapm_put_enum_double(kctrl, ucontrol); +} + +static const char * const mux_text[] = { "Mixer", "Loop" }; + +static const struct soc_enum mux_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(mux_text), mux_text); + +static const struct snd_kcontrol_new mux1 = + SOC_DAPM_ENUM_EXT("MUX1", mux_enum, tse850_get_mux1, tse850_put_mux1); + +static const struct snd_kcontrol_new mux2 = + SOC_DAPM_ENUM_EXT("MUX2", mux_enum, tse850_get_mux2, tse850_put_mux2); + +#define TSE850_DAPM_SINGLE_EXT(xname, reg, shift, max, invert, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = xget, \ + .put = xput, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) } + +static const struct snd_kcontrol_new mix[] = { + TSE850_DAPM_SINGLE_EXT("IN Switch", SND_SOC_NOPM, 0, 1, 0, + tse850_get_mix, tse850_put_mix), +}; + +static const char * const ana_text[] = { + "Low", "12V", "13V", "14V", "15V", "16V", "17V", "18V", "19V", "20V" +}; + +static const struct soc_enum ana_enum = + SOC_ENUM_SINGLE(SND_SOC_NOPM, 0, ARRAY_SIZE(ana_text), ana_text); + +static const struct snd_kcontrol_new out = + SOC_DAPM_ENUM_EXT("ANA", ana_enum, tse850_get_ana, tse850_put_ana); + +static const struct snd_soc_dapm_widget tse850_dapm_widgets[] = { + SND_SOC_DAPM_LINE("OUT1", NULL), + SND_SOC_DAPM_LINE("OUT2", NULL), + SND_SOC_DAPM_LINE("IN1", NULL), + SND_SOC_DAPM_LINE("IN2", NULL), + SND_SOC_DAPM_INPUT("DAC"), + SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0), + SOC_MIXER_ARRAY("MIX", SND_SOC_NOPM, 0, 0, mix), + SND_SOC_DAPM_MUX("MUX1", SND_SOC_NOPM, 0, 0, &mux1), + SND_SOC_DAPM_MUX("MUX2", SND_SOC_NOPM, 0, 0, &mux2), + SND_SOC_DAPM_OUT_DRV("OUT", SND_SOC_NOPM, 0, 0, &out, 1), +}; + +/* + * These connections are not entirely correct, since both IN1 and IN2 + * are always fed to MIX (if the "IN switch" is set so), i.e. without + * regard to the loop1 and loop2 relays that according to this only + * control MUX1 and MUX2 but in fact also control how the input signals + * are routed. + * But, 1) I don't know how to do it right, and 2) it doesn't seem to + * matter in practice since nothing is powered in those sections anyway. + */ +static const struct snd_soc_dapm_route tse850_intercon[] = { + { "OUT1", NULL, "MUX1" }, + { "OUT2", NULL, "MUX2" }, + + { "MUX1", "Loop", "IN1" }, + { "MUX1", "Mixer", "OUT" }, + + { "MUX2", "Loop", "IN2" }, + { "MUX2", "Mixer", "OUT" }, + + { "OUT", NULL, "MIX" }, + + { "MIX", NULL, "DAC" }, + { "MIX", "IN Switch", "IN1" }, + { "MIX", "IN Switch", "IN2" }, + + /* connect board input to the codec left channel output pin */ + { "DAC", NULL, "OUTL" }, +}; + +SND_SOC_DAILINK_DEFS(pcm, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "pcm512x-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link tse850_dailink = { + .name = "TSE-850", + .stream_name = "TSE-850-PCM", + .dai_fmt = SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFS, + SND_SOC_DAILINK_REG(pcm), +}; + +static struct snd_soc_card tse850_card = { + .name = "TSE-850-ASoC", + .owner = THIS_MODULE, + .dai_link = &tse850_dailink, + .num_links = 1, + .dapm_widgets = tse850_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tse850_dapm_widgets), + .dapm_routes = tse850_intercon, + .num_dapm_routes = ARRAY_SIZE(tse850_intercon), + .fully_routed = true, +}; + +static int tse850_dt_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *codec_np, *cpu_np; + struct snd_soc_dai_link *dailink = &tse850_dailink; + + if (!np) { + dev_err(&pdev->dev, "only device tree supported\n"); + return -EINVAL; + } + + cpu_np = of_parse_phandle(np, "axentia,cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "failed to get cpu dai\n"); + return -EINVAL; + } + dailink->cpus->of_node = cpu_np; + dailink->platforms->of_node = cpu_np; + of_node_put(cpu_np); + + codec_np = of_parse_phandle(np, "axentia,audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "failed to get codec info\n"); + return -EINVAL; + } + dailink->codecs->of_node = codec_np; + of_node_put(codec_np); + + return 0; +} + +static int tse850_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &tse850_card; + struct device *dev = card->dev = &pdev->dev; + struct tse850_priv *tse850; + int ret; + + tse850 = devm_kzalloc(dev, sizeof(*tse850), GFP_KERNEL); + if (!tse850) + return -ENOMEM; + + snd_soc_card_set_drvdata(card, tse850); + + ret = tse850_dt_init(pdev); + if (ret) { + dev_err(dev, "failed to init dt info\n"); + return ret; + } + + tse850->add = devm_gpiod_get(dev, "axentia,add", GPIOD_OUT_HIGH); + if (IS_ERR(tse850->add)) { + if (PTR_ERR(tse850->add) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'add' gpio\n"); + return PTR_ERR(tse850->add); + } + tse850->add_cache = 1; + + tse850->loop1 = devm_gpiod_get(dev, "axentia,loop1", GPIOD_OUT_HIGH); + if (IS_ERR(tse850->loop1)) { + if (PTR_ERR(tse850->loop1) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'loop1' gpio\n"); + return PTR_ERR(tse850->loop1); + } + tse850->loop1_cache = 1; + + tse850->loop2 = devm_gpiod_get(dev, "axentia,loop2", GPIOD_OUT_HIGH); + if (IS_ERR(tse850->loop2)) { + if (PTR_ERR(tse850->loop2) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'loop2' gpio\n"); + return PTR_ERR(tse850->loop2); + } + tse850->loop2_cache = 1; + + tse850->ana = devm_regulator_get(dev, "axentia,ana"); + if (IS_ERR(tse850->ana)) { + if (PTR_ERR(tse850->ana) != -EPROBE_DEFER) + dev_err(dev, "failed to get 'ana' regulator\n"); + return PTR_ERR(tse850->ana); + } + + ret = regulator_enable(tse850->ana); + if (ret < 0) { + dev_err(dev, "failed to enable the 'ana' regulator\n"); + return ret; + } + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(dev, "snd_soc_register_card failed\n"); + goto err_disable_ana; + } + + return 0; + +err_disable_ana: + regulator_disable(tse850->ana); + return ret; +} + +static int tse850_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + regulator_disable(tse850->ana); + + return 0; +} + +static const struct of_device_id tse850_dt_ids[] = { + { .compatible = "axentia,tse850-pcm5142", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tse850_dt_ids); + +static struct platform_driver tse850_driver = { + .driver = { + .name = "axentia-tse850-pcm5142", + .of_match_table = of_match_ptr(tse850_dt_ids), + }, + .probe = tse850_probe, + .remove = tse850_remove, +}; + +module_platform_driver(tse850_driver); + +/* Module information */ +MODULE_AUTHOR("Peter Rosin <peda@axentia.se>"); +MODULE_DESCRIPTION("ALSA SoC driver for TSE-850 with PCM5142 codec"); +MODULE_LICENSE("GPL v2"); |