mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
7.4 KiB
325 lines
7.4 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Au1000/Au1500/Au1100 I2S controller driver for ASoC |
|
* |
|
* (c) 2011 Manuel Lauss <[email protected]> |
|
* |
|
* 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 provider */ |
|
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { |
|
case SND_SOC_DAIFMT_BP_FP: /* CODEC consumer */ |
|
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_rate = 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", |
|
.legacy_dai_naming = 1, |
|
}; |
|
|
|
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(&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", |
|
.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");
|
|
|