~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/sound/soc/adi/axi-i2s.c

Version: ~ [ linux-6.11.5 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.58 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.114 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.169 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.228 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.284 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.322 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  1 // SPDX-License-Identifier: GPL-2.0-only
  2 /*
  3  * Copyright (C) 2012-2013, Analog Devices Inc.
  4  * Author: Lars-Peter Clausen <lars@metafoo.de>
  5  */
  6 
  7 #include <linux/clk.h>
  8 #include <linux/init.h>
  9 #include <linux/kernel.h>
 10 #include <linux/module.h>
 11 #include <linux/of.h>
 12 #include <linux/platform_device.h>
 13 #include <linux/regmap.h>
 14 #include <linux/slab.h>
 15 
 16 #include <sound/core.h>
 17 #include <sound/pcm.h>
 18 #include <sound/pcm_params.h>
 19 #include <sound/soc.h>
 20 #include <sound/dmaengine_pcm.h>
 21 
 22 #define AXI_I2S_REG_RESET       0x00
 23 #define AXI_I2S_REG_CTRL        0x04
 24 #define AXI_I2S_REG_CLK_CTRL    0x08
 25 #define AXI_I2S_REG_STATUS      0x10
 26 
 27 #define AXI_I2S_REG_RX_FIFO     0x28
 28 #define AXI_I2S_REG_TX_FIFO     0x2C
 29 
 30 #define AXI_I2S_RESET_GLOBAL    BIT(0)
 31 #define AXI_I2S_RESET_TX_FIFO   BIT(1)
 32 #define AXI_I2S_RESET_RX_FIFO   BIT(2)
 33 
 34 #define AXI_I2S_CTRL_TX_EN      BIT(0)
 35 #define AXI_I2S_CTRL_RX_EN      BIT(1)
 36 
 37 /* The frame size is configurable, but for now we always set it 64 bit */
 38 #define AXI_I2S_BITS_PER_FRAME 64
 39 
 40 struct axi_i2s {
 41         struct regmap *regmap;
 42         struct clk *clk;
 43         struct clk *clk_ref;
 44 
 45         bool   has_capture;
 46         bool   has_playback;
 47 
 48         struct snd_soc_dai_driver dai_driver;
 49 
 50         struct snd_dmaengine_dai_dma_data capture_dma_data;
 51         struct snd_dmaengine_dai_dma_data playback_dma_data;
 52 
 53         struct snd_ratnum ratnum;
 54         struct snd_pcm_hw_constraint_ratnums rate_constraints;
 55 };
 56 
 57 static int axi_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
 58         struct snd_soc_dai *dai)
 59 {
 60         struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
 61         unsigned int mask, val;
 62 
 63         if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
 64                 mask = AXI_I2S_CTRL_RX_EN;
 65         else
 66                 mask = AXI_I2S_CTRL_TX_EN;
 67 
 68         switch (cmd) {
 69         case SNDRV_PCM_TRIGGER_START:
 70         case SNDRV_PCM_TRIGGER_RESUME:
 71         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 72                 val = mask;
 73                 break;
 74         case SNDRV_PCM_TRIGGER_STOP:
 75         case SNDRV_PCM_TRIGGER_SUSPEND:
 76         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 77                 val = 0;
 78                 break;
 79         default:
 80                 return -EINVAL;
 81         }
 82 
 83         regmap_update_bits(i2s->regmap, AXI_I2S_REG_CTRL, mask, val);
 84 
 85         return 0;
 86 }
 87 
 88 static int axi_i2s_hw_params(struct snd_pcm_substream *substream,
 89         struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
 90 {
 91         struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
 92         unsigned int bclk_div, word_size;
 93         unsigned int bclk_rate;
 94 
 95         bclk_rate = params_rate(params) * AXI_I2S_BITS_PER_FRAME;
 96 
 97         word_size = AXI_I2S_BITS_PER_FRAME / 2 - 1;
 98         bclk_div = DIV_ROUND_UP(clk_get_rate(i2s->clk_ref), bclk_rate) / 2 - 1;
 99 
100         regmap_write(i2s->regmap, AXI_I2S_REG_CLK_CTRL, (word_size << 16) |
101                 bclk_div);
102 
103         return 0;
104 }
105 
106 static int axi_i2s_startup(struct snd_pcm_substream *substream,
107         struct snd_soc_dai *dai)
108 {
109         struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
110         uint32_t mask;
111         int ret;
112 
113         if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
114                 mask = AXI_I2S_RESET_RX_FIFO;
115         else
116                 mask = AXI_I2S_RESET_TX_FIFO;
117 
118         regmap_write(i2s->regmap, AXI_I2S_REG_RESET, mask);
119 
120         ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
121                            SNDRV_PCM_HW_PARAM_RATE,
122                            &i2s->rate_constraints);
123         if (ret)
124                 return ret;
125 
126         return clk_prepare_enable(i2s->clk_ref);
127 }
128 
129 static void axi_i2s_shutdown(struct snd_pcm_substream *substream,
130         struct snd_soc_dai *dai)
131 {
132         struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
133 
134         clk_disable_unprepare(i2s->clk_ref);
135 }
136 
137 static int axi_i2s_dai_probe(struct snd_soc_dai *dai)
138 {
139         struct axi_i2s *i2s = snd_soc_dai_get_drvdata(dai);
140 
141         snd_soc_dai_init_dma_data(
142                 dai,
143                 i2s->has_playback ? &i2s->playback_dma_data : NULL,
144                 i2s->has_capture  ? &i2s->capture_dma_data  : NULL);
145 
146         return 0;
147 }
148 
149 static const struct snd_soc_dai_ops axi_i2s_dai_ops = {
150         .probe = axi_i2s_dai_probe,
151         .startup = axi_i2s_startup,
152         .shutdown = axi_i2s_shutdown,
153         .trigger = axi_i2s_trigger,
154         .hw_params = axi_i2s_hw_params,
155 };
156 
157 static struct snd_soc_dai_driver axi_i2s_dai = {
158         .ops = &axi_i2s_dai_ops,
159         .symmetric_rate = 1,
160 };
161 
162 static const struct snd_soc_component_driver axi_i2s_component = {
163         .name = "axi-i2s",
164         .legacy_dai_naming = 1,
165 };
166 
167 static const struct regmap_config axi_i2s_regmap_config = {
168         .reg_bits = 32,
169         .reg_stride = 4,
170         .val_bits = 32,
171         .max_register = AXI_I2S_REG_STATUS,
172 };
173 
174 static void axi_i2s_parse_of(struct axi_i2s *i2s, const struct device_node *np)
175 {
176         struct property *dma_names;
177         const char *dma_name;
178 
179         of_property_for_each_string(np, "dma-names", dma_names, dma_name) {
180                 if (strcmp(dma_name, "rx") == 0)
181                         i2s->has_capture = true;
182                 if (strcmp(dma_name, "tx") == 0)
183                         i2s->has_playback = true;
184         }
185 }
186 
187 static int axi_i2s_probe(struct platform_device *pdev)
188 {
189         struct resource *res;
190         struct axi_i2s *i2s;
191         void __iomem *base;
192         int ret;
193 
194         i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
195         if (!i2s)
196                 return -ENOMEM;
197 
198         platform_set_drvdata(pdev, i2s);
199 
200         axi_i2s_parse_of(i2s, pdev->dev.of_node);
201 
202         base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
203         if (IS_ERR(base))
204                 return PTR_ERR(base);
205 
206         i2s->regmap = devm_regmap_init_mmio(&pdev->dev, base,
207                 &axi_i2s_regmap_config);
208         if (IS_ERR(i2s->regmap))
209                 return PTR_ERR(i2s->regmap);
210 
211         i2s->clk = devm_clk_get(&pdev->dev, "axi");
212         if (IS_ERR(i2s->clk))
213                 return PTR_ERR(i2s->clk);
214 
215         i2s->clk_ref = devm_clk_get(&pdev->dev, "ref");
216         if (IS_ERR(i2s->clk_ref))
217                 return PTR_ERR(i2s->clk_ref);
218 
219         ret = clk_prepare_enable(i2s->clk);
220         if (ret)
221                 return ret;
222 
223         if (i2s->has_playback) {
224                 axi_i2s_dai.playback.channels_min = 2;
225                 axi_i2s_dai.playback.channels_max = 2;
226                 axi_i2s_dai.playback.rates = SNDRV_PCM_RATE_KNOT;
227                 axi_i2s_dai.playback.formats =
228                         SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE;
229 
230                 i2s->playback_dma_data.addr = res->start + AXI_I2S_REG_TX_FIFO;
231                 i2s->playback_dma_data.addr_width = 4;
232                 i2s->playback_dma_data.maxburst = 1;
233         }
234 
235         if (i2s->has_capture) {
236                 axi_i2s_dai.capture.channels_min = 2;
237                 axi_i2s_dai.capture.channels_max = 2;
238                 axi_i2s_dai.capture.rates = SNDRV_PCM_RATE_KNOT;
239                 axi_i2s_dai.capture.formats =
240                         SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_U32_LE;
241 
242                 i2s->capture_dma_data.addr = res->start + AXI_I2S_REG_RX_FIFO;
243                 i2s->capture_dma_data.addr_width = 4;
244                 i2s->capture_dma_data.maxburst = 1;
245         }
246 
247         i2s->ratnum.num = clk_get_rate(i2s->clk_ref) / 2 / AXI_I2S_BITS_PER_FRAME;
248         i2s->ratnum.den_step = 1;
249         i2s->ratnum.den_min = 1;
250         i2s->ratnum.den_max = 64;
251 
252         i2s->rate_constraints.rats = &i2s->ratnum;
253         i2s->rate_constraints.nrats = 1;
254 
255         regmap_write(i2s->regmap, AXI_I2S_REG_RESET, AXI_I2S_RESET_GLOBAL);
256 
257         ret = devm_snd_soc_register_component(&pdev->dev, &axi_i2s_component,
258                                          &axi_i2s_dai, 1);
259         if (ret)
260                 goto err_clk_disable;
261 
262         ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
263         if (ret)
264                 goto err_clk_disable;
265 
266         dev_info(&pdev->dev, "probed, capture %s, playback %s\n",
267                  i2s->has_capture ? "enabled" : "disabled",
268                  i2s->has_playback ? "enabled" : "disabled");
269 
270         return 0;
271 
272 err_clk_disable:
273         clk_disable_unprepare(i2s->clk);
274         return ret;
275 }
276 
277 static void axi_i2s_dev_remove(struct platform_device *pdev)
278 {
279         struct axi_i2s *i2s = platform_get_drvdata(pdev);
280 
281         clk_disable_unprepare(i2s->clk);
282 }
283 
284 static const struct of_device_id axi_i2s_of_match[] = {
285         { .compatible = "adi,axi-i2s-1.00.a", },
286         {},
287 };
288 MODULE_DEVICE_TABLE(of, axi_i2s_of_match);
289 
290 static struct platform_driver axi_i2s_driver = {
291         .driver = {
292                 .name = "axi-i2s",
293                 .of_match_table = axi_i2s_of_match,
294         },
295         .probe = axi_i2s_probe,
296         .remove_new = axi_i2s_dev_remove,
297 };
298 module_platform_driver(axi_i2s_driver);
299 
300 MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
301 MODULE_DESCRIPTION("AXI I2S driver");
302 MODULE_LICENSE("GPL");
303 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php