mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 11:19:37 +00:00
![Uwe Kleine-König](/assets/img/avatar_default.png)
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is (mostly) ignored and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new() which already returns void. Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Acked-by: Takashi Iwai <tiwai@suse.de> Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com> Link: https://lore.kernel.org/r/20230315150745.67084-36-u.kleine-koenig@pengutronix.de Signed-off-by: Mark Brown <broonie@kernel.org>
323 lines
7.4 KiB
C
323 lines
7.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* 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 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 void 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 */
|
|
}
|
|
|
|
#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_new = au1xi2s_drvremove,
|
|
};
|
|
|
|
module_platform_driver(au1xi2s_driver);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver");
|
|
MODULE_AUTHOR("Manuel Lauss");
|