diff options
Diffstat (limited to 'sound/soc/samsung')
38 files changed, 10222 insertions, 0 deletions
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig new file mode 100644 index 00000000000..a6a6b5fa2f2 --- /dev/null +++ b/sound/soc/samsung/Kconfig @@ -0,0 +1,171 @@ +config SND_SOC_SAMSUNG + tristate "ASoC support for Samsung" + depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_S5P64X0 || ARCH_S5P6442 || ARCH_S5PV310 + select S3C64XX_DMA if ARCH_S3C64XX + select S3C2410_DMA if ARCH_S3C2410 + help + Say Y or M if you want to add support for codecs attached to + the Samsung SoCs' Audio interfaces. You will also need to + select the audio interfaces to support below. + +config SND_S3C24XX_I2S + tristate + select S3C2410_DMA + +config SND_S3C_I2SV2_SOC + tristate + +config SND_S3C2412_SOC_I2S + tristate + select SND_S3C_I2SV2_SOC + select S3C2410_DMA + +config SND_SAMSUNG_PCM + tristate + +config SND_SAMSUNG_AC97 + tristate + select SND_SOC_AC97_BUS + +config SND_SAMSUNG_SPDIF + tristate + select SND_SOC_SPDIF + +config SND_SAMSUNG_I2S + tristate + +config SND_SOC_SAMSUNG_NEO1973_WM8753 + tristate "SoC I2S Audio support for NEO1973 - WM8753" + depends on SND_SOC_SAMSUNG && MACH_NEO1973_GTA01 + select SND_S3C24XX_I2S + select SND_SOC_WM8753 + help + Say Y if you want to add support for SoC audio on smdk2440 + with the WM8753. + +config SND_SOC_SAMSUNG_NEO1973_GTA02_WM8753 + tristate "Audio support for the Openmoko Neo FreeRunner (GTA02)" + depends on SND_SOC_SAMSUNG && MACH_NEO1973_GTA02 + select SND_S3C24XX_I2S + select SND_SOC_WM8753 + help + This driver provides audio support for the Openmoko Neo FreeRunner + smartphone. + +config SND_SOC_SAMSUNG_JIVE_WM8750 + tristate "SoC I2S Audio support for Jive" + depends on SND_SOC_SAMSUNG && MACH_JIVE + select SND_SOC_WM8750 + select SND_S3C2412_SOC_I2S + help + Sat Y if you want to add support for SoC audio on the Jive. + +config SND_SOC_SAMSUNG_SMDK_WM8580 + tristate "SoC I2S Audio support for WM8580 on SMDK" + depends on SND_SOC_SAMSUNG && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDK6440 || MACH_SMDK6450 || MACH_SMDK6442 || MACH_SMDKV210 || MACH_SMDKC110) + select SND_SOC_WM8580 + select SND_SAMSUNG_I2S + help + Say Y if you want to add support for SoC audio on the SMDKs. + +config SND_SOC_SAMSUNG_SMDK_WM8994 + tristate "SoC I2S Audio support for WM8994 on SMDK" + depends on SND_SOC_SAMSUNG && (MACH_SMDKV310 || MACH_SMDKC210) + select SND_SOC_WM8994 + select SND_SAMSUNG_I2S + help + Say Y if you want to add support for SoC audio on the SMDKs. + +config SND_SOC_SAMSUNG_SMDK2443_WM9710 + tristate "SoC AC97 Audio support for SMDK2443 - WM9710" + depends on SND_SOC_SAMSUNG && MACH_SMDK2443 + select S3C2410_DMA + select AC97_BUS + select SND_SOC_AC97_CODEC + select SND_SAMSUNG_AC97 + help + Say Y if you want to add support for SoC audio on smdk2443 + with the WM9710. + +config SND_SOC_SAMSUNG_LN2440SBC_ALC650 + tristate "SoC AC97 Audio support for LN2440SBC - ALC650" + depends on SND_SOC_SAMSUNG && ARCH_S3C2410 + select S3C2410_DMA + select AC97_BUS + select SND_SOC_AC97_CODEC + select SND_SAMSUNG_AC97 + help + Say Y if you want to add support for SoC audio on ln2440sbc + with the ALC650. + +config SND_SOC_SAMSUNG_S3C24XX_UDA134X + tristate "SoC I2S Audio support UDA134X wired to a S3C24XX" + depends on SND_SOC_SAMSUNG && ARCH_S3C2410 + select SND_S3C24XX_I2S + select SND_SOC_L3 + select SND_SOC_UDA134X + +config SND_SOC_SAMSUNG_SIMTEC + tristate + help + Internal node for common S3C24XX/Simtec suppor + +config SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23 + tristate "SoC I2S Audio support for TLV320AIC23 on Simtec boards" + depends on SND_SOC_SAMSUNG && ARCH_S3C2410 + select SND_S3C24XX_I2S + select SND_SOC_TLV320AIC23 + select SND_SOC_SAMSUNG_SIMTEC + +config SND_SOC_SAMSUNG_SIMTEC_HERMES + tristate "SoC I2S Audio support for Simtec Hermes board" + depends on SND_SOC_SAMSUNG && ARCH_S3C2410 + select SND_S3C24XX_I2S + select SND_SOC_TLV320AIC3X + select SND_SOC_SAMSUNG_SIMTEC + +config SND_SOC_SAMSUNG_H1940_UDA1380 + tristate "Audio support for the HP iPAQ H1940" + depends on SND_SOC_SAMSUNG && ARCH_H1940 + select SND_S3C24XX_I2S + select SND_SOC_UDA1380 + help + This driver provides audio support for HP iPAQ h1940 PDA. + +config SND_SOC_SAMSUNG_RX1950_UDA1380 + tristate "Audio support for the HP iPAQ RX1950" + depends on SND_SOC_SAMSUNG && MACH_RX1950 + select SND_S3C24XX_I2S + select SND_SOC_UDA1380 + help + This driver provides audio support for HP iPAQ RX1950 PDA. + +config SND_SOC_SAMSUNG_SMDK_WM9713 + tristate "SoC AC97 Audio support for SMDK with WM9713" + depends on SND_SOC_SAMSUNG && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110 || MACH_SMDKV310 || MACH_SMDKC210) + select SND_SOC_WM9713 + select SND_SAMSUNG_AC97 + help + Sat Y if you want to add support for SoC audio on the SMDK. + +config SND_SOC_SMARTQ + tristate "SoC I2S Audio support for SmartQ board" + depends on SND_SOC_SAMSUNG && MACH_SMARTQ + select SND_SAMSUNG_I2S + select SND_SOC_WM8750 + +config SND_SOC_GONI_AQUILA_WM8994 + tristate "SoC I2S Audio support for AQUILA/GONI - WM8994" + depends on SND_SOC_SAMSUNG && (MACH_GONI || MACH_AQUILA) + select SND_SAMSUNG_I2S + select SND_SOC_WM8994 + help + Say Y if you want to add support for SoC audio on goni or aquila + with the WM8994. + +config SND_SOC_SAMSUNG_SMDK_SPDIF + tristate "SoC S/PDIF Audio support for SMDK" + depends on SND_SOC_SAMSUNG && (MACH_SMDKC100 || MACH_SMDKC110 || MACH_SMDKV210) + select SND_SAMSUNG_SPDIF + help + Say Y if you want to add support for SoC S/PDIF audio on the SMDK. diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile new file mode 100644 index 00000000000..705d4e8a672 --- /dev/null +++ b/sound/soc/samsung/Makefile @@ -0,0 +1,55 @@ +# S3c24XX Platform Support +snd-soc-s3c24xx-objs := dma.o +snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o +snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o +snd-soc-ac97-objs := ac97.o +snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o +snd-soc-samsung-spdif-objs := spdif.o +snd-soc-pcm-objs := pcm.o +snd-soc-i2s-objs := i2s.o + +obj-$(CONFIG_SND_SOC_SAMSUNG) += snd-soc-s3c24xx.o +obj-$(CONFIG_SND_S3C24XX_I2S) += snd-soc-s3c24xx-i2s.o +obj-$(CONFIG_SND_SAMSUNG_AC97) += snd-soc-ac97.o +obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o +obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o +obj-$(CONFIG_SND_SAMSUNG_SPDIF) += snd-soc-samsung-spdif.o +obj-$(CONFIG_SND_SAMSUNG_PCM) += snd-soc-pcm.o +obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o + +# S3C24XX Machine Support +snd-soc-jive-wm8750-objs := jive_wm8750.o +snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o +snd-soc-neo1973-gta02-wm8753-objs := neo1973_gta02_wm8753.o +snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o +snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o +snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o +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-h1940-uda1380-objs := h1940_uda1380.o +snd-soc-rx1950-uda1380-objs := rx1950_uda1380.o +snd-soc-smdk-wm8580-objs := smdk_wm8580.o +snd-soc-smdk-wm8994-objs := smdk_wm8994.o +snd-soc-smdk-wm9713-objs := smdk_wm9713.o +snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o +snd-soc-goni-wm8994-objs := goni_wm8994.o +snd-soc-smdk-spdif-objs := smdk_spdif.o + +obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o +obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o +obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_GTA02_WM8753) += snd-soc-neo1973-gta02-wm8753.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o +obj-$(CONFIG_SND_SOC_SAMSUNG_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o +obj-$(CONFIG_SND_SOC_SAMSUNG_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC) += snd-soc-s3c24xx-simtec.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o +obj-$(CONFIG_SND_SOC_SAMSUNG_H1940_UDA1380) += snd-soc-h1940-uda1380.o +obj-$(CONFIG_SND_SOC_SAMSUNG_RX1950_UDA1380) += snd-soc-rx1950-uda1380.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8580) += snd-soc-smdk-wm8580.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994) += snd-soc-smdk-wm8994.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM9713) += snd-soc-smdk-wm9713.o +obj-$(CONFIG_SND_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF) += snd-soc-smdk-spdif.o +obj-$(CONFIG_SND_SOC_GONI_AQUILA_WM8994) += snd-soc-goni-wm8994.o diff --git a/sound/soc/samsung/ac97.c b/sound/soc/samsung/ac97.c new file mode 100644 index 00000000000..4770a955034 --- /dev/null +++ b/sound/soc/samsung/ac97.c @@ -0,0 +1,520 @@ +/* sound/soc/samsung/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 "dma.h" +#include "ac97.h" + +#define AC_CMD_ADDR(x) (x << 16) +#define AC_CMD_DATA(x) (x & 0xffff) + +struct s3c_ac97_info { + 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_activate(struct snd_ac97 *ac97) +{ + u32 ac_glbctrl, stat; + + stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7; + if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE) + return; /* Return if already active */ + + INIT_COMPLETION(s3c_ac97.done); + + 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); + + if (!wait_for_completion_timeout(&s3c_ac97.done, HZ)) + pr_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); + + INIT_COMPLETION(s3c_ac97.done); + + 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); + + if (!wait_for_completion_timeout(&s3c_ac97.done, HZ)) + pr_err("AC97: Unable to read!"); + + stat = readl(s3c_ac97.regs + S3C_AC97_STAT); + addr = (stat >> 16) & 0x7f; + data = (stat & 0xffff); + + if (addr != reg) + pr_err("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); + + INIT_COMPLETION(s3c_ac97.done); + + 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); + + if (!wait_for_completion_timeout(&s3c_ac97.done, HZ)) + pr_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 void s3c_ac97_cold_reset(struct snd_ac97 *ac97) +{ + pr_debug("AC97: Cold reset\n"); + 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) +{ + u32 stat; + + stat = readl(s3c_ac97.regs + S3C_AC97_GLBSTAT) & 0x7; + if (stat == S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE) + return; /* Return if already active */ + + pr_debug("AC97: Warm reset\n"); + + writel(S3C_AC97_GLBCTRL_WARMRESET, s3c_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + writel(0, s3c_ac97.regs + S3C_AC97_GLBCTRL); + msleep(1); + + s3c_ac97_activate(ac97); +} + +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); + + complete(&s3c_ac97.done); + } + + ac_glbctrl = readl(s3c_ac97.regs + S3C_AC97_GLBCTRL); + ac_glbctrl |= (1<<30); /* Clear interrupt */ + writel(ac_glbctrl, s3c_ac97.regs + S3C_AC97_GLBCTRL); + + 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->cpu_dai; + struct s3c_dma_params *dma_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &s3c_ac97_pcm_out; + else + dma_data = &s3c_ac97_pcm_in; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + 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; + struct s3c_dma_params *dma_data = + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + 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(dma_data->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->cpu_dai; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + else + snd_soc_dai_set_dma_data(cpu_dai, substream, &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; + struct s3c_dma_params *dma_data = + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + 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(dma_data->channel, S3C2410_DMAOP_STARTED); + + return 0; +} + +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, +}; + +static struct snd_soc_dai_driver s3c_ac97_dai[] = { + [S3C_AC97_DAI_PCM] = { + .name = "samsung-ac97", + .ac97_control = 1, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &s3c_ac97_dai_ops, + }, + [S3C_AC97_DAI_MIC] = { + .name = "samsung-ac97-mic", + .ac97_control = 1, + .capture = { + .stream_name = "AC97 Mic Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &s3c_ac97_mic_dai_ops, + }, +}; + +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), "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 err1; + } + + s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97"); + if (IS_ERR(s3c_ac97.ac97_clk)) { + dev_err(&pdev->dev, "ac97 failed to get ac97_clock\n"); + ret = -ENODEV; + goto err2; + } + clk_enable(s3c_ac97.ac97_clk); + + if (ac97_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + ret = -EINVAL; + goto err3; + } + + ret = request_irq(irq_res->start, s3c_ac97_irq, + IRQF_DISABLED, "AC97", NULL); + if (ret < 0) { + dev_err(&pdev->dev, "ac97: interrupt request failed.\n"); + goto err4; + } + + ret = snd_soc_register_dais(&pdev->dev, s3c_ac97_dai, + ARRAY_SIZE(s3c_ac97_dai)); + if (ret) + goto err5; + + return 0; + +err5: + free_irq(irq_res->start, NULL); +err4: +err3: + clk_disable(s3c_ac97.ac97_clk); + clk_put(s3c_ac97.ac97_clk); +err2: + iounmap(s3c_ac97.regs); +err1: + 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(&pdev->dev, 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 = "samsung-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"); +MODULE_ALIAS("platform:samsung-ac97"); diff --git a/sound/soc/samsung/ac97.h b/sound/soc/samsung/ac97.h new file mode 100644 index 00000000000..0d0e1b51145 --- /dev/null +++ b/sound/soc/samsung/ac97.h @@ -0,0 +1,21 @@ +/* sound/soc/samsung/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 + +#endif /* __S3C_AC97_H_ */ diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c new file mode 100644 index 00000000000..21240198c5d --- /dev/null +++ b/sound/soc/samsung/dma.c @@ -0,0 +1,502 @@ +/* + * dma.c -- ALSA Soc Audio Layer + * + * (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Copyright 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * 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/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/dma.h> +#include <mach/hardware.h> +#include <mach/dma.h> + +#include "dma.h" + +static const struct snd_pcm_hardware dma_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE | + SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S8, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 128*1024, + .period_bytes_min = PAGE_SIZE, + .period_bytes_max = PAGE_SIZE*2, + .periods_min = 2, + .periods_max = 128, + .fifo_size = 32, +}; + +struct runtime_data { + spinlock_t lock; + int state; + unsigned int dma_loaded; + unsigned int dma_limit; + unsigned int dma_period; + dma_addr_t dma_start; + dma_addr_t dma_pos; + dma_addr_t dma_end; + struct s3c_dma_params *params; +}; + +/* dma_enqueue + * + * place a dma buffer onto the queue for the dma system + * to handle. +*/ +static void dma_enqueue(struct snd_pcm_substream *substream) +{ + struct runtime_data *prtd = substream->runtime->private_data; + dma_addr_t pos = prtd->dma_pos; + unsigned int limit; + int ret; + + pr_debug("Entered %s\n", __func__); + + if (s3c_dma_has_circular()) + limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period; + else + limit = prtd->dma_limit; + + pr_debug("%s: loaded %d, limit %d\n", + __func__, prtd->dma_loaded, limit); + + while (prtd->dma_loaded < limit) { + unsigned long len = prtd->dma_period; + + pr_debug("dma_loaded: %d\n", prtd->dma_loaded); + + if ((pos + len) > prtd->dma_end) { + len = prtd->dma_end - pos; + pr_debug("%s: corrected dma len %ld\n", __func__, len); + } + + ret = s3c2410_dma_enqueue(prtd->params->channel, + substream, pos, len); + + if (ret == 0) { + prtd->dma_loaded++; + pos += prtd->dma_period; + if (pos >= prtd->dma_end) + pos = prtd->dma_start; + } else + break; + } + + prtd->dma_pos = pos; +} + +static void audio_buffdone(struct s3c2410_dma_chan *channel, + void *dev_id, int size, + enum s3c2410_dma_buffresult result) +{ + struct snd_pcm_substream *substream = dev_id; + struct runtime_data *prtd; + + pr_debug("Entered %s\n", __func__); + + if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR) + return; + + prtd = substream->runtime->private_data; + + if (substream) + snd_pcm_period_elapsed(substream); + + spin_lock(&prtd->lock); + if (prtd->state & ST_RUNNING && !s3c_dma_has_circular()) { + prtd->dma_loaded--; + dma_enqueue(substream); + } + + spin_unlock(&prtd->lock); +} + +static int dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned long totbytes = params_buffer_bytes(params); + struct s3c_dma_params *dma = + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + int ret = 0; + + + pr_debug("Entered %s\n", __func__); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + if (!dma) + return 0; + + /* this may get called several times by oss emulation + * with different params -HW */ + if (prtd->params == NULL) { + /* prepare DMA */ + prtd->params = dma; + + pr_debug("params %p, client %p, channel %d\n", prtd->params, + prtd->params->client, prtd->params->channel); + + ret = s3c2410_dma_request(prtd->params->channel, + prtd->params->client, NULL); + + if (ret < 0) { + printk(KERN_ERR "failed to get dma channel\n"); + return ret; + } + + /* use the circular buffering if we have it available. */ + if (s3c_dma_has_circular()) + s3c2410_dma_setflags(prtd->params->channel, + S3C2410_DMAF_CIRCULAR); + } + + s3c2410_dma_set_buffdone_fn(prtd->params->channel, + audio_buffdone); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + runtime->dma_bytes = totbytes; + + spin_lock_irq(&prtd->lock); + prtd->dma_loaded = 0; + prtd->dma_limit = runtime->hw.periods_min; + prtd->dma_period = params_period_bytes(params); + prtd->dma_start = runtime->dma_addr; + prtd->dma_pos = prtd->dma_start; + prtd->dma_end = prtd->dma_start + totbytes; + spin_unlock_irq(&prtd->lock); + + return 0; +} + +static int dma_hw_free(struct snd_pcm_substream *substream) +{ + struct runtime_data *prtd = substream->runtime->private_data; + + pr_debug("Entered %s\n", __func__); + + /* TODO - do we need to ensure DMA flushed */ + snd_pcm_set_runtime_buffer(substream, NULL); + + if (prtd->params) { + s3c2410_dma_free(prtd->params->channel, prtd->params->client); + prtd->params = NULL; + } + + return 0; +} + +static int dma_prepare(struct snd_pcm_substream *substream) +{ + struct runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + pr_debug("Entered %s\n", __func__); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + if (!prtd->params) + return 0; + + /* channel needs configuring for mem=>device, increment memory addr, + * sync to pclk, half-word transfers to the IIS-FIFO. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + s3c2410_dma_devconfig(prtd->params->channel, + S3C2410_DMASRC_MEM, + prtd->params->dma_addr); + } else { + s3c2410_dma_devconfig(prtd->params->channel, + S3C2410_DMASRC_HW, + prtd->params->dma_addr); + } + + s3c2410_dma_config(prtd->params->channel, + prtd->params->dma_size); + + /* flush the DMA channel */ + s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH); + prtd->dma_loaded = 0; + prtd->dma_pos = prtd->dma_start; + + /* enqueue dma buffers */ + dma_enqueue(substream); + + return ret; +} + +static int dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct runtime_data *prtd = substream->runtime->private_data; + int ret = 0; + + pr_debug("Entered %s\n", __func__); + + spin_lock(&prtd->lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + prtd->state |= ST_RUNNING; + s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + prtd->state &= ~ST_RUNNING; + s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_STOP); + break; + + default: + ret = -EINVAL; + break; + } + + spin_unlock(&prtd->lock); + + return ret; +} + +static snd_pcm_uframes_t +dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct runtime_data *prtd = runtime->private_data; + unsigned long res; + dma_addr_t src, dst; + + pr_debug("Entered %s\n", __func__); + + spin_lock(&prtd->lock); + s3c2410_dma_getposition(prtd->params->channel, &src, &dst); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + res = dst - prtd->dma_start; + else + res = src - prtd->dma_start; + + spin_unlock(&prtd->lock); + + pr_debug("Pointer %x %x\n", src, dst); + + /* we seem to be getting the odd error from the pcm library due + * to out-of-bounds pointers. this is maybe due to the dma engine + * not having loaded the new values for the channel before being + * callled... (todo - fix ) + */ + + if (res >= snd_pcm_lib_buffer_bytes(substream)) { + if (res == snd_pcm_lib_buffer_bytes(substream)) + res = 0; + } + + return bytes_to_frames(substream->runtime, res); +} + +static int dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct runtime_data *prtd; + + pr_debug("Entered %s\n", __func__); + + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + snd_soc_set_runtime_hwparams(substream, &dma_hardware); + + prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + spin_lock_init(&prtd->lock); + + runtime->private_data = prtd; + return 0; +} + +static int dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct runtime_data *prtd = runtime->private_data; + + pr_debug("Entered %s\n", __func__); + + if (!prtd) + pr_debug("dma_close called with prtd == NULL\n"); + + kfree(prtd); + + return 0; +} + +static int dma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + pr_debug("Entered %s\n", __func__); + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops dma_ops = { + .open = dma_open, + .close = dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = dma_hw_params, + .hw_free = dma_hw_free, + .prepare = dma_prepare, + .trigger = dma_trigger, + .pointer = dma_pointer, + .mmap = dma_mmap, +}; + +static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = dma_hardware.buffer_bytes_max; + + pr_debug("Entered %s\n", __func__); + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} + +static void dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + pr_debug("Entered %s\n", __func__); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static u64 dma_mask = DMA_BIT_MASK(32); + +static int dma_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + int ret = 0; + + pr_debug("Entered %s\n", __func__); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &dma_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->driver->playback.channels_min) { + ret = preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->driver->capture.channels_min) { + ret = preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } +out: + return ret; +} + +static struct snd_soc_platform_driver samsung_asoc_platform = { + .ops = &dma_ops, + .pcm_new = dma_new, + .pcm_free = dma_free_dma_buffers, +}; + +static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform); +} + +static int __devexit samsung_asoc_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver asoc_dma_driver = { + .driver = { + .name = "samsung-audio", + .owner = THIS_MODULE, + }, + + .probe = samsung_asoc_platform_probe, + .remove = __devexit_p(samsung_asoc_platform_remove), +}; + +static int __init samsung_asoc_init(void) +{ + return platform_driver_register(&asoc_dma_driver); +} +module_init(samsung_asoc_init); + +static void __exit samsung_asoc_exit(void) +{ + platform_driver_unregister(&asoc_dma_driver); +} +module_exit(samsung_asoc_exit); + +MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("Samsung ASoC DMA Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-audio"); diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h new file mode 100644 index 00000000000..f8cd2b4223a --- /dev/null +++ b/sound/soc/samsung/dma.h @@ -0,0 +1,30 @@ +/* + * dma.h -- + * + * 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. + * + * ALSA PCM interface for the Samsung S3C24xx CPU + */ + +#ifndef _S3C_AUDIO_H +#define _S3C_AUDIO_H + +#define ST_RUNNING (1<<0) +#define ST_OPENED (1<<1) + +struct s3c_dma_params { + struct s3c2410_dma_client *client; /* stream identifier */ + int channel; /* Channel ID */ + dma_addr_t dma_addr; + int dma_size; /* Size of the DMA transfer */ +}; + +#define S3C24XX_DAI_I2S 0 + +/* platform data */ +extern struct snd_ac97_bus_ops s3c24xx_ac97_ops; + +#endif diff --git a/sound/soc/samsung/goni_wm8994.c b/sound/soc/samsung/goni_wm8994.c new file mode 100644 index 00000000000..34dd9ef1b9c --- /dev/null +++ b/sound/soc/samsung/goni_wm8994.c @@ -0,0 +1,314 @@ +/* + * goni_wm8994.c + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Author: Chanwoo Choi <cw00.choi@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/moduleparam.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <asm/mach-types.h> +#include <mach/gpio.h> +#include <mach/regs-clock.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/registers.h> +#include "../codecs/wm8994.h" +#include "dma.h" +#include "i2s.h" + +#define MACHINE_NAME 0 +#define CPU_VOICE_DAI 1 + +static const char *aquila_str[] = { + [MACHINE_NAME] = "aquila", + [CPU_VOICE_DAI] = "aquila-voice-dai", +}; + +static struct snd_soc_card goni; +static struct platform_device *goni_snd_device; + +/* 3.5 pie jack */ +static struct snd_soc_jack jack; + +/* 3.5 pie jack detection DAPM pins */ +static struct snd_soc_jack_pin jack_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, { + .pin = "Headset Stereophone", + .mask = SND_JACK_HEADPHONE | SND_JACK_MECHANICAL | + SND_JACK_AVOUT, + }, +}; + +/* 3.5 pie jack detection gpios */ +static struct snd_soc_jack_gpio jack_gpios[] = { + { + .gpio = S5PV210_GPH0(6), + .name = "DET_3.5", + .report = SND_JACK_HEADSET | SND_JACK_MECHANICAL | + SND_JACK_AVOUT, + .debounce_time = 200, + }, +}; + +static const struct snd_soc_dapm_widget goni_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Left Spk", NULL), + SND_SOC_DAPM_SPK("Ext Right Spk", NULL), + SND_SOC_DAPM_SPK("Ext Rcv", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Main Mic", NULL), + SND_SOC_DAPM_MIC("2nd Mic", NULL), + SND_SOC_DAPM_LINE("Radio In", NULL), +}; + +static const struct snd_soc_dapm_route goni_dapm_routes[] = { + {"Ext Left Spk", NULL, "SPKOUTLP"}, + {"Ext Left Spk", NULL, "SPKOUTLN"}, + + {"Ext Right Spk", NULL, "SPKOUTRP"}, + {"Ext Right Spk", NULL, "SPKOUTRN"}, + + {"Ext Rcv", NULL, "HPOUT2N"}, + {"Ext Rcv", NULL, "HPOUT2P"}, + + {"Headset Stereophone", NULL, "HPOUT1L"}, + {"Headset Stereophone", NULL, "HPOUT1R"}, + + {"IN1RN", NULL, "Headset Mic"}, + {"IN1RP", NULL, "Headset Mic"}, + + {"IN1RN", NULL, "2nd Mic"}, + {"IN1RP", NULL, "2nd Mic"}, + + {"IN1LN", NULL, "Main Mic"}, + {"IN1LP", NULL, "Main Mic"}, + + {"IN2LN", NULL, "Radio In"}, + {"IN2RN", NULL, "Radio In"}, +}; + +static int goni_wm8994_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret; + + /* add goni specific widgets */ + snd_soc_dapm_new_controls(dapm, goni_dapm_widgets, + ARRAY_SIZE(goni_dapm_widgets)); + + /* set up goni specific audio routes */ + snd_soc_dapm_add_routes(dapm, goni_dapm_routes, + ARRAY_SIZE(goni_dapm_routes)); + + /* set endpoints to not connected */ + snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN"); + snd_soc_dapm_nc_pin(dapm, "IN2RP:VXRP"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT1N"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT1P"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT2N"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT2P"); + + if (machine_is_aquila()) { + snd_soc_dapm_nc_pin(dapm, "SPKOUTRN"); + snd_soc_dapm_nc_pin(dapm, "SPKOUTRP"); + } + + snd_soc_dapm_sync(dapm); + + /* Headset jack detection */ + ret = snd_soc_jack_new(codec, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_MECHANICAL | SND_JACK_AVOUT, + &jack); + if (ret) + return ret; + + ret = snd_soc_jack_add_pins(&jack, ARRAY_SIZE(jack_pins), jack_pins); + if (ret) + return ret; + + ret = snd_soc_jack_add_gpios(&jack, ARRAY_SIZE(jack_gpios), jack_gpios); + if (ret) + return ret; + + return 0; +} + +static int goni_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int pll_out = 24000000; + int ret = 0; + + /* set the cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the codec FLL */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, pll_out, + params_rate(params) * 256); + if (ret < 0) + return ret; + + /* set the codec system clock */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + params_rate(params) * 256, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops goni_hifi_ops = { + .hw_params = goni_hifi_hw_params, +}; + +static int goni_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pll_out = 24000000; + int ret = 0; + + if (params_rate(params) != 8000) + return -EINVAL; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the codec FLL */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, 0, pll_out, + params_rate(params) * 256); + if (ret < 0) + return ret; + + /* set the codec system clock */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2, + params_rate(params) * 256, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_dai_driver voice_dai = { + .name = "goni-voice-dai", + .id = 0, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, +}; + +static struct snd_soc_ops goni_voice_ops = { + .hw_params = goni_voice_hw_params, +}; + +static struct snd_soc_dai_link goni_dai[] = { +{ + .name = "WM8994", + .stream_name = "WM8994 HiFi", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8994-hifi", + .platform_name = "samsung-audio", + .codec_name = "wm8994-codec.0-0x1a", + .init = goni_wm8994_init, + .ops = &goni_hifi_ops, +}, { + .name = "WM8994 Voice", + .stream_name = "Voice", + .cpu_dai_name = "goni-voice-dai", + .codec_dai_name = "wm8994-voice", + .platform_name = "samsung-audio", + .codec_name = "wm8994-codec.0-0x1a", + .ops = &goni_voice_ops, +}, +}; + +static struct snd_soc_card goni = { + .name = "goni", + .dai_link = goni_dai, + .num_links = ARRAY_SIZE(goni_dai), +}; + +static int __init goni_init(void) +{ + int ret; + + if (machine_is_aquila()) { + voice_dai.name = aquila_str[CPU_VOICE_DAI]; + goni_dai[1].cpu_dai_name = aquila_str[CPU_VOICE_DAI]; + goni.name = aquila_str[MACHINE_NAME]; + } else if (!machine_is_goni()) + return -ENODEV; + + goni_snd_device = platform_device_alloc("soc-audio", -1); + if (!goni_snd_device) + return -ENOMEM; + + /* register voice DAI here */ + ret = snd_soc_register_dai(&goni_snd_device->dev, &voice_dai); + if (ret) { + platform_device_put(goni_snd_device); + return ret; + } + + platform_set_drvdata(goni_snd_device, &goni); + ret = platform_device_add(goni_snd_device); + + if (ret) { + snd_soc_unregister_dai(&goni_snd_device->dev); + platform_device_put(goni_snd_device); + } + + return ret; +} + +static void __exit goni_exit(void) +{ + snd_soc_unregister_dai(&goni_snd_device->dev); + platform_device_unregister(goni_snd_device); +} + +module_init(goni_init); +module_exit(goni_exit); + +/* Module information */ +MODULE_DESCRIPTION("ALSA SoC WM8994 GONI(S5PV210)"); +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c new file mode 100644 index 00000000000..c45f7ce14d6 --- /dev/null +++ b/sound/soc/samsung/h1940_uda1380.c @@ -0,0 +1,296 @@ +/* + * h1940-uda1380.c -- ALSA Soc Audio Layer + * + * Copyright (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org> + * Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com> + * + * Based on version from Arnaud Patard <arnaud.patard@rtp-net.org> + * + * 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/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/gpio.h> + +#include <sound/soc.h> +#include <sound/uda1380.h> +#include <sound/jack.h> + +#include <plat/regs-iis.h> + +#include <mach/h1940-latch.h> + +#include <asm/mach-types.h> + +#include "dma.h" +#include "s3c24xx-i2s.h" +#include "../codecs/uda1380.h" + +static unsigned int rates[] = { + 11025, + 22050, + 44100, +}; + +static struct snd_pcm_hw_constraint_list hw_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static struct snd_soc_jack hp_jack; + +static struct snd_soc_jack_pin hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Speaker", + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + +static struct snd_soc_jack_gpio hp_jack_gpios[] = { + { + .gpio = S3C2410_GPG(4), + .name = "hp-gpio", + .report = SND_JACK_HEADPHONE, + .invert = 1, + .debounce_time = 200, + }, +}; + +static int h1940_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.rate_min = hw_rates.list[0]; + runtime->hw.rate_max = hw_rates.list[hw_rates.count - 1]; + runtime->hw.rates = SNDRV_PCM_RATE_KNOT; + + return snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_rates); +} + +static int h1940_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int div; + int ret; + unsigned int rate = params_rate(params); + + switch (rate) { + case 11025: + case 22050: + case 44100: + div = s3c24xx_i2s_get_clockrate() / (384 * rate); + if (s3c24xx_i2s_get_clockrate() % (384 * rate) > (192 * rate)) + div++; + break; + default: + dev_err(&rtd->dev, "%s: rate %d is not supported\n", + __func__, rate); + return -EINVAL; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* select clock source */ + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_PCLK, rate, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + S3C2410_IISMOD_384FS); + if (ret < 0) + return ret; + + /* set BCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(div, div)); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops h1940_ops = { + .startup = h1940_startup, + .hw_params = h1940_hw_params, +}; + +static int h1940_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(H1940_LATCH_AUDIO_POWER, 1); + else + gpio_set_value(H1940_LATCH_AUDIO_POWER, 0); + + return 0; +} + +/* h1940 machine dapm widgets */ +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", h1940_spk_power), +}; + +/* h1940 machine audio_map */ +static const struct snd_soc_dapm_route audio_map[] = { + /* headphone connected to VOUTLHP, VOUTRHP */ + {"Headphone Jack", NULL, "VOUTLHP"}, + {"Headphone Jack", NULL, "VOUTRHP"}, + + /* ext speaker connected to VOUTL, VOUTR */ + {"Speaker", NULL, "VOUTL"}, + {"Speaker", NULL, "VOUTR"}, + + /* mic is connected to VINM */ + {"VINM", NULL, "Mic Jack"}, +}; + +static struct platform_device *s3c24xx_snd_device; + +static int h1940_uda1380_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int err; + + /* Add h1940 specific widgets */ + err = snd_soc_dapm_new_controls(dapm, uda1380_dapm_widgets, + ARRAY_SIZE(uda1380_dapm_widgets)); + if (err) + return err; + + /* Set up h1940 specific audio path audio_mapnects */ + err = snd_soc_dapm_add_routes(dapm, audio_map, + ARRAY_SIZE(audio_map)); + if (err) + return err; + + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_enable_pin(dapm, "Speaker"); + snd_soc_dapm_enable_pin(dapm, "Mic Jack"); + + snd_soc_dapm_sync(dapm); + + snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, + &hp_jack); + + snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins), + hp_jack_pins); + + snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), + hp_jack_gpios); + + return 0; +} + +/* s3c24xx digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link h1940_uda1380_dai[] = { + { + .name = "uda1380", + .stream_name = "UDA1380 Duplex", + .cpu_dai_name = "s3c24xx-iis", + .codec_dai_name = "uda1380-hifi", + .init = h1940_uda1380_init, + .platform_name = "samsung-audio", + .codec_name = "uda1380-codec.0-001a", + .ops = &h1940_ops, + }, +}; + +static struct snd_soc_card h1940_asoc = { + .name = "h1940", + .dai_link = h1940_uda1380_dai, + .num_links = ARRAY_SIZE(h1940_uda1380_dai), +}; + +static int __init h1940_init(void) +{ + int ret; + + if (!machine_is_h1940()) + return -ENODEV; + + /* configure some gpios */ + ret = gpio_request(H1940_LATCH_AUDIO_POWER, "speaker-power"); + if (ret) + goto err_out; + + ret = gpio_direction_output(H1940_LATCH_AUDIO_POWER, 0); + if (ret) + goto err_gpio; + + s3c24xx_snd_device = platform_device_alloc("soc-audio", -1); + if (!s3c24xx_snd_device) { + ret = -ENOMEM; + goto err_gpio; + } + + platform_set_drvdata(s3c24xx_snd_device, &h1940_asoc); + ret = platform_device_add(s3c24xx_snd_device); + + if (ret) + goto err_plat; + + return 0; + +err_plat: + platform_device_put(s3c24xx_snd_device); +err_gpio: + gpio_free(H1940_LATCH_AUDIO_POWER); + +err_out: + return ret; +} + +static void __exit h1940_exit(void) +{ + platform_device_unregister(s3c24xx_snd_device); + snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), + hp_jack_gpios); + gpio_free(H1940_LATCH_AUDIO_POWER); +} + +module_init(h1940_init); +module_exit(h1940_exit); + +/* Module information */ +MODULE_AUTHOR("Arnaud Patard, Vasily Khoruzhick"); +MODULE_DESCRIPTION("ALSA SoC H1940"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c new file mode 100644 index 00000000000..d00ac3a7102 --- /dev/null +++ b/sound/soc/samsung/i2s.c @@ -0,0 +1,1258 @@ +/* sound/soc/samsung/i2s.c + * + * ALSA SoC Audio Layer - Samsung I2S Controller driver + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd. + * Jaswinder Singh <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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <plat/audio.h> + +#include "dma.h" +#include "i2s.h" + +#define I2SCON 0x0 +#define I2SMOD 0x4 +#define I2SFIC 0x8 +#define I2SPSR 0xc +#define I2STXD 0x10 +#define I2SRXD 0x14 +#define I2SFICS 0x18 +#define I2STXDS 0x1c + +#define CON_RSTCLR (1 << 31) +#define CON_FRXOFSTATUS (1 << 26) +#define CON_FRXORINTEN (1 << 25) +#define CON_FTXSURSTAT (1 << 24) +#define CON_FTXSURINTEN (1 << 23) +#define CON_TXSDMA_PAUSE (1 << 20) +#define CON_TXSDMA_ACTIVE (1 << 18) + +#define CON_FTXURSTATUS (1 << 17) +#define CON_FTXURINTEN (1 << 16) +#define CON_TXFIFO2_EMPTY (1 << 15) +#define CON_TXFIFO1_EMPTY (1 << 14) +#define CON_TXFIFO2_FULL (1 << 13) +#define CON_TXFIFO1_FULL (1 << 12) + +#define CON_LRINDEX (1 << 11) +#define CON_TXFIFO_EMPTY (1 << 10) +#define CON_RXFIFO_EMPTY (1 << 9) +#define CON_TXFIFO_FULL (1 << 8) +#define CON_RXFIFO_FULL (1 << 7) +#define CON_TXDMA_PAUSE (1 << 6) +#define CON_RXDMA_PAUSE (1 << 5) +#define CON_TXCH_PAUSE (1 << 4) +#define CON_RXCH_PAUSE (1 << 3) +#define CON_TXDMA_ACTIVE (1 << 2) +#define CON_RXDMA_ACTIVE (1 << 1) +#define CON_ACTIVE (1 << 0) + +#define MOD_OPCLK_CDCLK_OUT (0 << 30) +#define MOD_OPCLK_CDCLK_IN (1 << 30) +#define MOD_OPCLK_BCLK_OUT (2 << 30) +#define MOD_OPCLK_PCLK (3 << 30) +#define MOD_OPCLK_MASK (3 << 30) +#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */ + +#define MOD_BLCS_SHIFT 26 +#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT) +#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT) +#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT) +#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT) +#define MOD_BLCP_SHIFT 24 +#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT) +#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT) +#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT) +#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT) + +#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */ +#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */ +#define MOD_C1DD_HHALF (1 << 19) +#define MOD_C1DD_LHALF (1 << 18) +#define MOD_DC2_EN (1 << 17) +#define MOD_DC1_EN (1 << 16) +#define MOD_BLC_16BIT (0 << 13) +#define MOD_BLC_8BIT (1 << 13) +#define MOD_BLC_24BIT (2 << 13) +#define MOD_BLC_MASK (3 << 13) + +#define MOD_IMS_SYSMUX (1 << 10) +#define MOD_SLAVE (1 << 11) +#define MOD_TXONLY (0 << 8) +#define MOD_RXONLY (1 << 8) +#define MOD_TXRX (2 << 8) +#define MOD_MASK (3 << 8) +#define MOD_LR_LLOW (0 << 7) +#define MOD_LR_RLOW (1 << 7) +#define MOD_SDF_IIS (0 << 5) +#define MOD_SDF_MSB (1 << 5) +#define MOD_SDF_LSB (2 << 5) +#define MOD_SDF_MASK (3 << 5) +#define MOD_RCLK_256FS (0 << 3) +#define MOD_RCLK_512FS (1 << 3) +#define MOD_RCLK_384FS (2 << 3) +#define MOD_RCLK_768FS (3 << 3) +#define MOD_RCLK_MASK (3 << 3) +#define MOD_BCLK_32FS (0 << 1) +#define MOD_BCLK_48FS (1 << 1) +#define MOD_BCLK_16FS (2 << 1) +#define MOD_BCLK_24FS (3 << 1) +#define MOD_BCLK_MASK (3 << 1) +#define MOD_8BIT (1 << 0) + +#define MOD_CDCLKCON (1 << 12) + +#define PSR_PSREN (1 << 15) + +#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf) +#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf) + +#define FIC_TXFLUSH (1 << 15) +#define FIC_RXFLUSH (1 << 7) +#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf) +#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf) +#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f) + +#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) + +struct i2s_dai { + /* Platform device for this DAI */ + struct platform_device *pdev; + /* IOREMAP'd SFRs */ + void __iomem *addr; + /* Physical base address of SFRs */ + u32 base; + /* Rate of RCLK source clock */ + unsigned long rclk_srcrate; + /* Frame Clock */ + unsigned frmclk; + /* + * Specifically requested RCLK,BCLK by MACHINE Driver. + * 0 indicates CPU driver is free to choose any value. + */ + unsigned rfs, bfs; + /* I2S Controller's core clock */ + struct clk *clk; + /* Clock for generating I2S signals */ + struct clk *op_clk; + /* Array of clock names for op_clk */ + const char **src_clk; + /* Pointer to the Primary_Fifo if this is Sec_Fifo, NULL otherwise */ + struct i2s_dai *pri_dai; + /* Pointer to the Secondary_Fifo if it has one, NULL otherwise */ + struct i2s_dai *sec_dai; +#define DAI_OPENED (1 << 0) /* Dai is opened */ +#define DAI_MANAGER (1 << 1) /* Dai is the manager */ + unsigned mode; + /* Driver for this DAI */ + struct snd_soc_dai_driver i2s_dai_drv; + /* DMA parameters */ + struct s3c_dma_params dma_playback; + struct s3c_dma_params dma_capture; + u32 quirks; + u32 suspend_i2smod; + u32 suspend_i2scon; + u32 suspend_i2spsr; +}; + +/* Lock for cross i/f checks */ +static DEFINE_SPINLOCK(lock); + +/* If this is the 'overlay' stereo DAI */ +static inline bool is_secondary(struct i2s_dai *i2s) +{ + return i2s->pri_dai ? true : false; +} + +/* If operating in SoC-Slave mode */ +static inline bool is_slave(struct i2s_dai *i2s) +{ + return (readl(i2s->addr + I2SMOD) & MOD_SLAVE) ? true : false; +} + +/* If this interface of the controller is transmitting data */ +static inline bool tx_active(struct i2s_dai *i2s) +{ + u32 active; + + if (!i2s) + return false; + + active = readl(i2s->addr + I2SMOD); + + if (is_secondary(i2s)) + active &= CON_TXSDMA_ACTIVE; + else + active &= CON_TXDMA_ACTIVE; + + return active ? true : false; +} + +/* If the other interface of the controller is transmitting data */ +static inline bool other_tx_active(struct i2s_dai *i2s) +{ + struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + + return tx_active(other); +} + +/* If any interface of the controller is transmitting data */ +static inline bool any_tx_active(struct i2s_dai *i2s) +{ + return tx_active(i2s) || other_tx_active(i2s); +} + +/* If this interface of the controller is receiving data */ +static inline bool rx_active(struct i2s_dai *i2s) +{ + u32 active; + + if (!i2s) + return false; + + active = readl(i2s->addr + I2SMOD) & CON_RXDMA_ACTIVE; + + return active ? true : false; +} + +/* If the other interface of the controller is receiving data */ +static inline bool other_rx_active(struct i2s_dai *i2s) +{ + struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + + return rx_active(other); +} + +/* If any interface of the controller is receiving data */ +static inline bool any_rx_active(struct i2s_dai *i2s) +{ + return rx_active(i2s) || other_rx_active(i2s); +} + +/* If the other DAI is transmitting or receiving data */ +static inline bool other_active(struct i2s_dai *i2s) +{ + return other_rx_active(i2s) || other_tx_active(i2s); +} + +/* If this DAI is transmitting or receiving data */ +static inline bool this_active(struct i2s_dai *i2s) +{ + return tx_active(i2s) || rx_active(i2s); +} + +/* If the controller is active anyway */ +static inline bool any_active(struct i2s_dai *i2s) +{ + return this_active(i2s) || other_active(i2s); +} + +static inline struct i2s_dai *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +static inline bool is_opened(struct i2s_dai *i2s) +{ + if (i2s && (i2s->mode & DAI_OPENED)) + return true; + else + return false; +} + +static inline bool is_manager(struct i2s_dai *i2s) +{ + if (is_opened(i2s) && (i2s->mode & DAI_MANAGER)) + return true; + else + return false; +} + +/* Read RCLK of I2S (in multiples of LRCLK) */ +static inline unsigned get_rfs(struct i2s_dai *i2s) +{ + u32 rfs = (readl(i2s->addr + I2SMOD) >> 3) & 0x3; + + switch (rfs) { + case 3: return 768; + case 2: return 384; + case 1: return 512; + default: return 256; + } +} + +/* Write RCLK of I2S (in multiples of LRCLK) */ +static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs) +{ + u32 mod = readl(i2s->addr + I2SMOD); + + mod &= ~MOD_RCLK_MASK; + + switch (rfs) { + case 768: + mod |= MOD_RCLK_768FS; + break; + case 512: + mod |= MOD_RCLK_512FS; + break; + case 384: + mod |= MOD_RCLK_384FS; + break; + default: + mod |= MOD_RCLK_256FS; + break; + } + + writel(mod, i2s->addr + I2SMOD); +} + +/* Read Bit-Clock of I2S (in multiples of LRCLK) */ +static inline unsigned get_bfs(struct i2s_dai *i2s) +{ + u32 bfs = (readl(i2s->addr + I2SMOD) >> 1) & 0x3; + + switch (bfs) { + case 3: return 24; + case 2: return 16; + case 1: return 48; + default: return 32; + } +} + +/* Write Bit-Clock of I2S (in multiples of LRCLK) */ +static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs) +{ + u32 mod = readl(i2s->addr + I2SMOD); + + mod &= ~MOD_BCLK_MASK; + + switch (bfs) { + case 48: + mod |= MOD_BCLK_48FS; + break; + case 32: + mod |= MOD_BCLK_32FS; + break; + case 24: + mod |= MOD_BCLK_24FS; + break; + case 16: + mod |= MOD_BCLK_16FS; + break; + default: + dev_err(&i2s->pdev->dev, "Wrong BCLK Divider!\n"); + return; + } + + writel(mod, i2s->addr + I2SMOD); +} + +/* Sample-Size */ +static inline int get_blc(struct i2s_dai *i2s) +{ + int blc = readl(i2s->addr + I2SMOD); + + blc = (blc >> 13) & 0x3; + + switch (blc) { + case 2: return 24; + case 1: return 8; + default: return 16; + } +} + +/* TX Channel Control */ +static void i2s_txctrl(struct i2s_dai *i2s, int on) +{ + void __iomem *addr = i2s->addr; + u32 con = readl(addr + I2SCON); + u32 mod = readl(addr + I2SMOD) & ~MOD_MASK; + + if (on) { + con |= CON_ACTIVE; + con &= ~CON_TXCH_PAUSE; + + if (is_secondary(i2s)) { + con |= CON_TXSDMA_ACTIVE; + con &= ~CON_TXSDMA_PAUSE; + } else { + con |= CON_TXDMA_ACTIVE; + con &= ~CON_TXDMA_PAUSE; + } + + if (any_rx_active(i2s)) + mod |= MOD_TXRX; + else + mod |= MOD_TXONLY; + } else { + if (is_secondary(i2s)) { + con |= CON_TXSDMA_PAUSE; + con &= ~CON_TXSDMA_ACTIVE; + } else { + con |= CON_TXDMA_PAUSE; + con &= ~CON_TXDMA_ACTIVE; + } + + if (other_tx_active(i2s)) { + writel(con, addr + I2SCON); + return; + } + + con |= CON_TXCH_PAUSE; + + if (any_rx_active(i2s)) + mod |= MOD_RXONLY; + else + con &= ~CON_ACTIVE; + } + + writel(mod, addr + I2SMOD); + writel(con, addr + I2SCON); +} + +/* RX Channel Control */ +static void i2s_rxctrl(struct i2s_dai *i2s, int on) +{ + void __iomem *addr = i2s->addr; + u32 con = readl(addr + I2SCON); + u32 mod = readl(addr + I2SMOD) & ~MOD_MASK; + + if (on) { + con |= CON_RXDMA_ACTIVE | CON_ACTIVE; + con &= ~(CON_RXDMA_PAUSE | CON_RXCH_PAUSE); + + if (any_tx_active(i2s)) + mod |= MOD_TXRX; + else + mod |= MOD_RXONLY; + } else { + con |= CON_RXDMA_PAUSE | CON_RXCH_PAUSE; + con &= ~CON_RXDMA_ACTIVE; + + if (any_tx_active(i2s)) + mod |= MOD_TXONLY; + else + con &= ~CON_ACTIVE; + } + + writel(mod, addr + I2SMOD); + writel(con, addr + I2SCON); +} + +/* Flush FIFO of an interface */ +static inline void i2s_fifo(struct i2s_dai *i2s, u32 flush) +{ + void __iomem *fic; + u32 val; + + if (!i2s) + return; + + if (is_secondary(i2s)) + fic = i2s->addr + I2SFICS; + else + fic = i2s->addr + I2SFIC; + + /* Flush the FIFO */ + writel(readl(fic) | flush, fic); + + /* Be patient */ + val = msecs_to_loops(1) / 1000; /* 1 usec */ + while (--val) + cpu_relax(); + + writel(readl(fic) & ~flush, fic); +} + +static int i2s_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int rfs, int dir) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + u32 mod = readl(i2s->addr + I2SMOD); + + switch (clk_id) { + case SAMSUNG_I2S_CDCLK: + /* Shouldn't matter in GATING(CLOCK_IN) mode */ + if (dir == SND_SOC_CLOCK_IN) + rfs = 0; + + if ((rfs && other->rfs && (other->rfs != rfs)) || + (any_active(i2s) && + (((dir == SND_SOC_CLOCK_IN) + && !(mod & MOD_CDCLKCON)) || + ((dir == SND_SOC_CLOCK_OUT) + && (mod & MOD_CDCLKCON))))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + return -EAGAIN; + } + + if (dir == SND_SOC_CLOCK_IN) + mod |= MOD_CDCLKCON; + else + mod &= ~MOD_CDCLKCON; + + i2s->rfs = rfs; + break; + + case SAMSUNG_I2S_RCLKSRC_0: /* clock corrsponding to IISMOD[10] := 0 */ + case SAMSUNG_I2S_RCLKSRC_1: /* clock corrsponding to IISMOD[10] := 1 */ + if ((i2s->quirks & QUIRK_NO_MUXPSR) + || (clk_id == SAMSUNG_I2S_RCLKSRC_0)) + clk_id = 0; + else + clk_id = 1; + + if (!any_active(i2s)) { + if (i2s->op_clk) { + if ((clk_id && !(mod & MOD_IMS_SYSMUX)) || + (!clk_id && (mod & MOD_IMS_SYSMUX))) { + clk_disable(i2s->op_clk); + clk_put(i2s->op_clk); + } else { + i2s->rclk_srcrate = + clk_get_rate(i2s->op_clk); + return 0; + } + } + + i2s->op_clk = clk_get(&i2s->pdev->dev, + i2s->src_clk[clk_id]); + clk_enable(i2s->op_clk); + i2s->rclk_srcrate = clk_get_rate(i2s->op_clk); + + /* Over-ride the other's */ + if (other) { + other->op_clk = i2s->op_clk; + other->rclk_srcrate = i2s->rclk_srcrate; + } + } else if ((!clk_id && (mod & MOD_IMS_SYSMUX)) + || (clk_id && !(mod & MOD_IMS_SYSMUX))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + return -EAGAIN; + } else { + /* Call can't be on the active DAI */ + i2s->op_clk = other->op_clk; + i2s->rclk_srcrate = other->rclk_srcrate; + return 0; + } + + if (clk_id == 0) + mod &= ~MOD_IMS_SYSMUX; + else + mod |= MOD_IMS_SYSMUX; + break; + + default: + dev_err(&i2s->pdev->dev, "We don't serve that!\n"); + return -EINVAL; + } + + writel(mod, i2s->addr + I2SMOD); + + return 0; +} + +static int i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct i2s_dai *i2s = to_info(dai); + u32 mod = readl(i2s->addr + I2SMOD); + u32 tmp = 0; + + /* Format is priority */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + tmp |= MOD_LR_RLOW; + tmp |= MOD_SDF_MSB; + break; + case SND_SOC_DAIFMT_LEFT_J: + tmp |= MOD_LR_RLOW; + tmp |= MOD_SDF_LSB; + break; + case SND_SOC_DAIFMT_I2S: + tmp |= MOD_SDF_IIS; + break; + default: + dev_err(&i2s->pdev->dev, "Format not supported\n"); + return -EINVAL; + } + + /* + * INV flag is relative to the FORMAT flag - if set it simply + * flips the polarity specified by the Standard + */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + if (tmp & MOD_LR_RLOW) + tmp &= ~MOD_LR_RLOW; + else + tmp |= MOD_LR_RLOW; + break; + default: + dev_err(&i2s->pdev->dev, "Polarity not supported\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + tmp |= MOD_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + /* Set default source clock in Master mode */ + if (i2s->rclk_srcrate == 0) + i2s_set_sysclk(dai, SAMSUNG_I2S_RCLKSRC_0, + 0, SND_SOC_CLOCK_IN); + break; + default: + dev_err(&i2s->pdev->dev, "master/slave format not supported\n"); + return -EINVAL; + } + + if (any_active(i2s) && + ((mod & (MOD_SDF_MASK | MOD_LR_RLOW + | MOD_SLAVE)) != tmp)) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + return -EAGAIN; + } + + mod &= ~(MOD_SDF_MASK | MOD_LR_RLOW | MOD_SLAVE); + mod |= tmp; + writel(mod, i2s->addr + I2SMOD); + + return 0; +} + +static int i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + u32 mod = readl(i2s->addr + I2SMOD); + + if (!is_secondary(i2s)) + mod &= ~(MOD_DC2_EN | MOD_DC1_EN); + + switch (params_channels(params)) { + case 6: + mod |= MOD_DC2_EN; + case 4: + mod |= MOD_DC1_EN; + break; + case 2: + break; + default: + dev_err(&i2s->pdev->dev, "%d channels not supported\n", + params_channels(params)); + return -EINVAL; + } + + if (is_secondary(i2s)) + mod &= ~MOD_BLCS_MASK; + else + mod &= ~MOD_BLCP_MASK; + + if (is_manager(i2s)) + mod &= ~MOD_BLC_MASK; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + if (is_secondary(i2s)) + mod |= MOD_BLCS_8BIT; + else + mod |= MOD_BLCP_8BIT; + if (is_manager(i2s)) + mod |= MOD_BLC_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + if (is_secondary(i2s)) + mod |= MOD_BLCS_16BIT; + else + mod |= MOD_BLCP_16BIT; + if (is_manager(i2s)) + mod |= MOD_BLC_16BIT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + if (is_secondary(i2s)) + mod |= MOD_BLCS_24BIT; + else + mod |= MOD_BLCP_24BIT; + if (is_manager(i2s)) + mod |= MOD_BLC_24BIT; + break; + default: + dev_err(&i2s->pdev->dev, "Format(%d) not supported\n", + params_format(params)); + return -EINVAL; + } + writel(mod, i2s->addr + I2SMOD); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dai_set_dma_data(dai, substream, + (void *)&i2s->dma_playback); + else + snd_soc_dai_set_dma_data(dai, substream, + (void *)&i2s->dma_capture); + + i2s->frmclk = params_rate(params); + + return 0; +} + +/* We set constraints on the substream acc to the version of I2S */ +static int i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + unsigned long flags; + + spin_lock_irqsave(&lock, flags); + + i2s->mode |= DAI_OPENED; + + if (is_manager(other)) + i2s->mode &= ~DAI_MANAGER; + else + i2s->mode |= DAI_MANAGER; + + /* Enforce set_sysclk in Master mode */ + i2s->rclk_srcrate = 0; + + spin_unlock_irqrestore(&lock, flags); + + return 0; +} + +static void i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + unsigned long flags; + + spin_lock_irqsave(&lock, flags); + + i2s->mode &= ~DAI_OPENED; + i2s->mode &= ~DAI_MANAGER; + + if (is_opened(other)) + other->mode |= DAI_MANAGER; + + /* Reset any constraint on RFS and BFS */ + i2s->rfs = 0; + i2s->bfs = 0; + + spin_unlock_irqrestore(&lock, flags); + + /* Gate CDCLK by default */ + if (!is_opened(other)) + i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK, + 0, SND_SOC_CLOCK_IN); +} + +static int config_setup(struct i2s_dai *i2s) +{ + struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + unsigned rfs, bfs, blc; + u32 psr; + + blc = get_blc(i2s); + + bfs = i2s->bfs; + + if (!bfs && other) + bfs = other->bfs; + + /* Select least possible multiple(2) if no constraint set */ + if (!bfs) + bfs = blc * 2; + + rfs = i2s->rfs; + + if (!rfs && other) + rfs = other->rfs; + + if ((rfs == 256 || rfs == 512) && (blc == 24)) { + dev_err(&i2s->pdev->dev, + "%d-RFS not supported for 24-blc\n", rfs); + return -EINVAL; + } + + if (!rfs) { + if (bfs == 16 || bfs == 32) + rfs = 256; + else + rfs = 384; + } + + /* If already setup and running */ + if (any_active(i2s) && (get_rfs(i2s) != rfs || get_bfs(i2s) != bfs)) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + return -EAGAIN; + } + + /* Don't bother RFS, BFS & PSR in Slave mode */ + if (is_slave(i2s)) + return 0; + + set_bfs(i2s, bfs); + set_rfs(i2s, rfs); + + if (!(i2s->quirks & QUIRK_NO_MUXPSR)) { + psr = i2s->rclk_srcrate / i2s->frmclk / rfs; + writel(((psr - 1) << 8) | PSR_PSREN, i2s->addr + I2SPSR); + dev_dbg(&i2s->pdev->dev, + "RCLK_SRC=%luHz PSR=%u, RCLK=%dfs, BCLK=%dfs\n", + i2s->rclk_srcrate, psr, rfs, bfs); + } + + return 0; +} + +static int i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct i2s_dai *i2s = to_info(rtd->cpu_dai); + unsigned long flags; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + local_irq_save(flags); + + if (config_setup(i2s)) { + local_irq_restore(flags); + return -EINVAL; + } + + if (capture) + i2s_rxctrl(i2s, 1); + else + i2s_txctrl(i2s, 1); + + local_irq_restore(flags); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + local_irq_save(flags); + + if (capture) + i2s_rxctrl(i2s, 0); + else + i2s_txctrl(i2s, 0); + + if (capture) + i2s_fifo(i2s, FIC_RXFLUSH); + else + i2s_fifo(i2s, FIC_TXFLUSH); + + local_irq_restore(flags); + break; + } + + return 0; +} + +static int i2s_set_clkdiv(struct snd_soc_dai *dai, + int div_id, int div) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + + switch (div_id) { + case SAMSUNG_I2S_DIV_BCLK: + if ((any_active(i2s) && div && (get_bfs(i2s) != div)) + || (other && other->bfs && (other->bfs != div))) { + dev_err(&i2s->pdev->dev, + "%s:%d Other DAI busy\n", __func__, __LINE__); + return -EAGAIN; + } + i2s->bfs = div; + break; + default: + dev_err(&i2s->pdev->dev, + "Invalid clock divider(%d)\n", div_id); + return -EINVAL; + } + + return 0; +} + +static snd_pcm_sframes_t +i2s_delay(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + u32 reg = readl(i2s->addr + I2SFIC); + snd_pcm_sframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + delay = FIC_RXCOUNT(reg); + else if (is_secondary(i2s)) + delay = FICS_TXCOUNT(readl(i2s->addr + I2SFICS)); + else + delay = FIC_TXCOUNT(reg); + + return delay; +} + +#ifdef CONFIG_PM +static int i2s_suspend(struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + + if (dai->active) { + i2s->suspend_i2smod = readl(i2s->addr + I2SMOD); + i2s->suspend_i2scon = readl(i2s->addr + I2SCON); + i2s->suspend_i2spsr = readl(i2s->addr + I2SPSR); + } + + return 0; +} + +static int i2s_resume(struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + + if (dai->active) { + writel(i2s->suspend_i2scon, i2s->addr + I2SCON); + writel(i2s->suspend_i2smod, i2s->addr + I2SMOD); + writel(i2s->suspend_i2spsr, i2s->addr + I2SPSR); + } + + return 0; +} +#else +#define i2s_suspend NULL +#define i2s_resume NULL +#endif + +static int samsung_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = to_info(dai); + struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + + if (other && other->clk) /* If this is probe on secondary */ + goto probe_exit; + + i2s->addr = ioremap(i2s->base, 0x100); + if (i2s->addr == NULL) { + dev_err(&i2s->pdev->dev, "cannot ioremap registers\n"); + return -ENXIO; + } + + i2s->clk = clk_get(&i2s->pdev->dev, "iis"); + if (IS_ERR(i2s->clk)) { + dev_err(&i2s->pdev->dev, "failed to get i2s_clock\n"); + iounmap(i2s->addr); + return -ENOENT; + } + clk_enable(i2s->clk); + + if (other) { + other->addr = i2s->addr; + other->clk = i2s->clk; + } + + if (i2s->quirks & QUIRK_NEED_RSTCLR) + writel(CON_RSTCLR, i2s->addr + I2SCON); + +probe_exit: + /* Reset any constraint on RFS and BFS */ + i2s->rfs = 0; + i2s->bfs = 0; + i2s_txctrl(i2s, 0); + i2s_rxctrl(i2s, 0); + i2s_fifo(i2s, FIC_TXFLUSH); + i2s_fifo(other, FIC_TXFLUSH); + i2s_fifo(i2s, FIC_RXFLUSH); + + /* Gate CDCLK by default */ + if (!is_opened(other)) + i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK, + 0, SND_SOC_CLOCK_IN); + + return 0; +} + +static int samsung_i2s_dai_remove(struct snd_soc_dai *dai) +{ + struct i2s_dai *i2s = snd_soc_dai_get_drvdata(dai); + struct i2s_dai *other = i2s->pri_dai ? : i2s->sec_dai; + + if (!other || !other->clk) { + + if (i2s->quirks & QUIRK_NEED_RSTCLR) + writel(0, i2s->addr + I2SCON); + + clk_disable(i2s->clk); + clk_put(i2s->clk); + + iounmap(i2s->addr); + } + + i2s->clk = NULL; + + return 0; +} + +static struct snd_soc_dai_ops samsung_i2s_dai_ops = { + .trigger = i2s_trigger, + .hw_params = i2s_hw_params, + .set_fmt = i2s_set_fmt, + .set_clkdiv = i2s_set_clkdiv, + .set_sysclk = i2s_set_sysclk, + .startup = i2s_startup, + .shutdown = i2s_shutdown, + .delay = i2s_delay, +}; + +#define SAMSUNG_I2S_RATES SNDRV_PCM_RATE_8000_96000 + +#define SAMSUNG_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static __devinit +struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev, bool sec) +{ + struct i2s_dai *i2s; + + i2s = kzalloc(sizeof(struct i2s_dai), GFP_KERNEL); + if (i2s == NULL) + return NULL; + + i2s->pdev = pdev; + i2s->pri_dai = NULL; + i2s->sec_dai = NULL; + i2s->i2s_dai_drv.symmetric_rates = 1; + i2s->i2s_dai_drv.probe = samsung_i2s_dai_probe; + i2s->i2s_dai_drv.remove = samsung_i2s_dai_remove; + i2s->i2s_dai_drv.ops = &samsung_i2s_dai_ops; + i2s->i2s_dai_drv.suspend = i2s_suspend; + i2s->i2s_dai_drv.resume = i2s_resume; + i2s->i2s_dai_drv.playback.channels_min = 2; + i2s->i2s_dai_drv.playback.channels_max = 2; + i2s->i2s_dai_drv.playback.rates = SAMSUNG_I2S_RATES; + i2s->i2s_dai_drv.playback.formats = SAMSUNG_I2S_FMTS; + + if (!sec) { + i2s->i2s_dai_drv.capture.channels_min = 2; + i2s->i2s_dai_drv.capture.channels_max = 2; + i2s->i2s_dai_drv.capture.rates = SAMSUNG_I2S_RATES; + i2s->i2s_dai_drv.capture.formats = SAMSUNG_I2S_FMTS; + } else { /* Create a new platform_device for Secondary */ + i2s->pdev = platform_device_register_resndata(NULL, + pdev->name, pdev->id + SAMSUNG_I2S_SECOFF, + NULL, 0, NULL, 0); + if (IS_ERR(i2s->pdev)) { + kfree(i2s); + return NULL; + } + } + + /* Pre-assign snd_soc_dai_set_drvdata */ + dev_set_drvdata(&i2s->pdev->dev, i2s); + + return i2s; +} + +static __devinit int samsung_i2s_probe(struct platform_device *pdev) +{ + u32 dma_pl_chan, dma_cp_chan, dma_pl_sec_chan; + struct i2s_dai *pri_dai, *sec_dai = NULL; + struct s3c_audio_pdata *i2s_pdata; + struct samsung_i2s *i2s_cfg; + struct resource *res; + u32 regs_base, quirks; + int ret = 0; + + /* Call during Seconday interface registration */ + if (pdev->id >= SAMSUNG_I2S_SECOFF) { + sec_dai = dev_get_drvdata(&pdev->dev); + snd_soc_register_dai(&sec_dai->pdev->dev, + &sec_dai->i2s_dai_drv); + return 0; + } + + i2s_pdata = pdev->dev.platform_data; + if (i2s_pdata == NULL) { + dev_err(&pdev->dev, "Can't work without s3c_audio_pdata\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n"); + return -ENXIO; + } + dma_pl_chan = res->start; + + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!res) { + dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n"); + return -ENXIO; + } + dma_cp_chan = res->start; + + res = platform_get_resource(pdev, IORESOURCE_DMA, 2); + if (res) + dma_pl_sec_chan = res->start; + else + dma_pl_sec_chan = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Unable to get I2S SFR address\n"); + return -ENXIO; + } + + if (!request_mem_region(res->start, resource_size(res), + "samsung-i2s")) { + dev_err(&pdev->dev, "Unable to request SFR region\n"); + return -EBUSY; + } + regs_base = res->start; + + i2s_cfg = &i2s_pdata->type.i2s; + quirks = i2s_cfg->quirks; + + pri_dai = i2s_alloc_dai(pdev, false); + if (!pri_dai) { + dev_err(&pdev->dev, "Unable to alloc I2S_pri\n"); + ret = -ENOMEM; + goto err1; + } + + pri_dai->dma_playback.dma_addr = regs_base + I2STXD; + pri_dai->dma_capture.dma_addr = regs_base + I2SRXD; + pri_dai->dma_playback.client = + (struct s3c2410_dma_client *)&pri_dai->dma_playback; + pri_dai->dma_capture.client = + (struct s3c2410_dma_client *)&pri_dai->dma_capture; + pri_dai->dma_playback.channel = dma_pl_chan; + pri_dai->dma_capture.channel = dma_cp_chan; + pri_dai->src_clk = i2s_cfg->src_clk; + pri_dai->dma_playback.dma_size = 4; + pri_dai->dma_capture.dma_size = 4; + pri_dai->base = regs_base; + pri_dai->quirks = quirks; + + if (quirks & QUIRK_PRI_6CHAN) + pri_dai->i2s_dai_drv.playback.channels_max = 6; + + if (quirks & QUIRK_SEC_DAI) { + sec_dai = i2s_alloc_dai(pdev, true); + if (!sec_dai) { + dev_err(&pdev->dev, "Unable to alloc I2S_sec\n"); + ret = -ENOMEM; + goto err2; + } + sec_dai->dma_playback.dma_addr = regs_base + I2STXDS; + sec_dai->dma_playback.client = + (struct s3c2410_dma_client *)&sec_dai->dma_playback; + /* Use iDMA always if SysDMA not provided */ + sec_dai->dma_playback.channel = dma_pl_sec_chan ? : -1; + sec_dai->src_clk = i2s_cfg->src_clk; + sec_dai->dma_playback.dma_size = 4; + sec_dai->base = regs_base; + sec_dai->quirks = quirks; + sec_dai->pri_dai = pri_dai; + pri_dai->sec_dai = sec_dai; + } + + if (i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + ret = -EINVAL; + goto err3; + } + + snd_soc_register_dai(&pri_dai->pdev->dev, &pri_dai->i2s_dai_drv); + + return 0; +err3: + kfree(sec_dai); +err2: + kfree(pri_dai); +err1: + release_mem_region(regs_base, resource_size(res)); + + return ret; +} + +static __devexit int samsung_i2s_remove(struct platform_device *pdev) +{ + struct i2s_dai *i2s, *other; + + i2s = dev_get_drvdata(&pdev->dev); + other = i2s->pri_dai ? : i2s->sec_dai; + + if (other) { + other->pri_dai = NULL; + other->sec_dai = NULL; + } else { + struct resource *res; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + } + + i2s->pri_dai = NULL; + i2s->sec_dai = NULL; + + kfree(i2s); + + snd_soc_unregister_dai(&pdev->dev); + + return 0; +} + +static struct platform_driver samsung_i2s_driver = { + .probe = samsung_i2s_probe, + .remove = samsung_i2s_remove, + .driver = { + .name = "samsung-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init samsung_i2s_init(void) +{ + return platform_driver_register(&samsung_i2s_driver); +} +module_init(samsung_i2s_init); + +static void __exit samsung_i2s_exit(void) +{ + platform_driver_unregister(&samsung_i2s_driver); +} +module_exit(samsung_i2s_exit); + +/* Module information */ +MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>"); +MODULE_DESCRIPTION("Samsung I2S Interface"); +MODULE_ALIAS("platform:samsung-i2s"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/i2s.h b/sound/soc/samsung/i2s.h new file mode 100644 index 00000000000..8e15f6a616d --- /dev/null +++ b/sound/soc/samsung/i2s.h @@ -0,0 +1,29 @@ +/* sound/soc/samsung/i2s.h + * + * ALSA SoC Audio Layer - Samsung I2S Controller driver + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd. + * Jaswinder Singh <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 version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_SAMSUNG_I2S_H +#define __SND_SOC_SAMSUNG_I2S_H + +/* + * Maximum number of I2S blocks that any SoC can have. + * The secondary interface of a CPU dai(if there exists any), + * is indexed at [cpu-dai's ID + SAMSUNG_I2S_SECOFF] + */ +#define SAMSUNG_I2S_SECOFF 4 + +#define SAMSUNG_I2S_DIV_BCLK 1 + +#define SAMSUNG_I2S_RCLKSRC_0 0 +#define SAMSUNG_I2S_RCLKSRC_1 1 +#define SAMSUNG_I2S_CDCLK 2 + +#endif /* __SND_SOC_SAMSUNG_I2S_H */ diff --git a/sound/soc/samsung/jive_wm8750.c b/sound/soc/samsung/jive_wm8750.c new file mode 100644 index 00000000000..08802520e01 --- /dev/null +++ b/sound/soc/samsung/jive_wm8750.c @@ -0,0 +1,191 @@ +/* sound/soc/samsung/jive_wm8750.c + * + * Copyright 2007,2008 Simtec Electronics + * + * Based on sound/soc/pxa/spitz.c + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <asm/mach-types.h> + +#include "dma.h" +#include "s3c2412-i2s.h" + +#include "../codecs/wm8750.h" + +static const struct snd_soc_dapm_route audio_map[] = { + { "Headphone Jack", NULL, "LOUT1" }, + { "Headphone Jack", NULL, "ROUT1" }, + { "Internal Speaker", NULL, "LOUT2" }, + { "Internal Speaker", NULL, "ROUT2" }, + { "LINPUT1", NULL, "Line Input" }, + { "RINPUT1", NULL, "Line Input" }, +}; + +static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Internal Speaker", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static int jive_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct s3c_i2sv2_rate_calc div; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params), + s3c_i2sv2_get_clock(cpu_dai)); + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_RCLK, div.fs_div); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_PRESCALER, + div.clk_div - 1); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops jive_ops = { + .hw_params = jive_hw_params, +}; + +static int jive_wm8750_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int err; + + /* These endpoints are not being used. */ + snd_soc_dapm_nc_pin(dapm, "LINPUT2"); + snd_soc_dapm_nc_pin(dapm, "RINPUT2"); + snd_soc_dapm_nc_pin(dapm, "LINPUT3"); + snd_soc_dapm_nc_pin(dapm, "RINPUT3"); + snd_soc_dapm_nc_pin(dapm, "OUT3"); + snd_soc_dapm_nc_pin(dapm, "MONO"); + + /* Add jive specific widgets */ + err = snd_soc_dapm_new_controls(dapm, wm8750_dapm_widgets, + ARRAY_SIZE(wm8750_dapm_widgets)); + if (err) { + printk(KERN_ERR "%s: failed to add widgets (%d)\n", + __func__, err); + return err; + } + + snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + snd_soc_dapm_sync(dapm); + + return 0; +} + +static struct snd_soc_dai_link jive_dai = { + .name = "wm8750", + .stream_name = "WM8750", + .cpu_dai_name = "s3c2412-i2s", + .codec_dai_name = "wm8750-hifi", + .platform_name = "samsung-audio", + .codec_name = "wm8750-codec.0-0x1a", + .init = jive_wm8750_init, + .ops = &jive_ops, +}; + +/* jive audio machine driver */ +static struct snd_soc_card snd_soc_machine_jive = { + .name = "Jive", + .dai_link = &jive_dai, + .num_links = 1, +}; + +static struct platform_device *jive_snd_device; + +static int __init jive_init(void) +{ + int ret; + + if (!machine_is_jive()) + return 0; + + printk("JIVE WM8750 Audio support\n"); + + jive_snd_device = platform_device_alloc("soc-audio", -1); + if (!jive_snd_device) + return -ENOMEM; + + platform_set_drvdata(jive_snd_device, &snd_soc_machine_jive); + ret = platform_device_add(jive_snd_device); + + if (ret) + platform_device_put(jive_snd_device); + + return ret; +} + +static void __exit jive_exit(void) +{ + platform_device_unregister(jive_snd_device); +} + +module_init(jive_init); +module_exit(jive_exit); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("ALSA SoC Jive Audio support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/lm4857.h b/sound/soc/samsung/lm4857.h new file mode 100644 index 00000000000..0cf5b7011d6 --- /dev/null +++ b/sound/soc/samsung/lm4857.h @@ -0,0 +1,32 @@ +/* + * lm4857.h -- ALSA Soc Audio Layer + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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. + * + * Revision history + * 18th Jun 2007 Initial version. + */ + +#ifndef LM4857_H_ +#define LM4857_H_ + +/* The register offsets in the cache array */ +#define LM4857_MVOL 0 +#define LM4857_LVOL 1 +#define LM4857_RVOL 2 +#define LM4857_CTRL 3 + +/* the shifts required to set these bits */ +#define LM4857_3D 5 +#define LM4857_WAKEUP 5 +#define LM4857_EPGAIN 4 + +#endif /*LM4857_H_*/ + diff --git a/sound/soc/samsung/ln2440sbc_alc650.c b/sound/soc/samsung/ln2440sbc_alc650.c new file mode 100644 index 00000000000..a2bb34def74 --- /dev/null +++ b/sound/soc/samsung/ln2440sbc_alc650.c @@ -0,0 +1,77 @@ +/* + * SoC audio for ln2440sbc + * + * Copyright 2007 KonekTel, a.s. + * Author: Ivan Kuten + * ivan.kuten@promwad.com + * + * Heavily based on smdk2443_wm9710.c + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include "dma.h" +#include "ac97.h" + +static struct snd_soc_card ln2440sbc; + +static struct snd_soc_dai_link ln2440sbc_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "samsung-ac97", + .codec_dai_name = "ac97-hifi", + .codec_name = "ac97-codec", + .platform_name = "samsung-audio", +}, +}; + +static struct snd_soc_card ln2440sbc = { + .name = "LN2440SBC", + .dai_link = ln2440sbc_dai, + .num_links = ARRAY_SIZE(ln2440sbc_dai), +}; + +static struct platform_device *ln2440sbc_snd_ac97_device; + +static int __init ln2440sbc_init(void) +{ + int ret; + + ln2440sbc_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!ln2440sbc_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(ln2440sbc_snd_ac97_device, &ln2440sbc); + ret = platform_device_add(ln2440sbc_snd_ac97_device); + + if (ret) + platform_device_put(ln2440sbc_snd_ac97_device); + + return ret; +} + +static void __exit ln2440sbc_exit(void) +{ + platform_device_unregister(ln2440sbc_snd_ac97_device); +} + +module_init(ln2440sbc_init); +module_exit(ln2440sbc_exit); + +/* Module information */ +MODULE_AUTHOR("Ivan Kuten"); +MODULE_DESCRIPTION("ALSA SoC ALC650 LN2440SBC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/neo1973_gta02_wm8753.c b/sound/soc/samsung/neo1973_gta02_wm8753.c new file mode 100644 index 00000000000..3eec610c10f --- /dev/null +++ b/sound/soc/samsung/neo1973_gta02_wm8753.c @@ -0,0 +1,504 @@ +/* + * neo1973_gta02_wm8753.c -- SoC audio for Openmoko Freerunner(GTA02) + * + * Copyright 2007 Openmoko Inc + * Author: Graeme Gregory <graeme@openmoko.org> + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory <linux@wolfsonmicro.com> + * Copyright 2009 Wolfson Microelectronics + * + * 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/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <asm/mach-types.h> + +#include <plat/regs-iis.h> + +#include <mach/regs-clock.h> +#include <asm/io.h> +#include <mach/gta02.h> +#include "../codecs/wm8753.h" +#include "dma.h" +#include "s3c24xx-i2s.h" + +static struct snd_soc_card neo1973_gta02; + +static int neo1973_gta02_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int pll_out = 0, bclk = 0; + int ret = 0; + unsigned long iis_clkrate; + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + switch (params_rate(params)) { + case 8000: + case 16000: + pll_out = 12288000; + break; + case 48000: + bclk = WM8753_BCLK_DIV_4; + pll_out = 12288000; + break; + case 96000: + bclk = WM8753_BCLK_DIV_2; + pll_out = 12288000; + break; + case 11025: + bclk = WM8753_BCLK_DIV_16; + pll_out = 11289600; + break; + case 22050: + bclk = WM8753_BCLK_DIV_8; + pll_out = 11289600; + break; + case 44100: + bclk = WM8753_BCLK_DIV_4; + pll_out = 11289600; + break; + case 88200: + bclk = WM8753_BCLK_DIV_2; + pll_out = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(codec_dai, + WM8753_BCLKDIV, bclk); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(4, 4)); + if (ret < 0) + return ret; + + /* codec PLL input is PCLK/4 */ + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, + iis_clkrate / 4, pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_gta02_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + /* disable the PLL */ + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0); +} + +/* + * Neo1973 WM8753 HiFi DAI opserations. + */ +static struct snd_soc_ops neo1973_gta02_hifi_ops = { + .hw_params = neo1973_gta02_hifi_hw_params, + .hw_free = neo1973_gta02_hifi_hw_free, +}; + +static int neo1973_gta02_voice_hw_params( + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pcmdiv = 0; + int ret = 0; + unsigned long iis_clkrate; + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + if (params_rate(params) != 8000) + return -EINVAL; + if (params_channels(params) != 1) + return -EINVAL; + + pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ + + /* todo: gg check mode (DSP_B) against CSR datasheet */ + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, + 12288000, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec PCM division for sample rate */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, + pcmdiv); + if (ret < 0) + return ret; + + /* configure and enable PLL for 12.288MHz output */ + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, + iis_clkrate / 4, 12288000); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_gta02_voice_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + /* disable the PLL */ + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0); +} + +static struct snd_soc_ops neo1973_gta02_voice_ops = { + .hw_params = neo1973_gta02_voice_hw_params, + .hw_free = neo1973_gta02_voice_hw_free, +}; + +#define LM4853_AMP 1 +#define LM4853_SPK 2 + +static u8 lm4853_state; + +/* This has no effect, it exists only to maintain compatibility with + * existing ALSA state files. + */ +static int lm4853_set_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val = ucontrol->value.integer.value[0]; + + if (val) + lm4853_state |= LM4853_AMP; + else + lm4853_state &= ~LM4853_AMP; + + return 0; +} + +static int lm4853_get_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = lm4853_state & LM4853_AMP; + + return 0; +} + +static int lm4853_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val = ucontrol->value.integer.value[0]; + + if (val) { + lm4853_state |= LM4853_SPK; + gpio_set_value(GTA02_GPIO_HP_IN, 0); + } else { + lm4853_state &= ~LM4853_SPK; + gpio_set_value(GTA02_GPIO_HP_IN, 1); + } + + return 0; +} + +static int lm4853_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = (lm4853_state & LM4853_SPK) >> 1; + + return 0; +} + +static int lm4853_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, + int event) +{ + gpio_set_value(GTA02_GPIO_AMP_SHUT, SND_SOC_DAPM_EVENT_OFF(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Stereo Out", lm4853_event), + SND_SOC_DAPM_LINE("GSM Line Out", NULL), + SND_SOC_DAPM_LINE("GSM Line In", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_SPK("Handset Spk", NULL), +}; + + +/* example machine audio_mapnections */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* Connections to the lm4853 amp */ + {"Stereo Out", NULL, "LOUT1"}, + {"Stereo Out", NULL, "ROUT1"}, + + /* Connections to the GSM Module */ + {"GSM Line Out", NULL, "MONO1"}, + {"GSM Line Out", NULL, "MONO2"}, + {"RXP", NULL, "GSM Line In"}, + {"RXN", NULL, "GSM Line In"}, + + /* Connections to Headset */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Mic"}, + + /* Call Mic */ + {"MIC2", NULL, "Mic Bias"}, + {"MIC2N", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Handset Mic"}, + + /* Call Speaker */ + {"Handset Spk", NULL, "LOUT2"}, + {"Handset Spk", NULL, "ROUT2"}, + + /* Connect the ALC pins */ + {"ACIN", NULL, "ACOP"}, +}; + +static const struct snd_kcontrol_new wm8753_neo1973_gta02_controls[] = { + SOC_DAPM_PIN_SWITCH("Stereo Out"), + SOC_DAPM_PIN_SWITCH("GSM Line Out"), + SOC_DAPM_PIN_SWITCH("GSM Line In"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Handset Mic"), + SOC_DAPM_PIN_SWITCH("Handset Spk"), + + /* This has no effect, it exists only to maintain compatibility with + * existing ALSA state files. + */ + SOC_SINGLE_EXT("Amp State Switch", 6, 0, 1, 0, + lm4853_get_state, + lm4853_set_state), + SOC_SINGLE_EXT("Amp Spk Switch", 7, 0, 1, 0, + lm4853_get_spk, + lm4853_set_spk), +}; + +/* + * This is an example machine initialisation for a wm8753 connected to a + * neo1973 GTA02. + */ +static int neo1973_gta02_wm8753_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int err; + + /* set up NC codec pins */ + snd_soc_dapm_nc_pin(dapm, "OUT3"); + snd_soc_dapm_nc_pin(dapm, "OUT4"); + snd_soc_dapm_nc_pin(dapm, "LINE1"); + snd_soc_dapm_nc_pin(dapm, "LINE2"); + + /* Add neo1973 gta02 specific widgets */ + snd_soc_dapm_new_controls(dapm, wm8753_dapm_widgets, + ARRAY_SIZE(wm8753_dapm_widgets)); + + /* add neo1973 gta02 specific controls */ + err = snd_soc_add_controls(codec, wm8753_neo1973_gta02_controls, + ARRAY_SIZE(wm8753_neo1973_gta02_controls)); + + if (err < 0) + return err; + + /* set up neo1973 gta02 specific audio path audio_map */ + snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + + /* set endpoints to default off mode */ + snd_soc_dapm_disable_pin(dapm, "Stereo Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Handset Mic"); + snd_soc_dapm_disable_pin(dapm, "Handset Spk"); + + /* allow audio paths from the GSM modem to run during suspend */ + snd_soc_dapm_ignore_suspend(dapm, "Stereo Out"); + snd_soc_dapm_ignore_suspend(dapm, "GSM Line Out"); + snd_soc_dapm_ignore_suspend(dapm, "GSM Line In"); + snd_soc_dapm_ignore_suspend(dapm, "Headset Mic"); + snd_soc_dapm_ignore_suspend(dapm, "Handset Mic"); + snd_soc_dapm_ignore_suspend(dapm, "Handset Spk"); + + snd_soc_dapm_sync(dapm); + + return 0; +} + +/* + * BT Codec DAI + */ +static struct snd_soc_dai_driver bt_dai = { + .name = "bluetooth-dai", + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, +}; + +static struct snd_soc_dai_link neo1973_gta02_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .cpu_dai_name = "s3c24xx-i2s", + .codec_dai_name = "wm8753-hifi", + .init = neo1973_gta02_wm8753_init, + .platform_name = "samsung-audio", + .codec_name = "wm8753-codec.0-0x1a", + .ops = &neo1973_gta02_hifi_ops, +}, +{ /* Voice via BT */ + .name = "Bluetooth", + .stream_name = "Voice", + .cpu_dai_name = "bluetooth-dai", + .codec_dai_name = "wm8753-voice", + .ops = &neo1973_gta02_voice_ops, + .codec_name = "wm8753-codec.0-0x1a", + .platform_name = "samsung-audio", +}, +}; + +static struct snd_soc_card neo1973_gta02 = { + .name = "neo1973-gta02", + .dai_link = neo1973_gta02_dai, + .num_links = ARRAY_SIZE(neo1973_gta02_dai), +}; + +static struct platform_device *neo1973_gta02_snd_device; + +static int __init neo1973_gta02_init(void) +{ + int ret; + + if (!machine_is_neo1973_gta02()) { + printk(KERN_INFO + "Only GTA02 is supported by this ASoC driver\n"); + return -ENODEV; + } + + neo1973_gta02_snd_device = platform_device_alloc("soc-audio", -1); + if (!neo1973_gta02_snd_device) + return -ENOMEM; + + /* register bluetooth DAI here */ + ret = snd_soc_register_dai(&neo1973_gta02_snd_device->dev, &bt_dai); + if (ret) + goto err_put_device; + + platform_set_drvdata(neo1973_gta02_snd_device, &neo1973_gta02); + ret = platform_device_add(neo1973_gta02_snd_device); + + if (ret) + goto err_unregister_dai; + + /* Initialise GPIOs used by amp */ + ret = gpio_request(GTA02_GPIO_HP_IN, "GTA02_HP_IN"); + if (ret) { + pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_HP_IN); + goto err_del_device; + } + + ret = gpio_direction_output(GTA02_GPIO_HP_IN, 1); + if (ret) { + pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_HP_IN); + goto err_free_gpio_hp_in; + } + + ret = gpio_request(GTA02_GPIO_AMP_SHUT, "GTA02_AMP_SHUT"); + if (ret) { + pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_AMP_SHUT); + goto err_free_gpio_hp_in; + } + + ret = gpio_direction_output(GTA02_GPIO_AMP_SHUT, 1); + if (ret) { + pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_AMP_SHUT); + goto err_free_gpio_amp_shut; + } + + return 0; + +err_free_gpio_amp_shut: + gpio_free(GTA02_GPIO_AMP_SHUT); +err_free_gpio_hp_in: + gpio_free(GTA02_GPIO_HP_IN); +err_del_device: + platform_device_del(neo1973_gta02_snd_device); +err_unregister_dai: + snd_soc_unregister_dai(&neo1973_gta02_snd_device->dev); +err_put_device: + platform_device_put(neo1973_gta02_snd_device); + return ret; +} +module_init(neo1973_gta02_init); + +static void __exit neo1973_gta02_exit(void) +{ + snd_soc_unregister_dai(&neo1973_gta02_snd_device->dev); + platform_device_unregister(neo1973_gta02_snd_device); + gpio_free(GTA02_GPIO_HP_IN); + gpio_free(GTA02_GPIO_AMP_SHUT); +} +module_exit(neo1973_gta02_exit); + +/* Module information */ +MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org"); +MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 GTA02"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/neo1973_wm8753.c b/sound/soc/samsung/neo1973_wm8753.c new file mode 100644 index 00000000000..c7a24514beb --- /dev/null +++ b/sound/soc/samsung/neo1973_wm8753.c @@ -0,0 +1,706 @@ +/* + * neo1973_wm8753.c -- SoC audio for Neo1973 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +#include <asm/mach-types.h> +#include <asm/hardware/scoop.h> +#include <mach/regs-clock.h> +#include <mach/regs-gpio.h> +#include <mach/hardware.h> +#include <linux/io.h> +#include <mach/spi-gpio.h> + +#include <plat/regs-iis.h> + +#include "../codecs/wm8753.h" +#include "lm4857.h" +#include "dma.h" +#include "s3c24xx-i2s.h" + +/* define the scenarios */ +#define NEO_AUDIO_OFF 0 +#define NEO_GSM_CALL_AUDIO_HANDSET 1 +#define NEO_GSM_CALL_AUDIO_HEADSET 2 +#define NEO_GSM_CALL_AUDIO_BLUETOOTH 3 +#define NEO_STEREO_TO_SPEAKERS 4 +#define NEO_STEREO_TO_HEADPHONES 5 +#define NEO_CAPTURE_HANDSET 6 +#define NEO_CAPTURE_HEADSET 7 +#define NEO_CAPTURE_BLUETOOTH 8 + +static struct snd_soc_card neo1973; +static struct i2c_client *i2c; + +static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int pll_out = 0, bclk = 0; + int ret = 0; + unsigned long iis_clkrate; + + pr_debug("Entered %s\n", __func__); + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + switch (params_rate(params)) { + case 8000: + case 16000: + pll_out = 12288000; + break; + case 48000: + bclk = WM8753_BCLK_DIV_4; + pll_out = 12288000; + break; + case 96000: + bclk = WM8753_BCLK_DIV_2; + pll_out = 12288000; + break; + case 11025: + bclk = WM8753_BCLK_DIV_16; + pll_out = 11289600; + break; + case 22050: + bclk = WM8753_BCLK_DIV_8; + pll_out = 11289600; + break; + case 44100: + bclk = WM8753_BCLK_DIV_4; + pll_out = 11289600; + break; + case 88200: + bclk = WM8753_BCLK_DIV_2; + pll_out = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(4, 4)); + if (ret < 0) + return ret; + + /* codec PLL input is PCLK/4 */ + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, + iis_clkrate / 4, pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + pr_debug("Entered %s\n", __func__); + + /* disable the PLL */ + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0); +} + +/* + * Neo1973 WM8753 HiFi DAI opserations. + */ +static struct snd_soc_ops neo1973_hifi_ops = { + .hw_params = neo1973_hifi_hw_params, + .hw_free = neo1973_hifi_hw_free, +}; + +static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pcmdiv = 0; + int ret = 0; + unsigned long iis_clkrate; + + pr_debug("Entered %s\n", __func__); + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + if (params_rate(params) != 8000) + return -EINVAL; + if (params_channels(params) != 1) + return -EINVAL; + + pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ + + /* todo: gg check mode (DSP_B) against CSR datasheet */ + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, 12288000, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec PCM division for sample rate */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv); + if (ret < 0) + return ret; + + /* configure and enable PLL for 12.288MHz output */ + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, + iis_clkrate / 4, 12288000); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_voice_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + pr_debug("Entered %s\n", __func__); + + /* disable the PLL */ + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0); +} + +static struct snd_soc_ops neo1973_voice_ops = { + .hw_params = neo1973_voice_hw_params, + .hw_free = neo1973_voice_hw_free, +}; + +static int neo1973_scenario; + +static int neo1973_get_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = neo1973_scenario; + return 0; +} + +static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("Entered %s\n", __func__); + + switch (neo1973_scenario) { + case NEO_AUDIO_OFF: + snd_soc_dapm_disable_pin(dapm, "Audio Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Call Mic"); + break; + case NEO_GSM_CALL_AUDIO_HANDSET: + snd_soc_dapm_enable_pin(dapm, "Audio Out"); + snd_soc_dapm_enable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_enable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_enable_pin(dapm, "Call Mic"); + break; + case NEO_GSM_CALL_AUDIO_HEADSET: + snd_soc_dapm_enable_pin(dapm, "Audio Out"); + snd_soc_dapm_enable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_enable_pin(dapm, "GSM Line In"); + snd_soc_dapm_enable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Call Mic"); + break; + case NEO_GSM_CALL_AUDIO_BLUETOOTH: + snd_soc_dapm_disable_pin(dapm, "Audio Out"); + snd_soc_dapm_enable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_enable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Call Mic"); + break; + case NEO_STEREO_TO_SPEAKERS: + snd_soc_dapm_enable_pin(dapm, "Audio Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Call Mic"); + break; + case NEO_STEREO_TO_HEADPHONES: + snd_soc_dapm_enable_pin(dapm, "Audio Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Call Mic"); + break; + case NEO_CAPTURE_HANDSET: + snd_soc_dapm_disable_pin(dapm, "Audio Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_enable_pin(dapm, "Call Mic"); + break; + case NEO_CAPTURE_HEADSET: + snd_soc_dapm_disable_pin(dapm, "Audio Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line In"); + snd_soc_dapm_enable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Call Mic"); + break; + case NEO_CAPTURE_BLUETOOTH: + snd_soc_dapm_disable_pin(dapm, "Audio Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Call Mic"); + break; + default: + snd_soc_dapm_disable_pin(dapm, "Audio Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line Out"); + snd_soc_dapm_disable_pin(dapm, "GSM Line In"); + snd_soc_dapm_disable_pin(dapm, "Headset Mic"); + snd_soc_dapm_disable_pin(dapm, "Call Mic"); + } + + snd_soc_dapm_sync(dapm); + + return 0; +} + +static int neo1973_set_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("Entered %s\n", __func__); + + if (neo1973_scenario == ucontrol->value.integer.value[0]) + return 0; + + neo1973_scenario = ucontrol->value.integer.value[0]; + set_scenario_endpoints(codec, neo1973_scenario); + return 1; +} + +static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0}; + +static void lm4857_write_regs(void) +{ + pr_debug("Entered %s\n", __func__); + + if (i2c_master_send(i2c, lm4857_regs, 4) != 4) + printk(KERN_ERR "lm4857: i2c write failed\n"); +} + +static int lm4857_get_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int shift = mc->shift; + int mask = mc->max; + + pr_debug("Entered %s\n", __func__); + + ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask; + return 0; +} + +static int lm4857_set_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int shift = mc->shift; + int mask = mc->max; + + if (((lm4857_regs[reg] >> shift) & mask) == + ucontrol->value.integer.value[0]) + return 0; + + lm4857_regs[reg] &= ~(mask << shift); + lm4857_regs[reg] |= ucontrol->value.integer.value[0] << shift; + lm4857_write_regs(); + return 1; +} + +static int lm4857_get_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = lm4857_regs[LM4857_CTRL] & 0x0F; + + pr_debug("Entered %s\n", __func__); + + if (value) + value -= 5; + + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int lm4857_set_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = ucontrol->value.integer.value[0]; + + pr_debug("Entered %s\n", __func__); + + if (value) + value += 5; + + if ((lm4857_regs[LM4857_CTRL] & 0x0F) == value) + return 0; + + lm4857_regs[LM4857_CTRL] &= 0xF0; + lm4857_regs[LM4857_CTRL] |= value; + lm4857_write_regs(); + return 1; +} + +static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Audio Out", NULL), + SND_SOC_DAPM_LINE("GSM Line Out", NULL), + SND_SOC_DAPM_LINE("GSM Line In", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Call Mic", NULL), +}; + + +static const struct snd_soc_dapm_route dapm_routes[] = { + + /* Connections to the lm4857 amp */ + {"Audio Out", NULL, "LOUT1"}, + {"Audio Out", NULL, "ROUT1"}, + + /* Connections to the GSM Module */ + {"GSM Line Out", NULL, "MONO1"}, + {"GSM Line Out", NULL, "MONO2"}, + {"RXP", NULL, "GSM Line In"}, + {"RXN", NULL, "GSM Line In"}, + + /* Connections to Headset */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Mic"}, + + /* Call Mic */ + {"MIC2", NULL, "Mic Bias"}, + {"MIC2N", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Call Mic"}, + + /* Connect the ALC pins */ + {"ACIN", NULL, "ACOP"}, +}; + +static const char *lm4857_mode[] = { + "Off", + "Call Speaker", + "Stereo Speakers", + "Stereo Speakers + Headphones", + "Headphones" +}; + +static const struct soc_enum lm4857_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode), +}; + +static const char *neo_scenarios[] = { + "Off", + "GSM Handset", + "GSM Headset", + "GSM Bluetooth", + "Speakers", + "Headphones", + "Capture Handset", + "Capture Headset", + "Capture Bluetooth" +}; + +static const struct soc_enum neo_scenario_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(neo_scenarios), neo_scenarios), +}; + +static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0); +static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0); + +static const struct snd_kcontrol_new wm8753_neo1973_controls[] = { + SOC_SINGLE_EXT_TLV("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg, stereo_tlv), + SOC_SINGLE_EXT_TLV("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg, stereo_tlv), + SOC_SINGLE_EXT_TLV("Amp Mono Playback Volume", LM4857_MVOL, 0, 31, 0, + lm4857_get_reg, lm4857_set_reg, mono_tlv), + SOC_ENUM_EXT("Amp Mode", lm4857_mode_enum[0], + lm4857_get_mode, lm4857_set_mode), + SOC_ENUM_EXT("Neo Mode", neo_scenario_enum[0], + neo1973_get_scenario, neo1973_set_scenario), + SOC_SINGLE_EXT("Amp Spk 3D Playback Switch", LM4857_LVOL, 5, 1, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp HP 3d Playback Switch", LM4857_RVOL, 5, 1, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Fast Wakeup Playback Switch", LM4857_CTRL, 5, 1, 0, + lm4857_get_reg, lm4857_set_reg), + SOC_SINGLE_EXT("Amp Earpiece 6dB Playback Switch", LM4857_CTRL, 4, 1, 0, + lm4857_get_reg, lm4857_set_reg), +}; + +/* + * This is an example machine initialisation for a wm8753 connected to a + * neo1973 II. It is missing logic to detect hp/mic insertions and logic + * to re-route the audio in such an event. + */ +static int neo1973_wm8753_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int err; + + pr_debug("Entered %s\n", __func__); + + /* set up NC codec pins */ + snd_soc_dapm_nc_pin(dapm, "LOUT2"); + snd_soc_dapm_nc_pin(dapm, "ROUT2"); + snd_soc_dapm_nc_pin(dapm, "OUT3"); + snd_soc_dapm_nc_pin(dapm, "OUT4"); + snd_soc_dapm_nc_pin(dapm, "LINE1"); + snd_soc_dapm_nc_pin(dapm, "LINE2"); + + /* Add neo1973 specific widgets */ + snd_soc_dapm_new_controls(dapm, wm8753_dapm_widgets, + ARRAY_SIZE(wm8753_dapm_widgets)); + + /* set endpoints to default mode */ + set_scenario_endpoints(codec, NEO_AUDIO_OFF); + + /* add neo1973 specific controls */ + err = snd_soc_add_controls(codec, wm8753_neo1973_controls, + ARRAY_SIZE(8753_neo1973_controls)); + if (err < 0) + return err; + + /* set up neo1973 specific audio routes */ + err = snd_soc_dapm_add_routes(dapm, dapm_routes, + ARRAY_SIZE(dapm_routes)); + + snd_soc_dapm_sync(dapm); + return 0; +} + +/* + * BT Codec DAI + */ +static struct snd_soc_dai bt_dai = { + .name = "bluetooth-dai", + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, +}; + +static struct snd_soc_dai_link neo1973_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .platform_name = "samsung-audio", + .cpu_dai_name = "s3c24xx-i2s", + .codec_dai_name = "wm8753-hifi", + .codec_name = "wm8753-codec.0-0x1a", + .init = neo1973_wm8753_init, + .ops = &neo1973_hifi_ops, +}, +{ /* Voice via BT */ + .name = "Bluetooth", + .stream_name = "Voice", + .platform_name = "samsung-audio", + .cpu_dai_name = "bluetooth-dai", + .codec_dai_name = "wm8753-voice", + .codec_name = "wm8753-codec.0-0x1a", + .ops = &neo1973_voice_ops, +}, +}; + +static struct snd_soc_card neo1973 = { + .name = "neo1973", + .dai_link = neo1973_dai, + .num_links = ARRAY_SIZE(neo1973_dai), +}; + +static int lm4857_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + pr_debug("Entered %s\n", __func__); + + i2c = client; + + lm4857_write_regs(); + return 0; +} + +static int lm4857_i2c_remove(struct i2c_client *client) +{ + pr_debug("Entered %s\n", __func__); + + i2c = NULL; + + return 0; +} + +static u8 lm4857_state; + +static int lm4857_suspend(struct i2c_client *dev, pm_message_t state) +{ + pr_debug("Entered %s\n", __func__); + + dev_dbg(&dev->dev, "lm4857_suspend\n"); + lm4857_state = lm4857_regs[LM4857_CTRL] & 0xf; + if (lm4857_state) { + lm4857_regs[LM4857_CTRL] &= 0xf0; + lm4857_write_regs(); + } + return 0; +} + +static int lm4857_resume(struct i2c_client *dev) +{ + pr_debug("Entered %s\n", __func__); + + if (lm4857_state) { + lm4857_regs[LM4857_CTRL] |= (lm4857_state & 0x0f); + lm4857_write_regs(); + } + return 0; +} + +static void lm4857_shutdown(struct i2c_client *dev) +{ + pr_debug("Entered %s\n", __func__); + + dev_dbg(&dev->dev, "lm4857_shutdown\n"); + lm4857_regs[LM4857_CTRL] &= 0xf0; + lm4857_write_regs(); +} + +static const struct i2c_device_id lm4857_i2c_id[] = { + { "neo1973_lm4857", 0 }, + { } +}; + +static struct i2c_driver lm4857_i2c_driver = { + .driver = { + .name = "LM4857 I2C Amp", + .owner = THIS_MODULE, + }, + .suspend = lm4857_suspend, + .resume = lm4857_resume, + .shutdown = lm4857_shutdown, + .probe = lm4857_i2c_probe, + .remove = lm4857_i2c_remove, + .id_table = lm4857_i2c_id, +}; + +static struct platform_device *neo1973_snd_device; + +static int __init neo1973_init(void) +{ + int ret; + + pr_debug("Entered %s\n", __func__); + + if (!machine_is_neo1973_gta01()) { + printk(KERN_INFO + "Only GTA01 hardware supported by ASoC driver\n"); + return -ENODEV; + } + + neo1973_snd_device = platform_device_alloc("soc-audio", -1); + if (!neo1973_snd_device) + return -ENOMEM; + + platform_set_drvdata(neo1973_snd_device, &neo1973); + ret = platform_device_add(neo1973_snd_device); + + if (ret) { + platform_device_put(neo1973_snd_device); + return ret; + } + + ret = i2c_add_driver(&lm4857_i2c_driver); + + if (ret != 0) + platform_device_unregister(neo1973_snd_device); + + return ret; +} + +static void __exit neo1973_exit(void) +{ + pr_debug("Entered %s\n", __func__); + + i2c_del_driver(&lm4857_i2c_driver); + platform_device_unregister(neo1973_snd_device); +} + +module_init(neo1973_init); +module_exit(neo1973_exit); + +/* Module information */ +MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org"); +MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c new file mode 100644 index 00000000000..48d0b750406 --- /dev/null +++ b/sound/soc/samsung/pcm.c @@ -0,0 +1,552 @@ +/* sound/soc/samsung/pcm.c + * + * ALSA SoC Audio Layer - S3C PCM-Controller driver + * + * Copyright (c) 2009 Samsung Electronics Co. Ltd + * Author: Jaswinder Singh <jassi.brar@samsung.com> + * based upon I2S drivers by Ben Dooks. + * + * 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/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/io.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <plat/audio.h> +#include <plat/dma.h> + +#include "dma.h" +#include "pcm.h" + +static struct s3c2410_dma_client s3c_pcm_dma_client_out = { + .name = "PCM Stereo out" +}; + +static struct s3c2410_dma_client s3c_pcm_dma_client_in = { + .name = "PCM Stereo in" +}; + +static struct s3c_dma_params s3c_pcm_stereo_out[] = { + [0] = { + .client = &s3c_pcm_dma_client_out, + .dma_size = 4, + }, + [1] = { + .client = &s3c_pcm_dma_client_out, + .dma_size = 4, + }, +}; + +static struct s3c_dma_params s3c_pcm_stereo_in[] = { + [0] = { + .client = &s3c_pcm_dma_client_in, + .dma_size = 4, + }, + [1] = { + .client = &s3c_pcm_dma_client_in, + .dma_size = 4, + }, +}; + +static struct s3c_pcm_info s3c_pcm[2]; + +static void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on) +{ + void __iomem *regs = pcm->regs; + u32 ctl, clkctl; + + clkctl = readl(regs + S3C_PCM_CLKCTL); + ctl = readl(regs + S3C_PCM_CTL); + ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK + << S3C_PCM_CTL_TXDIPSTICK_SHIFT); + + if (on) { + ctl |= S3C_PCM_CTL_TXDMA_EN; + ctl |= S3C_PCM_CTL_TXFIFO_EN; + ctl |= S3C_PCM_CTL_ENABLE; + ctl |= (0x4<<S3C_PCM_CTL_TXDIPSTICK_SHIFT); + clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; + } else { + ctl &= ~S3C_PCM_CTL_TXDMA_EN; + ctl &= ~S3C_PCM_CTL_TXFIFO_EN; + + if (!(ctl & S3C_PCM_CTL_RXFIFO_EN)) { + ctl &= ~S3C_PCM_CTL_ENABLE; + if (!pcm->idleclk) + clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; + } + } + + writel(clkctl, regs + S3C_PCM_CLKCTL); + writel(ctl, regs + S3C_PCM_CTL); +} + +static void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on) +{ + void __iomem *regs = pcm->regs; + u32 ctl, clkctl; + + ctl = readl(regs + S3C_PCM_CTL); + clkctl = readl(regs + S3C_PCM_CLKCTL); + ctl &= ~(S3C_PCM_CTL_RXDIPSTICK_MASK + << S3C_PCM_CTL_RXDIPSTICK_SHIFT); + + if (on) { + ctl |= S3C_PCM_CTL_RXDMA_EN; + ctl |= S3C_PCM_CTL_RXFIFO_EN; + ctl |= S3C_PCM_CTL_ENABLE; + ctl |= (0x20<<S3C_PCM_CTL_RXDIPSTICK_SHIFT); + clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; + } else { + ctl &= ~S3C_PCM_CTL_RXDMA_EN; + ctl &= ~S3C_PCM_CTL_RXFIFO_EN; + + if (!(ctl & S3C_PCM_CTL_TXFIFO_EN)) { + ctl &= ~S3C_PCM_CTL_ENABLE; + if (!pcm->idleclk) + clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; + } + } + + writel(clkctl, regs + S3C_PCM_CLKCTL); + writel(ctl, regs + S3C_PCM_CTL); +} + +static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai); + unsigned long flags; + + dev_dbg(pcm->dev, "Entered %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&pcm->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c_pcm_snd_rxctrl(pcm, 1); + else + s3c_pcm_snd_txctrl(pcm, 1); + + spin_unlock_irqrestore(&pcm->lock, flags); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&pcm->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c_pcm_snd_rxctrl(pcm, 0); + else + s3c_pcm_snd_txctrl(pcm, 0); + + spin_unlock_irqrestore(&pcm->lock, flags); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int s3c_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct s3c_dma_params *dma_data; + void __iomem *regs = pcm->regs; + struct clk *clk; + int sclk_div, sync_div; + unsigned long flags; + u32 clkctl; + + dev_dbg(pcm->dev, "Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = pcm->dma_playback; + else + dma_data = pcm->dma_capture; + + snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data); + + /* Strictly check for sample size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + return -EINVAL; + } + + spin_lock_irqsave(&pcm->lock, flags); + + /* Get hold of the PCMSOURCE_CLK */ + clkctl = readl(regs + S3C_PCM_CLKCTL); + if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK) + clk = pcm->pclk; + else + clk = pcm->cclk; + + /* Set the SCLK divider */ + sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs / + params_rate(params) / 2 - 1; + + clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK + << S3C_PCM_CLKCTL_SCLKDIV_SHIFT); + clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK) + << S3C_PCM_CLKCTL_SCLKDIV_SHIFT); + + /* Set the SYNC divider */ + sync_div = pcm->sclk_per_fs - 1; + + clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK + << S3C_PCM_CLKCTL_SYNCDIV_SHIFT); + clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK) + << S3C_PCM_CLKCTL_SYNCDIV_SHIFT); + + writel(clkctl, regs + S3C_PCM_CLKCTL); + + spin_unlock_irqrestore(&pcm->lock, flags); + + dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n", + clk_get_rate(clk), pcm->sclk_per_fs, + sclk_div, sync_div); + + return 0; +} + +static int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai); + void __iomem *regs = pcm->regs; + unsigned long flags; + int ret = 0; + u32 ctl; + + dev_dbg(pcm->dev, "Entered %s\n", __func__); + + spin_lock_irqsave(&pcm->lock, flags); + + ctl = readl(regs + S3C_PCM_CTL); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do, NB_NF by default */ + break; + default: + dev_err(pcm->dev, "Unsupported clock inversion!\n"); + ret = -EINVAL; + goto exit; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* Nothing to do, Master by default */ + break; + default: + dev_err(pcm->dev, "Unsupported master/slave format!\n"); + ret = -EINVAL; + goto exit; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { + case SND_SOC_DAIFMT_CONT: + pcm->idleclk = 1; + break; + case SND_SOC_DAIFMT_GATED: + pcm->idleclk = 0; + break; + default: + dev_err(pcm->dev, "Invalid Clock gating request!\n"); + ret = -EINVAL; + goto exit; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC; + ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC; + break; + case SND_SOC_DAIFMT_DSP_B: + ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC; + ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC; + break; + default: + dev_err(pcm->dev, "Unsupported data format!\n"); + ret = -EINVAL; + goto exit; + } + + writel(ctl, regs + S3C_PCM_CTL); + +exit: + spin_unlock_irqrestore(&pcm->lock, flags); + + return ret; +} + +static int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai); + + switch (div_id) { + case S3C_PCM_SCLK_PER_FS: + pcm->sclk_per_fs = div; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai); + void __iomem *regs = pcm->regs; + u32 clkctl = readl(regs + S3C_PCM_CLKCTL); + + switch (clk_id) { + case S3C_PCM_CLKSRC_PCLK: + clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK; + break; + + case S3C_PCM_CLKSRC_MUX: + clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK; + + if (clk_get_rate(pcm->cclk) != freq) + clk_set_rate(pcm->cclk, freq); + + break; + + default: + return -EINVAL; + } + + writel(clkctl, regs + S3C_PCM_CLKCTL); + + return 0; +} + +static struct snd_soc_dai_ops s3c_pcm_dai_ops = { + .set_sysclk = s3c_pcm_set_sysclk, + .set_clkdiv = s3c_pcm_set_clkdiv, + .trigger = s3c_pcm_trigger, + .hw_params = s3c_pcm_hw_params, + .set_fmt = s3c_pcm_set_fmt, +}; + +#define S3C_PCM_RATES SNDRV_PCM_RATE_8000_96000 + +#define S3C_PCM_DAI_DECLARE \ + .symmetric_rates = 1, \ + .ops = &s3c_pcm_dai_ops, \ + .playback = { \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = S3C_PCM_RATES, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .capture = { \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = S3C_PCM_RATES, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + } + +struct snd_soc_dai_driver s3c_pcm_dai[] = { + [0] = { + .name = "samsung-pcm.0", + S3C_PCM_DAI_DECLARE, + }, + [1] = { + .name = "samsung-pcm.1", + S3C_PCM_DAI_DECLARE, + }, +}; +EXPORT_SYMBOL_GPL(s3c_pcm_dai); + +static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev) +{ + struct s3c_pcm_info *pcm; + struct resource *mem_res, *dmatx_res, *dmarx_res; + struct s3c_audio_pdata *pcm_pdata; + int ret; + + /* Check for valid device index */ + if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) { + dev_err(&pdev->dev, "id %d out of range\n", pdev->id); + return -EINVAL; + } + + pcm_pdata = pdev->dev.platform_data; + + /* 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 PCM-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 PCM-RX 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; + } + + if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure gpio\n"); + return -EINVAL; + } + + pcm = &s3c_pcm[pdev->id]; + pcm->dev = &pdev->dev; + + spin_lock_init(&pcm->lock); + + /* Default is 128fs */ + pcm->sclk_per_fs = 128; + + pcm->cclk = clk_get(&pdev->dev, "audio-bus"); + if (IS_ERR(pcm->cclk)) { + dev_err(&pdev->dev, "failed to get audio-bus\n"); + ret = PTR_ERR(pcm->cclk); + goto err1; + } + clk_enable(pcm->cclk); + + /* record our pcm structure for later use in the callbacks */ + dev_set_drvdata(&pdev->dev, pcm); + + if (!request_mem_region(mem_res->start, + resource_size(mem_res), "samsung-pcm")) { + dev_err(&pdev->dev, "Unable to request register region\n"); + ret = -EBUSY; + goto err2; + } + + pcm->regs = ioremap(mem_res->start, 0x100); + if (pcm->regs == NULL) { + dev_err(&pdev->dev, "cannot ioremap registers\n"); + ret = -ENXIO; + goto err3; + } + + pcm->pclk = clk_get(&pdev->dev, "pcm"); + if (IS_ERR(pcm->pclk)) { + dev_err(&pdev->dev, "failed to get pcm_clock\n"); + ret = -ENOENT; + goto err4; + } + clk_enable(pcm->pclk); + + ret = snd_soc_register_dai(&pdev->dev, &s3c_pcm_dai[pdev->id]); + if (ret != 0) { + dev_err(&pdev->dev, "failed to get pcm_clock\n"); + goto err5; + } + + s3c_pcm_stereo_in[pdev->id].dma_addr = mem_res->start + + S3C_PCM_RXFIFO; + s3c_pcm_stereo_out[pdev->id].dma_addr = mem_res->start + + S3C_PCM_TXFIFO; + + s3c_pcm_stereo_in[pdev->id].channel = dmarx_res->start; + s3c_pcm_stereo_out[pdev->id].channel = dmatx_res->start; + + pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id]; + pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id]; + + return 0; + +err5: + clk_disable(pcm->pclk); + clk_put(pcm->pclk); +err4: + iounmap(pcm->regs); +err3: + release_mem_region(mem_res->start, resource_size(mem_res)); +err2: + clk_disable(pcm->cclk); + clk_put(pcm->cclk); +err1: + return ret; +} + +static __devexit int s3c_pcm_dev_remove(struct platform_device *pdev) +{ + struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id]; + struct resource *mem_res; + + snd_soc_unregister_dai(&pdev->dev); + + iounmap(pcm->regs); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem_res->start, resource_size(mem_res)); + + clk_disable(pcm->cclk); + clk_disable(pcm->pclk); + clk_put(pcm->pclk); + clk_put(pcm->cclk); + + return 0; +} + +static struct platform_driver s3c_pcm_driver = { + .probe = s3c_pcm_dev_probe, + .remove = s3c_pcm_dev_remove, + .driver = { + .name = "samsung-pcm", + .owner = THIS_MODULE, + }, +}; + +static int __init s3c_pcm_init(void) +{ + return platform_driver_register(&s3c_pcm_driver); +} +module_init(s3c_pcm_init); + +static void __exit s3c_pcm_exit(void) +{ + platform_driver_unregister(&s3c_pcm_driver); +} +module_exit(s3c_pcm_exit); + +/* Module information */ +MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>"); +MODULE_DESCRIPTION("S3C PCM Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-pcm"); diff --git a/sound/soc/samsung/pcm.h b/sound/soc/samsung/pcm.h new file mode 100644 index 00000000000..03393dcf852 --- /dev/null +++ b/sound/soc/samsung/pcm.h @@ -0,0 +1,124 @@ +/* sound/soc/samsung/pcm.h + * + * 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_PCM_H +#define __S3C_PCM_H __FILE__ + +/*Register Offsets */ +#define S3C_PCM_CTL (0x00) +#define S3C_PCM_CLKCTL (0x04) +#define S3C_PCM_TXFIFO (0x08) +#define S3C_PCM_RXFIFO (0x0C) +#define S3C_PCM_IRQCTL (0x10) +#define S3C_PCM_IRQSTAT (0x14) +#define S3C_PCM_FIFOSTAT (0x18) +#define S3C_PCM_CLRINT (0x20) + +/* PCM_CTL Bit-Fields */ +#define S3C_PCM_CTL_TXDIPSTICK_MASK (0x3f) +#define S3C_PCM_CTL_TXDIPSTICK_SHIFT (13) +#define S3C_PCM_CTL_RXDIPSTICK_MASK (0x3f) +#define S3C_PCM_CTL_RXDIPSTICK_SHIFT (7) +#define S3C_PCM_CTL_TXDMA_EN (0x1<<6) +#define S3C_PCM_CTL_RXDMA_EN (0x1<<5) +#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1<<4) +#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1<<3) +#define S3C_PCM_CTL_TXFIFO_EN (0x1<<2) +#define S3C_PCM_CTL_RXFIFO_EN (0x1<<1) +#define S3C_PCM_CTL_ENABLE (0x1<<0) + +/* PCM_CLKCTL Bit-Fields */ +#define S3C_PCM_CLKCTL_SERCLK_EN (0x1<<19) +#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1<<18) +#define S3C_PCM_CLKCTL_SCLKDIV_MASK (0x1ff) +#define S3C_PCM_CLKCTL_SYNCDIV_MASK (0x1ff) +#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT (9) +#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT (0) + +/* PCM_TXFIFO Bit-Fields */ +#define S3C_PCM_TXFIFO_DVALID (0x1<<16) +#define S3C_PCM_TXFIFO_DATA_MSK (0xffff<<0) + +/* PCM_RXFIFO Bit-Fields */ +#define S3C_PCM_RXFIFO_DVALID (0x1<<16) +#define S3C_PCM_RXFIFO_DATA_MSK (0xffff<<0) + +/* PCM_IRQCTL Bit-Fields */ +#define S3C_PCM_IRQCTL_IRQEN (0x1<<14) +#define S3C_PCM_IRQCTL_WRDEN (0x1<<12) +#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1<<11) +#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1<<10) +#define S3C_PCM_IRQCTL_TXFULLEN (0x1<<9) +#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1<<8) +#define S3C_PCM_IRQCTL_TXSTARVEN (0x1<<7) +#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1<<6) +#define S3C_PCM_IRQCTL_RXEMPTEN (0x1<<5) +#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1<<4) +#define S3C_PCM_IRQCTL_RXFULLEN (0x1<<3) +#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1<<2) +#define S3C_PCM_IRQCTL_RXSTARVEN (0x1<<1) +#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1<<0) + +/* PCM_IRQSTAT Bit-Fields */ +#define S3C_PCM_IRQSTAT_IRQPND (0x1<<13) +#define S3C_PCM_IRQSTAT_WRD_XFER (0x1<<12) +#define S3C_PCM_IRQSTAT_TXEMPTY (0x1<<11) +#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1<<10) +#define S3C_PCM_IRQSTAT_TXFULL (0x1<<9) +#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1<<8) +#define S3C_PCM_IRQSTAT_TXSTARV (0x1<<7) +#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1<<6) +#define S3C_PCM_IRQSTAT_RXEMPT (0x1<<5) +#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1<<4) +#define S3C_PCM_IRQSTAT_RXFULL (0x1<<3) +#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1<<2) +#define S3C_PCM_IRQSTAT_RXSTARV (0x1<<1) +#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1<<0) + +/* PCM_FIFOSTAT Bit-Fields */ +#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f<<14) +#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1<<13) +#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1<<12) +#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1<<11) +#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1<<10) +#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f<<4) +#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1<<3) +#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1<<2) +#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1<<1) +#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1<<0) + +#define S3C_PCM_CLKSRC_PCLK 0 +#define S3C_PCM_CLKSRC_MUX 1 + +#define S3C_PCM_SCLK_PER_FS 0 + +/** + * struct s3c_pcm_info - S3C PCM Controller information + * @dev: The parent device passed to use from the probe. + * @regs: The pointer to the device register block. + * @dma_playback: DMA information for playback channel. + * @dma_capture: DMA information for capture channel. + */ +struct s3c_pcm_info { + spinlock_t lock; + struct device *dev; + void __iomem *regs; + + unsigned int sclk_per_fs; + + /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */ + unsigned int idleclk; + + struct clk *pclk; + struct clk *cclk; + + struct s3c_dma_params *dma_playback; + struct s3c_dma_params *dma_capture; +}; + +#endif /* __S3C_PCM_H */ diff --git a/sound/soc/samsung/regs-i2s-v2.h b/sound/soc/samsung/regs-i2s-v2.h new file mode 100644 index 00000000000..5e5e5680580 --- /dev/null +++ b/sound/soc/samsung/regs-i2s-v2.h @@ -0,0 +1,115 @@ +/* linux/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h + * + * Copyright 2007 Simtec Electronics <linux@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * 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. + * + * S3C2412 IIS register definition +*/ + +#ifndef __ASM_ARCH_REGS_S3C2412_IIS_H +#define __ASM_ARCH_REGS_S3C2412_IIS_H + +#define S3C2412_IISCON (0x00) +#define S3C2412_IISMOD (0x04) +#define S3C2412_IISFIC (0x08) +#define S3C2412_IISPSR (0x0C) +#define S3C2412_IISTXD (0x10) +#define S3C2412_IISRXD (0x14) + +#define S5PC1XX_IISFICS 0x18 +#define S5PC1XX_IISTXDS 0x1C + +#define S5PC1XX_IISCON_SW_RST (1 << 31) +#define S5PC1XX_IISCON_FRXOFSTATUS (1 << 26) +#define S5PC1XX_IISCON_FRXORINTEN (1 << 25) +#define S5PC1XX_IISCON_FTXSURSTAT (1 << 24) +#define S5PC1XX_IISCON_FTXSURINTEN (1 << 23) +#define S5PC1XX_IISCON_TXSDMAPAUSE (1 << 20) +#define S5PC1XX_IISCON_TXSDMACTIVE (1 << 18) + +#define S3C64XX_IISCON_FTXURSTATUS (1 << 17) +#define S3C64XX_IISCON_FTXURINTEN (1 << 16) +#define S3C64XX_IISCON_TXFIFO2_EMPTY (1 << 15) +#define S3C64XX_IISCON_TXFIFO1_EMPTY (1 << 14) +#define S3C64XX_IISCON_TXFIFO2_FULL (1 << 13) +#define S3C64XX_IISCON_TXFIFO1_FULL (1 << 12) + +#define S3C2412_IISCON_LRINDEX (1 << 11) +#define S3C2412_IISCON_TXFIFO_EMPTY (1 << 10) +#define S3C2412_IISCON_RXFIFO_EMPTY (1 << 9) +#define S3C2412_IISCON_TXFIFO_FULL (1 << 8) +#define S3C2412_IISCON_RXFIFO_FULL (1 << 7) +#define S3C2412_IISCON_TXDMA_PAUSE (1 << 6) +#define S3C2412_IISCON_RXDMA_PAUSE (1 << 5) +#define S3C2412_IISCON_TXCH_PAUSE (1 << 4) +#define S3C2412_IISCON_RXCH_PAUSE (1 << 3) +#define S3C2412_IISCON_TXDMA_ACTIVE (1 << 2) +#define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1) +#define S3C2412_IISCON_IIS_ACTIVE (1 << 0) + +#define S5PC1XX_IISMOD_OPCLK_CDCLK_OUT (0 << 30) +#define S5PC1XX_IISMOD_OPCLK_CDCLK_IN (1 << 30) +#define S5PC1XX_IISMOD_OPCLK_BCLK_OUT (2 << 30) +#define S5PC1XX_IISMOD_OPCLK_PCLK (3 << 30) +#define S5PC1XX_IISMOD_OPCLK_MASK (3 << 30) +#define S5PC1XX_IISMOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */ +#define S5PC1XX_IISMOD_BLCS_MASK 0x3 +#define S5PC1XX_IISMOD_BLCS_SHIFT 26 +#define S5PC1XX_IISMOD_BLCP_MASK 0x3 +#define S5PC1XX_IISMOD_BLCP_SHIFT 24 + +#define S3C64XX_IISMOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */ +#define S3C64XX_IISMOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */ +#define S3C64XX_IISMOD_C1DD_HHALF (1 << 19) +#define S3C64XX_IISMOD_C1DD_LHALF (1 << 18) +#define S3C64XX_IISMOD_DC2_EN (1 << 17) +#define S3C64XX_IISMOD_DC1_EN (1 << 16) +#define S3C64XX_IISMOD_BLC_16BIT (0 << 13) +#define S3C64XX_IISMOD_BLC_8BIT (1 << 13) +#define S3C64XX_IISMOD_BLC_24BIT (2 << 13) +#define S3C64XX_IISMOD_BLC_MASK (3 << 13) + +#define S3C2412_IISMOD_IMS_SYSMUX (1 << 10) +#define S3C2412_IISMOD_SLAVE (1 << 11) +#define S3C2412_IISMOD_MODE_TXONLY (0 << 8) +#define S3C2412_IISMOD_MODE_RXONLY (1 << 8) +#define S3C2412_IISMOD_MODE_TXRX (2 << 8) +#define S3C2412_IISMOD_MODE_MASK (3 << 8) +#define S3C2412_IISMOD_LR_LLOW (0 << 7) +#define S3C2412_IISMOD_LR_RLOW (1 << 7) +#define S3C2412_IISMOD_SDF_IIS (0 << 5) +#define S3C2412_IISMOD_SDF_MSB (1 << 5) +#define S3C2412_IISMOD_SDF_LSB (2 << 5) +#define S3C2412_IISMOD_SDF_MASK (3 << 5) +#define S3C2412_IISMOD_RCLK_256FS (0 << 3) +#define S3C2412_IISMOD_RCLK_512FS (1 << 3) +#define S3C2412_IISMOD_RCLK_384FS (2 << 3) +#define S3C2412_IISMOD_RCLK_768FS (3 << 3) +#define S3C2412_IISMOD_RCLK_MASK (3 << 3) +#define S3C2412_IISMOD_BCLK_32FS (0 << 1) +#define S3C2412_IISMOD_BCLK_48FS (1 << 1) +#define S3C2412_IISMOD_BCLK_16FS (2 << 1) +#define S3C2412_IISMOD_BCLK_24FS (3 << 1) +#define S3C2412_IISMOD_BCLK_MASK (3 << 1) +#define S3C2412_IISMOD_8BIT (1 << 0) + +#define S3C64XX_IISMOD_CDCLKCON (1 << 12) + +#define S3C2412_IISPSR_PSREN (1 << 15) + +#define S3C64XX_IISFIC_TX2COUNT(x) (((x) >> 24) & 0xf) +#define S3C64XX_IISFIC_TX1COUNT(x) (((x) >> 16) & 0xf) + +#define S3C2412_IISFIC_TXFLUSH (1 << 15) +#define S3C2412_IISFIC_RXFLUSH (1 << 7) +#define S3C2412_IISFIC_TXCOUNT(x) (((x) >> 8) & 0xf) +#define S3C2412_IISFIC_RXCOUNT(x) (((x) >> 0) & 0xf) + +#define S5PC1XX_IISFICS_TXFLUSH (1 << 15) +#define S5PC1XX_IISFICS_TXCOUNT(x) (((x) >> 8) & 0x7f) + +#endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */ diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c new file mode 100644 index 00000000000..f40027445dd --- /dev/null +++ b/sound/soc/samsung/rx1950_uda1380.c @@ -0,0 +1,320 @@ +/* + * rx1950.c -- ALSA Soc Audio Layer + * + * Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com> + * + * Based on smdk2440.c and magician.c + * + * Authors: Graeme Gregory graeme.gregory@wolfsonmicro.com + * Philipp Zabel <philipp.zabel@gmail.com> + * Denis Grigoriev <dgreenday@gmail.com> + * Vasily Khoruzhick <anarsoul@gmail.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/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/clk.h> + +#include <sound/soc.h> +#include <sound/uda1380.h> +#include <sound/jack.h> + +#include <plat/regs-iis.h> + +#include <mach/regs-clock.h> + +#include <asm/mach-types.h> + +#include "dma.h" +#include "s3c24xx-i2s.h" +#include "../codecs/uda1380.h" + +static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd); +static int rx1950_startup(struct snd_pcm_substream *substream); +static int rx1950_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); +static int rx1950_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event); + +static unsigned int rates[] = { + 16000, + 44100, + 48000, +}; + +static struct snd_pcm_hw_constraint_list hw_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; + +static struct snd_soc_jack hp_jack; + +static struct snd_soc_jack_pin hp_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Speaker", + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + +static struct snd_soc_jack_gpio hp_jack_gpios[] = { + [0] = { + .gpio = S3C2410_GPG(12), + .name = "hp-gpio", + .report = SND_JACK_HEADPHONE, + .invert = 1, + .debounce_time = 200, + }, +}; + +static struct snd_soc_ops rx1950_ops = { + .startup = rx1950_startup, + .hw_params = rx1950_hw_params, +}; + +/* s3c24xx digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link rx1950_uda1380_dai[] = { + { + .name = "uda1380", + .stream_name = "UDA1380 Duplex", + .cpu_dai_name = "s3c24xx-iis", + .codec_dai_name = "uda1380-hifi", + .init = rx1950_uda1380_init, + .platform_name = "samsung-audio", + .codec_name = "uda1380-codec.0-001a", + .ops = &rx1950_ops, + }, +}; + +static struct snd_soc_card rx1950_asoc = { + .name = "rx1950", + .dai_link = rx1950_uda1380_dai, + .num_links = ARRAY_SIZE(rx1950_uda1380_dai), +}; + +/* rx1950 machine dapm widgets */ +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", rx1950_spk_power), +}; + +/* rx1950 machine audio_map */ +static const struct snd_soc_dapm_route audio_map[] = { + /* headphone connected to VOUTLHP, VOUTRHP */ + {"Headphone Jack", NULL, "VOUTLHP"}, + {"Headphone Jack", NULL, "VOUTRHP"}, + + /* ext speaker connected to VOUTL, VOUTR */ + {"Speaker", NULL, "VOUTL"}, + {"Speaker", NULL, "VOUTR"}, + + /* mic is connected to VINM */ + {"VINM", NULL, "Mic Jack"}, +}; + +static struct platform_device *s3c24xx_snd_device; + +static int rx1950_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->hw.rate_min = hw_rates.list[0]; + runtime->hw.rate_max = hw_rates.list[hw_rates.count - 1]; + runtime->hw.rates = SNDRV_PCM_RATE_KNOT; + + return snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_rates); +} + +static int rx1950_spk_power(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(S3C2410_GPA(1), 1); + else + gpio_set_value(S3C2410_GPA(1), 0); + + return 0; +} + +static int rx1950_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int div; + int ret; + unsigned int rate = params_rate(params); + int clk_source, fs_mode; + + switch (rate) { + case 16000: + case 48000: + clk_source = S3C24XX_CLKSRC_PCLK; + fs_mode = S3C2410_IISMOD_256FS; + div = s3c24xx_i2s_get_clockrate() / (256 * rate); + if (s3c24xx_i2s_get_clockrate() % (256 * rate) > (128 * rate)) + div++; + break; + case 44100: + case 88200: + clk_source = S3C24XX_CLKSRC_MPLL; + fs_mode = S3C2410_IISMOD_384FS; + div = 1; + break; + default: + printk(KERN_ERR "%s: rate %d is not supported\n", + __func__, rate); + return -EINVAL; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* select clock source */ + ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source, rate, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + fs_mode); + if (ret < 0) + return ret; + + /* set BCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(div, div)); + if (ret < 0) + return ret; + + return 0; +} + +static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int err; + + /* Add rx1950 specific widgets */ + err = snd_soc_dapm_new_controls(dapm, uda1380_dapm_widgets, + ARRAY_SIZE(uda1380_dapm_widgets)); + + if (err) + return err; + + /* Set up rx1950 specific audio path audio_mapnects */ + err = snd_soc_dapm_add_routes(dapm, audio_map, + ARRAY_SIZE(audio_map)); + + if (err) + return err; + + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_enable_pin(dapm, "Speaker"); + snd_soc_dapm_enable_pin(dapm, "Mic Jack"); + + snd_soc_dapm_sync(dapm); + + snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, + &hp_jack); + + snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins), + hp_jack_pins); + + snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), + hp_jack_gpios); + + return 0; +} + +static int __init rx1950_init(void) +{ + int ret; + + if (!machine_is_rx1950()) + return -ENODEV; + + /* configure some gpios */ + ret = gpio_request(S3C2410_GPA(1), "speaker-power"); + if (ret) + goto err_gpio; + + ret = gpio_direction_output(S3C2410_GPA(1), 0); + if (ret) + goto err_gpio_conf; + + s3c24xx_snd_device = platform_device_alloc("soc-audio", -1); + if (!s3c24xx_snd_device) { + ret = -ENOMEM; + goto err_plat_alloc; + } + + platform_set_drvdata(s3c24xx_snd_device, &rx1950_asoc); + ret = platform_device_add(s3c24xx_snd_device); + + if (ret) { + platform_device_put(s3c24xx_snd_device); + goto err_plat_add; + } + + return 0; + +err_plat_add: +err_plat_alloc: +err_gpio_conf: + gpio_free(S3C2410_GPA(1)); + +err_gpio: + return ret; +} + +static void __exit rx1950_exit(void) +{ + platform_device_unregister(s3c24xx_snd_device); + snd_soc_jack_free_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios), + hp_jack_gpios); + gpio_free(S3C2410_GPA(1)); +} + +module_init(rx1950_init); +module_exit(rx1950_exit); + +/* Module information */ +MODULE_AUTHOR("Vasily Khoruzhick"); +MODULE_DESCRIPTION("ALSA SoC RX1950"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c new file mode 100644 index 00000000000..094f36e41e8 --- /dev/null +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -0,0 +1,757 @@ +/* sound/soc/samsung/s3c-i2c-v2.c + * + * ALSA Soc Audio Layer - I2S core for newer Samsung SoCs. + * + * Copyright (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com + * linux@wolfsonmicro.com + * + * Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * 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/delay.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/dma.h> + +#include "regs-i2s-v2.h" +#include "s3c-i2s-v2.h" +#include "dma.h" + +#undef S3C_IIS_V2_SUPPORTED + +#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) \ + || defined(CONFIG_CPU_S5PV210) +#define S3C_IIS_V2_SUPPORTED +#endif + +#ifdef CONFIG_PLAT_S3C64XX +#define S3C_IIS_V2_SUPPORTED +#endif + +#ifndef S3C_IIS_V2_SUPPORTED +#error Unsupported CPU model +#endif + +#define S3C2412_I2S_DEBUG_CON 0 + +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return snd_soc_dai_get_drvdata(cpu_dai); +} + +#define bit_set(v, b) (((v) & (b)) ? 1 : 0) + +#if S3C2412_I2S_DEBUG_CON +static void dbg_showcon(const char *fn, u32 con) +{ + printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn, + bit_set(con, S3C2412_IISCON_LRINDEX), + bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY), + bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY), + bit_set(con, S3C2412_IISCON_TXFIFO_FULL), + bit_set(con, S3C2412_IISCON_RXFIFO_FULL)); + + printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n", + fn, + bit_set(con, S3C2412_IISCON_TXDMA_PAUSE), + bit_set(con, S3C2412_IISCON_RXDMA_PAUSE), + bit_set(con, S3C2412_IISCON_TXCH_PAUSE), + bit_set(con, S3C2412_IISCON_RXCH_PAUSE)); + printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn, + bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE), + bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE), + bit_set(con, S3C2412_IISCON_IIS_ACTIVE)); +} +#else +static inline void dbg_showcon(const char *fn, u32 con) +{ +} +#endif + + +/* Turn on or off the transmission path. */ +static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) +{ + void __iomem *regs = i2s->regs; + u32 fic, con, mod; + + pr_debug("%s(%d)\n", __func__, on); + + fic = readl(regs + S3C2412_IISFIC); + con = readl(regs + S3C2412_IISCON); + mod = readl(regs + S3C2412_IISMOD); + + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); + + if (on) { + con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; + con &= ~S3C2412_IISCON_TXDMA_PAUSE; + con &= ~S3C2412_IISCON_TXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXONLY: + case S3C2412_IISMOD_MODE_TXRX: + /* do nothing, we are in the right mode */ + break; + + case S3C2412_IISMOD_MODE_RXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXRX; + break; + + default: + dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; + } + + writel(con, regs + S3C2412_IISCON); + writel(mod, regs + S3C2412_IISMOD); + } else { + /* Note, we do not have any indication that the FIFO problems + * tha the S3C2410/2440 had apply here, so we should be able + * to disable the DMA and TX without resetting the FIFOS. + */ + + con |= S3C2412_IISCON_TXDMA_PAUSE; + con |= S3C2412_IISCON_TXCH_PAUSE; + con &= ~S3C2412_IISCON_TXDMA_ACTIVE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXRX: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_RXONLY; + break; + + case S3C2412_IISMOD_MODE_TXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + con &= ~S3C2412_IISCON_IIS_ACTIVE; + break; + + default: + dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; + } + + writel(mod, regs + S3C2412_IISMOD); + writel(con, regs + S3C2412_IISCON); + } + + fic = readl(regs + S3C2412_IISFIC); + dbg_showcon(__func__, con); + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); +} + +static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) +{ + void __iomem *regs = i2s->regs; + u32 fic, con, mod; + + pr_debug("%s(%d)\n", __func__, on); + + fic = readl(regs + S3C2412_IISFIC); + con = readl(regs + S3C2412_IISCON); + mod = readl(regs + S3C2412_IISMOD); + + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); + + if (on) { + con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; + con &= ~S3C2412_IISCON_RXDMA_PAUSE; + con &= ~S3C2412_IISCON_RXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXRX: + case S3C2412_IISMOD_MODE_RXONLY: + /* do nothing, we are in the right mode */ + break; + + case S3C2412_IISMOD_MODE_TXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXRX; + break; + + default: + dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + } + + writel(mod, regs + S3C2412_IISMOD); + writel(con, regs + S3C2412_IISCON); + } else { + /* See txctrl notes on FIFOs. */ + + con &= ~S3C2412_IISCON_RXDMA_ACTIVE; + con |= S3C2412_IISCON_RXDMA_PAUSE; + con |= S3C2412_IISCON_RXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_RXONLY: + con &= ~S3C2412_IISCON_IIS_ACTIVE; + mod &= ~S3C2412_IISMOD_MODE_MASK; + break; + + case S3C2412_IISMOD_MODE_TXRX: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXONLY; + break; + + default: + dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + } + + writel(con, regs + S3C2412_IISCON); + writel(mod, regs + S3C2412_IISMOD); + } + + fic = readl(regs + S3C2412_IISFIC); + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); +} + +#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) + +/* + * Wait for the LR signal to allow synchronisation to the L/R clock + * from the codec. May only be needed for slave mode. + */ +static int s3c2412_snd_lrsync(struct s3c_i2sv2_info *i2s) +{ + u32 iiscon; + unsigned long loops = msecs_to_loops(5); + + pr_debug("Entered %s\n", __func__); + + while (--loops) { + iiscon = readl(i2s->regs + S3C2412_IISCON); + if (iiscon & S3C2412_IISCON_LRINDEX) + break; + + cpu_relax(); + } + + if (!loops) { + printk(KERN_ERR "%s: timeout\n", __func__); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * Set S3C2412 I2S DAI format + */ +static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("hw_params r: IISMOD: %x \n", iismod); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->master = 0; + iismod |= S3C2412_IISMOD_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->master = 1; + iismod &= ~S3C2412_IISMOD_SLAVE; + break; + default: + pr_err("unknwon master/slave format\n"); + return -EINVAL; + } + + iismod &= ~S3C2412_IISMOD_SDF_MASK; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + iismod |= S3C2412_IISMOD_LR_RLOW; + iismod |= S3C2412_IISMOD_SDF_MSB; + break; + case SND_SOC_DAIFMT_LEFT_J: + iismod |= S3C2412_IISMOD_LR_RLOW; + iismod |= S3C2412_IISMOD_SDF_LSB; + break; + case SND_SOC_DAIFMT_I2S: + iismod &= ~S3C2412_IISMOD_LR_RLOW; + iismod |= S3C2412_IISMOD_SDF_IIS; + break; + default: + pr_err("Unknown data format\n"); + return -EINVAL; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("hw_params w: IISMOD: %x \n", iismod); + return 0; +} + +static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + struct s3c_dma_params *dma_data; + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = i2s->dma_playback; + else + dma_data = i2s->dma_capture; + + snd_soc_dai_set_dma_data(dai, substream, dma_data); + + /* Working copies of register */ + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); + + iismod &= ~S3C64XX_IISMOD_BLC_MASK; + /* Sample size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod |= S3C64XX_IISMOD_BLC_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S24_LE: + iismod |= S3C64XX_IISMOD_BLC_24BIT; + break; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +static int s3c_i2sv2_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + pr_debug("Entered %s\n", __func__); + pr_debug("%s r: IISMOD: %x\n", __func__, iismod); + + switch (clk_id) { + case S3C_I2SV2_CLKSRC_PCLK: + iismod &= ~S3C2412_IISMOD_IMS_SYSMUX; + break; + + case S3C_I2SV2_CLKSRC_AUDIOBUS: + iismod |= S3C2412_IISMOD_IMS_SYSMUX; + break; + + case S3C_I2SV2_CLKSRC_CDCLK: + /* Error if controller doesn't have the CDCLKCON bit */ + if (!(i2s->feature & S3C_FEATURE_CDCLKCON)) + return -EINVAL; + + switch (dir) { + case SND_SOC_CLOCK_IN: + iismod |= S3C64XX_IISMOD_CDCLKCON; + break; + case SND_SOC_CLOCK_OUT: + iismod &= ~S3C64XX_IISMOD_CDCLKCON; + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c_i2sv2_info *i2s = to_info(rtd->cpu_dai); + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + unsigned long irqs; + int ret = 0; + struct s3c_dma_params *dma_data = + snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + pr_debug("Entered %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* On start, ensure that the FIFOs are cleared and reset. */ + + writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH, + i2s->regs + S3C2412_IISFIC); + + /* clear again, just in case */ + writel(0x0, i2s->regs + S3C2412_IISFIC); + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!i2s->master) { + ret = s3c2412_snd_lrsync(i2s); + if (ret) + goto exit_err; + } + + local_irq_save(irqs); + + if (capture) + s3c2412_snd_rxctrl(i2s, 1); + else + s3c2412_snd_txctrl(i2s, 1); + + local_irq_restore(irqs); + + /* + * Load the next buffer to DMA to meet the reqirement + * of the auto reload mechanism of S3C24XX. + * This call won't bother S3C64XX. + */ + s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + local_irq_save(irqs); + + if (capture) + s3c2412_snd_rxctrl(i2s, 0); + else + s3c2412_snd_txctrl(i2s, 0); + + local_irq_restore(irqs); + break; + default: + ret = -EINVAL; + break; + } + +exit_err: + return ret; +} + +/* + * Set S3C2412 Clock dividers + */ +static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 reg; + + pr_debug("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div); + + switch (div_id) { + case S3C_I2SV2_DIV_BCLK: + switch (div) { + case 16: + div = S3C2412_IISMOD_BCLK_16FS; + break; + + case 32: + div = S3C2412_IISMOD_BCLK_32FS; + break; + + case 24: + div = S3C2412_IISMOD_BCLK_24FS; + break; + + case 48: + div = S3C2412_IISMOD_BCLK_48FS; + break; + + default: + return -EINVAL; + } + + reg = readl(i2s->regs + S3C2412_IISMOD); + reg &= ~S3C2412_IISMOD_BCLK_MASK; + writel(reg | div, i2s->regs + S3C2412_IISMOD); + + pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); + break; + + case S3C_I2SV2_DIV_RCLK: + switch (div) { + case 256: + div = S3C2412_IISMOD_RCLK_256FS; + break; + + case 384: + div = S3C2412_IISMOD_RCLK_384FS; + break; + + case 512: + div = S3C2412_IISMOD_RCLK_512FS; + break; + + case 768: + div = S3C2412_IISMOD_RCLK_768FS; + break; + + default: + return -EINVAL; + } + + reg = readl(i2s->regs + S3C2412_IISMOD); + reg &= ~S3C2412_IISMOD_RCLK_MASK; + writel(reg | div, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); + break; + + case S3C_I2SV2_DIV_PRESCALER: + if (div >= 0) { + writel((div << 8) | S3C2412_IISPSR_PSREN, + i2s->regs + S3C2412_IISPSR); + } else { + writel(0x0, i2s->regs + S3C2412_IISPSR); + } + pr_debug("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR)); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + u32 reg = readl(i2s->regs + S3C2412_IISFIC); + snd_pcm_sframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = S3C2412_IISFIC_TXCOUNT(reg); + else + delay = S3C2412_IISFIC_RXCOUNT(reg); + + return delay; +} + +struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + if (iismod & S3C2412_IISMOD_IMS_SYSMUX) + return i2s->iis_cclk; + else + return i2s->iis_pclk; +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_get_clock); + +/* default table of all avaialable root fs divisors */ +static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 }; + +int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, + unsigned int *fstab, + unsigned int rate, struct clk *clk) +{ + unsigned long clkrate = clk_get_rate(clk); + unsigned int div; + unsigned int fsclk; + unsigned int actual; + unsigned int fs; + unsigned int fsdiv; + signed int deviation = 0; + unsigned int best_fs = 0; + unsigned int best_div = 0; + unsigned int best_rate = 0; + unsigned int best_deviation = INT_MAX; + + pr_debug("Input clock rate %ldHz\n", clkrate); + + if (fstab == NULL) + fstab = iis_fs_tab; + + for (fs = 0; fs < ARRAY_SIZE(iis_fs_tab); fs++) { + fsdiv = iis_fs_tab[fs]; + + fsclk = clkrate / fsdiv; + div = fsclk / rate; + + if ((fsclk % rate) > (rate / 2)) + div++; + + if (div <= 1) + continue; + + actual = clkrate / (fsdiv * div); + deviation = actual - rate; + + printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n", + fsdiv, div, actual, deviation); + + deviation = abs(deviation); + + if (deviation < best_deviation) { + best_fs = fsdiv; + best_div = div; + best_rate = actual; + best_deviation = deviation; + } + + if (deviation == 0) + break; + } + + printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n", + best_fs, best_div, best_rate); + + info->fs_div = best_fs; + info->clk_div = best_div; + + return 0; +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_iis_calc_rate); + +int s3c_i2sv2_probe(struct snd_soc_dai *dai, + struct s3c_i2sv2_info *i2s, + unsigned long base) +{ + struct device *dev = dai->dev; + unsigned int iismod; + + i2s->dev = dev; + + /* record our i2s structure for later use in the callbacks */ + snd_soc_dai_set_drvdata(dai, i2s); + + i2s->regs = ioremap(base, 0x100); + if (i2s->regs == NULL) { + dev_err(dev, "cannot ioremap registers\n"); + return -ENXIO; + } + + i2s->iis_pclk = clk_get(dev, "iis"); + if (IS_ERR(i2s->iis_pclk)) { + dev_err(dev, "failed to get iis_clock\n"); + iounmap(i2s->regs); + return -ENOENT; + } + + clk_enable(i2s->iis_pclk); + + /* Mark ourselves as in TXRX mode so we can run through our cleanup + * process without warnings. */ + iismod = readl(i2s->regs + S3C2412_IISMOD); + iismod |= S3C2412_IISMOD_MODE_TXRX; + writel(iismod, i2s->regs + S3C2412_IISMOD); + s3c2412_snd_txctrl(i2s, 0); + s3c2412_snd_rxctrl(i2s, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_probe); + +#ifdef CONFIG_PM +static int s3c2412_i2s_suspend(struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + u32 iismod; + + if (dai->active) { + i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD); + i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON); + i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR); + + /* some basic suspend checks */ + + iismod = readl(i2s->regs + S3C2412_IISMOD); + + if (iismod & S3C2412_IISCON_RXDMA_ACTIVE) + pr_warning("%s: RXDMA active?\n", __func__); + + if (iismod & S3C2412_IISCON_TXDMA_ACTIVE) + pr_warning("%s: TXDMA active?\n", __func__); + + if (iismod & S3C2412_IISCON_IIS_ACTIVE) + pr_warning("%s: IIS active\n", __func__); + } + + return 0; +} + +static int s3c2412_i2s_resume(struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + + pr_info("dai_active %d, IISMOD %08x, IISCON %08x\n", + dai->active, i2s->suspend_iismod, i2s->suspend_iiscon); + + if (dai->active) { + writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON); + writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD); + writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR); + + writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH, + i2s->regs + S3C2412_IISFIC); + + ndelay(250); + writel(0x0, i2s->regs + S3C2412_IISFIC); + } + + return 0; +} +#else +#define s3c2412_i2s_suspend NULL +#define s3c2412_i2s_resume NULL +#endif + +int s3c_i2sv2_register_dai(struct device *dev, int id, + struct snd_soc_dai_driver *drv) +{ + struct snd_soc_dai_ops *ops = drv->ops; + + ops->trigger = s3c2412_i2s_trigger; + if (!ops->hw_params) + ops->hw_params = s3c_i2sv2_hw_params; + ops->set_fmt = s3c2412_i2s_set_fmt; + ops->set_clkdiv = s3c2412_i2s_set_clkdiv; + ops->set_sysclk = s3c_i2sv2_set_sysclk; + + /* Allow overriding by (for example) IISv4 */ + if (!ops->delay) + ops->delay = s3c2412_i2s_delay; + + drv->suspend = s3c2412_i2s_suspend; + drv->resume = s3c2412_i2s_resume; + + return snd_soc_register_dai(dev, drv); +} +EXPORT_SYMBOL_GPL(s3c_i2sv2_register_dai); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c-i2s-v2.h b/sound/soc/samsung/s3c-i2s-v2.h new file mode 100644 index 00000000000..f8297d9bb8a --- /dev/null +++ b/sound/soc/samsung/s3c-i2s-v2.h @@ -0,0 +1,106 @@ +/* sound/soc/samsung/s3c-i2s-v2.h + * + * ALSA Soc Audio Layer - S3C_I2SV2 I2S driver + * + * Copyright (c) 2007 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * 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. +*/ + +/* This code is the core support for the I2S block found in a number of + * Samsung SoC devices which is unofficially named I2S-V2. Currently the + * S3C2412 and the S3C64XX series use this block to provide 1 or 2 I2S + * channels via configurable GPIO. + */ + +#ifndef __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H +#define __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H __FILE__ + +#define S3C_I2SV2_DIV_BCLK (1) +#define S3C_I2SV2_DIV_RCLK (2) +#define S3C_I2SV2_DIV_PRESCALER (3) + +#define S3C_I2SV2_CLKSRC_PCLK 0 +#define S3C_I2SV2_CLKSRC_AUDIOBUS 1 +#define S3C_I2SV2_CLKSRC_CDCLK 2 + +/* Set this flag for I2S controllers that have the bit IISMOD[12] + * bridge/break RCLK signal and external Xi2sCDCLK pin. + */ +#define S3C_FEATURE_CDCLKCON (1 << 0) + +/** + * struct s3c_i2sv2_info - S3C I2S-V2 information + * @dev: The parent device passed to use from the probe. + * @regs: The pointer to the device registe block. + * @feature: Set of bit-flags indicating features of the controller. + * @master: True if the I2S core is the I2S bit clock master. + * @dma_playback: DMA information for playback channel. + * @dma_capture: DMA information for capture channel. + * @suspend_iismod: PM save for the IISMOD register. + * @suspend_iiscon: PM save for the IISCON register. + * @suspend_iispsr: PM save for the IISPSR register. + * + * This is the private codec state for the hardware associated with an + * I2S channel such as the register mappings and clock sources. + */ +struct s3c_i2sv2_info { + struct device *dev; + void __iomem *regs; + + u32 feature; + + struct clk *iis_pclk; + struct clk *iis_cclk; + + unsigned char master; + + struct s3c_dma_params *dma_playback; + struct s3c_dma_params *dma_capture; + + u32 suspend_iismod; + u32 suspend_iiscon; + u32 suspend_iispsr; + + unsigned long base; +}; + +extern struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai); + +struct s3c_i2sv2_rate_calc { + unsigned int clk_div; /* for prescaler */ + unsigned int fs_div; /* for root frame clock */ +}; + +extern int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, + unsigned int *fstab, + unsigned int rate, struct clk *clk); + +/** + * s3c_i2sv2_probe - probe for i2s device helper + * @dai: The ASoC DAI structure supplied to the original probe. + * @i2s: Our local i2s structure to fill in. + * @base: The base address for the registers. + */ +extern int s3c_i2sv2_probe(struct snd_soc_dai *dai, + struct s3c_i2sv2_info *i2s, + unsigned long base); + +/** + * s3c_i2sv2_register_dai - register dai with soc core + * @dev: DAI device + * @id: DAI ID + * @drv: The driver structure to register + * + * Fill in any missing fields and then register the given dai with the + * soc core. + */ +extern int s3c_i2sv2_register_dai(struct device *dev, int id, + struct snd_soc_dai_driver *drv); + +#endif /* __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H */ diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c new file mode 100644 index 00000000000..7ea83786712 --- /dev/null +++ b/sound/soc/samsung/s3c2412-i2s.c @@ -0,0 +1,212 @@ +/* sound/soc/samsung/s3c2412-i2s.c + * + * ALSA Soc Audio Layer - S3C2412 I2S driver + * + * Copyright (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com + * linux@wolfsonmicro.com + * + * Copyright (c) 2007, 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * 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/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/io.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <mach/hardware.h> + +#include <mach/regs-gpio.h> +#include <mach/dma.h> + +#include "dma.h" +#include "regs-i2s-v2.h" +#include "s3c2412-i2s.h" + +#define S3C2412_I2S_DEBUG 0 + +static struct s3c2410_dma_client s3c2412_dma_client_out = { + .name = "I2S PCM Stereo out" +}; + +static struct s3c2410_dma_client s3c2412_dma_client_in = { + .name = "I2S PCM Stereo in" +}; + +static struct s3c_dma_params s3c2412_i2s_pcm_stereo_out = { + .client = &s3c2412_dma_client_out, + .channel = DMACH_I2S_OUT, + .dma_addr = S3C2410_PA_IIS + S3C2412_IISTXD, + .dma_size = 4, +}; + +static struct s3c_dma_params s3c2412_i2s_pcm_stereo_in = { + .client = &s3c2412_dma_client_in, + .channel = DMACH_I2S_IN, + .dma_addr = S3C2410_PA_IIS + S3C2412_IISRXD, + .dma_size = 4, +}; + +static struct s3c_i2sv2_info s3c2412_i2s; + +static int s3c2412_i2s_probe(struct snd_soc_dai *dai) +{ + int ret; + + pr_debug("Entered %s\n", __func__); + + ret = s3c_i2sv2_probe(dai, &s3c2412_i2s, S3C2410_PA_IIS); + if (ret) + return ret; + + s3c2412_i2s.dma_capture = &s3c2412_i2s_pcm_stereo_in; + s3c2412_i2s.dma_playback = &s3c2412_i2s_pcm_stereo_out; + + s3c2412_i2s.iis_cclk = clk_get(dai->dev, "i2sclk"); + if (s3c2412_i2s.iis_cclk == NULL) { + pr_err("failed to get i2sclk clock\n"); + iounmap(s3c2412_i2s.regs); + return -ENODEV; + } + + /* Set MPLL as the source for IIS CLK */ + + clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll")); + clk_enable(s3c2412_i2s.iis_cclk); + + s3c2412_i2s.iis_cclk = s3c2412_i2s.iis_pclk; + + /* Configure the I2S pins in correct mode */ + s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK); + s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK); + s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK); + s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI); + s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO); + + return 0; +} + +static int s3c2412_i2s_remove(struct snd_soc_dai *dai) +{ + clk_disable(s3c2412_i2s.iis_cclk); + clk_put(s3c2412_i2s.iis_cclk); + iounmap(s3c2412_i2s.regs); + + return 0; +} + +static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct s3c_i2sv2_info *i2s = snd_soc_dai_get_drvdata(cpu_dai); + struct s3c_dma_params *dma_data; + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = i2s->dma_playback; + else + dma_data = i2s->dma_capture; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod |= S3C2412_IISMOD_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + iismod &= ~S3C2412_IISMOD_8BIT; + break; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + + return 0; +} + +#define S3C2412_I2S_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_88200 | SNDRV_PCM_RATE_96000) + +static struct snd_soc_dai_ops s3c2412_i2s_dai_ops = { + .hw_params = s3c2412_i2s_hw_params, +}; + +static struct snd_soc_dai_driver s3c2412_i2s_dai = { + .probe = s3c2412_i2s_probe, + .remove = s3c2412_i2s_remove, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C2412_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C2412_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &s3c2412_i2s_dai_ops, +}; + +static __devinit int s3c2412_iis_dev_probe(struct platform_device *pdev) +{ + return snd_soc_register_dai(&pdev->dev, &s3c2412_i2s_dai); +} + +static __devexit int s3c2412_iis_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver s3c2412_iis_driver = { + .probe = s3c2412_iis_dev_probe, + .remove = s3c2412_iis_dev_remove, + .driver = { + .name = "s3c2412-iis", + .owner = THIS_MODULE, + }, +}; + +static int __init s3c2412_i2s_init(void) +{ + return platform_driver_register(&s3c2412_iis_driver); +} +module_init(s3c2412_i2s_init); + +static void __exit s3c2412_i2s_exit(void) +{ + platform_driver_unregister(&s3c2412_iis_driver); +} +module_exit(s3c2412_i2s_exit); + +/* Module information */ +MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("S3C2412 I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c2412-iis"); diff --git a/sound/soc/samsung/s3c2412-i2s.h b/sound/soc/samsung/s3c2412-i2s.h new file mode 100644 index 00000000000..02ad5794c0a --- /dev/null +++ b/sound/soc/samsung/s3c2412-i2s.h @@ -0,0 +1,27 @@ +/* sound/soc/samsung/s3c2412-i2s.c + * + * ALSA Soc Audio Layer - S3C2412 I2S driver + * + * Copyright (c) 2007 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * 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. +*/ + +#ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H +#define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__ + +#include "s3c-i2s-v2.h" + +#define S3C2412_DIV_BCLK S3C_I2SV2_DIV_BCLK +#define S3C2412_DIV_RCLK S3C_I2SV2_DIV_RCLK +#define S3C2412_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER + +#define S3C2412_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK +#define S3C2412_CLKSRC_I2SCLK S3C_I2SV2_CLKSRC_AUDIOBUS + +#endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */ diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c new file mode 100644 index 00000000000..13e41ed8e22 --- /dev/null +++ b/sound/soc/samsung/s3c24xx-i2s.c @@ -0,0 +1,519 @@ +/* + * s3c24xx-i2s.c -- ALSA Soc Audio Layer + * + * (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * Copyright 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * 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/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/jiffies.h> +#include <linux/io.h> +#include <linux/gpio.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <mach/hardware.h> +#include <mach/regs-gpio.h> +#include <mach/regs-clock.h> + +#include <asm/dma.h> +#include <mach/dma.h> + +#include <plat/regs-iis.h> + +#include "dma.h" +#include "s3c24xx-i2s.h" + +static struct s3c2410_dma_client s3c24xx_dma_client_out = { + .name = "I2S PCM Stereo out" +}; + +static struct s3c2410_dma_client s3c24xx_dma_client_in = { + .name = "I2S PCM Stereo in" +}; + +static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_out = { + .client = &s3c24xx_dma_client_out, + .channel = DMACH_I2S_OUT, + .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO, + .dma_size = 2, +}; + +static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_in = { + .client = &s3c24xx_dma_client_in, + .channel = DMACH_I2S_IN, + .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO, + .dma_size = 2, +}; + +struct s3c24xx_i2s_info { + void __iomem *regs; + struct clk *iis_clk; + u32 iiscon; + u32 iismod; + u32 iisfcon; + u32 iispsr; +}; +static struct s3c24xx_i2s_info s3c24xx_i2s; + +static void s3c24xx_snd_txctrl(int on) +{ + u32 iisfcon; + u32 iiscon; + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + + pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); + + if (on) { + iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE; + iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN; + iiscon &= ~S3C2410_IISCON_TXIDLE; + iismod |= S3C2410_IISMOD_TXMODE; + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + } else { + /* note, we have to disable the FIFOs otherwise bad things + * seem to happen when the DMA stops. According to the + * Samsung supplied kernel, this should allow the DMA + * engine and FIFOs to reset. If this isn't allowed, the + * DMA engine will simply freeze randomly. + */ + + iisfcon &= ~S3C2410_IISFCON_TXENABLE; + iisfcon &= ~S3C2410_IISFCON_TXDMA; + iiscon |= S3C2410_IISCON_TXIDLE; + iiscon &= ~S3C2410_IISCON_TXDMAEN; + iismod &= ~S3C2410_IISMOD_TXMODE; + + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + } + + pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); +} + +static void s3c24xx_snd_rxctrl(int on) +{ + u32 iisfcon; + u32 iiscon; + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + + pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); + + if (on) { + iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; + iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN; + iiscon &= ~S3C2410_IISCON_RXIDLE; + iismod |= S3C2410_IISMOD_RXMODE; + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + } else { + /* note, we have to disable the FIFOs otherwise bad things + * seem to happen when the DMA stops. According to the + * Samsung supplied kernel, this should allow the DMA + * engine and FIFOs to reset. If this isn't allowed, the + * DMA engine will simply freeze randomly. + */ + + iisfcon &= ~S3C2410_IISFCON_RXENABLE; + iisfcon &= ~S3C2410_IISFCON_RXDMA; + iiscon |= S3C2410_IISCON_RXIDLE; + iiscon &= ~S3C2410_IISCON_RXDMAEN; + iismod &= ~S3C2410_IISMOD_RXMODE; + + writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + } + + pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); +} + +/* + * Wait for the LR signal to allow synchronisation to the L/R clock + * from the codec. May only be needed for slave mode. + */ +static int s3c24xx_snd_lrsync(void) +{ + u32 iiscon; + int timeout = 50; /* 5ms */ + + pr_debug("Entered %s\n", __func__); + + while (1) { + iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + if (iiscon & S3C2410_IISCON_LRINDEX) + break; + + if (!timeout--) + return -ETIMEDOUT; + udelay(100); + } + + return 0; +} + +/* + * Check whether CPU is the master or slave + */ +static inline int s3c24xx_snd_is_clkmaster(void) +{ + pr_debug("Entered %s\n", __func__); + + return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1; +} + +/* + * Set S3C24xx I2S DAI format + */ +static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + pr_debug("hw_params r: IISMOD: %x \n", iismod); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iismod |= S3C2410_IISMOD_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iismod &= ~S3C2410_IISMOD_SLAVE; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + iismod |= S3C2410_IISMOD_MSB; + break; + case SND_SOC_DAIFMT_I2S: + iismod &= ~S3C2410_IISMOD_MSB; + break; + default: + return -EINVAL; + } + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + pr_debug("hw_params w: IISMOD: %x \n", iismod); + return 0; +} + +static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c_dma_params *dma_data; + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &s3c24xx_i2s_pcm_stereo_out; + else + dma_data = &s3c24xx_i2s_pcm_stereo_in; + + snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data); + + /* Working copies of register */ + iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + pr_debug("hw_params r: IISMOD: %x\n", iismod); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod &= ~S3C2410_IISMOD_16BIT; + dma_data->dma_size = 1; + break; + case SNDRV_PCM_FORMAT_S16_LE: + iismod |= S3C2410_IISMOD_16BIT; + dma_data->dma_size = 2; + break; + default: + return -EINVAL; + } + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + pr_debug("hw_params w: IISMOD: %x\n", iismod); + return 0; +} + +static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct s3c_dma_params *dma_data = + snd_soc_dai_get_dma_data(dai, substream); + + pr_debug("Entered %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!s3c24xx_snd_is_clkmaster()) { + ret = s3c24xx_snd_lrsync(); + if (ret) + goto exit_err; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c24xx_snd_rxctrl(1); + else + s3c24xx_snd_txctrl(1); + + s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + s3c24xx_snd_rxctrl(0); + else + s3c24xx_snd_txctrl(0); + break; + default: + ret = -EINVAL; + break; + } + +exit_err: + return ret; +} + +/* + * Set S3C24xx Clock source + */ +static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + + pr_debug("Entered %s\n", __func__); + + iismod &= ~S3C2440_IISMOD_MPLL; + + switch (clk_id) { + case S3C24XX_CLKSRC_PCLK: + break; + case S3C24XX_CLKSRC_MPLL: + iismod |= S3C2440_IISMOD_MPLL; + break; + default: + return -EINVAL; + } + + writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + return 0; +} + +/* + * Set S3C24xx Clock dividers + */ +static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + u32 reg; + + pr_debug("Entered %s\n", __func__); + + switch (div_id) { + case S3C24XX_DIV_BCLK: + reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK; + writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); + break; + case S3C24XX_DIV_MCLK: + reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS); + writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); + break; + case S3C24XX_DIV_PRESCALER: + writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR); + reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * To avoid duplicating clock code, allow machine driver to + * get the clockrate from here. + */ +u32 s3c24xx_i2s_get_clockrate(void) +{ + return clk_get_rate(s3c24xx_i2s.iis_clk); +} +EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); + +static int s3c24xx_i2s_probe(struct snd_soc_dai *dai) +{ + pr_debug("Entered %s\n", __func__); + + s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100); + if (s3c24xx_i2s.regs == NULL) + return -ENXIO; + + s3c24xx_i2s.iis_clk = clk_get(dai->dev, "iis"); + if (s3c24xx_i2s.iis_clk == NULL) { + pr_err("failed to get iis_clock\n"); + iounmap(s3c24xx_i2s.regs); + return -ENODEV; + } + clk_enable(s3c24xx_i2s.iis_clk); + + /* Configure the I2S pins in correct mode */ + s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK); + s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK); + s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK); + s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI); + s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO); + + writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON); + + s3c24xx_snd_txctrl(0); + s3c24xx_snd_rxctrl(0); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai) +{ + pr_debug("Entered %s\n", __func__); + + s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR); + + clk_disable(s3c24xx_i2s.iis_clk); + + return 0; +} + +static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai) +{ + pr_debug("Entered %s\n", __func__); + clk_enable(s3c24xx_i2s.iis_clk); + + writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR); + + return 0; +} +#else +#define s3c24xx_i2s_suspend NULL +#define s3c24xx_i2s_resume NULL +#endif + + +#define S3C24XX_I2S_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_88200 | SNDRV_PCM_RATE_96000) + +static struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = { + .trigger = s3c24xx_i2s_trigger, + .hw_params = s3c24xx_i2s_hw_params, + .set_fmt = s3c24xx_i2s_set_fmt, + .set_clkdiv = s3c24xx_i2s_set_clkdiv, + .set_sysclk = s3c24xx_i2s_set_sysclk, +}; + +static struct snd_soc_dai_driver s3c24xx_i2s_dai = { + .probe = s3c24xx_i2s_probe, + .suspend = s3c24xx_i2s_suspend, + .resume = s3c24xx_i2s_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C24XX_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C24XX_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &s3c24xx_i2s_dai_ops, +}; + +static __devinit int s3c24xx_iis_dev_probe(struct platform_device *pdev) +{ + return snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai); +} + +static __devexit int s3c24xx_iis_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver s3c24xx_iis_driver = { + .probe = s3c24xx_iis_dev_probe, + .remove = s3c24xx_iis_dev_remove, + .driver = { + .name = "s3c24xx-iis", + .owner = THIS_MODULE, + }, +}; + +static int __init s3c24xx_i2s_init(void) +{ + return platform_driver_register(&s3c24xx_iis_driver); +} +module_init(s3c24xx_i2s_init); + +static void __exit s3c24xx_i2s_exit(void) +{ + platform_driver_unregister(&s3c24xx_iis_driver); +} +module_exit(s3c24xx_i2s_exit); + +/* Module information */ +MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("s3c24xx I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c24xx-iis"); diff --git a/sound/soc/samsung/s3c24xx-i2s.h b/sound/soc/samsung/s3c24xx-i2s.h new file mode 100644 index 00000000000..f9ca04edacb --- /dev/null +++ b/sound/soc/samsung/s3c24xx-i2s.h @@ -0,0 +1,35 @@ +/* + * s3c24xx-i2s.c -- ALSA Soc Audio Layer + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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. + * + * Revision history + * 10th Nov 2006 Initial version. + */ + +#ifndef S3C24XXI2S_H_ +#define S3C24XXI2S_H_ + +/* clock sources */ +#define S3C24XX_CLKSRC_PCLK 0 +#define S3C24XX_CLKSRC_MPLL 1 + +/* Clock dividers */ +#define S3C24XX_DIV_MCLK 0 +#define S3C24XX_DIV_BCLK 1 +#define S3C24XX_DIV_PRESCALER 2 + +/* prescaler */ +#define S3C24XX_PRESCALE(a,b) \ + (((a - 1) << S3C2410_IISPSR_INTSHIFT) | ((b - 1) << S3C2410_IISPSR_EXTSHFIT)) + +u32 s3c24xx_i2s_get_clockrate(void); + +#endif /*S3C24XXI2S_H_*/ diff --git a/sound/soc/samsung/s3c24xx_simtec.c b/sound/soc/samsung/s3c24xx_simtec.c new file mode 100644 index 00000000000..a434032d183 --- /dev/null +++ b/sound/soc/samsung/s3c24xx_simtec.c @@ -0,0 +1,394 @@ +/* sound/soc/samsung/s3c24xx_simtec.c + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/clk.h> +#include <linux/i2c.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <plat/audio-simtec.h> + +#include "dma.h" +#include "s3c24xx-i2s.h" +#include "s3c24xx_simtec.h" + +static struct s3c24xx_audio_simtec_pdata *pdata; +static struct clk *xtal_clk; + +static int spk_gain; +static int spk_unmute; + +/** + * speaker_gain_get - read the speaker gain setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be updated. + * + * Read the value for the AMP gain control. + */ +static int speaker_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = spk_gain; + return 0; +} + +/** + * speaker_gain_set - set the value of the speaker amp gain + * @value: The value to write. + */ +static void speaker_gain_set(int value) +{ + gpio_set_value_cansleep(pdata->amp_gain[0], value & 1); + gpio_set_value_cansleep(pdata->amp_gain[1], value >> 1); +} + +/** + * speaker_gain_put - set the speaker gain setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be set. + * + * Set the value of the speaker gain from the specified + * @ucontrol setting. + * + * Note, if the speaker amp is muted, then we do not set a gain value + * as at-least one of the ICs that is fitted will try and power up even + * if the main control is set to off. + */ +static int speaker_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int value = ucontrol->value.integer.value[0]; + + spk_gain = value; + + if (!spk_unmute) + speaker_gain_set(value); + + return 0; +} + +static const struct snd_kcontrol_new amp_gain_controls[] = { + SOC_SINGLE_EXT("Speaker Gain", 0, 0, 3, 0, + speaker_gain_get, speaker_gain_put), +}; + +/** + * spk_unmute_state - set the unmute state of the speaker + * @to: zero to unmute, non-zero to ununmute. + */ +static void spk_unmute_state(int to) +{ + pr_debug("%s: to=%d\n", __func__, to); + + spk_unmute = to; + gpio_set_value(pdata->amp_gpio, to); + + /* if we're umuting, also re-set the gain */ + if (to && pdata->amp_gain[0] > 0) + speaker_gain_set(spk_gain); +} + +/** + * speaker_unmute_get - read the speaker unmute setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be updated. + * + * Read the value for the AMP gain control. + */ +static int speaker_unmute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = spk_unmute; + return 0; +} + +/** + * speaker_unmute_put - set the speaker unmute setting. + * @kcontrol: The control for the speaker gain. + * @ucontrol: The value that needs to be set. + * + * Set the value of the speaker gain from the specified + * @ucontrol setting. + */ +static int speaker_unmute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + spk_unmute_state(ucontrol->value.integer.value[0]); + return 0; +} + +/* This is added as a manual control as the speaker amps create clicks + * when their power state is changed, which are far more noticeable than + * anything produced by the CODEC itself. + */ +static const struct snd_kcontrol_new amp_unmute_controls[] = { + SOC_SINGLE_EXT("Speaker Switch", 0, 0, 1, 0, + speaker_unmute_get, speaker_unmute_put), +}; + +void simtec_audio_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + + if (pdata->amp_gpio > 0) { + pr_debug("%s: adding amp routes\n", __func__); + + snd_soc_add_controls(codec, amp_unmute_controls, + ARRAY_SIZE(amp_unmute_controls)); + } + + if (pdata->amp_gain[0] > 0) { + pr_debug("%s: adding amp controls\n", __func__); + snd_soc_add_controls(codec, amp_gain_controls, + ARRAY_SIZE(amp_gain_controls)); + } +} +EXPORT_SYMBOL_GPL(simtec_audio_init); + +#define CODEC_CLOCK 12000000 + +/** + * simtec_hw_params - update hardware parameters + * @substream: The audio substream instance. + * @params: The parameters requested. + * + * Update the codec data routing and configuration settings + * from the supplied data. + */ +static int simtec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + /* Set the CODEC as the bus clock master, I2S */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret) { + pr_err("%s: failed set cpu dai format\n", __func__); + return ret; + } + + /* Set the CODEC as the bus clock master */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret) { + pr_err("%s: failed set codec dai format\n", __func__); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + CODEC_CLOCK, SND_SOC_CLOCK_IN); + if (ret) { + pr_err( "%s: failed setting codec sysclk\n", __func__); + return ret; + } + + if (pdata->use_mpllin) { + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL, + 0, SND_SOC_CLOCK_OUT); + + if (ret) { + pr_err("%s: failed to set MPLLin as clksrc\n", + __func__); + return ret; + } + } + + if (pdata->output_cdclk) { + int cdclk_scale; + + cdclk_scale = clk_get_rate(xtal_clk) / CODEC_CLOCK; + cdclk_scale--; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + cdclk_scale); + } + + return 0; +} + +static int simtec_call_startup(struct s3c24xx_audio_simtec_pdata *pd) +{ + /* call any board supplied startup code, this currently only + * covers the bast/vr1000 which have a CPLD in the way of the + * LRCLK */ + if (pd->startup) + pd->startup(); + + return 0; +} + +static struct snd_soc_ops simtec_snd_ops = { + .hw_params = simtec_hw_params, +}; + +/** + * attach_gpio_amp - get and configure the necessary gpios + * @dev: The device we're probing. + * @pd: The platform data supplied by the board. + * + * If there is a GPIO based amplifier attached to the board, claim + * the necessary GPIO lines for it, and set default values. + */ +static int attach_gpio_amp(struct device *dev, + struct s3c24xx_audio_simtec_pdata *pd) +{ + int ret; + + /* attach gpio amp gain (if any) */ + if (pdata->amp_gain[0] > 0) { + ret = gpio_request(pd->amp_gain[0], "gpio-amp-gain0"); + if (ret) { + dev_err(dev, "cannot get amp gpio gain0\n"); + return ret; + } + + ret = gpio_request(pd->amp_gain[1], "gpio-amp-gain1"); + if (ret) { + dev_err(dev, "cannot get amp gpio gain1\n"); + gpio_free(pdata->amp_gain[0]); + return ret; + } + + gpio_direction_output(pd->amp_gain[0], 0); + gpio_direction_output(pd->amp_gain[1], 0); + } + + /* note, currently we assume GPA0 isn't valid amp */ + if (pdata->amp_gpio > 0) { + ret = gpio_request(pd->amp_gpio, "gpio-amp"); + if (ret) { + dev_err(dev, "cannot get amp gpio %d (%d)\n", + pd->amp_gpio, ret); + goto err_amp; + } + + /* set the amp off at startup */ + spk_unmute_state(0); + } + + return 0; + +err_amp: + if (pd->amp_gain[0] > 0) { + gpio_free(pd->amp_gain[0]); + gpio_free(pd->amp_gain[1]); + } + + return ret; +} + +static void detach_gpio_amp(struct s3c24xx_audio_simtec_pdata *pd) +{ + if (pd->amp_gain[0] > 0) { + gpio_free(pd->amp_gain[0]); + gpio_free(pd->amp_gain[1]); + } + + if (pd->amp_gpio > 0) + gpio_free(pd->amp_gpio); +} + +#ifdef CONFIG_PM +int simtec_audio_resume(struct device *dev) +{ + simtec_call_startup(pdata); + return 0; +} + +const struct dev_pm_ops simtec_audio_pmops = { + .resume = simtec_audio_resume, +}; +EXPORT_SYMBOL_GPL(simtec_audio_pmops); +#endif + +int __devinit simtec_audio_core_probe(struct platform_device *pdev, + struct snd_soc_card *card) +{ + struct platform_device *snd_dev; + int ret; + + card->dai_link->ops = &simtec_snd_ops; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data supplied\n"); + return -EINVAL; + } + + simtec_call_startup(pdata); + + xtal_clk = clk_get(&pdev->dev, "xtal"); + if (IS_ERR(xtal_clk)) { + dev_err(&pdev->dev, "could not get clkout0\n"); + return -EINVAL; + } + + dev_info(&pdev->dev, "xtal rate is %ld\n", clk_get_rate(xtal_clk)); + + ret = attach_gpio_amp(&pdev->dev, pdata); + if (ret) + goto err_clk; + + snd_dev = platform_device_alloc("soc-audio", -1); + if (!snd_dev) { + dev_err(&pdev->dev, "failed to alloc soc-audio devicec\n"); + ret = -ENOMEM; + goto err_gpio; + } + + platform_set_drvdata(snd_dev, card); + + ret = platform_device_add(snd_dev); + if (ret) { + dev_err(&pdev->dev, "failed to add soc-audio dev\n"); + goto err_pdev; + } + + platform_set_drvdata(pdev, snd_dev); + return 0; + +err_pdev: + platform_device_put(snd_dev); + +err_gpio: + detach_gpio_amp(pdata); + +err_clk: + clk_put(xtal_clk); + return ret; +} +EXPORT_SYMBOL_GPL(simtec_audio_core_probe); + +int __devexit simtec_audio_remove(struct platform_device *pdev) +{ + struct platform_device *snd_dev = platform_get_drvdata(pdev); + + platform_device_unregister(snd_dev); + + detach_gpio_amp(pdata); + clk_put(xtal_clk); + return 0; +} +EXPORT_SYMBOL_GPL(simtec_audio_remove); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("ALSA SoC Simtec Audio common support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c24xx_simtec.h b/sound/soc/samsung/s3c24xx_simtec.h new file mode 100644 index 00000000000..8270748a2c4 --- /dev/null +++ b/sound/soc/samsung/s3c24xx_simtec.h @@ -0,0 +1,22 @@ +/* sound/soc/samsung/s3c24xx_simtec.h + * + * Copyright 2009 Simtec Electronics + * + * 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. +*/ + +extern void simtec_audio_init(struct snd_soc_pcm_runtime *rtd); + +extern int simtec_audio_core_probe(struct platform_device *pdev, + struct snd_soc_card *card); + +extern int simtec_audio_remove(struct platform_device *pdev); + +#ifdef CONFIG_PM +extern const struct dev_pm_ops simtec_audio_pmops; +#define simtec_audio_pm &simtec_audio_pmops +#else +#define simtec_audio_pm NULL +#endif diff --git a/sound/soc/samsung/s3c24xx_simtec_hermes.c b/sound/soc/samsung/s3c24xx_simtec_hermes.c new file mode 100644 index 00000000000..bb4292e3596 --- /dev/null +++ b/sound/soc/samsung/s3c24xx_simtec_hermes.c @@ -0,0 +1,144 @@ +/* sound/soc/samsung/s3c24xx_simtec_hermes.c + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <plat/audio-simtec.h> + +#include "dma.h" +#include "s3c24xx-i2s.h" +#include "s3c24xx_simtec.h" + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_LINE("GSM Out", NULL), + SND_SOC_DAPM_LINE("GSM In", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_LINE("ZV", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_soc_dapm_route base_map[] = { + /* Headphone connected to HP{L,R}OUT and HP{L,R}COM */ + + { "Headphone Jack", NULL, "HPLOUT" }, + { "Headphone Jack", NULL, "HPLCOM" }, + { "Headphone Jack", NULL, "HPROUT" }, + { "Headphone Jack", NULL, "HPRCOM" }, + + /* ZV connected to Line1 */ + + { "LINE1L", NULL, "ZV" }, + { "LINE1R", NULL, "ZV" }, + + /* Line In connected to Line2 */ + + { "LINE2L", NULL, "Line In" }, + { "LINE2R", NULL, "Line In" }, + + /* Microphone connected to MIC3R and MIC_BIAS */ + + { "MIC3L", NULL, "Mic Jack" }, + + /* GSM connected to MONO_LOUT and MIC3L (in) */ + + { "GSM Out", NULL, "MONO_LOUT" }, + { "MIC3L", NULL, "GSM In" }, + + /* Speaker is connected to LINEOUT{LN,LP,RN,RP}, however we are + * not using the DAPM to power it up and down as there it makes + * a click when powering up. */ +}; + +/** + * simtec_hermes_init - initialise and add controls + * @codec; The codec instance to attach to. + * + * Attach our controls and configure the necessary codec + * mappings for our sound card instance. +*/ +static int simtec_hermes_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + snd_soc_dapm_new_controls(dapm, dapm_widgets, + ARRAY_SIZE(dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, base_map, ARRAY_SIZE(base_map)); + + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_enable_pin(dapm, "Line In"); + snd_soc_dapm_enable_pin(dapm, "Line Out"); + snd_soc_dapm_enable_pin(dapm, "Mic Jack"); + + simtec_audio_init(rtd); + snd_soc_dapm_sync(dapm); + + return 0; +} + +static struct snd_soc_dai_link simtec_dai_aic33 = { + .name = "tlv320aic33", + .stream_name = "TLV320AIC33", + .codec_name = "tlv320aic3x-codec.0-0x1a", + .cpu_dai_name = "s3c24xx-i2s", + .codec_dai_name = "tlv320aic3x-hifi", + .platform_name = "samsung-audio", + .init = simtec_hermes_init, +}; + +/* simtec audio machine driver */ +static struct snd_soc_card snd_soc_machine_simtec_aic33 = { + .name = "Simtec-Hermes", + .dai_link = &simtec_dai_aic33, + .num_links = 1, +}; + +static int __devinit simtec_audio_hermes_probe(struct platform_device *pd) +{ + dev_info(&pd->dev, "probing....\n"); + return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic33); +} + +static struct platform_driver simtec_audio_hermes_platdrv = { + .driver = { + .owner = THIS_MODULE, + .name = "s3c24xx-simtec-hermes-snd", + .pm = simtec_audio_pm, + }, + .probe = simtec_audio_hermes_probe, + .remove = __devexit_p(simtec_audio_remove), +}; + +MODULE_ALIAS("platform:s3c24xx-simtec-hermes-snd"); + +static int __init simtec_hermes_modinit(void) +{ + return platform_driver_register(&simtec_audio_hermes_platdrv); +} + +static void __exit simtec_hermes_modexit(void) +{ + platform_driver_unregister(&simtec_audio_hermes_platdrv); +} + +module_init(simtec_hermes_modinit); +module_exit(simtec_hermes_modexit); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("ALSA SoC Simtec Audio support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c new file mode 100644 index 00000000000..fbba4e36772 --- /dev/null +++ b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c @@ -0,0 +1,134 @@ +/* sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c + * + * Copyright 2009 Simtec Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <plat/audio-simtec.h> + +#include "dma.h" +#include "s3c24xx-i2s.h" +#include "s3c24xx_simtec.h" + +#include "../codecs/tlv320aic23.h" + +/* supported machines: + * + * Machine Connections AMP + * ------- ----------- --- + * BAST MIC, HPOUT, LOUT, LIN TPA2001D1 (HPOUTL,R) (gain hardwired) + * VR1000 HPOUT, LIN None + * VR2000 LIN, LOUT, MIC, HP LM4871 (HPOUTL,R) + * DePicture LIN, LOUT, MIC, HP LM4871 (HPOUTL,R) + * Anubis LIN, LOUT, MIC, HP TPA2001D1 (HPOUTL,R) + */ + +static const struct snd_soc_dapm_widget dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route base_map[] = { + { "Headphone Jack", NULL, "LHPOUT"}, + { "Headphone Jack", NULL, "RHPOUT"}, + + { "Line Out", NULL, "LOUT" }, + { "Line Out", NULL, "ROUT" }, + + { "LLINEIN", NULL, "Line In"}, + { "RLINEIN", NULL, "Line In"}, + + { "MICIN", NULL, "Mic Jack"}, +}; + +/** + * simtec_tlv320aic23_init - initialise and add controls + * @codec; The codec instance to attach to. + * + * Attach our controls and configure the necessary codec + * mappings for our sound card instance. +*/ +static int simtec_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + snd_soc_dapm_new_controls(dapm, dapm_widgets, + ARRAY_SIZE(dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, base_map, ARRAY_SIZE(base_map)); + + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_enable_pin(dapm, "Line In"); + snd_soc_dapm_enable_pin(dapm, "Line Out"); + snd_soc_dapm_enable_pin(dapm, "Mic Jack"); + + simtec_audio_init(rtd); + snd_soc_dapm_sync(dapm); + + return 0; +} + +static struct snd_soc_dai_link simtec_dai_aic23 = { + .name = "tlv320aic23", + .stream_name = "TLV320AIC23", + .codec_name = "tlv320aic3x-codec.0-0x1a", + .cpu_dai_name = "s3c24xx-i2s", + .codec_dai_name = "tlv320aic3x-hifi", + .platform_name = "samsung-audio", + .init = simtec_tlv320aic23_init, +}; + +/* simtec audio machine driver */ +static struct snd_soc_card snd_soc_machine_simtec_aic23 = { + .name = "Simtec", + .dai_link = &simtec_dai_aic23, + .num_links = 1, +}; + +static int __devinit simtec_audio_tlv320aic23_probe(struct platform_device *pd) +{ + return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic23); +} + +static struct platform_driver simtec_audio_tlv320aic23_platdrv = { + .driver = { + .owner = THIS_MODULE, + .name = "s3c24xx-simtec-tlv320aic23", + .pm = simtec_audio_pm, + }, + .probe = simtec_audio_tlv320aic23_probe, + .remove = __devexit_p(simtec_audio_remove), +}; + +MODULE_ALIAS("platform:s3c24xx-simtec-tlv320aic23"); + +static int __init simtec_tlv320aic23_modinit(void) +{ + return platform_driver_register(&simtec_audio_tlv320aic23_platdrv); +} + +static void __exit simtec_tlv320aic23_modexit(void) +{ + platform_driver_unregister(&simtec_audio_tlv320aic23_platdrv); +} + +module_init(simtec_tlv320aic23_modinit); +module_exit(simtec_tlv320aic23_modexit); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("ALSA SoC Simtec Audio support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/s3c24xx_uda134x.c b/sound/soc/samsung/s3c24xx_uda134x.c new file mode 100644 index 00000000000..cdc8ecbcb8e --- /dev/null +++ b/sound/soc/samsung/s3c24xx_uda134x.c @@ -0,0 +1,367 @@ +/* + * Modifications by Christian Pellegrin <chripell@evolware.org> + * + * s3c24xx_uda134x.c -- S3C24XX_UDA134X ALSA SoC Audio board driver + * + * Copyright 2007 Dension Audio Systems Ltd. + * Author: Zoltan Devai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/mutex.h> +#include <linux/gpio.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/s3c24xx_uda134x.h> +#include <sound/uda134x.h> + +#include <plat/regs-iis.h> + +#include "dma.h" +#include "s3c24xx-i2s.h" +#include "../codecs/uda134x.h" + + +/* #define ENFORCE_RATES 1 */ +/* + Unfortunately the S3C24XX in master mode has a limited capacity of + generating the clock for the codec. If you define this only rates + that are really available will be enforced. But be careful, most + user level application just want the usual sampling frequencies (8, + 11.025, 22.050, 44.1 kHz) and anyway resampling is a costly + operation for embedded systems. So if you aren't very lucky or your + hardware engineer wasn't very forward-looking it's better to leave + this undefined. If you do so an approximate value for the requested + sampling rate in the range -/+ 5% will be chosen. If this in not + possible an error will be returned. +*/ + +static struct clk *xtal; +static struct clk *pclk; +/* this is need because we don't have a place where to keep the + * pointers to the clocks in each substream. We get the clocks only + * when we are actually using them so we don't block stuff like + * frequency change or oscillator power-off */ +static int clk_users; +static DEFINE_MUTEX(clk_lock); + +static unsigned int rates[33 * 2]; +#ifdef ENFORCE_RATES +static struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, +}; +#endif + +static struct platform_device *s3c24xx_uda134x_snd_device; + +static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; +#ifdef ENFORCE_RATES + struct snd_pcm_runtime *runtime = substream->runtime; +#endif + + mutex_lock(&clk_lock); + pr_debug("%s %d\n", __func__, clk_users); + if (clk_users == 0) { + xtal = clk_get(&s3c24xx_uda134x_snd_device->dev, "xtal"); + if (!xtal) { + printk(KERN_ERR "%s cannot get xtal\n", __func__); + ret = -EBUSY; + } else { + pclk = clk_get(&s3c24xx_uda134x_snd_device->dev, + "pclk"); + if (!pclk) { + printk(KERN_ERR "%s cannot get pclk\n", + __func__); + clk_put(xtal); + ret = -EBUSY; + } + } + if (!ret) { + int i, j; + + for (i = 0; i < 2; i++) { + int fs = i ? 256 : 384; + + rates[i*33] = clk_get_rate(xtal) / fs; + for (j = 1; j < 33; j++) + rates[i*33 + j] = clk_get_rate(pclk) / + (j * fs); + } + } + } + clk_users += 1; + mutex_unlock(&clk_lock); + if (!ret) { +#ifdef ENFORCE_RATES + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_rates); + if (ret < 0) + printk(KERN_ERR "%s cannot set constraints\n", + __func__); +#endif + } + return ret; +} + +static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream) +{ + mutex_lock(&clk_lock); + pr_debug("%s %d\n", __func__, clk_users); + clk_users -= 1; + if (clk_users == 0) { + clk_put(xtal); + xtal = NULL; + clk_put(pclk); + pclk = NULL; + } + mutex_unlock(&clk_lock); +} + +static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret = 0; + int clk_source, fs_mode; + unsigned long rate = params_rate(params); + long err, cerr; + unsigned int div; + int i, bi; + + err = 999999; + bi = 0; + for (i = 0; i < 2*33; i++) { + cerr = rates[i] - rate; + if (cerr < 0) + cerr = -cerr; + if (cerr < err) { + err = cerr; + bi = i; + } + } + if (bi / 33 == 1) + fs_mode = S3C2410_IISMOD_256FS; + else + fs_mode = S3C2410_IISMOD_384FS; + if (bi % 33 == 0) { + clk_source = S3C24XX_CLKSRC_MPLL; + div = 1; + } else { + clk_source = S3C24XX_CLKSRC_PCLK; + div = bi % 33; + } + pr_debug("%s desired rate %lu, %d\n", __func__, rate, bi); + + clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate; + pr_debug("%s will use: %s %s %d sysclk %d err %ld\n", __func__, + fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS", + clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK", + div, clk, err); + + if ((err * 100 / rate) > 5) { + printk(KERN_ERR "S3C24XX_UDA134X: effective frequency " + "too different from desired (%ld%%)\n", + err * 100 / rate); + return -EINVAL; + } + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(div, div)); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops s3c24xx_uda134x_ops = { + .startup = s3c24xx_uda134x_startup, + .shutdown = s3c24xx_uda134x_shutdown, + .hw_params = s3c24xx_uda134x_hw_params, +}; + +static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = { + .name = "UDA134X", + .stream_name = "UDA134X", + .codec_name = "uda134x-hifi", + .codec_dai_name = "uda134x-hifi", + .cpu_dai_name = "s3c24xx-i2s", + .ops = &s3c24xx_uda134x_ops, + .platform_name = "samsung-audio", +}; + +static struct snd_soc_card snd_soc_s3c24xx_uda134x = { + .name = "S3C24XX_UDA134X", + .dai_link = &s3c24xx_uda134x_dai_link, + .num_links = 1, +}; + +static struct s3c24xx_uda134x_platform_data *s3c24xx_uda134x_l3_pins; + +static void setdat(int v) +{ + gpio_set_value(s3c24xx_uda134x_l3_pins->l3_data, v > 0); +} + +static void setclk(int v) +{ + gpio_set_value(s3c24xx_uda134x_l3_pins->l3_clk, v > 0); +} + +static void setmode(int v) +{ + gpio_set_value(s3c24xx_uda134x_l3_pins->l3_mode, v > 0); +} + +/* FIXME - This must be codec platform data but in which board file ?? */ +static struct uda134x_platform_data s3c24xx_uda134x = { + .l3 = { + .setdat = setdat, + .setclk = setclk, + .setmode = setmode, + .data_hold = 1, + .data_setup = 1, + .clock_high = 1, + .mode_hold = 1, + .mode = 1, + .mode_setup = 1, + }, +}; + +static int s3c24xx_uda134x_setup_pin(int pin, char *fun) +{ + if (gpio_request(pin, "s3c24xx_uda134x") < 0) { + printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: " + "l3 %s pin already in use", fun); + return -EBUSY; + } + gpio_direction_output(pin, 0); + return 0; +} + +static int s3c24xx_uda134x_probe(struct platform_device *pdev) +{ + int ret; + + printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver\n"); + + s3c24xx_uda134x_l3_pins = pdev->dev.platform_data; + if (s3c24xx_uda134x_l3_pins == NULL) { + printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: " + "unable to find platform data\n"); + return -ENODEV; + } + s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power; + s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model; + + if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data, + "data") < 0) + return -EBUSY; + if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk, + "clk") < 0) { + gpio_free(s3c24xx_uda134x_l3_pins->l3_data); + return -EBUSY; + } + if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode, + "mode") < 0) { + gpio_free(s3c24xx_uda134x_l3_pins->l3_data); + gpio_free(s3c24xx_uda134x_l3_pins->l3_clk); + return -EBUSY; + } + + s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1); + if (!s3c24xx_uda134x_snd_device) { + printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: " + "Unable to register\n"); + return -ENOMEM; + } + + platform_set_drvdata(s3c24xx_uda134x_snd_device, + &snd_soc_s3c24xx_uda134x); + ret = platform_device_add(s3c24xx_uda134x_snd_device); + if (ret) { + printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add\n"); + platform_device_put(s3c24xx_uda134x_snd_device); + } + + return ret; +} + +static int s3c24xx_uda134x_remove(struct platform_device *pdev) +{ + platform_device_unregister(s3c24xx_uda134x_snd_device); + gpio_free(s3c24xx_uda134x_l3_pins->l3_data); + gpio_free(s3c24xx_uda134x_l3_pins->l3_clk); + gpio_free(s3c24xx_uda134x_l3_pins->l3_mode); + return 0; +} + +static struct platform_driver s3c24xx_uda134x_driver = { + .probe = s3c24xx_uda134x_probe, + .remove = s3c24xx_uda134x_remove, + .driver = { + .name = "s3c24xx_uda134x", + .owner = THIS_MODULE, + }, +}; + +static int __init s3c24xx_uda134x_init(void) +{ + return platform_driver_register(&s3c24xx_uda134x_driver); +} + +static void __exit s3c24xx_uda134x_exit(void) +{ + platform_driver_unregister(&s3c24xx_uda134x_driver); +} + + +module_init(s3c24xx_uda134x_init); +module_exit(s3c24xx_uda134x_exit); + +MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>"); +MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c new file mode 100644 index 00000000000..61e2b5219d4 --- /dev/null +++ b/sound/soc/samsung/smartq_wm8987.c @@ -0,0 +1,290 @@ +/* sound/soc/samsung/smartq_wm8987.c + * + * Copyright 2010 Maurus Cuelenaere <mcuelenaere@gmail.com> + * + * Based on smdk6410_wm8987.c + * Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com + * Graeme Gregory - graeme.gregory@wolfsonmicro.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/platform_device.h> +#include <linux/gpio.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/jack.h> + +#include <asm/mach-types.h> + +#include "dma.h" +#include "i2s.h" + +#include "../codecs/wm8750.h" + +/* + * WM8987 is register compatible with WM8750, so using that as base driver. + */ + +static struct snd_soc_card snd_soc_smartq; + +static int smartq_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int clk = 0; + int ret; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 32000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + clk = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* Use PCLK for I2S signal generation */ + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* Gate the RCLK output on PAD */ + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +/* + * SmartQ WM8987 HiFi DAI operations. + */ +static struct snd_soc_ops smartq_hifi_ops = { + .hw_params = smartq_hifi_hw_params, +}; + +static struct snd_soc_jack smartq_jack; + +static struct snd_soc_jack_pin smartq_jack_pins[] = { + /* Disable speaker when headphone is plugged in */ + { + .pin = "Internal Speaker", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static struct snd_soc_jack_gpio smartq_jack_gpios[] = { + { + .gpio = S3C64XX_GPL(12), + .name = "headphone detect", + .report = SND_JACK_HEADPHONE, + .debounce_time = 200, + }, +}; + +static const struct snd_kcontrol_new wm8987_smartq_controls[] = { + SOC_DAPM_PIN_SWITCH("Internal Speaker"), + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Internal Mic"), +}; + +static int smartq_speaker_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, + int event) +{ + gpio_set_value(S3C64XX_GPK(12), SND_SOC_DAPM_EVENT_OFF(event)); + + return 0; +} + +static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Internal Speaker", smartq_speaker_event), + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Internal Mic", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LOUT2"}, + {"Headphone Jack", NULL, "ROUT2"}, + + {"Internal Speaker", NULL, "LOUT2"}, + {"Internal Speaker", NULL, "ROUT2"}, + + {"Mic Bias", NULL, "Internal Mic"}, + {"LINPUT2", NULL, "Mic Bias"}, +}; + +static int smartq_wm8987_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int err = 0; + + /* Add SmartQ specific widgets */ + snd_soc_dapm_new_controls(dapm, wm8987_dapm_widgets, + ARRAY_SIZE(wm8987_dapm_widgets)); + + /* add SmartQ specific controls */ + err = snd_soc_add_controls(codec, wm8987_smartq_controls, + ARRAY_SIZE(wm8987_smartq_controls)); + + if (err < 0) + return err; + + /* setup SmartQ specific audio path */ + snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + + /* set endpoints to not connected */ + snd_soc_dapm_nc_pin(dapm, "LINPUT1"); + snd_soc_dapm_nc_pin(dapm, "RINPUT1"); + snd_soc_dapm_nc_pin(dapm, "OUT3"); + snd_soc_dapm_nc_pin(dapm, "ROUT1"); + + /* set endpoints to default off mode */ + snd_soc_dapm_enable_pin(dapm, "Internal Speaker"); + snd_soc_dapm_enable_pin(dapm, "Internal Mic"); + snd_soc_dapm_disable_pin(dapm, "Headphone Jack"); + + err = snd_soc_dapm_sync(dapm); + if (err) + return err; + + /* Headphone jack detection */ + err = snd_soc_jack_new(codec, "Headphone Jack", + SND_JACK_HEADPHONE, &smartq_jack); + if (err) + return err; + + err = snd_soc_jack_add_pins(&smartq_jack, ARRAY_SIZE(smartq_jack_pins), + smartq_jack_pins); + if (err) + return err; + + err = snd_soc_jack_add_gpios(&smartq_jack, + ARRAY_SIZE(smartq_jack_gpios), + smartq_jack_gpios); + + return err; +} + +static struct snd_soc_dai_link smartq_dai[] = { + { + .name = "wm8987", + .stream_name = "SmartQ Hi-Fi", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8750-hifi", + .platform_name = "samsung-audio", + .codec_name = "wm8750-codec.0-0x1a", + .init = smartq_wm8987_init, + .ops = &smartq_hifi_ops, + }, +}; + +static struct snd_soc_card snd_soc_smartq = { + .name = "SmartQ", + .dai_link = smartq_dai, + .num_links = ARRAY_SIZE(smartq_dai), +}; + +static struct platform_device *smartq_snd_device; + +static int __init smartq_init(void) +{ + int ret; + + if (!machine_is_smartq7() && !machine_is_smartq5()) { + pr_info("Only SmartQ is supported by this ASoC driver\n"); + return -ENODEV; + } + + smartq_snd_device = platform_device_alloc("soc-audio", -1); + if (!smartq_snd_device) + return -ENOMEM; + + platform_set_drvdata(smartq_snd_device, &snd_soc_smartq); + + ret = platform_device_add(smartq_snd_device); + if (ret) { + platform_device_put(smartq_snd_device); + return ret; + } + + /* Initialise GPIOs used by amplifiers */ + ret = gpio_request(S3C64XX_GPK(12), "amplifiers shutdown"); + if (ret) { + dev_err(&smartq_snd_device->dev, "Failed to register GPK12\n"); + goto err_unregister_device; + } + + /* Disable amplifiers */ + ret = gpio_direction_output(S3C64XX_GPK(12), 1); + if (ret) { + dev_err(&smartq_snd_device->dev, "Failed to configure GPK12\n"); + goto err_free_gpio_amp_shut; + } + + return 0; + +err_free_gpio_amp_shut: + gpio_free(S3C64XX_GPK(12)); +err_unregister_device: + platform_device_unregister(smartq_snd_device); + + return ret; +} + +static void __exit smartq_exit(void) +{ + gpio_free(S3C64XX_GPK(12)); + snd_soc_jack_free_gpios(&smartq_jack, ARRAY_SIZE(smartq_jack_gpios), + smartq_jack_gpios); + + platform_device_unregister(smartq_snd_device); +} + +module_init(smartq_init); +module_exit(smartq_exit); + +/* Module information */ +MODULE_AUTHOR("Maurus Cuelenaere <mcuelenaere@gmail.com>"); +MODULE_DESCRIPTION("ALSA SoC SmartQ WM8987"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smdk2443_wm9710.c b/sound/soc/samsung/smdk2443_wm9710.c new file mode 100644 index 00000000000..3be7e7e92d6 --- /dev/null +++ b/sound/soc/samsung/smdk2443_wm9710.c @@ -0,0 +1,73 @@ +/* + * smdk2443_wm9710.c -- SoC audio for smdk2443 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include "dma.h" +#include "ac97.h" + +static struct snd_soc_card smdk2443; + +static struct snd_soc_dai_link smdk2443_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "samsung-ac97", + .codec_dai_name = "ac97-hifi", + .codec_name = "ac97-codec", + .platform_name = "samsung-audio", +}, +}; + +static struct snd_soc_card smdk2443 = { + .name = "SMDK2443", + .dai_link = smdk2443_dai, + .num_links = ARRAY_SIZE(smdk2443_dai), +}; + +static struct platform_device *smdk2443_snd_ac97_device; + +static int __init smdk2443_init(void) +{ + int ret; + + smdk2443_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!smdk2443_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(smdk2443_snd_ac97_device, &smdk2443); + ret = platform_device_add(smdk2443_snd_ac97_device); + + if (ret) + platform_device_put(smdk2443_snd_ac97_device); + + return ret; +} + +static void __exit smdk2443_exit(void) +{ + platform_device_unregister(smdk2443_snd_ac97_device); +} + +module_init(smdk2443_init); +module_exit(smdk2443_exit); + +/* Module information */ +MODULE_AUTHOR("Graeme Gregory, graeme.gregory@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("ALSA SoC WM9710 SMDK2443"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c new file mode 100644 index 00000000000..b5c3fad01bb --- /dev/null +++ b/sound/soc/samsung/smdk_spdif.c @@ -0,0 +1,226 @@ +/* + * smdk_spdif.c -- S/PDIF audio for SMDK + * + * Copyright 2010 Samsung Electronics Co. Ltd. + * + * 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 <linux/clk.h> + +#include <plat/devs.h> + +#include <sound/soc.h> + +#include "dma.h" +#include "spdif.h" + +/* Audio clock settings are belonged to board specific part. Every + * board can set audio source clock setting which is matched with H/W + * like this function-'set_audio_clock_heirachy'. + */ +static int set_audio_clock_heirachy(struct platform_device *pdev) +{ + struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif; + int ret = 0; + + fout_epll = clk_get(NULL, "fout_epll"); + if (IS_ERR(fout_epll)) { + printk(KERN_WARNING "%s: Cannot find fout_epll.\n", + __func__); + return -EINVAL; + } + + mout_epll = clk_get(NULL, "mout_epll"); + if (IS_ERR(mout_epll)) { + printk(KERN_WARNING "%s: Cannot find mout_epll.\n", + __func__); + ret = -EINVAL; + goto out1; + } + + sclk_audio0 = clk_get(&pdev->dev, "sclk_audio"); + if (IS_ERR(sclk_audio0)) { + printk(KERN_WARNING "%s: Cannot find sclk_audio.\n", + __func__); + ret = -EINVAL; + goto out2; + } + + sclk_spdif = clk_get(NULL, "sclk_spdif"); + if (IS_ERR(sclk_spdif)) { + printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n", + __func__); + ret = -EINVAL; + goto out3; + } + + /* Set audio clock hierarchy for S/PDIF */ + clk_set_parent(mout_epll, fout_epll); + clk_set_parent(sclk_audio0, mout_epll); + clk_set_parent(sclk_spdif, sclk_audio0); + + clk_put(sclk_spdif); +out3: + clk_put(sclk_audio0); +out2: + clk_put(mout_epll); +out1: + clk_put(fout_epll); + + return ret; +} + +/* We should haved to set clock directly on this part because of clock + * scheme of Samsudng SoCs did not support to set rates from abstrct + * clock of it's hierarchy. + */ +static int set_audio_clock_rate(unsigned long epll_rate, + unsigned long audio_rate) +{ + struct clk *fout_epll, *sclk_spdif; + + fout_epll = clk_get(NULL, "fout_epll"); + if (IS_ERR(fout_epll)) { + printk(KERN_ERR "%s: failed to get fout_epll\n", __func__); + return -ENOENT; + } + + clk_set_rate(fout_epll, epll_rate); + clk_put(fout_epll); + + sclk_spdif = clk_get(NULL, "sclk_spdif"); + if (IS_ERR(sclk_spdif)) { + printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__); + return -ENOENT; + } + + clk_set_rate(sclk_spdif, audio_rate); + clk_put(sclk_spdif); + + return 0; +} + +static int smdk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned long pll_out, rclk_rate; + int ret, ratio; + + switch (params_rate(params)) { + case 44100: + pll_out = 45158400; + break; + case 32000: + case 48000: + case 96000: + pll_out = 49152000; + break; + default: + return -EINVAL; + } + + /* Setting ratio to 512fs helps to use S/PDIF with HDMI without + * modify S/PDIF ASoC machine driver. + */ + ratio = 512; + rclk_rate = params_rate(params) * ratio; + + /* Set audio source clock rates */ + ret = set_audio_clock_rate(pll_out, rclk_rate); + if (ret < 0) + return ret; + + /* Set S/PDIF uses internal source clock */ + ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK, + rclk_rate, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return ret; +} + +static struct snd_soc_ops smdk_spdif_ops = { + .hw_params = smdk_hw_params, +}; + +static struct snd_soc_dai_link smdk_dai = { + .name = "S/PDIF", + .stream_name = "S/PDIF PCM Playback", + .platform_name = "samsung-audio", + .cpu_dai_name = "samsung-spdif", + .codec_dai_name = "dit-hifi", + .codec_name = "spdif-dit", + .ops = &smdk_spdif_ops, +}; + +static struct snd_soc_card smdk = { + .name = "SMDK-S/PDIF", + .dai_link = &smdk_dai, + .num_links = 1, +}; + +static struct platform_device *smdk_snd_spdif_dit_device; +static struct platform_device *smdk_snd_spdif_device; + +static int __init smdk_init(void) +{ + int ret; + + smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1); + if (!smdk_snd_spdif_dit_device) + return -ENOMEM; + + ret = platform_device_add(smdk_snd_spdif_dit_device); + if (ret) + goto err1; + + smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1); + if (!smdk_snd_spdif_device) { + ret = -ENOMEM; + goto err2; + } + + platform_set_drvdata(smdk_snd_spdif_device, &smdk); + + ret = platform_device_add(smdk_snd_spdif_device); + if (ret) + goto err3; + + /* Set audio clock hierarchy manually */ + ret = set_audio_clock_heirachy(smdk_snd_spdif_device); + if (ret) + goto err4; + + return 0; +err4: + platform_device_del(smdk_snd_spdif_device); +err3: + platform_device_put(smdk_snd_spdif_device); +err2: + platform_device_del(smdk_snd_spdif_dit_device); +err1: + platform_device_put(smdk_snd_spdif_dit_device); + return ret; +} + +static void __exit smdk_exit(void) +{ + platform_device_unregister(smdk_snd_spdif_device); + platform_device_unregister(smdk_snd_spdif_dit_device); +} + +module_init(smdk_init); +module_exit(smdk_exit); + +MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>"); +MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c new file mode 100644 index 00000000000..b2cff1a44ae --- /dev/null +++ b/sound/soc/samsung/smdk_wm8580.c @@ -0,0 +1,292 @@ +/* + * smdk_wm8580.c + * + * Copyright (c) 2009 Samsung Electronics Co. Ltd + * Author: Jaswinder Singh <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/platform_device.h> +#include <linux/clk.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/mach-types.h> + +#include "../codecs/wm8580.h" +#include "dma.h" +#include "i2s.h" + +/* + * Default CFG switch settings to use this driver: + * + * SMDK6410: Set CFG1 1-3 Off, CFG2 1-4 On + */ + +/* SMDK has a 12MHZ crystal attached to WM8580 */ +#define SMDK_WM8580_FREQ 12000000 + +static int smdk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pll_out; + int bfs, rfs, ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S8: + bfs = 16; + break; + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_S16_LE: + bfs = 32; + break; + default: + return -EINVAL; + } + + /* The Fvco for WM8580 PLLs must fall within [90,100]MHz. + * This criterion can't be met if we request PLL output + * as {8000x256, 64000x256, 11025x256}Hz. + * As a wayout, we rather change rfs to a minimum value that + * results in (params_rate(params) * rfs), and itself, acceptable + * to both - the CODEC and the CPU. + */ + switch (params_rate(params)) { + case 16000: + case 22050: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + rfs = 256; + break; + case 64000: + rfs = 384; + break; + case 8000: + case 11025: + rfs = 512; + break; + default: + return -EINVAL; + } + pll_out = params_rate(params) * rfs; + + /* Set the Codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* Set the AP DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* Set WM8580 to drive MCLK from its PLLA */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK, + WM8580_CLKSRC_PLLA); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(codec_dai, WM8580_PLLA, 0, + SMDK_WM8580_FREQ, pll_out); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA, + pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +/* + * SMDK WM8580 DAI operations. + */ +static struct snd_soc_ops smdk_ops = { + .hw_params = smdk_hw_params, +}; + +/* SMDK Playback widgets */ +static const struct snd_soc_dapm_widget wm8580_dapm_widgets_pbk[] = { + SND_SOC_DAPM_HP("Front", NULL), + SND_SOC_DAPM_HP("Center+Sub", NULL), + SND_SOC_DAPM_HP("Rear", NULL), +}; + +/* SMDK Capture widgets */ +static const struct snd_soc_dapm_widget wm8580_dapm_widgets_cpt[] = { + SND_SOC_DAPM_MIC("MicIn", NULL), + SND_SOC_DAPM_LINE("LineIn", NULL), +}; + +/* SMDK-PAIFTX connections */ +static const struct snd_soc_dapm_route audio_map_tx[] = { + /* MicIn feeds AINL */ + {"AINL", NULL, "MicIn"}, + + /* LineIn feeds AINL/R */ + {"AINL", NULL, "LineIn"}, + {"AINR", NULL, "LineIn"}, +}; + +/* SMDK-PAIFRX connections */ +static const struct snd_soc_dapm_route audio_map_rx[] = { + /* Front Left/Right are fed VOUT1L/R */ + {"Front", NULL, "VOUT1L"}, + {"Front", NULL, "VOUT1R"}, + + /* Center/Sub are fed VOUT2L/R */ + {"Center+Sub", NULL, "VOUT2L"}, + {"Center+Sub", NULL, "VOUT2R"}, + + /* Rear Left/Right are fed VOUT3L/R */ + {"Rear", NULL, "VOUT3L"}, + {"Rear", NULL, "VOUT3R"}, +}; + +static int smdk_wm8580_init_paiftx(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + /* Add smdk specific Capture widgets */ + snd_soc_dapm_new_controls(dapm, wm8580_dapm_widgets_cpt, + ARRAY_SIZE(wm8580_dapm_widgets_cpt)); + + /* Set up PAIFTX audio path */ + snd_soc_dapm_add_routes(dapm, audio_map_tx, ARRAY_SIZE(audio_map_tx)); + + /* Enabling the microphone requires the fitting of a 0R + * resistor to connect the line from the microphone jack. + */ + snd_soc_dapm_disable_pin(dapm, "MicIn"); + + /* signal a DAPM event */ + snd_soc_dapm_sync(dapm); + + return 0; +} + +static int smdk_wm8580_init_paifrx(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + /* Add smdk specific Playback widgets */ + snd_soc_dapm_new_controls(dapm, wm8580_dapm_widgets_pbk, + ARRAY_SIZE(wm8580_dapm_widgets_pbk)); + + /* Set up PAIFRX audio path */ + snd_soc_dapm_add_routes(dapm, audio_map_rx, ARRAY_SIZE(audio_map_rx)); + + /* signal a DAPM event */ + snd_soc_dapm_sync(dapm); + + return 0; +} + +enum { + PRI_PLAYBACK = 0, + PRI_CAPTURE, + SEC_PLAYBACK, +}; + +static struct snd_soc_dai_link smdk_dai[] = { + [PRI_PLAYBACK] = { /* Primary Playback i/f */ + .name = "WM8580 PAIF RX", + .stream_name = "Playback", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8580-hifi-playback", + .platform_name = "samsung-audio", + .codec_name = "wm8580-codec.0-001b", + .init = smdk_wm8580_init_paifrx, + .ops = &smdk_ops, + }, + [PRI_CAPTURE] = { /* Primary Capture i/f */ + .name = "WM8580 PAIF TX", + .stream_name = "Capture", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8580-hifi-capture", + .platform_name = "samsung-audio", + .codec_name = "wm8580-codec.0-001b", + .init = smdk_wm8580_init_paiftx, + .ops = &smdk_ops, + }, + [SEC_PLAYBACK] = { /* Sec_Fifo Playback i/f */ + .name = "Sec_FIFO TX", + .stream_name = "Playback", + .cpu_dai_name = "samsung-i2s.x", + .codec_dai_name = "wm8580-hifi-playback", + .platform_name = "samsung-audio", + .codec_name = "wm8580-codec.0-001b", + .init = smdk_wm8580_init_paifrx, + .ops = &smdk_ops, + }, +}; + +static struct snd_soc_card smdk = { + .name = "SMDK-I2S", + .dai_link = smdk_dai, + .num_links = 2, +}; + +static struct platform_device *smdk_snd_device; + +static int __init smdk_audio_init(void) +{ + int ret; + char *str; + + if (machine_is_smdkc100() || machine_is_smdk6442() + || machine_is_smdkv210() || machine_is_smdkc110()) { + smdk.num_links = 3; + /* Secondary is at offset SAMSUNG_I2S_SECOFF from Primary */ + str = (char *)smdk_dai[SEC_PLAYBACK].cpu_dai_name; + str[strlen(str) - 1] = '0' + SAMSUNG_I2S_SECOFF; + } else if (machine_is_smdk6410()) { + str = (char *)smdk_dai[PRI_PLAYBACK].cpu_dai_name; + str[strlen(str) - 1] = '2'; + str = (char *)smdk_dai[PRI_CAPTURE].cpu_dai_name; + str[strlen(str) - 1] = '2'; + } + + smdk_snd_device = platform_device_alloc("soc-audio", -1); + if (!smdk_snd_device) + return -ENOMEM; + + platform_set_drvdata(smdk_snd_device, &smdk); + ret = platform_device_add(smdk_snd_device); + + if (ret) + platform_device_put(smdk_snd_device); + + return ret; +} +module_init(smdk_audio_init); + +static void __exit smdk_audio_exit(void) +{ + platform_device_unregister(smdk_snd_device); +} +module_exit(smdk_audio_exit); + +MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com"); +MODULE_DESCRIPTION("ALSA SoC SMDK WM8580"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smdk_wm8994.c b/sound/soc/samsung/smdk_wm8994.c new file mode 100644 index 00000000000..e7c1009a1e1 --- /dev/null +++ b/sound/soc/samsung/smdk_wm8994.c @@ -0,0 +1,176 @@ +/* + * smdk_wm8994.c + * + * 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 "../codecs/wm8994.h" + + /* + * Default CFG switch settings to use this driver: + * SMDKV310: CFG5-1000, CFG7-111111 + */ + + /* + * Configure audio route as :- + * $ amixer sset 'DAC1' on,on + * $ amixer sset 'Right Headphone Mux' 'DAC' + * $ amixer sset 'Left Headphone Mux' 'DAC' + * $ amixer sset 'DAC1R Mixer AIF1.1' on + * $ amixer sset 'DAC1L Mixer AIF1.1' on + * $ amixer sset 'IN2L' on + * $ amixer sset 'IN2L PGA IN2LN' on + * $ amixer sset 'MIXINL IN2L' on + * $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on + * $ amixer sset 'IN2R' on + * $ amixer sset 'IN2R PGA IN2RN' on + * $ amixer sset 'MIXINR IN2R' on + * $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on + */ + +/* SMDK has a 16.934MHZ crystal attached to WM8994 */ +#define SMDK_WM8994_FREQ 16934000 + +static int smdk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pll_out; + int ret; + + /* AIF1CLK should be >=3MHz for optimal performance */ + if (params_rate(params) == 8000 || params_rate(params) == 11025) + pll_out = params_rate(params) * 512; + else + pll_out = params_rate(params) * 256; + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1, + SMDK_WM8994_FREQ, pll_out); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +/* + * SMDK WM8994 DAI operations. + */ +static struct snd_soc_ops smdk_ops = { + .hw_params = smdk_hw_params, +}; + +static int smdk_wm8994_init_paiftx(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + /* HeadPhone */ + snd_soc_dapm_enable_pin(dapm, "HPOUT1R"); + snd_soc_dapm_enable_pin(dapm, "HPOUT1L"); + + /* MicIn */ + snd_soc_dapm_enable_pin(dapm, "IN1LN"); + snd_soc_dapm_enable_pin(dapm, "IN1RN"); + + /* LineIn */ + snd_soc_dapm_enable_pin(dapm, "IN2LN"); + snd_soc_dapm_enable_pin(dapm, "IN2RN"); + + /* Other pins NC */ + snd_soc_dapm_nc_pin(dapm, "HPOUT2P"); + snd_soc_dapm_nc_pin(dapm, "HPOUT2N"); + snd_soc_dapm_nc_pin(dapm, "SPKOUTLN"); + snd_soc_dapm_nc_pin(dapm, "SPKOUTLP"); + snd_soc_dapm_nc_pin(dapm, "SPKOUTRP"); + snd_soc_dapm_nc_pin(dapm, "SPKOUTRN"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT1N"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT1P"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT2N"); + snd_soc_dapm_nc_pin(dapm, "LINEOUT2P"); + snd_soc_dapm_nc_pin(dapm, "IN1LP"); + snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN"); + snd_soc_dapm_nc_pin(dapm, "IN1RP"); + snd_soc_dapm_nc_pin(dapm, "IN2RP:VXRP"); + + snd_soc_dapm_sync(dapm); + + return 0; +} + +static struct snd_soc_dai_link smdk_dai[] = { + { /* Primary DAI i/f */ + .name = "WM8994 AIF1", + .stream_name = "Pri_Dai", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-audio", + .codec_name = "wm8994-codec", + .init = smdk_wm8994_init_paiftx, + .ops = &smdk_ops, + }, { /* Sec_Fifo Playback i/f */ + .name = "Sec_FIFO TX", + .stream_name = "Sec_Dai", + .cpu_dai_name = "samsung-i2s.4", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-audio", + .codec_name = "wm8994-codec", + .ops = &smdk_ops, + }, +}; + +static struct snd_soc_card smdk = { + .name = "SMDK-I2S", + .dai_link = smdk_dai, + .num_links = ARRAY_SIZE(smdk_dai), +}; + +static struct platform_device *smdk_snd_device; + +static int __init smdk_audio_init(void) +{ + int ret; + + smdk_snd_device = platform_device_alloc("soc-audio", -1); + if (!smdk_snd_device) + return -ENOMEM; + + platform_set_drvdata(smdk_snd_device, &smdk); + + ret = platform_device_add(smdk_snd_device); + if (ret) + platform_device_put(smdk_snd_device); + + return ret; +} +module_init(smdk_audio_init); + +static void __exit smdk_audio_exit(void) +{ + platform_device_unregister(smdk_snd_device); +} +module_exit(smdk_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC SMDK WM8994"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/smdk_wm9713.c b/sound/soc/samsung/smdk_wm9713.c new file mode 100644 index 00000000000..ae5fed6f772 --- /dev/null +++ b/sound/soc/samsung/smdk_wm9713.c @@ -0,0 +1,111 @@ +/* + * 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 "dma.h" +#include "ac97.h" + +static struct snd_soc_card smdk; + +/* + * Default CFG switch settings to use this driver: + * + * SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off + * SMDKC100: Set CFG6 1-3 On, CFG7 1 On + * SMDKC110: Set CFGB10 1-2 Off, CFGB12 1-3 On + * SMDKV210: Set CFGB10 1-2 Off, CFGB12 1-3 On + * SMDKV310: Set CFG2 1-2 Off, CFG4 All On, CFG7 All Off, CFG8 1-On + */ + +/* + Playback (HeadPhone):- + $ amixer sset 'Headphone' unmute + $ amixer sset 'Right Headphone Out Mux' 'Headphone' + $ amixer sset 'Left Headphone Out Mux' 'Headphone' + $ amixer sset 'Right HP Mixer PCM' unmute + $ amixer sset 'Left HP Mixer PCM' unmute + + Capture (LineIn):- + $ amixer sset 'Right Capture Source' 'Line' + $ amixer sset 'Left Capture Source' 'Line' +*/ + +static struct snd_soc_dai_link smdk_dai = { + .name = "AC97", + .stream_name = "AC97 PCM", + .platform_name = "samsung-audio", + .cpu_dai_name = "samsung-ac97", + .codec_dai_name = "wm9713-hifi", + .codec_name = "wm9713-codec", +}; + +static struct snd_soc_card smdk = { + .name = "SMDK WM9713", + .dai_link = &smdk_dai, + .num_links = 1, +}; + +static struct platform_device *smdk_snd_wm9713_device; +static struct platform_device *smdk_snd_ac97_device; + +static int __init smdk_init(void) +{ + int ret; + + smdk_snd_wm9713_device = platform_device_alloc("wm9713-codec", -1); + if (!smdk_snd_wm9713_device) + return -ENOMEM; + + ret = platform_device_add(smdk_snd_wm9713_device); + if (ret) + goto err1; + + smdk_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!smdk_snd_ac97_device) { + ret = -ENOMEM; + goto err2; + } + + platform_set_drvdata(smdk_snd_ac97_device, &smdk); + + ret = platform_device_add(smdk_snd_ac97_device); + if (ret) + goto err3; + + return 0; + +err3: + platform_device_put(smdk_snd_ac97_device); +err2: + platform_device_del(smdk_snd_wm9713_device); +err1: + platform_device_put(smdk_snd_wm9713_device); + return ret; +} + +static void __exit smdk_exit(void) +{ + platform_device_unregister(smdk_snd_ac97_device); + platform_device_unregister(smdk_snd_wm9713_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"); diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c new file mode 100644 index 00000000000..f0816404ea3 --- /dev/null +++ b/sound/soc/samsung/spdif.c @@ -0,0 +1,501 @@ +/* sound/soc/samsung/spdif.c + * + * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd + * http://www.samsung.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/io.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <plat/audio.h> +#include <mach/dma.h> + +#include "dma.h" +#include "spdif.h" + +/* Registers */ +#define CLKCON 0x00 +#define CON 0x04 +#define BSTAS 0x08 +#define CSTAS 0x0C +#define DATA_OUTBUF 0x10 +#define DCNT 0x14 +#define BSTAS_S 0x18 +#define DCNT_S 0x1C + +#define CLKCTL_MASK 0x7 +#define CLKCTL_MCLK_EXT (0x1 << 2) +#define CLKCTL_PWR_ON (0x1 << 0) + +#define CON_MASK 0x3ffffff +#define CON_FIFO_TH_SHIFT 19 +#define CON_FIFO_TH_MASK (0x7 << 19) +#define CON_USERDATA_23RDBIT (0x1 << 12) + +#define CON_SW_RESET (0x1 << 5) + +#define CON_MCLKDIV_MASK (0x3 << 3) +#define CON_MCLKDIV_256FS (0x0 << 3) +#define CON_MCLKDIV_384FS (0x1 << 3) +#define CON_MCLKDIV_512FS (0x2 << 3) + +#define CON_PCM_MASK (0x3 << 1) +#define CON_PCM_16BIT (0x0 << 1) +#define CON_PCM_20BIT (0x1 << 1) +#define CON_PCM_24BIT (0x2 << 1) + +#define CON_PCM_DATA (0x1 << 0) + +#define CSTAS_MASK 0x3fffffff +#define CSTAS_SAMP_FREQ_MASK (0xF << 24) +#define CSTAS_SAMP_FREQ_44 (0x0 << 24) +#define CSTAS_SAMP_FREQ_48 (0x2 << 24) +#define CSTAS_SAMP_FREQ_32 (0x3 << 24) +#define CSTAS_SAMP_FREQ_96 (0xA << 24) + +#define CSTAS_CATEGORY_MASK (0xFF << 8) +#define CSTAS_CATEGORY_CODE_CDP (0x01 << 8) + +#define CSTAS_NO_COPYRIGHT (0x1 << 2) + +/** + * struct samsung_spdif_info - Samsung S/PDIF Controller information + * @lock: Spin lock for S/PDIF. + * @dev: The parent device passed to use from the probe. + * @regs: The pointer to the device register block. + * @clk_rate: Current clock rate for calcurate ratio. + * @pclk: The peri-clock pointer for spdif master operation. + * @sclk: The source clock pointer for making sync signals. + * @save_clkcon: Backup clkcon reg. in suspend. + * @save_con: Backup con reg. in suspend. + * @save_cstas: Backup cstas reg. in suspend. + * @dma_playback: DMA information for playback channel. + */ +struct samsung_spdif_info { + spinlock_t lock; + struct device *dev; + void __iomem *regs; + unsigned long clk_rate; + struct clk *pclk; + struct clk *sclk; + u32 saved_clkcon; + u32 saved_con; + u32 saved_cstas; + struct s3c_dma_params *dma_playback; +}; + +static struct s3c2410_dma_client spdif_dma_client_out = { + .name = "S/PDIF Stereo out", +}; + +static struct s3c_dma_params spdif_stereo_out; +static struct samsung_spdif_info spdif_info; + +static inline struct samsung_spdif_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return snd_soc_dai_get_drvdata(cpu_dai); +} + +static void spdif_snd_txctrl(struct samsung_spdif_info *spdif, int on) +{ + void __iomem *regs = spdif->regs; + u32 clkcon; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + clkcon = readl(regs + CLKCON) & CLKCTL_MASK; + if (on) + writel(clkcon | CLKCTL_PWR_ON, regs + CLKCON); + else + writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON); +} + +static int spdif_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct samsung_spdif_info *spdif = to_info(cpu_dai); + u32 clkcon; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + clkcon = readl(spdif->regs + CLKCON); + + if (clk_id == SND_SOC_SPDIF_INT_MCLK) + clkcon &= ~CLKCTL_MCLK_EXT; + else + clkcon |= CLKCTL_MCLK_EXT; + + writel(clkcon, spdif->regs + CLKCON); + + spdif->clk_rate = freq; + + return 0; +} + +static int spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai); + unsigned long flags; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&spdif->lock, flags); + spdif_snd_txctrl(spdif, 1); + spin_unlock_irqrestore(&spdif->lock, flags); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&spdif->lock, flags); + spdif_snd_txctrl(spdif, 0); + spin_unlock_irqrestore(&spdif->lock, flags); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int spdif_sysclk_ratios[] = { + 512, 384, 256, +}; + +static int spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai); + void __iomem *regs = spdif->regs; + struct s3c_dma_params *dma_data; + u32 con, clkcon, cstas; + unsigned long flags; + int i, ratio; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = spdif->dma_playback; + else { + dev_err(spdif->dev, "Capture is not supported\n"); + return -EINVAL; + } + + snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data); + + spin_lock_irqsave(&spdif->lock, flags); + + con = readl(regs + CON) & CON_MASK; + cstas = readl(regs + CSTAS) & CSTAS_MASK; + clkcon = readl(regs + CLKCON) & CLKCTL_MASK; + + con &= ~CON_FIFO_TH_MASK; + con |= (0x7 << CON_FIFO_TH_SHIFT); + con |= CON_USERDATA_23RDBIT; + con |= CON_PCM_DATA; + + con &= ~CON_PCM_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + con |= CON_PCM_16BIT; + break; + default: + dev_err(spdif->dev, "Unsupported data size.\n"); + goto err; + } + + ratio = spdif->clk_rate / params_rate(params); + for (i = 0; i < ARRAY_SIZE(spdif_sysclk_ratios); i++) + if (ratio == spdif_sysclk_ratios[i]) + break; + if (i == ARRAY_SIZE(spdif_sysclk_ratios)) { + dev_err(spdif->dev, "Invalid clock ratio %ld/%d\n", + spdif->clk_rate, params_rate(params)); + goto err; + } + + con &= ~CON_MCLKDIV_MASK; + switch (ratio) { + case 256: + con |= CON_MCLKDIV_256FS; + break; + case 384: + con |= CON_MCLKDIV_384FS; + break; + case 512: + con |= CON_MCLKDIV_512FS; + break; + } + + cstas &= ~CSTAS_SAMP_FREQ_MASK; + switch (params_rate(params)) { + case 44100: + cstas |= CSTAS_SAMP_FREQ_44; + break; + case 48000: + cstas |= CSTAS_SAMP_FREQ_48; + break; + case 32000: + cstas |= CSTAS_SAMP_FREQ_32; + break; + case 96000: + cstas |= CSTAS_SAMP_FREQ_96; + break; + default: + dev_err(spdif->dev, "Invalid sampling rate %d\n", + params_rate(params)); + goto err; + } + + cstas &= ~CSTAS_CATEGORY_MASK; + cstas |= CSTAS_CATEGORY_CODE_CDP; + cstas |= CSTAS_NO_COPYRIGHT; + + writel(con, regs + CON); + writel(cstas, regs + CSTAS); + writel(clkcon, regs + CLKCON); + + spin_unlock_irqrestore(&spdif->lock, flags); + + return 0; +err: + spin_unlock_irqrestore(&spdif->lock, flags); + return -EINVAL; +} + +static void spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct samsung_spdif_info *spdif = to_info(rtd->cpu_dai); + void __iomem *regs = spdif->regs; + u32 con, clkcon; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + con = readl(regs + CON) & CON_MASK; + clkcon = readl(regs + CLKCON) & CLKCTL_MASK; + + writel(con | CON_SW_RESET, regs + CON); + cpu_relax(); + + writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON); +} + +#ifdef CONFIG_PM +static int spdif_suspend(struct snd_soc_dai *cpu_dai) +{ + struct samsung_spdif_info *spdif = to_info(cpu_dai); + u32 con = spdif->saved_con; + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + spdif->saved_clkcon = readl(spdif->regs + CLKCON) & CLKCTL_MASK; + spdif->saved_con = readl(spdif->regs + CON) & CON_MASK; + spdif->saved_cstas = readl(spdif->regs + CSTAS) & CSTAS_MASK; + + writel(con | CON_SW_RESET, spdif->regs + CON); + cpu_relax(); + + return 0; +} + +static int spdif_resume(struct snd_soc_dai *cpu_dai) +{ + struct samsung_spdif_info *spdif = to_info(cpu_dai); + + dev_dbg(spdif->dev, "Entered %s\n", __func__); + + writel(spdif->saved_clkcon, spdif->regs + CLKCON); + writel(spdif->saved_con, spdif->regs + CON); + writel(spdif->saved_cstas, spdif->regs + CSTAS); + + return 0; +} +#else +#define spdif_suspend NULL +#define spdif_resume NULL +#endif + +static struct snd_soc_dai_ops spdif_dai_ops = { + .set_sysclk = spdif_set_sysclk, + .trigger = spdif_trigger, + .hw_params = spdif_hw_params, + .shutdown = spdif_shutdown, +}; + +struct snd_soc_dai_driver samsung_spdif_dai = { + .name = "samsung-spdif", + .playback = { + .stream_name = "S/PDIF Playback", + .channels_min = 2, + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000), + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .ops = &spdif_dai_ops, + .suspend = spdif_suspend, + .resume = spdif_resume, +}; + +static __devinit int spdif_probe(struct platform_device *pdev) +{ + struct s3c_audio_pdata *spdif_pdata; + struct resource *mem_res, *dma_res; + struct samsung_spdif_info *spdif; + int ret; + + spdif_pdata = pdev->dev.platform_data; + + dev_dbg(&pdev->dev, "Entered %s\n", __func__); + + dma_res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dma_res) { + dev_err(&pdev->dev, "Unable to get 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; + } + + if (spdif_pdata && spdif_pdata->cfg_gpio + && spdif_pdata->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to configure GPIO pins\n"); + return -EINVAL; + } + + spdif = &spdif_info; + spdif->dev = &pdev->dev; + + spin_lock_init(&spdif->lock); + + spdif->pclk = clk_get(&pdev->dev, "spdif"); + if (IS_ERR(spdif->pclk)) { + dev_err(&pdev->dev, "failed to get peri-clock\n"); + ret = -ENOENT; + goto err0; + } + clk_enable(spdif->pclk); + + spdif->sclk = clk_get(&pdev->dev, "sclk_spdif"); + if (IS_ERR(spdif->sclk)) { + dev_err(&pdev->dev, "failed to get internal source clock\n"); + ret = -ENOENT; + goto err1; + } + clk_enable(spdif->sclk); + + /* Request S/PDIF Register's memory region */ + if (!request_mem_region(mem_res->start, + resource_size(mem_res), "samsung-spdif")) { + dev_err(&pdev->dev, "Unable to request register region\n"); + ret = -EBUSY; + goto err2; + } + + spdif->regs = ioremap(mem_res->start, 0x100); + if (spdif->regs == NULL) { + dev_err(&pdev->dev, "Cannot ioremap registers\n"); + ret = -ENXIO; + goto err3; + } + + dev_set_drvdata(&pdev->dev, spdif); + + ret = snd_soc_register_dai(&pdev->dev, &samsung_spdif_dai); + if (ret != 0) { + dev_err(&pdev->dev, "fail to register dai\n"); + goto err4; + } + + spdif_stereo_out.dma_size = 2; + spdif_stereo_out.client = &spdif_dma_client_out; + spdif_stereo_out.dma_addr = mem_res->start + DATA_OUTBUF; + spdif_stereo_out.channel = dma_res->start; + + spdif->dma_playback = &spdif_stereo_out; + + return 0; + +err4: + iounmap(spdif->regs); +err3: + release_mem_region(mem_res->start, resource_size(mem_res)); +err2: + clk_disable(spdif->sclk); + clk_put(spdif->sclk); +err1: + clk_disable(spdif->pclk); + clk_put(spdif->pclk); +err0: + return ret; +} + +static __devexit int spdif_remove(struct platform_device *pdev) +{ + struct samsung_spdif_info *spdif = &spdif_info; + struct resource *mem_res; + + snd_soc_unregister_dai(&pdev->dev); + + iounmap(spdif->regs); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem_res) + release_mem_region(mem_res->start, resource_size(mem_res)); + + clk_disable(spdif->sclk); + clk_put(spdif->sclk); + clk_disable(spdif->pclk); + clk_put(spdif->pclk); + + return 0; +} + +static struct platform_driver samsung_spdif_driver = { + .probe = spdif_probe, + .remove = spdif_remove, + .driver = { + .name = "samsung-spdif", + .owner = THIS_MODULE, + }, +}; + +static int __init spdif_init(void) +{ + return platform_driver_register(&samsung_spdif_driver); +} +module_init(spdif_init); + +static void __exit spdif_exit(void) +{ + platform_driver_unregister(&samsung_spdif_driver); +} +module_exit(spdif_exit); + +MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>"); +MODULE_DESCRIPTION("Samsung S/PDIF Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:samsung-spdif"); diff --git a/sound/soc/samsung/spdif.h b/sound/soc/samsung/spdif.h new file mode 100644 index 00000000000..4f72cb446db --- /dev/null +++ b/sound/soc/samsung/spdif.h @@ -0,0 +1,19 @@ +/* sound/soc/samsung/spdif.h + * + * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver + * + * Copyright (c) 2010 Samsung Electronics Co. Ltd + * http://www.samsung.com/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_SAMSUNG_SPDIF_H +#define __SND_SOC_SAMSUNG_SPDIF_H __FILE__ + +#define SND_SOC_SPDIF_INT_MCLK 0 +#define SND_SOC_SPDIF_EXT_MCLK 1 + +#endif /* __SND_SOC_SAMSUNG_SPDIF_H */ |