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.

ASoC: Add support for Loongson I2S controller

Loongson I2S controller is found on 7axxx/2kxxx chips from loongson,
it is a PCI device with two private DMA controllers, one for playback,
the other for capture.

The driver supports the use of DTS or ACPI to describe device resources.

Signed-off-by: Yingkun Meng <mengyingkun@loongson.cn>
Link: https://lore.kernel.org/r/20230615122718.3412942-1-mengyingkun@loongson.cn
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Yingkun Meng and committed by
Mark Brown
d84881e0 06f2c60e

+899
+1
sound/soc/Kconfig
··· 79 79 source "sound/soc/hisilicon/Kconfig" 80 80 source "sound/soc/jz4740/Kconfig" 81 81 source "sound/soc/kirkwood/Kconfig" 82 + source "sound/soc/loongson/Kconfig" 82 83 source "sound/soc/img/Kconfig" 83 84 source "sound/soc/intel/Kconfig" 84 85 source "sound/soc/mediatek/Kconfig"
+1
sound/soc/Makefile
··· 46 46 obj-$(CONFIG_SND_SOC) += google/ 47 47 obj-$(CONFIG_SND_SOC) += hisilicon/ 48 48 obj-$(CONFIG_SND_SOC) += jz4740/ 49 + obj-$(CONFIG_SND_SOC) += loongson/ 49 50 obj-$(CONFIG_SND_SOC) += img/ 50 51 obj-$(CONFIG_SND_SOC) += intel/ 51 52 obj-$(CONFIG_SND_SOC) += mediatek/
+16
sound/soc/loongson/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + menu "SoC Audio for Loongson CPUs" 3 + depends on LOONGARCH || COMPILE_TEST 4 + 5 + config SND_SOC_LOONGSON_I2S_PCI 6 + tristate "Loongson I2S-PCI Device Driver" 7 + select REGMAP_MMIO 8 + depends on PCI 9 + help 10 + Say Y or M if you want to add support for I2S driver for 11 + Loongson I2S controller. 12 + 13 + The controller is found in loongson bridge chips or SoCs, 14 + and work as a PCI device. 15 + 16 + endmenu
+4
sound/soc/loongson/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + #Platform Support 3 + snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o loongson_dma.o 4 + obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o
+350
sound/soc/loongson/loongson_dma.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // 3 + // Loongson ALSA SoC Platform (DMA) driver 4 + // 5 + // Copyright (C) 2023 Loongson Technology Corporation Limited 6 + // Author: Yingkun Meng <mengyingkun@loongson.cn> 7 + // 8 + 9 + #include <linux/module.h> 10 + #include <linux/io-64-nonatomic-lo-hi.h> 11 + #include <linux/delay.h> 12 + #include <linux/pm_runtime.h> 13 + #include <linux/dma-mapping.h> 14 + #include <sound/soc.h> 15 + #include <sound/pcm.h> 16 + #include <sound/pcm_params.h> 17 + #include "loongson_i2s.h" 18 + 19 + /* DMA dma_order Register */ 20 + #define DMA_ORDER_STOP (1 << 4) /* DMA stop */ 21 + #define DMA_ORDER_START (1 << 3) /* DMA start */ 22 + #define DMA_ORDER_ASK_VALID (1 << 2) /* DMA ask valid flag */ 23 + #define DMA_ORDER_AXI_UNCO (1 << 1) /* Uncache access */ 24 + #define DMA_ORDER_ADDR_64 (1 << 0) /* 64bits address support */ 25 + 26 + #define DMA_ORDER_ASK_MASK (~0x1fUL) /* Ask addr mask */ 27 + #define DMA_ORDER_CTRL_MASK (0x0fUL) /* Control mask */ 28 + 29 + /* 30 + * DMA registers descriptor. 31 + */ 32 + struct loongson_dma_desc { 33 + u32 order; /* Next descriptor address register */ 34 + u32 saddr; /* Source address register */ 35 + u32 daddr; /* Device address register */ 36 + u32 length; /* Total length register */ 37 + u32 step_length; /* Memory stride register */ 38 + u32 step_times; /* Repeat time register */ 39 + u32 cmd; /* Command register */ 40 + u32 stats; /* Status register */ 41 + u32 order_hi; /* Next descriptor high address register */ 42 + u32 saddr_hi; /* High source address register */ 43 + u32 res[6]; /* Reserved */ 44 + } __packed; 45 + 46 + struct loongson_runtime_data { 47 + struct loongson_dma_data *dma_data; 48 + 49 + struct loongson_dma_desc *dma_desc_arr; 50 + dma_addr_t dma_desc_arr_phy; 51 + int dma_desc_arr_size; 52 + 53 + struct loongson_dma_desc *dma_pos_desc; 54 + dma_addr_t dma_pos_desc_phy; 55 + }; 56 + 57 + static const struct snd_pcm_hardware ls_pcm_hardware = { 58 + .info = SNDRV_PCM_INFO_MMAP | 59 + SNDRV_PCM_INFO_INTERLEAVED | 60 + SNDRV_PCM_INFO_MMAP_VALID | 61 + SNDRV_PCM_INFO_RESUME | 62 + SNDRV_PCM_INFO_PAUSE, 63 + .formats = (SNDRV_PCM_FMTBIT_S8 | 64 + SNDRV_PCM_FMTBIT_S16_LE | 65 + SNDRV_PCM_FMTBIT_S20_3LE | 66 + SNDRV_PCM_FMTBIT_S24_LE), 67 + .period_bytes_min = 128, 68 + .period_bytes_max = 128 * 1024, 69 + .periods_min = 1, 70 + .periods_max = PAGE_SIZE / sizeof(struct loongson_dma_desc), 71 + .buffer_bytes_max = 1024 * 1024, 72 + }; 73 + 74 + static struct 75 + loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd) 76 + { 77 + void __iomem *order_reg = prtd->dma_data->order_addr; 78 + u64 val; 79 + 80 + val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK; 81 + val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); 82 + val |= DMA_ORDER_ASK_VALID; 83 + writeq(val, order_reg); 84 + 85 + while (readl(order_reg) & DMA_ORDER_ASK_VALID) 86 + udelay(2); 87 + 88 + return prtd->dma_pos_desc; 89 + } 90 + 91 + static int loongson_pcm_trigger(struct snd_soc_component *component, 92 + struct snd_pcm_substream *substream, int cmd) 93 + { 94 + struct loongson_runtime_data *prtd = substream->runtime->private_data; 95 + struct device *dev = substream->pcm->card->dev; 96 + void __iomem *order_reg = prtd->dma_data->order_addr; 97 + u64 val; 98 + int ret = 0; 99 + 100 + switch (cmd) { 101 + case SNDRV_PCM_TRIGGER_START: 102 + case SNDRV_PCM_TRIGGER_RESUME: 103 + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 104 + val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK; 105 + if (dev->coherent_dma_mask == DMA_BIT_MASK(64)) 106 + val |= DMA_ORDER_ADDR_64; 107 + else 108 + val &= ~DMA_ORDER_ADDR_64; 109 + val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); 110 + val |= DMA_ORDER_START; 111 + writeq(val, order_reg); 112 + 113 + while ((readl(order_reg) & DMA_ORDER_START)) 114 + udelay(2); 115 + break; 116 + case SNDRV_PCM_TRIGGER_STOP: 117 + case SNDRV_PCM_TRIGGER_SUSPEND: 118 + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 119 + dma_desc_save(prtd); 120 + 121 + /* dma stop */ 122 + val = readq(order_reg) | DMA_ORDER_STOP; 123 + writeq(val, order_reg); 124 + udelay(1000); 125 + 126 + break; 127 + default: 128 + dev_err(dev, "Invalid pcm trigger operation\n"); 129 + return -EINVAL; 130 + } 131 + 132 + return ret; 133 + } 134 + 135 + static int loongson_pcm_hw_params(struct snd_soc_component *component, 136 + struct snd_pcm_substream *substream, 137 + struct snd_pcm_hw_params *params) 138 + { 139 + struct snd_pcm_runtime *runtime = substream->runtime; 140 + struct device *dev = substream->pcm->card->dev; 141 + struct loongson_runtime_data *prtd = runtime->private_data; 142 + size_t buf_len = params_buffer_bytes(params); 143 + size_t period_len = params_period_bytes(params); 144 + dma_addr_t order_addr, mem_addr; 145 + struct loongson_dma_desc *desc; 146 + u32 num_periods; 147 + int i; 148 + 149 + if (buf_len % period_len) { 150 + dev_err(dev, "buf len not multiply of period len\n"); 151 + return -EINVAL; 152 + } 153 + 154 + num_periods = buf_len / period_len; 155 + if (!num_periods || num_periods > prtd->dma_desc_arr_size) { 156 + dev_err(dev, "dma data too small or too big\n"); 157 + return -EINVAL; 158 + } 159 + 160 + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 161 + runtime->dma_bytes = buf_len; 162 + 163 + /* initialize dma descriptor array */ 164 + mem_addr = runtime->dma_addr; 165 + order_addr = prtd->dma_desc_arr_phy; 166 + for (i = 0; i < num_periods; i++) { 167 + desc = &prtd->dma_desc_arr[i]; 168 + 169 + /* next descriptor physical address */ 170 + order_addr += sizeof(*desc); 171 + desc->order = lower_32_bits(order_addr | BIT(0)); 172 + desc->order_hi = upper_32_bits(order_addr); 173 + 174 + desc->saddr = lower_32_bits(mem_addr); 175 + desc->saddr_hi = upper_32_bits(mem_addr); 176 + desc->daddr = prtd->dma_data->dev_addr; 177 + 178 + desc->cmd = BIT(0); 179 + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 180 + desc->cmd |= BIT(12); 181 + 182 + desc->length = period_len >> 2; 183 + desc->step_length = 0; 184 + desc->step_times = 1; 185 + 186 + mem_addr += period_len; 187 + } 188 + desc = &prtd->dma_desc_arr[num_periods - 1]; 189 + desc->order = lower_32_bits(prtd->dma_desc_arr_phy | BIT(0)); 190 + desc->order_hi = upper_32_bits(prtd->dma_desc_arr_phy); 191 + 192 + /* init position descriptor */ 193 + *prtd->dma_pos_desc = *prtd->dma_desc_arr; 194 + 195 + return 0; 196 + } 197 + 198 + static snd_pcm_uframes_t 199 + loongson_pcm_pointer(struct snd_soc_component *component, 200 + struct snd_pcm_substream *substream) 201 + { 202 + struct snd_pcm_runtime *runtime = substream->runtime; 203 + struct loongson_runtime_data *prtd = runtime->private_data; 204 + struct loongson_dma_desc *desc; 205 + snd_pcm_uframes_t x; 206 + u64 addr; 207 + 208 + desc = dma_desc_save(prtd); 209 + addr = ((u64)desc->saddr_hi << 32) | desc->saddr; 210 + 211 + x = bytes_to_frames(runtime, addr - runtime->dma_addr); 212 + if (x == runtime->buffer_size) 213 + x = 0; 214 + return x; 215 + } 216 + 217 + static irqreturn_t loongson_pcm_dma_irq(int irq, void *devid) 218 + { 219 + struct snd_pcm_substream *substream = devid; 220 + 221 + snd_pcm_period_elapsed(substream); 222 + return IRQ_HANDLED; 223 + } 224 + 225 + static int loongson_pcm_open(struct snd_soc_component *component, 226 + struct snd_pcm_substream *substream) 227 + { 228 + struct snd_pcm_runtime *runtime = substream->runtime; 229 + struct snd_soc_pcm_runtime *rtd = substream->private_data; 230 + struct snd_card *card = substream->pcm->card; 231 + struct loongson_runtime_data *prtd; 232 + struct loongson_dma_data *dma_data; 233 + int ret; 234 + 235 + /* 236 + * For mysterious reasons (and despite what the manual says) 237 + * playback samples are lost if the DMA count is not a multiple 238 + * of the DMA burst size. Let's add a rule to enforce that. 239 + */ 240 + snd_pcm_hw_constraint_step(runtime, 0, 241 + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128); 242 + snd_pcm_hw_constraint_step(runtime, 0, 243 + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128); 244 + snd_pcm_hw_constraint_integer(substream->runtime, 245 + SNDRV_PCM_HW_PARAM_PERIODS); 246 + snd_soc_set_runtime_hwparams(substream, &ls_pcm_hardware); 247 + 248 + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); 249 + if (!prtd) 250 + return -ENOMEM; 251 + 252 + prtd->dma_desc_arr = dma_alloc_coherent(card->dev, PAGE_SIZE, 253 + &prtd->dma_desc_arr_phy, 254 + GFP_KERNEL); 255 + if (!prtd->dma_desc_arr) { 256 + ret = -ENOMEM; 257 + goto desc_err; 258 + } 259 + prtd->dma_desc_arr_size = PAGE_SIZE / sizeof(*prtd->dma_desc_arr); 260 + 261 + prtd->dma_pos_desc = dma_alloc_coherent(card->dev, 262 + sizeof(*prtd->dma_pos_desc), 263 + &prtd->dma_pos_desc_phy, 264 + GFP_KERNEL); 265 + if (!prtd->dma_pos_desc) { 266 + ret = -ENOMEM; 267 + goto pos_err; 268 + } 269 + 270 + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); 271 + prtd->dma_data = dma_data; 272 + 273 + substream->runtime->private_data = prtd; 274 + 275 + return 0; 276 + pos_err: 277 + dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr, 278 + prtd->dma_desc_arr_phy); 279 + desc_err: 280 + kfree(prtd); 281 + 282 + return ret; 283 + } 284 + 285 + static int loongson_pcm_close(struct snd_soc_component *component, 286 + struct snd_pcm_substream *substream) 287 + { 288 + struct snd_card *card = substream->pcm->card; 289 + struct loongson_runtime_data *prtd = substream->runtime->private_data; 290 + 291 + dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr, 292 + prtd->dma_desc_arr_phy); 293 + 294 + dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc), 295 + prtd->dma_pos_desc, prtd->dma_pos_desc_phy); 296 + 297 + kfree(prtd); 298 + return 0; 299 + } 300 + 301 + static int loongson_pcm_mmap(struct snd_soc_component *component, 302 + struct snd_pcm_substream *substream, 303 + struct vm_area_struct *vma) 304 + { 305 + return remap_pfn_range(vma, vma->vm_start, 306 + substream->dma_buffer.addr >> PAGE_SHIFT, 307 + vma->vm_end - vma->vm_start, vma->vm_page_prot); 308 + } 309 + 310 + static int loongson_pcm_new(struct snd_soc_component *component, 311 + struct snd_soc_pcm_runtime *rtd) 312 + { 313 + struct snd_card *card = rtd->card->snd_card; 314 + struct snd_pcm_substream *substream; 315 + struct loongson_dma_data *dma_data; 316 + unsigned int i; 317 + int ret; 318 + 319 + for_each_pcm_streams(i) { 320 + substream = rtd->pcm->streams[i].substream; 321 + if (!substream) 322 + continue; 323 + 324 + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), 325 + substream); 326 + ret = devm_request_irq(card->dev, dma_data->irq, 327 + loongson_pcm_dma_irq, 328 + IRQF_TRIGGER_HIGH, LS_I2S_DRVNAME, 329 + substream); 330 + if (ret < 0) { 331 + dev_err(card->dev, "request irq for DMA failed\n"); 332 + return ret; 333 + } 334 + } 335 + 336 + return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, 337 + card->dev, 338 + ls_pcm_hardware.buffer_bytes_max); 339 + } 340 + 341 + const struct snd_soc_component_driver loongson_i2s_component = { 342 + .name = LS_I2S_DRVNAME, 343 + .open = loongson_pcm_open, 344 + .close = loongson_pcm_close, 345 + .hw_params = loongson_pcm_hw_params, 346 + .trigger = loongson_pcm_trigger, 347 + .pointer = loongson_pcm_pointer, 348 + .mmap = loongson_pcm_mmap, 349 + .pcm_construct = loongson_pcm_new, 350 + };
+16
sound/soc/loongson/loongson_dma.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * ALSA ASoC interface for the Loongson platform 4 + * 5 + * Copyright (C) 2023 Loongson Technology Corporation Limited 6 + * Author: Yingkun Meng <mengyingkun@loongson.cn> 7 + */ 8 + 9 + #ifndef _LOONGSON_DMA_H 10 + #define _LOONGSON_DMA_H 11 + 12 + #include <sound/soc.h> 13 + 14 + extern const struct snd_soc_component_driver loongson_i2s_component; 15 + 16 + #endif
+269
sound/soc/loongson/loongson_i2s.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // 3 + // Common functions for loongson I2S controller driver 4 + // 5 + // Copyright (C) 2023 Loongson Technology Corporation Limited. 6 + // Author: Yingkun Meng <mengyingkun@loongson.cn> 7 + // 8 + 9 + #include <linux/module.h> 10 + #include <linux/platform_device.h> 11 + #include <linux/delay.h> 12 + #include <linux/pm_runtime.h> 13 + #include <linux/dma-mapping.h> 14 + #include <sound/soc.h> 15 + #include <linux/regmap.h> 16 + #include <sound/pcm_params.h> 17 + #include "loongson_i2s.h" 18 + 19 + #define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ 20 + SNDRV_PCM_FMTBIT_S16_LE | \ 21 + SNDRV_PCM_FMTBIT_S20_3LE | \ 22 + SNDRV_PCM_FMTBIT_S24_LE) 23 + 24 + static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd, 25 + struct snd_soc_dai *dai) 26 + { 27 + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); 28 + int ret = 0; 29 + 30 + switch (cmd) { 31 + case SNDRV_PCM_TRIGGER_START: 32 + case SNDRV_PCM_TRIGGER_RESUME: 33 + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 34 + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 35 + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 36 + I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 37 + I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN); 38 + else 39 + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 40 + I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 41 + I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN); 42 + break; 43 + case SNDRV_PCM_TRIGGER_STOP: 44 + case SNDRV_PCM_TRIGGER_SUSPEND: 45 + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 46 + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 47 + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 48 + I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 0); 49 + else 50 + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 51 + I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 0); 52 + break; 53 + default: 54 + ret = -EINVAL; 55 + } 56 + 57 + return ret; 58 + } 59 + 60 + static int loongson_i2s_hw_params(struct snd_pcm_substream *substream, 61 + struct snd_pcm_hw_params *params, 62 + struct snd_soc_dai *dai) 63 + { 64 + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); 65 + u32 clk_rate = i2s->clk_rate; 66 + u32 sysclk = i2s->sysclk; 67 + u32 bits = params_width(params); 68 + u32 chans = params_channels(params); 69 + u32 fs = params_rate(params); 70 + u32 bclk_ratio, mclk_ratio; 71 + u32 mclk_ratio_frac; 72 + u32 val = 0; 73 + 74 + switch (i2s->rev_id) { 75 + case 0: 76 + bclk_ratio = DIV_ROUND_CLOSEST(clk_rate, 77 + (bits * chans * fs * 2)) - 1; 78 + mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2)) - 1; 79 + 80 + /* According to 2k1000LA user manual, set bits == depth */ 81 + val |= (bits << 24); 82 + val |= (bits << 16); 83 + val |= (bclk_ratio << 8); 84 + val |= mclk_ratio; 85 + regmap_write(i2s->regmap, LS_I2S_CFG, val); 86 + 87 + break; 88 + case 1: 89 + bclk_ratio = DIV_ROUND_CLOSEST(sysclk, 90 + (bits * chans * fs * 2)) - 1; 91 + mclk_ratio = clk_rate / sysclk; 92 + mclk_ratio_frac = DIV_ROUND_CLOSEST(((u64)clk_rate << 16), 93 + sysclk) - (mclk_ratio << 16); 94 + 95 + regmap_read(i2s->regmap, LS_I2S_CFG, &val); 96 + val |= (bits << 24); 97 + val |= (bclk_ratio << 8); 98 + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 99 + val |= (bits << 16); 100 + else 101 + val |= bits; 102 + regmap_write(i2s->regmap, LS_I2S_CFG, val); 103 + 104 + val = (mclk_ratio_frac << 16) | mclk_ratio; 105 + regmap_write(i2s->regmap, LS_I2S_CFG1, val); 106 + 107 + break; 108 + default: 109 + dev_err(i2s->dev, "I2S revision invalid\n"); 110 + return -EINVAL; 111 + } 112 + 113 + return 0; 114 + } 115 + 116 + static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, 117 + unsigned int freq, int dir) 118 + { 119 + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); 120 + 121 + i2s->sysclk = freq; 122 + 123 + return 0; 124 + } 125 + 126 + static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) 127 + { 128 + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); 129 + u32 val; 130 + int ret; 131 + 132 + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 133 + case SND_SOC_DAIFMT_I2S: 134 + break; 135 + case SND_SOC_DAIFMT_RIGHT_J: 136 + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB, 137 + I2S_CTRL_MSB); 138 + break; 139 + default: 140 + return -EINVAL; 141 + } 142 + 143 + 144 + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { 145 + case SND_SOC_DAIFMT_BC_FC: 146 + break; 147 + case SND_SOC_DAIFMT_BP_FC: 148 + /* Enable master mode */ 149 + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER, 150 + I2S_CTRL_MASTER); 151 + if (i2s->rev_id == 1) { 152 + ret = regmap_read_poll_timeout_atomic(i2s->regmap, 153 + LS_I2S_CTRL, val, 154 + val & I2S_CTRL_CLK_READY, 155 + 10, 500000); 156 + if (ret < 0) 157 + dev_warn(dai->dev, "wait BCLK ready timeout\n"); 158 + } 159 + break; 160 + case SND_SOC_DAIFMT_BC_FP: 161 + /* Enable MCLK */ 162 + if (i2s->rev_id == 1) { 163 + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 164 + I2S_CTRL_MCLK_EN, 165 + I2S_CTRL_MCLK_EN); 166 + ret = regmap_read_poll_timeout_atomic(i2s->regmap, 167 + LS_I2S_CTRL, val, 168 + val & I2S_CTRL_MCLK_READY, 169 + 10, 500000); 170 + if (ret < 0) 171 + dev_warn(dai->dev, "wait MCLK ready timeout\n"); 172 + } 173 + break; 174 + case SND_SOC_DAIFMT_BP_FP: 175 + /* Enable MCLK */ 176 + if (i2s->rev_id == 1) { 177 + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, 178 + I2S_CTRL_MCLK_EN, 179 + I2S_CTRL_MCLK_EN); 180 + ret = regmap_read_poll_timeout_atomic(i2s->regmap, 181 + LS_I2S_CTRL, val, 182 + val & I2S_CTRL_MCLK_READY, 183 + 10, 500000); 184 + if (ret < 0) 185 + dev_warn(dai->dev, "wait MCLK ready timeout\n"); 186 + } 187 + 188 + /* Enable master mode */ 189 + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER, 190 + I2S_CTRL_MASTER); 191 + if (i2s->rev_id == 1) { 192 + ret = regmap_read_poll_timeout_atomic(i2s->regmap, 193 + LS_I2S_CTRL, val, 194 + val & I2S_CTRL_CLK_READY, 195 + 10, 500000); 196 + if (ret < 0) 197 + dev_warn(dai->dev, "wait BCLK ready timeout\n"); 198 + } 199 + break; 200 + default: 201 + return -EINVAL; 202 + } 203 + 204 + return 0; 205 + } 206 + 207 + static const struct snd_soc_dai_ops loongson_i2s_dai_ops = { 208 + .trigger = loongson_i2s_trigger, 209 + .hw_params = loongson_i2s_hw_params, 210 + .set_sysclk = loongson_i2s_set_dai_sysclk, 211 + .set_fmt = loongson_i2s_set_fmt, 212 + }; 213 + 214 + static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai) 215 + { 216 + struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev); 217 + 218 + snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data, 219 + &i2s->capture_dma_data); 220 + snd_soc_dai_set_drvdata(cpu_dai, i2s); 221 + 222 + return 0; 223 + } 224 + 225 + struct snd_soc_dai_driver loongson_i2s_dai = { 226 + .name = "loongson-i2s", 227 + .probe = loongson_i2s_dai_probe, 228 + .playback = { 229 + .stream_name = "CPU-Playback", 230 + .channels_min = 1, 231 + .channels_max = 2, 232 + .rates = SNDRV_PCM_RATE_8000_96000, 233 + .formats = LOONGSON_I2S_FORMATS, 234 + }, 235 + .capture = { 236 + .stream_name = "CPU-Capture", 237 + .channels_min = 1, 238 + .channels_max = 2, 239 + .rates = SNDRV_PCM_RATE_8000_96000, 240 + .formats = LOONGSON_I2S_FORMATS, 241 + }, 242 + .ops = &loongson_i2s_dai_ops, 243 + .symmetric_rate = 1, 244 + }; 245 + 246 + static int i2s_suspend(struct device *dev) 247 + { 248 + struct loongson_i2s *i2s = dev_get_drvdata(dev); 249 + 250 + regcache_cache_only(i2s->regmap, true); 251 + 252 + return 0; 253 + } 254 + 255 + static int i2s_resume(struct device *dev) 256 + { 257 + struct loongson_i2s *i2s = dev_get_drvdata(dev); 258 + int ret; 259 + 260 + regcache_cache_only(i2s->regmap, false); 261 + regcache_mark_dirty(i2s->regmap); 262 + ret = regcache_sync(i2s->regmap); 263 + 264 + return ret; 265 + } 266 + 267 + const struct dev_pm_ops loongson_i2s_pm = { 268 + SET_SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume) 269 + };
+71
sound/soc/loongson/loongson_i2s.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * ALSA I2S interface for the Loongson platform 4 + * 5 + * Copyright (C) 2023 Loongson Technology Corporation Limited 6 + * Author: Yingkun Meng <mengyingkun@loongson.cn> 7 + */ 8 + 9 + #ifndef _LOONGSON_I2S_H 10 + #define _LOONGSON_I2S_H 11 + 12 + #include <linux/regmap.h> 13 + #include <sound/dmaengine_pcm.h> 14 + 15 + /* I2S Common Registers */ 16 + #define LS_I2S_VER 0x00 /* I2S Version */ 17 + #define LS_I2S_CFG 0x04 /* I2S Config */ 18 + #define LS_I2S_CTRL 0x08 /* I2S Control */ 19 + #define LS_I2S_RX_DATA 0x0C /* I2S DMA RX Address */ 20 + #define LS_I2S_TX_DATA 0x10 /* I2S DMA TX Address */ 21 + 22 + /* 2K2000 I2S Specify Registers */ 23 + #define LS_I2S_CFG1 0x14 /* I2S Config1 */ 24 + 25 + /* 7A2000 I2S Specify Registers */ 26 + #define LS_I2S_TX_ORDER 0x100 /* TX DMA Order */ 27 + #define LS_I2S_RX_ORDER 0x110 /* RX DMA Order */ 28 + 29 + /* Loongson I2S Control Register */ 30 + #define I2S_CTRL_MCLK_READY (1 << 16) /* MCLK ready */ 31 + #define I2S_CTRL_MASTER (1 << 15) /* Master mode */ 32 + #define I2S_CTRL_MSB (1 << 14) /* MSB bit order */ 33 + #define I2S_CTRL_RX_EN (1 << 13) /* RX enable */ 34 + #define I2S_CTRL_TX_EN (1 << 12) /* TX enable */ 35 + #define I2S_CTRL_RX_DMA_EN (1 << 11) /* DMA RX enable */ 36 + #define I2S_CTRL_CLK_READY (1 << 8) /* BCLK ready */ 37 + #define I2S_CTRL_TX_DMA_EN (1 << 7) /* DMA TX enable */ 38 + #define I2S_CTRL_RESET (1 << 4) /* Controller soft reset */ 39 + #define I2S_CTRL_MCLK_EN (1 << 3) /* Enable MCLK */ 40 + #define I2S_CTRL_RX_INT_EN (1 << 1) /* RX interrupt enable */ 41 + #define I2S_CTRL_TX_INT_EN (1 << 0) /* TX interrupt enable */ 42 + 43 + #define LS_I2S_DRVNAME "loongson-i2s" 44 + 45 + struct loongson_dma_data { 46 + dma_addr_t dev_addr; /* device physical address for DMA */ 47 + void __iomem *order_addr; /* DMA order register */ 48 + u32 irq; /* DMA irq */ 49 + }; 50 + 51 + struct loongson_i2s { 52 + struct device *dev; 53 + union { 54 + struct snd_dmaengine_dai_dma_data playback_dma_data; 55 + struct loongson_dma_data tx_dma_data; 56 + }; 57 + union { 58 + struct snd_dmaengine_dai_dma_data capture_dma_data; 59 + struct loongson_dma_data rx_dma_data; 60 + }; 61 + struct regmap *regmap; 62 + void __iomem *reg_base; 63 + u32 rev_id; 64 + u32 clk_rate; 65 + u32 sysclk; 66 + }; 67 + 68 + extern const struct dev_pm_ops loongson_i2s_pm; 69 + extern struct snd_soc_dai_driver loongson_i2s_dai; 70 + 71 + #endif
+171
sound/soc/loongson/loongson_i2s_pci.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // 3 + // loongson_i2s_pci.c -- Loongson I2S controller driver 4 + // 5 + // Copyright (C) 2023 Loongson Technology Corporation Limited 6 + // Author: Yingkun Meng <mengyingkun@loongson.cn> 7 + // 8 + 9 + #include <linux/module.h> 10 + #include <linux/delay.h> 11 + #include <linux/pm_runtime.h> 12 + #include <linux/dma-mapping.h> 13 + #include <linux/acpi.h> 14 + #include <linux/pci.h> 15 + #include <sound/soc.h> 16 + #include "loongson_i2s.h" 17 + #include "loongson_dma.h" 18 + 19 + static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg) 20 + { 21 + switch (reg) { 22 + case LS_I2S_CFG: 23 + case LS_I2S_CTRL: 24 + case LS_I2S_RX_DATA: 25 + case LS_I2S_TX_DATA: 26 + case LS_I2S_CFG1: 27 + return true; 28 + default: 29 + return false; 30 + }; 31 + } 32 + 33 + static bool loongson_i2s_rd_reg(struct device *dev, unsigned int reg) 34 + { 35 + switch (reg) { 36 + case LS_I2S_VER: 37 + case LS_I2S_CFG: 38 + case LS_I2S_CTRL: 39 + case LS_I2S_RX_DATA: 40 + case LS_I2S_TX_DATA: 41 + case LS_I2S_CFG1: 42 + return true; 43 + default: 44 + return false; 45 + }; 46 + } 47 + 48 + static bool loongson_i2s_volatile_reg(struct device *dev, unsigned int reg) 49 + { 50 + switch (reg) { 51 + case LS_I2S_CFG: 52 + case LS_I2S_CTRL: 53 + case LS_I2S_RX_DATA: 54 + case LS_I2S_TX_DATA: 55 + case LS_I2S_CFG1: 56 + return true; 57 + default: 58 + return false; 59 + }; 60 + } 61 + 62 + static const struct regmap_config loongson_i2s_regmap_config = { 63 + .reg_bits = 32, 64 + .reg_stride = 4, 65 + .val_bits = 32, 66 + .max_register = LS_I2S_CFG1, 67 + .writeable_reg = loongson_i2s_wr_reg, 68 + .readable_reg = loongson_i2s_rd_reg, 69 + .volatile_reg = loongson_i2s_volatile_reg, 70 + .cache_type = REGCACHE_FLAT, 71 + }; 72 + 73 + static int loongson_i2s_pci_probe(struct pci_dev *pdev, 74 + const struct pci_device_id *pid) 75 + { 76 + const struct fwnode_handle *fwnode = pdev->dev.fwnode; 77 + struct loongson_dma_data *tx_data, *rx_data; 78 + struct loongson_i2s *i2s; 79 + int ret; 80 + 81 + if (pcim_enable_device(pdev)) { 82 + dev_err(&pdev->dev, "pci_enable_device failed\n"); 83 + return -ENODEV; 84 + } 85 + 86 + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); 87 + if (!i2s) 88 + return -ENOMEM; 89 + 90 + i2s->rev_id = pdev->revision; 91 + i2s->dev = &pdev->dev; 92 + pci_set_drvdata(pdev, i2s); 93 + 94 + ret = pcim_iomap_regions(pdev, 1 << 0, dev_name(&pdev->dev)); 95 + if (ret < 0) { 96 + dev_err(&pdev->dev, "iomap_regions failed\n"); 97 + return ret; 98 + } 99 + i2s->reg_base = pcim_iomap_table(pdev)[0]; 100 + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base, 101 + &loongson_i2s_regmap_config); 102 + if (IS_ERR(i2s->regmap)) { 103 + dev_err(&pdev->dev, "regmap_init_mmio failed\n"); 104 + return PTR_ERR(i2s->regmap); 105 + } 106 + 107 + tx_data = &i2s->tx_dma_data; 108 + rx_data = &i2s->rx_dma_data; 109 + 110 + tx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_TX_DATA; 111 + tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER; 112 + 113 + rx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_RX_DATA; 114 + rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER; 115 + 116 + tx_data->irq = fwnode_irq_get_byname(fwnode, "tx"); 117 + if (tx_data->irq < 0) { 118 + dev_err(&pdev->dev, "dma tx irq invalid\n"); 119 + return tx_data->irq; 120 + } 121 + 122 + rx_data->irq = fwnode_irq_get_byname(fwnode, "rx"); 123 + if (rx_data->irq < 0) { 124 + dev_err(&pdev->dev, "dma rx irq invalid\n"); 125 + return rx_data->irq; 126 + } 127 + 128 + device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate); 129 + if (!i2s->clk_rate) { 130 + dev_err(&pdev->dev, "clock-frequency property invalid\n"); 131 + return -EINVAL; 132 + } 133 + 134 + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); 135 + 136 + if (i2s->rev_id == 1) { 137 + regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET); 138 + udelay(200); 139 + } 140 + 141 + ret = devm_snd_soc_register_component(&pdev->dev, 142 + &loongson_i2s_component, 143 + &loongson_i2s_dai, 1); 144 + if (ret) { 145 + dev_err(&pdev->dev, "register DAI failed %d\n", ret); 146 + return ret; 147 + } 148 + 149 + return 0; 150 + } 151 + 152 + static const struct pci_device_id loongson_i2s_ids[] = { 153 + { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) }, 154 + { }, 155 + }; 156 + MODULE_DEVICE_TABLE(pci, loongson_i2s_ids); 157 + 158 + static struct pci_driver loongson_i2s_driver = { 159 + .name = "loongson-i2s-pci", 160 + .id_table = loongson_i2s_ids, 161 + .probe = loongson_i2s_pci_probe, 162 + .driver = { 163 + .owner = THIS_MODULE, 164 + .pm = pm_sleep_ptr(&loongson_i2s_pm), 165 + }, 166 + }; 167 + module_pci_driver(loongson_i2s_driver); 168 + 169 + MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver"); 170 + MODULE_AUTHOR("Loongson Technology Corporation Limited"); 171 + MODULE_LICENSE("GPL");