xref: /linux/sound/soc/meson/gx-formatter.c (revision e5c91aac491def6ab3f90c4cc246e3fcb0f8f058)
1*a295be8dSValerio Setti // SPDX-License-Identifier: (GPL-2.0 OR MIT)
2*a295be8dSValerio Setti //
3*a295be8dSValerio Setti // Copyright (c) 2026 BayLibre, SAS.
4*a295be8dSValerio Setti // Author: Valerio Setti <vsetti@baylibre.com>
5*a295be8dSValerio Setti 
6*a295be8dSValerio Setti #include <linux/module.h>
7*a295be8dSValerio Setti #include <linux/of_platform.h>
8*a295be8dSValerio Setti #include <linux/regmap.h>
9*a295be8dSValerio Setti #include <sound/soc.h>
10*a295be8dSValerio Setti 
11*a295be8dSValerio Setti #include "gx-formatter.h"
12*a295be8dSValerio Setti 
13*a295be8dSValerio Setti struct gx_formatter {
14*a295be8dSValerio Setti 	struct list_head list;
15*a295be8dSValerio Setti 	struct gx_stream *stream;
16*a295be8dSValerio Setti 	const struct gx_formatter_driver *drv;
17*a295be8dSValerio Setti 	bool enabled;
18*a295be8dSValerio Setti 	struct regmap *map;
19*a295be8dSValerio Setti };
20*a295be8dSValerio Setti 
21*a295be8dSValerio Setti static int gx_formatter_enable(struct gx_formatter *formatter)
22*a295be8dSValerio Setti {
23*a295be8dSValerio Setti 	int ret;
24*a295be8dSValerio Setti 
25*a295be8dSValerio Setti 	/* Do nothing if the formatter is already enabled */
26*a295be8dSValerio Setti 	if (formatter->enabled)
27*a295be8dSValerio Setti 		return 0;
28*a295be8dSValerio Setti 
29*a295be8dSValerio Setti 	/* Setup the stream parameter in the formatter */
30*a295be8dSValerio Setti 	if (formatter->drv->ops->prepare) {
31*a295be8dSValerio Setti 		ret = formatter->drv->ops->prepare(formatter->map,
32*a295be8dSValerio Setti 					   formatter->drv->quirks,
33*a295be8dSValerio Setti 					   formatter->stream);
34*a295be8dSValerio Setti 		if (ret)
35*a295be8dSValerio Setti 			return ret;
36*a295be8dSValerio Setti 	}
37*a295be8dSValerio Setti 
38*a295be8dSValerio Setti 	/* Finally, actually enable the formatter */
39*a295be8dSValerio Setti 	if (formatter->drv->ops->enable)
40*a295be8dSValerio Setti 		formatter->drv->ops->enable(formatter->map);
41*a295be8dSValerio Setti 
42*a295be8dSValerio Setti 	formatter->enabled = true;
43*a295be8dSValerio Setti 
44*a295be8dSValerio Setti 	return 0;
45*a295be8dSValerio Setti }
46*a295be8dSValerio Setti 
47*a295be8dSValerio Setti static void gx_formatter_disable(struct gx_formatter *formatter)
48*a295be8dSValerio Setti {
49*a295be8dSValerio Setti 	/* Do nothing if the formatter is already disabled */
50*a295be8dSValerio Setti 	if (!formatter->enabled)
51*a295be8dSValerio Setti 		return;
52*a295be8dSValerio Setti 
53*a295be8dSValerio Setti 	if (formatter->drv->ops->disable)
54*a295be8dSValerio Setti 		formatter->drv->ops->disable(formatter->map);
55*a295be8dSValerio Setti 
56*a295be8dSValerio Setti 	formatter->enabled = false;
57*a295be8dSValerio Setti }
58*a295be8dSValerio Setti 
59*a295be8dSValerio Setti static int gx_formatter_attach(struct gx_formatter *formatter)
60*a295be8dSValerio Setti {
61*a295be8dSValerio Setti 	struct gx_stream *ts = formatter->stream;
62*a295be8dSValerio Setti 	int ret = 0;
63*a295be8dSValerio Setti 
64*a295be8dSValerio Setti 	mutex_lock(&ts->lock);
65*a295be8dSValerio Setti 
66*a295be8dSValerio Setti 	/* Catch up if the stream is already running when we attach */
67*a295be8dSValerio Setti 	if (ts->ready) {
68*a295be8dSValerio Setti 		ret = gx_formatter_enable(formatter);
69*a295be8dSValerio Setti 		if (ret) {
70*a295be8dSValerio Setti 			pr_err("failed to enable formatter\n");
71*a295be8dSValerio Setti 			goto out;
72*a295be8dSValerio Setti 		}
73*a295be8dSValerio Setti 	}
74*a295be8dSValerio Setti 
75*a295be8dSValerio Setti 	list_add_tail(&formatter->list, &ts->formatter_list);
76*a295be8dSValerio Setti out:
77*a295be8dSValerio Setti 	mutex_unlock(&ts->lock);
78*a295be8dSValerio Setti 	return ret;
79*a295be8dSValerio Setti }
80*a295be8dSValerio Setti 
81*a295be8dSValerio Setti static void gx_formatter_detach(struct gx_formatter *formatter)
82*a295be8dSValerio Setti {
83*a295be8dSValerio Setti 	struct gx_stream *ts = formatter->stream;
84*a295be8dSValerio Setti 
85*a295be8dSValerio Setti 	if (!ts)
86*a295be8dSValerio Setti 		return;
87*a295be8dSValerio Setti 
88*a295be8dSValerio Setti 	mutex_lock(&ts->lock);
89*a295be8dSValerio Setti 	list_del(&formatter->list);
90*a295be8dSValerio Setti 	mutex_unlock(&ts->lock);
91*a295be8dSValerio Setti 
92*a295be8dSValerio Setti 	gx_formatter_disable(formatter);
93*a295be8dSValerio Setti }
94*a295be8dSValerio Setti 
95*a295be8dSValerio Setti static int gx_formatter_power_up(struct gx_formatter *formatter,
96*a295be8dSValerio Setti 				      struct snd_soc_dapm_widget *w)
97*a295be8dSValerio Setti {
98*a295be8dSValerio Setti 	struct gx_stream *ts = formatter->drv->ops->get_stream(w);
99*a295be8dSValerio Setti 	int ret;
100*a295be8dSValerio Setti 
101*a295be8dSValerio Setti 	/*
102*a295be8dSValerio Setti 	 * If we don't get a stream at this stage, it would mean that the
103*a295be8dSValerio Setti 	 * widget is powering up but is not attached to any backend DAI.
104*a295be8dSValerio Setti 	 * It should not happen, ever !
105*a295be8dSValerio Setti 	 */
106*a295be8dSValerio Setti 	if (WARN_ON(!ts))
107*a295be8dSValerio Setti 		return -ENODEV;
108*a295be8dSValerio Setti 
109*a295be8dSValerio Setti 	formatter->stream = ts;
110*a295be8dSValerio Setti 	INIT_LIST_HEAD(&formatter->list);
111*a295be8dSValerio Setti 	ret = gx_formatter_attach(formatter);
112*a295be8dSValerio Setti 	if (ret)
113*a295be8dSValerio Setti 		return ret;
114*a295be8dSValerio Setti 
115*a295be8dSValerio Setti 	return 0;
116*a295be8dSValerio Setti }
117*a295be8dSValerio Setti 
118*a295be8dSValerio Setti static void gx_formatter_power_down(struct gx_formatter *formatter)
119*a295be8dSValerio Setti {
120*a295be8dSValerio Setti 	gx_formatter_detach(formatter);
121*a295be8dSValerio Setti 	formatter->stream = NULL;
122*a295be8dSValerio Setti }
123*a295be8dSValerio Setti 
124*a295be8dSValerio Setti int gx_formatter_event(struct snd_soc_dapm_widget *w,
125*a295be8dSValerio Setti 		       struct snd_kcontrol *control,
126*a295be8dSValerio Setti 		       int event)
127*a295be8dSValerio Setti {
128*a295be8dSValerio Setti 	struct snd_soc_component *c;
129*a295be8dSValerio Setti 	struct gx_formatter *formatter;
130*a295be8dSValerio Setti 	int ret = 0;
131*a295be8dSValerio Setti 
132*a295be8dSValerio Setti 	c = snd_soc_dapm_to_component(w->dapm);
133*a295be8dSValerio Setti 
134*a295be8dSValerio Setti 	if (w->priv)
135*a295be8dSValerio Setti 		formatter = w->priv;
136*a295be8dSValerio Setti 	else
137*a295be8dSValerio Setti 		formatter = snd_soc_component_get_drvdata(c);
138*a295be8dSValerio Setti 
139*a295be8dSValerio Setti 	switch (event) {
140*a295be8dSValerio Setti 	case SND_SOC_DAPM_PRE_PMU:
141*a295be8dSValerio Setti 		ret = gx_formatter_power_up(formatter, w);
142*a295be8dSValerio Setti 		break;
143*a295be8dSValerio Setti 
144*a295be8dSValerio Setti 	case SND_SOC_DAPM_PRE_PMD:
145*a295be8dSValerio Setti 		gx_formatter_power_down(formatter);
146*a295be8dSValerio Setti 		break;
147*a295be8dSValerio Setti 
148*a295be8dSValerio Setti 	default:
149*a295be8dSValerio Setti 		dev_err(c->dev, "Unexpected event %d\n", event);
150*a295be8dSValerio Setti 		return -EINVAL;
151*a295be8dSValerio Setti 	}
152*a295be8dSValerio Setti 
153*a295be8dSValerio Setti 	return ret;
154*a295be8dSValerio Setti }
155*a295be8dSValerio Setti EXPORT_SYMBOL_GPL(gx_formatter_event);
156*a295be8dSValerio Setti 
157*a295be8dSValerio Setti int gx_formatter_probe(struct platform_device *pdev)
158*a295be8dSValerio Setti {
159*a295be8dSValerio Setti 	struct device *dev = &pdev->dev;
160*a295be8dSValerio Setti 	const struct gx_formatter_driver *drv;
161*a295be8dSValerio Setti 	struct gx_formatter *formatter;
162*a295be8dSValerio Setti 	void __iomem *regs;
163*a295be8dSValerio Setti 
164*a295be8dSValerio Setti 	drv = of_device_get_match_data(dev);
165*a295be8dSValerio Setti 	if (!drv) {
166*a295be8dSValerio Setti 		dev_err(dev, "failed to match device\n");
167*a295be8dSValerio Setti 		return -ENODEV;
168*a295be8dSValerio Setti 	}
169*a295be8dSValerio Setti 
170*a295be8dSValerio Setti 	formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
171*a295be8dSValerio Setti 	if (!formatter)
172*a295be8dSValerio Setti 		return -ENOMEM;
173*a295be8dSValerio Setti 	platform_set_drvdata(pdev, formatter);
174*a295be8dSValerio Setti 	formatter->drv = drv;
175*a295be8dSValerio Setti 
176*a295be8dSValerio Setti 	regs = devm_platform_ioremap_resource(pdev, 0);
177*a295be8dSValerio Setti 	if (IS_ERR(regs))
178*a295be8dSValerio Setti 		return PTR_ERR(regs);
179*a295be8dSValerio Setti 
180*a295be8dSValerio Setti 	formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg);
181*a295be8dSValerio Setti 	if (IS_ERR(formatter->map)) {
182*a295be8dSValerio Setti 		dev_err(dev, "failed to init regmap: %ld\n",
183*a295be8dSValerio Setti 			PTR_ERR(formatter->map));
184*a295be8dSValerio Setti 		return PTR_ERR(formatter->map);
185*a295be8dSValerio Setti 	}
186*a295be8dSValerio Setti 
187*a295be8dSValerio Setti 	return devm_snd_soc_register_component(dev, drv->component_drv,
188*a295be8dSValerio Setti 					       NULL, 0);
189*a295be8dSValerio Setti }
190*a295be8dSValerio Setti EXPORT_SYMBOL_GPL(gx_formatter_probe);
191*a295be8dSValerio Setti 
192*a295be8dSValerio Setti int gx_formatter_create(struct device *dev,
193*a295be8dSValerio Setti 			struct snd_soc_dapm_widget *w,
194*a295be8dSValerio Setti 			const struct gx_formatter_driver *drv,
195*a295be8dSValerio Setti 			struct regmap *regmap)
196*a295be8dSValerio Setti {
197*a295be8dSValerio Setti 	struct gx_formatter *formatter;
198*a295be8dSValerio Setti 
199*a295be8dSValerio Setti 	formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL);
200*a295be8dSValerio Setti 	if (!formatter)
201*a295be8dSValerio Setti 		return -ENOMEM;
202*a295be8dSValerio Setti 
203*a295be8dSValerio Setti 	formatter->drv = drv;
204*a295be8dSValerio Setti 	formatter->map = regmap;
205*a295be8dSValerio Setti 
206*a295be8dSValerio Setti 	w->priv = formatter;
207*a295be8dSValerio Setti 
208*a295be8dSValerio Setti 	return 0;
209*a295be8dSValerio Setti }
210*a295be8dSValerio Setti EXPORT_SYMBOL_GPL(gx_formatter_create);
211*a295be8dSValerio Setti 
212*a295be8dSValerio Setti int gx_stream_start(struct gx_stream *ts)
213*a295be8dSValerio Setti {
214*a295be8dSValerio Setti 	struct gx_formatter *formatter;
215*a295be8dSValerio Setti 	int ret = 0;
216*a295be8dSValerio Setti 
217*a295be8dSValerio Setti 	mutex_lock(&ts->lock);
218*a295be8dSValerio Setti 
219*a295be8dSValerio Setti 	/* Start all the formatters attached to the stream */
220*a295be8dSValerio Setti 	list_for_each_entry(formatter, &ts->formatter_list, list) {
221*a295be8dSValerio Setti 		ret = gx_formatter_enable(formatter);
222*a295be8dSValerio Setti 		if (ret) {
223*a295be8dSValerio Setti 			pr_err("failed to enable formatter\n");
224*a295be8dSValerio Setti 			goto out;
225*a295be8dSValerio Setti 		}
226*a295be8dSValerio Setti 	}
227*a295be8dSValerio Setti 
228*a295be8dSValerio Setti 	ts->ready = true;
229*a295be8dSValerio Setti 
230*a295be8dSValerio Setti out:
231*a295be8dSValerio Setti 	mutex_unlock(&ts->lock);
232*a295be8dSValerio Setti 	return ret;
233*a295be8dSValerio Setti }
234*a295be8dSValerio Setti EXPORT_SYMBOL_GPL(gx_stream_start);
235*a295be8dSValerio Setti 
236*a295be8dSValerio Setti void gx_stream_stop(struct gx_stream *ts)
237*a295be8dSValerio Setti {
238*a295be8dSValerio Setti 	struct gx_formatter *formatter;
239*a295be8dSValerio Setti 
240*a295be8dSValerio Setti 	mutex_lock(&ts->lock);
241*a295be8dSValerio Setti 	ts->ready = false;
242*a295be8dSValerio Setti 
243*a295be8dSValerio Setti 	/* Stop all the formatters attached to the stream */
244*a295be8dSValerio Setti 	list_for_each_entry(formatter, &ts->formatter_list, list) {
245*a295be8dSValerio Setti 		gx_formatter_disable(formatter);
246*a295be8dSValerio Setti 	}
247*a295be8dSValerio Setti 
248*a295be8dSValerio Setti 	mutex_unlock(&ts->lock);
249*a295be8dSValerio Setti }
250*a295be8dSValerio Setti EXPORT_SYMBOL_GPL(gx_stream_stop);
251*a295be8dSValerio Setti 
252*a295be8dSValerio Setti struct gx_stream *gx_stream_alloc(struct gx_iface *iface)
253*a295be8dSValerio Setti {
254*a295be8dSValerio Setti 	struct gx_stream *ts;
255*a295be8dSValerio Setti 
256*a295be8dSValerio Setti 	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
257*a295be8dSValerio Setti 	if (ts) {
258*a295be8dSValerio Setti 		INIT_LIST_HEAD(&ts->formatter_list);
259*a295be8dSValerio Setti 		mutex_init(&ts->lock);
260*a295be8dSValerio Setti 		ts->iface = iface;
261*a295be8dSValerio Setti 	}
262*a295be8dSValerio Setti 
263*a295be8dSValerio Setti 	return ts;
264*a295be8dSValerio Setti }
265*a295be8dSValerio Setti EXPORT_SYMBOL_GPL(gx_stream_alloc);
266*a295be8dSValerio Setti 
267*a295be8dSValerio Setti void gx_stream_free(struct gx_stream *ts)
268*a295be8dSValerio Setti {
269*a295be8dSValerio Setti 	/*
270*a295be8dSValerio Setti 	 * If the list is not empty, it would mean that one of the formatter
271*a295be8dSValerio Setti 	 * widget is still powered and attached to the interface while we
272*a295be8dSValerio Setti 	 * are removing the TDM DAI. It should not be possible
273*a295be8dSValerio Setti 	 */
274*a295be8dSValerio Setti 	WARN_ON(!list_empty(&ts->formatter_list));
275*a295be8dSValerio Setti 	mutex_destroy(&ts->lock);
276*a295be8dSValerio Setti 	kfree(ts);
277*a295be8dSValerio Setti }
278*a295be8dSValerio Setti EXPORT_SYMBOL_GPL(gx_stream_free);
279*a295be8dSValerio Setti 
280*a295be8dSValerio Setti MODULE_DESCRIPTION("Amlogic GX formatter driver");
281*a295be8dSValerio Setti MODULE_AUTHOR("Valerio Setti <vsetti@baylibre.com>");
282*a295be8dSValerio Setti MODULE_LICENSE("GPL");
283