162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Au12x0/Au1550 PSC ALSA ASoC audio support.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
662306a36Sopenharmony_ci *	Manuel Lauss <manuel.lauss@gmail.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Au1xxx-PSC I2S glue.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * NOTE: so far only PSC slave mode (bit- and frameclock) is supported.
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <linux/suspend.h>
1762306a36Sopenharmony_ci#include <sound/core.h>
1862306a36Sopenharmony_ci#include <sound/pcm.h>
1962306a36Sopenharmony_ci#include <sound/initval.h>
2062306a36Sopenharmony_ci#include <sound/soc.h>
2162306a36Sopenharmony_ci#include <asm/mach-au1x00/au1000.h>
2262306a36Sopenharmony_ci#include <asm/mach-au1x00/au1xxx_psc.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include "psc.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* supported I2S DAI hardware formats */
2762306a36Sopenharmony_ci#define AU1XPSC_I2S_DAIFMT \
2862306a36Sopenharmony_ci	(SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J |	\
2962306a36Sopenharmony_ci	 SND_SOC_DAIFMT_NB_NF)
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* supported I2S direction */
3262306a36Sopenharmony_ci#define AU1XPSC_I2S_DIR \
3362306a36Sopenharmony_ci	(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define AU1XPSC_I2S_RATES \
3662306a36Sopenharmony_ci	SNDRV_PCM_RATE_8000_192000
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#define AU1XPSC_I2S_FMTS \
3962306a36Sopenharmony_ci	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define I2SSTAT_BUSY(stype)	\
4262306a36Sopenharmony_ci	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB)
4362306a36Sopenharmony_ci#define I2SPCR_START(stype)	\
4462306a36Sopenharmony_ci	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TS : PSC_I2SPCR_RS)
4562306a36Sopenharmony_ci#define I2SPCR_STOP(stype)	\
4662306a36Sopenharmony_ci	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TP : PSC_I2SPCR_RP)
4762306a36Sopenharmony_ci#define I2SPCR_CLRFIFO(stype)	\
4862306a36Sopenharmony_ci	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TC : PSC_I2SPCR_RC)
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
5262306a36Sopenharmony_ci			       unsigned int fmt)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(cpu_dai);
5562306a36Sopenharmony_ci	unsigned long ct;
5662306a36Sopenharmony_ci	int ret;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	ret = -EINVAL;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	ct = pscdata->cfg;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	ct &= ~(PSC_I2SCFG_XM | PSC_I2SCFG_MLJ);	/* left-justified */
6362306a36Sopenharmony_ci	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
6462306a36Sopenharmony_ci	case SND_SOC_DAIFMT_I2S:
6562306a36Sopenharmony_ci		ct |= PSC_I2SCFG_XM;	/* enable I2S mode */
6662306a36Sopenharmony_ci		break;
6762306a36Sopenharmony_ci	case SND_SOC_DAIFMT_MSB:
6862306a36Sopenharmony_ci		break;
6962306a36Sopenharmony_ci	case SND_SOC_DAIFMT_LSB:
7062306a36Sopenharmony_ci		ct |= PSC_I2SCFG_MLJ;	/* LSB (right-) justified */
7162306a36Sopenharmony_ci		break;
7262306a36Sopenharmony_ci	default:
7362306a36Sopenharmony_ci		goto out;
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	ct &= ~(PSC_I2SCFG_BI | PSC_I2SCFG_WI);		/* IB-IF */
7762306a36Sopenharmony_ci	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
7862306a36Sopenharmony_ci	case SND_SOC_DAIFMT_NB_NF:
7962306a36Sopenharmony_ci		ct |= PSC_I2SCFG_BI | PSC_I2SCFG_WI;
8062306a36Sopenharmony_ci		break;
8162306a36Sopenharmony_ci	case SND_SOC_DAIFMT_NB_IF:
8262306a36Sopenharmony_ci		ct |= PSC_I2SCFG_BI;
8362306a36Sopenharmony_ci		break;
8462306a36Sopenharmony_ci	case SND_SOC_DAIFMT_IB_NF:
8562306a36Sopenharmony_ci		ct |= PSC_I2SCFG_WI;
8662306a36Sopenharmony_ci		break;
8762306a36Sopenharmony_ci	case SND_SOC_DAIFMT_IB_IF:
8862306a36Sopenharmony_ci		break;
8962306a36Sopenharmony_ci	default:
9062306a36Sopenharmony_ci		goto out;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
9462306a36Sopenharmony_ci	case SND_SOC_DAIFMT_BC_FC:	/* CODEC provider */
9562306a36Sopenharmony_ci		ct |= PSC_I2SCFG_MS;	/* PSC I2S consumer mode */
9662306a36Sopenharmony_ci		break;
9762306a36Sopenharmony_ci	case SND_SOC_DAIFMT_BP_FP:	/* CODEC consumer */
9862306a36Sopenharmony_ci		ct &= ~PSC_I2SCFG_MS;	/* PSC I2S provider mode */
9962306a36Sopenharmony_ci		break;
10062306a36Sopenharmony_ci	default:
10162306a36Sopenharmony_ci		goto out;
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	pscdata->cfg = ct;
10562306a36Sopenharmony_ci	ret = 0;
10662306a36Sopenharmony_ciout:
10762306a36Sopenharmony_ci	return ret;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream,
11162306a36Sopenharmony_ci				 struct snd_pcm_hw_params *params,
11262306a36Sopenharmony_ci				 struct snd_soc_dai *dai)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	int cfgbits;
11762306a36Sopenharmony_ci	unsigned long stat;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	/* check if the PSC is already streaming data */
12062306a36Sopenharmony_ci	stat = __raw_readl(I2S_STAT(pscdata));
12162306a36Sopenharmony_ci	if (stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB)) {
12262306a36Sopenharmony_ci		/* reject parameters not currently set up in hardware */
12362306a36Sopenharmony_ci		cfgbits = __raw_readl(I2S_CFG(pscdata));
12462306a36Sopenharmony_ci		if ((PSC_I2SCFG_GET_LEN(cfgbits) != params->msbits) ||
12562306a36Sopenharmony_ci		    (params_rate(params) != pscdata->rate))
12662306a36Sopenharmony_ci			return -EINVAL;
12762306a36Sopenharmony_ci	} else {
12862306a36Sopenharmony_ci		/* set sample bitdepth */
12962306a36Sopenharmony_ci		pscdata->cfg &= ~(0x1f << 4);
13062306a36Sopenharmony_ci		pscdata->cfg |= PSC_I2SCFG_SET_LEN(params->msbits);
13162306a36Sopenharmony_ci		/* remember current rate for other stream */
13262306a36Sopenharmony_ci		pscdata->rate = params_rate(params);
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci	return 0;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci/* Configure PSC late:  on my devel systems the codec  is I2S master and
13862306a36Sopenharmony_ci * supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit.  ASoC
13962306a36Sopenharmony_ci * uses aggressive PM and  switches the codec off  when it is not in use
14062306a36Sopenharmony_ci * which also means the PSC unit doesn't get any clocks and is therefore
14162306a36Sopenharmony_ci * dead. That's why this chunk here gets called from the trigger callback
14262306a36Sopenharmony_ci * because I can be reasonably certain the codec is driving the clocks.
14362306a36Sopenharmony_ci */
14462306a36Sopenharmony_cistatic int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	unsigned long tmo;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	/* bring PSC out of sleep, and configure I2S unit */
14962306a36Sopenharmony_ci	__raw_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
15062306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	tmo = 1000000;
15362306a36Sopenharmony_ci	while (!(__raw_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_SR) && tmo)
15462306a36Sopenharmony_ci		tmo--;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	if (!tmo)
15762306a36Sopenharmony_ci		goto psc_err;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	__raw_writel(0, I2S_CFG(pscdata));
16062306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
16162306a36Sopenharmony_ci	__raw_writel(pscdata->cfg | PSC_I2SCFG_DE_ENABLE, I2S_CFG(pscdata));
16262306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/* wait for I2S controller to become ready */
16562306a36Sopenharmony_ci	tmo = 1000000;
16662306a36Sopenharmony_ci	while (!(__raw_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_DR) && tmo)
16762306a36Sopenharmony_ci		tmo--;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (tmo)
17062306a36Sopenharmony_ci		return 0;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cipsc_err:
17362306a36Sopenharmony_ci	__raw_writel(0, I2S_CFG(pscdata));
17462306a36Sopenharmony_ci	__raw_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
17562306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
17662306a36Sopenharmony_ci	return -ETIMEDOUT;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int stype)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	unsigned long tmo, stat;
18262306a36Sopenharmony_ci	int ret;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	ret = 0;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	/* if both TX and RX are idle, configure the PSC  */
18762306a36Sopenharmony_ci	stat = __raw_readl(I2S_STAT(pscdata));
18862306a36Sopenharmony_ci	if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
18962306a36Sopenharmony_ci		ret = au1xpsc_i2s_configure(pscdata);
19062306a36Sopenharmony_ci		if (ret)
19162306a36Sopenharmony_ci			goto out;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	__raw_writel(I2SPCR_CLRFIFO(stype), I2S_PCR(pscdata));
19562306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
19662306a36Sopenharmony_ci	__raw_writel(I2SPCR_START(stype), I2S_PCR(pscdata));
19762306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	/* wait for start confirmation */
20062306a36Sopenharmony_ci	tmo = 1000000;
20162306a36Sopenharmony_ci	while (!(__raw_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
20262306a36Sopenharmony_ci		tmo--;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (!tmo) {
20562306a36Sopenharmony_ci		__raw_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
20662306a36Sopenharmony_ci		wmb(); /* drain writebuffer */
20762306a36Sopenharmony_ci		ret = -ETIMEDOUT;
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ciout:
21062306a36Sopenharmony_ci	return ret;
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	unsigned long tmo, stat;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	__raw_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));
21862306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	/* wait for stop confirmation */
22162306a36Sopenharmony_ci	tmo = 1000000;
22262306a36Sopenharmony_ci	while ((__raw_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)
22362306a36Sopenharmony_ci		tmo--;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	/* if both TX and RX are idle, disable PSC */
22662306a36Sopenharmony_ci	stat = __raw_readl(I2S_STAT(pscdata));
22762306a36Sopenharmony_ci	if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {
22862306a36Sopenharmony_ci		__raw_writel(0, I2S_CFG(pscdata));
22962306a36Sopenharmony_ci		wmb(); /* drain writebuffer */
23062306a36Sopenharmony_ci		__raw_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
23162306a36Sopenharmony_ci		wmb(); /* drain writebuffer */
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci	return 0;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
23762306a36Sopenharmony_ci			       struct snd_soc_dai *dai)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
24062306a36Sopenharmony_ci	int ret, stype = substream->stream;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	switch (cmd) {
24362306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_START:
24462306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_RESUME:
24562306a36Sopenharmony_ci		ret = au1xpsc_i2s_start(pscdata, stype);
24662306a36Sopenharmony_ci		break;
24762306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_STOP:
24862306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_SUSPEND:
24962306a36Sopenharmony_ci		ret = au1xpsc_i2s_stop(pscdata, stype);
25062306a36Sopenharmony_ci		break;
25162306a36Sopenharmony_ci	default:
25262306a36Sopenharmony_ci		ret = -EINVAL;
25362306a36Sopenharmony_ci	}
25462306a36Sopenharmony_ci	return ret;
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic int au1xpsc_i2s_startup(struct snd_pcm_substream *substream,
25862306a36Sopenharmony_ci			       struct snd_soc_dai *dai)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);
26162306a36Sopenharmony_ci	snd_soc_dai_set_dma_data(dai, substream, &pscdata->dmaids[0]);
26262306a36Sopenharmony_ci	return 0;
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_cistatic const struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = {
26662306a36Sopenharmony_ci	.startup	= au1xpsc_i2s_startup,
26762306a36Sopenharmony_ci	.trigger	= au1xpsc_i2s_trigger,
26862306a36Sopenharmony_ci	.hw_params	= au1xpsc_i2s_hw_params,
26962306a36Sopenharmony_ci	.set_fmt	= au1xpsc_i2s_set_fmt,
27062306a36Sopenharmony_ci};
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_cistatic const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = {
27362306a36Sopenharmony_ci	.playback = {
27462306a36Sopenharmony_ci		.rates		= AU1XPSC_I2S_RATES,
27562306a36Sopenharmony_ci		.formats	= AU1XPSC_I2S_FMTS,
27662306a36Sopenharmony_ci		.channels_min	= 2,
27762306a36Sopenharmony_ci		.channels_max	= 8,	/* 2 without external help */
27862306a36Sopenharmony_ci	},
27962306a36Sopenharmony_ci	.capture = {
28062306a36Sopenharmony_ci		.rates		= AU1XPSC_I2S_RATES,
28162306a36Sopenharmony_ci		.formats	= AU1XPSC_I2S_FMTS,
28262306a36Sopenharmony_ci		.channels_min	= 2,
28362306a36Sopenharmony_ci		.channels_max	= 8,	/* 2 without external help */
28462306a36Sopenharmony_ci	},
28562306a36Sopenharmony_ci	.ops = &au1xpsc_i2s_dai_ops,
28662306a36Sopenharmony_ci};
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic const struct snd_soc_component_driver au1xpsc_i2s_component = {
28962306a36Sopenharmony_ci	.name			= "au1xpsc-i2s",
29062306a36Sopenharmony_ci	.legacy_dai_naming	= 1,
29162306a36Sopenharmony_ci};
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic int au1xpsc_i2s_drvprobe(struct platform_device *pdev)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	struct resource *dmares;
29662306a36Sopenharmony_ci	unsigned long sel;
29762306a36Sopenharmony_ci	struct au1xpsc_audio_data *wd;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	wd = devm_kzalloc(&pdev->dev, sizeof(struct au1xpsc_audio_data),
30062306a36Sopenharmony_ci			  GFP_KERNEL);
30162306a36Sopenharmony_ci	if (!wd)
30262306a36Sopenharmony_ci		return -ENOMEM;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	wd->mmio = devm_platform_ioremap_resource(pdev, 0);
30562306a36Sopenharmony_ci	if (IS_ERR(wd->mmio))
30662306a36Sopenharmony_ci		return PTR_ERR(wd->mmio);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
30962306a36Sopenharmony_ci	if (!dmares)
31062306a36Sopenharmony_ci		return -EBUSY;
31162306a36Sopenharmony_ci	wd->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1);
31462306a36Sopenharmony_ci	if (!dmares)
31562306a36Sopenharmony_ci		return -EBUSY;
31662306a36Sopenharmony_ci	wd->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	/* preserve PSC clock source set up by platform (dev.platform_data
31962306a36Sopenharmony_ci	 * is already occupied by soc layer)
32062306a36Sopenharmony_ci	 */
32162306a36Sopenharmony_ci	sel = __raw_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK;
32262306a36Sopenharmony_ci	__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
32362306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
32462306a36Sopenharmony_ci	__raw_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(wd));
32562306a36Sopenharmony_ci	__raw_writel(0, I2S_CFG(wd));
32662306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	/* preconfigure: set max rx/tx fifo depths */
32962306a36Sopenharmony_ci	wd->cfg |= PSC_I2SCFG_RT_FIFO8 | PSC_I2SCFG_TT_FIFO8;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	/* don't wait for I2S core to become ready now; clocks may not
33262306a36Sopenharmony_ci	 * be running yet; depending on clock input for PSC a wait might
33362306a36Sopenharmony_ci	 * time out.
33462306a36Sopenharmony_ci	 */
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	/* name the DAI like this device instance ("au1xpsc-i2s.PSCINDEX") */
33762306a36Sopenharmony_ci	memcpy(&wd->dai_drv, &au1xpsc_i2s_dai_template,
33862306a36Sopenharmony_ci	       sizeof(struct snd_soc_dai_driver));
33962306a36Sopenharmony_ci	wd->dai_drv.name = dev_name(&pdev->dev);
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	platform_set_drvdata(pdev, wd);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	return devm_snd_soc_register_component(&pdev->dev,
34462306a36Sopenharmony_ci				&au1xpsc_i2s_component, &wd->dai_drv, 1);
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic void au1xpsc_i2s_drvremove(struct platform_device *pdev)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	__raw_writel(0, I2S_CFG(wd));
35262306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
35362306a36Sopenharmony_ci	__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
35462306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
35562306a36Sopenharmony_ci}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci#ifdef CONFIG_PM
35862306a36Sopenharmony_cistatic int au1xpsc_i2s_drvsuspend(struct device *dev)
35962306a36Sopenharmony_ci{
36062306a36Sopenharmony_ci	struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	/* save interesting register and disable PSC */
36362306a36Sopenharmony_ci	wd->pm[0] = __raw_readl(PSC_SEL(wd));
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	__raw_writel(0, I2S_CFG(wd));
36662306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
36762306a36Sopenharmony_ci	__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
36862306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	return 0;
37162306a36Sopenharmony_ci}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_cistatic int au1xpsc_i2s_drvresume(struct device *dev)
37462306a36Sopenharmony_ci{
37562306a36Sopenharmony_ci	struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	/* select I2S mode and PSC clock */
37862306a36Sopenharmony_ci	__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));
37962306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
38062306a36Sopenharmony_ci	__raw_writel(0, PSC_SEL(wd));
38162306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
38262306a36Sopenharmony_ci	__raw_writel(wd->pm[0], PSC_SEL(wd));
38362306a36Sopenharmony_ci	wmb(); /* drain writebuffer */
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	return 0;
38662306a36Sopenharmony_ci}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_cistatic const struct dev_pm_ops au1xpsci2s_pmops = {
38962306a36Sopenharmony_ci	.suspend	= au1xpsc_i2s_drvsuspend,
39062306a36Sopenharmony_ci	.resume		= au1xpsc_i2s_drvresume,
39162306a36Sopenharmony_ci};
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci#define AU1XPSCI2S_PMOPS &au1xpsci2s_pmops
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci#else
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci#define AU1XPSCI2S_PMOPS NULL
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci#endif
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_cistatic struct platform_driver au1xpsc_i2s_driver = {
40262306a36Sopenharmony_ci	.driver		= {
40362306a36Sopenharmony_ci		.name	= "au1xpsc_i2s",
40462306a36Sopenharmony_ci		.pm	= AU1XPSCI2S_PMOPS,
40562306a36Sopenharmony_ci	},
40662306a36Sopenharmony_ci	.probe		= au1xpsc_i2s_drvprobe,
40762306a36Sopenharmony_ci	.remove_new	= au1xpsc_i2s_drvremove,
40862306a36Sopenharmony_ci};
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_cimodule_platform_driver(au1xpsc_i2s_driver);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
41362306a36Sopenharmony_ciMODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver");
41462306a36Sopenharmony_ciMODULE_AUTHOR("Manuel Lauss");
415