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