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

ASoC: spacemit: add i2s support for K1 SoC

Add ASoC platform driver for the SpacemiT K1 SoC full-duplex I2S
controller.

Co-developer: Jinmei Wei <weijinmei@linux.spacemit.com>
Signed-off-by: Jinmei Wei <weijinmei@linux.spacemit.com>
Signed-off-by: Troy Mitchell <troy.mitchell@linux.spacemit.com>
Link: https://patch.msgid.link/20251017-k1-i2s-v5-2-401ae3775fcd@linux.spacemit.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Troy Mitchell and committed by
Mark Brown
fce21744 73978d27

+481
+1
sound/soc/Kconfig
··· 127 127 source "sound/soc/rockchip/Kconfig" 128 128 source "sound/soc/samsung/Kconfig" 129 129 source "sound/soc/sdca/Kconfig" 130 + source "sound/soc/spacemit/Kconfig" 130 131 source "sound/soc/spear/Kconfig" 131 132 source "sound/soc/sprd/Kconfig" 132 133 source "sound/soc/starfive/Kconfig"
+1
sound/soc/Makefile
··· 70 70 obj-$(CONFIG_SND_SOC) += samsung/ 71 71 obj-$(CONFIG_SND_SOC) += sdca/ 72 72 obj-$(CONFIG_SND_SOC) += sof/ 73 + obj-$(CONFIG_SND_SOC) += spacemit/ 73 74 obj-$(CONFIG_SND_SOC) += spear/ 74 75 obj-$(CONFIG_SND_SOC) += sprd/ 75 76 obj-$(CONFIG_SND_SOC) += starfive/
+16
sound/soc/spacemit/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + menu "SpacemiT" 3 + depends on COMPILE_TEST || ARCH_SPACEMIT 4 + depends on HAVE_CLK 5 + 6 + config SND_SOC_K1_I2S 7 + tristate "K1 I2S Device Driver" 8 + select SND_SOC_GENERIC_DMAENGINE_PCM 9 + select CMA 10 + select DMA_CMA 11 + help 12 + Say Y or M if you want to add support for I2S driver for 13 + K1 I2S controller. The device supports up to maximum of 14 + 2 channels each for play and record. 15 + 16 + endmenu
+5
sound/soc/spacemit/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + # K1 Platform Support 3 + snd-soc-k1-i2s-y := k1_i2s.o 4 + 5 + obj-$(CONFIG_SND_SOC_K1_I2S) += snd-soc-k1-i2s.o
+458
sound/soc/spacemit/k1_i2s.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (c) 2025 Troy Mitchell <troy.mitchell@linux.spacemit.com> */ 3 + 4 + #include <linux/bitfield.h> 5 + #include <linux/clk.h> 6 + #include <linux/reset.h> 7 + #include <sound/dmaengine_pcm.h> 8 + #include <sound/pcm.h> 9 + #include <sound/pcm_params.h> 10 + 11 + #define SSCR 0x00 /* SPI/I2S top control register */ 12 + #define SSFCR 0x04 /* SPI/I2S FIFO control register */ 13 + #define SSINTEN 0x08 /* SPI/I2S interrupt enable register */ 14 + #define SSDATR 0x10 /* SPI/I2S data register */ 15 + #define SSPSP 0x18 /* SPI/I2S programmable serial protocol control register */ 16 + #define SSRWT 0x24 /* SPI/I2S root control register */ 17 + 18 + /* SPI/I2S Work data size, register bits value 0~31 indicated data size 1~32 bits */ 19 + #define SSCR_FIELD_DSS GENMASK(9, 5) 20 + #define SSCR_DW_8BYTE FIELD_PREP(SSCR_FIELD_DSS, 0x7) 21 + #define SSCR_DW_16BYTE FIELD_PREP(SSCR_FIELD_DSS, 0xf) 22 + #define SSCR_DW_18BYTE FIELD_PREP(SSCR_FIELD_DSS, 0x11) 23 + #define SSCR_DW_32BYTE FIELD_PREP(SSCR_FIELD_DSS, 0x1f) 24 + 25 + #define SSCR_SSE BIT(0) /* SPI/I2S Enable */ 26 + #define SSCR_FRF_PSP GENMASK(2, 1) /* Frame Format*/ 27 + #define SSCR_TRAIL BIT(13) /* Trailing Byte */ 28 + 29 + #define SSFCR_FIELD_TFT GENMASK(3, 0) /* TXFIFO Trigger Threshold */ 30 + #define SSFCR_FIELD_RFT GENMASK(8, 5) /* RXFIFO Trigger Threshold */ 31 + #define SSFCR_TSRE BIT(10) /* Transmit Service Request Enable */ 32 + #define SSFCR_RSRE BIT(11) /* Receive Service Request Enable */ 33 + 34 + #define SSPSP_FSRT BIT(3) /* Frame Sync Relative Timing Bit */ 35 + #define SSPSP_SFRMP BIT(4) /* Serial Frame Polarity */ 36 + #define SSPSP_FIELD_SFRMWDTH GENMASK(17, 12) /* Serial Frame Width field */ 37 + 38 + #define SSRWT_RWOT BIT(0) /* Receive Without Transmit */ 39 + 40 + #define SPACEMIT_PCM_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ 41 + SNDRV_PCM_RATE_48000) 42 + #define SPACEMIT_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) 43 + 44 + #define SPACEMIT_I2S_PERIOD_SIZE 1024 45 + 46 + struct spacemit_i2s_dev { 47 + struct device *dev; 48 + 49 + void __iomem *base; 50 + 51 + struct reset_control *reset; 52 + 53 + struct clk *sysclk; 54 + struct clk *bclk; 55 + struct clk *sspa_clk; 56 + 57 + struct snd_dmaengine_dai_dma_data capture_dma_data; 58 + struct snd_dmaengine_dai_dma_data playback_dma_data; 59 + 60 + bool has_capture; 61 + bool has_playback; 62 + 63 + int dai_fmt; 64 + 65 + int started_count; 66 + }; 67 + 68 + static const struct snd_pcm_hardware spacemit_pcm_hardware = { 69 + .info = SNDRV_PCM_INFO_INTERLEAVED | 70 + SNDRV_PCM_INFO_BATCH, 71 + .formats = SPACEMIT_PCM_FORMATS, 72 + .rates = SPACEMIT_PCM_RATES, 73 + .rate_min = SNDRV_PCM_RATE_8000, 74 + .rate_max = SNDRV_PCM_RATE_192000, 75 + .channels_min = 1, 76 + .channels_max = 2, 77 + .buffer_bytes_max = SPACEMIT_I2S_PERIOD_SIZE * 4 * 4, 78 + .period_bytes_min = SPACEMIT_I2S_PERIOD_SIZE * 2, 79 + .period_bytes_max = SPACEMIT_I2S_PERIOD_SIZE * 4, 80 + .periods_min = 2, 81 + .periods_max = 4, 82 + }; 83 + 84 + static const struct snd_dmaengine_pcm_config spacemit_dmaengine_pcm_config = { 85 + .pcm_hardware = &spacemit_pcm_hardware, 86 + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, 87 + .chan_names = {"tx", "rx"}, 88 + .prealloc_buffer_size = 32 * 1024, 89 + }; 90 + 91 + static void spacemit_i2s_init(struct spacemit_i2s_dev *i2s) 92 + { 93 + u32 sscr_val, sspsp_val, ssfcr_val, ssrwt_val; 94 + 95 + sscr_val = SSCR_TRAIL | SSCR_FRF_PSP; 96 + ssfcr_val = FIELD_PREP(SSFCR_FIELD_TFT, 5) | 97 + FIELD_PREP(SSFCR_FIELD_RFT, 5) | 98 + SSFCR_RSRE | SSFCR_TSRE; 99 + ssrwt_val = SSRWT_RWOT; 100 + sspsp_val = SSPSP_SFRMP; 101 + 102 + writel(sscr_val, i2s->base + SSCR); 103 + writel(ssfcr_val, i2s->base + SSFCR); 104 + writel(sspsp_val, i2s->base + SSPSP); 105 + writel(ssrwt_val, i2s->base + SSRWT); 106 + writel(0, i2s->base + SSINTEN); 107 + } 108 + 109 + static int spacemit_i2s_hw_params(struct snd_pcm_substream *substream, 110 + struct snd_pcm_hw_params *params, 111 + struct snd_soc_dai *dai) 112 + { 113 + struct spacemit_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai); 114 + struct snd_dmaengine_dai_dma_data *dma_data; 115 + u32 data_width, data_bits; 116 + unsigned long bclk_rate; 117 + u32 val; 118 + int ret; 119 + 120 + val = readl(i2s->base + SSCR); 121 + if (val & SSCR_SSE) 122 + return 0; 123 + 124 + dma_data = &i2s->playback_dma_data; 125 + 126 + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 127 + dma_data = &i2s->capture_dma_data; 128 + 129 + switch (params_format(params)) { 130 + case SNDRV_PCM_FORMAT_S8: 131 + data_bits = 8; 132 + data_width = SSCR_DW_8BYTE; 133 + dma_data->maxburst = 8; 134 + dma_data->addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; 135 + break; 136 + case SNDRV_PCM_FORMAT_S16_LE: 137 + data_bits = 16; 138 + data_width = SSCR_DW_16BYTE; 139 + dma_data->maxburst = 16; 140 + dma_data->addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; 141 + break; 142 + case SNDRV_PCM_FORMAT_S32_LE: 143 + data_bits = 32; 144 + data_width = SSCR_DW_32BYTE; 145 + dma_data->maxburst = 32; 146 + dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; 147 + break; 148 + default: 149 + dev_dbg(i2s->dev, "unexpected data width type"); 150 + return -EINVAL; 151 + } 152 + 153 + switch (i2s->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 154 + case SND_SOC_DAIFMT_I2S: 155 + if (data_bits == 16) { 156 + data_width = SSCR_DW_32BYTE; 157 + dma_data->maxburst = 32; 158 + dma_data->addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; 159 + } 160 + 161 + snd_pcm_hw_constraint_minmax(substream->runtime, 162 + SNDRV_PCM_HW_PARAM_CHANNELS, 163 + 1, 2); 164 + snd_pcm_hw_constraint_mask64(substream->runtime, 165 + SNDRV_PCM_HW_PARAM_FORMAT, 166 + SNDRV_PCM_FMTBIT_S16_LE); 167 + break; 168 + case SND_SOC_DAIFMT_DSP_A: 169 + case SND_SOC_DAIFMT_DSP_B: 170 + snd_pcm_hw_constraint_minmax(substream->runtime, 171 + SNDRV_PCM_HW_PARAM_CHANNELS, 172 + 1, 1); 173 + snd_pcm_hw_constraint_mask64(substream->runtime, 174 + SNDRV_PCM_HW_PARAM_FORMAT, 175 + SNDRV_PCM_FMTBIT_S32_LE); 176 + break; 177 + default: 178 + dev_dbg(i2s->dev, "unexpected format type"); 179 + return -EINVAL; 180 + 181 + } 182 + 183 + val = readl(i2s->base + SSCR); 184 + val &= ~SSCR_DW_32BYTE; 185 + val |= data_width; 186 + writel(val, i2s->base + SSCR); 187 + 188 + bclk_rate = params_channels(params) * 189 + params_rate(params) * 190 + data_bits; 191 + 192 + ret = clk_set_rate(i2s->bclk, bclk_rate); 193 + if (ret) 194 + return ret; 195 + 196 + return clk_set_rate(i2s->sspa_clk, bclk_rate); 197 + } 198 + 199 + static int spacemit_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, 200 + unsigned int freq, int dir) 201 + { 202 + struct spacemit_i2s_dev *i2s = dev_get_drvdata(cpu_dai->dev); 203 + 204 + if (freq == 0) 205 + return 0; 206 + 207 + return clk_set_rate(i2s->sysclk, freq); 208 + } 209 + 210 + static int spacemit_i2s_set_fmt(struct snd_soc_dai *cpu_dai, 211 + unsigned int fmt) 212 + { 213 + struct spacemit_i2s_dev *i2s = dev_get_drvdata(cpu_dai->dev); 214 + u32 sspsp_val; 215 + 216 + sspsp_val = readl(i2s->base + SSPSP); 217 + sspsp_val &= ~SSPSP_FIELD_SFRMWDTH; 218 + sspsp_val |= SSPSP_FSRT; 219 + 220 + i2s->dai_fmt = fmt; 221 + 222 + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 223 + case SND_SOC_DAIFMT_I2S: 224 + sspsp_val |= FIELD_PREP(SSPSP_FIELD_SFRMWDTH, 0x10); 225 + break; 226 + case SND_SOC_DAIFMT_DSP_B: 227 + /* DSP_B: next frame asserted after previous frame end, so clear FSRT */ 228 + sspsp_val &= ~SSPSP_FSRT; 229 + fallthrough; 230 + case SND_SOC_DAIFMT_DSP_A: 231 + sspsp_val |= FIELD_PREP(SSPSP_FIELD_SFRMWDTH, 0x1); 232 + break; 233 + default: 234 + dev_dbg(i2s->dev, "unexpected format type"); 235 + return -EINVAL; 236 + } 237 + 238 + writel(sspsp_val, i2s->base + SSPSP); 239 + 240 + return 0; 241 + } 242 + 243 + static int spacemit_i2s_trigger(struct snd_pcm_substream *substream, 244 + int cmd, struct snd_soc_dai *dai) 245 + { 246 + struct spacemit_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai); 247 + u32 val; 248 + 249 + switch (cmd) { 250 + case SNDRV_PCM_TRIGGER_START: 251 + case SNDRV_PCM_TRIGGER_RESUME: 252 + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 253 + if (!i2s->started_count) { 254 + val = readl(i2s->base + SSCR); 255 + val |= SSCR_SSE; 256 + writel(val, i2s->base + SSCR); 257 + } 258 + i2s->started_count++; 259 + break; 260 + case SNDRV_PCM_TRIGGER_STOP: 261 + case SNDRV_PCM_TRIGGER_SUSPEND: 262 + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 263 + if (i2s->started_count) 264 + i2s->started_count--; 265 + 266 + if (!i2s->started_count) { 267 + val = readl(i2s->base + SSCR); 268 + val &= ~SSCR_SSE; 269 + writel(val, i2s->base + SSCR); 270 + } 271 + break; 272 + default: 273 + return -EINVAL; 274 + } 275 + 276 + return 0; 277 + } 278 + 279 + static int spacemit_i2s_dai_probe(struct snd_soc_dai *dai) 280 + { 281 + struct spacemit_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai); 282 + 283 + snd_soc_dai_init_dma_data(dai, 284 + i2s->has_playback ? &i2s->playback_dma_data : NULL, 285 + i2s->has_capture ? &i2s->capture_dma_data : NULL); 286 + 287 + reset_control_deassert(i2s->reset); 288 + 289 + spacemit_i2s_init(i2s); 290 + 291 + return 0; 292 + } 293 + 294 + static int spacemit_i2s_dai_remove(struct snd_soc_dai *dai) 295 + { 296 + struct spacemit_i2s_dev *i2s = snd_soc_dai_get_drvdata(dai); 297 + 298 + reset_control_assert(i2s->reset); 299 + 300 + return 0; 301 + } 302 + 303 + static const struct snd_soc_dai_ops spacemit_i2s_dai_ops = { 304 + .probe = spacemit_i2s_dai_probe, 305 + .remove = spacemit_i2s_dai_remove, 306 + .hw_params = spacemit_i2s_hw_params, 307 + .set_sysclk = spacemit_i2s_set_sysclk, 308 + .set_fmt = spacemit_i2s_set_fmt, 309 + .trigger = spacemit_i2s_trigger, 310 + }; 311 + 312 + static struct snd_soc_dai_driver spacemit_i2s_dai = { 313 + .ops = &spacemit_i2s_dai_ops, 314 + .playback = { 315 + .channels_min = 1, 316 + .channels_max = 2, 317 + .rates = SPACEMIT_PCM_RATES, 318 + .rate_min = SNDRV_PCM_RATE_8000, 319 + .rate_max = SNDRV_PCM_RATE_48000, 320 + .formats = SPACEMIT_PCM_FORMATS, 321 + }, 322 + .capture = { 323 + .channels_min = 1, 324 + .channels_max = 2, 325 + .rates = SPACEMIT_PCM_RATES, 326 + .rate_min = SNDRV_PCM_RATE_8000, 327 + .rate_max = SNDRV_PCM_RATE_48000, 328 + .formats = SPACEMIT_PCM_FORMATS, 329 + }, 330 + .symmetric_rate = 1, 331 + }; 332 + 333 + static int spacemit_i2s_init_dai(struct spacemit_i2s_dev *i2s, 334 + struct snd_soc_dai_driver **dp, 335 + dma_addr_t addr) 336 + { 337 + struct device_node *node = i2s->dev->of_node; 338 + struct snd_soc_dai_driver *dai; 339 + struct property *dma_names; 340 + const char *dma_name; 341 + 342 + of_property_for_each_string(node, "dma-names", dma_names, dma_name) { 343 + if (!strcmp(dma_name, "tx")) 344 + i2s->has_playback = true; 345 + if (!strcmp(dma_name, "rx")) 346 + i2s->has_capture = true; 347 + } 348 + 349 + dai = devm_kmemdup(i2s->dev, &spacemit_i2s_dai, 350 + sizeof(*dai), GFP_KERNEL); 351 + if (!dai) 352 + return -ENOMEM; 353 + 354 + if (i2s->has_playback) { 355 + dai->playback.stream_name = "Playback"; 356 + dai->playback.channels_min = 1; 357 + dai->playback.channels_max = 2; 358 + dai->playback.rates = SPACEMIT_PCM_RATES; 359 + dai->playback.formats = SPACEMIT_PCM_FORMATS; 360 + 361 + i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; 362 + i2s->playback_dma_data.maxburst = 32; 363 + i2s->playback_dma_data.addr = addr; 364 + } 365 + 366 + if (i2s->has_capture) { 367 + dai->capture.stream_name = "Capture"; 368 + dai->capture.channels_min = 1; 369 + dai->capture.channels_max = 2; 370 + dai->capture.rates = SPACEMIT_PCM_RATES; 371 + dai->capture.formats = SPACEMIT_PCM_FORMATS; 372 + 373 + i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; 374 + i2s->capture_dma_data.maxburst = 32; 375 + i2s->capture_dma_data.addr = addr; 376 + } 377 + 378 + if (dp) 379 + *dp = dai; 380 + 381 + return 0; 382 + } 383 + 384 + static const struct snd_soc_component_driver spacemit_i2s_component = { 385 + .name = "i2s-k1", 386 + .legacy_dai_naming = 1, 387 + }; 388 + 389 + static int spacemit_i2s_probe(struct platform_device *pdev) 390 + { 391 + struct snd_soc_dai_driver *dai; 392 + struct spacemit_i2s_dev *i2s; 393 + struct resource *res; 394 + struct clk *clk; 395 + int ret; 396 + 397 + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); 398 + if (!i2s) 399 + return -ENOMEM; 400 + 401 + i2s->dev = &pdev->dev; 402 + 403 + i2s->sysclk = devm_clk_get_enabled(i2s->dev, "sysclk"); 404 + if (IS_ERR(i2s->sysclk)) 405 + return dev_err_probe(i2s->dev, PTR_ERR(i2s->sysclk), 406 + "failed to enable sysbase clock\n"); 407 + 408 + i2s->bclk = devm_clk_get_enabled(i2s->dev, "bclk"); 409 + if (IS_ERR(i2s->bclk)) 410 + return dev_err_probe(i2s->dev, PTR_ERR(i2s->bclk), "failed to enable bit clock\n"); 411 + 412 + clk = devm_clk_get_enabled(i2s->dev, "sspa_bus"); 413 + if (IS_ERR(clk)) 414 + return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable sspa_bus clock\n"); 415 + 416 + i2s->sspa_clk = devm_clk_get_enabled(i2s->dev, "sspa"); 417 + if (IS_ERR(clk)) 418 + return dev_err_probe(i2s->dev, PTR_ERR(clk), "failed to enable sspa clock\n"); 419 + 420 + i2s->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 421 + if (IS_ERR(i2s->base)) 422 + return dev_err_probe(i2s->dev, PTR_ERR(i2s->base), "failed to map registers\n"); 423 + 424 + i2s->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); 425 + if (IS_ERR(i2s->reset)) 426 + return dev_err_probe(i2s->dev, PTR_ERR(i2s->reset), 427 + "failed to get reset control"); 428 + 429 + dev_set_drvdata(i2s->dev, i2s); 430 + 431 + spacemit_i2s_init_dai(i2s, &dai, res->start + SSDATR); 432 + 433 + ret = devm_snd_soc_register_component(i2s->dev, 434 + &spacemit_i2s_component, 435 + dai, 1); 436 + if (ret) 437 + return dev_err_probe(i2s->dev, ret, "failed to register component"); 438 + 439 + return devm_snd_dmaengine_pcm_register(&pdev->dev, &spacemit_dmaengine_pcm_config, 0); 440 + } 441 + 442 + static const struct of_device_id spacemit_i2s_of_match[] = { 443 + { .compatible = "spacemit,k1-i2s", }, 444 + { /* sentinel */ } 445 + }; 446 + MODULE_DEVICE_TABLE(of, spacemit_i2s_of_match); 447 + 448 + static struct platform_driver spacemit_i2s_driver = { 449 + .probe = spacemit_i2s_probe, 450 + .driver = { 451 + .name = "i2s-k1", 452 + .of_match_table = spacemit_i2s_of_match, 453 + }, 454 + }; 455 + module_platform_driver(spacemit_i2s_driver); 456 + 457 + MODULE_LICENSE("GPL"); 458 + MODULE_DESCRIPTION("I2S bus driver for SpacemiT K1 SoC");