/* * Au1000/Au1500/Au1100 I2S controller driver for ASoC * * (c) 2011 Manuel Lauss * * Note: clock supplied to the I2S controller must be 256x samplerate. */ #include #include #include #include #include #include #include #include #include #include "psc.h" #define I2S_RXTX 0x00 #define I2S_CFG 0x04 #define I2S_ENABLE 0x08 #define CFG_XU (1 << 25) /* tx underflow */ #define CFG_XO (1 << 24) #define CFG_RU (1 << 23) #define CFG_RO (1 << 22) #define CFG_TR (1 << 21) #define CFG_TE (1 << 20) #define CFG_TF (1 << 19) #define CFG_RR (1 << 18) #define CFG_RF (1 << 17) #define CFG_ICK (1 << 12) /* clock invert */ #define CFG_PD (1 << 11) /* set to make I2SDIO INPUT */ #define CFG_LB (1 << 10) /* loopback */ #define CFG_IC (1 << 9) /* word select invert */ #define CFG_FM_I2S (0 << 7) /* I2S format */ #define CFG_FM_LJ (1 << 7) /* left-justified */ #define CFG_FM_RJ (2 << 7) /* right-justified */ #define CFG_FM_MASK (3 << 7) #define CFG_TN (1 << 6) /* tx fifo en */ #define CFG_RN (1 << 5) /* rx fifo en */ #define CFG_SZ_8 (0x08) #define CFG_SZ_16 (0x10) #define CFG_SZ_18 (0x12) #define CFG_SZ_20 (0x14) #define CFG_SZ_24 (0x18) #define CFG_SZ_MASK (0x1f) #define EN_D (1 << 1) /* DISable */ #define EN_CE (1 << 0) /* clock enable */ /* only limited by clock generator and board design */ #define AU1XI2SC_RATES \ SNDRV_PCM_RATE_CONTINUOUS #define AU1XI2SC_FMTS \ (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \ SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE | \ SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE | \ SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE | \ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \ SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE | \ 0) static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg) { return __raw_readl(ctx->mmio + reg); } static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v) { __raw_writel(v, ctx->mmio + reg); wmb(); } static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai); unsigned long c; int ret; ret = -EINVAL; c = ctx->cfg; c &= ~CFG_FM_MASK; switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: c |= CFG_FM_I2S; break; case SND_SOC_DAIFMT_MSB: c |= CFG_FM_RJ; break; case SND_SOC_DAIFMT_LSB: c |= CFG_FM_LJ; break; default: goto out; } c &= ~(CFG_IC | CFG_ICK); /* IB-IF */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: c |= CFG_IC | CFG_ICK; break; case SND_SOC_DAIFMT_NB_IF: c |= CFG_IC; break; case SND_SOC_DAIFMT_IB_NF: c |= CFG_ICK; break; case SND_SOC_DAIFMT_IB_IF: break; default: goto out; } /* I2S controller only supports master */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */ break; default: goto out; } ret = 0; ctx->cfg = c; out: return ret; } static int au1xi2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); int stype = SUBSTREAM_TYPE(substream); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: /* power up */ WR(ctx, I2S_ENABLE, EN_D | EN_CE); WR(ctx, I2S_ENABLE, EN_CE); ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN; WR(ctx, I2S_CFG, ctx->cfg); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN); WR(ctx, I2S_CFG, ctx->cfg); WR(ctx, I2S_ENABLE, EN_D); /* power off */ break; default: return -EINVAL; } return 0; } static unsigned long msbits_to_reg(int msbits) { switch (msbits) { case 8: return CFG_SZ_8; case 16: return CFG_SZ_16; case 18: return CFG_SZ_18; case 20: return CFG_SZ_20; case 24: return CFG_SZ_24; } return 0; } static int au1xi2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); unsigned long v; v = msbits_to_reg(params->msbits); if (!v) return -EINVAL; ctx->cfg &= ~CFG_SZ_MASK; ctx->cfg |= v; return 0; } static int au1xi2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]); return 0; } static const const struct snd_soc_dai_ops au1xi2s_dai_ops = { .startup = au1xi2s_startup, .trigger = au1xi2s_trigger, .hw_params = au1xi2s_hw_params, .set_fmt = au1xi2s_set_fmt, }; static struct snd_soc_dai_driver au1xi2s_dai_driver = { .symmetric_rates = 1, .playback = { .rates = AU1XI2SC_RATES, .formats = AU1XI2SC_FMTS, .channels_min = 2, .channels_max = 2, }, .capture = { .rates = AU1XI2SC_RATES, .formats = AU1XI2SC_FMTS, .channels_min = 2, .channels_max = 2, }, .ops = &au1xi2s_dai_ops, }; static int __devinit au1xi2s_drvprobe(struct platform_device *pdev) { int ret; struct resource *iores, *dmares; struct au1xpsc_audio_data *ctx; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!iores) { ret = -ENODEV; goto out0; } ret = -EBUSY; if (!request_mem_region(iores->start, resource_size(iores), pdev->name)) goto out0; ctx->mmio = ioremap_nocache(iores->start, resource_size(iores)); if (!ctx->mmio) goto out1; dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); if (!dmares) goto out2; ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start; dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1); if (!dmares) goto out2; ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start; platform_set_drvdata(pdev, ctx); ret = snd_soc_register_dai(&pdev->dev, &au1xi2s_dai_driver); if (ret) goto out2; return 0; out2: iounmap(ctx->mmio); out1: release_mem_region(iores->start, resource_size(iores)); out0: kfree(ctx); return ret; } static int __devexit au1xi2s_drvremove(struct platform_device *pdev) { struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev); struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); snd_soc_unregister_dai(&pdev->dev); WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ iounmap(ctx->mmio); release_mem_region(r->start, resource_size(r)); kfree(ctx); return 0; } #ifdef CONFIG_PM static int au1xi2s_drvsuspend(struct device *dev) { struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev); WR(ctx, I2S_ENABLE, EN_D); /* clock off, disable */ return 0; } static int au1xi2s_drvresume(struct device *dev) { return 0; } static const struct dev_pm_ops au1xi2sc_pmops = { .suspend = au1xi2s_drvsuspend, .resume = au1xi2s_drvresume, }; #define AU1XI2SC_PMOPS (&au1xi2sc_pmops) #else #define AU1XI2SC_PMOPS NULL #endif static struct platform_driver au1xi2s_driver = { .driver = { .name = "alchemy-i2sc", .owner = THIS_MODULE, .pm = AU1XI2SC_PMOPS, }, .probe = au1xi2s_drvprobe, .remove = __devexit_p(au1xi2s_drvremove), }; static int __init au1xi2s_load(void) { return platform_driver_register(&au1xi2s_driver); } static void __exit au1xi2s_unload(void) { platform_driver_unregister(&au1xi2s_driver); } module_init(au1xi2s_load); module_exit(au1xi2s_unload); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver"); MODULE_AUTHOR("Manuel Lauss");