diff options
author | Manuel Lauss <mano@roarinelk.homelinux.net> | 2007-05-14 12:40:07 -0400 |
---|---|---|
committer | Jaroslav Kysela <perex@suse.cz> | 2007-07-20 05:11:17 -0400 |
commit | aef3b06ac69783d6a6d1e4357c62bab46dd16141 (patch) | |
tree | 7c4e86d880a08ee5639d87a3702f7318203710d5 /sound/soc/sh/ssi.c | |
parent | 80ab1c0e9ea90467e34dd3187b1d8162e8be314b (diff) |
[ALSA] SH7760 ASoC support
ALSA ASoC support for SH7760
This patch adds ALSA ASoC drivers for the Audio interfaces
of the SH7760 SoC:
Add driver for the SH7760 DMA engine (dmabrg)
Add AC97 driver for HAC unit(s) found on SH7760/SH7780
Add I2S driver for SSI unit(s) found on SH7760/SH7780
Add a generic SH7760-AC97 machine driver.
Hook it all up with the build system.
Signed-off-by: Manuel Lauss <mano@roarinelk.homelinux.net>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
Diffstat (limited to 'sound/soc/sh/ssi.c')
-rw-r--r-- | sound/soc/sh/ssi.c | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c new file mode 100644 index 000000000000..b72bc316cb8e --- /dev/null +++ b/sound/soc/sh/ssi.c | |||
@@ -0,0 +1,400 @@ | |||
1 | /* | ||
2 | * Serial Sound Interface (I2S) support for SH7760/SH7780 | ||
3 | * | ||
4 | * Copyright (c) 2007 Manuel Lauss <mano@roarinelk.homelinux.net> | ||
5 | * | ||
6 | * licensed under the terms outlined in the file COPYING at the root | ||
7 | * of the linux kernel sources. | ||
8 | * | ||
9 | * dont forget to set IPSEL/OMSEL register bits (in your board code) to | ||
10 | * enable SSI output pins! | ||
11 | */ | ||
12 | |||
13 | /* | ||
14 | * LIMITATIONS: | ||
15 | * The SSI unit has only one physical data line, so full duplex is | ||
16 | * impossible. This can be remedied on the SH7760 by using the | ||
17 | * other SSI unit for recording; however the SH7780 has only 1 SSI | ||
18 | * unit, and its pins are shared with the AC97 unit, among others. | ||
19 | * | ||
20 | * FEATURES: | ||
21 | * The SSI features "compressed mode": in this mode it continuously | ||
22 | * streams PCM data over the I2S lines and uses LRCK as a handshake | ||
23 | * signal. Can be used to send compressed data (AC3/DTS) to a DSP. | ||
24 | * The number of bits sent over the wire in a frame can be adjusted | ||
25 | * and can be independent from the actual sample bit depth. This is | ||
26 | * useful to support TDM mode codecs like the AD1939 which have a | ||
27 | * fixed TDM slot size, regardless of sample resolution. | ||
28 | */ | ||
29 | |||
30 | #include <linux/init.h> | ||
31 | #include <linux/module.h> | ||
32 | #include <linux/platform_device.h> | ||
33 | #include <sound/driver.h> | ||
34 | #include <sound/core.h> | ||
35 | #include <sound/pcm.h> | ||
36 | #include <sound/initval.h> | ||
37 | #include <sound/soc.h> | ||
38 | #include <asm/io.h> | ||
39 | |||
40 | #define SSICR 0x00 | ||
41 | #define SSISR 0x04 | ||
42 | |||
43 | #define CR_DMAEN (1 << 28) | ||
44 | #define CR_CHNL_SHIFT 22 | ||
45 | #define CR_CHNL_MASK (3 << CR_CHNL_SHIFT) | ||
46 | #define CR_DWL_SHIFT 19 | ||
47 | #define CR_DWL_MASK (7 << CR_DWL_SHIFT) | ||
48 | #define CR_SWL_SHIFT 16 | ||
49 | #define CR_SWL_MASK (7 << CR_SWL_SHIFT) | ||
50 | #define CR_SCK_MASTER (1 << 15) /* bitclock master bit */ | ||
51 | #define CR_SWS_MASTER (1 << 14) /* wordselect master bit */ | ||
52 | #define CR_SCKP (1 << 13) /* I2Sclock polarity */ | ||
53 | #define CR_SWSP (1 << 12) /* LRCK polarity */ | ||
54 | #define CR_SPDP (1 << 11) | ||
55 | #define CR_SDTA (1 << 10) /* i2s alignment (msb/lsb) */ | ||
56 | #define CR_PDTA (1 << 9) /* fifo data alignment */ | ||
57 | #define CR_DEL (1 << 8) /* delay data by 1 i2sclk */ | ||
58 | #define CR_BREN (1 << 7) /* clock gating in burst mode */ | ||
59 | #define CR_CKDIV_SHIFT 4 | ||
60 | #define CR_CKDIV_MASK (7 << CR_CKDIV_SHIFT) /* bitclock divider */ | ||
61 | #define CR_MUTE (1 << 3) /* SSI mute */ | ||
62 | #define CR_CPEN (1 << 2) /* compressed mode */ | ||
63 | #define CR_TRMD (1 << 1) /* transmit/receive select */ | ||
64 | #define CR_EN (1 << 0) /* enable SSI */ | ||
65 | |||
66 | #define SSIREG(reg) (*(unsigned long *)(ssi->mmio + (reg))) | ||
67 | |||
68 | struct ssi_priv { | ||
69 | unsigned long mmio; | ||
70 | unsigned long sysclk; | ||
71 | int inuse; | ||
72 | } ssi_cpu_data[] = { | ||
73 | #if defined(CONFIG_CPU_SUBTYPE_SH7760) | ||
74 | { | ||
75 | .mmio = 0xFE680000, | ||
76 | }, | ||
77 | { | ||
78 | .mmio = 0xFE690000, | ||
79 | }, | ||
80 | #elif defined(CONFIG_CPU_SUBTYPE_SH7780) | ||
81 | { | ||
82 | .mmio = 0xFFE70000, | ||
83 | }, | ||
84 | #else | ||
85 | #error "Unsupported SuperH SoC" | ||
86 | #endif | ||
87 | }; | ||
88 | |||
89 | /* | ||
90 | * track usage of the SSI; it is simplex-only so prevent attempts of | ||
91 | * concurrent playback + capture. FIXME: any locking required? | ||
92 | */ | ||
93 | static int ssi_startup(struct snd_pcm_substream *substream) | ||
94 | { | ||
95 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
96 | struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; | ||
97 | if (ssi->inuse) { | ||
98 | pr_debug("ssi: already in use!\n"); | ||
99 | return -EBUSY; | ||
100 | } else | ||
101 | ssi->inuse = 1; | ||
102 | return 0; | ||
103 | } | ||
104 | |||
105 | static void ssi_shutdown(struct snd_pcm_substream *substream) | ||
106 | { | ||
107 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
108 | struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; | ||
109 | |||
110 | ssi->inuse = 0; | ||
111 | } | ||
112 | |||
113 | static int ssi_trigger(struct snd_pcm_substream *substream, int cmd) | ||
114 | { | ||
115 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
116 | struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; | ||
117 | |||
118 | switch (cmd) { | ||
119 | case SNDRV_PCM_TRIGGER_START: | ||
120 | SSIREG(SSICR) |= CR_DMAEN | CR_EN; | ||
121 | break; | ||
122 | case SNDRV_PCM_TRIGGER_STOP: | ||
123 | SSIREG(SSICR) &= ~(CR_DMAEN | CR_EN); | ||
124 | break; | ||
125 | default: | ||
126 | return -EINVAL; | ||
127 | } | ||
128 | |||
129 | return 0; | ||
130 | } | ||
131 | |||
132 | static int ssi_hw_params(struct snd_pcm_substream *substream, | ||
133 | struct snd_pcm_hw_params *params) | ||
134 | { | ||
135 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
136 | struct ssi_priv *ssi = &ssi_cpu_data[rtd->dai->cpu_dai->id]; | ||
137 | unsigned long ssicr = SSIREG(SSICR); | ||
138 | unsigned int bits, channels, swl, recv, i; | ||
139 | |||
140 | channels = params_channels(params); | ||
141 | bits = params->msbits; | ||
142 | recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; | ||
143 | |||
144 | pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr); | ||
145 | pr_debug("bits: %d channels: %d\n", bits, channels); | ||
146 | |||
147 | ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA | | ||
148 | CR_SWL_MASK); | ||
149 | |||
150 | /* direction (send/receive) */ | ||
151 | if (!recv) | ||
152 | ssicr |= CR_TRMD; /* transmit */ | ||
153 | |||
154 | /* channels */ | ||
155 | if ((channels < 2) || (channels > 8) || (channels & 1)) { | ||
156 | pr_debug("ssi: invalid number of channels\n"); | ||
157 | return -EINVAL; | ||
158 | } | ||
159 | ssicr |= ((channels >> 1) - 1) << CR_CHNL_SHIFT; | ||
160 | |||
161 | /* DATA WORD LENGTH (DWL): databits in audio sample */ | ||
162 | i = 0; | ||
163 | switch (bits) { | ||
164 | case 32: ++i; | ||
165 | case 24: ++i; | ||
166 | case 22: ++i; | ||
167 | case 20: ++i; | ||
168 | case 18: ++i; | ||
169 | case 16: ++i; | ||
170 | ssicr |= i << CR_DWL_SHIFT; | ||
171 | case 8: break; | ||
172 | default: | ||
173 | pr_debug("ssi: invalid sample width\n"); | ||
174 | return -EINVAL; | ||
175 | } | ||
176 | |||
177 | /* | ||
178 | * SYSTEM WORD LENGTH: size in bits of half a frame over the I2S | ||
179 | * wires. This is usually bits_per_sample x channels/2; i.e. in | ||
180 | * Stereo mode the SWL equals DWL. SWL can be bigger than the | ||
181 | * product of (channels_per_slot x samplebits), e.g. for codecs | ||
182 | * like the AD1939 which only accept 32bit wide TDM slots. For | ||
183 | * "standard" I2S operation we set SWL = chans / 2 * DWL here. | ||
184 | * Waiting for ASoC to get TDM support ;-) | ||
185 | */ | ||
186 | if ((bits > 16) && (bits <= 24)) { | ||
187 | bits = 24; /* these are padded by the SSI */ | ||
188 | /*ssicr |= CR_PDTA;*/ /* cpu/data endianness ? */ | ||
189 | } | ||
190 | i = 0; | ||
191 | swl = (bits * channels) / 2; | ||
192 | switch (swl) { | ||
193 | case 256: ++i; | ||
194 | case 128: ++i; | ||
195 | case 64: ++i; | ||
196 | case 48: ++i; | ||
197 | case 32: ++i; | ||
198 | case 16: ++i; | ||
199 | ssicr |= i << CR_SWL_SHIFT; | ||
200 | case 8: break; | ||
201 | default: | ||
202 | pr_debug("ssi: invalid system word length computed\n"); | ||
203 | return -EINVAL; | ||
204 | } | ||
205 | |||
206 | SSIREG(SSICR) = ssicr; | ||
207 | |||
208 | pr_debug("ssi_hw_params() leave\nssicr is now %08lx\n", ssicr); | ||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | static int ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, | ||
213 | unsigned int freq, int dir) | ||
214 | { | ||
215 | struct ssi_priv *ssi = &ssi_cpu_data[cpu_dai->id]; | ||
216 | |||
217 | ssi->sysclk = freq; | ||
218 | |||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | /* | ||
223 | * This divider is used to generate the SSI_SCK (I2S bitclock) from the | ||
224 | * clock at the HAC_BIT_CLK ("oversampling clock") pin. | ||
225 | */ | ||
226 | static int ssi_set_clkdiv(struct snd_soc_cpu_dai *dai, int did, int div) | ||
227 | { | ||
228 | struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; | ||
229 | unsigned long ssicr; | ||
230 | int i; | ||
231 | |||
232 | i = 0; | ||
233 | ssicr = SSIREG(SSICR) & ~CR_CKDIV_MASK; | ||
234 | switch (div) { | ||
235 | case 16: ++i; | ||
236 | case 8: ++i; | ||
237 | case 4: ++i; | ||
238 | case 2: ++i; | ||
239 | SSIREG(SSICR) = ssicr | (i << CR_CKDIV_SHIFT); | ||
240 | case 1: break; | ||
241 | default: | ||
242 | pr_debug("ssi: invalid sck divider %d\n", div); | ||
243 | return -EINVAL; | ||
244 | } | ||
245 | |||
246 | return 0; | ||
247 | } | ||
248 | |||
249 | static int ssi_set_fmt(struct snd_soc_cpu_dai *dai, unsigned int fmt) | ||
250 | { | ||
251 | struct ssi_priv *ssi = &ssi_cpu_data[dai->id]; | ||
252 | unsigned long ssicr = SSIREG(SSICR); | ||
253 | |||
254 | pr_debug("ssi_set_fmt()\nssicr was 0x%08lx\n", ssicr); | ||
255 | |||
256 | ssicr &= ~(CR_DEL | CR_PDTA | CR_BREN | CR_SWSP | CR_SCKP | | ||
257 | CR_SWS_MASTER | CR_SCK_MASTER); | ||
258 | |||
259 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
260 | case SND_SOC_DAIFMT_I2S: | ||
261 | break; | ||
262 | case SND_SOC_DAIFMT_RIGHT_J: | ||
263 | ssicr |= CR_DEL | CR_PDTA; | ||
264 | break; | ||
265 | case SND_SOC_DAIFMT_LEFT_J: | ||
266 | ssicr |= CR_DEL; | ||
267 | break; | ||
268 | default: | ||
269 | pr_debug("ssi: unsupported format\n"); | ||
270 | return -EINVAL; | ||
271 | } | ||
272 | |||
273 | switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { | ||
274 | case SND_SOC_DAIFMT_CONT: | ||
275 | break; | ||
276 | case SND_SOC_DAIFMT_GATED: | ||
277 | ssicr |= CR_BREN; | ||
278 | break; | ||
279 | } | ||
280 | |||
281 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | ||
282 | case SND_SOC_DAIFMT_NB_NF: | ||
283 | ssicr |= CR_SCKP; /* sample data at low clkedge */ | ||
284 | break; | ||
285 | case SND_SOC_DAIFMT_NB_IF: | ||
286 | ssicr |= CR_SCKP | CR_SWSP; | ||
287 | break; | ||
288 | case SND_SOC_DAIFMT_IB_NF: | ||
289 | break; | ||
290 | case SND_SOC_DAIFMT_IB_IF: | ||
291 | ssicr |= CR_SWSP; /* word select starts low */ | ||
292 | break; | ||
293 | default: | ||
294 | pr_debug("ssi: invalid inversion\n"); | ||
295 | return -EINVAL; | ||
296 | } | ||
297 | |||
298 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
299 | case SND_SOC_DAIFMT_CBM_CFM: | ||
300 | break; | ||
301 | case SND_SOC_DAIFMT_CBS_CFM: | ||
302 | ssicr |= CR_SCK_MASTER; | ||
303 | break; | ||
304 | case SND_SOC_DAIFMT_CBM_CFS: | ||
305 | ssicr |= CR_SWS_MASTER; | ||
306 | break; | ||
307 | case SND_SOC_DAIFMT_CBS_CFS: | ||
308 | ssicr |= CR_SWS_MASTER | CR_SCK_MASTER; | ||
309 | break; | ||
310 | default: | ||
311 | pr_debug("ssi: invalid master/slave configuration\n"); | ||
312 | return -EINVAL; | ||
313 | } | ||
314 | |||
315 | SSIREG(SSICR) = ssicr; | ||
316 | pr_debug("ssi_set_fmt() leave\nssicr is now 0x%08lx\n", ssicr); | ||
317 | |||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | /* the SSI depends on an external clocksource (at HAC_BIT_CLK) even in | ||
322 | * Master mode, so really this is board specific; the SSI can do any | ||
323 | * rate with the right bitclk and divider settings. | ||
324 | */ | ||
325 | #define SSI_RATES \ | ||
326 | SNDRV_PCM_RATE_8000_192000 | ||
327 | |||
328 | /* the SSI can do 8-32 bit samples, with 8 possible channels */ | ||
329 | #define SSI_FMTS \ | ||
330 | (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ | ||
331 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ | ||
332 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ | ||
333 | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3LE | \ | ||
334 | SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE) | ||
335 | |||
336 | struct snd_soc_cpu_dai sh4_ssi_dai[] = { | ||
337 | { | ||
338 | .name = "SSI0", | ||
339 | .id = 0, | ||
340 | .type = SND_SOC_DAI_I2S, | ||
341 | .playback = { | ||
342 | .rates = SSI_RATES, | ||
343 | .formats = SSI_FMTS, | ||
344 | .channels_min = 2, | ||
345 | .channels_max = 8, | ||
346 | }, | ||
347 | .capture = { | ||
348 | .rates = SSI_RATES, | ||
349 | .formats = SSI_FMTS, | ||
350 | .channels_min = 2, | ||
351 | .channels_max = 8, | ||
352 | }, | ||
353 | .ops = { | ||
354 | .startup = ssi_startup, | ||
355 | .shutdown = ssi_shutdown, | ||
356 | .trigger = ssi_trigger, | ||
357 | .hw_params = ssi_hw_params, | ||
358 | }, | ||
359 | .dai_ops = { | ||
360 | .set_sysclk = ssi_set_sysclk, | ||
361 | .set_clkdiv = ssi_set_clkdiv, | ||
362 | .set_fmt = ssi_set_fmt, | ||
363 | }, | ||
364 | }, | ||
365 | #ifdef CONFIG_CPU_SUBTYPE_SH7760 | ||
366 | { | ||
367 | .name = "SSI1", | ||
368 | .id = 1, | ||
369 | .type = SND_SOC_DAI_I2S, | ||
370 | .playback = { | ||
371 | .rates = SSI_RATES, | ||
372 | .formats = SSI_FMTS, | ||
373 | .channels_min = 2, | ||
374 | .channels_max = 8, | ||
375 | }, | ||
376 | .capture = { | ||
377 | .rates = SSI_RATES, | ||
378 | .formats = SSI_FMTS, | ||
379 | .channels_min = 2, | ||
380 | .channels_max = 8, | ||
381 | }, | ||
382 | .ops = { | ||
383 | .startup = ssi_startup, | ||
384 | .shutdown = ssi_shutdown, | ||
385 | .trigger = ssi_trigger, | ||
386 | .hw_params = ssi_hw_params, | ||
387 | }, | ||
388 | .dai_ops = { | ||
389 | .set_sysclk = ssi_set_sysclk, | ||
390 | .set_clkdiv = ssi_set_clkdiv, | ||
391 | .set_fmt = ssi_set_fmt, | ||
392 | }, | ||
393 | }, | ||
394 | #endif | ||
395 | }; | ||
396 | EXPORT_SYMBOL_GPL(sh4_ssi_dai); | ||
397 | |||
398 | MODULE_LICENSE("GPL"); | ||
399 | MODULE_DESCRIPTION("SuperH onchip SSI (I2S) audio driver"); | ||
400 | MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); | ||