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 Google Chameleon v3 i2s driver

Add driver for the i2s IP present on Google Chameleon v3

Signed-off-by: Paweł Anikiel <pan@semihalf.com
Link: https://lore.kernel.org/r/20230508113037.137627-2-pan@semihalf.com
Signed-off-by: Mark Brown <broonie@kernel.org

authored by

Paweł Anikiel and committed by
Mark Brown
70264872 518a1742

+348
+1
sound/soc/Kconfig
··· 75 75 source "sound/soc/cirrus/Kconfig" 76 76 source "sound/soc/dwc/Kconfig" 77 77 source "sound/soc/fsl/Kconfig" 78 + source "sound/soc/google/Kconfig" 78 79 source "sound/soc/hisilicon/Kconfig" 79 80 source "sound/soc/jz4740/Kconfig" 80 81 source "sound/soc/kirkwood/Kconfig"
+1
sound/soc/Makefile
··· 43 43 obj-$(CONFIG_SND_SOC) += cirrus/ 44 44 obj-$(CONFIG_SND_SOC) += dwc/ 45 45 obj-$(CONFIG_SND_SOC) += fsl/ 46 + obj-$(CONFIG_SND_SOC) += google/ 46 47 obj-$(CONFIG_SND_SOC) += hisilicon/ 47 48 obj-$(CONFIG_SND_SOC) += jz4740/ 48 49 obj-$(CONFIG_SND_SOC) += img/
+6
sound/soc/google/Kconfig
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + 3 + config SND_SOC_CHV3_I2S 4 + tristate "Google Chameleon v3 I2S device" 5 + help 6 + Enable support for the Google Chameleon v3 I2S device.
+2
sound/soc/google/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + obj-$(CONFIG_SND_SOC_CHV3_I2S) += chv3-i2s.o
+338
sound/soc/google/chv3-i2s.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #include <linux/module.h> 3 + #include <linux/of.h> 4 + #include <linux/platform_device.h> 5 + 6 + #include <sound/soc.h> 7 + 8 + /* 9 + * The I2S interface consists of two ring buffers - one for RX and one for 10 + * TX. A ring buffer has a producer index and a consumer index. Depending 11 + * on which way the data is flowing, either the software or the hardware 12 + * writes data and updates the producer index, and the other end reads data 13 + * and updates the consumer index. 14 + * 15 + * The pointer managed by software is updated using the .ack callback 16 + * (see chv3_dma_ack). This seems to be the only way to reliably obtain 17 + * the appl_ptr from within the driver and pass it to hardware. 18 + * 19 + * Because of the two pointer design, the ring buffer can never be full. With 20 + * capture this isn't a problem, because the hardware being the producer 21 + * will wait for the consumer index to move out of the way. With playback, 22 + * however, this is problematic, because ALSA wants to fill up the buffer 23 + * completely when waiting for hardware. In the .ack callback, the driver 24 + * would have to wait for the consumer index to move out of the way by 25 + * busy-waiting, which would keep stalling the kernel for quite a long time. 26 + * 27 + * The workaround to this problem is to "lie" to ALSA that the hw_pointer 28 + * is one frame behind what it actually is (see chv3_dma_pointer). This 29 + * way, ALSA will not try to fill up the entire buffer, and all callbacks 30 + * are wait-free. 31 + */ 32 + 33 + #define I2S_TX_ENABLE 0x00 34 + #define I2S_TX_BASE_ADDR 0x04 35 + #define I2S_TX_BUFFER_SIZE 0x08 36 + #define I2S_TX_PRODUCER_IDX 0x0c 37 + #define I2S_TX_CONSUMER_IDX 0x10 38 + #define I2S_RX_ENABLE 0x14 39 + #define I2S_RX_BASE_ADDR 0x18 40 + #define I2S_RX_BUFFER_SIZE 0x1c 41 + #define I2S_RX_PRODUCER_IDX 0x20 42 + #define I2S_RX_CONSUMER_IDX 0x24 43 + 44 + #define I2S_SOFT_RESET 0x2c 45 + #define I2S_SOFT_RESET_RX_BIT 0x1 46 + #define I2S_SOFT_RESET_TX_BIT 0x2 47 + 48 + #define I2S_RX_IRQ 0x4c 49 + #define I2S_RX_IRQ_CONST 0x50 50 + #define I2S_TX_IRQ 0x54 51 + #define I2S_TX_IRQ_CONST 0x58 52 + 53 + #define I2S_IRQ_MASK 0x8 54 + #define I2S_IRQ_CLR 0xc 55 + #define I2S_IRQ_RX_BIT 0x1 56 + #define I2S_IRQ_TX_BIT 0x2 57 + 58 + #define I2S_MAX_BUFFER_SIZE 0x200000 59 + 60 + struct chv3_i2s_dev { 61 + struct device *dev; 62 + void __iomem *iobase; 63 + void __iomem *iobase_irq; 64 + struct snd_pcm_substream *rx_substream; 65 + struct snd_pcm_substream *tx_substream; 66 + int tx_bytes_to_fetch; 67 + }; 68 + 69 + static struct snd_soc_dai_driver chv3_i2s_dai = { 70 + .name = "chv3-i2s", 71 + .capture = { 72 + .channels_min = 1, 73 + .channels_max = 128, 74 + .rates = SNDRV_PCM_RATE_CONTINUOUS, 75 + .rate_min = 8000, 76 + .rate_max = 96000, 77 + .formats = SNDRV_PCM_FMTBIT_S32_LE, 78 + }, 79 + .playback = { 80 + .channels_min = 1, 81 + .channels_max = 128, 82 + .rates = SNDRV_PCM_RATE_CONTINUOUS, 83 + .rate_min = 8000, 84 + .rate_max = 96000, 85 + .formats = SNDRV_PCM_FMTBIT_S32_LE, 86 + }, 87 + }; 88 + 89 + static const struct snd_pcm_hardware chv3_dma_hw = { 90 + .info = SNDRV_PCM_INFO_INTERLEAVED | 91 + SNDRV_PCM_INFO_MMAP | 92 + SNDRV_PCM_INFO_MMAP_VALID | 93 + SNDRV_PCM_INFO_BLOCK_TRANSFER, 94 + .buffer_bytes_max = I2S_MAX_BUFFER_SIZE, 95 + .period_bytes_min = 64, 96 + .period_bytes_max = 8192, 97 + .periods_min = 4, 98 + .periods_max = 256, 99 + }; 100 + 101 + static inline void chv3_i2s_wr(struct chv3_i2s_dev *i2s, int offset, u32 val) 102 + { 103 + writel(val, i2s->iobase + offset); 104 + } 105 + 106 + static inline u32 chv3_i2s_rd(struct chv3_i2s_dev *i2s, int offset) 107 + { 108 + return readl(i2s->iobase + offset); 109 + } 110 + 111 + static irqreturn_t chv3_i2s_isr(int irq, void *data) 112 + { 113 + struct chv3_i2s_dev *i2s = data; 114 + u32 reg; 115 + 116 + reg = readl(i2s->iobase_irq + I2S_IRQ_CLR); 117 + if (!reg) 118 + return IRQ_NONE; 119 + 120 + if (reg & I2S_IRQ_RX_BIT) 121 + snd_pcm_period_elapsed(i2s->rx_substream); 122 + 123 + if (reg & I2S_IRQ_TX_BIT) 124 + snd_pcm_period_elapsed(i2s->tx_substream); 125 + 126 + writel(reg, i2s->iobase_irq + I2S_IRQ_CLR); 127 + 128 + return IRQ_HANDLED; 129 + } 130 + 131 + static int chv3_dma_open(struct snd_soc_component *component, 132 + struct snd_pcm_substream *substream) 133 + { 134 + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 135 + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); 136 + int res; 137 + 138 + snd_soc_set_runtime_hwparams(substream, &chv3_dma_hw); 139 + 140 + res = snd_pcm_hw_constraint_pow2(substream->runtime, 0, 141 + SNDRV_PCM_HW_PARAM_BUFFER_BYTES); 142 + if (res) 143 + return res; 144 + 145 + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 146 + i2s->rx_substream = substream; 147 + else 148 + i2s->tx_substream = substream; 149 + 150 + return 0; 151 + } 152 + static int chv3_dma_close(struct snd_soc_component *component, 153 + struct snd_pcm_substream *substream) 154 + { 155 + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 156 + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); 157 + 158 + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) 159 + chv3_i2s_wr(i2s, I2S_RX_ENABLE, 0); 160 + else 161 + chv3_i2s_wr(i2s, I2S_TX_ENABLE, 0); 162 + 163 + return 0; 164 + } 165 + 166 + static int chv3_dma_pcm_construct(struct snd_soc_component *component, 167 + struct snd_soc_pcm_runtime *rtd) 168 + { 169 + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); 170 + struct snd_pcm_substream *substream; 171 + int res; 172 + 173 + substream = rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; 174 + if (substream) { 175 + res = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, i2s->dev, 176 + I2S_MAX_BUFFER_SIZE, &substream->dma_buffer); 177 + if (res) 178 + return res; 179 + } 180 + 181 + substream = rtd->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; 182 + if (substream) { 183 + res = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, i2s->dev, 184 + I2S_MAX_BUFFER_SIZE, &substream->dma_buffer); 185 + if (res) 186 + return res; 187 + } 188 + 189 + return 0; 190 + } 191 + 192 + static int chv3_dma_hw_params(struct snd_soc_component *component, 193 + struct snd_pcm_substream *substream, 194 + struct snd_pcm_hw_params *params) 195 + { 196 + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 197 + return 0; 198 + } 199 + 200 + static int chv3_dma_prepare(struct snd_soc_component *component, 201 + struct snd_pcm_substream *substream) 202 + { 203 + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 204 + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); 205 + unsigned int buffer_bytes, period_bytes, period_size; 206 + 207 + buffer_bytes = snd_pcm_lib_buffer_bytes(substream); 208 + period_bytes = snd_pcm_lib_period_bytes(substream); 209 + period_size = substream->runtime->period_size; 210 + 211 + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { 212 + chv3_i2s_wr(i2s, I2S_SOFT_RESET, I2S_SOFT_RESET_RX_BIT); 213 + chv3_i2s_wr(i2s, I2S_RX_BASE_ADDR, substream->dma_buffer.addr); 214 + chv3_i2s_wr(i2s, I2S_RX_BUFFER_SIZE, buffer_bytes); 215 + chv3_i2s_wr(i2s, I2S_RX_IRQ, (period_size << 8) | 1); 216 + chv3_i2s_wr(i2s, I2S_RX_ENABLE, 1); 217 + } else { 218 + chv3_i2s_wr(i2s, I2S_SOFT_RESET, I2S_SOFT_RESET_TX_BIT); 219 + chv3_i2s_wr(i2s, I2S_TX_BASE_ADDR, substream->dma_buffer.addr); 220 + chv3_i2s_wr(i2s, I2S_TX_BUFFER_SIZE, buffer_bytes); 221 + chv3_i2s_wr(i2s, I2S_TX_IRQ, ((period_bytes / i2s->tx_bytes_to_fetch) << 8) | 1); 222 + chv3_i2s_wr(i2s, I2S_TX_ENABLE, 1); 223 + } 224 + writel(I2S_IRQ_RX_BIT | I2S_IRQ_TX_BIT, i2s->iobase_irq + I2S_IRQ_MASK); 225 + 226 + return 0; 227 + } 228 + 229 + static snd_pcm_uframes_t chv3_dma_pointer(struct snd_soc_component *component, 230 + struct snd_pcm_substream *substream) 231 + { 232 + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 233 + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); 234 + u32 frame_bytes, buffer_bytes; 235 + u32 idx_bytes; 236 + 237 + frame_bytes = substream->runtime->frame_bits * 8; 238 + buffer_bytes = snd_pcm_lib_buffer_bytes(substream); 239 + 240 + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { 241 + idx_bytes = chv3_i2s_rd(i2s, I2S_RX_PRODUCER_IDX); 242 + } else { 243 + idx_bytes = chv3_i2s_rd(i2s, I2S_TX_CONSUMER_IDX); 244 + /* lag the pointer by one frame */ 245 + idx_bytes = (idx_bytes - frame_bytes) & (buffer_bytes - 1); 246 + } 247 + 248 + return bytes_to_frames(substream->runtime, idx_bytes); 249 + } 250 + 251 + static int chv3_dma_ack(struct snd_soc_component *component, 252 + struct snd_pcm_substream *substream) 253 + { 254 + struct snd_pcm_runtime *runtime = substream->runtime; 255 + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 256 + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); 257 + unsigned int bytes, idx; 258 + 259 + bytes = frames_to_bytes(runtime, runtime->control->appl_ptr); 260 + idx = bytes & (snd_pcm_lib_buffer_bytes(substream) - 1); 261 + 262 + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) 263 + chv3_i2s_wr(i2s, I2S_RX_CONSUMER_IDX, idx); 264 + else 265 + chv3_i2s_wr(i2s, I2S_TX_PRODUCER_IDX, idx); 266 + 267 + return 0; 268 + } 269 + 270 + static const struct snd_soc_component_driver chv3_i2s_comp = { 271 + .name = "chv3-i2s-comp", 272 + .open = chv3_dma_open, 273 + .close = chv3_dma_close, 274 + .pcm_construct = chv3_dma_pcm_construct, 275 + .hw_params = chv3_dma_hw_params, 276 + .prepare = chv3_dma_prepare, 277 + .pointer = chv3_dma_pointer, 278 + .ack = chv3_dma_ack, 279 + }; 280 + 281 + static int chv3_i2s_probe(struct platform_device *pdev) 282 + { 283 + struct chv3_i2s_dev *i2s; 284 + int res; 285 + int irq; 286 + 287 + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); 288 + if (!i2s) 289 + return -ENOMEM; 290 + 291 + i2s->iobase = devm_platform_ioremap_resource(pdev, 0); 292 + if (IS_ERR(i2s->iobase)) 293 + return PTR_ERR(i2s->iobase); 294 + 295 + i2s->iobase_irq = devm_platform_ioremap_resource(pdev, 1); 296 + if (IS_ERR(i2s->iobase_irq)) 297 + return PTR_ERR(i2s->iobase_irq); 298 + 299 + i2s->tx_bytes_to_fetch = (chv3_i2s_rd(i2s, I2S_TX_IRQ_CONST) >> 8) & 0xffff; 300 + 301 + i2s->dev = &pdev->dev; 302 + dev_set_drvdata(&pdev->dev, i2s); 303 + 304 + irq = platform_get_irq(pdev, 0); 305 + if (irq < 0) 306 + return -ENXIO; 307 + res = devm_request_irq(i2s->dev, irq, chv3_i2s_isr, 0, "chv3-i2s", i2s); 308 + if (res) 309 + return res; 310 + 311 + res = devm_snd_soc_register_component(&pdev->dev, &chv3_i2s_comp, 312 + &chv3_i2s_dai, 1); 313 + if (res) { 314 + dev_err(&pdev->dev, "couldn't register component: %d\n", res); 315 + return res; 316 + } 317 + 318 + return 0; 319 + } 320 + 321 + static const struct of_device_id chv3_i2s_of_match[] = { 322 + { .compatible = "google,chv3-i2s" }, 323 + {}, 324 + }; 325 + 326 + static struct platform_driver chv3_i2s_driver = { 327 + .probe = chv3_i2s_probe, 328 + .driver = { 329 + .name = "chv3-i2s", 330 + .of_match_table = chv3_i2s_of_match, 331 + }, 332 + }; 333 + 334 + module_platform_driver(chv3_i2s_driver); 335 + 336 + MODULE_AUTHOR("Pawel Anikiel <pan@semihalf.com>"); 337 + MODULE_DESCRIPTION("Chameleon v3 I2S interface"); 338 + MODULE_LICENSE("GPL");