Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

224
sound/soc/samsung/Kconfig Normal file
View File

@@ -0,0 +1,224 @@
config SND_SOC_SAMSUNG
tristate "ASoC support for Samsung"
depends on PLAT_SAMSUNG
select S3C64XX_DMA if ARCH_S3C64XX
select S3C2410_DMA if ARCH_S3C24XX
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 "Audio support for Openmoko Neo1973 Smartphones (GTA02)"
depends on SND_SOC_SAMSUNG && MACH_NEO1973_GTA02
select SND_S3C24XX_I2S
select SND_SOC_WM8753
select SND_SOC_DFBMCS320
help
Say Y here to enable audio support for the Openmoko Neo1973
Smartphones.
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_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
depends on I2C=y && GENERIC_HARDIRQS
select MFD_WM8994
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_S3C24XX
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_S3C24XX
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_S3C24XX
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_S3C24XX
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)
depends on I2C=y && GENERIC_HARDIRQS
select SND_SAMSUNG_I2S
select MFD_WM8994
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
select SND_SAMSUNG_SPDIF
help
Say Y if you want to add support for SoC S/PDIF audio on the SMDK.
config SND_SOC_SMDK_WM8580_PCM
tristate "SoC PCM Audio support for WM8580 on SMDK"
depends on SND_SOC_SAMSUNG && (MACH_SMDK6450 || MACH_SMDKV210 || MACH_SMDKC110)
select SND_SOC_WM8580
select SND_SAMSUNG_PCM
help
Say Y if you want to add support for SoC audio on the SMDK.
config SND_SOC_SMDK_WM8994_PCM
tristate "SoC PCM Audio support for WM8994 on SMDK"
depends on SND_SOC_SAMSUNG
depends on I2C=y && GENERIC_HARDIRQS
select MFD_WM8994
select SND_SOC_WM8994
select SND_SAMSUNG_PCM
help
Say Y if you want to add support for SoC audio on the SMDK
config SND_SOC_SPEYSIDE
tristate "Audio support for Wolfson Speyside"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
select SND_SAMSUNG_I2S
select SND_SOC_WM8996
select SND_SOC_WM9081
select SND_SOC_WM0010
select SND_SOC_WM1250_EV1
config SND_SOC_TOBERMORY
tristate "Audio support for Wolfson Tobermory"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
select SND_SAMSUNG_I2S
select SND_SOC_WM8962
config SND_SOC_BELLS
tristate "Audio support for Wolfson Bells"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
select SND_SAMSUNG_I2S
select SND_SOC_WM5102
select SND_SOC_WM5110
select SND_SOC_WM9081
select SND_SOC_WM0010
select SND_SOC_WM1250_EV1
config SND_SOC_LOWLAND
tristate "Audio support for Wolfson Lowland"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
select SND_SAMSUNG_I2S
select SND_SOC_WM5100
select SND_SOC_WM9081
config SND_SOC_LITTLEMILL
tristate "Audio support for Wolfson Littlemill"
depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410
select SND_SAMSUNG_I2S
select MFD_WM8994
select SND_SOC_WM8994

View File

@@ -0,0 +1,69 @@
# S3c24XX Platform Support
snd-soc-s3c24xx-objs := dma.o
snd-soc-idma-objs := idma.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
obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-idma.o
# S3C24XX Machine Support
snd-soc-jive-wm8750-objs := jive_wm8750.o
snd-soc-neo1973-wm8753-objs := neo1973_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
snd-soc-smdk-wm8580pcm-objs := smdk_wm8580pcm.o
snd-soc-smdk-wm8994pcm-objs := smdk_wm8994pcm.o
snd-soc-speyside-objs := speyside.o
snd-soc-tobermory-objs := tobermory.o
snd-soc-lowland-objs := lowland.o
snd-soc-littlemill-objs := littlemill.o
snd-soc-bells-objs := bells.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_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
obj-$(CONFIG_SND_SOC_SMDK_WM8580_PCM) += snd-soc-smdk-wm8580pcm.o
obj-$(CONFIG_SND_SOC_SMDK_WM8994_PCM) += snd-soc-smdk-wm8994pcm.o
obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o
obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o
obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o
obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o
obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o

529
sound/soc/samsung/ac97.c Normal file
View File

