[alsa-devel] [PATCH 1/3] ARM: S3C64XX: Add AC97 platform resources
From: Jassi Brar jassi.brar@samsung.com
This patch defines the platform device and the resources: IRQ, DMA and MEM, needed by the AC97 controller driver.
Signed-off-by: Jassi Brar jassi.brar@samsung.com --- arch/arm/plat-s3c/include/plat/audio.h | 8 +++ arch/arm/plat-s3c/include/plat/devs.h | 3 + arch/arm/plat-s3c64xx/dev-audio.c | 78 ++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 0 deletions(-)
diff --git a/arch/arm/plat-s3c/include/plat/audio.h b/arch/arm/plat-s3c/include/plat/audio.h index f22d23b..e07c941 100644 --- a/arch/arm/plat-s3c/include/plat/audio.h +++ b/arch/arm/plat-s3c/include/plat/audio.h @@ -8,6 +8,14 @@ * published by the Free Software Foundation. */
+/* The machine init code calls s3c*_ac97_setup_gpio with + * one of these defines in order to select appropriate bank + * of GPIO for AC97 pins + */ +#define S3C64XX_AC97_GPD 0 +#define S3C64XX_AC97_GPE 1 +extern void s3c64xx_ac97_setup_gpio(int); + /** * struct s3c_audio_pdata - common platform data for audio device drivers * @cfg_gpio: Callback function to setup mux'ed pins in I2S/PCM/AC97 mode diff --git a/arch/arm/plat-s3c/include/plat/devs.h b/arch/arm/plat-s3c/include/plat/devs.h index c6f9b73..65317eb 100644 --- a/arch/arm/plat-s3c/include/plat/devs.h +++ b/arch/arm/plat-s3c/include/plat/devs.h @@ -35,7 +35,10 @@ extern struct platform_device s3c64xx_device_spi1; extern struct platform_device s3c64xx_device_pcm0; extern struct platform_device s3c64xx_device_pcm1;
+extern struct platform_device s3c64xx_device_ac97; + extern struct platform_device s3c_device_ts; + extern struct platform_device s3c_device_fb; extern struct platform_device s3c_device_ohci; extern struct platform_device s3c_device_lcd; diff --git a/arch/arm/plat-s3c64xx/dev-audio.c b/arch/arm/plat-s3c64xx/dev-audio.c index f6b7bfb..63088b7 100644 --- a/arch/arm/plat-s3c64xx/dev-audio.c +++ b/arch/arm/plat-s3c64xx/dev-audio.c @@ -11,6 +11,7 @@ #include <linux/kernel.h> #include <linux/string.h> #include <linux/platform_device.h> +#include <linux/dma-mapping.h>
#include <mach/irqs.h> #include <mach/map.h> @@ -254,3 +255,80 @@ struct platform_device s3c64xx_device_pcm1 = { }, }; EXPORT_SYMBOL(s3c64xx_device_pcm1); + +/* AC97 Controller platform devices */ + +static int s3c64xx_ac97_cfg_gpd(struct platform_device *pdev) +{ + s3c_gpio_cfgpin(S3C64XX_GPD(0), S3C64XX_GPD0_AC97_BITCLK); + s3c_gpio_cfgpin(S3C64XX_GPD(1), S3C64XX_GPD1_AC97_nRESET); + s3c_gpio_cfgpin(S3C64XX_GPD(2), S3C64XX_GPD2_AC97_SYNC); + s3c_gpio_cfgpin(S3C64XX_GPD(3), S3C64XX_GPD3_AC97_SDI); + s3c_gpio_cfgpin(S3C64XX_GPD(4), S3C64XX_GPD4_AC97_SDO); + + return 0; +} + +static int s3c64xx_ac97_cfg_gpe(struct platform_device *pdev) +{ + s3c_gpio_cfgpin(S3C64XX_GPE(0), S3C64XX_GPE0_AC97_BITCLK); + s3c_gpio_cfgpin(S3C64XX_GPE(1), S3C64XX_GPE1_AC97_nRESET); + s3c_gpio_cfgpin(S3C64XX_GPE(2), S3C64XX_GPE2_AC97_SYNC); + s3c_gpio_cfgpin(S3C64XX_GPE(3), S3C64XX_GPE3_AC97_SDI); + s3c_gpio_cfgpin(S3C64XX_GPE(4), S3C64XX_GPE4_AC97_SDO); + + return 0; +} + +static struct resource s3c64xx_ac97_resource[] = { + [0] = { + .start = S3C64XX_PA_AC97, + .end = S3C64XX_PA_AC97 + 0x100 - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = DMACH_AC97_PCMOUT, + .end = DMACH_AC97_PCMOUT, + .flags = IORESOURCE_DMA, + }, + [2] = { + .start = DMACH_AC97_PCMIN, + .end = DMACH_AC97_PCMIN, + .flags = IORESOURCE_DMA, + }, + [3] = { + .start = DMACH_AC97_MICIN, + .end = DMACH_AC97_MICIN, + .flags = IORESOURCE_DMA, + }, + [4] = { + .start = IRQ_AC97, + .end = IRQ_AC97, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct s3c_audio_pdata s3c_ac97_pdata; + +static u64 s3c64xx_ac97_dmamask = DMA_BIT_MASK(32); + +struct platform_device s3c64xx_device_ac97 = { + .name = "s3c-ac97", + .id = -1, + .num_resources = ARRAY_SIZE(s3c64xx_ac97_resource), + .resource = s3c64xx_ac97_resource, + .dev = { + .platform_data = &s3c_ac97_pdata, + .dma_mask = &s3c64xx_ac97_dmamask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; +EXPORT_SYMBOL(s3c64xx_device_ac97); + +void __init s3c64xx_ac97_setup_gpio(int num) +{ + if (num == S3C64XX_AC97_GPD) + s3c_ac97_pdata.cfg_gpio = s3c64xx_ac97_cfg_gpd; + else + s3c_ac97_pdata.cfg_gpio = s3c64xx_ac97_cfg_gpe; +}
From: Jassi Brar jassi.brar@samsung.com
Add the AC97 controller driver for Samsung SoCs that have one.
Signed-off-by: Jassi Brar jassi.brar@samsung.com --- sound/soc/s3c24xx/Kconfig | 6 +- sound/soc/s3c24xx/Makefile | 3 +- sound/soc/s3c24xx/s3c-ac97.c | 535 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/s3c24xx/s3c-ac97.h | 23 ++ 4 files changed, 565 insertions(+), 2 deletions(-) create mode 100644 sound/soc/s3c24xx/s3c-ac97.c create mode 100644 sound/soc/s3c24xx/s3c-ac97.h
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index b489f1a..ad3690e 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -32,7 +32,11 @@ config SND_S3C2443_SOC_AC97 select S3C2410_DMA select AC97_BUS select SND_SOC_AC97_BUS - + +config SND_S3C_SOC_AC97 + tristate + select SND_SOC_AC97_BUS + config SND_S3C24XX_SOC_NEO1973_WM8753 tristate "SoC I2S Audio support for NEO1973 - WM8753" depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01 diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index b744657..b7411bd 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -4,12 +4,14 @@ snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o +snd-soc-s3c-ac97-objs := s3c-ac97.o snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o snd-soc-s3c-pcm-objs := s3c-pcm.o
obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o +obj-$(CONFIG_SND_S3C_SOC_AC97) += snd-soc-s3c-ac97.o obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o @@ -37,4 +39,3 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o - diff --git a/sound/soc/s3c24xx/s3c-ac97.c b/sound/soc/s3c24xx/s3c-ac97.c new file mode 100644 index 0000000..acb8f51 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.c @@ -0,0 +1,535 @@ +/* sound/soc/s3c24xx/s3c-ac97.c + * + * ALSA SoC Audio Layer - S3C AC97 Controller driver + * Evolved from s3c2443-ac97.c + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd + * Author: Jaswinder Singh jassi.brar@samsung.com + * Credits: Graeme Gregory, Sean Choi + * + * 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/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/clk.h> + +#include <sound/soc.h> + +#include <plat/regs-ac97.h> +#include <mach/dma.h> +#include <plat/audio.h> + +#include "s3c-dma.h" +#include "s3c-ac97.h" + +#define AC_CMD_ADDR(x) (x << 16) +#define AC_CMD_DATA(x) (x & 0xffff) + +struct s3c_ac97_info { + unsigned state; + struct clk *ac97_clk; + void __iomem *regs; + struct mutex lock; + struct completion done; +}; +static struct s3c_ac97_info s3c_ac97; + +static struct s3c2410_dma_client s3c_dma_client_out = { + .name = "AC97 PCMOut" +}; + +static struct s3c2410_dma_client s3c_dma_client_in = { + .name = "AC97 PCMIn" +}; + +static struct s3c2410_dma_client s3c_dma_client_micin = { + .name = "AC97 MicIn" +}; + +static struct s3c_dma_params s3c_ac97_pcm_out = { + .client = &s3c_dma_client_out, + .dma_size = 4, +}; + +static struct s3c_dma_params s3c_ac97_pcm_in = { + .client = &s3c_dma_client_in, + .dma_size = 4, +}; + +static struct s3c_dma_params s3c_ac97_mic_in = { + .client = &s3c_dma_client_micin, + .dma_size = 4, +}; + +static void s3c_ac97_cold_reset(struct snd_ac97 *ac97) +{ + writel(S3C_AC97_GLBCTRL_COLDRESET, + s3c_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); +} + +static void s3c_ac97_warm_reset(struct snd_ac97 *ac97) +{ + writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); +} + +static void s3c_ac97_activate(struct snd_ac97 *ac97) +{ + u32 ac_glbctrl, stat; + + stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7; + switch (stat) { + case S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE: + return; + case S3C_AC97_GLBSTAT_MAINSTATE_READY: + case S3C_AC97_GLBSTAT_MAINSTATE_INIT: + break; + default: + s3c_ac97_cold_reset(ac97); + s3c_ac97_warm_reset(ac97); + break; + } + + ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON; + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE; + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE; + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + + INIT_COMPLETION(s3c_ac97.done); + + if (!wait_for_completion_timeout(&s3c_ac97.done, HZ)) + printk(KERN_ERR "AC97: Unable to activate!"); +} + +static unsigned short s3c_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + u32 ac_glbctrl, ac_codec_cmd; + u32 stat, addr, data; + + mutex_lock(&s3c_ac97.lock); + + s3c_ac97_activate(ac97); + + ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD); + ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg); + writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD); + + udelay(50); + + ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE; + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + + INIT_COMPLETION(s3c_ac97.done); + + if (!wait_for_completion_timeout(&s3c_ac97.done, HZ)) + printk(KERN_ERR "AC97: Unable to read!"); + + stat = readl(s3c_ac97.regs + S3C_AC97_STAT); + addr = (stat >> 16) & 0x7f; + data = (stat & 0xffff); + + if (addr != reg) + printk(KERN_ERR "s3c-ac97: req addr = %02x," + " rep addr = %02x\n", reg, addr); + + mutex_unlock(&s3c_ac97.lock); + + return (unsigned short)data; +} + +static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + u32 ac_glbctrl, ac_codec_cmd; + + mutex_lock(&s3c_ac97.lock); + + s3c_ac97_activate(ac97); + + ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD); + ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val); + writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD); + + udelay(50); + + ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE; + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + + INIT_COMPLETION(s3c_ac97.done); + + if (!wait_for_completion_timeout(&s3c_ac97.done, HZ)) + printk(KERN_ERR "AC97: Unable to write!"); + + ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD); + ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ; + writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD); + + mutex_unlock(&s3c_ac97.lock); +} + +static irqreturn_t s3c_ac97_irq(int irq, void *dev_id) +{ + u32 ac_glbctrl, ac_glbstat; + + ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT); + + if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) { + + ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE; + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + + ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl |= (1<<30); /* Clear interrupt */ + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + + complete(&s3c_ac97.done); + } + + return IRQ_HANDLED; +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = s3c_ac97_read, + .write = s3c_ac97_write, + .warm_reset = s3c_ac97_warm_reset, + .reset = s3c_ac97_cold_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int s3c_ac97_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; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cpu_dai->dma_data = &s3c_ac97_pcm_out; + else + cpu_dai->dma_data = &s3c_ac97_pcm_in; + + return 0; +} + +static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + u32 ac_glbctrl; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channel = ((struct s3c_dma_params *) + rtd->dai->cpu_dai->dma_data)->channel; + + ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK; + else + ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA; + else + ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + } + + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + + s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED); + + return 0; +} + +static int s3c_ac97_hw_mic_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; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + else + cpu_dai->dma_data = &s3c_ac97_mic_in; + + return 0; +} + +static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + u32 ac_glbctrl; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int channel = ((struct s3c_dma_params *) + rtd->dai->cpu_dai->dma_data)->channel; + + ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + } + + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + + s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED); + + return 0; +} + +#define S3C_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +static struct snd_soc_dai_ops s3c_ac97_dai_ops = { + .hw_params = s3c_ac97_hw_params, + .trigger = s3c_ac97_trigger, +}; + +static struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = { + .hw_params = s3c_ac97_hw_mic_params, + .trigger = s3c_ac97_mic_trigger, +}; + +struct snd_soc_dai s3c_ac97_dai[] = { + [S3C_AC97_DAI_PCM] = { + .name = "s3c-ac97", + .id = S3C_AC97_DAI_PCM, + .ac97_control = 1, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = S3C_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "AC97 Capture", + /* NOTE: If the codec ouputs just one slot, + * it *seems* our AC97 controller reads the only + * valid slot(if either 3 or 4) for PCM-In. + * For such cases, we record Mono. + */ + .channels_min = 1, + .channels_max = 2, + .rates = S3C_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &s3c_ac97_dai_ops, + }, + [S3C_AC97_DAI_MIC] = { + .name = "s3c-ac97-mic", + .id = S3C_AC97_DAI_MIC, + .ac97_control = 1, + .capture = { + .stream_name = "AC97 Mic Capture", + .channels_min = 1, + /* NOTE: If the codec(like WM9713) can't ouput just + * one slot, it *seems* our AC97 controller reads + * two slots(if one of them is Slot-6) for MIC also. + * For such cases, we record Stereo. + */ + .channels_max = 2, + .rates = S3C_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &s3c_ac97_mic_dai_ops, + }, +}; +EXPORT_SYMBOL_GPL(s3c_ac97_dai); + +static __devinit int s3c_ac97_probe(struct platform_device *pdev) +{ + struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res; + struct s3c_audio_pdata *ac97_pdata; + int ret; + + ac97_pdata = pdev->dev.platform_data; + if (!ac97_pdata || !ac97_pdata->cfg_gpio) { + dev_err(&pdev->dev, "cfg_gpio callback not provided!\n"); + return -EINVAL; + } + + /* Check for availability of necessary resource */ + dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmatx_res) { + dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n"); + return -ENXIO; + } + + dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!dmarx_res) { + dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n"); + return -ENXIO; + } + + dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2); + if (!dmamic_res) { + dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n"); + return -ENXIO; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + dev_err(&pdev->dev, "Unable to get register resource\n"); + return -ENXIO; + } + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq_res) { + dev_err(&pdev->dev, "AC97 IRQ not provided!\n"); + return -ENXIO; + } + + if (!request_mem_region(mem_res->start, + resource_size(mem_res), "s3c-ac97")) { + dev_err(&pdev->dev, "Unable to request register region\n"); + return -EBUSY; + } + + s3c_ac97_pcm_out.channel = dmatx_res->start; + s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA; + s3c_ac97_pcm_in.channel = dmarx_res->start; + s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA; + s3c_ac97_mic_in.channel = dmamic_res->start; + s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA; + + init_completion(&s3c_ac97.done); + mutex_init(&s3c_ac97.lock); + + s3c_ac97.regs = ioremap(mem_res->start, resource_size(mem_res)); + if (s3c_ac97.regs == NULL) { + dev_err(&pdev->dev, "Unable to ioremap register region\n"); + ret = -ENXIO; + goto lb1; + } + + s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97"); + if (IS_ERR(s3c_ac97.ac97_clk)) { + dev_err(&pdev->dev, "s3c-ac97 failed to get ac97_clock\n"); + ret = -ENODEV; + goto lb2; + } + clk_enable(s3c_ac97.ac97_clk); + + if (ac97_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + ret = -EINVAL; + goto lb3; + } + + ret = request_irq(irq_res->start, s3c_ac97_irq, + IRQF_DISABLED, "AC97", NULL); + if (ret < 0) { + printk(KERN_ERR "s3c-ac97: interrupt request failed.\n"); + goto lb4; + } + + s3c_ac97_dai[S3C_AC97_DAI_PCM].dev = &pdev->dev; + s3c_ac97_dai[S3C_AC97_DAI_MIC].dev = &pdev->dev; + + ret = snd_soc_register_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai)); + if (ret) + goto lb5; + + return 0; + +lb5: + free_irq(irq_res->start, NULL); +lb4: +lb3: + clk_disable(s3c_ac97.ac97_clk); + clk_put(s3c_ac97.ac97_clk); +lb2: + iounmap(s3c_ac97.regs); +lb1: + release_mem_region(mem_res->start, resource_size(mem_res)); + + return ret; +} + +static __devexit int s3c_ac97_remove(struct platform_device *pdev) +{ + struct resource *mem_res, *irq_res; + + snd_soc_unregister_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai)); + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (irq_res) + free_irq(irq_res->start, NULL); + + clk_disable(s3c_ac97.ac97_clk); + clk_put(s3c_ac97.ac97_clk); + + iounmap(s3c_ac97.regs); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem_res) + release_mem_region(mem_res->start, resource_size(mem_res)); + + return 0; +} + +static struct platform_driver s3c_ac97_driver = { + .probe = s3c_ac97_probe, + .remove = s3c_ac97_remove, + .driver = { + .name = "s3c-ac97", + .owner = THIS_MODULE, + }, +}; + +static int __init s3c_ac97_init(void) +{ + return platform_driver_register(&s3c_ac97_driver); +} +module_init(s3c_ac97_init); + +static void __exit s3c_ac97_exit(void) +{ + platform_driver_unregister(&s3c_ac97_driver); +} +module_exit(s3c_ac97_exit); + +MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com"); +MODULE_DESCRIPTION("AC97 driver for the Samsung SoC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c-ac97.h b/sound/soc/s3c24xx/s3c-ac97.h new file mode 100644 index 0000000..2781983 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.h @@ -0,0 +1,23 @@ +/* sound/soc/s3c24xx/s3c-ac97.h + * + * ALSA SoC Audio Layer - S3C AC97 Controller driver + * Evolved from s3c2443-ac97.h + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd + * Author: Jaswinder Singh jassi.brar@samsung.com + * Credits: Graeme Gregory, Sean Choi + * + * 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 __S3C_AC97_H_ +#define __S3C_AC97_H_ + +#define S3C_AC97_DAI_PCM 0 +#define S3C_AC97_DAI_MIC 1 + +extern struct snd_soc_dai s3c_ac97_dai[]; + +#endif /* __S3C_AC97_H_ */
From: Jassi Brar jassi.brar@samsung.com
Implementing basic support for Playback/Capture by SMDK's AC97 controller with WM9713 codec, this patch adds the ALSA machine driver for AC97 device.
Signed-off-by: Jassi Brar jassi.brar@samsung.com --- arch/arm/mach-s3c6410/mach-smdk6410.c | 9 ++- sound/soc/s3c24xx/Kconfig | 8 ++ sound/soc/s3c24xx/Makefile | 2 + sound/soc/s3c24xx/smdk_wm9713.c | 147 +++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 1 deletions(-) create mode 100644 sound/soc/s3c24xx/smdk_wm9713.c
diff --git a/arch/arm/mach-s3c6410/mach-smdk6410.c b/arch/arm/mach-s3c6410/mach-smdk6410.c index eba345f..259ebbb 100644 --- a/arch/arm/mach-s3c6410/mach-smdk6410.c +++ b/arch/arm/mach-s3c6410/mach-smdk6410.c @@ -55,6 +55,7 @@ #include <plat/gpio-cfg.h>
#include <plat/s3c6410.h> +#include <plat/audio.h> #include <plat/clock.h> #include <plat/devs.h> #include <plat/cpu.h> @@ -253,7 +254,9 @@ static struct platform_device *smdk6410_devices[] __initdata = { &smdk6410_b_pwr_5v, #endif &smdk6410_lcd_powerdev, - +#ifdef CONFIG_SND_SOC_SMDK_WM9713 + &s3c64xx_device_ac97, +#endif &smdk6410_smsc911x, };
@@ -475,6 +478,10 @@ static void __init smdk6410_machine_init(void) i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0)); i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
+#ifdef CONFIG_SND_SOC_SMDK_WM9713 + s3c64xx_ac97_setup_gpio(S3C64XX_AC97_GPD); +#endif + platform_add_devices(smdk6410_devices, ARRAY_SIZE(smdk6410_devices)); }
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index ad3690e..d1c6f93 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -115,3 +115,11 @@ config SND_S3C24XX_SOC_SIMTEC_HERMES select SND_S3C24XX_SOC_I2S select SND_SOC_TLV320AIC3X select SND_S3C24XX_SOC_SIMTEC + +config SND_SOC_SMDK_WM9713 + tristate "SoC AC97 Audio support for SMDK with WM9713" + depends on SND_S3C24XX_SOC && MACH_SMDK6410 + select SND_SOC_WM9713 + select SND_S3C_SOC_AC97 + help + Sat Y if you want to add support for SoC audio on the SMDK. diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index b7411bd..1117678 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -28,6 +28,7 @@ snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o snd-soc-smdk64xx-wm8580-objs := smdk64xx_wm8580.o +snd-soc-smdk-wm9713-objs := smdk_wm9713.o
obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o @@ -39,3 +40,4 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o +obj-$(CONFIG_SND_SOC_SMDK_WM9713) += snd-soc-smdk-wm9713.o diff --git a/sound/soc/s3c24xx/smdk_wm9713.c b/sound/soc/s3c24xx/smdk_wm9713.c new file mode 100644 index 0000000..4ce851e --- /dev/null +++ b/sound/soc/s3c24xx/smdk_wm9713.c @@ -0,0 +1,147 @@ +/* + * smdk_wm9713.c -- SoC audio for SMDK + * + * Copyright 2010 Samsung Electronics Co. Ltd. + * Author: Jaswinder Singh Brar jassi.brar@samsung.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/module.h> +#include <linux/device.h> +#include <sound/soc.h> + +#include "../codecs/wm9713.h" +#include "s3c-dma.h" +#include "s3c-ac97.h" + +static struct snd_soc_card smdk; + +/* + Playback :- + Headphone Playback Switch - On + $ amixer cset numid=4 1 + + Right Headphone Out Mux - Headphone + $ amixer cset numid=92 2 + Left Headphone Out Mux - Headphone + $ amixer cset numid=93 2 + + Right HP Mixer PCM Playback Switch - On + $ amixer cset numid=75 1 + Left HP Mixer PCM Playback Switch - On + $ amixer cset numid=81 1 + + Capture (LineIn):- + Right Capture Source - Line + $ amixer cset numid=86 2 + Left Capture Source - Line + $ amixer cset numid=87 2 + + Capture (MicIn):- + Right Capture Source - Mic1 + $ amixer cset numid=86 0 + Left Capture Source - Mic1 + $ amixer cset numid=87 0 +*/ + +static int smdk_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + /* CAPTURE - Stereo LineIn + * Currently there is no other way to change ACLink-In slots + * of Wm9713, so we make do with this call. + */ + /* Slots-3,4*/ + soc_ac97_ops.write(NULL, 0x5c, 0); + + return 0; +} + +static struct snd_soc_ops smdk_hifi_ops = { + .hw_params = smdk_hifi_hw_params, +}; + +static int smdk_mic_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + /* CAPTURE - Mono MicIn + * Currently there is no other way to change ACLink-In slots + * of Wm9713, so we make do with this call. + */ + /* Slots-6,9*/ + soc_ac97_ops.write(NULL, 0x5c, 2); + + return 0; +} + +static struct snd_soc_ops smdk_mic_ops = { + .hw_params = smdk_mic_hw_params, +}; + +static struct snd_soc_dai_link smdk_dai[] = { + [0] = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &s3c_ac97_dai[S3C_AC97_DAI_PCM], + .codec_dai = &wm9713_dai[WM9713_DAI_AC97_HIFI], + .ops = &smdk_hifi_ops, + }, + [1] = { + .name = "AC97", + .stream_name = "AC97 Mic", + .cpu_dai = &s3c_ac97_dai[S3C_AC97_DAI_MIC], + .codec_dai = &wm9713_dai[WM9713_DAI_AC97_HIFI], + .ops = &smdk_mic_ops, + }, +}; + +static struct snd_soc_card smdk = { + .name = "SMDK", + .platform = &s3c24xx_soc_platform, + .dai_link = smdk_dai, + .num_links = ARRAY_SIZE(smdk_dai), +}; + +static struct snd_soc_device smdk_snd_ac97_devdata = { + .card = &smdk, + .codec_dev = &soc_codec_dev_wm9713, +}; + +static struct platform_device *smdk_snd_ac97_device; + +static int __init smdk_init(void) +{ + int ret; + + smdk_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!smdk_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(smdk_snd_ac97_device, + &smdk_snd_ac97_devdata); + smdk_snd_ac97_devdata.dev = &smdk_snd_ac97_device->dev; + + ret = platform_device_add(smdk_snd_ac97_device); + if (ret) + platform_device_put(smdk_snd_ac97_device); + + return ret; +} + +static void __exit smdk_exit(void) +{ + platform_device_unregister(smdk_snd_ac97_device); +} + +module_init(smdk_init); +module_exit(smdk_exit); + +/* Module information */ +MODULE_AUTHOR("Jaswinder Singh Brar, jassi.brar@samsung.com"); +MODULE_DESCRIPTION("ALSA SoC SMDK+WM9713"); +MODULE_LICENSE("GPL");
On Tue, Jan 26, 2010 at 02:51:41PM +0900, jassisinghbrar@gmail.com wrote:
From: Jassi Brar jassi.brar@samsung.com
Implementing basic support for Playback/Capture by SMDK's AC97 controller with WM9713 codec, this patch adds the ALSA machine driver for AC97 device.
Might be better to split this into an arch/arm patch and a sound/soc patch - they don't appear to depend on each other so it'd ease merging.
+static int smdk_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
- /* CAPTURE - Stereo LineIn
* Currently there is no other way to change ACLink-In slots
* of Wm9713, so we make do with this call.
*/
- /* Slots-3,4*/
- soc_ac97_ops.write(NULL, 0x5c, 0);
- return 0;
+}
This one needs a bit more explanation - why are you changing the AC97 slot? Is this a general problem with the S3C AC97 controller? It'd also be better to use the symbolic register name rather than a number.
On Tue, Jan 26, 2010 at 7:19 PM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Jan 26, 2010 at 02:51:41PM +0900, jassisinghbrar@gmail.com wrote:
From: Jassi Brar jassi.brar@samsung.com
Implementing basic support for Playback/Capture by SMDK's AC97 controller with WM9713 codec, this patch adds the ALSA machine driver for AC97 device.
Might be better to split this into an arch/arm patch and a sound/soc patch - they don't appear to depend on each other so it'd ease merging.
ok, will do.
+static int smdk_hifi_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
+{
- /* CAPTURE - Stereo LineIn
- * Currently there is no other way to change ACLink-In slots
- * of Wm9713, so we make do with this call.
- */
- /* Slots-3,4*/
- soc_ac97_ops.write(NULL, 0x5c, 0);
- return 0;
+}
This one needs a bit more explanation - why are you changing the AC97 slot? Is this a general problem with the S3C AC97 controller? It'd also be better to use the symbolic register name rather than a number.
It is no problem. wm9713 can be configured to place data in one of four possible pair of slots 3-4 or 6-9 or 7-8 or 10-11. This call ensures the selected pair is 3-4(standard and compatible with Samsung AC97 controllers). Though we don't need to explicitly set this if we drop mic dai_link.
On Tue, Jan 26, 2010 at 07:52:45PM +0900, jassi brar wrote:
On Tue, Jan 26, 2010 at 7:19 PM, Mark Brown
This one needs a bit more explanation - why are you changing the AC97 slot? Is this a general problem with the S3C AC97 controller? It'd also be better to use the symbolic register name rather than a number.
It is no problem. wm9713 can be configured to place data in one of four possible pair of slots 3-4 or 6-9 or 7-8 or 10-11. This call ensures the selected pair is 3-4(standard and compatible with Samsung AC97 controllers). Though we don't need to explicitly set this if we drop mic dai_link.
Ah, OK. In this case dropping the second DAI link is the better option - the CODEC only presents a single stereo ADC pair on the AC97 bus with selectable inputs rather than having separate ADC(s) for microphone(s). This means you can't run both DAIs in parallel anyway.
On Tue, Jan 26, 2010 at 2:51 PM, jassisinghbrar@gmail.com wrote:
From: Jassi Brar jassi.brar@samsung.com
Add the AC97 controller driver for Samsung SoCs that have one.
Signed-off-by: Jassi Brar jassi.brar@samsung.com
sound/soc/s3c24xx/Kconfig | 6 +- sound/soc/s3c24xx/Makefile | 3 +- sound/soc/s3c24xx/s3c-ac97.c | 535 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/s3c24xx/s3c-ac97.h | 23 ++ 4 files changed, 565 insertions(+), 2 deletions(-) create mode 100644 sound/soc/s3c24xx/s3c-ac97.c create mode 100644 sound/soc/s3c24xx/s3c-ac97.h
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index b489f1a..ad3690e 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -32,7 +32,11 @@ config SND_S3C2443_SOC_AC97 select S3C2410_DMA select AC97_BUS select SND_SOC_AC97_BUS
+config SND_S3C_SOC_AC97
- tristate
- select SND_SOC_AC97_BUS
config SND_S3C24XX_SOC_NEO1973_WM8753 tristate "SoC I2S Audio support for NEO1973 - WM8753" depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01 diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index b744657..b7411bd 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -4,12 +4,14 @@ snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o +snd-soc-s3c-ac97-objs := s3c-ac97.o snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o snd-soc-s3c-pcm-objs := s3c-pcm.o
obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o +obj-$(CONFIG_SND_S3C_SOC_AC97) += snd-soc-s3c-ac97.o obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o @@ -37,4 +39,3 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o
diff --git a/sound/soc/s3c24xx/s3c-ac97.c b/sound/soc/s3c24xx/s3c-ac97.c new file mode 100644 index 0000000..acb8f51 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.c @@ -0,0 +1,535 @@ +/* sound/soc/s3c24xx/s3c-ac97.c
- ALSA SoC Audio Layer - S3C AC97 Controller driver
- Evolved from s3c2443-ac97.c
- Copyright (c) 2010 Samsung Electronics Co. Ltd
- Author: Jaswinder Singh jassi.brar@samsung.com
- Credits: Graeme Gregory, Sean Choi
- 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/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/clk.h>
+#include <sound/soc.h>
+#include <plat/regs-ac97.h> +#include <mach/dma.h> +#include <plat/audio.h>
+#include "s3c-dma.h" +#include "s3c-ac97.h"
+#define AC_CMD_ADDR(x) (x << 16) +#define AC_CMD_DATA(x) (x & 0xffff)
+struct s3c_ac97_info {
- unsigned state;
- struct clk *ac97_clk;
- void __iomem *regs;
- struct mutex lock;
- struct completion done;
+}; +static struct s3c_ac97_info s3c_ac97;
+static struct s3c2410_dma_client s3c_dma_client_out = {
- .name = "AC97 PCMOut"
+};
+static struct s3c2410_dma_client s3c_dma_client_in = {
- .name = "AC97 PCMIn"
+};
+static struct s3c2410_dma_client s3c_dma_client_micin = {
- .name = "AC97 MicIn"
+};
+static struct s3c_dma_params s3c_ac97_pcm_out = {
- .client = &s3c_dma_client_out,
- .dma_size = 4,
+};
+static struct s3c_dma_params s3c_ac97_pcm_in = {
- .client = &s3c_dma_client_in,
- .dma_size = 4,
+};
+static struct s3c_dma_params s3c_ac97_mic_in = {
- .client = &s3c_dma_client_micin,
- .dma_size = 4,
+};
+static void s3c_ac97_cold_reset(struct snd_ac97 *ac97) +{
- writel(S3C_AC97_GLBCTRL_COLDRESET,
- s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
+}
+static void s3c_ac97_warm_reset(struct snd_ac97 *ac97) +{
- writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
+}
+static void s3c_ac97_activate(struct snd_ac97 *ac97) +{
- u32 ac_glbctrl, stat;
- stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
- switch (stat) {
- case S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE:
- return;
- case S3C_AC97_GLBSTAT_MAINSTATE_READY:
- case S3C_AC97_GLBSTAT_MAINSTATE_INIT:
- break;
- default:
- s3c_ac97_cold_reset(ac97);
- s3c_ac97_warm_reset(ac97);
- break;
- }
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to activate!");
+}
+static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
- unsigned short reg)
+{
- u32 ac_glbctrl, ac_codec_cmd;
- u32 stat, addr, data;
- mutex_lock(&s3c_ac97.lock);
- s3c_ac97_activate(ac97);
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- udelay(50);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to read!");
- stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
- addr = (stat >> 16) & 0x7f;
- data = (stat & 0xffff);
- if (addr != reg)
- printk(KERN_ERR "s3c-ac97: req addr = %02x,"
- " rep addr = %02x\n", reg, addr);
- mutex_unlock(&s3c_ac97.lock);
- return (unsigned short)data;
+}
+static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
- unsigned short val)
+{
- u32 ac_glbctrl, ac_codec_cmd;
- mutex_lock(&s3c_ac97.lock);
- s3c_ac97_activate(ac97);
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- udelay(50);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to write!");
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- mutex_unlock(&s3c_ac97.lock);
+}
+static irqreturn_t s3c_ac97_irq(int irq, void *dev_id) +{
- u32 ac_glbctrl, ac_glbstat;
- ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
- if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= (1<<30); /* Clear interrupt */
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- complete(&s3c_ac97.done);
- }
- return IRQ_HANDLED;
+}
+struct snd_ac97_bus_ops soc_ac97_ops = {
- .read = s3c_ac97_read,
- .write = s3c_ac97_write,
- .warm_reset = s3c_ac97_warm_reset,
- .reset = s3c_ac97_cold_reset,
+}; +EXPORT_SYMBOL_GPL(soc_ac97_ops);
+static int s3c_ac97_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;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- cpu_dai->dma_data = &s3c_ac97_pcm_out;
- else
- cpu_dai->dma_data = &s3c_ac97_pcm_in;
- return 0;
+}
+static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
- struct snd_soc_dai *dai)
+{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
- rtd->dai->cpu_dai->dma_data)->channel;
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
- else
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
- else
- ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- break;
- }
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
- return 0;
+}
+static int s3c_ac97_hw_mic_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;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- return -ENODEV;
- else
- cpu_dai->dma_data = &s3c_ac97_mic_in;
- return 0;
+}
+static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
- int cmd, struct snd_soc_dai *dai)
+{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
- rtd->dai->cpu_dai->dma_data)->channel;
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- break;
- }
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
- return 0;
+}
+#define S3C_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
- SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
- SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
- SNDRV_PCM_RATE_48000)
+static struct snd_soc_dai_ops s3c_ac97_dai_ops = {
- .hw_params = s3c_ac97_hw_params,
- .trigger = s3c_ac97_trigger,
+};
+static struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
- .hw_params = s3c_ac97_hw_mic_params,
- .trigger = s3c_ac97_mic_trigger,
+};
+struct snd_soc_dai s3c_ac97_dai[] = {
- [S3C_AC97_DAI_PCM] = {
- .name = "s3c-ac97",
- .id = S3C_AC97_DAI_PCM,
- .ac97_control = 1,
- .playback = {
- .stream_name = "AC97 Playback",
- .channels_min = 2,
- .channels_max = 2,
- .rates = S3C_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .capture = {
- .stream_name = "AC97 Capture",
- /* NOTE: If the codec ouputs just one slot,
- * it *seems* our AC97 controller reads the only
- * valid slot(if either 3 or 4) for PCM-In.
- * For such cases, we record Mono.
- */
- .channels_min = 1,
I would like to remove this anomaly and set channels_min = 2
- .channels_max = 2,
- .rates = S3C_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = &s3c_ac97_dai_ops,
- },
- [S3C_AC97_DAI_MIC] = {
- .name = "s3c-ac97-mic",
- .id = S3C_AC97_DAI_MIC,
- .ac97_control = 1,
- .capture = {
- .stream_name = "AC97 Mic Capture",
- .channels_min = 1,
- /* NOTE: If the codec(like WM9713) can't ouput just
- * one slot, it *seems* our AC97 controller reads
- * two slots(if one of them is Slot-6) for MIC also.
- * For such cases, we record Stereo.
- */
- .channels_max = 2,
I would like to remove this anomaly and set channels_max = 1
And remove the second DAI_LINK in machine driver.
Please continue review and give me other feedback. Thanks.
- .rates = S3C_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = &s3c_ac97_mic_dai_ops,
- },
+}; +EXPORT_SYMBOL_GPL(s3c_ac97_dai);
+static __devinit int s3c_ac97_probe(struct platform_device *pdev) +{
- struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
- struct s3c_audio_pdata *ac97_pdata;
- int ret;
- ac97_pdata = pdev->dev.platform_data;
- if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
- dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
- return -EINVAL;
- }
- /* Check for availability of necessary resource */
- dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
- if (!dmatx_res) {
- dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
- return -ENXIO;
- }
- dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
- if (!dmarx_res) {
- dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
- return -ENXIO;
- }
- dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
- if (!dmamic_res) {
- dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
- return -ENXIO;
- }
- mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!mem_res) {
- dev_err(&pdev->dev, "Unable to get register resource\n");
- return -ENXIO;
- }
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (!irq_res) {
- dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
- return -ENXIO;
- }
- if (!request_mem_region(mem_res->start,
- resource_size(mem_res), "s3c-ac97")) {
- dev_err(&pdev->dev, "Unable to request register region\n");
- return -EBUSY;
- }
- s3c_ac97_pcm_out.channel = dmatx_res->start;
- s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
- s3c_ac97_pcm_in.channel = dmarx_res->start;
- s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
- s3c_ac97_mic_in.channel = dmamic_res->start;
- s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
- init_completion(&s3c_ac97.done);
- mutex_init(&s3c_ac97.lock);
- s3c_ac97.regs = ioremap(mem_res->start, resource_size(mem_res));
- if (s3c_ac97.regs == NULL) {
- dev_err(&pdev->dev, "Unable to ioremap register region\n");
- ret = -ENXIO;
- goto lb1;
- }
- s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
- if (IS_ERR(s3c_ac97.ac97_clk)) {
- dev_err(&pdev->dev, "s3c-ac97 failed to get ac97_clock\n");
- ret = -ENODEV;
- goto lb2;
- }
- clk_enable(s3c_ac97.ac97_clk);
- if (ac97_pdata->cfg_gpio(pdev)) {
- dev_err(&pdev->dev, "Unable to configure gpio\n");
- ret = -EINVAL;
- goto lb3;
- }
- ret = request_irq(irq_res->start, s3c_ac97_irq,
- IRQF_DISABLED, "AC97", NULL);
- if (ret < 0) {
- printk(KERN_ERR "s3c-ac97: interrupt request failed.\n");
- goto lb4;
- }
- s3c_ac97_dai[S3C_AC97_DAI_PCM].dev = &pdev->dev;
- s3c_ac97_dai[S3C_AC97_DAI_MIC].dev = &pdev->dev;
- ret = snd_soc_register_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
- if (ret)
- goto lb5;
- return 0;
+lb5:
- free_irq(irq_res->start, NULL);
+lb4: +lb3:
- clk_disable(s3c_ac97.ac97_clk);
- clk_put(s3c_ac97.ac97_clk);
+lb2:
- iounmap(s3c_ac97.regs);
+lb1:
- release_mem_region(mem_res->start, resource_size(mem_res));
- return ret;
+}
+static __devexit int s3c_ac97_remove(struct platform_device *pdev) +{
- struct resource *mem_res, *irq_res;
- snd_soc_unregister_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (irq_res)
- free_irq(irq_res->start, NULL);
- clk_disable(s3c_ac97.ac97_clk);
- clk_put(s3c_ac97.ac97_clk);
- iounmap(s3c_ac97.regs);
- mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (mem_res)
- release_mem_region(mem_res->start, resource_size(mem_res));
- return 0;
+}
+static struct platform_driver s3c_ac97_driver = {
- .probe = s3c_ac97_probe,
- .remove = s3c_ac97_remove,
- .driver = {
- .name = "s3c-ac97",
- .owner = THIS_MODULE,
- },
+};
+static int __init s3c_ac97_init(void) +{
- return platform_driver_register(&s3c_ac97_driver);
+} +module_init(s3c_ac97_init);
+static void __exit s3c_ac97_exit(void) +{
- platform_driver_unregister(&s3c_ac97_driver);
+} +module_exit(s3c_ac97_exit);
+MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com"); +MODULE_DESCRIPTION("AC97 driver for the Samsung SoC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c-ac97.h b/sound/soc/s3c24xx/s3c-ac97.h new file mode 100644 index 0000000..2781983 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.h @@ -0,0 +1,23 @@ +/* sound/soc/s3c24xx/s3c-ac97.h
- ALSA SoC Audio Layer - S3C AC97 Controller driver
- Evolved from s3c2443-ac97.h
- Copyright (c) 2010 Samsung Electronics Co. Ltd
- Author: Jaswinder Singh jassi.brar@samsung.com
- Credits: Graeme Gregory, Sean Choi
- 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 __S3C_AC97_H_ +#define __S3C_AC97_H_
+#define S3C_AC97_DAI_PCM 0 +#define S3C_AC97_DAI_MIC 1
+extern struct snd_soc_dai s3c_ac97_dai[];
+#endif /* __S3C_AC97_H_ */
1.6.2.5
-- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Tue, Jan 26, 2010 at 04:32:45PM +0900, jassi brar wrote:
On Tue, Jan 26, 2010 at 2:51 PM, jassisinghbrar@gmail.com wrote:
Please delete irrelevant context when replying to mails, it's much easier to find the actual new text in the message that way.
- /* NOTE: If the codec ouputs just one slot,
- * it *seems* our AC97 controller reads the only
- * valid slot(if either 3 or 4) for PCM-In.
- * For such cases, we record Mono.
- */
- .channels_min = 1,
I would like to remove this anomaly and set channels_min = 2
Why?
And remove the second DAI_LINK in machine driver.
Again, why?
On Tue, Jan 26, 2010 at 7:53 PM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Jan 26, 2010 at 04:32:45PM +0900, jassi brar wrote:
On Tue, Jan 26, 2010 at 2:51 PM, jassisinghbrar@gmail.com wrote:
Please delete irrelevant context when replying to mails, it's much easier to find the actual new text in the message that way.
ok
- /* NOTE: If the codec ouputs just one slot,
- * it *seems* our AC97 controller reads the only
- * valid slot(if either 3 or 4) for PCM-In.
- * For such cases, we record Mono.
- */
- .channels_min = 1,
I would like to remove this anomaly and set channels_min = 2
Why?
This hypothesis was based on wrong observation that AC97 can record stereo-mic if two slots are marked valid and one of them is 6th. I decided to remove it after that observation turned out wrong(testing error). Even though this seems logical, but I have not verified it so I wanted to remove.
And remove the second DAI_LINK in machine driver.
Again, why?
The wm9713 doesn't support mono capture and AC97 controller doesn't support stereo MIC capture. So the second dai link is improper.
On Tue, Jan 26, 2010 at 02:51:40PM +0900, jassisinghbrar@gmail.com wrote:
From: Jassi Brar jassi.brar@samsung.com
Add the AC97 controller driver for Samsung SoCs that have one.
Signed-off-by: Jassi Brar jassi.brar@samsung.com
sound/soc/s3c24xx/Kconfig | 6 +- sound/soc/s3c24xx/Makefile | 3 +- sound/soc/s3c24xx/s3c-ac97.c | 535 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/s3c24xx/s3c-ac97.h | 23 ++ 4 files changed, 565 insertions(+), 2 deletions(-) create mode 100644 sound/soc/s3c24xx/s3c-ac97.c create mode 100644 sound/soc/s3c24xx/s3c-ac97.h
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index b489f1a..ad3690e 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -32,7 +32,11 @@ config SND_S3C2443_SOC_AC97 select S3C2410_DMA select AC97_BUS select SND_SOC_AC97_BUS
+config SND_S3C_SOC_AC97
- tristate
- select SND_SOC_AC97_BUS
config SND_S3C24XX_SOC_NEO1973_WM8753 tristate "SoC I2S Audio support for NEO1973 - WM8753" depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01 diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index b744657..b7411bd 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -4,12 +4,14 @@ snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o +snd-soc-s3c-ac97-objs := s3c-ac97.o snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o snd-soc-s3c-pcm-objs := s3c-pcm.o
obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o +obj-$(CONFIG_SND_S3C_SOC_AC97) += snd-soc-s3c-ac97.o obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o @@ -37,4 +39,3 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o
diff --git a/sound/soc/s3c24xx/s3c-ac97.c b/sound/soc/s3c24xx/s3c-ac97.c new file mode 100644 index 0000000..acb8f51 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.c @@ -0,0 +1,535 @@ +/* sound/soc/s3c24xx/s3c-ac97.c
- ALSA SoC Audio Layer - S3C AC97 Controller driver
- Evolved from s3c2443-ac97.c
- Copyright (c) 2010 Samsung Electronics Co. Ltd
- Author: Jaswinder Singh jassi.brar@samsung.com
- Credits: Graeme Gregory, Sean Choi
- 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/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/clk.h>
+#include <sound/soc.h>
+#include <plat/regs-ac97.h> +#include <mach/dma.h> +#include <plat/audio.h>
+#include "s3c-dma.h" +#include "s3c-ac97.h"
+#define AC_CMD_ADDR(x) (x << 16) +#define AC_CMD_DATA(x) (x & 0xffff)
+struct s3c_ac97_info {
- unsigned state;
- struct clk *ac97_clk;
- void __iomem *regs;
- struct mutex lock;
- struct completion done;
+}; +static struct s3c_ac97_info s3c_ac97;
+static struct s3c2410_dma_client s3c_dma_client_out = {
- .name = "AC97 PCMOut"
+};
+static struct s3c2410_dma_client s3c_dma_client_in = {
- .name = "AC97 PCMIn"
+};
+static struct s3c2410_dma_client s3c_dma_client_micin = {
- .name = "AC97 MicIn"
+};
+static struct s3c_dma_params s3c_ac97_pcm_out = {
- .client = &s3c_dma_client_out,
- .dma_size = 4,
+};
+static struct s3c_dma_params s3c_ac97_pcm_in = {
- .client = &s3c_dma_client_in,
- .dma_size = 4,
+};
+static struct s3c_dma_params s3c_ac97_mic_in = {
- .client = &s3c_dma_client_micin,
- .dma_size = 4,
+};
+static void s3c_ac97_cold_reset(struct snd_ac97 *ac97) +{
- writel(S3C_AC97_GLBCTRL_COLDRESET,
s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
+}
+static void s3c_ac97_warm_reset(struct snd_ac97 *ac97) +{
- writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
+}
It would be nice t osee something to convert a 'struct snd_ac97' to a 'struct s3c_ac97_info' as this is being passed in to most place and would also help if there is ever >1 block.
+static void s3c_ac97_activate(struct snd_ac97 *ac97) +{
- u32 ac_glbctrl, stat;
- stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
- switch (stat) {
- case S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE:
return;
- case S3C_AC97_GLBSTAT_MAINSTATE_READY:
- case S3C_AC97_GLBSTAT_MAINSTATE_INIT:
break;
- default:
s3c_ac97_cold_reset(ac97);
s3c_ac97_warm_reset(ac97);
break;
- }
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
printk(KERN_ERR "AC97: Unable to activate!");
+}
+static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
- unsigned short reg)
+{
- u32 ac_glbctrl, ac_codec_cmd;
- u32 stat, addr, data;
- mutex_lock(&s3c_ac97.lock);
- s3c_ac97_activate(ac97);
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- udelay(50);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
printk(KERN_ERR "AC97: Unable to read!");
- stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
- addr = (stat >> 16) & 0x7f;
- data = (stat & 0xffff);
- if (addr != reg)
printk(KERN_ERR "s3c-ac97: req addr = %02x,"
" rep addr = %02x\n", reg, addr);
- mutex_unlock(&s3c_ac97.lock);
- return (unsigned short)data;
+}
+static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
- unsigned short val)
+{
- u32 ac_glbctrl, ac_codec_cmd;
- mutex_lock(&s3c_ac97.lock);
- s3c_ac97_activate(ac97);
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- udelay(50);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
printk(KERN_ERR "AC97: Unable to write!");
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- mutex_unlock(&s3c_ac97.lock);
+}
+static irqreturn_t s3c_ac97_irq(int irq, void *dev_id) +{
- u32 ac_glbctrl, ac_glbstat;
- ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
- if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl |= (1<<30); /* Clear interrupt */
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
complete(&s3c_ac97.done);
- }
- return IRQ_HANDLED;
+}
+struct snd_ac97_bus_ops soc_ac97_ops = {
- .read = s3c_ac97_read,
- .write = s3c_ac97_write,
- .warm_reset = s3c_ac97_warm_reset,
- .reset = s3c_ac97_cold_reset,
+}; +EXPORT_SYMBOL_GPL(soc_ac97_ops);
+static int s3c_ac97_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;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
cpu_dai->dma_data = &s3c_ac97_pcm_out;
- else
cpu_dai->dma_data = &s3c_ac97_pcm_in;
- return 0;
+}
+static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
+{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
rtd->dai->cpu_dai->dma_data)->channel;
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
- else
ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
else
ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
break;
- }
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
- return 0;
+}
+static int s3c_ac97_hw_mic_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;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
return -ENODEV;
- else
cpu_dai->dma_data = &s3c_ac97_mic_in;
- return 0;
+}
+static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
+{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
rtd->dai->cpu_dai->dma_data)->channel;
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
break;
- }
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
- return 0;
+}
+#define S3C_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000)
+static struct snd_soc_dai_ops s3c_ac97_dai_ops = {
- .hw_params = s3c_ac97_hw_params,
- .trigger = s3c_ac97_trigger,
+};
+static struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
- .hw_params = s3c_ac97_hw_mic_params,
- .trigger = s3c_ac97_mic_trigger,
+};
+struct snd_soc_dai s3c_ac97_dai[] = {
- [S3C_AC97_DAI_PCM] = {
.name = "s3c-ac97",
.id = S3C_AC97_DAI_PCM,
.ac97_control = 1,
.playback = {
.stream_name = "AC97 Playback",
.channels_min = 2,
.channels_max = 2,
.rates = S3C_AC97_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.stream_name = "AC97 Capture",
/* NOTE: If the codec ouputs just one slot,
* it *seems* our AC97 controller reads the only
* valid slot(if either 3 or 4) for PCM-In.
* For such cases, we record Mono.
*/
.channels_min = 1,
.channels_max = 2,
.rates = S3C_AC97_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &s3c_ac97_dai_ops,
- },
- [S3C_AC97_DAI_MIC] = {
.name = "s3c-ac97-mic",
.id = S3C_AC97_DAI_MIC,
.ac97_control = 1,
.capture = {
.stream_name = "AC97 Mic Capture",
.channels_min = 1,
/* NOTE: If the codec(like WM9713) can't ouput just
* one slot, it *seems* our AC97 controller reads
* two slots(if one of them is Slot-6) for MIC also.
* For such cases, we record Stereo.
*/
.channels_max = 2,
.rates = S3C_AC97_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &s3c_ac97_mic_dai_ops,
- },
+}; +EXPORT_SYMBOL_GPL(s3c_ac97_dai);
+static __devinit int s3c_ac97_probe(struct platform_device *pdev) +{
- struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
- struct s3c_audio_pdata *ac97_pdata;
- int ret;
- ac97_pdata = pdev->dev.platform_data;
- if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
return -EINVAL;
- }
- /* Check for availability of necessary resource */
- dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
- if (!dmatx_res) {
dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
return -ENXIO;
- }
- dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
- if (!dmarx_res) {
dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
return -ENXIO;
- }
- dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
- if (!dmamic_res) {
dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
return -ENXIO;
- }
- mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!mem_res) {
dev_err(&pdev->dev, "Unable to get register resource\n");
return -ENXIO;
- }
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (!irq_res) {
dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
return -ENXIO;
- }
- if (!request_mem_region(mem_res->start,
resource_size(mem_res), "s3c-ac97")) {
dev_err(&pdev->dev, "Unable to request register region\n");
return -EBUSY;
- }
- s3c_ac97_pcm_out.channel = dmatx_res->start;
- s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
- s3c_ac97_pcm_in.channel = dmarx_res->start;
- s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
- s3c_ac97_mic_in.channel = dmamic_res->start;
- s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
- init_completion(&s3c_ac97.done);
- mutex_init(&s3c_ac97.lock);
- s3c_ac97.regs = ioremap(mem_res->start, resource_size(mem_res));
- if (s3c_ac97.regs == NULL) {
dev_err(&pdev->dev, "Unable to ioremap register region\n");
ret = -ENXIO;
goto lb1;
- }
- s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
- if (IS_ERR(s3c_ac97.ac97_clk)) {
dev_err(&pdev->dev, "s3c-ac97 failed to get ac97_clock\n");
ret = -ENODEV;
goto lb2;
- }
- clk_enable(s3c_ac97.ac97_clk);
- if (ac97_pdata->cfg_gpio(pdev)) {
dev_err(&pdev->dev, "Unable to configure gpio\n");
ret = -EINVAL;
goto lb3;
- }
- ret = request_irq(irq_res->start, s3c_ac97_irq,
IRQF_DISABLED, "AC97", NULL);
- if (ret < 0) {
printk(KERN_ERR "s3c-ac97: interrupt request failed.\n");
goto lb4;
- }
- s3c_ac97_dai[S3C_AC97_DAI_PCM].dev = &pdev->dev;
- s3c_ac97_dai[S3C_AC97_DAI_MIC].dev = &pdev->dev;
- ret = snd_soc_register_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
- if (ret)
goto lb5;
- return 0;
+lb5:
- free_irq(irq_res->start, NULL);
+lb4: +lb3:
- clk_disable(s3c_ac97.ac97_clk);
- clk_put(s3c_ac97.ac97_clk);
+lb2:
- iounmap(s3c_ac97.regs);
+lb1:
- release_mem_region(mem_res->start, resource_size(mem_res));
- return ret;
+}
+static __devexit int s3c_ac97_remove(struct platform_device *pdev) +{
- struct resource *mem_res, *irq_res;
- snd_soc_unregister_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (irq_res)
free_irq(irq_res->start, NULL);
- clk_disable(s3c_ac97.ac97_clk);
- clk_put(s3c_ac97.ac97_clk);
- iounmap(s3c_ac97.regs);
- mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (mem_res)
release_mem_region(mem_res->start, resource_size(mem_res));
- return 0;
+}
+static struct platform_driver s3c_ac97_driver = {
- .probe = s3c_ac97_probe,
- .remove = s3c_ac97_remove,
- .driver = {
.name = "s3c-ac97",
.owner = THIS_MODULE,
- },
+};
+static int __init s3c_ac97_init(void) +{
- return platform_driver_register(&s3c_ac97_driver);
+} +module_init(s3c_ac97_init);
+static void __exit s3c_ac97_exit(void) +{
- platform_driver_unregister(&s3c_ac97_driver);
+} +module_exit(s3c_ac97_exit);
+MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com"); +MODULE_DESCRIPTION("AC97 driver for the Samsung SoC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c-ac97.h b/sound/soc/s3c24xx/s3c-ac97.h new file mode 100644 index 0000000..2781983 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.h @@ -0,0 +1,23 @@ +/* sound/soc/s3c24xx/s3c-ac97.h
- ALSA SoC Audio Layer - S3C AC97 Controller driver
- Evolved from s3c2443-ac97.h
- Copyright (c) 2010 Samsung Electronics Co. Ltd
- Author: Jaswinder Singh jassi.brar@samsung.com
- Credits: Graeme Gregory, Sean Choi
- 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 __S3C_AC97_H_ +#define __S3C_AC97_H_
+#define S3C_AC97_DAI_PCM 0 +#define S3C_AC97_DAI_MIC 1
+extern struct snd_soc_dai s3c_ac97_dai[];
+#endif /* __S3C_AC97_H_ */
1.6.2.5
-- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Out of interest, how similar are the two blocks and could the s3c2443 case be elided into this one with some minimal checks for which one is currently in use?
On Tue, Jan 26, 2010 at 7:23 PM, Ben Dooks ben-linux@fluff.org wrote:
On Tue, Jan 26, 2010 at 02:51:40PM +0900, jassisinghbrar@gmail.com wrote:
From: Jassi Brar jassi.brar@samsung.com
Add the AC97 controller driver for Samsung SoCs that have one.
Signed-off-by: Jassi Brar jassi.brar@samsung.com
sound/soc/s3c24xx/Kconfig | 6 +- sound/soc/s3c24xx/Makefile | 3 +- sound/soc/s3c24xx/s3c-ac97.c | 535 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/s3c24xx/s3c-ac97.h | 23 ++ 4 files changed, 565 insertions(+), 2 deletions(-) create mode 100644 sound/soc/s3c24xx/s3c-ac97.c create mode 100644 sound/soc/s3c24xx/s3c-ac97.h
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index b489f1a..ad3690e 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -32,7 +32,11 @@ config SND_S3C2443_SOC_AC97 select S3C2410_DMA select AC97_BUS select SND_SOC_AC97_BUS
+config SND_S3C_SOC_AC97
- tristate
- select SND_SOC_AC97_BUS
config SND_S3C24XX_SOC_NEO1973_WM8753 tristate "SoC I2S Audio support for NEO1973 - WM8753" depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01 diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index b744657..b7411bd 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -4,12 +4,14 @@ snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o +snd-soc-s3c-ac97-objs := s3c-ac97.o snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o snd-soc-s3c-pcm-objs := s3c-pcm.o
obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o +obj-$(CONFIG_SND_S3C_SOC_AC97) += snd-soc-s3c-ac97.o obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o @@ -37,4 +39,3 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o
diff --git a/sound/soc/s3c24xx/s3c-ac97.c b/sound/soc/s3c24xx/s3c-ac97.c new file mode 100644 index 0000000..acb8f51 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.c @@ -0,0 +1,535 @@ +/* sound/soc/s3c24xx/s3c-ac97.c
- ALSA SoC Audio Layer - S3C AC97 Controller driver
- Evolved from s3c2443-ac97.c
- Copyright (c) 2010 Samsung Electronics Co. Ltd
- Author: Jaswinder Singh jassi.brar@samsung.com
- Credits: Graeme Gregory, Sean Choi
- 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/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/clk.h>
+#include <sound/soc.h>
+#include <plat/regs-ac97.h> +#include <mach/dma.h> +#include <plat/audio.h>
+#include "s3c-dma.h" +#include "s3c-ac97.h"
+#define AC_CMD_ADDR(x) (x << 16) +#define AC_CMD_DATA(x) (x & 0xffff)
+struct s3c_ac97_info {
- unsigned state;
- struct clk *ac97_clk;
- void __iomem *regs;
- struct mutex lock;
- struct completion done;
+}; +static struct s3c_ac97_info s3c_ac97;
+static struct s3c2410_dma_client s3c_dma_client_out = {
- .name = "AC97 PCMOut"
+};
+static struct s3c2410_dma_client s3c_dma_client_in = {
- .name = "AC97 PCMIn"
+};
+static struct s3c2410_dma_client s3c_dma_client_micin = {
- .name = "AC97 MicIn"
+};
+static struct s3c_dma_params s3c_ac97_pcm_out = {
- .client = &s3c_dma_client_out,
- .dma_size = 4,
+};
+static struct s3c_dma_params s3c_ac97_pcm_in = {
- .client = &s3c_dma_client_in,
- .dma_size = 4,
+};
+static struct s3c_dma_params s3c_ac97_mic_in = {
- .client = &s3c_dma_client_micin,
- .dma_size = 4,
+};
+static void s3c_ac97_cold_reset(struct snd_ac97 *ac97) +{
- writel(S3C_AC97_GLBCTRL_COLDRESET,
- s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
+}
+static void s3c_ac97_warm_reset(struct snd_ac97 *ac97) +{
- writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
+}
It would be nice t osee something to convert a 'struct snd_ac97' to a 'struct s3c_ac97_info' as this is being passed in to most place and would also help if there is ever >1 block.
ok, will find a way to do that.
+static void s3c_ac97_activate(struct snd_ac97 *ac97) +{
- u32 ac_glbctrl, stat;
- stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
- switch (stat) {
- case S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE:
- return;
- case S3C_AC97_GLBSTAT_MAINSTATE_READY:
- case S3C_AC97_GLBSTAT_MAINSTATE_INIT:
- break;
- default:
- s3c_ac97_cold_reset(ac97);
- s3c_ac97_warm_reset(ac97);
- break;
- }
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to activate!");
+}
+static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
- unsigned short reg)
+{
- u32 ac_glbctrl, ac_codec_cmd;
- u32 stat, addr, data;
- mutex_lock(&s3c_ac97.lock);
- s3c_ac97_activate(ac97);
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- udelay(50);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to read!");
- stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
- addr = (stat >> 16) & 0x7f;
- data = (stat & 0xffff);
- if (addr != reg)
- printk(KERN_ERR "s3c-ac97: req addr = %02x,"
- " rep addr = %02x\n", reg, addr);
- mutex_unlock(&s3c_ac97.lock);
- return (unsigned short)data;
+}
+static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
- unsigned short val)
+{
- u32 ac_glbctrl, ac_codec_cmd;
- mutex_lock(&s3c_ac97.lock);
- s3c_ac97_activate(ac97);
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- udelay(50);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to write!");
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- mutex_unlock(&s3c_ac97.lock);
+}
+static irqreturn_t s3c_ac97_irq(int irq, void *dev_id) +{
- u32 ac_glbctrl, ac_glbstat;
- ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
- if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= (1<<30); /* Clear interrupt */
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- complete(&s3c_ac97.done);
- }
- return IRQ_HANDLED;
+}
+struct snd_ac97_bus_ops soc_ac97_ops = {
- .read = s3c_ac97_read,
- .write = s3c_ac97_write,
- .warm_reset = s3c_ac97_warm_reset,
- .reset = s3c_ac97_cold_reset,
+}; +EXPORT_SYMBOL_GPL(soc_ac97_ops);
+static int s3c_ac97_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;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- cpu_dai->dma_data = &s3c_ac97_pcm_out;
- else
- cpu_dai->dma_data = &s3c_ac97_pcm_in;
- return 0;
+}
+static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
- struct snd_soc_dai *dai)
+{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
- rtd->dai->cpu_dai->dma_data)->channel;
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
- else
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
- else
- ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- break;
- }
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
- return 0;
+}
+static int s3c_ac97_hw_mic_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;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- return -ENODEV;
- else
- cpu_dai->dma_data = &s3c_ac97_mic_in;
- return 0;
+}
+static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
- int cmd, struct snd_soc_dai *dai)
+{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
- rtd->dai->cpu_dai->dma_data)->channel;
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- break;
- }
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
- return 0;
+}
+#define S3C_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
- SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
- SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
- SNDRV_PCM_RATE_48000)
+static struct snd_soc_dai_ops s3c_ac97_dai_ops = {
- .hw_params = s3c_ac97_hw_params,
- .trigger = s3c_ac97_trigger,
+};
+static struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
- .hw_params = s3c_ac97_hw_mic_params,
- .trigger = s3c_ac97_mic_trigger,
+};
+struct snd_soc_dai s3c_ac97_dai[] = {
- [S3C_AC97_DAI_PCM] = {
- .name = "s3c-ac97",
- .id = S3C_AC97_DAI_PCM,
- .ac97_control = 1,
- .playback = {
- .stream_name = "AC97 Playback",
- .channels_min = 2,
- .channels_max = 2,
- .rates = S3C_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .capture = {
- .stream_name = "AC97 Capture",
- /* NOTE: If the codec ouputs just one slot,
- * it *seems* our AC97 controller reads the only
- * valid slot(if either 3 or 4) for PCM-In.
- * For such cases, we record Mono.
- */
- .channels_min = 1,
- .channels_max = 2,
- .rates = S3C_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = &s3c_ac97_dai_ops,
- },
- [S3C_AC97_DAI_MIC] = {
- .name = "s3c-ac97-mic",
- .id = S3C_AC97_DAI_MIC,
- .ac97_control = 1,
- .capture = {
- .stream_name = "AC97 Mic Capture",
- .channels_min = 1,
- /* NOTE: If the codec(like WM9713) can't ouput just
- * one slot, it *seems* our AC97 controller reads
- * two slots(if one of them is Slot-6) for MIC also.
- * For such cases, we record Stereo.
- */
- .channels_max = 2,
- .rates = S3C_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = &s3c_ac97_mic_dai_ops,
- },
+}; +EXPORT_SYMBOL_GPL(s3c_ac97_dai);
+static __devinit int s3c_ac97_probe(struct platform_device *pdev) +{
- struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
- struct s3c_audio_pdata *ac97_pdata;
- int ret;
- ac97_pdata = pdev->dev.platform_data;
- if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
- dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
- return -EINVAL;
- }
- /* Check for availability of necessary resource */
- dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
- if (!dmatx_res) {
- dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
- return -ENXIO;
- }
- dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
- if (!dmarx_res) {
- dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
- return -ENXIO;
- }
- dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
- if (!dmamic_res) {
- dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
- return -ENXIO;
- }
- mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!mem_res) {
- dev_err(&pdev->dev, "Unable to get register resource\n");
- return -ENXIO;
- }
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (!irq_res) {
- dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
- return -ENXIO;
- }
- if (!request_mem_region(mem_res->start,
- resource_size(mem_res), "s3c-ac97")) {
- dev_err(&pdev->dev, "Unable to request register region\n");
- return -EBUSY;
- }
- s3c_ac97_pcm_out.channel = dmatx_res->start;
- s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
- s3c_ac97_pcm_in.channel = dmarx_res->start;
- s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
- s3c_ac97_mic_in.channel = dmamic_res->start;
- s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
- init_completion(&s3c_ac97.done);
- mutex_init(&s3c_ac97.lock);
- s3c_ac97.regs = ioremap(mem_res->start, resource_size(mem_res));
- if (s3c_ac97.regs == NULL) {
- dev_err(&pdev->dev, "Unable to ioremap register region\n");
- ret = -ENXIO;
- goto lb1;
- }
- s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
- if (IS_ERR(s3c_ac97.ac97_clk)) {
- dev_err(&pdev->dev, "s3c-ac97 failed to get ac97_clock\n");
- ret = -ENODEV;
- goto lb2;
- }
- clk_enable(s3c_ac97.ac97_clk);
- if (ac97_pdata->cfg_gpio(pdev)) {
- dev_err(&pdev->dev, "Unable to configure gpio\n");
- ret = -EINVAL;
- goto lb3;
- }
- ret = request_irq(irq_res->start, s3c_ac97_irq,
- IRQF_DISABLED, "AC97", NULL);
- if (ret < 0) {
- printk(KERN_ERR "s3c-ac97: interrupt request failed.\n");
- goto lb4;
- }
- s3c_ac97_dai[S3C_AC97_DAI_PCM].dev = &pdev->dev;
- s3c_ac97_dai[S3C_AC97_DAI_MIC].dev = &pdev->dev;
- ret = snd_soc_register_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
- if (ret)
- goto lb5;
- return 0;
+lb5:
- free_irq(irq_res->start, NULL);
+lb4: +lb3:
- clk_disable(s3c_ac97.ac97_clk);
- clk_put(s3c_ac97.ac97_clk);
+lb2:
- iounmap(s3c_ac97.regs);
+lb1:
- release_mem_region(mem_res->start, resource_size(mem_res));
- return ret;
+}
+static __devexit int s3c_ac97_remove(struct platform_device *pdev) +{
- struct resource *mem_res, *irq_res;
- snd_soc_unregister_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (irq_res)
- free_irq(irq_res->start, NULL);
- clk_disable(s3c_ac97.ac97_clk);
- clk_put(s3c_ac97.ac97_clk);
- iounmap(s3c_ac97.regs);
- mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (mem_res)
- release_mem_region(mem_res->start, resource_size(mem_res));
- return 0;
+}
+static struct platform_driver s3c_ac97_driver = {
- .probe = s3c_ac97_probe,
- .remove = s3c_ac97_remove,
- .driver = {
- .name = "s3c-ac97",
- .owner = THIS_MODULE,
- },
+};
+static int __init s3c_ac97_init(void) +{
- return platform_driver_register(&s3c_ac97_driver);
+} +module_init(s3c_ac97_init);
+static void __exit s3c_ac97_exit(void) +{
- platform_driver_unregister(&s3c_ac97_driver);
+} +module_exit(s3c_ac97_exit);
+MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com"); +MODULE_DESCRIPTION("AC97 driver for the Samsung SoC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c-ac97.h b/sound/soc/s3c24xx/s3c-ac97.h new file mode 100644 index 0000000..2781983 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.h @@ -0,0 +1,23 @@ +/* sound/soc/s3c24xx/s3c-ac97.h
- ALSA SoC Audio Layer - S3C AC97 Controller driver
- Evolved from s3c2443-ac97.h
- Copyright (c) 2010 Samsung Electronics Co. Ltd
- Author: Jaswinder Singh jassi.brar@samsung.com
- Credits: Graeme Gregory, Sean Choi
- 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 __S3C_AC97_H_ +#define __S3C_AC97_H_
+#define S3C_AC97_DAI_PCM 0 +#define S3C_AC97_DAI_MIC 1
+extern struct snd_soc_dai s3c_ac97_dai[];
+#endif /* __S3C_AC97_H_ */
1.6.2.5
-- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Out of interest, how similar are the two blocks and could the s3c2443 case be elided into this one with some minimal checks for which one is currently in use?
Same except for a few 'dead' bits in 2443. The only reason I didn't submit patch to delete s3c2443-ac97.c is that my smdk2443 didn't produce any sound. (btw the s3c2443-ac97.c also fails to do so)
On Tue, Jan 26, 2010 at 08:03:09PM +0900, jassi brar wrote:
The only reason I didn't submit patch to delete s3c2443-ac97.c is that my smdk2443 didn't produce any sound. (btw the s3c2443-ac97.c also fails to do so)
If they both work equally well please just go ahead and remove the old driver. Keeping the old driver around if it doesn't work is likely to result in any fixups going into that.
On Tue, Jan 26, 2010 at 8:09 PM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Jan 26, 2010 at 08:03:09PM +0900, jassi brar wrote:
The only reason I didn't submit patch to delete s3c2443-ac97.c is that my smdk2443 didn't produce any sound. (btw the s3c2443-ac97.c also fails to do so)
If they both work equally well please just go ahead and remove the old driver. Keeping the old driver around if it doesn't work is likely to result in any fixups going into that.
ok, will submit a patch for that too. Btw, I don't have ln2440sbc and don't know how would it take the change, though I sincerely think it should be just fine.
On Tue, Jan 26, 2010 at 08:57:56PM +0900, jassi brar wrote:
On Tue, Jan 26, 2010 at 8:09 PM, Mark Brown
If they both work equally well please just go ahead and remove the old driver. Keeping the old driver around if it doesn't work is likely to result in any fixups going into that.
ok, will submit a patch for that too. Btw, I don't have ln2440sbc and don't know how would it take the change, though I sincerely think it should be just fine.
CC the original author on the patch updating it, if they don't reply then a build test is fine - I'd also expect it'll work fine (though thinking about it you'll need to add the platform data for the GPIO configuration to the 2443 instance of the struct device, I'm not sure if that was there or not?).
On Tue, Jan 26, 2010 at 8:03 PM, jassi brar jassisinghbrar@gmail.com wrote:
On Tue, Jan 26, 2010 at 7:23 PM, Ben Dooks ben-linux@fluff.org wrote:
On Tue, Jan 26, 2010 at 02:51:40PM +0900, jassisinghbrar@gmail.com wrote:
From: Jassi Brar jassi.brar@samsung.com
Add the AC97 controller driver for Samsung SoCs that have one.
Signed-off-by: Jassi Brar jassi.brar@samsung.com
sound/soc/s3c24xx/Kconfig | 6 +- sound/soc/s3c24xx/Makefile | 3 +- sound/soc/s3c24xx/s3c-ac97.c | 535 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/s3c24xx/s3c-ac97.h | 23 ++ 4 files changed, 565 insertions(+), 2 deletions(-) create mode 100644 sound/soc/s3c24xx/s3c-ac97.c create mode 100644 sound/soc/s3c24xx/s3c-ac97.h
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index b489f1a..ad3690e 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -32,7 +32,11 @@ config SND_S3C2443_SOC_AC97 select S3C2410_DMA select AC97_BUS select SND_SOC_AC97_BUS
+config SND_S3C_SOC_AC97
- tristate
- select SND_SOC_AC97_BUS
config SND_S3C24XX_SOC_NEO1973_WM8753 tristate "SoC I2S Audio support for NEO1973 - WM8753" depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA01 diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index b744657..b7411bd 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -4,12 +4,14 @@ snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o +snd-soc-s3c-ac97-objs := s3c-ac97.o snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o snd-soc-s3c-pcm-objs := s3c-pcm.o
obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o +obj-$(CONFIG_SND_S3C_SOC_AC97) += snd-soc-s3c-ac97.o obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o @@ -37,4 +39,3 @@ obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o
diff --git a/sound/soc/s3c24xx/s3c-ac97.c b/sound/soc/s3c24xx/s3c-ac97.c new file mode 100644 index 0000000..acb8f51 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.c @@ -0,0 +1,535 @@ +/* sound/soc/s3c24xx/s3c-ac97.c
- ALSA SoC Audio Layer - S3C AC97 Controller driver
- Evolved from s3c2443-ac97.c
- Copyright (c) 2010 Samsung Electronics Co. Ltd
- Author: Jaswinder Singh jassi.brar@samsung.com
- Credits: Graeme Gregory, Sean Choi
- 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/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/clk.h>
+#include <sound/soc.h>
+#include <plat/regs-ac97.h> +#include <mach/dma.h> +#include <plat/audio.h>
+#include "s3c-dma.h" +#include "s3c-ac97.h"
+#define AC_CMD_ADDR(x) (x << 16) +#define AC_CMD_DATA(x) (x & 0xffff)
+struct s3c_ac97_info {
- unsigned state;
- struct clk *ac97_clk;
- void __iomem *regs;
- struct mutex lock;
- struct completion done;
+}; +static struct s3c_ac97_info s3c_ac97;
+static struct s3c2410_dma_client s3c_dma_client_out = {
- .name = "AC97 PCMOut"
+};
+static struct s3c2410_dma_client s3c_dma_client_in = {
- .name = "AC97 PCMIn"
+};
+static struct s3c2410_dma_client s3c_dma_client_micin = {
- .name = "AC97 MicIn"
+};
+static struct s3c_dma_params s3c_ac97_pcm_out = {
- .client = &s3c_dma_client_out,
- .dma_size = 4,
+};
+static struct s3c_dma_params s3c_ac97_pcm_in = {
- .client = &s3c_dma_client_in,
- .dma_size = 4,
+};
+static struct s3c_dma_params s3c_ac97_mic_in = {
- .client = &s3c_dma_client_micin,
- .dma_size = 4,
+};
+static void s3c_ac97_cold_reset(struct snd_ac97 *ac97) +{
- writel(S3C_AC97_GLBCTRL_COLDRESET,
- s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
+}
+static void s3c_ac97_warm_reset(struct snd_ac97 *ac97) +{
- writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
+}
It would be nice t osee something to convert a 'struct snd_ac97' to a 'struct s3c_ac97_info' as this is being passed in to most place and would also help if there is ever >1 block.
ok, will find a way to do that.
after looking, I found there is no convenient way to extract driver private data from the snd_ac97 structure. Its possible, but after ugly voodoo stuff. Not to forget that no other ASoC ac97 controller driver does it either. And since we have just 1 AC97 controller in SoCs till date(no plans of adding another anytime soon) I think we can live with it?
+static void s3c_ac97_activate(struct snd_ac97 *ac97) +{
- u32 ac_glbctrl, stat;
- stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
- switch (stat) {
- case S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE:
- return;
- case S3C_AC97_GLBSTAT_MAINSTATE_READY:
- case S3C_AC97_GLBSTAT_MAINSTATE_INIT:
- break;
- default:
- s3c_ac97_cold_reset(ac97);
- s3c_ac97_warm_reset(ac97);
- break;
- }
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to activate!");
+}
+static unsigned short s3c_ac97_read(struct snd_ac97 *ac97,
- unsigned short reg)
+{
- u32 ac_glbctrl, ac_codec_cmd;
- u32 stat, addr, data;
- mutex_lock(&s3c_ac97.lock);
- s3c_ac97_activate(ac97);
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR(reg);
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- udelay(50);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to read!");
- stat = readl(s3c_ac97.regs + S3C_AC97_STAT);
- addr = (stat >> 16) & 0x7f;
- data = (stat & 0xffff);
- if (addr != reg)
- printk(KERN_ERR "s3c-ac97: req addr = %02x,"
- " rep addr = %02x\n", reg, addr);
- mutex_unlock(&s3c_ac97.lock);
- return (unsigned short)data;
+}
+static void s3c_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
- unsigned short val)
+{
- u32 ac_glbctrl, ac_codec_cmd;
- mutex_lock(&s3c_ac97.lock);
- s3c_ac97_activate(ac97);
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd = AC_CMD_ADDR(reg) | AC_CMD_DATA(val);
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- udelay(50);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to write!");
- ac_codec_cmd = readl(s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ;
- writel(ac_codec_cmd, s3c_ac97.regs + S3C_AC97_CODEC_CMD);
- mutex_unlock(&s3c_ac97.lock);
+}
+static irqreturn_t s3c_ac97_irq(int irq, void *dev_id) +{
- u32 ac_glbctrl, ac_glbstat;
- ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
- if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= (1<<30); /* Clear interrupt */
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- complete(&s3c_ac97.done);
- }
- return IRQ_HANDLED;
+}
+struct snd_ac97_bus_ops soc_ac97_ops = {
- .read = s3c_ac97_read,
- .write = s3c_ac97_write,
- .warm_reset = s3c_ac97_warm_reset,
- .reset = s3c_ac97_cold_reset,
+}; +EXPORT_SYMBOL_GPL(soc_ac97_ops);
+static int s3c_ac97_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;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- cpu_dai->dma_data = &s3c_ac97_pcm_out;
- else
- cpu_dai->dma_data = &s3c_ac97_pcm_in;
- return 0;
+}
+static int s3c_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
- struct snd_soc_dai *dai)
+{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
- rtd->dai->cpu_dai->dma_data)->channel;
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMINTM_MASK;
- else
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_PCMOUTTM_MASK;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
- ac_glbctrl |= S3C_AC97_GLBCTRL_PCMINTM_DMA;
- else
- ac_glbctrl |= S3C_AC97_GLBCTRL_PCMOUTTM_DMA;
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- break;
- }
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
- return 0;
+}
+static int s3c_ac97_hw_mic_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;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- return -ENODEV;
- else
- cpu_dai->dma_data = &s3c_ac97_mic_in;
- return 0;
+}
+static int s3c_ac97_mic_trigger(struct snd_pcm_substream *substream,
- int cmd, struct snd_soc_dai *dai)
+{
- u32 ac_glbctrl;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- int channel = ((struct s3c_dma_params *)
- rtd->dai->cpu_dai->dma_data)->channel;
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_MICINTM_MASK;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- ac_glbctrl |= S3C_AC97_GLBCTRL_MICINTM_DMA;
- break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- break;
- }
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
- return 0;
+}
+#define S3C_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
- SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
- SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
- SNDRV_PCM_RATE_48000)
+static struct snd_soc_dai_ops s3c_ac97_dai_ops = {
- .hw_params = s3c_ac97_hw_params,
- .trigger = s3c_ac97_trigger,
+};
+static struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
- .hw_params = s3c_ac97_hw_mic_params,
- .trigger = s3c_ac97_mic_trigger,
+};
+struct snd_soc_dai s3c_ac97_dai[] = {
- [S3C_AC97_DAI_PCM] = {
- .name = "s3c-ac97",
- .id = S3C_AC97_DAI_PCM,
- .ac97_control = 1,
- .playback = {
- .stream_name = "AC97 Playback",
- .channels_min = 2,
- .channels_max = 2,
- .rates = S3C_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .capture = {
- .stream_name = "AC97 Capture",
- /* NOTE: If the codec ouputs just one slot,
- * it *seems* our AC97 controller reads the only
- * valid slot(if either 3 or 4) for PCM-In.
- * For such cases, we record Mono.
- */
- .channels_min = 1,
- .channels_max = 2,
- .rates = S3C_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = &s3c_ac97_dai_ops,
- },
- [S3C_AC97_DAI_MIC] = {
- .name = "s3c-ac97-mic",
- .id = S3C_AC97_DAI_MIC,
- .ac97_control = 1,
- .capture = {
- .stream_name = "AC97 Mic Capture",
- .channels_min = 1,
- /* NOTE: If the codec(like WM9713) can't ouput just
- * one slot, it *seems* our AC97 controller reads
- * two slots(if one of them is Slot-6) for MIC also.
- * For such cases, we record Stereo.
- */
- .channels_max = 2,
- .rates = S3C_AC97_RATES,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = &s3c_ac97_mic_dai_ops,
- },
+}; +EXPORT_SYMBOL_GPL(s3c_ac97_dai);
+static __devinit int s3c_ac97_probe(struct platform_device *pdev) +{
- struct resource *mem_res, *dmatx_res, *dmarx_res, *dmamic_res, *irq_res;
- struct s3c_audio_pdata *ac97_pdata;
- int ret;
- ac97_pdata = pdev->dev.platform_data;
- if (!ac97_pdata || !ac97_pdata->cfg_gpio) {
- dev_err(&pdev->dev, "cfg_gpio callback not provided!\n");
- return -EINVAL;
- }
- /* Check for availability of necessary resource */
- dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
- if (!dmatx_res) {
- dev_err(&pdev->dev, "Unable to get AC97-TX dma resource\n");
- return -ENXIO;
- }
- dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
- if (!dmarx_res) {
- dev_err(&pdev->dev, "Unable to get AC97-RX dma resource\n");
- return -ENXIO;
- }
- dmamic_res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
- if (!dmamic_res) {
- dev_err(&pdev->dev, "Unable to get AC97-MIC dma resource\n");
- return -ENXIO;
- }
- mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!mem_res) {
- dev_err(&pdev->dev, "Unable to get register resource\n");
- return -ENXIO;
- }
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (!irq_res) {
- dev_err(&pdev->dev, "AC97 IRQ not provided!\n");
- return -ENXIO;
- }
- if (!request_mem_region(mem_res->start,
- resource_size(mem_res), "s3c-ac97")) {
- dev_err(&pdev->dev, "Unable to request register region\n");
- return -EBUSY;
- }
- s3c_ac97_pcm_out.channel = dmatx_res->start;
- s3c_ac97_pcm_out.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
- s3c_ac97_pcm_in.channel = dmarx_res->start;
- s3c_ac97_pcm_in.dma_addr = mem_res->start + S3C_AC97_PCM_DATA;
- s3c_ac97_mic_in.channel = dmamic_res->start;
- s3c_ac97_mic_in.dma_addr = mem_res->start + S3C_AC97_MIC_DATA;
- init_completion(&s3c_ac97.done);
- mutex_init(&s3c_ac97.lock);
- s3c_ac97.regs = ioremap(mem_res->start, resource_size(mem_res));
- if (s3c_ac97.regs == NULL) {
- dev_err(&pdev->dev, "Unable to ioremap register region\n");
- ret = -ENXIO;
- goto lb1;
- }
- s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
- if (IS_ERR(s3c_ac97.ac97_clk)) {
- dev_err(&pdev->dev, "s3c-ac97 failed to get ac97_clock\n");
- ret = -ENODEV;
- goto lb2;
- }
- clk_enable(s3c_ac97.ac97_clk);
- if (ac97_pdata->cfg_gpio(pdev)) {
- dev_err(&pdev->dev, "Unable to configure gpio\n");
- ret = -EINVAL;
- goto lb3;
- }
- ret = request_irq(irq_res->start, s3c_ac97_irq,
- IRQF_DISABLED, "AC97", NULL);
- if (ret < 0) {
- printk(KERN_ERR "s3c-ac97: interrupt request failed.\n");
- goto lb4;
- }
- s3c_ac97_dai[S3C_AC97_DAI_PCM].dev = &pdev->dev;
- s3c_ac97_dai[S3C_AC97_DAI_MIC].dev = &pdev->dev;
- ret = snd_soc_register_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
- if (ret)
- goto lb5;
- return 0;
+lb5:
- free_irq(irq_res->start, NULL);
+lb4: +lb3:
- clk_disable(s3c_ac97.ac97_clk);
- clk_put(s3c_ac97.ac97_clk);
+lb2:
- iounmap(s3c_ac97.regs);
+lb1:
- release_mem_region(mem_res->start, resource_size(mem_res));
- return ret;
+}
+static __devexit int s3c_ac97_remove(struct platform_device *pdev) +{
- struct resource *mem_res, *irq_res;
- snd_soc_unregister_dais(s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (irq_res)
- free_irq(irq_res->start, NULL);
- clk_disable(s3c_ac97.ac97_clk);
- clk_put(s3c_ac97.ac97_clk);
- iounmap(s3c_ac97.regs);
- mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (mem_res)
- release_mem_region(mem_res->start, resource_size(mem_res));
- return 0;
+}
+static struct platform_driver s3c_ac97_driver = {
- .probe = s3c_ac97_probe,
- .remove = s3c_ac97_remove,
- .driver = {
- .name = "s3c-ac97",
- .owner = THIS_MODULE,
- },
+};
+static int __init s3c_ac97_init(void) +{
- return platform_driver_register(&s3c_ac97_driver);
+} +module_init(s3c_ac97_init);
+static void __exit s3c_ac97_exit(void) +{
- platform_driver_unregister(&s3c_ac97_driver);
+} +module_exit(s3c_ac97_exit);
+MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com"); +MODULE_DESCRIPTION("AC97 driver for the Samsung SoC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c-ac97.h b/sound/soc/s3c24xx/s3c-ac97.h new file mode 100644 index 0000000..2781983 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-ac97.h @@ -0,0 +1,23 @@ +/* sound/soc/s3c24xx/s3c-ac97.h
- ALSA SoC Audio Layer - S3C AC97 Controller driver
- Evolved from s3c2443-ac97.h
- Copyright (c) 2010 Samsung Electronics Co. Ltd
- Author: Jaswinder Singh jassi.brar@samsung.com
- Credits: Graeme Gregory, Sean Choi
- 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 __S3C_AC97_H_ +#define __S3C_AC97_H_
+#define S3C_AC97_DAI_PCM 0 +#define S3C_AC97_DAI_MIC 1
+extern struct snd_soc_dai s3c_ac97_dai[];
+#endif /* __S3C_AC97_H_ */
1.6.2.5
-- To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Out of interest, how similar are the two blocks and could the s3c2443 case be elided into this one with some minimal checks for which one is currently in use?
Same except for a few 'dead' bits in 2443. The only reason I didn't submit patch to delete s3c2443-ac97.c is that my smdk2443 didn't produce any sound. (btw the s3c2443-ac97.c also fails to do so)
On Wed, Jan 27, 2010 at 11:45:24AM +0900, jassi brar wrote:
On Tue, Jan 26, 2010 at 8:03 PM, jassi brar jassisinghbrar@gmail.com wrote:
It would be nice t osee something to convert a 'struct snd_ac97' to a 'struct s3c_ac97_info' as this is being passed in to most place and would also help if there is ever >1 block.
ok, will find a way to do that.
after looking, I found there is no convenient way to extract driver private data from the snd_ac97 structure. Its possible, but after ugly voodoo stuff. Not to forget that no other ASoC ac97 controller driver does it either. And since we have just 1 AC97 controller in SoCs till date(no plans of adding another anytime soon) I think we can live with it?
I think this is fine. It's vanishingly unlikely that we'll see CPUs with multiple AC97 controllers and there are plenty of places in the core which assume that there will only be a single AC97 bus in the system.
Please also let me renew my reminder to cut irrelevant text from replies, it really does help with finding the new text in the mail and is especially helpful to those of us who read some of our mail on mobile phones.
On Tue, Jan 26, 2010 at 02:51:40PM +0900, jassisinghbrar@gmail.com wrote:
This looks good overall, just a few smallish issues:
+static void s3c_ac97_activate(struct snd_ac97 *ac97) +{
- u32 ac_glbctrl, stat;
- stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
- switch (stat) {
- case S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE:
return;
- case S3C_AC97_GLBSTAT_MAINSTATE_READY:
- case S3C_AC97_GLBSTAT_MAINSTATE_INIT:
break;
- default:
s3c_ac97_cold_reset(ac97);
s3c_ac97_warm_reset(ac97);
break;
- }
This automatic cold and warm reset looks a bit fishy - obviously if this code path ever gets hit in normal operation then it's going to seriously disrupt things since it'll reset the CODEC registers. A warm reset by itself wouldn't be a problem but I'd rather see explicit cold resets in the callers where that's required.
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
This also looks a bit odd, ACLINKON sounds like bringing up the link which is what a warm reset does.
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
printk(KERN_ERR "AC97: Unable to activate!");
This looks racy - the INIT_COMPLETION() happens after all the hardware configuration which suggests that if you're unlucky the event which should trigger the completion will have happened before the init. A bunch of interrupts arriving at an inconvenient time could trigger this, for example.
The same idiom appears in the register reads and writes.
- if (addr != reg)
printk(KERN_ERR "s3c-ac97: req addr = %02x,"
" rep addr = %02x\n", reg, addr);
Please don't split error messages over multiple lines, it makes grepping for them harder.
+static irqreturn_t s3c_ac97_irq(int irq, void *dev_id) +{
- u32 ac_glbctrl, ac_glbstat;
- ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
- if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
ac_glbctrl |= (1<<30); /* Clear interrupt */
writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
complete(&s3c_ac97.done);
- }
- return IRQ_HANDLED;
+}
You should only be returning IRQ_HANDLED if you actually handled an interrupt here.
+#define S3C_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000)
SNDRV_PCM_RATE_8000_48000.
.capture = {
.stream_name = "AC97 Capture",
/* NOTE: If the codec ouputs just one slot,
* it *seems* our AC97 controller reads the only
* valid slot(if either 3 or 4) for PCM-In.
* For such cases, we record Mono.
*/
This seems like unsurprising behaviour for an AC97 controller - the slot validity information is there for just this purpose. I'd just remove the comment as a result.
.capture = {
.stream_name = "AC97 Mic Capture",
.channels_min = 1,
/* NOTE: If the codec(like WM9713) can't ouput just
* one slot, it *seems* our AC97 controller reads
* two slots(if one of them is Slot-6) for MIC also.
* For such cases, we record Stereo.
*/
Similarly here.
- if (ac97_pdata->cfg_gpio(pdev)) {
dev_err(&pdev->dev, "Unable to configure gpio\n");
ret = -EINVAL;
goto lb3;
- }
Should really check for the function being non-NULL here.
+lb5:
- free_irq(irq_res->start, NULL);
Perhaps a better name than 'lb' - err, or fail or something. lb means nothing to me at least.
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (irq_res)
free_irq(irq_res->start, NULL);
This should never get called if the resources aren't allocated.
On Tue, Jan 26, 2010 at 7:47 PM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Jan 26, 2010 at 02:51:40PM +0900, jassisinghbrar@gmail.com wrote:
This looks good overall, just a few smallish issues:
+static void s3c_ac97_activate(struct snd_ac97 *ac97) +{
- u32 ac_glbctrl, stat;
- stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7;
- switch (stat) {
- case S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE:
- return;
- case S3C_AC97_GLBSTAT_MAINSTATE_READY:
- case S3C_AC97_GLBSTAT_MAINSTATE_INIT:
- break;
- default:
- s3c_ac97_cold_reset(ac97);
- s3c_ac97_warm_reset(ac97);
- break;
- }
This automatic cold and warm reset looks a bit fishy - obviously if this code path ever gets hit in normal operation then it's going to seriously disrupt things since it'll reset the CODEC registers. A warm reset by itself wouldn't be a problem but I'd rather see explicit cold resets in the callers where that's required.
Before read/write we need to ensure the link is active. And to reach the active state we have to do that as suggested by the FSM shown in SoCs' Manual. Also, this helps not relying on codec/core to perform particular steps of initializations.
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
This also looks a bit odd, ACLINKON sounds like bringing up the link which is what a warm reset does.
As shown in FSM of SoCs manual, this sets the controller state to READY. Please have a look at any manual's AC97 chapter.
- INIT_COMPLETION(s3c_ac97.done);
- if (!wait_for_completion_timeout(&s3c_ac97.done, HZ))
- printk(KERN_ERR "AC97: Unable to activate!");
This looks racy - the INIT_COMPLETION() happens after all the hardware configuration which suggests that if you're unlucky the event which should trigger the completion will have happened before the init. A bunch of interrupts arriving at an inconvenient time could trigger this, for example.
Yes, init needs to be moved early.
The same idiom appears in the register reads and writes.
- if (addr != reg)
- printk(KERN_ERR "s3c-ac97: req addr = %02x,"
- " rep addr = %02x\n", reg, addr);
Please don't split error messages over multiple lines, it makes grepping for them harder.
ok.
+static irqreturn_t s3c_ac97_irq(int irq, void *dev_id) +{
- u32 ac_glbctrl, ac_glbstat;
- ac_glbstat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT);
- if (ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY) {
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl &= ~S3C_AC97_GLBCTRL_CODECREADYIE;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL);
- ac_glbctrl |= (1<<30); /* Clear interrupt */
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- complete(&s3c_ac97.done);
- }
- return IRQ_HANDLED;
+}
You should only be returning IRQ_HANDLED if you actually handled an interrupt here.
I'd rather move the intr-clearing out of the block.
+#define S3C_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
- SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
- SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
- SNDRV_PCM_RATE_48000)
SNDRV_PCM_RATE_8000_48000.
yes.
- .capture = {
- .stream_name = "AC97 Capture",
- /* NOTE: If the codec ouputs just one slot,
- * it *seems* our AC97 controller reads the only
- * valid slot(if either 3 or 4) for PCM-In.
- * For such cases, we record Mono.
- */
This seems like unsurprising behaviour for an AC97 controller - the slot validity information is there for just this purpose. I'd just remove the comment as a result.
- .capture = {
- .stream_name = "AC97 Mic Capture",
- .channels_min = 1,
- /* NOTE: If the codec(like WM9713) can't ouput just
- * one slot, it *seems* our AC97 controller reads
- * two slots(if one of them is Slot-6) for MIC also.
- * For such cases, we record Stereo.
- */
Similarly here.
- if (ac97_pdata->cfg_gpio(pdev)) {
- dev_err(&pdev->dev, "Unable to configure gpio\n");
- ret = -EINVAL;
- goto lb3;
- }
Should really check for the function being non-NULL here.
already checked at the start and probe fails if its NULL
+lb5:
- free_irq(irq_res->start, NULL);
Perhaps a better name than 'lb' - err, or fail or something. lb means nothing to me at least.
means label to me :)
- irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
- if (irq_res)
- free_irq(irq_res->start, NULL);
This should never get called if the resources aren't allocated.
but the static code analyser doesn't understand that.
On Tue, Jan 26, 2010 at 08:17:25PM +0900, jassi brar wrote:
On Tue, Jan 26, 2010 at 7:47 PM, Mark Brown
- default:
- s3c_ac97_cold_reset(ac97);
- s3c_ac97_warm_reset(ac97);
- break;
- }
This automatic cold and warm reset looks a bit fishy - obviously if this code path ever gets hit in normal operation then it's going to seriously disrupt things since it'll reset the CODEC registers. A warm reset by itself wouldn't be a problem but I'd rather see explicit cold resets in the callers where that's required.
Before read/write we need to ensure the link is active. And to reach the active state we have to do that as suggested by the FSM shown in SoCs' Manual.
That's not addressing the problem, though - the big issue is not bringing up the AC97 link, it's the fact that you're doing an uncontrolled cold reset. If we hit this code path it'll revert the device registers to default which will upset the drivers for the CODEC rather badly. There should be no possibility of that happening, if it is happening it's something that callers really should know about.
Also, this helps not relying on codec/core to perform particular steps of initializations.
Equally well if the controller driver diverges from what other drivers do it's going to lead to interoperability skew for drivers.
For the warm reset function I'd suggest adding something which checks to see if the link is already active and suppresses the warm reset in that case. That will avoid slowing everything down with spurious warm resets when the link is already runnning and other drivers assume they need to bring it up - with the way things are structured at present many warm resets requested by other drivers are likely to be spurious.
Ideally the core would keep track of this and transparently trigger the warm reset when required, but driver local is fine since that's not there yet.
- ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
This also looks a bit odd, ACLINKON sounds like bringing up the link which is what a warm reset does.
As shown in FSM of SoCs manual, this sets the controller state to READY. Please have a look at any manual's AC97 chapter.
Right, but why is this being done by this function and not, for example, by the warm reset? It's the structure of the code I'm commenting on rather than the operations that get performed on the hardware.
+lb5:
- free_irq(irq_res->start, NULL);
Perhaps a better name than 'lb' - err, or fail or something. lb means nothing to me at least.
means label to me :)
If you'd spelt out label I'd probably have been OK, but in English lb is normally only an abbreviation for pounds as a unit of weight.
On Tue, Jan 26, 2010 at 8:52 PM, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Tue, Jan 26, 2010 at 08:17:25PM +0900, jassi brar wrote:
On Tue, Jan 26, 2010 at 7:47 PM, Mark Brown
- default:
- s3c_ac97_cold_reset(ac97);
- s3c_ac97_warm_reset(ac97);
- break;
- }
This automatic cold and warm reset looks a bit fishy - obviously if this code path ever gets hit in normal operation then it's going to seriously disrupt things since it'll reset the CODEC registers. A warm reset by itself wouldn't be a problem but I'd rather see explicit cold resets in the callers where that's required.
Before read/write we need to ensure the link is active. And to reach the active state we have to do that as suggested by the FSM shown in SoCs' Manual.
That's not addressing the problem, though - the big issue is not bringing up the AC97 link, it's the fact that you're doing an uncontrolled cold reset. If we hit this code path it'll revert the device registers to default which will upset the drivers for the CODEC rather badly. There should be no possibility of that happening, if it is happening it's something that callers really should know about.
It should never be reached after the link is up and running. Only if something goes wrong at runtime, would the controller state be changed. And this is an attempt to recover from that failure. If the upper layer should know of such failure, then maybe we should not do it. I have never seen such runtime error though.
Also, this helps not relying on codec/core to perform particular steps of initializations.
Equally well if the controller driver diverges from what other drivers do it's going to lead to interoperability skew for drivers.
For the warm reset function I'd suggest adding something which checks to see if the link is already active and suppresses the warm reset in that case. That will avoid slowing everything down with spurious warm resets when the link is already runnning and other drivers assume they need to bring it up - with the way things are structured at present many warm resets requested by other drivers are likely to be spurious.
ok, i will add the check in warm reset.
Ideally the core would keep track of this and transparently trigger the warm reset when required, but driver local is fine since that's not there yet.
- ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
This also looks a bit odd, ACLINKON sounds like bringing up the link which is what a warm reset does.
As shown in FSM of SoCs manual, this sets the controller state to READY. Please have a look at any manual's AC97 chapter.
Right, but why is this being done by this function and not, for example, by the warm reset? It's the structure of the code I'm commenting on rather than the operations that get performed on the hardware.
I assume the ALSA definition of cold/warm reset is same as that of the AC97 controller. Anything remaining should be done only when necessary. For ex, the ALSA might want to leave controller after just cold/warm reset ... as the SoC manual says it's in low-power mode without the AC-link on.
+lb5:
- free_irq(irq_res->start, NULL);
Perhaps a better name than 'lb' - err, or fail or something. lb means nothing to me at least.
means label to me :)
If you'd spelt out label I'd probably have been OK, but in English lb is normally only an abbreviation for pounds as a unit of weight.
ok, will rename the labels.
On Tue, Jan 26, 2010 at 09:11:55PM +0900, jassi brar wrote:
On Tue, Jan 26, 2010 at 8:52 PM, Mark Brown
That's not addressing the problem, though - the big issue is not bringing up the AC97 link, it's the fact that you're doing an uncontrolled cold reset. If we hit this code path it'll revert the device registers to default which will upset the drivers for the CODEC rather badly. There should be no possibility of that happening, if it is happening it's something that callers really should know about.
It should never be reached after the link is up and running. Only if something goes wrong at runtime, would the controller state be changed. And this is an attempt to recover from that failure. If the upper layer should know of such failure, then maybe we should not do it. I have never seen such runtime error though.
Sounds like the code should be changed to log and return an error if that case is hit?
- ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON;
- writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL);
- msleep(1);
...
I assume the ALSA definition of cold/warm reset is same as that of the AC97 controller. Anything remaining should be done only when necessary. For ex, the ALSA might want to leave controller after just cold/warm reset ... as the SoC manual says it's in low-power mode without the AC-link on.
A warm reset pretty much means "start the AC-link" - it's what you do to exit low power mode and start clocking the link (which is mastered by the CODEC), it doesn't have any other effect. That's a big part of why this is confusing, I'd expect a warm reset would also do any setup on the controller side that was needed to cope with the clock restarting.
Due to the way AC97 is structured this stuff is all shared between the controller and the CODECs on the bus. It's not particularly pretty from a software point of view at the minute.
On Tue, Jan 26, 2010 at 02:51:39PM +0900, jassisinghbrar@gmail.com wrote:
From: Jassi Brar jassi.brar@samsung.com
This patch defines the platform device and the resources: IRQ, DMA and MEM, needed by the AC97 controller driver.
Signed-off-by: Jassi Brar jassi.brar@samsung.com
Acked-by: Mark Brown broonie@opensource.wolfsonmicro.com
participants (4)
-
Ben Dooks
-
jassi brar
-
jassisinghbrar@gmail.com
-
Mark Brown