1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * IMG I2S input 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_IN_RX_FIFO 0x0 |
27 | |
28 | #define IMG_I2S_IN_CTL 0x4 |
29 | #define IMG_I2S_IN_CTL_ACTIVE_CHAN_MASK 0xfffffffc |
30 | #define IMG_I2S_IN_CTL_ACTIVE_CH_SHIFT 2 |
31 | #define IMG_I2S_IN_CTL_16PACK_MASK BIT(1) |
32 | #define IMG_I2S_IN_CTL_ME_MASK BIT(0) |
33 | |
34 | #define IMG_I2S_IN_CH_CTL 0x4 |
35 | #define IMG_I2S_IN_CH_CTL_CCDEL_MASK 0x38000 |
36 | #define IMG_I2S_IN_CH_CTL_CCDEL_SHIFT 15 |
37 | #define IMG_I2S_IN_CH_CTL_FEN_MASK BIT(14) |
38 | #define IMG_I2S_IN_CH_CTL_FMODE_MASK BIT(13) |
39 | #define IMG_I2S_IN_CH_CTL_16PACK_MASK BIT(12) |
40 | #define IMG_I2S_IN_CH_CTL_JUST_MASK BIT(10) |
41 | #define IMG_I2S_IN_CH_CTL_PACKH_MASK BIT(9) |
42 | #define IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK BIT(8) |
43 | #define IMG_I2S_IN_CH_CTL_BLKP_MASK BIT(7) |
44 | #define IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK BIT(6) |
45 | #define IMG_I2S_IN_CH_CTL_LRD_MASK BIT(3) |
46 | #define IMG_I2S_IN_CH_CTL_FW_MASK BIT(2) |
47 | #define IMG_I2S_IN_CH_CTL_SW_MASK BIT(1) |
48 | #define IMG_I2S_IN_CH_CTL_ME_MASK BIT(0) |
49 | |
50 | #define IMG_I2S_IN_CH_STRIDE 0x20 |
51 | |
52 | struct img_i2s_in { |
53 | void __iomem *base; |
54 | struct clk *clk_sys; |
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 | unsigned int active_channels; |
60 | struct snd_soc_dai_driver dai_driver; |
61 | u32 suspend_ctl; |
62 | u32 *suspend_ch_ctl; |
63 | }; |
64 | |
65 | static int img_i2s_in_runtime_suspend(struct device *dev) |
66 | { |
67 | struct img_i2s_in *i2s = dev_get_drvdata(dev); |
68 | |
69 | clk_disable_unprepare(clk: i2s->clk_sys); |
70 | |
71 | return 0; |
72 | } |
73 | |
74 | static int img_i2s_in_runtime_resume(struct device *dev) |
75 | { |
76 | struct img_i2s_in *i2s = dev_get_drvdata(dev); |
77 | int ret; |
78 | |
79 | ret = clk_prepare_enable(clk: i2s->clk_sys); |
80 | if (ret) { |
81 | dev_err(dev, "Unable to enable sys clock\n" ); |
82 | return ret; |
83 | } |
84 | |
85 | return 0; |
86 | } |
87 | |
88 | static inline void img_i2s_in_writel(struct img_i2s_in *i2s, u32 val, u32 reg) |
89 | { |
90 | writel(val, addr: i2s->base + reg); |
91 | } |
92 | |
93 | static inline u32 img_i2s_in_readl(struct img_i2s_in *i2s, u32 reg) |
94 | { |
95 | return readl(addr: i2s->base + reg); |
96 | } |
97 | |
98 | static inline void img_i2s_in_ch_writel(struct img_i2s_in *i2s, u32 chan, |
99 | u32 val, u32 reg) |
100 | { |
101 | writel(val, addr: i2s->channel_base + (chan * IMG_I2S_IN_CH_STRIDE) + reg); |
102 | } |
103 | |
104 | static inline u32 img_i2s_in_ch_readl(struct img_i2s_in *i2s, u32 chan, |
105 | u32 reg) |
106 | { |
107 | return readl(addr: i2s->channel_base + (chan * IMG_I2S_IN_CH_STRIDE) + reg); |
108 | } |
109 | |
110 | static inline void img_i2s_in_ch_disable(struct img_i2s_in *i2s, u32 chan) |
111 | { |
112 | u32 reg; |
113 | |
114 | reg = img_i2s_in_ch_readl(i2s, chan, IMG_I2S_IN_CH_CTL); |
115 | reg &= ~IMG_I2S_IN_CH_CTL_ME_MASK; |
116 | img_i2s_in_ch_writel(i2s, chan, val: reg, IMG_I2S_IN_CH_CTL); |
117 | } |
118 | |
119 | static inline void img_i2s_in_ch_enable(struct img_i2s_in *i2s, u32 chan) |
120 | { |
121 | u32 reg; |
122 | |
123 | reg = img_i2s_in_ch_readl(i2s, chan, IMG_I2S_IN_CH_CTL); |
124 | reg |= IMG_I2S_IN_CH_CTL_ME_MASK; |
125 | img_i2s_in_ch_writel(i2s, chan, val: reg, IMG_I2S_IN_CH_CTL); |
126 | } |
127 | |
128 | static inline void img_i2s_in_disable(struct img_i2s_in *i2s) |
129 | { |
130 | u32 reg; |
131 | |
132 | reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); |
133 | reg &= ~IMG_I2S_IN_CTL_ME_MASK; |
134 | img_i2s_in_writel(i2s, val: reg, IMG_I2S_IN_CTL); |
135 | } |
136 | |
137 | static inline void img_i2s_in_enable(struct img_i2s_in *i2s) |
138 | { |
139 | u32 reg; |
140 | |
141 | reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); |
142 | reg |= IMG_I2S_IN_CTL_ME_MASK; |
143 | img_i2s_in_writel(i2s, val: reg, IMG_I2S_IN_CTL); |
144 | } |
145 | |
146 | static inline void img_i2s_in_flush(struct img_i2s_in *i2s) |
147 | { |
148 | int i; |
149 | u32 reg; |
150 | |
151 | for (i = 0; i < i2s->active_channels; i++) { |
152 | reg = img_i2s_in_ch_readl(i2s, chan: i, IMG_I2S_IN_CH_CTL); |
153 | reg |= IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK; |
154 | img_i2s_in_ch_writel(i2s, chan: i, val: reg, IMG_I2S_IN_CH_CTL); |
155 | reg &= ~IMG_I2S_IN_CH_CTL_FIFO_FLUSH_MASK; |
156 | img_i2s_in_ch_writel(i2s, chan: i, val: reg, IMG_I2S_IN_CH_CTL); |
157 | } |
158 | } |
159 | |
160 | static int img_i2s_in_trigger(struct snd_pcm_substream *substream, int cmd, |
161 | struct snd_soc_dai *dai) |
162 | { |
163 | struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); |
164 | |
165 | switch (cmd) { |
166 | case SNDRV_PCM_TRIGGER_START: |
167 | case SNDRV_PCM_TRIGGER_RESUME: |
168 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
169 | img_i2s_in_enable(i2s); |
170 | break; |
171 | |
172 | case SNDRV_PCM_TRIGGER_STOP: |
173 | case SNDRV_PCM_TRIGGER_SUSPEND: |
174 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
175 | img_i2s_in_disable(i2s); |
176 | break; |
177 | default: |
178 | return -EINVAL; |
179 | } |
180 | |
181 | return 0; |
182 | } |
183 | |
184 | static int img_i2s_in_check_rate(struct img_i2s_in *i2s, |
185 | unsigned int sample_rate, unsigned int frame_size, |
186 | unsigned int *bclk_filter_enable, |
187 | unsigned int *bclk_filter_value) |
188 | { |
189 | unsigned int bclk_freq, cur_freq; |
190 | |
191 | bclk_freq = sample_rate * frame_size; |
192 | |
193 | cur_freq = clk_get_rate(clk: i2s->clk_sys); |
194 | |
195 | if (cur_freq >= bclk_freq * 8) { |
196 | *bclk_filter_enable = 1; |
197 | *bclk_filter_value = 0; |
198 | } else if (cur_freq >= bclk_freq * 7) { |
199 | *bclk_filter_enable = 1; |
200 | *bclk_filter_value = 1; |
201 | } else if (cur_freq >= bclk_freq * 6) { |
202 | *bclk_filter_enable = 0; |
203 | *bclk_filter_value = 0; |
204 | } else { |
205 | dev_err(i2s->dev, |
206 | "Sys clock rate %u insufficient for sample rate %u\n" , |
207 | cur_freq, sample_rate); |
208 | return -EINVAL; |
209 | } |
210 | |
211 | return 0; |
212 | } |
213 | |
214 | static int img_i2s_in_hw_params(struct snd_pcm_substream *substream, |
215 | struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) |
216 | { |
217 | struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); |
218 | unsigned int rate, channels, i2s_channels, frame_size; |
219 | unsigned int bclk_filter_enable, bclk_filter_value; |
220 | int i, ret = 0; |
221 | u32 reg, control_mask, chan_control_mask; |
222 | u32 control_set = 0, chan_control_set = 0; |
223 | snd_pcm_format_t format; |
224 | |
225 | rate = params_rate(p: params); |
226 | format = params_format(p: params); |
227 | channels = params_channels(p: params); |
228 | i2s_channels = channels / 2; |
229 | |
230 | switch (format) { |
231 | case SNDRV_PCM_FORMAT_S32_LE: |
232 | frame_size = 64; |
233 | chan_control_set |= IMG_I2S_IN_CH_CTL_SW_MASK; |
234 | chan_control_set |= IMG_I2S_IN_CH_CTL_FW_MASK; |
235 | chan_control_set |= IMG_I2S_IN_CH_CTL_PACKH_MASK; |
236 | break; |
237 | case SNDRV_PCM_FORMAT_S24_LE: |
238 | frame_size = 64; |
239 | chan_control_set |= IMG_I2S_IN_CH_CTL_SW_MASK; |
240 | chan_control_set |= IMG_I2S_IN_CH_CTL_FW_MASK; |
241 | break; |
242 | case SNDRV_PCM_FORMAT_S16_LE: |
243 | frame_size = 32; |
244 | control_set |= IMG_I2S_IN_CTL_16PACK_MASK; |
245 | chan_control_set |= IMG_I2S_IN_CH_CTL_16PACK_MASK; |
246 | break; |
247 | default: |
248 | return -EINVAL; |
249 | } |
250 | |
251 | if ((channels < 2) || |
252 | (channels > (i2s->max_i2s_chan * 2)) || |
253 | (channels % 2)) |
254 | return -EINVAL; |
255 | |
256 | control_set |= ((i2s_channels - 1) << IMG_I2S_IN_CTL_ACTIVE_CH_SHIFT); |
257 | |
258 | ret = img_i2s_in_check_rate(i2s, sample_rate: rate, frame_size, |
259 | bclk_filter_enable: &bclk_filter_enable, bclk_filter_value: &bclk_filter_value); |
260 | if (ret < 0) |
261 | return ret; |
262 | |
263 | if (bclk_filter_enable) |
264 | chan_control_set |= IMG_I2S_IN_CH_CTL_FEN_MASK; |
265 | |
266 | if (bclk_filter_value) |
267 | chan_control_set |= IMG_I2S_IN_CH_CTL_FMODE_MASK; |
268 | |
269 | control_mask = IMG_I2S_IN_CTL_16PACK_MASK | |
270 | IMG_I2S_IN_CTL_ACTIVE_CHAN_MASK; |
271 | |
272 | chan_control_mask = IMG_I2S_IN_CH_CTL_16PACK_MASK | |
273 | IMG_I2S_IN_CH_CTL_FEN_MASK | |
274 | IMG_I2S_IN_CH_CTL_FMODE_MASK | |
275 | IMG_I2S_IN_CH_CTL_SW_MASK | |
276 | IMG_I2S_IN_CH_CTL_FW_MASK | |
277 | IMG_I2S_IN_CH_CTL_PACKH_MASK; |
278 | |
279 | reg = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); |
280 | reg = (reg & ~control_mask) | control_set; |
281 | img_i2s_in_writel(i2s, val: reg, IMG_I2S_IN_CTL); |
282 | |
283 | for (i = 0; i < i2s->active_channels; i++) |
284 | img_i2s_in_ch_disable(i2s, chan: i); |
285 | |
286 | for (i = 0; i < i2s->max_i2s_chan; i++) { |
287 | reg = img_i2s_in_ch_readl(i2s, chan: i, IMG_I2S_IN_CH_CTL); |
288 | reg = (reg & ~chan_control_mask) | chan_control_set; |
289 | img_i2s_in_ch_writel(i2s, chan: i, val: reg, IMG_I2S_IN_CH_CTL); |
290 | } |
291 | |
292 | i2s->active_channels = i2s_channels; |
293 | |
294 | img_i2s_in_flush(i2s); |
295 | |
296 | for (i = 0; i < i2s->active_channels; i++) |
297 | img_i2s_in_ch_enable(i2s, chan: i); |
298 | |
299 | return 0; |
300 | } |
301 | |
302 | static int img_i2s_in_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
303 | { |
304 | struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); |
305 | int i, ret; |
306 | u32 chan_control_mask, lrd_set = 0, blkp_set = 0, chan_control_set = 0; |
307 | u32 reg; |
308 | |
309 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
310 | case SND_SOC_DAIFMT_NB_NF: |
311 | lrd_set |= IMG_I2S_IN_CH_CTL_LRD_MASK; |
312 | break; |
313 | case SND_SOC_DAIFMT_NB_IF: |
314 | break; |
315 | case SND_SOC_DAIFMT_IB_NF: |
316 | lrd_set |= IMG_I2S_IN_CH_CTL_LRD_MASK; |
317 | blkp_set |= IMG_I2S_IN_CH_CTL_BLKP_MASK; |
318 | break; |
319 | case SND_SOC_DAIFMT_IB_IF: |
320 | blkp_set |= IMG_I2S_IN_CH_CTL_BLKP_MASK; |
321 | break; |
322 | default: |
323 | return -EINVAL; |
324 | } |
325 | |
326 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
327 | case SND_SOC_DAIFMT_I2S: |
328 | chan_control_set |= IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK; |
329 | break; |
330 | case SND_SOC_DAIFMT_LEFT_J: |
331 | break; |
332 | default: |
333 | return -EINVAL; |
334 | } |
335 | |
336 | switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
337 | case SND_SOC_DAIFMT_BC_FC: |
338 | break; |
339 | default: |
340 | return -EINVAL; |
341 | } |
342 | |
343 | chan_control_mask = IMG_I2S_IN_CH_CTL_CLK_TRANS_MASK; |
344 | |
345 | ret = pm_runtime_resume_and_get(dev: i2s->dev); |
346 | if (ret < 0) |
347 | return ret; |
348 | |
349 | for (i = 0; i < i2s->active_channels; i++) |
350 | img_i2s_in_ch_disable(i2s, chan: i); |
351 | |
352 | /* |
353 | * BLKP and LRD must be set during separate register writes |
354 | */ |
355 | for (i = 0; i < i2s->max_i2s_chan; i++) { |
356 | reg = img_i2s_in_ch_readl(i2s, chan: i, IMG_I2S_IN_CH_CTL); |
357 | reg = (reg & ~chan_control_mask) | chan_control_set; |
358 | img_i2s_in_ch_writel(i2s, chan: i, val: reg, IMG_I2S_IN_CH_CTL); |
359 | reg = (reg & ~IMG_I2S_IN_CH_CTL_BLKP_MASK) | blkp_set; |
360 | img_i2s_in_ch_writel(i2s, chan: i, val: reg, IMG_I2S_IN_CH_CTL); |
361 | reg = (reg & ~IMG_I2S_IN_CH_CTL_LRD_MASK) | lrd_set; |
362 | img_i2s_in_ch_writel(i2s, chan: i, val: reg, IMG_I2S_IN_CH_CTL); |
363 | } |
364 | |
365 | for (i = 0; i < i2s->active_channels; i++) |
366 | img_i2s_in_ch_enable(i2s, chan: i); |
367 | |
368 | pm_runtime_put(dev: i2s->dev); |
369 | |
370 | return 0; |
371 | } |
372 | |
373 | static int img_i2s_in_dai_probe(struct snd_soc_dai *dai) |
374 | { |
375 | struct img_i2s_in *i2s = snd_soc_dai_get_drvdata(dai); |
376 | |
377 | snd_soc_dai_init_dma_data(dai, NULL, capture: &i2s->dma_data); |
378 | |
379 | return 0; |
380 | } |
381 | |
382 | static const struct snd_soc_dai_ops img_i2s_in_dai_ops = { |
383 | .probe = img_i2s_in_dai_probe, |
384 | .trigger = img_i2s_in_trigger, |
385 | .hw_params = img_i2s_in_hw_params, |
386 | .set_fmt = img_i2s_in_set_fmt |
387 | }; |
388 | |
389 | static const struct snd_soc_component_driver img_i2s_in_component = { |
390 | .name = "img-i2s-in" , |
391 | .legacy_dai_naming = 1, |
392 | }; |
393 | |
394 | static int img_i2s_in_dma_prepare_slave_config(struct snd_pcm_substream *st, |
395 | struct snd_pcm_hw_params *params, struct dma_slave_config *sc) |
396 | { |
397 | unsigned int i2s_channels = params_channels(p: params) / 2; |
398 | struct snd_soc_pcm_runtime *rtd = st->private_data; |
399 | struct snd_dmaengine_dai_dma_data *dma_data; |
400 | int ret; |
401 | |
402 | dma_data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), st); |
403 | |
404 | ret = snd_hwparams_to_dma_slave_config(substream: st, params, slave_config: sc); |
405 | if (ret) |
406 | return ret; |
407 | |
408 | sc->src_addr = dma_data->addr; |
409 | sc->src_addr_width = dma_data->addr_width; |
410 | sc->src_maxburst = 4 * i2s_channels; |
411 | |
412 | return 0; |
413 | } |
414 | |
415 | static const struct snd_dmaengine_pcm_config img_i2s_in_dma_config = { |
416 | .prepare_slave_config = img_i2s_in_dma_prepare_slave_config |
417 | }; |
418 | |
419 | static int img_i2s_in_probe(struct platform_device *pdev) |
420 | { |
421 | struct img_i2s_in *i2s; |
422 | struct resource *res; |
423 | void __iomem *base; |
424 | int ret, i; |
425 | struct reset_control *rst; |
426 | unsigned int max_i2s_chan_pow_2; |
427 | struct device *dev = &pdev->dev; |
428 | |
429 | i2s = devm_kzalloc(dev, size: sizeof(*i2s), GFP_KERNEL); |
430 | if (!i2s) |
431 | return -ENOMEM; |
432 | |
433 | platform_set_drvdata(pdev, data: i2s); |
434 | |
435 | i2s->dev = dev; |
436 | |
437 | base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
438 | if (IS_ERR(ptr: base)) |
439 | return PTR_ERR(ptr: base); |
440 | |
441 | i2s->base = base; |
442 | |
443 | if (of_property_read_u32(np: pdev->dev.of_node, propname: "img,i2s-channels" , |
444 | out_value: &i2s->max_i2s_chan)) { |
445 | dev_err(dev, "No img,i2s-channels property\n" ); |
446 | return -EINVAL; |
447 | } |
448 | |
449 | max_i2s_chan_pow_2 = 1 << get_count_order(count: i2s->max_i2s_chan); |
450 | |
451 | i2s->channel_base = base + (max_i2s_chan_pow_2 * 0x20); |
452 | |
453 | i2s->clk_sys = devm_clk_get(dev, id: "sys" ); |
454 | if (IS_ERR(ptr: i2s->clk_sys)) |
455 | return dev_err_probe(dev, err: PTR_ERR(ptr: i2s->clk_sys), |
456 | fmt: "Failed to acquire clock 'sys'\n" ); |
457 | |
458 | pm_runtime_enable(dev: &pdev->dev); |
459 | if (!pm_runtime_enabled(dev: &pdev->dev)) { |
460 | ret = img_i2s_in_runtime_resume(dev: &pdev->dev); |
461 | if (ret) |
462 | goto err_pm_disable; |
463 | } |
464 | ret = pm_runtime_resume_and_get(dev: &pdev->dev); |
465 | if (ret < 0) |
466 | goto err_suspend; |
467 | |
468 | i2s->active_channels = 1; |
469 | i2s->dma_data.addr = res->start + IMG_I2S_IN_RX_FIFO; |
470 | i2s->dma_data.addr_width = 4; |
471 | |
472 | i2s->dai_driver.capture.channels_min = 2; |
473 | i2s->dai_driver.capture.channels_max = i2s->max_i2s_chan * 2; |
474 | i2s->dai_driver.capture.rates = SNDRV_PCM_RATE_8000_192000; |
475 | i2s->dai_driver.capture.formats = SNDRV_PCM_FMTBIT_S32_LE | |
476 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE; |
477 | i2s->dai_driver.ops = &img_i2s_in_dai_ops; |
478 | |
479 | rst = devm_reset_control_get_exclusive(dev, id: "rst" ); |
480 | if (IS_ERR(ptr: rst)) { |
481 | if (PTR_ERR(ptr: rst) == -EPROBE_DEFER) { |
482 | ret = -EPROBE_DEFER; |
483 | pm_runtime_put(dev: &pdev->dev); |
484 | goto err_suspend; |
485 | } |
486 | |
487 | dev_dbg(dev, "No top level reset found\n" ); |
488 | |
489 | img_i2s_in_disable(i2s); |
490 | |
491 | for (i = 0; i < i2s->max_i2s_chan; i++) |
492 | img_i2s_in_ch_disable(i2s, chan: i); |
493 | } else { |
494 | reset_control_assert(rstc: rst); |
495 | reset_control_deassert(rstc: rst); |
496 | } |
497 | |
498 | img_i2s_in_writel(i2s, val: 0, IMG_I2S_IN_CTL); |
499 | |
500 | for (i = 0; i < i2s->max_i2s_chan; i++) |
501 | img_i2s_in_ch_writel(i2s, chan: i, |
502 | val: (4 << IMG_I2S_IN_CH_CTL_CCDEL_SHIFT) | |
503 | IMG_I2S_IN_CH_CTL_JUST_MASK | |
504 | IMG_I2S_IN_CH_CTL_FW_MASK, IMG_I2S_IN_CH_CTL); |
505 | |
506 | pm_runtime_put(dev: &pdev->dev); |
507 | |
508 | i2s->suspend_ch_ctl = devm_kcalloc(dev, |
509 | n: i2s->max_i2s_chan, size: sizeof(*i2s->suspend_ch_ctl), GFP_KERNEL); |
510 | if (!i2s->suspend_ch_ctl) { |
511 | ret = -ENOMEM; |
512 | goto err_suspend; |
513 | } |
514 | |
515 | ret = devm_snd_soc_register_component(dev, component_driver: &img_i2s_in_component, |
516 | dai_drv: &i2s->dai_driver, num_dai: 1); |
517 | if (ret) |
518 | goto err_suspend; |
519 | |
520 | ret = devm_snd_dmaengine_pcm_register(dev, config: &img_i2s_in_dma_config, flags: 0); |
521 | if (ret) |
522 | goto err_suspend; |
523 | |
524 | return 0; |
525 | |
526 | err_suspend: |
527 | if (!pm_runtime_enabled(dev: &pdev->dev)) |
528 | img_i2s_in_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_in_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_in_runtime_suspend(dev: &pdev->dev); |
540 | } |
541 | |
542 | #ifdef CONFIG_PM_SLEEP |
543 | static int img_i2s_in_suspend(struct device *dev) |
544 | { |
545 | struct img_i2s_in *i2s = dev_get_drvdata(dev); |
546 | int i, ret; |
547 | u32 reg; |
548 | |
549 | if (pm_runtime_status_suspended(dev)) { |
550 | ret = img_i2s_in_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_in_ch_readl(i2s, chan: i, IMG_I2S_IN_CH_CTL); |
557 | i2s->suspend_ch_ctl[i] = reg; |
558 | } |
559 | |
560 | i2s->suspend_ctl = img_i2s_in_readl(i2s, IMG_I2S_IN_CTL); |
561 | |
562 | img_i2s_in_runtime_suspend(dev); |
563 | |
564 | return 0; |
565 | } |
566 | |
567 | static int img_i2s_in_resume(struct device *dev) |
568 | { |
569 | struct img_i2s_in *i2s = dev_get_drvdata(dev); |
570 | int i, ret; |
571 | u32 reg; |
572 | |
573 | ret = img_i2s_in_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_in_ch_writel(i2s, chan: i, val: reg, IMG_I2S_IN_CH_CTL); |
580 | } |
581 | |
582 | img_i2s_in_writel(i2s, val: i2s->suspend_ctl, IMG_I2S_IN_CTL); |
583 | |
584 | if (pm_runtime_status_suspended(dev)) |
585 | img_i2s_in_runtime_suspend(dev); |
586 | |
587 | return 0; |
588 | } |
589 | #endif |
590 | |
591 | static const struct of_device_id img_i2s_in_of_match[] = { |
592 | { .compatible = "img,i2s-in" }, |
593 | {} |
594 | }; |
595 | MODULE_DEVICE_TABLE(of, img_i2s_in_of_match); |
596 | |
597 | static const struct dev_pm_ops img_i2s_in_pm_ops = { |
598 | SET_RUNTIME_PM_OPS(img_i2s_in_runtime_suspend, |
599 | img_i2s_in_runtime_resume, NULL) |
600 | SET_SYSTEM_SLEEP_PM_OPS(img_i2s_in_suspend, img_i2s_in_resume) |
601 | }; |
602 | |
603 | static struct platform_driver img_i2s_in_driver = { |
604 | .driver = { |
605 | .name = "img-i2s-in" , |
606 | .of_match_table = img_i2s_in_of_match, |
607 | .pm = &img_i2s_in_pm_ops |
608 | }, |
609 | .probe = img_i2s_in_probe, |
610 | .remove_new = img_i2s_in_dev_remove |
611 | }; |
612 | module_platform_driver(img_i2s_in_driver); |
613 | |
614 | MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>" ); |
615 | MODULE_DESCRIPTION("IMG I2S Input Driver" ); |
616 | MODULE_LICENSE("GPL v2" ); |
617 | |