[alsa-devel] [patch 0/6] kirkwood openrd client audio support - v2
[ added forgotten linux-arm-kernel cc: ]
Hi,
It's a new try to get merged support for openrd client audio. I've tried to fix all comments, I hope I didn't miss something.
In the changes, there's : - s/kirkwood/orion/ - Added a comment about data centered around 0 in the new control entry - fixed quilt diffstat header braindamage - added dapm support to cs42l51 codec - use ASoC register cache support - modified i2s driver to be a platform driver and move resource stuff to the right place
Arnaud Patard
This patch add audio related definitions and functions
Signed-off-by: Arnaud Patard apatard@mandriva.com
Index: sound-2.6/arch/arm/mach-kirkwood/common.c =================================================================== --- sound-2.6.orig/arch/arm/mach-kirkwood/common.c 2010-05-15 17:03:59.990086774 +0200 +++ sound-2.6/arch/arm/mach-kirkwood/common.c 2010-05-15 17:04:04.998087735 +0200 @@ -25,6 +25,7 @@ #include <asm/mach/time.h> #include <mach/kirkwood.h> #include <mach/bridge-regs.h> +#include <plat/audio.h> #include <plat/cache-feroceon-l2.h> #include <plat/ehci-orion.h> #include <plat/mvsdio.h> @@ -855,6 +856,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 orion_asoc_platform_data kirkwood_i2s_data = { + .dram = &kirkwood_mbus_dram_info, + .burst = 128, +}; + +static struct platform_device kirkwood_i2s_device = { + .name = "orion-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 @@ -914,6 +951,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, Index: sound-2.6/arch/arm/mach-kirkwood/common.h =================================================================== --- sound-2.6.orig/arch/arm/mach-kirkwood/common.h 2010-05-15 17:03:59.970086925 +0200 +++ sound-2.6/arch/arm/mach-kirkwood/common.h 2010-05-15 17:04:05.014086284 +0200 @@ -16,6 +16,7 @@ struct mv643xx_eth_platform_data; struct mv_sata_platform_data; struct mvsdio_platform_data; struct mtd_partition; +struct orion_asoc_platform_data;
/* * Basic Kirkwood init functions used early by machine-setup. @@ -41,6 +42,7 @@ void kirkwood_i2c_init(void); 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_audio_init(void);
extern int kirkwood_tclk; extern struct sys_timer kirkwood_timer; Index: sound-2.6/arch/arm/mach-kirkwood/include/mach/kirkwood.h =================================================================== --- sound-2.6.orig/arch/arm/mach-kirkwood/include/mach/kirkwood.h 2010-05-15 17:03:59.954086213 +0200 +++ sound-2.6/arch/arm/mach-kirkwood/include/mach/kirkwood.h 2010-05-15 17:04:05.054086065 +0200 @@ -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. */ Index: sound-2.6/arch/arm/plat-orion/include/plat/audio.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/arch/arm/plat-orion/include/plat/audio.h 2010-05-15 17:04:05.070087019 +0200 @@ -0,0 +1,11 @@ +#ifndef __PLAT_AUDIO_H +#define __PLAT_AUDIO_H + +#include <linux/mbus.h> + +struct orion_asoc_platform_data { + u32 tclk; + struct mbus_dram_target_info *dram; + int burst; +}; +#endif
On Sat, May 15, 2010 at 05:29:59PM +0200, apatard@mandriva.com wrote:
This patch add audio related definitions and functions
Signed-off-by: Arnaud Patard apatard@mandriva.com
This patch looks OK but obviously I'm not really reviewing the arch side. The CPU side ASoC drivers need do depend on this, though, so the patches for that will need to go in through the same tree or do a merge of some kind. I don't know what the Orion maintainer's thoughts are there?
This patch is reponsible for enabling audio on the openrd client board
Signed-off-by: Arnaud Patard apatard@mandriva.com
Index: sound-2.6/arch/arm/mach-kirkwood/openrd-setup.c =================================================================== --- sound-2.6.orig/arch/arm/mach-kirkwood/openrd-setup.c 2010-05-15 17:03:48.962586499 +0200 +++ sound-2.6/arch/arm/mach-kirkwood/openrd-setup.c 2010-05-15 17:04:50.818586283 +0200 @@ -15,6 +15,7 @@ #include <linux/mtd/partitions.h> #include <linux/ata_platform.h> #include <linux/mv643xx_eth.h> +#include <linux/i2c.h> #include <asm/mach-types.h> #include <asm/mach/arch.h> #include <mach/kirkwood.h> @@ -60,6 +61,12 @@ static unsigned int openrd_mpp_config[] 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)
On Sat, May 15, 2010 at 05:30:00PM +0200, apatard@mandriva.com wrote:
This patch is reponsible for enabling audio on the openrd client board
Signed-off-by: Arnaud Patard apatard@mandriva.com
This is OK by me and there's no cross-tree dependencies for the merge so should go via the Orion tree I guess? For what it's worth
Acked-by: Mark Brown broonie@opensource.wolfsonmicro.com
This patch is adding a new control which has the following capabilities: - tlv - variable data size (for instance, 7 ou 8 bit) - double mixer - data range centered around 0
Signed-off-by: Arnaud Patard apatard@mandriva.com
Index: sound-2.6/include/sound/soc.h =================================================================== --- sound-2.6.orig/include/sound/soc.h 2010-05-14 09:28:38.152498194 +0200 +++ sound-2.6/include/sound/soc.h 2010-05-14 09:31:47.204499340 +0200 @@ -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_kcon 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 Index: sound-2.6/sound/soc/soc-core.c =================================================================== --- sound-2.6.orig/sound/soc/soc-core.c 2010-05-14 09:28:38.180548514 +0200 +++ sound-2.6/sound/soc/soc-core.c 2010-05-14 09:31:47.264497529 +0200 @@ -2352,6 +2352,101 @@ int snd_soc_limit_volume(struct snd_soc_ 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<<mc->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<<mc->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 * @clk_id: DAI specific clock ID
On Sat, May 15, 2010 at 05:30:01PM +0200, apatard@mandriva.com wrote:
This patch is adding a new control which has the following capabilities:
- tlv
- variable data size (for instance, 7 ou 8 bit)
- double mixer
- data range centered around 0
Signed-off-by: Arnaud Patard apatard@mandriva.com
I've applied this one. Please remember to CC both myself and Liam on ASoc patches.
This patch is adding a ASoC driver for the cs42l51 from Cirrus Logic. Master mode and spi mode are not supported.
Signed-off-by: Arnaud Patard apatard@mandriva.com
Index: sound-2.6/sound/soc/codecs/cs42l51.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/codecs/cs42l51.c 2010-05-15 17:26:39.598586210 +0200 @@ -0,0 +1,756 @@ +/* + * cs42l51.c + * + * ASoC Driver for Cirrus Logic CS42L51 codecs + * + * Copyright (c) 2010 Arnaud Patard apatard@mandriva.com + * + * 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 as published by + * the Free Software Foundation; either version 2 of the License + * + * 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 <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <linux/i2c.h> + +#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, CHIP_REV_ID); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to read I2C\n"); + goto error; + } + + if ((ret != MK_CHIP_REV(CHIP_ID, CHIP_REV_A)) && + (ret != MK_CHIP_REV(CHIP_ID, 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 = DAC_CTL_DATA_SEL(1) | DAC_CTL_AMUTE | DAC_CTL_DACSZ(0); + ret = snd_soc_write(codec, DAC_CTL, reg); + if (ret < 0) + goto error_alloc; + + /* route microphone */ + ret = snd_soc_write(codec, ADC_INPUT, 0xF0); + 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, 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, 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 char *mic_boost[] = { "+16dB", "+32dB"}; +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 soc_enum cs42l51_mic_boost = + SOC_ENUM_DOUBLE(MIC_CTL, 0, 1, 2, mic_boost); + +static const struct snd_kcontrol_new cs42l51_snd_controls[] = { + SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", + PCMA_VOL, PCMB_VOL, 7, 0xffffff99, 0x18, adc_pcm_tlv), + SOC_DOUBLE_R("PCM Playback Switch", PCMA_VOL, PCMB_VOL, 7, 1, 1), + SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", + AOUTA_VOL, AOUTB_VOL, 8, 0xffffff19, 0x18, aout_tlv), + SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", + ADCA_VOL, ADCB_VOL, 7, 0xffffff99, 0x18, adc_pcm_tlv), + SOC_DOUBLE_R("ADC Mixer Switch", ADCA_VOL, ADCB_VOL, 7, 1, 1), + SOC_SINGLE("Playback Deemphasis Switch", DAC_CTL, 3, 1, 0), + SOC_SINGLE("Auto-Mute Switch", DAC_CTL, 2, 1, 0), + SOC_SINGLE("Soft Ramp Switch", DAC_CTL, 1, 1, 0), + SOC_SINGLE("Zero Cross Switch", DAC_CTL, 0, 0, 0), + SOC_ENUM("Mic Boost", cs42l51_mic_boost), + SOC_SINGLE_TLV("Bass Volume", TONE_CTL, 0, 0xf, 1, tone_tlv), + SOC_SINGLE_TLV("Treble Volume", 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, POWER_CTL1); + value &= ~POWER_CTL1_PDN; + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + value |= POWER_CTL1_PDN; + break; + default: + case SND_SOC_DAPM_POST_PMD: + break; + } + snd_soc_write(w->codec, POWER_CTL1, value); + + return 0; +} + +static const char *cs42l51_dac_names[] = {"PCM->DAC", + "PCM->SPE->DAC", "ADC->DAC"}; +static const struct soc_enum cs42l51_dac_mux_enum = + SOC_ENUM_SINGLE(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(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(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); + +#define DAPM_EVENT_PRE_POST_PMD (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD) + +static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS("Mic Bias", MIC_POWER_CTL, 1, 1), + SND_SOC_DAPM_PGA_E("Left PGA", POWER_CTL1, 3, 1, NULL, 0, + cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD), + SND_SOC_DAPM_PGA_E("Right PGA", POWER_CTL1, 4, 1, NULL, 0, + cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture", + POWER_CTL1, 1, 1, + cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture", + POWER_CTL1, 2, 1, + cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback", + POWER_CTL1, 5, 1, + cs42l51_pdn_event, DAPM_EVENT_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback", + POWER_CTL1, 6, 1, + cs42l51_pdn_event, DAPM_EVENT_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", MIC_POWER_CTL, 2, 1, NULL, 0), + SND_SOC_DAPM_MIXER("Mic Preamp Right", 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: + printk(KERN_ERR "cs42l51: invalid DAI format\n"); + ret = -EINVAL; + } + + switch (SND_SOC_DAIFMT_MASTER_MASK) { + default: + case SND_SOC_DAIFMT_CBM_CFM: + cs42l51->func = MODE_SLAVE_AUTO; + break; + case SND_SOC_DAIFMT_CBS_CFS: + cs42l51->func = MODE_MASTER; + break; + } + + return ret; +} + +struct cs42l51_ratios { + unsigned int ratio; + unsigned char speed_mode; + unsigned char mclk; +}; + +static struct cs42l51_ratios slave_ratios[] = { + { 512, QSM_MODE, 0 }, { 768, QSM_MODE, 0 }, { 1024, QSM_MODE, 0 }, + { 1536, QSM_MODE, 0 }, { 2048, QSM_MODE, 0 }, { 3072, QSM_MODE, 0 }, + { 256, HSM_MODE, 0 }, { 384, HSM_MODE, 0 }, { 512, HSM_MODE, 0 }, + { 768, HSM_MODE, 0 }, { 1024, HSM_MODE, 0 }, { 1536, HSM_MODE, 0 }, + { 128, SSM_MODE, 0 }, { 192, SSM_MODE, 0 }, { 256, SSM_MODE, 0 }, + { 384, SSM_MODE, 0 }, { 512, SSM_MODE, 0 }, { 768, SSM_MODE, 0 }, + { 128, DSM_MODE, 0 }, { 192, DSM_MODE, 0 }, { 256, DSM_MODE, 0 }, + { 384, DSM_MODE, 0 }, +}; + +static struct cs42l51_ratios slave_auto_ratios[] = { + { 1024, QSM_MODE, 0 }, { 1536, QSM_MODE, 0 }, { 2048, QSM_MODE, 1 }, + { 3072, QSM_MODE, 1 }, + { 512, HSM_MODE, 0 }, { 768, HSM_MODE, 0 }, { 1024, HSM_MODE, 1 }, + { 1536, HSM_MODE, 1 }, + { 256, SSM_MODE, 0 }, { 384, SSM_MODE, 0 }, { 512, SSM_MODE, 1 }, + { 768, SSM_MODE, 1 }, + { 128, DSM_MODE, 0 }, { 192, DSM_MODE, 0 }, { 256, DSM_MODE, 1 }, + { 384, 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) { + printk(KERN_ERR "cs42l51: 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; + + 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 */ + printk(KERN_ERR "cs42l51: could not find matching ratio\n"); + return -EINVAL; + } + + intf_ctl = snd_soc_read(codec, INTF_CTL); + power_ctl = snd_soc_read(codec, MIC_POWER_CTL); + + intf_ctl &= ~(INTF_CTL_MASTER|INTF_CTL_ADC_I2S|INTF_CTL_DAC_FORMAT(7)); + power_ctl &= ~(MIC_POWER_CTL_SPEED(3)|MIC_POWER_CTL_MCLK_DIV2); + + switch (cs42l51->func) { + case MODE_MASTER: + intf_ctl |= INTF_CTL_MASTER; + power_ctl |= MIC_POWER_CTL_SPEED(ratios[i].speed_mode); + break; + case MODE_SLAVE: + power_ctl |= MIC_POWER_CTL_SPEED(ratios[i].speed_mode); + break; + case MODE_SLAVE_AUTO: + power_ctl |= MIC_POWER_CTL_AUTO; + break; + } + + switch (cs42l51->audio_mode) { + case SND_SOC_DAIFMT_I2S: + intf_ctl |= INTF_CTL_ADC_I2S | INTF_CTL_DAC_FORMAT(DAC_DIF_I2S); + break; + case SND_SOC_DAIFMT_LEFT_J: + intf_ctl |= INTF_CTL_DAC_FORMAT(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: + intf_ctl |= INTF_CTL_DAC_FORMAT(DAC_DIF_RJ16); + break; + case SNDRV_PCM_FORMAT_S18_3LE: + case SNDRV_PCM_FORMAT_S18_3BE: + intf_ctl |= INTF_CTL_DAC_FORMAT(DAC_DIF_RJ18); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S20_3BE: + intf_ctl |= INTF_CTL_DAC_FORMAT(DAC_DIF_RJ20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_BE: + intf_ctl |= INTF_CTL_DAC_FORMAT(DAC_DIF_RJ24); + break; + default: + printk(KERN_ERR "cs42l51: unknown format\n"); + return -EINVAL; + } + break; + default: + printk(KERN_ERR "cs42l51: unknown format\n"); + return -EINVAL; + } + + if (ratios[i].mclk) + power_ctl |= MIC_POWER_CTL_MCLK_DIV2; + + ret = snd_soc_write(codec, INTF_CTL, intf_ctl); + if (ret < 0) + return ret; + + ret = snd_soc_write(codec, 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 = DAC_OUT_CTL_DACA_MUTE | DAC_OUT_CTL_DACB_MUTE; + + reg = snd_soc_read(codec, DAC_OUT_CTL); + + if (mute) + reg |= mask; + else + reg &= ~mask; + + return snd_soc_write(codec, 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; + } + + dev_info(&pdev->dev, "CS42L51 ALSA SoC Codec\n"); + + 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 "can't add i2c driver\n"); + 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 apatard@mandriva.com"); +MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); + Index: sound-2.6/sound/soc/codecs/cs42l51.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/codecs/cs42l51.h 2010-05-15 17:13:44.342087567 +0200 @@ -0,0 +1,168 @@ +/* + * cs42l51.h + * + * ASoC Driver for Cirrus Logic CS42L51 codecs + * + * Copyright (c) 2010 Arnaud Patard apatard@mandriva.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 CHIP_ID 0x1B +#define CHIP_REV_A 0x00 +#define CHIP_REV_B 0x01 + +#define CHIP_REV_ID 0x01 +#define MK_CHIP_REV(a, b) ((a)<<3|(b)) + +#define POWER_CTL1 0x02 +#define POWER_CTL1_PDN_DACB (1<<6) +#define POWER_CTL1_PDN_DACA (1<<5) +#define POWER_CTL1_PDN_PGAB (1<<4) +#define POWER_CTL1_PDN_PGAA (1<<3) +#define POWER_CTL1_PDN_ADCB (1<<2) +#define POWER_CTL1_PDN_ADCA (1<<1) +#define POWER_CTL1_PDN (1<<0) + +#define MIC_POWER_CTL 0x03 +#define MIC_POWER_CTL_AUTO (1<<7) +#define MIC_POWER_CTL_SPEED(x) (((x)&3)<<5) +#define QSM_MODE 3 +#define HSM_MODE 2 +#define SSM_MODE 1 +#define DSM_MODE 0 +#define MIC_POWER_CTL_3ST_SP (1<<4) +#define MIC_POWER_CTL_PDN_MICB (1<<3) +#define MIC_POWER_CTL_PDN_MICA (1<<2) +#define MIC_POWER_CTL_PDN_BIAS (1<<1) +#define MIC_POWER_CTL_MCLK_DIV2 (1<<0) + +#define INTF_CTL 0x04 +#define INTF_CTL_LOOPBACK (1<<7) +#define INTF_CTL_MASTER (1<<6) +#define INTF_CTL_DAC_FORMAT(x) (((x)&7)<<3) +#define DAC_DIF_LJ24 0x00 +#define DAC_DIF_I2S 0x01 +#define DAC_DIF_RJ24 0x02 +#define DAC_DIF_RJ20 0x03 +#define DAC_DIF_RJ18 0x04 +#define DAC_DIF_RJ16 0x05 +#define INTF_CTL_ADC_I2S (1<<2) +#define INTF_CTL_DIGMIX (1<<1) +#define INTF_CTL_MICMIX (1<<0) + +#define MIC_CTL 0x05 +#define MIC_CTL_ADC_SNGVOL (1<<7) +#define MIC_CTL_ADCD_DBOOST (1<<6) +#define MIC_CTL_ADCA_DBOOST (1<<5) +#define MIC_CTL_MICBIAS_SEL (1<<4) +#define MIC_CTL_MICBIAS_LVL(x) (((x)&3)<<2) +#define MIC_CTL_MICB_BOOST (1<<1) +#define MIC_CTL_MICA_BOOST (1<<0) + +#define ADC_CTL 0x06 +#define ADC_CTL_ADCB_HPFEN (1<<7) +#define ADC_CTL_ADCB_HPFRZ (1<<6) +#define ADC_CTL_ADCA_HPFEN (1<<5) +#define ADC_CTL_ADCA_HPFRZ (1<<4) +#define ADC_CTL_SOFTB (1<<3) +#define ADC_CTL_ZCROSSB (1<<2) +#define ADC_CTL_SOFTA (1<<1) +#define ADC_CTL_ZCROSSA (1<<0) + +#define ADC_INPUT 0x07 +#define ADC_INPUT_AINB_MUX(x) (((x)&3)<<6) +#define ADC_INPUT_AINA_MUX(x) (((x)&3)<<4) +#define ADC_INPUT_INV_ADCB (1<<3) +#define ADC_INPUT_INV_ADCA (1<<2) +#define ADC_INPUT_ADCB_MUTE (1<<1) +#define ADC_INPUT_ADCA_MUTE (1<<0) + +#define DAC_OUT_CTL 0x08 +#define DAC_OUT_CTL_HP_GAIN(x) (((x)&7)<<5) +#define DAC_OUT_CTL_DAC_SNGVOL (1<<4) +#define DAC_OUT_CTL_INV_PCMB (1<<3) +#define DAC_OUT_CTL_INV_PCMA (1<<2) +#define DAC_OUT_CTL_DACB_MUTE (1<<1) +#define DAC_OUT_CTL_DACA_MUTE (1<<0) + +#define DAC_CTL 0x09 +#define DAC_CTL_DATA_SEL(x) (((x)&3)<<6) +#define DAC_CTL_FREEZE (1<<5) +#define DAC_CTL_DEEMPH (1<<3) +#define DAC_CTL_AMUTE (1<<2) +#define DAC_CTL_DACSZ(x) (((x)&3)<<0) + +#define ALC_PGA_CTL 0x0A +#define ALC_PGB_CTL 0x0B +#define ALC_PGX_ALCX_SRDIS (1<<7) +#define ALC_PGX_ALCX_ZCDIS (1<<6) +#define ALC_PGX_PGX_VOL(x) (((x)&0x1f)<<0) + +#define ADCA_ATT 0x0C +#define ADCB_ATT 0x0D + +#define ADCA_VOL 0x0E +#define ADCB_VOL 0x0F +#define PCMA_VOL 0x10 +#define PCMB_VOL 0x11 +#define MIX_MUTE_ADCMIX (1<<7) +#define MIX_VOLUME(x) (((x)&0x7f)<<0) + +#define CS42L51_BEEP_FREQ 0x12 +#define CS42L51_BEEP_VOL 0x13 +#define CS42L51_BEEP_CONF 0x14 + +#define TONE_CTL 0x15 +#define TONE_CTL_TREB(x) (((x)&0xf)<<4) +#define TONE_CTL_BASS(x) (((x)&0xf)<<0) + +#define AOUTA_VOL 0x16 +#define AOUTB_VOL 0x17 +#define PCM_MIXER 0x18 +#define LIMIT_THRES_DIS 0x19 +#define LIMIT_REL 0x1A +#define LIMIT_ATT 0x1B +#define ALC_EN 0x1C +#define ALC_REL 0x1D +#define ALC_THRES 0x1E +#define NOISE_CONF 0x1F + +#define STATUS 0x20 +#define STATUS_SP_CLKERR (1<<6) +#define STATUS_SPEA_OVFL (1<<5) +#define STATUS_SPEB_OVFL (1<<4) +#define STATUS_PCMA_OVFL (1<<3) +#define STATUS_PCMB_OVFL (1<<2) +#define STATUS_ADCA_OVFL (1<<1) +#define STATUS_ADCB_OVFL (1<<0) + +#define 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) + +struct cs42l51_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai cs42l51_dai; +extern struct snd_soc_codec_device soc_codec_device_cs42l51; +#endif Index: sound-2.6/sound/soc/codecs/Kconfig =================================================================== --- sound-2.6.orig/sound/soc/codecs/Kconfig 2010-05-15 17:10:21.222086550 +0200 +++ sound-2.6/sound/soc/codecs/Kconfig 2010-05-15 17:13:44.362087086 +0200 @@ -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 Index: sound-2.6/sound/soc/codecs/Makefile =================================================================== --- sound-2.6.orig/sound/soc/codecs/Makefile 2010-05-15 17:10:21.246086628 +0200 +++ sound-2.6/sound/soc/codecs/Makefile 2010-05-15 17:13:44.366086568 +0200 @@ -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- 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
On Sat, May 15, 2010 at 05:30:02PM +0200, apatard@mandriva.com wrote:
This patch is adding a ASoC driver for the cs42l51 from Cirrus Logic. Master mode and spi mode are not supported.
Signed-off-by: Arnaud Patard apatard@mandriva.com
This seems basically OK but there are a few things of varying severity to fix below. The main one is the namespacing of the constants in the header file.
- /* route microphone */
- ret = snd_soc_write(codec, ADC_INPUT, 0xF0);
- if (ret < 0)
goto error_alloc;
This really should be either chip default or user visible, but hopefully when explicit control gets added users will be able to work out which non-default value they were using so it'll be OK.
+static const char *mic_boost[] = { "+16dB", "+32dB"};
- SOC_ENUM("Mic Boost", cs42l51_mic_boost),
This really should be a SOC_SINGLE_TLV() - that will mean that userspace software like Pulse that tries to automate gain control over the full path can include the gain from the boost amplifier.
+static const char *cs42l51_dac_names[] = {"PCM->DAC",
- "PCM->SPE->DAC", "ADC->DAC"};
Conventionally these would have different names - something like "Direct PCM", "DSP PCM" and "ADC" - since it looks a little nicer in the UI of apps but it doesn't really matter since these are only ever likely to be seen by people configuring systems and not end users.
+#define DAPM_EVENT_PRE_POST_PMD (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD)
This really ought to get pushed into the main DAPM header file if it's going to stick, though I'd be surprised to see other users so it's not terribly urgent.
- if (!rates) {
printk(KERN_ERR "cs42l51: could not find a valid sample rate\n");
return -EINVAL;
- }
You could use dev_() printks based on codec->dev or dai->dev.
- dev_info(&pdev->dev, "CS42L51 ALSA SoC Codec\n");
Not a big fan of chatty boots but it's no big deal.
+#define CHIP_ID 0x1B +#define CHIP_REV_A 0x00 +#define CHIP_REV_B 0x01
The macros in the header file should all be namespaced - they're likely to collide with other things when the header is included in machine drivers (eg, the registers for the CODEC). Some of them are moderately safe but things like these ones seem likely to run into trouble and it'd be better to be consistent.
This patch enables support for the i2s controller available on orion/kirkwood platforms
Signed-off-by: Arnaud Patard apatard@mandriva.com
Index: sound-2.6/sound/soc/orion/Kconfig =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/orion/Kconfig 2010-05-15 17:06:16.890586343 +0200 @@ -0,0 +1,11 @@ +config SND_ORION_SOC + tristate "SoC Audio for the Marvell Orion/Kirkwood chip" + depends on ARCH_KIRKWOOD || ARCH_ARCH_ORION5X + help + Say Y or M if you want to add support for codecs attached to + the Orion/Kirkwood I2S interface. You will also need + to select the audio interfaces to support below. + +config SND_ORION_SOC_I2S + tristate + Index: sound-2.6/sound/soc/orion/orion-dma.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/orion/orion-dma.c 2010-05-15 17:06:16.898586075 +0200 @@ -0,0 +1,352 @@ +/* + * orion-dma.c + * + * (c) 2010 Arnaud Patard apatard@mandriva.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/mbus.h> +#include <sound/soc.h> +#include "orion-dma.h" +#include "orion.h" + +#define ORION_RATES \ + (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +#define ORION_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct orion_dma_priv { + struct snd_pcm_substream *play_stream; + struct snd_pcm_substream *rec_stream; + struct orion_dma_data *data; +}; + +static struct snd_pcm_hardware orion_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 = ORION_FORMATS, + .rates = ORION_RATES, + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = ORION_SND_MAX_PERIOD_BYTES * ORION_SND_MAX_PERIODS, + .period_bytes_min = ORION_SND_MIN_PERIOD_BYTES, + .period_bytes_max = ORION_SND_MAX_PERIOD_BYTES, + .periods_min = ORION_SND_MIN_PERIODS, + .periods_max = ORION_SND_MAX_PERIODS, + .fifo_size = 0, +}; + +static u64 orion_dma_dmamask = 0xFFFFFFFFUL; + +static irqreturn_t orion_dma_irq(int irq, void *dev_id) +{ + struct orion_dma_priv *prdata = dev_id; + struct orion_dma_data *priv = prdata->data; + unsigned long mask, status, cause; + + mask = readl(priv->io + ORION_INT_MASK); + status = readl(priv->io + ORION_INT_CAUSE) & mask; + + cause = readl(priv->io + ORION_ERR_CAUSE); + if (unlikely(cause)) { + printk(KERN_WARNING "%s: got err interrupt 0x%lx\n", + __func__, cause); + writel(cause, priv->io + ORION_ERR_CAUSE); + return IRQ_HANDLED; + } + + /* we've enabled only bytes interrupts ... */ + if (status & ~(ORION_INT_CAUSE_PLAY_BYTES | \ + ORION_INT_CAUSE_REC_BYTES)) { + printk(KERN_WARNING "%s: unexpected interrupt %lx\n", + __func__, status); + return IRQ_HANDLED; + } + + /* ack int */ + writel(status, priv->io + ORION_INT_CAUSE); + + if (status & ORION_INT_CAUSE_PLAY_BYTES) + snd_pcm_period_elapsed(prdata->play_stream); + + if (status & ORION_INT_CAUSE_REC_BYTES) + snd_pcm_period_elapsed(prdata->rec_stream); + + return IRQ_HANDLED; +} + +static int orion_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 orion_dma_data *priv; + struct orion_dma_priv *prdata = cpu_dai->private_data; + + priv = snd_soc_dai_get_dma_data(cpu_dai, substream); + snd_soc_set_runtime_hwparams(substream, &orion_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, + ORION_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 orion_dma_priv), GFP_KERNEL); + if (prdata == NULL) + return -ENOMEM; + + prdata->data = priv; + + err = request_irq(priv->irq, orion_dma_irq, IRQF_SHARED, + "orion-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 + ORION_ERR_MASK); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prdata->play_stream = substream; + else + prdata->rec_stream = substream; + + return 0; +} + +static int orion_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 orion_dma_priv *prdata = cpu_dai->private_data; + struct orion_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 + ORION_ERR_MASK); + free_irq(priv->irq, prdata); + kfree(prdata); + soc_runtime->dai->cpu_dai->private_data = NULL; + } + + return 0; +} + +static int orion_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 orion_dma_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int orion_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 orion_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 + ORION_PLAY_BYTE_INT_COUNT); + writel(runtime->dma_addr, priv->io + ORION_PLAY_BUF_ADDR); + writel(size, priv->io + ORION_PLAY_BUF_SIZE); + } else { + writel(count, priv->io + ORION_REC_BYTE_INT_COUNT); + writel(runtime->dma_addr, priv->io + ORION_REC_BUF_ADDR); + writel(size, priv->io + ORION_REC_BUF_SIZE); + } + + + return 0; +} + +static snd_pcm_uframes_t orion_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 orion_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 + ORION_PLAY_BYTE_COUNT)); + else + count = bytes_to_frames(substream->runtime, + readl(priv->io + ORION_REC_BYTE_COUNT)); + + return count; +} + +struct snd_pcm_ops orion_dma_ops = { + .open = orion_dma_open, + .close = orion_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = orion_dma_hw_params, + .hw_free = orion_dma_hw_free, + .prepare = orion_dma_prepare, + .pointer = orion_dma_pointer, +}; + +static int orion_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 = orion_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 orion_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 = &orion_dma_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->playback.channels_min) { + ret = orion_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + if (dai->capture.channels_min) { + ret = orion_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + + return 0; +} + +static void orion_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 orion_soc_platform = { + .name = "orion-dma", + .pcm_ops = &orion_dma_ops, + .pcm_new = orion_dma_new, + .pcm_free = orion_dma_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(orion_soc_platform); + +static int __init orion_soc_platform_init(void) +{ + return snd_soc_register_platform(&orion_soc_platform); +} +module_init(orion_soc_platform_init); + +static void __exit orion_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&orion_soc_platform); +} +module_exit(orion_soc_platform_exit); + +MODULE_AUTHOR("Arnaud Patard apatard@mandriva.com"); +MODULE_DESCRIPTION("Marvell Orion/Kirkwood Audio DMA module"); +MODULE_LICENSE("GPL"); + Index: sound-2.6/sound/soc/orion/orion.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/orion/orion.h 2010-05-15 17:06:16.938086518 +0200 @@ -0,0 +1,126 @@ +/* + * orion.h + * + * (c) 2010 Arnaud Patard apatard@mandriva.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _ORION_AUDIO_H +#define _ORION_AUDIO_H + +#define ORION_RECORD_WIN 0 +#define ORION_PLAYBACK_WIN 1 +#define ORION_MAX_AUDIO_WIN 2 + +#define ORION_AUDIO_WIN_BASE_REG(win) (0xA00 + ((win)<<3)) +#define ORION_AUDIO_WIN_CTRL_REG(win) (0xA04 + ((win)<<3)) + + +#define ORION_RECCTL 0x1000 +#define ORION_RECCTL_SPDIF_EN (1<<11) +#define ORION_RECCTL_I2S_EN (1<<10) +#define ORION_RECCTL_PAUSE (1<<9) +#define ORION_RECCTL_MUTE (1<<8) +#define ORION_RECCTL_BURST_MASK (3<<5) +#define ORION_RECCTL_BURST_128 (2<<5) +#define ORION_RECCTL_BURST_32 (1<<5) +#define ORION_RECCTL_MONO (1<<4) +#define ORION_RECCTL_MONO_CHAN_RIGHT (1<<3) +#define ORION_RECCTL_MONO_CHAN_LEFT (0<<3) +#define ORION_RECCTL_SIZE_MASK (7<<0) +#define ORION_RECCTL_SIZE_16 (7<<0) +#define ORION_RECCTL_SIZE_16_C (3<<0) +#define ORION_RECCTL_SIZE_20 (2<<0) +#define ORION_RECCTL_SIZE_24 (1<<0) +#define ORION_RECCTL_SIZE_32 (0<<0) + +#define ORION_REC_BUF_ADDR 0x1004 +#define ORION_REC_BUF_SIZE 0x1008 +#define ORION_REC_BYTE_COUNT 0x100C + +#define ORION_PLAYCTL 0x1100 +#define ORION_PLAYCTL_PLAY_BUSY (1<<16) +#define ORION_PLAYCTL_BURST_MASK (3<<11) +#define ORION_PLAYCTL_BURST_128 (2<<11) +#define ORION_PLAYCTL_BURST_32 (1<<11) +#define ORION_PLAYCTL_PAUSE (1<<9) +#define ORION_PLAYCTL_SPDIF_MUTE (1<<8) +#define ORION_PLAYCTL_I2S_MUTE (1<<7) +#define ORION_PLAYCTL_SPDIF_EN (1<<4) +#define ORION_PLAYCTL_I2S_EN (1<<3) +#define ORION_PLAYCTL_SIZE_MASK (7<<0) +#define ORION_PLAYCTL_SIZE_16 (7<<0) +#define ORION_PLAYCTL_SIZE_16_C (3<<0) +#define ORION_PLAYCTL_SIZE_20 (2<<0) +#define ORION_PLAYCTL_SIZE_24 (1<<0) +#define ORION_PLAYCTL_SIZE_32 (0<<0) + +#define ORION_PLAY_BUF_ADDR 0x1104 +#define ORION_PLAY_BUF_SIZE 0x1108 +#define ORION_PLAY_BYTE_COUNT 0x110C + +#define ORION_DCO_CTL 0x1204 +#define ORION_DCO_CTL_OFFSET_MASK (0xFFF<<2) +#define ORION_DCO_CTL_OFFSET_0 (0x800<<2) +#define ORION_DCO_CTL_FREQ_MASK (3<<0) +#define ORION_DCO_CTL_FREQ_11 (0<<0) +#define ORION_DCO_CTL_FREQ_12 (1<<0) +#define ORION_DCO_CTL_FREQ_24 (2<<0) + +#define ORION_DCO_SPCR_STATUS 0x120c +#define ORION_DCO_SPCR_STATUS_DCO_LOCK (1<<16) + +#define ORION_ERR_CAUSE 0x1300 +#define ORION_ERR_MASK 0x1304 + +#define ORION_INT_CAUSE 0x1308 +#define ORION_INT_MASK 0x130C +#define ORION_INT_CAUSE_PLAY_BYTES (1<<14) +#define ORION_INT_CAUSE_REC_BYTES (1<<13) +#define ORION_INT_CAUSE_DMA_PLAY_END (1<<7) +#define ORION_INT_CAUSE_DMA_PLAY_3Q (1<<6) +#define ORION_INT_CAUSE_DMA_PLAY_HALF (1<<5) +#define ORION_INT_CAUSE_DMA_PLAY_1Q (1<<4) +#define ORION_INT_CAUSE_DMA_REC_END (1<<3) +#define ORION_INT_CAUSE_DMA_REC_3Q (1<<2) +#define ORION_INT_CAUSE_DMA_REC_HALF (1<<1) +#define ORION_INT_CAUSE_DMA_REC_1Q (1<<0) + +#define ORION_REC_BYTE_INT_COUNT 0x1310 +#define ORION_PLAY_BYTE_INT_COUNT 0x1314 +#define ORION_BYTE_INT_COUNT_MASK 0xffffff + +#define ORION_I2S_PLAYCTL 0x2508 +#define ORION_I2S_RECCTL 0x2408 +#define ORION_I2S_CTL_JUST_MASK (0xf<<26) +#define ORION_I2S_CTL_LJ (0<<26) +#define ORION_I2S_CTL_I2S (5<<26) +#define ORION_I2S_CTL_RJ (8<<26) +#define ORION_I2S_CTL_SIZE_MASK (3<<30) +#define ORION_I2S_CTL_SIZE_16 (3<<30) +#define ORION_I2S_CTL_SIZE_20 (2<<30) +#define ORION_I2S_CTL_SIZE_24 (1<<30) +#define ORION_I2S_CTL_SIZE_32 (0<<30) + +#define ORION_AUDIO_BUF_MAX (16*1024*1024) + +/* Theses values come from the marvell alsa driver */ +/* need to find where they come from */ +#define ORION_SND_MIN_PERIODS 8 +#define ORION_SND_MAX_PERIODS 16 +#define ORION_SND_MIN_PERIOD_BYTES 0x4000 +#define ORION_SND_MAX_PERIOD_BYTES 0x4000 + +struct orion_dma_data { + struct resource *mem; + void __iomem *io; + int irq; + int burst; + struct mbus_dram_target_info *dram; +}; + +#endif Index: sound-2.6/sound/soc/orion/orion-i2s.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/orion/orion-i2s.c 2010-05-15 17:07:11.582586475 +0200 @@ -0,0 +1,510 @@ +/* + * orion-i2s.c + * + * (c) 2010 Arnaud Patard apatard@mandriva.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/mbus.h> +#include <linux/delay.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <plat/audio.h> +#include "orion-i2s.h" +#include "orion.h" + +#define DRV_NAME "orion-i2s" + +#define ORION_I2S_RATES \ + (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) +#define ORION_I2S_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + + +struct snd_soc_dai orion_i2s_dai; +static struct orion_dma_data *priv; + +static int orion_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 = ORION_I2S_CTL_RJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + mask = ORION_I2S_CTL_LJ; + break; + case SND_SOC_DAIFMT_I2S: + mask = ORION_I2S_CTL_I2S; + break; + default: + return -EINVAL; + } + + /* + * Set same format for playback and record + * This avoids some troubles. + */ + value = readl(priv->io+ORION_I2S_PLAYCTL); + value &= ~ORION_I2S_CTL_JUST_MASK; + value |= mask; + writel(value, priv->io+ORION_I2S_PLAYCTL); + + value = readl(priv->io+ORION_I2S_RECCTL); + value &= ~ORION_I2S_CTL_JUST_MASK; + value |= mask; + writel(value, priv->io+ORION_I2S_RECCTL); + + return 0; +} + +static inline void orion_set_dco(void __iomem *io, unsigned long rate) +{ + unsigned long value; + + value = ORION_DCO_CTL_OFFSET_0; + switch (rate) { + default: + case 44100: + value |= ORION_DCO_CTL_FREQ_11; + break; + case 48000: + value |= ORION_DCO_CTL_FREQ_12; + break; + case 96000: + value |= ORION_DCO_CTL_FREQ_24; + break; + } + writel(value, io + ORION_DCO_CTL); + + /* wait for dco locked */ + do { + cpu_relax(); + value = readl(io + ORION_DCO_SPCR_STATUS); + value &= ORION_DCO_SPCR_STATUS; + } while (value == 0); +} + +static int orion_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 = ORION_I2S_PLAYCTL; + reg = ORION_PLAYCTL; + } else { + i2s_reg = ORION_I2S_RECCTL; + reg = ORION_RECCTL; + } + + /* set dco conf */ + orion_set_dco(priv->io, params_rate(params)); + + i2s_value = readl(priv->io+i2s_reg); + i2s_value &= ~ORION_I2S_CTL_SIZE_MASK; + + value = readl(priv->io+reg); + value &= ~ORION_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 |= ORION_I2S_CTL_SIZE_16; + value |= ORION_PLAYCTL_SIZE_16_C; + break; + /* + * doesn't work... S20_3LE != orion/kirkwood 20bit format ? + * + case SNDRV_PCM_FORMAT_S20_3LE: + i2s_value |= ORION_I2S_CTL_SIZE_20; + value |= ORION_PLAYCTL_SIZE_20; + break; + */ + case SNDRV_PCM_FORMAT_S24_LE: + i2s_value |= ORION_I2S_CTL_SIZE_24; + value |= ORION_PLAYCTL_SIZE_24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + i2s_value |= ORION_I2S_CTL_SIZE_32; + value |= ORION_PLAYCTL_SIZE_32; + break; + default: + return -EINVAL; + } + writel(i2s_value, priv->io+i2s_reg); + writel(value, priv->io+reg); + + return 0; +} + +static int orion_i2s_play_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + unsigned long value; + + /* + * specs says ORION_PLAYCTL must be read 2 times before + * changing it. So read 1 time here and 1 later. + */ + value = readl(priv->io + ORION_PLAYCTL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* stop audio, enable interrupts */ + value = readl(priv->io + ORION_PLAYCTL); + value |= ORION_PLAYCTL_PAUSE; + writel(value, priv->io + ORION_PLAYCTL); + + value = readl(priv->io + ORION_INT_MASK); + value |= ORION_INT_CAUSE_PLAY_BYTES; + writel(value, priv->io + ORION_INT_MASK); + + /* configure audio & enable i2s playback */ + value = readl(priv->io + ORION_PLAYCTL); + value &= ~ORION_PLAYCTL_BURST_MASK; + value &= ~(ORION_PLAYCTL_PAUSE|ORION_PLAYCTL_SPDIF_EN); + + if (priv->burst == 32) + value |= ORION_PLAYCTL_BURST_32; + else + value |= ORION_PLAYCTL_BURST_128; + value |= ORION_PLAYCTL_I2S_EN; + writel(value, priv->io + ORION_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* stop audio, disable interrupts */ + value = readl(priv->io + ORION_PLAYCTL); + value |= ORION_PLAYCTL_PAUSE; + writel(value, priv->io + ORION_PLAYCTL); + + value = readl(priv->io + ORION_INT_MASK); + value &= ~ORION_INT_CAUSE_PLAY_BYTES; + writel(value, priv->io + ORION_INT_MASK); + + /* disable all playbacks */ + value = readl(priv->io + ORION_PLAYCTL); + value &= ~(ORION_PLAYCTL_I2S_EN | ORION_PLAYCTL_SPDIF_EN); + writel(value, priv->io + ORION_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + value = readl(priv->io + ORION_PLAYCTL); + value |= ORION_PLAYCTL_PAUSE; + writel(value, priv->io + ORION_PLAYCTL); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + value = readl(priv->io + ORION_PLAYCTL); + value &= ~ORION_PLAYCTL_PAUSE; + writel(value, priv->io + ORION_PLAYCTL); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int orion_i2s_rec_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + unsigned long value; + + value = readl(priv->io + ORION_RECCTL); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* stop audio, enable interrupts */ + value = readl(priv->io + ORION_RECCTL); + value |= ORION_RECCTL_PAUSE; + writel(value, priv->io + ORION_RECCTL); + + value = readl(priv->io + ORION_INT_MASK); + value |= ORION_INT_CAUSE_REC_BYTES; + writel(value, priv->io + ORION_INT_MASK); + + /* configure audio & enable i2s record */ + value = readl(priv->io + ORION_RECCTL); + value &= ~ORION_RECCTL_BURST_MASK; + value &= ~ORION_RECCTL_MONO; + value &= ~(ORION_RECCTL_PAUSE | ORION_RECCTL_SPDIF_EN); + + if (priv->burst == 32) + value |= ORION_RECCTL_BURST_32; + else + value |= ORION_RECCTL_BURST_128; + value |= ORION_RECCTL_I2S_EN; + + writel(value, priv->io + ORION_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* stop audio, disable interrupts */ + value = readl(priv->io + ORION_RECCTL); + value |= ORION_RECCTL_PAUSE; + writel(value, priv->io + ORION_RECCTL); + + value = readl(priv->io + ORION_INT_MASK); + value &= ~ORION_INT_CAUSE_REC_BYTES; + writel(value, priv->io + ORION_INT_MASK); + + /* disable all records */ + value = readl(priv->io + ORION_RECCTL); + value &= ~(ORION_RECCTL_I2S_EN | ORION_RECCTL_SPDIF_EN); + writel(value, priv->io + ORION_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + value = readl(priv->io + ORION_RECCTL); + value |= ORION_RECCTL_PAUSE; + writel(value, priv->io + ORION_RECCTL); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + value = readl(priv->io + ORION_RECCTL); + value &= ~ORION_RECCTL_PAUSE; + writel(value, priv->io + ORION_RECCTL); + break; + + default: + return -EINVAL; + break; + } + + return 0; +} + +static int orion_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return orion_i2s_play_trigger(substream, cmd, dai); + else + return orion_i2s_rec_trigger(substream, cmd, dai); + + return 0; +} + +static void orion_i2s_conf_mbus_windows(void __iomem *base, + struct mbus_dram_target_info *dram) +{ + int win_num; + + /* First disable and clear windows */ + for (win_num = 0; win_num < ORION_MAX_AUDIO_WIN; win_num++) { + writel(0, base + ORION_AUDIO_WIN_CTRL_REG(win_num)); + writel(0, base + ORION_AUDIO_WIN_BASE_REG(win_num)); + } + + /* Setup windows for DDR */ + for (win_num = 0; win_num < ORION_MAX_AUDIO_WIN; win_num++) { + /* We will set the Window to DRAM_CS1 in default */ + struct mbus_dram_window *cs = &dram->cs[1]; + writel(cs->base & 0xffff0000, + base + ORION_AUDIO_WIN_BASE_REG(win_num)); + writel(((cs->size - 1) & 0xffff0000) | + (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1, + base + ORION_AUDIO_WIN_CTRL_REG(win_num)); + } +} + +static int orion_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 + ORION_INT_CAUSE); + writel(0, priv->io + ORION_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 + ORION_PLAYCTL); + value &= ~(ORION_PLAYCTL_I2S_EN|ORION_PLAYCTL_SPDIF_EN); + writel(value, priv->io + ORION_PLAYCTL); + + value = readl(priv->io + ORION_RECCTL); + value &= ~(ORION_RECCTL_I2S_EN | ORION_RECCTL_SPDIF_EN); + writel(value, priv->io + ORION_RECCTL); + + return 0; + +} + +static void orion_i2s_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ +} + +static struct snd_soc_dai_ops orion_i2s_dai_ops = { + .trigger = orion_i2s_trigger, + .hw_params = orion_i2s_hw_params, + .set_fmt = orion_i2s_set_fmt, +}; + + +struct snd_soc_dai orion_i2s_dai = { + .name = DRV_NAME, + .id = 0, + .probe = orion_i2s_probe, + .remove = orion_i2s_remove, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = ORION_I2S_RATES, + .formats = ORION_I2S_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = ORION_I2S_RATES, + .formats = ORION_I2S_FORMATS,}, + .ops = &orion_i2s_dai_ops, +}; +EXPORT_SYMBOL_GPL(orion_i2s_dai); + +static __devinit int orion_i2s_dev_probe(struct platform_device *pdev) +{ + struct resource *mem; + struct orion_asoc_platform_data *data = + pdev->dev.platform_data; + int err; + + priv = kzalloc(sizeof(struct orion_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; + } + + orion_i2s_conf_mbus_windows(priv->io, data->dram); + priv->dram = data->dram; + priv->burst = data->burst; + + orion_i2s_dai.capture.dma_data = priv; + orion_i2s_dai.playback.dma_data = priv; + + return snd_soc_register_dai(&orion_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 orion_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(&orion_i2s_dai); + return 0; +} + +static struct platform_driver orion_i2s_driver = { + .probe = orion_i2s_dev_probe, + .remove = orion_i2s_dev_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init orion_i2s_init(void) +{ + return platform_driver_register(&orion_i2s_driver); +} +module_init(orion_i2s_init); + +static void __exit orion_i2s_exit(void) +{ + platform_driver_unregister(&orion_i2s_driver); +} +module_exit(orion_i2s_exit); + +/* Module information */ +MODULE_AUTHOR("Arnaud Patard, apatard@mandriva.com"); +MODULE_DESCRIPTION("Orion/Kirkwood I2S SoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:orion-i2s"); Index: sound-2.6/sound/soc/orion/Makefile =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/orion/Makefile 2010-05-15 17:06:16.962086209 +0200 @@ -0,0 +1,6 @@ +snd-soc-orion-objs := orion-dma.o +snd-soc-orion-i2s-objs := orion-i2s.o + +obj-$(CONFIG_SND_ORION_SOC) += snd-soc-orion.o +obj-$(CONFIG_SND_ORION_SOC_I2S) += snd-soc-orion-i2s.o + Index: sound-2.6/sound/soc/Makefile =================================================================== --- sound-2.6.orig/sound/soc/Makefile 2010-05-15 17:02:58.526586319 +0200 +++ sound-2.6/sound/soc/Makefile 2010-05-15 17:06:16.970086495 +0200 @@ -8,6 +8,7 @@ obj-$(CONFIG_SND_SOC) += blackfin/ obj-$(CONFIG_SND_SOC) += davinci/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += imx/ +obj-$(CONFIG_SND_SOC) += orion/ obj-$(CONFIG_SND_SOC) += omap/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += s3c24xx/ Index: sound-2.6/sound/soc/orion/orion-dma.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/orion/orion-dma.h 2010-05-15 17:06:16.978086205 +0200 @@ -0,0 +1,17 @@ +/* + * orion-dma.h + * + * (c) 2010 Arnaud Patard apatard@mandriva.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _ORION_DMA_H +#define _ORION_DMA_H + +extern struct snd_soc_platform orion_soc_platform; + +#endif Index: sound-2.6/sound/soc/orion/orion-i2s.h =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/orion/orion-i2s.h 2010-05-15 17:06:16.994086084 +0200 @@ -0,0 +1,17 @@ +/* + * orion-i2s.h + * + * (c) 2010 Arnaud Patard apatard@mandriva.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _ORION_I2S_H +#define _ORION_I2S_H + +extern struct snd_soc_dai orion_i2s_dai; + +#endif Index: sound-2.6/sound/soc/Kconfig =================================================================== --- sound-2.6.orig/sound/soc/Kconfig 2010-05-15 17:02:58.506587369 +0200 +++ sound-2.6/sound/soc/Kconfig 2010-05-15 17:06:17.006088097 +0200 @@ -30,6 +30,7 @@ source "sound/soc/blackfin/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/fsl/Kconfig" source "sound/soc/imx/Kconfig" +source "sound/soc/orion/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig"
On Sat, May 15, 2010 at 05:30:03PM +0200, apatard@mandriva.com wrote:
This patch enables support for the i2s controller available on orion/kirkwood platforms
Signed-off-by: Arnaud Patard apatard@mandriva.com
All looks sensible enough, CCing in Liam. One thing...
- if (status & ~(ORION_INT_CAUSE_PLAY_BYTES | \
ORION_INT_CAUSE_REC_BYTES)) {
printk(KERN_WARNING "%s: unexpected interrupt %lx\n",
__func__, status);
return IRQ_HANDLED;
- }
Did the driver really handle the interrupt here? It's not even written an ack back so IRQ_NONE might be a better response (the problem with using IRQ_NONE before was that the driver may have handled some transfer interrupts before it got the empty status).
- /* First disable and clear windows */
- for (win_num = 0; win_num < ORION_MAX_AUDIO_WIN; win_num++) {
- writel(0, base + ORION_AUDIO_WIN_CTRL_REG(win_num));
- writel(0, base + ORION_AUDIO_WIN_BASE_REG(win_num));
- }
- /* Setup windows for DDR */
- for (win_num = 0; win_num < ORION_MAX_AUDIO_WIN; win_num++) {
- /* We will set the Window to DRAM_CS1 in default */
- struct mbus_dram_window *cs = &dram->cs[1];
- writel(cs->base & 0xffff0000,
- base + ORION_AUDIO_WIN_BASE_REG(win_num));
- writel(((cs->size - 1) & 0xffff0000) |
- (cs->mbus_attr << 8) |
- (dram->mbus_dram_target_id << 4) | 1,
- base + ORION_AUDIO_WIN_CTRL_REG(win_num));
- }
+}
The i2s controller has one window for playback, so that window must be set according to the current dma address, i.e, if the dma address falls into csX then the window should be configured to csX. same for record. sorry for the late comment. saeed
This patch is adding support for openrd client platforms. It's using the cs42l51 codec and has one mic and one speaker plugs.
Signed-off-by: Arnaud Patard apatard@mandriva.com
Index: sound-2.6/sound/soc/orion/Kconfig =================================================================== --- sound-2.6.orig/sound/soc/orion/Kconfig 2010-05-15 17:06:16.890586343 +0200 +++ sound-2.6/sound/soc/orion/Kconfig 2010-05-15 17:07:30.990586832 +0200 @@ -9,3 +9,12 @@ config SND_ORION_SOC config SND_ORION_SOC_I2S tristate
+config SND_KIRKWOOD_SOC_OPENRD + tristate "SoC Audio support for Kirkwood Openrd Client" + depends on SND_ORION_SOC && MACH_OPENRD_CLIENT + select SND_ORION_SOC_I2S + select SND_SOC_CS42L51 + help + Say Y if you want to add support for SoC audio on + Openrd Client. + Index: sound-2.6/sound/soc/orion/Makefile =================================================================== --- sound-2.6.orig/sound/soc/orion/Makefile 2010-05-15 17:06:16.962086209 +0200 +++ sound-2.6/sound/soc/orion/Makefile 2010-05-15 17:07:31.006586283 +0200 @@ -4,3 +4,6 @@ snd-soc-orion-i2s-objs := orion-i2s.o obj-$(CONFIG_SND_ORION_SOC) += snd-soc-orion.o obj-$(CONFIG_SND_ORION_SOC_I2S) += snd-soc-orion-i2s.o
+snd-soc-openrd-objs := kirkwood-openrd.o + +obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o Index: sound-2.6/sound/soc/orion/kirkwood-openrd.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/orion/kirkwood-openrd.c 2010-05-15 17:07:31.046586004 +0200 @@ -0,0 +1,126 @@ +/* + * kirkwood-openrd.c + * + * (c) 2010 Arnaud Patard apatard@mandriva.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <mach/kirkwood.h> +#include <plat/audio.h> +#include <asm/mach-types.h> +#include "orion-i2s.h" +#include "orion-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_CBM_CFM; + 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 = &orion_i2s_dai, + .codec_dai = &cs42l51_dai, + .ops = &openrd_client_ops, +}, +}; + + +static struct snd_soc_card openrd_client = { + .name = "OpenRD Client", + .platform = &orion_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 apatard@mandriva.com"); +MODULE_DESCRIPTION("ALSA SoC OpenRD Client"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:soc-audio");
On Sat, May 15, 2010 at 05:30:04PM +0200, apatard@mandriva.com wrote:
This patch is adding support for openrd client platforms. It's using the cs42l51 codec and has one mic and one speaker plugs.
Signed-off-by: Arnaud Patard apatard@mandriva.com
This all looks OK - CCing in Liam (and obviously this depends on both the CODEC and CPU drivers).
Index: sound-2.6/sound/soc/orion/Kconfig
--- sound-2.6.orig/sound/soc/orion/Kconfig 2010-05-15 17:06:16.890586343 +0200 +++ sound-2.6/sound/soc/orion/Kconfig 2010-05-15 17:07:30.990586832 +0200 @@ -9,3 +9,12 @@ config SND_ORION_SOC config SND_ORION_SOC_I2S tristate
+config SND_KIRKWOOD_SOC_OPENRD
- tristate "SoC Audio support for Kirkwood Openrd Client"
- depends on SND_ORION_SOC && MACH_OPENRD_CLIENT
- select SND_ORION_SOC_I2S
- select SND_SOC_CS42L51
- help
Say Y if you want to add support for SoC audio on
Openrd Client.
Index: sound-2.6/sound/soc/orion/Makefile
--- sound-2.6.orig/sound/soc/orion/Makefile 2010-05-15 17:06:16.962086209 +0200 +++ sound-2.6/sound/soc/orion/Makefile 2010-05-15 17:07:31.006586283 +0200 @@ -4,3 +4,6 @@ snd-soc-orion-i2s-objs := orion-i2s.o obj-$(CONFIG_SND_ORION_SOC) += snd-soc-orion.o obj-$(CONFIG_SND_ORION_SOC_I2S) += snd-soc-orion-i2s.o
+snd-soc-openrd-objs := kirkwood-openrd.o
+obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o Index: sound-2.6/sound/soc/orion/kirkwood-openrd.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ sound-2.6/sound/soc/orion/kirkwood-openrd.c 2010-05-15 17:07:31.046586004 +0200 @@ -0,0 +1,126 @@ +/*
- kirkwood-openrd.c
- (c) 2010 Arnaud Patard apatard@mandriva.com
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the
- Free Software Foundation; either version 2 of the License, or (at your
- option) any later version.
- */
+#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <mach/kirkwood.h> +#include <plat/audio.h> +#include <asm/mach-types.h> +#include "orion-i2s.h" +#include "orion-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_CBM_CFM;
- 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 = &orion_i2s_dai,
- .codec_dai = &cs42l51_dai,
- .ops = &openrd_client_ops,
+}, +};
+static struct snd_soc_card openrd_client = {
- .name = "OpenRD Client",
- .platform = &orion_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 apatard@mandriva.com"); +MODULE_DESCRIPTION("ALSA SoC OpenRD Client"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:soc-audio");
participants (3)
-
apatard@mandriva.com
-
Mark Brown
-
saeed bishara