[alsa-devel] [PATCH 0/3] EP93xx: Add i2s audio support
Repost of the i2s audio series for ep93xx. Patch 2/3 is unchanged. The core support has been modified to move setting of the SPOL and ORIDE bits of the i2sclkdiv register, as well as the i2s pin strapping configuration, to a new function called ep93xx_i2s_acquire which is called from the audio board drivers.
Ryan Mallon (3): EP93xx: Add i2s core support EP93xx: Add i2s audio driver EP93xx: Add Snapper CL15 i2s audio support
arch/arm/mach-ep93xx/clock.c | 67 +++- arch/arm/mach-ep93xx/core.c | 67 +++ arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h | 10 + arch/arm/mach-ep93xx/include/mach/platform.h | 3 + arch/arm/mach-ep93xx/snappercl15.c | 1 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/ep93xx/Kconfig | 18 + sound/soc/ep93xx/Makefile | 11 + sound/soc/ep93xx/ep93xx-i2s.c | 487 +++++++++++++++++++++++ sound/soc/ep93xx/ep93xx-i2s.h | 18 + sound/soc/ep93xx/ep93xx-pcm.c | 319 +++++++++++++++ sound/soc/ep93xx/ep93xx-pcm.h | 22 + sound/soc/ep93xx/snappercl15.c | 150 +++++++ 14 files changed, 1174 insertions(+), 1 deletions(-) create mode 100644 sound/soc/ep93xx/Kconfig create mode 100644 sound/soc/ep93xx/Makefile create mode 100644 sound/soc/ep93xx/ep93xx-i2s.c create mode 100644 sound/soc/ep93xx/ep93xx-i2s.h create mode 100644 sound/soc/ep93xx/ep93xx-pcm.c create mode 100644 sound/soc/ep93xx/ep93xx-pcm.h create mode 100644 sound/soc/ep93xx/snappercl15.c
Add core support for EP93xx i2s audio
Signed-off-by: Ryan Mallon ryan@bluewatersys.com --- arch/arm/mach-ep93xx/clock.c | 67 ++++++++++++++++++++++- arch/arm/mach-ep93xx/core.c | 67 +++++++++++++++++++++++ arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h | 10 ++++ arch/arm/mach-ep93xx/include/mach/platform.h | 3 + 4 files changed, 146 insertions(+), 1 deletions(-)
diff --git a/arch/arm/mach-ep93xx/clock.c b/arch/arm/mach-ep93xx/clock.c index 5f80092..e782af1 100644 --- a/arch/arm/mach-ep93xx/clock.c +++ b/arch/arm/mach-ep93xx/clock.c @@ -43,7 +43,8 @@ static unsigned long get_uart_rate(struct clk *clk);
static int set_keytchclk_rate(struct clk *clk, unsigned long rate); static int set_div_rate(struct clk *clk, unsigned long rate); - +static int set_i2s_sclk_rate(struct clk *clk, unsigned long rate); +static int set_i2s_lrclk_rate(struct clk *clk, unsigned long rate);
static struct clk clk_xtali = { .rate = EP93XX_EXT_CLK_RATE, @@ -108,6 +109,29 @@ static struct clk clk_video = { .set_rate = set_div_rate, };
+static struct clk clk_i2s_mclk = { + .sw_locked = 1, + .enable_reg = EP93XX_SYSCON_I2SCLKDIV, + .enable_mask = EP93XX_SYSCON_CLKDIV_ENABLE, + .set_rate = set_div_rate, +}; + +static struct clk clk_i2s_sclk = { + .sw_locked = 1, + .parent = &clk_i2s_mclk, + .enable_reg = EP93XX_SYSCON_I2SCLKDIV, + .enable_mask = EP93XX_SYSCON_I2SCLKDIV_SENA, + .set_rate = set_i2s_sclk_rate, +}; + +static struct clk clk_i2s_lrclk = { + .sw_locked = 1, + .parent = &clk_i2s_sclk, + .enable_reg = EP93XX_SYSCON_I2SCLKDIV, + .enable_mask = EP93XX_SYSCON_I2SCLKDIV_SENA, + .set_rate = set_i2s_lrclk_rate, +}; + /* DMA Clocks */ static struct clk clk_m2p0 = { .parent = &clk_h, @@ -186,6 +210,9 @@ static struct clk_lookup clocks[] = { INIT_CK("ep93xx-ohci", NULL, &clk_usb_host), INIT_CK("ep93xx-keypad", NULL, &clk_keypad), INIT_CK("ep93xx-fb", NULL, &clk_video), + INIT_CK("ep93xx-i2s", "mclk", &clk_i2s_mclk), + INIT_CK("ep93xx-i2s", "sclk", &clk_i2s_sclk), + INIT_CK("ep93xx-i2s", "lrclk", &clk_i2s_lrclk), INIT_CK(NULL, "pwm_clk", &clk_pwm), INIT_CK(NULL, "m2p0", &clk_m2p0), INIT_CK(NULL, "m2p1", &clk_m2p1), @@ -396,6 +423,44 @@ static int set_div_rate(struct clk *clk, unsigned long rate) return 0; }
+static int set_i2s_sclk_rate(struct clk *clk, unsigned long rate) +{ + unsigned val = __raw_readl(clk->enable_reg); + + if (rate == clk_i2s_mclk.rate / 2) + ep93xx_syscon_swlocked_write(val & ~EP93XX_I2SCLKDIV_SDIV, + clk->enable_reg); + else if (rate == clk_i2s_mclk.rate / 4) + ep93xx_syscon_swlocked_write(val | EP93XX_I2SCLKDIV_SDIV, + clk->enable_reg); + else + return -EINVAL; + + clk_i2s_sclk.rate = rate; + return 0; +} + +static int set_i2s_lrclk_rate(struct clk *clk, unsigned long rate) +{ + unsigned val = __raw_readl(clk->enable_reg) & + ~EP93XX_I2SCLKDIV_LRDIV_MASK; + + if (rate == clk_i2s_sclk.rate / 32) + ep93xx_syscon_swlocked_write(val | EP93XX_I2SCLKDIV_LRDIV32, + clk->enable_reg); + else if (rate == clk_i2s_sclk.rate / 64) + ep93xx_syscon_swlocked_write(val | EP93XX_I2SCLKDIV_LRDIV64, + clk->enable_reg); + else if (rate == clk_i2s_sclk.rate / 128) + ep93xx_syscon_swlocked_write(val | EP93XX_I2SCLKDIV_LRDIV128, + clk->enable_reg); + else + return -EINVAL; + + clk_i2s_lrclk.rate = rate; + return 0; +} + int clk_set_rate(struct clk *clk, unsigned long rate) { if (clk->set_rate) diff --git a/arch/arm/mach-ep93xx/core.c b/arch/arm/mach-ep93xx/core.c index 90fb591..005af0f 100644 --- a/arch/arm/mach-ep93xx/core.c +++ b/arch/arm/mach-ep93xx/core.c @@ -617,6 +617,73 @@ void ep93xx_keypad_release_gpio(struct platform_device *pdev) } EXPORT_SYMBOL(ep93xx_keypad_release_gpio);
+/************************************************************************* + * EP93xx I2S audio peripheral handling + *************************************************************************/ +static struct resource ep93xx_i2s_resource[] = { + { + .start = EP93XX_I2S_PHYS_BASE, + .end = EP93XX_I2S_PHYS_BASE + 0x100 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device ep93xx_i2s_device = { + .name = "ep93xx-i2s", + .id = -1, + .num_resources = ARRAY_SIZE(ep93xx_i2s_resource), + .resource = ep93xx_i2s_resource, +}; + +void __init ep93xx_register_i2s(void) +{ + platform_device_register(&ep93xx_i2s_device); +} + +#define EP93XX_SYSCON_DEVCFG_I2S_MASK (EP93XX_SYSCON_DEVCFG_I2SONSSP | \ + EP93XX_SYSCON_DEVCFG_I2SONAC97) + +#define EP93XX_I2SCLKDIV_MASK (EP93XX_SYSCON_I2SCLKDIV_ORIDE | \ + EP93XX_SYSCON_I2SCLKDIV_SPOL) + +int ep93xx_i2s_acquire(unsigned i2s_pins, unsigned i2s_config) +{ + unsigned val; + + /* Sanity check */ + if (i2s_pins & ~EP93XX_SYSCON_DEVCFG_I2S_MASK) + return -EINVAL; + if (i2s_config & ~EP93XX_I2SCLKDIV_MASK) + return -EINVAL; + + /* Must have only one of I2SONSSP/I2SONAC97 set */ + if ((i2s_pins & EP93XX_SYSCON_DEVCFG_I2SONSSP) == + (i2s_pins & EP93XX_SYSCON_DEVCFG_I2SONAC97)) + return -EINVAL; + + ep93xx_devcfg_clear_bits(EP93XX_SYSCON_DEVCFG_I2S_MASK); + ep93xx_devcfg_set_bits(i2s_pins); + + /* + * This is potentially racy with the clock api for i2s_mclk, sclk and + * lrclk. Since the i2s driver is the only user of those clocks we + * rely on it to prevent parallel use of this function and the + * clock api for the i2s clocks. + */ + val = __raw_readl(EP93XX_SYSCON_I2SCLKDIV); + val &= ~EP93XX_I2SCLKDIV_MASK; + val |= i2s_config; + ep93xx_syscon_swlocked_write(val, EP93XX_SYSCON_I2SCLKDIV); + + return 0; +} +EXPORT_SYMBOL(ep93xx_i2s_acquire); + +void ep93xx_i2s_release(void) +{ + ep93xx_devcfg_clear_bits(EP93XX_SYSCON_DEVCFG_I2S_MASK); +} +EXPORT_SYMBOL(ep93xx_i2s_release);
extern void ep93xx_gpio_init(void);
diff --git a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h index 93e2ecc..3fbb095 100644 --- a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h +++ b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h @@ -93,6 +93,7 @@ /* APB peripherals */ #define EP93XX_TIMER_BASE EP93XX_APB_IOMEM(0x00010000)
+#define EP93XX_I2S_PHYS_BASE EP93XX_APB_PHYS(0x00020000) #define EP93XX_I2S_BASE EP93XX_APB_IOMEM(0x00020000)
#define EP93XX_SECURITY_BASE EP93XX_APB_IOMEM(0x00030000) @@ -193,6 +194,15 @@ #define EP93XX_SYSCON_CLKDIV_ESEL (1<<14) #define EP93XX_SYSCON_CLKDIV_PSEL (1<<13) #define EP93XX_SYSCON_CLKDIV_PDIV_SHIFT 8 +#define EP93XX_SYSCON_I2SCLKDIV EP93XX_SYSCON_REG(0x8c) +#define EP93XX_SYSCON_I2SCLKDIV_SENA (1<<31) +#define EP93XX_SYSCON_I2SCLKDIV_ORIDE (1<<29) +#define EP93XX_SYSCON_I2SCLKDIV_SPOL (1<<19) +#define EP93XX_I2SCLKDIV_SDIV (1 << 16) +#define EP93XX_I2SCLKDIV_LRDIV32 (0 << 17) +#define EP93XX_I2SCLKDIV_LRDIV64 (1 << 17) +#define EP93XX_I2SCLKDIV_LRDIV128 (2 << 17) +#define EP93XX_I2SCLKDIV_LRDIV_MASK (3 << 17) #define EP93XX_SYSCON_KEYTCHCLKDIV EP93XX_SYSCON_REG(0x90) #define EP93XX_SYSCON_KEYTCHCLKDIV_TSEN (1<<31) #define EP93XX_SYSCON_KEYTCHCLKDIV_ADIV (1<<16) diff --git a/arch/arm/mach-ep93xx/include/mach/platform.h b/arch/arm/mach-ep93xx/include/mach/platform.h index c6dc14d..0f2822d 100644 --- a/arch/arm/mach-ep93xx/include/mach/platform.h +++ b/arch/arm/mach-ep93xx/include/mach/platform.h @@ -43,6 +43,9 @@ void ep93xx_pwm_release_gpio(struct platform_device *pdev); void ep93xx_register_keypad(struct ep93xx_keypad_platform_data *data); int ep93xx_keypad_acquire_gpio(struct platform_device *pdev); void ep93xx_keypad_release_gpio(struct platform_device *pdev); +void ep93xx_register_i2s(void); +int ep93xx_i2s_acquire(unsigned i2s_pins, unsigned i2s_config); +void ep93xx_i2s_release(void);
void ep93xx_init_devices(void); extern struct sys_timer ep93xx_timer;
On Tuesday, June 08, 2010 3:01 AM, Ryan Mallon wrote:
Add core support for EP93xx i2s audio
Signed-off-by: Ryan Mallon ryan@bluewatersys.com
Looks good.
Acked-by: H Hartley Sweeten hsweeten@visionengravers.com
H Hartley Sweeten wrote:
On Tuesday, June 08, 2010 3:01 AM, Ryan Mallon wrote:
Add core support for EP93xx i2s audio
Signed-off-by: Ryan Mallon ryan@bluewatersys.com
Looks good.
Acked-by: H Hartley Sweeten hsweeten@visionengravers.com
Okay, I have Liam and Hartley's acks on this one and Liam's on 3/3. Mark, do you want to apply the other two patches to your tree, or should they go via the arm tree?
Thanks, ~Ryan
On 8 Jun 2010, at 20:27, Ryan Mallon wrote:
Okay, I have Liam and Hartley's acks on this one and Liam's on 3/3. Mark, do you want to apply the other two patches to your tree, or should they go via the arm tree?
I don't urgently mind for patch 1 since there's no build time dependency - in order to avoid possible merge issues in the ARM tree it's as well if it goes that way, though I can pick it up if it makes sense for you guys to keep stuff together and you don't anticipate colliding with any work in the arch/arm code for the CPU.
Patch 3 I'll apply to ASoC tomorrow, it depends on the I2S driver for the CPU which is most definitely ASoC (and it's going to get caught up in the multi-CODEC API changes soon as well so would get build issues in -next if merged via a different tree anyway).
Mark Brown wrote:
On 8 Jun 2010, at 20:27, Ryan Mallon wrote:
Okay, I have Liam and Hartley's acks on this one and Liam's on 3/3. Mark, do you want to apply the other two patches to your tree, or should they go via the arm tree?
I don't urgently mind for patch 1 since there's no build time dependency - in order to avoid possible merge issues in the ARM tree it's as well if it goes that way, though I can pick it up if it makes sense for you guys to keep stuff together and you don't anticipate colliding with any work in the arch/arm code for the CPU.
Patch 3 I'll apply to ASoC tomorrow, it depends on the I2S driver for the CPU which is most definitely ASoC (and it's going to get caught up in the multi-CODEC API changes soon as well so would get build issues in -next if merged via a different tree anyway).
Patch 3 at least has a dependency on patch 1 for some #defines and the ep93xx_i2s functions calls.
~Ryan
On 9 Jun 2010, at 00:26, Ryan Mallon wrote:
Patch 3 at least has a dependency on patch 1 for some #defines and the ep93xx_i2s functions calls.
Oh, in that case best if I take them both through ASoC unless someone anticipates issues there. I'll arrange things so they can also be merged into the ARM tree in case of issues.
Mark Brown wrote:
On 9 Jun 2010, at 00:26, Ryan Mallon wrote:
Patch 3 at least has a dependency on patch 1 for some #defines and the ep93xx_i2s functions calls.
Oh, in that case best if I take them both through ASoC unless someone anticipates issues there. I'll arrange things so they can also be merged into the ARM tree in case of issues.
Ok, thanks.
~Ryan
On Tuesday, June 08, 2010 12:27 PM, Ryan Mallon wrote:
H Hartley Sweeten wrote:
On Tuesday, June 08, 2010 3:01 AM, Ryan Mallon wrote:
Add core support for EP93xx i2s audio
Signed-off-by: Ryan Mallon ryan@bluewatersys.com
Looks good.
Acked-by: H Hartley Sweeten hsweeten@visionengravers.com
Okay, I have Liam and Hartley's acks on this one and Liam's on 3/3. Mark, do you want to apply the other two patches to your tree, or should they go via the arm tree?
Ryan,
It appears Mark doesn't see ant reason to apply patch 1 to his tree. It's probably better to send that one thru Russell's patch system in case any other changes are made to the ep93xx core files.
Regards, Hartley
Add pcm and i2s SoC audio drivers for EP93xx
Signed-off-by: Ryan Mallon ryan@bluewatersys.com --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/ep93xx/Kconfig | 9 + sound/soc/ep93xx/Makefile | 8 + sound/soc/ep93xx/ep93xx-i2s.c | 487 +++++++++++++++++++++++++++++++++++++++++ sound/soc/ep93xx/ep93xx-i2s.h | 18 ++ sound/soc/ep93xx/ep93xx-pcm.c | 319 +++++++++++++++++++++++++++ sound/soc/ep93xx/ep93xx-pcm.h | 22 ++ 8 files changed, 865 insertions(+), 0 deletions(-) create mode 100644 sound/soc/ep93xx/Kconfig create mode 100644 sound/soc/ep93xx/Makefile create mode 100644 sound/soc/ep93xx/ep93xx-i2s.c create mode 100644 sound/soc/ep93xx/ep93xx-i2s.h create mode 100644 sound/soc/ep93xx/ep93xx-pcm.c create mode 100644 sound/soc/ep93xx/ep93xx-pcm.h
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index b1749bc..f7cb451 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -28,6 +28,7 @@ source "sound/soc/atmel/Kconfig" source "sound/soc/au1x/Kconfig" source "sound/soc/blackfin/Kconfig" source "sound/soc/davinci/Kconfig" +source "sound/soc/ep93xx/Kconfig" source "sound/soc/fsl/Kconfig" source "sound/soc/imx/Kconfig" source "sound/soc/omap/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 1470141..55b711a 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_SND_SOC) += atmel/ obj-$(CONFIG_SND_SOC) += au1x/ obj-$(CONFIG_SND_SOC) += blackfin/ obj-$(CONFIG_SND_SOC) += davinci/ +obj-$(CONFIG_SND_SOC) += ep93xx/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += imx/ obj-$(CONFIG_SND_SOC) += omap/ diff --git a/sound/soc/ep93xx/Kconfig b/sound/soc/ep93xx/Kconfig new file mode 100644 index 0000000..ba66ac8 --- /dev/null +++ b/sound/soc/ep93xx/Kconfig @@ -0,0 +1,9 @@ +config SND_EP93XX_SOC + tristate "SoC Audio support for the Cirrus Logic EP93xx series" + depends on ARCH_EP93XX && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the EP93xx I2S interface. + +config SND_EP93XX_SOC_I2S + tristate diff --git a/sound/soc/ep93xx/Makefile b/sound/soc/ep93xx/Makefile new file mode 100644 index 0000000..0239da3 --- /dev/null +++ b/sound/soc/ep93xx/Makefile @@ -0,0 +1,8 @@ +# 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 diff --git a/sound/soc/ep93xx/ep93xx-i2s.c b/sound/soc/ep93xx/ep93xx-i2s.c new file mode 100644 index 0000000..00b9466 --- /dev/null +++ b/sound/soc/ep93xx/ep93xx-i2s.c @@ -0,0 +1,487 @@ +/* + * linux/sound/soc/ep93xx-i2s.c + * EP93xx I2S driver + * + * Copyright (C) 2010 Ryan Mallon ryan@bluewatersys.com + * + * Based on the original driver by: + * Copyright (C) 2007 Chase Douglas chasedouglas@gmail + * Copyright (C) 2006 Lennert Buytenhek buytenh@wantstofly.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <mach/hardware.h> +#include <mach/ep93xx-regs.h> +#include <mach/dma.h> + +#include "ep93xx-pcm.h" +#include "ep93xx-i2s.h" + +#define EP93XX_I2S_TXCLKCFG 0x00 +#define EP93XX_I2S_RXCLKCFG 0x04 +#define EP93XX_I2S_GLCTRL 0x0C + +#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_LINCTRLDATA_R_JUST (1 << 2) /* Right justify */ + +#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 */ + +struct ep93xx_i2s_info { + struct clk *mclk; + struct clk *sclk; + struct clk *lrclk; + struct ep93xx_pcm_dma_params *dma_params; + struct resource *mem; + void __iomem *regs; +}; + +struct ep93xx_pcm_dma_params ep93xx_i2s_dma_params[] = { + [SNDRV_PCM_STREAM_PLAYBACK] = { + .name = "i2s-pcm-out", + .dma_port = EP93XX_DMA_M2P_PORT_I2S1, + }, + [SNDRV_PCM_STREAM_CAPTURE] = { + .name = "i2s-pcm-in", + .dma_port = EP93XX_DMA_M2P_PORT_I2S1, + }, +}; + +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; + int i; + + if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 && + (ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) { + /* Enable clocks */ + clk_enable(info->mclk); + clk_enable(info->sclk); + clk_enable(info->lrclk); + + /* Enable i2s */ + ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1); + } + + /* Enable fifos */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + for (i = 0; i < 3; i++) + ep93xx_i2s_write_reg(info, base_reg + (i * 4), 1); +} + +static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream) +{ + unsigned base_reg; + int i; + + /* Disable fifos */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + base_reg = EP93XX_I2S_TX0EN; + else + base_reg = EP93XX_I2S_RX0EN; + for (i = 0; i < 3; i++) + ep93xx_i2s_write_reg(info, base_reg + (i * 4), 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(info->lrclk); + clk_disable(info->sclk); + clk_disable(info->mclk); + } +} + +static int ep93xx_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct ep93xx_i2s_info *info = rtd->dai->cpu_dai->private_data; + + snd_soc_dai_set_dma_data(cpu_dai, substream, + &info->dma_params[substream->stream]); + return 0; +} + +static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct ep93xx_i2s_info *info = rtd->dai->cpu_dai->private_data; + + 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 = cpu_dai->private_data; + unsigned int clk_cfg, lin_ctrl; + + clk_cfg = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG); + lin_ctrl = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXLINCTRLDATA); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + clk_cfg |= EP93XX_I2S_CLKCFG_REL; + lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST; + break; + + case SND_SOC_DAIFMT_LEFT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + lin_ctrl &= ~EP93XX_I2S_LINCTRLDATA_R_JUST; + break; + + case SND_SOC_DAIFMT_RIGHT_J: + clk_cfg &= ~EP93XX_I2S_CLKCFG_REL; + lin_ctrl |= EP93XX_I2S_LINCTRLDATA_R_JUST; + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* CPU is master */ + clk_cfg |= EP93XX_I2S_CLKCFG_MASTER; + break; + + case SND_SOC_DAIFMT_CBM_CFM: + /* Codec is master */ + 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_REL); + 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_REL; + 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_REL; + break; + + case SND_SOC_DAIFMT_IB_IF: + /* Positive bit clock, lrclk low on right word */ + clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_REL; + 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, lin_ctrl); + ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, lin_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 snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct ep93xx_i2s_info *info = cpu_dai->private_data; + unsigned word_len, div, sdiv, lrdiv; + int found = 0, 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); + + /* + * Calculate the sdiv (bit clock) and lrdiv (left/right clock) values. + * If the lrclk is pulse length is larger than the word size, then the + * bit clock will be gated for the unused bits. + */ + div = (clk_get_rate(info->mclk) / params_rate(params)) * + params_channels(params); + for (sdiv = 2; sdiv <= 4; sdiv += 2) + for (lrdiv = 32; lrdiv <= 128; lrdiv <<= 1) + if (sdiv * lrdiv == div) { + found = 1; + goto out; + } +out: + if (!found) + return -EINVAL; + + 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; + + ep93xx_i2s_enable(info, substream->stream); + 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 = cpu_dai->private_data; + + if (dir == SND_SOC_CLOCK_IN || clk_id != 0) + return -EINVAL; + + return clk_set_rate(info->mclk, freq); +} + +#ifdef CONFIG_PM +static int ep93xx_i2s_suspend(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = dai->private_data; + + if (!dai->active) + return; + + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE); +} + +static int ep93xx_i2s_resume(struct snd_soc_dai *dai) +{ + struct ep93xx_i2s_info *info = dai->private_data; + + if (!dai->active) + return; + + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK); + ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE); +} +#else +#define ep93xx_i2s_suspend NULL +#define ep93xx_i2s_resume NULL +#endif + +static struct snd_soc_dai_ops ep93xx_i2s_dai_ops = { + .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_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai ep93xx_i2s_dai = { + .name = "ep93xx-i2s", + .id = 0, + .symmetric_rates= 1, + .suspend = ep93xx_i2s_suspend, + .resume = ep93xx_i2s_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = EP93XX_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = EP93XX_I2S_FORMATS, + }, + .ops = &ep93xx_i2s_dai_ops, +}; +EXPORT_SYMBOL_GPL(ep93xx_i2s_dai); + +static int ep93xx_i2s_probe(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info; + struct resource *res; + int err; + + info = kzalloc(sizeof(struct ep93xx_i2s_info), GFP_KERNEL); + if (!info) { + err = -ENOMEM; + goto fail; + } + + ep93xx_i2s_dai.dev = &pdev->dev; + ep93xx_i2s_dai.private_data = info; + info->dma_params = ep93xx_i2s_dma_params; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + err = -ENODEV; + goto fail; + } + + info->mem = request_mem_region(res->start, resource_size(res), + pdev->name); + if (!info->mem) { + err = -EBUSY; + goto fail; + } + + info->regs = ioremap(info->mem->start, resource_size(info->mem)); + if (!info->regs) { + err = -ENXIO; + goto fail_release_mem; + } + + info->mclk = clk_get(&pdev->dev, "mclk"); + if (IS_ERR(info->mclk)) { + err = PTR_ERR(info->mclk); + goto fail_unmap_mem; + } + + 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; + } + + err = snd_soc_register_dai(&ep93xx_i2s_dai); + 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_unmap_mem: + iounmap(info->regs); +fail_release_mem: + release_mem_region(info->mem->start, resource_size(info->mem)); + kfree(info); +fail: + return err; +} + +static int __devexit ep93xx_i2s_remove(struct platform_device *pdev) +{ + struct ep93xx_i2s_info *info = ep93xx_i2s_dai.private_data; + + snd_soc_unregister_dai(&ep93xx_i2s_dai); + clk_put(info->lrclk); + clk_put(info->sclk); + clk_put(info->mclk); + iounmap(info->regs); + release_mem_region(info->mem->start, resource_size(info->mem)); + kfree(info); + return 0; +} + +static struct platform_driver ep93xx_i2s_driver = { + .probe = ep93xx_i2s_probe, + .remove = __devexit_p(ep93xx_i2s_remove), + .driver = { + .name = "ep93xx-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init ep93xx_i2s_init(void) +{ + return platform_driver_register(&ep93xx_i2s_driver); +} + +static void __exit ep93xx_i2s_exit(void) +{ + platform_driver_unregister(&ep93xx_i2s_driver); +} + +module_init(ep93xx_i2s_init); +module_exit(ep93xx_i2s_exit); + +MODULE_ALIAS("platform:ep93xx-i2s"); +MODULE_AUTHOR("Ryan Mallon ryan@bluewatersys.com"); +MODULE_DESCRIPTION("EP93XX I2S driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/ep93xx/ep93xx-i2s.h b/sound/soc/ep93xx/ep93xx-i2s.h new file mode 100644 index 0000000..3bd4ebf --- /dev/null +++ b/sound/soc/ep93xx/ep93xx-i2s.h @@ -0,0 +1,18 @@ +/* + * linux/sound/soc/ep93xx-i2s.h + * EP93xx I2S driver + * + * Copyright (C) 2010 Ryan Mallon ryan@bluewatersys.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _EP93XX_SND_SOC_I2S_H +#define _EP93XX_SND_SOC_I2S_H + +extern struct snd_soc_dai ep93xx_i2s_dai; + +#endif /* _EP93XX_SND_SOC_I2S_H */ diff --git a/sound/soc/ep93xx/ep93xx-pcm.c b/sound/soc/ep93xx/ep93xx-pcm.c new file mode 100644 index 0000000..4ba9384 --- /dev/null +++ b/sound/soc/ep93xx/ep93xx-pcm.c @@ -0,0 +1,319 @@ +/* + * 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 ryan@bluewatersys.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/dma.h> +#include <mach/hardware.h> +#include <mach/ep93xx-regs.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), + + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = SNDRV_PCM_RATE_8000, + .rate_max = SNDRV_PCM_RATE_48000, + + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + + .buffer_bytes_max = 131072, + .period_bytes_min = 32, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 32, + .fifo_size = 32, +}; + +struct ep93xx_runtime_data +{ + struct ep93xx_dma_m2p_client cl; + struct ep93xx_pcm_dma_params *params; + int pointer_bytes; + struct tasklet_struct period_tasklet; + int periods; + struct ep93xx_dma_buffer buf[32]; +}; + +static void ep93xx_pcm_period_elapsed(unsigned long data) +{ + struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; + snd_pcm_period_elapsed(substream); +} + +static void ep93xx_pcm_buffer_started(void *cookie, + struct ep93xx_dma_buffer *buf) +{ +} + +static void ep93xx_pcm_buffer_finished(void *cookie, + struct ep93xx_dma_buffer *buf, + int bytes, int error) +{ + struct snd_pcm_substream *substream = cookie; + struct ep93xx_runtime_data *rtd = substream->runtime->private_data; + + if (buf == rtd->buf + rtd->periods - 1) + rtd->pointer_bytes = 0; + else + rtd->pointer_bytes += buf->size; + + if (!error) { + ep93xx_dma_m2p_submit_recursive(&rtd->cl, buf); + tasklet_schedule(&rtd->period_tasklet); + } else { + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + } +} + +static int ep93xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_rtd->dai->cpu_dai; + struct ep93xx_pcm_dma_params *dma_params; + struct ep93xx_runtime_data *rtd; + int ret; + + dma_params = snd_soc_dai_get_dma_data(cpu_dai, substream); + snd_soc_set_runtime_hwparams(substream, &ep93xx_pcm_hardware); + + rtd = kmalloc(sizeof(*rtd), GFP_KERNEL); + if (!rtd) + return -ENOMEM; + + memset(&rtd->period_tasklet, 0, sizeof(rtd->period_tasklet)); + rtd->period_tasklet.func = ep93xx_pcm_period_elapsed; + rtd->period_tasklet.data = (unsigned long)substream; + + rtd->cl.name = dma_params->name; + rtd->cl.flags = dma_params->dma_port | EP93XX_DMA_M2P_IGNORE_ERROR | + ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + EP93XX_DMA_M2P_TX : EP93XX_DMA_M2P_RX); + rtd->cl.cookie = substream; + rtd->cl.buffer_started = ep93xx_pcm_buffer_started; + rtd->cl.buffer_finished = ep93xx_pcm_buffer_finished; + ret = ep93xx_dma_m2p_client_register(&rtd->cl); + if (ret < 0) { + kfree(rtd); + return ret; + } + + substream->runtime->private_data = rtd; + return 0; +} + +static int ep93xx_pcm_close(struct snd_pcm_substream *substream) +{ + struct ep93xx_runtime_data *rtd = substream->runtime->private_data; + + ep93xx_dma_m2p_client_unregister(&rtd->cl); + kfree(rtd); + return 0; +} + +static int ep93xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ep93xx_runtime_data *rtd = runtime->private_data; + size_t totsize = params_buffer_bytes(params); + size_t period = params_period_bytes(params); + int i; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = totsize; + + rtd->periods = (totsize + period - 1) / period; + for (i = 0; i < rtd->periods; i++) { + rtd->buf[i].bus_addr = runtime->dma_addr + (i * period); + rtd->buf[i].size = period; + if ((i + 1) * period > totsize) + rtd->buf[i].size = totsize - (i * period); + } + + return 0; +} + +static int ep93xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int ep93xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ep93xx_runtime_data *rtd = substream->runtime->private_data; + int ret; + int i; + + ret = 0; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + rtd->pointer_bytes = 0; + for (i = 0; i < rtd->periods; i++) + ep93xx_dma_m2p_submit(&rtd->cl, rtd->buf + i); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ep93xx_dma_m2p_flush(&rtd->cl); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t ep93xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ep93xx_runtime_data *rtd = substream->runtime->private_data; + + /* FIXME: implement this with sub-period granularity */ + return bytes_to_frames(runtime, rtd->pointer_bytes); +} + +static int ep93xx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops ep93xx_pcm_ops = { + .open = ep93xx_pcm_open, + .close = ep93xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = ep93xx_pcm_hw_params, + .hw_free = ep93xx_pcm_hw_free, + .trigger = ep93xx_pcm_trigger, + .pointer = ep93xx_pcm_pointer, + .mmap = ep93xx_pcm_mmap, +}; + +static int ep93xx_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 = ep93xx_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + buf->bytes = size; + + return (buf->area == NULL) ? -ENOMEM : 0; +} + +static void ep93xx_pcm_free_dma_buffers(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_writecombine(pcm->card->dev, buf->bytes, buf->area, + buf->addr); + buf->area = NULL; + } +} + +static u64 ep93xx_pcm_dmamask = 0xffffffff; + +static int ep93xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &ep93xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = ep93xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (dai->capture.channels_min) { + ret = ep93xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + + return 0; +} + +struct snd_soc_platform ep93xx_soc_platform = { + .name = "ep93xx-audio", + .pcm_ops = &ep93xx_pcm_ops, + .pcm_new = &ep93xx_pcm_new, + .pcm_free = &ep93xx_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(ep93xx_soc_platform); + +static int __init ep93xx_soc_platform_init(void) +{ + return snd_soc_register_platform(&ep93xx_soc_platform); +} + +static void __exit ep93xx_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&ep93xx_soc_platform); +} + +module_init(ep93xx_soc_platform_init); +module_exit(ep93xx_soc_platform_exit); + +MODULE_AUTHOR("Ryan Mallon ryan@bluewatersys.com"); +MODULE_DESCRIPTION("EP93xx ALSA PCM interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/ep93xx/ep93xx-pcm.h b/sound/soc/ep93xx/ep93xx-pcm.h new file mode 100644 index 0000000..4ffdd3f --- /dev/null +++ b/sound/soc/ep93xx/ep93xx-pcm.h @@ -0,0 +1,22 @@ +/* + * sound/soc/ep93xx/ep93xx-pcm.h - EP93xx ALSA PCM interface + * + * Copyright (C) 2006 Lennert Buytenhek buytenh@wantstofly.org + * Copyright (C) 2006 Applied Data Systems + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _EP93XX_SND_SOC_PCM_H +#define _EP93XX_SND_SOC_PCM_H + +struct ep93xx_pcm_dma_params { + char *name; + int dma_port; +}; + +extern struct snd_soc_platform ep93xx_soc_platform; + +#endif /* _EP93XX_SND_SOC_PCM_H */
On Tue, Jun 08, 2010 at 10:01:11PM +1200, Ryan Mallon wrote:
Add pcm and i2s SoC audio drivers for EP93xx
Signed-off-by: Ryan Mallon ryan@bluewatersys.com
As I said yesterday I have applied this patch already. If there are any changes in the new version please send incremental patches updating the driver.
Mark Brown wrote:
On Tue, Jun 08, 2010 at 10:01:11PM +1200, Ryan Mallon wrote:
Add pcm and i2s SoC audio drivers for EP93xx
Signed-off-by: Ryan Mallon ryan@bluewatersys.com
As I said yesterday I have applied this patch already. If there are any changes in the new version please send incremental patches updating the driver.
There are no changes to the patch which you have picked up, only the other two. I reposted the whole series for completeness. Will post incremental patches if there are changes to patch 2/3.
~Ryan
On Tue, Jun 08, 2010 at 10:09:27PM +1200, Ryan Mallon wrote:
There are no changes to the patch which you have picked up, only the other two. I reposted the whole series for completeness. Will post incremental patches if there are changes to patch 2/3.
OK, please add something to that effect to the changelog when reposting without change so I know what's going on.
Add support for i2s audio on Bluewater Systems Snapper CL15 module
Signed-off-by: Ryan Mallon ryan@bluewatersys.com --- arch/arm/mach-ep93xx/snappercl15.c | 1 + sound/soc/ep93xx/Kconfig | 9 ++ sound/soc/ep93xx/Makefile | 3 + sound/soc/ep93xx/snappercl15.c | 150 ++++++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 0 deletions(-) create mode 100644 sound/soc/ep93xx/snappercl15.c
diff --git a/arch/arm/mach-ep93xx/snappercl15.c b/arch/arm/mach-ep93xx/snappercl15.c index 51134b0..fee53a4 100644 --- a/arch/arm/mach-ep93xx/snappercl15.c +++ b/arch/arm/mach-ep93xx/snappercl15.c @@ -157,6 +157,7 @@ static void __init snappercl15_init_machine(void) ep93xx_register_i2c(&snappercl15_i2c_gpio_data, snappercl15_i2c_data, ARRAY_SIZE(snappercl15_i2c_data)); ep93xx_register_fb(&snappercl15_fb_info); + ep93xx_register_i2s(); platform_device_register(&snappercl15_nand_device); }
diff --git a/sound/soc/ep93xx/Kconfig b/sound/soc/ep93xx/Kconfig index ba66ac8..f617f56 100644 --- a/sound/soc/ep93xx/Kconfig +++ b/sound/soc/ep93xx/Kconfig @@ -7,3 +7,12 @@ config SND_EP93XX_SOC
config SND_EP93XX_SOC_I2S tristate + +config SND_EP93XX_SOC_SNAPPERCL15 + tristate "SoC Audio support for Bluewater Systems Snapper CL15 module" + depends on SND_EP93XX_SOC && MACH_SNAPPER_CL15 + select SND_EP93XX_SOC_I2S + select SND_SOC_TLV320AIC23 + help + Say Y or M here if you want to add support for I2S audio on the + Bluewater Systems Snapper CL15 module. diff --git a/sound/soc/ep93xx/Makefile b/sound/soc/ep93xx/Makefile index 0239da3..272e60f 100644 --- a/sound/soc/ep93xx/Makefile +++ b/sound/soc/ep93xx/Makefile @@ -6,3 +6,6 @@ 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-snappercl15-objs := snappercl15.o + +obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15) += snd-soc-snappercl15.o diff --git a/sound/soc/ep93xx/snappercl15.c b/sound/soc/ep93xx/snappercl15.c new file mode 100644 index 0000000..6495534 --- /dev/null +++ b/sound/soc/ep93xx/snappercl15.c @@ -0,0 +1,150 @@ +/* + * snappercl15.c -- SoC audio for Bluewater Systems Snapper CL15 module + * + * Copyright (C) 2008 Bluewater Systems Ltd + * Author: Ryan Mallon ryan@bluewatersys.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "../codecs/tlv320aic23.h" +#include "ep93xx-pcm.h" +#include "ep93xx-i2s.h" + +#define CODEC_CLOCK 5644800 + +static int snappercl15_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int err; + + err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBS_CFS); + + err = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBS_CFS); + if (err) + return err; + + err = snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, + SND_SOC_CLOCK_IN); + if (err) + return err; + + err = snd_soc_dai_set_sysclk(cpu_dai, 0, CODEC_CLOCK, + SND_SOC_CLOCK_OUT); + if (err) + return err; + + return 0; +} + +static struct snd_soc_ops snappercl15_ops = { + .hw_params = snappercl15_hw_params, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +static int snappercl15_tlv320aic23_init(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, + ARRAY_SIZE(tlv320aic23_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + return 0; +} + +static struct snd_soc_dai_link snappercl15_dai = { + .name = "tlv320aic23", + .stream_name = "AIC23", + .cpu_dai = &ep93xx_i2s_dai, + .codec_dai = &tlv320aic23_dai, + .init = snappercl15_tlv320aic23_init, + .ops = &snappercl15_ops, +}; + +static struct snd_soc_card snd_soc_snappercl15 = { + .name = "Snapper CL15", + .platform = &ep93xx_soc_platform, + .dai_link = &snappercl15_dai, + .num_links = 1, +}; + +static struct snd_soc_device snappercl15_snd_devdata = { + .card = &snd_soc_snappercl15, + .codec_dev = &soc_codec_dev_tlv320aic23, +}; + +static struct platform_device *snappercl15_snd_device; + +static int __init snappercl15_init(void) +{ + int ret; + + if (!machine_is_snapper_cl15()) + return -ENODEV; + + ret = ep93xx_i2s_acquire(EP93XX_SYSCON_DEVCFG_I2SONAC97, + EP93XX_SYSCON_I2SCLKDIV_ORIDE | + EP93XX_SYSCON_I2SCLKDIV_SPOL); + if (ret) + return ret; + + snappercl15_snd_device = platform_device_alloc("soc-audio", -1); + if (!snappercl15_snd_device) + return -ENOMEM; + + platform_set_drvdata(snappercl15_snd_device, &snappercl15_snd_devdata); + snappercl15_snd_devdata.dev = &snappercl15_snd_device->dev; + ret = platform_device_add(snappercl15_snd_device); + if (ret) + platform_device_put(snappercl15_snd_device); + + return ret; +} + +static void __exit snappercl15_exit(void) +{ + platform_device_unregister(snappercl15_snd_device); + ep93xx_i2s_release(); +} + +module_init(snappercl15_init); +module_exit(snappercl15_exit); + +MODULE_AUTHOR("Ryan Mallon ryan@bluewatersys.com"); +MODULE_DESCRIPTION("ALSA SoC Snapper CL15"); +MODULE_LICENSE("GPL"); +
On Tue, Jun 08, 2010 at 10:01:09PM +1200, Ryan Mallon wrote:
Repost of the i2s audio series for ep93xx. Patch 2/3 is unchanged. The core support has been modified to move setting of the SPOL and ORIDE bits of the i2sclkdiv register, as well as the i2s pin strapping configuration, to a new function called ep93xx_i2s_acquire which is called from the audio board drivers.
I've now applied both patches. There was a trivial fixup required for the clocks due to an add/add overlap with the SPI clock - I think it's OK but please check.
Mark Brown wrote:
On Tue, Jun 08, 2010 at 10:01:09PM +1200, Ryan Mallon wrote:
Repost of the i2s audio series for ep93xx. Patch 2/3 is unchanged. The core support has been modified to move setting of the SPOL and ORIDE bits of the i2sclkdiv register, as well as the i2s pin strapping configuration, to a new function called ep93xx_i2s_acquire which is called from the audio board drivers.
I've now applied both patches. There was a trivial fixup required for the clocks due to an add/add overlap with the SPI clock - I think it's OK but please check.
Stupid question, but where was it applied? I can't find it in any of the branches at: http://opensource.wolfsonmicro.com/cgi-bin/gitweb.cgi?p=linux-2.6-asoc.git;a...
~Ryan
On 9 Jun 2010, at 22:40, Ryan Mallon wrote:
Stupid question, but where was it applied? I can't find it in any of the branches at: http://opensource.wolfsonmicro.com/cgi-bin/gitweb.cgi?p=linux-2.6-asoc.git;a...
Look in my kernel.org repository referenced in MAINTAINERS
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
Mark Brown wrote:
On 9 Jun 2010, at 22:40, Ryan Mallon wrote:
Stupid question, but where was it applied? I can't find it in any of the branches at: http://opensource.wolfsonmicro.com/cgi-bin/gitweb.cgi?p=linux-2.6-asoc.git;a...
Look in my kernel.org repository referenced in MAINTAINERS
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
Builds and runs fine.
Thanks, ~Ryan
participants (3)
-
H Hartley Sweeten
-
Mark Brown
-
Ryan Mallon