aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/qcom
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/qcom')
-rw-r--r--sound/soc/qcom/Kconfig25
-rw-r--r--sound/soc/qcom/Makefile11
-rw-r--r--sound/soc/qcom/lpass-cpu.c491
-rw-r--r--sound/soc/qcom/lpass-lpaif-ipq806x.h172
-rw-r--r--sound/soc/qcom/lpass-platform.c526
-rw-r--r--sound/soc/qcom/lpass.h51
-rw-r--r--sound/soc/qcom/storm.c162
7 files changed, 1438 insertions, 0 deletions
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
new file mode 100644
index 000000000000..5f58e4f1bca9
--- /dev/null
+++ b/sound/soc/qcom/Kconfig
@@ -0,0 +1,25 @@
1config SND_SOC_QCOM
2 tristate "ASoC support for QCOM platforms"
3 help
4 Say Y or M if you want to add support to use audio devices
5 in Qualcomm Technologies SOC-based platforms.
6
7config SND_SOC_LPASS_CPU
8 tristate
9 depends on SND_SOC_QCOM
10 select REGMAP_MMIO
11
12config SND_SOC_LPASS_PLATFORM
13 tristate
14 depends on SND_SOC_QCOM
15 select REGMAP_MMIO
16
17config SND_SOC_STORM
18 tristate "ASoC I2S support for Storm boards"
19 depends on (ARCH_QCOM && SND_SOC_QCOM) || COMPILE_TEST
20 select SND_SOC_LPASS_CPU
21 select SND_SOC_LPASS_PLATFORM
22 select SND_SOC_MAX98357A
23 help
24 Say Y or M if you want add support for SoC audio on the
25 Qualcomm Technologies IPQ806X-based Storm board.
diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile
new file mode 100644
index 000000000000..c5ce96c761c4
--- /dev/null
+++ b/sound/soc/qcom/Makefile
@@ -0,0 +1,11 @@
1# Platform
2snd-soc-lpass-cpu-objs := lpass-cpu.o
3snd-soc-lpass-platform-objs := lpass-platform.o
4
5obj-$(CONFIG_SND_SOC_LPASS_CPU) += snd-soc-lpass-cpu.o
6obj-$(CONFIG_SND_SOC_LPASS_PLATFORM) += snd-soc-lpass-platform.o
7
8# Machine
9snd-soc-storm-objs := storm.o
10
11obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o
diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c
new file mode 100644
index 000000000000..6698d058de29
--- /dev/null
+++ b/sound/soc/qcom/lpass-cpu.c
@@ -0,0 +1,491 @@
1/*
2 * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * lpass-cpu.c -- ALSA SoC CPU DAI driver for QTi LPASS
14 */
15
16#include <linux/clk.h>
17#include <linux/compiler.h>
18#include <linux/device.h>
19#include <linux/err.h>
20#include <linux/ioport.h>
21#include <linux/kernel.h>
22#include <linux/mod_devicetable.h>
23#include <linux/module.h>
24#include <linux/of.h>
25#include <linux/platform_device.h>
26#include <sound/pcm.h>
27#include <sound/pcm_params.h>
28#include <linux/regmap.h>
29#include <sound/soc.h>
30#include <sound/soc-dai.h>
31#include "lpass-lpaif-ipq806x.h"
32#include "lpass.h"
33
34static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id,
35 unsigned int freq, int dir)
36{
37 struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
38 int ret;
39
40 ret = clk_set_rate(drvdata->mi2s_osr_clk, freq);
41 if (ret)
42 dev_err(dai->dev, "%s() error setting mi2s osrclk to %u: %d\n",
43 __func__, freq, ret);
44
45 return ret;
46}
47
48static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream,
49 struct snd_soc_dai *dai)
50{
51 struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
52 int ret;
53
54 ret = clk_prepare_enable(drvdata->mi2s_osr_clk);
55 if (ret) {
56 dev_err(dai->dev, "%s() error in enabling mi2s osr clk: %d\n",
57 __func__, ret);
58 return ret;
59 }
60
61 ret = clk_prepare_enable(drvdata->mi2s_bit_clk);
62 if (ret) {
63 dev_err(dai->dev, "%s() error in enabling mi2s bit clk: %d\n",
64 __func__, ret);
65 clk_disable_unprepare(drvdata->mi2s_osr_clk);
66 return ret;
67 }
68
69 return 0;
70}
71
72static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream,
73 struct snd_soc_dai *dai)
74{
75 struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
76
77 clk_disable_unprepare(drvdata->mi2s_bit_clk);
78 clk_disable_unprepare(drvdata->mi2s_osr_clk);
79}
80
81static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream,
82 struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
83{
84 struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
85 snd_pcm_format_t format = params_format(params);
86 unsigned int channels = params_channels(params);
87 unsigned int rate = params_rate(params);
88 unsigned int regval;
89 int bitwidth, ret;
90
91 bitwidth = snd_pcm_format_width(format);
92 if (bitwidth < 0) {
93 dev_err(dai->dev, "%s() invalid bit width given: %d\n",
94 __func__, bitwidth);
95 return bitwidth;
96 }
97
98 regval = LPAIF_I2SCTL_LOOPBACK_DISABLE |
99 LPAIF_I2SCTL_WSSRC_INTERNAL;
100
101 switch (bitwidth) {
102 case 16:
103 regval |= LPAIF_I2SCTL_BITWIDTH_16;
104 break;
105 case 24:
106 regval |= LPAIF_I2SCTL_BITWIDTH_24;
107 break;
108 case 32:
109 regval |= LPAIF_I2SCTL_BITWIDTH_32;
110 break;
111 default:
112 dev_err(dai->dev, "%s() invalid bitwidth given: %d\n",
113 __func__, bitwidth);
114 return -EINVAL;
115 }
116
117 switch (channels) {
118 case 1:
119 regval |= LPAIF_I2SCTL_SPKMODE_SD0;
120 regval |= LPAIF_I2SCTL_SPKMONO_MONO;
121 break;
122 case 2:
123 regval |= LPAIF_I2SCTL_SPKMODE_SD0;
124 regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
125 break;
126 case 4:
127 regval |= LPAIF_I2SCTL_SPKMODE_QUAD01;
128 regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
129 break;
130 case 6:
131 regval |= LPAIF_I2SCTL_SPKMODE_6CH;
132 regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
133 break;
134 case 8:
135 regval |= LPAIF_I2SCTL_SPKMODE_8CH;
136 regval |= LPAIF_I2SCTL_SPKMONO_STEREO;
137 break;
138 default:
139 dev_err(dai->dev, "%s() invalid channels given: %u\n",
140 __func__, channels);
141 return -EINVAL;
142 }
143
144 ret = regmap_write(drvdata->lpaif_map,
145 LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), regval);
146 if (ret) {
147 dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
148 __func__, ret);
149 return ret;
150 }
151
152 ret = clk_set_rate(drvdata->mi2s_bit_clk, rate * bitwidth * 2);
153 if (ret) {
154 dev_err(dai->dev, "%s() error setting mi2s bitclk to %u: %d\n",
155 __func__, rate * bitwidth * 2, ret);
156 return ret;
157 }
158
159 return 0;
160}
161
162static int lpass_cpu_daiops_hw_free(struct snd_pcm_substream *substream,
163 struct snd_soc_dai *dai)
164{
165 struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
166 int ret;
167
168 ret = regmap_write(drvdata->lpaif_map,
169 LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), 0);
170 if (ret)
171 dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
172 __func__, ret);
173
174 return ret;
175}
176
177static int lpass_cpu_daiops_prepare(struct snd_pcm_substream *substream,
178 struct snd_soc_dai *dai)
179{
180 struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
181 int ret;
182
183 ret = regmap_update_bits(drvdata->lpaif_map,
184 LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S),
185 LPAIF_I2SCTL_SPKEN_MASK, LPAIF_I2SCTL_SPKEN_ENABLE);
186 if (ret)
187 dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
188 __func__, ret);
189
190 return ret;
191}
192
193static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream,
194 int cmd, struct snd_soc_dai *dai)
195{
196 struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
197 int ret;
198
199 switch (cmd) {
200 case SNDRV_PCM_TRIGGER_START:
201 case SNDRV_PCM_TRIGGER_RESUME:
202 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
203 ret = regmap_update_bits(drvdata->lpaif_map,
204 LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S),
205 LPAIF_I2SCTL_SPKEN_MASK,
206 LPAIF_I2SCTL_SPKEN_ENABLE);
207 if (ret)
208 dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
209 __func__, ret);
210 break;
211 case SNDRV_PCM_TRIGGER_STOP:
212 case SNDRV_PCM_TRIGGER_SUSPEND:
213 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
214 ret = regmap_update_bits(drvdata->lpaif_map,
215 LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S),
216 LPAIF_I2SCTL_SPKEN_MASK,
217 LPAIF_I2SCTL_SPKEN_DISABLE);
218 if (ret)
219 dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
220 __func__, ret);
221 break;
222 }
223
224 return ret;
225}
226
227static struct snd_soc_dai_ops lpass_cpu_dai_ops = {
228 .set_sysclk = lpass_cpu_daiops_set_sysclk,
229 .startup = lpass_cpu_daiops_startup,
230 .shutdown = lpass_cpu_daiops_shutdown,
231 .hw_params = lpass_cpu_daiops_hw_params,
232 .hw_free = lpass_cpu_daiops_hw_free,
233 .prepare = lpass_cpu_daiops_prepare,
234 .trigger = lpass_cpu_daiops_trigger,
235};
236
237static int lpass_cpu_dai_probe(struct snd_soc_dai *dai)
238{
239 struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai);
240 int ret;
241
242 /* ensure audio hardware is disabled */
243 ret = regmap_write(drvdata->lpaif_map,
244 LPAIF_I2SCTL_REG(LPAIF_I2S_PORT_MI2S), 0);
245 if (ret)
246 dev_err(dai->dev, "%s() error writing to i2sctl reg: %d\n",
247 __func__, ret);
248
249 return ret;
250}
251
252static struct snd_soc_dai_driver lpass_cpu_dai_driver = {
253 .playback = {
254 .stream_name = "lpass-cpu-playback",
255 .formats = SNDRV_PCM_FMTBIT_S16 |
256 SNDRV_PCM_FMTBIT_S24 |
257 SNDRV_PCM_FMTBIT_S32,
258 .rates = SNDRV_PCM_RATE_8000 |
259 SNDRV_PCM_RATE_16000 |
260 SNDRV_PCM_RATE_32000 |
261 SNDRV_PCM_RATE_48000 |
262 SNDRV_PCM_RATE_96000,
263 .rate_min = 8000,
264 .rate_max = 96000,
265 .channels_min = 1,
266 .channels_max = 8,
267 },
268 .probe = &lpass_cpu_dai_probe,
269 .ops = &lpass_cpu_dai_ops,
270};
271
272static const struct snd_soc_component_driver lpass_cpu_comp_driver = {
273 .name = "lpass-cpu",
274};
275
276static bool lpass_cpu_regmap_writeable(struct device *dev, unsigned int reg)
277{
278 int i;
279
280 for (i = 0; i < LPAIF_I2S_PORT_NUM; ++i)
281 if (reg == LPAIF_I2SCTL_REG(i))
282 return true;
283
284 for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) {
285 if (reg == LPAIF_IRQEN_REG(i))
286 return true;
287 if (reg == LPAIF_IRQCLEAR_REG(i))
288 return true;
289 }
290
291 for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) {
292 if (reg == LPAIF_RDMACTL_REG(i))
293 return true;
294 if (reg == LPAIF_RDMABASE_REG(i))
295 return true;
296 if (reg == LPAIF_RDMABUFF_REG(i))
297 return true;
298 if (reg == LPAIF_RDMAPER_REG(i))
299 return true;
300 }
301
302 return false;
303}
304
305static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg)
306{
307 int i;
308
309 for (i = 0; i < LPAIF_I2S_PORT_NUM; ++i)
310 if (reg == LPAIF_I2SCTL_REG(i))
311 return true;
312
313 for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i) {
314 if (reg == LPAIF_IRQEN_REG(i))
315 return true;
316 if (reg == LPAIF_IRQSTAT_REG(i))
317 return true;
318 }
319
320 for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i) {
321 if (reg == LPAIF_RDMACTL_REG(i))
322 return true;
323 if (reg == LPAIF_RDMABASE_REG(i))
324 return true;
325 if (reg == LPAIF_RDMABUFF_REG(i))
326 return true;
327 if (reg == LPAIF_RDMACURR_REG(i))
328 return true;
329 if (reg == LPAIF_RDMAPER_REG(i))
330 return true;
331 }
332
333 return false;
334}
335
336static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg)
337{
338 int i;
339
340 for (i = 0; i < LPAIF_IRQ_PORT_NUM; ++i)
341 if (reg == LPAIF_IRQSTAT_REG(i))
342 return true;
343
344 for (i = 0; i < LPAIF_RDMA_CHAN_NUM; ++i)
345 if (reg == LPAIF_RDMACURR_REG(i))
346 return true;
347
348 return false;
349}
350
351static const struct regmap_config lpass_cpu_regmap_config = {
352 .reg_bits = 32,
353 .reg_stride = 4,
354 .val_bits = 32,
355 .max_register = LPAIF_RDMAPER_REG(LPAIF_RDMA_CHAN_MAX),
356 .writeable_reg = lpass_cpu_regmap_writeable,
357 .readable_reg = lpass_cpu_regmap_readable,
358 .volatile_reg = lpass_cpu_regmap_volatile,
359 .cache_type = REGCACHE_FLAT,
360};
361
362static int lpass_cpu_platform_probe(struct platform_device *pdev)
363{
364 struct lpass_data *drvdata;
365 struct device_node *dsp_of_node;
366 struct resource *res;
367 int ret;
368
369 dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0);
370 if (dsp_of_node) {
371 dev_err(&pdev->dev, "%s() DSP exists and holds audio resources\n",
372 __func__);
373 return -EBUSY;
374 }
375
376 drvdata = devm_kzalloc(&pdev->dev, sizeof(struct lpass_data),
377 GFP_KERNEL);
378 if (!drvdata)
379 return -ENOMEM;
380 platform_set_drvdata(pdev, drvdata);
381
382 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpaif");
383 if (!res) {
384 dev_err(&pdev->dev, "%s() error getting resource\n", __func__);
385 return -ENODEV;
386 }
387
388 drvdata->lpaif = devm_ioremap_resource(&pdev->dev, res);
389 if (IS_ERR((void const __force *)drvdata->lpaif)) {
390 dev_err(&pdev->dev, "%s() error mapping reg resource: %ld\n",
391 __func__,
392 PTR_ERR((void const __force *)drvdata->lpaif));
393 return PTR_ERR((void const __force *)drvdata->lpaif);
394 }
395
396 drvdata->lpaif_map = devm_regmap_init_mmio(&pdev->dev, drvdata->lpaif,
397 &lpass_cpu_regmap_config);
398 if (IS_ERR(drvdata->lpaif_map)) {
399 dev_err(&pdev->dev, "%s() error initializing regmap: %ld\n",
400 __func__, PTR_ERR(drvdata->lpaif_map));
401 return PTR_ERR(drvdata->lpaif_map);
402 }
403
404 drvdata->mi2s_osr_clk = devm_clk_get(&pdev->dev, "mi2s-osr-clk");
405 if (IS_ERR(drvdata->mi2s_osr_clk)) {
406 dev_err(&pdev->dev, "%s() error getting mi2s-osr-clk: %ld\n",
407 __func__, PTR_ERR(drvdata->mi2s_osr_clk));
408 return PTR_ERR(drvdata->mi2s_osr_clk);
409 }
410
411 drvdata->mi2s_bit_clk = devm_clk_get(&pdev->dev, "mi2s-bit-clk");
412 if (IS_ERR(drvdata->mi2s_bit_clk)) {
413 dev_err(&pdev->dev, "%s() error getting mi2s-bit-clk: %ld\n",
414 __func__, PTR_ERR(drvdata->mi2s_bit_clk));
415 return PTR_ERR(drvdata->mi2s_bit_clk);
416 }
417
418 drvdata->ahbix_clk = devm_clk_get(&pdev->dev, "ahbix-clk");
419 if (IS_ERR(drvdata->ahbix_clk)) {
420 dev_err(&pdev->dev, "%s() error getting ahbix-clk: %ld\n",
421 __func__, PTR_ERR(drvdata->ahbix_clk));
422 return PTR_ERR(drvdata->ahbix_clk);
423 }
424
425 ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY);
426 if (ret) {
427 dev_err(&pdev->dev, "%s() error setting rate on ahbix_clk: %d\n",
428 __func__, ret);
429 return ret;
430 }
431 dev_dbg(&pdev->dev, "%s() set ahbix_clk rate to %lu\n", __func__,
432 clk_get_rate(drvdata->ahbix_clk));
433
434 ret = clk_prepare_enable(drvdata->ahbix_clk);
435 if (ret) {
436 dev_err(&pdev->dev, "%s() error enabling ahbix_clk: %d\n",
437 __func__, ret);
438 return ret;
439 }
440
441 ret = devm_snd_soc_register_component(&pdev->dev,
442 &lpass_cpu_comp_driver, &lpass_cpu_dai_driver, 1);
443 if (ret) {
444 dev_err(&pdev->dev, "%s() error registering cpu driver: %d\n",
445 __func__, ret);
446 goto err_clk;
447 }
448
449 ret = asoc_qcom_lpass_platform_register(pdev);
450 if (ret) {
451 dev_err(&pdev->dev, "%s() error registering platform driver: %d\n",
452 __func__, ret);
453 goto err_clk;
454 }
455
456 return 0;
457
458err_clk:
459 clk_disable_unprepare(drvdata->ahbix_clk);
460 return ret;
461}
462
463static int lpass_cpu_platform_remove(struct platform_device *pdev)
464{
465 struct lpass_data *drvdata = platform_get_drvdata(pdev);
466
467 clk_disable_unprepare(drvdata->ahbix_clk);
468
469 return 0;
470}
471
472#ifdef CONFIG_OF
473static const struct of_device_id lpass_cpu_device_id[] = {
474 { .compatible = "qcom,lpass-cpu" },
475 {}
476};
477MODULE_DEVICE_TABLE(of, lpass_cpu_device_id);
478#endif
479
480static struct platform_driver lpass_cpu_platform_driver = {
481 .driver = {
482 .name = "lpass-cpu",
483 .of_match_table = of_match_ptr(lpass_cpu_device_id),
484 },
485 .probe = lpass_cpu_platform_probe,
486 .remove = lpass_cpu_platform_remove,
487};
488module_platform_driver(lpass_cpu_platform_driver);
489
490MODULE_DESCRIPTION("QTi LPASS CPU Driver");
491MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/lpass-lpaif-ipq806x.h b/sound/soc/qcom/lpass-lpaif-ipq806x.h
new file mode 100644
index 000000000000..dc423b888842
--- /dev/null
+++ b/sound/soc/qcom/lpass-lpaif-ipq806x.h
@@ -0,0 +1,172 @@
1/*
2 * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * lpass-lpaif-ipq806x.h -- Definitions for the QTi LPAIF in the ipq806x LPASS
14 */
15
16#ifndef __LPASS_LPAIF_H__
17#define __LPASS_LPAIF_H__
18
19#define LPAIF_BANK_OFFSET 0x1000
20
21/* LPAIF I2S */
22
23#define LPAIF_I2SCTL_REG_BASE 0x0010
24#define LPAIF_I2SCTL_REG_STRIDE 0x4
25#define LPAIF_I2SCTL_REG_ADDR(addr, port) \
26 (LPAIF_I2SCTL_REG_BASE + (addr) + (LPAIF_I2SCTL_REG_STRIDE * (port)))
27
28enum lpaif_i2s_ports {
29 LPAIF_I2S_PORT_MIN = 0,
30
31 LPAIF_I2S_PORT_CODEC_SPK = 0,
32 LPAIF_I2S_PORT_CODEC_MIC = 1,
33 LPAIF_I2S_PORT_SEC_SPK = 2,
34 LPAIF_I2S_PORT_SEC_MIC = 3,
35 LPAIF_I2S_PORT_MI2S = 4,
36
37 LPAIF_I2S_PORT_MAX = 4,
38 LPAIF_I2S_PORT_NUM = 5,
39};
40
41#define LPAIF_I2SCTL_REG(port) LPAIF_I2SCTL_REG_ADDR(0x0, (port))
42
43#define LPAIF_I2SCTL_LOOPBACK_MASK 0x8000
44#define LPAIF_I2SCTL_LOOPBACK_SHIFT 15
45#define LPAIF_I2SCTL_LOOPBACK_DISABLE (0 << LPAIF_I2SCTL_LOOPBACK_SHIFT)
46#define LPAIF_I2SCTL_LOOPBACK_ENABLE (1 << LPAIF_I2SCTL_LOOPBACK_SHIFT)
47
48#define LPAIF_I2SCTL_SPKEN_MASK 0x4000
49#define LPAIF_I2SCTL_SPKEN_SHIFT 14
50#define LPAIF_I2SCTL_SPKEN_DISABLE (0 << LPAIF_I2SCTL_SPKEN_SHIFT)
51#define LPAIF_I2SCTL_SPKEN_ENABLE (1 << LPAIF_I2SCTL_SPKEN_SHIFT)
52
53#define LPAIF_I2SCTL_SPKMODE_MASK 0x3C00
54#define LPAIF_I2SCTL_SPKMODE_SHIFT 10
55#define LPAIF_I2SCTL_SPKMODE_NONE (0 << LPAIF_I2SCTL_SPKMODE_SHIFT)
56#define LPAIF_I2SCTL_SPKMODE_SD0 (1 << LPAIF_I2SCTL_SPKMODE_SHIFT)
57#define LPAIF_I2SCTL_SPKMODE_SD1 (2 << LPAIF_I2SCTL_SPKMODE_SHIFT)
58#define LPAIF_I2SCTL_SPKMODE_SD2 (3 << LPAIF_I2SCTL_SPKMODE_SHIFT)
59#define LPAIF_I2SCTL_SPKMODE_SD3 (4 << LPAIF_I2SCTL_SPKMODE_SHIFT)
60#define LPAIF_I2SCTL_SPKMODE_QUAD01 (5 << LPAIF_I2SCTL_SPKMODE_SHIFT)
61#define LPAIF_I2SCTL_SPKMODE_QUAD23 (6 << LPAIF_I2SCTL_SPKMODE_SHIFT)
62#define LPAIF_I2SCTL_SPKMODE_6CH (7 << LPAIF_I2SCTL_SPKMODE_SHIFT)
63#define LPAIF_I2SCTL_SPKMODE_8CH (8 << LPAIF_I2SCTL_SPKMODE_SHIFT)
64
65#define LPAIF_I2SCTL_SPKMONO_MASK 0x0200
66#define LPAIF_I2SCTL_SPKMONO_SHIFT 9
67#define LPAIF_I2SCTL_SPKMONO_STEREO (0 << LPAIF_I2SCTL_SPKMONO_SHIFT)
68#define LPAIF_I2SCTL_SPKMONO_MONO (1 << LPAIF_I2SCTL_SPKMONO_SHIFT)
69
70#define LPAIF_I2SCTL_WSSRC_MASK 0x0004
71#define LPAIF_I2SCTL_WSSRC_SHIFT 2
72#define LPAIF_I2SCTL_WSSRC_INTERNAL (0 << LPAIF_I2SCTL_WSSRC_SHIFT)
73#define LPAIF_I2SCTL_WSSRC_EXTERNAL (1 << LPAIF_I2SCTL_WSSRC_SHIFT)
74
75#define LPAIF_I2SCTL_BITWIDTH_MASK 0x0003
76#define LPAIF_I2SCTL_BITWIDTH_SHIFT 0
77#define LPAIF_I2SCTL_BITWIDTH_16 (0 << LPAIF_I2SCTL_BITWIDTH_SHIFT)
78#define LPAIF_I2SCTL_BITWIDTH_24 (1 << LPAIF_I2SCTL_BITWIDTH_SHIFT)
79#define LPAIF_I2SCTL_BITWIDTH_32 (2 << LPAIF_I2SCTL_BITWIDTH_SHIFT)
80
81/* LPAIF IRQ */
82
83#define LPAIF_IRQ_REG_BASE 0x3000
84#define LPAIF_IRQ_REG_STRIDE 0x1000
85#define LPAIF_IRQ_REG_ADDR(addr, port) \
86 (LPAIF_IRQ_REG_BASE + (addr) + (LPAIF_IRQ_REG_STRIDE * (port)))
87
88enum lpaif_irq_ports {
89 LPAIF_IRQ_PORT_MIN = 0,
90
91 LPAIF_IRQ_PORT_HOST = 0,
92 LPAIF_IRQ_PORT_ADSP = 1,
93
94 LPAIF_IRQ_PORT_MAX = 2,
95 LPAIF_IRQ_PORT_NUM = 3,
96};
97
98#define LPAIF_IRQEN_REG(port) LPAIF_IRQ_REG_ADDR(0x0, (port))
99#define LPAIF_IRQSTAT_REG(port) LPAIF_IRQ_REG_ADDR(0x4, (port))
100#define LPAIF_IRQCLEAR_REG(port) LPAIF_IRQ_REG_ADDR(0xC, (port))
101
102#define LPAIF_IRQ_BITSTRIDE 3
103#define LPAIF_IRQ_PER(chan) (1 << (LPAIF_IRQ_BITSTRIDE * (chan)))
104#define LPAIF_IRQ_XRUN(chan) (2 << (LPAIF_IRQ_BITSTRIDE * (chan)))
105#define LPAIF_IRQ_ERR(chan) (4 << (LPAIF_IRQ_BITSTRIDE * (chan)))
106#define LPAIF_IRQ_ALL(chan) (7 << (LPAIF_IRQ_BITSTRIDE * (chan)))
107
108/* LPAIF DMA */
109
110#define LPAIF_RDMA_REG_BASE 0x6000
111#define LPAIF_RDMA_REG_STRIDE 0x1000
112#define LPAIF_RDMA_REG_ADDR(addr, chan) \
113 (LPAIF_RDMA_REG_BASE + (addr) + (LPAIF_RDMA_REG_STRIDE * (chan)))
114
115enum lpaif_dma_channels {
116 LPAIF_RDMA_CHAN_MIN = 0,
117
118 LPAIF_RDMA_CHAN_MI2S = 0,
119 LPAIF_RDMA_CHAN_PCM0 = 1,
120 LPAIF_RDMA_CHAN_PCM1 = 2,
121
122 LPAIF_RDMA_CHAN_MAX = 4,
123 LPAIF_RDMA_CHAN_NUM = 5,
124};
125
126#define LPAIF_RDMACTL_REG(chan) LPAIF_RDMA_REG_ADDR(0x00, (chan))
127#define LPAIF_RDMABASE_REG(chan) LPAIF_RDMA_REG_ADDR(0x04, (chan))
128#define LPAIF_RDMABUFF_REG(chan) LPAIF_RDMA_REG_ADDR(0x08, (chan))
129#define LPAIF_RDMACURR_REG(chan) LPAIF_RDMA_REG_ADDR(0x0C, (chan))
130#define LPAIF_RDMAPER_REG(chan) LPAIF_RDMA_REG_ADDR(0x10, (chan))
131
132#define LPAIF_RDMACTL_BURSTEN_MASK 0x800
133#define LPAIF_RDMACTL_BURSTEN_SHIFT 11
134#define LPAIF_RDMACTL_BURSTEN_SINGLE (0 << LPAIF_RDMACTL_BURSTEN_SHIFT)
135#define LPAIF_RDMACTL_BURSTEN_INCR4 (1 << LPAIF_RDMACTL_BURSTEN_SHIFT)
136
137#define LPAIF_RDMACTL_WPSCNT_MASK 0x700
138#define LPAIF_RDMACTL_WPSCNT_SHIFT 8
139#define LPAIF_RDMACTL_WPSCNT_ONE (0 << LPAIF_RDMACTL_WPSCNT_SHIFT)
140#define LPAIF_RDMACTL_WPSCNT_TWO (1 << LPAIF_RDMACTL_WPSCNT_SHIFT)
141#define LPAIF_RDMACTL_WPSCNT_THREE (2 << LPAIF_RDMACTL_WPSCNT_SHIFT)
142#define LPAIF_RDMACTL_WPSCNT_FOUR (3 << LPAIF_RDMACTL_WPSCNT_SHIFT)
143#define LPAIF_RDMACTL_WPSCNT_SIX (5 << LPAIF_RDMACTL_WPSCNT_SHIFT)
144#define LPAIF_RDMACTL_WPSCNT_EIGHT (7 << LPAIF_RDMACTL_WPSCNT_SHIFT)
145
146#define LPAIF_RDMACTL_AUDINTF_MASK 0x0F0
147#define LPAIF_RDMACTL_AUDINTF_SHIFT 4
148#define LPAIF_RDMACTL_AUDINTF_NONE (0 << LPAIF_RDMACTL_AUDINTF_SHIFT)
149#define LPAIF_RDMACTL_AUDINTF_CODEC (1 << LPAIF_RDMACTL_AUDINTF_SHIFT)
150#define LPAIF_RDMACTL_AUDINTF_PCM (2 << LPAIF_RDMACTL_AUDINTF_SHIFT)
151#define LPAIF_RDMACTL_AUDINTF_SEC_I2S (3 << LPAIF_RDMACTL_AUDINTF_SHIFT)
152#define LPAIF_RDMACTL_AUDINTF_MI2S (4 << LPAIF_RDMACTL_AUDINTF_SHIFT)
153#define LPAIF_RDMACTL_AUDINTF_HDMI (5 << LPAIF_RDMACTL_AUDINTF_SHIFT)
154#define LPAIF_RDMACTL_AUDINTF_SEC_PCM (7 << LPAIF_RDMACTL_AUDINTF_SHIFT)
155
156#define LPAIF_RDMACTL_FIFOWM_MASK 0x00E
157#define LPAIF_RDMACTL_FIFOWM_SHIFT 1
158#define LPAIF_RDMACTL_FIFOWM_1 (0 << LPAIF_RDMACTL_FIFOWM_SHIFT)
159#define LPAIF_RDMACTL_FIFOWM_2 (1 << LPAIF_RDMACTL_FIFOWM_SHIFT)
160#define LPAIF_RDMACTL_FIFOWM_3 (2 << LPAIF_RDMACTL_FIFOWM_SHIFT)
161#define LPAIF_RDMACTL_FIFOWM_4 (3 << LPAIF_RDMACTL_FIFOWM_SHIFT)
162#define LPAIF_RDMACTL_FIFOWM_5 (4 << LPAIF_RDMACTL_FIFOWM_SHIFT)
163#define LPAIF_RDMACTL_FIFOWM_6 (5 << LPAIF_RDMACTL_FIFOWM_SHIFT)
164#define LPAIF_RDMACTL_FIFOWM_7 (6 << LPAIF_RDMACTL_FIFOWM_SHIFT)
165#define LPAIF_RDMACTL_FIFOWM_8 (7 << LPAIF_RDMACTL_FIFOWM_SHIFT)
166
167#define LPAIF_RDMACTL_ENABLE_MASK 0x1
168#define LPAIF_RDMACTL_ENABLE_SHIFT 0
169#define LPAIF_RDMACTL_ENABLE_OFF (0 << LPAIF_RDMACTL_ENABLE_SHIFT)
170#define LPAIF_RDMACTL_ENABLE_ON (1 << LPAIF_RDMACTL_ENABLE_SHIFT)
171
172#endif /* __LPASS_LPAIF_H__ */
diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c
new file mode 100644
index 000000000000..2fa6280dfb23
--- /dev/null
+++ b/sound/soc/qcom/lpass-platform.c
@@ -0,0 +1,526 @@
1/*
2 * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * lpass-platform.c -- ALSA SoC platform driver for QTi LPASS
14 */
15
16#include <linux/compiler.h>
17#include <linux/device.h>
18#include <linux/dma-mapping.h>
19#include <linux/err.h>
20#include <linux/export.h>
21#include <linux/kernel.h>
22#include <linux/module.h>
23#include <linux/io.h>
24#include <linux/platform_device.h>
25#include <sound/memalloc.h>
26#include <sound/pcm.h>
27#include <sound/pcm_params.h>
28#include <linux/regmap.h>
29#include <sound/soc.h>
30#include "lpass-lpaif-ipq806x.h"
31#include "lpass.h"
32
33#define LPASS_PLATFORM_BUFFER_SIZE (16 * 1024)
34#define LPASS_PLATFORM_PERIODS 2
35
36static struct snd_pcm_hardware lpass_platform_pcm_hardware = {
37 .info = SNDRV_PCM_INFO_MMAP |
38 SNDRV_PCM_INFO_MMAP_VALID |
39 SNDRV_PCM_INFO_INTERLEAVED |
40 SNDRV_PCM_INFO_PAUSE |
41 SNDRV_PCM_INFO_RESUME,
42 .formats = SNDRV_PCM_FMTBIT_S16 |
43 SNDRV_PCM_FMTBIT_S24 |
44 SNDRV_PCM_FMTBIT_S32,
45 .rates = SNDRV_PCM_RATE_8000_192000,
46 .rate_min = 8000,
47 .rate_max = 192000,
48 .channels_min = 1,
49 .channels_max = 8,
50 .buffer_bytes_max = LPASS_PLATFORM_BUFFER_SIZE,
51 .period_bytes_max = LPASS_PLATFORM_BUFFER_SIZE /
52 LPASS_PLATFORM_PERIODS,
53 .period_bytes_min = LPASS_PLATFORM_BUFFER_SIZE /
54 LPASS_PLATFORM_PERIODS,
55 .periods_min = LPASS_PLATFORM_PERIODS,
56 .periods_max = LPASS_PLATFORM_PERIODS,
57 .fifo_size = 0,
58};
59
60static int lpass_platform_pcmops_open(struct snd_pcm_substream *substream)
61{
62 struct snd_pcm_runtime *runtime = substream->runtime;
63 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
64 int ret;
65
66 snd_soc_set_runtime_hwparams(substream, &lpass_platform_pcm_hardware);
67
68 runtime->dma_bytes = lpass_platform_pcm_hardware.buffer_bytes_max;
69
70 ret = snd_pcm_hw_constraint_integer(runtime,
71 SNDRV_PCM_HW_PARAM_PERIODS);
72 if (ret < 0) {
73 dev_err(soc_runtime->dev, "%s() setting constraints failed: %d\n",
74 __func__, ret);
75 return -EINVAL;
76 }
77
78 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
79
80 return 0;
81}
82
83static int lpass_platform_pcmops_hw_params(struct snd_pcm_substream *substream,
84 struct snd_pcm_hw_params *params)
85{
86 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
87 struct lpass_data *drvdata =
88 snd_soc_platform_get_drvdata(soc_runtime->platform);
89 snd_pcm_format_t format = params_format(params);
90 unsigned int channels = params_channels(params);
91 unsigned int regval;
92 int bitwidth;
93 int ret;
94
95 bitwidth = snd_pcm_format_width(format);
96 if (bitwidth < 0) {
97 dev_err(soc_runtime->dev, "%s() invalid bit width given: %d\n",
98 __func__, bitwidth);
99 return bitwidth;
100 }
101
102 regval = LPAIF_RDMACTL_BURSTEN_INCR4 |
103 LPAIF_RDMACTL_AUDINTF_MI2S |
104 LPAIF_RDMACTL_FIFOWM_8;
105
106 switch (bitwidth) {
107 case 16:
108 switch (channels) {
109 case 1:
110 case 2:
111 regval |= LPAIF_RDMACTL_WPSCNT_ONE;
112 break;
113 case 4:
114 regval |= LPAIF_RDMACTL_WPSCNT_TWO;
115 break;
116 case 6:
117 regval |= LPAIF_RDMACTL_WPSCNT_THREE;
118 break;
119 case 8:
120 regval |= LPAIF_RDMACTL_WPSCNT_FOUR;
121 break;
122 default:
123 dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%d, ch=%u\n",
124 __func__, bitwidth, channels);
125 return -EINVAL;
126 }
127 break;
128 case 24:
129 case 32:
130 switch (channels) {
131 case 1:
132 regval |= LPAIF_RDMACTL_WPSCNT_ONE;
133 break;
134 case 2:
135 regval |= LPAIF_RDMACTL_WPSCNT_TWO;
136 break;
137 case 4:
138 regval |= LPAIF_RDMACTL_WPSCNT_FOUR;
139 break;
140 case 6:
141 regval |= LPAIF_RDMACTL_WPSCNT_SIX;
142 break;
143 case 8:
144 regval |= LPAIF_RDMACTL_WPSCNT_EIGHT;
145 break;
146 default:
147 dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%d, ch=%u\n",
148 __func__, bitwidth, channels);
149 return -EINVAL;
150 }
151 break;
152 default:
153 dev_err(soc_runtime->dev, "%s() invalid PCM config given: bw=%d, ch=%u\n",
154 __func__, bitwidth, channels);
155 return -EINVAL;
156 }
157
158 ret = regmap_write(drvdata->lpaif_map,
159 LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S), regval);
160 if (ret) {
161 dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
162 __func__, ret);
163 return ret;
164 }
165
166 return 0;
167}
168
169static int lpass_platform_pcmops_hw_free(struct snd_pcm_substream *substream)
170{
171 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
172 struct lpass_data *drvdata =
173 snd_soc_platform_get_drvdata(soc_runtime->platform);
174 int ret;
175
176 ret = regmap_write(drvdata->lpaif_map,
177 LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S), 0);
178 if (ret)
179 dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
180 __func__, ret);
181
182 return ret;
183}
184
185static int lpass_platform_pcmops_prepare(struct snd_pcm_substream *substream)
186{
187 struct snd_pcm_runtime *runtime = substream->runtime;
188 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
189 struct lpass_data *drvdata =
190 snd_soc_platform_get_drvdata(soc_runtime->platform);
191 int ret;
192
193 ret = regmap_write(drvdata->lpaif_map,
194 LPAIF_RDMABASE_REG(LPAIF_RDMA_CHAN_MI2S),
195 runtime->dma_addr);
196 if (ret) {
197 dev_err(soc_runtime->dev, "%s() error writing to rdmabase reg: %d\n",
198 __func__, ret);
199 return ret;
200 }
201
202 ret = regmap_write(drvdata->lpaif_map,
203 LPAIF_RDMABUFF_REG(LPAIF_RDMA_CHAN_MI2S),
204 (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1);
205 if (ret) {
206 dev_err(soc_runtime->dev, "%s() error writing to rdmabuff reg: %d\n",
207 __func__, ret);
208 return ret;
209 }
210
211 ret = regmap_write(drvdata->lpaif_map,
212 LPAIF_RDMAPER_REG(LPAIF_RDMA_CHAN_MI2S),
213 (snd_pcm_lib_period_bytes(substream) >> 2) - 1);
214 if (ret) {
215 dev_err(soc_runtime->dev, "%s() error writing to rdmaper reg: %d\n",
216 __func__, ret);
217 return ret;
218 }
219
220 ret = regmap_update_bits(drvdata->lpaif_map,
221 LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S),
222 LPAIF_RDMACTL_ENABLE_MASK, LPAIF_RDMACTL_ENABLE_ON);
223 if (ret) {
224 dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
225 __func__, ret);
226 return ret;
227 }
228
229 return 0;
230}
231
232static int lpass_platform_pcmops_trigger(struct snd_pcm_substream *substream,
233 int cmd)
234{
235 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
236 struct lpass_data *drvdata =
237 snd_soc_platform_get_drvdata(soc_runtime->platform);
238 int ret;
239
240 switch (cmd) {
241 case SNDRV_PCM_TRIGGER_START:
242 case SNDRV_PCM_TRIGGER_RESUME:
243 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
244 /* clear status before enabling interrupts */
245 ret = regmap_write(drvdata->lpaif_map,
246 LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST),
247 LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S));
248 if (ret) {
249 dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n",
250 __func__, ret);
251 return ret;
252 }
253
254 ret = regmap_update_bits(drvdata->lpaif_map,
255 LPAIF_IRQEN_REG(LPAIF_IRQ_PORT_HOST),
256 LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S),
257 LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S));
258 if (ret) {
259 dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n",
260 __func__, ret);
261 return ret;
262 }
263
264 ret = regmap_update_bits(drvdata->lpaif_map,
265 LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S),
266 LPAIF_RDMACTL_ENABLE_MASK,
267 LPAIF_RDMACTL_ENABLE_ON);
268 if (ret) {
269 dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
270 __func__, ret);
271 return ret;
272 }
273 break;
274 case SNDRV_PCM_TRIGGER_STOP:
275 case SNDRV_PCM_TRIGGER_SUSPEND:
276 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
277 ret = regmap_update_bits(drvdata->lpaif_map,
278 LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S),
279 LPAIF_RDMACTL_ENABLE_MASK,
280 LPAIF_RDMACTL_ENABLE_OFF);
281 if (ret) {
282 dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
283 __func__, ret);
284 return ret;
285 }
286
287 ret = regmap_update_bits(drvdata->lpaif_map,
288 LPAIF_IRQEN_REG(LPAIF_IRQ_PORT_HOST),
289 LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S), 0);
290 if (ret) {
291 dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n",
292 __func__, ret);
293 return ret;
294 }
295 break;
296 }
297
298 return 0;
299}
300
301static snd_pcm_uframes_t lpass_platform_pcmops_pointer(
302 struct snd_pcm_substream *substream)
303{
304 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
305 struct lpass_data *drvdata =
306 snd_soc_platform_get_drvdata(soc_runtime->platform);
307 unsigned int base_addr, curr_addr;
308 int ret;
309
310 ret = regmap_read(drvdata->lpaif_map,
311 LPAIF_RDMABASE_REG(LPAIF_RDMA_CHAN_MI2S), &base_addr);
312 if (ret) {
313 dev_err(soc_runtime->dev, "%s() error reading from rdmabase reg: %d\n",
314 __func__, ret);
315 return ret;
316 }
317
318 ret = regmap_read(drvdata->lpaif_map,
319 LPAIF_RDMACURR_REG(LPAIF_RDMA_CHAN_MI2S), &curr_addr);
320 if (ret) {
321 dev_err(soc_runtime->dev, "%s() error reading from rdmacurr reg: %d\n",
322 __func__, ret);
323 return ret;
324 }
325
326 return bytes_to_frames(substream->runtime, curr_addr - base_addr);
327}
328
329static int lpass_platform_pcmops_mmap(struct snd_pcm_substream *substream,
330 struct vm_area_struct *vma)
331{
332 struct snd_pcm_runtime *runtime = substream->runtime;
333
334 return dma_mmap_coherent(substream->pcm->card->dev, vma,
335 runtime->dma_area, runtime->dma_addr,
336 runtime->dma_bytes);
337}
338
339static struct snd_pcm_ops lpass_platform_pcm_ops = {
340 .open = lpass_platform_pcmops_open,
341 .ioctl = snd_pcm_lib_ioctl,
342 .hw_params = lpass_platform_pcmops_hw_params,
343 .hw_free = lpass_platform_pcmops_hw_free,
344 .prepare = lpass_platform_pcmops_prepare,
345 .trigger = lpass_platform_pcmops_trigger,
346 .pointer = lpass_platform_pcmops_pointer,
347 .mmap = lpass_platform_pcmops_mmap,
348};
349
350static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data)
351{
352 struct snd_pcm_substream *substream = data;
353 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
354 struct lpass_data *drvdata =
355 snd_soc_platform_get_drvdata(soc_runtime->platform);
356 unsigned int interrupts;
357 irqreturn_t ret = IRQ_NONE;
358 int rv;
359
360 rv = regmap_read(drvdata->lpaif_map,
361 LPAIF_IRQSTAT_REG(LPAIF_IRQ_PORT_HOST), &interrupts);
362 if (rv) {
363 dev_err(soc_runtime->dev, "%s() error reading from irqstat reg: %d\n",
364 __func__, rv);
365 return IRQ_NONE;
366 }
367 interrupts &= LPAIF_IRQ_ALL(LPAIF_RDMA_CHAN_MI2S);
368
369 if (interrupts & LPAIF_IRQ_PER(LPAIF_RDMA_CHAN_MI2S)) {
370 rv = regmap_write(drvdata->lpaif_map,
371 LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST),
372 LPAIF_IRQ_PER(LPAIF_RDMA_CHAN_MI2S));
373 if (rv) {
374 dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n",
375 __func__, rv);
376 return IRQ_NONE;
377 }
378 snd_pcm_period_elapsed(substream);
379 ret = IRQ_HANDLED;
380 }
381
382 if (interrupts & LPAIF_IRQ_XRUN(LPAIF_RDMA_CHAN_MI2S)) {
383 rv = regmap_write(drvdata->lpaif_map,
384 LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST),
385 LPAIF_IRQ_XRUN(LPAIF_RDMA_CHAN_MI2S));
386 if (rv) {
387 dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n",
388 __func__, rv);
389 return IRQ_NONE;
390 }
391 dev_warn(soc_runtime->dev, "%s() xrun warning\n", __func__);
392 snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
393 ret = IRQ_HANDLED;
394 }
395
396 if (interrupts & LPAIF_IRQ_ERR(LPAIF_RDMA_CHAN_MI2S)) {
397 rv = regmap_write(drvdata->lpaif_map,
398 LPAIF_IRQCLEAR_REG(LPAIF_IRQ_PORT_HOST),
399 LPAIF_IRQ_ERR(LPAIF_RDMA_CHAN_MI2S));
400 if (rv) {
401 dev_err(soc_runtime->dev, "%s() error writing to irqclear reg: %d\n",
402 __func__, rv);
403 return IRQ_NONE;
404 }
405 dev_err(soc_runtime->dev, "%s() bus access error\n", __func__);
406 snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
407 ret = IRQ_HANDLED;
408 }
409
410 return ret;
411}
412
413static int lpass_platform_alloc_buffer(struct snd_pcm_substream *substream,
414 struct snd_soc_pcm_runtime *soc_runtime)
415{
416 struct snd_dma_buffer *buf = &substream->dma_buffer;
417 size_t size = lpass_platform_pcm_hardware.buffer_bytes_max;
418
419 buf->dev.type = SNDRV_DMA_TYPE_DEV;
420 buf->dev.dev = soc_runtime->dev;
421 buf->private_data = NULL;
422 buf->area = dma_alloc_coherent(soc_runtime->dev, size, &buf->addr,
423 GFP_KERNEL);
424 if (!buf->area) {
425 dev_err(soc_runtime->dev, "%s: Could not allocate DMA buffer\n",
426 __func__);
427 return -ENOMEM;
428 }
429 buf->bytes = size;
430
431 return 0;
432}
433
434static void lpass_platform_free_buffer(struct snd_pcm_substream *substream,
435 struct snd_soc_pcm_runtime *soc_runtime)
436{
437 struct snd_dma_buffer *buf = &substream->dma_buffer;
438
439 if (buf->area) {
440 dma_free_coherent(soc_runtime->dev, buf->bytes, buf->area,
441 buf->addr);
442 }
443 buf->area = NULL;
444}
445
446static int lpass_platform_pcm_new(struct snd_soc_pcm_runtime *soc_runtime)
447{
448 struct snd_pcm *pcm = soc_runtime->pcm;
449 struct snd_pcm_substream *substream =
450 pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
451 struct lpass_data *drvdata =
452 snd_soc_platform_get_drvdata(soc_runtime->platform);
453 int ret;
454
455 soc_runtime->dev->coherent_dma_mask = DMA_BIT_MASK(32);
456 soc_runtime->dev->dma_mask = &soc_runtime->dev->coherent_dma_mask;
457
458 ret = lpass_platform_alloc_buffer(substream, soc_runtime);
459 if (ret)
460 return ret;
461
462 ret = devm_request_irq(soc_runtime->dev, drvdata->lpaif_irq,
463 lpass_platform_lpaif_irq, IRQF_TRIGGER_RISING,
464 "lpass-irq-lpaif", substream);
465 if (ret) {
466 dev_err(soc_runtime->dev, "%s() irq request failed: %d\n",
467 __func__, ret);
468 goto err_buf;
469 }
470
471 /* ensure audio hardware is disabled */
472 ret = regmap_write(drvdata->lpaif_map,
473 LPAIF_IRQEN_REG(LPAIF_IRQ_PORT_HOST), 0);
474 if (ret) {
475 dev_err(soc_runtime->dev, "%s() error writing to irqen reg: %d\n",
476 __func__, ret);
477 return ret;
478 }
479 ret = regmap_write(drvdata->lpaif_map,
480 LPAIF_RDMACTL_REG(LPAIF_RDMA_CHAN_MI2S), 0);
481 if (ret) {
482 dev_err(soc_runtime->dev, "%s() error writing to rdmactl reg: %d\n",
483 __func__, ret);
484 return ret;
485 }
486
487 return 0;
488
489err_buf:
490 lpass_platform_free_buffer(substream, soc_runtime);
491 return ret;
492}
493
494static void lpass_platform_pcm_free(struct snd_pcm *pcm)
495{
496 struct snd_pcm_substream *substream =
497 pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
498 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
499
500 lpass_platform_free_buffer(substream, soc_runtime);
501}
502
503static struct snd_soc_platform_driver lpass_platform_driver = {
504 .pcm_new = lpass_platform_pcm_new,
505 .pcm_free = lpass_platform_pcm_free,
506 .ops = &lpass_platform_pcm_ops,
507};
508
509int asoc_qcom_lpass_platform_register(struct platform_device *pdev)
510{
511 struct lpass_data *drvdata = platform_get_drvdata(pdev);
512
513 drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif");
514 if (drvdata->lpaif_irq < 0) {
515 dev_err(&pdev->dev, "%s() error getting irq handle: %d\n",
516 __func__, drvdata->lpaif_irq);
517 return -ENODEV;
518 }
519
520 return devm_snd_soc_register_platform(&pdev->dev,
521 &lpass_platform_driver);
522}
523EXPORT_SYMBOL_GPL(asoc_qcom_lpass_platform_register);
524
525MODULE_DESCRIPTION("QTi LPASS Platform Driver");
526MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h
new file mode 100644
index 000000000000..5c99b3dace86
--- /dev/null
+++ b/sound/soc/qcom/lpass.h
@@ -0,0 +1,51 @@
1/*
2 * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * lpass.h - Definitions for the QTi LPASS
14 */
15
16#ifndef __LPASS_H__
17#define __LPASS_H__
18
19#include <linux/clk.h>
20#include <linux/compiler.h>
21#include <linux/platform_device.h>
22#include <linux/regmap.h>
23
24#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000
25
26/* Both the CPU DAI and platform drivers will access this data */
27struct lpass_data {
28
29 /* AHB-I/X bus clocks inside the low-power audio subsystem (LPASS) */
30 struct clk *ahbix_clk;
31
32 /* MI2S system clock */
33 struct clk *mi2s_osr_clk;
34
35 /* MI2S bit clock (derived from system clock by a divider */
36 struct clk *mi2s_bit_clk;
37
38 /* low-power audio interface (LPAIF) registers */
39 void __iomem *lpaif;
40
41 /* regmap backed by the low-power audio interface (LPAIF) registers */
42 struct regmap *lpaif_map;
43
44 /* interrupts from the low-power audio interface (LPAIF) */
45 int lpaif_irq;
46};
47
48/* register the platform driver from the CPU DAI driver */
49int asoc_qcom_lpass_platform_register(struct platform_device *);
50
51#endif /* __LPASS_H__ */
diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c
new file mode 100644
index 000000000000..b8bd296190ad
--- /dev/null
+++ b/sound/soc/qcom/storm.c
@@ -0,0 +1,162 @@
1/*
2 * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 and
6 * only version 2 as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * storm.c -- ALSA SoC machine driver for QTi ipq806x-based Storm board
14 */
15
16#include <linux/device.h>
17#include <linux/module.h>
18#include <linux/of.h>
19#include <linux/mod_devicetable.h>
20#include <linux/platform_device.h>
21#include <sound/pcm.h>
22#include <sound/pcm_params.h>
23#include <sound/soc.h>
24
25#define STORM_SYSCLK_MULT 4
26
27static int storm_ops_hw_params(struct snd_pcm_substream *substream,
28 struct snd_pcm_hw_params *params)
29{
30 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
31 struct snd_soc_card *card = soc_runtime->card;
32 snd_pcm_format_t format = params_format(params);
33 unsigned int rate = params_rate(params);
34 unsigned int sysclk_freq;
35 int bitwidth, ret;
36
37 bitwidth = snd_pcm_format_width(format);
38 if (bitwidth < 0) {
39 dev_err(card->dev, "%s() invalid bit width given: %d\n",
40 __func__, bitwidth);
41 return bitwidth;
42 }
43
44 /*
45 * as the CPU DAI is the I2S bus master and no system clock is needed by
46 * the MAX98357a DAC, simply set the system clock to be a constant
47 * multiple of the bit clock for the clock divider
48 */
49 sysclk_freq = rate * bitwidth * 2 * STORM_SYSCLK_MULT;
50
51 ret = snd_soc_dai_set_sysclk(soc_runtime->cpu_dai, 0, sysclk_freq, 0);
52 if (ret) {
53 dev_err(card->dev, "%s() error setting sysclk to %u: %d\n",
54 __func__, sysclk_freq, ret);
55 return ret;
56 }
57
58 return 0;
59}
60
61static struct snd_soc_ops storm_soc_ops = {
62 .hw_params = storm_ops_hw_params,
63};
64
65static struct snd_soc_dai_link storm_dai_link = {
66 .name = "Primary",
67 .stream_name = "Primary",
68 .codec_dai_name = "HiFi",
69 .ops = &storm_soc_ops,
70};
71
72static struct snd_soc_card storm_soc_card = {
73 .name = "ipq806x-storm",
74 .dev = NULL,
75};
76
77static int storm_parse_of(struct snd_soc_card *card)
78{
79 struct snd_soc_dai_link *dai_link = card->dai_link;
80 struct device_node *np = card->dev->of_node;
81
82 dai_link->cpu_of_node = of_parse_phandle(np, "cpu", 0);
83 if (!dai_link->cpu_of_node) {
84 dev_err(card->dev, "%s() error getting cpu phandle\n",
85 __func__);
86 return -EINVAL;
87 }
88 dai_link->platform_of_node = dai_link->cpu_of_node;
89
90 dai_link->codec_of_node = of_parse_phandle(np, "codec", 0);
91 if (!dai_link->codec_of_node) {
92 dev_err(card->dev, "%s() error getting codec phandle\n",
93 __func__);
94 return -EINVAL;
95 }
96
97 return 0;
98}
99
100static int storm_platform_probe(struct platform_device *pdev)
101{
102 struct snd_soc_card *card = &storm_soc_card;
103 int ret;
104
105 if (card->dev) {
106 dev_err(&pdev->dev, "%s() error, existing soundcard\n",
107 __func__);
108 return -ENODEV;
109 }
110 card->dev = &pdev->dev;
111 platform_set_drvdata(pdev, card);
112
113 ret = snd_soc_of_parse_card_name(card, "qcom,model");
114 if (ret) {
115 dev_err(&pdev->dev, "%s() error parsing card name: %d\n",
116 __func__, ret);
117 return ret;
118 }
119
120 card->dai_link = &storm_dai_link;
121 card->num_links = 1;
122
123 ret = storm_parse_of(card);
124 if (ret) {
125 dev_err(&pdev->dev, "%s() error resolving dai links: %d\n",
126 __func__, ret);
127 return ret;
128 }
129
130 ret = devm_snd_soc_register_card(&pdev->dev, card);
131 if (ret == -EPROBE_DEFER) {
132 card->dev = NULL;
133 return ret;
134 } else if (ret) {
135 dev_err(&pdev->dev, "%s() error registering soundcard: %d\n",
136 __func__, ret);
137 return ret;
138 }
139
140 return 0;
141}
142
143#ifdef CONFIG_OF
144static const struct of_device_id storm_device_id[] = {
145 { .compatible = "google,storm-audio" },
146 {},
147};
148MODULE_DEVICE_TABLE(of, storm_device_id);
149#endif
150
151static struct platform_driver storm_platform_driver = {
152 .driver = {
153 .name = "storm-audio",
154 .of_match_table =
155 of_match_ptr(storm_device_id),
156 },
157 .probe = storm_platform_probe,
158};
159module_platform_driver(storm_platform_driver);
160
161MODULE_DESCRIPTION("QTi IPQ806x-based Storm Machine Driver");
162MODULE_LICENSE("GPL v2");