diff --git a/arch/arm/mach-kirkwood/common.c b/arch/arm/mach-kirkwood/common.c index 6072eaa5e66a..e1f3efedbcf1 100644 --- a/arch/arm/mach-kirkwood/common.c +++ b/arch/arm/mach-kirkwood/common.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -864,6 +865,42 @@ struct sys_timer kirkwood_timer = { .init = kirkwood_timer_init, }; +/***************************************************************************** + * Audio + ****************************************************************************/ +static struct resource kirkwood_i2s_resources[] = { + [0] = { + .start = AUDIO_PHYS_BASE, + .end = AUDIO_PHYS_BASE + SZ_16K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_KIRKWOOD_I2S, + .end = IRQ_KIRKWOOD_I2S, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kirkwood_asoc_platform_data kirkwood_i2s_data = { + .dram = &kirkwood_mbus_dram_info, + .burst = 128, +}; + +static struct platform_device kirkwood_i2s_device = { + .name = "kirkwood-i2s", + .id = -1, + .num_resources = ARRAY_SIZE(kirkwood_i2s_resources), + .resource = kirkwood_i2s_resources, + .dev = { + .platform_data = &kirkwood_i2s_data, + }, +}; + +void __init kirkwood_audio_init(void) +{ + kirkwood_clk_ctrl |= CGC_AUDIO; + platform_device_register(&kirkwood_i2s_device); +} /***************************************************************************** * General @@ -923,6 +960,7 @@ void __init kirkwood_init(void) kirkwood_spi_plat_data.tclk = kirkwood_tclk; kirkwood_uart0_data[0].uartclk = kirkwood_tclk; kirkwood_uart1_data[0].uartclk = kirkwood_tclk; + kirkwood_i2s_data.tclk = kirkwood_tclk; /* * Disable propagation of mbus errors to the CPU local bus, diff --git a/arch/arm/mach-kirkwood/common.h b/arch/arm/mach-kirkwood/common.h index 05e8a8a5692e..c9ab1cb3b39f 100644 --- a/arch/arm/mach-kirkwood/common.h +++ b/arch/arm/mach-kirkwood/common.h @@ -17,6 +17,7 @@ struct mv_sata_platform_data; struct mvsdio_platform_data; struct mtd_partition; struct mtd_info; +struct kirkwood_asoc_platform_data; /* * Basic Kirkwood init functions used early by machine-setup. @@ -43,6 +44,7 @@ void kirkwood_uart0_init(void); void kirkwood_uart1_init(void); void kirkwood_nand_init(struct mtd_partition *parts, int nr_parts, int delay); void kirkwood_nand_init_rnb(struct mtd_partition *parts, int nr_parts, int (*dev_ready)(struct mtd_info *)); +void kirkwood_audio_init(void); extern int kirkwood_tclk; extern struct sys_timer kirkwood_timer; diff --git a/arch/arm/mach-kirkwood/include/mach/kirkwood.h b/arch/arm/mach-kirkwood/include/mach/kirkwood.h index a15cf0ee22bd..838151d0744b 100644 --- a/arch/arm/mach-kirkwood/include/mach/kirkwood.h +++ b/arch/arm/mach-kirkwood/include/mach/kirkwood.h @@ -96,6 +96,9 @@ #define SDIO_PHYS_BASE (KIRKWOOD_REGS_PHYS_BASE | 0x90000) +#define AUDIO_PHYS_BASE (KIRKWOOD_REGS_PHYS_BASE | 0xA0000) +#define AUDIO_VIRT_BASE (KIRKWOOD_REGS_VIRT_BASE | 0xA0000) + /* * Supported devices and revisions. */ diff --git a/arch/arm/mach-kirkwood/openrd-setup.c b/arch/arm/mach-kirkwood/openrd-setup.c index ad3f1ec33796..e36067ad3aca 100644 --- a/arch/arm/mach-kirkwood/openrd-setup.c +++ b/arch/arm/mach-kirkwood/openrd-setup.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,12 @@ static unsigned int openrd_mpp_config[] __initdata = { 0 }; +static struct i2c_board_info i2c_board_info[] __initdata = { + { + I2C_BOARD_INFO("cs42l51", 0x4a), + }, +}; + static void __init openrd_init(void) { /* @@ -80,6 +87,12 @@ static void __init openrd_init(void) kirkwood_sdio_init(&openrd_mvsdio_data); kirkwood_i2c_init(); + + if (machine_is_openrd_client()) { + i2c_register_board_info(0, i2c_board_info, + ARRAY_SIZE(i2c_board_info)); + kirkwood_audio_init(); + } } static int __init openrd_pci_init(void) diff --git a/arch/arm/plat-orion/include/plat/audio.h b/arch/arm/plat-orion/include/plat/audio.h new file mode 100644 index 000000000000..9cf1f781329b --- /dev/null +++ b/arch/arm/plat-orion/include/plat/audio.h @@ -0,0 +1,11 @@ +#ifndef __PLAT_AUDIO_H +#define __PLAT_AUDIO_H + +#include + +struct kirkwood_asoc_platform_data { + u32 tclk; + struct mbus_dram_target_info *dram; + int burst; +}; +#endif diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 66ff4c124dbd..c5d9987bc897 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -273,6 +273,8 @@ #define SND_SOC_DAPM_POST_PMD 0x8 /* after widget power down */ #define SND_SOC_DAPM_PRE_REG 0x10 /* before audio path setup */ #define SND_SOC_DAPM_POST_REG 0x20 /* after audio path setup */ +#define SND_SOC_DAPM_PRE_POST_PMD \ + (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD) /* convenience event type detection */ #define SND_SOC_DAPM_EVENT_ON(e) \ diff --git a/include/sound/soc.h b/include/sound/soc.h index 697e7ffe39d7..65e9d03ed4f5 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -170,6 +170,21 @@ .get = xhandler_get, .put = xhandler_put, \ .private_value = (unsigned long)&xenum } +#define SOC_DOUBLE_R_SX_TLV(xname, xreg_left, xreg_right, xshift,\ + xmin, xmax, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_2r_sx, \ + .get = snd_soc_get_volsw_2r_sx, \ + .put = snd_soc_put_volsw_2r_sx, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg_left, \ + .rreg = xreg_right, .shift = xshift, \ + .min = xmin, .max = xmax} } + + /* * Simplified versions of above macros, declaring a struct and calculating * ARRAY_SIZE internally @@ -329,6 +344,12 @@ int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_soc_limit_volume(struct snd_soc_codec *codec, const char *name, int max); +int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); /** * struct snd_soc_jack_pin - Describes a pin to update based on jack detection diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index b1749bc67979..5e68ac880832 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -30,7 +30,9 @@ source "sound/soc/blackfin/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/fsl/Kconfig" source "sound/soc/imx/Kconfig" +source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" +source "sound/soc/kirkwood/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" source "sound/soc/s6000/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 1470141d4167..05d5d340968e 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -8,7 +8,9 @@ obj-$(CONFIG_SND_SOC) += blackfin/ obj-$(CONFIG_SND_SOC) += davinci/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += imx/ +obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ +obj-$(CONFIG_SND_SOC) += kirkwood/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += s3c24xx/ obj-$(CONFIG_SND_SOC) += s6000/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 31ac5538fe7e..c37c84458b58 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -22,6 +22,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4642 if I2C select SND_SOC_AK4671 if I2C select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC + select SND_SOC_CS42L51 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_DA7210 if I2C @@ -120,6 +121,9 @@ config SND_SOC_AK4671 config SND_SOC_CQ0093VC tristate +config SND_SOC_CS42L51 + tristate + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 91429eab0707..4a9c205caf56 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -9,6 +9,7 @@ snd-soc-ak4535-objs := ak4535.o snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-cq93vc-objs := cq93vc.o +snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o @@ -74,6 +75,7 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o +obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index c8ca1142b2f4..1def75e4862f 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -24,6 +24,7 @@ /* codec private data */ struct ad193x_priv { + unsigned int sysclk; struct snd_soc_codec codec; u8 reg_cache[AD193X_NUM_REGS]; }; @@ -251,15 +252,32 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +static int ad193x_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec); + switch (freq) { + case 12288000: + case 18432000: + case 24576000: + case 36864000: + ad193x->sysclk = freq; + return 0; + } + return -EINVAL; +} + static int ad193x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - int word_len = 0, reg = 0; + int word_len = 0, reg = 0, master_rate = 0; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec); /* bit size */ switch (params_format(params)) { @@ -275,6 +293,25 @@ static int ad193x_hw_params(struct snd_pcm_substream *substream, break; } + switch (ad193x->sysclk) { + case 12288000: + master_rate = AD193X_PLL_INPUT_256; + break; + case 18432000: + master_rate = AD193X_PLL_INPUT_384; + break; + case 24576000: + master_rate = AD193X_PLL_INPUT_512; + break; + case 36864000: + master_rate = AD193X_PLL_INPUT_768; + break; + } + + reg = snd_soc_read(codec, AD193X_PLL_CLK_CTRL0); + reg = (reg & AD193X_PLL_INPUT_MASK) | master_rate; + snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, reg); + reg = snd_soc_read(codec, AD193X_DAC_CTRL2); reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len; snd_soc_write(codec, AD193X_DAC_CTRL2, reg); @@ -348,6 +385,7 @@ static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) /* pll input: mclki/xi */ snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */ snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04); + ad193x->sysclk = 12288000; ret = snd_soc_register_codec(codec); if (ret != 0) { @@ -383,6 +421,7 @@ static struct snd_soc_dai_ops ad193x_dai_ops = { .hw_params = ad193x_hw_params, .digital_mute = ad193x_mute, .set_tdm_slot = ad193x_set_tdm_slot, + .set_sysclk = ad193x_set_dai_sysclk, .set_fmt = ad193x_set_dai_fmt, }; diff --git a/sound/soc/codecs/ad193x.h b/sound/soc/codecs/ad193x.h index a03c880d52f9..654ba64ae04c 100644 --- a/sound/soc/codecs/ad193x.h +++ b/sound/soc/codecs/ad193x.h @@ -11,6 +11,11 @@ #define AD193X_PLL_CLK_CTRL0 0x800 #define AD193X_PLL_POWERDOWN 0x01 +#define AD193X_PLL_INPUT_MASK (~0x6) +#define AD193X_PLL_INPUT_256 (0 << 1) +#define AD193X_PLL_INPUT_384 (1 << 1) +#define AD193X_PLL_INPUT_512 (2 << 1) +#define AD193X_PLL_INPUT_768 (3 << 1) #define AD193X_PLL_CLK_CTRL1 0x801 #define AD193X_DAC_CTRL0 0x802 #define AD193X_DAC_POWERDOWN 0x01 diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c new file mode 100644 index 000000000000..dd9b8550c402 --- /dev/null +++ b/sound/soc/codecs/cs42l51.c @@ -0,0 +1,763 @@ +/* + * cs42l51.c + * + * ASoC Driver for Cirrus Logic CS42L51 codecs + * + * Copyright (c) 2010 Arnaud Patard + * + * Based on cs4270.c - Copyright (c) Freescale Semiconductor + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For now: + * - Only I2C is support. Not SPI + * - master mode *NOT* supported + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs42l51.h" + +enum master_slave_mode { + MODE_SLAVE, + MODE_SLAVE_AUTO, + MODE_MASTER, +}; + +struct cs42l51_private { + unsigned int mclk; + unsigned int audio_mode; /* The mode (I2S or left-justified) */ + enum master_slave_mode func; + struct snd_soc_codec codec; + u8 reg_cache[CS42L51_NUMREGS]; +}; + +static struct snd_soc_codec *cs42l51_codec; + +#define CS42L51_FORMATS ( \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) + +static int cs42l51_fill_cache(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache + 1; + struct i2c_client *i2c_client = codec->control_data; + s32 length; + + length = i2c_smbus_read_i2c_block_data(i2c_client, + CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache); + if (length != CS42L51_NUMREGS) { + dev_err(&i2c_client->dev, + "I2C read failure, addr=0x%x (ret=%d vs %d)\n", + i2c_client->addr, length, CS42L51_NUMREGS); + return -EIO; + } + + return 0; +} + +static int cs42l51_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct snd_soc_codec *codec; + struct cs42l51_private *cs42l51; + int ret = 0; + int reg; + + if (cs42l51_codec) + return -EBUSY; + + /* Verify that we have a CS42L51 */ + ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to read I2C\n"); + goto error; + } + + if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) && + (ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) { + dev_err(&i2c_client->dev, "Invalid chip id\n"); + ret = -ENODEV; + goto error; + } + + dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n", + ret & 7); + + cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL); + if (!cs42l51) { + dev_err(&i2c_client->dev, "could not allocate codec\n"); + return -ENOMEM; + } + codec = &cs42l51->codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->dev = &i2c_client->dev; + codec->name = "CS42L51"; + codec->owner = THIS_MODULE; + codec->dai = &cs42l51_dai; + codec->num_dai = 1; + snd_soc_codec_set_drvdata(codec, cs42l51); + + codec->control_data = i2c_client; + codec->reg_cache = cs42l51->reg_cache; + codec->reg_cache_size = CS42L51_NUMREGS; + i2c_set_clientdata(i2c_client, codec); + + ret = cs42l51_fill_cache(codec); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to fill register cache\n"); + goto error_alloc; + } + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to set cache I/O: %d\n", ret); + goto error_alloc; + } + + /* + * DAC configuration + * - Use signal processor + * - auto mute + * - vol changes immediate + * - no de-emphasize + */ + reg = CS42L51_DAC_CTL_DATA_SEL(1) + | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0); + ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg); + if (ret < 0) + goto error_alloc; + + cs42l51_dai.dev = codec->dev; + cs42l51_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto error_alloc; + } + + ret = snd_soc_register_dai(&cs42l51_dai); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to register DAIe\n"); + goto error_reg; + } + + return 0; + +error_reg: + snd_soc_unregister_codec(codec); +error_alloc: + kfree(cs42l51); +error: + return ret; +} + +static int cs42l51_i2c_remove(struct i2c_client *client) +{ + struct cs42l51_private *cs42l51 = i2c_get_clientdata(client); + snd_soc_unregister_dai(&cs42l51_dai); + snd_soc_unregister_codec(cs42l51_codec); + cs42l51_codec = NULL; + kfree(cs42l51); + return 0; +} + + +static const struct i2c_device_id cs42l51_id[] = { + {"cs42l51", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs42l51_id); + +static struct i2c_driver cs42l51_i2c_driver = { + .driver = { + .name = "CS42L51 I2C", + .owner = THIS_MODULE, + }, + .id_table = cs42l51_id, + .probe = cs42l51_i2c_probe, + .remove = cs42l51_i2c_remove, +}; + +static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3; + + switch (value) { + default: + case 0: + ucontrol->value.integer.value[0] = 0; + break; + /* same value : (L+R)/2 and (R+L)/2 */ + case 1: + case 2: + ucontrol->value.integer.value[0] = 1; + break; + case 3: + ucontrol->value.integer.value[0] = 2; + break; + } + + return 0; +} + +#define CHAN_MIX_NORMAL 0x00 +#define CHAN_MIX_BOTH 0x55 +#define CHAN_MIX_SWAP 0xFF + +static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned char val; + + switch (ucontrol->value.integer.value[0]) { + default: + case 0: + val = CHAN_MIX_NORMAL; + break; + case 1: + val = CHAN_MIX_BOTH; + break; + case 2: + val = CHAN_MIX_SWAP; + break; + } + + snd_soc_write(codec, CS42L51_PCM_MIXER, val); + + return 1; +} + +static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0); +static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); +/* This is a lie. after -102 db, it stays at -102 */ +/* maybe a range would be better */ +static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0); + +static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); +static const char *chan_mix[] = { + "L R", + "L+R", + "R L", +}; + +static const struct soc_enum cs42l51_chan_mix = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix); + +static const struct snd_kcontrol_new cs42l51_snd_controls[] = { + SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", + CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, + 7, 0xffffff99, 0x18, adc_pcm_tlv), + SOC_DOUBLE_R("PCM Playback Switch", + CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1), + SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", + CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL, + 8, 0xffffff19, 0x18, aout_tlv), + SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", + CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, + 7, 0xffffff99, 0x18, adc_pcm_tlv), + SOC_DOUBLE_R("ADC Mixer Switch", + CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1), + SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0), + SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0), + SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0), + SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0), + SOC_DOUBLE_TLV("Mic Boost Volume", + CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), + SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv), + SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv), + SOC_ENUM_EXT("PCM channel mixer", + cs42l51_chan_mix, + cs42l51_get_chan_mix, cs42l51_set_chan_mix), +}; + +/* + * to power down, one must: + * 1.) Enable the PDN bit + * 2.) enable power-down for the select channels + * 3.) disable the PDN bit. + */ +static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + unsigned long value; + + value = snd_soc_read(w->codec, CS42L51_POWER_CTL1); + value &= ~CS42L51_POWER_CTL1_PDN; + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + value |= CS42L51_POWER_CTL1_PDN; + break; + default: + case SND_SOC_DAPM_POST_PMD: + break; + } + snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1, + CS42L51_POWER_CTL1_PDN, value); + + return 0; +} + +static const char *cs42l51_dac_names[] = {"Direct PCM", + "DSP PCM", "ADC"}; +static const struct soc_enum cs42l51_dac_mux_enum = + SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names); +static const struct snd_kcontrol_new cs42l51_dac_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum); + +static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left", + "MIC Left", "MIC+preamp Left"}; +static const struct soc_enum cs42l51_adcl_mux_enum = + SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names); +static const struct snd_kcontrol_new cs42l51_adcl_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum); + +static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right", + "MIC Right", "MIC+preamp Right"}; +static const struct soc_enum cs42l51_adcr_mux_enum = + SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names); +static const struct snd_kcontrol_new cs42l51_adcr_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum); + +static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), + SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture", + CS42L51_POWER_CTL1, 1, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture", + CS42L51_POWER_CTL1, 2, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback", + CS42L51_POWER_CTL1, 5, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback", + CS42L51_POWER_CTL1, 6, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + + /* analog/mic */ + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("MICR"), + + SND_SOC_DAPM_MIXER("Mic Preamp Left", + CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0), + SND_SOC_DAPM_MIXER("Mic Preamp Right", + CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0), + + /* HP */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + + /* mux */ + SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, + &cs42l51_dac_mux_controls), + SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0, + &cs42l51_adcl_mux_controls), + SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0, + &cs42l51_adcr_mux_controls), +}; + +static const struct snd_soc_dapm_route cs42l51_routes[] = { + {"HPL", NULL, "Left DAC"}, + {"HPR", NULL, "Right DAC"}, + + {"Left ADC", NULL, "Left PGA"}, + {"Right ADC", NULL, "Right PGA"}, + + {"Mic Preamp Left", NULL, "MICL"}, + {"Mic Preamp Right", NULL, "MICR"}, + + {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" }, + {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" }, + {"PGA-ADC Mux Left", "MIC Left", "MICL" }, + {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" }, + {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" }, + {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" }, + {"PGA-ADC Mux Right", "MIC Right", "MICR" }, + {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" }, + + {"Left PGA", NULL, "PGA-ADC Mux Left"}, + {"Right PGA", NULL, "PGA-ADC Mux Right"}, +}; + +static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + dev_err(codec->dev, "invalid DAI format\n"); + ret = -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + cs42l51->func = MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + cs42l51->func = MODE_SLAVE_AUTO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +struct cs42l51_ratios { + unsigned int ratio; + unsigned char speed_mode; + unsigned char mclk; +}; + +static struct cs42l51_ratios slave_ratios[] = { + { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 }, + { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, + { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 }, + { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 }, + { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, + { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 }, + { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 }, + { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, + { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 }, + { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, + { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 }, +}; + +static struct cs42l51_ratios slave_auto_ratios[] = { + { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, + { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 }, + { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, + { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 }, + { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, + { 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 }, + { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, + { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 }, +}; + +static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); + struct cs42l51_ratios *ratios = NULL; + int nr_ratios = 0; + unsigned int rates = 0; + unsigned int rate_min = -1; + unsigned int rate_max = 0; + int i; + + cs42l51->mclk = freq; + + switch (cs42l51->func) { + case MODE_MASTER: + return -EINVAL; + case MODE_SLAVE: + ratios = slave_ratios; + nr_ratios = ARRAY_SIZE(slave_ratios); + break; + case MODE_SLAVE_AUTO: + ratios = slave_auto_ratios; + nr_ratios = ARRAY_SIZE(slave_auto_ratios); + break; + } + + for (i = 0; i < nr_ratios; i++) { + unsigned int rate = freq / ratios[i].ratio; + rates |= snd_pcm_rate_to_rate_bit(rate); + if (rate < rate_min) + rate_min = rate; + if (rate > rate_max) + rate_max = rate; + } + rates &= ~SNDRV_PCM_RATE_KNOT; + + if (!rates) { + dev_err(codec->dev, "could not find a valid sample rate\n"); + return -EINVAL; + } + + codec_dai->playback.rates = rates; + codec_dai->playback.rate_min = rate_min; + codec_dai->playback.rate_max = rate_max; + + codec_dai->capture.rates = rates; + codec_dai->capture.rate_min = rate_min; + codec_dai->capture.rate_max = rate_max; + + return 0; +} + +static int cs42l51_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); + int ret; + unsigned int i; + unsigned int rate; + unsigned int ratio; + struct cs42l51_ratios *ratios = NULL; + int nr_ratios = 0; + int intf_ctl, power_ctl, fmt; + + switch (cs42l51->func) { + case MODE_MASTER: + return -EINVAL; + case MODE_SLAVE: + ratios = slave_ratios; + nr_ratios = ARRAY_SIZE(slave_ratios); + break; + case MODE_SLAVE_AUTO: + ratios = slave_auto_ratios; + nr_ratios = ARRAY_SIZE(slave_auto_ratios); + break; + } + + /* Figure out which MCLK/LRCK ratio to use */ + rate = params_rate(params); /* Sampling rate, in Hz */ + ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */ + for (i = 0; i < nr_ratios; i++) { + if (ratios[i].ratio == ratio) + break; + } + + if (i == nr_ratios) { + /* We did not find a matching ratio */ + dev_err(codec->dev, "could not find matching ratio\n"); + return -EINVAL; + } + + intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL); + power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL); + + intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S + | CS42L51_INTF_CTL_DAC_FORMAT(7)); + power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3) + | CS42L51_MIC_POWER_CTL_MCLK_DIV2); + + switch (cs42l51->func) { + case MODE_MASTER: + intf_ctl |= CS42L51_INTF_CTL_MASTER; + power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); + break; + case MODE_SLAVE: + power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); + break; + case MODE_SLAVE_AUTO: + power_ctl |= CS42L51_MIC_POWER_CTL_AUTO; + break; + } + + switch (cs42l51->audio_mode) { + case SND_SOC_DAIFMT_I2S: + intf_ctl |= CS42L51_INTF_CTL_ADC_I2S; + intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S); + break; + case SND_SOC_DAIFMT_LEFT_J: + intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24); + break; + case SND_SOC_DAIFMT_RIGHT_J: + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + fmt = CS42L51_DAC_DIF_RJ16; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + case SNDRV_PCM_FORMAT_S18_3BE: + fmt = CS42L51_DAC_DIF_RJ18; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S20_3BE: + fmt = CS42L51_DAC_DIF_RJ20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_BE: + fmt = CS42L51_DAC_DIF_RJ24; + break; + default: + dev_err(codec->dev, "unknown format\n"); + return -EINVAL; + } + intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt); + break; + default: + dev_err(codec->dev, "unknown format\n"); + return -EINVAL; + } + + if (ratios[i].mclk) + power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2; + + ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl); + if (ret < 0) + return ret; + + ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl); + if (ret < 0) + return ret; + + return 0; +} + +static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int reg; + int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE; + + reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL); + + if (mute) + reg |= mask; + else + reg &= ~mask; + + return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg); +} + +static struct snd_soc_dai_ops cs42l51_dai_ops = { + .hw_params = cs42l51_hw_params, + .set_sysclk = cs42l51_set_dai_sysclk, + .set_fmt = cs42l51_set_dai_fmt, + .digital_mute = cs42l51_dai_mute, +}; + +struct snd_soc_dai cs42l51_dai = { + .name = "CS42L51 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = CS42L51_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = CS42L51_FORMATS, + }, + .ops = &cs42l51_dai_ops, +}; +EXPORT_SYMBOL_GPL(cs42l51_dai); + + +static int cs42l51_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (!cs42l51_codec) { + dev_err(&pdev->dev, "CS42L51 codec not yet registered\n"); + return -EINVAL; + } + + socdev->card->codec = cs42l51_codec; + codec = socdev->card->codec; + + /* Register PCMs */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create PCMs\n"); + return ret; + } + + snd_soc_add_controls(codec, cs42l51_snd_controls, + ARRAY_SIZE(cs42l51_snd_controls)); + snd_soc_dapm_new_controls(codec, cs42l51_dapm_widgets, + ARRAY_SIZE(cs42l51_dapm_widgets)); + snd_soc_dapm_add_routes(codec, cs42l51_routes, + ARRAY_SIZE(cs42l51_routes)); + + return 0; +} + + +static int cs42l51_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_device_cs42l51 = { + .probe = cs42l51_probe, + .remove = cs42l51_remove +}; +EXPORT_SYMBOL_GPL(soc_codec_device_cs42l51); + +static int __init cs42l51_init(void) +{ + int ret; + + ret = i2c_add_driver(&cs42l51_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "%s: can't add i2c driver\n", __func__); + return ret; + } + return 0; +} +module_init(cs42l51_init); + +static void __exit cs42l51_exit(void) +{ + i2c_del_driver(&cs42l51_i2c_driver); +} +module_exit(cs42l51_exit); + +MODULE_AUTHOR("Arnaud Patard "); +MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l51.h b/sound/soc/codecs/cs42l51.h new file mode 100644 index 000000000000..8f0bd9786ad2 --- /dev/null +++ b/sound/soc/codecs/cs42l51.h @@ -0,0 +1,163 @@ +/* + * cs42l51.h + * + * ASoC Driver for Cirrus Logic CS42L51 codecs + * + * Copyright (c) 2010 Arnaud Patard + * + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _CS42L51_H +#define _CS42L51_H + +#define CS42L51_CHIP_ID 0x1B +#define CS42L51_CHIP_REV_A 0x00 +#define CS42L51_CHIP_REV_B 0x01 + +#define CS42L51_CHIP_REV_ID 0x01 +#define CS42L51_MK_CHIP_REV(a, b) ((a)<<3|(b)) + +#define CS42L51_POWER_CTL1 0x02 +#define CS42L51_POWER_CTL1_PDN_DACB (1<<6) +#define CS42L51_POWER_CTL1_PDN_DACA (1<<5) +#define CS42L51_POWER_CTL1_PDN_PGAB (1<<4) +#define CS42L51_POWER_CTL1_PDN_PGAA (1<<3) +#define CS42L51_POWER_CTL1_PDN_ADCB (1<<2) +#define CS42L51_POWER_CTL1_PDN_ADCA (1<<1) +#define CS42L51_POWER_CTL1_PDN (1<<0) + +#define CS42L51_MIC_POWER_CTL 0x03 +#define CS42L51_MIC_POWER_CTL_AUTO (1<<7) +#define CS42L51_MIC_POWER_CTL_SPEED(x) (((x)&3)<<5) +#define CS42L51_QSM_MODE 3 +#define CS42L51_HSM_MODE 2 +#define CS42L51_SSM_MODE 1 +#define CS42L51_DSM_MODE 0 +#define CS42L51_MIC_POWER_CTL_3ST_SP (1<<4) +#define CS42L51_MIC_POWER_CTL_PDN_MICB (1<<3) +#define CS42L51_MIC_POWER_CTL_PDN_MICA (1<<2) +#define CS42L51_MIC_POWER_CTL_PDN_BIAS (1<<1) +#define CS42L51_MIC_POWER_CTL_MCLK_DIV2 (1<<0) + +#define CS42L51_INTF_CTL 0x04 +#define CS42L51_INTF_CTL_LOOPBACK (1<<7) +#define CS42L51_INTF_CTL_MASTER (1<<6) +#define CS42L51_INTF_CTL_DAC_FORMAT(x) (((x)&7)<<3) +#define CS42L51_DAC_DIF_LJ24 0x00 +#define CS42L51_DAC_DIF_I2S 0x01 +#define CS42L51_DAC_DIF_RJ24 0x02 +#define CS42L51_DAC_DIF_RJ20 0x03 +#define CS42L51_DAC_DIF_RJ18 0x04 +#define CS42L51_DAC_DIF_RJ16 0x05 +#define CS42L51_INTF_CTL_ADC_I2S (1<<2) +#define CS42L51_INTF_CTL_DIGMIX (1<<1) +#define CS42L51_INTF_CTL_MICMIX (1<<0) + +#define CS42L51_MIC_CTL 0x05 +#define CS42L51_MIC_CTL_ADC_SNGVOL (1<<7) +#define CS42L51_MIC_CTL_ADCD_DBOOST (1<<6) +#define CS42L51_MIC_CTL_ADCA_DBOOST (1<<5) +#define CS42L51_MIC_CTL_MICBIAS_SEL (1<<4) +#define CS42L51_MIC_CTL_MICBIAS_LVL(x) (((x)&3)<<2) +#define CS42L51_MIC_CTL_MICB_BOOST (1<<1) +#define CS42L51_MIC_CTL_MICA_BOOST (1<<0) + +#define CS42L51_ADC_CTL 0x06 +#define CS42L51_ADC_CTL_ADCB_HPFEN (1<<7) +#define CS42L51_ADC_CTL_ADCB_HPFRZ (1<<6) +#define CS42L51_ADC_CTL_ADCA_HPFEN (1<<5) +#define CS42L51_ADC_CTL_ADCA_HPFRZ (1<<4) +#define CS42L51_ADC_CTL_SOFTB (1<<3) +#define CS42L51_ADC_CTL_ZCROSSB (1<<2) +#define CS42L51_ADC_CTL_SOFTA (1<<1) +#define CS42L51_ADC_CTL_ZCROSSA (1<<0) + +#define CS42L51_ADC_INPUT 0x07 +#define CS42L51_ADC_INPUT_AINB_MUX(x) (((x)&3)<<6) +#define CS42L51_ADC_INPUT_AINA_MUX(x) (((x)&3)<<4) +#define CS42L51_ADC_INPUT_INV_ADCB (1<<3) +#define CS42L51_ADC_INPUT_INV_ADCA (1<<2) +#define CS42L51_ADC_INPUT_ADCB_MUTE (1<<1) +#define CS42L51_ADC_INPUT_ADCA_MUTE (1<<0) + +#define CS42L51_DAC_OUT_CTL 0x08 +#define CS42L51_DAC_OUT_CTL_HP_GAIN(x) (((x)&7)<<5) +#define CS42L51_DAC_OUT_CTL_DAC_SNGVOL (1<<4) +#define CS42L51_DAC_OUT_CTL_INV_PCMB (1<<3) +#define CS42L51_DAC_OUT_CTL_INV_PCMA (1<<2) +#define CS42L51_DAC_OUT_CTL_DACB_MUTE (1<<1) +#define CS42L51_DAC_OUT_CTL_DACA_MUTE (1<<0) + +#define CS42L51_DAC_CTL 0x09 +#define CS42L51_DAC_CTL_DATA_SEL(x) (((x)&3)<<6) +#define CS42L51_DAC_CTL_FREEZE (1<<5) +#define CS42L51_DAC_CTL_DEEMPH (1<<3) +#define CS42L51_DAC_CTL_AMUTE (1<<2) +#define CS42L51_DAC_CTL_DACSZ(x) (((x)&3)<<0) + +#define CS42L51_ALC_PGA_CTL 0x0A +#define CS42L51_ALC_PGB_CTL 0x0B +#define CS42L51_ALC_PGX_ALCX_SRDIS (1<<7) +#define CS42L51_ALC_PGX_ALCX_ZCDIS (1<<6) +#define CS42L51_ALC_PGX_PGX_VOL(x) (((x)&0x1f)<<0) + +#define CS42L51_ADCA_ATT 0x0C +#define CS42L51_ADCB_ATT 0x0D + +#define CS42L51_ADCA_VOL 0x0E +#define CS42L51_ADCB_VOL 0x0F +#define CS42L51_PCMA_VOL 0x10 +#define CS42L51_PCMB_VOL 0x11 +#define CS42L51_MIX_MUTE_ADCMIX (1<<7) +#define CS42L51_MIX_VOLUME(x) (((x)&0x7f)<<0) + +#define CS42L51_BEEP_FREQ 0x12 +#define CS42L51_BEEP_VOL 0x13 +#define CS42L51_BEEP_CONF 0x14 + +#define CS42L51_TONE_CTL 0x15 +#define CS42L51_TONE_CTL_TREB(x) (((x)&0xf)<<4) +#define CS42L51_TONE_CTL_BASS(x) (((x)&0xf)<<0) + +#define CS42L51_AOUTA_VOL 0x16 +#define CS42L51_AOUTB_VOL 0x17 +#define CS42L51_PCM_MIXER 0x18 +#define CS42L51_LIMIT_THRES_DIS 0x19 +#define CS42L51_LIMIT_REL 0x1A +#define CS42L51_LIMIT_ATT 0x1B +#define CS42L51_ALC_EN 0x1C +#define CS42L51_ALC_REL 0x1D +#define CS42L51_ALC_THRES 0x1E +#define CS42L51_NOISE_CONF 0x1F + +#define CS42L51_STATUS 0x20 +#define CS42L51_STATUS_SP_CLKERR (1<<6) +#define CS42L51_STATUS_SPEA_OVFL (1<<5) +#define CS42L51_STATUS_SPEB_OVFL (1<<4) +#define CS42L51_STATUS_PCMA_OVFL (1<<3) +#define CS42L51_STATUS_PCMB_OVFL (1<<2) +#define CS42L51_STATUS_ADCA_OVFL (1<<1) +#define CS42L51_STATUS_ADCB_OVFL (1<<0) + +#define CS42L51_CHARGE_FREQ 0x21 + +#define CS42L51_FIRSTREG 0x01 +/* + * Hack: with register 0x21, it makes 33 registers. Looks like someone in the + * i2c layer doesn't like i2c smbus block read of 33 regs. Workaround by using + * 32 regs + */ +#define CS42L51_LASTREG 0x20 +#define CS42L51_NUMREGS (CS42L51_LASTREG - CS42L51_FIRSTREG + 1) + +extern struct snd_soc_dai cs42l51_dai; +extern struct snd_soc_codec_device soc_codec_device_cs42l51; +#endif diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c index a63191141052..9119836051a4 100644 --- a/sound/soc/codecs/spdif_transciever.c +++ b/sound/soc/codecs/spdif_transciever.c @@ -16,8 +16,10 @@ #include #include +#include #include #include +#include #include "spdif_transciever.h" @@ -26,6 +28,48 @@ MODULE_LICENSE("GPL"); #define STUB_RATES SNDRV_PCM_RATE_8000_96000 #define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE +static struct snd_soc_codec *spdif_dit_codec; + +static int spdif_dit_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + if (spdif_dit_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = spdif_dit_codec; + codec = spdif_dit_codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto err_create_pcms; + } + + return 0; + +err_create_pcms: + return ret; +} + +static int spdif_dit_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_spdif_dit = { + .probe = spdif_dit_codec_probe, + .remove = spdif_dit_codec_remove, +}; EXPORT_SYMBOL_GPL(soc_codec_dev_spdif_dit); + struct snd_soc_dai dit_stub_dai = { .name = "DIT", .playback = { @@ -40,13 +84,61 @@ EXPORT_SYMBOL_GPL(dit_stub_dai); static int spdif_dit_probe(struct platform_device *pdev) { + struct snd_soc_codec *codec; + int ret; + + if (spdif_dit_codec) { + dev_err(&pdev->dev, "Another Codec is registered\n"); + ret = -EINVAL; + goto err_reg_codec; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + codec->dev = &pdev->dev; + + mutex_init(&codec->mutex); + + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->name = "spdif-dit"; + codec->owner = THIS_MODULE; + codec->dai = &dit_stub_dai; + codec->num_dai = 1; + + spdif_dit_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err_reg_codec; + } + dit_stub_dai.dev = &pdev->dev; - return snd_soc_register_dai(&dit_stub_dai); + ret = snd_soc_register_dai(&dit_stub_dai); + if (ret < 0) { + dev_err(codec->dev, "Failed to register dai: %d\n", ret); + goto err_reg_dai; + } + + return 0; + +err_reg_dai: + snd_soc_unregister_codec(codec); +err_reg_codec: + kfree(spdif_dit_codec); + return ret; } static int spdif_dit_remove(struct platform_device *pdev) { snd_soc_unregister_dai(&dit_stub_dai); + snd_soc_unregister_codec(spdif_dit_codec); + kfree(spdif_dit_codec); + spdif_dit_codec = NULL; return 0; } diff --git a/sound/soc/codecs/spdif_transciever.h b/sound/soc/codecs/spdif_transciever.h index 296f2eb6c4ef..1e102124f546 100644 --- a/sound/soc/codecs/spdif_transciever.h +++ b/sound/soc/codecs/spdif_transciever.h @@ -12,6 +12,7 @@ #ifndef CODEC_STUBS_H #define CODEC_STUBS_H +extern struct snd_soc_codec_device soc_codec_dev_spdif_dit; extern struct snd_soc_dai dit_stub_dai; #endif /* CODEC_STUBS_H */ diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index c018772cc430..dd8d909788c1 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -30,8 +30,6 @@ #include "wm8990.h" -#define WM8990_VERSION "0.2" - /* codec private data */ struct wm8990_priv { unsigned int sysclk; @@ -1511,8 +1509,6 @@ static int wm8990_probe(struct platform_device *pdev) struct wm8990_priv *wm8990; int ret; - pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION); - setup = socdev->codec_data; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig new file mode 100644 index 000000000000..16ec2a2dba4d --- /dev/null +++ b/sound/soc/kirkwood/Kconfig @@ -0,0 +1,20 @@ +config SND_KIRKWOOD_SOC + tristate "SoC Audio for the Marvell Kirkwood chip" + depends on ARCH_KIRKWOOD + help + Say Y or M if you want to add support for codecs attached to + the Kirkwood I2S interface. You will also need to select the + audio interfaces to support below. + +config SND_KIRKWOOD_SOC_I2S + tristate + +config SND_KIRKWOOD_SOC_OPENRD + tristate "SoC Audio support for Kirkwood Openrd Client" + depends on SND_KIRKWOOD_SOC && MACH_OPENRD_CLIENT + select SND_KIRKWOOD_SOC_I2S + select SND_SOC_CS42L51 + help + Say Y if you want to add support for SoC audio on + Openrd Client. + diff --git a/sound/soc/kirkwood/Makefile b/sound/soc/kirkwood/Makefile new file mode 100644 index 000000000000..33a16dcab5b5 --- /dev/null +++ b/sound/soc/kirkwood/Makefile @@ -0,0 +1,9 @@ +snd-soc-kirkwood-objs := kirkwood-dma.o +snd-soc-kirkwood-i2s-objs := kirkwood-i2s.o + +obj-$(CONFIG_SND_KIRKWOOD_SOC) += snd-soc-kirkwood.o +obj-$(CONFIG_SND_KIRKWOOD_SOC_I2S) += snd-soc-kirkwood-i2s.o + +snd-soc-openrd-objs := kirkwood-openrd.o + +obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o diff --git a/sound/soc/kirkwood/kirkwood-dma.c b/sound/soc/kirkwood/kirkwood-dma.c new file mode 100644 index 000000000000..a30205be3e2b --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-dma.c @@ -0,0 +1,383 @@ +/* + * kirkwood-dma.c + * + * (c) 2010 Arnaud Patard + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "kirkwood-dma.h" +#include "kirkwood.h" + +#define KIRKWOOD_RATES \ + (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +#define KIRKWOOD_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct kirkwood_dma_priv { + struct snd_pcm_substream *play_stream; + struct snd_pcm_substream *rec_stream; + struct kirkwood_dma_data *data; +}; + +static struct snd_pcm_hardware kirkwood_dma_snd_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_PAUSE), + .formats = KIRKWOOD_FORMATS, + .rates = KIRKWOOD_RATES, + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS, + .period_bytes_min = KIRKWOOD_SND_MIN_PERIOD_BYTES, + .period_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES, + .periods_min = KIRKWOOD_SND_MIN_PERIODS, + .periods_max = KIRKWOOD_SND_MAX_PERIODS, + .fifo_size = 0, +}; + +static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL; + +static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id) +{ + struct kirkwood_dma_priv *prdata = dev_id; + struct kirkwood_dma_data *priv = prdata->data; + unsigned long mask, status, cause; + + mask = readl(priv->io + KIRKWOOD_INT_MASK); + status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask; + + cause = readl(priv->io + KIRKWOOD_ERR_CAUSE); + if (unlikely(cause)) { + printk(KERN_WARNING "%s: got err interrupt 0x%lx\n", + __func__, cause); + writel(cause, priv->io + KIRKWOOD_ERR_CAUSE); + return IRQ_HANDLED; + } + + /* we've enabled only bytes interrupts ... */ + if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \ + KIRKWOOD_INT_CAUSE_REC_BYTES)) { + printk(KERN_WARNING "%s: unexpected interrupt %lx\n", + __func__, status); + return IRQ_NONE; + } + + /* ack int */ + writel(status, priv->io + KIRKWOOD_INT_CAUSE); + + if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES) + snd_pcm_period_elapsed(prdata->play_stream); + + if (status & KIRKWOOD_INT_CAUSE_REC_BYTES) + snd_pcm_period_elapsed(prdata->rec_stream); + + return IRQ_HANDLED; +} + +static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win, + unsigned long dma, + struct mbus_dram_target_info *dram) +{ + int i; + + /* First disable and clear windows */ + writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); + writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); + + /* try to find matching cs for current dma address */ + for (i = 0; i < dram->num_cs; i++) { + struct mbus_dram_window *cs = dram->cs + i; + if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) { + writel(cs->base & 0xffff0000, + base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); + writel(((cs->size - 1) & 0xffff0000) | + (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1, + base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); + } + } +} + +static int kirkwood_dma_open(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; + struct kirkwood_dma_data *priv; + struct kirkwood_dma_priv *prdata = cpu_dai->private_data; + unsigned long addr; + + priv = snd_soc_dai_get_dma_data(cpu_dai, substream); + snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw); + + /* Ensure that all constraints linked to dma burst are fullfilled */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + priv->burst * 2, + KIRKWOOD_AUDIO_BUF_MAX-1); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + priv->burst); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + priv->burst); + if (err < 0) + return err; + + if (soc_runtime->dai->cpu_dai->private_data == NULL) { + prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL); + if (prdata == NULL) + return -ENOMEM; + + prdata->data = priv; + + err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED, + "kirkwood-i2s", prdata); + if (err) { + kfree(prdata); + return -EBUSY; + } + + soc_runtime->dai->cpu_dai->private_data = prdata; + + /* + * Enable Error interrupts. We're only ack'ing them but + * it's usefull for diagnostics + */ + writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK); + } + + addr = virt_to_phys(substream->dma_buffer.area); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prdata->play_stream = substream; + kirkwood_dma_conf_mbus_windows(priv->io, + KIRKWOOD_PLAYBACK_WIN, addr, priv->dram); + } else { + prdata->rec_stream = substream; + kirkwood_dma_conf_mbus_windows(priv->io, + KIRKWOOD_RECORD_WIN, addr, priv->dram); + } + + return 0; +} + +static int kirkwood_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; + struct kirkwood_dma_priv *prdata = cpu_dai->private_data; + struct kirkwood_dma_data *priv; + + priv = snd_soc_dai_get_dma_data(cpu_dai, substream); + + if (!prdata || !priv) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prdata->play_stream = NULL; + else + prdata->rec_stream = NULL; + + if (!prdata->play_stream && !prdata->rec_stream) { + writel(0, priv->io + KIRKWOOD_ERR_MASK); + free_irq(priv->irq, prdata); + kfree(prdata); + soc_runtime->dai->cpu_dai->private_data = NULL; + } + + return 0; +} + +static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + return 0; +} + +static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int kirkwood_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; + struct kirkwood_dma_data *priv; + unsigned long size, count; + + priv = snd_soc_dai_get_dma_data(cpu_dai, substream); + + /* compute buffer size in term of "words" as requested in specs */ + size = frames_to_bytes(runtime, runtime->buffer_size); + size = (size>>2)-1; + count = snd_pcm_lib_period_bytes(substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT); + writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR); + writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE); + } else { + writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT); + writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR); + writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE); + } + + + return 0; +} + +static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream + *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; + struct kirkwood_dma_data *priv; + snd_pcm_uframes_t count; + + priv = snd_soc_dai_get_dma_data(cpu_dai, substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + count = bytes_to_frames(substream->runtime, + readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT)); + else + count = bytes_to_frames(substream->runtime, + readl(priv->io + KIRKWOOD_REC_BYTE_COUNT)); + + return count; +} + +struct snd_pcm_ops kirkwood_dma_ops = { + .open = kirkwood_dma_open, + .close = kirkwood_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = kirkwood_dma_hw_params, + .hw_free = kirkwood_dma_hw_free, + .prepare = kirkwood_dma_prepare, + .pointer = kirkwood_dma_pointer, +}; + +static int kirkwood_dma_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 = kirkwood_dma_snd_hw.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + buf->private_data = NULL; + + return 0; +} + +static int kirkwood_dma_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + int ret; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &kirkwood_dma_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = kirkwood_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (dai->capture.channels_min) { + ret = kirkwood_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + + return 0; +} + +static void kirkwood_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +struct snd_soc_platform kirkwood_soc_platform = { + .name = "kirkwood-dma", + .pcm_ops = &kirkwood_dma_ops, + .pcm_new = kirkwood_dma_new, + .pcm_free = kirkwood_dma_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(kirkwood_soc_platform); + +static int __init kirkwood_soc_platform_init(void) +{ + return snd_soc_register_platform(&kirkwood_soc_platform); +} +module_init(kirkwood_soc_platform_init); + +static void __exit kirkwood_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&kirkwood_soc_platform); +} +module_exit(kirkwood_soc_platform_exit); + +MODULE_AUTHOR("Arnaud Patard "); +MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/kirkwood/kirkwood-dma.h b/sound/soc/kirkwood/kirkwood-dma.h new file mode 100644 index 000000000000..ba4454cd34f1 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-dma.h @@ -0,0 +1,17 @@ +/* + * kirkwood-dma.h + * + * (c) 2010 Arnaud Patard + * + * 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 _KIRKWOOD_DMA_H +#define _KIRKWOOD_DMA_H + +extern struct snd_soc_platform kirkwood_soc_platform; + +#endif diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c new file mode 100644 index 000000000000..0adc59778d5a --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -0,0 +1,485 @@ +/* + * kirkwood-i2s.c + * + * (c) 2010 Arnaud Patard + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "kirkwood-i2s.h" +#include "kirkwood.h" + +#define DRV_NAME "kirkwood-i2s" + +#define KIRKWOOD_I2S_RATES \ + (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +#define KIRKWOOD_I2S_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + + +struct snd_soc_dai kirkwood_i2s_dai; +static struct kirkwood_dma_data *priv; + +static int kirkwood_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + unsigned long mask; + unsigned long value; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + mask = KIRKWOOD_I2S_CTL_RJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + mask = KIRKWOOD_I2S_CTL_LJ; + break; + case SND_SOC_DAIFMT_I2S: + mask = KIRKWOOD_I2S_CTL_I2S; + break; + default: + return -EINVAL; + } + + /* + * Set same format for playback and record + * This avoids some troubles. + */ + value = readl(priv->io+KIRKWOOD_I2S_PLAYCTL); + value &= ~KIRKWOOD_I2S_CTL_JUST_MASK; + value |= mask; + writel(value, priv->io+KIRKWOOD_I2S_PLAYCTL); + + value = readl(priv->io+KIRKWOOD_I2S_RECCTL); + value &= ~KIRKWOOD_I2S_CTL_JUST_MASK; + value |= mask; + writel(value, priv->io+KIRKWOOD_I2S_RECCTL); + + return 0; +} + +static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate) +{ + unsigned long value; + + value = KIRKWOOD_DCO_CTL_OFFSET_0; + switch (rate) { + default: + case 44100: + value |= KIRKWOOD_DCO_CTL_FREQ_11; + break; + case 48000: + value |= KIRKWOOD_DCO_CTL_FREQ_12; + break; + case 96000: + value |= KIRKWOOD_DCO_CTL_FREQ_24; + break; + } + writel(value, io + KIRKWOOD_DCO_CTL); + + /* wait for dco locked */ + do { + cpu_relax(); + value = readl(io + KIRKWOOD_DCO_SPCR_STATUS); + value &= KIRKWOOD_DCO_SPCR_STATUS; + } while (value == 0); +} + +static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + unsigned int i2s_reg, reg; + unsigned long i2s_value, value; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_reg = KIRKWOOD_I2S_PLAYCTL; + reg = KIRKWOOD_PLAYCTL; + } else { + i2s_reg = KIRKWOOD_I2S_RECCTL; + reg = KIRKWOOD_RECCTL; + } + + /* set dco conf */ + kirkwood_set_dco(priv->io, params_rate(params)); + + i2s_value = readl(priv->io+i2s_reg); + i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK; + + value = readl(priv->io+reg); + value &= ~KIRKWOOD_PLAYCTL_SIZE_MASK; + + /* + * Size settings in play/rec i2s control regs and play/rec control + * regs must be the same. + */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16; + value |= KIRKWOOD_PLAYCTL_SIZE_16_C; + break; + /* + * doesn't work... S20_3LE != kirkwood 20bit format ? + * + case SNDRV_PCM_FORMAT_S20_3LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20; + value |= KIRKWOOD_PLAYCTL_SIZE_20; + break; + */ + case SNDRV_PCM_FORMAT_S24_LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24; + value |= KIRKWOOD_PLAYCTL_SIZE_24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32; + value |= KIRKWOOD_PLAYCTL_SIZE_32; + break; + default: + return -EINVAL; + } + writel(i2s_value, priv->io+i2s_reg); + writel(value, priv->io+reg); + + return 0; +} + +static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + unsigned long value; + + /* + * specs says KIRKWOOD_PLAYCTL must be read 2 times before + * changing it. So read 1 time here and 1 later. + */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* stop audio, enable interrupts */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value |= KIRKWOOD_PLAYCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* configure audio & enable i2s playback */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value &= ~KIRKWOOD_PLAYCTL_BURST_MASK; + value &= ~(KIRKWOOD_PLAYCTL_PAUSE|KIRKWOOD_PLAYCTL_SPDIF_EN); + + if (priv->burst == 32) + value |= KIRKWOOD_PLAYCTL_BURST_32; + else + value |= KIRKWOOD_PLAYCTL_BURST_128; + value |= KIRKWOOD_PLAYCTL_I2S_EN; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* stop audio, disable interrupts */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value |= KIRKWOOD_PLAYCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* disable all playbacks */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN); + writel(value, priv->io + KIRKWOOD_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value |= KIRKWOOD_PLAYCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value &= ~KIRKWOOD_PLAYCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_PLAYCTL); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + unsigned long value; + + value = readl(priv->io + KIRKWOOD_RECCTL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* stop audio, enable interrupts */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value |= KIRKWOOD_RECCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_RECCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value |= KIRKWOOD_INT_CAUSE_REC_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* configure audio & enable i2s record */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~KIRKWOOD_RECCTL_BURST_MASK; + value &= ~KIRKWOOD_RECCTL_MONO; + value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_SPDIF_EN); + + if (priv->burst == 32) + value |= KIRKWOOD_RECCTL_BURST_32; + else + value |= KIRKWOOD_RECCTL_BURST_128; + value |= KIRKWOOD_RECCTL_I2S_EN; + + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* stop audio, disable interrupts */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value |= KIRKWOOD_RECCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_RECCTL); + + value = readl(priv->io + KIRKWOOD_INT_MASK); + value &= ~KIRKWOOD_INT_CAUSE_REC_BYTES; + writel(value, priv->io + KIRKWOOD_INT_MASK); + + /* disable all records */ + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN); + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + value = readl(priv->io + KIRKWOOD_RECCTL); + value |= KIRKWOOD_RECCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~KIRKWOOD_RECCTL_PAUSE; + writel(value, priv->io + KIRKWOOD_RECCTL); + break; + + default: + return -EINVAL; + break; + } + + return 0; +} + +static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return kirkwood_i2s_play_trigger(substream, cmd, dai); + else + return kirkwood_i2s_rec_trigger(substream, cmd, dai); + + return 0; +} + +static int kirkwood_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + unsigned long value; + unsigned int reg_data; + + /* put system in a "safe" state : */ + /* disable audio interrupts */ + writel(0xffffffff, priv->io + KIRKWOOD_INT_CAUSE); + writel(0, priv->io + KIRKWOOD_INT_MASK); + + reg_data = readl(priv->io + 0x1200); + reg_data &= (~(0x333FF8)); + reg_data |= 0x111D18; + writel(reg_data, priv->io + 0x1200); + + msleep(500); + + reg_data = readl(priv->io + 0x1200); + reg_data &= (~(0x333FF8)); + reg_data |= 0x111D18; + writel(reg_data, priv->io + 0x1200); + + /* disable playback/record */ + value = readl(priv->io + KIRKWOOD_PLAYCTL); + value &= ~(KIRKWOOD_PLAYCTL_I2S_EN|KIRKWOOD_PLAYCTL_SPDIF_EN); + writel(value, priv->io + KIRKWOOD_PLAYCTL); + + value = readl(priv->io + KIRKWOOD_RECCTL); + value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN); + writel(value, priv->io + KIRKWOOD_RECCTL); + + return 0; + +} + +static void kirkwood_i2s_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ +} + +static struct snd_soc_dai_ops kirkwood_i2s_dai_ops = { + .trigger = kirkwood_i2s_trigger, + .hw_params = kirkwood_i2s_hw_params, + .set_fmt = kirkwood_i2s_set_fmt, +}; + + +struct snd_soc_dai kirkwood_i2s_dai = { + .name = DRV_NAME, + .id = 0, + .probe = kirkwood_i2s_probe, + .remove = kirkwood_i2s_remove, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = KIRKWOOD_I2S_RATES, + .formats = KIRKWOOD_I2S_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = KIRKWOOD_I2S_RATES, + .formats = KIRKWOOD_I2S_FORMATS,}, + .ops = &kirkwood_i2s_dai_ops, +}; +EXPORT_SYMBOL_GPL(kirkwood_i2s_dai); + +static __devinit int kirkwood_i2s_dev_probe(struct platform_device *pdev) +{ + struct resource *mem; + struct kirkwood_asoc_platform_data *data = + pdev->dev.platform_data; + int err; + + priv = kzalloc(sizeof(struct kirkwood_dma_data), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "allocation failed\n"); + err = -ENOMEM; + goto error; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "platform_get_resource failed\n"); + err = -ENXIO; + goto err_alloc; + } + + priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME); + if (!priv->mem) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + err = -EBUSY; + goto error; + } + + priv->io = ioremap(priv->mem->start, SZ_16K); + if (!priv->io) { + dev_err(&pdev->dev, "ioremap failed\n"); + err = -ENOMEM; + goto err_iomem; + } + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq <= 0) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + err = -ENXIO; + goto err_ioremap; + } + + if (!data || !data->dram) { + dev_err(&pdev->dev, "no platform data ?!\n"); + err = -EINVAL; + goto err_ioremap; + } + + priv->dram = data->dram; + priv->burst = data->burst; + + kirkwood_i2s_dai.capture.dma_data = priv; + kirkwood_i2s_dai.playback.dma_data = priv; + + return snd_soc_register_dai(&kirkwood_i2s_dai); + +err_ioremap: + iounmap(priv->io); +err_iomem: + release_mem_region(priv->mem->start, SZ_16K); +err_alloc: + kfree(priv); +error: + return err; +} + +static __devexit int kirkwood_i2s_dev_remove(struct platform_device *pdev) +{ + if (priv) { + iounmap(priv->io); + release_mem_region(priv->mem->start, SZ_16K); + kfree(priv); + } + snd_soc_unregister_dai(&kirkwood_i2s_dai); + return 0; +} + +static struct platform_driver kirkwood_i2s_driver = { + .probe = kirkwood_i2s_dev_probe, + .remove = kirkwood_i2s_dev_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init kirkwood_i2s_init(void) +{ + return platform_driver_register(&kirkwood_i2s_driver); +} +module_init(kirkwood_i2s_init); + +static void __exit kirkwood_i2s_exit(void) +{ + platform_driver_unregister(&kirkwood_i2s_driver); +} +module_exit(kirkwood_i2s_exit); + +/* Module information */ +MODULE_AUTHOR("Arnaud Patard, "); +MODULE_DESCRIPTION("Kirkwood I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:kirkwood-i2s"); diff --git a/sound/soc/kirkwood/kirkwood-i2s.h b/sound/soc/kirkwood/kirkwood-i2s.h new file mode 100644 index 000000000000..c5595c616d7a --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-i2s.h @@ -0,0 +1,17 @@ +/* + * kirkwood-i2s.h + * + * (c) 2010 Arnaud Patard + * + * 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 _KIRKWOOD_I2S_H +#define _KIRKWOOD_I2S_H + +extern struct snd_soc_dai kirkwood_i2s_dai; + +#endif diff --git a/sound/soc/kirkwood/kirkwood-openrd.c b/sound/soc/kirkwood/kirkwood-openrd.c new file mode 100644 index 000000000000..0353d06bc41a --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-openrd.c @@ -0,0 +1,126 @@ +/* + * kirkwood-openrd.c + * + * (c) 2010 Arnaud Patard + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "kirkwood-i2s.h" +#include "kirkwood-dma.h" +#include "../codecs/cs42l51.h" + +static int openrd_client_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + unsigned int freq, fmt; + + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) + return ret; + + switch (params_rate(params)) { + default: + case 44100: + freq = 11289600; + break; + case 48000: + freq = 12288000; + break; + case 96000: + freq = 24576000; + break; + } + + return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN); + +} + +static struct snd_soc_ops openrd_client_ops = { + .hw_params = openrd_client_hw_params, +}; + + +static struct snd_soc_dai_link openrd_client_dai[] = { +{ + .name = "CS42L51", + .stream_name = "CS42L51 HiFi", + .cpu_dai = &kirkwood_i2s_dai, + .codec_dai = &cs42l51_dai, + .ops = &openrd_client_ops, +}, +}; + + +static struct snd_soc_card openrd_client = { + .name = "OpenRD Client", + .platform = &kirkwood_soc_platform, + .dai_link = openrd_client_dai, + .num_links = ARRAY_SIZE(openrd_client_dai), +}; + +static struct snd_soc_device openrd_client_snd_devdata = { + .card = &openrd_client, + .codec_dev = &soc_codec_device_cs42l51, +}; + +static struct platform_device *openrd_client_snd_device; + +static int __init openrd_client_init(void) +{ + int ret; + + if (!machine_is_openrd_client()) + return 0; + + openrd_client_snd_device = platform_device_alloc("soc-audio", -1); + if (!openrd_client_snd_device) + return -ENOMEM; + + platform_set_drvdata(openrd_client_snd_device, + &openrd_client_snd_devdata); + openrd_client_snd_devdata.dev = &openrd_client_snd_device->dev; + + ret = platform_device_add(openrd_client_snd_device); + if (ret) { + printk(KERN_ERR "%s: platform_device_add failed\n", __func__); + platform_device_put(openrd_client_snd_device); + } + + return ret; +} + +static void __exit openrd_client_exit(void) +{ + platform_device_unregister(openrd_client_snd_device); +} + +module_init(openrd_client_init); +module_exit(openrd_client_exit); + +/* Module information */ +MODULE_AUTHOR("Arnaud Patard "); +MODULE_DESCRIPTION("ALSA SoC OpenRD Client"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:soc-audio"); diff --git a/sound/soc/kirkwood/kirkwood.h b/sound/soc/kirkwood/kirkwood.h new file mode 100644 index 000000000000..b6e4f68d71dd --- /dev/null +++ b/sound/soc/kirkwood/kirkwood.h @@ -0,0 +1,126 @@ +/* + * kirkwood.h + * + * (c) 2010 Arnaud Patard + * + * 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 _KIRKWOOD_AUDIO_H +#define _KIRKWOOD_AUDIO_H + +#define KIRKWOOD_RECORD_WIN 0 +#define KIRKWOOD_PLAYBACK_WIN 1 +#define KIRKWOOD_MAX_AUDIO_WIN 2 + +#define KIRKWOOD_AUDIO_WIN_BASE_REG(win) (0xA00 + ((win)<<3)) +#define KIRKWOOD_AUDIO_WIN_CTRL_REG(win) (0xA04 + ((win)<<3)) + + +#define KIRKWOOD_RECCTL 0x1000 +#define KIRKWOOD_RECCTL_SPDIF_EN (1<<11) +#define KIRKWOOD_RECCTL_I2S_EN (1<<10) +#define KIRKWOOD_RECCTL_PAUSE (1<<9) +#define KIRKWOOD_RECCTL_MUTE (1<<8) +#define KIRKWOOD_RECCTL_BURST_MASK (3<<5) +#define KIRKWOOD_RECCTL_BURST_128 (2<<5) +#define KIRKWOOD_RECCTL_BURST_32 (1<<5) +#define KIRKWOOD_RECCTL_MONO (1<<4) +#define KIRKWOOD_RECCTL_MONO_CHAN_RIGHT (1<<3) +#define KIRKWOOD_RECCTL_MONO_CHAN_LEFT (0<<3) +#define KIRKWOOD_RECCTL_SIZE_MASK (7<<0) +#define KIRKWOOD_RECCTL_SIZE_16 (7<<0) +#define KIRKWOOD_RECCTL_SIZE_16_C (3<<0) +#define KIRKWOOD_RECCTL_SIZE_20 (2<<0) +#define KIRKWOOD_RECCTL_SIZE_24 (1<<0) +#define KIRKWOOD_RECCTL_SIZE_32 (0<<0) + +#define KIRKWOOD_REC_BUF_ADDR 0x1004 +#define KIRKWOOD_REC_BUF_SIZE 0x1008 +#define KIRKWOOD_REC_BYTE_COUNT 0x100C + +#define KIRKWOOD_PLAYCTL 0x1100 +#define KIRKWOOD_PLAYCTL_PLAY_BUSY (1<<16) +#define KIRKWOOD_PLAYCTL_BURST_MASK (3<<11) +#define KIRKWOOD_PLAYCTL_BURST_128 (2<<11) +#define KIRKWOOD_PLAYCTL_BURST_32 (1<<11) +#define KIRKWOOD_PLAYCTL_PAUSE (1<<9) +#define KIRKWOOD_PLAYCTL_SPDIF_MUTE (1<<8) +#define KIRKWOOD_PLAYCTL_I2S_MUTE (1<<7) +#define KIRKWOOD_PLAYCTL_SPDIF_EN (1<<4) +#define KIRKWOOD_PLAYCTL_I2S_EN (1<<3) +#define KIRKWOOD_PLAYCTL_SIZE_MASK (7<<0) +#define KIRKWOOD_PLAYCTL_SIZE_16 (7<<0) +#define KIRKWOOD_PLAYCTL_SIZE_16_C (3<<0) +#define KIRKWOOD_PLAYCTL_SIZE_20 (2<<0) +#define KIRKWOOD_PLAYCTL_SIZE_24 (1<<0) +#define KIRKWOOD_PLAYCTL_SIZE_32 (0<<0) + +#define KIRKWOOD_PLAY_BUF_ADDR 0x1104 +#define KIRKWOOD_PLAY_BUF_SIZE 0x1108 +#define KIRKWOOD_PLAY_BYTE_COUNT 0x110C + +#define KIRKWOOD_DCO_CTL 0x1204 +#define KIRKWOOD_DCO_CTL_OFFSET_MASK (0xFFF<<2) +#define KIRKWOOD_DCO_CTL_OFFSET_0 (0x800<<2) +#define KIRKWOOD_DCO_CTL_FREQ_MASK (3<<0) +#define KIRKWOOD_DCO_CTL_FREQ_11 (0<<0) +#define KIRKWOOD_DCO_CTL_FREQ_12 (1<<0) +#define KIRKWOOD_DCO_CTL_FREQ_24 (2<<0) + +#define KIRKWOOD_DCO_SPCR_STATUS 0x120c +#define KIRKWOOD_DCO_SPCR_STATUS_DCO_LOCK (1<<16) + +#define KIRKWOOD_ERR_CAUSE 0x1300 +#define KIRKWOOD_ERR_MASK 0x1304 + +#define KIRKWOOD_INT_CAUSE 0x1308 +#define KIRKWOOD_INT_MASK 0x130C +#define KIRKWOOD_INT_CAUSE_PLAY_BYTES (1<<14) +#define KIRKWOOD_INT_CAUSE_REC_BYTES (1<<13) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_END (1<<7) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_3Q (1<<6) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_HALF (1<<5) +#define KIRKWOOD_INT_CAUSE_DMA_PLAY_1Q (1<<4) +#define KIRKWOOD_INT_CAUSE_DMA_REC_END (1<<3) +#define KIRKWOOD_INT_CAUSE_DMA_REC_3Q (1<<2) +#define KIRKWOOD_INT_CAUSE_DMA_REC_HALF (1<<1) +#define KIRKWOOD_INT_CAUSE_DMA_REC_1Q (1<<0) + +#define KIRKWOOD_REC_BYTE_INT_COUNT 0x1310 +#define KIRKWOOD_PLAY_BYTE_INT_COUNT 0x1314 +#define KIRKWOOD_BYTE_INT_COUNT_MASK 0xffffff + +#define KIRKWOOD_I2S_PLAYCTL 0x2508 +#define KIRKWOOD_I2S_RECCTL 0x2408 +#define KIRKWOOD_I2S_CTL_JUST_MASK (0xf<<26) +#define KIRKWOOD_I2S_CTL_LJ (0<<26) +#define KIRKWOOD_I2S_CTL_I2S (5<<26) +#define KIRKWOOD_I2S_CTL_RJ (8<<26) +#define KIRKWOOD_I2S_CTL_SIZE_MASK (3<<30) +#define KIRKWOOD_I2S_CTL_SIZE_16 (3<<30) +#define KIRKWOOD_I2S_CTL_SIZE_20 (2<<30) +#define KIRKWOOD_I2S_CTL_SIZE_24 (1<<30) +#define KIRKWOOD_I2S_CTL_SIZE_32 (0<<30) + +#define KIRKWOOD_AUDIO_BUF_MAX (16*1024*1024) + +/* Theses values come from the marvell alsa driver */ +/* need to find where they come from */ +#define KIRKWOOD_SND_MIN_PERIODS 8 +#define KIRKWOOD_SND_MAX_PERIODS 16 +#define KIRKWOOD_SND_MIN_PERIOD_BYTES 0x4000 +#define KIRKWOOD_SND_MAX_PERIOD_BYTES 0x4000 + +struct kirkwood_dma_data { + struct resource *mem; + void __iomem *io; + int irq; + int burst; + struct mbus_dram_target_info *dram; +}; + +#endif diff --git a/sound/soc/nuc900/Kconfig b/sound/soc/nuc900/Kconfig new file mode 100644 index 000000000000..a0ed1c618f60 --- /dev/null +++ b/sound/soc/nuc900/Kconfig @@ -0,0 +1,27 @@ +## +## NUC900 series AC97 API +## +config SND_SOC_NUC900 + tristate "SoC Audio for NUC900 series" + depends on ARCH_W90X900 + help + This option enables support for AC97 mode on the NUC900 SoC. + +config SND_SOC_NUC900_AC97 + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + + +## +## Boards +## +config SND_SOC_NUC900EVB + tristate "NUC900 AC97 support for demo board" + depends on SND_SOC_NUC900 + select SND_SOC_NUC900_AC97 + select SND_SOC_AC97_CODEC + help + Select this option to enable audio (AC97) on the + NUC900 demoboard. diff --git a/sound/soc/nuc900/Makefile b/sound/soc/nuc900/Makefile new file mode 100644 index 000000000000..7e46c7150316 --- /dev/null +++ b/sound/soc/nuc900/Makefile @@ -0,0 +1,11 @@ +# NUC900 series audio +snd-soc-nuc900-pcm-objs := nuc900-pcm.o +snd-soc-nuc900-ac97-objs := nuc900-ac97.o + +obj-$(CONFIG_SND_SOC_NUC900) += snd-soc-nuc900-pcm.o +obj-$(CONFIG_SND_SOC_NUC900_AC97) += snd-soc-nuc900-ac97.o + +# Boards +snd-soc-nuc900-audio-objs := nuc900-audio.o + +obj-$(CONFIG_SND_SOC_NUC900EVB) += snd-soc-nuc900-audio.o diff --git a/sound/soc/nuc900/nuc900-ac97.c b/sound/soc/nuc900/nuc900-ac97.c new file mode 100644 index 000000000000..f7b44e081420 --- /dev/null +++ b/sound/soc/nuc900/nuc900-ac97.c @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2009-2010 Nuvoton technology corporation. + * + * Wan ZongShun + * + * 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;version 2 of the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "nuc900-auido.h" + +static DEFINE_MUTEX(ac97_mutex); +struct nuc900_audio *nuc900_ac97_data; + +static int nuc900_checkready(void) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + + if (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS0) & CODEC_READY)) + return -EPERM; + + return 0; +} + +/* AC97 controller reads codec register */ +static unsigned short nuc900_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long timeout = 0x10000, val; + + mutex_lock(&ac97_mutex); + + val = nuc900_checkready(); + if (!!val) { + dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); + goto out; + } + + /* set the R_WB bit and write register index */ + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, R_WB | reg); + + /* set the valid frame bit and valid slots */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + val |= (VALID_FRAME | SLOT1_VALID); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val); + + udelay(100); + + /* polling the AC_R_FINISH */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val &= AC_R_FINISH; + while (!val && timeout--) + mdelay(1); + + if (!timeout) { + dev_err(nuc900_audio->dev, "AC97 read register time out !\n"); + val = -EPERM; + goto out; + } + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0) ; + val &= ~SLOT1_VALID; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val); + + if (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS1) >> 2 != reg) { + dev_err(nuc900_audio->dev, + "R_INDEX of REG_ACTL_ACIS1 not match!\n"); + } + + udelay(100); + val = (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS2) & 0xFFFF); + +out: + mutex_unlock(&ac97_mutex); + return val; +} + +/* AC97 controller writes to codec register */ +static void nuc900_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long tmp, timeout = 0x10000; + + mutex_lock(&ac97_mutex); + + tmp = nuc900_checkready(); + if (!!tmp) + dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); + + /* clear the R_WB bit and write register index */ + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, reg); + + /* write register value */ + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS2, val); + + /* set the valid frame bit and valid slots */ + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp |= SLOT1_VALID | SLOT2_VALID | VALID_FRAME; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + udelay(100); + + /* polling the AC_W_FINISH */ + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + tmp &= AC_W_FINISH; + while (tmp && timeout--) + mdelay(1); + + if (!timeout) + dev_err(nuc900_audio->dev, "AC97 write register time out !\n"); + + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp &= ~(SLOT1_VALID | SLOT2_VALID); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + mutex_unlock(&ac97_mutex); + +} + +static void nuc900_ac97_warm_reset(struct snd_ac97 *ac97) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long val; + + mutex_lock(&ac97_mutex); + + /* warm reset AC 97 */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val |= AC_W_RES; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); + + udelay(1000); + + val = nuc900_checkready(); + if (!!val) + dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); + + mutex_unlock(&ac97_mutex); +} + +static void nuc900_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long val; + + mutex_lock(&ac97_mutex); + + /* reset Audio Controller */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val |= ACTL_RESET_BIT; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + udelay(1000); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val &= (~ACTL_RESET_BIT); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + udelay(1000); + + /* reset AC-link interface */ + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val |= AC_RESET; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + udelay(1000); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + val &= ~AC_RESET; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + udelay(1000); + + /* cold reset AC 97 */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val |= AC_C_RES; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); + + udelay(1000); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); + val &= (~AC_C_RES); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); + + udelay(1000); + + mutex_unlock(&ac97_mutex); + +} + +/* AC97 controller operations */ +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = nuc900_ac97_read, + .write = nuc900_ac97_write, + .reset = nuc900_ac97_cold_reset, + .warm_reset = nuc900_ac97_warm_reset, +} +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int nuc900_ac97_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + int ret, stype = SUBSTREAM_TYPE(substream); + unsigned long val, tmp; + + ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + if (PCM_TX == stype) { + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp |= (SLOT3_VALID | SLOT4_VALID | VALID_FRAME); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); + tmp |= (P_DMA_END_IRQ | P_DMA_MIDDLE_IRQ); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, tmp); + val |= AC_PLAY; + } else { + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); + tmp |= (R_DMA_END_IRQ | R_DMA_MIDDLE_IRQ); + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, tmp); + val |= AC_RECORD; + } + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + if (PCM_TX == stype) { + tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); + tmp &= ~(SLOT3_VALID | SLOT4_VALID); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, RESET_PRSR); + val &= ~AC_PLAY; + } else { + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, RESET_PRSR); + val &= ~AC_RECORD; + } + + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int nuc900_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + unsigned long val; + + mutex_lock(&ac97_mutex); + + /* enable unit clock */ + clk_enable(nuc900_audio->clk); + + /* enable audio controller and AC-link interface */ + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + val |= (IIS_AC_PIN_SEL | ACLINK_EN); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); + + mutex_unlock(&ac97_mutex); + + return 0; +} + +static void nuc900_ac97_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct nuc900_audio *nuc900_audio = nuc900_ac97_data; + + clk_disable(nuc900_audio->clk); +} + +static struct snd_soc_dai_ops nuc900_ac97_dai_ops = { + .trigger = nuc900_ac97_trigger, +}; + +struct snd_soc_dai nuc900_ac97_dai = { + .name = "nuc900-ac97", + .probe = nuc900_ac97_probe, + .remove = nuc900_ac97_remove, + .ac97_control = 1, + .playback = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + }, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &nuc900_ac97_dai_ops, +} +EXPORT_SYMBOL_GPL(nuc900_ac97_dai); + +static int __devinit nuc900_ac97_drvprobe(struct platform_device *pdev) +{ + struct nuc900_audio *nuc900_audio; + int ret; + + if (nuc900_ac97_data) + return -EBUSY; + + nuc900_audio = kzalloc(sizeof(struct nuc900_audio), GFP_KERNEL); + if (!nuc900_audio) + return -ENOMEM; + + spin_lock_init(&nuc900_audio->lock); + + nuc900_audio->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!nuc900_audio->res) { + ret = -ENODEV; + goto out0; + } + + if (!request_mem_region(nuc900_audio->res->start, + resource_size(nuc900_audio->res), pdev->name)) { + ret = -EBUSY; + goto out0; + } + + nuc900_audio->mmio = ioremap(nuc900_audio->res->start, + resource_size(nuc900_audio->res)); + if (!nuc900_audio->mmio) { + ret = -ENOMEM; + goto out1; + } + + nuc900_audio->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(nuc900_audio->clk)) { + ret = PTR_ERR(nuc900_audio->clk); + goto out2; + } + + nuc900_audio->irq_num = platform_get_irq(pdev, 0); + if (!nuc900_audio->irq_num) { + ret = -EBUSY; + goto out2; + } + + nuc900_ac97_data = nuc900_audio; + + nuc900_audio->dev = nuc900_ac97_dai.dev = &pdev->dev; + + ret = snd_soc_register_dai(&nuc900_ac97_dai); + if (ret) + goto out3; + + mfp_set_groupg(nuc900_audio->dev); /* enbale ac97 multifunction pin*/ + + return 0; + +out3: + clk_put(nuc900_audio->clk); +out2: + iounmap(nuc900_audio->mmio); +out1: + release_mem_region(nuc900_audio->res->start, + resource_size(nuc900_audio->res)); +out0: + kfree(nuc900_audio); + return ret; +} + +static int __devexit nuc900_ac97_drvremove(struct platform_device *pdev) +{ + + snd_soc_unregister_dai(&nuc900_ac97_dai); + + clk_put(nuc900_ac97_data->clk); + iounmap(nuc900_ac97_data->mmio); + release_mem_region(nuc900_ac97_data->res->start, + resource_size(nuc900_ac97_data->res)); + + nuc900_ac97_data = NULL; + + return 0; +} + +static struct platform_driver nuc900_ac97_driver = { + .driver = { + .name = "nuc900-audio", + .owner = THIS_MODULE, + }, + .probe = nuc900_ac97_drvprobe, + .remove = __devexit_p(nuc900_ac97_drvremove), +}; + +static int __init nuc900_ac97_init(void) +{ + return platform_driver_register(&nuc900_ac97_driver); +} + +static void __exit nuc900_ac97_exit(void) +{ + platform_driver_unregister(&nuc900_ac97_driver); +} + +module_init(nuc900_ac97_init); +module_exit(nuc900_ac97_exit); + +MODULE_AUTHOR("Wan ZongShun "); +MODULE_DESCRIPTION("NUC900 AC97 SoC driver!"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nuc900-ac97"); diff --git a/sound/soc/nuc900/nuc900-audio.c b/sound/soc/nuc900/nuc900-audio.c new file mode 100644 index 000000000000..b33d5b844d71 --- /dev/null +++ b/sound/soc/nuc900/nuc900-audio.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010 Nuvoton technology corporation. + * + * Wan ZongShun + * + * 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;version 2 of the License. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/ac97.h" +#include "nuc900-auido.h" + +static struct snd_soc_dai_link nuc900evb_ac97_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &nuc900_ac97_dai, + .codec_dai = &ac97_dai, +}; + +static struct snd_soc_card nuc900evb_audio_machine = { + .name = "NUC900EVB_AC97", + .dai_link = &nuc900evb_ac97_dai, + .num_links = 1, + .platform = &nuc900_soc_platform, +}; + +static struct snd_soc_device nuc900evb_ac97_devdata = { + .card = &nuc900evb_audio_machine, + .codec_dev = &soc_codec_dev_ac97, +}; + +static struct platform_device *nuc900evb_asoc_dev; + +static int __init nuc900evb_audio_init(void) +{ + int ret; + + ret = -ENOMEM; + nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1); + if (!nuc900evb_asoc_dev) + goto out; + + /* nuc900 board audio device */ + platform_set_drvdata(nuc900evb_asoc_dev, &nuc900evb_ac97_devdata); + + nuc900evb_ac97_devdata.dev = &nuc900evb_asoc_dev->dev; + ret = platform_device_add(nuc900evb_asoc_dev); + + if (ret) { + platform_device_put(nuc900evb_asoc_dev); + nuc900evb_asoc_dev = NULL; + } + +out: + return ret; +} + +static void __exit nuc900evb_audio_exit(void) +{ + platform_device_unregister(nuc900evb_asoc_dev); +} + +module_init(nuc900evb_audio_init); +module_exit(nuc900evb_audio_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("NUC900 Series ASoC audio support"); +MODULE_AUTHOR("Wan ZongShun"); diff --git a/sound/soc/nuc900/nuc900-auido.h b/sound/soc/nuc900/nuc900-auido.h new file mode 100644 index 000000000000..95ac4ef2f353 --- /dev/null +++ b/sound/soc/nuc900/nuc900-auido.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010 Nuvoton technology corporation. + * + * Wan ZongShun + * + * 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;version 2 of the License. + * + */ + +#ifndef _NUC900_AUDIO_H +#define _NUC900_AUDIO_H + +#include + +/* Audio Control Registers */ +#define ACTL_CON 0x00 +#define ACTL_RESET 0x04 +#define ACTL_RDSTB 0x08 +#define ACTL_RDST_LENGTH 0x0C +#define ACTL_RDSTC 0x10 +#define ACTL_RSR 0x14 +#define ACTL_PDSTB 0x18 +#define ACTL_PDST_LENGTH 0x1C +#define ACTL_PDSTC 0x20 +#define ACTL_PSR 0x24 +#define ACTL_IISCON 0x28 +#define ACTL_ACCON 0x2C +#define ACTL_ACOS0 0x30 +#define ACTL_ACOS1 0x34 +#define ACTL_ACOS2 0x38 +#define ACTL_ACIS0 0x3C +#define ACTL_ACIS1 0x40 +#define ACTL_ACIS2 0x44 +#define ACTL_COUNTER 0x48 + +/* bit definition of REG_ACTL_CON register */ +#define R_DMA_IRQ 0x1000 +#define T_DMA_IRQ 0x0800 +#define IIS_AC_PIN_SEL 0x0100 +#define FIFO_TH 0x0080 +#define ADC_EN 0x0010 +#define M80_EN 0x0008 +#define ACLINK_EN 0x0004 +#define IIS_EN 0x0002 + +/* bit definition of REG_ACTL_RESET register */ +#define W5691_PLAY 0x20000 +#define ACTL_RESET_BIT 0x10000 +#define RECORD_RIGHT_CHNNEL 0x08000 +#define RECORD_LEFT_CHNNEL 0x04000 +#define PLAY_RIGHT_CHNNEL 0x02000 +#define PLAY_LEFT_CHNNEL 0x01000 +#define DAC_PLAY 0x00800 +#define ADC_RECORD 0x00400 +#define M80_PLAY 0x00200 +#define AC_RECORD 0x00100 +#define AC_PLAY 0x00080 +#define IIS_RECORD 0x00040 +#define IIS_PLAY 0x00020 +#define DAC_RESET 0x00010 +#define ADC_RESET 0x00008 +#define M80_RESET 0x00004 +#define AC_RESET 0x00002 +#define IIS_RESET 0x00001 + +/* bit definition of REG_ACTL_ACCON register */ +#define AC_BCLK_PU_EN 0x20 +#define AC_R_FINISH 0x10 +#define AC_W_FINISH 0x08 +#define AC_W_RES 0x04 +#define AC_C_RES 0x02 + +/* bit definition of ACTL_RSR register */ +#define R_FIFO_EMPTY 0x04 +#define R_DMA_END_IRQ 0x02 +#define R_DMA_MIDDLE_IRQ 0x01 + +/* bit definition of ACTL_PSR register */ +#define P_FIFO_EMPTY 0x04 +#define P_DMA_END_IRQ 0x02 +#define P_DMA_MIDDLE_IRQ 0x01 + +/* bit definition of ACTL_ACOS0 register */ +#define SLOT1_VALID 0x01 +#define SLOT2_VALID 0x02 +#define SLOT3_VALID 0x04 +#define SLOT4_VALID 0x08 +#define VALID_FRAME 0x10 + +/* bit definition of ACTL_ACOS1 register */ +#define R_WB 0x80 + +#define CODEC_READY 0x10 +#define RESET_PRSR 0x00 +#define AUDIO_WRITE(addr, val) __raw_writel(val, addr) +#define AUDIO_READ(addr) __raw_readl(addr) +#define PCM_TX 0 +#define PCM_RX 1 +#define SUBSTREAM_TYPE(substream) \ + ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX) + +struct nuc900_audio { + void __iomem *mmio; + spinlock_t lock; + dma_addr_t dma_addr[2]; + unsigned long buffersize[2]; + unsigned long irq_num; + struct snd_pcm_substream *substream; + struct resource *res; + struct clk *clk; + struct device *dev; + +}; + +extern struct nuc900_audio *nuc900_ac97_data; +extern struct snd_soc_dai nuc900_ac97_dai; +extern struct snd_soc_platform nuc900_soc_platform; + +#endif /*end _NUC900_AUDIO_H */ diff --git a/sound/soc/nuc900/nuc900-pcm.c b/sound/soc/nuc900/nuc900-pcm.c new file mode 100644 index 000000000000..32a503c1c4be --- /dev/null +++ b/sound/soc/nuc900/nuc900-pcm.c @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2010 Nuvoton technology corporation. + * + * Wan ZongShun + * + * 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;version 2 of the License. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "nuc900-auido.h" + +static const struct snd_pcm_hardware nuc900_pcm_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, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 4*1024, + .period_bytes_min = 1*1024, + .period_bytes_max = 4*1024, + .periods_min = 1, + .periods_max = 1024, +}; + +static int nuc900_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long flags, stype = SUBSTREAM_TYPE(substream); + int ret = 0; + + spin_lock_irqsave(&nuc900_audio->lock, flags); + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + nuc900_audio->substream = substream; + nuc900_audio->dma_addr[stype] = runtime->dma_addr; + nuc900_audio->buffersize[stype] = params_buffer_bytes(params); + + spin_unlock_irqrestore(&nuc900_audio->lock, flags); + + return ret; +} + +static void nuc900_update_dma_register(struct snd_pcm_substream *substream, + dma_addr_t dma_addr, size_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + void __iomem *mmio_addr, *mmio_len; + + if (SUBSTREAM_TYPE(substream) == PCM_TX) { + mmio_addr = nuc900_audio->mmio + ACTL_PDSTB; + mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH; + } else { + mmio_addr = nuc900_audio->mmio + ACTL_RDSTB; + mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH; + } + + AUDIO_WRITE(mmio_addr, dma_addr); + AUDIO_WRITE(mmio_len, count); +} + +static void nuc900_dma_start(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long val; + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + val |= (T_DMA_IRQ | R_DMA_IRQ); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); +} + +static void nuc900_dma_stop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long val; + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + val &= ~(T_DMA_IRQ | R_DMA_IRQ); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); +} + +static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id) +{ + struct snd_pcm_substream *substream = dev_id; + struct nuc900_audio *nuc900_audio = substream->runtime->private_data; + unsigned long val; + + spin_lock(&nuc900_audio->lock); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); + + if (val & R_DMA_IRQ) { + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); + + if (val & R_DMA_MIDDLE_IRQ) { + val |= R_DMA_MIDDLE_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); + } + + if (val & R_DMA_END_IRQ) { + val |= R_DMA_END_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); + } + } else if (val & T_DMA_IRQ) { + AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); + + if (val & P_DMA_MIDDLE_IRQ) { + val |= P_DMA_MIDDLE_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); + } + + if (val & P_DMA_END_IRQ) { + val |= P_DMA_END_IRQ; + AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); + } + } else { + dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n"); + spin_unlock(&nuc900_audio->lock); + return IRQ_HANDLED; + } + + spin_unlock(&nuc900_audio->lock); + + snd_pcm_period_elapsed(substream); + + return IRQ_HANDLED; +} + +static int nuc900_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int nuc900_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + unsigned long flags, val, stype = SUBSTREAM_TYPE(substream);; + + spin_lock_irqsave(&nuc900_audio->lock, flags); + + nuc900_update_dma_register(substream, + nuc900_audio->dma_addr[stype], nuc900_audio->buffersize[stype]); + + val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); + + switch (runtime->channels) { + case 1: + if (PCM_TX == stype) { + val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); + val |= PLAY_RIGHT_CHNNEL; + } else { + val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); + val |= RECORD_RIGHT_CHNNEL; + } + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + break; + case 2: + if (PCM_TX == stype) + val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); + else + val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); + AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); + break; + default: + return -EINVAL; + } + spin_unlock_irqrestore(&nuc900_audio->lock, flags); + return 0; +} + +static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + nuc900_dma_start(substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + nuc900_dma_stop(substream); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +int nuc900_dma_getposition(struct snd_pcm_substream *substream, + dma_addr_t *src, dma_addr_t *dst) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + + if (src != NULL) + *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC); + + if (dst != NULL) + *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC); + + return 0; +} + +static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + dma_addr_t src, dst; + unsigned long res; + + nuc900_dma_getposition(substream, &src, &dst); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + res = dst - runtime->dma_addr; + else + res = src - runtime->dma_addr; + + return bytes_to_frames(substream->runtime, res); +} + +static int nuc900_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio; + + snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware); + + nuc900_audio = nuc900_ac97_data; + + if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt, + IRQF_DISABLED, "nuc900-dma", substream)) + return -EBUSY; + + runtime->private_data = nuc900_audio; + + return 0; +} + +static int nuc900_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct nuc900_audio *nuc900_audio = runtime->private_data; + + free_irq(nuc900_audio->irq_num, substream); + + return 0; +} + +static int nuc900_dma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops nuc900_dma_ops = { + .open = nuc900_dma_open, + .close = nuc900_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = nuc900_dma_hw_params, + .hw_free = nuc900_dma_hw_free, + .prepare = nuc900_dma_prepare, + .trigger = nuc900_dma_trigger, + .pointer = nuc900_dma_pointer, + .mmap = nuc900_dma_mmap, +}; + +static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32); +static int nuc900_dma_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + if (!card->dev->dma_mask) + card->dev->dma_mask = &nuc900_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, 4 * 1024, (4 * 1024) - 1); + + return 0; +} + +struct snd_soc_platform nuc900_soc_platform = { + .name = "nuc900-dma", + .pcm_ops = &nuc900_dma_ops, + .pcm_new = nuc900_dma_new, + .pcm_free = nuc900_dma_free_dma_buffers, +} +EXPORT_SYMBOL_GPL(nuc900_soc_platform); + +static int __init nuc900_soc_platform_init(void) +{ + return snd_soc_register_platform(&nuc900_soc_platform); +} + +static void __exit nuc900_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&nuc900_soc_platform); +} + +module_init(nuc900_soc_platform_init); +module_exit(nuc900_soc_platform_exit); + +MODULE_AUTHOR("Wan ZongShun, "); +MODULE_DESCRIPTION("nuc900 Audio DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 2a7cc222d098..292d817c9a94 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -1,6 +1,6 @@ config SND_S3C24XX_SOC tristate "SoC Audio for the Samsung S3CXXXX chips" - depends on ARCH_S3C2410 || ARCH_S3C64XX + depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 select S3C64XX_DMA if ARCH_S3C64XX help Say Y or M if you want to add support for codecs attached to @@ -120,7 +120,7 @@ config SND_S3C24XX_SOC_SIMTEC_HERMES config SND_SOC_SMDK_WM9713 tristate "SoC AC97 Audio support for SMDK with WM9713" - depends on SND_S3C24XX_SOC && MACH_SMDK6410 + depends on SND_S3C24XX_SOC && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110) select SND_SOC_WM9713 select SND_S3C_SOC_AC97 help diff --git a/sound/soc/s3c24xx/smdk_wm9713.c b/sound/soc/s3c24xx/smdk_wm9713.c index 24fd39f38ccb..5527b9e88c98 100644 --- a/sound/soc/s3c24xx/smdk_wm9713.c +++ b/sound/soc/s3c24xx/smdk_wm9713.c @@ -25,6 +25,9 @@ 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 */ /* diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 998569d60330..254dd1c6914d 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2352,6 +2352,101 @@ int snd_soc_limit_volume(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(snd_soc_limit_volume); +/** + * snd_soc_info_volsw_2r_sx - double with tlv and variable data size + * mixer info callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Returns 0 for success. + */ +int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + int min = mc->min; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = max-min; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r_sx); + +/** + * snd_soc_get_volsw_2r_sx - double with tlv and variable data size + * mixer get callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Returns 0 for success. + */ +int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int mask = (1<shift)-1; + int min = mc->min; + int val = snd_soc_read(codec, mc->reg) & mask; + int valr = snd_soc_read(codec, mc->rreg) & mask; + + ucontrol->value.integer.value[0] = ((val & 0xff)-min); + ucontrol->value.integer.value[1] = ((valr & 0xff)-min); + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx); + +/** + * snd_soc_put_volsw_2r_sx - double with tlv and variable data size + * mixer put callback + * @kcontrol: mixer control + * @uinfo: control element information + * + * Returns 0 for success. + */ +int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int mask = (1<shift)-1; + int min = mc->min; + int ret; + unsigned int val, valr, oval, ovalr; + + val = ((ucontrol->value.integer.value[0]+min) & 0xff); + val &= mask; + valr = ((ucontrol->value.integer.value[1]+min) & 0xff); + valr &= mask; + + oval = snd_soc_read(codec, mc->reg) & mask; + ovalr = snd_soc_read(codec, mc->rreg) & mask; + + ret = 0; + if (oval != val) { + ret = snd_soc_write(codec, mc->reg, val); + if (ret < 0) + return 0; + ret = 1; + } + if (ovalr != valr) { + ret = snd_soc_write(codec, mc->rreg, valr); + if (ret < 0) + return 0; + ret = 1; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r_sx); + /** * snd_soc_dai_set_sysclk - configure DAI system or master clock. * @dai: DAI