@@ -0,0 +1,529 @@
/* 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 <jassisinghbrar@gmail.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/io.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <mach/dma.h>
#include "regs-ac97.h"
#include <linux/platform_data/asoc-s3c.h>
#include "dma.h"
#define AC_CMD_ADDR(x) (x << 16)
#define AC_CMD_DATA(x) (x & 0xffff)
#define S3C_AC97_DAI_PCM 0
#define S3C_AC97_DAI_MIC 1
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);
if (!dma_data->ops)
dma_data->ops = samsung_dma_get_ops();
dma_data->ops->started(dma_data->channel);
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);
if (!dma_data->ops)
dma_data->ops = samsung_dma_get_ops();
dma_data->ops->started(dma_data->channel);
return 0;
}
static const struct snd_soc_dai_ops s3c_ac97_dai_ops = {
.hw_params = s3c_ac97_hw_params,
.trigger = s3c_ac97_trigger,
};
static const 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 const struct snd_soc_component_driver s3c_ac97_component = {
.name = "s3c-ac97",
};
static 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_prepare_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,
0, "AC97", NULL);
if (ret < 0) {
dev_err(&pdev->dev, "ac97: interrupt request failed.\n");
goto err4;
}
ret = snd_soc_register_component(&pdev->dev, &s3c_ac97_component,
s3c_ac97_dai, ARRAY_SIZE(s3c_ac97_dai));
if (ret)
goto err5;
ret = asoc_dma_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
goto err6;
}
return 0;
err6:
snd_soc_unregister_component(&pdev->dev);
err5:
free_irq(irq_res->start, NULL);
err4:
err3:
clk_disable_unprepare(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 int s3c_ac97_remove(struct platform_device *pdev)
{
struct resource *mem_res, *irq_res;
asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (irq_res)
free_irq(irq_res->start, NULL);
clk_disable_unprepare(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,
},
};
module_platform_driver(s3c_ac97_driver);
MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
MODULE_DESCRIPTION("AC97 driver for the Samsung SoC");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-ac97");

454
sound/soc/samsung/bells.c Normal file
View File

@@ -0,0 +1,454 @@
/*
* Bells audio support
*
* Copyright 2012 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 <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm5102.h"
#include "../codecs/wm9081.h"
/* BCLK2 is fixed at this currently */
#define BCLK2_RATE (64 * 8000)
/*
* Expect a 24.576MHz crystal if one is fitted (the driver will function
* if this is not fitted).
*/
#define MCLK_RATE 24576000
#define SYS_AUDIO_RATE 44100
#define SYS_MCLK_RATE (SYS_AUDIO_RATE * 512)
#define DAI_AP_DSP 0
#define DAI_DSP_CODEC 1
#define DAI_CODEC_CP 2
#define DAI_CODEC_SUB 3
struct bells_drvdata {
int sysclk_rate;
int asyncclk_rate;
};
static struct bells_drvdata wm2200_drvdata = {
.sysclk_rate = 22579200,
};
static struct bells_drvdata wm5102_drvdata = {
.sysclk_rate = 45158400,
.asyncclk_rate = 49152000,
};
static struct bells_drvdata wm5110_drvdata = {
.sysclk_rate = 135475200,
.asyncclk_rate = 147456000,
};
static int bells_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[DAI_DSP_CODEC].codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct bells_drvdata *bells = card->drvdata;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
break;
ret = snd_soc_codec_set_pll(codec, WM5102_FLL1,
ARIZONA_FLL_SRC_MCLK1,
MCLK_RATE,
bells->sysclk_rate);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
if (bells->asyncclk_rate) {
ret = snd_soc_codec_set_pll(codec, WM5102_FLL2,
ARIZONA_FLL_SRC_AIF2BCLK,
BCLK2_RATE,
bells->asyncclk_rate);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
}
break;
default:
break;
}
return 0;
}
static int bells_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[DAI_DSP_CODEC].codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct bells_drvdata *bells = card->drvdata;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, 0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
if (bells->asyncclk_rate) {
ret = snd_soc_codec_set_pll(codec, WM5102_FLL2,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
}
break;
default:
break;
}
dapm->bias_level = level;
return 0;
}
static int bells_late_probe(struct snd_soc_card *card)
{
struct bells_drvdata *bells = card->drvdata;
struct snd_soc_codec *wm0010 = card->rtd[DAI_AP_DSP].codec;
struct snd_soc_codec *codec = card->rtd[DAI_DSP_CODEC].codec;
struct snd_soc_dai *aif1_dai = card->rtd[DAI_DSP_CODEC].codec_dai;
struct snd_soc_dai *aif2_dai;
struct snd_soc_dai *aif3_dai;
struct snd_soc_dai *wm9081_dai;
int ret;
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_SYSCLK,
ARIZONA_CLK_SRC_FLL1,
bells->sysclk_rate,
SND_SOC_CLOCK_IN);
if (ret != 0) {
dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_sysclk(wm0010, 0, 0, SYS_MCLK_RATE, 0);
if (ret != 0) {
dev_err(wm0010->dev, "Failed to set WM0010 clock: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0);
if (ret != 0)
dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_OPCLK, 0,
SYS_MCLK_RATE, SND_SOC_CLOCK_OUT);
if (ret != 0)
dev_err(codec->dev, "Failed to set OPCLK: %d\n", ret);
if (card->num_rtd == DAI_CODEC_CP)
return 0;
ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK,
ARIZONA_CLK_SRC_FLL2,
bells->asyncclk_rate,
SND_SOC_CLOCK_IN);
if (ret != 0) {
dev_err(codec->dev, "Failed to set ASYNCCLK: %d\n", ret);
return ret;
}
aif2_dai = card->rtd[DAI_CODEC_CP].cpu_dai;
ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0);
if (ret != 0) {
dev_err(aif2_dai->dev, "Failed to set AIF2 clock: %d\n", ret);
return ret;
}
if (card->num_rtd == DAI_CODEC_SUB)
return 0;
aif3_dai = card->rtd[DAI_CODEC_SUB].cpu_dai;
wm9081_dai = card->rtd[DAI_CODEC_SUB].codec_dai;
ret = snd_soc_dai_set_sysclk(aif3_dai, ARIZONA_CLK_SYSCLK, 0, 0);
if (ret != 0) {
dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
return ret;
}
ret = snd_soc_codec_set_sysclk(wm9081_dai->codec, WM9081_SYSCLK_MCLK,
0, SYS_MCLK_RATE, 0);
if (ret != 0) {
dev_err(wm9081_dai->dev, "Failed to set MCLK: %d\n", ret);
return ret;
}
return 0;
}
static const struct snd_soc_pcm_stream baseband_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 2,
.channels_max = 2,
};
static const struct snd_soc_pcm_stream sub_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = SYS_AUDIO_RATE,
.rate_max = SYS_AUDIO_RATE,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link bells_dai_wm2200[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm0010-sdi1",
.platform_name = "samsung-i2s.0",
.codec_name = "spi0.0",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.cpu_dai_name = "wm0010-sdi2",
.codec_dai_name = "wm2200",
.codec_name = "wm2200.1-003a",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.params = &sub_params,
.ignore_suspend = 1,
},
};
static struct snd_soc_dai_link bells_dai_wm5102[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm0010-sdi1",
.platform_name = "samsung-i2s.0",
.codec_name = "spi0.0",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.cpu_dai_name = "wm0010-sdi2",
.codec_dai_name = "wm5102-aif1",
.codec_name = "wm5102-codec",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.params = &sub_params,
.ignore_suspend = 1,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm5102-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
.params = &baseband_params,
},
{
.name = "Sub",
.stream_name = "Sub",
.cpu_dai_name = "wm5102-aif3",
.codec_dai_name = "wm9081-hifi",
.codec_name = "wm9081.1-006c",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS,
.ignore_suspend = 1,
.params = &sub_params,
},
};
static struct snd_soc_dai_link bells_dai_wm5110[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm0010-sdi1",
.platform_name = "samsung-i2s.0",
.codec_name = "spi0.0",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.cpu_dai_name = "wm0010-sdi2",
.codec_dai_name = "wm5110-aif1",
.codec_name = "wm5110-codec",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.params = &sub_params,
.ignore_suspend = 1,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm5110-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
.params = &baseband_params,
},
{
.name = "Sub",
.stream_name = "Sub",
.cpu_dai_name = "wm5110-aif3",
.codec_dai_name = "wm9081-hifi",
.codec_name = "wm9081.1-006c",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS,
.ignore_suspend = 1,
.params = &sub_params,
},
};
static struct snd_soc_codec_conf bells_codec_conf[] = {
{
.dev_name = "wm9081.1-006c",
.name_prefix = "Sub",
},
};
static struct snd_soc_dapm_route bells_routes[] = {
{ "Sub CLK_SYS", NULL, "OPCLK" },
};
static struct snd_soc_card bells_cards[] = {
{
.name = "Bells WM2200",
.owner = THIS_MODULE,
.dai_link = bells_dai_wm2200,
.num_links = ARRAY_SIZE(bells_dai_wm2200),
.codec_conf = bells_codec_conf,
.num_configs = ARRAY_SIZE(bells_codec_conf),
.late_probe = bells_late_probe,
.dapm_routes = bells_routes,
.num_dapm_routes = ARRAY_SIZE(bells_routes),
.set_bias_level = bells_set_bias_level,
.set_bias_level_post = bells_set_bias_level_post,
.drvdata = &wm2200_drvdata,
},
{
.name = "Bells WM5102",
.owner = THIS_MODULE,
.dai_link = bells_dai_wm5102,
.num_links = ARRAY_SIZE(bells_dai_wm5102),
.codec_conf = bells_codec_conf,
.num_configs = ARRAY_SIZE(bells_codec_conf),
.late_probe = bells_late_probe,
.dapm_routes = bells_routes,
.num_dapm_routes = ARRAY_SIZE(bells_routes),
.set_bias_level = bells_set_bias_level,
.set_bias_level_post = bells_set_bias_level_post,
.drvdata = &wm5102_drvdata,
},
{
.name = "Bells WM5110",
.owner = THIS_MODULE,
.dai_link = bells_dai_wm5110,
.num_links = ARRAY_SIZE(bells_dai_wm5110),
.codec_conf = bells_codec_conf,
.num_configs = ARRAY_SIZE(bells_codec_conf),
.late_probe = bells_late_probe,
.dapm_routes = bells_routes,
.num_dapm_routes = ARRAY_SIZE(bells_routes),
.set_bias_level = bells_set_bias_level,
.set_bias_level_post = bells_set_bias_level_post,
.drvdata = &wm5110_drvdata,
},
};
static int bells_probe(struct platform_device *pdev)
{
int ret;
bells_cards[pdev->id].dev = &pdev->dev;
ret = snd_soc_register_card(&bells_cards[pdev->id]);
if (ret) {
dev_err(&pdev->dev,
"snd_soc_register_card(%s) failed: %d\n",
bells_cards[pdev->id].name, ret);
return ret;
}
return 0;
}
static int bells_remove(struct platform_device *pdev)
{
snd_soc_unregister_card(&bells_cards[pdev->id]);
return 0;
}
static struct platform_driver bells_driver = {
.driver = {
.name = "bells",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = bells_probe,
.remove = bells_remove,
};
module_platform_driver(bells_driver);
MODULE_DESCRIPTION("Bells audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bells");

450
sound/soc/samsung/dma.c Normal file
View File

@@ -0,0 +1,450 @@
/*
* 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/slab.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <asm/dma.h>
#include <mach/hardware.h>
#include <mach/dma.h>
#include "dma.h"
#define ST_RUNNING (1<<0)
#define ST_OPENED (1<<1)
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,
.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_period;
dma_addr_t dma_start;
dma_addr_t dma_pos;
dma_addr_t dma_end;
struct s3c_dma_params *params;
};
static void audio_buffdone(void *data);
/* 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;
struct samsung_dma_prep dma_info;
pr_debug("Entered %s\n", __func__);
limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;
pr_debug("%s: loaded %d, limit %d\n",
__func__, prtd->dma_loaded, limit);
dma_info.cap = (samsung_dma_has_circular() ? DMA_CYCLIC : DMA_SLAVE);
dma_info.direction =
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM);
dma_info.fp = audio_buffdone;
dma_info.fp_param = substream;
dma_info.period = prtd->dma_period;
dma_info.len = prtd->dma_period*limit;
while (prtd->dma_loaded < limit) {
pr_debug("dma_loaded: %d\n", prtd->dma_loaded);
if ((pos + dma_info.period) > prtd->dma_end) {
dma_info.period = prtd->dma_end - pos;
pr_debug("%s: corrected dma len %ld\n",
__func__, dma_info.period);
}
dma_info.buf = pos;
prtd->params->ops->prepare(prtd->params->ch, &dma_info);
prtd->dma_loaded++;
pos += prtd->dma_period;
if (pos >= prtd->dma_end)
pos = prtd->dma_start;
}
prtd->dma_pos = pos;
}
static void audio_buffdone(void *data)
{
struct snd_pcm_substream *substream = data;
struct runtime_data *prtd = substream->runtime->private_data;
pr_debug("Entered %s\n", __func__);
if (prtd->state & ST_RUNNING) {
prtd->dma_pos += prtd->dma_period;
if (prtd->dma_pos >= prtd->dma_end)
prtd->dma_pos = prtd->dma_start;
if (substream)
snd_pcm_period_elapsed(substream);
spin_lock(&prtd->lock);
if (!samsung_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);
struct samsung_dma_req req;
struct samsung_dma_config config;
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);
prtd->params->ops = samsung_dma_get_ops();
req.cap = (samsung_dma_has_circular() ?
DMA_CYCLIC : DMA_SLAVE);
req.client = prtd->params->client;
config.direction =
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK
? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM);
config.width = prtd->params->dma_size;
config.fifo = prtd->params->dma_addr;
prtd->params->ch = prtd->params->ops->request(
prtd->params->channel, &req, rtd->cpu_dai->dev,
prtd->params->ch_name);
prtd->params->ops->config(prtd->params->ch, &config);
}
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = totbytes;
spin_lock_irq(&prtd->lock);
prtd->dma_loaded = 0;
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__);
snd_pcm_set_runtime_buffer(substream, NULL);
if (prtd->params) {
prtd->params->ops->flush(prtd->params->ch);
prtd->params->ops->release(prtd->params->ch,
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;
/* flush the DMA channel */
prtd->params->ops->flush(prtd->params->ch);
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:
prtd->state |= ST_RUNNING;
prtd->params->ops->trigger(prtd->params->ch);
break;
case SNDRV_PCM_TRIGGER_STOP:
prtd->state &= ~ST_RUNNING;
prtd->params->ops->stop(prtd->params->ch);
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;
pr_debug("Entered %s\n", __func__);
res = prtd->dma_pos - prtd->dma_start;
pr_debug("Pointer offset: %lu\n", res);
/* 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
* called... (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_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->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 = DMA_BIT_MASK(32);
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
if (ret)
goto out;
}
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
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,
};
int asoc_dma_platform_register(struct device *dev)
{
return snd_soc_register_platform(dev, &samsung_asoc_platform);
}
EXPORT_SYMBOL_GPL(asoc_dma_platform_register);
void asoc_dma_platform_unregister(struct device *dev)
{
snd_soc_unregister_platform(dev);
}
EXPORT_SYMBOL_GPL(asoc_dma_platform_unregister);
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("Samsung ASoC DMA Driver");
MODULE_LICENSE("GPL");

28
sound/soc/samsung/dma.h Normal file
View File

@@ -0,0 +1,28 @@
/*
* 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 SoC
*/
#ifndef _S3C_AUDIO_H
#define _S3C_AUDIO_H
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 */
unsigned ch;
struct samsung_dma_ops *ops;
char *ch_name;
};
int asoc_dma_platform_register(struct device *dev);
void asoc_dma_platform_unregister(struct device *dev);
#endif

View File

@@ -0,0 +1,307 @@
/*
* 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 <sound/soc.h>
#include <sound/jack.h>
#include <asm/mach-types.h>
#include <mach/gpio.h>
#include "../codecs/wm8994.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;
/* 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");
}
/* 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 const struct snd_soc_component_driver voice_component = {
.name = "goni-voice",
};
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-aif1",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8994-codec.0-001a",
.init = goni_wm8994_init,
.ops = &goni_hifi_ops,
}, {
.name = "WM8994 Voice",
.stream_name = "Voice",
.cpu_dai_name = "goni-voice-dai",
.codec_dai_name = "wm8994-aif2",
.codec_name = "wm8994-codec.0-001a",
.ops = &goni_voice_ops,
},
};
static struct snd_soc_card goni = {
.name = "goni",
.owner = THIS_MODULE,
.dai_link = goni_dai,
.num_links = ARRAY_SIZE(goni_dai),
.dapm_widgets = goni_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(goni_dapm_widgets),
.dapm_routes = goni_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(goni_dapm_routes),
};
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_component(&goni_snd_device->dev, &voice_component,
&voice_dai, 1);
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_component(&goni_snd_device->dev);
platform_device_put(goni_snd_device);
}
return ret;
}
static void __exit goni_exit(void)
{
snd_soc_unregister_component(&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");

View File

@@ -0,0 +1,280 @@
/*
* 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/types.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include "regs-iis.h"
#include <asm/mach-types.h>
#include "s3c24xx-i2s.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(S3C_GPIO_END + 9, 1);
else
gpio_set_value(S3C_GPIO_END + 9, 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;
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_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 = "s3c24xx-iis",
.codec_name = "uda1380-codec.0-001a",
.ops = &h1940_ops,
},
};
static struct snd_soc_card h1940_asoc = {
.name = "h1940",
.owner = THIS_MODULE,
.dai_link = h1940_uda1380_dai,
.num_links = ARRAY_SIZE(h1940_uda1380_dai),
.dapm_widgets = uda1380_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
};
static int __init h1940_init(void)
{
int ret;
if (!machine_is_h1940())
return -ENODEV;
/* configure some gpios */
ret = gpio_request(S3C_GPIO_END + 9, "speaker-power");
if (ret)
goto err_out;
ret = gpio_direction_output(S3C_GPIO_END + 9, 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(S3C_GPIO_END + 9);
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(S3C_GPIO_END + 9);
}
module_init(h1940_init);
module_exit(h1940_exit);
/* Module information */
MODULE_AUTHOR("Arnaud Patard, Vasily Khoruzhick");
MODULE_DESCRIPTION("ALSA SoC H1940");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,143 @@
/*
* linux/sound/soc/samsung/i2s-regs.h
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung I2S driver's register header
*
* 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_SAMSUNG_I2S_REGS_H
#define __SND_SOC_SAMSUNG_I2S_REGS_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 I2SAHB 0x20
#define I2SSTR0 0x24
#define I2SSIZE 0x28
#define I2STRNCNT 0x2c
#define I2SLVL0ADDR 0x30
#define I2SLVL1ADDR 0x34
#define I2SLVL2ADDR 0x38
#define I2SLVL3ADDR 0x3c
#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 AHB_INTENLVL0 (1 << 24)
#define AHB_LVL0INT (1 << 20)
#define AHB_CLRLVL0INT (1 << 16)
#define AHB_DMARLD (1 << 5)
#define AHB_INTMASK (1 << 3)
#define AHB_DMAEN (1 << 0)
#define AHB_LVLINTMASK (0xf << 20)
#define I2SSIZE_TRNMSK (0xffff)
#define I2SSIZE_SHIFT (16)
#endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */

