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