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