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