1347
sound/soc/samsung/i2s.c Normal file

File diff suppressed because it is too large Load Diff

22
sound/soc/samsung/i2s.h Normal file
View File

@@ -0,0 +1,22 @@
/* sound/soc/samsung/i2s.h
*
* ALSA SoC Audio Layer - Samsung I2S Controller driver
*
* Copyright (c) 2010 Samsung Electronics Co. Ltd.
* Jaswinder Singh <jassisinghbrar@gmail.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
#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 */

451
sound/soc/samsung/idma.c Normal file
View File

@@ -0,0 +1,451 @@
/*
* sound/soc/samsung/idma.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* I2S0's Internal DMA driver
*
* 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/interrupt.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "i2s.h"
#include "idma.h"
#include "dma.h"
#include "i2s-regs.h"
#define ST_RUNNING (1<<0)
#define ST_OPENED (1<<1)
static const struct snd_pcm_hardware idma_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_S24_LE |
SNDRV_PCM_FMTBIT_U24_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = MAX_IDMA_BUFFER,
.period_bytes_min = 128,
.period_bytes_max = MAX_IDMA_PERIOD,
.periods_min = 1,
.periods_max = 2,
};
struct idma_ctrl {
spinlock_t lock;
int state;
dma_addr_t start;
dma_addr_t pos;
dma_addr_t end;
dma_addr_t period;
dma_addr_t periodsz;
void *token;
void (*cb)(void *dt, int bytes_xfer);
};
static struct idma_info {
spinlock_t lock;
void __iomem *regs;
dma_addr_t lp_tx_addr;
} idma;
static int idma_irq;
static void idma_getpos(dma_addr_t *src)
{
*src = idma.lp_tx_addr +
(readl(idma.regs + I2STRNCNT) & 0xffffff) * 4;
}
static int idma_enqueue(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = substream->runtime->private_data;
u32 val;
spin_lock(&prtd->lock);
prtd->token = (void *) substream;
spin_unlock(&prtd->lock);
/* Internal DMA Level0 Interrupt Address */
val = idma.lp_tx_addr + prtd->periodsz;
writel(val, idma.regs + I2SLVL0ADDR);
/* Start address0 of I2S internal DMA operation. */
val = idma.lp_tx_addr;
writel(val, idma.regs + I2SSTR0);
/*
* Transfer block size for I2S internal DMA.
* Should decide transfer size before start dma operation
*/
val = readl(idma.regs + I2SSIZE);
val &= ~(I2SSIZE_TRNMSK << I2SSIZE_SHIFT);
val |= (((runtime->dma_bytes >> 2) &
I2SSIZE_TRNMSK) << I2SSIZE_SHIFT);
writel(val, idma.regs + I2SSIZE);
val = readl(idma.regs + I2SAHB);
val |= AHB_INTENLVL0;
writel(val, idma.regs + I2SAHB);
return 0;
}
static void idma_setcallbk(struct snd_pcm_substream *substream,
void (*cb)(void *, int))
{
struct idma_ctrl *prtd = substream->runtime->private_data;
spin_lock(&prtd->lock);
prtd->cb = cb;
spin_unlock(&prtd->lock);
}
static void idma_control(int op)
{
u32 val = readl(idma.regs + I2SAHB);
spin_lock(&idma.lock);
switch (op) {
case LPAM_DMA_START:
val |= (AHB_INTENLVL0 | AHB_DMAEN);
break;
case LPAM_DMA_STOP:
val &= ~(AHB_INTENLVL0 | AHB_DMAEN);
break;
default:
spin_unlock(&idma.lock);
return;
}
writel(val, idma.regs + I2SAHB);
spin_unlock(&idma.lock);
}
static void idma_done(void *id, int bytes_xfer)
{
struct snd_pcm_substream *substream = id;
struct idma_ctrl *prtd = substream->runtime->private_data;
if (prtd && (prtd->state & ST_RUNNING))
snd_pcm_period_elapsed(substream);
}
static int idma_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = substream->runtime->private_data;
u32 mod = readl(idma.regs + I2SMOD);
u32 ahb = readl(idma.regs + I2SAHB);
ahb |= (AHB_DMARLD | AHB_INTMASK);
mod |= MOD_TXS_IDMA;
writel(ahb, idma.regs + I2SAHB);
writel(mod, idma.regs + I2SMOD);
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
runtime->dma_bytes = params_buffer_bytes(params);
prtd->start = prtd->pos = runtime->dma_addr;
prtd->period = params_periods(params);
prtd->periodsz = params_period_bytes(params);
prtd->end = runtime->dma_addr + runtime->dma_bytes;
idma_setcallbk(substream, idma_done);
return 0;
}
static int idma_hw_free(struct snd_pcm_substream *substream)
{
snd_pcm_set_runtime_buffer(substream, NULL);
return 0;
}
static int idma_prepare(struct snd_pcm_substream *substream)
{
struct idma_ctrl *prtd = substream->runtime->private_data;
prtd->pos = prtd->start;
/* flush the DMA channel */
idma_control(LPAM_DMA_STOP);
idma_enqueue(substream);
return 0;
}
static int idma_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct idma_ctrl *prtd = substream->runtime->private_data;
int ret = 0;
spin_lock(&prtd->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
prtd->state |= ST_RUNNING;
idma_control(LPAM_DMA_START);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
prtd->state &= ~ST_RUNNING;
idma_control(LPAM_DMA_STOP);
break;
default:
ret = -EINVAL;
break;
}
spin_unlock(&prtd->lock);
return ret;
}
static snd_pcm_uframes_t
idma_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = runtime->private_data;
dma_addr_t src;
unsigned long res;
spin_lock(&prtd->lock);
idma_getpos(&src);
res = src - prtd->start;
spin_unlock(&prtd->lock);
return bytes_to_frames(substream->runtime, res);
}
static int idma_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma)
{
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned long size, offset;
int ret;
/* From snd_pcm_lib_mmap_iomem */
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
vma->vm_flags |= VM_IO;
size = vma->vm_end - vma->vm_start;
offset = vma->vm_pgoff << PAGE_SHIFT;
ret = io_remap_pfn_range(vma, vma->vm_start,
(runtime->dma_addr + offset) >> PAGE_SHIFT,
size, vma->vm_page_prot);
return ret;
}
static irqreturn_t iis_irq(int irqno, void *dev_id)
{
struct idma_ctrl *prtd = (struct idma_ctrl *)dev_id;
u32 iiscon, iisahb, val, addr;
iisahb = readl(idma.regs + I2SAHB);
iiscon = readl(idma.regs + I2SCON);
val = (iisahb & AHB_LVL0INT) ? AHB_CLRLVL0INT : 0;
if (val) {
iisahb |= val;
writel(iisahb, idma.regs + I2SAHB);
addr = readl(idma.regs + I2SLVL0ADDR) - idma.lp_tx_addr;
addr += prtd->periodsz;
addr %= (prtd->end - prtd->start);
addr += idma.lp_tx_addr;
writel(addr, idma.regs + I2SLVL0ADDR);
if (prtd->cb)
prtd->cb(prtd->token, prtd->period);
}
return IRQ_HANDLED;
}
static int idma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd;
int ret;
snd_soc_set_runtime_hwparams(substream, &idma_hardware);
prtd = kzalloc(sizeof(struct idma_ctrl), GFP_KERNEL);
if (prtd == NULL)
return -ENOMEM;
ret = request_irq(idma_irq, iis_irq, 0, "i2s", prtd);
if (ret < 0) {
pr_err("fail to claim i2s irq , ret = %d\n", ret);
kfree(prtd);
return ret;
}
spin_lock_init(&prtd->lock);
runtime->private_data = prtd;
return 0;
}
static int idma_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct idma_ctrl *prtd = runtime->private_data;
free_irq(idma_irq, prtd);
if (!prtd)
pr_err("idma_close called with prtd == NULL\n");
kfree(prtd);
return 0;
}
static struct snd_pcm_ops idma_ops = {
.open = idma_open,
.close = idma_close,
.ioctl = snd_pcm_lib_ioctl,
.trigger = idma_trigger,
.pointer = idma_pointer,
.mmap = idma_mmap,
.hw_params = idma_hw_params,
.hw_free = idma_hw_free,
.prepare = idma_prepare,
};
static void idma_free(struct snd_pcm *pcm)
{
struct snd_pcm_substream *substream;
struct snd_dma_buffer *buf;
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
if (!substream)
return;
buf = &substream->dma_buffer;
if (!buf->area)
return;
iounmap(buf->area);
buf->area = NULL;
buf->addr = 0;
}
static int preallocate_idma_buffer(struct snd_pcm *pcm, int stream)
{
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
/* Assign PCM buffer pointers */
buf->dev.type = SNDRV_DMA_TYPE_CONTINUOUS;
buf->addr = idma.lp_tx_addr;
buf->bytes = idma_hardware.buffer_bytes_max;
buf->area = (unsigned char *)ioremap(buf->addr, buf->bytes);
return 0;
}
static u64 idma_mask = DMA_BIT_MASK(32);
static int idma_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
int ret = 0;
if (!card->dev->dma_mask)
card->dev->dma_mask = &idma_mask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = preallocate_idma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
}
return ret;
}
void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr)
{
spin_lock_init(&idma.lock);
idma.regs = regs;
idma.lp_tx_addr = addr;
}
EXPORT_SYMBOL_GPL(idma_reg_addr_init);
static struct snd_soc_platform_driver asoc_idma_platform = {
.ops = &idma_ops,
.pcm_new = idma_new,
.pcm_free = idma_free,
};
static int asoc_idma_platform_probe(struct platform_device *pdev)
{
idma_irq = platform_get_irq(pdev, 0);
if (idma_irq < 0)
return idma_irq;
return snd_soc_register_platform(&pdev->dev, &asoc_idma_platform);
}
static int asoc_idma_platform_remove(struct platform_device *pdev)
{
snd_soc_unregister_platform(&pdev->dev);
return 0;
}
static struct platform_driver asoc_idma_driver = {
.driver = {
.name = "samsung-idma",
.owner = THIS_MODULE,
},
.probe = asoc_idma_platform_probe,
.remove = asoc_idma_platform_remove,
};
module_platform_driver(asoc_idma_driver);
MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
MODULE_DESCRIPTION("Samsung ASoC IDMA Driver");
MODULE_LICENSE("GPL");

26
sound/soc/samsung/idma.h Normal file
View File

@@ -0,0 +1,26 @@
/*
* sound/soc/samsung/idma.h
*
* Copyright (c) 2011 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 as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
#ifndef __SND_SOC_SAMSUNG_IDMA_H_
#define __SND_SOC_SAMSUNG_IDMA_H_
extern void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr);
/* dma_state */
#define LPAM_DMA_STOP 0
#define LPAM_DMA_START 1
#define MAX_IDMA_PERIOD (128 * 1024)
#define MAX_IDMA_BUFFER (160 * 1024)
#endif /* __SND_SOC_SAMSUNG_IDMA_H_ */

View File

@@ -0,0 +1,174 @@
/* 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 <sound/soc.h>
#include <asm/mach-types.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;
/* 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");
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 = "s3c2412-i2s",
.codec_name = "wm8750.0-001a",
.init = jive_wm8750_init,
.ops = &jive_ops,
};
/* jive audio machine driver */
static struct snd_soc_card snd_soc_machine_jive = {
.name = "Jive",
.owner = THIS_MODULE,
.dai_link = &jive_dai,
.num_links = 1,
.dapm_widgets = wm8750_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
};
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");

View File

