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-ipq806x.c -- ALSA SoC CPU DAI driver for QTi LPASS |
6 | * Splited out the IPQ8064 soc specific from lpass-cpu.c |
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/soc.h> |
18 | #include <sound/soc-dai.h> |
19 | |
20 | #include "lpass-lpaif-reg.h" |
21 | #include "lpass.h" |
22 | |
23 | enum lpaif_i2s_ports { |
24 | IPQ806X_LPAIF_I2S_PORT_CODEC_SPK, |
25 | IPQ806X_LPAIF_I2S_PORT_CODEC_MIC, |
26 | IPQ806X_LPAIF_I2S_PORT_SEC_SPK, |
27 | IPQ806X_LPAIF_I2S_PORT_SEC_MIC, |
28 | IPQ806X_LPAIF_I2S_PORT_MI2S, |
29 | }; |
30 | |
31 | enum lpaif_dma_channels { |
32 | IPQ806X_LPAIF_RDMA_CHAN_MI2S, |
33 | IPQ806X_LPAIF_RDMA_CHAN_PCM0, |
34 | IPQ806X_LPAIF_RDMA_CHAN_PCM1, |
35 | }; |
36 | |
37 | static struct snd_soc_dai_driver ipq806x_lpass_cpu_dai_driver = { |
38 | .id = IPQ806X_LPAIF_I2S_PORT_MI2S, |
39 | .playback = { |
40 | .stream_name = "lpass-cpu-playback" , |
41 | .formats = SNDRV_PCM_FMTBIT_S16 | |
42 | SNDRV_PCM_FMTBIT_S24 | |
43 | SNDRV_PCM_FMTBIT_S32, |
44 | .rates = SNDRV_PCM_RATE_8000 | |
45 | SNDRV_PCM_RATE_16000 | |
46 | SNDRV_PCM_RATE_32000 | |
47 | SNDRV_PCM_RATE_48000 | |
48 | SNDRV_PCM_RATE_96000, |
49 | .rate_min = 8000, |
50 | .rate_max = 96000, |
51 | .channels_min = 1, |
52 | .channels_max = 8, |
53 | }, |
54 | .ops = &asoc_qcom_lpass_cpu_dai_ops, |
55 | }; |
56 | |
57 | static int ipq806x_lpass_init(struct platform_device *pdev) |
58 | { |
59 | struct lpass_data *drvdata = platform_get_drvdata(pdev); |
60 | struct device *dev = &pdev->dev; |
61 | int ret; |
62 | |
63 | drvdata->ahbix_clk = devm_clk_get(dev, id: "ahbix-clk" ); |
64 | if (IS_ERR(ptr: drvdata->ahbix_clk)) { |
65 | dev_err(dev, "error getting ahbix-clk: %ld\n" , |
66 | PTR_ERR(drvdata->ahbix_clk)); |
67 | ret = PTR_ERR(ptr: drvdata->ahbix_clk); |
68 | goto err_ahbix_clk; |
69 | } |
70 | |
71 | ret = clk_set_rate(clk: drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY); |
72 | if (ret) { |
73 | dev_err(dev, "error setting rate on ahbix_clk: %d\n" , ret); |
74 | goto err_ahbix_clk; |
75 | } |
76 | dev_dbg(dev, "set ahbix_clk rate to %lu\n" , |
77 | clk_get_rate(drvdata->ahbix_clk)); |
78 | |
79 | ret = clk_prepare_enable(clk: drvdata->ahbix_clk); |
80 | if (ret) { |
81 | dev_err(dev, "error enabling ahbix_clk: %d\n" , ret); |
82 | goto err_ahbix_clk; |
83 | } |
84 | |
85 | err_ahbix_clk: |
86 | return ret; |
87 | } |
88 | |
89 | static int ipq806x_lpass_exit(struct platform_device *pdev) |
90 | { |
91 | struct lpass_data *drvdata = platform_get_drvdata(pdev); |
92 | |
93 | clk_disable_unprepare(clk: drvdata->ahbix_clk); |
94 | |
95 | return 0; |
96 | } |
97 | |
98 | static int ipq806x_lpass_alloc_dma_channel(struct lpass_data *drvdata, int dir, unsigned int dai_id) |
99 | { |
100 | if (dir == SNDRV_PCM_STREAM_PLAYBACK) |
101 | return IPQ806X_LPAIF_RDMA_CHAN_MI2S; |
102 | else /* Capture currently not implemented */ |
103 | return -EINVAL; |
104 | } |
105 | |
106 | static int ipq806x_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id) |
107 | { |
108 | return 0; |
109 | } |
110 | |
111 | static const struct lpass_variant ipq806x_data = { |
112 | .i2sctrl_reg_base = 0x0010, |
113 | .i2sctrl_reg_stride = 0x04, |
114 | .i2s_ports = 5, |
115 | .irq_reg_base = 0x3000, |
116 | .irq_reg_stride = 0x1000, |
117 | .irq_ports = 3, |
118 | .rdma_reg_base = 0x6000, |
119 | .rdma_reg_stride = 0x1000, |
120 | .rdma_channels = 4, |
121 | .wrdma_reg_base = 0xB000, |
122 | .wrdma_reg_stride = 0x1000, |
123 | .wrdma_channel_start = 5, |
124 | .wrdma_channels = 4, |
125 | .loopback = REG_FIELD_ID(0x0010, 15, 15, 5, 0x4), |
126 | .spken = REG_FIELD_ID(0x0010, 14, 14, 5, 0x4), |
127 | .spkmode = REG_FIELD_ID(0x0010, 10, 13, 5, 0x4), |
128 | .spkmono = REG_FIELD_ID(0x0010, 9, 9, 5, 0x4), |
129 | .micen = REG_FIELD_ID(0x0010, 8, 8, 5, 0x4), |
130 | .micmode = REG_FIELD_ID(0x0010, 4, 7, 5, 0x4), |
131 | .micmono = REG_FIELD_ID(0x0010, 3, 3, 5, 0x4), |
132 | .wssrc = REG_FIELD_ID(0x0010, 2, 2, 5, 0x4), |
133 | .bitwidth = REG_FIELD_ID(0x0010, 0, 1, 5, 0x4), |
134 | |
135 | .rdma_dyncclk = REG_FIELD_ID(0x6000, 12, 12, 4, 0x1000), |
136 | .rdma_bursten = REG_FIELD_ID(0x6000, 11, 11, 4, 0x1000), |
137 | .rdma_wpscnt = REG_FIELD_ID(0x6000, 8, 10, 4, 0x1000), |
138 | .rdma_intf = REG_FIELD_ID(0x6000, 4, 7, 4, 0x1000), |
139 | .rdma_fifowm = REG_FIELD_ID(0x6000, 1, 3, 4, 0x1000), |
140 | .rdma_enable = REG_FIELD_ID(0x6000, 0, 0, 4, 0x1000), |
141 | |
142 | .wrdma_dyncclk = REG_FIELD_ID(0xB000, 12, 12, 4, 0x1000), |
143 | .wrdma_bursten = REG_FIELD_ID(0xB000, 11, 11, 4, 0x1000), |
144 | .wrdma_wpscnt = REG_FIELD_ID(0xB000, 8, 10, 4, 0x1000), |
145 | .wrdma_intf = REG_FIELD_ID(0xB000, 4, 7, 4, 0x1000), |
146 | .wrdma_fifowm = REG_FIELD_ID(0xB000, 1, 3, 4, 0x1000), |
147 | .wrdma_enable = REG_FIELD_ID(0xB000, 0, 0, 4, 0x1000), |
148 | |
149 | .dai_driver = &ipq806x_lpass_cpu_dai_driver, |
150 | .num_dai = 1, |
151 | .dai_osr_clk_names = (const char *[]) { |
152 | "mi2s-osr-clk" , |
153 | }, |
154 | .dai_bit_clk_names = (const char *[]) { |
155 | "mi2s-bit-clk" , |
156 | }, |
157 | .init = ipq806x_lpass_init, |
158 | .exit = ipq806x_lpass_exit, |
159 | .alloc_dma_channel = ipq806x_lpass_alloc_dma_channel, |
160 | .free_dma_channel = ipq806x_lpass_free_dma_channel, |
161 | }; |
162 | |
163 | static const struct of_device_id ipq806x_lpass_cpu_device_id[] __maybe_unused = { |
164 | { .compatible = "qcom,lpass-cpu" , .data = &ipq806x_data }, |
165 | {} |
166 | }; |
167 | MODULE_DEVICE_TABLE(of, ipq806x_lpass_cpu_device_id); |
168 | |
169 | static struct platform_driver ipq806x_lpass_cpu_platform_driver = { |
170 | .driver = { |
171 | .name = "lpass-cpu" , |
172 | .of_match_table = of_match_ptr(ipq806x_lpass_cpu_device_id), |
173 | }, |
174 | .probe = asoc_qcom_lpass_cpu_platform_probe, |
175 | .remove_new = asoc_qcom_lpass_cpu_platform_remove, |
176 | }; |
177 | module_platform_driver(ipq806x_lpass_cpu_platform_driver); |
178 | |
179 | MODULE_DESCRIPTION("QTi LPASS CPU Driver" ); |
180 | MODULE_LICENSE("GPL" ); |
181 | |