16dc4fa17SJerome Brunet // SPDX-License-Identifier: (GPL-2.0 OR MIT) 26dc4fa17SJerome Brunet // 36dc4fa17SJerome Brunet // Copyright (c) 2018 BayLibre, SAS. 46dc4fa17SJerome Brunet // Author: Jerome Brunet <jbrunet@baylibre.com> 56dc4fa17SJerome Brunet 66dc4fa17SJerome Brunet #include <linux/clk.h> 76dc4fa17SJerome Brunet #include <linux/of_irq.h> 86dc4fa17SJerome Brunet #include <linux/of_platform.h> 96dc4fa17SJerome Brunet #include <linux/module.h> 106dc4fa17SJerome Brunet #include <linux/regmap.h> 116dc4fa17SJerome Brunet #include <linux/reset.h> 126dc4fa17SJerome Brunet #include <sound/pcm_params.h> 136dc4fa17SJerome Brunet #include <sound/soc.h> 146dc4fa17SJerome Brunet #include <sound/soc-dai.h> 156dc4fa17SJerome Brunet 166dc4fa17SJerome Brunet #include "axg-fifo.h" 176dc4fa17SJerome Brunet 186dc4fa17SJerome Brunet /* 196dc4fa17SJerome Brunet * This file implements the platform operations common to the playback and 206dc4fa17SJerome Brunet * capture frontend DAI. The logic behind this two types of fifo is very 216dc4fa17SJerome Brunet * similar but some difference exist. 226dc4fa17SJerome Brunet * These differences the respective DAI drivers 236dc4fa17SJerome Brunet */ 246dc4fa17SJerome Brunet 256dc4fa17SJerome Brunet static struct snd_pcm_hardware axg_fifo_hw = { 266dc4fa17SJerome Brunet .info = (SNDRV_PCM_INFO_INTERLEAVED | 276dc4fa17SJerome Brunet SNDRV_PCM_INFO_MMAP | 286dc4fa17SJerome Brunet SNDRV_PCM_INFO_MMAP_VALID | 296dc4fa17SJerome Brunet SNDRV_PCM_INFO_BLOCK_TRANSFER | 306dc4fa17SJerome Brunet SNDRV_PCM_INFO_PAUSE), 316dc4fa17SJerome Brunet 326dc4fa17SJerome Brunet .formats = AXG_FIFO_FORMATS, 336dc4fa17SJerome Brunet .rate_min = 5512, 346dc4fa17SJerome Brunet .rate_max = 192000, 356dc4fa17SJerome Brunet .channels_min = 1, 366dc4fa17SJerome Brunet .channels_max = AXG_FIFO_CH_MAX, 376dc4fa17SJerome Brunet .period_bytes_min = AXG_FIFO_MIN_DEPTH, 386dc4fa17SJerome Brunet .period_bytes_max = UINT_MAX, 396dc4fa17SJerome Brunet .periods_min = 2, 406dc4fa17SJerome Brunet .periods_max = UINT_MAX, 416dc4fa17SJerome Brunet 426dc4fa17SJerome Brunet /* No real justification for this */ 436dc4fa17SJerome Brunet .buffer_bytes_max = 1 * 1024 * 1024, 446dc4fa17SJerome Brunet }; 456dc4fa17SJerome Brunet 466dc4fa17SJerome Brunet static struct snd_soc_dai *axg_fifo_dai(struct snd_pcm_substream *ss) 476dc4fa17SJerome Brunet { 486dc4fa17SJerome Brunet struct snd_soc_pcm_runtime *rtd = ss->private_data; 496dc4fa17SJerome Brunet 506dc4fa17SJerome Brunet return rtd->cpu_dai; 516dc4fa17SJerome Brunet } 526dc4fa17SJerome Brunet 536dc4fa17SJerome Brunet static struct axg_fifo *axg_fifo_data(struct snd_pcm_substream *ss) 546dc4fa17SJerome Brunet { 556dc4fa17SJerome Brunet struct snd_soc_dai *dai = axg_fifo_dai(ss); 566dc4fa17SJerome Brunet 576dc4fa17SJerome Brunet return snd_soc_dai_get_drvdata(dai); 586dc4fa17SJerome Brunet } 596dc4fa17SJerome Brunet 606dc4fa17SJerome Brunet static struct device *axg_fifo_dev(struct snd_pcm_substream *ss) 616dc4fa17SJerome Brunet { 626dc4fa17SJerome Brunet struct snd_soc_dai *dai = axg_fifo_dai(ss); 636dc4fa17SJerome Brunet 646dc4fa17SJerome Brunet return dai->dev; 656dc4fa17SJerome Brunet } 666dc4fa17SJerome Brunet 676dc4fa17SJerome Brunet static void __dma_enable(struct axg_fifo *fifo, bool enable) 686dc4fa17SJerome Brunet { 696dc4fa17SJerome Brunet regmap_update_bits(fifo->map, FIFO_CTRL0, CTRL0_DMA_EN, 706dc4fa17SJerome Brunet enable ? CTRL0_DMA_EN : 0); 716dc4fa17SJerome Brunet } 726dc4fa17SJerome Brunet 736dc4fa17SJerome Brunet static int axg_fifo_pcm_trigger(struct snd_pcm_substream *ss, int cmd) 746dc4fa17SJerome Brunet { 756dc4fa17SJerome Brunet struct axg_fifo *fifo = axg_fifo_data(ss); 766dc4fa17SJerome Brunet 776dc4fa17SJerome Brunet switch (cmd) { 786dc4fa17SJerome Brunet case SNDRV_PCM_TRIGGER_START: 796dc4fa17SJerome Brunet case SNDRV_PCM_TRIGGER_RESUME: 806dc4fa17SJerome Brunet case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 816dc4fa17SJerome Brunet __dma_enable(fifo, true); 826dc4fa17SJerome Brunet break; 836dc4fa17SJerome Brunet case SNDRV_PCM_TRIGGER_SUSPEND: 846dc4fa17SJerome Brunet case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 856dc4fa17SJerome Brunet case SNDRV_PCM_TRIGGER_STOP: 866dc4fa17SJerome Brunet __dma_enable(fifo, false); 876dc4fa17SJerome Brunet break; 886dc4fa17SJerome Brunet default: 896dc4fa17SJerome Brunet return -EINVAL; 906dc4fa17SJerome Brunet } 916dc4fa17SJerome Brunet 926dc4fa17SJerome Brunet return 0; 936dc4fa17SJerome Brunet } 946dc4fa17SJerome Brunet 956dc4fa17SJerome Brunet static snd_pcm_uframes_t axg_fifo_pcm_pointer(struct snd_pcm_substream *ss) 966dc4fa17SJerome Brunet { 976dc4fa17SJerome Brunet struct axg_fifo *fifo = axg_fifo_data(ss); 986dc4fa17SJerome Brunet struct snd_pcm_runtime *runtime = ss->runtime; 996dc4fa17SJerome Brunet unsigned int addr; 1006dc4fa17SJerome Brunet 1016dc4fa17SJerome Brunet regmap_read(fifo->map, FIFO_STATUS2, &addr); 1026dc4fa17SJerome Brunet 1036dc4fa17SJerome Brunet return bytes_to_frames(runtime, addr - (unsigned int)runtime->dma_addr); 1046dc4fa17SJerome Brunet } 1056dc4fa17SJerome Brunet 1066dc4fa17SJerome Brunet static int axg_fifo_pcm_hw_params(struct snd_pcm_substream *ss, 1076dc4fa17SJerome Brunet struct snd_pcm_hw_params *params) 1086dc4fa17SJerome Brunet { 1096dc4fa17SJerome Brunet struct snd_pcm_runtime *runtime = ss->runtime; 1106dc4fa17SJerome Brunet struct axg_fifo *fifo = axg_fifo_data(ss); 1116dc4fa17SJerome Brunet dma_addr_t end_ptr; 1126dc4fa17SJerome Brunet unsigned int burst_num; 1136dc4fa17SJerome Brunet int ret; 1146dc4fa17SJerome Brunet 1156dc4fa17SJerome Brunet ret = snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(params)); 1166dc4fa17SJerome Brunet if (ret < 0) 1176dc4fa17SJerome Brunet return ret; 1186dc4fa17SJerome Brunet 1196dc4fa17SJerome Brunet /* Setup dma memory pointers */ 1206dc4fa17SJerome Brunet end_ptr = runtime->dma_addr + runtime->dma_bytes - AXG_FIFO_BURST; 1216dc4fa17SJerome Brunet regmap_write(fifo->map, FIFO_START_ADDR, runtime->dma_addr); 1226dc4fa17SJerome Brunet regmap_write(fifo->map, FIFO_FINISH_ADDR, end_ptr); 1236dc4fa17SJerome Brunet 1246dc4fa17SJerome Brunet /* Setup interrupt periodicity */ 1256dc4fa17SJerome Brunet burst_num = params_period_bytes(params) / AXG_FIFO_BURST; 1266dc4fa17SJerome Brunet regmap_write(fifo->map, FIFO_INT_ADDR, burst_num); 1276dc4fa17SJerome Brunet 1286dc4fa17SJerome Brunet /* Enable block count irq */ 1296dc4fa17SJerome Brunet regmap_update_bits(fifo->map, FIFO_CTRL0, 1306dc4fa17SJerome Brunet CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT), 1316dc4fa17SJerome Brunet CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT)); 1326dc4fa17SJerome Brunet 1336dc4fa17SJerome Brunet return 0; 1346dc4fa17SJerome Brunet } 1356dc4fa17SJerome Brunet 1366dc4fa17SJerome Brunet static int axg_fifo_pcm_hw_free(struct snd_pcm_substream *ss) 1376dc4fa17SJerome Brunet { 1386dc4fa17SJerome Brunet struct axg_fifo *fifo = axg_fifo_data(ss); 1396dc4fa17SJerome Brunet 1406dc4fa17SJerome Brunet /* Disable the block count irq */ 1416dc4fa17SJerome Brunet regmap_update_bits(fifo->map, FIFO_CTRL0, 1426dc4fa17SJerome Brunet CTRL0_INT_EN(FIFO_INT_COUNT_REPEAT), 0); 1436dc4fa17SJerome Brunet 1446dc4fa17SJerome Brunet return snd_pcm_lib_free_pages(ss); 1456dc4fa17SJerome Brunet } 1466dc4fa17SJerome Brunet 1476dc4fa17SJerome Brunet static void axg_fifo_ack_irq(struct axg_fifo *fifo, u8 mask) 1486dc4fa17SJerome Brunet { 1496dc4fa17SJerome Brunet regmap_update_bits(fifo->map, FIFO_CTRL1, 1506dc4fa17SJerome Brunet CTRL1_INT_CLR(FIFO_INT_MASK), 1516dc4fa17SJerome Brunet CTRL1_INT_CLR(mask)); 1526dc4fa17SJerome Brunet 1536dc4fa17SJerome Brunet /* Clear must also be cleared */ 1546dc4fa17SJerome Brunet regmap_update_bits(fifo->map, FIFO_CTRL1, 1556dc4fa17SJerome Brunet CTRL1_INT_CLR(FIFO_INT_MASK), 1566dc4fa17SJerome Brunet 0); 1576dc4fa17SJerome Brunet } 1586dc4fa17SJerome Brunet 1596dc4fa17SJerome Brunet static irqreturn_t axg_fifo_pcm_irq_block(int irq, void *dev_id) 1606dc4fa17SJerome Brunet { 1616dc4fa17SJerome Brunet struct snd_pcm_substream *ss = dev_id; 1626dc4fa17SJerome Brunet struct axg_fifo *fifo = axg_fifo_data(ss); 1636dc4fa17SJerome Brunet unsigned int status; 1646dc4fa17SJerome Brunet 1656dc4fa17SJerome Brunet regmap_read(fifo->map, FIFO_STATUS1, &status); 1666dc4fa17SJerome Brunet 1676dc4fa17SJerome Brunet status = STATUS1_INT_STS(status) & FIFO_INT_MASK; 1686dc4fa17SJerome Brunet if (status & FIFO_INT_COUNT_REPEAT) 1696dc4fa17SJerome Brunet snd_pcm_period_elapsed(ss); 1706dc4fa17SJerome Brunet else 1716dc4fa17SJerome Brunet dev_dbg(axg_fifo_dev(ss), "unexpected irq - STS 0x%02x\n", 1726dc4fa17SJerome Brunet status); 1736dc4fa17SJerome Brunet 1746dc4fa17SJerome Brunet /* Ack irqs */ 1756dc4fa17SJerome Brunet axg_fifo_ack_irq(fifo, status); 1766dc4fa17SJerome Brunet 177036e4963SJerome Brunet return IRQ_RETVAL(status); 1786dc4fa17SJerome Brunet } 1796dc4fa17SJerome Brunet 1806dc4fa17SJerome Brunet static int axg_fifo_pcm_open(struct snd_pcm_substream *ss) 1816dc4fa17SJerome Brunet { 1826dc4fa17SJerome Brunet struct axg_fifo *fifo = axg_fifo_data(ss); 1836dc4fa17SJerome Brunet struct device *dev = axg_fifo_dev(ss); 1846dc4fa17SJerome Brunet int ret; 1856dc4fa17SJerome Brunet 1866dc4fa17SJerome Brunet snd_soc_set_runtime_hwparams(ss, &axg_fifo_hw); 1876dc4fa17SJerome Brunet 1886dc4fa17SJerome Brunet /* 1896dc4fa17SJerome Brunet * Make sure the buffer and period size are multiple of the FIFO 1906dc4fa17SJerome Brunet * minimum depth size 1916dc4fa17SJerome Brunet */ 1926dc4fa17SJerome Brunet ret = snd_pcm_hw_constraint_step(ss->runtime, 0, 1936dc4fa17SJerome Brunet SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 1946dc4fa17SJerome Brunet AXG_FIFO_MIN_DEPTH); 1956dc4fa17SJerome Brunet if (ret) 1966dc4fa17SJerome Brunet return ret; 1976dc4fa17SJerome Brunet 1986dc4fa17SJerome Brunet ret = snd_pcm_hw_constraint_step(ss->runtime, 0, 1996dc4fa17SJerome Brunet SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 2006dc4fa17SJerome Brunet AXG_FIFO_MIN_DEPTH); 2016dc4fa17SJerome Brunet if (ret) 2026dc4fa17SJerome Brunet return ret; 2036dc4fa17SJerome Brunet 2046dc4fa17SJerome Brunet ret = request_irq(fifo->irq, axg_fifo_pcm_irq_block, 0, 2056dc4fa17SJerome Brunet dev_name(dev), ss); 206*dadfab72SJerome Brunet if (ret) 207*dadfab72SJerome Brunet return ret; 2086dc4fa17SJerome Brunet 2096dc4fa17SJerome Brunet /* Enable pclk to access registers and clock the fifo ip */ 2106dc4fa17SJerome Brunet ret = clk_prepare_enable(fifo->pclk); 2116dc4fa17SJerome Brunet if (ret) 2126dc4fa17SJerome Brunet return ret; 2136dc4fa17SJerome Brunet 2146dc4fa17SJerome Brunet /* Setup status2 so it reports the memory pointer */ 2156dc4fa17SJerome Brunet regmap_update_bits(fifo->map, FIFO_CTRL1, 2166dc4fa17SJerome Brunet CTRL1_STATUS2_SEL_MASK, 2176dc4fa17SJerome Brunet CTRL1_STATUS2_SEL(STATUS2_SEL_DDR_READ)); 2186dc4fa17SJerome Brunet 2196dc4fa17SJerome Brunet /* Make sure the dma is initially disabled */ 2206dc4fa17SJerome Brunet __dma_enable(fifo, false); 2216dc4fa17SJerome Brunet 2226dc4fa17SJerome Brunet /* Disable irqs until params are ready */ 2236dc4fa17SJerome Brunet regmap_update_bits(fifo->map, FIFO_CTRL0, 2246dc4fa17SJerome Brunet CTRL0_INT_EN(FIFO_INT_MASK), 0); 2256dc4fa17SJerome Brunet 2266dc4fa17SJerome Brunet /* Clear any pending interrupt */ 2276dc4fa17SJerome Brunet axg_fifo_ack_irq(fifo, FIFO_INT_MASK); 2286dc4fa17SJerome Brunet 2296dc4fa17SJerome Brunet /* Take memory arbitror out of reset */ 2306dc4fa17SJerome Brunet ret = reset_control_deassert(fifo->arb); 2316dc4fa17SJerome Brunet if (ret) 2326dc4fa17SJerome Brunet clk_disable_unprepare(fifo->pclk); 2336dc4fa17SJerome Brunet 2346dc4fa17SJerome Brunet return ret; 2356dc4fa17SJerome Brunet } 2366dc4fa17SJerome Brunet 2376dc4fa17SJerome Brunet static int axg_fifo_pcm_close(struct snd_pcm_substream *ss) 2386dc4fa17SJerome Brunet { 2396dc4fa17SJerome Brunet struct axg_fifo *fifo = axg_fifo_data(ss); 2406dc4fa17SJerome Brunet int ret; 2416dc4fa17SJerome Brunet 2426dc4fa17SJerome Brunet /* Put the memory arbitror back in reset */ 2436dc4fa17SJerome Brunet ret = reset_control_assert(fifo->arb); 2446dc4fa17SJerome Brunet 2456dc4fa17SJerome Brunet /* Disable fifo ip and register access */ 2466dc4fa17SJerome Brunet clk_disable_unprepare(fifo->pclk); 2476dc4fa17SJerome Brunet 2486dc4fa17SJerome Brunet /* remove IRQ */ 2496dc4fa17SJerome Brunet free_irq(fifo->irq, ss); 2506dc4fa17SJerome Brunet 2516dc4fa17SJerome Brunet return ret; 2526dc4fa17SJerome Brunet } 2536dc4fa17SJerome Brunet 2546dc4fa17SJerome Brunet const struct snd_pcm_ops axg_fifo_pcm_ops = { 2556dc4fa17SJerome Brunet .open = axg_fifo_pcm_open, 2566dc4fa17SJerome Brunet .close = axg_fifo_pcm_close, 2576dc4fa17SJerome Brunet .ioctl = snd_pcm_lib_ioctl, 2586dc4fa17SJerome Brunet .hw_params = axg_fifo_pcm_hw_params, 2596dc4fa17SJerome Brunet .hw_free = axg_fifo_pcm_hw_free, 2606dc4fa17SJerome Brunet .pointer = axg_fifo_pcm_pointer, 2616dc4fa17SJerome Brunet .trigger = axg_fifo_pcm_trigger, 2626dc4fa17SJerome Brunet }; 2636dc4fa17SJerome Brunet EXPORT_SYMBOL_GPL(axg_fifo_pcm_ops); 2646dc4fa17SJerome Brunet 2656dc4fa17SJerome Brunet int axg_fifo_pcm_new(struct snd_soc_pcm_runtime *rtd, unsigned int type) 2666dc4fa17SJerome Brunet { 2676dc4fa17SJerome Brunet struct snd_card *card = rtd->card->snd_card; 2686dc4fa17SJerome Brunet size_t size = axg_fifo_hw.buffer_bytes_max; 2696dc4fa17SJerome Brunet 2706dc4fa17SJerome Brunet return snd_pcm_lib_preallocate_pages(rtd->pcm->streams[type].substream, 2716dc4fa17SJerome Brunet SNDRV_DMA_TYPE_DEV, card->dev, 2726dc4fa17SJerome Brunet size, size); 2736dc4fa17SJerome Brunet } 2746dc4fa17SJerome Brunet EXPORT_SYMBOL_GPL(axg_fifo_pcm_new); 2756dc4fa17SJerome Brunet 2766dc4fa17SJerome Brunet static const struct regmap_config axg_fifo_regmap_cfg = { 2776dc4fa17SJerome Brunet .reg_bits = 32, 2786dc4fa17SJerome Brunet .val_bits = 32, 2796dc4fa17SJerome Brunet .reg_stride = 4, 2806dc4fa17SJerome Brunet .max_register = FIFO_STATUS2, 2816dc4fa17SJerome Brunet }; 2826dc4fa17SJerome Brunet 2836dc4fa17SJerome Brunet int axg_fifo_probe(struct platform_device *pdev) 2846dc4fa17SJerome Brunet { 2856dc4fa17SJerome Brunet struct device *dev = &pdev->dev; 2866dc4fa17SJerome Brunet const struct axg_fifo_match_data *data; 2876dc4fa17SJerome Brunet struct axg_fifo *fifo; 2886dc4fa17SJerome Brunet struct resource *res; 2896dc4fa17SJerome Brunet void __iomem *regs; 2906dc4fa17SJerome Brunet 2916dc4fa17SJerome Brunet data = of_device_get_match_data(dev); 2926dc4fa17SJerome Brunet if (!data) { 2936dc4fa17SJerome Brunet dev_err(dev, "failed to match device\n"); 2946dc4fa17SJerome Brunet return -ENODEV; 2956dc4fa17SJerome Brunet } 2966dc4fa17SJerome Brunet 2976dc4fa17SJerome Brunet fifo = devm_kzalloc(dev, sizeof(*fifo), GFP_KERNEL); 2986dc4fa17SJerome Brunet if (!fifo) 2996dc4fa17SJerome Brunet return -ENOMEM; 3006dc4fa17SJerome Brunet platform_set_drvdata(pdev, fifo); 3016dc4fa17SJerome Brunet 3026dc4fa17SJerome Brunet res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 3036dc4fa17SJerome Brunet regs = devm_ioremap_resource(dev, res); 3046dc4fa17SJerome Brunet if (IS_ERR(regs)) 3056dc4fa17SJerome Brunet return PTR_ERR(regs); 3066dc4fa17SJerome Brunet 3076dc4fa17SJerome Brunet fifo->map = devm_regmap_init_mmio(dev, regs, &axg_fifo_regmap_cfg); 3086dc4fa17SJerome Brunet if (IS_ERR(fifo->map)) { 3096dc4fa17SJerome Brunet dev_err(dev, "failed to init regmap: %ld\n", 3106dc4fa17SJerome Brunet PTR_ERR(fifo->map)); 3116dc4fa17SJerome Brunet return PTR_ERR(fifo->map); 3126dc4fa17SJerome Brunet } 3136dc4fa17SJerome Brunet 3146dc4fa17SJerome Brunet fifo->pclk = devm_clk_get(dev, NULL); 3156dc4fa17SJerome Brunet if (IS_ERR(fifo->pclk)) { 3166dc4fa17SJerome Brunet if (PTR_ERR(fifo->pclk) != -EPROBE_DEFER) 3176dc4fa17SJerome Brunet dev_err(dev, "failed to get pclk: %ld\n", 3186dc4fa17SJerome Brunet PTR_ERR(fifo->pclk)); 3196dc4fa17SJerome Brunet return PTR_ERR(fifo->pclk); 3206dc4fa17SJerome Brunet } 3216dc4fa17SJerome Brunet 3226dc4fa17SJerome Brunet fifo->arb = devm_reset_control_get_exclusive(dev, NULL); 3236dc4fa17SJerome Brunet if (IS_ERR(fifo->arb)) { 3246dc4fa17SJerome Brunet if (PTR_ERR(fifo->arb) != -EPROBE_DEFER) 3256dc4fa17SJerome Brunet dev_err(dev, "failed to get arb reset: %ld\n", 3266dc4fa17SJerome Brunet PTR_ERR(fifo->arb)); 3276dc4fa17SJerome Brunet return PTR_ERR(fifo->arb); 3286dc4fa17SJerome Brunet } 3296dc4fa17SJerome Brunet 3306dc4fa17SJerome Brunet fifo->irq = of_irq_get(dev->of_node, 0); 3316dc4fa17SJerome Brunet if (fifo->irq <= 0) { 3326dc4fa17SJerome Brunet dev_err(dev, "failed to get irq: %d\n", fifo->irq); 3336dc4fa17SJerome Brunet return fifo->irq; 3346dc4fa17SJerome Brunet } 3356dc4fa17SJerome Brunet 3366dc4fa17SJerome Brunet return devm_snd_soc_register_component(dev, data->component_drv, 3376dc4fa17SJerome Brunet data->dai_drv, 1); 3386dc4fa17SJerome Brunet } 3396dc4fa17SJerome Brunet EXPORT_SYMBOL_GPL(axg_fifo_probe); 3406dc4fa17SJerome Brunet 3416dc4fa17SJerome Brunet MODULE_DESCRIPTION("Amlogic AXG fifo driver"); 3426dc4fa17SJerome Brunet MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 3436dc4fa17SJerome Brunet MODULE_LICENSE("GPL v2"); 344