diff options
Diffstat (limited to 'sound/soc/blackfin')
-rw-r--r-- | sound/soc/blackfin/Kconfig | 23 | ||||
-rw-r--r-- | sound/soc/blackfin/Makefile | 6 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-ac97.c | 19 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-ad1938.c | 142 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-ad73311.c | 16 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-i2s.c | 18 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-sport.c | 4 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-ssm2602.c | 16 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-tdm-pcm.c | 330 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-tdm-pcm.h | 21 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-tdm.c | 343 | ||||
-rw-r--r-- | sound/soc/blackfin/bf5xx-tdm.h | 14 |
12 files changed, 917 insertions, 35 deletions
diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index 811596f4c092..8a4de4de30f2 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig | |||
@@ -7,6 +7,15 @@ config SND_BF5XX_I2S | |||
7 | mode (supports single stereo In/Out). | 7 | mode (supports single stereo In/Out). |
8 | You will also need to select the audio interfaces to support below. | 8 | You will also need to select the audio interfaces to support below. |
9 | 9 | ||
10 | config SND_BF5XX_TDM | ||
11 | tristate "SoC I2S(TDM mode) Audio for the ADI BF5xx chip" | ||
12 | depends on (BLACKFIN && SND_SOC) | ||
13 | help | ||
14 | Say Y or M if you want to add support for codecs attached to | ||
15 | the Blackfin SPORT (synchronous serial ports) interface in TDM | ||
16 | mode. | ||
17 | You will also need to select the audio interfaces to support below. | ||
18 | |||
10 | config SND_BF5XX_SOC_SSM2602 | 19 | config SND_BF5XX_SOC_SSM2602 |
11 | tristate "SoC SSM2602 Audio support for BF52x ezkit" | 20 | tristate "SoC SSM2602 Audio support for BF52x ezkit" |
12 | depends on SND_BF5XX_I2S | 21 | depends on SND_BF5XX_I2S |
@@ -69,6 +78,10 @@ config SND_BF5XX_SOC_I2S | |||
69 | tristate | 78 | tristate |
70 | select SND_BF5XX_SOC_SPORT | 79 | select SND_BF5XX_SOC_SPORT |
71 | 80 | ||
81 | config SND_BF5XX_SOC_TDM | ||
82 | tristate | ||
83 | select SND_BF5XX_SOC_SPORT | ||
84 | |||
72 | config SND_BF5XX_SOC_AC97 | 85 | config SND_BF5XX_SOC_AC97 |
73 | tristate | 86 | tristate |
74 | select AC97_BUS | 87 | select AC97_BUS |
@@ -83,9 +96,17 @@ config SND_BF5XX_SOC_AD1980 | |||
83 | help | 96 | help |
84 | Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT. | 97 | Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT. |
85 | 98 | ||
99 | config SND_BF5XX_SOC_AD1938 | ||
100 | tristate "SoC AD1938 Audio support for Blackfin" | ||
101 | depends on SND_BF5XX_TDM | ||
102 | select SND_BF5XX_SOC_TDM | ||
103 | select SND_SOC_AD1938 | ||
104 | help | ||
105 | Say Y if you want to add support for AD1938 codec on Blackfin. | ||
106 | |||
86 | config SND_BF5XX_SPORT_NUM | 107 | config SND_BF5XX_SPORT_NUM |
87 | int "Set a SPORT for Sound chip" | 108 | int "Set a SPORT for Sound chip" |
88 | depends on (SND_BF5XX_I2S || SND_BF5XX_AC97) | 109 | depends on (SND_BF5XX_I2S || SND_BF5XX_AC97 || SND_BF5XX_TDM) |
89 | range 0 3 if BF54x | 110 | range 0 3 if BF54x |
90 | range 0 1 if !BF54x | 111 | range 0 1 if !BF54x |
91 | default 0 | 112 | default 0 |
diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index 97bb37a6359c..f4d760741fab 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile | |||
@@ -1,21 +1,27 @@ | |||
1 | # Blackfin Platform Support | 1 | # Blackfin Platform Support |
2 | snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o | 2 | snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o |
3 | snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o | 3 | snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o |
4 | snd-bf5xx-tdm-objs := bf5xx-tdm-pcm.o | ||
4 | snd-soc-bf5xx-sport-objs := bf5xx-sport.o | 5 | snd-soc-bf5xx-sport-objs := bf5xx-sport.o |
5 | snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o | 6 | snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o |
6 | snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o | 7 | snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o |
8 | snd-soc-bf5xx-tdm-objs := bf5xx-tdm.o | ||
7 | 9 | ||
8 | obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o | 10 | obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o |
9 | obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o | 11 | obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o |
12 | obj-$(CONFIG_SND_BF5XX_TDM) += snd-bf5xx-tdm.o | ||
10 | obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o | 13 | obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o |
11 | obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o | 14 | obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o |
12 | obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o | 15 | obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o |
16 | obj-$(CONFIG_SND_BF5XX_SOC_TDM) += snd-soc-bf5xx-tdm.o | ||
13 | 17 | ||
14 | # Blackfin Machine Support | 18 | # Blackfin Machine Support |
15 | snd-ad1980-objs := bf5xx-ad1980.o | 19 | snd-ad1980-objs := bf5xx-ad1980.o |
16 | snd-ssm2602-objs := bf5xx-ssm2602.o | 20 | snd-ssm2602-objs := bf5xx-ssm2602.o |
17 | snd-ad73311-objs := bf5xx-ad73311.o | 21 | snd-ad73311-objs := bf5xx-ad73311.o |
22 | snd-ad1938-objs := bf5xx-ad1938.o | ||
18 | 23 | ||
19 | obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o | 24 | obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o |
20 | obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o | 25 | obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o |
21 | obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o | 26 | obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o |
27 | obj-$(CONFIG_SND_BF5XX_SOC_AD1938) += snd-ad1938.o | ||
diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c index 8a935f2d1767..2758b9017a7f 100644 --- a/sound/soc/blackfin/bf5xx-ac97.c +++ b/sound/soc/blackfin/bf5xx-ac97.c | |||
@@ -31,6 +31,15 @@ | |||
31 | #include "bf5xx-sport.h" | 31 | #include "bf5xx-sport.h" |
32 | #include "bf5xx-ac97.h" | 32 | #include "bf5xx-ac97.h" |
33 | 33 | ||
34 | /* Anomaly notes: | ||
35 | * 05000250 - AD1980 is running in TDM mode and RFS/TFS are generated by SPORT | ||
36 | * contrtoller. But, RFSDIV and TFSDIV are always set to 16*16-1, | ||
37 | * while the max AC97 data size is 13*16. The DIV is always larger | ||
38 | * than data size. AD73311 and ad2602 are not running in TDM mode. | ||
39 | * AD1836 and AD73322 depend on external RFS/TFS only. So, this | ||
40 | * anomaly does not affect blackfin sound drivers. | ||
41 | */ | ||
42 | |||
34 | static int *cmd_count; | 43 | static int *cmd_count; |
35 | static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; | 44 | static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; |
36 | 45 | ||
@@ -268,28 +277,24 @@ static int bf5xx_ac97_resume(struct snd_soc_dai *dai) | |||
268 | if (!dai->active) | 277 | if (!dai->active) |
269 | return 0; | 278 | return 0; |
270 | 279 | ||
271 | ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1); | 280 | ret = sport_set_multichannel(sport, 16, 0x1F, 1); |
272 | if (ret) { | 281 | if (ret) { |
273 | pr_err("SPORT is busy!\n"); | 282 | pr_err("SPORT is busy!\n"); |
274 | return -EBUSY; | 283 | return -EBUSY; |
275 | } | 284 | } |
276 | 285 | ||
277 | ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1)); | 286 | ret = sport_config_rx(sport, IRFS, 0xF, 0, (16*16-1)); |
278 | if (ret) { | 287 | if (ret) { |
279 | pr_err("SPORT is busy!\n"); | 288 | pr_err("SPORT is busy!\n"); |
280 | return -EBUSY; | 289 | return -EBUSY; |
281 | } | 290 | } |
282 | 291 | ||
283 | ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1)); | 292 | ret = sport_config_tx(sport, ITFS, 0xF, 0, (16*16-1)); |
284 | if (ret) { | 293 | if (ret) { |
285 | pr_err("SPORT is busy!\n"); | 294 | pr_err("SPORT is busy!\n"); |
286 | return -EBUSY; | 295 | return -EBUSY; |
287 | } | 296 | } |
288 | 297 | ||
289 | if (dai->capture.active) | ||
290 | sport_rx_start(sport); | ||
291 | if (dai->playback.active) | ||
292 | sport_tx_start(sport); | ||
293 | return 0; | 298 | return 0; |
294 | } | 299 | } |
295 | 300 | ||
diff --git a/sound/soc/blackfin/bf5xx-ad1938.c b/sound/soc/blackfin/bf5xx-ad1938.c new file mode 100644 index 000000000000..08269e91810c --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ad1938.c | |||
@@ -0,0 +1,142 @@ | |||
1 | /* | ||
2 | * File: sound/soc/blackfin/bf5xx-ad1938.c | ||
3 | * Author: Barry Song <Barry.Song@analog.com> | ||
4 | * | ||
5 | * Created: Thur June 4 2009 | ||
6 | * Description: Board driver for ad1938 sound chip | ||
7 | * | ||
8 | * Bugs: Enter bugs at http://blackfin.uclinux.org/ | ||
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 as published by | ||
12 | * the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU General Public License for more details. | ||
19 | * | ||
20 | * You should have received a copy of the GNU General Public License | ||
21 | * along with this program; if not, see the file COPYING, or write | ||
22 | * to the Free Software Foundation, Inc., | ||
23 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
24 | */ | ||
25 | |||
26 | #include <linux/module.h> | ||
27 | #include <linux/moduleparam.h> | ||
28 | #include <linux/device.h> | ||
29 | #include <sound/core.h> | ||
30 | #include <sound/pcm.h> | ||
31 | #include <sound/soc.h> | ||
32 | #include <sound/soc-dapm.h> | ||
33 | #include <sound/pcm_params.h> | ||
34 | |||
35 | #include <asm/blackfin.h> | ||
36 | #include <asm/cacheflush.h> | ||
37 | #include <asm/irq.h> | ||
38 | #include <asm/dma.h> | ||
39 | #include <asm/portmux.h> | ||
40 | |||
41 | #include "../codecs/ad1938.h" | ||
42 | #include "bf5xx-sport.h" | ||
43 | |||
44 | #include "bf5xx-tdm-pcm.h" | ||
45 | #include "bf5xx-tdm.h" | ||
46 | |||
47 | static struct snd_soc_card bf5xx_ad1938; | ||
48 | |||
49 | static int bf5xx_ad1938_startup(struct snd_pcm_substream *substream) | ||
50 | { | ||
51 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
52 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
53 | |||
54 | cpu_dai->private_data = sport_handle; | ||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | static int bf5xx_ad1938_hw_params(struct snd_pcm_substream *substream, | ||
59 | struct snd_pcm_hw_params *params) | ||
60 | { | ||
61 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
62 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
63 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; | ||
64 | int ret = 0; | ||
65 | /* set cpu DAI configuration */ | ||
66 | ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | | ||
67 | SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); | ||
68 | if (ret < 0) | ||
69 | return ret; | ||
70 | |||
71 | /* set codec DAI configuration */ | ||
72 | ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | | ||
73 | SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); | ||
74 | if (ret < 0) | ||
75 | return ret; | ||
76 | |||
77 | /* set codec DAI slots, 8 channels, all channels are enabled */ | ||
78 | ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 8); | ||
79 | if (ret < 0) | ||
80 | return ret; | ||
81 | |||
82 | return 0; | ||
83 | } | ||
84 | |||
85 | static struct snd_soc_ops bf5xx_ad1938_ops = { | ||
86 | .startup = bf5xx_ad1938_startup, | ||
87 | .hw_params = bf5xx_ad1938_hw_params, | ||
88 | }; | ||
89 | |||
90 | static struct snd_soc_dai_link bf5xx_ad1938_dai = { | ||
91 | .name = "ad1938", | ||
92 | .stream_name = "AD1938", | ||
93 | .cpu_dai = &bf5xx_tdm_dai, | ||
94 | .codec_dai = &ad1938_dai, | ||
95 | .ops = &bf5xx_ad1938_ops, | ||
96 | }; | ||
97 | |||
98 | static struct snd_soc_card bf5xx_ad1938 = { | ||
99 | .name = "bf5xx_ad1938", | ||
100 | .platform = &bf5xx_tdm_soc_platform, | ||
101 | .dai_link = &bf5xx_ad1938_dai, | ||
102 | .num_links = 1, | ||
103 | }; | ||
104 | |||
105 | static struct snd_soc_device bf5xx_ad1938_snd_devdata = { | ||
106 | .card = &bf5xx_ad1938, | ||
107 | .codec_dev = &soc_codec_dev_ad1938, | ||
108 | }; | ||
109 | |||
110 | static struct platform_device *bfxx_ad1938_snd_device; | ||
111 | |||
112 | static int __init bf5xx_ad1938_init(void) | ||
113 | { | ||
114 | int ret; | ||
115 | |||
116 | bfxx_ad1938_snd_device = platform_device_alloc("soc-audio", -1); | ||
117 | if (!bfxx_ad1938_snd_device) | ||
118 | return -ENOMEM; | ||
119 | |||
120 | platform_set_drvdata(bfxx_ad1938_snd_device, &bf5xx_ad1938_snd_devdata); | ||
121 | bf5xx_ad1938_snd_devdata.dev = &bfxx_ad1938_snd_device->dev; | ||
122 | ret = platform_device_add(bfxx_ad1938_snd_device); | ||
123 | |||
124 | if (ret) | ||
125 | platform_device_put(bfxx_ad1938_snd_device); | ||
126 | |||
127 | return ret; | ||
128 | } | ||
129 | |||
130 | static void __exit bf5xx_ad1938_exit(void) | ||
131 | { | ||
132 | platform_device_unregister(bfxx_ad1938_snd_device); | ||
133 | } | ||
134 | |||
135 | module_init(bf5xx_ad1938_init); | ||
136 | module_exit(bf5xx_ad1938_exit); | ||
137 | |||
138 | /* Module information */ | ||
139 | MODULE_AUTHOR("Barry Song"); | ||
140 | MODULE_DESCRIPTION("ALSA SoC AD1938 board driver"); | ||
141 | MODULE_LICENSE("GPL"); | ||
142 | |||
diff --git a/sound/soc/blackfin/bf5xx-ad73311.c b/sound/soc/blackfin/bf5xx-ad73311.c index edfbdc024e66..9825b71d0e28 100644 --- a/sound/soc/blackfin/bf5xx-ad73311.c +++ b/sound/soc/blackfin/bf5xx-ad73311.c | |||
@@ -203,23 +203,23 @@ static struct snd_soc_device bf5xx_ad73311_snd_devdata = { | |||
203 | .codec_dev = &soc_codec_dev_ad73311, | 203 | .codec_dev = &soc_codec_dev_ad73311, |
204 | }; | 204 | }; |
205 | 205 | ||
206 | static struct platform_device *bf52x_ad73311_snd_device; | 206 | static struct platform_device *bf5xx_ad73311_snd_device; |
207 | 207 | ||
208 | static int __init bf5xx_ad73311_init(void) | 208 | static int __init bf5xx_ad73311_init(void) |
209 | { | 209 | { |
210 | int ret; | 210 | int ret; |
211 | 211 | ||
212 | pr_debug("%s enter\n", __func__); | 212 | pr_debug("%s enter\n", __func__); |
213 | bf52x_ad73311_snd_device = platform_device_alloc("soc-audio", -1); | 213 | bf5xx_ad73311_snd_device = platform_device_alloc("soc-audio", -1); |
214 | if (!bf52x_ad73311_snd_device) | 214 | if (!bf5xx_ad73311_snd_device) |
215 | return -ENOMEM; | 215 | return -ENOMEM; |
216 | 216 | ||
217 | platform_set_drvdata(bf52x_ad73311_snd_device, &bf5xx_ad73311_snd_devdata); | 217 | platform_set_drvdata(bf5xx_ad73311_snd_device, &bf5xx_ad73311_snd_devdata); |
218 | bf5xx_ad73311_snd_devdata.dev = &bf52x_ad73311_snd_device->dev; | 218 | bf5xx_ad73311_snd_devdata.dev = &bf5xx_ad73311_snd_device->dev; |
219 | ret = platform_device_add(bf52x_ad73311_snd_device); | 219 | ret = platform_device_add(bf5xx_ad73311_snd_device); |
220 | 220 | ||
221 | if (ret) | 221 | if (ret) |
222 | platform_device_put(bf52x_ad73311_snd_device); | 222 | platform_device_put(bf5xx_ad73311_snd_device); |
223 | 223 | ||
224 | return ret; | 224 | return ret; |
225 | } | 225 | } |
@@ -227,7 +227,7 @@ static int __init bf5xx_ad73311_init(void) | |||
227 | static void __exit bf5xx_ad73311_exit(void) | 227 | static void __exit bf5xx_ad73311_exit(void) |
228 | { | 228 | { |
229 | pr_debug("%s enter\n", __func__); | 229 | pr_debug("%s enter\n", __func__); |
230 | platform_device_unregister(bf52x_ad73311_snd_device); | 230 | platform_device_unregister(bf5xx_ad73311_snd_device); |
231 | } | 231 | } |
232 | 232 | ||
233 | module_init(bf5xx_ad73311_init); | 233 | module_init(bf5xx_ad73311_init); |
diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c index 964824419678..876abade27e1 100644 --- a/sound/soc/blackfin/bf5xx-i2s.c +++ b/sound/soc/blackfin/bf5xx-i2s.c | |||
@@ -50,6 +50,7 @@ struct bf5xx_i2s_port { | |||
50 | u16 tcr2; | 50 | u16 tcr2; |
51 | u16 rcr2; | 51 | u16 rcr2; |
52 | int counter; | 52 | int counter; |
53 | int configured; | ||
53 | }; | 54 | }; |
54 | 55 | ||
55 | static struct bf5xx_i2s_port bf5xx_i2s; | 56 | static struct bf5xx_i2s_port bf5xx_i2s; |
@@ -168,7 +169,7 @@ static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, | |||
168 | break; | 169 | break; |
169 | } | 170 | } |
170 | 171 | ||
171 | if (bf5xx_i2s.counter == 1) { | 172 | if (!bf5xx_i2s.configured) { |
172 | /* | 173 | /* |
173 | * TX and RX are not independent,they are enabled at the | 174 | * TX and RX are not independent,they are enabled at the |
174 | * same time, even if only one side is running. So, we | 175 | * same time, even if only one side is running. So, we |
@@ -177,6 +178,7 @@ static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, | |||
177 | * | 178 | * |
178 | * CPU DAI:slave mode. | 179 | * CPU DAI:slave mode. |
179 | */ | 180 | */ |
181 | bf5xx_i2s.configured = 1; | ||
180 | ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1, | 182 | ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1, |
181 | bf5xx_i2s.rcr2, 0, 0); | 183 | bf5xx_i2s.rcr2, 0, 0); |
182 | if (ret) { | 184 | if (ret) { |
@@ -200,6 +202,9 @@ static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream, | |||
200 | { | 202 | { |
201 | pr_debug("%s enter\n", __func__); | 203 | pr_debug("%s enter\n", __func__); |
202 | bf5xx_i2s.counter--; | 204 | bf5xx_i2s.counter--; |
205 | /* No active stream, SPORT is allowed to be configured again. */ | ||
206 | if (!bf5xx_i2s.counter) | ||
207 | bf5xx_i2s.configured = 0; | ||
203 | } | 208 | } |
204 | 209 | ||
205 | static int bf5xx_i2s_probe(struct platform_device *pdev, | 210 | static int bf5xx_i2s_probe(struct platform_device *pdev, |
@@ -244,8 +249,7 @@ static int bf5xx_i2s_suspend(struct snd_soc_dai *dai) | |||
244 | return 0; | 249 | return 0; |
245 | } | 250 | } |
246 | 251 | ||
247 | static int bf5xx_i2s_resume(struct platform_device *pdev, | 252 | static int bf5xx_i2s_resume(struct snd_soc_dai *dai) |
248 | struct snd_soc_dai *dai) | ||
249 | { | 253 | { |
250 | int ret; | 254 | int ret; |
251 | struct sport_device *sport = | 255 | struct sport_device *sport = |
@@ -255,22 +259,18 @@ static int bf5xx_i2s_resume(struct platform_device *pdev, | |||
255 | if (!dai->active) | 259 | if (!dai->active) |
256 | return 0; | 260 | return 0; |
257 | 261 | ||
258 | ret = sport_config_rx(sport_handle, RFSR | RCKFE, RSFSE|0x1f, 0, 0); | 262 | ret = sport_config_rx(sport, RFSR | RCKFE, RSFSE|0x1f, 0, 0); |
259 | if (ret) { | 263 | if (ret) { |
260 | pr_err("SPORT is busy!\n"); | 264 | pr_err("SPORT is busy!\n"); |
261 | return -EBUSY; | 265 | return -EBUSY; |
262 | } | 266 | } |
263 | 267 | ||
264 | ret = sport_config_tx(sport_handle, TFSR | TCKFE, TSFSE|0x1f, 0, 0); | 268 | ret = sport_config_tx(sport, TFSR | TCKFE, TSFSE|0x1f, 0, 0); |
265 | if (ret) { | 269 | if (ret) { |
266 | pr_err("SPORT is busy!\n"); | 270 | pr_err("SPORT is busy!\n"); |
267 | return -EBUSY; | 271 | return -EBUSY; |
268 | } | 272 | } |
269 | 273 | ||
270 | if (dai->capture.active) | ||
271 | sport_rx_start(sport); | ||
272 | if (dai->playback.active) | ||
273 | sport_tx_start(sport); | ||
274 | return 0; | 274 | return 0; |
275 | } | 275 | } |
276 | 276 | ||
diff --git a/sound/soc/blackfin/bf5xx-sport.c b/sound/soc/blackfin/bf5xx-sport.c index b7953c8cf838..469ce7fab20c 100644 --- a/sound/soc/blackfin/bf5xx-sport.c +++ b/sound/soc/blackfin/bf5xx-sport.c | |||
@@ -190,7 +190,7 @@ static inline int sport_hook_rx_dummy(struct sport_device *sport) | |||
190 | desc = get_dma_next_desc_ptr(sport->dma_rx_chan); | 190 | desc = get_dma_next_desc_ptr(sport->dma_rx_chan); |
191 | /* Copy the descriptor which will be damaged to backup */ | 191 | /* Copy the descriptor which will be damaged to backup */ |
192 | temp_desc = *desc; | 192 | temp_desc = *desc; |
193 | desc->x_count = 0xa; | 193 | desc->x_count = sport->dummy_count / 2; |
194 | desc->y_count = 0; | 194 | desc->y_count = 0; |
195 | desc->next_desc_addr = sport->dummy_rx_desc; | 195 | desc->next_desc_addr = sport->dummy_rx_desc; |
196 | local_irq_restore(flags); | 196 | local_irq_restore(flags); |
@@ -309,7 +309,7 @@ static inline int sport_hook_tx_dummy(struct sport_device *sport) | |||
309 | desc = get_dma_next_desc_ptr(sport->dma_tx_chan); | 309 | desc = get_dma_next_desc_ptr(sport->dma_tx_chan); |
310 | /* Store the descriptor which will be damaged */ | 310 | /* Store the descriptor which will be damaged */ |
311 | temp_desc = *desc; | 311 | temp_desc = *desc; |
312 | desc->x_count = 0xa; | 312 | desc->x_count = sport->dummy_count / 2; |
313 | desc->y_count = 0; | 313 | desc->y_count = 0; |
314 | desc->next_desc_addr = sport->dummy_tx_desc; | 314 | desc->next_desc_addr = sport->dummy_tx_desc; |
315 | local_irq_restore(flags); | 315 | local_irq_restore(flags); |
diff --git a/sound/soc/blackfin/bf5xx-ssm2602.c b/sound/soc/blackfin/bf5xx-ssm2602.c index bc0cdded7116..3a00fa4dbe6d 100644 --- a/sound/soc/blackfin/bf5xx-ssm2602.c +++ b/sound/soc/blackfin/bf5xx-ssm2602.c | |||
@@ -148,24 +148,24 @@ static struct snd_soc_device bf5xx_ssm2602_snd_devdata = { | |||
148 | .codec_data = &bf5xx_ssm2602_setup, | 148 | .codec_data = &bf5xx_ssm2602_setup, |
149 | }; | 149 | }; |
150 | 150 | ||
151 | static struct platform_device *bf52x_ssm2602_snd_device; | 151 | static struct platform_device *bf5xx_ssm2602_snd_device; |
152 | 152 | ||
153 | static int __init bf5xx_ssm2602_init(void) | 153 | static int __init bf5xx_ssm2602_init(void) |
154 | { | 154 | { |
155 | int ret; | 155 | int ret; |
156 | 156 | ||
157 | pr_debug("%s enter\n", __func__); | 157 | pr_debug("%s enter\n", __func__); |
158 | bf52x_ssm2602_snd_device = platform_device_alloc("soc-audio", -1); | 158 | bf5xx_ssm2602_snd_device = platform_device_alloc("soc-audio", -1); |
159 | if (!bf52x_ssm2602_snd_device) | 159 | if (!bf5xx_ssm2602_snd_device) |
160 | return -ENOMEM; | 160 | return -ENOMEM; |
161 | 161 | ||
162 | platform_set_drvdata(bf52x_ssm2602_snd_device, | 162 | platform_set_drvdata(bf5xx_ssm2602_snd_device, |
163 | &bf5xx_ssm2602_snd_devdata); | 163 | &bf5xx_ssm2602_snd_devdata); |
164 | bf5xx_ssm2602_snd_devdata.dev = &bf52x_ssm2602_snd_device->dev; | 164 | bf5xx_ssm2602_snd_devdata.dev = &bf5xx_ssm2602_snd_device->dev; |
165 | ret = platform_device_add(bf52x_ssm2602_snd_device); | 165 | ret = platform_device_add(bf5xx_ssm2602_snd_device); |
166 | 166 | ||
167 | if (ret) | 167 | if (ret) |
168 | platform_device_put(bf52x_ssm2602_snd_device); | 168 | platform_device_put(bf5xx_ssm2602_snd_device); |
169 | 169 | ||
170 | return ret; | 170 | return ret; |
171 | } | 171 | } |
@@ -173,7 +173,7 @@ static int __init bf5xx_ssm2602_init(void) | |||
173 | static void __exit bf5xx_ssm2602_exit(void) | 173 | static void __exit bf5xx_ssm2602_exit(void) |
174 | { | 174 | { |
175 | pr_debug("%s enter\n", __func__); | 175 | pr_debug("%s enter\n", __func__); |
176 | platform_device_unregister(bf52x_ssm2602_snd_device); | 176 | platform_device_unregister(bf5xx_ssm2602_snd_device); |
177 | } | 177 | } |
178 | 178 | ||
179 | module_init(bf5xx_ssm2602_init); | 179 | module_init(bf5xx_ssm2602_init); |
diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.c b/sound/soc/blackfin/bf5xx-tdm-pcm.c new file mode 100644 index 000000000000..ccb5e823bd18 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.c | |||
@@ -0,0 +1,330 @@ | |||
1 | /* | ||
2 | * File: sound/soc/blackfin/bf5xx-tdm-pcm.c | ||
3 | * Author: Barry Song <Barry.Song@analog.com> | ||
4 | * | ||
5 | * Created: Tue June 06 2009 | ||
6 | * Description: DMA driver for tdm codec | ||
7 | * | ||
8 | * Modified: | ||
9 | * Copyright 2009 Analog Devices Inc. | ||
10 | * | ||
11 | * Bugs: Enter bugs at http://blackfin.uclinux.org/ | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or modify | ||
14 | * it under the terms of the GNU General Public License as published by | ||
15 | * the Free Software Foundation; either version 2 of the License, or | ||
16 | * (at your option) any later version. | ||
17 | * | ||
18 | * This program is distributed in the hope that it will be useful, | ||
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
21 | * GNU General Public License for more details. | ||
22 | * | ||
23 | * You should have received a copy of the GNU General Public License | ||
24 | * along with this program; if not, see the file COPYING, or write | ||
25 | * to the Free Software Foundation, Inc., | ||
26 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
27 | */ | ||
28 | |||
29 | #include <linux/module.h> | ||
30 | #include <linux/init.h> | ||
31 | #include <linux/platform_device.h> | ||
32 | #include <linux/slab.h> | ||
33 | #include <linux/dma-mapping.h> | ||
34 | |||
35 | #include <sound/core.h> | ||
36 | #include <sound/pcm.h> | ||
37 | #include <sound/pcm_params.h> | ||
38 | #include <sound/soc.h> | ||
39 | |||
40 | #include <asm/dma.h> | ||
41 | |||
42 | #include "bf5xx-tdm-pcm.h" | ||
43 | #include "bf5xx-tdm.h" | ||
44 | #include "bf5xx-sport.h" | ||
45 | |||
46 | #define PCM_BUFFER_MAX 0x10000 | ||
47 | #define FRAGMENT_SIZE_MIN (4*1024) | ||
48 | #define FRAGMENTS_MIN 2 | ||
49 | #define FRAGMENTS_MAX 32 | ||
50 | |||
51 | static void bf5xx_dma_irq(void *data) | ||
52 | { | ||
53 | struct snd_pcm_substream *pcm = data; | ||
54 | snd_pcm_period_elapsed(pcm); | ||
55 | } | ||
56 | |||
57 | static const struct snd_pcm_hardware bf5xx_pcm_hardware = { | ||
58 | .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
59 | SNDRV_PCM_INFO_RESUME), | ||
60 | .formats = SNDRV_PCM_FMTBIT_S32_LE, | ||
61 | .rates = SNDRV_PCM_RATE_48000, | ||
62 | .channels_min = 2, | ||
63 | .channels_max = 8, | ||
64 | .buffer_bytes_max = PCM_BUFFER_MAX, | ||
65 | .period_bytes_min = FRAGMENT_SIZE_MIN, | ||
66 | .period_bytes_max = PCM_BUFFER_MAX/2, | ||
67 | .periods_min = FRAGMENTS_MIN, | ||
68 | .periods_max = FRAGMENTS_MAX, | ||
69 | }; | ||
70 | |||
71 | static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, | ||
72 | struct snd_pcm_hw_params *params) | ||
73 | { | ||
74 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max; | ||
75 | snd_pcm_lib_malloc_pages(substream, size * 4); | ||
76 | |||
77 | return 0; | ||
78 | } | ||
79 | |||
80 | static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) | ||
81 | { | ||
82 | snd_pcm_lib_free_pages(substream); | ||
83 | |||
84 | return 0; | ||
85 | } | ||
86 | |||
87 | static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) | ||
88 | { | ||
89 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
90 | struct sport_device *sport = runtime->private_data; | ||
91 | int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size); | ||
92 | |||
93 | fragsize_bytes /= runtime->channels; | ||
94 | /* inflate the fragsize to match the dma width of SPORT */ | ||
95 | fragsize_bytes *= 8; | ||
96 | |||
97 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
98 | sport_set_tx_callback(sport, bf5xx_dma_irq, substream); | ||
99 | sport_config_tx_dma(sport, runtime->dma_area, | ||
100 | runtime->periods, fragsize_bytes); | ||
101 | } else { | ||
102 | sport_set_rx_callback(sport, bf5xx_dma_irq, substream); | ||
103 | sport_config_rx_dma(sport, runtime->dma_area, | ||
104 | runtime->periods, fragsize_bytes); | ||
105 | } | ||
106 | |||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) | ||
111 | { | ||
112 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
113 | struct sport_device *sport = runtime->private_data; | ||
114 | int ret = 0; | ||
115 | |||
116 | switch (cmd) { | ||
117 | case SNDRV_PCM_TRIGGER_START: | ||
118 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
119 | sport_tx_start(sport); | ||
120 | else | ||
121 | sport_rx_start(sport); | ||
122 | break; | ||
123 | case SNDRV_PCM_TRIGGER_STOP: | ||
124 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
125 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
126 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
127 | sport_tx_stop(sport); | ||
128 | else | ||
129 | sport_rx_stop(sport); | ||
130 | break; | ||
131 | default: | ||
132 | ret = -EINVAL; | ||
133 | } | ||
134 | |||
135 | return ret; | ||
136 | } | ||
137 | |||
138 | static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) | ||
139 | { | ||
140 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
141 | struct sport_device *sport = runtime->private_data; | ||
142 | unsigned int diff; | ||
143 | snd_pcm_uframes_t frames; | ||
144 | |||
145 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
146 | diff = sport_curr_offset_tx(sport); | ||
147 | frames = diff / (8*4); /* 32 bytes per frame */ | ||
148 | } else { | ||
149 | diff = sport_curr_offset_rx(sport); | ||
150 | frames = diff / (8*4); | ||
151 | } | ||
152 | return frames; | ||
153 | } | ||
154 | |||
155 | static int bf5xx_pcm_open(struct snd_pcm_substream *substream) | ||
156 | { | ||
157 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
158 | int ret = 0; | ||
159 | |||
160 | snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); | ||
161 | |||
162 | ret = snd_pcm_hw_constraint_integer(runtime, | ||
163 | SNDRV_PCM_HW_PARAM_PERIODS); | ||
164 | if (ret < 0) | ||
165 | goto out; | ||
166 | |||
167 | if (sport_handle != NULL) | ||
168 | runtime->private_data = sport_handle; | ||
169 | else { | ||
170 | pr_err("sport_handle is NULL\n"); | ||
171 | ret = -ENODEV; | ||
172 | } | ||
173 | out: | ||
174 | return ret; | ||
175 | } | ||
176 | |||
177 | static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, | ||
178 | snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count) | ||
179 | { | ||
180 | unsigned int *src; | ||
181 | unsigned int *dst; | ||
182 | int i; | ||
183 | |||
184 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
185 | src = buf; | ||
186 | dst = (unsigned int *)substream->runtime->dma_area; | ||
187 | |||
188 | dst += pos * 8; | ||
189 | while (count--) { | ||
190 | for (i = 0; i < substream->runtime->channels; i++) | ||
191 | *(dst + i) = *src++; | ||
192 | dst += 8; | ||
193 | } | ||
194 | } else { | ||
195 | src = (unsigned int *)substream->runtime->dma_area; | ||
196 | dst = buf; | ||
197 | |||
198 | src += pos * 8; | ||
199 | while (count--) { | ||
200 | for (i = 0; i < substream->runtime->channels; i++) | ||
201 | *dst++ = *(src+i); | ||
202 | src += 8; | ||
203 | } | ||
204 | } | ||
205 | |||
206 | return 0; | ||
207 | } | ||
208 | |||
209 | static int bf5xx_pcm_silence(struct snd_pcm_substream *substream, | ||
210 | int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count) | ||
211 | { | ||
212 | unsigned char *buf = substream->runtime->dma_area; | ||
213 | buf += pos * 8 * 4; | ||
214 | memset(buf, '\0', count * 8 * 4); | ||
215 | |||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | |||
220 | struct snd_pcm_ops bf5xx_pcm_tdm_ops = { | ||
221 | .open = bf5xx_pcm_open, | ||
222 | .ioctl = snd_pcm_lib_ioctl, | ||
223 | .hw_params = bf5xx_pcm_hw_params, | ||
224 | .hw_free = bf5xx_pcm_hw_free, | ||
225 | .prepare = bf5xx_pcm_prepare, | ||
226 | .trigger = bf5xx_pcm_trigger, | ||
227 | .pointer = bf5xx_pcm_pointer, | ||
228 | .copy = bf5xx_pcm_copy, | ||
229 | .silence = bf5xx_pcm_silence, | ||
230 | }; | ||
231 | |||
232 | static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) | ||
233 | { | ||
234 | struct snd_pcm_substream *substream = pcm->streams[stream].substream; | ||
235 | struct snd_dma_buffer *buf = &substream->dma_buffer; | ||
236 | size_t size = bf5xx_pcm_hardware.buffer_bytes_max; | ||
237 | |||
238 | buf->dev.type = SNDRV_DMA_TYPE_DEV; | ||
239 | buf->dev.dev = pcm->card->dev; | ||
240 | buf->private_data = NULL; | ||
241 | buf->area = dma_alloc_coherent(pcm->card->dev, size * 4, | ||
242 | &buf->addr, GFP_KERNEL); | ||
243 | if (!buf->area) { | ||
244 | pr_err("Failed to allocate dma memory \ | ||
245 | Please increase uncached DMA memory region\n"); | ||
246 | return -ENOMEM; | ||
247 | } | ||
248 | buf->bytes = size; | ||
249 | |||
250 | if (stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
251 | sport_handle->tx_buf = buf->area; | ||
252 | else | ||
253 | sport_handle->rx_buf = buf->area; | ||
254 | |||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) | ||
259 | { | ||
260 | struct snd_pcm_substream *substream; | ||
261 | struct snd_dma_buffer *buf; | ||
262 | int stream; | ||
263 | |||
264 | for (stream = 0; stream < 2; stream++) { | ||
265 | substream = pcm->streams[stream].substream; | ||
266 | if (!substream) | ||
267 | continue; | ||
268 | |||
269 | buf = &substream->dma_buffer; | ||
270 | if (!buf->area) | ||
271 | continue; | ||
272 | dma_free_coherent(NULL, buf->bytes, buf->area, 0); | ||
273 | buf->area = NULL; | ||
274 | } | ||
275 | if (sport_handle) | ||
276 | sport_done(sport_handle); | ||
277 | } | ||
278 | |||
279 | static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32); | ||
280 | |||
281 | static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai, | ||
282 | struct snd_pcm *pcm) | ||
283 | { | ||
284 | int ret = 0; | ||
285 | |||
286 | if (!card->dev->dma_mask) | ||
287 | card->dev->dma_mask = &bf5xx_pcm_dmamask; | ||
288 | if (!card->dev->coherent_dma_mask) | ||
289 | card->dev->coherent_dma_mask = DMA_BIT_MASK(32); | ||
290 | |||
291 | if (dai->playback.channels_min) { | ||
292 | ret = bf5xx_pcm_preallocate_dma_buffer(pcm, | ||
293 | SNDRV_PCM_STREAM_PLAYBACK); | ||
294 | if (ret) | ||
295 | goto out; | ||
296 | } | ||
297 | |||
298 | if (dai->capture.channels_min) { | ||
299 | ret = bf5xx_pcm_preallocate_dma_buffer(pcm, | ||
300 | SNDRV_PCM_STREAM_CAPTURE); | ||
301 | if (ret) | ||
302 | goto out; | ||
303 | } | ||
304 | out: | ||
305 | return ret; | ||
306 | } | ||
307 | |||
308 | struct snd_soc_platform bf5xx_tdm_soc_platform = { | ||
309 | .name = "bf5xx-audio", | ||
310 | .pcm_ops = &bf5xx_pcm_tdm_ops, | ||
311 | .pcm_new = bf5xx_pcm_tdm_new, | ||
312 | .pcm_free = bf5xx_pcm_free_dma_buffers, | ||
313 | }; | ||
314 | EXPORT_SYMBOL_GPL(bf5xx_tdm_soc_platform); | ||
315 | |||
316 | static int __init bfin_pcm_tdm_init(void) | ||
317 | { | ||
318 | return snd_soc_register_platform(&bf5xx_tdm_soc_platform); | ||
319 | } | ||
320 | module_init(bfin_pcm_tdm_init); | ||
321 | |||
322 | static void __exit bfin_pcm_tdm_exit(void) | ||
323 | { | ||
324 | snd_soc_unregister_platform(&bf5xx_tdm_soc_platform); | ||
325 | } | ||
326 | module_exit(bfin_pcm_tdm_exit); | ||
327 | |||
328 | MODULE_AUTHOR("Barry Song"); | ||
329 | MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module"); | ||
330 | MODULE_LICENSE("GPL"); | ||
diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.h b/sound/soc/blackfin/bf5xx-tdm-pcm.h new file mode 100644 index 000000000000..ddc5047df88c --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.h | |||
@@ -0,0 +1,21 @@ | |||
1 | /* | ||
2 | * sound/soc/blackfin/bf5xx-tdm-pcm.h -- ALSA PCM interface for the Blackfin | ||
3 | * | ||
4 | * Copyright 2009 Analog Device Inc. | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #ifndef _BF5XX_TDM_PCM_H | ||
12 | #define _BF5XX_TDM_PCM_H | ||
13 | |||
14 | struct bf5xx_pcm_dma_params { | ||
15 | char *name; /* stream identifier */ | ||
16 | }; | ||
17 | |||
18 | /* platform data */ | ||
19 | extern struct snd_soc_platform bf5xx_tdm_soc_platform; | ||
20 | |||
21 | #endif | ||
diff --git a/sound/soc/blackfin/bf5xx-tdm.c b/sound/soc/blackfin/bf5xx-tdm.c new file mode 100644 index 000000000000..3096badf09a5 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm.c | |||
@@ -0,0 +1,343 @@ | |||
1 | /* | ||
2 | * File: sound/soc/blackfin/bf5xx-tdm.c | ||
3 | * Author: Barry Song <Barry.Song@analog.com> | ||
4 | * | ||
5 | * Created: Thurs June 04 2009 | ||
6 | * Description: Blackfin I2S(TDM) CPU DAI driver | ||
7 | * Even though TDM mode can be as part of I2S DAI, but there | ||
8 | * are so much difference in configuration and data flow, | ||
9 | * it's very ugly to integrate I2S and TDM into a module | ||
10 | * | ||
11 | * Modified: | ||
12 | * Copyright 2009 Analog Devices Inc. | ||
13 | * | ||
14 | * Bugs: Enter bugs at http://blackfin.uclinux.org/ | ||
15 | * | ||
16 | * This program is free software; you can redistribute it and/or modify | ||
17 | * it under the terms of the GNU General Public License as published by | ||
18 | * the Free Software Foundation; either version 2 of the License, or | ||
19 | * (at your option) any later version. | ||
20 | * | ||
21 | * This program is distributed in the hope that it will be useful, | ||
22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
24 | * GNU General Public License for more details. | ||
25 | * | ||
26 | * You should have received a copy of the GNU General Public License | ||
27 | * along with this program; if not, see the file COPYING, or write | ||
28 | * to the Free Software Foundation, Inc., | ||
29 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
30 | */ | ||
31 | |||
32 | #include <linux/init.h> | ||
33 | #include <linux/module.h> | ||
34 | #include <linux/device.h> | ||
35 | #include <sound/core.h> | ||
36 | #include <sound/pcm.h> | ||
37 | #include <sound/pcm_params.h> | ||
38 | #include <sound/initval.h> | ||
39 | #include <sound/soc.h> | ||
40 | |||
41 | #include <asm/irq.h> | ||
42 | #include <asm/portmux.h> | ||
43 | #include <linux/mutex.h> | ||
44 | #include <linux/gpio.h> | ||
45 | |||
46 | #include "bf5xx-sport.h" | ||
47 | #include "bf5xx-tdm.h" | ||
48 | |||
49 | struct bf5xx_tdm_port { | ||
50 | u16 tcr1; | ||
51 | u16 rcr1; | ||
52 | u16 tcr2; | ||
53 | u16 rcr2; | ||
54 | int configured; | ||
55 | }; | ||
56 | |||
57 | static struct bf5xx_tdm_port bf5xx_tdm; | ||
58 | static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; | ||
59 | |||
60 | static struct sport_param sport_params[2] = { | ||
61 | { | ||
62 | .dma_rx_chan = CH_SPORT0_RX, | ||
63 | .dma_tx_chan = CH_SPORT0_TX, | ||
64 | .err_irq = IRQ_SPORT0_ERROR, | ||
65 | .regs = (struct sport_register *)SPORT0_TCR1, | ||
66 | }, | ||
67 | { | ||
68 | .dma_rx_chan = CH_SPORT1_RX, | ||
69 | .dma_tx_chan = CH_SPORT1_TX, | ||
70 | .err_irq = IRQ_SPORT1_ERROR, | ||
71 | .regs = (struct sport_register *)SPORT1_TCR1, | ||
72 | } | ||
73 | }; | ||
74 | |||
75 | /* | ||
76 | * Setting the TFS pin selector for SPORT 0 based on whether the selected | ||
77 | * port id F or G. If the port is F then no conflict should exist for the | ||
78 | * TFS. When Port G is selected and EMAC then there is a conflict between | ||
79 | * the PHY interrupt line and TFS. Current settings prevent the conflict | ||
80 | * by ignoring the TFS pin when Port G is selected. This allows both | ||
81 | * ssm2602 using Port G and EMAC concurrently. | ||
82 | */ | ||
83 | #ifdef CONFIG_BF527_SPORT0_PORTF | ||
84 | #define LOCAL_SPORT0_TFS (P_SPORT0_TFS) | ||
85 | #else | ||
86 | #define LOCAL_SPORT0_TFS (0) | ||
87 | #endif | ||
88 | |||
89 | static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, | ||
90 | P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0}, | ||
91 | {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI, | ||
92 | P_SPORT1_RSCLK, P_SPORT1_TFS, 0} }; | ||
93 | |||
94 | static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai, | ||
95 | unsigned int fmt) | ||
96 | { | ||
97 | int ret = 0; | ||
98 | |||
99 | /* interface format:support TDM,slave mode */ | ||
100 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
101 | case SND_SOC_DAIFMT_DSP_A: | ||
102 | break; | ||
103 | default: | ||
104 | printk(KERN_ERR "%s: Unknown DAI format type\n", __func__); | ||
105 | ret = -EINVAL; | ||
106 | break; | ||
107 | } | ||
108 | |||
109 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
110 | case SND_SOC_DAIFMT_CBM_CFM: | ||
111 | break; | ||
112 | case SND_SOC_DAIFMT_CBS_CFS: | ||
113 | case SND_SOC_DAIFMT_CBM_CFS: | ||
114 | case SND_SOC_DAIFMT_CBS_CFM: | ||
115 | ret = -EINVAL; | ||
116 | break; | ||
117 | default: | ||
118 | printk(KERN_ERR "%s: Unknown DAI master type\n", __func__); | ||
119 | ret = -EINVAL; | ||
120 | break; | ||
121 | } | ||
122 | |||
123 | return ret; | ||
124 | } | ||
125 | |||
126 | static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream, | ||
127 | struct snd_pcm_hw_params *params, | ||
128 | struct snd_soc_dai *dai) | ||
129 | { | ||
130 | int ret = 0; | ||
131 | |||
132 | bf5xx_tdm.tcr2 &= ~0x1f; | ||
133 | bf5xx_tdm.rcr2 &= ~0x1f; | ||
134 | switch (params_format(params)) { | ||
135 | case SNDRV_PCM_FORMAT_S32_LE: | ||
136 | bf5xx_tdm.tcr2 |= 31; | ||
137 | bf5xx_tdm.rcr2 |= 31; | ||
138 | sport_handle->wdsize = 4; | ||
139 | break; | ||
140 | /* at present, we only support 32bit transfer */ | ||
141 | default: | ||
142 | pr_err("not supported PCM format yet\n"); | ||
143 | return -EINVAL; | ||
144 | break; | ||
145 | } | ||
146 | |||
147 | if (!bf5xx_tdm.configured) { | ||
148 | /* | ||
149 | * TX and RX are not independent,they are enabled at the | ||
150 | * same time, even if only one side is running. So, we | ||
151 | * need to configure both of them at the time when the first | ||
152 | * stream is opened. | ||
153 | * | ||
154 | * CPU DAI:slave mode. | ||
155 | */ | ||
156 | ret = sport_config_rx(sport_handle, bf5xx_tdm.rcr1, | ||
157 | bf5xx_tdm.rcr2, 0, 0); | ||
158 | if (ret) { | ||
159 | pr_err("SPORT is busy!\n"); | ||
160 | return -EBUSY; | ||
161 | } | ||
162 | |||
163 | ret = sport_config_tx(sport_handle, bf5xx_tdm.tcr1, | ||
164 | bf5xx_tdm.tcr2, 0, 0); | ||
165 | if (ret) { | ||
166 | pr_err("SPORT is busy!\n"); | ||
167 | return -EBUSY; | ||
168 | } | ||
169 | |||
170 | bf5xx_tdm.configured = 1; | ||
171 | } | ||
172 | |||
173 | return 0; | ||
174 | } | ||
175 | |||
176 | static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream, | ||
177 | struct snd_soc_dai *dai) | ||
178 | { | ||
179 | /* No active stream, SPORT is allowed to be configured again. */ | ||
180 | if (!dai->active) | ||
181 | bf5xx_tdm.configured = 0; | ||
182 | } | ||
183 | |||
184 | #ifdef CONFIG_PM | ||
185 | static int bf5xx_tdm_suspend(struct snd_soc_dai *dai) | ||
186 | { | ||
187 | struct sport_device *sport = | ||
188 | (struct sport_device *)dai->private_data; | ||
189 | |||
190 | if (!dai->active) | ||
191 | return 0; | ||
192 | if (dai->capture.active) | ||
193 | sport_rx_stop(sport); | ||
194 | if (dai->playback.active) | ||
195 | sport_tx_stop(sport); | ||
196 | return 0; | ||
197 | } | ||
198 | |||
199 | static int bf5xx_tdm_resume(struct snd_soc_dai *dai) | ||
200 | { | ||
201 | int ret; | ||
202 | struct sport_device *sport = | ||
203 | (struct sport_device *)dai->private_data; | ||
204 | |||
205 | if (!dai->active) | ||
206 | return 0; | ||
207 | |||
208 | ret = sport_set_multichannel(sport, 8, 0xFF, 1); | ||
209 | if (ret) { | ||
210 | pr_err("SPORT is busy!\n"); | ||
211 | ret = -EBUSY; | ||
212 | } | ||
213 | |||
214 | ret = sport_config_rx(sport, IRFS, 0x1F, 0, 0); | ||
215 | if (ret) { | ||
216 | pr_err("SPORT is busy!\n"); | ||
217 | ret = -EBUSY; | ||
218 | } | ||
219 | |||
220 | ret = sport_config_tx(sport, ITFS, 0x1F, 0, 0); | ||
221 | if (ret) { | ||
222 | pr_err("SPORT is busy!\n"); | ||
223 | ret = -EBUSY; | ||
224 | } | ||
225 | |||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | #else | ||
230 | #define bf5xx_tdm_suspend NULL | ||
231 | #define bf5xx_tdm_resume NULL | ||
232 | #endif | ||
233 | |||
234 | static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = { | ||
235 | .hw_params = bf5xx_tdm_hw_params, | ||
236 | .set_fmt = bf5xx_tdm_set_dai_fmt, | ||
237 | .shutdown = bf5xx_tdm_shutdown, | ||
238 | }; | ||
239 | |||
240 | struct snd_soc_dai bf5xx_tdm_dai = { | ||
241 | .name = "bf5xx-tdm", | ||
242 | .id = 0, | ||
243 | .suspend = bf5xx_tdm_suspend, | ||
244 | .resume = bf5xx_tdm_resume, | ||
245 | .playback = { | ||
246 | .channels_min = 2, | ||
247 | .channels_max = 8, | ||
248 | .rates = SNDRV_PCM_RATE_48000, | ||
249 | .formats = SNDRV_PCM_FMTBIT_S32_LE,}, | ||
250 | .capture = { | ||
251 | .channels_min = 2, | ||
252 | .channels_max = 8, | ||
253 | .rates = SNDRV_PCM_RATE_48000, | ||
254 | .formats = SNDRV_PCM_FMTBIT_S32_LE,}, | ||
255 | .ops = &bf5xx_tdm_dai_ops, | ||
256 | }; | ||
257 | EXPORT_SYMBOL_GPL(bf5xx_tdm_dai); | ||
258 | |||
259 | static int __devinit bfin_tdm_probe(struct platform_device *pdev) | ||
260 | { | ||
261 | int ret = 0; | ||
262 | |||
263 | if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) { | ||
264 | pr_err("Requesting Peripherals failed\n"); | ||
265 | return -EFAULT; | ||
266 | } | ||
267 | |||
268 | /* request DMA for SPORT */ | ||
269 | sport_handle = sport_init(&sport_params[sport_num], 4, \ | ||
270 | 8 * sizeof(u32), NULL); | ||
271 | if (!sport_handle) { | ||
272 | peripheral_free_list(&sport_req[sport_num][0]); | ||
273 | return -ENODEV; | ||
274 | } | ||
275 | |||
276 | /* SPORT works in TDM mode */ | ||
277 | ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1); | ||
278 | if (ret) { | ||
279 | pr_err("SPORT is busy!\n"); | ||
280 | ret = -EBUSY; | ||
281 | goto sport_config_err; | ||
282 | } | ||
283 | |||
284 | ret = sport_config_rx(sport_handle, IRFS, 0x1F, 0, 0); | ||
285 | if (ret) { | ||
286 | pr_err("SPORT is busy!\n"); | ||
287 | ret = -EBUSY; | ||
288 | goto sport_config_err; | ||
289 | } | ||
290 | |||
291 | ret = sport_config_tx(sport_handle, ITFS, 0x1F, 0, 0); | ||
292 | if (ret) { | ||
293 | pr_err("SPORT is busy!\n"); | ||
294 | ret = -EBUSY; | ||
295 | goto sport_config_err; | ||
296 | } | ||
297 | |||
298 | ret = snd_soc_register_dai(&bf5xx_tdm_dai); | ||
299 | if (ret) { | ||
300 | pr_err("Failed to register DAI: %d\n", ret); | ||
301 | goto sport_config_err; | ||
302 | } | ||
303 | return 0; | ||
304 | |||
305 | sport_config_err: | ||
306 | peripheral_free_list(&sport_req[sport_num][0]); | ||
307 | return ret; | ||
308 | } | ||
309 | |||
310 | static int __devexit bfin_tdm_remove(struct platform_device *pdev) | ||
311 | { | ||
312 | peripheral_free_list(&sport_req[sport_num][0]); | ||
313 | snd_soc_unregister_dai(&bf5xx_tdm_dai); | ||
314 | |||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static struct platform_driver bfin_tdm_driver = { | ||
319 | .probe = bfin_tdm_probe, | ||
320 | .remove = __devexit_p(bfin_tdm_remove), | ||
321 | .driver = { | ||
322 | .name = "bfin-tdm", | ||
323 | .owner = THIS_MODULE, | ||
324 | }, | ||
325 | }; | ||
326 | |||
327 | static int __init bfin_tdm_init(void) | ||
328 | { | ||
329 | return platform_driver_register(&bfin_tdm_driver); | ||
330 | } | ||
331 | module_init(bfin_tdm_init); | ||
332 | |||
333 | static void __exit bfin_tdm_exit(void) | ||
334 | { | ||
335 | platform_driver_unregister(&bfin_tdm_driver); | ||
336 | } | ||
337 | module_exit(bfin_tdm_exit); | ||
338 | |||
339 | /* Module information */ | ||
340 | MODULE_AUTHOR("Barry Song"); | ||
341 | MODULE_DESCRIPTION("TDM driver for ADI Blackfin"); | ||
342 | MODULE_LICENSE("GPL"); | ||
343 | |||
diff --git a/sound/soc/blackfin/bf5xx-tdm.h b/sound/soc/blackfin/bf5xx-tdm.h new file mode 100644 index 000000000000..618ec3d90cd4 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm.h | |||
@@ -0,0 +1,14 @@ | |||
1 | /* | ||
2 | * sound/soc/blackfin/bf5xx-tdm.h | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | */ | ||
8 | |||
9 | #ifndef _BF5XX_TDM_H | ||
10 | #define _BF5XX_TDM_H | ||
11 | |||
12 | extern struct snd_soc_dai bf5xx_tdm_dai; | ||
13 | |||
14 | #endif | ||