diff options
Diffstat (limited to 'sound/soc')
28 files changed, 3392 insertions, 8 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index b1749bc67979..5e68ac880832 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig | |||
@@ -30,7 +30,9 @@ source "sound/soc/blackfin/Kconfig" | |||
30 | source "sound/soc/davinci/Kconfig" | 30 | source "sound/soc/davinci/Kconfig" |
31 | source "sound/soc/fsl/Kconfig" | 31 | source "sound/soc/fsl/Kconfig" |
32 | source "sound/soc/imx/Kconfig" | 32 | source "sound/soc/imx/Kconfig" |
33 | source "sound/soc/nuc900/Kconfig" | ||
33 | source "sound/soc/omap/Kconfig" | 34 | source "sound/soc/omap/Kconfig" |
35 | source "sound/soc/kirkwood/Kconfig" | ||
34 | source "sound/soc/pxa/Kconfig" | 36 | source "sound/soc/pxa/Kconfig" |
35 | source "sound/soc/s3c24xx/Kconfig" | 37 | source "sound/soc/s3c24xx/Kconfig" |
36 | source "sound/soc/s6000/Kconfig" | 38 | source "sound/soc/s6000/Kconfig" |
diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 1470141d4167..05d5d340968e 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile | |||
@@ -8,7 +8,9 @@ obj-$(CONFIG_SND_SOC) += blackfin/ | |||
8 | obj-$(CONFIG_SND_SOC) += davinci/ | 8 | obj-$(CONFIG_SND_SOC) += davinci/ |
9 | obj-$(CONFIG_SND_SOC) += fsl/ | 9 | obj-$(CONFIG_SND_SOC) += fsl/ |
10 | obj-$(CONFIG_SND_SOC) += imx/ | 10 | obj-$(CONFIG_SND_SOC) += imx/ |
11 | obj-$(CONFIG_SND_SOC) += nuc900/ | ||
11 | obj-$(CONFIG_SND_SOC) += omap/ | 12 | obj-$(CONFIG_SND_SOC) += omap/ |
13 | obj-$(CONFIG_SND_SOC) += kirkwood/ | ||
12 | obj-$(CONFIG_SND_SOC) += pxa/ | 14 | obj-$(CONFIG_SND_SOC) += pxa/ |
13 | obj-$(CONFIG_SND_SOC) += s3c24xx/ | 15 | obj-$(CONFIG_SND_SOC) += s3c24xx/ |
14 | obj-$(CONFIG_SND_SOC) += s6000/ | 16 | obj-$(CONFIG_SND_SOC) += s6000/ |
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 31ac5538fe7e..c37c84458b58 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig | |||
@@ -22,6 +22,7 @@ config SND_SOC_ALL_CODECS | |||
22 | select SND_SOC_AK4642 if I2C | 22 | select SND_SOC_AK4642 if I2C |
23 | select SND_SOC_AK4671 if I2C | 23 | select SND_SOC_AK4671 if I2C |
24 | select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC | 24 | select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC |
25 | select SND_SOC_CS42L51 if I2C | ||
25 | select SND_SOC_CS4270 if I2C | 26 | select SND_SOC_CS4270 if I2C |
26 | select SND_SOC_MAX9877 if I2C | 27 | select SND_SOC_MAX9877 if I2C |
27 | select SND_SOC_DA7210 if I2C | 28 | select SND_SOC_DA7210 if I2C |
@@ -120,6 +121,9 @@ config SND_SOC_AK4671 | |||
120 | config SND_SOC_CQ0093VC | 121 | config SND_SOC_CQ0093VC |
121 | tristate | 122 | tristate |
122 | 123 | ||
124 | config SND_SOC_CS42L51 | ||
125 | tristate | ||
126 | |||
123 | # Cirrus Logic CS4270 Codec | 127 | # Cirrus Logic CS4270 Codec |
124 | config SND_SOC_CS4270 | 128 | config SND_SOC_CS4270 |
125 | tristate | 129 | tristate |
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 91429eab0707..4a9c205caf56 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile | |||
@@ -9,6 +9,7 @@ snd-soc-ak4535-objs := ak4535.o | |||
9 | snd-soc-ak4642-objs := ak4642.o | 9 | snd-soc-ak4642-objs := ak4642.o |
10 | snd-soc-ak4671-objs := ak4671.o | 10 | snd-soc-ak4671-objs := ak4671.o |
11 | snd-soc-cq93vc-objs := cq93vc.o | 11 | snd-soc-cq93vc-objs := cq93vc.o |
12 | snd-soc-cs42l51-objs := cs42l51.o | ||
12 | snd-soc-cs4270-objs := cs4270.o | 13 | snd-soc-cs4270-objs := cs4270.o |
13 | snd-soc-cx20442-objs := cx20442.o | 14 | snd-soc-cx20442-objs := cx20442.o |
14 | snd-soc-da7210-objs := da7210.o | 15 | snd-soc-da7210-objs := da7210.o |
@@ -74,6 +75,7 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o | |||
74 | obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o | 75 | obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o |
75 | obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o | 76 | obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o |
76 | obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o | 77 | obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o |
78 | obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o | ||
77 | obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o | 79 | obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o |
78 | obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o | 80 | obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o |
79 | obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o | 81 | obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o |
diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index c8ca1142b2f4..1def75e4862f 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c | |||
@@ -24,6 +24,7 @@ | |||
24 | 24 | ||
25 | /* codec private data */ | 25 | /* codec private data */ |
26 | struct ad193x_priv { | 26 | struct ad193x_priv { |
27 | unsigned int sysclk; | ||
27 | struct snd_soc_codec codec; | 28 | struct snd_soc_codec codec; |
28 | u8 reg_cache[AD193X_NUM_REGS]; | 29 | u8 reg_cache[AD193X_NUM_REGS]; |
29 | }; | 30 | }; |
@@ -251,15 +252,32 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, | |||
251 | return 0; | 252 | return 0; |
252 | } | 253 | } |
253 | 254 | ||
255 | static int ad193x_set_dai_sysclk(struct snd_soc_dai *codec_dai, | ||
256 | int clk_id, unsigned int freq, int dir) | ||
257 | { | ||
258 | struct snd_soc_codec *codec = codec_dai->codec; | ||
259 | struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec); | ||
260 | switch (freq) { | ||
261 | case 12288000: | ||
262 | case 18432000: | ||
263 | case 24576000: | ||
264 | case 36864000: | ||
265 | ad193x->sysclk = freq; | ||
266 | return 0; | ||
267 | } | ||
268 | return -EINVAL; | ||
269 | } | ||
270 | |||
254 | static int ad193x_hw_params(struct snd_pcm_substream *substream, | 271 | static int ad193x_hw_params(struct snd_pcm_substream *substream, |
255 | struct snd_pcm_hw_params *params, | 272 | struct snd_pcm_hw_params *params, |
256 | struct snd_soc_dai *dai) | 273 | struct snd_soc_dai *dai) |
257 | { | 274 | { |
258 | int word_len = 0, reg = 0; | 275 | int word_len = 0, reg = 0, master_rate = 0; |
259 | 276 | ||
260 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | 277 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
261 | struct snd_soc_device *socdev = rtd->socdev; | 278 | struct snd_soc_device *socdev = rtd->socdev; |
262 | struct snd_soc_codec *codec = socdev->card->codec; | 279 | struct snd_soc_codec *codec = socdev->card->codec; |
280 | struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec); | ||
263 | 281 | ||
264 | /* bit size */ | 282 | /* bit size */ |
265 | switch (params_format(params)) { | 283 | switch (params_format(params)) { |
@@ -275,6 +293,25 @@ static int ad193x_hw_params(struct snd_pcm_substream *substream, | |||
275 | break; | 293 | break; |
276 | } | 294 | } |
277 | 295 | ||
296 | switch (ad193x->sysclk) { | ||
297 | case 12288000: | ||
298 | master_rate = AD193X_PLL_INPUT_256; | ||
299 | break; | ||
300 | case 18432000: | ||
301 | master_rate = AD193X_PLL_INPUT_384; | ||
302 | break; | ||
303 | case 24576000: | ||
304 | master_rate = AD193X_PLL_INPUT_512; | ||
305 | break; | ||
306 | case 36864000: | ||
307 | master_rate = AD193X_PLL_INPUT_768; | ||
308 | break; | ||
309 | } | ||
310 | |||
311 | reg = snd_soc_read(codec, AD193X_PLL_CLK_CTRL0); | ||
312 | reg = (reg & AD193X_PLL_INPUT_MASK) | master_rate; | ||
313 | snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, reg); | ||
314 | |||
278 | reg = snd_soc_read(codec, AD193X_DAC_CTRL2); | 315 | reg = snd_soc_read(codec, AD193X_DAC_CTRL2); |
279 | reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len; | 316 | reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len; |
280 | snd_soc_write(codec, AD193X_DAC_CTRL2, reg); | 317 | snd_soc_write(codec, AD193X_DAC_CTRL2, reg); |
@@ -348,6 +385,7 @@ static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) | |||
348 | /* pll input: mclki/xi */ | 385 | /* pll input: mclki/xi */ |
349 | snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */ | 386 | snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */ |
350 | snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04); | 387 | snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04); |
388 | ad193x->sysclk = 12288000; | ||
351 | 389 | ||
352 | ret = snd_soc_register_codec(codec); | 390 | ret = snd_soc_register_codec(codec); |
353 | if (ret != 0) { | 391 | if (ret != 0) { |
@@ -383,6 +421,7 @@ static struct snd_soc_dai_ops ad193x_dai_ops = { | |||
383 | .hw_params = ad193x_hw_params, | 421 | .hw_params = ad193x_hw_params, |
384 | .digital_mute = ad193x_mute, | 422 | .digital_mute = ad193x_mute, |
385 | .set_tdm_slot = ad193x_set_tdm_slot, | 423 | .set_tdm_slot = ad193x_set_tdm_slot, |
424 | .set_sysclk = ad193x_set_dai_sysclk, | ||
386 | .set_fmt = ad193x_set_dai_fmt, | 425 | .set_fmt = ad193x_set_dai_fmt, |
387 | }; | 426 | }; |
388 | 427 | ||
diff --git a/sound/soc/codecs/ad193x.h b/sound/soc/codecs/ad193x.h index a03c880d52f9..654ba64ae04c 100644 --- a/sound/soc/codecs/ad193x.h +++ b/sound/soc/codecs/ad193x.h | |||
@@ -11,6 +11,11 @@ | |||
11 | 11 | ||
12 | #define AD193X_PLL_CLK_CTRL0 0x800 | 12 | #define AD193X_PLL_CLK_CTRL0 0x800 |
13 | #define AD193X_PLL_POWERDOWN 0x01 | 13 | #define AD193X_PLL_POWERDOWN 0x01 |
14 | #define AD193X_PLL_INPUT_MASK (~0x6) | ||
15 | #define AD193X_PLL_INPUT_256 (0 << 1) | ||
16 | #define AD193X_PLL_INPUT_384 (1 << 1) | ||
17 | #define AD193X_PLL_INPUT_512 (2 << 1) | ||
18 | #define AD193X_PLL_INPUT_768 (3 << 1) | ||
14 | #define AD193X_PLL_CLK_CTRL1 0x801 | 19 | #define AD193X_PLL_CLK_CTRL1 0x801 |
15 | #define AD193X_DAC_CTRL0 0x802 | 20 | #define AD193X_DAC_CTRL0 0x802 |
16 | #define AD193X_DAC_POWERDOWN 0x01 | 21 | #define AD193X_DAC_POWERDOWN 0x01 |
diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c new file mode 100644 index 000000000000..dd9b8550c402 --- /dev/null +++ b/sound/soc/codecs/cs42l51.c | |||
@@ -0,0 +1,763 @@ | |||
1 | /* | ||
2 | * cs42l51.c | ||
3 | * | ||
4 | * ASoC Driver for Cirrus Logic CS42L51 codecs | ||
5 | * | ||
6 | * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com> | ||
7 | * | ||
8 | * Based on cs4270.c - Copyright (c) Freescale Semiconductor | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License version 2 as | ||
12 | * published by the Free Software Foundation. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * For now: | ||
20 | * - Only I2C is support. Not SPI | ||
21 | * - master mode *NOT* supported | ||
22 | */ | ||
23 | |||
24 | #include <linux/module.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <sound/core.h> | ||
28 | #include <sound/soc.h> | ||
29 | #include <sound/soc-dapm.h> | ||
30 | #include <sound/tlv.h> | ||
31 | #include <sound/initval.h> | ||
32 | #include <sound/pcm_params.h> | ||
33 | #include <sound/pcm.h> | ||
34 | #include <linux/i2c.h> | ||
35 | |||
36 | #include "cs42l51.h" | ||
37 | |||
38 | enum master_slave_mode { | ||
39 | MODE_SLAVE, | ||
40 | MODE_SLAVE_AUTO, | ||
41 | MODE_MASTER, | ||
42 | }; | ||
43 | |||
44 | struct cs42l51_private { | ||
45 | unsigned int mclk; | ||
46 | unsigned int audio_mode; /* The mode (I2S or left-justified) */ | ||
47 | enum master_slave_mode func; | ||
48 | struct snd_soc_codec codec; | ||
49 | u8 reg_cache[CS42L51_NUMREGS]; | ||
50 | }; | ||
51 | |||
52 | static struct snd_soc_codec *cs42l51_codec; | ||
53 | |||
54 | #define CS42L51_FORMATS ( \ | ||
55 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ | ||
56 | SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ | ||
57 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ | ||
58 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) | ||
59 | |||
60 | static int cs42l51_fill_cache(struct snd_soc_codec *codec) | ||
61 | { | ||
62 | u8 *cache = codec->reg_cache + 1; | ||
63 | struct i2c_client *i2c_client = codec->control_data; | ||
64 | s32 length; | ||
65 | |||
66 | length = i2c_smbus_read_i2c_block_data(i2c_client, | ||
67 | CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache); | ||
68 | if (length != CS42L51_NUMREGS) { | ||
69 | dev_err(&i2c_client->dev, | ||
70 | "I2C read failure, addr=0x%x (ret=%d vs %d)\n", | ||
71 | i2c_client->addr, length, CS42L51_NUMREGS); | ||
72 | return -EIO; | ||
73 | } | ||
74 | |||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | static int cs42l51_i2c_probe(struct i2c_client *i2c_client, | ||
79 | const struct i2c_device_id *id) | ||
80 | { | ||
81 | struct snd_soc_codec *codec; | ||
82 | struct cs42l51_private *cs42l51; | ||
83 | int ret = 0; | ||
84 | int reg; | ||
85 | |||
86 | if (cs42l51_codec) | ||
87 | return -EBUSY; | ||
88 | |||
89 | /* Verify that we have a CS42L51 */ | ||
90 | ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID); | ||
91 | if (ret < 0) { | ||
92 | dev_err(&i2c_client->dev, "failed to read I2C\n"); | ||
93 | goto error; | ||
94 | } | ||
95 | |||
96 | if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) && | ||
97 | (ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) { | ||
98 | dev_err(&i2c_client->dev, "Invalid chip id\n"); | ||
99 | ret = -ENODEV; | ||
100 | goto error; | ||
101 | } | ||
102 | |||
103 | dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n", | ||
104 | ret & 7); | ||
105 | |||
106 | cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL); | ||
107 | if (!cs42l51) { | ||
108 | dev_err(&i2c_client->dev, "could not allocate codec\n"); | ||
109 | return -ENOMEM; | ||
110 | } | ||
111 | codec = &cs42l51->codec; | ||
112 | |||
113 | mutex_init(&codec->mutex); | ||
114 | INIT_LIST_HEAD(&codec->dapm_widgets); | ||
115 | INIT_LIST_HEAD(&codec->dapm_paths); | ||
116 | |||
117 | codec->dev = &i2c_client->dev; | ||
118 | codec->name = "CS42L51"; | ||
119 | codec->owner = THIS_MODULE; | ||
120 | codec->dai = &cs42l51_dai; | ||
121 | codec->num_dai = 1; | ||
122 | snd_soc_codec_set_drvdata(codec, cs42l51); | ||
123 | |||
124 | codec->control_data = i2c_client; | ||
125 | codec->reg_cache = cs42l51->reg_cache; | ||
126 | codec->reg_cache_size = CS42L51_NUMREGS; | ||
127 | i2c_set_clientdata(i2c_client, codec); | ||
128 | |||
129 | ret = cs42l51_fill_cache(codec); | ||
130 | if (ret < 0) { | ||
131 | dev_err(&i2c_client->dev, "failed to fill register cache\n"); | ||
132 | goto error_alloc; | ||
133 | } | ||
134 | |||
135 | ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); | ||
136 | if (ret < 0) { | ||
137 | dev_err(&i2c_client->dev, "Failed to set cache I/O: %d\n", ret); | ||
138 | goto error_alloc; | ||
139 | } | ||
140 | |||
141 | /* | ||
142 | * DAC configuration | ||
143 | * - Use signal processor | ||
144 | * - auto mute | ||
145 | * - vol changes immediate | ||
146 | * - no de-emphasize | ||
147 | */ | ||
148 | reg = CS42L51_DAC_CTL_DATA_SEL(1) | ||
149 | | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0); | ||
150 | ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg); | ||
151 | if (ret < 0) | ||
152 | goto error_alloc; | ||
153 | |||
154 | cs42l51_dai.dev = codec->dev; | ||
155 | cs42l51_codec = codec; | ||
156 | |||
157 | ret = snd_soc_register_codec(codec); | ||
158 | if (ret != 0) { | ||
159 | dev_err(codec->dev, "Failed to register codec: %d\n", ret); | ||
160 | goto error_alloc; | ||
161 | } | ||
162 | |||
163 | ret = snd_soc_register_dai(&cs42l51_dai); | ||
164 | if (ret < 0) { | ||
165 | dev_err(&i2c_client->dev, "failed to register DAIe\n"); | ||
166 | goto error_reg; | ||
167 | } | ||
168 | |||
169 | return 0; | ||
170 | |||
171 | error_reg: | ||
172 | snd_soc_unregister_codec(codec); | ||
173 | error_alloc: | ||
174 | kfree(cs42l51); | ||
175 | error: | ||
176 | return ret; | ||
177 | } | ||
178 | |||
179 | static int cs42l51_i2c_remove(struct i2c_client *client) | ||
180 | { | ||
181 | struct cs42l51_private *cs42l51 = i2c_get_clientdata(client); | ||
182 | snd_soc_unregister_dai(&cs42l51_dai); | ||
183 | snd_soc_unregister_codec(cs42l51_codec); | ||
184 | cs42l51_codec = NULL; | ||
185 | kfree(cs42l51); | ||
186 | return 0; | ||
187 | } | ||
188 | |||
189 | |||
190 | static const struct i2c_device_id cs42l51_id[] = { | ||
191 | {"cs42l51", 0}, | ||
192 | {} | ||
193 | }; | ||
194 | MODULE_DEVICE_TABLE(i2c, cs42l51_id); | ||
195 | |||
196 | static struct i2c_driver cs42l51_i2c_driver = { | ||
197 | .driver = { | ||
198 | .name = "CS42L51 I2C", | ||
199 | .owner = THIS_MODULE, | ||
200 | }, | ||
201 | .id_table = cs42l51_id, | ||
202 | .probe = cs42l51_i2c_probe, | ||
203 | .remove = cs42l51_i2c_remove, | ||
204 | }; | ||
205 | |||
206 | static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol, | ||
207 | struct snd_ctl_elem_value *ucontrol) | ||
208 | { | ||
209 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
210 | unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3; | ||
211 | |||
212 | switch (value) { | ||
213 | default: | ||
214 | case 0: | ||
215 | ucontrol->value.integer.value[0] = 0; | ||
216 | break; | ||
217 | /* same value : (L+R)/2 and (R+L)/2 */ | ||
218 | case 1: | ||
219 | case 2: | ||
220 | ucontrol->value.integer.value[0] = 1; | ||
221 | break; | ||
222 | case 3: | ||
223 | ucontrol->value.integer.value[0] = 2; | ||
224 | break; | ||
225 | } | ||
226 | |||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | #define CHAN_MIX_NORMAL 0x00 | ||
231 | #define CHAN_MIX_BOTH 0x55 | ||
232 | #define CHAN_MIX_SWAP 0xFF | ||
233 | |||
234 | static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol, | ||
235 | struct snd_ctl_elem_value *ucontrol) | ||
236 | { | ||
237 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
238 | unsigned char val; | ||
239 | |||
240 | switch (ucontrol->value.integer.value[0]) { | ||
241 | default: | ||
242 | case 0: | ||
243 | val = CHAN_MIX_NORMAL; | ||
244 | break; | ||
245 | case 1: | ||
246 | val = CHAN_MIX_BOTH; | ||
247 | break; | ||
248 | case 2: | ||
249 | val = CHAN_MIX_SWAP; | ||
250 | break; | ||
251 | } | ||
252 | |||
253 | snd_soc_write(codec, CS42L51_PCM_MIXER, val); | ||
254 | |||
255 | return 1; | ||
256 | } | ||
257 | |||
258 | static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0); | ||
259 | static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); | ||
260 | /* This is a lie. after -102 db, it stays at -102 */ | ||
261 | /* maybe a range would be better */ | ||
262 | static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0); | ||
263 | |||
264 | static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); | ||
265 | static const char *chan_mix[] = { | ||
266 | "L R", | ||
267 | "L+R", | ||
268 | "R L", | ||
269 | }; | ||
270 | |||
271 | static const struct soc_enum cs42l51_chan_mix = | ||
272 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix); | ||
273 | |||
274 | static const struct snd_kcontrol_new cs42l51_snd_controls[] = { | ||
275 | SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", | ||
276 | CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, | ||
277 | 7, 0xffffff99, 0x18, adc_pcm_tlv), | ||
278 | SOC_DOUBLE_R("PCM Playback Switch", | ||
279 | CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1), | ||
280 | SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", | ||
281 | CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL, | ||
282 | 8, 0xffffff19, 0x18, aout_tlv), | ||
283 | SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", | ||
284 | CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, | ||
285 | 7, 0xffffff99, 0x18, adc_pcm_tlv), | ||
286 | SOC_DOUBLE_R("ADC Mixer Switch", | ||
287 | CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1), | ||
288 | SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0), | ||
289 | SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0), | ||
290 | SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0), | ||
291 | SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0), | ||
292 | SOC_DOUBLE_TLV("Mic Boost Volume", | ||
293 | CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), | ||
294 | SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv), | ||
295 | SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv), | ||
296 | SOC_ENUM_EXT("PCM channel mixer", | ||
297 | cs42l51_chan_mix, | ||
298 | cs42l51_get_chan_mix, cs42l51_set_chan_mix), | ||
299 | }; | ||
300 | |||
301 | /* | ||
302 | * to power down, one must: | ||
303 | * 1.) Enable the PDN bit | ||
304 | * 2.) enable power-down for the select channels | ||
305 | * 3.) disable the PDN bit. | ||
306 | */ | ||
307 | static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w, | ||
308 | struct snd_kcontrol *kcontrol, int event) | ||
309 | { | ||
310 | unsigned long value; | ||
311 | |||
312 | value = snd_soc_read(w->codec, CS42L51_POWER_CTL1); | ||
313 | value &= ~CS42L51_POWER_CTL1_PDN; | ||
314 | |||
315 | switch (event) { | ||
316 | case SND_SOC_DAPM_PRE_PMD: | ||
317 | value |= CS42L51_POWER_CTL1_PDN; | ||
318 | break; | ||
319 | default: | ||
320 | case SND_SOC_DAPM_POST_PMD: | ||
321 | break; | ||
322 | } | ||
323 | snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1, | ||
324 | CS42L51_POWER_CTL1_PDN, value); | ||
325 | |||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | static const char *cs42l51_dac_names[] = {"Direct PCM", | ||
330 | "DSP PCM", "ADC"}; | ||
331 | static const struct soc_enum cs42l51_dac_mux_enum = | ||
332 | SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names); | ||
333 | static const struct snd_kcontrol_new cs42l51_dac_mux_controls = | ||
334 | SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum); | ||
335 | |||
336 | static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left", | ||
337 | "MIC Left", "MIC+preamp Left"}; | ||
338 | static const struct soc_enum cs42l51_adcl_mux_enum = | ||
339 | SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names); | ||
340 | static const struct snd_kcontrol_new cs42l51_adcl_mux_controls = | ||
341 | SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum); | ||
342 | |||
343 | static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right", | ||
344 | "MIC Right", "MIC+preamp Right"}; | ||
345 | static const struct soc_enum cs42l51_adcr_mux_enum = | ||
346 | SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names); | ||
347 | static const struct snd_kcontrol_new cs42l51_adcr_mux_controls = | ||
348 | SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum); | ||
349 | |||
350 | static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { | ||
351 | SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), | ||
352 | SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0, | ||
353 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
354 | SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, | ||
355 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
356 | SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture", | ||
357 | CS42L51_POWER_CTL1, 1, 1, | ||
358 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
359 | SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture", | ||
360 | CS42L51_POWER_CTL1, 2, 1, | ||
361 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
362 | SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback", | ||
363 | CS42L51_POWER_CTL1, 5, 1, | ||
364 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
365 | SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback", | ||
366 | CS42L51_POWER_CTL1, 6, 1, | ||
367 | cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), | ||
368 | |||
369 | /* analog/mic */ | ||
370 | SND_SOC_DAPM_INPUT("AIN1L"), | ||
371 | SND_SOC_DAPM_INPUT("AIN1R"), | ||
372 | SND_SOC_DAPM_INPUT("AIN2L"), | ||
373 | SND_SOC_DAPM_INPUT("AIN2R"), | ||
374 | SND_SOC_DAPM_INPUT("MICL"), | ||
375 | SND_SOC_DAPM_INPUT("MICR"), | ||
376 | |||
377 | SND_SOC_DAPM_MIXER("Mic Preamp Left", | ||
378 | CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0), | ||
379 | SND_SOC_DAPM_MIXER("Mic Preamp Right", | ||
380 | CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0), | ||
381 | |||
382 | /* HP */ | ||
383 | SND_SOC_DAPM_OUTPUT("HPL"), | ||
384 | SND_SOC_DAPM_OUTPUT("HPR"), | ||
385 | |||
386 | /* mux */ | ||
387 | SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, | ||
388 | &cs42l51_dac_mux_controls), | ||
389 | SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0, | ||
390 | &cs42l51_adcl_mux_controls), | ||
391 | SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0, | ||
392 | &cs42l51_adcr_mux_controls), | ||
393 | }; | ||
394 | |||
395 | static const struct snd_soc_dapm_route cs42l51_routes[] = { | ||
396 | {"HPL", NULL, "Left DAC"}, | ||
397 | {"HPR", NULL, "Right DAC"}, | ||
398 | |||
399 | {"Left ADC", NULL, "Left PGA"}, | ||
400 | {"Right ADC", NULL, "Right PGA"}, | ||
401 | |||
402 | {"Mic Preamp Left", NULL, "MICL"}, | ||
403 | {"Mic Preamp Right", NULL, "MICR"}, | ||
404 | |||
405 | {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" }, | ||
406 | {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" }, | ||
407 | {"PGA-ADC Mux Left", "MIC Left", "MICL" }, | ||
408 | {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" }, | ||
409 | {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" }, | ||
410 | {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" }, | ||
411 | {"PGA-ADC Mux Right", "MIC Right", "MICR" }, | ||
412 | {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" }, | ||
413 | |||
414 | {"Left PGA", NULL, "PGA-ADC Mux Left"}, | ||
415 | {"Right PGA", NULL, "PGA-ADC Mux Right"}, | ||
416 | }; | ||
417 | |||
418 | static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai, | ||
419 | unsigned int format) | ||
420 | { | ||
421 | struct snd_soc_codec *codec = codec_dai->codec; | ||
422 | struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); | ||
423 | int ret = 0; | ||
424 | |||
425 | switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
426 | case SND_SOC_DAIFMT_I2S: | ||
427 | case SND_SOC_DAIFMT_LEFT_J: | ||
428 | case SND_SOC_DAIFMT_RIGHT_J: | ||
429 | cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK; | ||
430 | break; | ||
431 | default: | ||
432 | dev_err(codec->dev, "invalid DAI format\n"); | ||
433 | ret = -EINVAL; | ||
434 | } | ||
435 | |||
436 | switch (format & SND_SOC_DAIFMT_MASTER_MASK) { | ||
437 | case SND_SOC_DAIFMT_CBM_CFM: | ||
438 | cs42l51->func = MODE_MASTER; | ||
439 | break; | ||
440 | case SND_SOC_DAIFMT_CBS_CFS: | ||
441 | cs42l51->func = MODE_SLAVE_AUTO; | ||
442 | break; | ||
443 | default: | ||
444 | ret = -EINVAL; | ||
445 | break; | ||
446 | } | ||
447 | |||
448 | return ret; | ||
449 | } | ||
450 | |||
451 | struct cs42l51_ratios { | ||
452 | unsigned int ratio; | ||
453 | unsigned char speed_mode; | ||
454 | unsigned char mclk; | ||
455 | }; | ||
456 | |||
457 | static struct cs42l51_ratios slave_ratios[] = { | ||
458 | { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 }, | ||
459 | { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, | ||
460 | { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 }, | ||
461 | { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 }, | ||
462 | { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, | ||
463 | { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 }, | ||
464 | { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 }, | ||
465 | { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, | ||
466 | { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 }, | ||
467 | { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, | ||
468 | { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 }, | ||
469 | }; | ||
470 | |||
471 | static struct cs42l51_ratios slave_auto_ratios[] = { | ||
472 | { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, | ||
473 | { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 }, | ||
474 | { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, | ||
475 | { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 }, | ||
476 | { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, | ||
477 | { 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 }, | ||
478 | { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, | ||
479 | { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 }, | ||
480 | }; | ||
481 | |||
482 | static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai, | ||
483 | int clk_id, unsigned int freq, int dir) | ||
484 | { | ||
485 | struct snd_soc_codec *codec = codec_dai->codec; | ||
486 | struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); | ||
487 | struct cs42l51_ratios *ratios = NULL; | ||
488 | int nr_ratios = 0; | ||
489 | unsigned int rates = 0; | ||
490 | unsigned int rate_min = -1; | ||
491 | unsigned int rate_max = 0; | ||
492 | int i; | ||
493 | |||
494 | cs42l51->mclk = freq; | ||
495 | |||
496 | switch (cs42l51->func) { | ||
497 | case MODE_MASTER: | ||
498 | return -EINVAL; | ||
499 | case MODE_SLAVE: | ||
500 | ratios = slave_ratios; | ||
501 | nr_ratios = ARRAY_SIZE(slave_ratios); | ||
502 | break; | ||
503 | case MODE_SLAVE_AUTO: | ||
504 | ratios = slave_auto_ratios; | ||
505 | nr_ratios = ARRAY_SIZE(slave_auto_ratios); | ||
506 | break; | ||
507 | } | ||
508 | |||
509 | for (i = 0; i < nr_ratios; i++) { | ||
510 | unsigned int rate = freq / ratios[i].ratio; | ||
511 | rates |= snd_pcm_rate_to_rate_bit(rate); | ||
512 | if (rate < rate_min) | ||
513 | rate_min = rate; | ||
514 | if (rate > rate_max) | ||
515 | rate_max = rate; | ||
516 | } | ||
517 | rates &= ~SNDRV_PCM_RATE_KNOT; | ||
518 | |||
519 | if (!rates) { | ||
520 | dev_err(codec->dev, "could not find a valid sample rate\n"); | ||
521 | return -EINVAL; | ||
522 | } | ||
523 | |||
524 | codec_dai->playback.rates = rates; | ||
525 | codec_dai->playback.rate_min = rate_min; | ||
526 | codec_dai->playback.rate_max = rate_max; | ||
527 | |||
528 | codec_dai->capture.rates = rates; | ||
529 | codec_dai->capture.rate_min = rate_min; | ||
530 | codec_dai->capture.rate_max = rate_max; | ||
531 | |||
532 | return 0; | ||
533 | } | ||
534 | |||
535 | static int cs42l51_hw_params(struct snd_pcm_substream *substream, | ||
536 | struct snd_pcm_hw_params *params, | ||
537 | struct snd_soc_dai *dai) | ||
538 | { | ||
539 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
540 | struct snd_soc_device *socdev = rtd->socdev; | ||
541 | struct snd_soc_codec *codec = socdev->card->codec; | ||
542 | struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); | ||
543 | int ret; | ||
544 | unsigned int i; | ||
545 | unsigned int rate; | ||
546 | unsigned int ratio; | ||
547 | struct cs42l51_ratios *ratios = NULL; | ||
548 | int nr_ratios = 0; | ||
549 | int intf_ctl, power_ctl, fmt; | ||
550 | |||
551 | switch (cs42l51->func) { | ||
552 | case MODE_MASTER: | ||
553 | return -EINVAL; | ||
554 | case MODE_SLAVE: | ||
555 | ratios = slave_ratios; | ||
556 | nr_ratios = ARRAY_SIZE(slave_ratios); | ||
557 | break; | ||
558 | case MODE_SLAVE_AUTO: | ||
559 | ratios = slave_auto_ratios; | ||
560 | nr_ratios = ARRAY_SIZE(slave_auto_ratios); | ||
561 | break; | ||
562 | } | ||
563 | |||
564 | /* Figure out which MCLK/LRCK ratio to use */ | ||
565 | rate = params_rate(params); /* Sampling rate, in Hz */ | ||
566 | ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */ | ||
567 | for (i = 0; i < nr_ratios; i++) { | ||
568 | if (ratios[i].ratio == ratio) | ||
569 | break; | ||
570 | } | ||
571 | |||
572 | if (i == nr_ratios) { | ||
573 | /* We did not find a matching ratio */ | ||
574 | dev_err(codec->dev, "could not find matching ratio\n"); | ||
575 | return -EINVAL; | ||
576 | } | ||
577 | |||
578 | intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL); | ||
579 | power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL); | ||
580 | |||
581 | intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S | ||
582 | | CS42L51_INTF_CTL_DAC_FORMAT(7)); | ||
583 | power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3) | ||
584 | | CS42L51_MIC_POWER_CTL_MCLK_DIV2); | ||
585 | |||
586 | switch (cs42l51->func) { | ||
587 | case MODE_MASTER: | ||
588 | intf_ctl |= CS42L51_INTF_CTL_MASTER; | ||
589 | power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); | ||
590 | break; | ||
591 | case MODE_SLAVE: | ||
592 | power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); | ||
593 | break; | ||
594 | case MODE_SLAVE_AUTO: | ||
595 | power_ctl |= CS42L51_MIC_POWER_CTL_AUTO; | ||
596 | break; | ||
597 | } | ||
598 | |||
599 | switch (cs42l51->audio_mode) { | ||
600 | case SND_SOC_DAIFMT_I2S: | ||
601 | intf_ctl |= CS42L51_INTF_CTL_ADC_I2S; | ||
602 | intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S); | ||
603 | break; | ||
604 | case SND_SOC_DAIFMT_LEFT_J: | ||
605 | intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24); | ||
606 | break; | ||
607 | case SND_SOC_DAIFMT_RIGHT_J: | ||
608 | switch (params_format(params)) { | ||
609 | case SNDRV_PCM_FORMAT_S16_LE: | ||
610 | case SNDRV_PCM_FORMAT_S16_BE: | ||
611 | fmt = CS42L51_DAC_DIF_RJ16; | ||
612 | break; | ||
613 | case SNDRV_PCM_FORMAT_S18_3LE: | ||
614 | case SNDRV_PCM_FORMAT_S18_3BE: | ||
615 | fmt = CS42L51_DAC_DIF_RJ18; | ||
616 | break; | ||
617 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
618 | case SNDRV_PCM_FORMAT_S20_3BE: | ||
619 | fmt = CS42L51_DAC_DIF_RJ20; | ||
620 | break; | ||
621 | case SNDRV_PCM_FORMAT_S24_LE: | ||
622 | case SNDRV_PCM_FORMAT_S24_BE: | ||
623 | fmt = CS42L51_DAC_DIF_RJ24; | ||
624 | break; | ||
625 | default: | ||
626 | dev_err(codec->dev, "unknown format\n"); | ||
627 | return -EINVAL; | ||
628 | } | ||
629 | intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt); | ||
630 | break; | ||
631 | default: | ||
632 | dev_err(codec->dev, "unknown format\n"); | ||
633 | return -EINVAL; | ||
634 | } | ||
635 | |||
636 | if (ratios[i].mclk) | ||
637 | power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2; | ||
638 | |||
639 | ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl); | ||
640 | if (ret < 0) | ||
641 | return ret; | ||
642 | |||
643 | ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl); | ||
644 | if (ret < 0) | ||
645 | return ret; | ||
646 | |||
647 | return 0; | ||
648 | } | ||
649 | |||
650 | static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute) | ||
651 | { | ||
652 | struct snd_soc_codec *codec = dai->codec; | ||
653 | int reg; | ||
654 | int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE; | ||
655 | |||
656 | reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL); | ||
657 | |||
658 | if (mute) | ||
659 | reg |= mask; | ||
660 | else | ||
661 | reg &= ~mask; | ||
662 | |||
663 | return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg); | ||
664 | } | ||
665 | |||
666 | static struct snd_soc_dai_ops cs42l51_dai_ops = { | ||
667 | .hw_params = cs42l51_hw_params, | ||
668 | .set_sysclk = cs42l51_set_dai_sysclk, | ||
669 | .set_fmt = cs42l51_set_dai_fmt, | ||
670 | .digital_mute = cs42l51_dai_mute, | ||
671 | }; | ||
672 | |||
673 | struct snd_soc_dai cs42l51_dai = { | ||
674 | .name = "CS42L51 HiFi", | ||
675 | .playback = { | ||
676 | .stream_name = "Playback", | ||
677 | .channels_min = 1, | ||
678 | .channels_max = 2, | ||
679 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
680 | .formats = CS42L51_FORMATS, | ||
681 | }, | ||
682 | .capture = { | ||
683 | .stream_name = "Capture", | ||
684 | .channels_min = 1, | ||
685 | .channels_max = 2, | ||
686 | .rates = SNDRV_PCM_RATE_8000_96000, | ||
687 | .formats = CS42L51_FORMATS, | ||
688 | }, | ||
689 | .ops = &cs42l51_dai_ops, | ||
690 | }; | ||
691 | EXPORT_SYMBOL_GPL(cs42l51_dai); | ||
692 | |||
693 | |||
694 | static int cs42l51_probe(struct platform_device *pdev) | ||
695 | { | ||
696 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
697 | struct snd_soc_codec *codec; | ||
698 | int ret = 0; | ||
699 | |||
700 | if (!cs42l51_codec) { | ||
701 | dev_err(&pdev->dev, "CS42L51 codec not yet registered\n"); | ||
702 | return -EINVAL; | ||
703 | } | ||
704 | |||
705 | socdev->card->codec = cs42l51_codec; | ||
706 | codec = socdev->card->codec; | ||
707 | |||
708 | /* Register PCMs */ | ||
709 | ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); | ||
710 | if (ret < 0) { | ||
711 | dev_err(&pdev->dev, "failed to create PCMs\n"); | ||
712 | return ret; | ||
713 | } | ||
714 | |||
715 | snd_soc_add_controls(codec, cs42l51_snd_controls, | ||
716 | ARRAY_SIZE(cs42l51_snd_controls)); | ||
717 | snd_soc_dapm_new_controls(codec, cs42l51_dapm_widgets, | ||
718 | ARRAY_SIZE(cs42l51_dapm_widgets)); | ||
719 | snd_soc_dapm_add_routes(codec, cs42l51_routes, | ||
720 | ARRAY_SIZE(cs42l51_routes)); | ||
721 | |||
722 | return 0; | ||
723 | } | ||
724 | |||
725 | |||
726 | static int cs42l51_remove(struct platform_device *pdev) | ||
727 | { | ||
728 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
729 | |||
730 | snd_soc_free_pcms(socdev); | ||
731 | snd_soc_dapm_free(socdev); | ||
732 | |||
733 | return 0; | ||
734 | } | ||
735 | |||
736 | struct snd_soc_codec_device soc_codec_device_cs42l51 = { | ||
737 | .probe = cs42l51_probe, | ||
738 | .remove = cs42l51_remove | ||
739 | }; | ||
740 | EXPORT_SYMBOL_GPL(soc_codec_device_cs42l51); | ||
741 | |||
742 | static int __init cs42l51_init(void) | ||
743 | { | ||
744 | int ret; | ||
745 | |||
746 | ret = i2c_add_driver(&cs42l51_i2c_driver); | ||
747 | if (ret != 0) { | ||
748 | printk(KERN_ERR "%s: can't add i2c driver\n", __func__); | ||
749 | return ret; | ||
750 | } | ||
751 | return 0; | ||
752 | } | ||
753 | module_init(cs42l51_init); | ||
754 | |||
755 | static void __exit cs42l51_exit(void) | ||
756 | { | ||
757 | i2c_del_driver(&cs42l51_i2c_driver); | ||
758 | } | ||
759 | module_exit(cs42l51_exit); | ||
760 | |||
761 | MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>"); | ||
762 | MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver"); | ||
763 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/codecs/cs42l51.h b/sound/soc/codecs/cs42l51.h new file mode 100644 index 000000000000..8f0bd9786ad2 --- /dev/null +++ b/sound/soc/codecs/cs42l51.h | |||
@@ -0,0 +1,163 @@ | |||
1 | /* | ||
2 | * cs42l51.h | ||
3 | * | ||
4 | * ASoC Driver for Cirrus Logic CS42L51 codecs | ||
5 | * | ||
6 | * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | */ | ||
18 | #ifndef _CS42L51_H | ||
19 | #define _CS42L51_H | ||
20 | |||
21 | #define CS42L51_CHIP_ID 0x1B | ||
22 | #define CS42L51_CHIP_REV_A 0x00 | ||
23 | #define CS42L51_CHIP_REV_B 0x01 | ||
24 | |||
25 | #define CS42L51_CHIP_REV_ID 0x01 | ||
26 | #define CS42L51_MK_CHIP_REV(a, b) ((a)<<3|(b)) | ||
27 | |||
28 | #define CS42L51_POWER_CTL1 0x02 | ||
29 | #define CS42L51_POWER_CTL1_PDN_DACB (1<<6) | ||
30 | #define CS42L51_POWER_CTL1_PDN_DACA (1<<5) | ||
31 | #define CS42L51_POWER_CTL1_PDN_PGAB (1<<4) | ||
32 | #define CS42L51_POWER_CTL1_PDN_PGAA (1<<3) | ||
33 | #define CS42L51_POWER_CTL1_PDN_ADCB (1<<2) | ||
34 | #define CS42L51_POWER_CTL1_PDN_ADCA (1<<1) | ||
35 | #define CS42L51_POWER_CTL1_PDN (1<<0) | ||
36 | |||
37 | #define CS42L51_MIC_POWER_CTL 0x03 | ||
38 | #define CS42L51_MIC_POWER_CTL_AUTO (1<<7) | ||
39 | #define CS42L51_MIC_POWER_CTL_SPEED(x) (((x)&3)<<5) | ||
40 | #define CS42L51_QSM_MODE 3 | ||
41 | #define CS42L51_HSM_MODE 2 | ||
42 | #define CS42L51_SSM_MODE 1 | ||
43 | #define CS42L51_DSM_MODE 0 | ||
44 | #define CS42L51_MIC_POWER_CTL_3ST_SP (1<<4) | ||
45 | #define CS42L51_MIC_POWER_CTL_PDN_MICB (1<<3) | ||
46 | #define CS42L51_MIC_POWER_CTL_PDN_MICA (1<<2) | ||
47 | #define CS42L51_MIC_POWER_CTL_PDN_BIAS (1<<1) | ||
48 | #define CS42L51_MIC_POWER_CTL_MCLK_DIV2 (1<<0) | ||
49 | |||
50 | #define CS42L51_INTF_CTL 0x04 | ||
51 | #define CS42L51_INTF_CTL_LOOPBACK (1<<7) | ||
52 | #define CS42L51_INTF_CTL_MASTER (1<<6) | ||
53 | #define CS42L51_INTF_CTL_DAC_FORMAT(x) (((x)&7)<<3) | ||
54 | #define CS42L51_DAC_DIF_LJ24 0x00 | ||
55 | #define CS42L51_DAC_DIF_I2S 0x01 | ||
56 | #define CS42L51_DAC_DIF_RJ24 0x02 | ||
57 | #define CS42L51_DAC_DIF_RJ20 0x03 | ||
58 | #define CS42L51_DAC_DIF_RJ18 0x04 | ||
59 | #define CS42L51_DAC_DIF_RJ16 0x05 | ||
60 | #define CS42L51_INTF_CTL_ADC_I2S (1<<2) | ||
61 | #define CS42L51_INTF_CTL_DIGMIX (1<<1) | ||
62 | #define CS42L51_INTF_CTL_MICMIX (1<<0) | ||
63 | |||
64 | #define CS42L51_MIC_CTL 0x05 | ||
65 | #define CS42L51_MIC_CTL_ADC_SNGVOL (1<<7) | ||
66 | #define CS42L51_MIC_CTL_ADCD_DBOOST (1<<6) | ||
67 | #define CS42L51_MIC_CTL_ADCA_DBOOST (1<<5) | ||
68 | #define CS42L51_MIC_CTL_MICBIAS_SEL (1<<4) | ||
69 | #define CS42L51_MIC_CTL_MICBIAS_LVL(x) (((x)&3)<<2) | ||
70 | #define CS42L51_MIC_CTL_MICB_BOOST (1<<1) | ||
71 | #define CS42L51_MIC_CTL_MICA_BOOST (1<<0) | ||
72 | |||
73 | #define CS42L51_ADC_CTL 0x06 | ||
74 | #define CS42L51_ADC_CTL_ADCB_HPFEN (1<<7) | ||
75 | #define CS42L51_ADC_CTL_ADCB_HPFRZ (1<<6) | ||
76 | #define CS42L51_ADC_CTL_ADCA_HPFEN (1<<5) | ||
77 | #define CS42L51_ADC_CTL_ADCA_HPFRZ (1<<4) | ||
78 | #define CS42L51_ADC_CTL_SOFTB (1<<3) | ||
79 | #define CS42L51_ADC_CTL_ZCROSSB (1<<2) | ||
80 | #define CS42L51_ADC_CTL_SOFTA (1<<1) | ||
81 | #define CS42L51_ADC_CTL_ZCROSSA (1<<0) | ||
82 | |||
83 | #define CS42L51_ADC_INPUT 0x07 | ||
84 | #define CS42L51_ADC_INPUT_AINB_MUX(x) (((x)&3)<<6) | ||
85 | #define CS42L51_ADC_INPUT_AINA_MUX(x) (((x)&3)<<4) | ||
86 | #define CS42L51_ADC_INPUT_INV_ADCB (1<<3) | ||
87 | #define CS42L51_ADC_INPUT_INV_ADCA (1<<2) | ||
88 | #define CS42L51_ADC_INPUT_ADCB_MUTE (1<<1) | ||
89 | #define CS42L51_ADC_INPUT_ADCA_MUTE (1<<0) | ||
90 | |||
91 | #define CS42L51_DAC_OUT_CTL 0x08 | ||
92 | #define CS42L51_DAC_OUT_CTL_HP_GAIN(x) (((x)&7)<<5) | ||
93 | #define CS42L51_DAC_OUT_CTL_DAC_SNGVOL (1<<4) | ||
94 | #define CS42L51_DAC_OUT_CTL_INV_PCMB (1<<3) | ||
95 | #define CS42L51_DAC_OUT_CTL_INV_PCMA (1<<2) | ||
96 | #define CS42L51_DAC_OUT_CTL_DACB_MUTE (1<<1) | ||
97 | #define CS42L51_DAC_OUT_CTL_DACA_MUTE (1<<0) | ||
98 | |||
99 | #define CS42L51_DAC_CTL 0x09 | ||
100 | #define CS42L51_DAC_CTL_DATA_SEL(x) (((x)&3)<<6) | ||
101 | #define CS42L51_DAC_CTL_FREEZE (1<<5) | ||
102 | #define CS42L51_DAC_CTL_DEEMPH (1<<3) | ||
103 | #define CS42L51_DAC_CTL_AMUTE (1<<2) | ||
104 | #define CS42L51_DAC_CTL_DACSZ(x) (((x)&3)<<0) | ||
105 | |||
106 | #define CS42L51_ALC_PGA_CTL 0x0A | ||
107 | #define CS42L51_ALC_PGB_CTL 0x0B | ||
108 | #define CS42L51_ALC_PGX_ALCX_SRDIS (1<<7) | ||
109 | #define CS42L51_ALC_PGX_ALCX_ZCDIS (1<<6) | ||
110 | #define CS42L51_ALC_PGX_PGX_VOL(x) (((x)&0x1f)<<0) | ||
111 | |||
112 | #define CS42L51_ADCA_ATT 0x0C | ||
113 | #define CS42L51_ADCB_ATT 0x0D | ||
114 | |||
115 | #define CS42L51_ADCA_VOL 0x0E | ||
116 | #define CS42L51_ADCB_VOL 0x0F | ||
117 | #define CS42L51_PCMA_VOL 0x10 | ||
118 | #define CS42L51_PCMB_VOL 0x11 | ||
119 | #define CS42L51_MIX_MUTE_ADCMIX (1<<7) | ||
120 | #define CS42L51_MIX_VOLUME(x) (((x)&0x7f)<<0) | ||
121 | |||
122 | #define CS42L51_BEEP_FREQ 0x12 | ||
123 | #define CS42L51_BEEP_VOL 0x13 | ||
124 | #define CS42L51_BEEP_CONF 0x14 | ||
125 | |||
126 | #define CS42L51_TONE_CTL 0x15 | ||
127 | #define CS42L51_TONE_CTL_TREB(x) (((x)&0xf)<<4) | ||
128 | #define CS42L51_TONE_CTL_BASS(x) (((x)&0xf)<<0) | ||
129 | |||
130 | #define CS42L51_AOUTA_VOL 0x16 | ||
131 | #define CS42L51_AOUTB_VOL 0x17 | ||
132 | #define CS42L51_PCM_MIXER 0x18 | ||
133 | #define CS42L51_LIMIT_THRES_DIS 0x19 | ||
134 | #define CS42L51_LIMIT_REL 0x1A | ||
135 | #define CS42L51_LIMIT_ATT 0x1B | ||
136 | #define CS42L51_ALC_EN 0x1C | ||
137 | #define CS42L51_ALC_REL 0x1D | ||
138 | #define CS42L51_ALC_THRES 0x1E | ||
139 | #define CS42L51_NOISE_CONF 0x1F | ||
140 | |||
141 | #define CS42L51_STATUS 0x20 | ||
142 | #define CS42L51_STATUS_SP_CLKERR (1<<6) | ||
143 | #define CS42L51_STATUS_SPEA_OVFL (1<<5) | ||
144 | #define CS42L51_STATUS_SPEB_OVFL (1<<4) | ||
145 | #define CS42L51_STATUS_PCMA_OVFL (1<<3) | ||
146 | #define CS42L51_STATUS_PCMB_OVFL (1<<2) | ||
147 | #define CS42L51_STATUS_ADCA_OVFL (1<<1) | ||
148 | #define CS42L51_STATUS_ADCB_OVFL (1<<0) | ||
149 | |||
150 | #define CS42L51_CHARGE_FREQ 0x21 | ||
151 | |||
152 | #define CS42L51_FIRSTREG 0x01 | ||
153 | /* | ||
154 | * Hack: with register 0x21, it makes 33 registers. Looks like someone in the | ||
155 | * i2c layer doesn't like i2c smbus block read of 33 regs. Workaround by using | ||
156 | * 32 regs | ||
157 | */ | ||
158 | #define CS42L51_LASTREG 0x20 | ||
159 | #define CS42L51_NUMREGS (CS42L51_LASTREG - CS42L51_FIRSTREG + 1) | ||
160 | |||
161 | extern struct snd_soc_dai cs42l51_dai; | ||
162 | extern struct snd_soc_codec_device soc_codec_device_cs42l51; | ||
163 | #endif | ||
diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c index a63191141052..9119836051a4 100644 --- a/sound/soc/codecs/spdif_transciever.c +++ b/sound/soc/codecs/spdif_transciever.c | |||
@@ -16,8 +16,10 @@ | |||
16 | 16 | ||
17 | #include <linux/module.h> | 17 | #include <linux/module.h> |
18 | #include <linux/moduleparam.h> | 18 | #include <linux/moduleparam.h> |
19 | #include <linux/slab.h> | ||
19 | #include <sound/soc.h> | 20 | #include <sound/soc.h> |
20 | #include <sound/pcm.h> | 21 | #include <sound/pcm.h> |
22 | #include <sound/initval.h> | ||
21 | 23 | ||
22 | #include "spdif_transciever.h" | 24 | #include "spdif_transciever.h" |
23 | 25 | ||
@@ -26,6 +28,48 @@ MODULE_LICENSE("GPL"); | |||
26 | #define STUB_RATES SNDRV_PCM_RATE_8000_96000 | 28 | #define STUB_RATES SNDRV_PCM_RATE_8000_96000 |
27 | #define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE | 29 | #define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE |
28 | 30 | ||
31 | static struct snd_soc_codec *spdif_dit_codec; | ||
32 | |||
33 | static int spdif_dit_codec_probe(struct platform_device *pdev) | ||
34 | { | ||
35 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
36 | struct snd_soc_codec *codec; | ||
37 | int ret; | ||
38 | |||
39 | if (spdif_dit_codec == NULL) { | ||
40 | dev_err(&pdev->dev, "Codec device not registered\n"); | ||
41 | return -ENODEV; | ||
42 | } | ||
43 | |||
44 | socdev->card->codec = spdif_dit_codec; | ||
45 | codec = spdif_dit_codec; | ||
46 | |||
47 | ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); | ||
48 | if (ret < 0) { | ||
49 | dev_err(codec->dev, "failed to create pcms: %d\n", ret); | ||
50 | goto err_create_pcms; | ||
51 | } | ||
52 | |||
53 | return 0; | ||
54 | |||
55 | err_create_pcms: | ||
56 | return ret; | ||
57 | } | ||
58 | |||
59 | static int spdif_dit_codec_remove(struct platform_device *pdev) | ||
60 | { | ||
61 | struct snd_soc_device *socdev = platform_get_drvdata(pdev); | ||
62 | |||
63 | snd_soc_free_pcms(socdev); | ||
64 | |||
65 | return 0; | ||
66 | } | ||
67 | |||
68 | struct snd_soc_codec_device soc_codec_dev_spdif_dit = { | ||
69 | .probe = spdif_dit_codec_probe, | ||
70 | .remove = spdif_dit_codec_remove, | ||
71 | }; EXPORT_SYMBOL_GPL(soc_codec_dev_spdif_dit); | ||
72 | |||
29 | struct snd_soc_dai dit_stub_dai = { | 73 | struct snd_soc_dai dit_stub_dai = { |
30 | .name = "DIT", | 74 | .name = "DIT", |
31 | .playback = { | 75 | .playback = { |
@@ -40,13 +84,61 @@ EXPORT_SYMBOL_GPL(dit_stub_dai); | |||
40 | 84 | ||
41 | static int spdif_dit_probe(struct platform_device *pdev) | 85 | static int spdif_dit_probe(struct platform_device *pdev) |
42 | { | 86 | { |
87 | struct snd_soc_codec *codec; | ||
88 | int ret; | ||
89 | |||
90 | if (spdif_dit_codec) { | ||
91 | dev_err(&pdev->dev, "Another Codec is registered\n"); | ||
92 | ret = -EINVAL; | ||
93 | goto err_reg_codec; | ||
94 | } | ||
95 | |||
96 | codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); | ||
97 | if (codec == NULL) | ||
98 | return -ENOMEM; | ||
99 | |||
100 | codec->dev = &pdev->dev; | ||
101 | |||
102 | mutex_init(&codec->mutex); | ||
103 | |||
104 | INIT_LIST_HEAD(&codec->dapm_widgets); | ||
105 | INIT_LIST_HEAD(&codec->dapm_paths); | ||
106 | |||
107 | codec->name = "spdif-dit"; | ||
108 | codec->owner = THIS_MODULE; | ||
109 | codec->dai = &dit_stub_dai; | ||
110 | codec->num_dai = 1; | ||
111 | |||
112 | spdif_dit_codec = codec; | ||
113 | |||
114 | ret = snd_soc_register_codec(codec); | ||
115 | if (ret < 0) { | ||
116 | dev_err(codec->dev, "Failed to register codec: %d\n", ret); | ||
117 | goto err_reg_codec; | ||
118 | } | ||
119 | |||
43 | dit_stub_dai.dev = &pdev->dev; | 120 | dit_stub_dai.dev = &pdev->dev; |
44 | return snd_soc_register_dai(&dit_stub_dai); | 121 | ret = snd_soc_register_dai(&dit_stub_dai); |
122 | if (ret < 0) { | ||
123 | dev_err(codec->dev, "Failed to register dai: %d\n", ret); | ||
124 | goto err_reg_dai; | ||
125 | } | ||
126 | |||
127 | return 0; | ||
128 | |||
129 | err_reg_dai: | ||
130 | snd_soc_unregister_codec(codec); | ||
131 | err_reg_codec: | ||
132 | kfree(spdif_dit_codec); | ||
133 | return ret; | ||
45 | } | 134 | } |
46 | 135 | ||
47 | static int spdif_dit_remove(struct platform_device *pdev) | 136 | static int spdif_dit_remove(struct platform_device *pdev) |
48 | { | 137 | { |
49 | snd_soc_unregister_dai(&dit_stub_dai); | 138 | snd_soc_unregister_dai(&dit_stub_dai); |
139 | snd_soc_unregister_codec(spdif_dit_codec); | ||
140 | kfree(spdif_dit_codec); | ||
141 | spdif_dit_codec = NULL; | ||
50 | return 0; | 142 | return 0; |
51 | } | 143 | } |
52 | 144 | ||
diff --git a/sound/soc/codecs/spdif_transciever.h b/sound/soc/codecs/spdif_transciever.h index 296f2eb6c4ef..1e102124f546 100644 --- a/sound/soc/codecs/spdif_transciever.h +++ b/sound/soc/codecs/spdif_transciever.h | |||
@@ -12,6 +12,7 @@ | |||
12 | #ifndef CODEC_STUBS_H | 12 | #ifndef CODEC_STUBS_H |
13 | #define CODEC_STUBS_H | 13 | #define CODEC_STUBS_H |
14 | 14 | ||
15 | extern struct snd_soc_codec_device soc_codec_dev_spdif_dit; | ||
15 | extern struct snd_soc_dai dit_stub_dai; | 16 | extern struct snd_soc_dai dit_stub_dai; |
16 | 17 | ||
17 | #endif /* CODEC_STUBS_H */ | 18 | #endif /* CODEC_STUBS_H */ |
diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index c018772cc430..dd8d909788c1 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c | |||
@@ -30,8 +30,6 @@ | |||
30 | 30 | ||
31 | #include "wm8990.h" | 31 | #include "wm8990.h" |
32 | 32 | ||
33 | #define WM8990_VERSION "0.2" | ||
34 | |||
35 | /* codec private data */ | 33 | /* codec private data */ |
36 | struct wm8990_priv { | 34 | struct wm8990_priv { |
37 | unsigned int sysclk; | 35 | unsigned int sysclk; |
@@ -1511,8 +1509,6 @@ static int wm8990_probe(struct platform_device *pdev) | |||
1511 | struct wm8990_priv *wm8990; | 1509 | struct wm8990_priv *wm8990; |
1512 | int ret; | 1510 | int ret; |
1513 | 1511 | ||
1514 | pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION); | ||
1515 | |||
1516 | setup = socdev->codec_data; | 1512 | setup = socdev->codec_data; |
1517 | codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); | 1513 | codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); |
1518 | if (codec == NULL) | 1514 | if (codec == NULL) |
diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig new file mode 100644 index 000000000000..16ec2a2dba4d --- /dev/null +++ b/sound/soc/kirkwood/Kconfig | |||
@@ -0,0 +1,20 @@ | |||
1 | config SND_KIRKWOOD_SOC | ||
2 | tristate "SoC Audio for the Marvell Kirkwood chip" | ||
3 | depends on ARCH_KIRKWOOD | ||
4 | help | ||
5 | Say Y or M if you want to add support for codecs attached to | ||
6 | the Kirkwood I2S interface. You will also need to select the | ||
7 | audio interfaces to support below. | ||
8 | |||
9 | config SND_KIRKWOOD_SOC_I2S | ||
10 | tristate | ||
11 | |||
12 | config SND_KIRKWOOD_SOC_OPENRD | ||
13 | tristate "SoC Audio support for Kirkwood Openrd Client" | ||
14 | depends on SND_KIRKWOOD_SOC && MACH_OPENRD_CLIENT | ||
15 | select SND_KIRKWOOD_SOC_I2S | ||
16 | select SND_SOC_CS42L51 | ||
17 | help | ||
18 | Say Y if you want to add support for SoC audio on | ||
19 | Openrd Client. | ||
20 | |||
diff --git a/sound/soc/kirkwood/Makefile b/sound/soc/kirkwood/Makefile new file mode 100644 index 000000000000..33a16dcab5b5 --- /dev/null +++ b/sound/soc/kirkwood/Makefile | |||
@@ -0,0 +1,9 @@ | |||
1 | snd-soc-kirkwood-objs := kirkwood-dma.o | ||
2 | snd-soc-kirkwood-i2s-objs := kirkwood-i2s.o | ||
3 | |||
4 | obj-$(CONFIG_SND_KIRKWOOD_SOC) += snd-soc-kirkwood.o | ||
5 | obj-$(CONFIG_SND_KIRKWOOD_SOC_I2S) += snd-soc-kirkwood-i2s.o | ||
6 | |||
7 | snd-soc-openrd-objs := kirkwood-openrd.o | ||
8 | |||
9 | obj-$(CONFIG_SND_KIRKWOOD_SOC_OPENRD) += snd-soc-openrd.o | ||
diff --git a/sound/soc/kirkwood/kirkwood-dma.c b/sound/soc/kirkwood/kirkwood-dma.c new file mode 100644 index 000000000000..a30205be3e2b --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-dma.c | |||
@@ -0,0 +1,383 @@ | |||
1 | /* | ||
2 | * kirkwood-dma.c | ||
3 | * | ||
4 | * (c) 2010 Arnaud Patard <apatard@mandriva.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the | ||
8 | * Free Software Foundation; either version 2 of the License, or (at your | ||
9 | * option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/device.h> | ||
15 | #include <linux/io.h> | ||
16 | #include <linux/slab.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/dma-mapping.h> | ||
19 | #include <linux/mbus.h> | ||
20 | #include <sound/soc.h> | ||
21 | #include "kirkwood-dma.h" | ||
22 | #include "kirkwood.h" | ||
23 | |||
24 | #define KIRKWOOD_RATES \ | ||
25 | (SNDRV_PCM_RATE_44100 | \ | ||
26 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) | ||
27 | #define KIRKWOOD_FORMATS \ | ||
28 | (SNDRV_PCM_FMTBIT_S16_LE | \ | ||
29 | SNDRV_PCM_FMTBIT_S24_LE | \ | ||
30 | SNDRV_PCM_FMTBIT_S32_LE) | ||
31 | |||
32 | struct kirkwood_dma_priv { | ||
33 | struct snd_pcm_substream *play_stream; | ||
34 | struct snd_pcm_substream *rec_stream; | ||
35 | struct kirkwood_dma_data *data; | ||
36 | }; | ||
37 | |||
38 | static struct snd_pcm_hardware kirkwood_dma_snd_hw = { | ||
39 | .info = (SNDRV_PCM_INFO_INTERLEAVED | | ||
40 | SNDRV_PCM_INFO_MMAP | | ||
41 | SNDRV_PCM_INFO_MMAP_VALID | | ||
42 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
43 | SNDRV_PCM_INFO_PAUSE), | ||
44 | .formats = KIRKWOOD_FORMATS, | ||
45 | .rates = KIRKWOOD_RATES, | ||
46 | .rate_min = 44100, | ||
47 | .rate_max = 96000, | ||
48 | .channels_min = 1, | ||
49 | .channels_max = 2, | ||
50 | .buffer_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES * KIRKWOOD_SND_MAX_PERIODS, | ||
51 | .period_bytes_min = KIRKWOOD_SND_MIN_PERIOD_BYTES, | ||
52 | .period_bytes_max = KIRKWOOD_SND_MAX_PERIOD_BYTES, | ||
53 | .periods_min = KIRKWOOD_SND_MIN_PERIODS, | ||
54 | .periods_max = KIRKWOOD_SND_MAX_PERIODS, | ||
55 | .fifo_size = 0, | ||
56 | }; | ||
57 | |||
58 | static u64 kirkwood_dma_dmamask = 0xFFFFFFFFUL; | ||
59 | |||
60 | static irqreturn_t kirkwood_dma_irq(int irq, void *dev_id) | ||
61 | { | ||
62 | struct kirkwood_dma_priv *prdata = dev_id; | ||
63 | struct kirkwood_dma_data *priv = prdata->data; | ||
64 | unsigned long mask, status, cause; | ||
65 | |||
66 | mask = readl(priv->io + KIRKWOOD_INT_MASK); | ||
67 | status = readl(priv->io + KIRKWOOD_INT_CAUSE) & mask; | ||
68 | |||
69 | cause = readl(priv->io + KIRKWOOD_ERR_CAUSE); | ||
70 | if (unlikely(cause)) { | ||
71 | printk(KERN_WARNING "%s: got err interrupt 0x%lx\n", | ||
72 | __func__, cause); | ||
73 | writel(cause, priv->io + KIRKWOOD_ERR_CAUSE); | ||
74 | return IRQ_HANDLED; | ||
75 | } | ||
76 | |||
77 | /* we've enabled only bytes interrupts ... */ | ||
78 | if (status & ~(KIRKWOOD_INT_CAUSE_PLAY_BYTES | \ | ||
79 | KIRKWOOD_INT_CAUSE_REC_BYTES)) { | ||
80 | printk(KERN_WARNING "%s: unexpected interrupt %lx\n", | ||
81 | __func__, status); | ||
82 | return IRQ_NONE; | ||
83 | } | ||
84 | |||
85 | /* ack int */ | ||
86 | writel(status, priv->io + KIRKWOOD_INT_CAUSE); | ||
87 | |||
88 | if (status & KIRKWOOD_INT_CAUSE_PLAY_BYTES) | ||
89 | snd_pcm_period_elapsed(prdata->play_stream); | ||
90 | |||
91 | if (status & KIRKWOOD_INT_CAUSE_REC_BYTES) | ||
92 | snd_pcm_period_elapsed(prdata->rec_stream); | ||
93 | |||
94 | return IRQ_HANDLED; | ||
95 | } | ||
96 | |||
97 | static void kirkwood_dma_conf_mbus_windows(void __iomem *base, int win, | ||
98 | unsigned long dma, | ||
99 | struct mbus_dram_target_info *dram) | ||
100 | { | ||
101 | int i; | ||
102 | |||
103 | /* First disable and clear windows */ | ||
104 | writel(0, base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); | ||
105 | writel(0, base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); | ||
106 | |||
107 | /* try to find matching cs for current dma address */ | ||
108 | for (i = 0; i < dram->num_cs; i++) { | ||
109 | struct mbus_dram_window *cs = dram->cs + i; | ||
110 | if ((cs->base & 0xffff0000) < (dma & 0xffff0000)) { | ||
111 | writel(cs->base & 0xffff0000, | ||
112 | base + KIRKWOOD_AUDIO_WIN_BASE_REG(win)); | ||
113 | writel(((cs->size - 1) & 0xffff0000) | | ||
114 | (cs->mbus_attr << 8) | | ||
115 | (dram->mbus_dram_target_id << 4) | 1, | ||
116 | base + KIRKWOOD_AUDIO_WIN_CTRL_REG(win)); | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | |||
121 | static int kirkwood_dma_open(struct snd_pcm_substream *substream) | ||
122 | { | ||
123 | int err; | ||
124 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
125 | struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; | ||
126 | struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; | ||
127 | struct kirkwood_dma_data *priv; | ||
128 | struct kirkwood_dma_priv *prdata = cpu_dai->private_data; | ||
129 | unsigned long addr; | ||
130 | |||
131 | priv = snd_soc_dai_get_dma_data(cpu_dai, substream); | ||
132 | snd_soc_set_runtime_hwparams(substream, &kirkwood_dma_snd_hw); | ||
133 | |||
134 | /* Ensure that all constraints linked to dma burst are fullfilled */ | ||
135 | err = snd_pcm_hw_constraint_minmax(runtime, | ||
136 | SNDRV_PCM_HW_PARAM_BUFFER_BYTES, | ||
137 | priv->burst * 2, | ||
138 | KIRKWOOD_AUDIO_BUF_MAX-1); | ||
139 | if (err < 0) | ||
140 | return err; | ||
141 | |||
142 | err = snd_pcm_hw_constraint_step(runtime, 0, | ||
143 | SNDRV_PCM_HW_PARAM_BUFFER_BYTES, | ||
144 | priv->burst); | ||
145 | if (err < 0) | ||
146 | return err; | ||
147 | |||
148 | err = snd_pcm_hw_constraint_step(substream->runtime, 0, | ||
149 | SNDRV_PCM_HW_PARAM_PERIOD_BYTES, | ||
150 | priv->burst); | ||
151 | if (err < 0) | ||
152 | return err; | ||
153 | |||
154 | if (soc_runtime->dai->cpu_dai->private_data == NULL) { | ||
155 | prdata = kzalloc(sizeof(struct kirkwood_dma_priv), GFP_KERNEL); | ||
156 | if (prdata == NULL) | ||
157 | return -ENOMEM; | ||
158 | |||
159 | prdata->data = priv; | ||
160 | |||
161 | err = request_irq(priv->irq, kirkwood_dma_irq, IRQF_SHARED, | ||
162 | "kirkwood-i2s", prdata); | ||
163 | if (err) { | ||
164 | kfree(prdata); | ||
165 | return -EBUSY; | ||
166 | } | ||
167 | |||
168 | soc_runtime->dai->cpu_dai->private_data = prdata; | ||
169 | |||
170 | /* | ||
171 | * Enable Error interrupts. We're only ack'ing them but | ||
172 | * it's usefull for diagnostics | ||
173 | */ | ||
174 | writel((unsigned long)-1, priv->io + KIRKWOOD_ERR_MASK); | ||
175 | } | ||
176 | |||
177 | addr = virt_to_phys(substream->dma_buffer.area); | ||
178 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
179 | prdata->play_stream = substream; | ||
180 | kirkwood_dma_conf_mbus_windows(priv->io, | ||
181 | KIRKWOOD_PLAYBACK_WIN, addr, priv->dram); | ||
182 | } else { | ||
183 | prdata->rec_stream = substream; | ||
184 | kirkwood_dma_conf_mbus_windows(priv->io, | ||
185 | KIRKWOOD_RECORD_WIN, addr, priv->dram); | ||
186 | } | ||
187 | |||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | static int kirkwood_dma_close(struct snd_pcm_substream *substream) | ||
192 | { | ||
193 | struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; | ||
194 | struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; | ||
195 | struct kirkwood_dma_priv *prdata = cpu_dai->private_data; | ||
196 | struct kirkwood_dma_data *priv; | ||
197 | |||
198 | priv = snd_soc_dai_get_dma_data(cpu_dai, substream); | ||
199 | |||
200 | if (!prdata || !priv) | ||
201 | return 0; | ||
202 | |||
203 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
204 | prdata->play_stream = NULL; | ||
205 | else | ||
206 | prdata->rec_stream = NULL; | ||
207 | |||
208 | if (!prdata->play_stream && !prdata->rec_stream) { | ||
209 | writel(0, priv->io + KIRKWOOD_ERR_MASK); | ||
210 | free_irq(priv->irq, prdata); | ||
211 | kfree(prdata); | ||
212 | soc_runtime->dai->cpu_dai->private_data = NULL; | ||
213 | } | ||
214 | |||
215 | return 0; | ||
216 | } | ||
217 | |||
218 | static int kirkwood_dma_hw_params(struct snd_pcm_substream *substream, | ||
219 | struct snd_pcm_hw_params *params) | ||
220 | { | ||
221 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
222 | |||
223 | snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); | ||
224 | runtime->dma_bytes = params_buffer_bytes(params); | ||
225 | |||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | static int kirkwood_dma_hw_free(struct snd_pcm_substream *substream) | ||
230 | { | ||
231 | snd_pcm_set_runtime_buffer(substream, NULL); | ||
232 | return 0; | ||
233 | } | ||
234 | |||
235 | static int kirkwood_dma_prepare(struct snd_pcm_substream *substream) | ||
236 | { | ||
237 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
238 | struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; | ||
239 | struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; | ||
240 | struct kirkwood_dma_data *priv; | ||
241 | unsigned long size, count; | ||
242 | |||
243 | priv = snd_soc_dai_get_dma_data(cpu_dai, substream); | ||
244 | |||
245 | /* compute buffer size in term of "words" as requested in specs */ | ||
246 | size = frames_to_bytes(runtime, runtime->buffer_size); | ||
247 | size = (size>>2)-1; | ||
248 | count = snd_pcm_lib_period_bytes(substream); | ||
249 | |||
250 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
251 | writel(count, priv->io + KIRKWOOD_PLAY_BYTE_INT_COUNT); | ||
252 | writel(runtime->dma_addr, priv->io + KIRKWOOD_PLAY_BUF_ADDR); | ||
253 | writel(size, priv->io + KIRKWOOD_PLAY_BUF_SIZE); | ||
254 | } else { | ||
255 | writel(count, priv->io + KIRKWOOD_REC_BYTE_INT_COUNT); | ||
256 | writel(runtime->dma_addr, priv->io + KIRKWOOD_REC_BUF_ADDR); | ||
257 | writel(size, priv->io + KIRKWOOD_REC_BUF_SIZE); | ||
258 | } | ||
259 | |||
260 | |||
261 | return 0; | ||
262 | } | ||
263 | |||
264 | static snd_pcm_uframes_t kirkwood_dma_pointer(struct snd_pcm_substream | ||
265 | *substream) | ||
266 | { | ||
267 | struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; | ||
268 | struct snd_soc_dai *cpu_dai = soc_runtime->dai->cpu_dai; | ||
269 | struct kirkwood_dma_data *priv; | ||
270 | snd_pcm_uframes_t count; | ||
271 | |||
272 | priv = snd_soc_dai_get_dma_data(cpu_dai, substream); | ||
273 | |||
274 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
275 | count = bytes_to_frames(substream->runtime, | ||
276 | readl(priv->io + KIRKWOOD_PLAY_BYTE_COUNT)); | ||
277 | else | ||
278 | count = bytes_to_frames(substream->runtime, | ||
279 | readl(priv->io + KIRKWOOD_REC_BYTE_COUNT)); | ||
280 | |||
281 | return count; | ||
282 | } | ||
283 | |||
284 | struct snd_pcm_ops kirkwood_dma_ops = { | ||
285 | .open = kirkwood_dma_open, | ||
286 | .close = kirkwood_dma_close, | ||
287 | .ioctl = snd_pcm_lib_ioctl, | ||
288 | .hw_params = kirkwood_dma_hw_params, | ||
289 | .hw_free = kirkwood_dma_hw_free, | ||
290 | .prepare = kirkwood_dma_prepare, | ||
291 | .pointer = kirkwood_dma_pointer, | ||
292 | }; | ||
293 | |||
294 | static int kirkwood_dma_preallocate_dma_buffer(struct snd_pcm *pcm, | ||
295 | int stream) | ||
296 | { | ||
297 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
298 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
299 | size_t size = kirkwood_dma_snd_hw.buffer_bytes_max; | ||
300 | |||
301 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | ||
302 | buf->dev.dev = pcm->card->dev; | ||
303 | buf->area = dma_alloc_coherent(pcm->card->dev, size, | ||
304 | &buf->addr, GFP_KERNEL); | ||
305 | if (!buf->area) | ||
306 | return -ENOMEM; | ||
307 | buf->bytes = size; | ||
308 | buf->private_data = NULL; | ||
309 | |||
310 | return 0; | ||
311 | } | ||
312 | |||
313 | static int kirkwood_dma_new(struct snd_card *card, | ||
314 | struct snd_soc_dai *dai, struct snd_pcm *pcm) | ||
315 | { | ||
316 | int ret; | ||
317 | |||
318 | if (!card->dev->dma_mask) | ||
319 | card->dev->dma_mask = &kirkwood_dma_dmamask; | ||
320 | if (!card->dev->coherent_dma_mask) | ||
321 | card->dev->coherent_dma_mask = 0xffffffff; | ||
322 | |||
323 | if (dai->playback.channels_min) { | ||
324 | ret = kirkwood_dma_preallocate_dma_buffer(pcm, | ||
325 | SNDRV_PCM_STREAM_PLAYBACK); | ||
326 | if (ret) | ||
327 | return ret; | ||
328 | } | ||
329 | |||
330 | if (dai->capture.channels_min) { | ||
331 | ret = kirkwood_dma_preallocate_dma_buffer(pcm, | ||
332 | SNDRV_PCM_STREAM_CAPTURE); | ||
333 | if (ret) | ||
334 | return ret; | ||
335 | } | ||
336 | |||
337 | return 0; | ||
338 | } | ||
339 | |||
340 | static void kirkwood_dma_free_dma_buffers(struct snd_pcm *pcm) | ||
341 | { | ||
342 | struct snd_pcm_substream *substream; | ||
343 | struct snd_dma_buffer *buf; | ||
344 | int stream; | ||
345 | |||
346 | for (stream = 0; stream < 2; stream++) { | ||
347 | substream = pcm->streams[stream].substream; | ||
348 | if (!substream) | ||
349 | continue; | ||
350 | buf = &substream->dma_buffer; | ||
351 | if (!buf->area) | ||
352 | continue; | ||
353 | |||
354 | dma_free_coherent(pcm->card->dev, buf->bytes, | ||
355 | buf->area, buf->addr); | ||
356 | buf->area = NULL; | ||
357 | } | ||
358 | } | ||
359 | |||
360 | struct snd_soc_platform kirkwood_soc_platform = { | ||
361 | .name = "kirkwood-dma", | ||
362 | .pcm_ops = &kirkwood_dma_ops, | ||
363 | .pcm_new = kirkwood_dma_new, | ||
364 | .pcm_free = kirkwood_dma_free_dma_buffers, | ||
365 | }; | ||
366 | EXPORT_SYMBOL_GPL(kirkwood_soc_platform); | ||
367 | |||
368 | static int __init kirkwood_soc_platform_init(void) | ||
369 | { | ||
370 | return snd_soc_register_platform(&kirkwood_soc_platform); | ||
371 | } | ||
372 | module_init(kirkwood_soc_platform_init); | ||
373 | |||
374 | static void __exit kirkwood_soc_platform_exit(void) | ||
375 | { | ||
376 | snd_soc_unregister_platform(&kirkwood_soc_platform); | ||
377 | } | ||
378 | module_exit(kirkwood_soc_platform_exit); | ||
379 | |||
380 | MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>"); | ||
381 | MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module"); | ||
382 | MODULE_LICENSE("GPL"); | ||
383 | |||
diff --git a/sound/soc/kirkwood/kirkwood-dma.h b/sound/soc/kirkwood/kirkwood-dma.h new file mode 100644 index 000000000000..ba4454cd34f1 --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-dma.h | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * kirkwood-dma.h | ||
3 | * | ||
4 | * (c) 2010 Arnaud Patard <apatard@mandriva.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the | ||
8 | * Free Software Foundation; either version 2 of the License, or (at your | ||
9 | * option) any later version. | ||
10 | */ | ||
11 | |||
12 | #ifndef _KIRKWOOD_DMA_H | ||
13 | #define _KIRKWOOD_DMA_H | ||
14 | |||
15 | extern struct snd_soc_platform kirkwood_soc_platform; | ||
16 | |||
17 | #endif | ||
diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c new file mode 100644 index 000000000000..0adc59778d5a --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-i2s.c | |||
@@ -0,0 +1,485 @@ | |||
1 | /* | ||
2 | * kirkwood-i2s.c | ||
3 | * | ||
4 | * (c) 2010 Arnaud Patard <apatard@mandriva.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the | ||
8 | * Free Software Foundation; either version 2 of the License, or (at your | ||
9 | * option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/io.h> | ||
16 | #include <linux/slab.h> | ||
17 | #include <linux/mbus.h> | ||
18 | #include <linux/delay.h> | ||
19 | #include <sound/pcm.h> | ||
20 | #include <sound/pcm_params.h> | ||
21 | #include <sound/soc.h> | ||
22 | #include <plat/audio.h> | ||
23 | #include "kirkwood-i2s.h" | ||
24 | #include "kirkwood.h" | ||
25 | |||
26 | #define DRV_NAME "kirkwood-i2s" | ||
27 | |||
28 | #define KIRKWOOD_I2S_RATES \ | ||
29 | (SNDRV_PCM_RATE_44100 | \ | ||
30 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) | ||
31 | #define KIRKWOOD_I2S_FORMATS \ | ||
32 | (SNDRV_PCM_FMTBIT_S16_LE | \ | ||
33 | SNDRV_PCM_FMTBIT_S24_LE | \ | ||
34 | SNDRV_PCM_FMTBIT_S32_LE) | ||
35 | |||
36 | |||
37 | struct snd_soc_dai kirkwood_i2s_dai; | ||
38 | static struct kirkwood_dma_data *priv; | ||
39 | |||
40 | static int kirkwood_i2s_set_fmt(struct snd_soc_dai *cpu_dai, | ||
41 | unsigned int fmt) | ||
42 | { | ||
43 | unsigned long mask; | ||
44 | unsigned long value; | ||
45 | |||
46 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
47 | case SND_SOC_DAIFMT_RIGHT_J: | ||
48 | mask = KIRKWOOD_I2S_CTL_RJ; | ||
49 | break; | ||
50 | case SND_SOC_DAIFMT_LEFT_J: | ||
51 | mask = KIRKWOOD_I2S_CTL_LJ; | ||
52 | break; | ||
53 | case SND_SOC_DAIFMT_I2S: | ||
54 | mask = KIRKWOOD_I2S_CTL_I2S; | ||
55 | break; | ||
56 | default: | ||
57 | return -EINVAL; | ||
58 | } | ||
59 | |||
60 | /* | ||
61 | * Set same format for playback and record | ||
62 | * This avoids some troubles. | ||
63 | */ | ||
64 | value = readl(priv->io+KIRKWOOD_I2S_PLAYCTL); | ||
65 | value &= ~KIRKWOOD_I2S_CTL_JUST_MASK; | ||
66 | value |= mask; | ||
67 | writel(value, priv->io+KIRKWOOD_I2S_PLAYCTL); | ||
68 | |||
69 | value = readl(priv->io+KIRKWOOD_I2S_RECCTL); | ||
70 | value &= ~KIRKWOOD_I2S_CTL_JUST_MASK; | ||
71 | value |= mask; | ||
72 | writel(value, priv->io+KIRKWOOD_I2S_RECCTL); | ||
73 | |||
74 | return 0; | ||
75 | } | ||
76 | |||
77 | static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate) | ||
78 | { | ||
79 | unsigned long value; | ||
80 | |||
81 | value = KIRKWOOD_DCO_CTL_OFFSET_0; | ||
82 | switch (rate) { | ||
83 | default: | ||
84 | case 44100: | ||
85 | value |= KIRKWOOD_DCO_CTL_FREQ_11; | ||
86 | break; | ||
87 | case 48000: | ||
88 | value |= KIRKWOOD_DCO_CTL_FREQ_12; | ||
89 | break; | ||
90 | case 96000: | ||
91 | value |= KIRKWOOD_DCO_CTL_FREQ_24; | ||
92 | break; | ||
93 | } | ||
94 | writel(value, io + KIRKWOOD_DCO_CTL); | ||
95 | |||
96 | /* wait for dco locked */ | ||
97 | do { | ||
98 | cpu_relax(); | ||
99 | value = readl(io + KIRKWOOD_DCO_SPCR_STATUS); | ||
100 | value &= KIRKWOOD_DCO_SPCR_STATUS; | ||
101 | } while (value == 0); | ||
102 | } | ||
103 | |||
104 | static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream, | ||
105 | struct snd_pcm_hw_params *params, | ||
106 | struct snd_soc_dai *dai) | ||
107 | { | ||
108 | unsigned int i2s_reg, reg; | ||
109 | unsigned long i2s_value, value; | ||
110 | |||
111 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
112 | i2s_reg = KIRKWOOD_I2S_PLAYCTL; | ||
113 | reg = KIRKWOOD_PLAYCTL; | ||
114 | } else { | ||
115 | i2s_reg = KIRKWOOD_I2S_RECCTL; | ||
116 | reg = KIRKWOOD_RECCTL; | ||
117 | } | ||
118 | |||
119 | /* set dco conf */ | ||
120 | kirkwood_set_dco(priv->io, params_rate(params)); | ||
121 | |||
122 | i2s_value = readl(priv->io+i2s_reg); | ||
123 | i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK; | ||
124 | |||
125 | value = readl(priv->io+reg); | ||
126 | value &= ~KIRKWOOD_PLAYCTL_SIZE_MASK; | ||
127 | |||
128 | /* | ||
129 | * Size settings in play/rec i2s control regs and play/rec control | ||
130 | * regs must be the same. | ||
131 | */ | ||
132 | switch (params_format(params)) { | ||
133 | case SNDRV_PCM_FORMAT_S16_LE: | ||
134 | i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16; | ||
135 | value |= KIRKWOOD_PLAYCTL_SIZE_16_C; | ||
136 | break; | ||
137 | /* | ||
138 | * doesn't work... S20_3LE != kirkwood 20bit format ? | ||
139 | * | ||
140 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
141 | i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20; | ||
142 | value |= KIRKWOOD_PLAYCTL_SIZE_20; | ||
143 | break; | ||
144 | */ | ||
145 | case SNDRV_PCM_FORMAT_S24_LE: | ||
146 | i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24; | ||
147 | value |= KIRKWOOD_PLAYCTL_SIZE_24; | ||
148 | break; | ||
149 | case SNDRV_PCM_FORMAT_S32_LE: | ||
150 | i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32; | ||
151 | value |= KIRKWOOD_PLAYCTL_SIZE_32; | ||
152 | break; | ||
153 | default: | ||
154 | return -EINVAL; | ||
155 | } | ||
156 | writel(i2s_value, priv->io+i2s_reg); | ||
157 | writel(value, priv->io+reg); | ||
158 | |||
159 | return 0; | ||
160 | } | ||
161 | |||
162 | static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream, | ||
163 | int cmd, struct snd_soc_dai *dai) | ||
164 | { | ||
165 | unsigned long value; | ||
166 | |||
167 | /* | ||
168 | * specs says KIRKWOOD_PLAYCTL must be read 2 times before | ||
169 | * changing it. So read 1 time here and 1 later. | ||
170 | */ | ||
171 | value = readl(priv->io + KIRKWOOD_PLAYCTL); | ||
172 | |||
173 | switch (cmd) { | ||
174 | case SNDRV_PCM_TRIGGER_START: | ||
175 | /* stop audio, enable interrupts */ | ||
176 | value = readl(priv->io + KIRKWOOD_PLAYCTL); | ||
177 | value |= KIRKWOOD_PLAYCTL_PAUSE; | ||
178 | writel(value, priv->io + KIRKWOOD_PLAYCTL); | ||
179 | |||
180 | value = readl(priv->io + KIRKWOOD_INT_MASK); | ||
181 | value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES; | ||
182 | writel(value, priv->io + KIRKWOOD_INT_MASK); | ||
183 | |||
184 | /* configure audio & enable i2s playback */ | ||
185 | value = readl(priv->io + KIRKWOOD_PLAYCTL); | ||
186 | value &= ~KIRKWOOD_PLAYCTL_BURST_MASK; | ||
187 | value &= ~(KIRKWOOD_PLAYCTL_PAUSE|KIRKWOOD_PLAYCTL_SPDIF_EN); | ||
188 | |||
189 | if (priv->burst == 32) | ||
190 | value |= KIRKWOOD_PLAYCTL_BURST_32; | ||
191 | else | ||
192 | value |= KIRKWOOD_PLAYCTL_BURST_128; | ||
193 | value |= KIRKWOOD_PLAYCTL_I2S_EN; | ||
194 | writel(value, priv->io + KIRKWOOD_PLAYCTL); | ||
195 | break; | ||
196 | |||
197 | case SNDRV_PCM_TRIGGER_STOP: | ||
198 | /* stop audio, disable interrupts */ | ||
199 | value = readl(priv->io + KIRKWOOD_PLAYCTL); | ||
200 | value |= KIRKWOOD_PLAYCTL_PAUSE; | ||
201 | writel(value, priv->io + KIRKWOOD_PLAYCTL); | ||
202 | |||
203 | value = readl(priv->io + KIRKWOOD_INT_MASK); | ||
204 | value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES; | ||
205 | writel(value, priv->io + KIRKWOOD_INT_MASK); | ||
206 | |||
207 | /* disable all playbacks */ | ||
208 | value = readl(priv->io + KIRKWOOD_PLAYCTL); | ||
209 | value &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN); | ||
210 | writel(value, priv->io + KIRKWOOD_PLAYCTL); | ||
211 | break; | ||
212 | |||
213 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
214 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
215 | value = readl(priv->io + KIRKWOOD_PLAYCTL); | ||
216 | value |= KIRKWOOD_PLAYCTL_PAUSE; | ||
217 | writel(value, priv->io + KIRKWOOD_PLAYCTL); | ||
218 | break; | ||
219 | |||
220 | case SNDRV_PCM_TRIGGER_RESUME: | ||
221 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
222 | value = readl(priv->io + KIRKWOOD_PLAYCTL); | ||
223 | value &= ~KIRKWOOD_PLAYCTL_PAUSE; | ||
224 | writel(value, priv->io + KIRKWOOD_PLAYCTL); | ||
225 | break; | ||
226 | |||
227 | default: | ||
228 | return -EINVAL; | ||
229 | } | ||
230 | |||
231 | return 0; | ||
232 | } | ||
233 | |||
234 | static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream, | ||
235 | int cmd, struct snd_soc_dai *dai) | ||
236 | { | ||
237 | unsigned long value; | ||
238 | |||
239 | value = readl(priv->io + KIRKWOOD_RECCTL); | ||
240 | |||
241 | switch (cmd) { | ||
242 | case SNDRV_PCM_TRIGGER_START: | ||
243 | /* stop audio, enable interrupts */ | ||
244 | value = readl(priv->io + KIRKWOOD_RECCTL); | ||
245 | value |= KIRKWOOD_RECCTL_PAUSE; | ||
246 | writel(value, priv->io + KIRKWOOD_RECCTL); | ||
247 | |||
248 | value = readl(priv->io + KIRKWOOD_INT_MASK); | ||
249 | value |= KIRKWOOD_INT_CAUSE_REC_BYTES; | ||
250 | writel(value, priv->io + KIRKWOOD_INT_MASK); | ||
251 | |||
252 | /* configure audio & enable i2s record */ | ||
253 | value = readl(priv->io + KIRKWOOD_RECCTL); | ||
254 | value &= ~KIRKWOOD_RECCTL_BURST_MASK; | ||
255 | value &= ~KIRKWOOD_RECCTL_MONO; | ||
256 | value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_SPDIF_EN); | ||
257 | |||
258 | if (priv->burst == 32) | ||
259 | value |= KIRKWOOD_RECCTL_BURST_32; | ||
260 | else | ||
261 | value |= KIRKWOOD_RECCTL_BURST_128; | ||
262 | value |= KIRKWOOD_RECCTL_I2S_EN; | ||
263 | |||
264 | writel(value, priv->io + KIRKWOOD_RECCTL); | ||
265 | break; | ||
266 | |||
267 | case SNDRV_PCM_TRIGGER_STOP: | ||
268 | /* stop audio, disable interrupts */ | ||
269 | value = readl(priv->io + KIRKWOOD_RECCTL); | ||
270 | value |= KIRKWOOD_RECCTL_PAUSE; | ||
271 | writel(value, priv->io + KIRKWOOD_RECCTL); | ||
272 | |||
273 | value = readl(priv->io + KIRKWOOD_INT_MASK); | ||
274 | value &= ~KIRKWOOD_INT_CAUSE_REC_BYTES; | ||
275 | writel(value, priv->io + KIRKWOOD_INT_MASK); | ||
276 | |||
277 | /* disable all records */ | ||
278 | value = readl(priv->io + KIRKWOOD_RECCTL); | ||
279 | value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN); | ||
280 | writel(value, priv->io + KIRKWOOD_RECCTL); | ||
281 | break; | ||
282 | |||
283 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
284 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
285 | value = readl(priv->io + KIRKWOOD_RECCTL); | ||
286 | value |= KIRKWOOD_RECCTL_PAUSE; | ||
287 | writel(value, priv->io + KIRKWOOD_RECCTL); | ||
288 | break; | ||
289 | |||
290 | case SNDRV_PCM_TRIGGER_RESUME: | ||
291 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
292 | value = readl(priv->io + KIRKWOOD_RECCTL); | ||
293 | value &= ~KIRKWOOD_RECCTL_PAUSE; | ||
294 | writel(value, priv->io + KIRKWOOD_RECCTL); | ||
295 | break; | ||
296 | |||
297 | default: | ||
298 | return -EINVAL; | ||
299 | break; | ||
300 | } | ||
301 | |||
302 | return 0; | ||
303 | } | ||
304 | |||
305 | static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd, | ||
306 | struct snd_soc_dai *dai) | ||
307 | { | ||
308 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
309 | return kirkwood_i2s_play_trigger(substream, cmd, dai); | ||
310 | else | ||
311 | return kirkwood_i2s_rec_trigger(substream, cmd, dai); | ||
312 | |||
313 | return 0; | ||
314 | } | ||
315 | |||
316 | static int kirkwood_i2s_probe(struct platform_device *pdev, | ||
317 | struct snd_soc_dai *dai) | ||
318 | { | ||
319 | unsigned long value; | ||
320 | unsigned int reg_data; | ||
321 | |||
322 | /* put system in a "safe" state : */ | ||
323 | /* disable audio interrupts */ | ||
324 | writel(0xffffffff, priv->io + KIRKWOOD_INT_CAUSE); | ||
325 | writel(0, priv->io + KIRKWOOD_INT_MASK); | ||
326 | |||
327 | reg_data = readl(priv->io + 0x1200); | ||
328 | reg_data &= (~(0x333FF8)); | ||
329 | reg_data |= 0x111D18; | ||
330 | writel(reg_data, priv->io + 0x1200); | ||
331 | |||
332 | msleep(500); | ||
333 | |||
334 | reg_data = readl(priv->io + 0x1200); | ||
335 | reg_data &= (~(0x333FF8)); | ||
336 | reg_data |= 0x111D18; | ||
337 | writel(reg_data, priv->io + 0x1200); | ||
338 | |||
339 | /* disable playback/record */ | ||
340 | value = readl(priv->io + KIRKWOOD_PLAYCTL); | ||
341 | value &= ~(KIRKWOOD_PLAYCTL_I2S_EN|KIRKWOOD_PLAYCTL_SPDIF_EN); | ||
342 | writel(value, priv->io + KIRKWOOD_PLAYCTL); | ||
343 | |||
344 | value = readl(priv->io + KIRKWOOD_RECCTL); | ||
345 | value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN); | ||
346 | writel(value, priv->io + KIRKWOOD_RECCTL); | ||
347 | |||
348 | return 0; | ||
349 | |||
350 | } | ||
351 | |||
352 | static void kirkwood_i2s_remove(struct platform_device *pdev, | ||
353 | struct snd_soc_dai *dai) | ||
354 | { | ||
355 | } | ||
356 | |||
357 | static struct snd_soc_dai_ops kirkwood_i2s_dai_ops = { | ||
358 | .trigger = kirkwood_i2s_trigger, | ||
359 | .hw_params = kirkwood_i2s_hw_params, | ||
360 | .set_fmt = kirkwood_i2s_set_fmt, | ||
361 | }; | ||
362 | |||
363 | |||
364 | struct snd_soc_dai kirkwood_i2s_dai = { | ||
365 | .name = DRV_NAME, | ||
366 | .id = 0, | ||
367 | .probe = kirkwood_i2s_probe, | ||
368 | .remove = kirkwood_i2s_remove, | ||
369 | .playback = { | ||
370 | .channels_min = 1, | ||
371 | .channels_max = 2, | ||
372 | .rates = KIRKWOOD_I2S_RATES, | ||
373 | .formats = KIRKWOOD_I2S_FORMATS,}, | ||
374 | .capture = { | ||
375 | .channels_min = 1, | ||
376 | .channels_max = 2, | ||
377 | .rates = KIRKWOOD_I2S_RATES, | ||
378 | .formats = KIRKWOOD_I2S_FORMATS,}, | ||
379 | .ops = &kirkwood_i2s_dai_ops, | ||
380 | }; | ||
381 | EXPORT_SYMBOL_GPL(kirkwood_i2s_dai); | ||
382 | |||
383 | static __devinit int kirkwood_i2s_dev_probe(struct platform_device *pdev) | ||
384 | { | ||
385 | struct resource *mem; | ||
386 | struct kirkwood_asoc_platform_data *data = | ||
387 | pdev->dev.platform_data; | ||
388 | int err; | ||
389 | |||
390 | priv = kzalloc(sizeof(struct kirkwood_dma_data), GFP_KERNEL); | ||
391 | if (!priv) { | ||
392 | dev_err(&pdev->dev, "allocation failed\n"); | ||
393 | err = -ENOMEM; | ||
394 | goto error; | ||
395 | } | ||
396 | |||
397 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
398 | if (!mem) { | ||
399 | dev_err(&pdev->dev, "platform_get_resource failed\n"); | ||
400 | err = -ENXIO; | ||
401 | goto err_alloc; | ||
402 | } | ||
403 | |||
404 | priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME); | ||
405 | if (!priv->mem) { | ||
406 | dev_err(&pdev->dev, "request_mem_region failed\n"); | ||
407 | err = -EBUSY; | ||
408 | goto error; | ||
409 | } | ||
410 | |||
411 | priv->io = ioremap(priv->mem->start, SZ_16K); | ||
412 | if (!priv->io) { | ||
413 | dev_err(&pdev->dev, "ioremap failed\n"); | ||
414 | err = -ENOMEM; | ||
415 | goto err_iomem; | ||
416 | } | ||
417 | |||
418 | priv->irq = platform_get_irq(pdev, 0); | ||
419 | if (priv->irq <= 0) { | ||
420 | dev_err(&pdev->dev, "platform_get_irq failed\n"); | ||
421 | err = -ENXIO; | ||
422 | goto err_ioremap; | ||
423 | } | ||
424 | |||
425 | if (!data || !data->dram) { | ||
426 | dev_err(&pdev->dev, "no platform data ?!\n"); | ||
427 | err = -EINVAL; | ||
428 | goto err_ioremap; | ||
429 | } | ||
430 | |||
431 | priv->dram = data->dram; | ||
432 | priv->burst = data->burst; | ||
433 | |||
434 | kirkwood_i2s_dai.capture.dma_data = priv; | ||
435 | kirkwood_i2s_dai.playback.dma_data = priv; | ||
436 | |||
437 | return snd_soc_register_dai(&kirkwood_i2s_dai); | ||
438 | |||
439 | err_ioremap: | ||
440 | iounmap(priv->io); | ||
441 | err_iomem: | ||
442 | release_mem_region(priv->mem->start, SZ_16K); | ||
443 | err_alloc: | ||
444 | kfree(priv); | ||
445 | error: | ||
446 | return err; | ||
447 | } | ||
448 | |||
449 | static __devexit int kirkwood_i2s_dev_remove(struct platform_device *pdev) | ||
450 | { | ||
451 | if (priv) { | ||
452 | iounmap(priv->io); | ||
453 | release_mem_region(priv->mem->start, SZ_16K); | ||
454 | kfree(priv); | ||
455 | } | ||
456 | snd_soc_unregister_dai(&kirkwood_i2s_dai); | ||
457 | return 0; | ||
458 | } | ||
459 | |||
460 | static struct platform_driver kirkwood_i2s_driver = { | ||
461 | .probe = kirkwood_i2s_dev_probe, | ||
462 | .remove = kirkwood_i2s_dev_remove, | ||
463 | .driver = { | ||
464 | .name = DRV_NAME, | ||
465 | .owner = THIS_MODULE, | ||
466 | }, | ||
467 | }; | ||
468 | |||
469 | static int __init kirkwood_i2s_init(void) | ||
470 | { | ||
471 | return platform_driver_register(&kirkwood_i2s_driver); | ||
472 | } | ||
473 | module_init(kirkwood_i2s_init); | ||
474 | |||
475 | static void __exit kirkwood_i2s_exit(void) | ||
476 | { | ||
477 | platform_driver_unregister(&kirkwood_i2s_driver); | ||
478 | } | ||
479 | module_exit(kirkwood_i2s_exit); | ||
480 | |||
481 | /* Module information */ | ||
482 | MODULE_AUTHOR("Arnaud Patard, <apatard@mandriva.com>"); | ||
483 | MODULE_DESCRIPTION("Kirkwood I2S SoC Interface"); | ||
484 | MODULE_LICENSE("GPL"); | ||
485 | MODULE_ALIAS("platform:kirkwood-i2s"); | ||
diff --git a/sound/soc/kirkwood/kirkwood-i2s.h b/sound/soc/kirkwood/kirkwood-i2s.h new file mode 100644 index 000000000000..c5595c616d7a --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-i2s.h | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | * kirkwood-i2s.h | ||
3 | * | ||
4 | * (c) 2010 Arnaud Patard <apatard@mandriva.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the | ||
8 | * Free Software Foundation; either version 2 of the License, or (at your | ||
9 | * option) any later version. | ||
10 | */ | ||
11 | |||
12 | #ifndef _KIRKWOOD_I2S_H | ||
13 | #define _KIRKWOOD_I2S_H | ||
14 | |||
15 | extern struct snd_soc_dai kirkwood_i2s_dai; | ||
16 | |||
17 | #endif | ||
diff --git a/sound/soc/kirkwood/kirkwood-openrd.c b/sound/soc/kirkwood/kirkwood-openrd.c new file mode 100644 index 000000000000..0353d06bc41a --- /dev/null +++ b/sound/soc/kirkwood/kirkwood-openrd.c | |||
@@ -0,0 +1,126 @@ | |||
1 | /* | ||
2 | * kirkwood-openrd.c | ||
3 | * | ||
4 | * (c) 2010 Arnaud Patard <apatard@mandriva.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the | ||
8 | * Free Software Foundation; either version 2 of the License, or (at your | ||
9 | * option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/moduleparam.h> | ||
14 | #include <linux/interrupt.h> | ||
15 | #include <linux/platform_device.h> | ||
16 | #include <linux/slab.h> | ||
17 | #include <sound/soc.h> | ||
18 | #include <mach/kirkwood.h> | ||
19 | #include <plat/audio.h> | ||
20 | #include <asm/mach-types.h> | ||
21 | #include "kirkwood-i2s.h" | ||
22 | #include "kirkwood-dma.h" | ||
23 | #include "../codecs/cs42l51.h" | ||
24 | |||
25 | static int openrd_client_hw_params(struct snd_pcm_substream *substream, | ||
26 | struct snd_pcm_hw_params *params) | ||
27 | { | ||
28 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
29 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; | ||
30 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
31 | int ret; | ||
32 | unsigned int freq, fmt; | ||
33 | |||
34 | fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS; | ||
35 | ret = snd_soc_dai_set_fmt(cpu_dai, fmt); | ||
36 | if (ret < 0) | ||
37 | return ret; | ||
38 | |||
39 | ret = snd_soc_dai_set_fmt(codec_dai, fmt); | ||
40 | if (ret < 0) | ||
41 | return ret; | ||
42 | |||
43 | switch (params_rate(params)) { | ||
44 | default: | ||
45 | case 44100: | ||
46 | freq = 11289600; | ||
47 | break; | ||
48 | case 48000: | ||
49 | freq = 12288000; | ||
50 | break; | ||
51 | case 96000: | ||
52 | freq = 24576000; | ||
53 | break; | ||
54 | } | ||
55 | |||
56 | return snd_soc_dai_set_sysclk(codec_dai, 0, freq, SND_SOC_CLOCK_IN); | ||
57 | |||
58 | } | ||
59 | |||
60 | static struct snd_soc_ops openrd_client_ops = { | ||
61 | .hw_params = openrd_client_hw_params, | ||
62 | }; | ||
63 | |||
64 | |||
65 | static struct snd_soc_dai_link openrd_client_dai[] = { | ||
66 | { | ||
67 | .name = "CS42L51", | ||
68 | .stream_name = "CS42L51 HiFi", | ||
69 | .cpu_dai = &kirkwood_i2s_dai, | ||
70 | .codec_dai = &cs42l51_dai, | ||
71 | .ops = &openrd_client_ops, | ||
72 | }, | ||
73 | }; | ||
74 | |||
75 | |||
76 | static struct snd_soc_card openrd_client = { | ||
77 | .name = "OpenRD Client", | ||
78 | .platform = &kirkwood_soc_platform, | ||
79 | .dai_link = openrd_client_dai, | ||
80 | .num_links = ARRAY_SIZE(openrd_client_dai), | ||
81 | }; | ||
82 | |||
83 | static struct snd_soc_device openrd_client_snd_devdata = { | ||
84 | .card = &openrd_client, | ||
85 | .codec_dev = &soc_codec_device_cs42l51, | ||
86 | }; | ||
87 | |||
88 | static struct platform_device *openrd_client_snd_device; | ||
89 | |||
90 | static int __init openrd_client_init(void) | ||
91 | { | ||
92 | int ret; | ||
93 | |||
94 | if (!machine_is_openrd_client()) | ||
95 | return 0; | ||
96 | |||
97 | openrd_client_snd_device = platform_device_alloc("soc-audio", -1); | ||
98 | if (!openrd_client_snd_device) | ||
99 | return -ENOMEM; | ||
100 | |||
101 | platform_set_drvdata(openrd_client_snd_device, | ||
102 | &openrd_client_snd_devdata); | ||
103 | openrd_client_snd_devdata.dev = &openrd_client_snd_device->dev; | ||
104 | |||
105 | ret = platform_device_add(openrd_client_snd_device); | ||
106 | if (ret) { | ||
107 | printk(KERN_ERR "%s: platform_device_add failed\n", __func__); | ||
108 | platform_device_put(openrd_client_snd_device); | ||
109 | } | ||
110 | |||
111 | return ret; | ||
112 | } | ||
113 | |||
114 | static void __exit openrd_client_exit(void) | ||
115 | { | ||
116 | platform_device_unregister(openrd_client_snd_device); | ||
117 | } | ||
118 | |||
119 | module_init(openrd_client_init); | ||
120 | module_exit(openrd_client_exit); | ||
121 | |||
122 | /* Module information */ | ||
123 | MODULE_AUTHOR("Arnaud Patard <apatard@mandriva.com>"); | ||
124 | MODULE_DESCRIPTION("ALSA SoC OpenRD Client"); | ||
125 | MODULE_LICENSE("GPL"); | ||
126 | MODULE_ALIAS("platform:soc-audio"); | ||
diff --git a/sound/soc/kirkwood/kirkwood.h b/sound/soc/kirkwood/kirkwood.h new file mode 100644 index 000000000000..b6e4f68d71dd --- /dev/null +++ b/sound/soc/kirkwood/kirkwood.h | |||
@@ -0,0 +1,126 @@ | |||
1 | /* | ||
2 | * kirkwood.h | ||
3 | * | ||
4 | * (c) 2010 Arnaud Patard <apatard@mandriva.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the | ||
8 | * Free Software Foundation; either version 2 of the License, or (at your | ||
9 | * option) any later version. | ||
10 | */ | ||
11 | |||
12 | #ifndef _KIRKWOOD_AUDIO_H | ||
13 | #define _KIRKWOOD_AUDIO_H | ||
14 | |||
15 | #define KIRKWOOD_RECORD_WIN 0 | ||
16 | #define KIRKWOOD_PLAYBACK_WIN 1 | ||
17 | #define KIRKWOOD_MAX_AUDIO_WIN 2 | ||
18 | |||
19 | #define KIRKWOOD_AUDIO_WIN_BASE_REG(win) (0xA00 + ((win)<<3)) | ||
20 | #define KIRKWOOD_AUDIO_WIN_CTRL_REG(win) (0xA04 + ((win)<<3)) | ||
21 | |||
22 | |||
23 | #define KIRKWOOD_RECCTL 0x1000 | ||
24 | #define KIRKWOOD_RECCTL_SPDIF_EN (1<<11) | ||
25 | #define KIRKWOOD_RECCTL_I2S_EN (1<<10) | ||
26 | #define KIRKWOOD_RECCTL_PAUSE (1<<9) | ||
27 | #define KIRKWOOD_RECCTL_MUTE (1<<8) | ||
28 | #define KIRKWOOD_RECCTL_BURST_MASK (3<<5) | ||
29 | #define KIRKWOOD_RECCTL_BURST_128 (2<<5) | ||
30 | #define KIRKWOOD_RECCTL_BURST_32 (1<<5) | ||
31 | #define KIRKWOOD_RECCTL_MONO (1<<4) | ||
32 | #define KIRKWOOD_RECCTL_MONO_CHAN_RIGHT (1<<3) | ||
33 | #define KIRKWOOD_RECCTL_MONO_CHAN_LEFT (0<<3) | ||
34 | #define KIRKWOOD_RECCTL_SIZE_MASK (7<<0) | ||
35 | #define KIRKWOOD_RECCTL_SIZE_16 (7<<0) | ||
36 | #define KIRKWOOD_RECCTL_SIZE_16_C (3<<0) | ||
37 | #define KIRKWOOD_RECCTL_SIZE_20 (2<<0) | ||
38 | #define KIRKWOOD_RECCTL_SIZE_24 (1<<0) | ||
39 | #define KIRKWOOD_RECCTL_SIZE_32 (0<<0) | ||
40 | |||
41 | #define KIRKWOOD_REC_BUF_ADDR 0x1004 | ||
42 | #define KIRKWOOD_REC_BUF_SIZE 0x1008 | ||
43 | #define KIRKWOOD_REC_BYTE_COUNT 0x100C | ||
44 | |||
45 | #define KIRKWOOD_PLAYCTL 0x1100 | ||
46 | #define KIRKWOOD_PLAYCTL_PLAY_BUSY (1<<16) | ||
47 | #define KIRKWOOD_PLAYCTL_BURST_MASK (3<<11) | ||
48 | #define KIRKWOOD_PLAYCTL_BURST_128 (2<<11) | ||
49 | #define KIRKWOOD_PLAYCTL_BURST_32 (1<<11) | ||
50 | #define KIRKWOOD_PLAYCTL_PAUSE (1<<9) | ||
51 | #define KIRKWOOD_PLAYCTL_SPDIF_MUTE (1<<8) | ||
52 | #define KIRKWOOD_PLAYCTL_I2S_MUTE (1<<7) | ||
53 | #define KIRKWOOD_PLAYCTL_SPDIF_EN (1<<4) | ||
54 | #define KIRKWOOD_PLAYCTL_I2S_EN (1<<3) | ||
55 | #define KIRKWOOD_PLAYCTL_SIZE_MASK (7<<0) | ||
56 | #define KIRKWOOD_PLAYCTL_SIZE_16 (7<<0) | ||
57 | #define KIRKWOOD_PLAYCTL_SIZE_16_C (3<<0) | ||
58 | #define KIRKWOOD_PLAYCTL_SIZE_20 (2<<0) | ||
59 | #define KIRKWOOD_PLAYCTL_SIZE_24 (1<<0) | ||
60 | #define KIRKWOOD_PLAYCTL_SIZE_32 (0<<0) | ||
61 | |||
62 | #define KIRKWOOD_PLAY_BUF_ADDR 0x1104 | ||
63 | #define KIRKWOOD_PLAY_BUF_SIZE 0x1108 | ||
64 | #define KIRKWOOD_PLAY_BYTE_COUNT 0x110C | ||
65 | |||
66 | #define KIRKWOOD_DCO_CTL 0x1204 | ||
67 | #define KIRKWOOD_DCO_CTL_OFFSET_MASK (0xFFF<<2) | ||
68 | #define KIRKWOOD_DCO_CTL_OFFSET_0 (0x800<<2) | ||
69 | #define KIRKWOOD_DCO_CTL_FREQ_MASK (3<<0) | ||
70 | #define KIRKWOOD_DCO_CTL_FREQ_11 (0<<0) | ||
71 | #define KIRKWOOD_DCO_CTL_FREQ_12 (1<<0) | ||
72 | #define KIRKWOOD_DCO_CTL_FREQ_24 (2<<0) | ||
73 | |||
74 | #define KIRKWOOD_DCO_SPCR_STATUS 0x120c | ||
75 | #define KIRKWOOD_DCO_SPCR_STATUS_DCO_LOCK (1<<16) | ||
76 | |||
77 | #define KIRKWOOD_ERR_CAUSE 0x1300 | ||
78 | #define KIRKWOOD_ERR_MASK 0x1304 | ||
79 | |||
80 | #define KIRKWOOD_INT_CAUSE 0x1308 | ||
81 | #define KIRKWOOD_INT_MASK 0x130C | ||
82 | #define KIRKWOOD_INT_CAUSE_PLAY_BYTES (1<<14) | ||
83 | #define KIRKWOOD_INT_CAUSE_REC_BYTES (1<<13) | ||
84 | #define KIRKWOOD_INT_CAUSE_DMA_PLAY_END (1<<7) | ||
85 | #define KIRKWOOD_INT_CAUSE_DMA_PLAY_3Q (1<<6) | ||
86 | #define KIRKWOOD_INT_CAUSE_DMA_PLAY_HALF (1<<5) | ||
87 | #define KIRKWOOD_INT_CAUSE_DMA_PLAY_1Q (1<<4) | ||
88 | #define KIRKWOOD_INT_CAUSE_DMA_REC_END (1<<3) | ||
89 | #define KIRKWOOD_INT_CAUSE_DMA_REC_3Q (1<<2) | ||
90 | #define KIRKWOOD_INT_CAUSE_DMA_REC_HALF (1<<1) | ||
91 | #define KIRKWOOD_INT_CAUSE_DMA_REC_1Q (1<<0) | ||
92 | |||
93 | #define KIRKWOOD_REC_BYTE_INT_COUNT 0x1310 | ||
94 | #define KIRKWOOD_PLAY_BYTE_INT_COUNT 0x1314 | ||
95 | #define KIRKWOOD_BYTE_INT_COUNT_MASK 0xffffff | ||
96 | |||
97 | #define KIRKWOOD_I2S_PLAYCTL 0x2508 | ||
98 | #define KIRKWOOD_I2S_RECCTL 0x2408 | ||
99 | #define KIRKWOOD_I2S_CTL_JUST_MASK (0xf<<26) | ||
100 | #define KIRKWOOD_I2S_CTL_LJ (0<<26) | ||
101 | #define KIRKWOOD_I2S_CTL_I2S (5<<26) | ||
102 | #define KIRKWOOD_I2S_CTL_RJ (8<<26) | ||
103 | #define KIRKWOOD_I2S_CTL_SIZE_MASK (3<<30) | ||
104 | #define KIRKWOOD_I2S_CTL_SIZE_16 (3<<30) | ||
105 | #define KIRKWOOD_I2S_CTL_SIZE_20 (2<<30) | ||
106 | #define KIRKWOOD_I2S_CTL_SIZE_24 (1<<30) | ||
107 | #define KIRKWOOD_I2S_CTL_SIZE_32 (0<<30) | ||
108 | |||
109 | #define KIRKWOOD_AUDIO_BUF_MAX (16*1024*1024) | ||
110 | |||
111 | /* Theses values come from the marvell alsa driver */ | ||
112 | /* need to find where they come from */ | ||
113 | #define KIRKWOOD_SND_MIN_PERIODS 8 | ||
114 | #define KIRKWOOD_SND_MAX_PERIODS 16 | ||
115 | #define KIRKWOOD_SND_MIN_PERIOD_BYTES 0x4000 | ||
116 | #define KIRKWOOD_SND_MAX_PERIOD_BYTES 0x4000 | ||
117 | |||
118 | struct kirkwood_dma_data { | ||
119 | struct resource *mem; | ||
120 | void __iomem *io; | ||
121 | int irq; | ||
122 | int burst; | ||
123 | struct mbus_dram_target_info *dram; | ||
124 | }; | ||
125 | |||
126 | #endif | ||
diff --git a/sound/soc/nuc900/Kconfig b/sound/soc/nuc900/Kconfig new file mode 100644 index 000000000000..a0ed1c618f60 --- /dev/null +++ b/sound/soc/nuc900/Kconfig | |||
@@ -0,0 +1,27 @@ | |||
1 | ## | ||
2 | ## NUC900 series AC97 API | ||
3 | ## | ||
4 | config SND_SOC_NUC900 | ||
5 | tristate "SoC Audio for NUC900 series" | ||
6 | depends on ARCH_W90X900 | ||
7 | help | ||
8 | This option enables support for AC97 mode on the NUC900 SoC. | ||
9 | |||
10 | config SND_SOC_NUC900_AC97 | ||
11 | tristate | ||
12 | select AC97_BUS | ||
13 | select SND_AC97_CODEC | ||
14 | select SND_SOC_AC97_BUS | ||
15 | |||
16 | |||
17 | ## | ||
18 | ## Boards | ||
19 | ## | ||
20 | config SND_SOC_NUC900EVB | ||
21 | tristate "NUC900 AC97 support for demo board" | ||
22 | depends on SND_SOC_NUC900 | ||
23 | select SND_SOC_NUC900_AC97 | ||
24 | select SND_SOC_AC97_CODEC | ||
25 | help | ||
26 | Select this option to enable audio (AC97) on the | ||
27 | NUC900 demoboard. | ||
diff --git a/sound/soc/nuc900/Makefile b/sound/soc/nuc900/Makefile new file mode 100644 index 000000000000..7e46c7150316 --- /dev/null +++ b/sound/soc/nuc900/Makefile | |||
@@ -0,0 +1,11 @@ | |||
1 | # NUC900 series audio | ||
2 | snd-soc-nuc900-pcm-objs := nuc900-pcm.o | ||
3 | snd-soc-nuc900-ac97-objs := nuc900-ac97.o | ||
4 | |||
5 | obj-$(CONFIG_SND_SOC_NUC900) += snd-soc-nuc900-pcm.o | ||
6 | obj-$(CONFIG_SND_SOC_NUC900_AC97) += snd-soc-nuc900-ac97.o | ||
7 | |||
8 | # Boards | ||
9 | snd-soc-nuc900-audio-objs := nuc900-audio.o | ||
10 | |||
11 | obj-$(CONFIG_SND_SOC_NUC900EVB) += snd-soc-nuc900-audio.o | ||
diff --git a/sound/soc/nuc900/nuc900-ac97.c b/sound/soc/nuc900/nuc900-ac97.c new file mode 100644 index 000000000000..f7b44e081420 --- /dev/null +++ b/sound/soc/nuc900/nuc900-ac97.c | |||
@@ -0,0 +1,442 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2009-2010 Nuvoton technology corporation. | ||
3 | * | ||
4 | * Wan ZongShun <mcuos.com@gmail.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation;version 2 of the License. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/slab.h> | ||
15 | #include <linux/device.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <linux/mutex.h> | ||
18 | #include <linux/suspend.h> | ||
19 | #include <sound/core.h> | ||
20 | #include <sound/pcm.h> | ||
21 | #include <sound/initval.h> | ||
22 | #include <sound/soc.h> | ||
23 | #include <linux/device.h> | ||
24 | #include <linux/clk.h> | ||
25 | |||
26 | #include <mach/mfp.h> | ||
27 | |||
28 | #include "nuc900-auido.h" | ||
29 | |||
30 | static DEFINE_MUTEX(ac97_mutex); | ||
31 | struct nuc900_audio *nuc900_ac97_data; | ||
32 | |||
33 | static int nuc900_checkready(void) | ||
34 | { | ||
35 | struct nuc900_audio *nuc900_audio = nuc900_ac97_data; | ||
36 | |||
37 | if (!(AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS0) & CODEC_READY)) | ||
38 | return -EPERM; | ||
39 | |||
40 | return 0; | ||
41 | } | ||
42 | |||
43 | /* AC97 controller reads codec register */ | ||
44 | static unsigned short nuc900_ac97_read(struct snd_ac97 *ac97, | ||
45 | unsigned short reg) | ||
46 | { | ||
47 | struct nuc900_audio *nuc900_audio = nuc900_ac97_data; | ||
48 | unsigned long timeout = 0x10000, val; | ||
49 | |||
50 | mutex_lock(&ac97_mutex); | ||
51 | |||
52 | val = nuc900_checkready(); | ||
53 | if (!!val) { | ||
54 | dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); | ||
55 | goto out; | ||
56 | } | ||
57 | |||
58 | /* set the R_WB bit and write register index */ | ||
59 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, R_WB | reg); | ||
60 | |||
61 | /* set the valid frame bit and valid slots */ | ||
62 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); | ||
63 | val |= (VALID_FRAME | SLOT1_VALID); | ||
64 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val); | ||
65 | |||
66 | udelay(100); | ||
67 | |||
68 | /* polling the AC_R_FINISH */ | ||
69 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); | ||
70 | val &= AC_R_FINISH; | ||
71 | while (!val && timeout--) | ||
72 | mdelay(1); | ||
73 | |||
74 | if (!timeout) { | ||
75 | dev_err(nuc900_audio->dev, "AC97 read register time out !\n"); | ||
76 | val = -EPERM; | ||
77 | goto out; | ||
78 | } | ||
79 | |||
80 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0) ; | ||
81 | val &= ~SLOT1_VALID; | ||
82 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, val); | ||
83 | |||
84 | if (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS1) >> 2 != reg) { | ||
85 | dev_err(nuc900_audio->dev, | ||
86 | "R_INDEX of REG_ACTL_ACIS1 not match!\n"); | ||
87 | } | ||
88 | |||
89 | udelay(100); | ||
90 | val = (AUDIO_READ(nuc900_audio->mmio + ACTL_ACIS2) & 0xFFFF); | ||
91 | |||
92 | out: | ||
93 | mutex_unlock(&ac97_mutex); | ||
94 | return val; | ||
95 | } | ||
96 | |||
97 | /* AC97 controller writes to codec register */ | ||
98 | static void nuc900_ac97_write(struct snd_ac97 *ac97, unsigned short reg, | ||
99 | unsigned short val) | ||
100 | { | ||
101 | struct nuc900_audio *nuc900_audio = nuc900_ac97_data; | ||
102 | unsigned long tmp, timeout = 0x10000; | ||
103 | |||
104 | mutex_lock(&ac97_mutex); | ||
105 | |||
106 | tmp = nuc900_checkready(); | ||
107 | if (!!tmp) | ||
108 | dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); | ||
109 | |||
110 | /* clear the R_WB bit and write register index */ | ||
111 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS1, reg); | ||
112 | |||
113 | /* write register value */ | ||
114 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS2, val); | ||
115 | |||
116 | /* set the valid frame bit and valid slots */ | ||
117 | tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); | ||
118 | tmp |= SLOT1_VALID | SLOT2_VALID | VALID_FRAME; | ||
119 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); | ||
120 | |||
121 | udelay(100); | ||
122 | |||
123 | /* polling the AC_W_FINISH */ | ||
124 | tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); | ||
125 | tmp &= AC_W_FINISH; | ||
126 | while (tmp && timeout--) | ||
127 | mdelay(1); | ||
128 | |||
129 | if (!timeout) | ||
130 | dev_err(nuc900_audio->dev, "AC97 write register time out !\n"); | ||
131 | |||
132 | tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); | ||
133 | tmp &= ~(SLOT1_VALID | SLOT2_VALID); | ||
134 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); | ||
135 | |||
136 | mutex_unlock(&ac97_mutex); | ||
137 | |||
138 | } | ||
139 | |||
140 | static void nuc900_ac97_warm_reset(struct snd_ac97 *ac97) | ||
141 | { | ||
142 | struct nuc900_audio *nuc900_audio = nuc900_ac97_data; | ||
143 | unsigned long val; | ||
144 | |||
145 | mutex_lock(&ac97_mutex); | ||
146 | |||
147 | /* warm reset AC 97 */ | ||
148 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); | ||
149 | val |= AC_W_RES; | ||
150 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); | ||
151 | |||
152 | udelay(1000); | ||
153 | |||
154 | val = nuc900_checkready(); | ||
155 | if (!!val) | ||
156 | dev_err(nuc900_audio->dev, "AC97 codec is not ready\n"); | ||
157 | |||
158 | mutex_unlock(&ac97_mutex); | ||
159 | } | ||
160 | |||
161 | static void nuc900_ac97_cold_reset(struct snd_ac97 *ac97) | ||
162 | { | ||
163 | struct nuc900_audio *nuc900_audio = nuc900_ac97_data; | ||
164 | unsigned long val; | ||
165 | |||
166 | mutex_lock(&ac97_mutex); | ||
167 | |||
168 | /* reset Audio Controller */ | ||
169 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); | ||
170 | val |= ACTL_RESET_BIT; | ||
171 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); | ||
172 | |||
173 | udelay(1000); | ||
174 | |||
175 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); | ||
176 | val &= (~ACTL_RESET_BIT); | ||
177 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); | ||
178 | |||
179 | udelay(1000); | ||
180 | |||
181 | /* reset AC-link interface */ | ||
182 | |||
183 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); | ||
184 | val |= AC_RESET; | ||
185 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); | ||
186 | |||
187 | udelay(1000); | ||
188 | |||
189 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); | ||
190 | val &= ~AC_RESET; | ||
191 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); | ||
192 | |||
193 | udelay(1000); | ||
194 | |||
195 | /* cold reset AC 97 */ | ||
196 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); | ||
197 | val |= AC_C_RES; | ||
198 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); | ||
199 | |||
200 | udelay(1000); | ||
201 | |||
202 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_ACCON); | ||
203 | val &= (~AC_C_RES); | ||
204 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACCON, val); | ||
205 | |||
206 | udelay(1000); | ||
207 | |||
208 | mutex_unlock(&ac97_mutex); | ||
209 | |||
210 | } | ||
211 | |||
212 | /* AC97 controller operations */ | ||
213 | struct snd_ac97_bus_ops soc_ac97_ops = { | ||
214 | .read = nuc900_ac97_read, | ||
215 | .write = nuc900_ac97_write, | ||
216 | .reset = nuc900_ac97_cold_reset, | ||
217 | .warm_reset = nuc900_ac97_warm_reset, | ||
218 | } | ||
219 | EXPORT_SYMBOL_GPL(soc_ac97_ops); | ||
220 | |||
221 | static int nuc900_ac97_trigger(struct snd_pcm_substream *substream, | ||
222 | int cmd, struct snd_soc_dai *dai) | ||
223 | { | ||
224 | struct nuc900_audio *nuc900_audio = nuc900_ac97_data; | ||
225 | int ret, stype = SUBSTREAM_TYPE(substream); | ||
226 | unsigned long val, tmp; | ||
227 | |||
228 | ret = 0; | ||
229 | |||
230 | switch (cmd) { | ||
231 | case SNDRV_PCM_TRIGGER_START: | ||
232 | case SNDRV_PCM_TRIGGER_RESUME: | ||
233 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); | ||
234 | if (PCM_TX == stype) { | ||
235 | tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); | ||
236 | tmp |= (SLOT3_VALID | SLOT4_VALID | VALID_FRAME); | ||
237 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); | ||
238 | |||
239 | tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); | ||
240 | tmp |= (P_DMA_END_IRQ | P_DMA_MIDDLE_IRQ); | ||
241 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, tmp); | ||
242 | val |= AC_PLAY; | ||
243 | } else { | ||
244 | tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); | ||
245 | tmp |= (R_DMA_END_IRQ | R_DMA_MIDDLE_IRQ); | ||
246 | |||
247 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, tmp); | ||
248 | val |= AC_RECORD; | ||
249 | } | ||
250 | |||
251 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); | ||
252 | |||
253 | break; | ||
254 | case SNDRV_PCM_TRIGGER_STOP: | ||
255 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
256 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); | ||
257 | if (PCM_TX == stype) { | ||
258 | tmp = AUDIO_READ(nuc900_audio->mmio + ACTL_ACOS0); | ||
259 | tmp &= ~(SLOT3_VALID | SLOT4_VALID); | ||
260 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_ACOS0, tmp); | ||
261 | |||
262 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, RESET_PRSR); | ||
263 | val &= ~AC_PLAY; | ||
264 | } else { | ||
265 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, RESET_PRSR); | ||
266 | val &= ~AC_RECORD; | ||
267 | } | ||
268 | |||
269 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); | ||
270 | |||
271 | break; | ||
272 | default: | ||
273 | ret = -EINVAL; | ||
274 | } | ||
275 | |||
276 | return ret; | ||
277 | } | ||
278 | |||
279 | static int nuc900_ac97_probe(struct platform_device *pdev, | ||
280 | struct snd_soc_dai *dai) | ||
281 | { | ||
282 | struct nuc900_audio *nuc900_audio = nuc900_ac97_data; | ||
283 | unsigned long val; | ||
284 | |||
285 | mutex_lock(&ac97_mutex); | ||
286 | |||
287 | /* enable unit clock */ | ||
288 | clk_enable(nuc900_audio->clk); | ||
289 | |||
290 | /* enable audio controller and AC-link interface */ | ||
291 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); | ||
292 | val |= (IIS_AC_PIN_SEL | ACLINK_EN); | ||
293 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); | ||
294 | |||
295 | mutex_unlock(&ac97_mutex); | ||
296 | |||
297 | return 0; | ||
298 | } | ||
299 | |||
300 | static void nuc900_ac97_remove(struct platform_device *pdev, | ||
301 | struct snd_soc_dai *dai) | ||
302 | { | ||
303 | struct nuc900_audio *nuc900_audio = nuc900_ac97_data; | ||
304 | |||
305 | clk_disable(nuc900_audio->clk); | ||
306 | } | ||
307 | |||
308 | static struct snd_soc_dai_ops nuc900_ac97_dai_ops = { | ||
309 | .trigger = nuc900_ac97_trigger, | ||
310 | }; | ||
311 | |||
312 | struct snd_soc_dai nuc900_ac97_dai = { | ||
313 | .name = "nuc900-ac97", | ||
314 | .probe = nuc900_ac97_probe, | ||
315 | .remove = nuc900_ac97_remove, | ||
316 | .ac97_control = 1, | ||
317 | .playback = { | ||
318 | .rates = SNDRV_PCM_RATE_8000_48000, | ||
319 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
320 | .channels_min = 1, | ||
321 | .channels_max = 2, | ||
322 | }, | ||
323 | .capture = { | ||
324 | .rates = SNDRV_PCM_RATE_8000_48000, | ||
325 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
326 | .channels_min = 1, | ||
327 | .channels_max = 2, | ||
328 | }, | ||
329 | .ops = &nuc900_ac97_dai_ops, | ||
330 | } | ||
331 | EXPORT_SYMBOL_GPL(nuc900_ac97_dai); | ||
332 | |||
333 | static int __devinit nuc900_ac97_drvprobe(struct platform_device *pdev) | ||
334 | { | ||
335 | struct nuc900_audio *nuc900_audio; | ||
336 | int ret; | ||
337 | |||
338 | if (nuc900_ac97_data) | ||
339 | return -EBUSY; | ||
340 | |||
341 | nuc900_audio = kzalloc(sizeof(struct nuc900_audio), GFP_KERNEL); | ||
342 | if (!nuc900_audio) | ||
343 | return -ENOMEM; | ||
344 | |||
345 | spin_lock_init(&nuc900_audio->lock); | ||
346 | |||
347 | nuc900_audio->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
348 | if (!nuc900_audio->res) { | ||
349 | ret = -ENODEV; | ||
350 | goto out0; | ||
351 | } | ||
352 | |||
353 | if (!request_mem_region(nuc900_audio->res->start, | ||
354 | resource_size(nuc900_audio->res), pdev->name)) { | ||
355 | ret = -EBUSY; | ||
356 | goto out0; | ||
357 | } | ||
358 | |||
359 | nuc900_audio->mmio = ioremap(nuc900_audio->res->start, | ||
360 | resource_size(nuc900_audio->res)); | ||
361 | if (!nuc900_audio->mmio) { | ||
362 | ret = -ENOMEM; | ||
363 | goto out1; | ||
364 | } | ||
365 | |||
366 | nuc900_audio->clk = clk_get(&pdev->dev, NULL); | ||
367 | if (IS_ERR(nuc900_audio->clk)) { | ||
368 | ret = PTR_ERR(nuc900_audio->clk); | ||
369 | goto out2; | ||
370 | } | ||
371 | |||
372 | nuc900_audio->irq_num = platform_get_irq(pdev, 0); | ||
373 | if (!nuc900_audio->irq_num) { | ||
374 | ret = -EBUSY; | ||
375 | goto out2; | ||
376 | } | ||
377 | |||
378 | nuc900_ac97_data = nuc900_audio; | ||
379 | |||
380 | nuc900_audio->dev = nuc900_ac97_dai.dev = &pdev->dev; | ||
381 | |||
382 | ret = snd_soc_register_dai(&nuc900_ac97_dai); | ||
383 | if (ret) | ||
384 | goto out3; | ||
385 | |||
386 | mfp_set_groupg(nuc900_audio->dev); /* enbale ac97 multifunction pin*/ | ||
387 | |||
388 | return 0; | ||
389 | |||
390 | out3: | ||
391 | clk_put(nuc900_audio->clk); | ||
392 | out2: | ||
393 | iounmap(nuc900_audio->mmio); | ||
394 | out1: | ||
395 | release_mem_region(nuc900_audio->res->start, | ||
396 | resource_size(nuc900_audio->res)); | ||
397 | out0: | ||
398 | kfree(nuc900_audio); | ||
399 | return ret; | ||
400 | } | ||
401 | |||
402 | static int __devexit nuc900_ac97_drvremove(struct platform_device *pdev) | ||
403 | { | ||
404 | |||
405 | snd_soc_unregister_dai(&nuc900_ac97_dai); | ||
406 | |||
407 | clk_put(nuc900_ac97_data->clk); | ||
408 | iounmap(nuc900_ac97_data->mmio); | ||
409 | release_mem_region(nuc900_ac97_data->res->start, | ||
410 | resource_size(nuc900_ac97_data->res)); | ||
411 | |||
412 | nuc900_ac97_data = NULL; | ||
413 | |||
414 | return 0; | ||
415 | } | ||
416 | |||
417 | static struct platform_driver nuc900_ac97_driver = { | ||
418 | .driver = { | ||
419 | .name = "nuc900-audio", | ||
420 | .owner = THIS_MODULE, | ||
421 | }, | ||
422 | .probe = nuc900_ac97_drvprobe, | ||
423 | .remove = __devexit_p(nuc900_ac97_drvremove), | ||
424 | }; | ||
425 | |||
426 | static int __init nuc900_ac97_init(void) | ||
427 | { | ||
428 | return platform_driver_register(&nuc900_ac97_driver); | ||
429 | } | ||
430 | |||
431 | static void __exit nuc900_ac97_exit(void) | ||
432 | { | ||
433 | platform_driver_unregister(&nuc900_ac97_driver); | ||
434 | } | ||
435 | |||
436 | module_init(nuc900_ac97_init); | ||
437 | module_exit(nuc900_ac97_exit); | ||
438 | |||
439 | MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); | ||
440 | MODULE_DESCRIPTION("NUC900 AC97 SoC driver!"); | ||
441 | MODULE_LICENSE("GPL"); | ||
442 | MODULE_ALIAS("platform:nuc900-ac97"); | ||
diff --git a/sound/soc/nuc900/nuc900-audio.c b/sound/soc/nuc900/nuc900-audio.c new file mode 100644 index 000000000000..b33d5b844d71 --- /dev/null +++ b/sound/soc/nuc900/nuc900-audio.c | |||
@@ -0,0 +1,81 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Nuvoton technology corporation. | ||
3 | * | ||
4 | * Wan ZongShun <mcuos.com@gmail.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation;version 2 of the License. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/moduleparam.h> | ||
14 | #include <linux/timer.h> | ||
15 | #include <linux/interrupt.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | |||
18 | #include <sound/core.h> | ||
19 | #include <sound/pcm.h> | ||
20 | #include <sound/soc.h> | ||
21 | #include <sound/soc-dapm.h> | ||
22 | |||
23 | #include "../codecs/ac97.h" | ||
24 | #include "nuc900-auido.h" | ||
25 | |||
26 | static struct snd_soc_dai_link nuc900evb_ac97_dai = { | ||
27 | .name = "AC97", | ||
28 | .stream_name = "AC97 HiFi", | ||
29 | .cpu_dai = &nuc900_ac97_dai, | ||
30 | .codec_dai = &ac97_dai, | ||
31 | }; | ||
32 | |||
33 | static struct snd_soc_card nuc900evb_audio_machine = { | ||
34 | .name = "NUC900EVB_AC97", | ||
35 | .dai_link = &nuc900evb_ac97_dai, | ||
36 | .num_links = 1, | ||
37 | .platform = &nuc900_soc_platform, | ||
38 | }; | ||
39 | |||
40 | static struct snd_soc_device nuc900evb_ac97_devdata = { | ||
41 | .card = &nuc900evb_audio_machine, | ||
42 | .codec_dev = &soc_codec_dev_ac97, | ||
43 | }; | ||
44 | |||
45 | static struct platform_device *nuc900evb_asoc_dev; | ||
46 | |||
47 | static int __init nuc900evb_audio_init(void) | ||
48 | { | ||
49 | int ret; | ||
50 | |||
51 | ret = -ENOMEM; | ||
52 | nuc900evb_asoc_dev = platform_device_alloc("soc-audio", -1); | ||
53 | if (!nuc900evb_asoc_dev) | ||
54 | goto out; | ||
55 | |||
56 | /* nuc900 board audio device */ | ||
57 | platform_set_drvdata(nuc900evb_asoc_dev, &nuc900evb_ac97_devdata); | ||
58 | |||
59 | nuc900evb_ac97_devdata.dev = &nuc900evb_asoc_dev->dev; | ||
60 | ret = platform_device_add(nuc900evb_asoc_dev); | ||
61 | |||
62 | if (ret) { | ||
63 | platform_device_put(nuc900evb_asoc_dev); | ||
64 | nuc900evb_asoc_dev = NULL; | ||
65 | } | ||
66 | |||
67 | out: | ||
68 | return ret; | ||
69 | } | ||
70 | |||
71 | static void __exit nuc900evb_audio_exit(void) | ||
72 | { | ||
73 | platform_device_unregister(nuc900evb_asoc_dev); | ||
74 | } | ||
75 | |||
76 | module_init(nuc900evb_audio_init); | ||
77 | module_exit(nuc900evb_audio_exit); | ||
78 | |||
79 | MODULE_LICENSE("GPL"); | ||
80 | MODULE_DESCRIPTION("NUC900 Series ASoC audio support"); | ||
81 | MODULE_AUTHOR("Wan ZongShun"); | ||
diff --git a/sound/soc/nuc900/nuc900-auido.h b/sound/soc/nuc900/nuc900-auido.h new file mode 100644 index 000000000000..95ac4ef2f353 --- /dev/null +++ b/sound/soc/nuc900/nuc900-auido.h | |||
@@ -0,0 +1,121 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Nuvoton technology corporation. | ||
3 | * | ||
4 | * Wan ZongShun <mcuos.com@gmail.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation;version 2 of the License. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #ifndef _NUC900_AUDIO_H | ||
13 | #define _NUC900_AUDIO_H | ||
14 | |||
15 | #include <linux/io.h> | ||
16 | |||
17 | /* Audio Control Registers */ | ||
18 | #define ACTL_CON 0x00 | ||
19 | #define ACTL_RESET 0x04 | ||
20 | #define ACTL_RDSTB 0x08 | ||
21 | #define ACTL_RDST_LENGTH 0x0C | ||
22 | #define ACTL_RDSTC 0x10 | ||
23 | #define ACTL_RSR 0x14 | ||
24 | #define ACTL_PDSTB 0x18 | ||
25 | #define ACTL_PDST_LENGTH 0x1C | ||
26 | #define ACTL_PDSTC 0x20 | ||
27 | #define ACTL_PSR 0x24 | ||
28 | #define ACTL_IISCON 0x28 | ||
29 | #define ACTL_ACCON 0x2C | ||
30 | #define ACTL_ACOS0 0x30 | ||
31 | #define ACTL_ACOS1 0x34 | ||
32 | #define ACTL_ACOS2 0x38 | ||
33 | #define ACTL_ACIS0 0x3C | ||
34 | #define ACTL_ACIS1 0x40 | ||
35 | #define ACTL_ACIS2 0x44 | ||
36 | #define ACTL_COUNTER 0x48 | ||
37 | |||
38 | /* bit definition of REG_ACTL_CON register */ | ||
39 | #define R_DMA_IRQ 0x1000 | ||
40 | #define T_DMA_IRQ 0x0800 | ||
41 | #define IIS_AC_PIN_SEL 0x0100 | ||
42 | #define FIFO_TH 0x0080 | ||
43 | #define ADC_EN 0x0010 | ||
44 | #define M80_EN 0x0008 | ||
45 | #define ACLINK_EN 0x0004 | ||
46 | #define IIS_EN 0x0002 | ||
47 | |||
48 | /* bit definition of REG_ACTL_RESET register */ | ||
49 | #define W5691_PLAY 0x20000 | ||
50 | #define ACTL_RESET_BIT 0x10000 | ||
51 | #define RECORD_RIGHT_CHNNEL 0x08000 | ||
52 | #define RECORD_LEFT_CHNNEL 0x04000 | ||
53 | #define PLAY_RIGHT_CHNNEL 0x02000 | ||
54 | #define PLAY_LEFT_CHNNEL 0x01000 | ||
55 | #define DAC_PLAY 0x00800 | ||
56 | #define ADC_RECORD 0x00400 | ||
57 | #define M80_PLAY 0x00200 | ||
58 | #define AC_RECORD 0x00100 | ||
59 | #define AC_PLAY 0x00080 | ||
60 | #define IIS_RECORD 0x00040 | ||
61 | #define IIS_PLAY 0x00020 | ||
62 | #define DAC_RESET 0x00010 | ||
63 | #define ADC_RESET 0x00008 | ||
64 | #define M80_RESET 0x00004 | ||
65 | #define AC_RESET 0x00002 | ||
66 | #define IIS_RESET 0x00001 | ||
67 | |||
68 | /* bit definition of REG_ACTL_ACCON register */ | ||
69 | #define AC_BCLK_PU_EN 0x20 | ||
70 | #define AC_R_FINISH 0x10 | ||
71 | #define AC_W_FINISH 0x08 | ||
72 | #define AC_W_RES 0x04 | ||
73 | #define AC_C_RES 0x02 | ||
74 | |||
75 | /* bit definition of ACTL_RSR register */ | ||
76 | #define R_FIFO_EMPTY 0x04 | ||
77 | #define R_DMA_END_IRQ 0x02 | ||
78 | #define R_DMA_MIDDLE_IRQ 0x01 | ||
79 | |||
80 | /* bit definition of ACTL_PSR register */ | ||
81 | #define P_FIFO_EMPTY 0x04 | ||
82 | #define P_DMA_END_IRQ 0x02 | ||
83 | #define P_DMA_MIDDLE_IRQ 0x01 | ||
84 | |||
85 | /* bit definition of ACTL_ACOS0 register */ | ||
86 | #define SLOT1_VALID 0x01 | ||
87 | #define SLOT2_VALID 0x02 | ||
88 | #define SLOT3_VALID 0x04 | ||
89 | #define SLOT4_VALID 0x08 | ||
90 | #define VALID_FRAME 0x10 | ||
91 | |||
92 | /* bit definition of ACTL_ACOS1 register */ | ||
93 | #define R_WB 0x80 | ||
94 | |||
95 | #define CODEC_READY 0x10 | ||
96 | #define RESET_PRSR 0x00 | ||
97 | #define AUDIO_WRITE(addr, val) __raw_writel(val, addr) | ||
98 | #define AUDIO_READ(addr) __raw_readl(addr) | ||
99 | #define PCM_TX 0 | ||
100 | #define PCM_RX 1 | ||
101 | #define SUBSTREAM_TYPE(substream) \ | ||
102 | ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX) | ||
103 | |||
104 | struct nuc900_audio { | ||
105 | void __iomem *mmio; | ||
106 | spinlock_t lock; | ||
107 | dma_addr_t dma_addr[2]; | ||
108 | unsigned long buffersize[2]; | ||
109 | unsigned long irq_num; | ||
110 | struct snd_pcm_substream *substream; | ||
111 | struct resource *res; | ||
112 | struct clk *clk; | ||
113 | struct device *dev; | ||
114 | |||
115 | }; | ||
116 | |||
117 | extern struct nuc900_audio *nuc900_ac97_data; | ||
118 | extern struct snd_soc_dai nuc900_ac97_dai; | ||
119 | extern struct snd_soc_platform nuc900_soc_platform; | ||
120 | |||
121 | #endif /*end _NUC900_AUDIO_H */ | ||
diff --git a/sound/soc/nuc900/nuc900-pcm.c b/sound/soc/nuc900/nuc900-pcm.c new file mode 100644 index 000000000000..32a503c1c4be --- /dev/null +++ b/sound/soc/nuc900/nuc900-pcm.c | |||
@@ -0,0 +1,352 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Nuvoton technology corporation. | ||
3 | * | ||
4 | * Wan ZongShun <mcuos.com@gmail.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation;version 2 of the License. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/io.h> | ||
15 | #include <linux/platform_device.h> | ||
16 | #include <linux/slab.h> | ||
17 | #include <linux/dma-mapping.h> | ||
18 | |||
19 | #include <sound/core.h> | ||
20 | #include <sound/pcm.h> | ||
21 | #include <sound/pcm_params.h> | ||
22 | #include <sound/soc.h> | ||
23 | |||
24 | #include <mach/hardware.h> | ||
25 | |||
26 | #include "nuc900-auido.h" | ||
27 | |||
28 | static const struct snd_pcm_hardware nuc900_pcm_hardware = { | ||
29 | .info = SNDRV_PCM_INFO_INTERLEAVED | | ||
30 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
31 | SNDRV_PCM_INFO_MMAP | | ||
32 | SNDRV_PCM_INFO_MMAP_VALID | | ||
33 | SNDRV_PCM_INFO_PAUSE | | ||
34 | SNDRV_PCM_INFO_RESUME, | ||
35 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | ||
36 | .channels_min = 1, | ||
37 | .channels_max = 2, | ||
38 | .buffer_bytes_max = 4*1024, | ||
39 | .period_bytes_min = 1*1024, | ||
40 | .period_bytes_max = 4*1024, | ||
41 | .periods_min = 1, | ||
42 | .periods_max = 1024, | ||
43 | }; | ||
44 | |||
45 | static int nuc900_dma_hw_params(struct snd_pcm_substream *substream, | ||
46 | struct snd_pcm_hw_params *params) | ||
47 | { | ||
48 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
49 | struct nuc900_audio *nuc900_audio = runtime->private_data; | ||
50 | unsigned long flags, stype = SUBSTREAM_TYPE(substream); | ||
51 | int ret = 0; | ||
52 | |||
53 | spin_lock_irqsave(&nuc900_audio->lock, flags); | ||
54 | |||
55 | ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); | ||
56 | if (ret < 0) | ||
57 | return ret; | ||
58 | |||
59 | nuc900_audio->substream = substream; | ||
60 | nuc900_audio->dma_addr[stype] = runtime->dma_addr; | ||
61 | nuc900_audio->buffersize[stype] = params_buffer_bytes(params); | ||
62 | |||
63 | spin_unlock_irqrestore(&nuc900_audio->lock, flags); | ||
64 | |||
65 | return ret; | ||
66 | } | ||
67 | |||
68 | static void nuc900_update_dma_register(struct snd_pcm_substream *substream, | ||
69 | dma_addr_t dma_addr, size_t count) | ||
70 | { | ||
71 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
72 | struct nuc900_audio *nuc900_audio = runtime->private_data; | ||
73 | void __iomem *mmio_addr, *mmio_len; | ||
74 | |||
75 | if (SUBSTREAM_TYPE(substream) == PCM_TX) { | ||
76 | mmio_addr = nuc900_audio->mmio + ACTL_PDSTB; | ||
77 | mmio_len = nuc900_audio->mmio + ACTL_PDST_LENGTH; | ||
78 | } else { | ||
79 | mmio_addr = nuc900_audio->mmio + ACTL_RDSTB; | ||
80 | mmio_len = nuc900_audio->mmio + ACTL_RDST_LENGTH; | ||
81 | } | ||
82 | |||
83 | AUDIO_WRITE(mmio_addr, dma_addr); | ||
84 | AUDIO_WRITE(mmio_len, count); | ||
85 | } | ||
86 | |||
87 | static void nuc900_dma_start(struct snd_pcm_substream *substream) | ||
88 | { | ||
89 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
90 | struct nuc900_audio *nuc900_audio = runtime->private_data; | ||
91 | unsigned long val; | ||
92 | |||
93 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); | ||
94 | val |= (T_DMA_IRQ | R_DMA_IRQ); | ||
95 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); | ||
96 | } | ||
97 | |||
98 | static void nuc900_dma_stop(struct snd_pcm_substream *substream) | ||
99 | { | ||
100 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
101 | struct nuc900_audio *nuc900_audio = runtime->private_data; | ||
102 | unsigned long val; | ||
103 | |||
104 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); | ||
105 | val &= ~(T_DMA_IRQ | R_DMA_IRQ); | ||
106 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val); | ||
107 | } | ||
108 | |||
109 | static irqreturn_t nuc900_dma_interrupt(int irq, void *dev_id) | ||
110 | { | ||
111 | struct snd_pcm_substream *substream = dev_id; | ||
112 | struct nuc900_audio *nuc900_audio = substream->runtime->private_data; | ||
113 | unsigned long val; | ||
114 | |||
115 | spin_lock(&nuc900_audio->lock); | ||
116 | |||
117 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_CON); | ||
118 | |||
119 | if (val & R_DMA_IRQ) { | ||
120 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | R_DMA_IRQ); | ||
121 | |||
122 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_RSR); | ||
123 | |||
124 | if (val & R_DMA_MIDDLE_IRQ) { | ||
125 | val |= R_DMA_MIDDLE_IRQ; | ||
126 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); | ||
127 | } | ||
128 | |||
129 | if (val & R_DMA_END_IRQ) { | ||
130 | val |= R_DMA_END_IRQ; | ||
131 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RSR, val); | ||
132 | } | ||
133 | } else if (val & T_DMA_IRQ) { | ||
134 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_CON, val | T_DMA_IRQ); | ||
135 | |||
136 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_PSR); | ||
137 | |||
138 | if (val & P_DMA_MIDDLE_IRQ) { | ||
139 | val |= P_DMA_MIDDLE_IRQ; | ||
140 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); | ||
141 | } | ||
142 | |||
143 | if (val & P_DMA_END_IRQ) { | ||
144 | val |= P_DMA_END_IRQ; | ||
145 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_PSR, val); | ||
146 | } | ||
147 | } else { | ||
148 | dev_err(nuc900_audio->dev, "Wrong DMA interrupt status!\n"); | ||
149 | spin_unlock(&nuc900_audio->lock); | ||
150 | return IRQ_HANDLED; | ||
151 | } | ||
152 | |||
153 | spin_unlock(&nuc900_audio->lock); | ||
154 | |||
155 | snd_pcm_period_elapsed(substream); | ||
156 | |||
157 | return IRQ_HANDLED; | ||
158 | } | ||
159 | |||
160 | static int nuc900_dma_hw_free(struct snd_pcm_substream *substream) | ||
161 | { | ||
162 | snd_pcm_lib_free_pages(substream); | ||
163 | return 0; | ||
164 | } | ||
165 | |||
166 | static int nuc900_dma_prepare(struct snd_pcm_substream *substream) | ||
167 | { | ||
168 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
169 | struct nuc900_audio *nuc900_audio = runtime->private_data; | ||
170 | unsigned long flags, val, stype = SUBSTREAM_TYPE(substream);; | ||
171 | |||
172 | spin_lock_irqsave(&nuc900_audio->lock, flags); | ||
173 | |||
174 | nuc900_update_dma_register(substream, | ||
175 | nuc900_audio->dma_addr[stype], nuc900_audio->buffersize[stype]); | ||
176 | |||
177 | val = AUDIO_READ(nuc900_audio->mmio + ACTL_RESET); | ||
178 | |||
179 | switch (runtime->channels) { | ||
180 | case 1: | ||
181 | if (PCM_TX == stype) { | ||
182 | val &= ~(PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); | ||
183 | val |= PLAY_RIGHT_CHNNEL; | ||
184 | } else { | ||
185 | val &= ~(RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); | ||
186 | val |= RECORD_RIGHT_CHNNEL; | ||
187 | } | ||
188 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); | ||
189 | break; | ||
190 | case 2: | ||
191 | if (PCM_TX == stype) | ||
192 | val |= (PLAY_LEFT_CHNNEL | PLAY_RIGHT_CHNNEL); | ||
193 | else | ||
194 | val |= (RECORD_LEFT_CHNNEL | RECORD_RIGHT_CHNNEL); | ||
195 | AUDIO_WRITE(nuc900_audio->mmio + ACTL_RESET, val); | ||
196 | break; | ||
197 | default: | ||
198 | return -EINVAL; | ||
199 | } | ||
200 | spin_unlock_irqrestore(&nuc900_audio->lock, flags); | ||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static int nuc900_dma_trigger(struct snd_pcm_substream *substream, int cmd) | ||
205 | { | ||
206 | int ret = 0; | ||
207 | |||
208 | switch (cmd) { | ||
209 | case SNDRV_PCM_TRIGGER_START: | ||
210 | case SNDRV_PCM_TRIGGER_RESUME: | ||
211 | nuc900_dma_start(substream); | ||
212 | break; | ||
213 | |||
214 | case SNDRV_PCM_TRIGGER_STOP: | ||
215 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
216 | nuc900_dma_stop(substream); | ||
217 | break; | ||
218 | |||
219 | default: | ||
220 | ret = -EINVAL; | ||
221 | break; | ||
222 | } | ||
223 | |||
224 | return ret; | ||
225 | } | ||
226 | |||
227 | int nuc900_dma_getposition(struct snd_pcm_substream *substream, | ||
228 | dma_addr_t *src, dma_addr_t *dst) | ||
229 | { | ||
230 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
231 | struct nuc900_audio *nuc900_audio = runtime->private_data; | ||
232 | |||
233 | if (src != NULL) | ||
234 | *src = AUDIO_READ(nuc900_audio->mmio + ACTL_PDSTC); | ||
235 | |||
236 | if (dst != NULL) | ||
237 | *dst = AUDIO_READ(nuc900_audio->mmio + ACTL_RDSTC); | ||
238 | |||
239 | return 0; | ||
240 | } | ||
241 | |||
242 | static snd_pcm_uframes_t nuc900_dma_pointer(struct snd_pcm_substream *substream) | ||
243 | { | ||
244 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
245 | dma_addr_t src, dst; | ||
246 | unsigned long res; | ||
247 | |||
248 | nuc900_dma_getposition(substream, &src, &dst); | ||
249 | |||
250 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) | ||
251 | res = dst - runtime->dma_addr; | ||
252 | else | ||
253 | res = src - runtime->dma_addr; | ||
254 | |||
255 | return bytes_to_frames(substream->runtime, res); | ||
256 | } | ||
257 | |||
258 | static int nuc900_dma_open(struct snd_pcm_substream *substream) | ||
259 | { | ||
260 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
261 | struct nuc900_audio *nuc900_audio; | ||
262 | |||
263 | snd_soc_set_runtime_hwparams(substream, &nuc900_pcm_hardware); | ||
264 | |||
265 | nuc900_audio = nuc900_ac97_data; | ||
266 | |||
267 | if (request_irq(nuc900_audio->irq_num, nuc900_dma_interrupt, | ||
268 | IRQF_DISABLED, "nuc900-dma", substream)) | ||
269 | return -EBUSY; | ||
270 | |||
271 | runtime->private_data = nuc900_audio; | ||
272 | |||
273 | return 0; | ||
274 | } | ||
275 | |||
276 | static int nuc900_dma_close(struct snd_pcm_substream *substream) | ||
277 | { | ||
278 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
279 | struct nuc900_audio *nuc900_audio = runtime->private_data; | ||
280 | |||
281 | free_irq(nuc900_audio->irq_num, substream); | ||
282 | |||
283 | return 0; | ||
284 | } | ||
285 | |||
286 | static int nuc900_dma_mmap(struct snd_pcm_substream *substream, | ||
287 | struct vm_area_struct *vma) | ||
288 | { | ||
289 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
290 | |||
291 | return dma_mmap_writecombine(substream->pcm->card->dev, vma, | ||
292 | runtime->dma_area, | ||
293 | runtime->dma_addr, | ||
294 | runtime->dma_bytes); | ||
295 | } | ||
296 | |||
297 | static struct snd_pcm_ops nuc900_dma_ops = { | ||
298 | .open = nuc900_dma_open, | ||
299 | .close = nuc900_dma_close, | ||
300 | .ioctl = snd_pcm_lib_ioctl, | ||
301 | .hw_params = nuc900_dma_hw_params, | ||
302 | .hw_free = nuc900_dma_hw_free, | ||
303 | .prepare = nuc900_dma_prepare, | ||
304 | .trigger = nuc900_dma_trigger, | ||
305 | .pointer = nuc900_dma_pointer, | ||
306 | .mmap = nuc900_dma_mmap, | ||
307 | }; | ||
308 | |||
309 | static void nuc900_dma_free_dma_buffers(struct snd_pcm *pcm) | ||
310 | { | ||
311 | snd_pcm_lib_preallocate_free_for_all(pcm); | ||
312 | } | ||
313 | |||
314 | static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32); | ||
315 | static int nuc900_dma_new(struct snd_card *card, | ||
316 | struct snd_soc_dai *dai, struct snd_pcm *pcm) | ||
317 | { | ||
318 | if (!card->dev->dma_mask) | ||
319 | card->dev->dma_mask = &nuc900_pcm_dmamask; | ||
320 | if (!card->dev->coherent_dma_mask) | ||
321 | card->dev->coherent_dma_mask = DMA_BIT_MASK(32); | ||
322 | |||
323 | snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, | ||
324 | card->dev, 4 * 1024, (4 * 1024) - 1); | ||
325 | |||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | struct snd_soc_platform nuc900_soc_platform = { | ||
330 | .name = "nuc900-dma", | ||
331 | .pcm_ops = &nuc900_dma_ops, | ||
332 | .pcm_new = nuc900_dma_new, | ||
333 | .pcm_free = nuc900_dma_free_dma_buffers, | ||
334 | } | ||
335 | EXPORT_SYMBOL_GPL(nuc900_soc_platform); | ||
336 | |||
337 | static int __init nuc900_soc_platform_init(void) | ||
338 | { | ||
339 | return snd_soc_register_platform(&nuc900_soc_platform); | ||
340 | } | ||
341 | |||
342 | static void __exit nuc900_soc_platform_exit(void) | ||
343 | { | ||
344 | snd_soc_unregister_platform(&nuc900_soc_platform); | ||
345 | } | ||
346 | |||
347 | module_init(nuc900_soc_platform_init); | ||
348 | module_exit(nuc900_soc_platform_exit); | ||
349 | |||
350 | MODULE_AUTHOR("Wan ZongShun, <mcuos.com@gmail.com>"); | ||
351 | MODULE_DESCRIPTION("nuc900 Audio DMA module"); | ||
352 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 2a7cc222d098..292d817c9a94 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig | |||
@@ -1,6 +1,6 @@ | |||
1 | config SND_S3C24XX_SOC | 1 | config SND_S3C24XX_SOC |
2 | tristate "SoC Audio for the Samsung S3CXXXX chips" | 2 | tristate "SoC Audio for the Samsung S3CXXXX chips" |
3 | depends on ARCH_S3C2410 || ARCH_S3C64XX | 3 | depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 |
4 | select S3C64XX_DMA if ARCH_S3C64XX | 4 | select S3C64XX_DMA if ARCH_S3C64XX |
5 | help | 5 | help |
6 | Say Y or M if you want to add support for codecs attached to | 6 | Say Y or M if you want to add support for codecs attached to |
@@ -120,7 +120,7 @@ config SND_S3C24XX_SOC_SIMTEC_HERMES | |||
120 | 120 | ||
121 | config SND_SOC_SMDK_WM9713 | 121 | config SND_SOC_SMDK_WM9713 |
122 | tristate "SoC AC97 Audio support for SMDK with WM9713" | 122 | tristate "SoC AC97 Audio support for SMDK with WM9713" |
123 | depends on SND_S3C24XX_SOC && MACH_SMDK6410 | 123 | depends on SND_S3C24XX_SOC && (MACH_SMDK6410 || MACH_SMDKC100 || MACH_SMDKV210 || MACH_SMDKC110) |
124 | select SND_SOC_WM9713 | 124 | select SND_SOC_WM9713 |
125 | select SND_S3C_SOC_AC97 | 125 | select SND_S3C_SOC_AC97 |
126 | help | 126 | help |
diff --git a/sound/soc/s3c24xx/smdk_wm9713.c b/sound/soc/s3c24xx/smdk_wm9713.c index 24fd39f38ccb..5527b9e88c98 100644 --- a/sound/soc/s3c24xx/smdk_wm9713.c +++ b/sound/soc/s3c24xx/smdk_wm9713.c | |||
@@ -25,6 +25,9 @@ static struct snd_soc_card smdk; | |||
25 | * Default CFG switch settings to use this driver: | 25 | * Default CFG switch settings to use this driver: |
26 | * | 26 | * |
27 | * SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off | 27 | * SMDK6410: Set CFG1 1-3 On, CFG2 1-4 Off |
28 | * SMDKC100: Set CFG6 1-3 On, CFG7 1 On | ||
29 | * SMDKC110: Set CFGB10 1-2 Off, CFGB12 1-3 On | ||
30 | * SMDKV210: Set CFGB10 1-2 Off, CFGB12 1-3 On | ||
28 | */ | 31 | */ |
29 | 32 | ||
30 | /* | 33 | /* |
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 998569d60330..254dd1c6914d 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c | |||
@@ -2353,6 +2353,101 @@ int snd_soc_limit_volume(struct snd_soc_codec *codec, | |||
2353 | EXPORT_SYMBOL_GPL(snd_soc_limit_volume); | 2353 | EXPORT_SYMBOL_GPL(snd_soc_limit_volume); |
2354 | 2354 | ||
2355 | /** | 2355 | /** |
2356 | * snd_soc_info_volsw_2r_sx - double with tlv and variable data size | ||
2357 | * mixer info callback | ||
2358 | * @kcontrol: mixer control | ||
2359 | * @uinfo: control element information | ||
2360 | * | ||
2361 | * Returns 0 for success. | ||
2362 | */ | ||
2363 | int snd_soc_info_volsw_2r_sx(struct snd_kcontrol *kcontrol, | ||
2364 | struct snd_ctl_elem_info *uinfo) | ||
2365 | { | ||
2366 | struct soc_mixer_control *mc = | ||
2367 | (struct soc_mixer_control *)kcontrol->private_value; | ||
2368 | int max = mc->max; | ||
2369 | int min = mc->min; | ||
2370 | |||
2371 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
2372 | uinfo->count = 2; | ||
2373 | uinfo->value.integer.min = 0; | ||
2374 | uinfo->value.integer.max = max-min; | ||
2375 | |||
2376 | return 0; | ||
2377 | } | ||
2378 | EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r_sx); | ||
2379 | |||
2380 | /** | ||
2381 | * snd_soc_get_volsw_2r_sx - double with tlv and variable data size | ||
2382 | * mixer get callback | ||
2383 | * @kcontrol: mixer control | ||
2384 | * @uinfo: control element information | ||
2385 | * | ||
2386 | * Returns 0 for success. | ||
2387 | */ | ||
2388 | int snd_soc_get_volsw_2r_sx(struct snd_kcontrol *kcontrol, | ||
2389 | struct snd_ctl_elem_value *ucontrol) | ||
2390 | { | ||
2391 | struct soc_mixer_control *mc = | ||
2392 | (struct soc_mixer_control *)kcontrol->private_value; | ||
2393 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
2394 | unsigned int mask = (1<<mc->shift)-1; | ||
2395 | int min = mc->min; | ||
2396 | int val = snd_soc_read(codec, mc->reg) & mask; | ||
2397 | int valr = snd_soc_read(codec, mc->rreg) & mask; | ||
2398 | |||
2399 | ucontrol->value.integer.value[0] = ((val & 0xff)-min); | ||
2400 | ucontrol->value.integer.value[1] = ((valr & 0xff)-min); | ||
2401 | return 0; | ||
2402 | } | ||
2403 | EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r_sx); | ||
2404 | |||
2405 | /** | ||
2406 | * snd_soc_put_volsw_2r_sx - double with tlv and variable data size | ||
2407 | * mixer put callback | ||
2408 | * @kcontrol: mixer control | ||
2409 | * @uinfo: control element information | ||
2410 | * | ||
2411 | * Returns 0 for success. | ||
2412 | */ | ||
2413 | int snd_soc_put_volsw_2r_sx(struct snd_kcontrol *kcontrol, | ||
2414 | struct snd_ctl_elem_value *ucontrol) | ||
2415 | { | ||
2416 | struct soc_mixer_control *mc = | ||
2417 | (struct soc_mixer_control *)kcontrol->private_value; | ||
2418 | struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); | ||
2419 | unsigned int mask = (1<<mc->shift)-1; | ||
2420 | int min = mc->min; | ||
2421 | int ret; | ||
2422 | unsigned int val, valr, oval, ovalr; | ||
2423 | |||
2424 | val = ((ucontrol->value.integer.value[0]+min) & 0xff); | ||
2425 | val &= mask; | ||
2426 | valr = ((ucontrol->value.integer.value[1]+min) & 0xff); | ||
2427 | valr &= mask; | ||
2428 | |||
2429 | oval = snd_soc_read(codec, mc->reg) & mask; | ||
2430 | ovalr = snd_soc_read(codec, mc->rreg) & mask; | ||
2431 | |||
2432 | ret = 0; | ||
2433 | if (oval != val) { | ||
2434 | ret = snd_soc_write(codec, mc->reg, val); | ||
2435 | if (ret < 0) | ||
2436 | return 0; | ||
2437 | ret = 1; | ||
2438 | } | ||
2439 | if (ovalr != valr) { | ||
2440 | ret = snd_soc_write(codec, mc->rreg, valr); | ||
2441 | if (ret < 0) | ||
2442 | return 0; | ||
2443 | ret = 1; | ||
2444 | } | ||
2445 | |||
2446 | return 0; | ||
2447 | } | ||
2448 | EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r_sx); | ||
2449 | |||
2450 | /** | ||
2356 | * snd_soc_dai_set_sysclk - configure DAI system or master clock. | 2451 | * snd_soc_dai_set_sysclk - configure DAI system or master clock. |
2357 | * @dai: DAI | 2452 | * @dai: DAI |
2358 | * @clk_id: DAI specific clock ID | 2453 | * @clk_id: DAI specific clock ID |