1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * IMG I2S output controller driver |
4 | * |
5 | * Copyright (C) 2015 Imagination Technologies Ltd. |
6 | * |
7 | * Author: Damien Horsley <Damien.Horsley@imgtec.com> |
8 | */ |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/init.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/pm_runtime.h> |
17 | #include <linux/reset.h> |
18 | |
19 | #include <sound/core.h> |
20 | #include <sound/dmaengine_pcm.h> |
21 | #include <sound/initval.h> |
22 | #include <sound/pcm.h> |
23 | #include <sound/pcm_params.h> |
24 | #include <sound/soc.h> |
25 | |
26 | #define IMG_I2S_OUT_TX_FIFO 0x0 |
27 | |
28 | #define IMG_I2S_OUT_CTL 0x4 |
29 | #define IMG_I2S_OUT_CTL_DATA_EN_MASK BIT(24) |
30 | #define IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK 0xffe000 |
31 | #define IMG_I2S_OUT_CTL_ACTIVE_CHAN_SHIFT 13 |
32 | #define IMG_I2S_OUT_CTL_FRM_SIZE_MASK BIT(8) |
33 | #define IMG_I2S_OUT_CTL_MASTER_MASK BIT(6) |
34 | #define IMG_I2S_OUT_CTL_CLK_MASK BIT(5) |
35 | #define IMG_I2S_OUT_CTL_CLK_EN_MASK BIT(4) |
36 | #define IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK BIT(3) |
37 | #define IMG_I2S_OUT_CTL_BCLK_POL_MASK BIT(2) |
38 | #define IMG_I2S_OUT_CTL_ME_MASK BIT(0) |
39 | |
40 | #define IMG_I2S_OUT_CH_CTL 0x4 |
41 | #define IMG_I2S_OUT_CHAN_CTL_CH_MASK BIT(11) |
42 | #define IMG_I2S_OUT_CHAN_CTL_LT_MASK BIT(10) |
43 | #define IMG_I2S_OUT_CHAN_CTL_FMT_MASK 0xf0 |
44 | #define IMG_I2S_OUT_CHAN_CTL_FMT_SHIFT 4 |
45 | #define IMG_I2S_OUT_CHAN_CTL_JUST_MASK BIT(3) |
46 | #define IMG_I2S_OUT_CHAN_CTL_CLKT_MASK BIT(1) |
47 | #define IMG_I2S_OUT_CHAN_CTL_ME_MASK BIT(0) |
48 | |
49 | #define IMG_I2S_OUT_CH_STRIDE 0x20 |
50 | |
51 | struct img_i2s_out { |
52 | void __iomem *base; |
53 | struct clk *clk_sys; |
54 | struct clk *clk_ref; |
55 | struct snd_dmaengine_dai_dma_data dma_data; |
56 | struct device *dev; |
57 | unsigned int max_i2s_chan; |
58 | void __iomem *channel_base; |
59 | bool force_clk_active; |
60 | unsigned int active_channels; |
61 | struct reset_control *rst; |
62 | struct snd_soc_dai_driver dai_driver; |
63 | u32 suspend_ctl; |
64 | u32 *suspend_ch_ctl; |
65 | }; |
66 | |
67 | static int img_i2s_out_runtime_suspend(struct device *dev) |
68 | { |
69 | struct img_i2s_out *i2s = dev_get_drvdata(dev); |
70 | |
71 | clk_disable_unprepare(clk: i2s->clk_ref); |
72 | clk_disable_unprepare(clk: i2s->clk_sys); |
73 | |
74 | return 0; |
75 | } |
76 | |
77 | static int img_i2s_out_runtime_resume(struct device *dev) |
78 | { |
79 | struct img_i2s_out *i2s = dev_get_drvdata(dev); |
80 | int ret; |
81 | |
82 | ret = clk_prepare_enable(clk: i2s->clk_sys); |
83 | if (ret) { |
84 | dev_err(dev, "clk_enable failed: %d\n" , ret); |
85 | return ret; |
86 | } |
87 | |
88 | ret = clk_prepare_enable(clk: i2s->clk_ref); |
89 | if (ret) { |
90 | dev_err(dev, "clk_enable failed: %d\n" , ret); |
91 | clk_disable_unprepare(clk: i2s->clk_sys); |
92 | return ret; |
93 | } |
94 | |
95 | return 0; |
96 | } |
97 | |
98 | static inline void img_i2s_out_writel(struct img_i2s_out *i2s, u32 val, |
99 | u32 reg) |
100 | { |
101 | writel(val, addr: i2s->base + reg); |
102 | } |
103 | |
104 | static inline u32 img_i2s_out_readl(struct img_i2s_out *i2s, u32 reg) |
105 | { |
106 | return readl(addr: i2s->base + reg); |
107 | } |
108 | |
109 | static inline void img_i2s_out_ch_writel(struct img_i2s_out *i2s, |
110 | u32 chan, u32 val, u32 reg) |
111 | { |
112 | writel(val, addr: i2s->channel_base + (chan * IMG_I2S_OUT_CH_STRIDE) + reg); |
113 | } |
114 | |
115 | static inline u32 img_i2s_out_ch_readl(struct img_i2s_out *i2s, u32 chan, |
116 | u32 reg) |
117 | { |
118 | return readl(addr: i2s->channel_base + (chan * IMG_I2S_OUT_CH_STRIDE) + reg); |
119 | } |
120 | |
121 | static inline void img_i2s_out_ch_disable(struct img_i2s_out *i2s, u32 chan) |
122 | { |
123 | u32 reg; |
124 | |
125 | reg = img_i2s_out_ch_readl(i2s, chan, IMG_I2S_OUT_CH_CTL); |
126 | reg &= ~IMG_I2S_OUT_CHAN_CTL_ME_MASK; |
127 | img_i2s_out_ch_writel(i2s, chan, val: reg, IMG_I2S_OUT_CH_CTL); |
128 | } |
129 | |
130 | static inline void img_i2s_out_ch_enable(struct img_i2s_out *i2s, u32 chan) |
131 | { |
132 | u32 reg; |
133 | |
134 | reg = img_i2s_out_ch_readl(i2s, chan, IMG_I2S_OUT_CH_CTL); |
135 | reg |= IMG_I2S_OUT_CHAN_CTL_ME_MASK; |
136 | img_i2s_out_ch_writel(i2s, chan, val: reg, IMG_I2S_OUT_CH_CTL); |
137 | } |
138 | |
139 | static inline void img_i2s_out_disable(struct img_i2s_out *i2s) |
140 | { |
141 | u32 reg; |
142 | |
143 | reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); |
144 | reg &= ~IMG_I2S_OUT_CTL_ME_MASK; |
145 | img_i2s_out_writel(i2s, val: reg, IMG_I2S_OUT_CTL); |
146 | } |
147 | |
148 | static inline void img_i2s_out_enable(struct img_i2s_out *i2s) |
149 | { |
150 | u32 reg; |
151 | |
152 | reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); |
153 | reg |= IMG_I2S_OUT_CTL_ME_MASK; |
154 | img_i2s_out_writel(i2s, val: reg, IMG_I2S_OUT_CTL); |
155 | } |
156 | |
157 | static void img_i2s_out_reset(struct img_i2s_out *i2s) |
158 | { |
159 | int i; |
160 | u32 core_ctl, chan_ctl; |
161 | |
162 | core_ctl = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL) & |
163 | ~IMG_I2S_OUT_CTL_ME_MASK & |
164 | ~IMG_I2S_OUT_CTL_DATA_EN_MASK; |
165 | |
166 | if (!i2s->force_clk_active) |
167 | core_ctl &= ~IMG_I2S_OUT_CTL_CLK_EN_MASK; |
168 | |
169 | chan_ctl = img_i2s_out_ch_readl(i2s, chan: 0, IMG_I2S_OUT_CH_CTL) & |
170 | ~IMG_I2S_OUT_CHAN_CTL_ME_MASK; |
171 | |
172 | reset_control_assert(rstc: i2s->rst); |
173 | reset_control_deassert(rstc: i2s->rst); |
174 | |
175 | for (i = 0; i < i2s->max_i2s_chan; i++) |
176 | img_i2s_out_ch_writel(i2s, chan: i, val: chan_ctl, IMG_I2S_OUT_CH_CTL); |
177 | |
178 | for (i = 0; i < i2s->active_channels; i++) |
179 | img_i2s_out_ch_enable(i2s, chan: i); |
180 | |
181 | img_i2s_out_writel(i2s, val: core_ctl, IMG_I2S_OUT_CTL); |
182 | img_i2s_out_enable(i2s); |
183 | } |
184 | |
185 | static int img_i2s_out_trigger(struct snd_pcm_substream *substream, int cmd, |
186 | struct snd_soc_dai *dai) |
187 | { |
188 | struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); |
189 | u32 reg; |
190 | |
191 | switch (cmd) { |
192 | case SNDRV_PCM_TRIGGER_START: |
193 | case SNDRV_PCM_TRIGGER_RESUME: |
194 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
195 | reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); |
196 | if (!i2s->force_clk_active) |
197 | reg |= IMG_I2S_OUT_CTL_CLK_EN_MASK; |
198 | reg |= IMG_I2S_OUT_CTL_DATA_EN_MASK; |
199 | img_i2s_out_writel(i2s, val: reg, IMG_I2S_OUT_CTL); |
200 | break; |
201 | case SNDRV_PCM_TRIGGER_STOP: |
202 | case SNDRV_PCM_TRIGGER_SUSPEND: |
203 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
204 | img_i2s_out_reset(i2s); |
205 | break; |
206 | default: |
207 | return -EINVAL; |
208 | } |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static int img_i2s_out_hw_params(struct snd_pcm_substream *substream, |
214 | struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) |
215 | { |
216 | struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); |
217 | unsigned int channels, i2s_channels; |
218 | long pre_div_a, pre_div_b, diff_a, diff_b, rate, clk_rate; |
219 | int i; |
220 | u32 reg, control_mask, control_set = 0; |
221 | snd_pcm_format_t format; |
222 | |
223 | rate = params_rate(p: params); |
224 | format = params_format(p: params); |
225 | channels = params_channels(p: params); |
226 | i2s_channels = channels / 2; |
227 | |
228 | if (format != SNDRV_PCM_FORMAT_S32_LE) |
229 | return -EINVAL; |
230 | |
231 | if ((channels < 2) || |
232 | (channels > (i2s->max_i2s_chan * 2)) || |
233 | (channels % 2)) |
234 | return -EINVAL; |
235 | |
236 | pre_div_a = clk_round_rate(clk: i2s->clk_ref, rate: rate * 256); |
237 | if (pre_div_a < 0) |
238 | return pre_div_a; |
239 | pre_div_b = clk_round_rate(clk: i2s->clk_ref, rate: rate * 384); |
240 | if (pre_div_b < 0) |
241 | return pre_div_b; |
242 | |
243 | diff_a = abs((pre_div_a / 256) - rate); |
244 | diff_b = abs((pre_div_b / 384) - rate); |
245 | |
246 | /* If diffs are equal, use lower clock rate */ |
247 | if (diff_a > diff_b) |
248 | clk_set_rate(clk: i2s->clk_ref, rate: pre_div_b); |
249 | else |
250 | clk_set_rate(clk: i2s->clk_ref, rate: pre_div_a); |
251 | |
252 | /* |
253 | * Another driver (eg alsa machine driver) may have rejected the above |
254 | * change. Get the current rate and set the register bit according to |
255 | * the new minimum diff |
256 | */ |
257 | clk_rate = clk_get_rate(clk: i2s->clk_ref); |
258 | |
259 | diff_a = abs((clk_rate / 256) - rate); |
260 | diff_b = abs((clk_rate / 384) - rate); |
261 | |
262 | if (diff_a > diff_b) |
263 | control_set |= IMG_I2S_OUT_CTL_CLK_MASK; |
264 | |
265 | control_set |= ((i2s_channels - 1) << |
266 | IMG_I2S_OUT_CTL_ACTIVE_CHAN_SHIFT) & |
267 | IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK; |
268 | |
269 | control_mask = IMG_I2S_OUT_CTL_CLK_MASK | |
270 | IMG_I2S_OUT_CTL_ACTIVE_CHAN_MASK; |
271 | |
272 | img_i2s_out_disable(i2s); |
273 | |
274 | reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); |
275 | reg = (reg & ~control_mask) | control_set; |
276 | img_i2s_out_writel(i2s, val: reg, IMG_I2S_OUT_CTL); |
277 | |
278 | for (i = 0; i < i2s_channels; i++) |
279 | img_i2s_out_ch_enable(i2s, chan: i); |
280 | |
281 | for (; i < i2s->max_i2s_chan; i++) |
282 | img_i2s_out_ch_disable(i2s, chan: i); |
283 | |
284 | img_i2s_out_enable(i2s); |
285 | |
286 | i2s->active_channels = i2s_channels; |
287 | |
288 | return 0; |
289 | } |
290 | |
291 | static int img_i2s_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
292 | { |
293 | struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); |
294 | int i, ret; |
295 | bool force_clk_active; |
296 | u32 chan_control_mask, control_mask, chan_control_set = 0; |
297 | u32 reg, control_set = 0; |
298 | |
299 | force_clk_active = ((fmt & SND_SOC_DAIFMT_CLOCK_MASK) == |
300 | SND_SOC_DAIFMT_CONT); |
301 | |
302 | if (force_clk_active) |
303 | control_set |= IMG_I2S_OUT_CTL_CLK_EN_MASK; |
304 | |
305 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
306 | case SND_SOC_DAIFMT_BC_FC: |
307 | break; |
308 | case SND_SOC_DAIFMT_BP_FP: |
309 | control_set |= IMG_I2S_OUT_CTL_MASTER_MASK; |
310 | break; |
311 | default: |
312 | return -EINVAL; |
313 | } |
314 | |
315 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
316 | case SND_SOC_DAIFMT_NB_NF: |
317 | control_set |= IMG_I2S_OUT_CTL_BCLK_POL_MASK; |
318 | break; |
319 | case SND_SOC_DAIFMT_NB_IF: |
320 | control_set |= IMG_I2S_OUT_CTL_BCLK_POL_MASK; |
321 | control_set |= IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK; |
322 | break; |
323 | case SND_SOC_DAIFMT_IB_NF: |
324 | break; |
325 | case SND_SOC_DAIFMT_IB_IF: |
326 | control_set |= IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK; |
327 | break; |
328 | default: |
329 | return -EINVAL; |
330 | } |
331 | |
332 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
333 | case SND_SOC_DAIFMT_I2S: |
334 | chan_control_set |= IMG_I2S_OUT_CHAN_CTL_CLKT_MASK; |
335 | break; |
336 | case SND_SOC_DAIFMT_LEFT_J: |
337 | break; |
338 | default: |
339 | return -EINVAL; |
340 | } |
341 | |
342 | control_mask = IMG_I2S_OUT_CTL_CLK_EN_MASK | |
343 | IMG_I2S_OUT_CTL_MASTER_MASK | |
344 | IMG_I2S_OUT_CTL_BCLK_POL_MASK | |
345 | IMG_I2S_OUT_CTL_FRM_CLK_POL_MASK; |
346 | |
347 | chan_control_mask = IMG_I2S_OUT_CHAN_CTL_CLKT_MASK; |
348 | |
349 | ret = pm_runtime_resume_and_get(dev: i2s->dev); |
350 | if (ret < 0) |
351 | return ret; |
352 | |
353 | img_i2s_out_disable(i2s); |
354 | |
355 | reg = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); |
356 | reg = (reg & ~control_mask) | control_set; |
357 | img_i2s_out_writel(i2s, val: reg, IMG_I2S_OUT_CTL); |
358 | |
359 | for (i = 0; i < i2s->active_channels; i++) |
360 | img_i2s_out_ch_disable(i2s, chan: i); |
361 | |
362 | for (i = 0; i < i2s->max_i2s_chan; i++) { |
363 | reg = img_i2s_out_ch_readl(i2s, chan: i, IMG_I2S_OUT_CH_CTL); |
364 | reg = (reg & ~chan_control_mask) | chan_control_set; |
365 | img_i2s_out_ch_writel(i2s, chan: i, val: reg, IMG_I2S_OUT_CH_CTL); |
366 | } |
367 | |
368 | for (i = 0; i < i2s->active_channels; i++) |
369 | img_i2s_out_ch_enable(i2s, chan: i); |
370 | |
371 | img_i2s_out_enable(i2s); |
372 | pm_runtime_put(dev: i2s->dev); |
373 | |
374 | i2s->force_clk_active = force_clk_active; |
375 | |
376 | return 0; |
377 | } |
378 | |
379 | static int img_i2s_out_dai_probe(struct snd_soc_dai *dai) |
380 | { |
381 | struct img_i2s_out *i2s = snd_soc_dai_get_drvdata(dai); |
382 | |
383 | snd_soc_dai_init_dma_data(dai, playback: &i2s->dma_data, NULL); |
384 | |
385 | return 0; |
386 | } |
387 | |
388 | static const struct snd_soc_dai_ops img_i2s_out_dai_ops = { |
389 | .probe = img_i2s_out_dai_probe, |
390 | .trigger = img_i2s_out_trigger, |
391 | .hw_params = img_i2s_out_hw_params, |
392 | .set_fmt = img_i2s_out_set_fmt |
393 | }; |
394 | |
395 | static const struct snd_soc_component_driver img_i2s_out_component = { |
396 | .name = "img-i2s-out" , |
397 | .legacy_dai_naming = 1, |
398 | }; |
399 | |
400 | static int img_i2s_out_dma_prepare_slave_config(struct snd_pcm_substream *st, |
401 | struct snd_pcm_hw_params *params, struct dma_slave_config *sc) |
402 | { |
403 | unsigned int i2s_channels = params_channels(p: params) / 2; |
404 | struct snd_soc_pcm_runtime *rtd = st->private_data; |
405 | struct snd_dmaengine_dai_dma_data *dma_data; |
406 | int ret; |
407 | |
408 | dma_data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), st); |
409 | |
410 | ret = snd_hwparams_to_dma_slave_config(substream: st, params, slave_config: sc); |
411 | if (ret) |
412 | return ret; |
413 | |
414 | sc->dst_addr = dma_data->addr; |
415 | sc->dst_addr_width = dma_data->addr_width; |
416 | sc->dst_maxburst = 4 * i2s_channels; |
417 | |
418 | return 0; |
419 | } |
420 | |
421 | static const struct snd_dmaengine_pcm_config img_i2s_out_dma_config = { |
422 | .prepare_slave_config = img_i2s_out_dma_prepare_slave_config |
423 | }; |
424 | |
425 | static int img_i2s_out_probe(struct platform_device *pdev) |
426 | { |
427 | struct img_i2s_out *i2s; |
428 | struct resource *res; |
429 | void __iomem *base; |
430 | int i, ret; |
431 | unsigned int max_i2s_chan_pow_2; |
432 | u32 reg; |
433 | struct device *dev = &pdev->dev; |
434 | |
435 | i2s = devm_kzalloc(dev: &pdev->dev, size: sizeof(*i2s), GFP_KERNEL); |
436 | if (!i2s) |
437 | return -ENOMEM; |
438 | |
439 | platform_set_drvdata(pdev, data: i2s); |
440 | |
441 | i2s->dev = &pdev->dev; |
442 | |
443 | base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
444 | if (IS_ERR(ptr: base)) |
445 | return PTR_ERR(ptr: base); |
446 | |
447 | i2s->base = base; |
448 | |
449 | if (of_property_read_u32(np: pdev->dev.of_node, propname: "img,i2s-channels" , |
450 | out_value: &i2s->max_i2s_chan)) { |
451 | dev_err(&pdev->dev, "No img,i2s-channels property\n" ); |
452 | return -EINVAL; |
453 | } |
454 | |
455 | max_i2s_chan_pow_2 = 1 << get_count_order(count: i2s->max_i2s_chan); |
456 | |
457 | i2s->channel_base = base + (max_i2s_chan_pow_2 * 0x20); |
458 | |
459 | i2s->rst = devm_reset_control_get_exclusive(dev: &pdev->dev, id: "rst" ); |
460 | if (IS_ERR(ptr: i2s->rst)) |
461 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: i2s->rst), |
462 | fmt: "No top level reset found\n" ); |
463 | |
464 | i2s->clk_sys = devm_clk_get(dev: &pdev->dev, id: "sys" ); |
465 | if (IS_ERR(ptr: i2s->clk_sys)) |
466 | return dev_err_probe(dev, err: PTR_ERR(ptr: i2s->clk_sys), |
467 | fmt: "Failed to acquire clock 'sys'\n" ); |
468 | |
469 | i2s->clk_ref = devm_clk_get(dev: &pdev->dev, id: "ref" ); |
470 | if (IS_ERR(ptr: i2s->clk_ref)) |
471 | return dev_err_probe(dev, err: PTR_ERR(ptr: i2s->clk_ref), |
472 | fmt: "Failed to acquire clock 'ref'\n" ); |
473 | |
474 | i2s->suspend_ch_ctl = devm_kcalloc(dev, |
475 | n: i2s->max_i2s_chan, size: sizeof(*i2s->suspend_ch_ctl), GFP_KERNEL); |
476 | if (!i2s->suspend_ch_ctl) |
477 | return -ENOMEM; |
478 | |
479 | pm_runtime_enable(dev: &pdev->dev); |
480 | if (!pm_runtime_enabled(dev: &pdev->dev)) { |
481 | ret = img_i2s_out_runtime_resume(dev: &pdev->dev); |
482 | if (ret) |
483 | goto err_pm_disable; |
484 | } |
485 | ret = pm_runtime_resume_and_get(dev: &pdev->dev); |
486 | if (ret < 0) |
487 | goto err_suspend; |
488 | |
489 | reg = IMG_I2S_OUT_CTL_FRM_SIZE_MASK; |
490 | img_i2s_out_writel(i2s, val: reg, IMG_I2S_OUT_CTL); |
491 | |
492 | reg = IMG_I2S_OUT_CHAN_CTL_JUST_MASK | |
493 | IMG_I2S_OUT_CHAN_CTL_LT_MASK | |
494 | IMG_I2S_OUT_CHAN_CTL_CH_MASK | |
495 | (8 << IMG_I2S_OUT_CHAN_CTL_FMT_SHIFT); |
496 | |
497 | for (i = 0; i < i2s->max_i2s_chan; i++) |
498 | img_i2s_out_ch_writel(i2s, chan: i, val: reg, IMG_I2S_OUT_CH_CTL); |
499 | |
500 | img_i2s_out_reset(i2s); |
501 | pm_runtime_put(dev: &pdev->dev); |
502 | |
503 | i2s->active_channels = 1; |
504 | i2s->dma_data.addr = res->start + IMG_I2S_OUT_TX_FIFO; |
505 | i2s->dma_data.addr_width = 4; |
506 | i2s->dma_data.maxburst = 4; |
507 | |
508 | i2s->dai_driver.playback.channels_min = 2; |
509 | i2s->dai_driver.playback.channels_max = i2s->max_i2s_chan * 2; |
510 | i2s->dai_driver.playback.rates = SNDRV_PCM_RATE_8000_192000; |
511 | i2s->dai_driver.playback.formats = SNDRV_PCM_FMTBIT_S32_LE; |
512 | i2s->dai_driver.ops = &img_i2s_out_dai_ops; |
513 | |
514 | ret = devm_snd_soc_register_component(dev: &pdev->dev, |
515 | component_driver: &img_i2s_out_component, dai_drv: &i2s->dai_driver, num_dai: 1); |
516 | if (ret) |
517 | goto err_suspend; |
518 | |
519 | ret = devm_snd_dmaengine_pcm_register(dev: &pdev->dev, |
520 | config: &img_i2s_out_dma_config, flags: 0); |
521 | if (ret) |
522 | goto err_suspend; |
523 | |
524 | return 0; |
525 | |
526 | err_suspend: |
527 | if (!pm_runtime_status_suspended(dev: &pdev->dev)) |
528 | img_i2s_out_runtime_suspend(dev: &pdev->dev); |
529 | err_pm_disable: |
530 | pm_runtime_disable(dev: &pdev->dev); |
531 | |
532 | return ret; |
533 | } |
534 | |
535 | static void img_i2s_out_dev_remove(struct platform_device *pdev) |
536 | { |
537 | pm_runtime_disable(dev: &pdev->dev); |
538 | if (!pm_runtime_status_suspended(dev: &pdev->dev)) |
539 | img_i2s_out_runtime_suspend(dev: &pdev->dev); |
540 | } |
541 | |
542 | #ifdef CONFIG_PM_SLEEP |
543 | static int img_i2s_out_suspend(struct device *dev) |
544 | { |
545 | struct img_i2s_out *i2s = dev_get_drvdata(dev); |
546 | int i, ret; |
547 | u32 reg; |
548 | |
549 | if (pm_runtime_status_suspended(dev)) { |
550 | ret = img_i2s_out_runtime_resume(dev); |
551 | if (ret) |
552 | return ret; |
553 | } |
554 | |
555 | for (i = 0; i < i2s->max_i2s_chan; i++) { |
556 | reg = img_i2s_out_ch_readl(i2s, chan: i, IMG_I2S_OUT_CH_CTL); |
557 | i2s->suspend_ch_ctl[i] = reg; |
558 | } |
559 | |
560 | i2s->suspend_ctl = img_i2s_out_readl(i2s, IMG_I2S_OUT_CTL); |
561 | |
562 | img_i2s_out_runtime_suspend(dev); |
563 | |
564 | return 0; |
565 | } |
566 | |
567 | static int img_i2s_out_resume(struct device *dev) |
568 | { |
569 | struct img_i2s_out *i2s = dev_get_drvdata(dev); |
570 | int i, ret; |
571 | u32 reg; |
572 | |
573 | ret = img_i2s_out_runtime_resume(dev); |
574 | if (ret) |
575 | return ret; |
576 | |
577 | for (i = 0; i < i2s->max_i2s_chan; i++) { |
578 | reg = i2s->suspend_ch_ctl[i]; |
579 | img_i2s_out_ch_writel(i2s, chan: i, val: reg, IMG_I2S_OUT_CH_CTL); |
580 | } |
581 | |
582 | img_i2s_out_writel(i2s, val: i2s->suspend_ctl, IMG_I2S_OUT_CTL); |
583 | |
584 | if (pm_runtime_status_suspended(dev)) |
585 | img_i2s_out_runtime_suspend(dev); |
586 | |
587 | return 0; |
588 | } |
589 | #endif |
590 | |
591 | static const struct of_device_id img_i2s_out_of_match[] = { |
592 | { .compatible = "img,i2s-out" }, |
593 | {} |
594 | }; |
595 | MODULE_DEVICE_TABLE(of, img_i2s_out_of_match); |
596 | |
597 | static const struct dev_pm_ops img_i2s_out_pm_ops = { |
598 | SET_RUNTIME_PM_OPS(img_i2s_out_runtime_suspend, |
599 | img_i2s_out_runtime_resume, NULL) |
600 | SET_SYSTEM_SLEEP_PM_OPS(img_i2s_out_suspend, img_i2s_out_resume) |
601 | }; |
602 | |
603 | static struct platform_driver img_i2s_out_driver = { |
604 | .driver = { |
605 | .name = "img-i2s-out" , |
606 | .of_match_table = img_i2s_out_of_match, |
607 | .pm = &img_i2s_out_pm_ops |
608 | }, |
609 | .probe = img_i2s_out_probe, |
610 | .remove_new = img_i2s_out_dev_remove |
611 | }; |
612 | module_platform_driver(img_i2s_out_driver); |
613 | |
614 | MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>" ); |
615 | MODULE_DESCRIPTION("IMG I2S Output Driver" ); |
616 | MODULE_LICENSE("GPL v2" ); |
617 | |