diff options
author | Liam Girdwood <liam.girdwood@wolfsonmicro.com> | 2006-10-12 08:28:10 -0400 |
---|---|---|
committer | Jaroslav Kysela <perex@suse.cz> | 2007-02-09 03:00:44 -0500 |
commit | 3e7cc3d3d1c435f83533b8bf2cf1833855be2901 (patch) | |
tree | 7c7084680a58df61cdcedd6b86f5dda730722798 | |
parent | f11a96d5cd94202479e603f9dfaff6e92f342135 (diff) |
[ALSA] ASoC pxa2xx I2S support
This patch adds pxa2xx I2S ASoC audio support. Features:-
o Supports playback/capture
o 16 bit PCM
o 8k - 96k sample rates
o Supports master and slave mode.
From: Liam Girdwood <liam.girdwood@wolfsonmicro.com>
Signed-off-by: Richard Purdie <rpurdie@rpsys.net>
Signed-off-by: Liam Girdwood <liam.girdwood@wolfsonmicro.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
-rw-r--r-- | sound/soc/pxa/pxa2xx-i2s.c | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c new file mode 100644 index 000000000000..c3b7a4bb7bd7 --- /dev/null +++ b/sound/soc/pxa/pxa2xx-i2s.c | |||
@@ -0,0 +1,306 @@ | |||
1 | /* | ||
2 | * pxa2xx-i2s.c -- ALSA Soc Audio Layer | ||
3 | * | ||
4 | * Copyright 2005 Wolfson Microelectronics PLC. | ||
5 | * Author: Liam Girdwood | ||
6 | * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms of the GNU General Public License as published by the | ||
10 | * Free Software Foundation; either version 2 of the License, or (at your | ||
11 | * option) any later version. | ||
12 | * | ||
13 | * Revision history | ||
14 | * 12th Aug 2005 Initial version. | ||
15 | */ | ||
16 | |||
17 | #include <linux/init.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/delay.h> | ||
21 | #include <sound/driver.h> | ||
22 | #include <sound/core.h> | ||
23 | #include <sound/pcm.h> | ||
24 | #include <sound/initval.h> | ||
25 | #include <sound/soc.h> | ||
26 | |||
27 | #include <asm/hardware.h> | ||
28 | #include <asm/arch/pxa-regs.h> | ||
29 | #include <asm/arch/audio.h> | ||
30 | |||
31 | #include "pxa2xx-pcm.h" | ||
32 | |||
33 | /* used to disable sysclk if external crystal is used */ | ||
34 | static int extclk; | ||
35 | module_param(extclk, int, 0); | ||
36 | MODULE_PARM_DESC(extclk, "set to 1 to disable pxa2xx i2s sysclk"); | ||
37 | |||
38 | struct pxa_i2s_port { | ||
39 | u32 sadiv; | ||
40 | u32 sacr0; | ||
41 | u32 sacr1; | ||
42 | u32 saimr; | ||
43 | int master; | ||
44 | }; | ||
45 | static struct pxa_i2s_port pxa_i2s; | ||
46 | |||
47 | #define PXA_I2S_DAIFMT \ | ||
48 | (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF) | ||
49 | |||
50 | #define PXA_I2S_DIR \ | ||
51 | (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) | ||
52 | |||
53 | #define PXA_I2S_RATES \ | ||
54 | (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ | ||
55 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ | ||
56 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) | ||
57 | |||
58 | /* priv is divider */ | ||
59 | static struct snd_soc_dai_mode pxa2xx_i2s_modes[] = { | ||
60 | /* pxa2xx I2S frame and clock master modes */ | ||
61 | {PXA_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, SND_SOC_DAITDM_LRDW(0,0), | ||
62 | SNDRV_PCM_FMTBIT_S16_LE, SNDRV_PCM_RATE_8000, PXA_I2S_DIR, | ||
63 | SND_SOC_DAI_BFS_DIV, 256, SND_SOC_FSBD(4), 0x48}, | ||
64 | {PXA_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, SND_SOC_DAITDM_LRDW(0,0), | ||
65 | SNDRV_PCM_FMTBIT_S16_LE, SNDRV_PCM_RATE_11025, PXA_I2S_DIR, | ||
66 | SND_SOC_DAI_BFS_DIV, 256, SND_SOC_FSBD(4), 0x34}, | ||
67 | {PXA_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, SND_SOC_DAITDM_LRDW(0,0), | ||
68 | SNDRV_PCM_FMTBIT_S16_LE, SNDRV_PCM_RATE_16000, PXA_I2S_DIR, | ||
69 | SND_SOC_DAI_BFS_DIV, 256, SND_SOC_FSBD(4), 0x24}, | ||
70 | {PXA_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, SND_SOC_DAITDM_LRDW(0,0), | ||
71 | SNDRV_PCM_FMTBIT_S16_LE, SNDRV_PCM_RATE_22050, PXA_I2S_DIR, | ||
72 | SND_SOC_DAI_BFS_DIV, 256, SND_SOC_FSBD(4), 0x1a}, | ||
73 | {PXA_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, SND_SOC_DAITDM_LRDW(0,0), | ||
74 | SNDRV_PCM_FMTBIT_S16_LE, SNDRV_PCM_RATE_44100, PXA_I2S_DIR, | ||
75 | SND_SOC_DAI_BFS_DIV, 256, SND_SOC_FSBD(4), 0xd}, | ||
76 | {PXA_I2S_DAIFMT | SND_SOC_DAIFMT_CBS_CFS, SND_SOC_DAITDM_LRDW(0,0), | ||
77 | SNDRV_PCM_FMTBIT_S16_LE, SNDRV_PCM_RATE_48000, PXA_I2S_DIR, | ||
78 | SND_SOC_DAI_BFS_DIV, 256, SND_SOC_FSBD(4), 0xc}, | ||
79 | |||
80 | /* pxa2xx I2S frame master and clock slave mode */ | ||
81 | {PXA_I2S_DAIFMT | SND_SOC_DAIFMT_CBM_CFS, SND_SOC_DAITDM_LRDW(0,0), | ||
82 | SNDRV_PCM_FMTBIT_S16_LE, PXA_I2S_RATES, PXA_I2S_DIR, 0, | ||
83 | SND_SOC_FS_ALL, SND_SOC_FSB(64), 0x48}, | ||
84 | |||
85 | }; | ||
86 | |||
87 | static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_out = { | ||
88 | .name = "I2S PCM Stereo out", | ||
89 | .dev_addr = __PREG(SADR), | ||
90 | .drcmr = &DRCMRTXSADR, | ||
91 | .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | | ||
92 | DCMD_BURST32 | DCMD_WIDTH4, | ||
93 | }; | ||
94 | |||
95 | static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_in = { | ||
96 | .name = "I2S PCM Stereo in", | ||
97 | .dev_addr = __PREG(SADR), | ||
98 | .drcmr = &DRCMRRXSADR, | ||
99 | .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | | ||
100 | DCMD_BURST32 | DCMD_WIDTH4, | ||
101 | }; | ||
102 | |||
103 | static struct pxa2xx_gpio gpio_bus[] = { | ||
104 | { /* I2S SoC Slave */ | ||
105 | .rx = GPIO29_SDATA_IN_I2S_MD, | ||
106 | .tx = GPIO30_SDATA_OUT_I2S_MD, | ||
107 | .clk = GPIO28_BITCLK_IN_I2S_MD, | ||
108 | .frm = GPIO31_SYNC_I2S_MD, | ||
109 | }, | ||
110 | { /* I2S SoC Master */ | ||
111 | #ifdef CONFIG_PXA27x | ||
112 | .sys = GPIO113_I2S_SYSCLK_MD, | ||
113 | #else | ||
114 | .sys = GPIO32_SYSCLK_I2S_MD, | ||
115 | #endif | ||
116 | .rx = GPIO29_SDATA_IN_I2S_MD, | ||
117 | .tx = GPIO30_SDATA_OUT_I2S_MD, | ||
118 | .clk = GPIO28_BITCLK_OUT_I2S_MD, | ||
119 | .frm = GPIO31_SYNC_I2S_MD, | ||
120 | }, | ||
121 | }; | ||
122 | |||
123 | static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream) | ||
124 | { | ||
125 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
126 | |||
127 | if (!rtd->cpu_dai->active) { | ||
128 | SACR0 |= SACR0_RST; | ||
129 | SACR0 = 0; | ||
130 | } | ||
131 | |||
132 | return 0; | ||
133 | } | ||
134 | |||
135 | /* wait for I2S controller to be ready */ | ||
136 | static int pxa_i2s_wait(void) | ||
137 | { | ||
138 | int i; | ||
139 | |||
140 | /* flush the Rx FIFO */ | ||
141 | for(i = 0; i < 16; i++) | ||
142 | SADR; | ||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream, | ||
147 | struct snd_pcm_hw_params *params) | ||
148 | { | ||
149 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
150 | |||
151 | pxa_i2s.master = 0; | ||
152 | if (rtd->cpu_dai->dai_runtime.fmt & SND_SOC_DAIFMT_CBS_CFS) | ||
153 | pxa_i2s.master = 1; | ||
154 | |||
155 | if (pxa_i2s.master && !extclk) | ||
156 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].sys); | ||
157 | |||
158 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].rx); | ||
159 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].tx); | ||
160 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].frm); | ||
161 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].clk); | ||
162 | pxa_set_cken(CKEN8_I2S, 1); | ||
163 | pxa_i2s_wait(); | ||
164 | |||
165 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
166 | rtd->cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_out; | ||
167 | else | ||
168 | rtd->cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_in; | ||
169 | |||
170 | /* is port used by another stream */ | ||
171 | if (!(SACR0 & SACR0_ENB)) { | ||
172 | |||
173 | SACR0 = 0; | ||
174 | SACR1 = 0; | ||
175 | if (pxa_i2s.master) | ||
176 | SACR0 |= SACR0_BCKD; | ||
177 | |||
178 | SACR0 |= SACR0_RFTH(14) | SACR0_TFTH(1); | ||
179 | |||
180 | if (rtd->cpu_dai->dai_runtime.fmt & SND_SOC_DAIFMT_LEFT_J) | ||
181 | SACR1 |= SACR1_AMSL; | ||
182 | } | ||
183 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
184 | SAIMR |= SAIMR_TFS; | ||
185 | else | ||
186 | SAIMR |= SAIMR_RFS; | ||
187 | |||
188 | SADIV = rtd->cpu_dai->dai_runtime.priv; | ||
189 | return 0; | ||
190 | } | ||
191 | |||
192 | static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd) | ||
193 | { | ||
194 | int ret = 0; | ||
195 | |||
196 | switch (cmd) { | ||
197 | case SNDRV_PCM_TRIGGER_START: | ||
198 | SACR0 |= SACR0_ENB; | ||
199 | break; | ||
200 | case SNDRV_PCM_TRIGGER_RESUME: | ||
201 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
202 | case SNDRV_PCM_TRIGGER_STOP: | ||
203 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
204 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
205 | break; | ||
206 | default: | ||
207 | ret = -EINVAL; | ||
208 | } | ||
209 | |||
210 | return ret; | ||
211 | } | ||
212 | |||
213 | static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream) | ||
214 | { | ||
215 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
216 | SACR1 |= SACR1_DRPL; | ||
217 | SAIMR &= ~SAIMR_TFS; | ||
218 | } else { | ||
219 | SACR1 |= SACR1_DREC; | ||
220 | SAIMR &= ~SAIMR_RFS; | ||
221 | } | ||
222 | |||
223 | if (SACR1 & (SACR1_DREC | SACR1_DRPL)) { | ||
224 | SACR0 &= ~SACR0_ENB; | ||
225 | pxa_i2s_wait(); | ||
226 | pxa_set_cken(CKEN8_I2S, 0); | ||
227 | } | ||
228 | } | ||
229 | |||
230 | #ifdef CONFIG_PM | ||
231 | static int pxa2xx_i2s_suspend(struct platform_device *dev, | ||
232 | struct snd_soc_cpu_dai *dai) | ||
233 | { | ||
234 | if (!dai->active) | ||
235 | return 0; | ||
236 | |||
237 | /* store registers */ | ||
238 | pxa_i2s.sacr0 = SACR0; | ||
239 | pxa_i2s.sacr1 = SACR1; | ||
240 | pxa_i2s.saimr = SAIMR; | ||
241 | pxa_i2s.sadiv = SADIV; | ||
242 | |||
243 | /* deactivate link */ | ||
244 | SACR0 &= ~SACR0_ENB; | ||
245 | pxa_i2s_wait(); | ||
246 | return 0; | ||
247 | } | ||
248 | |||
249 | static int pxa2xx_i2s_resume(struct platform_device *pdev, | ||
250 | struct snd_soc_cpu_dai *dai) | ||
251 | { | ||
252 | if (!dai->active) | ||
253 | return 0; | ||
254 | |||
255 | pxa_i2s_wait(); | ||
256 | |||
257 | SACR0 = pxa_i2s.sacr0 &= ~SACR0_ENB; | ||
258 | SACR1 = pxa_i2s.sacr1; | ||
259 | SAIMR = pxa_i2s.saimr; | ||
260 | SADIV = pxa_i2s.sadiv; | ||
261 | SACR0 |= SACR0_ENB; | ||
262 | |||
263 | return 0; | ||
264 | } | ||
265 | |||
266 | #else | ||
267 | #define pxa2xx_i2s_suspend NULL | ||
268 | #define pxa2xx_i2s_resume NULL | ||
269 | #endif | ||
270 | |||
271 | /* pxa2xx I2S sysclock is always 256 FS */ | ||
272 | static unsigned int pxa_i2s_config_sysclk(struct snd_soc_cpu_dai *iface, | ||
273 | struct snd_soc_clock_info *info, unsigned int clk) | ||
274 | { | ||
275 | return info->rate << 8; | ||
276 | } | ||
277 | |||
278 | struct snd_soc_cpu_dai pxa_i2s_dai = { | ||
279 | .name = "pxa2xx-i2s", | ||
280 | .id = 0, | ||
281 | .type = SND_SOC_DAI_I2S, | ||
282 | .suspend = pxa2xx_i2s_suspend, | ||
283 | .resume = pxa2xx_i2s_resume, | ||
284 | .config_sysclk = pxa_i2s_config_sysclk, | ||
285 | .playback = { | ||
286 | .channels_min = 2, | ||
287 | .channels_max = 2,}, | ||
288 | .capture = { | ||
289 | .channels_min = 2, | ||
290 | .channels_max = 2,}, | ||
291 | .ops = { | ||
292 | .startup = pxa2xx_i2s_startup, | ||
293 | .shutdown = pxa2xx_i2s_shutdown, | ||
294 | .trigger = pxa2xx_i2s_trigger, | ||
295 | .hw_params = pxa2xx_i2s_hw_params,}, | ||
296 | .caps = { | ||
297 | .num_modes = ARRAY_SIZE(pxa2xx_i2s_modes), | ||
298 | .mode = pxa2xx_i2s_modes,}, | ||
299 | }; | ||
300 | |||
301 | EXPORT_SYMBOL_GPL(pxa_i2s_dai); | ||
302 | |||
303 | /* Module information */ | ||
304 | MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); | ||
305 | MODULE_DESCRIPTION("pxa2xx I2S SoC Interface"); | ||
306 | MODULE_LICENSE("GPL"); | ||