diff options
Diffstat (limited to 'sound/soc/cirrus')
-rw-r--r-- | sound/soc/cirrus/Kconfig | 42 | ||||
-rw-r--r-- | sound/soc/cirrus/Makefile | 12 | ||||
-rw-r--r-- | sound/soc/cirrus/edb93xx.c | 117 | ||||
-rw-r--r-- | sound/soc/cirrus/ep93xx-i2s.c | 538 | ||||
-rw-r--r-- | sound/soc/cirrus/ep93xx-pcm.c | 67 | ||||
-rw-r--r-- | sound/soc/cirrus/ep93xx-pcm.h | 11 |
6 files changed, 787 insertions, 0 deletions
diff --git a/sound/soc/cirrus/Kconfig b/sound/soc/cirrus/Kconfig new file mode 100644 index 0000000000..38a83c4dcc --- /dev/null +++ b/sound/soc/cirrus/Kconfig @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_EP93XX_SOC + tristate "SoC Audio support for the Cirrus Logic EP93xx series" + depends on ARCH_EP93XX || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for codecs attached to + the EP93xx I2S or AC97 interfaces. + +config SND_EP93XX_SOC_I2S + tristate "I2S controller support for the Cirrus Logic EP93xx series" + depends on SND_EP93XX_SOC + help + Say Y or M if you want to add support for codecs attached to + the EP93xx I2S interface. + +if SND_EP93XX_SOC_I2S + +config SND_EP93XX_SOC_I2S_WATCHDOG + bool "IRQ based underflow watchdog workaround" + default y + help + I2S controller on EP93xx seems to have undocumented HW issue. + Underflow of internal I2S controller FIFO could confuse the + state machine and the whole stream can be shifted by one byte + until I2S is disabled. This option enables IRQ based watchdog + which disables and re-enables I2S in case of underflow and + fills FIFO with zeroes. + + If you are unsure how to answer this question, answer Y. + +endif # if SND_EP93XX_SOC_I2S + +config SND_EP93XX_SOC_EDB93XX + tristate "SoC Audio support for Cirrus Logic EDB93xx boards" + depends on SND_EP93XX_SOC && (MACH_EDB9301 || MACH_EDB9302 || MACH_EDB9302A || MACH_EDB9307A || MACH_EDB9315A) + select SND_EP93XX_SOC_I2S + select SND_SOC_CS4271_I2C if I2C + select SND_SOC_CS4271_SPI if SPI_MASTER + help + Say Y or M here if you want to add support for I2S audio on the + Cirrus Logic EDB93xx boards. diff --git a/sound/soc/cirrus/Makefile b/sound/soc/cirrus/Makefile new file mode 100644 index 0000000000..19a86daad6 --- /dev/null +++ b/sound/soc/cirrus/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# EP93xx Platform Support +snd-soc-ep93xx-objs := ep93xx-pcm.o +snd-soc-ep93xx-i2s-objs := ep93xx-i2s.o + +obj-$(CONFIG_SND_EP93XX_SOC) += snd-soc-ep93xx.o +obj-$(CONFIG_SND_EP93XX_SOC_I2S) += snd-soc-ep93xx-i2s.o + +# EP93XX Machine Support +snd-soc-edb93xx-objs := edb93xx.o + +obj-$(CONFIG_SND_EP93XX_SOC_EDB93XX) += snd-soc-edb93xx.o diff --git a/sound/soc/cirrus/edb93xx.c b/sound/soc/cirrus/edb93xx.c new file mode 100644 index 0000000000..f49caab21a --- /dev/null +++ b/sound/soc/cirrus/edb93xx.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SoC audio for EDB93xx + * + * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru> + * + * This driver support CS4271 codec being master or slave, working + * in control port mode, connected either via SPI or I2C. + * The data format accepted is I2S or left-justified. + * DAPM support not implemented. + */ + +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/soc/cirrus/ep93xx.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <asm/mach-types.h> + +static int edb93xx_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); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + int err; + unsigned int mclk_rate; + unsigned int rate = params_rate(params); + + /* + * According to CS4271 datasheet we use MCLK/LRCK=256 for + * rates below 50kHz and 128 for higher sample rates + */ + if (rate < 50000) + mclk_rate = rate * 64 * 4; + else + mclk_rate = rate * 64 * 2; + + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate, + SND_SOC_CLOCK_IN); + if (err) + return err; + + return snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate, + SND_SOC_CLOCK_OUT); +} + +static const struct snd_soc_ops edb93xx_ops = { + .hw_params = edb93xx_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_CPU("ep93xx-i2s")), + DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0", "cs4271-hifi")), + DAILINK_COMP_ARRAY(COMP_PLATFORM("ep93xx-i2s"))); + +static struct snd_soc_dai_link edb93xx_dai = { + .name = "CS4271", + .stream_name = "CS4271 HiFi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBC_CFC, + .ops = &edb93xx_ops, + SND_SOC_DAILINK_REG(hifi), +}; + +static struct snd_soc_card snd_soc_edb93xx = { + .name = "EDB93XX", + .owner = THIS_MODULE, + .dai_link = &edb93xx_dai, + .num_links = 1, +}; + +static int edb93xx_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_edb93xx; + int ret; + + ret = ep93xx_i2s_acquire(); + if (ret) + return ret; + + card->dev = &pdev->dev; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", + ret); + ep93xx_i2s_release(); + } + + return ret; +} + +static void edb93xx_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + ep93xx_i2s_release(); +} + +static struct platform_driver edb93xx_driver = { + .driver = { + .name = "edb93xx-audio", + }, + .probe = edb93xx_probe, + .remove_new = edb93xx_remove, +}; + +module_platform_driver(edb93xx_driver); + +MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>"); +MODULE_DESCRIPTION("ALSA SoC EDB93xx"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:edb93xx-audio"); diff --git a/sound/soc/cirrus/ep93xx-i2s.c b/sound/soc/cirrus/ep93xx-i2s.c new file mode 100644 index 0000000000..522de4b802 --- /dev/null +++ b/sound/soc/cirrus/ep93xx-i2s.c @@ -0,0 +1,538 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/sound/soc/ep93xx-i2s.c + * EP93xx I2S driver + * + * Copyright (C) 2010 Ryan Mallon + * + * Based on the original driver by: + * Copyright (C) 2007 Chase Douglas <chasedouglas@gmail> + * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of.h> + +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <linux/platform_data/dma-ep93xx.h> +#include <linux/soc/cirrus/ep93xx.h> + +#include "ep93xx-pcm.h" + +#define EP93XX_I2S_TXCLKCFG 0x00 +#define EP93XX_I2S_RXCLKCFG 0x04 +#define EP93XX_I2S_GLSTS 0x08 +#define EP93XX_I2S_GLCTRL 0x0C + +#define EP93XX_I2S_I2STX0LFT 0x10 +#define EP93XX_I2S_I2STX0RT 0x14 + +#define EP93XX_I2S_TXLINCTRLDATA 0x28 +#define EP93XX_I2S_TXCTRL 0x2C +#define EP93XX_I2S_TXWRDLEN 0x30 +#define EP93XX_I2S_TX0EN 0x34 + +#define EP93XX_I2S_RXLINCTRLDATA 0x58 +#define EP93XX_I2S_RXCTRL 0x5C +#define EP93XX_I2S_RXWRDLEN 0x60 +#define EP93XX_I2S_RX0EN 0x64 + +#define EP93XX_I2S_WRDLEN_16 (0 << 0) +#define EP93XX_I2S_WRDLEN_24 (1 << 0) +#define EP93XX_I2S_WRDLEN_32 (2 << 0) + +#define EP93XX_I2S_RXLINCTRLDATA_R_JUST BIT(1) /* Right justify */ + +#define EP93XX_I2S_TXLINCTRLDATA_R_JUST BIT(2) /* Right justify */ + +/* + * Transmit empty interrupt level select: + * 0 - Generate interrupt when FIFO is half empty + * 1 - Generate interrupt when FIFO is empty + */ +#define EP93XX_I2S_TXCTRL_TXEMPTY_LVL BIT(0) +#define EP93XX_I2S_TXCTRL_TXUFIE BIT(1) /* Transmit interrupt enable */ + +#define EP93XX_I2S_CLKCFG_LRS (1 << 0) /* lrclk polarity */ +#define EP93XX_I2S_CLKCFG_CKP (1 << 1) /* Bit clock polarity */ +#define EP93XX_I2S_CLKCFG_REL (1 << 2) /* First bit transition */ +#define EP93XX_I2S_CLKCFG_MASTER (1 << 3) /* Master mode */ +#define EP93XX_I2S_CLKCFG_NBCG (1 << 4) /* Not bit clock gating */ + +#define EP93XX_I2S_GLSTS_TX0_FIFO_FULL BIT(12) + +struct ep93xx_i2s_info { + struct clk *mclk; + struct clk *sclk; + struct clk *lrclk; + void __iomem *regs; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +static struct ep93xx_dma_data ep93xx_i2s_dma_data[] = { + [SNDRV_PCM_STREAM_PLAYBACK] = { + .name = "i2s-pcm-out", + .port = EP93XX_DMA_I2S1, + .direction = DMA_MEM_TO_DEV, + }, + [SNDRV_PCM_STREAM_CAPTURE] = { + .name = "i2s-pcm-in", + .port = EP93XX_DMA_I2S1, + .direction = DMA_DEV_TO_MEM, + }, +}; + +static inline void ep93xx_i2s_write_reg(struct ep93xx_i2s_info *info, + unsigned reg, unsigned val) +{ + __raw_writel(val, info->regs + reg); +} + +static inline unsigned ep93xx_i2s_read_reg(struct ep93xx_i2s_info *info, + unsigned reg) +{ + return __raw_readl(info->regs + reg); +} + +static void ep93xx_i2s_enable(struct ep93xx_i2s_info *info, int stream) +{ + unsigned base_reg; + + if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 && + (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) { + /* Enable clocks */ + clk_prepare_enable(info->mclk); + clk_prepare_enable(info->sclk); + clk_prepare_enable(info->lrclk); + + /* Enable i2s */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1); + } + + /* Enable fifo */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + ep93xx_i2s_write_reg(info, base_reg, 1); + + /* Enable TX IRQs (FIFO empty or underflow) */ + if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG) && + stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCTRL, + EP93XX_I2S_TXCTRL_TXEMPTY_LVL | + EP93XX_I2S_TXCTRL_TXUFIE); +} + +static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream) +{ + unsigned base_reg; + + /* Disable IRQs */ + if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG) && + stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCTRL, 0); + + /* Disable fifo */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + ep93xx_i2s_write_reg(info, base_reg, 0); + + if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 && + (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) { + /* Disable i2s */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 0); + + /* Disable clocks */ + clk_disable_unprepare(info->lrclk); + clk_disable_unprepare(info->sclk); + clk_disable_unprepare(info->mclk); + } +} + +/* + * According to documentation I2S controller can handle underflow conditions + * just fine, but in reality the state machine is sometimes confused so that + * the whole stream is shifted by one byte. The watchdog below disables the TX + * FIFO, fills the buffer with zeroes and re-enables the FIFO. State machine + * is being reset and by filling the buffer we get some time before next + * underflow happens. + */ +static irqreturn_t ep93xx_i2s_interrupt(int irq, void *dev_id) +{ + struct ep93xx_i2s_info *info = dev_id; + + /* Disable FIFO */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_TX0EN, 0); + /* + * Fill TX FIFO with zeroes, this way we can defer next IRQs as much as + * possible and get more time for DMA to catch up. Actually there are + * only 8 samples in this FIFO, so even on 8kHz maximum deferral here is + * 1ms. + */ + while (!(ep93xx_i2s_read_reg(info, EP93XX_I2S_GLSTS) & + EP93XX_I2S_GLSTS_TX0_FIFO_FULL)) { + ep93xx_i2s_write_reg(info, EP93XX_I2S_I2STX0LFT, 0); + ep93xx_i2s_write_reg(info, EP93XX_I2S_I2STX0RT, 0); + } + /* Re-enable FIFO */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_TX0EN, 1); + + return IRQ_HANDLED; +} + +static int ep93xx_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + info->dma_params_tx.filter_data = + &ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_PLAYBACK]; + info->dma_params_rx.filter_data = + &ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_CAPTURE]; + + snd_soc_dai_init_dma_data(dai, &info->dma_params_tx, + &info->dma_params_rx); + + return 0; +} + +static int ep93xx_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + ep93xx_i2s_enable(info, substream->stream); + + return 0; +} + +static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + + ep93xx_i2s_disable(info, substream->stream); +} + +static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai); + unsigned int clk_cfg; + unsigned int txlin_ctrl = 0; + unsigned int rxlin_ctrl = 0; + + clk_cfg = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + clk_cfg |= EP93XX_I2S_CLKCFG_REL; + break; + + case SND_SOC_DAIFMT_LEFT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + break; + + case SND_SOC_DAIFMT_RIGHT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + rxlin_ctrl |= EP93XX_I2S_RXLINCTRLDATA_R_JUST; + txlin_ctrl |= EP93XX_I2S_TXLINCTRLDATA_R_JUST; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: + /* CPU is provider */ + clk_cfg |= EP93XX_I2S_CLKCFG_MASTER; + break; + + case SND_SOC_DAIFMT_BC_FC: + /* Codec is provider */ + clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Negative bit clock, lrclk low on left word */ + clk_cfg &= ~(EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_LRS); + break; + + case SND_SOC_DAIFMT_NB_IF: + /* Negative bit clock, lrclk low on right word */ + clk_cfg &= ~EP93XX_I2S_CLKCFG_CKP; + clk_cfg |= EP93XX_I2S_CLKCFG_LRS; + break; + + case SND_SOC_DAIFMT_IB_NF: + /* Positive bit clock, lrclk low on left word */ + clk_cfg |= EP93XX_I2S_CLKCFG_CKP; + clk_cfg &= ~EP93XX_I2S_CLKCFG_LRS; + break; + + case SND_SOC_DAIFMT_IB_IF: + /* Positive bit clock, lrclk low on right word */ + clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_LRS; + break; + } + + /* Write new register values */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXCLKCFG, clk_cfg); + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCLKCFG, clk_cfg); + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXLINCTRLDATA, rxlin_ctrl); + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, txlin_ctrl); + return 0; +} + +static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai); + unsigned word_len, div, sdiv, lrdiv; + int err; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = EP93XX_I2S_WRDLEN_16; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + word_len = EP93XX_I2S_WRDLEN_24; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + word_len = EP93XX_I2S_WRDLEN_32; + break; + + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXWRDLEN, word_len); + else + ep93xx_i2s_write_reg(info, EP93XX_I2S_RXWRDLEN, word_len); + + /* + * EP93xx I2S module can be setup so SCLK / LRCLK value can be + * 32, 64, 128. MCLK / SCLK value can be 2 and 4. + * We set LRCLK equal to `rate' and minimum SCLK / LRCLK + * value is 64, because our sample size is 32 bit * 2 channels. + * I2S standard permits us to transmit more bits than + * the codec uses. + */ + div = clk_get_rate(info->mclk) / params_rate(params); + sdiv = 4; + if (div > (256 + 512) / 2) { + lrdiv = 128; + } else { + lrdiv = 64; + if (div < (128 + 256) / 2) + sdiv = 2; + } + + err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv); + if (err) + return err; + + err = clk_set_rate(info->lrclk, clk_get_rate(info->sclk) / lrdiv); + if (err) + return err; + + return 0; +} + +static int ep93xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, + unsigned int freq, int dir) +{ + struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai); + + if (dir == SND_SOC_CLOCK_IN || clk_id != 0) + return -EINVAL; + if (!freq) + return 0; + + return clk_set_rate(info->mclk, freq); +} + +#ifdef CONFIG_PM +static int ep93xx_i2s_suspend(struct snd_soc_component *component) +{ + struct ep93xx_i2s_info *info = snd_soc_component_get_drvdata(component); + + if (!snd_soc_component_active(component)) + return 0; + + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE); + + return 0; +} + +static int ep93xx_i2s_resume(struct snd_soc_component *component) +{ + struct ep93xx_i2s_info *info = snd_soc_component_get_drvdata(component); + + if (!snd_soc_component_active(component)) + return 0; + + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE); + + return 0; +} +#else +#define ep93xx_i2s_suspend NULL +#define ep93xx_i2s_resume NULL +#endif + +static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .probe = ep93xx_i2s_dai_probe, + .startup = ep93xx_i2s_startup, + .shutdown = ep93xx_i2s_shutdown, + .hw_params = ep93xx_i2s_hw_params, + .set_sysclk = ep93xx_i2s_set_sysclk, + .set_fmt = ep93xx_i2s_set_dai_fmt, +}; + +#define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver ep93xx_i2s_dai = { + .symmetric_rate = 1, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = EP93XX_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = EP93XX_I2S_FORMATS, + }, + .ops = &ep93xx_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver ep93xx_i2s_component = { + .name = "ep93xx-i2s", + .suspend = ep93xx_i2s_suspend, + .resume = ep93xx_i2s_resume, + .legacy_dai_naming = 1, +}; + +static int ep93xx_i2s_probe(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info; + int err; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG)) { + int irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq < 0 ? irq : -ENODEV; + + err = devm_request_irq(&pdev->dev, irq, ep93xx_i2s_interrupt, 0, + pdev->name, info); + if (err) + return err; + } + + info->mclk = clk_get(&pdev->dev, "mclk"); + if (IS_ERR(info->mclk)) { + err = PTR_ERR(info->mclk); + goto fail; + } + + info->sclk = clk_get(&pdev->dev, "sclk"); + if (IS_ERR(info->sclk)) { + err = PTR_ERR(info->sclk); + goto fail_put_mclk; + } + + info->lrclk = clk_get(&pdev->dev, "lrclk"); + if (IS_ERR(info->lrclk)) { + err = PTR_ERR(info->lrclk); + goto fail_put_sclk; + } + + dev_set_drvdata(&pdev->dev, info); + + err = devm_snd_soc_register_component(&pdev->dev, &ep93xx_i2s_component, + &ep93xx_i2s_dai, 1); + if (err) + goto fail_put_lrclk; + + err = devm_ep93xx_pcm_platform_register(&pdev->dev); + if (err) + goto fail_put_lrclk; + + return 0; + +fail_put_lrclk: + clk_put(info->lrclk); +fail_put_sclk: + clk_put(info->sclk); +fail_put_mclk: + clk_put(info->mclk); +fail: + return err; +} + +static void ep93xx_i2s_remove(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info = dev_get_drvdata(&pdev->dev); + + clk_put(info->lrclk); + clk_put(info->sclk); + clk_put(info->mclk); +} + +static const struct of_device_id ep93xx_i2s_of_ids[] = { + { .compatible = "cirrus,ep9301-i2s" }, + {} +}; +MODULE_DEVICE_TABLE(of, ep93xx_i2s_of_ids); + +static struct platform_driver ep93xx_i2s_driver = { + .probe = ep93xx_i2s_probe, + .remove_new = ep93xx_i2s_remove, + .driver = { + .name = "ep93xx-i2s", + .of_match_table = ep93xx_i2s_of_ids, + }, +}; + +module_platform_driver(ep93xx_i2s_driver); + +MODULE_ALIAS("platform:ep93xx-i2s"); +MODULE_AUTHOR("Ryan Mallon"); +MODULE_DESCRIPTION("EP93XX I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/cirrus/ep93xx-pcm.c b/sound/soc/cirrus/ep93xx-pcm.c new file mode 100644 index 0000000000..fa72acd8d3 --- /dev/null +++ b/sound/soc/cirrus/ep93xx-pcm.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/sound/arm/ep93xx-pcm.c - EP93xx ALSA PCM interface + * + * Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> + * Copyright (C) 2006 Applied Data Systems + * + * Rewritten for the SoC audio subsystem (Based on PXA2xx code): + * Copyright (c) 2008 Ryan Mallon + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/dmaengine.h> + +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include <linux/platform_data/dma-ep93xx.h> + +#include "ep93xx-pcm.h" + +static const struct snd_pcm_hardware ep93xx_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .buffer_bytes_max = 131072, + .period_bytes_min = 32, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 32, + .fifo_size = 32, +}; + +static bool ep93xx_pcm_dma_filter(struct dma_chan *chan, void *filter_param) +{ + struct ep93xx_dma_data *data = filter_param; + + if (data->direction == ep93xx_dma_chan_direction(chan)) { + chan->private = data; + return true; + } + + return false; +} + +static const struct snd_dmaengine_pcm_config ep93xx_dmaengine_pcm_config = { + .pcm_hardware = &ep93xx_pcm_hardware, + .compat_filter_fn = ep93xx_pcm_dma_filter, + .prealloc_buffer_size = 131072, +}; + +int devm_ep93xx_pcm_platform_register(struct device *dev) +{ + return devm_snd_dmaengine_pcm_register(dev, + &ep93xx_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_NO_DT | + SND_DMAENGINE_PCM_FLAG_COMPAT); +} +EXPORT_SYMBOL_GPL(devm_ep93xx_pcm_platform_register); + +MODULE_AUTHOR("Ryan Mallon"); +MODULE_DESCRIPTION("EP93xx ALSA PCM interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/cirrus/ep93xx-pcm.h b/sound/soc/cirrus/ep93xx-pcm.h new file mode 100644 index 0000000000..8e1c722bff --- /dev/null +++ b/sound/soc/cirrus/ep93xx-pcm.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + */ + +#ifndef __EP93XX_PCM_H__ +#define __EP93XX_PCM_H__ + +int devm_ep93xx_pcm_platform_register(struct device *dev); + +#endif |