226d0f22d0
Add a new variable for storing resources accessed subsequent to the one accessed using request_mem_region, so the one accessed using request_mem_region can be released if needed. The resource variable names are also changed to be more descriptive. This code is also missing some calls to iounmap. The semantic match that finds this problem is as follows: (http://coccinelle.lip6.fr/) // <smpl> @r@ expression E, E1; identifier f; statement S1,S2,S3; @@ if (E == NULL) { ... when != if (E == NULL || ...) S1 else S2 when != E = E1 *E->f ... when any return ...; } else S3 // </smpl> Signed-off-by: Julia Lawall <julia@diku.dk> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
349 lines
7.7 KiB
C
349 lines
7.7 KiB
C
/*
|
|
* Au1000/Au1500/Au1100 I2S controller driver for ASoC
|
|
*
|
|
* (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
|
|
*
|
|
* Note: clock supplied to the I2S controller must be 256x samplerate.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/suspend.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/soc.h>
|
|
#include <asm/mach-au1x00/au1000.h>
|
|
|
|
#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 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");
|