@@ -0,0 +1,341 @@
/*
* Littlemill audio support
*
* Copyright 2011 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 <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm8994.h"
static int sample_rate = 44100;
static int littlemill_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
int ret;
if (dapm->dev != aif1_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
/*
* If we've not already clocked things via hw_params()
* then do so now, otherwise these are noops.
*/
if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
WM8994_FLL_SRC_MCLK2, 32768,
sample_rate * 512);
if (ret < 0) {
pr_err("Failed to start FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif1_dai,
WM8994_SYSCLK_FLL1,
sample_rate * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
}
break;
default:
break;
}
return 0;
}
static int littlemill_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
int ret;
if (dapm->dev != aif1_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to switch away from FLL1: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL1: %d\n", ret);
return ret;
}
break;
default:
break;
}
dapm->bias_level = level;
return 0;
}
static int littlemill_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;
int ret;
sample_rate = params_rate(params);
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
WM8994_FLL_SRC_MCLK2, 32768,
sample_rate * 512);
if (ret < 0) {
pr_err("Failed to start FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai,
WM8994_SYSCLK_FLL1,
sample_rate * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
return 0;
}
static struct snd_soc_ops littlemill_ops = {
.hw_params = littlemill_hw_params,
};
static const struct snd_soc_pcm_stream baseband_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 8000,
.rate_max = 8000,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link littlemill_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8994-codec",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ops = &littlemill_ops,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm8994-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
.params = &baseband_params,
},
};
static int bbclk_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_card *card = w->dapm->card;
struct snd_soc_dai *aif2_dai = card->rtd[1].cpu_dai;
int ret;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
WM8994_FLL_SRC_BCLK, 64 * 8000,
8000 * 256);
if (ret < 0) {
pr_err("Failed to start FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_FLL2,
8000 * 256,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
break;
case SND_SOC_DAPM_POST_PMD:
ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to switch away from FLL2: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL2: %d\n", ret);
return ret;
}
break;
default:
return -EINVAL;
}
return 0;
}
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
};
static struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("AMIC", NULL),
SND_SOC_DAPM_MIC("DMIC", NULL),
SND_SOC_DAPM_SUPPLY_S("Baseband Clock", -1, SND_SOC_NOPM, 0, 0,
bbclk_ev,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
};
static struct snd_soc_dapm_route audio_paths[] = {
{ "Headphone", NULL, "HPOUT1L" },
{ "Headphone", NULL, "HPOUT1R" },
{ "AMIC", NULL, "MICBIAS1" }, /* Default for AMICBIAS jumper */
{ "IN1LN", NULL, "AMIC" },
{ "DMIC", NULL, "MICBIAS2" }, /* Default for DMICBIAS jumper */
{ "DMIC1DAT", NULL, "DMIC" },
{ "DMIC2DAT", NULL, "DMIC" },
{ "AIF2CLK", NULL, "Baseband Clock" },
};
static struct snd_soc_jack littlemill_headset;
static int littlemill_late_probe(struct snd_soc_card *card)
{
struct snd_soc_codec *codec = card->rtd[0].codec;
struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
struct snd_soc_dai *aif2_dai = card->rtd[1].cpu_dai;
int ret;
ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_jack_new(codec, "Headset",
SND_JACK_HEADSET | SND_JACK_MECHANICAL |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3 |
SND_JACK_BTN_4 | SND_JACK_BTN_5,
&littlemill_headset);
if (ret)
return ret;
/* This will check device compatibility itself */
wm8958_mic_detect(codec, &littlemill_headset, NULL, NULL, NULL, NULL);
/* As will this */
wm8994_mic_detect(codec, &littlemill_headset, 1);
return 0;
}
static struct snd_soc_card littlemill = {
.name = "Littlemill",
.owner = THIS_MODULE,
.dai_link = littlemill_dai,
.num_links = ARRAY_SIZE(littlemill_dai),
.set_bias_level = littlemill_set_bias_level,
.set_bias_level_post = littlemill_set_bias_level_post,
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.late_probe = littlemill_late_probe,
};
static int littlemill_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &littlemill;
int ret;
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
return 0;
}
static int littlemill_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver littlemill_driver = {
.driver = {
.name = "littlemill",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = littlemill_probe,
.remove = littlemill_remove,
};
module_platform_driver(littlemill_driver);
MODULE_DESCRIPTION("Littlemill audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:littlemill");

View File

@@ -0,0 +1,72 @@
/*
* 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 <sound/soc.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-ac97",
},
};
static struct snd_soc_card ln2440sbc = {
.name = "LN2440SBC",
.owner = THIS_MODULE,
.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");

224
sound/soc/samsung/lowland.c Normal file
View File

@@ -0,0 +1,224 @@
/*
* Lowland audio support
*
* Copyright 2011 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 <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm5100.h"
#include "../codecs/wm9081.h"
#define MCLK1_RATE (44100 * 512)
#define CLKOUT_RATE (44100 * 256)
static struct snd_soc_jack lowland_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin lowland_headset_pins[] = {
{
.pin = "Headphone",
.mask = SND_JACK_HEADPHONE | SND_JACK_LINEOUT,
},
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
};
static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
int ret;
ret = snd_soc_codec_set_sysclk(codec, WM5100_CLK_SYSCLK,
WM5100_CLKSRC_MCLK1, MCLK1_RATE,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK clock source: %d\n", ret);
return ret;
}
/* Clock OPCLK, used by the other audio components. */
ret = snd_soc_codec_set_sysclk(codec, WM5100_CLK_OPCLK, 0,
CLKOUT_RATE, 0);
if (ret < 0) {
pr_err("Failed to set OPCLK rate: %d\n", ret);
return ret;
}
ret = snd_soc_jack_new(codec, "Headset",
SND_JACK_LINEOUT | SND_JACK_HEADSET |
SND_JACK_BTN_0,
&lowland_headset);
if (ret)
return ret;
ret = snd_soc_jack_add_pins(&lowland_headset,
ARRAY_SIZE(lowland_headset_pins),
lowland_headset_pins);
if (ret)
return ret;
wm5100_detect(codec, &lowland_headset);
return 0;
}
static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
snd_soc_dapm_nc_pin(&codec->dapm, "LINEOUT");
/* At any time the WM9081 is active it will have this clock */
return snd_soc_codec_set_sysclk(codec, WM9081_SYSCLK_MCLK, 0,
CLKOUT_RATE, 0);
}
static const struct snd_soc_pcm_stream sub_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 44100,
.rate_max = 44100,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link lowland_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm5100-aif1",
.platform_name = "samsung-i2s.0",
.codec_name = "wm5100.1-001a",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.init = lowland_wm5100_init,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm5100-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
},
{
.name = "Sub Speaker",
.stream_name = "Sub Speaker",
.cpu_dai_name = "wm5100-aif3",
.codec_dai_name = "wm9081-hifi",
.codec_name = "wm9081.1-006c",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
.params = &sub_params,
.init = lowland_wm9081_init,
},
};
static struct snd_soc_codec_conf lowland_codec_conf[] = {
{
.dev_name = "wm9081.1-006c",
.name_prefix = "Sub",
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("Main DMIC"),
SOC_DAPM_PIN_SWITCH("Main AMIC"),
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
SOC_DAPM_PIN_SWITCH("Headphone"),
};
static struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
SND_SOC_DAPM_MIC("Main AMIC", NULL),
SND_SOC_DAPM_MIC("Main DMIC", NULL),
};
static struct snd_soc_dapm_route audio_paths[] = {
{ "Sub IN1", NULL, "HPOUT2L" },
{ "Sub IN2", NULL, "HPOUT2R" },
{ "Main Speaker", NULL, "Sub SPKN" },
{ "Main Speaker", NULL, "Sub SPKP" },
{ "Main Speaker", NULL, "SPKDAT1" },
};
static struct snd_soc_card lowland = {
.name = "Lowland",
.owner = THIS_MODULE,
.dai_link = lowland_dai,
.num_links = ARRAY_SIZE(lowland_dai),
.codec_conf = lowland_codec_conf,
.num_configs = ARRAY_SIZE(lowland_codec_conf),
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
};
static int lowland_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &lowland;
int ret;
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
return 0;
}
static int lowland_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver lowland_driver = {
.driver = {
.name = "lowland",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = lowland_probe,
.remove = lowland_remove,
};
module_platform_driver(lowland_driver);
MODULE_DESCRIPTION("Lowland audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:lowland");

View File

@@ -0,0 +1,471 @@
/*
* neo1973_wm8753.c -- SoC audio for Openmoko Neo1973 and Freerunner devices
*
* Copyright 2007 Openmoko Inc
* Author: Graeme Gregory <graeme@openmoko.org>
* Copyright 2007 Wolfson Microelectronics PLC.
* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or 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/platform_device.h>
#include <linux/gpio.h>
#include <sound/soc.h>
#include <asm/mach-types.h>
#include "regs-iis.h"
#include "../codecs/wm8753.h"
#include "s3c24xx-i2s.h"
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;
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;
/* 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;
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;
/* 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,
};
/* Shared routes and controls */
static const struct snd_soc_dapm_widget neo1973_wm8753_dapm_widgets[] = {
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),
};
static const struct snd_soc_dapm_route neo1973_wm8753_routes[] = {
/* 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"},
/* Connect the ALC pins */
{"ACIN", NULL, "ACOP"},
};
static const struct snd_kcontrol_new neo1973_wm8753_controls[] = {
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"),
};
/* GTA02 specific routes and controls */
static int gta02_speaker_enabled;
static int lm4853_set_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
gta02_speaker_enabled = ucontrol->value.integer.value[0];
gpio_set_value(S3C2410_GPJ(2), !gta02_speaker_enabled);
return 0;
}
static int lm4853_get_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = gta02_speaker_enabled;
return 0;
}
static int lm4853_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
gpio_set_value(S3C2410_GPJ(1), SND_SOC_DAPM_EVENT_OFF(event));
return 0;
}
static const struct snd_soc_dapm_route neo1973_gta02_routes[] = {
/* Connections to the amp */
{"Stereo Out", NULL, "LOUT1"},
{"Stereo Out", NULL, "ROUT1"},
/* Call Speaker */
{"Handset Spk", NULL, "LOUT2"},
{"Handset Spk", NULL, "ROUT2"},
};
static const struct snd_kcontrol_new neo1973_gta02_wm8753_controls[] = {
SOC_DAPM_PIN_SWITCH("Handset Spk"),
SOC_DAPM_PIN_SWITCH("Stereo Out"),
SOC_SINGLE_BOOL_EXT("Amp Spk Switch", 0,
lm4853_get_spk,
lm4853_set_spk),
};
static const struct snd_soc_dapm_widget neo1973_gta02_wm8753_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Handset Spk", NULL),
SND_SOC_DAPM_SPK("Stereo Out", lm4853_event),
};
static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = &codec->dapm;
int ret;
ret = snd_soc_dapm_new_controls(dapm, neo1973_gta02_wm8753_dapm_widgets,
ARRAY_SIZE(neo1973_gta02_wm8753_dapm_widgets));
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(dapm, neo1973_gta02_routes,
ARRAY_SIZE(neo1973_gta02_routes));
if (ret)
return ret;
ret = snd_soc_add_card_controls(codec->card, neo1973_gta02_wm8753_controls,
ARRAY_SIZE(neo1973_gta02_wm8753_controls));
if (ret)
return ret;
snd_soc_dapm_disable_pin(dapm, "Stereo Out");
snd_soc_dapm_disable_pin(dapm, "Handset Spk");
snd_soc_dapm_ignore_suspend(dapm, "Stereo Out");
snd_soc_dapm_ignore_suspend(dapm, "Handset Spk");
return 0;
}
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 ret;
/* 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 specific widgets */
ret = snd_soc_dapm_new_controls(dapm, neo1973_wm8753_dapm_widgets,
ARRAY_SIZE(neo1973_wm8753_dapm_widgets));
if (ret)
return ret;
/* add neo1973 specific controls */
ret = snd_soc_add_card_controls(rtd->card, neo1973_wm8753_controls,
ARRAY_SIZE(neo1973_wm8753_controls));
if (ret)
return ret;
/* set up neo1973 specific audio routes */
ret = snd_soc_dapm_add_routes(dapm, neo1973_wm8753_routes,
ARRAY_SIZE(neo1973_wm8753_routes));
if (ret)
return ret;
/* set endpoints to default off mode */
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");
/* allow audio paths from the GSM modem to run during suspend */
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");
if (machine_is_neo1973_gta02()) {
ret = neo1973_gta02_wm8753_init(codec);
if (ret)
return ret;
}
return 0;
}
static struct snd_soc_dai_link neo1973_dai[] = {
{ /* Hifi Playback - for similatious use with voice below */
.name = "WM8753",
.stream_name = "WM8753 HiFi",
.platform_name = "s3c24xx-iis",
.cpu_dai_name = "s3c24xx-iis",
.codec_dai_name = "wm8753-hifi",
.codec_name = "wm8753.0-001a",
.init = neo1973_wm8753_init,
.ops = &neo1973_hifi_ops,
},
{ /* Voice via BT */
.name = "Bluetooth",
.stream_name = "Voice",
.cpu_dai_name = "dfbmcs320-pcm",
.codec_dai_name = "wm8753-voice",
.codec_name = "wm8753.0-001a",
.ops = &neo1973_voice_ops,
},
};
static struct snd_soc_aux_dev neo1973_aux_devs[] = {
{
.name = "dfbmcs320",
.codec_name = "dfbmcs320.0",
},
};
static struct snd_soc_codec_conf neo1973_codec_conf[] = {
{
.dev_name = "lm4857.0-007c",
.name_prefix = "Amp",
},
};
static const struct gpio neo1973_gta02_gpios[] = {
{ S3C2410_GPJ(2), GPIOF_OUT_INIT_HIGH, "GTA02_HP_IN" },
{ S3C2410_GPJ(1), GPIOF_OUT_INIT_HIGH, "GTA02_AMP_SHUT" },
};
static struct snd_soc_card neo1973 = {
.name = "neo1973",
.owner = THIS_MODULE,
.dai_link = neo1973_dai,
.num_links = ARRAY_SIZE(neo1973_dai),
.aux_dev = neo1973_aux_devs,
.num_aux_devs = ARRAY_SIZE(neo1973_aux_devs),
.codec_conf = neo1973_codec_conf,
.num_configs = ARRAY_SIZE(neo1973_codec_conf),
};
static struct platform_device *neo1973_snd_device;
static int __init neo1973_init(void)
{
int ret;
if (!machine_is_neo1973_gta02())
return -ENODEV;
if (machine_is_neo1973_gta02()) {
neo1973.name = "neo1973gta02";
neo1973.num_aux_devs = 1;
ret = gpio_request_array(neo1973_gta02_gpios,
ARRAY_SIZE(neo1973_gta02_gpios));
if (ret)
return ret;
}
neo1973_snd_device = platform_device_alloc("soc-audio", -1);
if (!neo1973_snd_device) {
ret = -ENOMEM;
goto err_gpio_free;
}
platform_set_drvdata(neo1973_snd_device, &neo1973);
ret = platform_device_add(neo1973_snd_device);
if (ret)
goto err_put_device;
return 0;
err_put_device:
platform_device_put(neo1973_snd_device);
err_gpio_free:
if (machine_is_neo1973_gta02()) {
gpio_free_array(neo1973_gta02_gpios,
ARRAY_SIZE(neo1973_gta02_gpios));
}
return ret;
}
module_init(neo1973_init);
static void __exit neo1973_exit(void)
{
platform_device_unregister(neo1973_snd_device);
if (machine_is_neo1973_gta02()) {
gpio_free_array(neo1973_gta02_gpios,
ARRAY_SIZE(neo1973_gta02_gpios));
}
}
module_exit(neo1973_exit);
/* Module information */
MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org");
MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 and Frerunner");
MODULE_LICENSE("GPL");

