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