blob: 66c183615ce0260889148085e724fe59f5207d29 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#include <linux/module.h>
14#include <linux/platform_device.h>
15#include <linux/dma-mapping.h>
16#include <linux/debugfs.h>
17#include <linux/delay.h>
18#include <linux/uaccess.h>
19#include <linux/android_pmem.h>
20#include <linux/irq.h>
21#include <linux/interrupt.h>
22#include <linux/spinlock.h>
23#include <linux/slab.h>
24#include <linux/msm_audio.h>
25#include <linux/clk.h>
26#include <sound/core.h>
27#include <sound/pcm.h>
28#include <sound/soc.h>
29#include <mach/msm_iomap-8x60.h>
30#include <mach/audio_dma_msm8k.h>
31#include <sound/dai.h>
Bryan Huntsman1682f242011-09-29 11:12:13 -070032#include "lpass-pcm.h"
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070033
34struct dai_baseinfo {
35 void __iomem *base;
36};
37
38static struct dai_baseinfo dai_info;
39
40struct dai_drv {
41 u8 *buffer;
42 u32 buffer_phys;
43 int channels;
44 irqreturn_t (*callback) (int intrsrc, void *private_data);
45 void *private_data;
46 int in_use;
47 u32 buffer_len;
48 u32 period_len;
49 u32 master_mode;
50};
51
52static struct dai_drv *dai[MAX_CHANNELS];
53static spinlock_t dai_lock;
54
55static int dai_find_dma_channel(uint32_t intrsrc)
56{
57 int i, dma_channel = 0;
58 pr_debug("%s\n", __func__);
59
60 for (i = 0; i <= 27; i += 3) {
61 if (intrsrc & (1 << i)) {
62 dma_channel = i / 3;
63 break;
64 }
65 }
66 return dma_channel;
67}
68
69void register_dma_irq_handler(int dma_ch,
70 irqreturn_t (*callback) (int intrsrc, void *private_data),
71 void *private_data)
72{
73 pr_debug("%s\n", __func__);
74 dai[dma_ch]->callback = callback;
75 dai[dma_ch]->private_data = private_data;
76}
77
78void unregister_dma_irq_handler(int dma_ch)
79{
80 pr_debug("%s\n", __func__);
81 dai[dma_ch]->callback = NULL;
82 dai[dma_ch]->private_data = NULL;
83}
84
85static irqreturn_t dai_irq_handler(int irq, void *data)
86{
87 unsigned long flag;
88 uint32_t intrsrc;
89 uint32_t dma_ch = 0;
90 irqreturn_t ret = IRQ_HANDLED;
91
92 pr_debug("%s\n", __func__);
93 spin_lock_irqsave(&dai_lock, flag);
94 intrsrc = readl(dai_info.base + LPAIF_IRQ_STAT(0));
95 writel(intrsrc, dai_info.base + LPAIF_IRQ_CLEAR(0));
Deepa Madiregama6a3a01a2011-10-28 06:34:17 +053096 mb();
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070097 while (intrsrc) {
98 dma_ch = dai_find_dma_channel(intrsrc);
99
100 if (!dai[dma_ch]->callback)
101 goto handled;
102 if (!dai[dma_ch]->private_data)
103 goto handled;
104 ret = dai[dma_ch]->callback(intrsrc,
105 dai[dma_ch]->private_data);
106 intrsrc &= ~(0x7 << (dma_ch * 3));
107 }
108handled:
109 spin_unlock_irqrestore(&dai_lock, flag);
110 return ret;
111}
112
113void dai_print_state(uint32_t dma_ch)
114{
115 int i = 0;
116 unsigned long *ptrmem = (unsigned long *)dai_info.base;
117
118 for (i = 0; i < 4; i++, ++ptrmem)
119 pr_debug("[0x%08x]=0x%08x\n", (unsigned int)ptrmem,
120 (unsigned int)*ptrmem);
121
122 ptrmem = (unsigned long *)(dai_info.base
123 + DMA_CH_CTL_BASE + DMA_CH_INDEX(dma_ch));
124 for (i = 0; i < 10; i++, ++ptrmem)
125 pr_debug("[0x%08x]=0x%08x\n", (unsigned int)ptrmem,
126 (unsigned int) *ptrmem);
127}
128
129static int dai_enable_irq(uint32_t dma_ch)
130{
131 int ret;
132 pr_debug("%s\n", __func__);
133 ret = request_irq(LPASS_SCSS_AUDIO_IF_OUT0_IRQ, dai_irq_handler,
134 IRQF_TRIGGER_RISING | IRQF_SHARED, "msm-i2s",
135 (void *) (dma_ch+1));
136 if (ret < 0) {
137 pr_debug("Request Irq Failed err = %d\n", ret);
138 return ret;
139 }
140 return ret;
141}
142
143static void dai_config_dma(uint32_t dma_ch)
144{
145 pr_debug("%s dma_ch = %u\n", __func__, dma_ch);
146
147 writel(dai[dma_ch]->buffer_phys,
148 dai_info.base + LPAIF_DMA_BASE(dma_ch));
149 writel(((dai[dma_ch]->buffer_len >> 2) - 1),
150 dai_info.base + LPAIF_DMA_BUFF_LEN(dma_ch));
151 writel(((dai[dma_ch]->period_len >> 2) - 1),
152 dai_info.base + LPAIF_DMA_PER_LEN(dma_ch));
Deepa Madiregama6a3a01a2011-10-28 06:34:17 +0530153 mb();
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700154}
155
156static void dai_enable_codec(uint32_t dma_ch, int codec)
157{
158 uint32_t intrVal;
159 uint32_t i2sctl;
160 pr_debug("%s\n", __func__);
161
162 intrVal = readl(dai_info.base + LPAIF_IRQ_EN(0));
163 intrVal = intrVal | (7 << (dma_ch * 3));
164 writel(intrVal, dai_info.base + LPAIF_IRQ_EN(0));
165 if (codec == DAI_SPKR) {
166 writel(0x0813, dai_info.base + LPAIF_DMA_CTL(dma_ch));
167 i2sctl = 0x4400;
168 i2sctl |= (dai[dma_ch]->master_mode ? WS_SRC_INT : WS_SRC_EXT);
169 writel(i2sctl, dai_info.base + LPAIF_I2S_CTL_OFFSET(DAI_SPKR));
170 } else if (codec == DAI_MIC) {
171 writel(0x81b, dai_info.base + LPAIF_DMA_CTL(dma_ch));
172 i2sctl = 0x0110;
173 i2sctl |= (dai[dma_ch]->master_mode ? WS_SRC_INT : WS_SRC_EXT);
174 writel(i2sctl, dai_info.base + LPAIF_I2S_CTL_OFFSET(DAI_MIC));
175 }
176}
177
178static void dai_disable_codec(uint32_t dma_ch, int codec)
179{
180 uint32_t intrVal = 0;
181 uint32_t intrVal1 = 0;
182 unsigned long flag = 0x0;
183
184 pr_debug("%s\n", __func__);
185 spin_lock_irqsave(&dai_lock, flag);
186
187 intrVal1 = readl(dai_info.base + LPAIF_I2S_CTL_OFFSET(codec));
188
189 if (codec == DAI_SPKR)
190 intrVal1 = intrVal1 & ~(1 << 14);
191 else if (codec == DAI_MIC)
192 intrVal1 = intrVal1 & ~(1 << 8);
193
194 writel(intrVal1, dai_info.base + LPAIF_I2S_CTL_OFFSET(codec));
195 intrVal = 0x0;
196 writel(intrVal, dai_info.base + LPAIF_DMA_CTL(dma_ch));
197
198 spin_unlock_irqrestore(&dai_lock, flag);
199}
200
201int dai_open(uint32_t dma_ch)
202{
203
204 pr_debug("%s\n", __func__);
205 if (!dai_info.base) {
206 pr_debug("%s failed as no msm-dai device\n", __func__);
207 return -ENODEV;
208 }
209 if (dma_ch >= MAX_CHANNELS) {
210 pr_debug("%s over max channesl %d\n", __func__, dma_ch);
211 return -ENODEV;
212 }
213 return 0;
214}
215
216void dai_close(uint32_t dma_ch)
217{
218 pr_debug("%s\n", __func__);
219 if ((dma_ch >= 0) && (dma_ch < 5))
220 dai_disable_codec(dma_ch, DAI_SPKR);
221 else
222 dai_disable_codec(dma_ch, DAI_MIC);
223 free_irq(LPASS_SCSS_AUDIO_IF_OUT0_IRQ, (void *) (dma_ch + 1));
224}
225
226void dai_set_master_mode(uint32_t dma_ch, int mode)
227{
228 if (dma_ch < MAX_CHANNELS)
229 dai[dma_ch]->master_mode = mode;
230 else
231 pr_err("%s: invalid dma channel\n", __func__);
232}
233
234int dai_set_params(uint32_t dma_ch, struct dai_dma_params *params)
235{
236 pr_debug("%s\n", __func__);
237 dai[dma_ch]->buffer = params->buffer;
238 dai[dma_ch]->buffer_phys = params->src_start;
239 dai[dma_ch]->channels = params->channels;
240 dai[dma_ch]->buffer_len = params->buffer_size;
241 dai[dma_ch]->period_len = params->period_size;
Deepa Madiregama6a3a01a2011-10-28 06:34:17 +0530242 mb();
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700243 dai_config_dma(dma_ch);
244 return dma_ch;
245}
246
247int dai_start(uint32_t dma_ch)
248{
249 unsigned long flag = 0x0;
250
251 spin_lock_irqsave(&dai_lock, flag);
252 dai_enable_irq(dma_ch);
253 if ((dma_ch >= 0) && (dma_ch < 5))
254 dai_enable_codec(dma_ch, DAI_SPKR);
255 else
256 dai_enable_codec(dma_ch, DAI_MIC);
257 spin_unlock_irqrestore(&dai_lock, flag);
258 dai_print_state(dma_ch);
259 return 0;
260}
261
262#define HDMI_BURST_INCR4 (1 << 11)
263#define HDMI_WPSCNT (1 << 8)
264#define HDMI_AUDIO_INTF (5 << 4)
265#define HDMI_FIFO_WATER_MARK (7 << 1)
266#define HDMI_ENABLE (1)
267
268int dai_start_hdmi(uint32_t dma_ch)
269{
270 unsigned long flag = 0x0;
271 uint32_t val;
272
273 pr_debug("%s dma_ch = %u\n", __func__, dma_ch);
274
275 spin_lock_irqsave(&dai_lock, flag);
276
277 dai_enable_irq(dma_ch);
278
279 if ((dma_ch >= 0) && (dma_ch < 5)) {
280
281 val = readl(dai_info.base + LPAIF_IRQ_EN(0));
282 val = val | (7 << (dma_ch * 3));
283 writel(val, dai_info.base + LPAIF_IRQ_EN(0));
Deepa Madiregama6a3a01a2011-10-28 06:34:17 +0530284 mb();
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700285
286
287 val = (HDMI_BURST_INCR4 | HDMI_WPSCNT | HDMI_AUDIO_INTF |
288 HDMI_FIFO_WATER_MARK | HDMI_ENABLE);
289
290 writel(val, dai_info.base + LPAIF_DMA_CTL(dma_ch));
291 }
292 spin_unlock_irqrestore(&dai_lock, flag);
293
Deepa Madiregama6a3a01a2011-10-28 06:34:17 +0530294 mb();
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700295 dai_print_state(dma_ch);
296 return 0;
297}
298
Deepa Madiregama6a3a01a2011-10-28 06:34:17 +0530299int wait_for_dma_cnt_stop(uint32_t dma_ch)
300{
301 uint32_t dma_per_cnt_reg_val, dma_per_cnt, prev_dma_per_cnt;
302 uint32_t i;
303
304 pr_info("%s dma_ch %u\n", __func__, dma_ch);
305
306 dma_per_cnt_reg_val = readl_relaxed(dai_info.base +
307 LPAIF_DMA_PER_CNT(dma_ch));
308
309 dma_per_cnt =
310 ((LPAIF_DMA_PER_CNT_PER_CNT_MASK & dma_per_cnt_reg_val) >>
311 LPAIF_DMA_PER_CNT_PER_CNT_SHIFT) -
312 ((LPAIF_DMA_PER_CNT_FIFO_WORDCNT_MASK & dma_per_cnt_reg_val) >>
313 LPAIF_DMA_PER_CNT_FIFO_WORDCNT_SHIFT);
314
315 prev_dma_per_cnt = dma_per_cnt;
316
317 i = 1;
318 pr_info("%s: i = %u dma_per_cnt_reg_val 0x%08x , dma_per_cnt %u\n",
319 __func__, i, dma_per_cnt_reg_val, dma_per_cnt);
320
321 while (i <= 50) {
322 msleep(50);
323
324 dma_per_cnt_reg_val = readl_relaxed(dai_info.base +
325 LPAIF_DMA_PER_CNT(dma_ch));
326
327 dma_per_cnt =
328 ((LPAIF_DMA_PER_CNT_PER_CNT_MASK & dma_per_cnt_reg_val) >>
329 LPAIF_DMA_PER_CNT_PER_CNT_SHIFT) -
330 ((LPAIF_DMA_PER_CNT_FIFO_WORDCNT_MASK & dma_per_cnt_reg_val) >>
331 LPAIF_DMA_PER_CNT_FIFO_WORDCNT_SHIFT);
332
333 i++;
334
335 pr_info("%s: i = %u dma_per_cnt_reg_val 0x%08x , dma_per_cnt %u\n",
336 __func__, i, dma_per_cnt_reg_val, dma_per_cnt);
337
338 if (prev_dma_per_cnt == dma_per_cnt)
339 break;
340
341 prev_dma_per_cnt = dma_per_cnt;
342 }
343 return 0;
344}
345
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700346void dai_stop_hdmi(uint32_t dma_ch)
347{
348 unsigned long flag = 0x0;
349 uint32_t intrVal;
350 uint32_t int_mask = 0x00000007;
351
352 pr_debug("%s dma_ch %u\n", __func__, dma_ch);
353
354 spin_lock_irqsave(&dai_lock, flag);
355
356 free_irq(LPASS_SCSS_AUDIO_IF_OUT0_IRQ, (void *) (dma_ch + 1));
357
358
359 intrVal = 0x0;
360 writel(intrVal, dai_info.base + LPAIF_DMA_CTL(dma_ch));
361
Deepa Madiregama6a3a01a2011-10-28 06:34:17 +0530362 mb();
363
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700364 intrVal = readl(dai_info.base + LPAIF_IRQ_EN(0));
365
366 int_mask = ((int_mask) << (dma_ch * 3));
367 int_mask = ~int_mask;
368
Deepa Madiregama6a3a01a2011-10-28 06:34:17 +0530369 intrVal = intrVal & int_mask;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700370 writel(intrVal, dai_info.base + LPAIF_IRQ_EN(0));
371
Deepa Madiregama6a3a01a2011-10-28 06:34:17 +0530372 mb();
373
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700374 spin_unlock_irqrestore(&dai_lock, flag);
375}
376
377int dai_stop(uint32_t dma_ch)
378{
379 pr_debug("%s\n", __func__);
380 return 0;
381}
382
383
384uint32_t dai_get_dma_pos(uint32_t dma_ch)
385{
386
387 uint32_t addr;
388
389 pr_debug("%s\n", __func__);
390 addr = readl(dai_info.base + LPAIF_DMA_CURR_ADDR(dma_ch));
391
392 return addr;
393}
394
395static int __devinit dai_probe(struct platform_device *pdev)
396{
397 int rc = 0;
398 int i = 0;
399 struct resource *src;
400 src = platform_get_resource_byname(pdev, IORESOURCE_MEM, "msm-dai");
401 if (!src) {
402 rc = -ENODEV;
403 pr_debug("%s Error rc=%d\n", __func__, rc);
404 goto error;
405 }
406 for (i = 0; i <= MAX_CHANNELS; i++) {
407 dai[i] = kzalloc(sizeof(struct dai_drv), GFP_KERNEL);
408 if (!dai[0]) {
409 pr_debug("Allocation failed for dma_channel = 0\n");
410 return -ENODEV;
411 }
412 }
413 dai_info.base = ioremap(src->start, (src->end - src->start) + 1);
414 pr_debug("%s: msm-dai: 0x%08x\n", __func__,
415 (unsigned int)dai_info.base);
416 spin_lock_init(&dai_lock);
417error:
418 return rc;
419}
420
421static int dai_remove(struct platform_device *pdev)
422{
423 iounmap(dai_info.base);
424 return 0;
425}
426
427static struct platform_driver dai_driver = {
428 .probe = dai_probe,
429 .remove = dai_remove,
430 .driver = {
431 .name = "msm-dai",
432 .owner = THIS_MODULE
433 },
434};
435
436static struct resource msm_lpa_resources[] = {
437 {
438 .start = MSM_LPA_PHYS,
439 .end = MSM_LPA_END,
440 .flags = IORESOURCE_MEM,
441 .name = "msm-dai",
442 },
443};
444
445static struct platform_device *codec_device;
446
447static int msm_dai_dev_register(const char *name)
448{
449 int ret = 0;
450
451 pr_debug("%s : called\n", __func__);
452 codec_device = platform_device_alloc(name, -1);
453 if (codec_device == NULL) {
454 pr_debug("Failed to allocate %s\n", name);
455 return -ENODEV;
456 }
457
458 platform_set_drvdata(codec_device, (void *)&dai_info);
459 platform_device_add_resources(codec_device, &msm_lpa_resources[0],
460 ARRAY_SIZE(msm_lpa_resources));
461 ret = platform_device_add(codec_device);
462 if (ret != 0) {
463 pr_debug("Failed to register %s: %d\n", name, ret);
464 platform_device_put(codec_device);
465 }
466 return ret;
467}
468
469static int __init dai_init(void)
470{
471 if (msm_dai_dev_register("msm-dai")) {
472 pr_notice("dai_init: msm-dai Failed");
473 return -ENODEV;
474 }
475 return platform_driver_register(&dai_driver);
476}
477
478static void __exit dai_exit(void)
479{
480 platform_driver_unregister(&dai_driver);
481 platform_device_put(codec_device);
482}
483
484module_init(dai_init);
485module_exit(dai_exit);
486
487MODULE_DESCRIPTION("MSM I2S driver");
488MODULE_LICENSE("GPL v2");