diff options
Diffstat (limited to 'sound/soc/mid-x86/sst_platform.c')
-rw-r--r-- | sound/soc/mid-x86/sst_platform.c | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/sound/soc/mid-x86/sst_platform.c b/sound/soc/mid-x86/sst_platform.c new file mode 100644 index 000000000000..ee2c22475a76 --- /dev/null +++ b/sound/soc/mid-x86/sst_platform.c | |||
@@ -0,0 +1,474 @@ | |||
1 | /* | ||
2 | * sst_platform.c - Intel MID Platform driver | ||
3 | * | ||
4 | * Copyright (C) 2010 Intel Corp | ||
5 | * Author: Vinod Koul <vinod.koul@intel.com> | ||
6 | * Author: Harsha Priya <priya.harsha@intel.com> | ||
7 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; version 2 of the License. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but | ||
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
16 | * General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | ||
21 | * | ||
22 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
23 | * | ||
24 | * | ||
25 | */ | ||
26 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
27 | |||
28 | #include <linux/slab.h> | ||
29 | #include <linux/io.h> | ||
30 | #include <sound/core.h> | ||
31 | #include <sound/pcm.h> | ||
32 | #include <sound/pcm_params.h> | ||
33 | #include <sound/soc.h> | ||
34 | #include "../../../drivers/staging/intel_sst/intel_sst_ioctl.h" | ||
35 | #include "../../../drivers/staging/intel_sst/intel_sst.h" | ||
36 | #include "sst_platform.h" | ||
37 | |||
38 | static struct snd_pcm_hardware sst_platform_pcm_hw = { | ||
39 | .info = (SNDRV_PCM_INFO_INTERLEAVED | | ||
40 | SNDRV_PCM_INFO_DOUBLE | | ||
41 | SNDRV_PCM_INFO_PAUSE | | ||
42 | SNDRV_PCM_INFO_RESUME | | ||
43 | SNDRV_PCM_INFO_MMAP| | ||
44 | SNDRV_PCM_INFO_MMAP_VALID | | ||
45 | SNDRV_PCM_INFO_BLOCK_TRANSFER | | ||
46 | SNDRV_PCM_INFO_SYNC_START), | ||
47 | .formats = (SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_U16 | | ||
48 | SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_U24 | | ||
49 | SNDRV_PCM_FMTBIT_S32 | SNDRV_PCM_FMTBIT_U32), | ||
50 | .rates = (SNDRV_PCM_RATE_8000| | ||
51 | SNDRV_PCM_RATE_44100 | | ||
52 | SNDRV_PCM_RATE_48000), | ||
53 | .rate_min = SST_MIN_RATE, | ||
54 | .rate_max = SST_MAX_RATE, | ||
55 | .channels_min = SST_MIN_CHANNEL, | ||
56 | .channels_max = SST_MAX_CHANNEL, | ||
57 | .buffer_bytes_max = SST_MAX_BUFFER, | ||
58 | .period_bytes_min = SST_MIN_PERIOD_BYTES, | ||
59 | .period_bytes_max = SST_MAX_PERIOD_BYTES, | ||
60 | .periods_min = SST_MIN_PERIODS, | ||
61 | .periods_max = SST_MAX_PERIODS, | ||
62 | .fifo_size = SST_FIFO_SIZE, | ||
63 | }; | ||
64 | |||
65 | /* MFLD - MSIC */ | ||
66 | struct snd_soc_dai_driver sst_platform_dai[] = { | ||
67 | { | ||
68 | .name = "Headset-cpu-dai", | ||
69 | .id = 0, | ||
70 | .playback = { | ||
71 | .channels_min = SST_STEREO, | ||
72 | .channels_max = SST_STEREO, | ||
73 | .rates = SNDRV_PCM_RATE_48000, | ||
74 | .formats = SNDRV_PCM_FMTBIT_S24_LE, | ||
75 | }, | ||
76 | .capture = { | ||
77 | .channels_min = 1, | ||
78 | .channels_max = 5, | ||
79 | .rates = SNDRV_PCM_RATE_48000, | ||
80 | .formats = SNDRV_PCM_FMTBIT_S24_LE, | ||
81 | }, | ||
82 | }, | ||
83 | { | ||
84 | .name = "Speaker-cpu-dai", | ||
85 | .id = 1, | ||
86 | .playback = { | ||
87 | .channels_min = SST_MONO, | ||
88 | .channels_max = SST_STEREO, | ||
89 | .rates = SNDRV_PCM_RATE_48000, | ||
90 | .formats = SNDRV_PCM_FMTBIT_S24_LE, | ||
91 | }, | ||
92 | }, | ||
93 | { | ||
94 | .name = "Vibra1-cpu-dai", | ||
95 | .id = 2, | ||
96 | .playback = { | ||
97 | .channels_min = SST_MONO, | ||
98 | .channels_max = SST_MONO, | ||
99 | .rates = SNDRV_PCM_RATE_48000, | ||
100 | .formats = SNDRV_PCM_FMTBIT_S24_LE, | ||
101 | }, | ||
102 | }, | ||
103 | { | ||
104 | .name = "Vibra2-cpu-dai", | ||
105 | .id = 3, | ||
106 | .playback = { | ||
107 | .channels_min = SST_MONO, | ||
108 | .channels_max = SST_STEREO, | ||
109 | .rates = SNDRV_PCM_RATE_48000, | ||
110 | .formats = SNDRV_PCM_FMTBIT_S24_LE, | ||
111 | }, | ||
112 | }, | ||
113 | }; | ||
114 | |||
115 | /* helper functions */ | ||
116 | static inline void sst_set_stream_status(struct sst_runtime_stream *stream, | ||
117 | int state) | ||
118 | { | ||
119 | spin_lock(&stream->status_lock); | ||
120 | stream->stream_status = state; | ||
121 | spin_unlock(&stream->status_lock); | ||
122 | } | ||
123 | |||
124 | static inline int sst_get_stream_status(struct sst_runtime_stream *stream) | ||
125 | { | ||
126 | int state; | ||
127 | |||
128 | spin_lock(&stream->status_lock); | ||
129 | state = stream->stream_status; | ||
130 | spin_unlock(&stream->status_lock); | ||
131 | return state; | ||
132 | } | ||
133 | |||
134 | static void sst_fill_pcm_params(struct snd_pcm_substream *substream, | ||
135 | struct snd_sst_stream_params *param) | ||
136 | { | ||
137 | |||
138 | param->uc.pcm_params.codec = SST_CODEC_TYPE_PCM; | ||
139 | param->uc.pcm_params.num_chan = (u8) substream->runtime->channels; | ||
140 | param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits; | ||
141 | param->uc.pcm_params.reserved = 0; | ||
142 | param->uc.pcm_params.sfreq = substream->runtime->rate; | ||
143 | param->uc.pcm_params.ring_buffer_size = | ||
144 | snd_pcm_lib_buffer_bytes(substream); | ||
145 | param->uc.pcm_params.period_count = substream->runtime->period_size; | ||
146 | param->uc.pcm_params.ring_buffer_addr = | ||
147 | virt_to_phys(substream->dma_buffer.area); | ||
148 | pr_debug("period_cnt = %d\n", param->uc.pcm_params.period_count); | ||
149 | pr_debug("sfreq= %d, wd_sz = %d\n", | ||
150 | param->uc.pcm_params.sfreq, param->uc.pcm_params.pcm_wd_sz); | ||
151 | } | ||
152 | |||
153 | static int sst_platform_alloc_stream(struct snd_pcm_substream *substream) | ||
154 | { | ||
155 | struct sst_runtime_stream *stream = | ||
156 | substream->runtime->private_data; | ||
157 | struct snd_sst_stream_params param = {{{0,},},}; | ||
158 | struct snd_sst_params str_params = {0}; | ||
159 | int ret_val; | ||
160 | |||
161 | /* set codec params and inform SST driver the same */ | ||
162 | sst_fill_pcm_params(substream, ¶m); | ||
163 | substream->runtime->dma_area = substream->dma_buffer.area; | ||
164 | str_params.sparams = param; | ||
165 | str_params.codec = param.uc.pcm_params.codec; | ||
166 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
167 | str_params.ops = STREAM_OPS_PLAYBACK; | ||
168 | str_params.device_type = substream->pcm->device + 1; | ||
169 | pr_debug("Playbck stream,Device %d\n", | ||
170 | substream->pcm->device); | ||
171 | } else { | ||
172 | str_params.ops = STREAM_OPS_CAPTURE; | ||
173 | str_params.device_type = SND_SST_DEVICE_CAPTURE; | ||
174 | pr_debug("Capture stream,Device %d\n", | ||
175 | substream->pcm->device); | ||
176 | } | ||
177 | ret_val = stream->sstdrv_ops->pcm_control->open(&str_params); | ||
178 | pr_debug("SST_SND_PLAY/CAPTURE ret_val = %x\n", ret_val); | ||
179 | if (ret_val < 0) | ||
180 | return ret_val; | ||
181 | |||
182 | stream->stream_info.str_id = ret_val; | ||
183 | pr_debug("str id : %d\n", stream->stream_info.str_id); | ||
184 | return ret_val; | ||
185 | } | ||
186 | |||
187 | static void sst_period_elapsed(void *mad_substream) | ||
188 | { | ||
189 | struct snd_pcm_substream *substream = mad_substream; | ||
190 | struct sst_runtime_stream *stream; | ||
191 | int status; | ||
192 | |||
193 | if (!substream || !substream->runtime) | ||
194 | return; | ||
195 | stream = substream->runtime->private_data; | ||
196 | if (!stream) | ||
197 | return; | ||
198 | status = sst_get_stream_status(stream); | ||
199 | if (status != SST_PLATFORM_RUNNING) | ||
200 | return; | ||
201 | snd_pcm_period_elapsed(substream); | ||
202 | } | ||
203 | |||
204 | static int sst_platform_init_stream(struct snd_pcm_substream *substream) | ||
205 | { | ||
206 | struct sst_runtime_stream *stream = | ||
207 | substream->runtime->private_data; | ||
208 | int ret_val; | ||
209 | |||
210 | pr_debug("setting buffer ptr param\n"); | ||
211 | sst_set_stream_status(stream, SST_PLATFORM_INIT); | ||
212 | stream->stream_info.period_elapsed = sst_period_elapsed; | ||
213 | stream->stream_info.mad_substream = substream; | ||
214 | stream->stream_info.buffer_ptr = 0; | ||
215 | stream->stream_info.sfreq = substream->runtime->rate; | ||
216 | ret_val = stream->sstdrv_ops->pcm_control->device_control( | ||
217 | SST_SND_STREAM_INIT, &stream->stream_info); | ||
218 | if (ret_val) | ||
219 | pr_err("control_set ret error %d\n", ret_val); | ||
220 | return ret_val; | ||
221 | |||
222 | } | ||
223 | /* end -- helper functions */ | ||
224 | |||
225 | static int sst_platform_open(struct snd_pcm_substream *substream) | ||
226 | { | ||
227 | struct snd_pcm_runtime *runtime; | ||
228 | struct sst_runtime_stream *stream; | ||
229 | int ret_val = 0; | ||
230 | |||
231 | pr_debug("sst_platform_open called\n"); | ||
232 | runtime = substream->runtime; | ||
233 | runtime->hw = sst_platform_pcm_hw; | ||
234 | stream = kzalloc(sizeof(*stream), GFP_KERNEL); | ||
235 | if (!stream) | ||
236 | return -ENOMEM; | ||
237 | spin_lock_init(&stream->status_lock); | ||
238 | stream->stream_info.str_id = 0; | ||
239 | sst_set_stream_status(stream, SST_PLATFORM_INIT); | ||
240 | stream->stream_info.mad_substream = substream; | ||
241 | /* allocate memory for SST API set */ | ||
242 | stream->sstdrv_ops = kzalloc(sizeof(*stream->sstdrv_ops), | ||
243 | GFP_KERNEL); | ||
244 | if (!stream->sstdrv_ops) { | ||
245 | pr_err("sst: mem allocation for ops fail\n"); | ||
246 | kfree(stream); | ||
247 | return -ENOMEM; | ||
248 | } | ||
249 | stream->sstdrv_ops->vendor_id = MSIC_VENDOR_ID; | ||
250 | /* registering with SST driver to get access to SST APIs to use */ | ||
251 | ret_val = register_sst_card(stream->sstdrv_ops); | ||
252 | if (ret_val) { | ||
253 | pr_err("sst: sst card registration failed\n"); | ||
254 | return ret_val; | ||
255 | } | ||
256 | runtime->private_data = stream; | ||
257 | return snd_pcm_hw_constraint_integer(runtime, | ||
258 | SNDRV_PCM_HW_PARAM_PERIODS); | ||
259 | } | ||
260 | |||
261 | static int sst_platform_close(struct snd_pcm_substream *substream) | ||
262 | { | ||
263 | struct sst_runtime_stream *stream; | ||
264 | int ret_val = 0, str_id; | ||
265 | |||
266 | pr_debug("sst_platform_close called\n"); | ||
267 | stream = substream->runtime->private_data; | ||
268 | str_id = stream->stream_info.str_id; | ||
269 | if (str_id) | ||
270 | ret_val = stream->sstdrv_ops->pcm_control->close(str_id); | ||
271 | kfree(stream->sstdrv_ops); | ||
272 | kfree(stream); | ||
273 | return ret_val; | ||
274 | } | ||
275 | |||
276 | static int sst_platform_pcm_prepare(struct snd_pcm_substream *substream) | ||
277 | { | ||
278 | struct sst_runtime_stream *stream; | ||
279 | int ret_val = 0, str_id; | ||
280 | |||
281 | pr_debug("sst_platform_pcm_prepare called\n"); | ||
282 | stream = substream->runtime->private_data; | ||
283 | str_id = stream->stream_info.str_id; | ||
284 | if (stream->stream_info.str_id) { | ||
285 | ret_val = stream->sstdrv_ops->pcm_control->device_control( | ||
286 | SST_SND_DROP, &str_id); | ||
287 | return ret_val; | ||
288 | } | ||
289 | |||
290 | ret_val = sst_platform_alloc_stream(substream); | ||
291 | if (ret_val < 0) | ||
292 | return ret_val; | ||
293 | snprintf(substream->pcm->id, sizeof(substream->pcm->id), | ||
294 | "%d", stream->stream_info.str_id); | ||
295 | |||
296 | ret_val = sst_platform_init_stream(substream); | ||
297 | if (ret_val) | ||
298 | return ret_val; | ||
299 | substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER; | ||
300 | return ret_val; | ||
301 | } | ||
302 | |||
303 | static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream, | ||
304 | int cmd) | ||
305 | { | ||
306 | int ret_val = 0, str_id; | ||
307 | struct sst_runtime_stream *stream; | ||
308 | int str_cmd, status; | ||
309 | |||
310 | pr_debug("sst_platform_pcm_trigger called\n"); | ||
311 | stream = substream->runtime->private_data; | ||
312 | str_id = stream->stream_info.str_id; | ||
313 | switch (cmd) { | ||
314 | case SNDRV_PCM_TRIGGER_START: | ||
315 | pr_debug("sst: Trigger Start\n"); | ||
316 | str_cmd = SST_SND_START; | ||
317 | status = SST_PLATFORM_RUNNING; | ||
318 | stream->stream_info.mad_substream = substream; | ||
319 | break; | ||
320 | case SNDRV_PCM_TRIGGER_STOP: | ||
321 | pr_debug("sst: in stop\n"); | ||
322 | str_cmd = SST_SND_DROP; | ||
323 | status = SST_PLATFORM_DROPPED; | ||
324 | break; | ||
325 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
326 | pr_debug("sst: in pause\n"); | ||
327 | str_cmd = SST_SND_PAUSE; | ||
328 | status = SST_PLATFORM_PAUSED; | ||
329 | break; | ||
330 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
331 | pr_debug("sst: in pause release\n"); | ||
332 | str_cmd = SST_SND_RESUME; | ||
333 | status = SST_PLATFORM_RUNNING; | ||
334 | break; | ||
335 | default: | ||
336 | return -EINVAL; | ||
337 | } | ||
338 | ret_val = stream->sstdrv_ops->pcm_control->device_control(str_cmd, | ||
339 | &str_id); | ||
340 | if (!ret_val) | ||
341 | sst_set_stream_status(stream, status); | ||
342 | |||
343 | return ret_val; | ||
344 | } | ||
345 | |||
346 | |||
347 | static snd_pcm_uframes_t sst_platform_pcm_pointer | ||
348 | (struct snd_pcm_substream *substream) | ||
349 | { | ||
350 | struct sst_runtime_stream *stream; | ||
351 | int ret_val, status; | ||
352 | struct pcm_stream_info *str_info; | ||
353 | |||
354 | stream = substream->runtime->private_data; | ||
355 | status = sst_get_stream_status(stream); | ||
356 | if (status == SST_PLATFORM_INIT) | ||
357 | return 0; | ||
358 | str_info = &stream->stream_info; | ||
359 | ret_val = stream->sstdrv_ops->pcm_control->device_control( | ||
360 | SST_SND_BUFFER_POINTER, str_info); | ||
361 | if (ret_val) { | ||
362 | pr_err("sst: error code = %d\n", ret_val); | ||
363 | return ret_val; | ||
364 | } | ||
365 | return stream->stream_info.buffer_ptr; | ||
366 | } | ||
367 | |||
368 | static int sst_platform_pcm_hw_params(struct snd_pcm_substream *substream, | ||
369 | struct snd_pcm_hw_params *params) | ||
370 | { | ||
371 | snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); | ||
372 | memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); | ||
373 | |||
374 | return 0; | ||
375 | } | ||
376 | |||
377 | static struct snd_pcm_ops sst_platform_ops = { | ||
378 | .open = sst_platform_open, | ||
379 | .close = sst_platform_close, | ||
380 | .ioctl = snd_pcm_lib_ioctl, | ||
381 | .prepare = sst_platform_pcm_prepare, | ||
382 | .trigger = sst_platform_pcm_trigger, | ||
383 | .pointer = sst_platform_pcm_pointer, | ||
384 | .hw_params = sst_platform_pcm_hw_params, | ||
385 | }; | ||
386 | |||
387 | static void sst_pcm_free(struct snd_pcm *pcm) | ||
388 | { | ||
389 | pr_debug("sst_pcm_free called\n"); | ||
390 | snd_pcm_lib_preallocate_free_for_all(pcm); | ||
391 | } | ||
392 | |||
393 | int sst_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, | ||
394 | struct snd_pcm *pcm) | ||
395 | { | ||
396 | int retval = 0; | ||
397 | |||
398 | pr_debug("sst_pcm_new called\n"); | ||
399 | if (dai->driver->playback.channels_min || | ||
400 | dai->driver->capture.channels_min) { | ||
401 | retval = snd_pcm_lib_preallocate_pages_for_all(pcm, | ||
402 | SNDRV_DMA_TYPE_CONTINUOUS, | ||
403 | snd_dma_continuous_data(GFP_KERNEL), | ||
404 | SST_MIN_BUFFER, SST_MAX_BUFFER); | ||
405 | if (retval) { | ||
406 | pr_err("dma buffer allocationf fail\n"); | ||
407 | return retval; | ||
408 | } | ||
409 | } | ||
410 | return retval; | ||
411 | } | ||
412 | struct snd_soc_platform_driver sst_soc_platform_drv = { | ||
413 | .ops = &sst_platform_ops, | ||
414 | .pcm_new = sst_pcm_new, | ||
415 | .pcm_free = sst_pcm_free, | ||
416 | }; | ||
417 | |||
418 | static int sst_platform_probe(struct platform_device *pdev) | ||
419 | { | ||
420 | int ret; | ||
421 | |||
422 | pr_debug("sst_platform_probe called\n"); | ||
423 | ret = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv); | ||
424 | if (ret) { | ||
425 | pr_err("registering soc platform failed\n"); | ||
426 | return ret; | ||
427 | } | ||
428 | |||
429 | ret = snd_soc_register_dais(&pdev->dev, | ||
430 | sst_platform_dai, ARRAY_SIZE(sst_platform_dai)); | ||
431 | if (ret) { | ||
432 | pr_err("registering cpu dais failed\n"); | ||
433 | snd_soc_unregister_platform(&pdev->dev); | ||
434 | } | ||
435 | return ret; | ||
436 | } | ||
437 | |||
438 | static int sst_platform_remove(struct platform_device *pdev) | ||
439 | { | ||
440 | |||
441 | snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(sst_platform_dai)); | ||
442 | snd_soc_unregister_platform(&pdev->dev); | ||
443 | pr_debug("sst_platform_remove sucess\n"); | ||
444 | return 0; | ||
445 | } | ||
446 | |||
447 | static struct platform_driver sst_platform_driver = { | ||
448 | .driver = { | ||
449 | .name = "sst-platform", | ||
450 | .owner = THIS_MODULE, | ||
451 | }, | ||
452 | .probe = sst_platform_probe, | ||
453 | .remove = sst_platform_remove, | ||
454 | }; | ||
455 | |||
456 | static int __init sst_soc_platform_init(void) | ||
457 | { | ||
458 | pr_debug("sst_soc_platform_init called\n"); | ||
459 | return platform_driver_register(&sst_platform_driver); | ||
460 | } | ||
461 | module_init(sst_soc_platform_init); | ||
462 | |||
463 | static void __exit sst_soc_platform_exit(void) | ||
464 | { | ||
465 | platform_driver_unregister(&sst_platform_driver); | ||
466 | pr_debug("sst_soc_platform_exit sucess\n"); | ||
467 | } | ||
468 | module_exit(sst_soc_platform_exit); | ||
469 | |||
470 | MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver"); | ||
471 | MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); | ||
472 | MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); | ||
473 | MODULE_LICENSE("GPL v2"); | ||
474 | MODULE_ALIAS("platform:sst-platform"); | ||