659
sound/soc/samsung/pcm.c Normal file
View File

@@ -0,0 +1,659 @@
/* sound/soc/samsung/pcm.c
*
* ALSA SoC Audio Layer - S3C PCM-Controller driver
*
* Copyright (c) 2009 Samsung Electronics Co. Ltd
* Author: Jaswinder Singh <jassisinghbrar@gmail.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/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <linux/platform_data/asoc-s3c.h>
#include <mach/dma.h>
#include "dma.h"
#include "pcm.h"
/*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)
/**
* 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;
};
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_IB_NF:
/* Nothing to do, IB_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 const 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, \
}
static 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,
},
};
static const struct snd_soc_component_driver s3c_pcm_component = {
.name = "s3c-pcm",
};
static 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_prepare_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_prepare_enable(pcm->pclk);
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];
pm_runtime_enable(&pdev->dev);
ret = snd_soc_register_component(&pdev->dev, &s3c_pcm_component,
&s3c_pcm_dai[pdev->id], 1);
if (ret != 0) {
dev_err(&pdev->dev, "failed to get register DAI: %d\n", ret);
goto err5;
}
ret = asoc_dma_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
goto err6;
}
return 0;
err6:
snd_soc_unregister_component(&pdev->dev);
err5:
clk_disable_unprepare(pcm->pclk);
clk_put(pcm->pclk);
err4:
iounmap(pcm->regs);
err3:
release_mem_region(mem_res->start, resource_size(mem_res));
err2:
clk_disable_unprepare(pcm->cclk);
clk_put(pcm->cclk);
err1:
return ret;
}
static int s3c_pcm_dev_remove(struct platform_device *pdev)
{
struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id];
struct resource *mem_res;
asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&pdev->dev);
pm_runtime_disable(&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_unprepare(pcm->cclk);
clk_disable_unprepare(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,
},
};
module_platform_driver(s3c_pcm_driver);
/* Module information */
MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
MODULE_DESCRIPTION("S3C PCM Controller Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-pcm");

17
sound/soc/samsung/pcm.h Normal file
View File

@@ -0,0 +1,17 @@
/* 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__
#define S3C_PCM_CLKSRC_PCLK 0
#define S3C_PCM_CLKSRC_MUX 1
#define S3C_PCM_SCLK_PER_FS 0
#endif /* __S3C_PCM_H */

View File

@@ -0,0 +1,67 @@
/* arch/arm/mach-s3c2410/include/mach/regs-ac97.h
*
* Copyright (c) 2006 Simtec Electronics <linux@simtec.co.uk>
* http://www.simtec.co.uk/products/SWLINUX/
*
* 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.
*
* S3C2440 AC97 Controller
*/
#ifndef __ASM_ARCH_REGS_AC97_H
#define __ASM_ARCH_REGS_AC97_H __FILE__
#define S3C_AC97_GLBCTRL (0x00)
#define S3C_AC97_GLBCTRL_CODECREADYIE (1<<22)
#define S3C_AC97_GLBCTRL_PCMOUTURIE (1<<21)
#define S3C_AC97_GLBCTRL_PCMINORIE (1<<20)
#define S3C_AC97_GLBCTRL_MICINORIE (1<<19)
#define S3C_AC97_GLBCTRL_PCMOUTTIE (1<<18)
#define S3C_AC97_GLBCTRL_PCMINTIE (1<<17)
#define S3C_AC97_GLBCTRL_MICINTIE (1<<16)
#define S3C_AC97_GLBCTRL_PCMOUTTM_OFF (0<<12)
#define S3C_AC97_GLBCTRL_PCMOUTTM_PIO (1<<12)
#define S3C_AC97_GLBCTRL_PCMOUTTM_DMA (2<<12)
#define S3C_AC97_GLBCTRL_PCMOUTTM_MASK (3<<12)
#define S3C_AC97_GLBCTRL_PCMINTM_OFF (0<<10)
#define S3C_AC97_GLBCTRL_PCMINTM_PIO (1<<10)
#define S3C_AC97_GLBCTRL_PCMINTM_DMA (2<<10)
#define S3C_AC97_GLBCTRL_PCMINTM_MASK (3<<10)
#define S3C_AC97_GLBCTRL_MICINTM_OFF (0<<8)
#define S3C_AC97_GLBCTRL_MICINTM_PIO (1<<8)
#define S3C_AC97_GLBCTRL_MICINTM_DMA (2<<8)
#define S3C_AC97_GLBCTRL_MICINTM_MASK (3<<8)
#define S3C_AC97_GLBCTRL_TRANSFERDATAENABLE (1<<3)
#define S3C_AC97_GLBCTRL_ACLINKON (1<<2)
#define S3C_AC97_GLBCTRL_WARMRESET (1<<1)
#define S3C_AC97_GLBCTRL_COLDRESET (1<<0)
#define S3C_AC97_GLBSTAT (0x04)
#define S3C_AC97_GLBSTAT_CODECREADY (1<<22)
#define S3C_AC97_GLBSTAT_PCMOUTUR (1<<21)
#define S3C_AC97_GLBSTAT_PCMINORI (1<<20)
#define S3C_AC97_GLBSTAT_MICINORI (1<<19)
#define S3C_AC97_GLBSTAT_PCMOUTTI (1<<18)
#define S3C_AC97_GLBSTAT_PCMINTI (1<<17)
#define S3C_AC97_GLBSTAT_MICINTI (1<<16)
#define S3C_AC97_GLBSTAT_MAINSTATE_IDLE (0<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_INIT (1<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_READY (2<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE (3<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_LP (4<<0)
#define S3C_AC97_GLBSTAT_MAINSTATE_WARM (5<<0)
#define S3C_AC97_CODEC_CMD (0x08)
#define S3C_AC97_CODEC_CMD_READ (1<<23)
#define S3C_AC97_STAT (0x0c)
#define S3C_AC97_PCM_ADDR (0x10)
#define S3C_AC97_PCM_DATA (0x18)
#define S3C_AC97_MIC_DATA (0x1C)
#endif /* __ASM_ARCH_REGS_AC97_H */

View File

@@ -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 */

View File

@@ -0,0 +1,70 @@
/* arch/arm/plat-samsung/include/plat/regs-iis.h
*
* Copyright (c) 2003 Simtec Electronics <linux@simtec.co.uk>
* http://www.simtec.co.uk/products/SWLINUX/
*
* 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.
*
* S3C2410 IIS register definition
*/
#ifndef __ASM_ARCH_REGS_IIS_H
#define __ASM_ARCH_REGS_IIS_H
#define S3C2410_IISCON (0x00)
#define S3C2410_IISCON_LRINDEX (1 << 8)
#define S3C2410_IISCON_TXFIFORDY (1 << 7)
#define S3C2410_IISCON_RXFIFORDY (1 << 6)
#define S3C2410_IISCON_TXDMAEN (1 << 5)
#define S3C2410_IISCON_RXDMAEN (1 << 4)
#define S3C2410_IISCON_TXIDLE (1 << 3)
#define S3C2410_IISCON_RXIDLE (1 << 2)
#define S3C2410_IISCON_PSCEN (1 << 1)
#define S3C2410_IISCON_IISEN (1 << 0)
#define S3C2410_IISMOD (0x04)
#define S3C2440_IISMOD_MPLL (1 << 9)
#define S3C2410_IISMOD_SLAVE (1 << 8)
#define S3C2410_IISMOD_NOXFER (0 << 6)
#define S3C2410_IISMOD_RXMODE (1 << 6)
#define S3C2410_IISMOD_TXMODE (2 << 6)
#define S3C2410_IISMOD_TXRXMODE (3 << 6)
#define S3C2410_IISMOD_LR_LLOW (0 << 5)
#define S3C2410_IISMOD_LR_RLOW (1 << 5)
#define S3C2410_IISMOD_IIS (0 << 4)
#define S3C2410_IISMOD_MSB (1 << 4)
#define S3C2410_IISMOD_8BIT (0 << 3)
#define S3C2410_IISMOD_16BIT (1 << 3)
#define S3C2410_IISMOD_BITMASK (1 << 3)
#define S3C2410_IISMOD_256FS (0 << 2)
#define S3C2410_IISMOD_384FS (1 << 2)
#define S3C2410_IISMOD_16FS (0 << 0)
#define S3C2410_IISMOD_32FS (1 << 0)
#define S3C2410_IISMOD_48FS (2 << 0)
#define S3C2410_IISMOD_FS_MASK (3 << 0)
#define S3C2410_IISPSR (0x08)
#define S3C2410_IISPSR_INTMASK (31 << 5)
#define S3C2410_IISPSR_INTSHIFT (5)
#define S3C2410_IISPSR_EXTMASK (31 << 0)
#define S3C2410_IISPSR_EXTSHFIT (0)
#define S3C2410_IISFCON (0x0c)
#define S3C2410_IISFCON_TXDMA (1 << 15)
#define S3C2410_IISFCON_RXDMA (1 << 14)
#define S3C2410_IISFCON_TXENABLE (1 << 13)
#define S3C2410_IISFCON_RXENABLE (1 << 12)
#define S3C2410_IISFCON_TXMASK (0x3f << 6)
#define S3C2410_IISFCON_TXSHIFT (6)
#define S3C2410_IISFCON_RXMASK (0x3f)
#define S3C2410_IISFCON_RXSHIFT (0)
#define S3C2410_IISFIFO (0x10)
#endif /* __ASM_ARCH_REGS_IIS_H */

