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

TOMOYO Linux Cross Reference
Linux/sound/soc/fsl/lpc3xxx-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-or-later
  2 //
  3 // Author: Kevin Wells <kevin.wells@nxp.com>
  4 //
  5 // Copyright (C) 2008 NXP Semiconductors
  6 // Copyright 2023 Timesys Corporation <piotr.wojtaszczyk@timesys.com>
  7 
  8 #include <linux/init.h>
  9 #include <linux/module.h>
 10 #include <linux/interrupt.h>
 11 #include <linux/device.h>
 12 #include <linux/delay.h>
 13 #include <linux/clk.h>
 14 #include <linux/io.h>
 15 
 16 #include <sound/core.h>
 17 #include <sound/pcm.h>
 18 #include <sound/pcm_params.h>
 19 #include <sound/dmaengine_pcm.h>
 20 #include <sound/initval.h>
 21 #include <sound/soc.h>
 22 
 23 #include "lpc3xxx-i2s.h"
 24 
 25 #define I2S_PLAYBACK_FLAG 0x1
 26 #define I2S_CAPTURE_FLAG 0x2
 27 
 28 #define LPC3XXX_I2S_RATES ( \
 29         SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
 30         SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
 31         SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
 32 
 33 #define LPC3XXX_I2S_FORMATS ( \
 34         SNDRV_PCM_FMTBIT_S8 | \
 35         SNDRV_PCM_FMTBIT_S16_LE | \
 36         SNDRV_PCM_FMTBIT_S32_LE)
 37 
 38 static void __lpc3xxx_find_clkdiv(u32 *clkx, u32 *clky, int freq, int xbytes, u32 clkrate)
 39 {
 40         u32 i2srate;
 41         u32 idxx, idyy;
 42         u32 savedbitclkrate, diff, trate, baseclk;
 43 
 44         /* Adjust rate for sample size (bits) and 2 channels and offset for
 45          * divider in clock output
 46          */
 47         i2srate = (freq / 100) * 2 * (8 * xbytes);
 48         i2srate = i2srate << 1;
 49         clkrate = clkrate / 100;
 50         baseclk = clkrate;
 51         *clkx = 1;
 52         *clky = 1;
 53 
 54         /* Find the best divider */
 55         *clkx = *clky = 0;
 56         savedbitclkrate = 0;
 57         diff = ~0;
 58         for (idxx = 1; idxx < 0xFF; idxx++) {
 59                 for (idyy = 1; idyy < 0xFF; idyy++) {
 60                         trate = (baseclk * idxx) / idyy;
 61                         if (abs(trate - i2srate) < diff) {
 62                                 diff = abs(trate - i2srate);
 63                                 savedbitclkrate = trate;
 64                                 *clkx = idxx;
 65                                 *clky = idyy;
 66                         }
 67                 }
 68         }
 69 }
 70 
 71 static int lpc3xxx_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai)
 72 {
 73         struct lpc3xxx_i2s_info *i2s_info_p = snd_soc_dai_get_drvdata(cpu_dai);
 74         struct device *dev = i2s_info_p->dev;
 75         u32 flag;
 76         int ret = 0;
 77 
 78         guard(mutex)(&i2s_info_p->lock);
 79 
 80         if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 81                 flag = I2S_PLAYBACK_FLAG;
 82         else
 83                 flag = I2S_CAPTURE_FLAG;
 84 
 85         if (flag & i2s_info_p->streams_in_use) {
 86                 dev_warn(dev, "I2S channel is busy\n");
 87                 ret = -EBUSY;
 88                 return ret;
 89         }
 90 
 91         if (i2s_info_p->streams_in_use == 0) {
 92                 ret = clk_prepare_enable(i2s_info_p->clk);
 93                 if (ret) {
 94                         dev_err(dev, "Can't enable clock, err=%d\n", ret);
 95                         return ret;
 96                 }
 97         }
 98 
 99         i2s_info_p->streams_in_use |= flag;
100         return 0;
101 }
102 
103 static void lpc3xxx_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai)
104 {
105         struct lpc3xxx_i2s_info *i2s_info_p = snd_soc_dai_get_drvdata(cpu_dai);
106         struct regmap *regs = i2s_info_p->regs;
107         const u32 stop_bits = (LPC3XXX_I2S_RESET | LPC3XXX_I2S_STOP);
108         u32 flag;
109 
110         guard(mutex)(&i2s_info_p->lock);
111 
112         if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
113                 flag = I2S_PLAYBACK_FLAG;
114                 regmap_write(regs, LPC3XXX_REG_I2S_TX_RATE, 0);
115                 regmap_update_bits(regs, LPC3XXX_REG_I2S_DAO, stop_bits, stop_bits);
116         } else {
117                 flag = I2S_CAPTURE_FLAG;
118                 regmap_write(regs, LPC3XXX_REG_I2S_RX_RATE, 0);
119                 regmap_update_bits(regs, LPC3XXX_REG_I2S_DAI, stop_bits, stop_bits);
120         }
121         i2s_info_p->streams_in_use &= ~flag;
122 
123         if (i2s_info_p->streams_in_use == 0)
124                 clk_disable_unprepare(i2s_info_p->clk);
125 }
126 
127 static int lpc3xxx_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
128                                       int clk_id, unsigned int freq, int dir)
129 {
130         struct lpc3xxx_i2s_info *i2s_info_p = snd_soc_dai_get_drvdata(cpu_dai);
131 
132         /* Will use in HW params later */
133         i2s_info_p->freq = freq;
134 
135         return 0;
136 }
137 
138 static int lpc3xxx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
139 {
140         struct lpc3xxx_i2s_info *i2s_info_p = snd_soc_dai_get_drvdata(cpu_dai);
141         struct device *dev = i2s_info_p->dev;
142 
143         if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) {
144                 dev_warn(dev, "unsupported bus format %d\n", fmt);
145                 return -EINVAL;
146         }
147 
148         if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_BP_FP) {
149                 dev_warn(dev, "unsupported clock provider %d\n", fmt);
150                 return -EINVAL;
151         }
152 
153         return 0;
154 }
155 
156 static int lpc3xxx_i2s_hw_params(struct snd_pcm_substream *substream,
157                                  struct snd_pcm_hw_params *params,
158                                  struct snd_soc_dai *cpu_dai)
159 {
160         struct lpc3xxx_i2s_info *i2s_info_p = snd_soc_dai_get_drvdata(cpu_dai);
161         struct device *dev = i2s_info_p->dev;
162         struct regmap *regs = i2s_info_p->regs;
163         int xfersize;
164         u32 tmp, clkx, clky;
165 
166         tmp = LPC3XXX_I2S_RESET | LPC3XXX_I2S_STOP;
167         switch (params_format(params)) {
168         case SNDRV_PCM_FORMAT_S8:
169                 tmp |= LPC3XXX_I2S_WW8 | LPC3XXX_I2S_WS_HP(LPC3XXX_I2S_WW8_HP);
170                 xfersize = 1;
171                 break;
172 
173         case SNDRV_PCM_FORMAT_S16_LE:
174                 tmp |= LPC3XXX_I2S_WW16 | LPC3XXX_I2S_WS_HP(LPC3XXX_I2S_WW16_HP);
175                 xfersize = 2;
176                 break;
177 
178         case SNDRV_PCM_FORMAT_S32_LE:
179                 tmp |= LPC3XXX_I2S_WW32 | LPC3XXX_I2S_WS_HP(LPC3XXX_I2S_WW32_HP);
180                 xfersize = 4;
181                 break;
182 
183         default:
184                 dev_warn(dev, "Unsupported audio data format %d\n", params_format(params));
185                 return -EINVAL;
186         }
187 
188         if (params_channels(params) == 1)
189                 tmp |= LPC3XXX_I2S_MONO;
190 
191         __lpc3xxx_find_clkdiv(&clkx, &clky, i2s_info_p->freq, xfersize, i2s_info_p->clkrate);
192 
193         dev_dbg(dev, "Stream                : %s\n",
194                 substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? "playback" : "capture");
195         dev_dbg(dev, "Desired clock rate    : %d\n", i2s_info_p->freq);
196         dev_dbg(dev, "Base clock rate       : %d\n", i2s_info_p->clkrate);
197         dev_dbg(dev, "Transfer size (bytes) : %d\n", xfersize);
198         dev_dbg(dev, "Clock divider (x)     : %d\n", clkx);
199         dev_dbg(dev, "Clock divider (y)     : %d\n", clky);
200         dev_dbg(dev, "Channels              : %d\n", params_channels(params));
201         dev_dbg(dev, "Data format           : %s\n", "I2S");
202 
203         if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
204                 regmap_write(regs, LPC3XXX_REG_I2S_DMA1,
205                              LPC3XXX_I2S_DMA1_TX_EN | LPC3XXX_I2S_DMA0_TX_DEPTH(4));
206                 regmap_write(regs, LPC3XXX_REG_I2S_TX_RATE, (clkx << 8) | clky);
207                 regmap_write(regs, LPC3XXX_REG_I2S_DAO, tmp);
208         } else {
209                 regmap_write(regs, LPC3XXX_REG_I2S_DMA0,
210                              LPC3XXX_I2S_DMA0_RX_EN | LPC3XXX_I2S_DMA1_RX_DEPTH(4));
211                 regmap_write(regs, LPC3XXX_REG_I2S_RX_RATE, (clkx << 8) | clky);
212                 regmap_write(regs, LPC3XXX_REG_I2S_DAI, tmp);
213         }
214 
215         return 0;
216 }
217 
218 static int lpc3xxx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
219                                struct snd_soc_dai *cpu_dai)
220 {
221         struct lpc3xxx_i2s_info *i2s_info_p = snd_soc_dai_get_drvdata(cpu_dai);
222         struct regmap *regs = i2s_info_p->regs;
223         int ret = 0;
224 
225         switch (cmd) {
226         case SNDRV_PCM_TRIGGER_STOP:
227         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
228         case SNDRV_PCM_TRIGGER_SUSPEND:
229                 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
230                         regmap_update_bits(regs, LPC3XXX_REG_I2S_DAO,
231                                            LPC3XXX_I2S_STOP, LPC3XXX_I2S_STOP);
232                 else
233                         regmap_update_bits(regs, LPC3XXX_REG_I2S_DAI,
234                                            LPC3XXX_I2S_STOP, LPC3XXX_I2S_STOP);
235                 break;
236 
237         case SNDRV_PCM_TRIGGER_START:
238         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
239         case SNDRV_PCM_TRIGGER_RESUME:
240                 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
241                         regmap_update_bits(regs, LPC3XXX_REG_I2S_DAO,
242                                            (LPC3XXX_I2S_RESET | LPC3XXX_I2S_STOP), 0);
243                 else
244                         regmap_update_bits(regs, LPC3XXX_REG_I2S_DAI,
245                                            (LPC3XXX_I2S_RESET | LPC3XXX_I2S_STOP), 0);
246                 break;
247         default:
248                 ret = -EINVAL;
249         }
250 
251         return ret;
252 }
253 
254 static int lpc3xxx_i2s_dai_probe(struct snd_soc_dai *dai)
255 {
256         struct lpc3xxx_i2s_info *i2s_info_p = snd_soc_dai_get_drvdata(dai);
257 
258         snd_soc_dai_init_dma_data(dai, &i2s_info_p->playback_dma_config,
259                                   &i2s_info_p->capture_dma_config);
260         return 0;
261 }
262 
263 const struct snd_soc_dai_ops lpc3xxx_i2s_dai_ops = {
264         .probe  = lpc3xxx_i2s_dai_probe,
265         .startup = lpc3xxx_i2s_startup,
266         .shutdown = lpc3xxx_i2s_shutdown,
267         .trigger = lpc3xxx_i2s_trigger,
268         .hw_params = lpc3xxx_i2s_hw_params,
269         .set_sysclk = lpc3xxx_i2s_set_dai_sysclk,
270         .set_fmt = lpc3xxx_i2s_set_dai_fmt,
271 };
272 
273 struct snd_soc_dai_driver lpc3xxx_i2s_dai_driver = {
274         .playback = {
275                 .channels_min = 1,
276                 .channels_max = 2,
277                 .rates = LPC3XXX_I2S_RATES,
278                 .formats = LPC3XXX_I2S_FORMATS,
279                 },
280         .capture = {
281                 .channels_min = 1,
282                 .channels_max = 2,
283                 .rates = LPC3XXX_I2S_RATES,
284                 .formats = LPC3XXX_I2S_FORMATS,
285                 },
286         .ops = &lpc3xxx_i2s_dai_ops,
287         .symmetric_rate = 1,
288         .symmetric_channels = 1,
289         .symmetric_sample_bits = 1,
290 };
291 
292 static const struct snd_soc_component_driver lpc32xx_i2s_component = {
293         .name = "lpc32xx-i2s",
294         .legacy_dai_naming = 1,
295 };
296 
297 static const struct regmap_config lpc32xx_i2s_regconfig = {
298         .reg_bits = 32,
299         .reg_stride = 4,
300         .val_bits = 32,
301         .max_register = LPC3XXX_REG_I2S_RX_RATE,
302 };
303 
304 static int lpc32xx_i2s_probe(struct platform_device *pdev)
305 {
306         struct device *dev = &pdev->dev;
307         struct lpc3xxx_i2s_info *i2s_info_p;
308         struct resource *res;
309         void __iomem *iomem;
310         int ret;
311 
312         i2s_info_p = devm_kzalloc(dev, sizeof(*i2s_info_p), GFP_KERNEL);
313         if (!i2s_info_p)
314                 return -ENOMEM;
315 
316         platform_set_drvdata(pdev, i2s_info_p);
317         i2s_info_p->dev = dev;
318 
319         iomem = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
320         if (IS_ERR(iomem))
321                 return dev_err_probe(dev, PTR_ERR(iomem), "Can't map registers\n");
322 
323         i2s_info_p->regs = devm_regmap_init_mmio(dev, iomem, &lpc32xx_i2s_regconfig);
324         if (IS_ERR(i2s_info_p->regs))
325                 return dev_err_probe(dev, PTR_ERR(i2s_info_p->regs),
326                                      "failed to init register map: %pe\n", i2s_info_p->regs);
327 
328         i2s_info_p->clk = devm_clk_get(dev, NULL);
329         if (IS_ERR(i2s_info_p->clk))
330                 return dev_err_probe(dev, PTR_ERR(i2s_info_p->clk), "Can't get clock\n");
331 
332         i2s_info_p->clkrate = clk_get_rate(i2s_info_p->clk);
333         if (i2s_info_p->clkrate == 0)
334                 return dev_err_probe(dev, -EINVAL, "Invalid returned clock rate\n");
335 
336         mutex_init(&i2s_info_p->lock);
337 
338         ret = devm_snd_soc_register_component(dev, &lpc32xx_i2s_component,
339                                               &lpc3xxx_i2s_dai_driver, 1);
340         if (ret)
341                 return dev_err_probe(dev, ret, "Can't register cpu_dai component\n");
342 
343         i2s_info_p->playback_dma_config.addr = (dma_addr_t)(res->start + LPC3XXX_REG_I2S_TX_FIFO);
344         i2s_info_p->playback_dma_config.maxburst = 4;
345 
346         i2s_info_p->capture_dma_config.addr = (dma_addr_t)(res->start + LPC3XXX_REG_I2S_RX_FIFO);
347         i2s_info_p->capture_dma_config.maxburst = 4;
348 
349         ret = lpc3xxx_pcm_register(pdev);
350         if (ret)
351                 return dev_err_probe(dev, ret, "Can't register pcm component\n");
352 
353         return 0;
354 }
355 
356 static const struct of_device_id lpc32xx_i2s_match[] = {
357         { .compatible = "nxp,lpc3220-i2s" },
358         {},
359 };
360 MODULE_DEVICE_TABLE(of, lpc32xx_i2s_match);
361 
362 static struct platform_driver lpc32xx_i2s_driver = {
363         .probe = lpc32xx_i2s_probe,
364         .driver         = {
365                 .name   = "lpc3xxx-i2s",
366                 .of_match_table = lpc32xx_i2s_match,
367         },
368 };
369 
370 module_platform_driver(lpc32xx_i2s_driver);
371 
372 MODULE_AUTHOR("Kevin Wells <kevin.wells@nxp.com>");
373 MODULE_AUTHOR("Piotr Wojtaszczyk <piotr.wojtaszczyk@timesys.com>");
374 MODULE_DESCRIPTION("ASoC LPC3XXX I2S interface");
375 MODULE_LICENSE("GPL");
376 

~ [ 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