diff options
author | Tony Lindgren <tony@atomide.com> | 2010-03-01 17:19:05 -0500 |
---|---|---|
committer | Tony Lindgren <tony@atomide.com> | 2010-03-01 17:19:05 -0500 |
commit | d702d12167a2c05a346f49aac7a311d597762495 (patch) | |
tree | baae42c299cce34d6df24b5d01f8b1d0b481bd9a /sound/soc/sh/siu_pcm.c | |
parent | 9418c65f9bd861d0f7e39aab9cfb3aa6f2275d11 (diff) | |
parent | ac0f6f927db539e03e1f3f61bcd4ed57d5cde7a9 (diff) |
Merge with mainline to remove plat-omap/Kconfig conflict
Conflicts:
arch/arm/plat-omap/Kconfig
Diffstat (limited to 'sound/soc/sh/siu_pcm.c')
-rw-r--r-- | sound/soc/sh/siu_pcm.c | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/sound/soc/sh/siu_pcm.c b/sound/soc/sh/siu_pcm.c new file mode 100644 index 000000000000..c5efc30f0136 --- /dev/null +++ b/sound/soc/sh/siu_pcm.c | |||
@@ -0,0 +1,616 @@ | |||
1 | /* | ||
2 | * siu_pcm.c - ALSA driver for Renesas SH7343, SH7722 SIU peripheral. | ||
3 | * | ||
4 | * Copyright (C) 2009-2010 Guennadi Liakhovetski <g.liakhovetski@gmx.de> | ||
5 | * Copyright (C) 2006 Carlos Munoz <carlos@kenati.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; either version 2 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
20 | */ | ||
21 | #include <linux/delay.h> | ||
22 | #include <linux/dma-mapping.h> | ||
23 | #include <linux/dmaengine.h> | ||
24 | #include <linux/interrupt.h> | ||
25 | #include <linux/module.h> | ||
26 | #include <linux/platform_device.h> | ||
27 | #include <linux/slab.h> | ||
28 | |||
29 | #include <sound/control.h> | ||
30 | #include <sound/core.h> | ||
31 | #include <sound/pcm.h> | ||
32 | #include <sound/pcm_params.h> | ||
33 | #include <sound/soc-dai.h> | ||
34 | |||
35 | #include <asm/dma-sh.h> | ||
36 | #include <asm/siu.h> | ||
37 | |||
38 | #include "siu.h" | ||
39 | |||
40 | #define GET_MAX_PERIODS(buf_bytes, period_bytes) \ | ||
41 | ((buf_bytes) / (period_bytes)) | ||
42 | #define PERIOD_OFFSET(buf_addr, period_num, period_bytes) \ | ||
43 | ((buf_addr) + ((period_num) * (period_bytes))) | ||
44 | |||
45 | #define RWF_STM_RD 0x01 /* Read in progress */ | ||
46 | #define RWF_STM_WT 0x02 /* Write in progress */ | ||
47 | |||
48 | struct siu_port *siu_ports[SIU_PORT_NUM]; | ||
49 | |||
50 | /* transfersize is number of u32 dma transfers per period */ | ||
51 | static int siu_pcm_stmwrite_stop(struct siu_port *port_info) | ||
52 | { | ||
53 | struct siu_info *info = siu_i2s_dai.private_data; | ||
54 | u32 __iomem *base = info->reg; | ||
55 | struct siu_stream *siu_stream = &port_info->playback; | ||
56 | u32 stfifo; | ||
57 | |||
58 | if (!siu_stream->rw_flg) | ||
59 | return -EPERM; | ||
60 | |||
61 | /* output FIFO disable */ | ||
62 | stfifo = siu_read32(base + SIU_STFIFO); | ||
63 | siu_write32(base + SIU_STFIFO, stfifo & ~0x0c180c18); | ||
64 | pr_debug("%s: STFIFO %x -> %x\n", __func__, | ||
65 | stfifo, stfifo & ~0x0c180c18); | ||
66 | |||
67 | /* during stmwrite clear */ | ||
68 | siu_stream->rw_flg = 0; | ||
69 | |||
70 | return 0; | ||
71 | } | ||
72 | |||
73 | static int siu_pcm_stmwrite_start(struct siu_port *port_info) | ||
74 | { | ||
75 | struct siu_stream *siu_stream = &port_info->playback; | ||
76 | |||
77 | if (siu_stream->rw_flg) | ||
78 | return -EPERM; | ||
79 | |||
80 | /* Current period in buffer */ | ||
81 | port_info->playback.cur_period = 0; | ||
82 | |||
83 | /* during stmwrite flag set */ | ||
84 | siu_stream->rw_flg = RWF_STM_WT; | ||
85 | |||
86 | /* DMA transfer start */ | ||
87 | tasklet_schedule(&siu_stream->tasklet); | ||
88 | |||
89 | return 0; | ||
90 | } | ||
91 | |||
92 | static void siu_dma_tx_complete(void *arg) | ||
93 | { | ||
94 | struct siu_stream *siu_stream = arg; | ||
95 | |||
96 | if (!siu_stream->rw_flg) | ||
97 | return; | ||
98 | |||
99 | /* Update completed period count */ | ||
100 | if (++siu_stream->cur_period >= | ||
101 | GET_MAX_PERIODS(siu_stream->buf_bytes, | ||
102 | siu_stream->period_bytes)) | ||
103 | siu_stream->cur_period = 0; | ||
104 | |||
105 | pr_debug("%s: done period #%d (%u/%u bytes), cookie %d\n", | ||
106 | __func__, siu_stream->cur_period, | ||
107 | siu_stream->cur_period * siu_stream->period_bytes, | ||
108 | siu_stream->buf_bytes, siu_stream->cookie); | ||
109 | |||
110 | tasklet_schedule(&siu_stream->tasklet); | ||
111 | |||
112 | /* Notify alsa: a period is done */ | ||
113 | snd_pcm_period_elapsed(siu_stream->substream); | ||
114 | } | ||
115 | |||
116 | static int siu_pcm_wr_set(struct siu_port *port_info, | ||
117 | dma_addr_t buff, u32 size) | ||
118 | { | ||
119 | struct siu_info *info = siu_i2s_dai.private_data; | ||
120 | u32 __iomem *base = info->reg; | ||
121 | struct siu_stream *siu_stream = &port_info->playback; | ||
122 | struct snd_pcm_substream *substream = siu_stream->substream; | ||
123 | struct device *dev = substream->pcm->card->dev; | ||
124 | struct dma_async_tx_descriptor *desc; | ||
125 | dma_cookie_t cookie; | ||
126 | struct scatterlist sg; | ||
127 | u32 stfifo; | ||
128 | |||
129 | sg_init_table(&sg, 1); | ||
130 | sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)), | ||
131 | size, offset_in_page(buff)); | ||
132 | sg_dma_address(&sg) = buff; | ||
133 | |||
134 | desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan, | ||
135 | &sg, 1, DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); | ||
136 | if (!desc) { | ||
137 | dev_err(dev, "Failed to allocate a dma descriptor\n"); | ||
138 | return -ENOMEM; | ||
139 | } | ||
140 | |||
141 | desc->callback = siu_dma_tx_complete; | ||
142 | desc->callback_param = siu_stream; | ||
143 | cookie = desc->tx_submit(desc); | ||
144 | if (cookie < 0) { | ||
145 | dev_err(dev, "Failed to submit a dma transfer\n"); | ||
146 | return cookie; | ||
147 | } | ||
148 | |||
149 | siu_stream->tx_desc = desc; | ||
150 | siu_stream->cookie = cookie; | ||
151 | |||
152 | dma_async_issue_pending(siu_stream->chan); | ||
153 | |||
154 | /* only output FIFO enable */ | ||
155 | stfifo = siu_read32(base + SIU_STFIFO); | ||
156 | siu_write32(base + SIU_STFIFO, stfifo | (port_info->stfifo & 0x0c180c18)); | ||
157 | dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__, | ||
158 | stfifo, stfifo | (port_info->stfifo & 0x0c180c18)); | ||
159 | |||
160 | return 0; | ||
161 | } | ||
162 | |||
163 | static int siu_pcm_rd_set(struct siu_port *port_info, | ||
164 | dma_addr_t buff, size_t size) | ||
165 | { | ||
166 | struct siu_info *info = siu_i2s_dai.private_data; | ||
167 | u32 __iomem *base = info->reg; | ||
168 | struct siu_stream *siu_stream = &port_info->capture; | ||
169 | struct snd_pcm_substream *substream = siu_stream->substream; | ||
170 | struct device *dev = substream->pcm->card->dev; | ||
171 | struct dma_async_tx_descriptor *desc; | ||
172 | dma_cookie_t cookie; | ||
173 | struct scatterlist sg; | ||
174 | u32 stfifo; | ||
175 | |||
176 | dev_dbg(dev, "%s: %u@%llx\n", __func__, size, (unsigned long long)buff); | ||
177 | |||
178 | sg_init_table(&sg, 1); | ||
179 | sg_set_page(&sg, pfn_to_page(PFN_DOWN(buff)), | ||
180 | size, offset_in_page(buff)); | ||
181 | sg_dma_address(&sg) = buff; | ||
182 | |||
183 | desc = siu_stream->chan->device->device_prep_slave_sg(siu_stream->chan, | ||
184 | &sg, 1, DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); | ||
185 | if (!desc) { | ||
186 | dev_err(dev, "Failed to allocate dma descriptor\n"); | ||
187 | return -ENOMEM; | ||
188 | } | ||
189 | |||
190 | desc->callback = siu_dma_tx_complete; | ||
191 | desc->callback_param = siu_stream; | ||
192 | cookie = desc->tx_submit(desc); | ||
193 | if (cookie < 0) { | ||
194 | dev_err(dev, "Failed to submit dma descriptor\n"); | ||
195 | return cookie; | ||
196 | } | ||
197 | |||
198 | siu_stream->tx_desc = desc; | ||
199 | siu_stream->cookie = cookie; | ||
200 | |||
201 | dma_async_issue_pending(siu_stream->chan); | ||
202 | |||
203 | /* only input FIFO enable */ | ||
204 | stfifo = siu_read32(base + SIU_STFIFO); | ||
205 | siu_write32(base + SIU_STFIFO, siu_read32(base + SIU_STFIFO) | | ||
206 | (port_info->stfifo & 0x13071307)); | ||
207 | dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__, | ||
208 | stfifo, stfifo | (port_info->stfifo & 0x13071307)); | ||
209 | |||
210 | return 0; | ||
211 | } | ||
212 | |||
213 | static void siu_io_tasklet(unsigned long data) | ||
214 | { | ||
215 | struct siu_stream *siu_stream = (struct siu_stream *)data; | ||
216 | struct snd_pcm_substream *substream = siu_stream->substream; | ||
217 | struct device *dev = substream->pcm->card->dev; | ||
218 | struct snd_pcm_runtime *rt = substream->runtime; | ||
219 | struct siu_port *port_info = siu_port_info(substream); | ||
220 | |||
221 | dev_dbg(dev, "%s: flags %x\n", __func__, siu_stream->rw_flg); | ||
222 | |||
223 | if (!siu_stream->rw_flg) { | ||
224 | dev_dbg(dev, "%s: stream inactive\n", __func__); | ||
225 | return; | ||
226 | } | ||
227 | |||
228 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { | ||
229 | dma_addr_t buff; | ||
230 | size_t count; | ||
231 | u8 *virt; | ||
232 | |||
233 | buff = (dma_addr_t)PERIOD_OFFSET(rt->dma_addr, | ||
234 | siu_stream->cur_period, | ||
235 | siu_stream->period_bytes); | ||
236 | virt = PERIOD_OFFSET(rt->dma_area, | ||
237 | siu_stream->cur_period, | ||
238 | siu_stream->period_bytes); | ||
239 | count = siu_stream->period_bytes; | ||
240 | |||
241 | /* DMA transfer start */ | ||
242 | siu_pcm_rd_set(port_info, buff, count); | ||
243 | } else { | ||
244 | siu_pcm_wr_set(port_info, | ||
245 | (dma_addr_t)PERIOD_OFFSET(rt->dma_addr, | ||
246 | siu_stream->cur_period, | ||
247 | siu_stream->period_bytes), | ||
248 | siu_stream->period_bytes); | ||
249 | } | ||
250 | } | ||
251 | |||
252 | /* Capture */ | ||
253 | static int siu_pcm_stmread_start(struct siu_port *port_info) | ||
254 | { | ||
255 | struct siu_stream *siu_stream = &port_info->capture; | ||
256 | |||
257 | if (siu_stream->xfer_cnt > 0x1000000) | ||
258 | return -EINVAL; | ||
259 | if (siu_stream->rw_flg) | ||
260 | return -EPERM; | ||
261 | |||
262 | /* Current period in buffer */ | ||
263 | siu_stream->cur_period = 0; | ||
264 | |||
265 | /* during stmread flag set */ | ||
266 | siu_stream->rw_flg = RWF_STM_RD; | ||
267 | |||
268 | tasklet_schedule(&siu_stream->tasklet); | ||
269 | |||
270 | return 0; | ||
271 | } | ||
272 | |||
273 | static int siu_pcm_stmread_stop(struct siu_port *port_info) | ||
274 | { | ||
275 | struct siu_info *info = siu_i2s_dai.private_data; | ||
276 | u32 __iomem *base = info->reg; | ||
277 | struct siu_stream *siu_stream = &port_info->capture; | ||
278 | struct device *dev = siu_stream->substream->pcm->card->dev; | ||
279 | u32 stfifo; | ||
280 | |||
281 | if (!siu_stream->rw_flg) | ||
282 | return -EPERM; | ||
283 | |||
284 | /* input FIFO disable */ | ||
285 | stfifo = siu_read32(base + SIU_STFIFO); | ||
286 | siu_write32(base + SIU_STFIFO, stfifo & ~0x13071307); | ||
287 | dev_dbg(dev, "%s: STFIFO %x -> %x\n", __func__, | ||
288 | stfifo, stfifo & ~0x13071307); | ||
289 | |||
290 | /* during stmread flag clear */ | ||
291 | siu_stream->rw_flg = 0; | ||
292 | |||
293 | return 0; | ||
294 | } | ||
295 | |||
296 | static int siu_pcm_hw_params(struct snd_pcm_substream *ss, | ||
297 | struct snd_pcm_hw_params *hw_params) | ||
298 | { | ||
299 | struct siu_info *info = siu_i2s_dai.private_data; | ||
300 | struct device *dev = ss->pcm->card->dev; | ||
301 | int ret; | ||
302 | |||
303 | dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id); | ||
304 | |||
305 | ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); | ||
306 | if (ret < 0) | ||
307 | dev_err(dev, "snd_pcm_lib_malloc_pages() failed\n"); | ||
308 | |||
309 | return ret; | ||
310 | } | ||
311 | |||
312 | static int siu_pcm_hw_free(struct snd_pcm_substream *ss) | ||
313 | { | ||
314 | struct siu_info *info = siu_i2s_dai.private_data; | ||
315 | struct siu_port *port_info = siu_port_info(ss); | ||
316 | struct device *dev = ss->pcm->card->dev; | ||
317 | struct siu_stream *siu_stream; | ||
318 | |||
319 | if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
320 | siu_stream = &port_info->playback; | ||
321 | else | ||
322 | siu_stream = &port_info->capture; | ||
323 | |||
324 | dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id); | ||
325 | |||
326 | return snd_pcm_lib_free_pages(ss); | ||
327 | } | ||
328 | |||
329 | static bool filter(struct dma_chan *chan, void *slave) | ||
330 | { | ||
331 | struct sh_dmae_slave *param = slave; | ||
332 | |||
333 | pr_debug("%s: slave ID %d\n", __func__, param->slave_id); | ||
334 | |||
335 | if (unlikely(param->dma_dev != chan->device->dev)) | ||
336 | return false; | ||
337 | |||
338 | chan->private = param; | ||
339 | return true; | ||
340 | } | ||
341 | |||
342 | static int siu_pcm_open(struct snd_pcm_substream *ss) | ||
343 | { | ||
344 | /* Playback / Capture */ | ||
345 | struct siu_info *info = siu_i2s_dai.private_data; | ||
346 | struct siu_port *port_info = siu_port_info(ss); | ||
347 | struct siu_stream *siu_stream; | ||
348 | u32 port = info->port_id; | ||
349 | struct siu_platform *pdata = siu_i2s_dai.dev->platform_data; | ||
350 | struct device *dev = ss->pcm->card->dev; | ||
351 | dma_cap_mask_t mask; | ||
352 | struct sh_dmae_slave *param; | ||
353 | |||
354 | dma_cap_zero(mask); | ||
355 | dma_cap_set(DMA_SLAVE, mask); | ||
356 | |||
357 | dev_dbg(dev, "%s, port=%d@%p\n", __func__, port, port_info); | ||
358 | |||
359 | if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
360 | siu_stream = &port_info->playback; | ||
361 | param = &siu_stream->param; | ||
362 | param->slave_id = port ? SHDMA_SLAVE_SIUB_TX : | ||
363 | SHDMA_SLAVE_SIUA_TX; | ||
364 | } else { | ||
365 | siu_stream = &port_info->capture; | ||
366 | param = &siu_stream->param; | ||
367 | param->slave_id = port ? SHDMA_SLAVE_SIUB_RX : | ||
368 | SHDMA_SLAVE_SIUA_RX; | ||
369 | } | ||
370 | |||
371 | param->dma_dev = pdata->dma_dev; | ||
372 | /* Get DMA channel */ | ||
373 | siu_stream->chan = dma_request_channel(mask, filter, param); | ||
374 | if (!siu_stream->chan) { | ||
375 | dev_err(dev, "DMA channel allocation failed!\n"); | ||
376 | return -EBUSY; | ||
377 | } | ||
378 | |||
379 | siu_stream->substream = ss; | ||
380 | |||
381 | return 0; | ||
382 | } | ||
383 | |||
384 | static int siu_pcm_close(struct snd_pcm_substream *ss) | ||
385 | { | ||
386 | struct siu_info *info = siu_i2s_dai.private_data; | ||
387 | struct device *dev = ss->pcm->card->dev; | ||
388 | struct siu_port *port_info = siu_port_info(ss); | ||
389 | struct siu_stream *siu_stream; | ||
390 | |||
391 | dev_dbg(dev, "%s: port=%d\n", __func__, info->port_id); | ||
392 | |||
393 | if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
394 | siu_stream = &port_info->playback; | ||
395 | else | ||
396 | siu_stream = &port_info->capture; | ||
397 | |||
398 | dma_release_channel(siu_stream->chan); | ||
399 | siu_stream->chan = NULL; | ||
400 | |||
401 | siu_stream->substream = NULL; | ||
402 | |||
403 | return 0; | ||
404 | } | ||
405 | |||
406 | static int siu_pcm_prepare(struct snd_pcm_substream *ss) | ||
407 | { | ||
408 | struct siu_info *info = siu_i2s_dai.private_data; | ||
409 | struct siu_port *port_info = siu_port_info(ss); | ||
410 | struct device *dev = ss->pcm->card->dev; | ||
411 | struct snd_pcm_runtime *rt = ss->runtime; | ||
412 | struct siu_stream *siu_stream; | ||
413 | snd_pcm_sframes_t xfer_cnt; | ||
414 | |||
415 | if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
416 | siu_stream = &port_info->playback; | ||
417 | else | ||
418 | siu_stream = &port_info->capture; | ||
419 | |||
420 | rt = siu_stream->substream->runtime; | ||
421 | |||
422 | siu_stream->buf_bytes = snd_pcm_lib_buffer_bytes(ss); | ||
423 | siu_stream->period_bytes = snd_pcm_lib_period_bytes(ss); | ||
424 | |||
425 | dev_dbg(dev, "%s: port=%d, %d channels, period=%u bytes\n", __func__, | ||
426 | info->port_id, rt->channels, siu_stream->period_bytes); | ||
427 | |||
428 | /* We only support buffers that are multiples of the period */ | ||
429 | if (siu_stream->buf_bytes % siu_stream->period_bytes) { | ||
430 | dev_err(dev, "%s() - buffer=%d not multiple of period=%d\n", | ||
431 | __func__, siu_stream->buf_bytes, | ||
432 | siu_stream->period_bytes); | ||
433 | return -EINVAL; | ||
434 | } | ||
435 | |||
436 | xfer_cnt = bytes_to_frames(rt, siu_stream->period_bytes); | ||
437 | if (!xfer_cnt || xfer_cnt > 0x1000000) | ||
438 | return -EINVAL; | ||
439 | |||
440 | siu_stream->format = rt->format; | ||
441 | siu_stream->xfer_cnt = xfer_cnt; | ||
442 | |||
443 | dev_dbg(dev, "port=%d buf=%lx buf_bytes=%d period_bytes=%d " | ||
444 | "format=%d channels=%d xfer_cnt=%d\n", info->port_id, | ||
445 | (unsigned long)rt->dma_addr, siu_stream->buf_bytes, | ||
446 | siu_stream->period_bytes, | ||
447 | siu_stream->format, rt->channels, (int)xfer_cnt); | ||
448 | |||
449 | return 0; | ||
450 | } | ||
451 | |||
452 | static int siu_pcm_trigger(struct snd_pcm_substream *ss, int cmd) | ||
453 | { | ||
454 | struct siu_info *info = siu_i2s_dai.private_data; | ||
455 | struct device *dev = ss->pcm->card->dev; | ||
456 | struct siu_port *port_info = siu_port_info(ss); | ||
457 | int ret; | ||
458 | |||
459 | dev_dbg(dev, "%s: port=%d@%p, cmd=%d\n", __func__, | ||
460 | info->port_id, port_info, cmd); | ||
461 | |||
462 | switch (cmd) { | ||
463 | case SNDRV_PCM_TRIGGER_START: | ||
464 | if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
465 | ret = siu_pcm_stmwrite_start(port_info); | ||
466 | else | ||
467 | ret = siu_pcm_stmread_start(port_info); | ||
468 | |||
469 | if (ret < 0) | ||
470 | dev_warn(dev, "%s: start failed on port=%d\n", | ||
471 | __func__, info->port_id); | ||
472 | |||
473 | break; | ||
474 | case SNDRV_PCM_TRIGGER_STOP: | ||
475 | if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
476 | siu_pcm_stmwrite_stop(port_info); | ||
477 | else | ||
478 | siu_pcm_stmread_stop(port_info); | ||
479 | ret = 0; | ||
480 | |||
481 | break; | ||
482 | default: | ||
483 | dev_err(dev, "%s() unsupported cmd=%d\n", __func__, cmd); | ||
484 | ret = -EINVAL; | ||
485 | } | ||
486 | |||
487 | return ret; | ||
488 | } | ||
489 | |||
490 | /* | ||
491 | * So far only resolution of one period is supported, subject to extending the | ||
492 | * dmangine API | ||
493 | */ | ||
494 | static snd_pcm_uframes_t siu_pcm_pointer_dma(struct snd_pcm_substream *ss) | ||
495 | { | ||
496 | struct device *dev = ss->pcm->card->dev; | ||
497 | struct siu_info *info = siu_i2s_dai.private_data; | ||
498 | u32 __iomem *base = info->reg; | ||
499 | struct siu_port *port_info = siu_port_info(ss); | ||
500 | struct snd_pcm_runtime *rt = ss->runtime; | ||
501 | size_t ptr; | ||
502 | struct siu_stream *siu_stream; | ||
503 | |||
504 | if (ss->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
505 | siu_stream = &port_info->playback; | ||
506 | else | ||
507 | siu_stream = &port_info->capture; | ||
508 | |||
509 | /* | ||
510 | * ptr is the offset into the buffer where the dma is currently at. We | ||
511 | * check if the dma buffer has just wrapped. | ||
512 | */ | ||
513 | ptr = PERIOD_OFFSET(rt->dma_addr, | ||
514 | siu_stream->cur_period, | ||
515 | siu_stream->period_bytes) - rt->dma_addr; | ||
516 | |||
517 | dev_dbg(dev, | ||
518 | "%s: port=%d, events %x, FSTS %x, xferred %u/%u, cookie %d\n", | ||
519 | __func__, info->port_id, siu_read32(base + SIU_EVNTC), | ||
520 | siu_read32(base + SIU_SBFSTS), ptr, siu_stream->buf_bytes, | ||
521 | siu_stream->cookie); | ||
522 | |||
523 | if (ptr >= siu_stream->buf_bytes) | ||
524 | ptr = 0; | ||
525 | |||
526 | return bytes_to_frames(ss->runtime, ptr); | ||
527 | } | ||
528 | |||
529 | static int siu_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, | ||
530 | struct snd_pcm *pcm) | ||
531 | { | ||
532 | /* card->dev == socdev->dev, see snd_soc_new_pcms() */ | ||
533 | struct siu_info *info = siu_i2s_dai.private_data; | ||
534 | struct platform_device *pdev = to_platform_device(card->dev); | ||
535 | int ret; | ||
536 | int i; | ||
537 | |||
538 | /* pdev->id selects between SIUA and SIUB */ | ||
539 | if (pdev->id < 0 || pdev->id >= SIU_PORT_NUM) | ||
540 | return -EINVAL; | ||
541 | |||
542 | info->port_id = pdev->id; | ||
543 | |||
544 | /* | ||
545 | * While the siu has 2 ports, only one port can be on at a time (only 1 | ||
546 | * SPB). So far all the boards using the siu had only one of the ports | ||
547 | * wired to a codec. To simplify things, we only register one port with | ||
548 | * alsa. In case both ports are needed, it should be changed here | ||
549 | */ | ||
550 | for (i = pdev->id; i < pdev->id + 1; i++) { | ||
551 | struct siu_port **port_info = &siu_ports[i]; | ||
552 | |||
553 | ret = siu_init_port(i, port_info, card); | ||
554 | if (ret < 0) | ||
555 | return ret; | ||
556 | |||
557 | ret = snd_pcm_lib_preallocate_pages_for_all(pcm, | ||
558 | SNDRV_DMA_TYPE_DEV, NULL, | ||
559 | SIU_BUFFER_BYTES_MAX, SIU_BUFFER_BYTES_MAX); | ||
560 | if (ret < 0) { | ||
561 | dev_err(card->dev, | ||
562 | "snd_pcm_lib_preallocate_pages_for_all() err=%d", | ||
563 | ret); | ||
564 | goto fail; | ||
565 | } | ||
566 | |||
567 | (*port_info)->pcm = pcm; | ||
568 | |||
569 | /* IO tasklets */ | ||
570 | tasklet_init(&(*port_info)->playback.tasklet, siu_io_tasklet, | ||
571 | (unsigned long)&(*port_info)->playback); | ||
572 | tasklet_init(&(*port_info)->capture.tasklet, siu_io_tasklet, | ||
573 | (unsigned long)&(*port_info)->capture); | ||
574 | } | ||
575 | |||
576 | dev_info(card->dev, "SuperH SIU driver initialized.\n"); | ||
577 | return 0; | ||
578 | |||
579 | fail: | ||
580 | siu_free_port(siu_ports[pdev->id]); | ||
581 | dev_err(card->dev, "SIU: failed to initialize.\n"); | ||
582 | return ret; | ||
583 | } | ||
584 | |||
585 | static void siu_pcm_free(struct snd_pcm *pcm) | ||
586 | { | ||
587 | struct platform_device *pdev = to_platform_device(pcm->card->dev); | ||
588 | struct siu_port *port_info = siu_ports[pdev->id]; | ||
589 | |||
590 | tasklet_kill(&port_info->capture.tasklet); | ||
591 | tasklet_kill(&port_info->playback.tasklet); | ||
592 | |||
593 | siu_free_port(port_info); | ||
594 | snd_pcm_lib_preallocate_free_for_all(pcm); | ||
595 | |||
596 | dev_dbg(pcm->card->dev, "%s\n", __func__); | ||
597 | } | ||
598 | |||
599 | static struct snd_pcm_ops siu_pcm_ops = { | ||
600 | .open = siu_pcm_open, | ||
601 | .close = siu_pcm_close, | ||
602 | .ioctl = snd_pcm_lib_ioctl, | ||
603 | .hw_params = siu_pcm_hw_params, | ||
604 | .hw_free = siu_pcm_hw_free, | ||
605 | .prepare = siu_pcm_prepare, | ||
606 | .trigger = siu_pcm_trigger, | ||
607 | .pointer = siu_pcm_pointer_dma, | ||
608 | }; | ||
609 | |||
610 | struct snd_soc_platform siu_platform = { | ||
611 | .name = "siu-audio", | ||
612 | .pcm_ops = &siu_pcm_ops, | ||
613 | .pcm_new = siu_pcm_new, | ||
614 | .pcm_free = siu_pcm_free, | ||
615 | }; | ||
616 | EXPORT_SYMBOL_GPL(siu_platform); | ||