diff options
Diffstat (limited to 'sound/soc')
49 files changed, 5313 insertions, 273 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 97b25523317..27658521516 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -28,6 +28,7 @@ source "sound/soc/at91/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/fsl/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 30414037763..4869c9ae7a0 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,4 +1,4 @@ snd-soc-core-objs := soc-core.o soc-dapm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o -obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ s3c24xx/ sh/ +obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ s3c24xx/ sh/ fsl/ diff --git a/sound/soc/at91/at91-pcm.c b/sound/soc/at91/at91-pcm.c index b39b95a4704..67c88e322fb 100644 --- a/sound/soc/at91/at91-pcm.c +++ b/sound/soc/at91/at91-pcm.c @@ -23,7 +23,6 @@ #include <linux/dma-mapping.h> #include <linux/atmel_pdc.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> diff --git a/sound/soc/at91/at91-ssc.c b/sound/soc/at91/at91-ssc.c index 3d4e32cff75..f642d2dd4ec 100644 --- a/sound/soc/at91/at91-ssc.c +++ b/sound/soc/at91/at91-ssc.c @@ -22,7 +22,6 @@ #include <linux/clk.h> #include <linux/atmel_pdc.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> diff --git a/sound/soc/at91/eti_b1_wm8731.c b/sound/soc/at91/eti_b1_wm8731.c index 820a676c56b..ad3ad9d662f 100644 --- a/sound/soc/at91/eti_b1_wm8731.c +++ b/sound/soc/at91/eti_b1_wm8731.c @@ -28,7 +28,6 @@ #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 78248808a9d..898a7d36328 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -37,3 +37,6 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270 +config SND_SOC_TLV320AIC3X + tristate + depends on SND_SOC && I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 7ad78e36d50..c6e5338c266 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -4,6 +4,7 @@ snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o snd-soc-wm9712-objs := wm9712.o snd-soc-cs4270-objs := cs4270.o +snd-soc-tlv320aic3x-objs := tlv320aic3x.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o @@ -11,3 +12,4 @@ obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index 0b8a6f8b366..242130cf1ab 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -19,7 +19,6 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/ac97_codec.h> diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index dab22cc97ea..bf2ab72d49b 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -28,7 +28,6 @@ #include <linux/module.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/soc.h> #include <sound/initval.h> @@ -48,12 +47,130 @@ struct cs4270_private { unsigned int mode; /* The mode (I2S or left-justified) */ }; -/* The number of MCLK/LRCK ratios supported by the CS4270 */ -#define NUM_MCLK_RATIOS 9 +/* + * The codec isn't really big-endian or little-endian, since the I2S + * interface requires data to be sent serially with the MSbit first. + * However, to support BE and LE I2S devices, we specify both here. That + * way, ALSA will always match the bit patterns. + */ +#define CS4270_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + 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_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) + +#ifdef USE_I2C + +/* CS4270 registers addresses */ +#define CS4270_CHIPID 0x01 /* Chip ID */ +#define CS4270_PWRCTL 0x02 /* Power Control */ +#define CS4270_MODE 0x03 /* Mode Control */ +#define CS4270_FORMAT 0x04 /* Serial Format, ADC/DAC Control */ +#define CS4270_TRANS 0x05 /* Transition Control */ +#define CS4270_MUTE 0x06 /* Mute Control */ +#define CS4270_VOLA 0x07 /* DAC Channel A Volume Control */ +#define CS4270_VOLB 0x08 /* DAC Channel B Volume Control */ + +#define CS4270_FIRSTREG 0x01 +#define CS4270_LASTREG 0x08 +#define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1) -/* The actual MCLK/LRCK ratios, in increasing numerical order */ -static unsigned int mclk_ratios[NUM_MCLK_RATIOS] = - {64, 96, 128, 192, 256, 384, 512, 768, 1024}; +/* Bit masks for the CS4270 registers */ +#define CS4270_CHIPID_ID 0xF0 +#define CS4270_CHIPID_REV 0x0F +#define CS4270_PWRCTL_FREEZE 0x80 +#define CS4270_PWRCTL_PDN_ADC 0x20 +#define CS4270_PWRCTL_PDN_DAC 0x02 +#define CS4270_PWRCTL_PDN 0x01 +#define CS4270_MODE_SPEED_MASK 0x30 +#define CS4270_MODE_1X 0x00 +#define CS4270_MODE_2X 0x10 +#define CS4270_MODE_4X 0x20 +#define CS4270_MODE_SLAVE 0x30 +#define CS4270_MODE_DIV_MASK 0x0E +#define CS4270_MODE_DIV1 0x00 +#define CS4270_MODE_DIV15 0x02 +#define CS4270_MODE_DIV2 0x04 +#define CS4270_MODE_DIV3 0x06 +#define CS4270_MODE_DIV4 0x08 +#define CS4270_MODE_POPGUARD 0x01 +#define CS4270_FORMAT_FREEZE_A 0x80 +#define CS4270_FORMAT_FREEZE_B 0x40 +#define CS4270_FORMAT_LOOPBACK 0x20 +#define CS4270_FORMAT_DAC_MASK 0x18 +#define CS4270_FORMAT_DAC_LJ 0x00 +#define CS4270_FORMAT_DAC_I2S 0x08 +#define CS4270_FORMAT_DAC_RJ16 0x18 +#define CS4270_FORMAT_DAC_RJ24 0x10 +#define CS4270_FORMAT_ADC_MASK 0x01 +#define CS4270_FORMAT_ADC_LJ 0x00 +#define CS4270_FORMAT_ADC_I2S 0x01 +#define CS4270_TRANS_ONE_VOL 0x80 +#define CS4270_TRANS_SOFT 0x40 +#define CS4270_TRANS_ZERO 0x20 +#define CS4270_TRANS_INV_ADC_A 0x08 +#define CS4270_TRANS_INV_ADC_B 0x10 +#define CS4270_TRANS_INV_DAC_A 0x02 +#define CS4270_TRANS_INV_DAC_B 0x04 +#define CS4270_TRANS_DEEMPH 0x01 +#define CS4270_MUTE_AUTO 0x20 +#define CS4270_MUTE_ADC_A 0x08 +#define CS4270_MUTE_ADC_B 0x10 +#define CS4270_MUTE_POLARITY 0x04 +#define CS4270_MUTE_DAC_A 0x01 +#define CS4270_MUTE_DAC_B 0x02 + +/* + * Clock Ratio Selection for Master Mode with I2C enabled + * + * The data for this chart is taken from Table 5 of the CS4270 reference + * manual. + * + * This table is used to determine how to program the Mode Control register. + * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling + * rates the CS4270 currently supports. + * + * Each element in this array corresponds to the ratios in mclk_ratios[]. + * These two arrays need to be in sync. + * + * 'speed_mode' is the corresponding bit pattern to be written to the + * MODE bits of the Mode Control Register + * + * 'mclk' is the corresponding bit pattern to be wirten to the MCLK bits of + * the Mode Control Register. + * + * In situations where a single ratio is represented by multiple speed + * modes, we favor the slowest speed. E.g, for a ratio of 128, we pick + * double-speed instead of quad-speed. However, the CS4270 errata states + * that Divide-By-1.5 can cause failures, so we avoid that mode where + * possible. + * + * ERRATA: There is an errata for the CS4270 where divide-by-1.5 does not + * work if VD = 3.3V. If this effects you, select the + * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will + * never select any sample rates that require divide-by-1.5. + */ +static struct { + unsigned int ratio; + u8 speed_mode; + u8 mclk; +} cs4270_mode_ratios[] = { + {64, CS4270_MODE_4X, CS4270_MODE_DIV1}, +#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA + {96, CS4270_MODE_4X, CS4270_MODE_DIV15}, +#endif + {128, CS4270_MODE_2X, CS4270_MODE_DIV1}, + {192, CS4270_MODE_4X, CS4270_MODE_DIV3}, + {256, CS4270_MODE_1X, CS4270_MODE_DIV1}, + {384, CS4270_MODE_2X, CS4270_MODE_DIV3}, + {512, CS4270_MODE_1X, CS4270_MODE_DIV2}, + {768, CS4270_MODE_1X, CS4270_MODE_DIV3}, + {1024, CS4270_MODE_1X, CS4270_MODE_DIV4} +}; + +/* The number of MCLK/LRCK ratios supported by the CS4270 */ +#define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios) /* * Determine the CS4270 samples rates. @@ -97,7 +214,7 @@ static int cs4270_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, cs4270->mclk = freq; for (i = 0; i < NUM_MCLK_RATIOS; i++) { - unsigned int rate = freq / mclk_ratios[i]; + unsigned int rate = freq / cs4270_mode_ratios[i].ratio; rates |= snd_pcm_rate_to_rate_bit(rate); if (rate < rate_min) rate_min = rate; @@ -155,80 +272,6 @@ static int cs4270_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, } /* - * The codec isn't really big-endian or little-endian, since the I2S - * interface requires data to be sent serially with the MSbit first. - * However, to support BE and LE I2S devices, we specify both here. That - * way, ALSA will always match the bit patterns. - */ -#define CS4270_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ - 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_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \ - SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) - -#ifdef USE_I2C - -/* CS4270 registers addresses */ -#define CS4270_CHIPID 0x01 /* Chip ID */ -#define CS4270_PWRCTL 0x02 /* Power Control */ -#define CS4270_MODE 0x03 /* Mode Control */ -#define CS4270_FORMAT 0x04 /* Serial Format, ADC/DAC Control */ -#define CS4270_TRANS 0x05 /* Transition Control */ -#define CS4270_MUTE 0x06 /* Mute Control */ -#define CS4270_VOLA 0x07 /* DAC Channel A Volume Control */ -#define CS4270_VOLB 0x08 /* DAC Channel B Volume Control */ - -#define CS4270_FIRSTREG 0x01 -#define CS4270_LASTREG 0x08 -#define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1) - -/* Bit masks for the CS4270 registers */ -#define CS4270_CHIPID_ID 0xF0 -#define CS4270_CHIPID_REV 0x0F -#define CS4270_PWRCTL_FREEZE 0x80 -#define CS4270_PWRCTL_PDN_ADC 0x20 -#define CS4270_PWRCTL_PDN_DAC 0x02 -#define CS4270_PWRCTL_PDN 0x01 -#define CS4270_MODE_SPEED_MASK 0x30 -#define CS4270_MODE_1X 0x00 -#define CS4270_MODE_2X 0x10 -#define CS4270_MODE_4X 0x20 -#define CS4270_MODE_SLAVE 0x30 -#define CS4270_MODE_DIV_MASK 0x0E -#define CS4270_MODE_DIV1 0x00 -#define CS4270_MODE_DIV15 0x02 -#define CS4270_MODE_DIV2 0x04 -#define CS4270_MODE_DIV3 0x06 -#define CS4270_MODE_DIV4 0x08 -#define CS4270_MODE_POPGUARD 0x01 -#define CS4270_FORMAT_FREEZE_A 0x80 -#define CS4270_FORMAT_FREEZE_B 0x40 -#define CS4270_FORMAT_LOOPBACK 0x20 -#define CS4270_FORMAT_DAC_MASK 0x18 -#define CS4270_FORMAT_DAC_LJ 0x00 -#define CS4270_FORMAT_DAC_I2S 0x08 -#define CS4270_FORMAT_DAC_RJ16 0x18 -#define CS4270_FORMAT_DAC_RJ24 0x10 -#define CS4270_FORMAT_ADC_MASK 0x01 -#define CS4270_FORMAT_ADC_LJ 0x00 -#define CS4270_FORMAT_ADC_I2S 0x01 -#define CS4270_TRANS_ONE_VOL 0x80 -#define CS4270_TRANS_SOFT 0x40 -#define CS4270_TRANS_ZERO 0x20 -#define CS4270_TRANS_INV_ADC_A 0x08 -#define CS4270_TRANS_INV_ADC_B 0x10 -#define CS4270_TRANS_INV_DAC_A 0x02 -#define CS4270_TRANS_INV_DAC_B 0x04 -#define CS4270_TRANS_DEEMPH 0x01 -#define CS4270_MUTE_AUTO 0x20 -#define CS4270_MUTE_ADC_A 0x08 -#define CS4270_MUTE_ADC_B 0x10 -#define CS4270_MUTE_POLARITY 0x04 -#define CS4270_MUTE_DAC_A 0x01 -#define CS4270_MUTE_DAC_B 0x02 - -/* * A list of addresses on which this CS4270 could use. I2C addresses are * 7 bits. For the CS4270, the upper four bits are always 1001, and the * lower three bits are determined via the AD2, AD1, and AD0 pins @@ -315,53 +358,6 @@ static int cs4270_i2c_write(struct snd_soc_codec *codec, unsigned int reg, } /* - * Clock Ratio Selection for Master Mode with I2C enabled - * - * The data for this chart is taken from Table 5 of the CS4270 reference - * manual. - * - * This table is used to determine how to program the Mode Control register. - * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling - * rates the CS4270 currently supports. - * - * Each element in this array corresponds to the ratios in mclk_ratios[]. - * These two arrays need to be in sync. - * - * 'speed_mode' is the corresponding bit pattern to be written to the - * MODE bits of the Mode Control Register - * - * 'mclk' is the corresponding bit pattern to be wirten to the MCLK bits of - * the Mode Control Register. - * - * In situations where a single ratio is represented by multiple speed - * modes, we favor the slowest speed. E.g, for a ratio of 128, we pick - * double-speed instead of quad-speed. However, the CS4270 errata states - * that Divide-By-1.5 can cause failures, so we avoid that mode where - * possible. - * - * ERRATA: There is an errata for the CS4270 where divide-by-1.5 does not - * work if VD = 3.3V. If this effects you, select the - * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will - * never select any sample rates that require divide-by-1.5. - */ -static struct { - u8 speed_mode; - u8 mclk; -} cs4270_mode_ratios[NUM_MCLK_RATIOS] = { - {CS4270_MODE_4X, CS4270_MODE_DIV1}, /* 64 */ -#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA - {CS4270_MODE_4X, CS4270_MODE_DIV15}, /* 96 */ -#endif - {CS4270_MODE_2X, CS4270_MODE_DIV1}, /* 128 */ - {CS4270_MODE_4X, CS4270_MODE_DIV3}, /* 192 */ - {CS4270_MODE_1X, CS4270_MODE_DIV1}, /* 256 */ - {CS4270_MODE_2X, CS4270_MODE_DIV3}, /* 384 */ - {CS4270_MODE_1X, CS4270_MODE_DIV2}, /* 512 */ - {CS4270_MODE_1X, CS4270_MODE_DIV3}, /* 768 */ - {CS4270_MODE_1X, CS4270_MODE_DIV4} /* 1024 */ -}; - -/* * Program the CS4270 with the given hardware parameters. * * The .dai_ops functions are used to provide board-specific data, like @@ -388,7 +384,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, ratio = cs4270->mclk / rate; /* MCLK/LRCK ratio */ for (i = 0; i < NUM_MCLK_RATIOS; i++) { - if (mclk_ratios[i] == ratio) + if (cs4270_mode_ratios[i].ratio == ratio) break; } @@ -669,7 +665,7 @@ error: return ret; } -#endif +#endif /* USE_I2C*/ struct snd_soc_codec_dai cs4270_dai = { .name = "CS4270", @@ -687,10 +683,6 @@ struct snd_soc_codec_dai cs4270_dai = { .rates = 0, .formats = CS4270_FORMATS, }, - .dai_ops = { - .set_sysclk = cs4270_set_dai_sysclk, - .set_fmt = cs4270_set_dai_fmt, - } }; EXPORT_SYMBOL_GPL(cs4270_dai); @@ -752,6 +744,8 @@ static int cs4270_probe(struct platform_device *pdev) if (codec->control_data) { /* Initialize codec ops */ cs4270_dai.ops.hw_params = cs4270_hw_params; + cs4270_dai.dai_ops.set_sysclk = cs4270_set_dai_sysclk; + cs4270_dai.dai_ops.set_fmt = cs4270_set_dai_fmt; #ifdef CONFIG_SND_SOC_CS4270_HWMUTE cs4270_dai.dai_ops.digital_mute = cs4270_mute; #endif diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c new file mode 100644 index 00000000000..710e0287ef8 --- /dev/null +++ b/sound/soc/codecs/tlv320aic3x.c @@ -0,0 +1,1274 @@ +/* + * ALSA SoC TLV320AIC3X codec driver + * + * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> + * + * Based on sound/soc/codecs/wm8753.c by Liam Girdwood + * + * 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. + * + * Notes: + * The AIC3X is a driver for a low power stereo audio + * codecs aic31, aic32, aic33. + * + * It supports full aic33 codec functionality. + * The compatibility with aic32, aic31 is as follows: + * aic32 | aic31 + * --------------------------------------- + * MONO_LOUT -> N/A | MONO_LOUT -> N/A + * | IN1L -> LINE1L + * | IN1R -> LINE1R + * | IN2L -> LINE2L + * | IN2R -> LINE2R + * | MIC3L/R -> N/A + * truncated internal functionality in + * accordance with documentation + * --------------------------------------- + * + * Hence the machine layer should disable unsupported inputs/outputs by + * snd_soc_dapm_set_endpoint(codec, "MONO_LOUT", 0), etc. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "tlv320aic3x.h" + +#define AUDIO_NAME "aic3x" +#define AIC3X_VERSION "0.1" + +/* codec private data */ +struct aic3x_priv { + unsigned int sysclk; + int master; +}; + +/* + * AIC3X register cache + * We can't read the AIC3X register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u8 aic3x_reg[AIC3X_CACHEREGNUM] = { + 0x00, 0x00, 0x00, 0x10, /* 0 */ + 0x04, 0x00, 0x00, 0x00, /* 4 */ + 0x00, 0x00, 0x00, 0x01, /* 8 */ + 0x00, 0x00, 0x00, 0x80, /* 12 */ + 0x80, 0xff, 0xff, 0x78, /* 16 */ + 0x78, 0x78, 0x78, 0x78, /* 20 */ + 0x78, 0x00, 0x00, 0xfe, /* 24 */ + 0x00, 0x00, 0xfe, 0x00, /* 28 */ + 0x18, 0x18, 0x00, 0x00, /* 32 */ + 0x00, 0x00, 0x00, 0x00, /* 36 */ + 0x00, 0x00, 0x00, 0x80, /* 40 */ + 0x80, 0x00, 0x00, 0x00, /* 44 */ + 0x00, 0x00, 0x00, 0x04, /* 48 */ + 0x00, 0x00, 0x00, 0x00, /* 52 */ + 0x00, 0x00, 0x04, 0x00, /* 56 */ + 0x00, 0x00, 0x00, 0x00, /* 60 */ + 0x00, 0x04, 0x00, 0x00, /* 64 */ + 0x00, 0x00, 0x00, 0x00, /* 68 */ + 0x04, 0x00, 0x00, 0x00, /* 72 */ + 0x00, 0x00, 0x00, 0x00, /* 76 */ + 0x00, 0x00, 0x00, 0x00, /* 80 */ + 0x00, 0x00, 0x00, 0x00, /* 84 */ + 0x00, 0x00, 0x00, 0x00, /* 88 */ + 0x00, 0x00, 0x00, 0x00, /* 92 */ + 0x00, 0x00, 0x00, 0x00, /* 96 */ + 0x00, 0x00, 0x02, /* 100 */ +}; + +/* + * read aic3x register cache + */ +static inline unsigned int aic3x_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + if (reg >= AIC3X_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write aic3x register cache + */ +static inline void aic3x_write_reg_cache(struct snd_soc_codec *codec, + u8 reg, u8 value) +{ + u8 *cache = codec->reg_cache; + if (reg >= AIC3X_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the aic3x register space + */ +static int aic3x_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D8 aic3x register offset + * D7...D0 register data + */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + aic3x_write_reg_cache(codec, data[0], data[1]); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw_aic3x, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, mask, invert) } + +/* + * All input lines are connected when !0xf and disconnected with 0xf bit field, + * so we have to use specific dapm_put call for input mixer + */ +static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0x01; + unsigned short val, val_mask; + int ret; + struct snd_soc_dapm_path *path; + int found = 0; + + val = (ucontrol->value.integer.value[0] & mask); + + mask = 0xf; + if (val) + val = mask; + + if (invert) + val = mask - val; + val_mask = mask << shift; + val = val << shift; + + mutex_lock(&widget->codec->mutex); + + if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) { + /* find dapm widget path assoc with kcontrol */ + list_for_each_entry(path, &widget->codec->dapm_paths, list) { + if (path->kcontrol != kcontrol) + continue; + + /* found, now check type */ + found = 1; + if (val) + /* new connection */ + path->connect = invert ? 0 : 1; + else + /* old connection must be powered down */ + path->connect = invert ? 1 : 0; + break; + } + + if (found) + snd_soc_dapm_sync_endpoints(widget->codec); + } + + ret = snd_soc_update_bits(widget->codec, reg, val_mask, val); + + mutex_unlock(&widget->codec->mutex); + return ret; +} + +static const char *aic3x_left_dac_mux[] = { "DAC_L1", "DAC_L3", "DAC_L2" }; +static const char *aic3x_right_dac_mux[] = { "DAC_R1", "DAC_R3", "DAC_R2" }; +static const char *aic3x_left_hpcom_mux[] = + { "differential of HPLOUT", "constant VCM", "single-ended" }; +static const char *aic3x_right_hpcom_mux[] = + { "differential of HPROUT", "constant VCM", "single-ended", + "differential of HPLCOM", "external feedback" }; +static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" }; + +#define LDAC_ENUM 0 +#define RDAC_ENUM 1 +#define LHPCOM_ENUM 2 +#define RHPCOM_ENUM 3 +#define LINE1L_ENUM 4 +#define LINE1R_ENUM 5 +#define LINE2L_ENUM 6 +#define LINE2R_ENUM 7 + +static const struct soc_enum aic3x_enum[] = { + SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux), + SOC_ENUM_SINGLE(DAC_LINE_MUX, 4, 3, aic3x_right_dac_mux), + SOC_ENUM_SINGLE(HPLCOM_CFG, 4, 3, aic3x_left_hpcom_mux), + SOC_ENUM_SINGLE(HPRCOM_CFG, 3, 5, aic3x_right_hpcom_mux), + SOC_ENUM_SINGLE(LINE1L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), +}; + +static const struct snd_kcontrol_new aic3x_snd_controls[] = { + /* Output */ + SOC_DOUBLE_R("PCM Playback Volume", LDAC_VOL, RDAC_VOL, 0, 0x7f, 1), + + SOC_DOUBLE_R("Line DAC Playback Volume", DACL1_2_LLOPM_VOL, + DACR1_2_RLOPM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("Line DAC Playback Switch", LLOPM_CTRL, RLOPM_CTRL, 3, + 0x01, 0), + SOC_DOUBLE_R("Line PGA Bypass Playback Volume", PGAL_2_LLOPM_VOL, + PGAR_2_RLOPM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("Line Line2 Bypass Playback Volume", LINE2L_2_LLOPM_VOL, + LINE2R_2_RLOPM_VOL, 0, 0x7f, 1), + + SOC_DOUBLE_R("Mono DAC Playback Volume", DACL1_2_MONOLOPM_VOL, + DACR1_2_MONOLOPM_VOL, 0, 0x7f, 1), + SOC_SINGLE("Mono DAC Playback Switch", MONOLOPM_CTRL, 3, 0x01, 0), + SOC_DOUBLE_R("Mono PGA Bypass Playback Volume", PGAL_2_MONOLOPM_VOL, + PGAR_2_MONOLOPM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("Mono Line2 Bypass Playback Volume", LINE2L_2_MONOLOPM_VOL, + LINE2R_2_MONOLOPM_VOL, 0, 0x7f, 1), + + SOC_DOUBLE_R("HP DAC Playback Volume", DACL1_2_HPLOUT_VOL, + DACR1_2_HPROUT_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("HP DAC Playback Switch", HPLOUT_CTRL, HPROUT_CTRL, 3, + 0x01, 0), + SOC_DOUBLE_R("HP PGA Bypass Playback Volume", PGAL_2_HPLOUT_VOL, + PGAR_2_HPROUT_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("HP Line2 Bypass Playback Volume", LINE2L_2_HPLOUT_VOL, + LINE2R_2_HPROUT_VOL, 0, 0x7f, 1), + + SOC_DOUBLE_R("HPCOM DAC Playback Volume", DACL1_2_HPLCOM_VOL, + DACR1_2_HPRCOM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("HPCOM DAC Playback Switch", HPLCOM_CTRL, HPRCOM_CTRL, 3, + 0x01, 0), + SOC_DOUBLE_R("HPCOM PGA Bypass Playback Volume", PGAL_2_HPLCOM_VOL, + PGAR_2_HPRCOM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("HPCOM Line2 Bypass Playback Volume", LINE2L_2_HPLCOM_VOL, + LINE2R_2_HPRCOM_VOL, 0, 0x7f, 1), + + /* + * Note: enable Automatic input Gain Controller with care. It can + * adjust PGA to max value when ADC is on and will never go back. + */ + SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0), + + /* Input */ + SOC_DOUBLE_R("PGA Capture Volume", LADC_VOL, RADC_VOL, 0, 0x7f, 0), + SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1), +}; + +/* add non dapm controls */ +static int aic3x_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(aic3x_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&aic3x_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Left DAC Mux */ +static const struct snd_kcontrol_new aic3x_left_dac_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]); + +/* Right DAC Mux */ +static const struct snd_kcontrol_new aic3x_right_dac_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[RDAC_ENUM]); + +/* Left HPCOM Mux */ +static const struct snd_kcontrol_new aic3x_left_hpcom_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LHPCOM_ENUM]); + +/* Right HPCOM Mux */ +static const struct snd_kcontrol_new aic3x_right_hpcom_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[RHPCOM_ENUM]); + +/* Left DAC_L1 Mixer */ +static const struct snd_kcontrol_new aic3x_left_dac_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", DACL1_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", DACL1_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", DACL1_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", DACL1_2_HPLCOM_VOL, 7, 1, 0), +}; + +/* Right DAC_R1 Mixer */ +static const struct snd_kcontrol_new aic3x_right_dac_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", DACR1_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", DACR1_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", DACR1_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", DACR1_2_HPRCOM_VOL, 7, 1, 0), +}; + +/* Left PGA Mixer */ +static const struct snd_kcontrol_new aic3x_left_pga_mixer_controls[] = { + SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_LADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line2L Switch", LINE2L_2_LADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_LADC_CTRL, 4, 1, 1), +}; + +/* Right PGA Mixer */ +static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = { + SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line2R Switch", LINE2R_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_RADC_CTRL, 0, 1, 1), +}; + +/* Left Line1 Mux */ +static const struct snd_kcontrol_new aic3x_left_line1_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_ENUM]); + +/* Right Line1 Mux */ +static const struct snd_kcontrol_new aic3x_right_line1_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_ENUM]); + +/* Left Line2 Mux */ +static const struct snd_kcontrol_new aic3x_left_line2_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE2L_ENUM]); + +/* Right Line2 Mux */ +static const struct snd_kcontrol_new aic3x_right_line2_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE2R_ENUM]); + +/* Left PGA Bypass Mixer */ +static const struct snd_kcontrol_new aic3x_left_pga_bp_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", PGAL_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", PGAL_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", PGAL_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", PGAL_2_HPLCOM_VOL, 7, 1, 0), +}; + +/* Right PGA Bypass Mixer */ +static const struct snd_kcontrol_new aic3x_right_pga_bp_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", PGAR_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", PGAR_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", PGAR_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", PGAR_2_HPRCOM_VOL, 7, 1, 0), +}; + +/* Left Line2 Bypass Mixer */ +static const struct snd_kcontrol_new aic3x_left_line2_bp_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", LINE2L_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", LINE2L_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", LINE2L_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", LINE2L_2_HPLCOM_VOL, 7, 1, 0), +}; + +/* Right Line2 Bypass Mixer */ +static const struct snd_kcontrol_new aic3x_right_line2_bp_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", LINE2R_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", LINE2R_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", LINE2R_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", LINE2R_2_HPRCOM_VOL, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { + /* Left DAC to Left Outputs */ + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", DAC_PWR, 7, 0), + SND_SOC_DAPM_MUX("Left DAC Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_dac_mux_controls), + SND_SOC_DAPM_MIXER("Left DAC_L1 Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_dac_mixer_controls[0], + ARRAY_SIZE(aic3x_left_dac_mixer_controls)), + SND_SOC_DAPM_MUX("Left HPCOM Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_hpcom_mux_controls), + SND_SOC_DAPM_PGA("Left Line Out", LLOPM_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left HP Out", HPLOUT_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left HP Com", HPLCOM_CTRL, 0, 0, NULL, 0), + + /* Right DAC to Right Outputs */ + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", DAC_PWR, 6, 0), + SND_SOC_DAPM_MUX("Right DAC Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_dac_mux_controls), + SND_SOC_DAPM_MIXER("Right DAC_R1 Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_dac_mixer_controls[0], + ARRAY_SIZE(aic3x_right_dac_mixer_controls)), + SND_SOC_DAPM_MUX("Right HPCOM Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_hpcom_mux_controls), + SND_SOC_DAPM_PGA("Right Line Out", RLOPM_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right HP Out", HPROUT_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right HP Com", HPRCOM_CTRL, 0, 0, NULL, 0), + + /* Mono Output */ + SND_SOC_DAPM_PGA("Mono Out", MONOLOPM_CTRL, 0, 0, NULL, 0), + + /* Left Inputs to Left ADC */ + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", LINE1L_2_LADC_CTRL, 2, 0), + SND_SOC_DAPM_MIXER("Left PGA Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_pga_mixer_controls[0], + ARRAY_SIZE(aic3x_left_pga_mixer_controls)), + SND_SOC_DAPM_MUX("Left Line1L Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_line1_mux_controls), + SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_line2_mux_controls), + + /* Right Inputs to Right ADC */ + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", + LINE1R_2_RADC_CTRL, 2, 0), + SND_SOC_DAPM_MIXER("Right PGA Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_pga_mixer_controls[0], + ARRAY_SIZE(aic3x_right_pga_mixer_controls)), + SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_line1_mux_controls), + SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_line2_mux_controls), + + /* Mic Bias */ + SND_SOC_DAPM_MICBIAS("Mic Bias 2V", MICBIAS_CTRL, 6, 0), + SND_SOC_DAPM_MICBIAS("Mic Bias 2.5V", MICBIAS_CTRL, 7, 0), + SND_SOC_DAPM_MICBIAS("Mic Bias AVDD", MICBIAS_CTRL, 6, 0), + SND_SOC_DAPM_MICBIAS("Mic Bias AVDD", MICBIAS_CTRL, 7, 0), + + /* Left PGA to Left Output bypass */ + SND_SOC_DAPM_MIXER("Left PGA Bypass Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_pga_bp_mixer_controls[0], + ARRAY_SIZE(aic3x_left_pga_bp_mixer_controls)), + + /* Right PGA to Right Output bypass */ + SND_SOC_DAPM_MIXER("Right PGA Bypass Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_pga_bp_mixer_controls[0], + ARRAY_SIZE(aic3x_right_pga_bp_mixer_controls)), + + /* Left Line2 to Left Output bypass */ + SND_SOC_DAPM_MIXER("Left Line2 Bypass Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_line2_bp_mixer_controls[0], + ARRAY_SIZE(aic3x_left_line2_bp_mixer_controls)), + + /* Right Line2 to Right Output bypass */ + SND_SOC_DAPM_MIXER("Right Line2 Bypass Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_line2_bp_mixer_controls[0], + ARRAY_SIZE(aic3x_right_line2_bp_mixer_controls)), + + SND_SOC_DAPM_OUTPUT("LLOUT"), + SND_SOC_DAPM_OUTPUT("RLOUT"), + SND_SOC_DAPM_OUTPUT("MONO_LOUT"), + SND_SOC_DAPM_OUTPUT("HPLOUT"), + SND_SOC_DAPM_OUTPUT("HPROUT"), + SND_SOC_DAPM_OUTPUT("HPLCOM"), + SND_SOC_DAPM_OUTPUT("HPRCOM"), + + SND_SOC_DAPM_INPUT("MIC3L"), + SND_SOC_DAPM_INPUT("MIC3R"), + SND_SOC_DAPM_INPUT("LINE1L"), + SND_SOC_DAPM_INPUT("LINE1R"), + SND_SOC_DAPM_INPUT("LINE2L"), + SND_SOC_DAPM_INPUT("LINE2R"), +}; + +static const char *intercon[][3] = { + /* Left Output */ + {"Left DAC Mux", "DAC_L1", "Left DAC"}, + {"Left DAC Mux", "DAC_L2", "Left DAC"}, + {"Left DAC Mux", "DAC_L3", "Left DAC"}, + + {"Left DAC_L1 Mixer", "Line Switch", "Left DAC Mux"}, + {"Left DAC_L1 Mixer", "Mono Switch", "Left DAC Mux"}, + {"Left DAC_L1 Mixer", "HP Switch", "Left DAC Mux"}, + {"Left DAC_L1 Mixer", "HPCOM Switch", "Left DAC Mux"}, + {"Left Line Out", NULL, "Left DAC Mux"}, + {"Left HP Out", NULL, "Left DAC Mux"}, + + {"Left HPCOM Mux", "differential of HPLOUT", "Left DAC_L1 Mixer"}, + {"Left HPCOM Mux", "constant VCM", "Left DAC_L1 Mixer"}, + {"Left HPCOM Mux", "single-ended", "Left DAC_L1 Mixer"}, + + {"Left Line Out", NULL, "Left DAC_L1 Mixer"}, + {"Mono Out", NULL, "Left DAC_L1 Mixer"}, + {"Left HP Out", NULL, "Left DAC_L1 Mixer"}, + {"Left HP Com", NULL, "Left HPCOM Mux"}, + + {"LLOUT", NULL, "Left Line Out"}, + {"LLOUT", NULL, "Left Line Out"}, + {"HPLOUT", NULL, "Left HP Out"}, + {"HPLCOM", NULL, "Left HP Com"}, + + /* Right Output */ + {"Right DAC Mux", "DAC_R1", "Right DAC"}, + {"Right DAC Mux", "DAC_R2", "Right DAC"}, + {"Right DAC Mux", "DAC_R3", "Right DAC"}, + + {"Right DAC_R1 Mixer", "Line Switch", "Right DAC Mux"}, + {"Right DAC_R1 Mixer", "Mono Switch", "Right DAC Mux"}, + {"Right DAC_R1 Mixer", "HP Switch", "Right DAC Mux"}, + {"Right DAC_R1 Mixer", "HPCOM Switch", "Right DAC Mux"}, + {"Right Line Out", NULL, "Right DAC Mux"}, + {"Right HP Out", NULL, "Right DAC Mux"}, + + {"Right HPCOM Mux", "differential of HPROUT", "Right DAC_R1 Mixer"}, + {"Right HPCOM Mux", "constant VCM", "Right DAC_R1 Mixer"}, + {"Right HPCOM Mux", "single-ended", "Right DAC_R1 Mixer"}, + {"Right HPCOM Mux", "differential of HPLCOM", "Right DAC_R1 Mixer"}, + {"Right HPCOM Mux", "external feedback", "Right DAC_R1 Mixer"}, + + {"Right Line Out", NULL, "Right DAC_R1 Mixer"}, + {"Mono Out", NULL, "Right DAC_R1 Mixer"}, + {"Right HP Out", NULL, "Right DAC_R1 Mixer"}, + {"Right HP Com", NULL, "Right HPCOM Mux"}, + + {"RLOUT", NULL, "Right Line Out"}, + {"RLOUT", NULL, "Right Line Out"}, + {"HPROUT", NULL, "Right HP Out"}, + {"HPRCOM", NULL, "Right HP Com"}, + + /* Mono Output */ + {"MONOLOUT", NULL, "Mono Out"}, + {"MONOLOUT", NULL, "Mono Out"}, + + /* Left Input */ + {"Left Line1L Mux", "single-ended", "LINE1L"}, + {"Left Line1L Mux", "differential", "LINE1L"}, + + {"Left Line2L Mux", "single-ended", "LINE2L"}, + {"Left Line2L Mux", "differential", "LINE2L"}, + + {"Left PGA Mixer", "Line1L Switch", "Left Line1L Mux"}, + {"Left PGA Mixer", "Line2L Switch", "Left Line2L Mux"}, + {"Left PGA Mixer", "Mic3L Switch", "MIC3L"}, + + {"Left ADC", NULL, "Left PGA Mixer"}, + + /* Right Input */ + {"Right Line1R Mux", "single-ended", "LINE1R"}, + {"Right Line1R Mux", "differential", "LINE1R"}, + + {"Right Line2R Mux", "single-ended", "LINE2R"}, + {"Right Line2R Mux", "differential", "LINE2R"}, + + {"Right PGA Mixer", "Line1R Switch", "Right Line1R Mux"}, + {"Right PGA Mixer", "Line2R Switch", "Right Line2R Mux"}, + {"Right PGA Mixer", "Mic3R Switch", "MIC3R"}, + + {"Right ADC", NULL, "Right PGA Mixer"}, + + /* Left PGA Bypass */ + {"Left PGA Bypass Mixer", "Line Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "Mono Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "HP Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "HPCOM Switch", "Left PGA Mixer"}, + + {"Left HPCOM Mux", "differential of HPLOUT", "Left PGA Bypass Mixer"}, + {"Left HPCOM Mux", "constant VCM", "Left PGA Bypass Mixer"}, + {"Left HPCOM Mux", "single-ended", "Left PGA Bypass Mixer"}, + + {"Left Line Out", NULL, "Left PGA Bypass Mixer"}, + {"Mono Out", NULL, "Left PGA Bypass Mixer"}, + {"Left HP Out", NULL, "Left PGA Bypass Mixer"}, + + /* Right PGA Bypass */ + {"Right PGA Bypass Mixer", "Line Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "Mono Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "HP Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "HPCOM Switch", "Right PGA Mixer"}, + + {"Right HPCOM Mux", "differential of HPROUT", "Right PGA Bypass Mixer"}, + {"Right HPCOM Mux", "constant VCM", "Right PGA Bypass Mixer"}, + {"Right HPCOM Mux", "single-ended", "Right PGA Bypass Mixer"}, + {"Right HPCOM Mux", "differential of HPLCOM", "Right PGA Bypass Mixer"}, + {"Right HPCOM Mux", "external feedback", "Right PGA Bypass Mixer"}, + + {"Right Line Out", NULL, "Right PGA Bypass Mixer"}, + {"Mono Out", NULL, "Right PGA Bypass Mixer"}, + {"Right HP Out", NULL, "Right PGA Bypass Mixer"}, + + /* Left Line2 Bypass */ + {"Left Line2 Bypass Mixer", "Line Switch", "Left Line2L Mux"}, + {"Left Line2 Bypass Mixer", "Mono Switch", "Left Line2L Mux"}, + {"Left Line2 Bypass Mixer", "HP Switch", "Left Line2L Mux"}, + {"Left Line2 Bypass Mixer", "HPCOM Switch", "Left Line2L Mux"}, + + {"Left HPCOM Mux", "differential of HPLOUT", "Left Line2 Bypass Mixer"}, + {"Left HPCOM Mux", "constant VCM", "Left Line2 Bypass Mixer"}, + {"Left HPCOM Mux", "single-ended", "Left Line2 Bypass Mixer"}, + + {"Left Line Out", NULL, "Left Line2 Bypass Mixer"}, + {"Mono Out", NULL, "Left Line2 Bypass Mixer"}, + {"Left HP Out", NULL, "Left Line2 Bypass Mixer"}, + + /* Right Line2 Bypass */ + {"Right Line2 Bypass Mixer", "Line Switch", "Right Line2R Mux"}, + {"Right Line2 Bypass Mixer", "Mono Switch", "Right Line2R Mux"}, + {"Right Line2 Bypass Mixer", "HP Switch", "Right Line2R Mux"}, + {"Right Line2 Bypass Mixer", "HPCOM Switch", "Right Line2R Mux"}, + + {"Right HPCOM Mux", "differential of HPROUT", "Right Line2 Bypass Mixer"}, + {"Right HPCOM Mux", "constant VCM", "Right Line2 Bypass Mixer"}, + {"Right HPCOM Mux", "single-ended", "Right Line2 Bypass Mixer"}, + {"Right HPCOM Mux", "differential of HPLCOM", "Right Line2 Bypass Mixer"}, + {"Right HPCOM Mux", "external feedback", "Right Line2 Bypass Mixer"}, + + {"Right Line Out", NULL, "Right Line2 Bypass Mixer"}, + {"Mono Out", NULL, "Right Line2 Bypass Mixer"}, + {"Right HP Out", NULL, "Right Line2 Bypass Mixer"}, + + /* terminator */ + {NULL, NULL, NULL}, +}; + +static int aic3x_add_widgets(struct snd_soc_codec *codec) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(aic3x_dapm_widgets); i++) + snd_soc_dapm_new_control(codec, &aic3x_dapm_widgets[i]); + + /* set up audio path interconnects */ + for (i = 0; intercon[i][0] != NULL; i++) + snd_soc_dapm_connect_input(codec, intercon[i][0], + intercon[i][1], intercon[i][2]); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct aic3x_rate_divs { + u32 mclk; + u32 rate; + u32 fsref_reg; + u8 sr_reg:4; + u8 pllj_reg; + u16 plld_reg; +}; + +/* AIC3X codec mclk clock divider coefficients */ +static const struct aic3x_rate_divs aic3x_divs[] = { + /* 8k */ + {22579200, 8000, 48000, 0xa, 8, 7075}, + {33868800, 8000, 48000, 0xa, 5, 8049}, + /* 11.025k */ + {22579200, 11025, 44100, 0x6, 8, 0}, + {33868800, 11025, 44100, 0x6, 5, 3333}, + /* 16k */ + {22579200, 16000, 48000, 0x4, 8, 7075}, + {33868800, 16000, 48000, 0x4, 5, 8049}, + /* 22.05k */ + {22579200, 22050, 44100, 0x2, 8, 0}, + {33868800, 22050, 44100, 0x2, 5, 3333}, + /* 32k */ + {22579200, 32000, 48000, 0x1, 8, 7075}, + {33868800, 32000, 48000, 0x1, 5, 8049}, + /* 44.1k */ + {22579200, 44100, 44100, 0x0, 8, 0}, + {33868800, 44100, 44100, 0x0, 5, 3333}, + /* 48k */ + {22579200, 48000, 48000, 0x0, 8, 7075}, + {33868800, 48000, 48000, 0x0, 5, 8049}, + /* 64k */ + {22579200, 96000, 96000, 0x1, 8, 7075}, + {33868800, 96000, 96000, 0x1, 5, 8049}, + /* 88.2k */ + {22579200, 88200, 88200, 0x0, 8, 0}, + {33868800, 88200, 88200, 0x0, 5, 3333}, + /* 96k */ + {22579200, 96000, 96000, 0x0, 8, 7075}, + {33868800, 96000, 96000, 0x0, 5, 8049}, +}; + +static inline int aic3x_get_divs(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(aic3x_divs); i++) { + if (aic3x_divs[i].rate == rate && aic3x_divs[i].mclk == mclk) + return i; + } + + return 0; +} + +static int aic3x_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_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct aic3x_priv *aic3x = codec->private_data; + int i; + u8 data, pll_p, pll_r, pll_j; + u16 pll_d; + + i = aic3x_get_divs(aic3x->sysclk, params_rate(params)); + + /* Route Left DAC to left channel input and + * right DAC to right channel input */ + data = (LDAC2LCH | RDAC2RCH); + switch (aic3x_divs[i].fsref_reg) { + case 44100: + data |= FSREF_44100; + break; + case 48000: + data |= FSREF_48000; + break; + case 88200: + data |= FSREF_44100 | DUAL_RATE_MODE; + break; + case 96000: + data |= FSREF_48000 | DUAL_RATE_MODE; + break; + } + aic3x_write(codec, AIC3X_CODEC_DATAPATH_REG, data); + + /* codec sample rate select */ + data = aic3x_divs[i].sr_reg; + data |= (data << 4); + aic3x_write(codec, AIC3X_SAMPLE_RATE_SEL_REG, data); + + /* Use PLL for generation Fsref by equation: + * Fsref = (MCLK * K * R)/(2048 * P); + * Fix P = 2 and R = 1 and calculate K, if + * K = J.D, i.e. J - an interger portion of K and D is the fractional + * one with 4 digits of precision; + * Example: + * For MCLK = 22.5792 MHz and Fsref = 48kHz: + * Select P = 2, R= 1, K = 8.7074, which results in J = 8, D = 7074 + */ + pll_p = 2; + pll_r = 1; + pll_j = aic3x_divs[i].pllj_reg; + pll_d = aic3x_divs[i].plld_reg; + + data = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT)); + aic3x_write(codec, AIC3X_OVRF_STATUS_AND_PLLR_REG, pll_r << PLLR_SHIFT); + aic3x_write(codec, AIC3X_PLL_PROGB_REG, pll_j << PLLJ_SHIFT); + aic3x_write(codec, AIC3X_PLL_PROGC_REG, (pll_d >> 6) << PLLD_MSB_SHIFT); + aic3x_write(codec, AIC3X_PLL_PROGD_REG, + (pll_d & 0x3F) << PLLD_LSB_SHIFT); + + /* select data word length */ + data = + aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4)); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + data |= (0x01 << 4); + break; + case SNDRV_PCM_FORMAT_S24_LE: + data |= (0x02 << 4); + break; + case SNDRV_PCM_FORMAT_S32_LE: + data |= (0x03 << 4); + break; + } + aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, data); + + return 0; +} + +static int aic3x_mute(struct snd_soc_codec_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 ldac_reg = aic3x_read_reg_cache(codec, LDAC_VOL) & ~MUTE_ON; + u8 rdac_reg = aic3x_read_reg_cache(codec, RDAC_VOL) & ~MUTE_ON; + + if (mute) { + aic3x_write(codec, LDAC_VOL, ldac_reg | MUTE_ON); + aic3x_write(codec, RDAC_VOL, rdac_reg | MUTE_ON); + } else { + aic3x_write(codec, LDAC_VOL, ldac_reg); + aic3x_write(codec, RDAC_VOL, rdac_reg); + } + + return 0; +} + +static int aic3x_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic3x_priv *aic3x = codec->private_data; + + switch (freq) { + case 22579200: + case 33868800: + aic3x->sysclk = freq; + return 0; + } + + return -EINVAL; +} + +static int aic3x_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic3x_priv *aic3x = codec->private_data; + u8 iface_areg = 0; + u8 iface_breg = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aic3x->master = 1; + iface_areg |= BIT_CLK_MASTER | WORD_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + aic3x->master = 0; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_DSP_A: + iface_breg |= (0x01 << 6); + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface_breg |= (0x02 << 6); + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_breg |= (0x03 << 6); + break; + default: + return -EINVAL; + } + + /* set iface */ + aic3x_write(codec, AIC3X_ASD_INTF_CTRLA, iface_areg); + aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, iface_breg); + + return 0; +} + +static int aic3x_dapm_event(struct snd_soc_codec *codec, int event) +{ + struct aic3x_priv *aic3x = codec->private_data; + u8 reg; + + switch (event) { + case SNDRV_CTL_POWER_D0: + /* all power is driven by DAPM system */ + if (aic3x->master) { + /* enable pll */ + reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, + reg | PLL_ENABLE); + } + break; + case SNDRV_CTL_POWER_D1: + case SNDRV_CTL_POWER_D2: + break; + case SNDRV_CTL_POWER_D3hot: + /* + * all power is driven by DAPM system, + * so output power is safe if bypass was set + */ + if (aic3x->master) { + /* disable pll */ + reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, + reg & ~PLL_ENABLE); + } + break; + case SNDRV_CTL_POWER_D3cold: + /* force all power off */ + reg = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL); + aic3x_write(codec, LINE1L_2_LADC_CTRL, reg & ~LADC_PWR_ON); + reg = aic3x_read_reg_cache(codec, LINE1R_2_RADC_CTRL); + aic3x_write(codec, LINE1R_2_RADC_CTRL, reg & ~RADC_PWR_ON); + + reg = aic3x_read_reg_cache(codec, DAC_PWR); + aic3x_write(codec, DAC_PWR, reg & ~(LDAC_PWR_ON | RDAC_PWR_ON)); + + reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL); + aic3x_write(codec, HPLOUT_CTRL, reg & ~HPLOUT_PWR_ON); + reg = aic3x_read_reg_cache(codec, HPROUT_CTRL); + aic3x_write(codec, HPROUT_CTRL, reg & ~HPROUT_PWR_ON); + + reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL); + aic3x_write(codec, HPLCOM_CTRL, reg & ~HPLCOM_PWR_ON); + reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL); + aic3x_write(codec, HPRCOM_CTRL, reg & ~HPRCOM_PWR_ON); + + reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL); + aic3x_write(codec, MONOLOPM_CTRL, reg & ~MONOLOPM_PWR_ON); + + reg = aic3x_read_reg_cache(codec, LLOPM_CTRL); + aic3x_write(codec, LLOPM_CTRL, reg & ~LLOPM_PWR_ON); + reg = aic3x_read_reg_cache(codec, RLOPM_CTRL); + aic3x_write(codec, RLOPM_CTRL, reg & ~RLOPM_PWR_ON); + + if (aic3x->master) { + /* disable pll */ + reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, + reg & ~PLL_ENABLE); + } + break; + } + codec->dapm_state = event; + + return 0; +} + +#define AIC3X_RATES SNDRV_PCM_RATE_8000_96000 +#define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_codec_dai aic3x_dai = { + .name = "aic3x", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3X_RATES, + .formats = AIC3X_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3X_RATES, + .formats = AIC3X_FORMATS,}, + .ops = { + .hw_params = aic3x_hw_params, + }, + .dai_ops = { + .digital_mute = aic3x_mute, + .set_sysclk = aic3x_set_dai_sysclk, + .set_fmt = aic3x_set_dai_fmt, + } +}; +EXPORT_SYMBOL_GPL(aic3x_dai); + +static int aic3x_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + aic3x_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + + return 0; +} + +static int aic3x_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u8 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++) { + data[0] = i; + data[1] = cache[i]; + codec->hw_write(codec->control_data, data, 2); + } + + aic3x_dapm_event(codec, codec->suspend_dapm_state); + + return 0; +} + +/* + * initialise the AIC3X driver + * register the mixer and dsp interfaces with the kernel + */ +static int aic3x_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "aic3x"; + codec->owner = THIS_MODULE; + codec->read = aic3x_read_reg_cache; + codec->write = aic3x_write; + codec->dapm_event = aic3x_dapm_event; + codec->dai = &aic3x_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(aic3x_reg); + codec->reg_cache = kmemdup(aic3x_reg, sizeof(aic3x_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + aic3x_write(codec, AIC3X_PAGE_SELECT, PAGE0_SELECT); + aic3x_write(codec, AIC3X_RESET, SOFT_RESET); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "aic3x: failed to create pcms\n"); + goto pcm_err; + } + + /* DAC default volume and mute */ + aic3x_write(codec, LDAC_VOL, DEFAULT_VOL | MUTE_ON); + aic3x_write(codec, RDAC_VOL, DEFAULT_VOL | MUTE_ON); + + /* DAC to HP default volume and route to Output mixer */ + aic3x_write(codec, DACL1_2_HPLOUT_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACR1_2_HPROUT_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACL1_2_HPLCOM_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACR1_2_HPRCOM_VOL, DEFAULT_VOL | ROUTE_ON); + /* DAC to Line Out default volume and route to Output mixer */ + aic3x_write(codec, DACL1_2_LLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACR1_2_RLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + /* DAC to Mono Line Out default volume and route to Output mixer */ + aic3x_write(codec, DACL1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACR1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + + /* unmute all outputs */ + reg = aic3x_read_reg_cache(codec, LLOPM_CTRL); + aic3x_write(codec, LLOPM_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, RLOPM_CTRL); + aic3x_write(codec, RLOPM_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL); + aic3x_write(codec, MONOLOPM_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL); + aic3x_write(codec, HPLOUT_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, HPROUT_CTRL); + aic3x_write(codec, HPROUT_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL); + aic3x_write(codec, HPLCOM_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL); + aic3x_write(codec, HPRCOM_CTRL, reg | UNMUTE); + + /* ADC default volume and unmute */ + aic3x_write(codec, LADC_VOL, DEFAULT_GAIN); + aic3x_write(codec, RADC_VOL, DEFAULT_GAIN); + /* By default route Line1 to ADC PGA mixer */ + aic3x_write(codec, LINE1L_2_LADC_CTRL, 0x0); + aic3x_write(codec, LINE1R_2_RADC_CTRL, 0x0); + + /* PGA to HP Bypass default volume, disconnect from Output Mixer */ + aic3x_write(codec, PGAL_2_HPLOUT_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAR_2_HPROUT_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAL_2_HPLCOM_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAR_2_HPRCOM_VOL, DEFAULT_VOL); + /* PGA to Line Out default volume, disconnect from Output Mixer */ + aic3x_write(codec, PGAL_2_LLOPM_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAR_2_RLOPM_VOL, DEFAULT_VOL); + /* PGA to Mono Line Out default volume, disconnect from Output Mixer */ + aic3x_write(codec, PGAL_2_MONOLOPM_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAR_2_MONOLOPM_VOL, DEFAULT_VOL); + + /* Line2 to HP Bypass default volume, disconnect from Output Mixer */ + aic3x_write(codec, LINE2L_2_HPLOUT_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2R_2_HPROUT_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2L_2_HPLCOM_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2R_2_HPRCOM_VOL, DEFAULT_VOL); + /* Line2 Line Out default volume, disconnect from Output Mixer */ + aic3x_write(codec, LINE2L_2_LLOPM_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2R_2_RLOPM_VOL, DEFAULT_VOL); + /* Line2 to Mono Out default volume, disconnect from Output Mixer */ + aic3x_write(codec, LINE2L_2_MONOLOPM_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL); + + /* off, with power on */ + aic3x_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + + aic3x_add_controls(codec); + aic3x_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "aic3x: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *aic3x_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* + * AIC3X 2 wire address can be up to 4 devices with device addresses + * 0x18, 0x19, 0x1A, 0x1B + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver aic3x_i2c_driver; +static struct i2c_client client_template; + +/* + * If the i2c layer weren't so broken, we could pass this kind of data + * around + */ +static int aic3x_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = aic3x_socdev; + struct aic3x_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + printk(KERN_ERR "aic3x: failed to attach codec at addr %x\n", + addr); + goto err; + } + + ret = aic3x_init(socdev); + if (ret < 0) { + printk(KERN_ERR "aic3x: failed to initialise AIC3X\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int aic3x_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int aic3x_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, aic3x_codec_probe); +} + +/* machine i2c codec control layer */ +static struct i2c_driver aic3x_i2c_driver = { + .driver = { + .name = "aic3x I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_I2CDEV, + .attach_adapter = aic3x_i2c_attach, + .detach_client = aic3x_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "AIC3X", + .driver = &aic3x_i2c_driver, +}; +#endif + +static int aic3x_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct aic3x_setup_data *setup; + struct snd_soc_codec *codec; + struct aic3x_priv *aic3x; + int ret = 0; + + printk(KERN_INFO "AIC3X Audio Codec %s\n", AIC3X_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); + if (aic3x == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = aic3x; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + aic3x_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t) i2c_master_send; + ret = i2c_add_driver(&aic3x_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +static int aic3x_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + /* power down chip */ + if (codec->control_data) + aic3x_dapm_event(codec, SNDRV_CTL_POWER_D3); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&aic3x_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_aic3x = { + .probe = aic3x_probe, + .remove = aic3x_remove, + .suspend = aic3x_suspend, + .resume = aic3x_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_aic3x); + +MODULE_DESCRIPTION("ASoC TLV320AIC3X codec driver"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h new file mode 100644 index 00000000000..d0cdeeb629d --- /dev/null +++ b/sound/soc/codecs/tlv320aic3x.h @@ -0,0 +1,181 @@ +/* + * ALSA SoC TLV320AIC3X codec driver + * + * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _AIC3X_H +#define _AIC3X_H + +/* AIC3X register space */ +#define AIC3X_CACHEREGNUM 103 + +/* Page select register */ +#define AIC3X_PAGE_SELECT 0 +/* Software reset register */ +#define AIC3X_RESET 1 +/* Codec Sample rate select register */ +#define AIC3X_SAMPLE_RATE_SEL_REG 2 +/* PLL progrramming register A */ +#define AIC3X_PLL_PROGA_REG 3 +/* PLL progrramming register B */ +#define AIC3X_PLL_PROGB_REG 4 +/* PLL progrramming register C */ +#define AIC3X_PLL_PROGC_REG 5 +/* PLL progrramming register D */ +#define AIC3X_PLL_PROGD_REG 6 +/* Codec datapath setup register */ +#define AIC3X_CODEC_DATAPATH_REG 7 +/* Audio serial data interface control register A */ +#define AIC3X_ASD_INTF_CTRLA 8 +/* Audio serial data interface control register B */ +#define AIC3X_ASD_INTF_CTRLB 9 +/* Audio overflow status and PLL R value programming register */ +#define AIC3X_OVRF_STATUS_AND_PLLR_REG 11 + +/* ADC PGA Gain control registers */ +#define LADC_VOL 15 +#define RADC_VOL 16 +/* MIC3 control registers */ +#define MIC3LR_2_LADC_CTRL 17 +#define MIC3LR_2_RADC_CTRL 18 +/* Line1 Input control registers */ +#define LINE1L_2_LADC_CTRL 19 +#define LINE1R_2_RADC_CTRL 22 +/* Line2 Input control registers */ +#define LINE2L_2_LADC_CTRL 20 +#define LINE2R_2_RADC_CTRL 23 +/* MICBIAS Control Register */ +#define MICBIAS_CTRL 25 + +/* AGC Control Registers A, B, C */ +#define LAGC_CTRL_A 26 +#define LAGC_CTRL_B 27 +#define LAGC_CTRL_C 28 +#define RAGC_CTRL_A 29 +#define RAGC_CTRL_B 30 +#define RAGC_CTRL_C 31 + +/* DAC Power and Left High Power Output control registers */ +#define DAC_PWR 37 +#define HPLCOM_CFG 37 +/* Right High Power Output control registers */ +#define HPRCOM_CFG 38 +/* DAC Output Switching control registers */ +#define DAC_LINE_MUX 41 +/* High Power Output Driver Pop Reduction registers */ +#define HPOUT_POP_REDUCTION 42 +/* DAC Digital control registers */ +#define LDAC_VOL 43 +#define RDAC_VOL 44 +/* High Power Output control registers */ +#define LINE2L_2_HPLOUT_VOL 45 +#define LINE2R_2_HPROUT_VOL 62 +#define PGAL_2_HPLOUT_VOL 46 +#define PGAR_2_HPROUT_VOL 63 +#define DACL1_2_HPLOUT_VOL 47 +#define DACR1_2_HPROUT_VOL 64 +#define HPLOUT_CTRL 51 +#define HPROUT_CTRL 65 +/* High Power COM control registers */ +#define LINE2L_2_HPLCOM_VOL 52 +#define LINE2R_2_HPRCOM_VOL 69 +#define PGAL_2_HPLCOM_VOL 53 +#define PGAR_2_HPRCOM_VOL 70 +#define DACL1_2_HPLCOM_VOL 54 +#define DACR1_2_HPRCOM_VOL 71 +#define HPLCOM_CTRL 58 +#define HPRCOM_CTRL 72 +/* Mono Line Output Plus/Minus control registers */ +#define LINE2L_2_MONOLOPM_VOL 73 +#define LINE2R_2_MONOLOPM_VOL 76 +#define PGAL_2_MONOLOPM_VOL 74 +#define PGAR_2_MONOLOPM_VOL 77 +#define DACL1_2_MONOLOPM_VOL 75 +#define DACR1_2_MONOLOPM_VOL 78 +#define MONOLOPM_CTRL 79 +/* Line Output Plus/Minus control registers */ +#define LINE2L_2_LLOPM_VOL 80 +#define LINE2R_2_RLOPM_VOL 90 +#define PGAL_2_LLOPM_VOL 81 +#define PGAR_2_RLOPM_VOL 91 +#define DACL1_2_LLOPM_VOL 82 +#define DACR1_2_RLOPM_VOL 92 +#define LLOPM_CTRL 86 +#define RLOPM_CTRL 93 +/* Clock generation control register */ +#define AIC3X_CLKGEN_CTRL_REG 102 + +/* Page select register bits */ +#define PAGE0_SELECT 0 +#define PAGE1_SELECT 1 + +/* Audio serial data interface control register A bits */ +#define BIT_CLK_MASTER 0x80 +#define WORD_CLK_MASTER 0x40 + +/* Codec Datapath setup register 7 */ +#define FSREF_44100 (1 << 7) +#define FSREF_48000 (0 << 7) +#define DUAL_RATE_MODE ((1 << 5) | (1 << 6)) +#define LDAC2LCH (0x1 << 3) +#define RDAC2RCH (0x1 << 1) + +/* PLL registers bitfields */ +#define PLLP_SHIFT 0 +#define PLLR_SHIFT 0 +#define PLLJ_SHIFT 2 +#define PLLD_MSB_SHIFT 0 +#define PLLD_LSB_SHIFT 2 + +/* Clock generation register bits */ +#define PLL_CLKIN_SHIFT 4 +#define MCLK_SOURCE 0x0 +#define PLL_CLKDIV_SHIFT 0 + +/* Software reset register bits */ +#define SOFT_RESET 0x80 + +/* PLL progrramming register A bits */ +#define PLL_ENABLE 0x80 + +/* Route bits */ +#define ROUTE_ON 0x80 + +/* Mute bits */ +#define UNMUTE 0x08 +#define MUTE_ON 0x80 + +/* Power bits */ +#define LADC_PWR_ON 0x04 +#define RADC_PWR_ON 0x04 +#define LDAC_PWR_ON 0x80 +#define RDAC_PWR_ON 0x40 +#define HPLOUT_PWR_ON 0x01 +#define HPROUT_PWR_ON 0x01 +#define HPLCOM_PWR_ON 0x01 +#define HPRCOM_PWR_ON 0x01 +#define MONOLOPM_PWR_ON 0x01 +#define LLOPM_PWR_ON 0x01 +#define RLOPM_PWR_ON 0x01 + +#define INVERT_VOL(val) (0x7f - val) + +/* Default output volume (inverted) */ +#define DEFAULT_VOL INVERT_VOL(0x50) +/* Default input volume */ +#define DEFAULT_GAIN 0x20 + +struct aic3x_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_codec_dai aic3x_dai; +extern struct snd_soc_codec_device soc_codec_dev_aic3x; + +#endif /* _AIC3X_H */ diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 7ca0b526828..9c33fe87492 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -19,7 +19,6 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -562,13 +561,13 @@ static int wm8731_init(struct snd_soc_device *socdev) /* set the update bits */ reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V); - wm8731_write(codec, WM8731_LOUT1V, reg | 0x0100); + wm8731_write(codec, WM8731_LOUT1V, reg & ~0x0100); reg = wm8731_read_reg_cache(codec, WM8731_ROUT1V); - wm8731_write(codec, WM8731_ROUT1V, reg | 0x0100); + wm8731_write(codec, WM8731_ROUT1V, reg & ~0x0100); reg = wm8731_read_reg_cache(codec, WM8731_LINVOL); - wm8731_write(codec, WM8731_LINVOL, reg | 0x0100); + wm8731_write(codec, WM8731_LINVOL, reg & ~0x0100); reg = wm8731_read_reg_cache(codec, WM8731_RINVOL); - wm8731_write(codec, WM8731_RINVOL, reg | 0x0100); + wm8731_write(codec, WM8731_RINVOL, reg & ~0x0100); wm8731_add_controls(codec); wm8731_add_widgets(codec); diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 28684eeda73..77a857b997a 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -19,7 +19,6 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -189,7 +188,7 @@ SOC_ENUM("Bass Boost", wm8750_enum[0]), SOC_ENUM("Bass Filter", wm8750_enum[1]), SOC_SINGLE("Bass Volume", WM8750_BASS, 0, 15, 1), -SOC_SINGLE("Treble Volume", WM8750_TREBLE, 0, 15, 0), +SOC_SINGLE("Treble Volume", WM8750_TREBLE, 0, 15, 1), SOC_ENUM("Treble Cut-off", wm8750_enum[2]), SOC_SINGLE("3D Switch", WM8750_3D, 0, 1, 0), diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index efced934566..ddd9c71b3fd 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -41,13 +41,13 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> +#include <sound/tlv.h> #include <asm/div64.h> #include "wm8753.h" @@ -258,6 +258,8 @@ static int wm8753_set_dai(struct snd_kcontrol *kcontrol, return 1; } +static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600); + static const struct snd_kcontrol_new wm8753_snd_controls[] = { SOC_DOUBLE_R("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0), @@ -287,8 +289,8 @@ SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1), SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1), SOC_ENUM("Treble Cut-off", wm8753_enum[2]), -SOC_DOUBLE("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1), -SOC_SINGLE("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1), +SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1, rec_mix_tlv), +SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1, rec_mix_tlv), SOC_DOUBLE_R("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0), SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0), diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 986b5d59cef..590baea3c4c 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -19,7 +19,6 @@ #include <linux/version.h> #include <linux/kernel.h> #include <linux/device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/ac97_codec.h> @@ -102,7 +101,8 @@ SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0), SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0), SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0), SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0), -SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 0), +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), @@ -131,7 +131,7 @@ SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1), SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1), SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1), -SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 0), +SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1), SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1), SOC_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0), @@ -145,8 +145,8 @@ SOC_ENUM("Bass Control", wm9712_enum[5]), SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1), SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1), SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0), -SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 0), -SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 0), +SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1), +SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1), SOC_SINGLE("Capture ADC Switch", AC97_REC_GAIN, 15, 1, 1), SOC_ENUM("Capture Volume Steps", wm9712_enum[6]), diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig new file mode 100644 index 00000000000..257101f44e9 --- /dev/null +++ b/sound/soc/fsl/Kconfig @@ -0,0 +1,20 @@ +menu "ALSA SoC audio for Freescale SOCs" + +config SND_SOC_MPC8610 + bool "ALSA SoC support for the MPC8610 SOC" + depends on SND_SOC && MPC8610_HPCD + default y if MPC8610 + help + Say Y if you want to add support for codecs attached to the SSI + device on an MPC8610. + +config SND_SOC_MPC8610_HPCD + bool "ALSA SoC support for the Freescale MPC8610 HPCD board" + depends on SND_SOC_MPC8610 + select SND_SOC_CS4270 + select SND_SOC_CS4270_VD33_ERRATA + default y if MPC8610_HPCD + help + Say Y if you want to enable audio on the Freescale MPC8610 HPCD. + +endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile new file mode 100644 index 00000000000..62f680a4a77 --- /dev/null +++ b/sound/soc/fsl/Makefile @@ -0,0 +1,6 @@ +# MPC8610 HPCD Machine Support +obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += mpc8610_hpcd.o + +# MPC8610 Platform Support +obj-$(CONFIG_SND_SOC_MPC8610) += fsl_ssi.o fsl_dma.o + diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c new file mode 100644 index 00000000000..652514fc814 --- /dev/null +++ b/sound/soc/fsl/fsl_dma.c @@ -0,0 +1,841 @@ +/* + * Freescale DMA ALSA SoC PCM driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed + * under the terms of the GNU General Public License version 2. This + * program is licensed "as is" without any warranty of any kind, whether + * express or implied. + * + * This driver implements ASoC support for the Elo DMA controller, which is + * the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms, + * the PCM driver is what handles the DMA buffer. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/io.h> + +#include "fsl_dma.h" + +/* + * The formats that the DMA controller supports, which is anything + * that is 8, 16, or 32 bits. + */ +#define FSLDMA_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_U16_BE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_U24_LE | \ + SNDRV_PCM_FMTBIT_U24_BE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_S32_BE | \ + SNDRV_PCM_FMTBIT_U32_LE | \ + SNDRV_PCM_FMTBIT_U32_BE) + +#define FSLDMA_PCM_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \ + SNDRV_PCM_RATE_CONTINUOUS) + +/* DMA global data. This structure is used by fsl_dma_open() to determine + * which DMA channels to assign to a substream. Unfortunately, ASoC V1 does + * not allow the machine driver to provide this information to the PCM + * driver in advance, and there's no way to differentiate between the two + * DMA controllers. So for now, this driver only supports one SSI device + * using two DMA channels. We cannot support multiple DMA devices. + * + * ssi_stx_phys: bus address of SSI STX register + * ssi_srx_phys: bus address of SSI SRX register + * dma_channel: pointer to the DMA channel's registers + * irq: IRQ for this DMA channel + * assigned: set to 1 if that DMA channel is assigned to a substream + */ +static struct { + dma_addr_t ssi_stx_phys; + dma_addr_t ssi_srx_phys; + struct ccsr_dma_channel __iomem *dma_channel[2]; + unsigned int irq[2]; + unsigned int assigned[2]; +} dma_global_data; + +/* + * The number of DMA links to use. Two is the bare minimum, but if you + * have really small links you might need more. + */ +#define NUM_DMA_LINKS 2 + +/** fsl_dma_private: p-substream DMA data + * + * Each substream has a 1-to-1 association with a DMA channel. + * + * The link[] array is first because it needs to be aligned on a 32-byte + * boundary, so putting it first will ensure alignment without padding the + * structure. + * + * @link[]: array of link descriptors + * @controller_id: which DMA controller (0, 1, ...) + * @channel_id: which DMA channel on the controller (0, 1, 2, ...) + * @dma_channel: pointer to the DMA channel's registers + * @irq: IRQ for this DMA channel + * @substream: pointer to the substream object, needed by the ISR + * @ssi_sxx_phys: bus address of the STX or SRX register to use + * @ld_buf_phys: physical address of the LD buffer + * @current_link: index into link[] of the link currently being processed + * @dma_buf_phys: physical address of the DMA buffer + * @dma_buf_next: physical address of the next period to process + * @dma_buf_end: physical address of the byte after the end of the DMA + * @buffer period_size: the size of a single period + * @num_periods: the number of periods in the DMA buffer + */ +struct fsl_dma_private { + struct fsl_dma_link_descriptor link[NUM_DMA_LINKS]; + unsigned int controller_id; + unsigned int channel_id; + struct ccsr_dma_channel __iomem *dma_channel; + unsigned int irq; + struct snd_pcm_substream *substream; + dma_addr_t ssi_sxx_phys; + dma_addr_t ld_buf_phys; + unsigned int current_link; + dma_addr_t dma_buf_phys; + dma_addr_t dma_buf_next; + dma_addr_t dma_buf_end; + size_t period_size; + unsigned int num_periods; +}; + +/** + * fsl_dma_hardare: define characteristics of the PCM hardware. + * + * The PCM hardware is the Freescale DMA controller. This structure defines + * the capabilities of that hardware. + * + * Since the sampling rate and data format are not controlled by the DMA + * controller, we specify no limits for those values. The only exception is + * period_bytes_min, which is set to a reasonably low value to prevent the + * DMA controller from generating too many interrupts per second. + * + * Since each link descriptor has a 32-bit byte count field, we set + * period_bytes_max to the largest 32-bit number. We also have no maximum + * number of periods. + */ +static const struct snd_pcm_hardware fsl_dma_hardware = { + + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = FSLDMA_PCM_FORMATS, + .rates = FSLDMA_PCM_RATES, + .rate_min = 5512, + .rate_max = 192000, + .period_bytes_min = 512, /* A reasonable limit */ + .period_bytes_max = (u32) -1, + .periods_min = NUM_DMA_LINKS, + .periods_max = (unsigned int) -1, + .buffer_bytes_max = 128 * 1024, /* A reasonable limit */ +}; + +/** + * fsl_dma_abort_stream: tell ALSA that the DMA transfer has aborted + * + * This function should be called by the ISR whenever the DMA controller + * halts data transfer. + */ +static void fsl_dma_abort_stream(struct snd_pcm_substream *substream) +{ + unsigned long flags; + + snd_pcm_stream_lock_irqsave(substream, flags); + + if (snd_pcm_running(substream)) + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + + snd_pcm_stream_unlock_irqrestore(substream, flags); +} + +/** + * fsl_dma_update_pointers - update LD pointers to point to the next period + * + * As each period is completed, this function changes the the link + * descriptor pointers for that period to point to the next period. + */ +static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private) +{ + struct fsl_dma_link_descriptor *link = + &dma_private->link[dma_private->current_link]; + + /* Update our link descriptors to point to the next period */ + if (dma_private->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + link->source_addr = + cpu_to_be32(dma_private->dma_buf_next); + else + link->dest_addr = + cpu_to_be32(dma_private->dma_buf_next); + + /* Update our variables for next time */ + dma_private->dma_buf_next += dma_private->period_size; + + if (dma_private->dma_buf_next >= dma_private->dma_buf_end) + dma_private->dma_buf_next = dma_private->dma_buf_phys; + + if (++dma_private->current_link >= NUM_DMA_LINKS) + dma_private->current_link = 0; +} + +/** + * fsl_dma_isr: interrupt handler for the DMA controller + * + * @irq: IRQ of the DMA channel + * @dev_id: pointer to the dma_private structure for this DMA channel + */ +static irqreturn_t fsl_dma_isr(int irq, void *dev_id) +{ + struct fsl_dma_private *dma_private = dev_id; + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + irqreturn_t ret = IRQ_NONE; + u32 sr, sr2 = 0; + + /* We got an interrupt, so read the status register to see what we + were interrupted for. + */ + sr = in_be32(&dma_channel->sr); + + if (sr & CCSR_DMA_SR_TE) { + dev_err(dma_private->substream->pcm->card->dev, + "DMA transmit error (controller=%u channel=%u irq=%u\n", + dma_private->controller_id, + dma_private->channel_id, irq); + fsl_dma_abort_stream(dma_private->substream); + sr2 |= CCSR_DMA_SR_TE; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_CH) + ret = IRQ_HANDLED; + + if (sr & CCSR_DMA_SR_PE) { + dev_err(dma_private->substream->pcm->card->dev, + "DMA%u programming error (channel=%u irq=%u)\n", + dma_private->controller_id, + dma_private->channel_id, irq); + fsl_dma_abort_stream(dma_private->substream); + sr2 |= CCSR_DMA_SR_PE; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_EOLNI) { + sr2 |= CCSR_DMA_SR_EOLNI; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_CB) + ret = IRQ_HANDLED; + + if (sr & CCSR_DMA_SR_EOSI) { + struct snd_pcm_substream *substream = dma_private->substream; + + /* Tell ALSA we completed a period. */ + snd_pcm_period_elapsed(substream); + + /* + * Update our link descriptors to point to the next period. We + * only need to do this if the number of periods is not equal to + * the number of links. + */ + if (dma_private->num_periods != NUM_DMA_LINKS) + fsl_dma_update_pointers(dma_private); + + sr2 |= CCSR_DMA_SR_EOSI; + ret = IRQ_HANDLED; + } + + if (sr & CCSR_DMA_SR_EOLSI) { + sr2 |= CCSR_DMA_SR_EOLSI; + ret = IRQ_HANDLED; + } + + /* Clear the bits that we set */ + if (sr2) + out_be32(&dma_channel->sr, sr2); + + return ret; +} + +/** + * fsl_dma_new: initialize this PCM driver. + * + * This function is called when the codec driver calls snd_soc_new_pcms(), + * once for each .dai_link in the machine driver's snd_soc_machine + * structure. + */ +static int fsl_dma_new(struct snd_card *card, struct snd_soc_codec_dai *dai, + struct snd_pcm *pcm) +{ + static u64 fsl_dma_dmamask = DMA_BIT_MASK(32); + int ret; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &fsl_dma_dmamask; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = fsl_dma_dmamask; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, + fsl_dma_hardware.buffer_bytes_max, + &pcm->streams[0].substream->dma_buffer); + if (ret) { + dev_err(card->dev, + "Can't allocate playback DMA buffer (size=%u)\n", + fsl_dma_hardware.buffer_bytes_max); + return -ENOMEM; + } + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, + fsl_dma_hardware.buffer_bytes_max, + &pcm->streams[1].substream->dma_buffer); + if (ret) { + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); + dev_err(card->dev, + "Can't allocate capture DMA buffer (size=%u)\n", + fsl_dma_hardware.buffer_bytes_max); + return -ENOMEM; + } + + return 0; +} + +/** + * fsl_dma_open: open a new substream. + * + * Each substream has its own DMA buffer. + */ +static int fsl_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private; + dma_addr_t ld_buf_phys; + unsigned int channel; + int ret = 0; + + /* + * Reject any DMA buffer whose size is not a multiple of the period + * size. We need to make sure that the DMA buffer can be evenly divided + * into periods. + */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(substream->pcm->card->dev, "invalid buffer size\n"); + return ret; + } + + channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + if (dma_global_data.assigned[channel]) { + dev_err(substream->pcm->card->dev, + "DMA channel already assigned\n"); + return -EBUSY; + } + + dma_private = dma_alloc_coherent(substream->pcm->dev, + sizeof(struct fsl_dma_private), &ld_buf_phys, GFP_KERNEL); + if (!dma_private) { + dev_err(substream->pcm->card->dev, + "can't allocate DMA private data\n"); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_private->ssi_sxx_phys = dma_global_data.ssi_stx_phys; + else + dma_private->ssi_sxx_phys = dma_global_data.ssi_srx_phys; + + dma_private->dma_channel = dma_global_data.dma_channel[channel]; + dma_private->irq = dma_global_data.irq[channel]; + dma_private->substream = substream; + dma_private->ld_buf_phys = ld_buf_phys; + dma_private->dma_buf_phys = substream->dma_buffer.addr; + + /* We only support one DMA controller for now */ + dma_private->controller_id = 0; + dma_private->channel_id = channel; + + ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "DMA", dma_private); + if (ret) { + dev_err(substream->pcm->card->dev, + "can't register ISR for IRQ %u (ret=%i)\n", + dma_private->irq, ret); + dma_free_coherent(substream->pcm->dev, + sizeof(struct fsl_dma_private), + dma_private, dma_private->ld_buf_phys); + return ret; + } + + dma_global_data.assigned[channel] = 1; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware); + runtime->private_data = dma_private; + + return 0; +} + +/** + * fsl_dma_hw_params: allocate the DMA buffer and the DMA link descriptors. + * + * ALSA divides the DMA buffer into N periods. We create NUM_DMA_LINKS link + * descriptors that ping-pong from one period to the next. For example, if + * there are six periods and two link descriptors, this is how they look + * before playback starts: + * + * The last link descriptor + * ____________ points back to the first + * | | + * V | + * ___ ___ | + * | |->| |->| + * |___| |___| + * | | + * | | + * V V + * _________________________________________ + * | | | | | | | The DMA buffer is + * | | | | | | | divided into 6 parts + * |______|______|______|______|______|______| + * + * and here's how they look after the first period is finished playing: + * + * ____________ + * | | + * V | + * ___ ___ | + * | |->| |->| + * |___| |___| + * | | + * |______________ + * | | + * V V + * _________________________________________ + * | | | | | | | + * | | | | | | | + * |______|______|______|______|______|______| + * + * The first link descriptor now points to the third period. The DMA + * controller is currently playing the second period. When it finishes, it + * will jump back to the first descriptor and play the third period. + * + * There are four reasons we do this: + * + * 1. The only way to get the DMA controller to automatically restart the + * transfer when it gets to the end of the buffer is to use chaining + * mode. Basic direct mode doesn't offer that feature. + * 2. We need to receive an interrupt at the end of every period. The DMA + * controller can generate an interrupt at the end of every link transfer + * (aka segment). Making each period into a DMA segment will give us the + * interrupts we need. + * 3. By creating only two link descriptors, regardless of the number of + * periods, we do not need to reallocate the link descriptors if the + * number of periods changes. + * 4. All of the audio data is still stored in a single, contiguous DMA + * buffer, which is what ALSA expects. We're just dividing it into + * contiguous parts, and creating a link descriptor for each one. + * + * Note that due to a quirk of the SSI's STX register, the target address + * for the DMA operations depends on the sample size. So we don't program + * the dest_addr (for playback -- source_addr for capture) fields in the + * link descriptors here. We do that in fsl_dma_prepare() + */ +static int fsl_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + + dma_addr_t temp_addr; /* Pointer to next period */ + u64 temp_link; /* Pointer to next link descriptor */ + u32 mr; /* Temporary variable for MR register */ + + unsigned int i; + + /* Get all the parameters we need */ + size_t buffer_size = params_buffer_bytes(hw_params); + size_t period_size = params_period_bytes(hw_params); + + /* Initialize our DMA tracking variables */ + dma_private->period_size = period_size; + dma_private->num_periods = params_periods(hw_params); + dma_private->dma_buf_end = dma_private->dma_buf_phys + buffer_size; + dma_private->dma_buf_next = dma_private->dma_buf_phys + + (NUM_DMA_LINKS * period_size); + if (dma_private->dma_buf_next >= dma_private->dma_buf_end) + dma_private->dma_buf_next = dma_private->dma_buf_phys; + + /* + * Initialize each link descriptor. + * + * The actual address in STX0 (destination for playback, source for + * capture) is based on the sample size, but we don't know the sample + * size in this function, so we'll have to adjust that later. See + * comments in fsl_dma_prepare(). + * + * The DMA controller does not have a cache, so the CPU does not + * need to tell it to flush its cache. However, the DMA + * controller does need to tell the CPU to flush its cache. + * That's what the SNOOP bit does. + * + * Also, even though the DMA controller supports 36-bit addressing, for + * simplicity we currently support only 32-bit addresses for the audio + * buffer itself. + */ + temp_addr = substream->dma_buffer.addr; + temp_link = dma_private->ld_buf_phys + + sizeof(struct fsl_dma_link_descriptor); + + for (i = 0; i < NUM_DMA_LINKS; i++) { + struct fsl_dma_link_descriptor *link = &dma_private->link[i]; + + link->count = cpu_to_be32(period_size); + link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP); + link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP); + link->next = cpu_to_be64(temp_link); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + link->source_addr = cpu_to_be32(temp_addr); + else + link->dest_addr = cpu_to_be32(temp_addr); + + temp_addr += period_size; + temp_link += sizeof(struct fsl_dma_link_descriptor); + } + /* The last link descriptor points to the first */ + dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys); + + /* Tell the DMA controller where the first link descriptor is */ + out_be32(&dma_channel->clndar, + CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys)); + out_be32(&dma_channel->eclndar, + CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys)); + + /* The manual says the BCR must be clear before enabling EMP */ + out_be32(&dma_channel->bcr, 0); + + /* + * Program the mode register for interrupts, external master control, + * and source/destination hold. Also clear the Channel Abort bit. + */ + mr = in_be32(&dma_channel->mr) & + ~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE); + + /* + * We want External Master Start and External Master Pause enabled, + * because the SSI is controlling the DMA controller. We want the DMA + * controller to be set up in advance, and then we signal only the SSI + * to start transfering. + * + * We want End-Of-Segment Interrupts enabled, because this will generate + * an interrupt at the end of each segment (each link descriptor + * represents one segment). Each DMA segment is the same thing as an + * ALSA period, so this is how we get an interrupt at the end of every + * period. + * + * We want Error Interrupt enabled, so that we can get an error if + * the DMA controller is mis-programmed somehow. + */ + mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN | + CCSR_DMA_MR_EMS_EN; + + /* For playback, we want the destination address to be held. For + capture, set the source address to be held. */ + mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE; + + out_be32(&dma_channel->mr, mr); + + return 0; +} + +/** + * fsl_dma_prepare - prepare the DMA registers for playback. + * + * This function is called after the specifics of the audio data are known, + * i.e. snd_pcm_runtime is initialized. + * + * In this function, we finish programming the registers of the DMA + * controller that are dependent on the sample size. + * + * One of the drawbacks with big-endian is that when copying integers of + * different sizes to a fixed-sized register, the address to which the + * integer must be copied is dependent on the size of the integer. + * + * For example, if P is the address of a 32-bit register, and X is a 32-bit + * integer, then X should be copied to address P. However, if X is a 16-bit + * integer, then it should be copied to P+2. If X is an 8-bit register, + * then it should be copied to P+3. + * + * So for playback of 8-bit samples, the DMA controller must transfer single + * bytes from the DMA buffer to the last byte of the STX0 register, i.e. + * offset by 3 bytes. For 16-bit samples, the offset is two bytes. + * + * For 24-bit samples, the offset is 1 byte. However, the DMA controller + * does not support 3-byte copies (the DAHTS register supports only 1, 2, 4, + * and 8 bytes at a time). So we do not support packed 24-bit samples. + * 24-bit data must be padded to 32 bits. + */ +static int fsl_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + u32 mr; + unsigned int i; + dma_addr_t ssi_sxx_phys; /* Bus address of SSI STX register */ + unsigned int frame_size; /* Number of bytes per frame */ + + ssi_sxx_phys = dma_private->ssi_sxx_phys; + + mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK | + CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK); + + switch (runtime->sample_bits) { + case 8: + mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1; + ssi_sxx_phys += 3; + break; + case 16: + mr |= CCSR_DMA_MR_DAHTS_2 | CCSR_DMA_MR_SAHTS_2; + ssi_sxx_phys += 2; + break; + case 32: + mr |= CCSR_DMA_MR_DAHTS_4 | CCSR_DMA_MR_SAHTS_4; + break; + default: + dev_err(substream->pcm->card->dev, + "unsupported sample size %u\n", runtime->sample_bits); + return -EINVAL; + } + + frame_size = runtime->frame_bits / 8; + /* + * BWC should always be a multiple of the frame size. BWC determines + * how many bytes are sent/received before the DMA controller checks the + * SSI to see if it needs to stop. For playback, the transmit FIFO can + * hold three frames, so we want to send two frames at a time. For + * capture, the receive FIFO is triggered when it contains one frame, so + * we want to receive one frame at a time. + */ + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mr |= CCSR_DMA_MR_BWC(2 * frame_size); + else + mr |= CCSR_DMA_MR_BWC(frame_size); + + out_be32(&dma_channel->mr, mr); + + /* + * Program the address of the DMA transfer to/from the SSI. + */ + for (i = 0; i < NUM_DMA_LINKS; i++) { + struct fsl_dma_link_descriptor *link = &dma_private->link[i]; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + link->dest_addr = cpu_to_be32(ssi_sxx_phys); + else + link->source_addr = cpu_to_be32(ssi_sxx_phys); + } + + return 0; +} + +/** + * fsl_dma_pointer: determine the current position of the DMA transfer + * + * This function is called by ALSA when ALSA wants to know where in the + * stream buffer the hardware currently is. + * + * For playback, the SAR register contains the physical address of the most + * recent DMA transfer. For capture, the value is in the DAR register. + * + * The base address of the buffer is stored in the source_addr field of the + * first link descriptor. + */ +static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel; + dma_addr_t position; + snd_pcm_uframes_t frames; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + position = in_be32(&dma_channel->sar); + else + position = in_be32(&dma_channel->dar); + + frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys); + + /* + * If the current address is just past the end of the buffer, wrap it + * around. + */ + if (frames == runtime->buffer_size) + frames = 0; + + return frames; +} + +/** + * fsl_dma_hw_free: release resources allocated in fsl_dma_hw_params() + * + * Release the resources allocated in fsl_dma_hw_params() and de-program the + * registers. + * + * This function can be called multiple times. + */ +static int fsl_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + + if (dma_private) { + struct ccsr_dma_channel __iomem *dma_channel; + + dma_channel = dma_private->dma_channel; + + /* Stop the DMA */ + out_be32(&dma_channel->mr, CCSR_DMA_MR_CA); + out_be32(&dma_channel->mr, 0); + + /* Reset all the other registers */ + out_be32(&dma_channel->sr, -1); + out_be32(&dma_channel->clndar, 0); + out_be32(&dma_channel->eclndar, 0); + out_be32(&dma_channel->satr, 0); + out_be32(&dma_channel->sar, 0); + out_be32(&dma_channel->datr, 0); + out_be32(&dma_channel->dar, 0); + out_be32(&dma_channel->bcr, 0); + out_be32(&dma_channel->nlndar, 0); + out_be32(&dma_channel->enlndar, 0); + } + + return 0; +} + +/** + * fsl_dma_close: close the stream. + */ +static int fsl_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct fsl_dma_private *dma_private = runtime->private_data; + int dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + if (dma_private) { + if (dma_private->irq) + free_irq(dma_private->irq, dma_private); + + if (dma_private->ld_buf_phys) { + dma_unmap_single(substream->pcm->dev, + dma_private->ld_buf_phys, + sizeof(dma_private->link), DMA_TO_DEVICE); + } + + /* Deallocate the fsl_dma_private structure */ + dma_free_coherent(substream->pcm->dev, + sizeof(struct fsl_dma_private), + dma_private, dma_private->ld_buf_phys); + substream->runtime->private_data = NULL; + } + + dma_global_data.assigned[dir] = 0; + + return 0; +} + +/* + * Remove this PCM driver. + */ +static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) { + substream = pcm->streams[i].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +static struct snd_pcm_ops fsl_dma_ops = { + .open = fsl_dma_open, + .close = fsl_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = fsl_dma_hw_params, + .hw_free = fsl_dma_hw_free, + .prepare = fsl_dma_prepare, + .pointer = fsl_dma_pointer, +}; + +struct snd_soc_platform fsl_soc_platform = { + .name = "fsl-dma", + .pcm_ops = &fsl_dma_ops, + .pcm_new = fsl_dma_new, + .pcm_free = fsl_dma_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(fsl_soc_platform); + +/** + * fsl_dma_configure: store the DMA parameters from the fabric driver. + * + * This function is called by the ASoC fabric driver to give us the DMA and + * SSI channel information. + * + * Unfortunately, ASoC V1 does make it possible to determine the DMA/SSI + * data when a substream is created, so for now we need to store this data + * into a global variable. This means that we can only support one DMA + * controller, and hence only one SSI. + */ +int fsl_dma_configure(struct fsl_dma_info *dma_info) +{ + static int initialized; + + /* We only support one DMA controller for now */ + if (initialized) + return 0; + + dma_global_data.ssi_stx_phys = dma_info->ssi_stx_phys; + dma_global_data.ssi_srx_phys = dma_info->ssi_srx_phys; + dma_global_data.dma_channel[0] = dma_info->dma_channel[0]; + dma_global_data.dma_channel[1] = dma_info->dma_channel[1]; + dma_global_data.irq[0] = dma_info->dma_irq[0]; + dma_global_data.irq[1] = dma_info->dma_irq[1]; + dma_global_data.assigned[0] = 0; + dma_global_data.assigned[1] = 0; + + initialized = 1; + return 1; +} +EXPORT_SYMBOL_GPL(fsl_dma_configure); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_dma.h b/sound/soc/fsl/fsl_dma.h new file mode 100644 index 00000000000..430a6ce8b0d --- /dev/null +++ b/sound/soc/fsl/fsl_dma.h @@ -0,0 +1,149 @@ +/* + * mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _MPC8610_PCM_H +#define _MPC8610_PCM_H + +struct ccsr_dma { + u8 res0[0x100]; + struct ccsr_dma_channel { + __be32 mr; /* Mode register */ + __be32 sr; /* Status register */ + __be32 eclndar; /* Current link descriptor extended addr reg */ + __be32 clndar; /* Current link descriptor address register */ + __be32 satr; /* Source attributes register */ + __be32 sar; /* Source address register */ + __be32 datr; /* Destination attributes register */ + __be32 dar; /* Destination address register */ + __be32 bcr; /* Byte count register */ + __be32 enlndar; /* Next link descriptor extended address reg */ + __be32 nlndar; /* Next link descriptor address register */ + u8 res1[4]; + __be32 eclsdar; /* Current list descriptor extended addr reg */ + __be32 clsdar; /* Current list descriptor address register */ + __be32 enlsdar; /* Next list descriptor extended address reg */ + __be32 nlsdar; /* Next list descriptor address register */ + __be32 ssr; /* Source stride register */ + __be32 dsr; /* Destination stride register */ + u8 res2[0x38]; + } channel[4]; + __be32 dgsr; +}; + +#define CCSR_DMA_MR_BWC_DISABLED 0x0F000000 +#define CCSR_DMA_MR_BWC_SHIFT 24 +#define CCSR_DMA_MR_BWC_MASK 0x0F000000 +#define CCSR_DMA_MR_BWC(x) \ + ((ilog2(x) << CCSR_DMA_MR_BWC_SHIFT) & CCSR_DMA_MR_BWC_MASK) +#define CCSR_DMA_MR_EMP_EN 0x00200000 +#define CCSR_DMA_MR_EMS_EN 0x00040000 +#define CCSR_DMA_MR_DAHTS_MASK 0x00030000 +#define CCSR_DMA_MR_DAHTS_1 0x00000000 +#define CCSR_DMA_MR_DAHTS_2 0x00010000 +#define CCSR_DMA_MR_DAHTS_4 0x00020000 +#define CCSR_DMA_MR_DAHTS_8 0x00030000 +#define CCSR_DMA_MR_SAHTS_MASK 0x0000C000 +#define CCSR_DMA_MR_SAHTS_1 0x00000000 +#define CCSR_DMA_MR_SAHTS_2 0x00004000 +#define CCSR_DMA_MR_SAHTS_4 0x00008000 +#define CCSR_DMA_MR_SAHTS_8 0x0000C000 +#define CCSR_DMA_MR_DAHE 0x00002000 +#define CCSR_DMA_MR_SAHE 0x00001000 +#define CCSR_DMA_MR_SRW 0x00000400 +#define CCSR_DMA_MR_EOSIE 0x00000200 +#define CCSR_DMA_MR_EOLNIE 0x00000100 +#define CCSR_DMA_MR_EOLSIE 0x00000080 +#define CCSR_DMA_MR_EIE 0x00000040 +#define CCSR_DMA_MR_XFE 0x00000020 +#define CCSR_DMA_MR_CDSM_SWSM 0x00000010 +#define CCSR_DMA_MR_CA 0x00000008 +#define CCSR_DMA_MR_CTM 0x00000004 +#define CCSR_DMA_MR_CC 0x00000002 +#define CCSR_DMA_MR_CS 0x00000001 + +#define CCSR_DMA_SR_TE 0x00000080 +#define CCSR_DMA_SR_CH 0x00000020 +#define CCSR_DMA_SR_PE 0x00000010 +#define CCSR_DMA_SR_EOLNI 0x00000008 +#define CCSR_DMA_SR_CB 0x00000004 +#define CCSR_DMA_SR_EOSI 0x00000002 +#define CCSR_DMA_SR_EOLSI 0x00000001 + +/* ECLNDAR takes bits 32-36 of the CLNDAR register */ +static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x) +{ + return (x >> 32) & 0xf; +} + +#define CCSR_DMA_CLNDAR_ADDR(x) ((x) & 0xFFFFFFFE) +#define CCSR_DMA_CLNDAR_EOSIE 0x00000008 + +/* SATR and DATR, combined */ +#define CCSR_DMA_ATR_PBATMU 0x20000000 +#define CCSR_DMA_ATR_TFLOWLVL_0 0x00000000 +#define CCSR_DMA_ATR_TFLOWLVL_1 0x06000000 +#define CCSR_DMA_ATR_TFLOWLVL_2 0x08000000 +#define CCSR_DMA_ATR_TFLOWLVL_3 0x0C000000 +#define CCSR_DMA_ATR_PCIORDER 0x02000000 +#define CCSR_DMA_ATR_SME 0x01000000 +#define CCSR_DMA_ATR_NOSNOOP 0x00040000 +#define CCSR_DMA_ATR_SNOOP 0x00050000 +#define CCSR_DMA_ATR_ESAD_MASK 0x0000000F + +/** + * List Descriptor for extended chaining mode DMA operations. + * + * The CLSDAR register points to the first (in a linked-list) List + * Descriptor. Each object must be aligned on a 32-byte boundary. Each + * list descriptor points to a linked-list of link Descriptors. + */ +struct fsl_dma_list_descriptor { + __be64 next; /* Address of next list descriptor */ + __be64 first_link; /* Address of first link descriptor */ + __be32 source; /* Source stride */ + __be32 dest; /* Destination stride */ + u8 res[8]; /* Reserved */ +} __attribute__ ((aligned(32), packed)); + +/** + * Link Descriptor for basic and extended chaining mode DMA operations. + * + * A Link Descriptor points to a single DMA buffer. Each link descriptor + * must be aligned on a 32-byte boundary. + */ +struct fsl_dma_link_descriptor { + __be32 source_attr; /* Programmed into SATR register */ + __be32 source_addr; /* Programmed into SAR register */ + __be32 dest_attr; /* Programmed into DATR register */ + __be32 dest_addr; /* Programmed into DAR register */ + __be64 next; /* Address of next link descriptor */ + __be32 count; /* Byte count */ + u8 res[4]; /* Reserved */ +} __attribute__ ((aligned(32), packed)); + +/* DMA information needed to create a snd_soc_cpu_dai object + * + * ssi_stx_phys: bus address of SSI STX register to use + * ssi_srx_phys: bus address of SSI SRX register to use + * dma[0]: points to the DMA channel to use for playback + * dma[1]: points to the DMA channel to use for capture + * dma_irq[0]: IRQ of the DMA channel to use for playback + * dma_irq[1]: IRQ of the DMA channel to use for capture + */ +struct fsl_dma_info { + dma_addr_t ssi_stx_phys; + dma_addr_t ssi_srx_phys; + struct ccsr_dma_channel __iomem *dma_channel[2]; + unsigned int dma_irq[2]; +}; + +extern struct snd_soc_platform fsl_soc_platform; + +int fsl_dma_configure(struct fsl_dma_info *dma_info); + +#endif diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c new file mode 100644 index 00000000000..145ad13d52d --- /dev/null +++ b/sound/soc/fsl/fsl_ssi.c @@ -0,0 +1,644 @@ +/* + * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed + * under the terms of the GNU General Public License version 2. This + * program is licensed "as is" without any warranty of any kind, whether + * express or implied. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> + +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <asm/immap_86xx.h> + +#include "fsl_ssi.h" + +/** + * FSLSSI_I2S_RATES: sample rates supported by the I2S + * + * This driver currently only supports the SSI running in I2S slave mode, + * which means the codec determines the sample rate. Therefore, we tell + * ALSA that we support all rates and let the codec driver decide what rates + * are really supported. + */ +#define FSLSSI_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \ + SNDRV_PCM_RATE_CONTINUOUS) + +/** + * FSLSSI_I2S_FORMATS: audio formats supported by the SSI + * + * This driver currently only supports the SSI running in I2S slave mode. + * + * The SSI has a limitation in that the samples must be in the same byte + * order as the host CPU. This is because when multiple bytes are written + * to the STX register, the bytes and bits must be written in the same + * order. The STX is a shift register, so all the bits need to be aligned + * (bit-endianness must match byte-endianness). Processors typically write + * the bits within a byte in the same order that the bytes of a word are + * written in. So if the host CPU is big-endian, then only big-endian + * samples will be written to STX properly. + */ +#ifdef __BIG_ENDIAN +#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_S24_BE) +#else +#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE) +#endif + +/** + * fsl_ssi_private: per-SSI private data + * + * @name: short name for this device ("SSI0", "SSI1", etc) + * @ssi: pointer to the SSI's registers + * @ssi_phys: physical address of the SSI registers + * @irq: IRQ of this SSI + * @dev: struct device pointer + * @playback: the number of playback streams opened + * @capture: the number of capture streams opened + * @cpu_dai: the CPU DAI for this device + * @dev_attr: the sysfs device attribute structure + * @stats: SSI statistics + */ +struct fsl_ssi_private { + char name[8]; + struct ccsr_ssi __iomem *ssi; + dma_addr_t ssi_phys; + unsigned int irq; + struct device *dev; + unsigned int playback; + unsigned int capture; + struct snd_soc_cpu_dai cpu_dai; + struct device_attribute dev_attr; + + struct { + unsigned int rfrc; + unsigned int tfrc; + unsigned int cmdau; + unsigned int cmddu; + unsigned int rxt; + unsigned int rdr1; + unsigned int rdr0; + unsigned int tde1; + unsigned int tde0; + unsigned int roe1; + unsigned int roe0; + unsigned int tue1; + unsigned int tue0; + unsigned int tfs; + unsigned int rfs; + unsigned int tls; + unsigned int rls; + unsigned int rff1; + unsigned int rff0; + unsigned int tfe1; + unsigned int tfe0; + } stats; +}; + +/** + * fsl_ssi_isr: SSI interrupt handler + * + * Although it's possible to use the interrupt handler to send and receive + * data to/from the SSI, we use the DMA instead. Programming is more + * complicated, but the performance is much better. + * + * This interrupt handler is used only to gather statistics. + * + * @irq: IRQ of the SSI device + * @dev_id: pointer to the ssi_private structure for this SSI device + */ +static irqreturn_t fsl_ssi_isr(int irq, void *dev_id) +{ + struct fsl_ssi_private *ssi_private = dev_id; + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + irqreturn_t ret = IRQ_NONE; + __be32 sisr; + __be32 sisr2 = 0; + + /* We got an interrupt, so read the status register to see what we + were interrupted for. We mask it with the Interrupt Enable register + so that we only check for events that we're interested in. + */ + sisr = in_be32(&ssi->sisr) & in_be32(&ssi->sier); + + if (sisr & CCSR_SSI_SISR_RFRC) { + ssi_private->stats.rfrc++; + sisr2 |= CCSR_SSI_SISR_RFRC; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_TFRC) { + ssi_private->stats.tfrc++; + sisr2 |= CCSR_SSI_SISR_TFRC; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_CMDAU) { + ssi_private->stats.cmdau++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_CMDDU) { + ssi_private->stats.cmddu++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_RXT) { + ssi_private->stats.rxt++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_RDR1) { + ssi_private->stats.rdr1++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_RDR0) { + ssi_private->stats.rdr0++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_TDE1) { + ssi_private->stats.tde1++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_TDE0) { + ssi_private->stats.tde0++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_ROE1) { + ssi_private->stats.roe1++; + sisr2 |= CCSR_SSI_SISR_ROE1; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_ROE0) { + ssi_private->stats.roe0++; + sisr2 |= CCSR_SSI_SISR_ROE0; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_TUE1) { + ssi_private->stats.tue1++; + sisr2 |= CCSR_SSI_SISR_TUE1; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_TUE0) { + ssi_private->stats.tue0++; + sisr2 |= CCSR_SSI_SISR_TUE0; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_TFS) { + ssi_private->stats.tfs++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_RFS) { + ssi_private->stats.rfs++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_TLS) { + ssi_private->stats.tls++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_RLS) { + ssi_private->stats.rls++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_RFF1) { + ssi_private->stats.rff1++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_RFF0) { + ssi_private->stats.rff0++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_TFE1) { + ssi_private->stats.tfe1++; + ret = IRQ_HANDLED; + } + + if (sisr & CCSR_SSI_SISR_TFE0) { + ssi_private->stats.tfe0++; + ret = IRQ_HANDLED; + } + + /* Clear the bits that we set */ + if (sisr2) + out_be32(&ssi->sisr, sisr2); + + return ret; +} + +/** + * fsl_ssi_startup: create a new substream + * + * This is the first function called when a stream is opened. + * + * If this is the first stream open, then grab the IRQ and program most of + * the SSI registers. + */ +static int fsl_ssi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data; + + /* + * If this is the first stream opened, then request the IRQ + * and initialize the SSI registers. + */ + if (!ssi_private->playback && !ssi_private->capture) { + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + int ret; + + ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0, + ssi_private->name, ssi_private); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "could not claim irq %u\n", ssi_private->irq); + return ret; + } + + /* + * Section 16.5 of the MPC8610 reference manual says that the + * SSI needs to be disabled before updating the registers we set + * here. + */ + clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + + /* + * Program the SSI into I2S Slave Non-Network Synchronous mode. + * Also enable the transmit and receive FIFO. + * + * FIXME: Little-endian samples require a different shift dir + */ + clrsetbits_be32(&ssi->scr, CCSR_SSI_SCR_I2S_MODE_MASK, + CCSR_SSI_SCR_TFR_CLK_DIS | + CCSR_SSI_SCR_I2S_MODE_SLAVE | CCSR_SSI_SCR_SYN); + + out_be32(&ssi->stcr, + CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 | + CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS | + CCSR_SSI_STCR_TSCKP); + + out_be32(&ssi->srcr, + CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 | + CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS | + CCSR_SSI_SRCR_RSCKP); + + /* + * The DC and PM bits are only used if the SSI is the clock + * master. + */ + + /* 4. Enable the interrupts and DMA requests */ + out_be32(&ssi->sier, + CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE | + CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN | + CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN | + CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE | + CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN); + + /* + * Set the watermark for transmit FIFI 0 and receive FIFO 0. We + * don't use FIFO 1. Since the SSI only supports stereo, the + * watermark should never be an odd number. + */ + out_be32(&ssi->sfcsr, + CCSR_SSI_SFCSR_TFWM0(6) | CCSR_SSI_SFCSR_RFWM0(2)); + + /* + * We keep the SSI disabled because if we enable it, then the + * DMA controller will start. It's not supposed to start until + * the SCR.TE (or SCR.RE) bit is set, but it does anyway. The + * DMA controller will transfer one "BWC" of data (i.e. the + * amount of data that the MR.BWC bits are set to). The reason + * this is bad is because at this point, the PCM driver has not + * finished initializing the DMA controller. + */ + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ssi_private->playback++; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ssi_private->capture++; + + return 0; +} + +/** + * fsl_ssi_prepare: prepare the SSI. + * + * Most of the SSI registers have been programmed in the startup function, + * but the word length must be programmed here. Unfortunately, programming + * the SxCCR.WL bits requires the SSI to be temporarily disabled. This can + * cause a problem with supporting simultaneous playback and capture. If + * the SSI is already playing a stream, then that stream may be temporarily + * stopped when you start capture. + * + * Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the + * clock master. + */ +static int fsl_ssi_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data; + + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + u32 wl; + + wl = CCSR_SSI_SxCCR_WL(snd_pcm_format_width(runtime->format)); + + clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clrsetbits_be32(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl); + else + clrsetbits_be32(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl); + + setbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + + return 0; +} + +/** + * fsl_ssi_trigger: start and stop the DMA transfer. + * + * This function is called by ALSA to start, stop, pause, and resume the DMA + * transfer of data. + * + * The DMA channel is in external master start and pause mode, which + * means the SSI completely controls the flow of data. + */ +static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data; + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + setbits32(&ssi->scr, CCSR_SSI_SCR_TE); + } else { + setbits32(&ssi->scr, CCSR_SSI_SCR_RE); + + /* + * I think we need this delay to allow time for the SSI + * to put data into its FIFO. Without it, ALSA starts + * to complain about overruns. + */ + msleep(1); + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clrbits32(&ssi->scr, CCSR_SSI_SCR_TE); + else + clrbits32(&ssi->scr, CCSR_SSI_SCR_RE); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/** + * fsl_ssi_shutdown: shutdown the SSI + * + * Shutdown the SSI if there are no other substreams open. + */ +static void fsl_ssi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ssi_private->playback--; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ssi_private->capture--; + + /* + * If this is the last active substream, disable the SSI and release + * the IRQ. + */ + if (!ssi_private->playback && !ssi_private->capture) { + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + + clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + + free_irq(ssi_private->irq, ssi_private); + } +} + +/** + * fsl_ssi_set_sysclk: set the clock frequency and direction + * + * This function is called by the machine driver to tell us what the clock + * frequency and direction are. + * + * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN), + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) + */ +static int fsl_ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + + return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL; +} + +/** + * fsl_ssi_set_fmt: set the serial format. + * + * This function is called by the machine driver to tell us what serial + * format to use. + * + * Currently, we only support I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * + * @format: one of SND_SOC_DAIFMT_xxx + */ +static int fsl_ssi_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format) +{ + return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; +} + +/** + * fsl_ssi_dai_template: template CPU DAI for the SSI + */ +static struct snd_soc_cpu_dai fsl_ssi_dai_template = { + .playback = { + /* The SSI does not support monaural audio. */ + .channels_min = 2, + .channels_max = 2, + .rates = FSLSSI_I2S_RATES, + .formats = FSLSSI_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = FSLSSI_I2S_RATES, + .formats = FSLSSI_I2S_FORMATS, + }, + .ops = { + .startup = fsl_ssi_startup, + .prepare = fsl_ssi_prepare, + .shutdown = fsl_ssi_shutdown, + .trigger = fsl_ssi_trigger, + }, + .dai_ops = { + .set_sysclk = fsl_ssi_set_sysclk, + .set_fmt = fsl_ssi_set_fmt, + }, +}; + +/** + * fsl_sysfs_ssi_show: display SSI statistics + * + * Display the statistics for the current SSI device. + */ +static ssize_t fsl_sysfs_ssi_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_ssi_private *ssi_private = + container_of(attr, struct fsl_ssi_private, dev_attr); + ssize_t length; + + length = sprintf(buf, "rfrc=%u", ssi_private->stats.rfrc); + length += sprintf(buf + length, "\ttfrc=%u", ssi_private->stats.tfrc); + length += sprintf(buf + length, "\tcmdau=%u", ssi_private->stats.cmdau); + length += sprintf(buf + length, "\tcmddu=%u", ssi_private->stats.cmddu); + length += sprintf(buf + length, "\trxt=%u", ssi_private->stats.rxt); + length += sprintf(buf + length, "\trdr1=%u", ssi_private->stats.rdr1); + length += sprintf(buf + length, "\trdr0=%u", ssi_private->stats.rdr0); + length += sprintf(buf + length, "\ttde1=%u", ssi_private->stats.tde1); + length += sprintf(buf + length, "\ttde0=%u", ssi_private->stats.tde0); + length += sprintf(buf + length, "\troe1=%u", ssi_private->stats.roe1); + length += sprintf(buf + length, "\troe0=%u", ssi_private->stats.roe0); + length += sprintf(buf + length, "\ttue1=%u", ssi_private->stats.tue1); + length += sprintf(buf + length, "\ttue0=%u", ssi_private->stats.tue0); + length += sprintf(buf + length, "\ttfs=%u", ssi_private->stats.tfs); + length += sprintf(buf + length, "\trfs=%u", ssi_private->stats.rfs); + length += sprintf(buf + length, "\ttls=%u", ssi_private->stats.tls); + length += sprintf(buf + length, "\trls=%u", ssi_private->stats.rls); + length += sprintf(buf + length, "\trff1=%u", ssi_private->stats.rff1); + length += sprintf(buf + length, "\trff0=%u", ssi_private->stats.rff0); + length += sprintf(buf + length, "\ttfe1=%u", ssi_private->stats.tfe1); + length += sprintf(buf + length, "\ttfe0=%u\n", ssi_private->stats.tfe0); + + return length; +} + +/** + * fsl_ssi_create_dai: create a snd_soc_cpu_dai structure + * + * This function is called by the machine driver to create a snd_soc_cpu_dai + * structure. The function creates an ssi_private object, which contains + * the snd_soc_cpu_dai. It also creates the sysfs statistics device. + */ +struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) +{ + struct snd_soc_cpu_dai *fsl_ssi_dai; + struct fsl_ssi_private *ssi_private; + int ret = 0; + struct device_attribute *dev_attr; + + ssi_private = kzalloc(sizeof(struct fsl_ssi_private), GFP_KERNEL); + if (!ssi_private) { + dev_err(ssi_info->dev, "could not allocate DAI object\n"); + return NULL; + } + memcpy(&ssi_private->cpu_dai, &fsl_ssi_dai_template, + sizeof(struct snd_soc_cpu_dai)); + + fsl_ssi_dai = &ssi_private->cpu_dai; + dev_attr = &ssi_private->dev_attr; + + sprintf(ssi_private->name, "ssi%u", (u8) ssi_info->id); + ssi_private->ssi = ssi_info->ssi; + ssi_private->ssi_phys = ssi_info->ssi_phys; + ssi_private->irq = ssi_info->irq; + ssi_private->dev = ssi_info->dev; + + ssi_private->dev->driver_data = fsl_ssi_dai; + + /* Initialize the the device_attribute structure */ + dev_attr->attr.name = "ssi-stats"; + dev_attr->attr.mode = S_IRUGO; + dev_attr->show = fsl_sysfs_ssi_show; + + ret = device_create_file(ssi_private->dev, dev_attr); + if (ret) { + dev_err(ssi_info->dev, "could not create sysfs %s file\n", + ssi_private->dev_attr.attr.name); + kfree(fsl_ssi_dai); + return NULL; + } + + fsl_ssi_dai->private_data = ssi_private; + fsl_ssi_dai->name = ssi_private->name; + fsl_ssi_dai->id = ssi_info->id; + + return fsl_ssi_dai; +} +EXPORT_SYMBOL_GPL(fsl_ssi_create_dai); + +/** + * fsl_ssi_destroy_dai: destroy the snd_soc_cpu_dai object + * + * This function undoes the operations of fsl_ssi_create_dai() + */ +void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai) +{ + struct fsl_ssi_private *ssi_private = + container_of(fsl_ssi_dai, struct fsl_ssi_private, cpu_dai); + + device_remove_file(ssi_private->dev, &ssi_private->dev_attr); + + kfree(ssi_private); +} +EXPORT_SYMBOL_GPL(fsl_ssi_destroy_dai); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h new file mode 100644 index 00000000000..c5ce88e1565 --- /dev/null +++ b/sound/soc/fsl/fsl_ssi.h @@ -0,0 +1,224 @@ +/* + * fsl_ssi.h - ALSA SSI interface for the Freescale MPC8610 SoC + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed + * under the terms of the GNU General Public License version 2. This + * program is licensed "as is" without any warranty of any kind, whether + * express or implied. + */ + +#ifndef _MPC8610_I2S_H +#define _MPC8610_I2S_H + +/* SSI Register Map */ +struct ccsr_ssi { + __be32 stx0; /* 0x.0000 - SSI Transmit Data Register 0 */ + __be32 stx1; /* 0x.0004 - SSI Transmit Data Register 1 */ + __be32 srx0; /* 0x.0008 - SSI Receive Data Register 0 */ + __be32 srx1; /* 0x.000C - SSI Receive Data Register 1 */ + __be32 scr; /* 0x.0010 - SSI Control Register */ + __be32 sisr; /* 0x.0014 - SSI Interrupt Status Register Mixed */ + __be32 sier; /* 0x.0018 - SSI Interrupt Enable Register */ + __be32 stcr; /* 0x.001C - SSI Transmit Configuration Register */ + __be32 srcr; /* 0x.0020 - SSI Receive Configuration Register */ + __be32 stccr; /* 0x.0024 - SSI Transmit Clock Control Register */ + __be32 srccr; /* 0x.0028 - SSI Receive Clock Control Register */ + __be32 sfcsr; /* 0x.002C - SSI FIFO Control/Status Register */ + __be32 str; /* 0x.0030 - SSI Test Register */ + __be32 sor; /* 0x.0034 - SSI Option Register */ + __be32 sacnt; /* 0x.0038 - SSI AC97 Control Register */ + __be32 sacadd; /* 0x.003C - SSI AC97 Command Address Register */ + __be32 sacdat; /* 0x.0040 - SSI AC97 Command Data Register */ + __be32 satag; /* 0x.0044 - SSI AC97 Tag Register */ + __be32 stmsk; /* 0x.0048 - SSI Transmit Time Slot Mask Register */ + __be32 srmsk; /* 0x.004C - SSI Receive Time Slot Mask Register */ + __be32 saccst; /* 0x.0050 - SSI AC97 Channel Status Register */ + __be32 saccen; /* 0x.0054 - SSI AC97 Channel Enable Register */ + __be32 saccdis; /* 0x.0058 - SSI AC97 Channel Disable Register */ +}; + +#define CCSR_SSI_SCR_RFR_CLK_DIS 0x00000800 +#define CCSR_SSI_SCR_TFR_CLK_DIS 0x00000400 +#define CCSR_SSI_SCR_TCH_EN 0x00000100 +#define CCSR_SSI_SCR_SYS_CLK_EN 0x00000080 +#define CCSR_SSI_SCR_I2S_MODE_MASK 0x00000060 +#define CCSR_SSI_SCR_I2S_MODE_NORMAL 0x00000000 +#define CCSR_SSI_SCR_I2S_MODE_MASTER 0x00000020 +#define CCSR_SSI_SCR_I2S_MODE_SLAVE 0x00000040 +#define CCSR_SSI_SCR_SYN 0x00000010 +#define CCSR_SSI_SCR_NET 0x00000008 +#define CCSR_SSI_SCR_RE 0x00000004 +#define CCSR_SSI_SCR_TE 0x00000002 +#define CCSR_SSI_SCR_SSIEN 0x00000001 + +#define CCSR_SSI_SISR_RFRC 0x01000000 +#define CCSR_SSI_SISR_TFRC 0x00800000 +#define CCSR_SSI_SISR_CMDAU 0x00040000 +#define CCSR_SSI_SISR_CMDDU 0x00020000 +#define CCSR_SSI_SISR_RXT 0x00010000 +#define CCSR_SSI_SISR_RDR1 0x00008000 +#define CCSR_SSI_SISR_RDR0 0x00004000 +#define CCSR_SSI_SISR_TDE1 0x00002000 +#define CCSR_SSI_SISR_TDE0 0x00001000 +#define CCSR_SSI_SISR_ROE1 0x00000800 +#define CCSR_SSI_SISR_ROE0 0x00000400 +#define CCSR_SSI_SISR_TUE1 0x00000200 +#define CCSR_SSI_SISR_TUE0 0x00000100 +#define CCSR_SSI_SISR_TFS 0x00000080 +#define CCSR_SSI_SISR_RFS 0x00000040 +#define CCSR_SSI_SISR_TLS 0x00000020 +#define CCSR_SSI_SISR_RLS 0x00000010 +#define CCSR_SSI_SISR_RFF1 0x00000008 +#define CCSR_SSI_SISR_RFF0 0x00000004 +#define CCSR_SSI_SISR_TFE1 0x00000002 +#define CCSR_SSI_SISR_TFE0 0x00000001 + +#define CCSR_SSI_SIER_RFRC_EN 0x01000000 +#define CCSR_SSI_SIER_TFRC_EN 0x00800000 +#define CCSR_SSI_SIER_RDMAE 0x00400000 +#define CCSR_SSI_SIER_RIE 0x00200000 +#define CCSR_SSI_SIER_TDMAE 0x00100000 +#define CCSR_SSI_SIER_TIE 0x00080000 +#define CCSR_SSI_SIER_CMDAU_EN 0x00040000 +#define CCSR_SSI_SIER_CMDDU_EN 0x00020000 +#define CCSR_SSI_SIER_RXT_EN 0x00010000 +#define CCSR_SSI_SIER_RDR1_EN 0x00008000 +#define CCSR_SSI_SIER_RDR0_EN 0x00004000 +#define CCSR_SSI_SIER_TDE1_EN 0x00002000 +#define CCSR_SSI_SIER_TDE0_EN 0x00001000 +#define CCSR_SSI_SIER_ROE1_EN 0x00000800 +#define CCSR_SSI_SIER_ROE0_EN 0x00000400 +#define CCSR_SSI_SIER_TUE1_EN 0x00000200 +#define CCSR_SSI_SIER_TUE0_EN 0x00000100 +#define CCSR_SSI_SIER_TFS_EN 0x00000080 +#define CCSR_SSI_SIER_RFS_EN 0x00000040 +#define CCSR_SSI_SIER_TLS_EN 0x00000020 +#define CCSR_SSI_SIER_RLS_EN 0x00000010 +#define CCSR_SSI_SIER_RFF1_EN 0x00000008 +#define CCSR_SSI_SIER_RFF0_EN 0x00000004 +#define CCSR_SSI_SIER_TFE1_EN 0x00000002 +#define CCSR_SSI_SIER_TFE0_EN 0x00000001 + +#define CCSR_SSI_STCR_TXBIT0 0x00000200 +#define CCSR_SSI_STCR_TFEN1 0x00000100 +#define CCSR_SSI_STCR_TFEN0 0x00000080 +#define CCSR_SSI_STCR_TFDIR 0x00000040 +#define CCSR_SSI_STCR_TXDIR 0x00000020 +#define CCSR_SSI_STCR_TSHFD 0x00000010 +#define CCSR_SSI_STCR_TSCKP 0x00000008 +#define CCSR_SSI_STCR_TFSI 0x00000004 +#define CCSR_SSI_STCR_TFSL 0x00000002 +#define CCSR_SSI_STCR_TEFS 0x00000001 + +#define CCSR_SSI_SRCR_RXEXT 0x00000400 +#define CCSR_SSI_SRCR_RXBIT0 0x00000200 +#define CCSR_SSI_SRCR_RFEN1 0x00000100 +#define CCSR_SSI_SRCR_RFEN0 0x00000080 +#define CCSR_SSI_SRCR_RFDIR 0x00000040 +#define CCSR_SSI_SRCR_RXDIR 0x00000020 +#define CCSR_SSI_SRCR_RSHFD 0x00000010 +#define CCSR_SSI_SRCR_RSCKP 0x00000008 +#define CCSR_SSI_SRCR_RFSI 0x00000004 +#define CCSR_SSI_SRCR_RFSL 0x00000002 +#define CCSR_SSI_SRCR_REFS 0x00000001 + +/* STCCR and SRCCR */ +#define CCSR_SSI_SxCCR_DIV2 0x00040000 +#define CCSR_SSI_SxCCR_PSR 0x00020000 +#define CCSR_SSI_SxCCR_WL_SHIFT 13 +#define CCSR_SSI_SxCCR_WL_MASK 0x0001E000 +#define CCSR_SSI_SxCCR_WL(x) \ + (((((x) / 2) - 1) << CCSR_SSI_SxCCR_WL_SHIFT) & CCSR_SSI_SxCCR_WL_MASK) +#define CCSR_SSI_SxCCR_DC_SHIFT 8 +#define CCSR_SSI_SxCCR_DC_MASK 0x00001F00 +#define CCSR_SSI_SxCCR_DC(x) \ + ((((x) - 1) << CCSR_SSI_SxCCR_DC_SHIFT) & CCSR_SSI_SxCCR_DC_MASK) +#define CCSR_SSI_SxCCR_PM_SHIFT 0 +#define CCSR_SSI_SxCCR_PM_MASK 0x000000FF +#define CCSR_SSI_SxCCR_PM(x) \ + ((((x) - 1) << CCSR_SSI_SxCCR_PM_SHIFT) & CCSR_SSI_SxCCR_PM_MASK) + +/* + * The xFCNT bits are read-only, and the xFWM bits are read/write. Use the + * CCSR_SSI_SFCSR_xFCNTy() macros to read the FIFO counters, and use the + * CCSR_SSI_SFCSR_xFWMy() macros to set the watermarks. + */ +#define CCSR_SSI_SFCSR_RFCNT1_SHIFT 28 +#define CCSR_SSI_SFCSR_RFCNT1_MASK 0xF0000000 +#define CCSR_SSI_SFCSR_RFCNT1(x) \ + (((x) & CCSR_SSI_SFCSR_RFCNT1_MASK) >> CCSR_SSI_SFCSR_RFCNT1_SHIFT) +#define CCSR_SSI_SFCSR_TFCNT1_SHIFT 24 +#define CCSR_SSI_SFCSR_TFCNT1_MASK 0x0F000000 +#define CCSR_SSI_SFCSR_TFCNT1(x) \ + (((x) & CCSR_SSI_SFCSR_TFCNT1_MASK) >> CCSR_SSI_SFCSR_TFCNT1_SHIFT) +#define CCSR_SSI_SFCSR_RFWM1_SHIFT 20 +#define CCSR_SSI_SFCSR_RFWM1_MASK 0x00F00000 +#define CCSR_SSI_SFCSR_RFWM1(x) \ + (((x) << CCSR_SSI_SFCSR_RFWM1_SHIFT) & CCSR_SSI_SFCSR_RFWM1_MASK) +#define CCSR_SSI_SFCSR_TFWM1_SHIFT 16 +#define CCSR_SSI_SFCSR_TFWM1_MASK 0x000F0000 +#define CCSR_SSI_SFCSR_TFWM1(x) \ + (((x) << CCSR_SSI_SFCSR_TFWM1_SHIFT) & CCSR_SSI_SFCSR_TFWM1_MASK) +#define CCSR_SSI_SFCSR_RFCNT0_SHIFT 12 +#define CCSR_SSI_SFCSR_RFCNT0_MASK 0x0000F000 +#define CCSR_SSI_SFCSR_RFCNT0(x) \ + (((x) & CCSR_SSI_SFCSR_RFCNT0_MASK) >> CCSR_SSI_SFCSR_RFCNT0_SHIFT) +#define CCSR_SSI_SFCSR_TFCNT0_SHIFT 8 +#define CCSR_SSI_SFCSR_TFCNT0_MASK 0x00000F00 +#define CCSR_SSI_SFCSR_TFCNT0(x) \ + (((x) & CCSR_SSI_SFCSR_TFCNT0_MASK) >> CCSR_SSI_SFCSR_TFCNT0_SHIFT) +#define CCSR_SSI_SFCSR_RFWM0_SHIFT 4 +#define CCSR_SSI_SFCSR_RFWM0_MASK 0x000000F0 +#define CCSR_SSI_SFCSR_RFWM0(x) \ + (((x) << CCSR_SSI_SFCSR_RFWM0_SHIFT) & CCSR_SSI_SFCSR_RFWM0_MASK) +#define CCSR_SSI_SFCSR_TFWM0_SHIFT 0 +#define CCSR_SSI_SFCSR_TFWM0_MASK 0x0000000F +#define CCSR_SSI_SFCSR_TFWM0(x) \ + (((x) << CCSR_SSI_SFCSR_TFWM0_SHIFT) & CCSR_SSI_SFCSR_TFWM0_MASK) + +#define CCSR_SSI_STR_TEST 0x00008000 +#define CCSR_SSI_STR_RCK2TCK 0x00004000 +#define CCSR_SSI_STR_RFS2TFS 0x00002000 +#define CCSR_SSI_STR_RXSTATE(x) (((x) >> 8) & 0x1F) +#define CCSR_SSI_STR_TXD2RXD 0x00000080 +#define CCSR_SSI_STR_TCK2RCK 0x00000040 +#define CCSR_SSI_STR_TFS2RFS 0x00000020 +#define CCSR_SSI_STR_TXSTATE(x) ((x) & 0x1F) + +#define CCSR_SSI_SOR_CLKOFF 0x00000040 +#define CCSR_SSI_SOR_RX_CLR 0x00000020 +#define CCSR_SSI_SOR_TX_CLR 0x00000010 +#define CCSR_SSI_SOR_INIT 0x00000008 +#define CCSR_SSI_SOR_WAIT_SHIFT 1 +#define CCSR_SSI_SOR_WAIT_MASK 0x00000006 +#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT) +#define CCSR_SSI_SOR_SYNRST 0x00000001 + +/* Instantiation data for an SSI interface + * + * This structure contains all the information that the the SSI driver needs + * to instantiate an SSI interface with ALSA. The machine driver should + * create this structure, fill it in, call fsl_ssi_create_dai(), and then + * delete the structure. + * + * id: which SSI this is (0, 1, etc. ) + * ssi: pointer to the SSI's registers + * ssi_phys: physical address of the SSI registers + * irq: IRQ of this SSI + * dev: struct device, used to create the sysfs statistics file +*/ +struct fsl_ssi_info { + unsigned int id; + struct ccsr_ssi __iomem *ssi; + dma_addr_t ssi_phys; + unsigned int irq; + struct device *dev; +}; + +struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info); +void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai); + +#endif + diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c new file mode 100644 index 00000000000..f26c4b2e8b6 --- /dev/null +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -0,0 +1,631 @@ +/** + * Freescale MPC8610HPCD ALSA SoC Fabric driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed + * under the terms of the GNU General Public License version 2. This + * program is licensed "as is" without any warranty of any kind, whether + * express or implied. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <sound/soc.h> +#include <asm/immap_86xx.h> + +#include "../codecs/cs4270.h" +#include "fsl_dma.h" +#include "fsl_ssi.h" + +/** + * mpc8610_hpcd_data: fabric-specific ASoC device data + * + * This structure contains data for a single sound platform device on an + * MPC8610 HPCD. Some of the data is taken from the device tree. + */ +struct mpc8610_hpcd_data { + struct snd_soc_device sound_devdata; + struct snd_soc_dai_link dai; + struct snd_soc_machine machine; + unsigned int dai_format; + unsigned int codec_clk_direction; + unsigned int cpu_clk_direction; + unsigned int clk_frequency; + struct ccsr_guts __iomem *guts; + struct ccsr_ssi __iomem *ssi; + unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */ + unsigned int ssi_irq; + unsigned int dma_id; /* 0 = DMA1, 1 = DMA2, etc */ + unsigned int dma_irq[2]; + struct ccsr_dma_channel __iomem *dma[2]; + unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/ +}; + +/** + * mpc8610_hpcd_machine_probe: initalize the board + * + * This function is called when platform_device_add() is called. It is used + * to initialize the board-specific hardware. + * + * Here we program the DMACR and PMUXCR registers. + */ +static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device) +{ + struct mpc8610_hpcd_data *machine_data = + sound_device->dev.platform_data; + + /* Program the signal routing between the SSI and the DMA */ + guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1, + machine_data->dma_channel_id[0], CCSR_GUTS_DMACR_DEV_SSI); + guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1, + machine_data->dma_channel_id[1], CCSR_GUTS_DMACR_DEV_SSI); + + guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id, + machine_data->dma_channel_id[0], 0); + guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id, + machine_data->dma_channel_id[1], 0); + + guts_set_pmuxcr_dma(machine_data->guts, 1, 0, 0); + guts_set_pmuxcr_dma(machine_data->guts, 1, 3, 0); + guts_set_pmuxcr_dma(machine_data->guts, 0, 3, 0); + + switch (machine_data->ssi_id) { + case 0: + clrsetbits_be32(&machine_data->guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI); + break; + case 1: + clrsetbits_be32(&machine_data->guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI); + break; + } + + return 0; +} + +/** + * mpc8610_hpcd_startup: program the board with various hardware parameters + * + * This function takes board-specific information, like clock frequencies + * and serial data formats, and passes that information to the codec and + * transport drivers. + */ +static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai; + struct mpc8610_hpcd_data *machine_data = + rtd->socdev->dev->platform_data; + int ret = 0; + + /* Tell the CPU driver what the serial protocol is. */ + if (cpu_dai->dai_ops.set_fmt) { + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, + machine_data->dai_format); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "could not set CPU driver audio format\n"); + return ret; + } + } + + /* Tell the codec driver what the serial protocol is. */ + if (codec_dai->dai_ops.set_fmt) { + ret = codec_dai->dai_ops.set_fmt(codec_dai, + machine_data->dai_format); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "could not set codec driver audio format\n"); + return ret; + } + } + + /* + * Tell the CPU driver what the clock frequency is, and whether it's a + * slave or master. + */ + if (cpu_dai->dai_ops.set_sysclk) { + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, 0, + machine_data->clk_frequency, + machine_data->cpu_clk_direction); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "could not set CPU driver clock parameters\n"); + return ret; + } + } + + /* + * Tell the codec driver what the MCLK frequency is, and whether it's + * a slave or master. + */ + if (codec_dai->dai_ops.set_sysclk) { + ret = codec_dai->dai_ops.set_sysclk(codec_dai, 0, + machine_data->clk_frequency, + machine_data->codec_clk_direction); + if (ret < 0) { + dev_err(substream->pcm->card->dev, + "could not set codec driver clock params\n"); + return ret; + } + } + + return 0; +} + +/** + * mpc8610_hpcd_machine_remove: Remove the sound device + * + * This function is called to remove the sound device for one SSI. We + * de-program the DMACR and PMUXCR register. + */ +int mpc8610_hpcd_machine_remove(struct platform_device *sound_device) +{ + struct mpc8610_hpcd_data *machine_data = + sound_device->dev.platform_data; + + /* Restore the signal routing */ + + guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1, + machine_data->dma_channel_id[0], 0); + guts_set_dmacr(machine_data->guts, machine_data->dma_id + 1, + machine_data->dma_channel_id[1], 0); + + switch (machine_data->ssi_id) { + case 0: + clrsetbits_be32(&machine_data->guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA); + break; + case 1: + clrsetbits_be32(&machine_data->guts->pmuxcr, + CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI1_LA); + break; + } + + return 0; +} + +/** + * mpc8610_hpcd_ops: ASoC fabric driver operations + */ +static struct snd_soc_ops mpc8610_hpcd_ops = { + .startup = mpc8610_hpcd_startup, +}; + +/** + * mpc8610_hpcd_machine: ASoC machine data + */ +static struct snd_soc_machine mpc8610_hpcd_machine = { + .probe = mpc8610_hpcd_machine_probe, + .remove = mpc8610_hpcd_machine_remove, + .name = "MPC8610 HPCD", + .num_links = 1, +}; + +/** + * mpc8610_hpcd_probe: OF probe function for the fabric driver + * + * This function gets called when an SSI node is found in the device tree. + * + * Although this is a fabric driver, the SSI node is the "master" node with + * respect to audio hardware connections. Therefore, we create a new ASoC + * device for each new SSI node that has a codec attached. + * + * FIXME: Currently, we only support one DMA controller, so if there are + * multiple SSI nodes with codecs, only the first will be supported. + * + * FIXME: Even if we did support multiple DMA controllers, we have no + * mechanism for assigning DMA controllers and channels to the individual + * SSI devices. We also probably aren't compatible with the generic Elo DMA + * device driver. + */ +static int mpc8610_hpcd_probe(struct of_device *ofdev, + const struct of_device_id *match) +{ + struct device_node *np = ofdev->node; + struct device_node *codec_np = NULL; + struct device_node *guts_np = NULL; + struct device_node *dma_np = NULL; + struct device_node *dma_channel_np = NULL; + const phandle *codec_ph; + const char *sprop; + const u32 *iprop; + struct resource res; + struct platform_device *sound_device = NULL; + struct mpc8610_hpcd_data *machine_data; + struct fsl_ssi_info ssi_info; + struct fsl_dma_info dma_info; + int ret = -ENODEV; + + machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL); + if (!machine_data) + return -ENOMEM; + + memset(&ssi_info, 0, sizeof(ssi_info)); + memset(&dma_info, 0, sizeof(dma_info)); + + ssi_info.dev = &ofdev->dev; + + /* + * We are only interested in SSIs with a codec phandle in them, so let's + * make sure this SSI has one. + */ + codec_ph = of_get_property(np, "codec-handle", NULL); + if (!codec_ph) + goto error; + + codec_np = of_find_node_by_phandle(*codec_ph); + if (!codec_np) + goto error; + + /* The MPC8610 HPCD only knows about the CS4270 codec, so reject + anything else. */ + if (!of_device_is_compatible(codec_np, "cirrus,cs4270")) + goto error; + + /* Get the device ID */ + iprop = of_get_property(np, "cell-index", NULL); + if (!iprop) { + dev_err(&ofdev->dev, "cell-index property not found\n"); + ret = -EINVAL; + goto error; + } + machine_data->ssi_id = *iprop; + ssi_info.id = *iprop; + + /* Get the serial format and clock direction. */ + sprop = of_get_property(np, "fsl,mode", NULL); + if (!sprop) { + dev_err(&ofdev->dev, "fsl,mode property not found\n"); + ret = -EINVAL; + goto error; + } + + if (strcasecmp(sprop, "i2s-slave") == 0) { + machine_data->dai_format = SND_SOC_DAIFMT_I2S; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + + /* + * In i2s-slave mode, the codec has its own clock source, so we + * need to get the frequency from the device tree and pass it to + * the codec driver. + */ + iprop = of_get_property(codec_np, "clock-frequency", NULL); + if (!iprop || !*iprop) { + dev_err(&ofdev->dev, "codec bus-frequency property " + "is missing or invalid\n"); + ret = -EINVAL; + goto error; + } + machine_data->clk_frequency = *iprop; + } else if (strcasecmp(sprop, "i2s-master") == 0) { + machine_data->dai_format = SND_SOC_DAIFMT_I2S; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "lj-slave") == 0) { + machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "lj-master") == 0) { + machine_data->dai_format = SND_SOC_DAIFMT_LEFT_J; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "rj-master") == 0) { + machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "rj-master") == 0) { + machine_data->dai_format = SND_SOC_DAIFMT_RIGHT_J; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else if (strcasecmp(sprop, "ac97-slave") == 0) { + machine_data->dai_format = SND_SOC_DAIFMT_AC97; + machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN; + } else if (strcasecmp(sprop, "ac97-master") == 0) { + machine_data->dai_format = SND_SOC_DAIFMT_AC97; + machine_data->codec_clk_direction = SND_SOC_CLOCK_IN; + machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT; + } else { + dev_err(&ofdev->dev, + "unrecognized fsl,mode property \"%s\"\n", sprop); + ret = -EINVAL; + goto error; + } + + if (!machine_data->clk_frequency) { + dev_err(&ofdev->dev, "unknown clock frequency\n"); + ret = -EINVAL; + goto error; + } + + /* Read the SSI information from the device tree */ + ret = of_address_to_resource(np, 0, &res); + if (ret) { + dev_err(&ofdev->dev, "could not obtain SSI address\n"); + goto error; + } + if (!res.start) { + dev_err(&ofdev->dev, "invalid SSI address\n"); + goto error; + } + ssi_info.ssi_phys = res.start; + + machine_data->ssi = ioremap(ssi_info.ssi_phys, sizeof(struct ccsr_ssi)); + if (!machine_data->ssi) { + dev_err(&ofdev->dev, "could not map SSI address %x\n", + ssi_info.ssi_phys); + ret = -EINVAL; + goto error; + } + ssi_info.ssi = machine_data->ssi; + + + /* Get the IRQ of the SSI */ + machine_data->ssi_irq = irq_of_parse_and_map(np, 0); + if (!machine_data->ssi_irq) { + dev_err(&ofdev->dev, "could not get SSI IRQ\n"); + ret = -EINVAL; + goto error; + } + ssi_info.irq = machine_data->ssi_irq; + + + /* Map the global utilities registers. */ + guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts"); + if (!guts_np) { + dev_err(&ofdev->dev, "could not obtain address of GUTS\n"); + ret = -EINVAL; + goto error; + } + machine_data->guts = of_iomap(guts_np, 0); + of_node_put(guts_np); + if (!machine_data->guts) { + dev_err(&ofdev->dev, "could not map GUTS\n"); + ret = -EINVAL; + goto error; + } + + /* Find the DMA channels to use. For now, we always use the first DMA + controller. */ + for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") { + iprop = of_get_property(dma_np, "cell-index", NULL); + if (iprop && (*iprop == 0)) { + of_node_put(dma_np); + break; + } + } + if (!dma_np) { + dev_err(&ofdev->dev, "could not find DMA node\n"); + ret = -EINVAL; + goto error; + } + machine_data->dma_id = *iprop; + + /* + * Find the DMA channels to use. For now, we always use DMA channel 0 + * for playback, and DMA channel 1 for capture. + */ + while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) { + iprop = of_get_property(dma_channel_np, "cell-index", NULL); + /* Is it DMA channel 0? */ + if (iprop && (*iprop == 0)) { + /* dma_channel[0] and dma_irq[0] are for playback */ + dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0); + dma_info.dma_irq[0] = + irq_of_parse_and_map(dma_channel_np, 0); + machine_data->dma_channel_id[0] = *iprop; + continue; + } + if (iprop && (*iprop == 1)) { + /* dma_channel[1] and dma_irq[1] are for capture */ + dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0); + dma_info.dma_irq[1] = + irq_of_parse_and_map(dma_channel_np, 0); + machine_data->dma_channel_id[1] = *iprop; + continue; + } + } + if (!dma_info.dma_channel[0] || !dma_info.dma_channel[1] || + !dma_info.dma_irq[0] || !dma_info.dma_irq[1]) { + dev_err(&ofdev->dev, "could not find DMA channels\n"); + ret = -EINVAL; + goto error; + } + + dma_info.ssi_stx_phys = ssi_info.ssi_phys + + offsetof(struct ccsr_ssi, stx0); + dma_info.ssi_srx_phys = ssi_info.ssi_phys + + offsetof(struct ccsr_ssi, srx0); + + /* We have the DMA information, so tell the DMA driver what it is */ + if (!fsl_dma_configure(&dma_info)) { + dev_err(&ofdev->dev, "could not instantiate DMA device\n"); + ret = -EBUSY; + goto error; + } + + /* + * Initialize our DAI data structure. We should probably get this + * information from the device tree. + */ + machine_data->dai.name = "CS4270"; + machine_data->dai.stream_name = "CS4270"; + + machine_data->dai.cpu_dai = fsl_ssi_create_dai(&ssi_info); + machine_data->dai.codec_dai = &cs4270_dai; /* The codec_dai we want */ + machine_data->dai.ops = &mpc8610_hpcd_ops; + + mpc8610_hpcd_machine.dai_link = &machine_data->dai; + + /* Allocate a new audio platform device structure */ + sound_device = platform_device_alloc("soc-audio", -1); + if (!sound_device) { + dev_err(&ofdev->dev, "platform device allocation failed\n"); + ret = -ENOMEM; + goto error; + } + + machine_data->sound_devdata.machine = &mpc8610_hpcd_machine; + machine_data->sound_devdata.codec_dev = &soc_codec_device_cs4270; + machine_data->sound_devdata.platform = &fsl_soc_platform; + + sound_device->dev.platform_data = machine_data; + + + /* Set the platform device and ASoC device to point to each other */ + platform_set_drvdata(sound_device, &machine_data->sound_devdata); + + machine_data->sound_devdata.dev = &sound_device->dev; + + + /* Tell ASoC to probe us. This will call mpc8610_hpcd_machine.probe(), + if it exists. */ + ret = platform_device_add(sound_device); + + if (ret) { + dev_err(&ofdev->dev, "platform device add failed\n"); + goto error; + } + + dev_set_drvdata(&ofdev->dev, sound_device); + + return 0; + +error: + of_node_put(codec_np); + of_node_put(guts_np); + of_node_put(dma_np); + of_node_put(dma_channel_np); + + if (sound_device) + platform_device_unregister(sound_device); + + if (machine_data->dai.cpu_dai) + fsl_ssi_destroy_dai(machine_data->dai.cpu_dai); + + if (ssi_info.ssi) + iounmap(ssi_info.ssi); + + if (ssi_info.irq) + irq_dispose_mapping(ssi_info.irq); + + if (dma_info.dma_channel[0]) + iounmap(dma_info.dma_channel[0]); + + if (dma_info.dma_channel[1]) + iounmap(dma_info.dma_channel[1]); + + if (dma_info.dma_irq[0]) + irq_dispose_mapping(dma_info.dma_irq[0]); + + if (dma_info.dma_irq[1]) + irq_dispose_mapping(dma_info.dma_irq[1]); + + if (machine_data->guts) + iounmap(machine_data->guts); + + kfree(machine_data); + + return ret; +} + +/** + * mpc8610_hpcd_remove: remove the OF device + * + * This function is called when the OF device is removed. + */ +static int mpc8610_hpcd_remove(struct of_device *ofdev) +{ + struct platform_device *sound_device = dev_get_drvdata(&ofdev->dev); + struct mpc8610_hpcd_data *machine_data = + sound_device->dev.platform_data; + + platform_device_unregister(sound_device); + + if (machine_data->dai.cpu_dai) + fsl_ssi_destroy_dai(machine_data->dai.cpu_dai); + + if (machine_data->ssi) + iounmap(machine_data->ssi); + + if (machine_data->dma[0]) + iounmap(machine_data->dma[0]); + + if (machine_data->dma[1]) + iounmap(machine_data->dma[1]); + + if (machine_data->dma_irq[0]) + irq_dispose_mapping(machine_data->dma_irq[0]); + + if (machine_data->dma_irq[1]) + irq_dispose_mapping(machine_data->dma_irq[1]); + + if (machine_data->guts) + iounmap(machine_data->guts); + + kfree(machine_data); + sound_device->dev.platform_data = NULL; + + dev_set_drvdata(&ofdev->dev, NULL); + + return 0; +} + +static struct of_device_id mpc8610_hpcd_match[] = { + { + .compatible = "fsl,mpc8610-ssi", + }, + {} +}; +MODULE_DEVICE_TABLE(of, mpc8610_hpcd_match); + +static struct of_platform_driver mpc8610_hpcd_of_driver = { + .owner = THIS_MODULE, + .name = "mpc8610_hpcd", + .match_table = mpc8610_hpcd_match, + .probe = mpc8610_hpcd_probe, + .remove = mpc8610_hpcd_remove, +}; + +/** + * mpc8610_hpcd_init: fabric driver initialization. + * + * This function is called when this module is loaded. + */ +static int __init mpc8610_hpcd_init(void) +{ + int ret; + + printk(KERN_INFO "Freescale MPC8610 HPCD ALSA SoC fabric driver\n"); + + ret = of_register_platform_driver(&mpc8610_hpcd_of_driver); + + if (ret) + printk(KERN_ERR + "mpc8610-hpcd: failed to register platform driver\n"); + + return ret; +} + +/** + * mpc8610_hpcd_exit: fabric driver exit + * + * This function is called when this driver is unloaded. + */ +static void __exit mpc8610_hpcd_exit(void) +{ + of_unregister_platform_driver(&mpc8610_hpcd_of_driver); +} + +module_init(mpc8610_hpcd_init); +module_exit(mpc8610_hpcd_exit); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC fabric driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index a83e22937c2..484f883459e 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -53,3 +53,12 @@ config SND_PXA2XX_SOC_TOSA help Say Y if you want to add support for SoC audio on Sharp Zaurus SL-C6000x models (Tosa). + +config SND_PXA2XX_SOC_E800 + tristate "SoC AC97 Audio support for e800" + depends on SND_PXA2XX_SOC && MACH_E800 + select SND_SOC_WM9712 + select SND_PXA2XX_SOC_AC97 + help + Say Y if you want to add support for SoC audio on the + Toshiba e800 PDA diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 78e0d6b07d1..04e5646f75b 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -11,10 +11,12 @@ obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o snd-soc-corgi-objs := corgi.o snd-soc-poodle-objs := poodle.o snd-soc-tosa-objs := tosa.o +snd-soc-e800-objs := e800_wm9712.o snd-soc-spitz-objs := spitz.o obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o obj-$(CONFIG_SND_PXA2XX_SOC_TOSA) += snd-soc-tosa.o +obj-$(CONFIG_SND_PXA2XX_SOC_E800) += snd-soc-e800.o obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c index 5ee51a994ac..3f34e531beb 100644 --- a/sound/soc/pxa/corgi.c +++ b/sound/soc/pxa/corgi.c @@ -22,7 +22,6 @@ #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> diff --git a/sound/soc/pxa/e800_wm9712.c b/sound/soc/pxa/e800_wm9712.c new file mode 100644 index 00000000000..06e8afb2527 --- /dev/null +++ b/sound/soc/pxa/e800_wm9712.c @@ -0,0 +1,89 @@ +/* + * e800-wm9712.c -- SoC audio for e800 + * + * Based on tosa.c + * + * Copyright 2007 (c) Ian Molton <spyro@f2s.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; version 2 ONLY. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/hardware.h> +#include <asm/arch/audio.h> + +#include "../codecs/wm9712.h" +#include "pxa2xx-pcm.h" +#include "pxa2xx-ac97.h" + +static struct snd_soc_machine e800; + +static struct snd_soc_dai_link e800_dai[] = { +{ + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX], + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX], +}, +}; + +static struct snd_soc_machine e800 = { + .name = "Toshiba e800", + .dai_link = e800_dai, + .num_links = ARRAY_SIZE(e800_dai), +}; + +static struct snd_soc_device e800_snd_devdata = { + .machine = &e800, + .platform = &pxa2xx_soc_platform, + .codec_dev = &soc_codec_dev_wm9712, +}; + +static struct platform_device *e800_snd_device; + +static int __init e800_init(void) +{ + int ret; + + if (!machine_is_e800()) + return -ENODEV; + + e800_snd_device = platform_device_alloc("soc-audio", -1); + if (!e800_snd_device) + return -ENOMEM; + + platform_set_drvdata(e800_snd_device, &e800_snd_devdata); + e800_snd_devdata.dev = &e800_snd_device->dev; + ret = platform_device_add(e800_snd_device); + + if (ret) + platform_device_put(e800_snd_device); + + return ret; +} + +static void __exit e800_exit(void) +{ + platform_device_unregister(e800_snd_device); +} + +module_init(e800_init); +module_exit(e800_exit); + +/* Module information */ +MODULE_AUTHOR("Ian Molton <spyro@f2s.com>"); +MODULE_DESCRIPTION("ALSA SoC driver for e800"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c index 0915cf74042..5ae59bd309a 100644 --- a/sound/soc/pxa/poodle.c +++ b/sound/soc/pxa/poodle.c @@ -19,7 +19,6 @@ #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index 60e6f4677f9..815c1533625 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -17,7 +17,6 @@ #include <linux/wait.h> #include <linux/delay.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/ac97_codec.h> diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 50c5c83f67d..692b9000248 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -18,7 +18,6 @@ #include <linux/module.h> #include <linux/device.h> #include <linux/delay.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/initval.h> diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index 35e8fa3a469..daeaa4c8b87 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -16,7 +16,6 @@ #include <linux/slab.h> #include <linux/dma-mapping.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c index 4dd8f35312b..d56709e1543 100644 --- a/sound/soc/pxa/spitz.c +++ b/sound/soc/pxa/spitz.c @@ -22,7 +22,6 @@ #include <linux/timer.h> #include <linux/interrupt.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c index 5504e30acf1..e4d40b528ca 100644 --- a/sound/soc/pxa/tosa.c +++ b/sound/soc/pxa/tosa.c @@ -25,7 +25,6 @@ #include <linux/moduleparam.h> #include <linux/device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 5632a2e1518..1f6dbfc4caa 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -10,6 +10,9 @@ config SND_S3C24XX_SOC config SND_S3C24XX_SOC_I2S tristate +config SND_S3C2412_SOC_I2S + tristate + config SND_S3C2443_SOC_AC97 tristate select AC97_BUS @@ -34,4 +37,12 @@ config SND_S3C24XX_SOC_SMDK2443_WM9710 Say Y if you want to add support for SoC audio on smdk2443 with the WM9710. +config SND_S3C24XX_SOC_LN2440SBC_ALC650 + tristate "SoC AC97 Audio support for LN2440SBC - ALC650" + depends on SND_S3C24XX_SOC + select SND_S3C2443_SOC_AC97 + select SND_SOC_AC97_CODEC + help + Say Y if you want to add support for SoC audio on ln2440sbc + with the ALC650. diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index 13c92f0fa1e..0aa5fb0b970 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -1,15 +1,19 @@ # S3c24XX Platform Support snd-soc-s3c24xx-objs := s3c24xx-pcm.o snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o +snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o +obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o # S3C24XX Machine Support snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o +snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o +obj-$(CONFIG_SND_S3C24XX_SOC_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o diff --git a/sound/soc/s3c24xx/ln2440sbc_alc650.c b/sound/soc/s3c24xx/ln2440sbc_alc650.c new file mode 100644 index 00000000000..9ed8f2e8da1 --- /dev/null +++ b/sound/soc/s3c24xx/ln2440sbc_alc650.c @@ -0,0 +1,85 @@ +/* + * SoC audio for ln2440sbc + * + * Copyright 2007 KonekTel, a.s. + * Author: Ivan Kuten + * ivan.kuten@promwad.com + * + * Heavily based on smdk2443_wm9710.c + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "../codecs/ac97.h" +#include "s3c24xx-pcm.h" +#include "s3c24xx-ac97.h" + +static struct snd_soc_machine ln2440sbc; + +static struct snd_soc_dai_link ln2440sbc_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &s3c2443_ac97_dai[0], + .codec_dai = &ac97_dai, +}, +}; + +static struct snd_soc_machine ln2440sbc = { + .name = "LN2440SBC", + .dai_link = ln2440sbc_dai, + .num_links = ARRAY_SIZE(ln2440sbc_dai), +}; + +static struct snd_soc_device ln2440sbc_snd_ac97_devdata = { + .machine = &ln2440sbc, + .platform = &s3c24xx_soc_platform, + .codec_dev = &soc_codec_dev_ac97, +}; + +static struct platform_device *ln2440sbc_snd_ac97_device; + +static int __init ln2440sbc_init(void) +{ + int ret; + + ln2440sbc_snd_ac97_device = platform_device_alloc("soc-audio", -1); + if (!ln2440sbc_snd_ac97_device) + return -ENOMEM; + + platform_set_drvdata(ln2440sbc_snd_ac97_device, + &ln2440sbc_snd_ac97_devdata); + ln2440sbc_snd_ac97_devdata.dev = &ln2440sbc_snd_ac97_device->dev; + ret = platform_device_add(ln2440sbc_snd_ac97_device); + + if (ret) + platform_device_put(ln2440sbc_snd_ac97_device); + + return ret; +} + +static void __exit ln2440sbc_exit(void) +{ + platform_device_unregister(ln2440sbc_snd_ac97_device); +} + +module_init(ln2440sbc_init); +module_exit(ln2440sbc_exit); + +/* Module information */ +MODULE_AUTHOR("Ivan Kuten"); +MODULE_DESCRIPTION("ALSA SoC ALC650 LN2440SBC"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c index f1f6b9478af..6ee115ceb01 100644 --- a/sound/soc/s3c24xx/neo1973_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_wm8753.c @@ -22,7 +22,6 @@ #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/i2c.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> @@ -30,13 +29,15 @@ #include <asm/mach-types.h> #include <asm/hardware/scoop.h> -#include <asm/arch/regs-iis.h> #include <asm/arch/regs-clock.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <asm/arch/audio.h> #include <asm/io.h> #include <asm/arch/spi-gpio.h> + +#include <asm/plat-s3c24xx/regs-iis.h> + #include "../codecs/wm8753.h" #include "lm4857.h" #include "s3c24xx-pcm.h" diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c new file mode 100644 index 00000000000..c4a46dd589b --- /dev/null +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -0,0 +1,744 @@ +/* sound/soc/s3c24xx/s3c2412-i2s.c + * + * ALSA Soc Audio Layer - S3C2412 I2S driver + * + * Copyright (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com + * linux@wolfsonmicro.com + * + * Copyright (c) 2007, 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/kernel.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/hardware.h> + +#include <linux/io.h> +#include <asm/dma.h> + +#include <asm/plat-s3c24xx/regs-s3c2412-iis.h> + +#include <asm/arch/regs-gpio.h> +#include <asm/arch/audio.h> +#include <asm/arch/dma.h> + +#include "s3c24xx-pcm.h" +#include "s3c2412-i2s.h" + +#define S3C2412_I2S_DEBUG 0 +#define S3C2412_I2S_DEBUG_CON 0 + +#if S3C2412_I2S_DEBUG +#define DBG(x...) printk(KERN_INFO x) +#else +#define DBG(x...) do { } while (0) +#endif + +static struct s3c2410_dma_client s3c2412_dma_client_out = { + .name = "I2S PCM Stereo out" +}; + +static struct s3c2410_dma_client s3c2412_dma_client_in = { + .name = "I2S PCM Stereo in" +}; + +static struct s3c24xx_pcm_dma_params s3c2412_i2s_pcm_stereo_out = { + .client = &s3c2412_dma_client_out, + .channel = DMACH_I2S_OUT, + .dma_addr = S3C2410_PA_IIS + S3C2412_IISTXD, + .dma_size = 4, +}; + +static struct s3c24xx_pcm_dma_params s3c2412_i2s_pcm_stereo_in = { + .client = &s3c2412_dma_client_in, + .channel = DMACH_I2S_IN, + .dma_addr = S3C2410_PA_IIS + S3C2412_IISRXD, + .dma_size = 4, +}; + +struct s3c2412_i2s_info { + struct device *dev; + void __iomem *regs; + struct clk *iis_clk; + struct clk *iis_pclk; + struct clk *iis_cclk; + + u32 suspend_iismod; + u32 suspend_iiscon; + u32 suspend_iispsr; +}; + +static struct s3c2412_i2s_info s3c2412_i2s; + +#define bit_set(v, b) (((v) & (b)) ? 1 : 0) + +#if S3C2412_I2S_DEBUG_CON +static void dbg_showcon(const char *fn, u32 con) +{ + printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn, + bit_set(con, S3C2412_IISCON_LRINDEX), + bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY), + bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY), + bit_set(con, S3C2412_IISCON_TXFIFO_FULL), + bit_set(con, S3C2412_IISCON_RXFIFO_FULL)); + + printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n", + fn, + bit_set(con, S3C2412_IISCON_TXDMA_PAUSE), + bit_set(con, S3C2412_IISCON_RXDMA_PAUSE), + bit_set(con, S3C2412_IISCON_TXCH_PAUSE), + bit_set(con, S3C2412_IISCON_RXCH_PAUSE)); + printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn, + bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE), + bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE), + bit_set(con, S3C2412_IISCON_IIS_ACTIVE)); +} +#else +static inline void dbg_showcon(const char *fn, u32 con) +{ +} +#endif + +/* Turn on or off the transmission path. */ +static void s3c2412_snd_txctrl(int on) +{ + struct s3c2412_i2s_info *i2s = &s3c2412_i2s; + void __iomem *regs = i2s->regs; + u32 fic, con, mod; + + DBG("%s(%d)\n", __func__, on); + + fic = readl(regs + S3C2412_IISFIC); + con = readl(regs + S3C2412_IISCON); + mod = readl(regs + S3C2412_IISMOD); + + DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); + + if (on) { + con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; + con &= ~S3C2412_IISCON_TXDMA_PAUSE; + con &= ~S3C2412_IISCON_TXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXONLY: + case S3C2412_IISMOD_MODE_TXRX: + /* do nothing, we are in the right mode */ + break; + + case S3C2412_IISMOD_MODE_RXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXRX; + break; + + default: + dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n"); + } + + writel(con, regs + S3C2412_IISCON); + writel(mod, regs + S3C2412_IISMOD); + } else { + /* Note, we do not have any indication that the FIFO problems + * tha the S3C2410/2440 had apply here, so we should be able + * to disable the DMA and TX without resetting the FIFOS. + */ + + con |= S3C2412_IISCON_TXDMA_PAUSE; + con |= S3C2412_IISCON_TXCH_PAUSE; + con &= ~S3C2412_IISCON_TXDMA_ACTIVE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXRX: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_RXONLY; + break; + + case S3C2412_IISMOD_MODE_TXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + con &= ~S3C2412_IISCON_IIS_ACTIVE; + break; + + default: + dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n"); + } + + writel(mod, regs + S3C2412_IISMOD); + writel(con, regs + S3C2412_IISCON); + } + + fic = readl(regs + S3C2412_IISFIC); + dbg_showcon(__func__, con); + DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); +} + +static void s3c2412_snd_rxctrl(int on) +{ + struct s3c2412_i2s_info *i2s = &s3c2412_i2s; + void __iomem *regs = i2s->regs; + u32 fic, con, mod; + + DBG("%s(%d)\n", __func__, on); + + fic = readl(regs + S3C2412_IISFIC); + con = readl(regs + S3C2412_IISCON); + mod = readl(regs + S3C2412_IISMOD); + + DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); + + if (on) { + con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; + con &= ~S3C2412_IISCON_RXDMA_PAUSE; + con &= ~S3C2412_IISCON_RXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXRX: + case S3C2412_IISMOD_MODE_RXONLY: + /* do nothing, we are in the right mode */ + break; + + case S3C2412_IISMOD_MODE_TXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXRX; + break; + + default: + dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + } + + writel(mod, regs + S3C2412_IISMOD); + writel(con, regs + S3C2412_IISCON); + } else { + /* See txctrl notes on FIFOs. */ + + con &= ~S3C2412_IISCON_RXDMA_ACTIVE; + con |= S3C2412_IISCON_RXDMA_PAUSE; + con |= S3C2412_IISCON_RXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_RXONLY: + con &= ~S3C2412_IISCON_IIS_ACTIVE; + mod &= ~S3C2412_IISMOD_MODE_MASK; + break; + + case S3C2412_IISMOD_MODE_TXRX: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXONLY; + break; + + default: + dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + } + + writel(con, regs + S3C2412_IISCON); + writel(mod, regs + S3C2412_IISMOD); + } + + fic = readl(regs + S3C2412_IISFIC); + DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); +} + + +/* + * Wait for the LR signal to allow synchronisation to the L/R clock + * from the codec. May only be needed for slave mode. + */ +static int s3c2412_snd_lrsync(void) +{ + u32 iiscon; + unsigned long timeout = jiffies + msecs_to_jiffies(5); + + DBG("Entered %s\n", __func__); + + while (1) { + iiscon = readl(s3c2412_i2s.regs + S3C2412_IISCON); + if (iiscon & S3C2412_IISCON_LRINDEX) + break; + + if (timeout < jiffies) { + printk(KERN_ERR "%s: timeout\n", __func__); + return -ETIMEDOUT; + } + } + + return 0; +} + +/* + * Check whether CPU is the master or slave + */ +static inline int s3c2412_snd_is_clkmaster(void) +{ + u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); + + DBG("Entered %s\n", __func__); + + iismod &= S3C2412_IISMOD_MASTER_MASK; + return !(iismod == S3C2412_IISMOD_SLAVE); +} + +/* + * Set S3C2412 I2S DAI format + */ +static int s3c2412_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, + unsigned int fmt) +{ + u32 iismod; + + + DBG("Entered %s\n", __func__); + + iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); + DBG("hw_params r: IISMOD: %x \n", iismod); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iismod &= ~S3C2412_IISMOD_MASTER_MASK; + iismod |= S3C2412_IISMOD_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iismod &= ~S3C2412_IISMOD_MASTER_MASK; + iismod |= S3C2412_IISMOD_MASTER_INTERNAL; + break; + default: + DBG("unknwon master/slave format\n"); + return -EINVAL; + } + + iismod &= ~S3C2412_IISMOD_SDF_MASK; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + iismod |= S3C2412_IISMOD_SDF_MSB; + break; + case SND_SOC_DAIFMT_LEFT_J: + iismod |= S3C2412_IISMOD_SDF_LSB; + break; + case SND_SOC_DAIFMT_I2S: + iismod |= S3C2412_IISMOD_SDF_IIS; + break; + default: + DBG("Unknown data format\n"); + return -EINVAL; + } + + writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD); + DBG("hw_params w: IISMOD: %x \n", iismod); + return 0; +} + +static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + u32 iismod; + + DBG("Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rtd->dai->cpu_dai->dma_data = &s3c2412_i2s_pcm_stereo_out; + else + rtd->dai->cpu_dai->dma_data = &s3c2412_i2s_pcm_stereo_in; + + /* Working copies of register */ + iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); + DBG("%s: r: IISMOD: %x\n", __func__, iismod); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod |= S3C2412_IISMOD_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + iismod &= ~S3C2412_IISMOD_8BIT; + break; + } + + writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD); + DBG("%s: w: IISMOD: %x\n", __func__, iismod); + return 0; +} + +static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + unsigned long irqs; + int ret = 0; + + DBG("Entered %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* On start, ensure that the FIFOs are cleared and reset. */ + + writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH, + s3c2412_i2s.regs + S3C2412_IISFIC); + + /* clear again, just in case */ + writel(0x0, s3c2412_i2s.regs + S3C2412_IISFIC); + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!s3c2412_snd_is_clkmaster()) { + ret = s3c2412_snd_lrsync(); + if (ret) + goto exit_err; + } + + local_irq_save(irqs); + + if (capture) + s3c2412_snd_rxctrl(1); + else + s3c2412_snd_txctrl(1); + + local_irq_restore(irqs); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + local_irq_save(irqs); + + if (capture) + s3c2412_snd_rxctrl(0); + else + s3c2412_snd_txctrl(0); + + local_irq_restore(irqs); + break; + default: + ret = -EINVAL; + break; + } + +exit_err: + return ret; +} + +/* default table of all avaialable root fs divisors */ +static unsigned int s3c2412_iis_fs[] = { 256, 512, 384, 768, 0 }; + +int s3c2412_iis_calc_rate(struct s3c2412_rate_calc *info, + unsigned int *fstab, + unsigned int rate, struct clk *clk) +{ + unsigned long clkrate = clk_get_rate(clk); + unsigned int div; + unsigned int fsclk; + unsigned int actual; + unsigned int fs; + unsigned int fsdiv; + signed int deviation = 0; + unsigned int best_fs = 0; + unsigned int best_div = 0; + unsigned int best_rate = 0; + unsigned int best_deviation = INT_MAX; + + + if (fstab == NULL) + fstab = s3c2412_iis_fs; + + for (fs = 0;; fs++) { + fsdiv = s3c2412_iis_fs[fs]; + + if (fsdiv == 0) + break; + + fsclk = clkrate / fsdiv; + div = fsclk / rate; + + if ((fsclk % rate) > (rate / 2)) + div++; + + if (div <= 1) + continue; + + actual = clkrate / (fsdiv * div); + deviation = actual - rate; + + printk(KERN_DEBUG "%dfs: div %d => result %d, deviation %d\n", + fsdiv, div, actual, deviation); + + deviation = abs(deviation); + + if (deviation < best_deviation) { + best_fs = fsdiv; + best_div = div; + best_rate = actual; + best_deviation = deviation; + } + + if (deviation == 0) + break; + } + + printk(KERN_DEBUG "best: fs=%d, div=%d, rate=%d\n", + best_fs, best_div, best_rate); + + info->fs_div = best_fs; + info->clk_div = best_div; + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2412_iis_calc_rate); + +/* + * Set S3C2412 Clock source + */ +static int s3c2412_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); + + DBG("%s(%p, %d, %u, %d)\n", __func__, cpu_dai, clk_id, + freq, dir); + + switch (clk_id) { + case S3C2412_CLKSRC_PCLK: + iismod &= ~S3C2412_IISMOD_MASTER_MASK; + iismod |= S3C2412_IISMOD_MASTER_INTERNAL; + break; + case S3C2412_CLKSRC_I2SCLK: + iismod &= ~S3C2412_IISMOD_MASTER_MASK; + iismod |= S3C2412_IISMOD_MASTER_EXTERNAL; + break; + default: + return -EINVAL; + } + + writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD); + return 0; +} + +/* + * Set S3C2412 Clock dividers + */ +static int s3c2412_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai, + int div_id, int div) +{ + struct s3c2412_i2s_info *i2s = &s3c2412_i2s; + u32 reg; + + DBG("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div); + + switch (div_id) { + case S3C2412_DIV_BCLK: + reg = readl(i2s->regs + S3C2412_IISMOD); + reg &= ~S3C2412_IISMOD_BCLK_MASK; + writel(reg | div, i2s->regs + S3C2412_IISMOD); + + DBG("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); + break; + + case S3C2412_DIV_RCLK: + if (div > 3) { + /* convert value to bit field */ + + switch (div) { + case 256: + div = S3C2412_IISMOD_RCLK_256FS; + break; + + case 384: + div = S3C2412_IISMOD_RCLK_384FS; + break; + + case 512: + div = S3C2412_IISMOD_RCLK_512FS; + break; + + case 768: + div = S3C2412_IISMOD_RCLK_768FS; + break; + + default: + return -EINVAL; + } + } + + reg = readl(s3c2412_i2s.regs + S3C2412_IISMOD); + reg &= ~S3C2412_IISMOD_RCLK_MASK; + writel(reg | div, i2s->regs + S3C2412_IISMOD); + DBG("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); + break; + + case S3C2412_DIV_PRESCALER: + if (div >= 0) { + writel((div << 8) | S3C2412_IISPSR_PSREN, + i2s->regs + S3C2412_IISPSR); + } else { + writel(0x0, i2s->regs + S3C2412_IISPSR); + } + DBG("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR)); + break; + + default: + return -EINVAL; + } + + return 0; +} + +struct clk *s3c2412_get_iisclk(void) +{ + return s3c2412_i2s.iis_clk; +} +EXPORT_SYMBOL_GPL(s3c2412_get_iisclk); + + +static int s3c2412_i2s_probe(struct platform_device *pdev) +{ + DBG("Entered %s\n", __func__); + + s3c2412_i2s.dev = &pdev->dev; + + s3c2412_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100); + if (s3c2412_i2s.regs == NULL) + return -ENXIO; + + s3c2412_i2s.iis_pclk = clk_get(&pdev->dev, "iis"); + if (s3c2412_i2s.iis_pclk == NULL) { + DBG("failed to get iis_clock\n"); + iounmap(s3c2412_i2s.regs); + return -ENODEV; + } + + s3c2412_i2s.iis_cclk = clk_get(&pdev->dev, "i2sclk"); + if (s3c2412_i2s.iis_cclk == NULL) { + DBG("failed to get i2sclk clock\n"); + iounmap(s3c2412_i2s.regs); + return -ENODEV; + } + + clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll")); + + clk_enable(s3c2412_i2s.iis_pclk); + clk_enable(s3c2412_i2s.iis_cclk); + + s3c2412_i2s.iis_clk = s3c2412_i2s.iis_pclk; + + /* Configure the I2S pins in correct mode */ + s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK); + s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK); + s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK); + s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI); + s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO); + + s3c2412_snd_txctrl(0); + s3c2412_snd_rxctrl(0); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c2412_i2s_suspend(struct platform_device *dev, + struct snd_soc_cpu_dai *dai) +{ + struct s3c2412_i2s_info *i2s = &s3c2412_i2s; + u32 iismod; + + if (dai->active) { + i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD); + i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON); + i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR); + + /* some basic suspend checks */ + + iismod = readl(i2s->regs + S3C2412_IISMOD); + + if (iismod & S3C2412_IISCON_RXDMA_ACTIVE) + dev_warn(&dev->dev, "%s: RXDMA active?\n", __func__); + + if (iismod & S3C2412_IISCON_TXDMA_ACTIVE) + dev_warn(&dev->dev, "%s: TXDMA active?\n", __func__); + + if (iismod & S3C2412_IISCON_IIS_ACTIVE) + dev_warn(&dev->dev, "%s: IIS active\n", __func__); + } + + return 0; +} + +static int s3c2412_i2s_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *dai) +{ + struct s3c2412_i2s_info *i2s = &s3c2412_i2s; + + dev_info(&pdev->dev, "dai_active %d, IISMOD %08x, IISCON %08x\n", + dai->active, i2s->suspend_iismod, i2s->suspend_iiscon); + + if (dai->active) { + writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON); + writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD); + writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR); + + writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH, + i2s->regs + S3C2412_IISFIC); + + ndelay(250); + writel(0x0, i2s->regs + S3C2412_IISFIC); + + } + + return 0; +} +#else +#define s3c2412_i2s_suspend NULL +#define s3c2412_i2s_resume NULL +#endif /* CONFIG_PM */ + +#define S3C2412_I2S_RATES \ + (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +struct snd_soc_cpu_dai s3c2412_i2s_dai = { + .name = "s3c2412-i2s", + .id = 0, + .type = SND_SOC_DAI_I2S, + .probe = s3c2412_i2s_probe, + .suspend = s3c2412_i2s_suspend, + .resume = s3c2412_i2s_resume, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C2412_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C2412_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .trigger = s3c2412_i2s_trigger, + .hw_params = s3c2412_i2s_hw_params, + }, + .dai_ops = { + .set_fmt = s3c2412_i2s_set_fmt, + .set_clkdiv = s3c2412_i2s_set_clkdiv, + .set_sysclk = s3c2412_i2s_set_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(s3c2412_i2s_dai); + +/* Module information */ +MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("S3C2412 I2S SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/s3c2412-i2s.h b/sound/soc/s3c24xx/s3c2412-i2s.h new file mode 100644 index 00000000000..27f48e1ffa8 --- /dev/null +++ b/sound/soc/s3c24xx/s3c2412-i2s.h @@ -0,0 +1,38 @@ +/* sound/soc/s3c24xx/s3c2412-i2s.c + * + * ALSA Soc Audio Layer - S3C2412 I2S driver + * + * Copyright (c) 2007 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. +*/ + +#ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H +#define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__ + +#define S3C2412_DIV_BCLK (1) +#define S3C2412_DIV_RCLK (2) +#define S3C2412_DIV_PRESCALER (3) + +#define S3C2412_CLKSRC_PCLK (0) +#define S3C2412_CLKSRC_I2SCLK (1) + +extern struct clk *s3c2412_get_iisclk(void); + +extern struct snd_soc_cpu_dai s3c2412_i2s_dai; + +struct s3c2412_rate_calc { + unsigned int clk_div; /* for prescaler */ + unsigned int fs_div; /* for root frame clock */ +}; + +extern int s3c2412_iis_calc_rate(struct s3c2412_rate_calc *info, + unsigned int *fstab, + unsigned int rate, struct clk *clk); + +#endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */ diff --git a/sound/soc/s3c24xx/s3c2443-ac97.c b/sound/soc/s3c24xx/s3c2443-ac97.c index 758a2637e7a..1c1ddbf7f3c 100644 --- a/sound/soc/s3c24xx/s3c2443-ac97.c +++ b/sound/soc/s3c24xx/s3c2443-ac97.c @@ -23,7 +23,6 @@ #include <linux/delay.h> #include <linux/clk.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/ac97_codec.h> @@ -253,7 +252,7 @@ static int s3c2443_ac97_probe(struct platform_device *pdev) ac_glbctrl |= S3C_AC97_GLBCTRL_TRANSFERDATAENABLE; writel(ac_glbctrl, s3c24xx_ac97.regs + S3C_AC97_GLBCTRL); - ret = request_irq(IRQ_S3C2443_AC97, s3c2443_ac97_irq, + ret = request_irq(IRQ_S3C244x_AC97, s3c2443_ac97_irq, IRQF_DISABLED, "AC97", NULL); if (ret < 0) { printk(KERN_ERR "s3c24xx-ac97: interrupt request failed.\n"); @@ -266,7 +265,7 @@ static int s3c2443_ac97_probe(struct platform_device *pdev) static void s3c2443_ac97_remove(struct platform_device *pdev) { - free_irq(IRQ_S3C2443_AC97, NULL); + free_irq(IRQ_S3C244x_AC97, NULL); clk_disable(s3c24xx_ac97.ac97_clk); clk_put(s3c24xx_ac97.ac97_clk); iounmap(s3c24xx_ac97.regs); diff --git a/sound/soc/s3c24xx/s3c24xx-ac97.h b/sound/soc/s3c24xx/s3c24xx-ac97.h index 2b835e8260f..bf03e8ed16c 100644 --- a/sound/soc/s3c24xx/s3c24xx-ac97.h +++ b/sound/soc/s3c24xx/s3c24xx-ac97.h @@ -20,6 +20,12 @@ #define AC_CMD_ADDR(x) (x << 16) #define AC_CMD_DATA(x) (x & 0xffff) +#ifdef CONFIG_CPU_S3C2440 +#define IRQ_S3C244x_AC97 IRQ_S3C2440_AC97 +#else +#define IRQ_S3C244x_AC97 IRQ_S3C2443_AC97 +#endif + extern struct snd_soc_cpu_dai s3c2443_ac97_dai[]; #endif /*S3C24XXAC97_H_*/ diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.c b/sound/soc/s3c24xx/s3c24xx-i2s.c index cd89c4105fc..0a3c630951b 100644 --- a/sound/soc/s3c24xx/s3c24xx-i2s.c +++ b/sound/soc/s3c24xx/s3c24xx-i2s.c @@ -24,7 +24,7 @@ #include <linux/device.h> #include <linux/delay.h> #include <linux/clk.h> -#include <sound/driver.h> +#include <linux/jiffies.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -33,13 +33,14 @@ #include <asm/hardware.h> #include <asm/io.h> -#include <asm/arch/regs-iis.h> #include <asm/arch/regs-gpio.h> #include <asm/arch/regs-clock.h> #include <asm/arch/audio.h> #include <asm/dma.h> #include <asm/arch/dma.h> +#include <asm/plat-s3c24xx/regs-iis.h> + #include "s3c24xx-pcm.h" #include "s3c24xx-i2s.h" @@ -75,6 +76,10 @@ static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_in = { struct s3c24xx_i2s_info { void __iomem *regs; struct clk *iis_clk; + u32 iiscon; + u32 iismod; + u32 iisfcon; + u32 iispsr; }; static struct s3c24xx_i2s_info s3c24xx_i2s; @@ -184,7 +189,7 @@ static int s3c24xx_snd_lrsync(void) if (iiscon & S3C2410_IISCON_LRINDEX) break; - if (timeout < jiffies) + if (time_after(jiffies, timeout)) return -ETIMEDOUT; } @@ -405,6 +410,38 @@ static int s3c24xx_i2s_probe(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +int s3c24xx_i2s_suspend(struct platform_device *pdev, + struct snd_soc_cpu_dai *cpu_dai) +{ + s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); + s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); + s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); + s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR); + + clk_disable(s3c24xx_i2s.iis_clk); + + return 0; +} + +int s3c24xx_i2s_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *cpu_dai) +{ + clk_enable(s3c24xx_i2s.iis_clk); + + writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); + writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); + writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); + writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR); + + return 0; +} +#else +#define s3c24xx_i2s_suspend NULL +#define s3c24xx_i2s_resume NULL +#endif + + #define S3C24XX_I2S_RATES \ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ @@ -415,6 +452,8 @@ struct snd_soc_cpu_dai s3c24xx_i2s_dai = { .id = 0, .type = SND_SOC_DAI_I2S, .probe = s3c24xx_i2s_probe, + .suspend = s3c24xx_i2s_suspend, + .resume = s3c24xx_i2s_resume, .playback = { .channels_min = 2, .channels_max = 2, diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c index 4107a87d4de..29a6c82f873 100644 --- a/sound/soc/s3c24xx/s3c24xx-pcm.c +++ b/sound/soc/s3c24xx/s3c24xx-pcm.c @@ -24,7 +24,6 @@ #include <linux/slab.h> #include <linux/dma-mapping.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -49,7 +48,9 @@ static const struct snd_pcm_hardware s3c24xx_pcm_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID, + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U8 | @@ -176,28 +177,6 @@ static int s3c24xx_pcm_hw_params(struct snd_pcm_substream *substream, } } - /* channel needs configuring for mem=>device, increment memory addr, - * sync to pclk, half-word transfers to the IIS-FIFO. */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - s3c2410_dma_devconfig(prtd->params->channel, - S3C2410_DMASRC_MEM, S3C2410_DISRCC_INC | - S3C2410_DISRCC_APB, prtd->params->dma_addr); - - s3c2410_dma_config(prtd->params->channel, - prtd->params->dma_size, - S3C2410_DCON_SYNC_PCLK | - S3C2410_DCON_HANDSHAKE); - } else { - s3c2410_dma_config(prtd->params->channel, - prtd->params->dma_size, - S3C2410_DCON_HANDSHAKE | - S3C2410_DCON_SYNC_PCLK); - - s3c2410_dma_devconfig(prtd->params->channel, - S3C2410_DMASRC_HW, 0x3, - prtd->params->dma_addr); - } - s3c2410_dma_set_buffdone_fn(prtd->params->channel, s3c24xx_audio_buffdone); @@ -246,6 +225,28 @@ static int s3c24xx_pcm_prepare(struct snd_pcm_substream *substream) if (!prtd->params) return 0; + /* channel needs configuring for mem=>device, increment memory addr, + * sync to pclk, half-word transfers to the IIS-FIFO. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + s3c2410_dma_devconfig(prtd->params->channel, + S3C2410_DMASRC_MEM, S3C2410_DISRCC_INC | + S3C2410_DISRCC_APB, prtd->params->dma_addr); + + s3c2410_dma_config(prtd->params->channel, + prtd->params->dma_size, + S3C2410_DCON_SYNC_PCLK | + S3C2410_DCON_HANDSHAKE); + } else { + s3c2410_dma_config(prtd->params->channel, + prtd->params->dma_size, + S3C2410_DCON_HANDSHAKE | + S3C2410_DCON_SYNC_PCLK); + + s3c2410_dma_devconfig(prtd->params->channel, + S3C2410_DMASRC_HW, 0x3, + prtd->params->dma_addr); + } + /* flush the DMA channel */ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH); prtd->dma_loaded = 0; diff --git a/sound/soc/s3c24xx/smdk2443_wm9710.c b/sound/soc/s3c24xx/smdk2443_wm9710.c index d46cd811ceb..b4a56302b9a 100644 --- a/sound/soc/s3c24xx/smdk2443_wm9710.c +++ b/sound/soc/s3c24xx/smdk2443_wm9710.c @@ -17,7 +17,6 @@ #include <linux/module.h> #include <linux/device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> diff --git a/sound/soc/sh/dma-sh7760.c b/sound/soc/sh/dma-sh7760.c index cdee374b843..7a3ce80d672 100644 --- a/sound/soc/sh/dma-sh7760.c +++ b/sound/soc/sh/dma-sh7760.c @@ -16,7 +16,6 @@ #include <linux/init.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> diff --git a/sound/soc/sh/hac.c b/sound/soc/sh/hac.c index 8e3f03908cd..b7b676b3d67 100644 --- a/sound/soc/sh/hac.c +++ b/sound/soc/sh/hac.c @@ -21,7 +21,6 @@ #include <linux/interrupt.h> #include <linux/wait.h> #include <linux/delay.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/ac97_codec.h> @@ -105,7 +104,7 @@ static int hac_get_codec_data(struct hac_priv *hac, unsigned short r, unsigned int to1, to2, i; unsigned short adr; - for (i = 0; i < AC97_READ_RETRY; ++i) { + for (i = AC97_READ_RETRY; i; i--) { *v = 0; /* wait for HAC to receive something from the codec */ for (to1 = TMO_E4; @@ -132,7 +131,7 @@ static int hac_get_codec_data(struct hac_priv *hac, unsigned short r, udelay(21); } HACREG(HACRSR) &= ~(RSR_STDRY | RSR_STARY); - return (i < AC97_READ_RETRY); + return i; } static unsigned short hac_read_codec_aux(struct hac_priv *hac, @@ -141,7 +140,7 @@ static unsigned short hac_read_codec_aux(struct hac_priv *hac, unsigned short val; unsigned int i, to; - for (i = 0; i < AC97_READ_RETRY; i++) { + for (i = AC97_READ_RETRY; i; i--) { /* send_read_request */ local_irq_disable(); HACREG(HACTSR) &= ~(TSR_CMDAMT); @@ -159,10 +158,7 @@ static unsigned short hac_read_codec_aux(struct hac_priv *hac, break; } - if (i == AC97_READ_RETRY) - return ~0; - - return val; + return i ? val : ~0; } static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg, @@ -172,7 +168,7 @@ static void hac_ac97_write(struct snd_ac97 *ac97, unsigned short reg, struct hac_priv *hac = &hac_cpu_data[unit_id]; unsigned int i, to; /* write_codec_aux */ - for (i = 0; i < AC97_WRITE_RETRY; i++) { + for (i = AC97_WRITE_RETRY; i; i--) { /* send_write_request */ local_irq_disable(); HACREG(HACTSR) &= ~(TSR_CMDDMT | TSR_CMDAMT); diff --git a/sound/soc/sh/sh7760-ac97.c b/sound/soc/sh/sh7760-ac97.c index 5563f14511f..2f91de84c5c 100644 --- a/sound/soc/sh/sh7760-ac97.c +++ b/sound/soc/sh/sh7760-ac97.c @@ -9,7 +9,6 @@ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c index b72bc316cb8..3388bc3d62d 100644 --- a/sound/soc/sh/ssi.c +++ b/sound/soc/sh/ssi.c @@ -30,7 +30,6 @@ #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/initval.h> diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e6a67b58f29..9eb5479787c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -32,7 +32,6 @@ #include <linux/pm.h> #include <linux/bitops.h> #include <linux/platform_device.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -288,16 +287,25 @@ static void close_delayed_work(struct work_struct *work) /* are we waiting on this codec DAI stream */ if (codec_dai->pop_wait == 1) { + /* power down the codec to D1 if no longer active */ + if (codec->active == 0) { + dbg("pop wq D1 %s %s\n", codec->name, + codec_dai->playback.stream_name); + snd_soc_dapm_device_event(socdev, + SNDRV_CTL_POWER_D1); + } + codec_dai->pop_wait = 0; - snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name, + snd_soc_dapm_stream_event(codec, + codec_dai->playback.stream_name, SND_SOC_DAPM_STREAM_STOP); /* power down the codec power domain if no longer active */ if (codec->active == 0) { dbg("pop wq D3 %s %s\n", codec->name, codec_dai->playback.stream_name); - if (codec->dapm_event) - codec->dapm_event(codec, SNDRV_CTL_POWER_D3hot); + snd_soc_dapm_device_event(socdev, + SNDRV_CTL_POWER_D3hot); } } } @@ -353,12 +361,12 @@ static int soc_codec_close(struct snd_pcm_substream *substream) } else { /* capture streams can be powered down now */ snd_soc_dapm_stream_event(codec, - codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_STOP); + codec_dai->capture.stream_name, + SND_SOC_DAPM_STREAM_STOP); - if (codec->active == 0 && codec_dai->pop_wait == 0){ - if (codec->dapm_event) - codec->dapm_event(codec, SNDRV_CTL_POWER_D3hot); - } + if (codec->active == 0 && codec_dai->pop_wait == 0) + snd_soc_dapm_device_event(socdev, + SNDRV_CTL_POWER_D3hot); } mutex_unlock(&pcm_mutex); @@ -433,8 +441,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) /* no delayed work - do we need to power up codec */ if (codec->dapm_state != SNDRV_CTL_POWER_D0) { - if (codec->dapm_event) - codec->dapm_event(codec, SNDRV_CTL_POWER_D1); + snd_soc_dapm_device_event(socdev, SNDRV_CTL_POWER_D1); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) snd_soc_dapm_stream_event(codec, @@ -445,8 +452,7 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_START); - if (codec->dapm_event) - codec->dapm_event(codec, SNDRV_CTL_POWER_D0); + snd_soc_dapm_device_event(socdev, SNDRV_CTL_POWER_D0); if (codec_dai->dai_ops.digital_mute) codec_dai->dai_ops.digital_mute(codec_dai, 0); @@ -639,6 +645,10 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) dai->dai_ops.digital_mute(dai, 1); } + /* suspend all pcms */ + for (i = 0; i < machine->num_links; i++) + snd_pcm_suspend_all(machine->dai_link[i].pcm); + if (machine->suspend_pre) machine->suspend_pre(pdev, state); @@ -873,6 +883,7 @@ static int soc_new_pcm(struct snd_soc_device *socdev, return ret; } + dai_link->pcm = pcm; pcm->private_data = rtd; soc_pcm_ops.mmap = socdev->platform->pcm_ops->mmap; soc_pcm_ops.pointer = socdev->platform->pcm_ops->pointer; @@ -1090,7 +1101,6 @@ int snd_soc_register_card(struct snd_soc_device *socdev) struct snd_soc_machine *machine = socdev->machine; int ret = 0, i, ac97 = 0, err = 0; - mutex_lock(&codec->mutex); for(i = 0; i < machine->num_links; i++) { if (socdev->machine->dai_link[i].init) { err = socdev->machine->dai_link[i].init(codec); @@ -1116,12 +1126,14 @@ int snd_soc_register_card(struct snd_soc_device *socdev) goto out; } + mutex_lock(&codec->mutex); #ifdef CONFIG_SND_SOC_AC97_BUS if (ac97) { ret = soc_ac97_dev_register(codec); if (ret < 0) { printk(KERN_ERR "asoc: AC97 device register failed\n"); snd_card_free(codec->card); + mutex_unlock(&codec->mutex); goto out; } } @@ -1134,8 +1146,10 @@ int snd_soc_register_card(struct snd_soc_device *socdev) err = device_create_file(socdev->dev, &dev_attr_codec_reg); if (err < 0) printk(KERN_WARNING "asoc: failed to add codec sysfs entries\n"); -out: + mutex_unlock(&codec->mutex); + +out: return ret; } EXPORT_SYMBOL_GPL(snd_soc_register_card); @@ -1215,7 +1229,6 @@ struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, memcpy(&template, _template, sizeof(template)); if (long_name) template.name = long_name; - template.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; template.index = 0; return snd_ctl_new1(&template, data); @@ -1350,13 +1363,16 @@ EXPORT_SYMBOL_GPL(snd_soc_info_enum_ext); int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int mask = kcontrol->private_value; + int max = kcontrol->private_value; + + if (max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->type = - mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; - uinfo->value.integer.max = mask; + uinfo->value.integer.max = max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext); @@ -1373,15 +1389,18 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext); int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f; - uinfo->type = - mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + if (max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = shift == rshift ? 1 : 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = mask; + uinfo->value.integer.max = max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw); @@ -1402,7 +1421,8 @@ int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f; - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; + int mask = (1 << fls(max)) - 1; int invert = (kcontrol->private_value >> 24) & 0x01; ucontrol->value.integer.value[0] = @@ -1412,10 +1432,10 @@ int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, (snd_soc_read(codec, reg) >> rshift) & mask; if (invert) { ucontrol->value.integer.value[0] = - mask - ucontrol->value.integer.value[0]; + max - ucontrol->value.integer.value[0]; if (shift != rshift) ucontrol->value.integer.value[1] = - mask - ucontrol->value.integer.value[1]; + max - ucontrol->value.integer.value[1]; } return 0; @@ -1438,25 +1458,24 @@ int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f; - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; + int mask = (1 << fls(max)) - 1; int invert = (kcontrol->private_value >> 24) & 0x01; - int err; unsigned short val, val2, val_mask; val = (ucontrol->value.integer.value[0] & mask); if (invert) - val = mask - val; + val = max - val; val_mask = mask << shift; val = val << shift; if (shift != rshift) { val2 = (ucontrol->value.integer.value[1] & mask); if (invert) - val2 = mask - val2; + val2 = max - val2; val_mask |= mask << rshift; val |= val2 << rshift; } - err = snd_soc_update_bits(codec, reg, val_mask, val); - return err; + return snd_soc_update_bits(codec, reg, val_mask, val); } EXPORT_SYMBOL_GPL(snd_soc_put_volsw); @@ -1473,13 +1492,16 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw); int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int mask = (kcontrol->private_value >> 12) & 0xff; + int max = (kcontrol->private_value >> 12) & 0xff; + + if (max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->type = - mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2; uinfo->value.integer.min = 0; - uinfo->value.integer.max = mask; + uinfo->value.integer.max = max; return 0; } EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r); @@ -1500,7 +1522,8 @@ int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int reg2 = (kcontrol->private_value >> 24) & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; - int mask = (kcontrol->private_value >> 12) & 0xff; + int max = (kcontrol->private_value >> 12) & 0xff; + int mask = (1<<fls(max))-1; int invert = (kcontrol->private_value >> 20) & 0x01; ucontrol->value.integer.value[0] = @@ -1509,9 +1532,9 @@ int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, (snd_soc_read(codec, reg2) >> shift) & mask; if (invert) { ucontrol->value.integer.value[0] = - mask - ucontrol->value.integer.value[0]; + max - ucontrol->value.integer.value[0]; ucontrol->value.integer.value[1] = - mask - ucontrol->value.integer.value[1]; + max - ucontrol->value.integer.value[1]; } return 0; @@ -1534,7 +1557,8 @@ int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int reg2 = (kcontrol->private_value >> 24) & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; - int mask = (kcontrol->private_value >> 12) & 0xff; + int max = (kcontrol->private_value >> 12) & 0xff; + int mask = (1 << fls(max)) - 1; int invert = (kcontrol->private_value >> 20) & 0x01; int err; unsigned short val, val2, val_mask; @@ -1544,8 +1568,8 @@ int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, val2 = (ucontrol->value.integer.value[1] & mask); if (invert) { - val = mask - val; - val2 = mask - val2; + val = max - val; + val2 = max - val2; } val = val << shift; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 29a546fecac..620d7ea3c15 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -43,7 +43,6 @@ #include <linux/bitops.h> #include <linux/platform_device.h> #include <linux/jiffies.h> -#include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -524,11 +523,13 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue; if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, SND_SOC_DAPM_PRE_PMU); + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMU); if (ret < 0) return ret; } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, SND_SOC_DAPM_PRE_PMD); + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMD); if (ret < 0) return ret; } @@ -539,11 +540,13 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue; if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, SND_SOC_DAPM_POST_PMU); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); if (ret < 0) return ret; } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, SND_SOC_DAPM_POST_PMD); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret; } @@ -567,26 +570,30 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) if (power) { /* power up event */ if (w->event_flags & SND_SOC_DAPM_PRE_PMU) { - ret = w->event(w, SND_SOC_DAPM_PRE_PMU); + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMU); if (ret < 0) return ret; } dapm_update_bits(w); if (w->event_flags & SND_SOC_DAPM_POST_PMU){ - ret = w->event(w, SND_SOC_DAPM_POST_PMU); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); if (ret < 0) return ret; } } else { /* power down event */ if (w->event_flags & SND_SOC_DAPM_PRE_PMD) { - ret = w->event(w, SND_SOC_DAPM_PRE_PMD); + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMD); if (ret < 0) return ret; } dapm_update_bits(w); if (w->event_flags & SND_SOC_DAPM_POST_PMD) { - ret = w->event(w, SND_SOC_DAPM_POST_PMD); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret; } @@ -692,7 +699,7 @@ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, return 0; } -/* test and update the power status of a mixer widget */ +/* test and update the power status of a mixer or switch widget */ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kcontrol, int reg, int val_mask, int val, int invert) @@ -700,7 +707,8 @@ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget, struct snd_soc_dapm_path *path; int found = 0; - if (widget->id != snd_soc_dapm_mixer) + if (widget->id != snd_soc_dapm_mixer && + widget->id != snd_soc_dapm_switch) return -ENODEV; if (!snd_soc_test_bits(widget->codec, reg, val_mask, val)) @@ -963,7 +971,6 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec) { struct snd_soc_dapm_widget *w; - mutex_lock(&codec->mutex); list_for_each_entry(w, &codec->dapm_widgets, list) { if (w->new) @@ -998,7 +1005,6 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec) } dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP); - mutex_unlock(&codec->mutex); return 0; } EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets); @@ -1019,8 +1025,9 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f; - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; int invert = (kcontrol->private_value >> 24) & 0x01; + int mask = (1 << fls(max)) - 1; /* return the saved value if we are powered down */ if (widget->id == snd_soc_dapm_pga && !widget->power) { @@ -1035,10 +1042,10 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, (snd_soc_read(widget->codec, reg) >> rshift) & mask; if (invert) { ucontrol->value.integer.value[0] = - mask - ucontrol->value.integer.value[0]; + max - ucontrol->value.integer.value[0]; if (shift != rshift) ucontrol->value.integer.value[1] = - mask - ucontrol->value.integer.value[1]; + max - ucontrol->value.integer.value[1]; } return 0; @@ -1061,7 +1068,8 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, int reg = kcontrol->private_value & 0xff; int shift = (kcontrol->private_value >> 8) & 0x0f; int rshift = (kcontrol->private_value >> 12) & 0x0f; - int mask = (kcontrol->private_value >> 16) & 0xff; + int max = (kcontrol->private_value >> 16) & 0xff; + int mask = (1 << fls(max)) - 1; int invert = (kcontrol->private_value >> 24) & 0x01; unsigned short val, val2, val_mask; int ret; @@ -1069,13 +1077,13 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, val = (ucontrol->value.integer.value[0] & mask); if (invert) - val = mask - val; + val = max - val; val_mask = mask << shift; val = val << shift; if (shift != rshift) { val2 = (ucontrol->value.integer.value[1] & mask); if (invert) - val2 = mask - val2; + val2 = max - val2; val_mask |= mask << rshift; val |= val2 << rshift; } @@ -1093,13 +1101,17 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, dapm_mixer_update_power(widget, kcontrol, reg, val_mask, val, invert); if (widget->event) { if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { - ret = widget->event(widget, SND_SOC_DAPM_PRE_REG); - if (ret < 0) + ret = widget->event(widget, kcontrol, + SND_SOC_DAPM_PRE_REG); + if (ret < 0) { + ret = 1; goto out; + } } ret = snd_soc_update_bits(widget->codec, reg, val_mask, val); if (widget->event_flags & SND_SOC_DAPM_POST_REG) - ret = widget->event(widget, SND_SOC_DAPM_POST_REG); + ret = widget->event(widget, kcontrol, + SND_SOC_DAPM_POST_REG); } else ret = snd_soc_update_bits(widget->codec, reg, val_mask, val); @@ -1174,13 +1186,15 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, dapm_mux_update_power(widget, kcontrol, mask, mux, e); if (widget->event) { if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { - ret = widget->event(widget, SND_SOC_DAPM_PRE_REG); + ret = widget->event(widget, + kcontrol, SND_SOC_DAPM_PRE_REG); if (ret < 0) goto out; } ret = snd_soc_update_bits(widget->codec, e->reg, mask, val); if (widget->event_flags & SND_SOC_DAPM_POST_REG) - ret = widget->event(widget, SND_SOC_DAPM_POST_REG); + ret = widget->event(widget, + kcontrol, SND_SOC_DAPM_POST_REG); } else ret = snd_soc_update_bits(widget->codec, e->reg, mask, val); @@ -1280,6 +1294,29 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event); /** + * snd_soc_dapm_device_event - send a device event to the dapm core + * @socdev: audio device + * @event: device event + * + * Sends a device event to the dapm core. The core then makes any + * necessary machine or codec power changes.. + * + * Returns 0 for success else error. + */ +int snd_soc_dapm_device_event(struct snd_soc_device *socdev, int event) +{ + struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_machine *machine = socdev->machine; + + if (machine->dapm_event) + machine->dapm_event(machine, event); + if (codec->dapm_event) + codec->dapm_event(codec, event); + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_device_event); + +/** * snd_soc_dapm_set_endpoint - set audio endpoint status * @codec: audio codec * @endpoint: audio signal endpoint (or start point) |