1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // Copyright 2020 NXP |
3 | |
4 | #include <linux/clk.h> |
5 | #include <linux/clk-provider.h> |
6 | #include <linux/delay.h> |
7 | #include <linux/dmaengine.h> |
8 | #include <linux/mod_devicetable.h> |
9 | #include <linux/module.h> |
10 | #include <linux/pm_runtime.h> |
11 | #include <linux/regmap.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/time.h> |
14 | #include <linux/pm_qos.h> |
15 | #include <sound/core.h> |
16 | #include <sound/dmaengine_pcm.h> |
17 | #include <sound/pcm_params.h> |
18 | #include <linux/dma-mapping.h> |
19 | |
20 | #include "fsl_aud2htx.h" |
21 | #include "imx-pcm.h" |
22 | |
23 | static int fsl_aud2htx_trigger(struct snd_pcm_substream *substream, int cmd, |
24 | struct snd_soc_dai *dai) |
25 | { |
26 | struct fsl_aud2htx *aud2htx = snd_soc_dai_get_drvdata(dai); |
27 | |
28 | switch (cmd) { |
29 | case SNDRV_PCM_TRIGGER_START: |
30 | case SNDRV_PCM_TRIGGER_RESUME: |
31 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
32 | regmap_update_bits(map: aud2htx->regmap, AUD2HTX_CTRL, |
33 | AUD2HTX_CTRL_EN, AUD2HTX_CTRL_EN); |
34 | regmap_update_bits(map: aud2htx->regmap, AUD2HTX_CTRL_EXT, |
35 | AUD2HTX_CTRE_DE, AUD2HTX_CTRE_DE); |
36 | break; |
37 | case SNDRV_PCM_TRIGGER_SUSPEND: |
38 | case SNDRV_PCM_TRIGGER_STOP: |
39 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
40 | regmap_update_bits(map: aud2htx->regmap, AUD2HTX_CTRL_EXT, |
41 | AUD2HTX_CTRE_DE, val: 0); |
42 | regmap_update_bits(map: aud2htx->regmap, AUD2HTX_CTRL, |
43 | AUD2HTX_CTRL_EN, val: 0); |
44 | break; |
45 | default: |
46 | return -EINVAL; |
47 | } |
48 | return 0; |
49 | } |
50 | |
51 | static int fsl_aud2htx_dai_probe(struct snd_soc_dai *cpu_dai) |
52 | { |
53 | struct fsl_aud2htx *aud2htx = dev_get_drvdata(dev: cpu_dai->dev); |
54 | |
55 | /* DMA request when number of entries < WTMK_LOW */ |
56 | regmap_update_bits(map: aud2htx->regmap, AUD2HTX_CTRL_EXT, |
57 | AUD2HTX_CTRE_DT_MASK, val: 0); |
58 | |
59 | /* Disable interrupts*/ |
60 | regmap_update_bits(map: aud2htx->regmap, AUD2HTX_IRQ_MASK, |
61 | AUD2HTX_WM_HIGH_IRQ_MASK | |
62 | AUD2HTX_WM_LOW_IRQ_MASK | |
63 | AUD2HTX_OVF_MASK, |
64 | AUD2HTX_WM_HIGH_IRQ_MASK | |
65 | AUD2HTX_WM_LOW_IRQ_MASK | |
66 | AUD2HTX_OVF_MASK); |
67 | |
68 | /* Configure watermark */ |
69 | regmap_update_bits(map: aud2htx->regmap, AUD2HTX_CTRL_EXT, |
70 | AUD2HTX_CTRE_WL_MASK, |
71 | AUD2HTX_WTMK_LOW << AUD2HTX_CTRE_WL_SHIFT); |
72 | regmap_update_bits(map: aud2htx->regmap, AUD2HTX_CTRL_EXT, |
73 | AUD2HTX_CTRE_WH_MASK, |
74 | AUD2HTX_WTMK_HIGH << AUD2HTX_CTRE_WH_SHIFT); |
75 | |
76 | snd_soc_dai_init_dma_data(dai: cpu_dai, playback: &aud2htx->dma_params_tx, |
77 | capture: &aud2htx->dma_params_rx); |
78 | |
79 | return 0; |
80 | } |
81 | |
82 | static const struct snd_soc_dai_ops fsl_aud2htx_dai_ops = { |
83 | .probe = fsl_aud2htx_dai_probe, |
84 | .trigger = fsl_aud2htx_trigger, |
85 | }; |
86 | |
87 | static struct snd_soc_dai_driver fsl_aud2htx_dai = { |
88 | .playback = { |
89 | .stream_name = "CPU-Playback" , |
90 | .channels_min = 1, |
91 | .channels_max = 8, |
92 | .rates = SNDRV_PCM_RATE_32000 | |
93 | SNDRV_PCM_RATE_44100 | |
94 | SNDRV_PCM_RATE_48000 | |
95 | SNDRV_PCM_RATE_88200 | |
96 | SNDRV_PCM_RATE_96000 | |
97 | SNDRV_PCM_RATE_176400 | |
98 | SNDRV_PCM_RATE_192000, |
99 | .formats = FSL_AUD2HTX_FORMATS, |
100 | }, |
101 | .ops = &fsl_aud2htx_dai_ops, |
102 | }; |
103 | |
104 | static const struct snd_soc_component_driver fsl_aud2htx_component = { |
105 | .name = "fsl-aud2htx" , |
106 | .legacy_dai_naming = 1, |
107 | }; |
108 | |
109 | static const struct reg_default fsl_aud2htx_reg_defaults[] = { |
110 | {AUD2HTX_CTRL, 0x00000000}, |
111 | {AUD2HTX_CTRL_EXT, 0x00000000}, |
112 | {AUD2HTX_WR, 0x00000000}, |
113 | {AUD2HTX_STATUS, 0x00000000}, |
114 | {AUD2HTX_IRQ_NOMASK, 0x00000000}, |
115 | {AUD2HTX_IRQ_MASKED, 0x00000000}, |
116 | {AUD2HTX_IRQ_MASK, 0x00000000}, |
117 | }; |
118 | |
119 | static bool fsl_aud2htx_readable_reg(struct device *dev, unsigned int reg) |
120 | { |
121 | switch (reg) { |
122 | case AUD2HTX_CTRL: |
123 | case AUD2HTX_CTRL_EXT: |
124 | case AUD2HTX_STATUS: |
125 | case AUD2HTX_IRQ_NOMASK: |
126 | case AUD2HTX_IRQ_MASKED: |
127 | case AUD2HTX_IRQ_MASK: |
128 | return true; |
129 | default: |
130 | return false; |
131 | } |
132 | } |
133 | |
134 | static bool fsl_aud2htx_writeable_reg(struct device *dev, unsigned int reg) |
135 | { |
136 | switch (reg) { |
137 | case AUD2HTX_CTRL: |
138 | case AUD2HTX_CTRL_EXT: |
139 | case AUD2HTX_WR: |
140 | case AUD2HTX_IRQ_NOMASK: |
141 | case AUD2HTX_IRQ_MASKED: |
142 | case AUD2HTX_IRQ_MASK: |
143 | return true; |
144 | default: |
145 | return false; |
146 | } |
147 | } |
148 | |
149 | static bool fsl_aud2htx_volatile_reg(struct device *dev, unsigned int reg) |
150 | { |
151 | switch (reg) { |
152 | case AUD2HTX_STATUS: |
153 | case AUD2HTX_IRQ_NOMASK: |
154 | case AUD2HTX_IRQ_MASKED: |
155 | return true; |
156 | default: |
157 | return false; |
158 | } |
159 | } |
160 | |
161 | static const struct regmap_config fsl_aud2htx_regmap_config = { |
162 | .reg_bits = 32, |
163 | .reg_stride = 4, |
164 | .val_bits = 32, |
165 | |
166 | .max_register = AUD2HTX_IRQ_MASK, |
167 | .reg_defaults = fsl_aud2htx_reg_defaults, |
168 | .num_reg_defaults = ARRAY_SIZE(fsl_aud2htx_reg_defaults), |
169 | .readable_reg = fsl_aud2htx_readable_reg, |
170 | .volatile_reg = fsl_aud2htx_volatile_reg, |
171 | .writeable_reg = fsl_aud2htx_writeable_reg, |
172 | .cache_type = REGCACHE_RBTREE, |
173 | }; |
174 | |
175 | static const struct of_device_id fsl_aud2htx_dt_ids[] = { |
176 | { .compatible = "fsl,imx8mp-aud2htx" ,}, |
177 | {} |
178 | }; |
179 | MODULE_DEVICE_TABLE(of, fsl_aud2htx_dt_ids); |
180 | |
181 | static irqreturn_t fsl_aud2htx_isr(int irq, void *dev_id) |
182 | { |
183 | return IRQ_HANDLED; |
184 | } |
185 | |
186 | static int fsl_aud2htx_probe(struct platform_device *pdev) |
187 | { |
188 | struct fsl_aud2htx *aud2htx; |
189 | struct resource *res; |
190 | void __iomem *regs; |
191 | int ret, irq; |
192 | |
193 | aud2htx = devm_kzalloc(dev: &pdev->dev, size: sizeof(*aud2htx), GFP_KERNEL); |
194 | if (!aud2htx) |
195 | return -ENOMEM; |
196 | |
197 | aud2htx->pdev = pdev; |
198 | |
199 | regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
200 | if (IS_ERR(ptr: regs)) |
201 | return PTR_ERR(ptr: regs); |
202 | |
203 | aud2htx->regmap = devm_regmap_init_mmio(&pdev->dev, regs, |
204 | &fsl_aud2htx_regmap_config); |
205 | if (IS_ERR(ptr: aud2htx->regmap)) { |
206 | dev_err(&pdev->dev, "failed to init regmap" ); |
207 | return PTR_ERR(ptr: aud2htx->regmap); |
208 | } |
209 | |
210 | irq = platform_get_irq(pdev, 0); |
211 | if (irq < 0) |
212 | return irq; |
213 | |
214 | ret = devm_request_irq(dev: &pdev->dev, irq, handler: fsl_aud2htx_isr, irqflags: 0, |
215 | devname: dev_name(dev: &pdev->dev), dev_id: aud2htx); |
216 | if (ret) { |
217 | dev_err(&pdev->dev, "failed to claim irq %u: %d\n" , irq, ret); |
218 | return ret; |
219 | } |
220 | |
221 | aud2htx->bus_clk = devm_clk_get(dev: &pdev->dev, id: "bus" ); |
222 | if (IS_ERR(ptr: aud2htx->bus_clk)) { |
223 | dev_err(&pdev->dev, "failed to get mem clock\n" ); |
224 | return PTR_ERR(ptr: aud2htx->bus_clk); |
225 | } |
226 | |
227 | aud2htx->dma_params_tx.chan_name = "tx" ; |
228 | aud2htx->dma_params_tx.maxburst = AUD2HTX_MAXBURST; |
229 | aud2htx->dma_params_tx.addr = res->start + AUD2HTX_WR; |
230 | |
231 | platform_set_drvdata(pdev, data: aud2htx); |
232 | pm_runtime_enable(dev: &pdev->dev); |
233 | |
234 | regcache_cache_only(map: aud2htx->regmap, enable: true); |
235 | |
236 | /* |
237 | * Register platform component before registering cpu dai for there |
238 | * is not defer probe for platform component in snd_soc_add_pcm_runtime(). |
239 | */ |
240 | ret = devm_snd_dmaengine_pcm_register(dev: &pdev->dev, NULL, flags: 0); |
241 | if (ret) { |
242 | dev_err(&pdev->dev, "failed to pcm register\n" ); |
243 | pm_runtime_disable(dev: &pdev->dev); |
244 | return ret; |
245 | } |
246 | |
247 | ret = devm_snd_soc_register_component(dev: &pdev->dev, |
248 | component_driver: &fsl_aud2htx_component, |
249 | dai_drv: &fsl_aud2htx_dai, num_dai: 1); |
250 | if (ret) { |
251 | dev_err(&pdev->dev, "failed to register ASoC DAI\n" ); |
252 | pm_runtime_disable(dev: &pdev->dev); |
253 | return ret; |
254 | } |
255 | |
256 | return ret; |
257 | } |
258 | |
259 | static void fsl_aud2htx_remove(struct platform_device *pdev) |
260 | { |
261 | pm_runtime_disable(dev: &pdev->dev); |
262 | } |
263 | |
264 | static int __maybe_unused fsl_aud2htx_runtime_suspend(struct device *dev) |
265 | { |
266 | struct fsl_aud2htx *aud2htx = dev_get_drvdata(dev); |
267 | |
268 | regcache_cache_only(map: aud2htx->regmap, enable: true); |
269 | clk_disable_unprepare(clk: aud2htx->bus_clk); |
270 | |
271 | return 0; |
272 | } |
273 | |
274 | static int __maybe_unused fsl_aud2htx_runtime_resume(struct device *dev) |
275 | { |
276 | struct fsl_aud2htx *aud2htx = dev_get_drvdata(dev); |
277 | int ret; |
278 | |
279 | ret = clk_prepare_enable(clk: aud2htx->bus_clk); |
280 | if (ret) |
281 | return ret; |
282 | |
283 | regcache_cache_only(map: aud2htx->regmap, enable: false); |
284 | regcache_mark_dirty(map: aud2htx->regmap); |
285 | regcache_sync(map: aud2htx->regmap); |
286 | |
287 | return 0; |
288 | } |
289 | |
290 | static const struct dev_pm_ops fsl_aud2htx_pm_ops = { |
291 | SET_RUNTIME_PM_OPS(fsl_aud2htx_runtime_suspend, |
292 | fsl_aud2htx_runtime_resume, |
293 | NULL) |
294 | SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
295 | pm_runtime_force_resume) |
296 | }; |
297 | |
298 | static struct platform_driver fsl_aud2htx_driver = { |
299 | .probe = fsl_aud2htx_probe, |
300 | .remove_new = fsl_aud2htx_remove, |
301 | .driver = { |
302 | .name = "fsl-aud2htx" , |
303 | .pm = &fsl_aud2htx_pm_ops, |
304 | .of_match_table = fsl_aud2htx_dt_ids, |
305 | }, |
306 | }; |
307 | module_platform_driver(fsl_aud2htx_driver); |
308 | |
309 | MODULE_AUTHOR("Shengjiu Wang <Shengjiu.Wang@nxp.com>" ); |
310 | MODULE_DESCRIPTION("NXP AUD2HTX driver" ); |
311 | MODULE_LICENSE("GPL v2" ); |
312 | |