View File

@@ -0,0 +1,301 @@
/*
* 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/types.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include "regs-iis.h"
#include <asm/mach-types.h>
#include "s3c24xx-i2s.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 = "s3c24xx-iis",
.codec_name = "uda1380-codec.0-001a",
.ops = &rx1950_ops,
},
};
/* 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 snd_soc_card rx1950_asoc = {
.name = "rx1950",
.owner = THIS_MODULE,
.dai_link = rx1950_uda1380_dai,
.num_links = ARRAY_SIZE(rx1950_uda1380_dai),
.dapm_widgets = uda1380_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
};
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;
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_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");

View File

@@ -0,0 +1,758 @@
/* 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/module.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <sound/soc.h>
#include <sound/pcm_params.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_component(struct device *dev, int id,
struct snd_soc_component_driver *cmp_drv,
struct snd_soc_dai_driver *dai_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_component(dev, cmp_drv, dai_drv, 1);
}
EXPORT_SYMBOL_GPL(s3c_i2sv2_register_component);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,107 @@
/* 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_component - register component and 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_component(struct device *dev, int id,
struct snd_soc_component_driver *cmp_drv,
struct snd_soc_dai_driver *dai_drv);
#endif /* __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H */

View File

@@ -0,0 +1,213 @@
/* 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/delay.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <mach/dma.h>
#include "dma.h"
#include "regs-i2s-v2.h"
#include "s3c2412-i2s.h"
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 (IS_ERR(s3c2412_i2s.iis_cclk)) {
pr_err("failed to get i2sclk clock\n");
iounmap(s3c2412_i2s.regs);
return PTR_ERR(s3c2412_i2s.iis_cclk);
}
/* 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 (GPE0...GPE4) in correct mode */
s3c_gpio_cfgall_range(S3C2410_GPE(0), 5, S3C_GPIO_SFN(2),
S3C_GPIO_PULL_NONE);
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 const 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 const struct snd_soc_component_driver s3c2412_i2s_component = {
.name = "s3c2412-i2s",
};
static int s3c2412_iis_dev_probe(struct platform_device *pdev)
{
int ret = 0;
ret = s3c_i2sv2_register_component(&pdev->dev, -1,
&s3c2412_i2s_component,
&s3c2412_i2s_dai);
if (ret) {
pr_err("failed to register the dai\n");
return ret;
}
ret = asoc_dma_platform_register(&pdev->dev);
if (ret) {
pr_err("failed to register the DMA: %d\n", ret);
goto err;
}
return 0;
err:
snd_soc_unregister_component(&pdev->dev);
return ret;
}
static int s3c2412_iis_dev_remove(struct platform_device *pdev)
{
asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&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,
},
};
module_platform_driver(s3c2412_iis_driver);
/* Module information */
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("S3C2412 I2S SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s3c2412-iis");

View File

@@ -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 */

View File

@@ -0,0 +1,517 @@
/*
* 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/delay.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <mach/dma.h>
#include "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 (IS_ERR(s3c24xx_i2s.iis_clk)) {
pr_err("failed to get iis_clock\n");
iounmap(s3c24xx_i2s.regs);
return PTR_ERR(s3c24xx_i2s.iis_clk);
}
clk_enable(s3c24xx_i2s.iis_clk);
/* Configure the I2S pins (GPE0...GPE4) in correct mode */
s3c_gpio_cfgall_range(S3C2410_GPE(0), 5, S3C_GPIO_SFN(2),
S3C_GPIO_PULL_NONE);
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 const 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 const struct snd_soc_component_driver s3c24xx_i2s_component = {
.name = "s3c24xx-i2s",
};
static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
{
int ret = 0;
ret = snd_soc_register_component(&pdev->dev, &s3c24xx_i2s_component,
&s3c24xx_i2s_dai, 1);
if (ret) {
pr_err("failed to register the dai\n");
return ret;
}
ret = asoc_dma_platform_register(&pdev->dev);
if (ret) {
pr_err("failed to register the dma: %d\n", ret);
goto err;
}
return 0;
err:
snd_soc_unregister_component(&pdev->dev);
return ret;
}
static int s3c24xx_iis_dev_remove(struct platform_device *pdev)
{
asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&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,
},
};
module_platform_driver(s3c24xx_iis_driver);
/* Module information */
MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s3c24xx-iis");

View File

@@ -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_*/

View File

