18f2fe346SLars-Peter Clausen /* 28f2fe346SLars-Peter Clausen * Copyright (C) 2012-2013, Analog Devices Inc. 38f2fe346SLars-Peter Clausen * Author: Lars-Peter Clausen <lars@metafoo.de> 48f2fe346SLars-Peter Clausen * 58f2fe346SLars-Peter Clausen * Licensed under the GPL-2. 68f2fe346SLars-Peter Clausen */ 78f2fe346SLars-Peter Clausen 88f2fe346SLars-Peter Clausen #include <linux/clk.h> 98f2fe346SLars-Peter Clausen #include <linux/init.h> 108f2fe346SLars-Peter Clausen #include <linux/kernel.h> 118f2fe346SLars-Peter Clausen #include <linux/module.h> 128f2fe346SLars-Peter Clausen #include <linux/of.h> 138f2fe346SLars-Peter Clausen #include <linux/platform_device.h> 148f2fe346SLars-Peter Clausen #include <linux/regmap.h> 158f2fe346SLars-Peter Clausen #include <linux/slab.h> 168f2fe346SLars-Peter Clausen 178f2fe346SLars-Peter Clausen #include <sound/core.h> 188f2fe346SLars-Peter Clausen #include <sound/pcm.h> 198f2fe346SLars-Peter Clausen #include <sound/pcm_params.h> 208f2fe346SLars-Peter Clausen #include <sound/soc.h> 218f2fe346SLars-Peter Clausen #include <sound/dmaengine_pcm.h> 228f2fe346SLars-Peter Clausen 238f2fe346SLars-Peter Clausen #define AXI_I2S_REG_RESET 0x00 248f2fe346SLars-Peter Clausen #define AXI_I2S_REG_CTRL 0x04 258f2fe346SLars-Peter Clausen #define AXI_I2S_REG_CLK_CTRL 0x08 268f2fe346SLars-Peter Clausen #define AXI_I2S_REG_STATUS 0x10 278f2fe346SLars-Peter Clausen 288f2fe346SLars-Peter Clausen #define AXI_I2S_REG_RX_FIFO 0x28 298f2fe346SLars-Peter Clausen #define AXI_I2S_REG_TX_FIFO 0x2C 308f2fe346SLars-Peter Clausen 318f2fe346SLars-Peter Clausen #define AXI_I2S_RESET_GLOBAL BIT(0) 328f2fe346SLars-Peter Clausen #define AXI_I2S_RESET_TX_FIFO BIT(1) 338f2fe346SLars-Peter Clausen #define AXI_I2S_RESET_RX_FIFO BIT(2) 348f2fe346SLars-Peter Clausen 358f2fe346SLars-Peter Clausen #define AXI_I2S_CTRL_TX_EN BIT(0) 368f2fe346SLars-Peter Clausen #define AXI_I2S_CTRL_RX_EN BIT(1) 378f2fe346SLars-Peter Clausen 388f2fe346SLars-Peter Clausen /* The frame size is configurable, but for now we always set it 64 bit */ 398f2fe346SLars-Peter Clausen #define AXI_I2S_BITS_PER_FRAME 64 408f2fe346SLars-Peter Clausen 418f2fe346SLars-Peter Clausen struct axi_i2s { 428f2fe346SLars-Peter Clausen struct regmap *regmap; 438f2fe346SLars-Peter Clausen struct clk *clk; 448f2fe346SLars-Peter Clausen struct clk *clk_ref; 458f2fe346SLars-Peter Clausen 468f2fe346SLars-Peter Clausen struct snd_soc_dai_driver dai_driver; 478f2fe346SLars-Peter Clausen 488f2fe346SLars-Peter Clausen struct snd_dmaengine_dai_dma_data capture_dma_data; 498f2fe346SLars-Peter Clausen struct snd_dmaengine_dai_dma_data playback_dma_data; 508f2fe346SLars-Peter Clausen 518f2fe346SLars-Peter Clausen struct snd_ratnum ratnum; 528f2fe346SLars-Peter Clausen struct snd_pcm_hw_constraint_ratnums rate_constraints; 538f2fe346SLars-Peter Clausen }; 548f2fe346SLars-Peter Clausen 558f2fe346SLars-Peter Clausen static int axi_i2s_trigger(struct snd_pcm_substream *substream, int cmd, 568f2fe346SLars-Peter Clausen struct snd_soc_dai *dai) 578f2fe346SLars-Peter Clausen { 588f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 598f2fe346SLars-Peter Clausen unsigned int mask, val; 608f2fe346SLars-Peter Clausen 618f2fe346SLars-Peter Clausen if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 628f2fe346SLars-Peter Clausen mask = AXI_I2S_CTRL_RX_EN; 638f2fe346SLars-Peter Clausen else 648f2fe346SLars-Peter Clausen mask = AXI_I2S_CTRL_TX_EN; 658f2fe346SLars-Peter Clausen 668f2fe346SLars-Peter Clausen switch (cmd) { 678f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_START: 688f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_RESUME: 698f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 708f2fe346SLars-Peter Clausen val = mask; 718f2fe346SLars-Peter Clausen break; 728f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_STOP: 738f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_SUSPEND: 748f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 758f2fe346SLars-Peter Clausen val = 0; 768f2fe346SLars-Peter Clausen break; 778f2fe346SLars-Peter Clausen default: 788f2fe346SLars-Peter Clausen return -EINVAL; 798f2fe346SLars-Peter Clausen } 808f2fe346SLars-Peter Clausen 818f2fe346SLars-Peter Clausen regmap_update_bits(i2s->regmap, AXI_I2S_REG_CTRL, mask, val); 828f2fe346SLars-Peter Clausen 838f2fe346SLars-Peter Clausen return 0; 848f2fe346SLars-Peter Clausen } 858f2fe346SLars-Peter Clausen 868f2fe346SLars-Peter Clausen static int axi_i2s_hw_params(struct snd_pcm_substream *substream, 878f2fe346SLars-Peter Clausen struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) 888f2fe346SLars-Peter Clausen { 898f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 908f2fe346SLars-Peter Clausen unsigned int bclk_div, word_size; 918f2fe346SLars-Peter Clausen unsigned int bclk_rate; 928f2fe346SLars-Peter Clausen 938f2fe346SLars-Peter Clausen bclk_rate = params_rate(params) * AXI_I2S_BITS_PER_FRAME; 948f2fe346SLars-Peter Clausen 958f2fe346SLars-Peter Clausen word_size = AXI_I2S_BITS_PER_FRAME / 2 - 1; 968f2fe346SLars-Peter Clausen bclk_div = DIV_ROUND_UP(clk_get_rate(i2s->clk_ref), bclk_rate) / 2 - 1; 978f2fe346SLars-Peter Clausen 988f2fe346SLars-Peter Clausen regmap_write(i2s->regmap, AXI_I2S_REG_CLK_CTRL, (word_size << 16) | 998f2fe346SLars-Peter Clausen bclk_div); 1008f2fe346SLars-Peter Clausen 1018f2fe346SLars-Peter Clausen return 0; 1028f2fe346SLars-Peter Clausen } 1038f2fe346SLars-Peter Clausen 1048f2fe346SLars-Peter Clausen static int axi_i2s_startup(struct snd_pcm_substream *substream, 1058f2fe346SLars-Peter Clausen struct snd_soc_dai *dai) 1068f2fe346SLars-Peter Clausen { 1078f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 1088f2fe346SLars-Peter Clausen uint32_t mask; 1098f2fe346SLars-Peter Clausen int ret; 1108f2fe346SLars-Peter Clausen 1118f2fe346SLars-Peter Clausen if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 1128f2fe346SLars-Peter Clausen mask = AXI_I2S_RESET_RX_FIFO; 1138f2fe346SLars-Peter Clausen else 1148f2fe346SLars-Peter Clausen mask = AXI_I2S_RESET_TX_FIFO; 1158f2fe346SLars-Peter Clausen 1168f2fe346SLars-Peter Clausen regmap_write(i2s->regmap, AXI_I2S_REG_RESET, mask); 1178f2fe346SLars-Peter Clausen 1188f2fe346SLars-Peter Clausen ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0, 1198f2fe346SLars-Peter Clausen SNDRV_PCM_HW_PARAM_RATE, 1208f2fe346SLars-Peter Clausen &i2s->rate_constraints); 1218f2fe346SLars-Peter Clausen if (ret) 1228f2fe346SLars-Peter Clausen return ret; 1238f2fe346SLars-Peter Clausen 1248f2fe346SLars-Peter Clausen return clk_prepare_enable(i2s->clk_ref); 1258f2fe346SLars-Peter Clausen } 1268f2fe346SLars-Peter Clausen 1278f2fe346SLars-Peter Clausen static void axi_i2s_shutdown(struct snd_pcm_substream *substream, 1288f2fe346SLars-Peter Clausen struct snd_soc_dai *dai) 1298f2fe346SLars-Peter Clausen { 1308f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 1318f2fe346SLars-Peter Clausen 1328f2fe346SLars-Peter Clausen clk_disable_unprepare(i2s->clk_ref); 1338f2fe346SLars-Peter Clausen } 1348f2fe346SLars-Peter Clausen 1358f2fe346SLars-Peter Clausen static int axi_i2s_dai_probe(struct snd_soc_dai *dai) 1368f2fe346SLars-Peter Clausen { 1378f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 1388f2fe346SLars-Peter Clausen 1398f2fe346SLars-Peter Clausen snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, 1408f2fe346SLars-Peter Clausen &i2s->capture_dma_data); 1418f2fe346SLars-Peter Clausen 1428f2fe346SLars-Peter Clausen return 0; 1438f2fe346SLars-Peter Clausen } 1448f2fe346SLars-Peter Clausen 1458f2fe346SLars-Peter Clausen static const struct snd_soc_dai_ops axi_i2s_dai_ops = { 1468f2fe346SLars-Peter Clausen .startup = axi_i2s_startup, 1478f2fe346SLars-Peter Clausen .shutdown = axi_i2s_shutdown, 1488f2fe346SLars-Peter Clausen .trigger = axi_i2s_trigger, 1498f2fe346SLars-Peter Clausen .hw_params = axi_i2s_hw_params, 1508f2fe346SLars-Peter Clausen }; 1518f2fe346SLars-Peter Clausen 1528f2fe346SLars-Peter Clausen static struct snd_soc_dai_driver axi_i2s_dai = { 1538f2fe346SLars-Peter Clausen .probe = axi_i2s_dai_probe, 1548f2fe346SLars-Peter Clausen .playback = { 1558f2fe346SLars-Peter Clausen .channels_min = 2, 1568f2fe346SLars-Peter Clausen .channels_max = 2, 1578f2fe346SLars-Peter Clausen .rates = SNDRV_PCM_RATE_KNOT, 1588f2fe346SLars-Peter Clausen .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE, 1598f2fe346SLars-Peter Clausen }, 1608f2fe346SLars-Peter Clausen .capture = { 1618f2fe346SLars-Peter Clausen .channels_min = 2, 1628f2fe346SLars-Peter Clausen .channels_max = 2, 1638f2fe346SLars-Peter Clausen .rates = SNDRV_PCM_RATE_KNOT, 1648f2fe346SLars-Peter Clausen .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE, 1658f2fe346SLars-Peter Clausen }, 1668f2fe346SLars-Peter Clausen .ops = &axi_i2s_dai_ops, 1678f2fe346SLars-Peter Clausen .symmetric_rates = 1, 1688f2fe346SLars-Peter Clausen }; 1698f2fe346SLars-Peter Clausen 1708f2fe346SLars-Peter Clausen static const struct snd_soc_component_driver axi_i2s_component = { 1718f2fe346SLars-Peter Clausen .name = "axi-i2s", 1728f2fe346SLars-Peter Clausen }; 1738f2fe346SLars-Peter Clausen 1748f2fe346SLars-Peter Clausen static const struct regmap_config axi_i2s_regmap_config = { 1758f2fe346SLars-Peter Clausen .reg_bits = 32, 1768f2fe346SLars-Peter Clausen .reg_stride = 4, 1778f2fe346SLars-Peter Clausen .val_bits = 32, 1788f2fe346SLars-Peter Clausen .max_register = AXI_I2S_REG_STATUS, 1798f2fe346SLars-Peter Clausen }; 1808f2fe346SLars-Peter Clausen 1818f2fe346SLars-Peter Clausen static int axi_i2s_probe(struct platform_device *pdev) 1828f2fe346SLars-Peter Clausen { 1838f2fe346SLars-Peter Clausen struct resource *res; 1848f2fe346SLars-Peter Clausen struct axi_i2s *i2s; 1858f2fe346SLars-Peter Clausen void __iomem *base; 1868f2fe346SLars-Peter Clausen int ret; 1878f2fe346SLars-Peter Clausen 1888f2fe346SLars-Peter Clausen i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); 1898f2fe346SLars-Peter Clausen if (!i2s) 1908f2fe346SLars-Peter Clausen return -ENOMEM; 1918f2fe346SLars-Peter Clausen 1928f2fe346SLars-Peter Clausen platform_set_drvdata(pdev, i2s); 1938f2fe346SLars-Peter Clausen 1948f2fe346SLars-Peter Clausen res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1957fd7a48bSFengguang Wu base = devm_ioremap_resource(&pdev->dev, res); 1967fd7a48bSFengguang Wu if (IS_ERR(base)) 1977fd7a48bSFengguang Wu return PTR_ERR(base); 1988f2fe346SLars-Peter Clausen 1998f2fe346SLars-Peter Clausen i2s->regmap = devm_regmap_init_mmio(&pdev->dev, base, 2008f2fe346SLars-Peter Clausen &axi_i2s_regmap_config); 2018f2fe346SLars-Peter Clausen if (IS_ERR(i2s->regmap)) 2028f2fe346SLars-Peter Clausen return PTR_ERR(i2s->regmap); 2038f2fe346SLars-Peter Clausen 2048f2fe346SLars-Peter Clausen i2s->clk = devm_clk_get(&pdev->dev, "axi"); 2058f2fe346SLars-Peter Clausen if (IS_ERR(i2s->clk)) 2068f2fe346SLars-Peter Clausen return PTR_ERR(i2s->clk); 2078f2fe346SLars-Peter Clausen 2088f2fe346SLars-Peter Clausen i2s->clk_ref = devm_clk_get(&pdev->dev, "ref"); 2098f2fe346SLars-Peter Clausen if (IS_ERR(i2s->clk_ref)) 2108f2fe346SLars-Peter Clausen return PTR_ERR(i2s->clk_ref); 2118f2fe346SLars-Peter Clausen 2128f2fe346SLars-Peter Clausen ret = clk_prepare_enable(i2s->clk); 2138f2fe346SLars-Peter Clausen if (ret) 2148f2fe346SLars-Peter Clausen return ret; 2158f2fe346SLars-Peter Clausen 2168f2fe346SLars-Peter Clausen i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO; 2178f2fe346SLars-Peter Clausen i2s->playback_dma_data.addr_width = 4; 2188f2fe346SLars-Peter Clausen i2s->playback_dma_data.maxburst = 1; 2198f2fe346SLars-Peter Clausen 2208f2fe346SLars-Peter Clausen i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO; 2218f2fe346SLars-Peter Clausen i2s->capture_dma_data.addr_width = 4; 2228f2fe346SLars-Peter Clausen i2s->capture_dma_data.maxburst = 1; 2238f2fe346SLars-Peter Clausen 2248f2fe346SLars-Peter Clausen i2s->ratnum.num = clk_get_rate(i2s->clk_ref) / 2 / AXI_I2S_BITS_PER_FRAME; 2258f2fe346SLars-Peter Clausen i2s->ratnum.den_step = 1; 2268f2fe346SLars-Peter Clausen i2s->ratnum.den_min = 1; 2278f2fe346SLars-Peter Clausen i2s->ratnum.den_max = 64; 2288f2fe346SLars-Peter Clausen 2298f2fe346SLars-Peter Clausen i2s->rate_constraints.rats = &i2s->ratnum; 2308f2fe346SLars-Peter Clausen i2s->rate_constraints.nrats = 1; 2318f2fe346SLars-Peter Clausen 2328f2fe346SLars-Peter Clausen regmap_write(i2s->regmap, AXI_I2S_REG_RESET, AXI_I2S_RESET_GLOBAL); 2338f2fe346SLars-Peter Clausen 2348f2fe346SLars-Peter Clausen ret = devm_snd_soc_register_component(&pdev->dev, &axi_i2s_component, 2358f2fe346SLars-Peter Clausen &axi_i2s_dai, 1); 2368f2fe346SLars-Peter Clausen if (ret) 2378f2fe346SLars-Peter Clausen goto err_clk_disable; 2388f2fe346SLars-Peter Clausen 239153e66f5SLars-Peter Clausen ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); 2408f2fe346SLars-Peter Clausen if (ret) 2418f2fe346SLars-Peter Clausen goto err_clk_disable; 2428f2fe346SLars-Peter Clausen 243*ae6f636bSAndrew Jackson return 0; 244*ae6f636bSAndrew Jackson 2458f2fe346SLars-Peter Clausen err_clk_disable: 2468f2fe346SLars-Peter Clausen clk_disable_unprepare(i2s->clk); 2478f2fe346SLars-Peter Clausen return ret; 2488f2fe346SLars-Peter Clausen } 2498f2fe346SLars-Peter Clausen 2508f2fe346SLars-Peter Clausen static int axi_i2s_dev_remove(struct platform_device *pdev) 2518f2fe346SLars-Peter Clausen { 2528f2fe346SLars-Peter Clausen struct axi_i2s *i2s = platform_get_drvdata(pdev); 2538f2fe346SLars-Peter Clausen 2548f2fe346SLars-Peter Clausen clk_disable_unprepare(i2s->clk); 2558f2fe346SLars-Peter Clausen 2568f2fe346SLars-Peter Clausen return 0; 2578f2fe346SLars-Peter Clausen } 2588f2fe346SLars-Peter Clausen 2598f2fe346SLars-Peter Clausen static const struct of_device_id axi_i2s_of_match[] = { 2608f2fe346SLars-Peter Clausen { .compatible = "adi,axi-i2s-1.00.a", }, 2618f2fe346SLars-Peter Clausen {}, 2628f2fe346SLars-Peter Clausen }; 2638f2fe346SLars-Peter Clausen MODULE_DEVICE_TABLE(of, axi_i2s_of_match); 2648f2fe346SLars-Peter Clausen 2658f2fe346SLars-Peter Clausen static struct platform_driver axi_i2s_driver = { 2668f2fe346SLars-Peter Clausen .driver = { 2678f2fe346SLars-Peter Clausen .name = "axi-i2s", 2688f2fe346SLars-Peter Clausen .of_match_table = axi_i2s_of_match, 2698f2fe346SLars-Peter Clausen }, 2708f2fe346SLars-Peter Clausen .probe = axi_i2s_probe, 2718f2fe346SLars-Peter Clausen .remove = axi_i2s_dev_remove, 2728f2fe346SLars-Peter Clausen }; 2738f2fe346SLars-Peter Clausen module_platform_driver(axi_i2s_driver); 2748f2fe346SLars-Peter Clausen 2758f2fe346SLars-Peter Clausen MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); 2768f2fe346SLars-Peter Clausen MODULE_DESCRIPTION("AXI I2S driver"); 2778f2fe346SLars-Peter Clausen MODULE_LICENSE("GPL"); 278