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