@@ -0,0 +1,388 @@
/* 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/gpio.h>
#include <linux/clk.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <linux/platform_data/asoc-s3c24xx_simtec.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_card *card = rtd->card;
if (pdata->amp_gpio > 0) {
pr_debug("%s: adding amp routes\n", __func__);
snd_soc_add_card_controls(card, 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_card_controls(card, 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
static 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 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 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");

View File

@@ -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

View File

@@ -0,0 +1,123 @@
/* 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 <sound/soc.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_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);
return 0;
}
static struct snd_soc_dai_link simtec_dai_aic33 = {
.name = "tlv320aic33",
.stream_name = "TLV320AIC33",
.codec_name = "tlv320aic3x-codec.0-001a",
.cpu_dai_name = "s3c24xx-iis",
.codec_dai_name = "tlv320aic3x-hifi",
.platform_name = "s3c24xx-iis",
.init = simtec_hermes_init,
};
/* simtec audio machine driver */
static struct snd_soc_card snd_soc_machine_simtec_aic33 = {
.name = "Simtec-Hermes",
.owner = THIS_MODULE,
.dai_link = &simtec_dai_aic33,
.num_links = 1,
.dapm_widgets = dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
.dapm_routes = base_map,
.num_dapm_routes = ARRAY_SIZE(base_map),
};
static int 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 = simtec_audio_remove,
};
module_platform_driver(simtec_audio_hermes_platdrv);
MODULE_ALIAS("platform:s3c24xx-simtec-hermes-snd");
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,111 @@
/* 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 <sound/soc.h>
#include "s3c24xx_simtec.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_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);
return 0;
}
static struct snd_soc_dai_link simtec_dai_aic23 = {
.name = "tlv320aic23",
.stream_name = "TLV320AIC23",
.codec_name = "tlv320aic3x-codec.0-001a",
.cpu_dai_name = "s3c24xx-iis",
.codec_dai_name = "tlv320aic3x-hifi",
.platform_name = "s3c24xx-iis",
.init = simtec_tlv320aic23_init,
};
/* simtec audio machine driver */
static struct snd_soc_card snd_soc_machine_simtec_aic23 = {
.name = "Simtec",
.owner = THIS_MODULE,
.dai_link = &simtec_dai_aic23,
.num_links = 1,
.dapm_widgets = dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
.dapm_routes = base_map,
.num_dapm_routes = ARRAY_SIZE(base_map),
};
static int 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_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "s3c24xx-simtec-tlv320aic23",
.pm = simtec_audio_pm,
},
.probe = simtec_audio_tlv320aic23_probe,
.remove = simtec_audio_remove,
};
module_platform_driver(simtec_audio_tlv320aic23_driver);
MODULE_ALIAS("platform:s3c24xx-simtec-tlv320aic23");
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,351 @@
/*
* 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/clk.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/s3c24xx_uda134x.h>
#include "regs-iis.h"
#include "s3c24xx-i2s.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 (IS_ERR(xtal)) {
printk(KERN_ERR "%s cannot get xtal\n", __func__);
ret = PTR_ERR(xtal);
} else {
pclk = clk_get(&s3c24xx_uda134x_snd_device->dev,
"pclk");
if (IS_ERR(pclk)) {
printk(KERN_ERR "%s cannot get pclk\n",
__func__);
clk_put(xtal);
ret = PTR_ERR(pclk);
}
}
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-codec",
.codec_dai_name = "uda134x-hifi",
.cpu_dai_name = "s3c24xx-iis",
.ops = &s3c24xx_uda134x_ops,
.platform_name = "s3c24xx-iis",
};
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
.name = "S3C24XX_UDA134X",
.owner = THIS_MODULE,
.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);
platform_device_add_data(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x, sizeof(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,
},
};
module_platform_driver(s3c24xx_uda134x_driver);
MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,275 @@
/* 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/gpio.h>
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <asm/mach-types.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;
/* 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");
/* 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-i2s.0",
.codec_name = "wm8750.0-0x1a",
.init = smartq_wm8987_init,
.ops = &smartq_hifi_ops,
},
};
static struct snd_soc_card snd_soc_smartq = {
.name = "SmartQ",
.owner = THIS_MODULE,
.dai_link = smartq_dai,
.num_links = ARRAY_SIZE(smartq_dai),
.dapm_widgets = wm8987_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(wm8987_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
.controls = wm8987_smartq_controls,
.num_controls = ARRAY_SIZE(wm8987_smartq_controls),
};
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");

View File

@@ -0,0 +1,68 @@
/*
* 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 <sound/soc.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-ac97",
},
};
static struct snd_soc_card smdk2443 = {
.name = "SMDK2443",
.owner = THIS_MODULE,
.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");

View File

@@ -0,0 +1,223 @@
/*
* 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/clk.h>
#include <linux/module.h>
#include <sound/soc.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-spdif",
.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",
.owner = THIS_MODULE,
.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");

View File

@@ -0,0 +1,255 @@
/*
* smdk_wm8580.c
*
* Copyright (c) 2009 Samsung Electronics Co. Ltd
* Author: Jaswinder Singh <jassisinghbrar@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 <sound/soc.h>
#include <sound/pcm_params.h>
#include <asm/mach-types.h>
#include "../codecs/wm8580.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 smdk_wm8580_dapm_widgets[] = {
SND_SOC_DAPM_HP("Front", NULL),
SND_SOC_DAPM_HP("Center+Sub", NULL),
SND_SOC_DAPM_HP("Rear", NULL),
SND_SOC_DAPM_MIC("MicIn", NULL),
SND_SOC_DAPM_LINE("LineIn", NULL),
};
/* SMDK-PAIFTX connections */
static const struct snd_soc_dapm_route smdk_wm8580_audio_map[] = {
/* MicIn feeds AINL */
{"AINL", NULL, "MicIn"},
/* LineIn feeds AINL/R */
{"AINL", NULL, "LineIn"},
{"AINR", NULL, "LineIn"},
/* 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;
/* 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");
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-i2s.0",
.codec_name = "wm8580.0-001b",
.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-i2s.0",
.codec_name = "wm8580.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-sec",
.codec_dai_name = "wm8580-hifi-playback",
.platform_name = "samsung-i2s-sec",
.codec_name = "wm8580.0-001b",
.ops = &smdk_ops,
},
};
static struct snd_soc_card smdk = {
.name = "SMDK-I2S",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = 2,
.dapm_widgets = smdk_wm8580_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(smdk_wm8580_dapm_widgets),
.dapm_routes = smdk_wm8580_audio_map,
.num_dapm_routes = ARRAY_SIZE(smdk_wm8580_audio_map),
};
static struct platform_device *smdk_snd_device;
static int __init smdk_audio_init(void)
{
int ret;
char *str;
if (machine_is_smdkc100()
|| machine_is_smdkv210() || machine_is_smdkc110()) {
smdk.num_links = 3;
} 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, jassisinghbrar@gmail.com");
MODULE_DESCRIPTION("ALSA SoC SMDK WM8580");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,196 @@
/*
* sound/soc/samsung/smdk_wm8580pcm.c
*
* Copyright (c) 2011 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 <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/pcm.h>
#include <asm/mach-types.h>
#include "../codecs/wm8580.h"
#include "dma.h"
#include "pcm.h"
/*
* Board Settings:
* o '1' means 'ON'
* o '0' means 'OFF'
* o 'X' means 'Don't care'
*
* SMDK6410, SMDK6440, SMDK6450 Base B/D: CFG1-0000, CFG2-1111
* SMDKC110, SMDKV210: CFGB11-100100, CFGB12-0000
*/
#define SMDK_WM8580_EXT_OSC 12000000
#define SMDK_WM8580_EXT_MCLK 4096000
#define SMDK_WM8580_EXT_VOICE 2048000
static unsigned long mclk_freq;
static unsigned long xtal_freq;
/*
* If MCLK clock directly gets from XTAL, we don't have to use PLL
* to make MCLK, but if XTAL clock source connects with other codec
* pin (like XTI), we should have to set codec's PLL to make MCLK.
* Because Samsung SoC does not support pcmcdclk output like I2S.
*/
static int smdk_wm8580_pcm_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 rfs, ret;
switch (params_rate(params)) {
case 8000:
break;
default:
printk(KERN_ERR "%s:%d Sampling Rate %u not supported!\n",
__func__, __LINE__, params_rate(params));
return -EINVAL;
}
rfs = mclk_freq / params_rate(params) / 2;
/* Set the codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* Set the cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_B
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
if (mclk_freq == xtal_freq) {
ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_MCLK,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK,
WM8580_CLKSRC_MCLK);
if (ret < 0)
return ret;
} else {
ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
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,
xtal_freq, mclk_freq);
if (ret < 0)
return ret;
}
/* Set PCM source clock on CPU */
ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* Set SCLK_DIV for making bclk */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops smdk_wm8580_pcm_ops = {
.hw_params = smdk_wm8580_pcm_hw_params,
};
static struct snd_soc_dai_link smdk_dai[] = {
{
.name = "WM8580 PAIF PCM RX",
.stream_name = "Playback",
.cpu_dai_name = "samsung-pcm.0",
.codec_dai_name = "wm8580-hifi-playback",
.platform_name = "samsung-audio",
.codec_name = "wm8580.0-001b",
.ops = &smdk_wm8580_pcm_ops,
}, {
.name = "WM8580 PAIF PCM TX",
.stream_name = "Capture",
.cpu_dai_name = "samsung-pcm.0",
.codec_dai_name = "wm8580-hifi-capture",
.platform_name = "samsung-pcm.0",
.codec_name = "wm8580.0-001b",
.ops = &smdk_wm8580_pcm_ops,
},
};
static struct snd_soc_card smdk_pcm = {
.name = "SMDK-PCM",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = 2,
};
/*
* After SMDKC110 Base Board's Rev is '0.1', 12MHz External OSC(X1)
* is absent (or not connected), so we connect EXT_VOICE_CLK(OSC4),
* 2.0484Mhz, directly with MCLK both Codec and SoC.
*/
static int snd_smdk_probe(struct platform_device *pdev)
{
int ret = 0;
xtal_freq = SMDK_WM8580_EXT_OSC;
mclk_freq = SMDK_WM8580_EXT_MCLK;
if (machine_is_smdkc110() || machine_is_smdkv210())
xtal_freq = mclk_freq = SMDK_WM8580_EXT_VOICE;
smdk_pcm.dev = &pdev->dev;
ret = snd_soc_register_card(&smdk_pcm);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret);
return ret;
}
return 0;
}
static int snd_smdk_remove(struct platform_device *pdev)
{
snd_soc_unregister_card(&smdk_pcm);
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct platform_driver snd_smdk_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "samsung-smdk-pcm",
},
.probe = snd_smdk_probe,
.remove = snd_smdk_remove,
};
module_platform_driver(snd_smdk_driver);
MODULE_AUTHOR("Sangbeom Kim, <sbkim73@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC SMDK WM8580 for PCM");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,215 @@
/*
* 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"
#include <sound/pcm_params.h>
#include <linux/module.h>
#include <linux/of.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_format(params) == SNDRV_PCM_FORMAT_S24_LE)
pll_out = params_rate(params) * 384;
else 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");
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-i2s.0",
.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-sec",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-i2s-sec",
.codec_name = "wm8994-codec",
.ops = &smdk_ops,
},
};
static struct snd_soc_card smdk = {
.name = "SMDK-I2S",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = ARRAY_SIZE(smdk_dai),
};
static int smdk_audio_probe(struct platform_device *pdev)
{
int ret;
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &smdk;
card->dev = &pdev->dev;
if (np) {
smdk_dai[0].cpu_dai_name = NULL;
smdk_dai[0].cpu_of_node = of_parse_phandle(np,
"samsung,i2s-controller", 0);
if (!smdk_dai[0].cpu_of_node) {
dev_err(&pdev->dev,
"Property 'samsung,i2s-controller' missing or invalid\n");
ret = -EINVAL;
}
smdk_dai[0].platform_name = NULL;
smdk_dai[0].platform_of_node = smdk_dai[0].cpu_of_node;
}
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret);
return ret;
}
static int smdk_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id samsung_wm8994_of_match[] = {
{ .compatible = "samsung,smdk-wm8994", },
{},
};
MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match);
#endif /* CONFIG_OF */
static struct platform_driver smdk_audio_driver = {
.driver = {
.name = "smdk-audio",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(samsung_wm8994_of_match),
},
.probe = smdk_audio_probe,
.remove = smdk_audio_remove,
};
module_platform_driver(smdk_audio_driver);
MODULE_DESCRIPTION("ALSA SoC SMDK WM8994");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:smdk-audio");

View File

@@ -0,0 +1,166 @@
/*
* sound/soc/samsung/smdk_wm8994pcm.c
*
* Copyright (c) 2011 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 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 <sound/soc.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "../codecs/wm8994.h"
#include "dma.h"
#include "pcm.h"
/*
* Board Settings:
* o '1' means 'ON'
* o '0' means 'OFF'
* o 'X' means 'Don't care'
*
* SMDKC210, SMDKV310: CFG3- 1001, 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.9344MHZ crystal attached to WM8994 */
#define SMDK_WM8994_FREQ 16934400
static int smdk_wm8994_pcm_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 long mclk_freq;
int rfs, ret;
switch(params_rate(params)) {
case 8000:
rfs = 512;
break;
default:
dev_err(cpu_dai->dev, "%s:%d Sampling Rate %u not supported!\n",
__func__, __LINE__, params_rate(params));
return -EINVAL;
}
mclk_freq = params_rate(params) * rfs;
/* Set the codec DAI configuration */
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/* Set the cpu DAI configuration */
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_B
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
SMDK_WM8994_FREQ, mclk_freq);
if (ret < 0)
return ret;
/* Set PCM source clock on CPU */
ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX,
mclk_freq, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* Set SCLK_DIV for making bclk */
ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops smdk_wm8994_pcm_ops = {
.hw_params = smdk_wm8994_pcm_hw_params,
};
static struct snd_soc_dai_link smdk_dai[] = {
{
.name = "WM8994 PAIF PCM",
.stream_name = "Primary PCM",
.cpu_dai_name = "samsung-pcm.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-pcm.0",
.codec_name = "wm8994-codec",
.ops = &smdk_wm8994_pcm_ops,
},
};
static struct snd_soc_card smdk_pcm = {
.name = "SMDK-PCM",
.owner = THIS_MODULE,
.dai_link = smdk_dai,
.num_links = 1,
};
static int snd_smdk_probe(struct platform_device *pdev)
{
int ret = 0;
smdk_pcm.dev = &pdev->dev;
ret = snd_soc_register_card(&smdk_pcm);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret);
return ret;
}
return 0;
}
static int snd_smdk_remove(struct platform_device *pdev)
{
snd_soc_unregister_card(&smdk_pcm);
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct platform_driver snd_smdk_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "samsung-smdk-pcm",
},
.probe = snd_smdk_probe,
.remove = snd_smdk_remove,
};
module_platform_driver(snd_smdk_driver);
MODULE_AUTHOR("Sangbeom Kim, <sbkim73@samsung.com>");
MODULE_DESCRIPTION("ALSA SoC SMDK WM8994 for PCM");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,108 @@
/*
* smdk_wm9713.c -- SoC audio for SMDK
*
* Copyright 2010 Samsung Electronics Co. Ltd.
* Author: Jaswinder Singh Brar <jassisinghbrar@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 <sound/soc.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-ac97",
.cpu_dai_name = "samsung-ac97",
.codec_dai_name = "wm9713-hifi",
.codec_name = "wm9713-codec",
};
static struct snd_soc_card smdk = {
.name = "SMDK WM9713",
.owner = THIS_MODULE,
.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, jassisinghbrar@gmail.com");
MODULE_DESCRIPTION("ALSA SoC SMDK+WM9713");
MODULE_LICENSE("GPL");

504
sound/soc/samsung/spdif.c Normal file
View File

@@ -0,0 +1,504 @@
/* 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 <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <linux/platform_data/asoc-s3c.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 const struct snd_soc_dai_ops spdif_dai_ops = {
.set_sysclk = spdif_set_sysclk,
.trigger = spdif_trigger,
.hw_params = spdif_hw_params,
.shutdown = spdif_shutdown,
};
static 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 const struct snd_soc_component_driver samsung_spdif_component = {
.name = "samsung-spdif",
};
static 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_prepare_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_prepare_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_component(&pdev->dev, &samsung_spdif_component,
&samsung_spdif_dai, 1);
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;
ret = asoc_dma_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "failed to register DMA: %d\n", ret);
goto err5;
}
return 0;
err5:
snd_soc_unregister_component(&pdev->dev);
err4:
iounmap(spdif->regs);
err3:
release_mem_region(mem_res->start, resource_size(mem_res));
err2:
clk_disable_unprepare(spdif->sclk);
clk_put(spdif->sclk);
err1:
clk_disable_unprepare(spdif->pclk);
clk_put(spdif->pclk);
err0:
return ret;
}
static int spdif_remove(struct platform_device *pdev)
{
struct samsung_spdif_info *spdif = &spdif_info;
struct resource *mem_res;
asoc_dma_platform_unregister(&pdev->dev);
snd_soc_unregister_component(&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_unprepare(spdif->sclk);
clk_put(spdif->sclk);
clk_disable_unprepare(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,
},
};
module_platform_driver(samsung_spdif_driver);
MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
MODULE_DESCRIPTION("Samsung S/PDIF Controller Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:samsung-spdif");

19
sound/soc/samsung/spdif.h Normal file
View File

@@ -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 */

View File

