Commit | Line | Data |
---|---|---|
3e7cc3d3 LG |
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. | |
3e7cc3d3 LG |
12 | */ |
13 | ||
14 | #include <linux/init.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/device.h> | |
17 | #include <linux/delay.h> | |
5a2cc50f | 18 | #include <linux/clk.h> |
3e7cc3d3 LG |
19 | #include <sound/core.h> |
20 | #include <sound/pcm.h> | |
21 | #include <sound/initval.h> | |
22 | #include <sound/soc.h> | |
23 | ||
be509729 | 24 | #include <asm/arch/hardware.h> |
3e7cc3d3 | 25 | #include <asm/arch/pxa-regs.h> |
a683b14d | 26 | #include <asm/arch/pxa2xx-gpio.h> |
3e7cc3d3 LG |
27 | #include <asm/arch/audio.h> |
28 | ||
29 | #include "pxa2xx-pcm.h" | |
eaff2ae7 | 30 | #include "pxa2xx-i2s.h" |
3e7cc3d3 LG |
31 | |
32 | struct pxa_i2s_port { | |
33 | u32 sadiv; | |
34 | u32 sacr0; | |
35 | u32 sacr1; | |
36 | u32 saimr; | |
37 | int master; | |
eaff2ae7 | 38 | u32 fmt; |
3e7cc3d3 LG |
39 | }; |
40 | static struct pxa_i2s_port pxa_i2s; | |
5a2cc50f | 41 | static struct clk *clk_i2s; |
3e7cc3d3 | 42 | |
3e7cc3d3 LG |
43 | static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_out = { |
44 | .name = "I2S PCM Stereo out", | |
45 | .dev_addr = __PREG(SADR), | |
46 | .drcmr = &DRCMRTXSADR, | |
47 | .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | | |
48 | DCMD_BURST32 | DCMD_WIDTH4, | |
49 | }; | |
50 | ||
51 | static struct pxa2xx_pcm_dma_params pxa2xx_i2s_pcm_stereo_in = { | |
52 | .name = "I2S PCM Stereo in", | |
53 | .dev_addr = __PREG(SADR), | |
54 | .drcmr = &DRCMRRXSADR, | |
55 | .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | | |
56 | DCMD_BURST32 | DCMD_WIDTH4, | |
57 | }; | |
58 | ||
59 | static struct pxa2xx_gpio gpio_bus[] = { | |
60 | { /* I2S SoC Slave */ | |
61 | .rx = GPIO29_SDATA_IN_I2S_MD, | |
62 | .tx = GPIO30_SDATA_OUT_I2S_MD, | |
63 | .clk = GPIO28_BITCLK_IN_I2S_MD, | |
64 | .frm = GPIO31_SYNC_I2S_MD, | |
65 | }, | |
66 | { /* I2S SoC Master */ | |
67 | #ifdef CONFIG_PXA27x | |
68 | .sys = GPIO113_I2S_SYSCLK_MD, | |
69 | #else | |
70 | .sys = GPIO32_SYSCLK_I2S_MD, | |
71 | #endif | |
72 | .rx = GPIO29_SDATA_IN_I2S_MD, | |
73 | .tx = GPIO30_SDATA_OUT_I2S_MD, | |
74 | .clk = GPIO28_BITCLK_OUT_I2S_MD, | |
75 | .frm = GPIO31_SYNC_I2S_MD, | |
76 | }, | |
77 | }; | |
78 | ||
79 | static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream) | |
80 | { | |
81 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
917f93ac | 82 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; |
3e7cc3d3 | 83 | |
5a2cc50f | 84 | clk_i2s = clk_get(NULL, "I2SCLK"); |
85 | if (IS_ERR(clk_i2s)) | |
86 | return PTR_ERR(clk_i2s); | |
87 | ||
eaff2ae7 | 88 | if (!cpu_dai->active) { |
3e7cc3d3 LG |
89 | SACR0 |= SACR0_RST; |
90 | SACR0 = 0; | |
91 | } | |
92 | ||
93 | return 0; | |
94 | } | |
95 | ||
96 | /* wait for I2S controller to be ready */ | |
97 | static int pxa_i2s_wait(void) | |
98 | { | |
99 | int i; | |
100 | ||
101 | /* flush the Rx FIFO */ | |
102 | for(i = 0; i < 16; i++) | |
103 | SADR; | |
104 | return 0; | |
105 | } | |
106 | ||
917f93ac | 107 | static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, |
eaff2ae7 | 108 | unsigned int fmt) |
3e7cc3d3 | 109 | { |
eaff2ae7 PZ |
110 | /* interface format */ |
111 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
112 | case SND_SOC_DAIFMT_I2S: | |
113 | pxa_i2s.fmt = 0; | |
114 | break; | |
115 | case SND_SOC_DAIFMT_LEFT_J: | |
116 | pxa_i2s.fmt = SACR1_AMSL; | |
117 | break; | |
118 | } | |
3e7cc3d3 | 119 | |
eaff2ae7 PZ |
120 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
121 | case SND_SOC_DAIFMT_CBS_CFS: | |
3e7cc3d3 | 122 | pxa_i2s.master = 1; |
eaff2ae7 PZ |
123 | break; |
124 | case SND_SOC_DAIFMT_CBM_CFS: | |
125 | pxa_i2s.master = 0; | |
126 | break; | |
127 | default: | |
128 | break; | |
129 | } | |
130 | return 0; | |
131 | } | |
3e7cc3d3 | 132 | |
917f93ac | 133 | static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai, |
eaff2ae7 PZ |
134 | int clk_id, unsigned int freq, int dir) |
135 | { | |
136 | if (clk_id != PXA2XX_I2S_SYSCLK) | |
137 | return -ENODEV; | |
138 | ||
139 | if (pxa_i2s.master && dir == SND_SOC_CLOCK_OUT) | |
3e7cc3d3 LG |
140 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].sys); |
141 | ||
eaff2ae7 PZ |
142 | return 0; |
143 | } | |
144 | ||
145 | static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream, | |
146 | struct snd_pcm_hw_params *params) | |
147 | { | |
148 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
917f93ac | 149 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; |
eaff2ae7 | 150 | |
3e7cc3d3 LG |
151 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].rx); |
152 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].tx); | |
153 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].frm); | |
154 | pxa_gpio_mode(gpio_bus[pxa_i2s.master].clk); | |
5a2cc50f | 155 | clk_enable(clk_i2s); |
3e7cc3d3 LG |
156 | pxa_i2s_wait(); |
157 | ||
158 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
eaff2ae7 | 159 | cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_out; |
3e7cc3d3 | 160 | else |
eaff2ae7 | 161 | cpu_dai->dma_data = &pxa2xx_i2s_pcm_stereo_in; |
3e7cc3d3 LG |
162 | |
163 | /* is port used by another stream */ | |
164 | if (!(SACR0 & SACR0_ENB)) { | |
165 | ||
166 | SACR0 = 0; | |
167 | SACR1 = 0; | |
168 | if (pxa_i2s.master) | |
169 | SACR0 |= SACR0_BCKD; | |
170 | ||
171 | SACR0 |= SACR0_RFTH(14) | SACR0_TFTH(1); | |
eaff2ae7 | 172 | SACR1 |= pxa_i2s.fmt; |
3e7cc3d3 LG |
173 | } |
174 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
175 | SAIMR |= SAIMR_TFS; | |
176 | else | |
177 | SAIMR |= SAIMR_RFS; | |
178 | ||
eaff2ae7 PZ |
179 | switch (params_rate(params)) { |
180 | case 8000: | |
181 | SADIV = 0x48; | |
182 | break; | |
183 | case 11025: | |
184 | SADIV = 0x34; | |
185 | break; | |
186 | case 16000: | |
187 | SADIV = 0x24; | |
188 | break; | |
189 | case 22050: | |
190 | SADIV = 0x1a; | |
191 | break; | |
192 | case 44100: | |
193 | SADIV = 0xd; | |
194 | break; | |
195 | case 48000: | |
196 | SADIV = 0xc; | |
197 | break; | |
198 | case 96000: /* not in manual and possibly slightly inaccurate */ | |
199 | SADIV = 0x6; | |
200 | break; | |
201 | } | |
202 | ||
3e7cc3d3 LG |
203 | return 0; |
204 | } | |
205 | ||
206 | static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd) | |
207 | { | |
208 | int ret = 0; | |
209 | ||
210 | switch (cmd) { | |
211 | case SNDRV_PCM_TRIGGER_START: | |
212 | SACR0 |= SACR0_ENB; | |
213 | break; | |
214 | case SNDRV_PCM_TRIGGER_RESUME: | |
215 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
216 | case SNDRV_PCM_TRIGGER_STOP: | |
217 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
218 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
219 | break; | |
220 | default: | |
221 | ret = -EINVAL; | |
222 | } | |
223 | ||
224 | return ret; | |
225 | } | |
226 | ||
227 | static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream) | |
228 | { | |
229 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
230 | SACR1 |= SACR1_DRPL; | |
231 | SAIMR &= ~SAIMR_TFS; | |
232 | } else { | |
233 | SACR1 |= SACR1_DREC; | |
234 | SAIMR &= ~SAIMR_RFS; | |
235 | } | |
236 | ||
237 | if (SACR1 & (SACR1_DREC | SACR1_DRPL)) { | |
238 | SACR0 &= ~SACR0_ENB; | |
239 | pxa_i2s_wait(); | |
5a2cc50f | 240 | clk_disable(clk_i2s); |
3e7cc3d3 | 241 | } |
5a2cc50f | 242 | |
243 | clk_put(clk_i2s); | |
3e7cc3d3 LG |
244 | } |
245 | ||
246 | #ifdef CONFIG_PM | |
247 | static int pxa2xx_i2s_suspend(struct platform_device *dev, | |
917f93ac | 248 | struct snd_soc_dai *dai) |
3e7cc3d3 LG |
249 | { |
250 | if (!dai->active) | |
251 | return 0; | |
252 | ||
253 | /* store registers */ | |
254 | pxa_i2s.sacr0 = SACR0; | |
255 | pxa_i2s.sacr1 = SACR1; | |
256 | pxa_i2s.saimr = SAIMR; | |
257 | pxa_i2s.sadiv = SADIV; | |
258 | ||
259 | /* deactivate link */ | |
260 | SACR0 &= ~SACR0_ENB; | |
261 | pxa_i2s_wait(); | |
262 | return 0; | |
263 | } | |
264 | ||
265 | static int pxa2xx_i2s_resume(struct platform_device *pdev, | |
917f93ac | 266 | struct snd_soc_dai *dai) |
3e7cc3d3 LG |
267 | { |
268 | if (!dai->active) | |
269 | return 0; | |
270 | ||
271 | pxa_i2s_wait(); | |
272 | ||
273 | SACR0 = pxa_i2s.sacr0 &= ~SACR0_ENB; | |
274 | SACR1 = pxa_i2s.sacr1; | |
275 | SAIMR = pxa_i2s.saimr; | |
276 | SADIV = pxa_i2s.sadiv; | |
277 | SACR0 |= SACR0_ENB; | |
278 | ||
279 | return 0; | |
280 | } | |
281 | ||
282 | #else | |
283 | #define pxa2xx_i2s_suspend NULL | |
284 | #define pxa2xx_i2s_resume NULL | |
285 | #endif | |
286 | ||
eaff2ae7 PZ |
287 | #define PXA2XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ |
288 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ | |
289 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) | |
3e7cc3d3 | 290 | |
917f93ac | 291 | struct snd_soc_dai pxa_i2s_dai = { |
3e7cc3d3 LG |
292 | .name = "pxa2xx-i2s", |
293 | .id = 0, | |
294 | .type = SND_SOC_DAI_I2S, | |
295 | .suspend = pxa2xx_i2s_suspend, | |
296 | .resume = pxa2xx_i2s_resume, | |
3e7cc3d3 LG |
297 | .playback = { |
298 | .channels_min = 2, | |
eaff2ae7 PZ |
299 | .channels_max = 2, |
300 | .rates = PXA2XX_I2S_RATES, | |
301 | .formats = SNDRV_PCM_FMTBIT_S16_LE,}, | |
3e7cc3d3 LG |
302 | .capture = { |
303 | .channels_min = 2, | |
eaff2ae7 PZ |
304 | .channels_max = 2, |
305 | .rates = PXA2XX_I2S_RATES, | |
306 | .formats = SNDRV_PCM_FMTBIT_S16_LE,}, | |
3e7cc3d3 LG |
307 | .ops = { |
308 | .startup = pxa2xx_i2s_startup, | |
309 | .shutdown = pxa2xx_i2s_shutdown, | |
310 | .trigger = pxa2xx_i2s_trigger, | |
311 | .hw_params = pxa2xx_i2s_hw_params,}, | |
eaff2ae7 PZ |
312 | .dai_ops = { |
313 | .set_fmt = pxa2xx_i2s_set_dai_fmt, | |
314 | .set_sysclk = pxa2xx_i2s_set_dai_sysclk, | |
315 | }, | |
3e7cc3d3 LG |
316 | }; |
317 | ||
318 | EXPORT_SYMBOL_GPL(pxa_i2s_dai); | |
319 | ||
320 | /* Module information */ | |
321 | MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); | |
322 | MODULE_DESCRIPTION("pxa2xx I2S SoC Interface"); | |
323 | MODULE_LICENSE("GPL"); |