1fda8d26eSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 28f2fe346SLars-Peter Clausen /* 38f2fe346SLars-Peter Clausen * Copyright (C) 2012-2013, Analog Devices Inc. 48f2fe346SLars-Peter Clausen * Author: Lars-Peter Clausen <lars@metafoo.de> 58f2fe346SLars-Peter Clausen */ 68f2fe346SLars-Peter Clausen 78f2fe346SLars-Peter Clausen #include <linux/clk.h> 88f2fe346SLars-Peter Clausen #include <linux/init.h> 98f2fe346SLars-Peter Clausen #include <linux/kernel.h> 108f2fe346SLars-Peter Clausen #include <linux/module.h> 118f2fe346SLars-Peter Clausen #include <linux/of.h> 128f2fe346SLars-Peter Clausen #include <linux/platform_device.h> 138f2fe346SLars-Peter Clausen #include <linux/regmap.h> 148f2fe346SLars-Peter Clausen #include <linux/slab.h> 158f2fe346SLars-Peter Clausen 168f2fe346SLars-Peter Clausen #include <sound/core.h> 178f2fe346SLars-Peter Clausen #include <sound/pcm.h> 188f2fe346SLars-Peter Clausen #include <sound/pcm_params.h> 198f2fe346SLars-Peter Clausen #include <sound/soc.h> 208f2fe346SLars-Peter Clausen #include <sound/dmaengine_pcm.h> 218f2fe346SLars-Peter Clausen 228f2fe346SLars-Peter Clausen #define AXI_I2S_REG_RESET 0x00 238f2fe346SLars-Peter Clausen #define AXI_I2S_REG_CTRL 0x04 248f2fe346SLars-Peter Clausen #define AXI_I2S_REG_CLK_CTRL 0x08 258f2fe346SLars-Peter Clausen #define AXI_I2S_REG_STATUS 0x10 268f2fe346SLars-Peter Clausen 278f2fe346SLars-Peter Clausen #define AXI_I2S_REG_RX_FIFO 0x28 288f2fe346SLars-Peter Clausen #define AXI_I2S_REG_TX_FIFO 0x2C 298f2fe346SLars-Peter Clausen 308f2fe346SLars-Peter Clausen #define AXI_I2S_RESET_GLOBAL BIT(0) 318f2fe346SLars-Peter Clausen #define AXI_I2S_RESET_TX_FIFO BIT(1) 328f2fe346SLars-Peter Clausen #define AXI_I2S_RESET_RX_FIFO BIT(2) 338f2fe346SLars-Peter Clausen 348f2fe346SLars-Peter Clausen #define AXI_I2S_CTRL_TX_EN BIT(0) 358f2fe346SLars-Peter Clausen #define AXI_I2S_CTRL_RX_EN BIT(1) 368f2fe346SLars-Peter Clausen 378f2fe346SLars-Peter Clausen /* The frame size is configurable, but for now we always set it 64 bit */ 388f2fe346SLars-Peter Clausen #define AXI_I2S_BITS_PER_FRAME 64 398f2fe346SLars-Peter Clausen 408f2fe346SLars-Peter Clausen struct axi_i2s { 418f2fe346SLars-Peter Clausen struct regmap *regmap; 428f2fe346SLars-Peter Clausen struct clk *clk; 438f2fe346SLars-Peter Clausen struct clk *clk_ref; 448f2fe346SLars-Peter Clausen 457bf7d055SLuca Ceresoli bool has_capture; 467bf7d055SLuca Ceresoli bool has_playback; 477bf7d055SLuca Ceresoli 488f2fe346SLars-Peter Clausen struct snd_soc_dai_driver dai_driver; 498f2fe346SLars-Peter Clausen 508f2fe346SLars-Peter Clausen struct snd_dmaengine_dai_dma_data capture_dma_data; 518f2fe346SLars-Peter Clausen struct snd_dmaengine_dai_dma_data playback_dma_data; 528f2fe346SLars-Peter Clausen 538f2fe346SLars-Peter Clausen struct snd_ratnum ratnum; 548f2fe346SLars-Peter Clausen struct snd_pcm_hw_constraint_ratnums rate_constraints; 558f2fe346SLars-Peter Clausen }; 568f2fe346SLars-Peter Clausen 578f2fe346SLars-Peter Clausen static int axi_i2s_trigger(struct snd_pcm_substream *substream, int cmd, 588f2fe346SLars-Peter Clausen struct snd_soc_dai *dai) 598f2fe346SLars-Peter Clausen { 608f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 618f2fe346SLars-Peter Clausen unsigned int mask, val; 628f2fe346SLars-Peter Clausen 638f2fe346SLars-Peter Clausen if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 648f2fe346SLars-Peter Clausen mask = AXI_I2S_CTRL_RX_EN; 658f2fe346SLars-Peter Clausen else 668f2fe346SLars-Peter Clausen mask = AXI_I2S_CTRL_TX_EN; 678f2fe346SLars-Peter Clausen 688f2fe346SLars-Peter Clausen switch (cmd) { 698f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_START: 708f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_RESUME: 718f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 728f2fe346SLars-Peter Clausen val = mask; 738f2fe346SLars-Peter Clausen break; 748f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_STOP: 758f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_SUSPEND: 768f2fe346SLars-Peter Clausen case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 778f2fe346SLars-Peter Clausen val = 0; 788f2fe346SLars-Peter Clausen break; 798f2fe346SLars-Peter Clausen default: 808f2fe346SLars-Peter Clausen return -EINVAL; 818f2fe346SLars-Peter Clausen } 828f2fe346SLars-Peter Clausen 838f2fe346SLars-Peter Clausen regmap_update_bits(i2s->regmap, AXI_I2S_REG_CTRL, mask, val); 848f2fe346SLars-Peter Clausen 858f2fe346SLars-Peter Clausen return 0; 868f2fe346SLars-Peter Clausen } 878f2fe346SLars-Peter Clausen 888f2fe346SLars-Peter Clausen static int axi_i2s_hw_params(struct snd_pcm_substream *substream, 898f2fe346SLars-Peter Clausen struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) 908f2fe346SLars-Peter Clausen { 918f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 928f2fe346SLars-Peter Clausen unsigned int bclk_div, word_size; 938f2fe346SLars-Peter Clausen unsigned int bclk_rate; 948f2fe346SLars-Peter Clausen 958f2fe346SLars-Peter Clausen bclk_rate = params_rate(params) * AXI_I2S_BITS_PER_FRAME; 968f2fe346SLars-Peter Clausen 978f2fe346SLars-Peter Clausen word_size = AXI_I2S_BITS_PER_FRAME / 2 - 1; 988f2fe346SLars-Peter Clausen bclk_div = DIV_ROUND_UP(clk_get_rate(i2s->clk_ref), bclk_rate) / 2 - 1; 998f2fe346SLars-Peter Clausen 1008f2fe346SLars-Peter Clausen regmap_write(i2s->regmap, AXI_I2S_REG_CLK_CTRL, (word_size << 16) | 1018f2fe346SLars-Peter Clausen bclk_div); 1028f2fe346SLars-Peter Clausen 1038f2fe346SLars-Peter Clausen return 0; 1048f2fe346SLars-Peter Clausen } 1058f2fe346SLars-Peter Clausen 1068f2fe346SLars-Peter Clausen static int axi_i2s_startup(struct snd_pcm_substream *substream, 1078f2fe346SLars-Peter Clausen struct snd_soc_dai *dai) 1088f2fe346SLars-Peter Clausen { 1098f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 1108f2fe346SLars-Peter Clausen uint32_t mask; 1118f2fe346SLars-Peter Clausen int ret; 1128f2fe346SLars-Peter Clausen 1138f2fe346SLars-Peter Clausen if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 1148f2fe346SLars-Peter Clausen mask = AXI_I2S_RESET_RX_FIFO; 1158f2fe346SLars-Peter Clausen else 1168f2fe346SLars-Peter Clausen mask = AXI_I2S_RESET_TX_FIFO; 1178f2fe346SLars-Peter Clausen 1188f2fe346SLars-Peter Clausen regmap_write(i2s->regmap, AXI_I2S_REG_RESET, mask); 1198f2fe346SLars-Peter Clausen 1208f2fe346SLars-Peter Clausen ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0, 1218f2fe346SLars-Peter Clausen SNDRV_PCM_HW_PARAM_RATE, 1228f2fe346SLars-Peter Clausen &i2s->rate_constraints); 1238f2fe346SLars-Peter Clausen if (ret) 1248f2fe346SLars-Peter Clausen return ret; 1258f2fe346SLars-Peter Clausen 1268f2fe346SLars-Peter Clausen return clk_prepare_enable(i2s->clk_ref); 1278f2fe346SLars-Peter Clausen } 1288f2fe346SLars-Peter Clausen 1298f2fe346SLars-Peter Clausen static void axi_i2s_shutdown(struct snd_pcm_substream *substream, 1308f2fe346SLars-Peter Clausen struct snd_soc_dai *dai) 1318f2fe346SLars-Peter Clausen { 1328f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 1338f2fe346SLars-Peter Clausen 1348f2fe346SLars-Peter Clausen clk_disable_unprepare(i2s->clk_ref); 1358f2fe346SLars-Peter Clausen } 1368f2fe346SLars-Peter Clausen 1378f2fe346SLars-Peter Clausen static int axi_i2s_dai_probe(struct snd_soc_dai *dai) 1388f2fe346SLars-Peter Clausen { 1398f2fe346SLars-Peter Clausen struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai); 1408f2fe346SLars-Peter Clausen 1417bf7d055SLuca Ceresoli snd_soc_dai_init_dma_data( 1427bf7d055SLuca Ceresoli dai, 1437bf7d055SLuca Ceresoli i2s->has_playback ? &i2s->playback_dma_data : NULL, 1447bf7d055SLuca Ceresoli i2s->has_capture ? &i2s->capture_dma_data : NULL); 1458f2fe346SLars-Peter Clausen 1468f2fe346SLars-Peter Clausen return 0; 1478f2fe346SLars-Peter Clausen } 1488f2fe346SLars-Peter Clausen 1498f2fe346SLars-Peter Clausen static const struct snd_soc_dai_ops axi_i2s_dai_ops = { 1508f2fe346SLars-Peter Clausen .startup = axi_i2s_startup, 1518f2fe346SLars-Peter Clausen .shutdown = axi_i2s_shutdown, 1528f2fe346SLars-Peter Clausen .trigger = axi_i2s_trigger, 1538f2fe346SLars-Peter Clausen .hw_params = axi_i2s_hw_params, 1548f2fe346SLars-Peter Clausen }; 1558f2fe346SLars-Peter Clausen 1568f2fe346SLars-Peter Clausen static struct snd_soc_dai_driver axi_i2s_dai = { 1578f2fe346SLars-Peter Clausen .probe = axi_i2s_dai_probe, 1588f2fe346SLars-Peter Clausen .ops = &axi_i2s_dai_ops, 15955d0056bSKuninori Morimoto .symmetric_rate = 1, 1608f2fe346SLars-Peter Clausen }; 1618f2fe346SLars-Peter Clausen 1628f2fe346SLars-Peter Clausen static const struct snd_soc_component_driver axi_i2s_component = { 1638f2fe346SLars-Peter Clausen .name = "axi-i2s", 1649a34161aSCharles Keepax .legacy_dai_naming = 1, 1658f2fe346SLars-Peter Clausen }; 1668f2fe346SLars-Peter Clausen 1678f2fe346SLars-Peter Clausen static const struct regmap_config axi_i2s_regmap_config = { 1688f2fe346SLars-Peter Clausen .reg_bits = 32, 1698f2fe346SLars-Peter Clausen .reg_stride = 4, 1708f2fe346SLars-Peter Clausen .val_bits = 32, 1718f2fe346SLars-Peter Clausen .max_register = AXI_I2S_REG_STATUS, 1728f2fe346SLars-Peter Clausen }; 1738f2fe346SLars-Peter Clausen 1747bf7d055SLuca Ceresoli static void axi_i2s_parse_of(struct axi_i2s *i2s, const struct device_node *np) 1757bf7d055SLuca Ceresoli { 1767bf7d055SLuca Ceresoli struct property *dma_names; 1777bf7d055SLuca Ceresoli const char *dma_name; 1787bf7d055SLuca Ceresoli 1797bf7d055SLuca Ceresoli of_property_for_each_string(np, "dma-names", dma_names, dma_name) { 1807bf7d055SLuca Ceresoli if (strcmp(dma_name, "rx") == 0) 1817bf7d055SLuca Ceresoli i2s->has_capture = true; 1827bf7d055SLuca Ceresoli if (strcmp(dma_name, "tx") == 0) 1837bf7d055SLuca Ceresoli i2s->has_playback = true; 1847bf7d055SLuca Ceresoli } 1857bf7d055SLuca Ceresoli } 1867bf7d055SLuca Ceresoli 1878f2fe346SLars-Peter Clausen static int axi_i2s_probe(struct platform_device *pdev) 1888f2fe346SLars-Peter Clausen { 1898f2fe346SLars-Peter Clausen struct resource *res; 1908f2fe346SLars-Peter Clausen struct axi_i2s *i2s; 1918f2fe346SLars-Peter Clausen void __iomem *base; 1928f2fe346SLars-Peter Clausen int ret; 1938f2fe346SLars-Peter Clausen 1948f2fe346SLars-Peter Clausen i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); 1958f2fe346SLars-Peter Clausen if (!i2s) 1968f2fe346SLars-Peter Clausen return -ENOMEM; 1978f2fe346SLars-Peter Clausen 1988f2fe346SLars-Peter Clausen platform_set_drvdata(pdev, i2s); 1998f2fe346SLars-Peter Clausen 2007bf7d055SLuca Ceresoli axi_i2s_parse_of(i2s, pdev->dev.of_node); 2017bf7d055SLuca Ceresoli 2021b7f94ddSYang Yingliang base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 2037fd7a48bSFengguang Wu if (IS_ERR(base)) 2047fd7a48bSFengguang Wu return PTR_ERR(base); 2058f2fe346SLars-Peter Clausen 2068f2fe346SLars-Peter Clausen i2s->regmap = devm_regmap_init_mmio(&pdev->dev, base, 2078f2fe346SLars-Peter Clausen &axi_i2s_regmap_config); 2088f2fe346SLars-Peter Clausen if (IS_ERR(i2s->regmap)) 2098f2fe346SLars-Peter Clausen return PTR_ERR(i2s->regmap); 2108f2fe346SLars-Peter Clausen 2118f2fe346SLars-Peter Clausen i2s->clk = devm_clk_get(&pdev->dev, "axi"); 2128f2fe346SLars-Peter Clausen if (IS_ERR(i2s->clk)) 2138f2fe346SLars-Peter Clausen return PTR_ERR(i2s->clk); 2148f2fe346SLars-Peter Clausen 2158f2fe346SLars-Peter Clausen i2s->clk_ref = devm_clk_get(&pdev->dev, "ref"); 2168f2fe346SLars-Peter Clausen if (IS_ERR(i2s->clk_ref)) 2178f2fe346SLars-Peter Clausen return PTR_ERR(i2s->clk_ref); 2188f2fe346SLars-Peter Clausen 2198f2fe346SLars-Peter Clausen ret = clk_prepare_enable(i2s->clk); 2208f2fe346SLars-Peter Clausen if (ret) 2218f2fe346SLars-Peter Clausen return ret; 2228f2fe346SLars-Peter Clausen 2237bf7d055SLuca Ceresoli if (i2s->has_playback) { 2247bf7d055SLuca Ceresoli axi_i2s_dai.playback.channels_min = 2; 2257bf7d055SLuca Ceresoli axi_i2s_dai.playback.channels_max = 2; 2267bf7d055SLuca Ceresoli axi_i2s_dai.playback.rates = SNDRV_PCM_RATE_KNOT; 2277bf7d055SLuca Ceresoli axi_i2s_dai.playback.formats = 2287bf7d055SLuca Ceresoli SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE; 2297bf7d055SLuca Ceresoli 2308f2fe346SLars-Peter Clausen i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO; 2318f2fe346SLars-Peter Clausen i2s->playback_dma_data.addr_width = 4; 2328f2fe346SLars-Peter Clausen i2s->playback_dma_data.maxburst = 1; 2337bf7d055SLuca Ceresoli } 2347bf7d055SLuca Ceresoli 2357bf7d055SLuca Ceresoli if (i2s->has_capture) { 2367bf7d055SLuca Ceresoli axi_i2s_dai.capture.channels_min = 2; 2377bf7d055SLuca Ceresoli axi_i2s_dai.capture.channels_max = 2; 2387bf7d055SLuca Ceresoli axi_i2s_dai.capture.rates = SNDRV_PCM_RATE_KNOT; 2397bf7d055SLuca Ceresoli axi_i2s_dai.capture.formats = 2407bf7d055SLuca Ceresoli SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE; 2418f2fe346SLars-Peter Clausen 2428f2fe346SLars-Peter Clausen i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO; 2438f2fe346SLars-Peter Clausen i2s->capture_dma_data.addr_width = 4; 2448f2fe346SLars-Peter Clausen i2s->capture_dma_data.maxburst = 1; 2457bf7d055SLuca Ceresoli } 2468f2fe346SLars-Peter Clausen 2478f2fe346SLars-Peter Clausen i2s->ratnum.num = clk_get_rate(i2s->clk_ref) / 2 / AXI_I2S_BITS_PER_FRAME; 2488f2fe346SLars-Peter Clausen i2s->ratnum.den_step = 1; 2498f2fe346SLars-Peter Clausen i2s->ratnum.den_min = 1; 2508f2fe346SLars-Peter Clausen i2s->ratnum.den_max = 64; 2518f2fe346SLars-Peter Clausen 2528f2fe346SLars-Peter Clausen i2s->rate_constraints.rats = &i2s->ratnum; 2538f2fe346SLars-Peter Clausen i2s->rate_constraints.nrats = 1; 2548f2fe346SLars-Peter Clausen 2558f2fe346SLars-Peter Clausen regmap_write(i2s->regmap, AXI_I2S_REG_RESET, AXI_I2S_RESET_GLOBAL); 2568f2fe346SLars-Peter Clausen 2578f2fe346SLars-Peter Clausen ret = devm_snd_soc_register_component(&pdev->dev, &axi_i2s_component, 2588f2fe346SLars-Peter Clausen &axi_i2s_dai, 1); 2598f2fe346SLars-Peter Clausen if (ret) 2608f2fe346SLars-Peter Clausen goto err_clk_disable; 2618f2fe346SLars-Peter Clausen 262153e66f5SLars-Peter Clausen ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); 2638f2fe346SLars-Peter Clausen if (ret) 2648f2fe346SLars-Peter Clausen goto err_clk_disable; 2658f2fe346SLars-Peter Clausen 2667bf7d055SLuca Ceresoli dev_info(&pdev->dev, "probed, capture %s, playback %s\n", 2677bf7d055SLuca Ceresoli i2s->has_capture ? "enabled" : "disabled", 2687bf7d055SLuca Ceresoli i2s->has_playback ? "enabled" : "disabled"); 2697bf7d055SLuca Ceresoli 270ae6f636bSAndrew Jackson return 0; 271ae6f636bSAndrew Jackson 2728f2fe346SLars-Peter Clausen err_clk_disable: 2738f2fe346SLars-Peter Clausen clk_disable_unprepare(i2s->clk); 2748f2fe346SLars-Peter Clausen return ret; 2758f2fe346SLars-Peter Clausen } 2768f2fe346SLars-Peter Clausen 277*711c5b4eSUwe Kleine-König static void axi_i2s_dev_remove(struct platform_device *pdev) 2788f2fe346SLars-Peter Clausen { 2798f2fe346SLars-Peter Clausen struct axi_i2s *i2s = platform_get_drvdata(pdev); 2808f2fe346SLars-Peter Clausen 2818f2fe346SLars-Peter Clausen clk_disable_unprepare(i2s->clk); 2828f2fe346SLars-Peter Clausen } 2838f2fe346SLars-Peter Clausen 2848f2fe346SLars-Peter Clausen static const struct of_device_id axi_i2s_of_match[] = { 2858f2fe346SLars-Peter Clausen { .compatible = "adi,axi-i2s-1.00.a", }, 2868f2fe346SLars-Peter Clausen {}, 2878f2fe346SLars-Peter Clausen }; 2888f2fe346SLars-Peter Clausen MODULE_DEVICE_TABLE(of, axi_i2s_of_match); 2898f2fe346SLars-Peter Clausen 2908f2fe346SLars-Peter Clausen static struct platform_driver axi_i2s_driver = { 2918f2fe346SLars-Peter Clausen .driver = { 2928f2fe346SLars-Peter Clausen .name = "axi-i2s", 2938f2fe346SLars-Peter Clausen .of_match_table = axi_i2s_of_match, 2948f2fe346SLars-Peter Clausen }, 2958f2fe346SLars-Peter Clausen .probe = axi_i2s_probe, 296*711c5b4eSUwe Kleine-König .remove_new = axi_i2s_dev_remove, 2978f2fe346SLars-Peter Clausen }; 2988f2fe346SLars-Peter Clausen module_platform_driver(axi_i2s_driver); 2998f2fe346SLars-Peter Clausen 3008f2fe346SLars-Peter Clausen MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); 3018f2fe346SLars-Peter Clausen MODULE_DESCRIPTION("AXI I2S driver"); 3028f2fe346SLars-Peter Clausen MODULE_LICENSE("GPL"); 303