@@ -0,0 +1,364 @@
/*
* Speyside audio support
*
* Copyright 2011 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 <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm8996.h"
#include "../codecs/wm9081.h"
#define WM8996_HPSEL_GPIO 214
#define MCLK_AUDIO_RATE (512 * 48000)
static int speyside_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[1].codec_dai;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_dai_set_sysclk(codec_dai, WM8996_SYSCLK_MCLK2,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8996_FLL_MCLK2,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL\n");
return ret;
}
break;
default:
break;
}
return 0;
}
static int speyside_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[1].codec_dai;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
ret = snd_soc_dai_set_pll(codec_dai, 0,
WM8996_FLL_MCLK2,
32768, MCLK_AUDIO_RATE);
if (ret < 0) {
pr_err("Failed to start FLL\n");
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai,
WM8996_SYSCLK_FLL,
MCLK_AUDIO_RATE,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
}
break;
default:
break;
}
card->dapm.bias_level = level;
return 0;
}
static struct snd_soc_jack speyside_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin speyside_headset_pins[] = {
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
};
/* Default the headphone selection to active high */
static int speyside_jack_polarity;
static int speyside_get_micbias(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink)
{
if (speyside_jack_polarity && (strcmp(source->name, "MICB1") == 0))
return 1;
if (!speyside_jack_polarity && (strcmp(source->name, "MICB2") == 0))
return 1;
return 0;
}
static void speyside_set_polarity(struct snd_soc_codec *codec,
int polarity)
{
speyside_jack_polarity = !polarity;
gpio_direction_output(WM8996_HPSEL_GPIO, speyside_jack_polarity);
/* Re-run DAPM to make sure we're using the correct mic bias */
snd_soc_dapm_sync(&codec->dapm);
}
static int speyside_wm0010_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *dai = rtd->codec_dai;
int ret;
ret = snd_soc_dai_set_sysclk(dai, 0, MCLK_AUDIO_RATE, 0);
if (ret < 0)
return ret;
return 0;
}
static int speyside_wm8996_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *dai = rtd->codec_dai;
struct snd_soc_codec *codec = rtd->codec;
int ret;
ret = snd_soc_dai_set_sysclk(dai, WM8996_SYSCLK_MCLK2, 32768, 0);
if (ret < 0)
return ret;
ret = gpio_request(WM8996_HPSEL_GPIO, "HP_SEL");
if (ret != 0)
pr_err("Failed to request HP_SEL GPIO: %d\n", ret);
gpio_direction_output(WM8996_HPSEL_GPIO, speyside_jack_polarity);
ret = snd_soc_jack_new(codec, "Headset",
SND_JACK_LINEOUT | SND_JACK_HEADSET |
SND_JACK_BTN_0,
&speyside_headset);
if (ret)
return ret;
ret = snd_soc_jack_add_pins(&speyside_headset,
ARRAY_SIZE(speyside_headset_pins),
speyside_headset_pins);
if (ret)
return ret;
wm8996_detect(codec, &speyside_headset, speyside_set_polarity);
return 0;
}
static int speyside_late_probe(struct snd_soc_card *card)
{
snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone");
snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic");
snd_soc_dapm_ignore_suspend(&card->dapm, "Main AMIC");
snd_soc_dapm_ignore_suspend(&card->dapm, "Main DMIC");
snd_soc_dapm_ignore_suspend(&card->dapm, "Main Speaker");
snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Output");
snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Input");
return 0;
}
static const struct snd_soc_pcm_stream dsp_codec_params = {
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
};
static struct snd_soc_dai_link speyside_dai[] = {
{
.name = "CPU-DSP",
.stream_name = "CPU-DSP",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm0010-sdi1",
.platform_name = "samsung-i2s.0",
.codec_name = "spi0.0",
.init = speyside_wm0010_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
},
{
.name = "DSP-CODEC",
.stream_name = "DSP-CODEC",
.cpu_dai_name = "wm0010-sdi2",
.codec_dai_name = "wm8996-aif1",
.codec_name = "wm8996.1-001a",
.init = speyside_wm8996_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.params = &dsp_codec_params,
.ignore_suspend = 1,
},
{
.name = "Baseband",
.stream_name = "Baseband",
.cpu_dai_name = "wm8996-aif2",
.codec_dai_name = "wm1250-ev1",
.codec_name = "wm1250-ev1.1-0027",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ignore_suspend = 1,
},
};
static int speyside_wm9081_init(struct snd_soc_dapm_context *dapm)
{
/* At any time the WM9081 is active it will have this clock */
return snd_soc_codec_set_sysclk(dapm->codec, WM9081_SYSCLK_MCLK, 0,
MCLK_AUDIO_RATE, 0);
}
static struct snd_soc_aux_dev speyside_aux_dev[] = {
{
.name = "wm9081",
.codec_name = "wm9081.1-006c",
.init = speyside_wm9081_init,
},
};
static struct snd_soc_codec_conf speyside_codec_conf[] = {
{
.dev_name = "wm9081.1-006c",
.name_prefix = "Sub",
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("Main DMIC"),
SOC_DAPM_PIN_SWITCH("Main AMIC"),
SOC_DAPM_PIN_SWITCH("WM1250 Input"),
SOC_DAPM_PIN_SWITCH("WM1250 Output"),
SOC_DAPM_PIN_SWITCH("Headphone"),
};
static struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
SND_SOC_DAPM_MIC("Main AMIC", NULL),
SND_SOC_DAPM_MIC("Main DMIC", NULL),
};
static struct snd_soc_dapm_route audio_paths[] = {
{ "IN1RN", NULL, "MICB1" },
{ "IN1RP", NULL, "MICB1" },
{ "IN1RN", NULL, "MICB2" },
{ "IN1RP", NULL, "MICB2" },
{ "MICB1", NULL, "Headset Mic", speyside_get_micbias },
{ "MICB2", NULL, "Headset Mic", speyside_get_micbias },
{ "IN1LP", NULL, "MICB2" },
{ "IN1RN", NULL, "MICB1" },
{ "MICB2", NULL, "Main AMIC" },
{ "DMIC1DAT", NULL, "MICB1" },
{ "DMIC2DAT", NULL, "MICB1" },
{ "MICB1", NULL, "Main DMIC" },
{ "Headphone", NULL, "HPOUT1L" },
{ "Headphone", NULL, "HPOUT1R" },
{ "Sub IN1", NULL, "HPOUT2L" },
{ "Sub IN2", NULL, "HPOUT2R" },
{ "Main Speaker", NULL, "Sub SPKN" },
{ "Main Speaker", NULL, "Sub SPKP" },
{ "Main Speaker", NULL, "SPKDAT" },
};
static struct snd_soc_card speyside = {
.name = "Speyside",
.owner = THIS_MODULE,
.dai_link = speyside_dai,
.num_links = ARRAY_SIZE(speyside_dai),
.aux_dev = speyside_aux_dev,
.num_aux_devs = ARRAY_SIZE(speyside_aux_dev),
.codec_conf = speyside_codec_conf,
.num_configs = ARRAY_SIZE(speyside_codec_conf),
.set_bias_level = speyside_set_bias_level,
.set_bias_level_post = speyside_set_bias_level_post,
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.fully_routed = true,
.late_probe = speyside_late_probe,
};
static int speyside_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &speyside;
int ret;
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
return 0;
}
static int speyside_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver speyside_driver = {
.driver = {
.name = "speyside",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = speyside_probe,
.remove = speyside_remove,
};
module_platform_driver(speyside_driver);
MODULE_DESCRIPTION("Speyside audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:speyside");

View File

@@ -0,0 +1,258 @@
/*
* Tobermory audio support
*
* Copyright 2011 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 <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/jack.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "../codecs/wm8962.h"
static int sample_rate = 44100;
static int tobermory_set_bias_level(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
WM8962_FLL_MCLK, 32768,
sample_rate * 512);
if (ret < 0)
pr_err("Failed to start FLL: %d\n", ret);
ret = snd_soc_dai_set_sysclk(codec_dai,
WM8962_SYSCLK_FLL,
sample_rate * 512,
SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to set SYSCLK: %d\n", ret);
return ret;
}
}
break;
default:
break;
}
return 0;
}
static int tobermory_set_bias_level_post(struct snd_soc_card *card,
struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
int ret;
if (dapm->dev != codec_dai->dev)
return 0;
switch (level) {
case SND_SOC_BIAS_STANDBY:
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
32768, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("Failed to switch away from FLL: %d\n", ret);
return ret;
}
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
0, 0, 0);
if (ret < 0) {
pr_err("Failed to stop FLL: %d\n", ret);
return ret;
}
break;
default:
break;
}
dapm->bias_level = level;
return 0;
}
static int tobermory_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
sample_rate = params_rate(params);
return 0;
}
static struct snd_soc_ops tobermory_ops = {
.hw_params = tobermory_hw_params,
};
static struct snd_soc_dai_link tobermory_dai[] = {
{
.name = "CPU",
.stream_name = "CPU",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8962",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8962.1-001a",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
.ops = &tobermory_ops,
},
};
static const struct snd_kcontrol_new controls[] = {
SOC_DAPM_PIN_SWITCH("Main Speaker"),
SOC_DAPM_PIN_SWITCH("DMIC"),
};
static struct snd_soc_dapm_widget widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
SND_SOC_DAPM_MIC("DMIC", NULL),
SND_SOC_DAPM_MIC("AMIC", NULL),
SND_SOC_DAPM_SPK("Main Speaker", NULL),
};
static struct snd_soc_dapm_route audio_paths[] = {
{ "Headphone", NULL, "HPOUTL" },
{ "Headphone", NULL, "HPOUTR" },
{ "Main Speaker", NULL, "SPKOUTL" },
{ "Main Speaker", NULL, "SPKOUTR" },
{ "Headset Mic", NULL, "MICBIAS" },
{ "IN4L", NULL, "Headset Mic" },
{ "IN4R", NULL, "Headset Mic" },
{ "AMIC", NULL, "MICBIAS" },
{ "IN1L", NULL, "AMIC" },
{ "IN1R", NULL, "AMIC" },
{ "DMIC", NULL, "MICBIAS" },
{ "DMICDAT", NULL, "DMIC" },
};
static struct snd_soc_jack tobermory_headset;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin tobermory_headset_pins[] = {
{
.pin = "Headset Mic",
.mask = SND_JACK_MICROPHONE,
},
{
.pin = "Headphone",
.mask = SND_JACK_MICROPHONE,
},
};
static int tobermory_late_probe(struct snd_soc_card *card)
{
struct snd_soc_codec *codec = card->rtd[0].codec;
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
int ret;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
32768, SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
ret = snd_soc_jack_new(codec, "Headset",
SND_JACK_HEADSET | SND_JACK_BTN_0,
&tobermory_headset);
if (ret)
return ret;
ret = snd_soc_jack_add_pins(&tobermory_headset,
ARRAY_SIZE(tobermory_headset_pins),
tobermory_headset_pins);
if (ret)
return ret;
wm8962_mic_detect(codec, &tobermory_headset);
return 0;
}
static struct snd_soc_card tobermory = {
.name = "Tobermory",
.owner = THIS_MODULE,
.dai_link = tobermory_dai,
.num_links = ARRAY_SIZE(tobermory_dai),
.set_bias_level = tobermory_set_bias_level,
.set_bias_level_post = tobermory_set_bias_level_post,
.controls = controls,
.num_controls = ARRAY_SIZE(controls),
.dapm_widgets = widgets,
.num_dapm_widgets = ARRAY_SIZE(widgets),
.dapm_routes = audio_paths,
.num_dapm_routes = ARRAY_SIZE(audio_paths),
.fully_routed = true,
.late_probe = tobermory_late_probe,
};
static int tobermory_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &tobermory;
int ret;
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
return 0;
}
static int tobermory_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static struct platform_driver tobermory_driver = {
.driver = {
.name = "tobermory",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = tobermory_probe,
.remove = tobermory_remove,
};
module_platform_driver(tobermory_driver);
MODULE_DESCRIPTION("Tobermory audio support");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:tobermory");