1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved.
4 *
5 * lpass-apq8016.c -- ALSA SoC CPU DAI driver for APQ8016 LPASS
6 */
7
8
9#include <linux/clk.h>
10#include <linux/device.h>
11#include <linux/err.h>
12#include <linux/kernel.h>
13#include <linux/module.h>
14#include <linux/of.h>
15#include <linux/platform_device.h>
16#include <sound/pcm.h>
17#include <sound/pcm_params.h>
18#include <sound/soc.h>
19#include <sound/soc-dai.h>
20
21#include <dt-bindings/sound/apq8016-lpass.h>
22#include "lpass-lpaif-reg.h"
23#include "lpass.h"
24
25static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = {
26 [MI2S_PRIMARY] = {
27 .id = MI2S_PRIMARY,
28 .name = "Primary MI2S",
29 .playback = {
30 .stream_name = "Primary Playback",
31 .formats = SNDRV_PCM_FMTBIT_S16 |
32 SNDRV_PCM_FMTBIT_S24 |
33 SNDRV_PCM_FMTBIT_S32,
34 .rates = SNDRV_PCM_RATE_8000 |
35 SNDRV_PCM_RATE_16000 |
36 SNDRV_PCM_RATE_32000 |
37 SNDRV_PCM_RATE_48000 |
38 SNDRV_PCM_RATE_96000,
39 .rate_min = 8000,
40 .rate_max = 96000,
41 .channels_min = 1,
42 .channels_max = 8,
43 },
44 .ops = &asoc_qcom_lpass_cpu_dai_ops,
45 },
46 [MI2S_SECONDARY] = {
47 .id = MI2S_SECONDARY,
48 .name = "Secondary MI2S",
49 .playback = {
50 .stream_name = "Secondary Playback",
51 .formats = SNDRV_PCM_FMTBIT_S16 |
52 SNDRV_PCM_FMTBIT_S24 |
53 SNDRV_PCM_FMTBIT_S32,
54 .rates = SNDRV_PCM_RATE_8000 |
55 SNDRV_PCM_RATE_16000 |
56 SNDRV_PCM_RATE_32000 |
57 SNDRV_PCM_RATE_48000 |
58 SNDRV_PCM_RATE_96000,
59 .rate_min = 8000,
60 .rate_max = 96000,
61 .channels_min = 1,
62 .channels_max = 8,
63 },
64 .ops = &asoc_qcom_lpass_cpu_dai_ops,
65 },
66 [MI2S_TERTIARY] = {
67 .id = MI2S_TERTIARY,
68 .name = "Tertiary MI2S",
69 .capture = {
70 .stream_name = "Tertiary Capture",
71 .formats = SNDRV_PCM_FMTBIT_S16 |
72 SNDRV_PCM_FMTBIT_S24 |
73 SNDRV_PCM_FMTBIT_S32,
74 .rates = SNDRV_PCM_RATE_8000 |
75 SNDRV_PCM_RATE_16000 |
76 SNDRV_PCM_RATE_32000 |
77 SNDRV_PCM_RATE_48000 |
78 SNDRV_PCM_RATE_96000,
79 .rate_min = 8000,
80 .rate_max = 96000,
81 .channels_min = 1,
82 .channels_max = 8,
83 },
84 .ops = &asoc_qcom_lpass_cpu_dai_ops,
85 },
86 [MI2S_QUATERNARY] = {
87 .id = MI2S_QUATERNARY,
88 .name = "Quatenary MI2S",
89 .playback = {
90 .stream_name = "Quatenary Playback",
91 .formats = SNDRV_PCM_FMTBIT_S16 |
92 SNDRV_PCM_FMTBIT_S24 |
93 SNDRV_PCM_FMTBIT_S32,
94 .rates = SNDRV_PCM_RATE_8000 |
95 SNDRV_PCM_RATE_16000 |
96 SNDRV_PCM_RATE_32000 |
97 SNDRV_PCM_RATE_48000 |
98 SNDRV_PCM_RATE_96000,
99 .rate_min = 8000,
100 .rate_max = 96000,
101 .channels_min = 1,
102 .channels_max = 8,
103 },
104 .capture = {
105 .stream_name = "Quatenary Capture",
106 .formats = SNDRV_PCM_FMTBIT_S16 |
107 SNDRV_PCM_FMTBIT_S24 |
108 SNDRV_PCM_FMTBIT_S32,
109 .rates = SNDRV_PCM_RATE_8000 |
110 SNDRV_PCM_RATE_16000 |
111 SNDRV_PCM_RATE_32000 |
112 SNDRV_PCM_RATE_48000 |
113 SNDRV_PCM_RATE_96000,
114 .rate_min = 8000,
115 .rate_max = 96000,
116 .channels_min = 1,
117 .channels_max = 8,
118 },
119 .ops = &asoc_qcom_lpass_cpu_dai_ops,
120 },
121};
122
123static int apq8016_lpass_alloc_dma_channel(struct lpass_data *drvdata,
124 int direction, unsigned int dai_id)
125{
126 const struct lpass_variant *v = drvdata->variant;
127 int chan = 0;
128
129 if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
130 chan = find_first_zero_bit(addr: &drvdata->dma_ch_bit_map,
131 size: v->rdma_channels);
132
133 if (chan >= v->rdma_channels)
134 return -EBUSY;
135 } else {
136 chan = find_next_zero_bit(addr: &drvdata->dma_ch_bit_map,
137 size: v->wrdma_channel_start +
138 v->wrdma_channels,
139 offset: v->wrdma_channel_start);
140
141 if (chan >= v->wrdma_channel_start + v->wrdma_channels)
142 return -EBUSY;
143 }
144
145 set_bit(nr: chan, addr: &drvdata->dma_ch_bit_map);
146
147 return chan;
148}
149
150static int apq8016_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id)
151{
152 clear_bit(nr: chan, addr: &drvdata->dma_ch_bit_map);
153
154 return 0;
155}
156
157static int apq8016_lpass_init(struct platform_device *pdev)
158{
159 struct lpass_data *drvdata = platform_get_drvdata(pdev);
160 const struct lpass_variant *variant = drvdata->variant;
161 struct device *dev = &pdev->dev;
162 int ret, i;
163
164
165 drvdata->clks = devm_kcalloc(dev, n: variant->num_clks,
166 size: sizeof(*drvdata->clks), GFP_KERNEL);
167 if (!drvdata->clks)
168 return -ENOMEM;
169 drvdata->num_clks = variant->num_clks;
170
171 for (i = 0; i < drvdata->num_clks; i++)
172 drvdata->clks[i].id = variant->clk_name[i];
173
174 ret = devm_clk_bulk_get(dev, num_clks: drvdata->num_clks, clks: drvdata->clks);
175 if (ret) {
176 dev_err(dev, "Failed to get clocks %d\n", ret);
177 return ret;
178 }
179
180 ret = clk_bulk_prepare_enable(num_clks: drvdata->num_clks, clks: drvdata->clks);
181 if (ret) {
182 dev_err(dev, "apq8016 clk_enable failed\n");
183 return ret;
184 }
185
186 drvdata->ahbix_clk = devm_clk_get(dev, id: "ahbix-clk");
187 if (IS_ERR(ptr: drvdata->ahbix_clk)) {
188 dev_err(dev, "error getting ahbix-clk: %ld\n",
189 PTR_ERR(drvdata->ahbix_clk));
190 ret = PTR_ERR(ptr: drvdata->ahbix_clk);
191 goto err_ahbix_clk;
192 }
193
194 ret = clk_set_rate(clk: drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY);
195 if (ret) {
196 dev_err(dev, "error setting rate on ahbix_clk: %d\n", ret);
197 goto err_ahbix_clk;
198 }
199 dev_dbg(dev, "set ahbix_clk rate to %lu\n",
200 clk_get_rate(drvdata->ahbix_clk));
201
202 ret = clk_prepare_enable(clk: drvdata->ahbix_clk);
203 if (ret) {
204 dev_err(dev, "error enabling ahbix_clk: %d\n", ret);
205 goto err_ahbix_clk;
206 }
207
208 return 0;
209
210err_ahbix_clk:
211 clk_bulk_disable_unprepare(num_clks: drvdata->num_clks, clks: drvdata->clks);
212 return ret;
213}
214
215static int apq8016_lpass_exit(struct platform_device *pdev)
216{
217 struct lpass_data *drvdata = platform_get_drvdata(pdev);
218
219 clk_bulk_disable_unprepare(num_clks: drvdata->num_clks, clks: drvdata->clks);
220 clk_disable_unprepare(clk: drvdata->ahbix_clk);
221
222 return 0;
223}
224
225
226static const struct lpass_variant apq8016_data = {
227 .i2sctrl_reg_base = 0x1000,
228 .i2sctrl_reg_stride = 0x1000,
229 .i2s_ports = 4,
230 .irq_reg_base = 0x6000,
231 .irq_reg_stride = 0x1000,
232 .irq_ports = 3,
233 .rdma_reg_base = 0x8400,
234 .rdma_reg_stride = 0x1000,
235 .rdma_channels = 2,
236 .dmactl_audif_start = 1,
237 .wrdma_reg_base = 0xB000,
238 .wrdma_reg_stride = 0x1000,
239 .wrdma_channel_start = 5,
240 .wrdma_channels = 2,
241 .loopback = REG_FIELD_ID(0x1000, 15, 15, 4, 0x1000),
242 .spken = REG_FIELD_ID(0x1000, 14, 14, 4, 0x1000),
243 .spkmode = REG_FIELD_ID(0x1000, 10, 13, 4, 0x1000),
244 .spkmono = REG_FIELD_ID(0x1000, 9, 9, 4, 0x1000),
245 .micen = REG_FIELD_ID(0x1000, 8, 8, 4, 0x1000),
246 .micmode = REG_FIELD_ID(0x1000, 4, 7, 4, 0x1000),
247 .micmono = REG_FIELD_ID(0x1000, 3, 3, 4, 0x1000),
248 .wssrc = REG_FIELD_ID(0x1000, 2, 2, 4, 0x1000),
249 .bitwidth = REG_FIELD_ID(0x1000, 0, 1, 4, 0x1000),
250
251 .rdma_dyncclk = REG_FIELD_ID(0x8400, 12, 12, 2, 0x1000),
252 .rdma_bursten = REG_FIELD_ID(0x8400, 11, 11, 2, 0x1000),
253 .rdma_wpscnt = REG_FIELD_ID(0x8400, 8, 10, 2, 0x1000),
254 .rdma_intf = REG_FIELD_ID(0x8400, 4, 7, 2, 0x1000),
255 .rdma_fifowm = REG_FIELD_ID(0x8400, 1, 3, 2, 0x1000),
256 .rdma_enable = REG_FIELD_ID(0x8400, 0, 0, 2, 0x1000),
257
258 .wrdma_dyncclk = REG_FIELD_ID(0xB000, 12, 12, 2, 0x1000),
259 .wrdma_bursten = REG_FIELD_ID(0xB000, 11, 11, 2, 0x1000),
260 .wrdma_wpscnt = REG_FIELD_ID(0xB000, 8, 10, 2, 0x1000),
261 .wrdma_intf = REG_FIELD_ID(0xB000, 4, 7, 2, 0x1000),
262 .wrdma_fifowm = REG_FIELD_ID(0xB000, 1, 3, 2, 0x1000),
263 .wrdma_enable = REG_FIELD_ID(0xB000, 0, 0, 2, 0x1000),
264
265 .clk_name = (const char*[]) {
266 "pcnoc-mport-clk",
267 "pcnoc-sway-clk",
268 },
269 .num_clks = 2,
270 .dai_driver = apq8016_lpass_cpu_dai_driver,
271 .num_dai = ARRAY_SIZE(apq8016_lpass_cpu_dai_driver),
272 .dai_osr_clk_names = (const char *[]) {
273 "mi2s-osr-clk0",
274 "mi2s-osr-clk1",
275 "mi2s-osr-clk2",
276 "mi2s-osr-clk3",
277 },
278 .dai_bit_clk_names = (const char *[]) {
279 "mi2s-bit-clk0",
280 "mi2s-bit-clk1",
281 "mi2s-bit-clk2",
282 "mi2s-bit-clk3",
283 },
284 .init = apq8016_lpass_init,
285 .exit = apq8016_lpass_exit,
286 .alloc_dma_channel = apq8016_lpass_alloc_dma_channel,
287 .free_dma_channel = apq8016_lpass_free_dma_channel,
288};
289
290static const struct of_device_id apq8016_lpass_cpu_device_id[] __maybe_unused = {
291 { .compatible = "qcom,lpass-cpu-apq8016", .data = &apq8016_data },
292 { .compatible = "qcom,apq8016-lpass-cpu", .data = &apq8016_data },
293 {}
294};
295MODULE_DEVICE_TABLE(of, apq8016_lpass_cpu_device_id);
296
297static struct platform_driver apq8016_lpass_cpu_platform_driver = {
298 .driver = {
299 .name = "apq8016-lpass-cpu",
300 .of_match_table = of_match_ptr(apq8016_lpass_cpu_device_id),
301 },
302 .probe = asoc_qcom_lpass_cpu_platform_probe,
303 .remove_new = asoc_qcom_lpass_cpu_platform_remove,
304};
305module_platform_driver(apq8016_lpass_cpu_platform_driver);
306
307MODULE_DESCRIPTION("APQ8016 LPASS CPU Driver");
308MODULE_LICENSE("GPL");
309
310

source code of linux/sound/soc/qcom/lpass-apq8016.c