1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * IMG parallel 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_PRL_OUT_TX_FIFO 0 |
27 | |
28 | #define IMG_PRL_OUT_CTL 0x4 |
29 | #define IMG_PRL_OUT_CTL_CH_MASK BIT(4) |
30 | #define IMG_PRL_OUT_CTL_PACKH_MASK BIT(3) |
31 | #define IMG_PRL_OUT_CTL_EDGE_MASK BIT(2) |
32 | #define IMG_PRL_OUT_CTL_ME_MASK BIT(1) |
33 | #define IMG_PRL_OUT_CTL_SRST_MASK BIT(0) |
34 | |
35 | struct img_prl_out { |
36 | void __iomem *base; |
37 | struct clk *clk_sys; |
38 | struct clk *clk_ref; |
39 | struct snd_dmaengine_dai_dma_data dma_data; |
40 | struct device *dev; |
41 | struct reset_control *rst; |
42 | }; |
43 | |
44 | static int img_prl_out_suspend(struct device *dev) |
45 | { |
46 | struct img_prl_out *prl = dev_get_drvdata(dev); |
47 | |
48 | clk_disable_unprepare(clk: prl->clk_ref); |
49 | |
50 | return 0; |
51 | } |
52 | |
53 | static int img_prl_out_resume(struct device *dev) |
54 | { |
55 | struct img_prl_out *prl = dev_get_drvdata(dev); |
56 | int ret; |
57 | |
58 | ret = clk_prepare_enable(clk: prl->clk_ref); |
59 | if (ret) { |
60 | dev_err(dev, "clk_enable failed: %d\n" , ret); |
61 | return ret; |
62 | } |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static inline void img_prl_out_writel(struct img_prl_out *prl, |
68 | u32 val, u32 reg) |
69 | { |
70 | writel(val, addr: prl->base + reg); |
71 | } |
72 | |
73 | static inline u32 img_prl_out_readl(struct img_prl_out *prl, u32 reg) |
74 | { |
75 | return readl(addr: prl->base + reg); |
76 | } |
77 | |
78 | static void img_prl_out_reset(struct img_prl_out *prl) |
79 | { |
80 | u32 ctl; |
81 | |
82 | ctl = img_prl_out_readl(prl, IMG_PRL_OUT_CTL) & |
83 | ~IMG_PRL_OUT_CTL_ME_MASK; |
84 | |
85 | reset_control_assert(rstc: prl->rst); |
86 | reset_control_deassert(rstc: prl->rst); |
87 | |
88 | img_prl_out_writel(prl, val: ctl, IMG_PRL_OUT_CTL); |
89 | } |
90 | |
91 | static int img_prl_out_trigger(struct snd_pcm_substream *substream, int cmd, |
92 | struct snd_soc_dai *dai) |
93 | { |
94 | struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); |
95 | u32 reg; |
96 | |
97 | switch (cmd) { |
98 | case SNDRV_PCM_TRIGGER_START: |
99 | case SNDRV_PCM_TRIGGER_RESUME: |
100 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
101 | reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL); |
102 | reg |= IMG_PRL_OUT_CTL_ME_MASK; |
103 | img_prl_out_writel(prl, val: reg, IMG_PRL_OUT_CTL); |
104 | break; |
105 | case SNDRV_PCM_TRIGGER_STOP: |
106 | case SNDRV_PCM_TRIGGER_SUSPEND: |
107 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
108 | img_prl_out_reset(prl); |
109 | break; |
110 | default: |
111 | return -EINVAL; |
112 | } |
113 | |
114 | return 0; |
115 | } |
116 | |
117 | static int img_prl_out_hw_params(struct snd_pcm_substream *substream, |
118 | struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) |
119 | { |
120 | struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); |
121 | unsigned int rate, channels; |
122 | u32 reg, control_set = 0; |
123 | |
124 | rate = params_rate(p: params); |
125 | channels = params_channels(p: params); |
126 | |
127 | switch (params_format(p: params)) { |
128 | case SNDRV_PCM_FORMAT_S32_LE: |
129 | control_set |= IMG_PRL_OUT_CTL_PACKH_MASK; |
130 | break; |
131 | case SNDRV_PCM_FORMAT_S24_LE: |
132 | break; |
133 | default: |
134 | return -EINVAL; |
135 | } |
136 | |
137 | if (channels != 2) |
138 | return -EINVAL; |
139 | |
140 | clk_set_rate(clk: prl->clk_ref, rate: rate * 256); |
141 | |
142 | reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL); |
143 | reg = (reg & ~IMG_PRL_OUT_CTL_PACKH_MASK) | control_set; |
144 | img_prl_out_writel(prl, val: reg, IMG_PRL_OUT_CTL); |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | static int img_prl_out_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) |
150 | { |
151 | struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); |
152 | u32 reg, control_set = 0; |
153 | int ret; |
154 | |
155 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
156 | case SND_SOC_DAIFMT_NB_NF: |
157 | break; |
158 | case SND_SOC_DAIFMT_NB_IF: |
159 | control_set |= IMG_PRL_OUT_CTL_EDGE_MASK; |
160 | break; |
161 | default: |
162 | return -EINVAL; |
163 | } |
164 | |
165 | ret = pm_runtime_resume_and_get(dev: prl->dev); |
166 | if (ret < 0) |
167 | return ret; |
168 | |
169 | reg = img_prl_out_readl(prl, IMG_PRL_OUT_CTL); |
170 | reg = (reg & ~IMG_PRL_OUT_CTL_EDGE_MASK) | control_set; |
171 | img_prl_out_writel(prl, val: reg, IMG_PRL_OUT_CTL); |
172 | pm_runtime_put(dev: prl->dev); |
173 | |
174 | return 0; |
175 | } |
176 | |
177 | static int img_prl_out_dai_probe(struct snd_soc_dai *dai) |
178 | { |
179 | struct img_prl_out *prl = snd_soc_dai_get_drvdata(dai); |
180 | |
181 | snd_soc_dai_init_dma_data(dai, playback: &prl->dma_data, NULL); |
182 | |
183 | return 0; |
184 | } |
185 | |
186 | static const struct snd_soc_dai_ops img_prl_out_dai_ops = { |
187 | .probe = img_prl_out_dai_probe, |
188 | .trigger = img_prl_out_trigger, |
189 | .hw_params = img_prl_out_hw_params, |
190 | .set_fmt = img_prl_out_set_fmt |
191 | }; |
192 | |
193 | static struct snd_soc_dai_driver img_prl_out_dai = { |
194 | .playback = { |
195 | .channels_min = 2, |
196 | .channels_max = 2, |
197 | .rates = SNDRV_PCM_RATE_8000_192000, |
198 | .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S24_LE |
199 | }, |
200 | .ops = &img_prl_out_dai_ops |
201 | }; |
202 | |
203 | static const struct snd_soc_component_driver img_prl_out_component = { |
204 | .name = "img-prl-out" , |
205 | .legacy_dai_naming = 1, |
206 | }; |
207 | |
208 | static int img_prl_out_probe(struct platform_device *pdev) |
209 | { |
210 | struct img_prl_out *prl; |
211 | struct resource *res; |
212 | void __iomem *base; |
213 | int ret; |
214 | struct device *dev = &pdev->dev; |
215 | |
216 | prl = devm_kzalloc(dev: &pdev->dev, size: sizeof(*prl), GFP_KERNEL); |
217 | if (!prl) |
218 | return -ENOMEM; |
219 | |
220 | platform_set_drvdata(pdev, data: prl); |
221 | |
222 | prl->dev = &pdev->dev; |
223 | |
224 | base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
225 | if (IS_ERR(ptr: base)) |
226 | return PTR_ERR(ptr: base); |
227 | |
228 | prl->base = base; |
229 | |
230 | prl->rst = devm_reset_control_get_exclusive(dev: &pdev->dev, id: "rst" ); |
231 | if (IS_ERR(ptr: prl->rst)) |
232 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: prl->rst), |
233 | fmt: "No top level reset found\n" ); |
234 | |
235 | prl->clk_sys = devm_clk_get(dev: &pdev->dev, id: "sys" ); |
236 | if (IS_ERR(ptr: prl->clk_sys)) |
237 | return dev_err_probe(dev, err: PTR_ERR(ptr: prl->clk_sys), |
238 | fmt: "Failed to acquire clock 'sys'\n" ); |
239 | |
240 | prl->clk_ref = devm_clk_get(dev: &pdev->dev, id: "ref" ); |
241 | if (IS_ERR(ptr: prl->clk_ref)) |
242 | return dev_err_probe(dev, err: PTR_ERR(ptr: prl->clk_ref), |
243 | fmt: "Failed to acquire clock 'ref'\n" ); |
244 | |
245 | ret = clk_prepare_enable(clk: prl->clk_sys); |
246 | if (ret) |
247 | return ret; |
248 | |
249 | img_prl_out_writel(prl, IMG_PRL_OUT_CTL_EDGE_MASK, IMG_PRL_OUT_CTL); |
250 | img_prl_out_reset(prl); |
251 | |
252 | pm_runtime_enable(dev: &pdev->dev); |
253 | if (!pm_runtime_enabled(dev: &pdev->dev)) { |
254 | ret = img_prl_out_resume(dev: &pdev->dev); |
255 | if (ret) |
256 | goto err_pm_disable; |
257 | } |
258 | |
259 | prl->dma_data.addr = res->start + IMG_PRL_OUT_TX_FIFO; |
260 | prl->dma_data.addr_width = 4; |
261 | prl->dma_data.maxburst = 4; |
262 | |
263 | ret = devm_snd_soc_register_component(dev: &pdev->dev, |
264 | component_driver: &img_prl_out_component, |
265 | dai_drv: &img_prl_out_dai, num_dai: 1); |
266 | if (ret) |
267 | goto err_suspend; |
268 | |
269 | ret = devm_snd_dmaengine_pcm_register(dev: &pdev->dev, NULL, flags: 0); |
270 | if (ret) |
271 | goto err_suspend; |
272 | |
273 | return 0; |
274 | |
275 | err_suspend: |
276 | if (!pm_runtime_status_suspended(dev: &pdev->dev)) |
277 | img_prl_out_suspend(dev: &pdev->dev); |
278 | err_pm_disable: |
279 | pm_runtime_disable(dev: &pdev->dev); |
280 | clk_disable_unprepare(clk: prl->clk_sys); |
281 | |
282 | return ret; |
283 | } |
284 | |
285 | static void img_prl_out_dev_remove(struct platform_device *pdev) |
286 | { |
287 | struct img_prl_out *prl = platform_get_drvdata(pdev); |
288 | |
289 | pm_runtime_disable(dev: &pdev->dev); |
290 | if (!pm_runtime_status_suspended(dev: &pdev->dev)) |
291 | img_prl_out_suspend(dev: &pdev->dev); |
292 | |
293 | clk_disable_unprepare(clk: prl->clk_sys); |
294 | } |
295 | |
296 | static const struct of_device_id img_prl_out_of_match[] = { |
297 | { .compatible = "img,parallel-out" }, |
298 | {} |
299 | }; |
300 | MODULE_DEVICE_TABLE(of, img_prl_out_of_match); |
301 | |
302 | static const struct dev_pm_ops img_prl_out_pm_ops = { |
303 | SET_RUNTIME_PM_OPS(img_prl_out_suspend, |
304 | img_prl_out_resume, NULL) |
305 | }; |
306 | |
307 | static struct platform_driver img_prl_out_driver = { |
308 | .driver = { |
309 | .name = "img-parallel-out" , |
310 | .of_match_table = img_prl_out_of_match, |
311 | .pm = &img_prl_out_pm_ops |
312 | }, |
313 | .probe = img_prl_out_probe, |
314 | .remove_new = img_prl_out_dev_remove |
315 | }; |
316 | module_platform_driver(img_prl_out_driver); |
317 | |
318 | MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>" ); |
319 | MODULE_DESCRIPTION("IMG Parallel Output Driver" ); |
320 | MODULE_LICENSE("GPL v2" ); |
321 | |