Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add audio support for LPC32XX CPUs

Merge series from Piotr Wojtaszczyk <piotr.wojtaszczyk@timesys.com>:

This pach set is to bring back audio to machines with a LPC32XX CPU.
The legacy LPC32XX SoC used to have audio spport in linux 2.6.27.
The support was dropped due to lack of interest from mainaeners.

+618
+73
Documentation/devicetree/bindings/sound/nxp,lpc3220-i2s.yaml
··· 1 + # SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2 + %YAML 1.2 3 + --- 4 + $id: http://devicetree.org/schemas/sound/nxp,lpc3220-i2s.yaml# 5 + $schema: http://devicetree.org/meta-schemas/core.yaml# 6 + 7 + title: NXP LPC32XX I2S Controller 8 + 9 + description: 10 + The I2S controller in LPC32XX SoCs, ASoC DAI. 11 + 12 + maintainers: 13 + - J.M.B. Downing <jonathan.downing@nautel.com> 14 + - Piotr Wojtaszczyk <piotr.wojtaszczyk@timesys.com> 15 + 16 + allOf: 17 + - $ref: dai-common.yaml# 18 + 19 + properties: 20 + compatible: 21 + enum: 22 + - nxp,lpc3220-i2s 23 + 24 + reg: 25 + maxItems: 1 26 + 27 + interrupts: 28 + maxItems: 1 29 + 30 + clocks: 31 + items: 32 + - description: input clock of the peripheral. 33 + 34 + dmas: 35 + items: 36 + - description: RX DMA Channel 37 + - description: TX DMA Channel 38 + 39 + dma-names: 40 + items: 41 + - const: rx 42 + - const: tx 43 + 44 + "#sound-dai-cells": 45 + const: 0 46 + 47 + required: 48 + - compatible 49 + - reg 50 + - interrupts 51 + - clocks 52 + - dmas 53 + - dma-names 54 + - '#sound-dai-cells' 55 + 56 + additionalProperties: false 57 + 58 + examples: 59 + - | 60 + #include <dt-bindings/clock/lpc32xx-clock.h> 61 + #include <dt-bindings/interrupt-controller/irq.h> 62 + 63 + i2s@20094000 { 64 + compatible = "nxp,lpc3220-i2s"; 65 + reg = <0x20094000 0x1000>; 66 + interrupts = <22 IRQ_TYPE_LEVEL_HIGH>; 67 + clocks = <&clk LPC32XX_CLK_I2S0>; 68 + dmas = <&dma 0 1>, <&dma 13 1>; 69 + dma-names = "rx", "tx"; 70 + #sound-dai-cells = <0>; 71 + }; 72 + 73 + ...
+10
MAINTAINERS
··· 8909 8909 F: sound/soc/fsl/fsl* 8910 8910 F: sound/soc/fsl/imx* 8911 8911 8912 + FREESCALE SOC LPC32XX SOUND DRIVERS 8913 + M: J.M.B. Downing <jonathan.downing@nautel.com> 8914 + M: Piotr Wojtaszczyk <piotr.wojtaszczyk@timesys.com> 8915 + R: Vladimir Zapolskiy <vz@mleia.com> 8916 + L: alsa-devel@alsa-project.org (moderated for non-subscribers) 8917 + L: linuxppc-dev@lists.ozlabs.org 8918 + S: Maintained 8919 + F: Documentation/devicetree/bindings/sound/nxp,lpc3220-i2s.yaml 8920 + F: sound/soc/fsl/lpc3xxx-* 8921 + 8912 8922 FREESCALE SOC SOUND QMC DRIVER 8913 8923 M: Herve Codina <herve.codina@bootlin.com> 8914 8924 L: alsa-devel@alsa-project.org (moderated for non-subscribers)
+7
sound/soc/fsl/Kconfig
··· 131 131 This option is only useful for out-of-tree drivers since 132 132 in-tree drivers select it automatically. 133 133 134 + config SND_SOC_FSL_LPC3XXX 135 + tristate "SoC Audio for NXP LPC32XX CPUs" 136 + depends on ARCH_LPC32XX || COMPILE_TEST 137 + select SND_SOC_GENERIC_DMAENGINE_PCM 138 + help 139 + Say Y or M if you want to add support for the LPC3XXX I2S interface. 140 + 134 141 config SND_SOC_IMX_PCM_DMA 135 142 tristate 136 143 select SND_SOC_GENERIC_DMAENGINE_PCM
+2
sound/soc/fsl/Makefile
··· 11 11 snd-soc-fsl-audmix-y := fsl_audmix.o 12 12 snd-soc-fsl-asoc-card-y := fsl-asoc-card.o 13 13 snd-soc-fsl-asrc-y := fsl_asrc.o fsl_asrc_dma.o 14 + snd-soc-fsl-lpc3xxx-y := lpc3xxx-pcm.o lpc3xxx-i2s.o 14 15 snd-soc-fsl-sai-y := fsl_sai.o 15 16 snd-soc-fsl-ssi-y := fsl_ssi.o 16 17 snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o ··· 30 29 obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o 31 30 obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o 32 31 obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o 32 + obj-$(CONFIG_SND_SOC_FSL_LPC3XXX) += snd-soc-fsl-lpc3xxx.o 33 33 obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o 34 34 obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o 35 35 obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o
+375
sound/soc/fsl/lpc3xxx-i2s.c
··· 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: %d\n", ret); 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");
+79
sound/soc/fsl/lpc3xxx-i2s.h
··· 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 + 9 + #ifndef __SOUND_SOC_LPC3XXX_I2S_H 10 + #define __SOUND_SOC_LPC3XXX_I2S_H 11 + 12 + #include <linux/types.h> 13 + #include <linux/regmap.h> 14 + 15 + struct lpc3xxx_i2s_info { 16 + struct device *dev; 17 + struct clk *clk; 18 + struct mutex lock; /* To serialize user-space access */ 19 + struct regmap *regs; 20 + u32 streams_in_use; 21 + u32 clkrate; 22 + int freq; 23 + struct snd_dmaengine_dai_dma_data playback_dma_config; 24 + struct snd_dmaengine_dai_dma_data capture_dma_config; 25 + }; 26 + 27 + int lpc3xxx_pcm_register(struct platform_device *pdev); 28 + 29 + /* I2S controller register offsets */ 30 + #define LPC3XXX_REG_I2S_DAO 0x00 31 + #define LPC3XXX_REG_I2S_DAI 0x04 32 + #define LPC3XXX_REG_I2S_TX_FIFO 0x08 33 + #define LPC3XXX_REG_I2S_RX_FIFO 0x0C 34 + #define LPC3XXX_REG_I2S_STAT 0x10 35 + #define LPC3XXX_REG_I2S_DMA0 0x14 36 + #define LPC3XXX_REG_I2S_DMA1 0x18 37 + #define LPC3XXX_REG_I2S_IRQ 0x1C 38 + #define LPC3XXX_REG_I2S_TX_RATE 0x20 39 + #define LPC3XXX_REG_I2S_RX_RATE 0x24 40 + 41 + /* i2s_daO i2s_dai register definitions */ 42 + #define LPC3XXX_I2S_WW8 FIELD_PREP(0x3, 0) /* Word width is 8bit */ 43 + #define LPC3XXX_I2S_WW16 FIELD_PREP(0x3, 1) /* Word width is 16bit */ 44 + #define LPC3XXX_I2S_WW32 FIELD_PREP(0x3, 3) /* Word width is 32bit */ 45 + #define LPC3XXX_I2S_MONO BIT(2) /* Mono */ 46 + #define LPC3XXX_I2S_STOP BIT(3) /* Stop, diables the access to FIFO, mutes the channel */ 47 + #define LPC3XXX_I2S_RESET BIT(4) /* Reset the channel */ 48 + #define LPC3XXX_I2S_WS_SEL BIT(5) /* Channel Master(0) or slave(1) mode select */ 49 + #define LPC3XXX_I2S_WS_HP(s) FIELD_PREP(0x7FC0, s) /* Word select half period - 1 */ 50 + #define LPC3XXX_I2S_MUTE BIT(15) /* Mute the channel, Transmit channel only */ 51 + 52 + #define LPC3XXX_I2S_WW32_HP 0x1f /* Word select half period for 32bit word width */ 53 + #define LPC3XXX_I2S_WW16_HP 0x0f /* Word select half period for 16bit word width */ 54 + #define LPC3XXX_I2S_WW8_HP 0x7 /* Word select half period for 8bit word width */ 55 + 56 + /* i2s_stat register definitions */ 57 + #define LPC3XXX_I2S_IRQ_STAT BIT(0) 58 + #define LPC3XXX_I2S_DMA0_REQ BIT(1) 59 + #define LPC3XXX_I2S_DMA1_REQ BIT(2) 60 + 61 + /* i2s_dma0 Configuration register definitions */ 62 + #define LPC3XXX_I2S_DMA0_RX_EN BIT(0) /* Enable RX DMA1 */ 63 + #define LPC3XXX_I2S_DMA0_TX_EN BIT(1) /* Enable TX DMA1 */ 64 + #define LPC3XXX_I2S_DMA0_RX_DEPTH(s) FIELD_PREP(0xF00, s) /* Set the DMA1 RX Request level */ 65 + #define LPC3XXX_I2S_DMA0_TX_DEPTH(s) FIELD_PREP(0xF0000, s) /* Set the DMA1 TX Request level */ 66 + 67 + /* i2s_dma1 Configuration register definitions */ 68 + #define LPC3XXX_I2S_DMA1_RX_EN BIT(0) /* Enable RX DMA1 */ 69 + #define LPC3XXX_I2S_DMA1_TX_EN BIT(1) /* Enable TX DMA1 */ 70 + #define LPC3XXX_I2S_DMA1_RX_DEPTH(s) FIELD_PREP(0x700, s) /* Set the DMA1 RX Request level */ 71 + #define LPC3XXX_I2S_DMA1_TX_DEPTH(s) FIELD_PREP(0x70000, s) /* Set the DMA1 TX Request level */ 72 + 73 + /* i2s_irq register definitions */ 74 + #define LPC3XXX_I2S_RX_IRQ_EN BIT(0) /* Enable RX IRQ */ 75 + #define LPC3XXX_I2S_TX_IRQ_EN BIT(1) /* Enable TX IRQ */ 76 + #define LPC3XXX_I2S_IRQ_RX_DEPTH(s) FIELD_PREP(0xFF00, s) /* valid values ar 0 to 7 */ 77 + #define LPC3XXX_I2S_IRQ_TX_DEPTH(s) FIELD_PREP(0xFF0000, s) /* valid values ar 0 to 7 */ 78 + 79 + #endif
+72
sound/soc/fsl/lpc3xxx-pcm.c
··· 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/module.h> 9 + #include <linux/init.h> 10 + #include <linux/platform_device.h> 11 + #include <linux/slab.h> 12 + #include <linux/dma-mapping.h> 13 + #include <linux/amba/pl08x.h> 14 + 15 + #include <sound/core.h> 16 + #include <sound/pcm.h> 17 + #include <sound/pcm_params.h> 18 + #include <sound/dmaengine_pcm.h> 19 + #include <sound/soc.h> 20 + 21 + #include "lpc3xxx-i2s.h" 22 + 23 + #define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ 24 + SNDRV_PCM_FMTBIT_U8 | \ 25 + SNDRV_PCM_FMTBIT_S16_LE | \ 26 + SNDRV_PCM_FMTBIT_U16_LE | \ 27 + SNDRV_PCM_FMTBIT_S24_LE | \ 28 + SNDRV_PCM_FMTBIT_U24_LE | \ 29 + SNDRV_PCM_FMTBIT_S32_LE | \ 30 + SNDRV_PCM_FMTBIT_U32_LE | \ 31 + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) 32 + 33 + static const struct snd_pcm_hardware lpc3xxx_pcm_hardware = { 34 + .info = (SNDRV_PCM_INFO_MMAP | 35 + SNDRV_PCM_INFO_MMAP_VALID | 36 + SNDRV_PCM_INFO_INTERLEAVED | 37 + SNDRV_PCM_INFO_BLOCK_TRANSFER | 38 + SNDRV_PCM_INFO_PAUSE | 39 + SNDRV_PCM_INFO_RESUME), 40 + .formats = STUB_FORMATS, 41 + .period_bytes_min = 128, 42 + .period_bytes_max = 2048, 43 + .periods_min = 2, 44 + .periods_max = 1024, 45 + .buffer_bytes_max = 128 * 1024 46 + }; 47 + 48 + static const struct snd_dmaengine_pcm_config lpc3xxx_dmaengine_pcm_config = { 49 + .pcm_hardware = &lpc3xxx_pcm_hardware, 50 + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, 51 + .compat_filter_fn = pl08x_filter_id, 52 + .prealloc_buffer_size = 128 * 1024, 53 + }; 54 + 55 + const struct snd_soc_component_driver lpc3xxx_soc_platform_driver = { 56 + .name = "lpc32xx-pcm", 57 + }; 58 + 59 + int lpc3xxx_pcm_register(struct platform_device *pdev) 60 + { 61 + int ret; 62 + 63 + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, &lpc3xxx_dmaengine_pcm_config, 0); 64 + if (ret) { 65 + dev_err(&pdev->dev, "failed to register dmaengine: %d\n", ret); 66 + return ret; 67 + } 68 + 69 + return devm_snd_soc_register_component(&pdev->dev, &lpc3xxx_soc_platform_driver, 70 + NULL, 0); 71 + } 72 + EXPORT_SYMBOL(lpc3xxx_pcm_register);