s3c24xx-i2s.c (11704B)
1// SPDX-License-Identifier: GPL-2.0+ 2// 3// s3c24xx-i2s.c -- ALSA Soc Audio Layer 4// 5// (c) 2006 Wolfson Microelectronics PLC. 6// Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com 7// 8// Copyright 2004-2005 Simtec Electronics 9// http://armlinux.simtec.co.uk/ 10// Ben Dooks <ben@simtec.co.uk> 11 12#include <linux/delay.h> 13#include <linux/clk.h> 14#include <linux/io.h> 15#include <linux/gpio.h> 16#include <linux/module.h> 17 18#include <sound/soc.h> 19#include <sound/pcm_params.h> 20 21#include "regs-iis.h" 22#include "dma.h" 23#include "s3c24xx-i2s.h" 24 25static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_out = { 26 .chan_name = "tx", 27 .addr_width = 2, 28}; 29 30static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_in = { 31 .chan_name = "rx", 32 .addr_width = 2, 33}; 34 35struct s3c24xx_i2s_info { 36 void __iomem *regs; 37 struct clk *iis_clk; 38 u32 iiscon; 39 u32 iismod; 40 u32 iisfcon; 41 u32 iispsr; 42}; 43static struct s3c24xx_i2s_info s3c24xx_i2s; 44 45static void s3c24xx_snd_txctrl(int on) 46{ 47 u32 iisfcon; 48 u32 iiscon; 49 u32 iismod; 50 51 iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); 52 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 53 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 54 55 pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); 56 57 if (on) { 58 iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE; 59 iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN; 60 iiscon &= ~S3C2410_IISCON_TXIDLE; 61 iismod |= S3C2410_IISMOD_TXMODE; 62 63 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 64 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 65 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 66 } else { 67 /* note, we have to disable the FIFOs otherwise bad things 68 * seem to happen when the DMA stops. According to the 69 * Samsung supplied kernel, this should allow the DMA 70 * engine and FIFOs to reset. If this isn't allowed, the 71 * DMA engine will simply freeze randomly. 72 */ 73 74 iisfcon &= ~S3C2410_IISFCON_TXENABLE; 75 iisfcon &= ~S3C2410_IISFCON_TXDMA; 76 iiscon |= S3C2410_IISCON_TXIDLE; 77 iiscon &= ~S3C2410_IISCON_TXDMAEN; 78 iismod &= ~S3C2410_IISMOD_TXMODE; 79 80 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 81 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 82 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 83 } 84 85 pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); 86} 87 88static void s3c24xx_snd_rxctrl(int on) 89{ 90 u32 iisfcon; 91 u32 iiscon; 92 u32 iismod; 93 94 iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); 95 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 96 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 97 98 pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); 99 100 if (on) { 101 iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; 102 iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN; 103 iiscon &= ~S3C2410_IISCON_RXIDLE; 104 iismod |= S3C2410_IISMOD_RXMODE; 105 106 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 107 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 108 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 109 } else { 110 /* note, we have to disable the FIFOs otherwise bad things 111 * seem to happen when the DMA stops. According to the 112 * Samsung supplied kernel, this should allow the DMA 113 * engine and FIFOs to reset. If this isn't allowed, the 114 * DMA engine will simply freeze randomly. 115 */ 116 117 iisfcon &= ~S3C2410_IISFCON_RXENABLE; 118 iisfcon &= ~S3C2410_IISFCON_RXDMA; 119 iiscon |= S3C2410_IISCON_RXIDLE; 120 iiscon &= ~S3C2410_IISCON_RXDMAEN; 121 iismod &= ~S3C2410_IISMOD_RXMODE; 122 123 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 124 writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 125 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 126 } 127 128 pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); 129} 130 131/* 132 * Wait for the LR signal to allow synchronisation to the L/R clock 133 * from the codec. May only be needed for slave mode. 134 */ 135static int s3c24xx_snd_lrsync(void) 136{ 137 u32 iiscon; 138 int timeout = 50; /* 5ms */ 139 140 while (1) { 141 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 142 if (iiscon & S3C2410_IISCON_LRINDEX) 143 break; 144 145 if (!timeout--) 146 return -ETIMEDOUT; 147 udelay(100); 148 } 149 150 return 0; 151} 152 153/* 154 * Check whether CPU is the master or slave 155 */ 156static inline int s3c24xx_snd_is_clkmaster(void) 157{ 158 return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1; 159} 160 161/* 162 * Set S3C24xx I2S DAI format 163 */ 164static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai, 165 unsigned int fmt) 166{ 167 u32 iismod; 168 169 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 170 pr_debug("hw_params r: IISMOD: %x \n", iismod); 171 172 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 173 case SND_SOC_DAIFMT_CBM_CFM: 174 iismod |= S3C2410_IISMOD_SLAVE; 175 break; 176 case SND_SOC_DAIFMT_CBS_CFS: 177 iismod &= ~S3C2410_IISMOD_SLAVE; 178 break; 179 default: 180 return -EINVAL; 181 } 182 183 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 184 case SND_SOC_DAIFMT_LEFT_J: 185 iismod |= S3C2410_IISMOD_MSB; 186 break; 187 case SND_SOC_DAIFMT_I2S: 188 iismod &= ~S3C2410_IISMOD_MSB; 189 break; 190 default: 191 return -EINVAL; 192 } 193 194 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 195 pr_debug("hw_params w: IISMOD: %x \n", iismod); 196 197 return 0; 198} 199 200static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, 201 struct snd_pcm_hw_params *params, 202 struct snd_soc_dai *dai) 203{ 204 struct snd_dmaengine_dai_dma_data *dma_data; 205 u32 iismod; 206 207 dma_data = snd_soc_dai_get_dma_data(dai, substream); 208 209 /* Working copies of register */ 210 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 211 pr_debug("hw_params r: IISMOD: %x\n", iismod); 212 213 switch (params_width(params)) { 214 case 8: 215 iismod &= ~S3C2410_IISMOD_16BIT; 216 dma_data->addr_width = 1; 217 break; 218 case 16: 219 iismod |= S3C2410_IISMOD_16BIT; 220 dma_data->addr_width = 2; 221 break; 222 default: 223 return -EINVAL; 224 } 225 226 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 227 pr_debug("hw_params w: IISMOD: %x\n", iismod); 228 229 return 0; 230} 231 232static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, 233 struct snd_soc_dai *dai) 234{ 235 int ret = 0; 236 237 switch (cmd) { 238 case SNDRV_PCM_TRIGGER_START: 239 case SNDRV_PCM_TRIGGER_RESUME: 240 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 241 if (!s3c24xx_snd_is_clkmaster()) { 242 ret = s3c24xx_snd_lrsync(); 243 if (ret) 244 goto exit_err; 245 } 246 247 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 248 s3c24xx_snd_rxctrl(1); 249 else 250 s3c24xx_snd_txctrl(1); 251 252 break; 253 case SNDRV_PCM_TRIGGER_STOP: 254 case SNDRV_PCM_TRIGGER_SUSPEND: 255 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 256 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 257 s3c24xx_snd_rxctrl(0); 258 else 259 s3c24xx_snd_txctrl(0); 260 break; 261 default: 262 ret = -EINVAL; 263 break; 264 } 265 266exit_err: 267 return ret; 268} 269 270/* 271 * Set S3C24xx Clock source 272 */ 273static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, 274 int clk_id, unsigned int freq, int dir) 275{ 276 u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 277 278 iismod &= ~S3C2440_IISMOD_MPLL; 279 280 switch (clk_id) { 281 case S3C24XX_CLKSRC_PCLK: 282 break; 283 case S3C24XX_CLKSRC_MPLL: 284 iismod |= S3C2440_IISMOD_MPLL; 285 break; 286 default: 287 return -EINVAL; 288 } 289 290 writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 291 return 0; 292} 293 294/* 295 * Set S3C24xx Clock dividers 296 */ 297static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, 298 int div_id, int div) 299{ 300 u32 reg; 301 302 switch (div_id) { 303 case S3C24XX_DIV_BCLK: 304 reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK; 305 writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); 306 break; 307 case S3C24XX_DIV_MCLK: 308 reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS); 309 writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); 310 break; 311 case S3C24XX_DIV_PRESCALER: 312 writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR); 313 reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 314 writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON); 315 break; 316 default: 317 return -EINVAL; 318 } 319 320 return 0; 321} 322 323/* 324 * To avoid duplicating clock code, allow machine driver to 325 * get the clockrate from here. 326 */ 327u32 s3c24xx_i2s_get_clockrate(void) 328{ 329 return clk_get_rate(s3c24xx_i2s.iis_clk); 330} 331EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); 332 333static int s3c24xx_i2s_probe(struct snd_soc_dai *dai) 334{ 335 int ret; 336 snd_soc_dai_init_dma_data(dai, &s3c24xx_i2s_pcm_stereo_out, 337 &s3c24xx_i2s_pcm_stereo_in); 338 339 s3c24xx_i2s.iis_clk = devm_clk_get(dai->dev, "iis"); 340 if (IS_ERR(s3c24xx_i2s.iis_clk)) { 341 pr_err("failed to get iis_clock\n"); 342 return PTR_ERR(s3c24xx_i2s.iis_clk); 343 } 344 ret = clk_prepare_enable(s3c24xx_i2s.iis_clk); 345 if (ret) 346 return ret; 347 348 writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON); 349 350 s3c24xx_snd_txctrl(0); 351 s3c24xx_snd_rxctrl(0); 352 353 return 0; 354} 355 356#ifdef CONFIG_PM 357static int s3c24xx_i2s_suspend(struct snd_soc_component *component) 358{ 359 s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); 360 s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); 361 s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); 362 s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR); 363 364 clk_disable_unprepare(s3c24xx_i2s.iis_clk); 365 366 return 0; 367} 368 369static int s3c24xx_i2s_resume(struct snd_soc_component *component) 370{ 371 int ret; 372 373 ret = clk_prepare_enable(s3c24xx_i2s.iis_clk); 374 if (ret) 375 return ret; 376 377 writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); 378 writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); 379 writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); 380 writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR); 381 382 return 0; 383} 384#else 385#define s3c24xx_i2s_suspend NULL 386#define s3c24xx_i2s_resume NULL 387#endif 388 389#define S3C24XX_I2S_RATES \ 390 (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ 391 SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ 392 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) 393 394static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = { 395 .trigger = s3c24xx_i2s_trigger, 396 .hw_params = s3c24xx_i2s_hw_params, 397 .set_fmt = s3c24xx_i2s_set_fmt, 398 .set_clkdiv = s3c24xx_i2s_set_clkdiv, 399 .set_sysclk = s3c24xx_i2s_set_sysclk, 400}; 401 402static struct snd_soc_dai_driver s3c24xx_i2s_dai = { 403 .probe = s3c24xx_i2s_probe, 404 .playback = { 405 .channels_min = 2, 406 .channels_max = 2, 407 .rates = S3C24XX_I2S_RATES, 408 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, 409 .capture = { 410 .channels_min = 2, 411 .channels_max = 2, 412 .rates = S3C24XX_I2S_RATES, 413 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, 414 .ops = &s3c24xx_i2s_dai_ops, 415}; 416 417static const struct snd_soc_component_driver s3c24xx_i2s_component = { 418 .name = "s3c24xx-i2s", 419 .suspend = s3c24xx_i2s_suspend, 420 .resume = s3c24xx_i2s_resume, 421}; 422 423static int s3c24xx_iis_dev_probe(struct platform_device *pdev) 424{ 425 struct resource *res; 426 int ret; 427 428 s3c24xx_i2s.regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 429 if (IS_ERR(s3c24xx_i2s.regs)) 430 return PTR_ERR(s3c24xx_i2s.regs); 431 432 s3c24xx_i2s_pcm_stereo_out.addr = res->start + S3C2410_IISFIFO; 433 s3c24xx_i2s_pcm_stereo_in.addr = res->start + S3C2410_IISFIFO; 434 435 ret = samsung_asoc_dma_platform_register(&pdev->dev, NULL, 436 "tx", "rx", NULL); 437 if (ret) { 438 dev_err(&pdev->dev, "Failed to register the DMA: %d\n", ret); 439 return ret; 440 } 441 442 ret = devm_snd_soc_register_component(&pdev->dev, 443 &s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1); 444 if (ret) 445 dev_err(&pdev->dev, "Failed to register the DAI\n"); 446 447 return ret; 448} 449 450static struct platform_driver s3c24xx_iis_driver = { 451 .probe = s3c24xx_iis_dev_probe, 452 .driver = { 453 .name = "s3c24xx-iis", 454 }, 455}; 456 457module_platform_driver(s3c24xx_iis_driver); 458 459/* Module information */ 460MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); 461MODULE_DESCRIPTION("s3c24xx I2S SoC Interface"); 462MODULE_LICENSE("GPL"); 463MODULE_ALIAS("platform:s3c24xx-iis");