diff options
Diffstat (limited to 'sound/soc/au1x/i2sc.c')
| -rw-r--r-- | sound/soc/au1x/i2sc.c | 324 | 
1 files changed, 324 insertions, 0 deletions
diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c new file mode 100644 index 00000000000..b3f37f6edbc --- /dev/null +++ b/sound/soc/au1x/i2sc.c @@ -0,0 +1,324 @@ +/* + * 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 const struct snd_soc_component_driver au1xi2s_component = { +	.name		= "au1xi2s", +}; + +static int au1xi2s_drvprobe(struct platform_device *pdev) +{ +	struct resource *iores, *dmares; +	struct au1xpsc_audio_data *ctx; + +	ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); +	if (!ctx) +		return -ENOMEM; + +	iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!iores) +		return -ENODEV; + +	if (!devm_request_mem_region(&pdev->dev, iores->start, +				     resource_size(iores), +				     pdev->name)) +		return -EBUSY; + +	ctx->mmio = devm_ioremap_nocache(&pdev->dev, iores->start, +					 resource_size(iores)); +	if (!ctx->mmio) +		return -EBUSY; + +	dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); +	if (!dmares) +		return -EBUSY; +	ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start; + +	dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1); +	if (!dmares) +		return -EBUSY; +	ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start; + +	platform_set_drvdata(pdev, ctx); + +	return snd_soc_register_component(&pdev->dev, &au1xi2s_component, +					  &au1xi2s_dai_driver, 1); +} + +static int au1xi2s_drvremove(struct platform_device *pdev) +{ +	struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev); + +	snd_soc_unregister_component(&pdev->dev); + +	WR(ctx, I2S_ENABLE, EN_D);	/* clock off, disable */ + +	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		= au1xi2s_drvremove, +}; + +module_platform_driver(au1xi2s_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver"); +MODULE_AUTHOR("Manuel Lauss");  | 
