diff options
Diffstat (limited to 'sound/soc/au1x/i2sc.c')
-rw-r--r-- | sound/soc/au1x/i2sc.c | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c new file mode 100644 index 000000000000..19e0d2a9c828 --- /dev/null +++ b/sound/soc/au1x/i2sc.c | |||
@@ -0,0 +1,346 @@ | |||
1 | /* | ||
2 | * Au1000/Au1500/Au1100 I2S controller driver for ASoC | ||
3 | * | ||
4 | * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com> | ||
5 | * | ||
6 | * Note: clock supplied to the I2S controller must be 256x samplerate. | ||
7 | */ | ||
8 | |||
9 | #include <linux/init.h> | ||
10 | #include <linux/module.h> | ||
11 | #include <linux/slab.h> | ||
12 | #include <linux/suspend.h> | ||
13 | #include <sound/core.h> | ||
14 | #include <sound/pcm.h> | ||
15 | #include <sound/initval.h> | ||
16 | #include <sound/soc.h> | ||
17 | #include <asm/mach-au1x00/au1000.h> | ||
18 | |||
19 | #include "psc.h" | ||
20 | |||
21 | #define I2S_RXTX 0x00 | ||
22 | #define I2S_CFG 0x04 | ||
23 | #define I2S_ENABLE 0x08 | ||
24 | |||
25 | #define CFG_XU (1 << 25) /* tx underflow */ | ||
26 | #define CFG_XO (1 << 24) | ||
27 | #define CFG_RU (1 << 23) | ||
28 | #define CFG_RO (1 << 22) | ||
29 | #define CFG_TR (1 << 21) | ||
30 | #define CFG_TE (1 << 20) | ||
31 | #define CFG_TF (1 << 19) | ||
32 | #define CFG_RR (1 << 18) | ||
33 | #define CFG_RF (1 << 17) | ||
34 | #define CFG_ICK (1 << 12) /* clock invert */ | ||
35 | #define CFG_PD (1 << 11) /* set to make I2SDIO INPUT */ | ||
36 | #define CFG_LB (1 << 10) /* loopback */ | ||
37 | #define CFG_IC (1 << 9) /* word select invert */ | ||
38 | #define CFG_FM_I2S (0 << 7) /* I2S format */ | ||
39 | #define CFG_FM_LJ (1 << 7) /* left-justified */ | ||
40 | #define CFG_FM_RJ (2 << 7) /* right-justified */ | ||
41 | #define CFG_FM_MASK (3 << 7) | ||
42 | #define CFG_TN (1 << 6) /* tx fifo en */ | ||
43 | #define CFG_RN (1 << 5) /* rx fifo en */ | ||
44 | #define CFG_SZ_8 (0x08) | ||
45 | #define CFG_SZ_16 (0x10) | ||
46 | #define CFG_SZ_18 (0x12) | ||
47 | #define CFG_SZ_20 (0x14) | ||
48 | #define CFG_SZ_24 (0x18) | ||
49 | #define CFG_SZ_MASK (0x1f) | ||
50 | #define EN_D (1 << 1) /* DISable */ | ||
51 | #define EN_CE (1 << 0) /* clock enable */ | ||
52 | |||
53 | /* only limited by clock generator and board design */ | ||
54 | #define AU1XI2SC_RATES \ | ||
55 | SNDRV_PCM_RATE_CONTINUOUS | ||
56 | |||
57 | #define AU1XI2SC_FMTS \ | ||
58 | (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ | ||
59 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ | ||
60 | SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \ | ||
61 | SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE | \ | ||
62 | SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE | \ | ||
63 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ | ||
64 | SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE | \ | ||
65 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \ | ||
66 | SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE | \ | ||
67 | 0) | ||
68 | |||
69 | static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg) | ||
70 | { | ||
71 | return __raw_readl(ctx->mmio + reg); | ||
72 | } | ||
73 | |||
74 | static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v) | ||
75 | { | ||
76 | __raw_writel(v, ctx->mmio + reg); | ||
77 | wmb(); | ||
78 | } | ||
79 | |||
80 | static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) | ||
81 | { | ||
82 | struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai); | ||
83 | unsigned long c; | ||
84 | int ret; | ||
85 | |||
86 | ret = -EINVAL; | ||
87 | c = ctx->cfg; | ||
88 | |||
89 | c &= ~CFG_FM_MASK; | ||
90 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
91 | case SND_SOC_DAIFMT_I2S: | ||
92 | c |= CFG_FM_I2S; | ||
93 | break; | ||
94 | case SND_SOC_DAIFMT_MSB: | ||
95 | c |= CFG_FM_RJ; | ||
96 | break; | ||
97 | case SND_SOC_DAIFMT_LSB: | ||
98 | c |= CFG_FM_LJ; | ||
99 | break; | ||
100 | default: | ||
101 | goto out; | ||
102 | } | ||
103 | |||
104 | c &= ~(CFG_IC | CFG_ICK); /* IB-IF */ | ||
105 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | ||
106 | case SND_SOC_DAIFMT_NB_NF: | ||
107 | c |= CFG_IC | CFG_ICK; | ||
108 | break; | ||
109 | case SND_SOC_DAIFMT_NB_IF: | ||
110 | c |= CFG_IC; | ||
111 | break; | ||
112 | case SND_SOC_DAIFMT_IB_NF: | ||
113 | c |= CFG_ICK; | ||
114 | break; | ||
115 | case SND_SOC_DAIFMT_IB_IF: | ||
116 | break; | ||
117 | default: | ||
118 | goto out; | ||
119 | } | ||
120 | |||
121 | /* I2S controller only supports master */ | ||
122 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
123 | case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */ | ||
124 | break; | ||
125 | default: | ||
126 | goto out; | ||
127 | } | ||
128 | |||
129 | ret = 0; | ||
130 | ctx->cfg = c; | ||
131 | out: | ||
132 | return ret; | ||
133 | } | ||
134 | |||
135 | static int au1xi2s_trigger(struct snd_pcm_substream *substream, | ||
136 | int cmd, struct snd_soc_dai *dai) | ||
137 | { | ||
138 | struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); | ||
139 | int stype = SUBSTREAM_TYPE(substream); | ||
140 | |||
141 | switch (cmd) { | ||
142 | case SNDRV_PCM_TRIGGER_START: | ||
143 | case SNDRV_PCM_TRIGGER_RESUME: | ||
144 | /* power up */ | ||
145 | WR(ctx, I2S_ENABLE, EN_D | EN_CE); | ||
146 | WR(ctx, I2S_ENABLE, EN_CE); | ||
147 | ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN; | ||
148 | WR(ctx, I2S_CFG, ctx->cfg); | ||
149 | break; | ||
150 | case SNDRV_PCM_TRIGGER_STOP: | ||
151 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
152 | ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN); | ||
153 | WR(ctx, I2S_CFG, ctx->cfg); | ||
154 | WR(ctx, I2S_ENABLE, EN_D); /* power off */ | ||
155 | break; | ||
156 | default: | ||
157 | return -EINVAL; | ||
158 | } | ||
159 | |||
160 | return 0; | ||
161 | } | ||
162 | |||
163 | static unsigned long msbits_to_reg(int msbits) | ||
164 | { | ||
165 | switch (msbits) { | ||
166 | case 8: | ||
167 | return CFG_SZ_8; | ||
168 | case 16: | ||
169 | return CFG_SZ_16; | ||
170 | case 18: | ||
171 | return CFG_SZ_18; | ||
172 | case 20: | ||
173 | return CFG_SZ_20; | ||
174 | case 24: | ||
175 | return CFG_SZ_24; | ||
176 | } | ||
177 | return 0; | ||
178 | } | ||
179 | |||
180 | static int au1xi2s_hw_params(struct snd_pcm_substream *substream, | ||
181 | struct snd_pcm_hw_params *params, | ||
182 | struct snd_soc_dai *dai) | ||
183 | { | ||
184 | struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); | ||
185 | unsigned long v; | ||
186 | |||
187 | v = msbits_to_reg(params->msbits); | ||
188 | if (!v) | ||
189 | return -EINVAL; | ||
190 | |||
191 | ctx->cfg &= ~CFG_SZ_MASK; | ||
192 | ctx->cfg |= v; | ||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | static int au1xi2s_startup(struct snd_pcm_substream *substream, | ||
197 | struct snd_soc_dai *dai) | ||
198 | { | ||
199 | struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); | ||
200 | snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]); | ||
201 | return 0; | ||
202 | } | ||
203 | |||
204 | static const struct snd_soc_dai_ops au1xi2s_dai_ops = { | ||
205 | .startup = au1xi2s_startup, | ||
206 | .trigger = au1xi2s_trigger, | ||
207 | .hw_params = au1xi2s_hw_params, | ||
208 | .set_fmt = au1xi2s_set_fmt, | ||
209 | }; | ||
210 | |||
211 | static struct snd_soc_dai_driver au1xi2s_dai_driver = { | ||
212 | .symmetric_rates = 1, | ||
213 | .playback = { | ||
214 | .rates = AU1XI2SC_RATES, | ||
215 | .formats = AU1XI2SC_FMTS, | ||
216 | .channels_min = 2, | ||
217 | .channels_max = 2, | ||
218 | }, | ||
219 | .capture = { | ||
220 | .rates = AU1XI2SC_RATES, | ||
221 | .formats = AU1XI2SC_FMTS, | ||
222 | .channels_min = 2, | ||
223 | .channels_max = 2, | ||
224 | }, | ||
225 | .ops = &au1xi2s_dai_ops, | ||
226 | }; | ||
227 | |||
228 | static int __devinit au1xi2s_drvprobe(struct platform_device *pdev) | ||
229 | { | ||
230 | int ret; | ||
231 | struct resource *r; | ||
232 | struct au1xpsc_audio_data *ctx; | ||
233 | |||
234 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | ||
235 | if (!ctx) | ||
236 | return -ENOMEM; | ||
237 | |||
238 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
239 | if (!r) { | ||
240 | ret = -ENODEV; | ||
241 | goto out0; | ||
242 | } | ||
243 | |||
244 | ret = -EBUSY; | ||
245 | if (!request_mem_region(r->start, resource_size(r), pdev->name)) | ||
246 | goto out0; | ||
247 | |||
248 | ctx->mmio = ioremap_nocache(r->start, resource_size(r)); | ||
249 | if (!ctx->mmio) | ||
250 | goto out1; | ||
251 | |||
252 | r = platform_get_resource(pdev, IORESOURCE_DMA, 0); | ||
253 | if (!r) | ||
254 | goto out1; | ||
255 | ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = r->start; | ||
256 | |||
257 | r = platform_get_resource(pdev, IORESOURCE_DMA, 1); | ||
258 | if (!r) | ||
259 | goto out1; | ||
260 | ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = r->start; | ||
261 | |||
262 | platform_set_drvdata(pdev, ctx); | ||
263 | |||
264 | ret = snd_soc_register_dai(&pdev->dev, &au1xi2s_dai_driver); | ||
265 | if (ret) | ||
266 | goto out1; | ||
267 | |||
268 | return 0; | ||
269 | |||
270 | out1: | ||
271 | release_mem_region(r->start, resource_size(r)); | ||
272 | out0: | ||
273 | kfree(ctx); | ||
274 | return ret; | ||
275 | } | ||
276 | |||
277 | static int __devexit au1xi2s_drvremove(struct platform_device *pdev) | ||
278 | { | ||
279 | struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev); | ||
280 | struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
281 | |||
282 | snd_soc_unregister_dai(&pdev->dev); | ||
283 | |||
284 | WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ | ||
285 | |||
286 | iounmap(ctx->mmio); | ||
287 | release_mem_region(r->start, resource_size(r)); | ||
288 | kfree(ctx); | ||
289 | |||
290 | return 0; | ||
291 | } | ||
292 | |||
293 | #ifdef CONFIG_PM | ||
294 | static int au1xi2s_drvsuspend(struct device *dev) | ||
295 | { | ||
296 | struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev); | ||
297 | |||
298 | WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ | ||
299 | |||
300 | return 0; | ||
301 | } | ||
302 | |||
303 | static int au1xi2s_drvresume(struct device *dev) | ||
304 | { | ||
305 | return 0; | ||
306 | } | ||
307 | |||
308 | static const struct dev_pm_ops au1xi2sc_pmops = { | ||
309 | .suspend = au1xi2s_drvsuspend, | ||
310 | .resume = au1xi2s_drvresume, | ||
311 | }; | ||
312 | |||
313 | #define AU1XI2SC_PMOPS (&au1xi2sc_pmops) | ||
314 | |||
315 | #else | ||
316 | |||
317 | #define AU1XI2SC_PMOPS NULL | ||
318 | |||
319 | #endif | ||
320 | |||
321 | static struct platform_driver au1xi2s_driver = { | ||
322 | .driver = { | ||
323 | .name = "alchemy-i2sc", | ||
324 | .owner = THIS_MODULE, | ||
325 | .pm = AU1XI2SC_PMOPS, | ||
326 | }, | ||
327 | .probe = au1xi2s_drvprobe, | ||
328 | .remove = __devexit_p(au1xi2s_drvremove), | ||
329 | }; | ||
330 | |||
331 | static int __init au1xi2s_load(void) | ||
332 | { | ||
333 | return platform_driver_register(&au1xi2s_driver); | ||
334 | } | ||
335 | |||
336 | static void __exit au1xi2s_unload(void) | ||
337 | { | ||
338 | platform_driver_unregister(&au1xi2s_driver); | ||
339 | } | ||
340 | |||
341 | module_init(au1xi2s_load); | ||
342 | module_exit(au1xi2s_unload); | ||
343 | |||
344 | MODULE_LICENSE("GPL"); | ||
345 | MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver"); | ||
346 | MODULE_AUTHOR("Manuel Lauss"); | ||