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