11a11d88fSJerome Brunet // SPDX-License-Identifier: (GPL-2.0 OR MIT) 21a11d88fSJerome Brunet // 31a11d88fSJerome Brunet // Copyright (c) 2018 BayLibre, SAS. 41a11d88fSJerome Brunet // Author: Jerome Brunet <jbrunet@baylibre.com> 51a11d88fSJerome Brunet 61a11d88fSJerome Brunet #include <linux/clk.h> 71a11d88fSJerome Brunet #include <linux/module.h> 81a11d88fSJerome Brunet #include <linux/of_platform.h> 91a11d88fSJerome Brunet #include <linux/regmap.h> 101a11d88fSJerome Brunet #include <sound/soc.h> 111a11d88fSJerome Brunet 121a11d88fSJerome Brunet #include "axg-tdm-formatter.h" 131a11d88fSJerome Brunet 141a11d88fSJerome Brunet struct axg_tdm_formatter { 151a11d88fSJerome Brunet struct list_head list; 161a11d88fSJerome Brunet struct axg_tdm_stream *stream; 171a11d88fSJerome Brunet const struct axg_tdm_formatter_driver *drv; 181a11d88fSJerome Brunet struct clk *pclk; 191a11d88fSJerome Brunet struct clk *sclk; 201a11d88fSJerome Brunet struct clk *lrclk; 211a11d88fSJerome Brunet struct clk *sclk_sel; 221a11d88fSJerome Brunet struct clk *lrclk_sel; 231a11d88fSJerome Brunet bool enabled; 241a11d88fSJerome Brunet struct regmap *map; 251a11d88fSJerome Brunet }; 261a11d88fSJerome Brunet 271a11d88fSJerome Brunet int axg_tdm_formatter_set_channel_masks(struct regmap *map, 281a11d88fSJerome Brunet struct axg_tdm_stream *ts, 291a11d88fSJerome Brunet unsigned int offset) 301a11d88fSJerome Brunet { 311a11d88fSJerome Brunet unsigned int val, ch = ts->channels; 321a11d88fSJerome Brunet unsigned long mask; 331a11d88fSJerome Brunet int i, j; 341a11d88fSJerome Brunet 351a11d88fSJerome Brunet /* 361a11d88fSJerome Brunet * Distribute the channels of the stream over the available slots 371a11d88fSJerome Brunet * of each TDM lane 381a11d88fSJerome Brunet */ 391a11d88fSJerome Brunet for (i = 0; i < AXG_TDM_NUM_LANES; i++) { 401a11d88fSJerome Brunet val = 0; 411a11d88fSJerome Brunet mask = ts->mask[i]; 421a11d88fSJerome Brunet 431a11d88fSJerome Brunet for (j = find_first_bit(&mask, 32); 441a11d88fSJerome Brunet (j < 32) && ch; 451a11d88fSJerome Brunet j = find_next_bit(&mask, 32, j + 1)) { 461a11d88fSJerome Brunet val |= 1 << j; 471a11d88fSJerome Brunet ch -= 1; 481a11d88fSJerome Brunet } 491a11d88fSJerome Brunet 501a11d88fSJerome Brunet regmap_write(map, offset, val); 511a11d88fSJerome Brunet offset += regmap_get_reg_stride(map); 521a11d88fSJerome Brunet } 531a11d88fSJerome Brunet 541a11d88fSJerome Brunet /* 551a11d88fSJerome Brunet * If we still have channel left at the end of the process, it means 561a11d88fSJerome Brunet * the stream has more channels than we can accommodate and we should 571a11d88fSJerome Brunet * have caught this earlier. 581a11d88fSJerome Brunet */ 591a11d88fSJerome Brunet if (WARN_ON(ch != 0)) { 601a11d88fSJerome Brunet pr_err("channel mask error\n"); 611a11d88fSJerome Brunet return -EINVAL; 621a11d88fSJerome Brunet } 631a11d88fSJerome Brunet 641a11d88fSJerome Brunet return 0; 651a11d88fSJerome Brunet } 661a11d88fSJerome Brunet EXPORT_SYMBOL_GPL(axg_tdm_formatter_set_channel_masks); 671a11d88fSJerome Brunet 681a11d88fSJerome Brunet static int axg_tdm_formatter_enable(struct axg_tdm_formatter *formatter) 691a11d88fSJerome Brunet { 701a11d88fSJerome Brunet struct axg_tdm_stream *ts = formatter->stream; 71*f01bc67fSJerome Brunet bool invert = formatter->drv->quirks->invert_sclk; 721a11d88fSJerome Brunet int ret; 731a11d88fSJerome Brunet 741a11d88fSJerome Brunet /* Do nothing if the formatter is already enabled */ 751a11d88fSJerome Brunet if (formatter->enabled) 761a11d88fSJerome Brunet return 0; 771a11d88fSJerome Brunet 781a11d88fSJerome Brunet /* 791a11d88fSJerome Brunet * If sclk is inverted, invert it back and provide the inversion 801a11d88fSJerome Brunet * required by the formatter 811a11d88fSJerome Brunet */ 821a11d88fSJerome Brunet invert ^= axg_tdm_sclk_invert(ts->iface->fmt); 831a11d88fSJerome Brunet ret = clk_set_phase(formatter->sclk, invert ? 180 : 0); 841a11d88fSJerome Brunet if (ret) 851a11d88fSJerome Brunet return ret; 861a11d88fSJerome Brunet 871a11d88fSJerome Brunet /* Setup the stream parameter in the formatter */ 88*f01bc67fSJerome Brunet ret = formatter->drv->ops->prepare(formatter->map, 89*f01bc67fSJerome Brunet formatter->drv->quirks, 90*f01bc67fSJerome Brunet formatter->stream); 911a11d88fSJerome Brunet if (ret) 921a11d88fSJerome Brunet return ret; 931a11d88fSJerome Brunet 941a11d88fSJerome Brunet /* Enable the signal clocks feeding the formatter */ 951a11d88fSJerome Brunet ret = clk_prepare_enable(formatter->sclk); 961a11d88fSJerome Brunet if (ret) 971a11d88fSJerome Brunet return ret; 981a11d88fSJerome Brunet 991a11d88fSJerome Brunet ret = clk_prepare_enable(formatter->lrclk); 1001a11d88fSJerome Brunet if (ret) { 1011a11d88fSJerome Brunet clk_disable_unprepare(formatter->sclk); 1021a11d88fSJerome Brunet return ret; 1031a11d88fSJerome Brunet } 1041a11d88fSJerome Brunet 1051a11d88fSJerome Brunet /* Finally, actually enable the formatter */ 1061a11d88fSJerome Brunet formatter->drv->ops->enable(formatter->map); 1071a11d88fSJerome Brunet formatter->enabled = true; 1081a11d88fSJerome Brunet 1091a11d88fSJerome Brunet return 0; 1101a11d88fSJerome Brunet } 1111a11d88fSJerome Brunet 1121a11d88fSJerome Brunet static void axg_tdm_formatter_disable(struct axg_tdm_formatter *formatter) 1131a11d88fSJerome Brunet { 1141a11d88fSJerome Brunet /* Do nothing if the formatter is already disabled */ 1151a11d88fSJerome Brunet if (!formatter->enabled) 1161a11d88fSJerome Brunet return; 1171a11d88fSJerome Brunet 1181a11d88fSJerome Brunet formatter->drv->ops->disable(formatter->map); 1191a11d88fSJerome Brunet clk_disable_unprepare(formatter->lrclk); 1201a11d88fSJerome Brunet clk_disable_unprepare(formatter->sclk); 1211a11d88fSJerome Brunet formatter->enabled = false; 1221a11d88fSJerome Brunet } 1231a11d88fSJerome Brunet 1241a11d88fSJerome Brunet static int axg_tdm_formatter_attach(struct axg_tdm_formatter *formatter) 1251a11d88fSJerome Brunet { 1261a11d88fSJerome Brunet struct axg_tdm_stream *ts = formatter->stream; 1271a11d88fSJerome Brunet int ret = 0; 1281a11d88fSJerome Brunet 1291a11d88fSJerome Brunet mutex_lock(&ts->lock); 1301a11d88fSJerome Brunet 1311a11d88fSJerome Brunet /* Catch up if the stream is already running when we attach */ 1321a11d88fSJerome Brunet if (ts->ready) { 1331a11d88fSJerome Brunet ret = axg_tdm_formatter_enable(formatter); 1341a11d88fSJerome Brunet if (ret) { 1351a11d88fSJerome Brunet pr_err("failed to enable formatter\n"); 1361a11d88fSJerome Brunet goto out; 1371a11d88fSJerome Brunet } 1381a11d88fSJerome Brunet } 1391a11d88fSJerome Brunet 1401a11d88fSJerome Brunet list_add_tail(&formatter->list, &ts->formatter_list); 1411a11d88fSJerome Brunet out: 1421a11d88fSJerome Brunet mutex_unlock(&ts->lock); 1431a11d88fSJerome Brunet return ret; 1441a11d88fSJerome Brunet } 1451a11d88fSJerome Brunet 1461a11d88fSJerome Brunet static void axg_tdm_formatter_dettach(struct axg_tdm_formatter *formatter) 1471a11d88fSJerome Brunet { 1481a11d88fSJerome Brunet struct axg_tdm_stream *ts = formatter->stream; 1491a11d88fSJerome Brunet 1501a11d88fSJerome Brunet mutex_lock(&ts->lock); 1511a11d88fSJerome Brunet list_del(&formatter->list); 1521a11d88fSJerome Brunet mutex_unlock(&ts->lock); 1531a11d88fSJerome Brunet 1541a11d88fSJerome Brunet axg_tdm_formatter_disable(formatter); 1551a11d88fSJerome Brunet } 1561a11d88fSJerome Brunet 1571a11d88fSJerome Brunet static int axg_tdm_formatter_power_up(struct axg_tdm_formatter *formatter, 1581a11d88fSJerome Brunet struct snd_soc_dapm_widget *w) 1591a11d88fSJerome Brunet { 1601a11d88fSJerome Brunet struct axg_tdm_stream *ts = formatter->drv->ops->get_stream(w); 1611a11d88fSJerome Brunet int ret; 1621a11d88fSJerome Brunet 1631a11d88fSJerome Brunet /* 1641a11d88fSJerome Brunet * If we don't get a stream at this stage, it would mean that the 1651a11d88fSJerome Brunet * widget is powering up but is not attached to any backend DAI. 1661a11d88fSJerome Brunet * It should not happen, ever ! 1671a11d88fSJerome Brunet */ 1681a11d88fSJerome Brunet if (WARN_ON(!ts)) 1691a11d88fSJerome Brunet return -ENODEV; 1701a11d88fSJerome Brunet 1711a11d88fSJerome Brunet /* Clock our device */ 1721a11d88fSJerome Brunet ret = clk_prepare_enable(formatter->pclk); 1731a11d88fSJerome Brunet if (ret) 1741a11d88fSJerome Brunet return ret; 1751a11d88fSJerome Brunet 1761a11d88fSJerome Brunet /* Reparent the bit clock to the TDM interface */ 1771a11d88fSJerome Brunet ret = clk_set_parent(formatter->sclk_sel, ts->iface->sclk); 1781a11d88fSJerome Brunet if (ret) 1791a11d88fSJerome Brunet goto disable_pclk; 1801a11d88fSJerome Brunet 1811a11d88fSJerome Brunet /* Reparent the sample clock to the TDM interface */ 1821a11d88fSJerome Brunet ret = clk_set_parent(formatter->lrclk_sel, ts->iface->lrclk); 1831a11d88fSJerome Brunet if (ret) 1841a11d88fSJerome Brunet goto disable_pclk; 1851a11d88fSJerome Brunet 1861a11d88fSJerome Brunet formatter->stream = ts; 1871a11d88fSJerome Brunet ret = axg_tdm_formatter_attach(formatter); 1881a11d88fSJerome Brunet if (ret) 1891a11d88fSJerome Brunet goto disable_pclk; 1901a11d88fSJerome Brunet 1911a11d88fSJerome Brunet return 0; 1921a11d88fSJerome Brunet 1931a11d88fSJerome Brunet disable_pclk: 1941a11d88fSJerome Brunet clk_disable_unprepare(formatter->pclk); 1951a11d88fSJerome Brunet return ret; 1961a11d88fSJerome Brunet } 1971a11d88fSJerome Brunet 1981a11d88fSJerome Brunet static void axg_tdm_formatter_power_down(struct axg_tdm_formatter *formatter) 1991a11d88fSJerome Brunet { 2001a11d88fSJerome Brunet axg_tdm_formatter_dettach(formatter); 2011a11d88fSJerome Brunet clk_disable_unprepare(formatter->pclk); 2021a11d88fSJerome Brunet formatter->stream = NULL; 2031a11d88fSJerome Brunet } 2041a11d88fSJerome Brunet 2051a11d88fSJerome Brunet int axg_tdm_formatter_event(struct snd_soc_dapm_widget *w, 2061a11d88fSJerome Brunet struct snd_kcontrol *control, 2071a11d88fSJerome Brunet int event) 2081a11d88fSJerome Brunet { 2091a11d88fSJerome Brunet struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); 2101a11d88fSJerome Brunet struct axg_tdm_formatter *formatter = snd_soc_component_get_drvdata(c); 2111a11d88fSJerome Brunet int ret = 0; 2121a11d88fSJerome Brunet 2131a11d88fSJerome Brunet switch (event) { 2141a11d88fSJerome Brunet case SND_SOC_DAPM_PRE_PMU: 2151a11d88fSJerome Brunet ret = axg_tdm_formatter_power_up(formatter, w); 2161a11d88fSJerome Brunet break; 2171a11d88fSJerome Brunet 2181a11d88fSJerome Brunet case SND_SOC_DAPM_PRE_PMD: 2191a11d88fSJerome Brunet axg_tdm_formatter_power_down(formatter); 2201a11d88fSJerome Brunet break; 2211a11d88fSJerome Brunet 2221a11d88fSJerome Brunet default: 2231a11d88fSJerome Brunet dev_err(c->dev, "Unexpected event %d\n", event); 2241a11d88fSJerome Brunet return -EINVAL; 2251a11d88fSJerome Brunet } 2261a11d88fSJerome Brunet 2271a11d88fSJerome Brunet return ret; 2281a11d88fSJerome Brunet } 2291a11d88fSJerome Brunet EXPORT_SYMBOL_GPL(axg_tdm_formatter_event); 2301a11d88fSJerome Brunet 2311a11d88fSJerome Brunet int axg_tdm_formatter_probe(struct platform_device *pdev) 2321a11d88fSJerome Brunet { 2331a11d88fSJerome Brunet struct device *dev = &pdev->dev; 2341a11d88fSJerome Brunet const struct axg_tdm_formatter_driver *drv; 2351a11d88fSJerome Brunet struct axg_tdm_formatter *formatter; 2361a11d88fSJerome Brunet struct resource *res; 2371a11d88fSJerome Brunet void __iomem *regs; 2381a11d88fSJerome Brunet int ret; 2391a11d88fSJerome Brunet 2401a11d88fSJerome Brunet drv = of_device_get_match_data(dev); 2411a11d88fSJerome Brunet if (!drv) { 2421a11d88fSJerome Brunet dev_err(dev, "failed to match device\n"); 2431a11d88fSJerome Brunet return -ENODEV; 2441a11d88fSJerome Brunet } 2451a11d88fSJerome Brunet 2461a11d88fSJerome Brunet formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); 2471a11d88fSJerome Brunet if (!formatter) 2481a11d88fSJerome Brunet return -ENOMEM; 2491a11d88fSJerome Brunet platform_set_drvdata(pdev, formatter); 2501a11d88fSJerome Brunet formatter->drv = drv; 2511a11d88fSJerome Brunet 2521a11d88fSJerome Brunet res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2531a11d88fSJerome Brunet regs = devm_ioremap_resource(dev, res); 2541a11d88fSJerome Brunet if (IS_ERR(regs)) 2551a11d88fSJerome Brunet return PTR_ERR(regs); 2561a11d88fSJerome Brunet 2571a11d88fSJerome Brunet formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg); 2581a11d88fSJerome Brunet if (IS_ERR(formatter->map)) { 2591a11d88fSJerome Brunet dev_err(dev, "failed to init regmap: %ld\n", 2601a11d88fSJerome Brunet PTR_ERR(formatter->map)); 2611a11d88fSJerome Brunet return PTR_ERR(formatter->map); 2621a11d88fSJerome Brunet } 2631a11d88fSJerome Brunet 2641a11d88fSJerome Brunet /* Peripharal clock */ 2651a11d88fSJerome Brunet formatter->pclk = devm_clk_get(dev, "pclk"); 2661a11d88fSJerome Brunet if (IS_ERR(formatter->pclk)) { 2671a11d88fSJerome Brunet ret = PTR_ERR(formatter->pclk); 2681a11d88fSJerome Brunet if (ret != -EPROBE_DEFER) 2691a11d88fSJerome Brunet dev_err(dev, "failed to get pclk: %d\n", ret); 2701a11d88fSJerome Brunet return ret; 2711a11d88fSJerome Brunet } 2721a11d88fSJerome Brunet 2731a11d88fSJerome Brunet /* Formatter bit clock */ 2741a11d88fSJerome Brunet formatter->sclk = devm_clk_get(dev, "sclk"); 2751a11d88fSJerome Brunet if (IS_ERR(formatter->sclk)) { 2761a11d88fSJerome Brunet ret = PTR_ERR(formatter->sclk); 2771a11d88fSJerome Brunet if (ret != -EPROBE_DEFER) 2781a11d88fSJerome Brunet dev_err(dev, "failed to get sclk: %d\n", ret); 2791a11d88fSJerome Brunet return ret; 2801a11d88fSJerome Brunet } 2811a11d88fSJerome Brunet 2821a11d88fSJerome Brunet /* Formatter sample clock */ 2831a11d88fSJerome Brunet formatter->lrclk = devm_clk_get(dev, "lrclk"); 2841a11d88fSJerome Brunet if (IS_ERR(formatter->lrclk)) { 2851a11d88fSJerome Brunet ret = PTR_ERR(formatter->lrclk); 2861a11d88fSJerome Brunet if (ret != -EPROBE_DEFER) 2871a11d88fSJerome Brunet dev_err(dev, "failed to get lrclk: %d\n", ret); 2881a11d88fSJerome Brunet return ret; 2891a11d88fSJerome Brunet } 2901a11d88fSJerome Brunet 2911a11d88fSJerome Brunet /* Formatter bit clock input multiplexer */ 2921a11d88fSJerome Brunet formatter->sclk_sel = devm_clk_get(dev, "sclk_sel"); 2931a11d88fSJerome Brunet if (IS_ERR(formatter->sclk_sel)) { 2941a11d88fSJerome Brunet ret = PTR_ERR(formatter->sclk_sel); 2951a11d88fSJerome Brunet if (ret != -EPROBE_DEFER) 2961a11d88fSJerome Brunet dev_err(dev, "failed to get sclk_sel: %d\n", ret); 2971a11d88fSJerome Brunet return ret; 2981a11d88fSJerome Brunet } 2991a11d88fSJerome Brunet 3001a11d88fSJerome Brunet /* Formatter sample clock input multiplexer */ 3011a11d88fSJerome Brunet formatter->lrclk_sel = devm_clk_get(dev, "lrclk_sel"); 3021a11d88fSJerome Brunet if (IS_ERR(formatter->lrclk_sel)) { 3031a11d88fSJerome Brunet ret = PTR_ERR(formatter->lrclk_sel); 3041a11d88fSJerome Brunet if (ret != -EPROBE_DEFER) 3051a11d88fSJerome Brunet dev_err(dev, "failed to get lrclk_sel: %d\n", ret); 3061a11d88fSJerome Brunet return ret; 3071a11d88fSJerome Brunet } 3081a11d88fSJerome Brunet 3091a11d88fSJerome Brunet return devm_snd_soc_register_component(dev, drv->component_drv, 3101a11d88fSJerome Brunet NULL, 0); 3111a11d88fSJerome Brunet } 3121a11d88fSJerome Brunet EXPORT_SYMBOL_GPL(axg_tdm_formatter_probe); 3131a11d88fSJerome Brunet 3141a11d88fSJerome Brunet int axg_tdm_stream_start(struct axg_tdm_stream *ts) 3151a11d88fSJerome Brunet { 3161a11d88fSJerome Brunet struct axg_tdm_formatter *formatter; 3171a11d88fSJerome Brunet int ret = 0; 3181a11d88fSJerome Brunet 3191a11d88fSJerome Brunet mutex_lock(&ts->lock); 3201a11d88fSJerome Brunet ts->ready = true; 3211a11d88fSJerome Brunet 3221a11d88fSJerome Brunet /* Start all the formatters attached to the stream */ 3231a11d88fSJerome Brunet list_for_each_entry(formatter, &ts->formatter_list, list) { 3241a11d88fSJerome Brunet ret = axg_tdm_formatter_enable(formatter); 3251a11d88fSJerome Brunet if (ret) { 3261a11d88fSJerome Brunet pr_err("failed to start tdm stream\n"); 3271a11d88fSJerome Brunet goto out; 3281a11d88fSJerome Brunet } 3291a11d88fSJerome Brunet } 3301a11d88fSJerome Brunet 3311a11d88fSJerome Brunet out: 3321a11d88fSJerome Brunet mutex_unlock(&ts->lock); 3331a11d88fSJerome Brunet return ret; 3341a11d88fSJerome Brunet } 3351a11d88fSJerome Brunet EXPORT_SYMBOL_GPL(axg_tdm_stream_start); 3361a11d88fSJerome Brunet 3371a11d88fSJerome Brunet void axg_tdm_stream_stop(struct axg_tdm_stream *ts) 3381a11d88fSJerome Brunet { 3391a11d88fSJerome Brunet struct axg_tdm_formatter *formatter; 3401a11d88fSJerome Brunet 3411a11d88fSJerome Brunet mutex_lock(&ts->lock); 3421a11d88fSJerome Brunet ts->ready = false; 3431a11d88fSJerome Brunet 3441a11d88fSJerome Brunet /* Stop all the formatters attached to the stream */ 3451a11d88fSJerome Brunet list_for_each_entry(formatter, &ts->formatter_list, list) { 3461a11d88fSJerome Brunet axg_tdm_formatter_disable(formatter); 3471a11d88fSJerome Brunet } 3481a11d88fSJerome Brunet 3491a11d88fSJerome Brunet mutex_unlock(&ts->lock); 3501a11d88fSJerome Brunet } 3511a11d88fSJerome Brunet EXPORT_SYMBOL_GPL(axg_tdm_stream_stop); 3521a11d88fSJerome Brunet 3531a11d88fSJerome Brunet struct axg_tdm_stream *axg_tdm_stream_alloc(struct axg_tdm_iface *iface) 3541a11d88fSJerome Brunet { 3551a11d88fSJerome Brunet struct axg_tdm_stream *ts; 3561a11d88fSJerome Brunet 3571a11d88fSJerome Brunet ts = kzalloc(sizeof(*ts), GFP_KERNEL); 3581a11d88fSJerome Brunet if (ts) { 3591a11d88fSJerome Brunet INIT_LIST_HEAD(&ts->formatter_list); 3601a11d88fSJerome Brunet mutex_init(&ts->lock); 3611a11d88fSJerome Brunet ts->iface = iface; 3621a11d88fSJerome Brunet } 3631a11d88fSJerome Brunet 3641a11d88fSJerome Brunet return ts; 3651a11d88fSJerome Brunet } 3661a11d88fSJerome Brunet EXPORT_SYMBOL_GPL(axg_tdm_stream_alloc); 3671a11d88fSJerome Brunet 3681a11d88fSJerome Brunet void axg_tdm_stream_free(struct axg_tdm_stream *ts) 3691a11d88fSJerome Brunet { 3701a11d88fSJerome Brunet /* 3711a11d88fSJerome Brunet * If the list is not empty, it would mean that one of the formatter 3721a11d88fSJerome Brunet * widget is still powered and attached to the interface while we 3731a11d88fSJerome Brunet * we are removing the TDM DAI. It should not be possible 3741a11d88fSJerome Brunet */ 3751a11d88fSJerome Brunet WARN_ON(!list_empty(&ts->formatter_list)); 3761a11d88fSJerome Brunet mutex_destroy(&ts->lock); 3771a11d88fSJerome Brunet kfree(ts); 3781a11d88fSJerome Brunet } 3791a11d88fSJerome Brunet EXPORT_SYMBOL_GPL(axg_tdm_stream_free); 3801a11d88fSJerome Brunet 3811a11d88fSJerome Brunet MODULE_DESCRIPTION("Amlogic AXG TDM formatter driver"); 3821a11d88fSJerome Brunet MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 3831a11d88fSJerome Brunet MODULE_LICENSE("GPL v2"); 384