Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 //
0003 // s3c24xx-i2s.c  --  ALSA Soc Audio Layer
0004 //
0005 // (c) 2006 Wolfson Microelectronics PLC.
0006 // Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
0007 //
0008 // Copyright 2004-2005 Simtec Electronics
0009 //  http://armlinux.simtec.co.uk/
0010 //  Ben Dooks <ben@simtec.co.uk>
0011 
0012 #include <linux/delay.h>
0013 #include <linux/clk.h>
0014 #include <linux/io.h>
0015 #include <linux/module.h>
0016 
0017 #include <sound/soc.h>
0018 #include <sound/pcm_params.h>
0019 
0020 #include "regs-iis.h"
0021 #include "dma.h"
0022 #include "s3c24xx-i2s.h"
0023 
0024 static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_out = {
0025     .chan_name  = "tx",
0026     .addr_width = 2,
0027 };
0028 
0029 static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_in = {
0030     .chan_name  = "rx",
0031     .addr_width = 2,
0032 };
0033 
0034 struct s3c24xx_i2s_info {
0035     void __iomem    *regs;
0036     struct clk  *iis_clk;
0037     u32     iiscon;
0038     u32     iismod;
0039     u32     iisfcon;
0040     u32     iispsr;
0041 };
0042 static struct s3c24xx_i2s_info s3c24xx_i2s;
0043 
0044 static void s3c24xx_snd_txctrl(int on)
0045 {
0046     u32 iisfcon;
0047     u32 iiscon;
0048     u32 iismod;
0049 
0050     iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
0051     iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
0052     iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
0053 
0054     pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
0055 
0056     if (on) {
0057         iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
0058         iiscon  |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
0059         iiscon  &= ~S3C2410_IISCON_TXIDLE;
0060         iismod  |= S3C2410_IISMOD_TXMODE;
0061 
0062         writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
0063         writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
0064         writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
0065     } else {
0066         /* note, we have to disable the FIFOs otherwise bad things
0067          * seem to happen when the DMA stops. According to the
0068          * Samsung supplied kernel, this should allow the DMA
0069          * engine and FIFOs to reset. If this isn't allowed, the
0070          * DMA engine will simply freeze randomly.
0071          */
0072 
0073         iisfcon &= ~S3C2410_IISFCON_TXENABLE;
0074         iisfcon &= ~S3C2410_IISFCON_TXDMA;
0075         iiscon  |=  S3C2410_IISCON_TXIDLE;
0076         iiscon  &= ~S3C2410_IISCON_TXDMAEN;
0077         iismod  &= ~S3C2410_IISMOD_TXMODE;
0078 
0079         writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
0080         writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
0081         writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
0082     }
0083 
0084     pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
0085 }
0086 
0087 static void s3c24xx_snd_rxctrl(int on)
0088 {
0089     u32 iisfcon;
0090     u32 iiscon;
0091     u32 iismod;
0092 
0093     iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
0094     iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
0095     iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
0096 
0097     pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
0098 
0099     if (on) {
0100         iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
0101         iiscon  |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
0102         iiscon  &= ~S3C2410_IISCON_RXIDLE;
0103         iismod  |= S3C2410_IISMOD_RXMODE;
0104 
0105         writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
0106         writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
0107         writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
0108     } else {
0109         /* note, we have to disable the FIFOs otherwise bad things
0110          * seem to happen when the DMA stops. According to the
0111          * Samsung supplied kernel, this should allow the DMA
0112          * engine and FIFOs to reset. If this isn't allowed, the
0113          * DMA engine will simply freeze randomly.
0114          */
0115 
0116         iisfcon &= ~S3C2410_IISFCON_RXENABLE;
0117         iisfcon &= ~S3C2410_IISFCON_RXDMA;
0118         iiscon  |= S3C2410_IISCON_RXIDLE;
0119         iiscon  &= ~S3C2410_IISCON_RXDMAEN;
0120         iismod  &= ~S3C2410_IISMOD_RXMODE;
0121 
0122         writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
0123         writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
0124         writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
0125     }
0126 
0127     pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
0128 }
0129 
0130 /*
0131  * Wait for the LR signal to allow synchronisation to the L/R clock
0132  * from the codec. May only be needed for slave mode.
0133  */
0134 static int s3c24xx_snd_lrsync(void)
0135 {
0136     u32 iiscon;
0137     int timeout = 50; /* 5ms */
0138 
0139     while (1) {
0140         iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
0141         if (iiscon & S3C2410_IISCON_LRINDEX)
0142             break;
0143 
0144         if (!timeout--)
0145             return -ETIMEDOUT;
0146         udelay(100);
0147     }
0148 
0149     return 0;
0150 }
0151 
0152 /*
0153  * Check whether CPU is the master or slave
0154  */
0155 static inline int s3c24xx_snd_is_clkmaster(void)
0156 {
0157     return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
0158 }
0159 
0160 /*
0161  * Set S3C24xx I2S DAI format
0162  */
0163 static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
0164         unsigned int fmt)
0165 {
0166     u32 iismod;
0167 
0168     iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
0169     pr_debug("hw_params r: IISMOD: %x \n", iismod);
0170 
0171     switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
0172     case SND_SOC_DAIFMT_BC_FC:
0173         iismod |= S3C2410_IISMOD_SLAVE;
0174         break;
0175     case SND_SOC_DAIFMT_BP_FP:
0176         iismod &= ~S3C2410_IISMOD_SLAVE;
0177         break;
0178     default:
0179         return -EINVAL;
0180     }
0181 
0182     switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
0183     case SND_SOC_DAIFMT_LEFT_J:
0184         iismod |= S3C2410_IISMOD_MSB;
0185         break;
0186     case SND_SOC_DAIFMT_I2S:
0187         iismod &= ~S3C2410_IISMOD_MSB;
0188         break;
0189     default:
0190         return -EINVAL;
0191     }
0192 
0193     writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
0194     pr_debug("hw_params w: IISMOD: %x \n", iismod);
0195 
0196     return 0;
0197 }
0198 
0199 static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
0200                  struct snd_pcm_hw_params *params,
0201                  struct snd_soc_dai *dai)
0202 {
0203     struct snd_dmaengine_dai_dma_data *dma_data;
0204     u32 iismod;
0205 
0206     dma_data = snd_soc_dai_get_dma_data(dai, substream);
0207 
0208     /* Working copies of register */
0209     iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
0210     pr_debug("hw_params r: IISMOD: %x\n", iismod);
0211 
0212     switch (params_width(params)) {
0213     case 8:
0214         iismod &= ~S3C2410_IISMOD_16BIT;
0215         dma_data->addr_width = 1;
0216         break;
0217     case 16:
0218         iismod |= S3C2410_IISMOD_16BIT;
0219         dma_data->addr_width = 2;
0220         break;
0221     default:
0222         return -EINVAL;
0223     }
0224 
0225     writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
0226     pr_debug("hw_params w: IISMOD: %x\n", iismod);
0227 
0228     return 0;
0229 }
0230 
0231 static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
0232                    struct snd_soc_dai *dai)
0233 {
0234     int ret = 0;
0235 
0236     switch (cmd) {
0237     case SNDRV_PCM_TRIGGER_START:
0238     case SNDRV_PCM_TRIGGER_RESUME:
0239     case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
0240         if (!s3c24xx_snd_is_clkmaster()) {
0241             ret = s3c24xx_snd_lrsync();
0242             if (ret)
0243                 goto exit_err;
0244         }
0245 
0246         if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
0247             s3c24xx_snd_rxctrl(1);
0248         else
0249             s3c24xx_snd_txctrl(1);
0250 
0251         break;
0252     case SNDRV_PCM_TRIGGER_STOP:
0253     case SNDRV_PCM_TRIGGER_SUSPEND:
0254     case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
0255         if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
0256             s3c24xx_snd_rxctrl(0);
0257         else
0258             s3c24xx_snd_txctrl(0);
0259         break;
0260     default:
0261         ret = -EINVAL;
0262         break;
0263     }
0264 
0265 exit_err:
0266     return ret;
0267 }
0268 
0269 /*
0270  * Set S3C24xx Clock source
0271  */
0272 static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
0273     int clk_id, unsigned int freq, int dir)
0274 {
0275     u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
0276 
0277     iismod &= ~S3C2440_IISMOD_MPLL;
0278 
0279     switch (clk_id) {
0280     case S3C24XX_CLKSRC_PCLK:
0281         break;
0282     case S3C24XX_CLKSRC_MPLL:
0283         iismod |= S3C2440_IISMOD_MPLL;
0284         break;
0285     default:
0286         return -EINVAL;
0287     }
0288 
0289     writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
0290     return 0;
0291 }
0292 
0293 /*
0294  * Set S3C24xx Clock dividers
0295  */
0296 static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
0297     int div_id, int div)
0298 {
0299     u32 reg;
0300 
0301     switch (div_id) {
0302     case S3C24XX_DIV_BCLK:
0303         reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
0304         writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
0305         break;
0306     case S3C24XX_DIV_MCLK:
0307         reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
0308         writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
0309         break;
0310     case S3C24XX_DIV_PRESCALER:
0311         writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
0312         reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
0313         writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
0314         break;
0315     default:
0316         return -EINVAL;
0317     }
0318 
0319     return 0;
0320 }
0321 
0322 /*
0323  * To avoid duplicating clock code, allow machine driver to
0324  * get the clockrate from here.
0325  */
0326 u32 s3c24xx_i2s_get_clockrate(void)
0327 {
0328     return clk_get_rate(s3c24xx_i2s.iis_clk);
0329 }
0330 EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);
0331 
0332 static int s3c24xx_i2s_probe(struct snd_soc_dai *dai)
0333 {
0334     int ret;
0335     snd_soc_dai_init_dma_data(dai, &s3c24xx_i2s_pcm_stereo_out,
0336                     &s3c24xx_i2s_pcm_stereo_in);
0337 
0338     s3c24xx_i2s.iis_clk = devm_clk_get(dai->dev, "iis");
0339     if (IS_ERR(s3c24xx_i2s.iis_clk)) {
0340         pr_err("failed to get iis_clock\n");
0341         return PTR_ERR(s3c24xx_i2s.iis_clk);
0342     }
0343     ret = clk_prepare_enable(s3c24xx_i2s.iis_clk);
0344     if (ret)
0345         return ret;
0346 
0347     writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
0348 
0349     s3c24xx_snd_txctrl(0);
0350     s3c24xx_snd_rxctrl(0);
0351 
0352     return 0;
0353 }
0354 
0355 #ifdef CONFIG_PM
0356 static int s3c24xx_i2s_suspend(struct snd_soc_component *component)
0357 {
0358     s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
0359     s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
0360     s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
0361     s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);
0362 
0363     clk_disable_unprepare(s3c24xx_i2s.iis_clk);
0364 
0365     return 0;
0366 }
0367 
0368 static int s3c24xx_i2s_resume(struct snd_soc_component *component)
0369 {
0370     int ret;
0371 
0372     ret = clk_prepare_enable(s3c24xx_i2s.iis_clk);
0373     if (ret)
0374         return ret;
0375 
0376     writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
0377     writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
0378     writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
0379     writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);
0380 
0381     return 0;
0382 }
0383 #else
0384 #define s3c24xx_i2s_suspend NULL
0385 #define s3c24xx_i2s_resume NULL
0386 #endif
0387 
0388 #define S3C24XX_I2S_RATES \
0389     (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
0390     SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
0391     SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
0392 
0393 static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
0394     .trigger    = s3c24xx_i2s_trigger,
0395     .hw_params  = s3c24xx_i2s_hw_params,
0396     .set_fmt    = s3c24xx_i2s_set_fmt,
0397     .set_clkdiv = s3c24xx_i2s_set_clkdiv,
0398     .set_sysclk = s3c24xx_i2s_set_sysclk,
0399 };
0400 
0401 static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
0402     .probe = s3c24xx_i2s_probe,
0403     .playback = {
0404         .channels_min = 2,
0405         .channels_max = 2,
0406         .rates = S3C24XX_I2S_RATES,
0407         .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
0408     .capture = {
0409         .channels_min = 2,
0410         .channels_max = 2,
0411         .rates = S3C24XX_I2S_RATES,
0412         .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
0413     .ops = &s3c24xx_i2s_dai_ops,
0414 };
0415 
0416 static const struct snd_soc_component_driver s3c24xx_i2s_component = {
0417     .name           = "s3c24xx-i2s",
0418     .suspend        = s3c24xx_i2s_suspend,
0419     .resume         = s3c24xx_i2s_resume,
0420     .legacy_dai_naming  = 1,
0421 };
0422 
0423 static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
0424 {
0425     struct resource *res;
0426     int ret;
0427 
0428     s3c24xx_i2s.regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
0429     if (IS_ERR(s3c24xx_i2s.regs))
0430         return PTR_ERR(s3c24xx_i2s.regs);
0431 
0432     s3c24xx_i2s_pcm_stereo_out.addr = res->start + S3C2410_IISFIFO;
0433     s3c24xx_i2s_pcm_stereo_in.addr = res->start + S3C2410_IISFIFO;
0434 
0435     ret = samsung_asoc_dma_platform_register(&pdev->dev, NULL,
0436                          "tx", "rx", NULL);
0437     if (ret) {
0438         dev_err(&pdev->dev, "Failed to register the DMA: %d\n", ret);
0439         return ret;
0440     }
0441 
0442     ret = devm_snd_soc_register_component(&pdev->dev,
0443             &s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1);
0444     if (ret)
0445         dev_err(&pdev->dev, "Failed to register the DAI\n");
0446 
0447     return ret;
0448 }
0449 
0450 static struct platform_driver s3c24xx_iis_driver = {
0451     .probe  = s3c24xx_iis_dev_probe,
0452     .driver = {
0453         .name = "s3c24xx-iis",
0454     },
0455 };
0456 
0457 module_platform_driver(s3c24xx_iis_driver);
0458 
0459 /* Module information */
0460 MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
0461 MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
0462 MODULE_LICENSE("GPL");
0463 MODULE_ALIAS("platform:s3c24xx-iis");