brintos

brintos / linux-shallow public Read only

0
0
Text · 10.6 KiB · bee0135 Raw
415 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Au12x0/Au1550 PSC ALSA ASoC audio support.4 *5 * (c) 2007-2008 MSC Vertriebsges.m.b.H.,6 *	Manuel Lauss <manuel.lauss@gmail.com>7 *8 * Au1xxx-PSC I2S glue.9 *10 * NOTE: so far only PSC slave mode (bit- and frameclock) is supported.11 */12 13#include <linux/init.h>14#include <linux/module.h>15#include <linux/slab.h>16#include <linux/suspend.h>17#include <sound/core.h>18#include <sound/pcm.h>19#include <sound/initval.h>20#include <sound/soc.h>21#include <asm/mach-au1x00/au1000.h>22#include <asm/mach-au1x00/au1xxx_psc.h>23 24#include "psc.h"25 26/* supported I2S DAI hardware formats */27#define AU1XPSC_I2S_DAIFMT \28	(SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J |	\29	 SND_SOC_DAIFMT_NB_NF)30 31/* supported I2S direction */32#define AU1XPSC_I2S_DIR \33	(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)34 35#define AU1XPSC_I2S_RATES \36	SNDRV_PCM_RATE_8000_19200037 38#define AU1XPSC_I2S_FMTS \39	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)40 41#define I2SSTAT_BUSY(stype)	\42	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB)43#define I2SPCR_START(stype)	\44	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TS : PSC_I2SPCR_RS)45#define I2SPCR_STOP(stype)	\46	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TP : PSC_I2SPCR_RP)47#define I2SPCR_CLRFIFO(stype)	\48	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TC : PSC_I2SPCR_RC)49 50 51static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai,52			       unsigned int fmt)53{54	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(cpu_dai);55	unsigned long ct;56	int ret;57 58	ret = -EINVAL;59 60	ct = pscdata->cfg;61 62	ct &= ~(PSC_I2SCFG_XM | PSC_I2SCFG_MLJ);	/* left-justified */63	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {64	case SND_SOC_DAIFMT_I2S:65		ct |= PSC_I2SCFG_XM;	/* enable I2S mode */66		break;67	case SND_SOC_DAIFMT_MSB:68		break;69	case SND_SOC_DAIFMT_LSB:70		ct |= PSC_I2SCFG_MLJ;	/* LSB (right-) justified */71		break;72	default:73		goto out;74	}75 76	ct &= ~(PSC_I2SCFG_BI | PSC_I2SCFG_WI);		/* IB-IF */77	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {78	case SND_SOC_DAIFMT_NB_NF:79		ct |= PSC_I2SCFG_BI | PSC_I2SCFG_WI;80		break;81	case SND_SOC_DAIFMT_NB_IF:82		ct |= PSC_I2SCFG_BI;83		break;84	case SND_SOC_DAIFMT_IB_NF:85		ct |= PSC_I2SCFG_WI;86		break;87	case SND_SOC_DAIFMT_IB_IF:88		break;89	default:90		goto out;91	}92 93	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {94	case SND_SOC_DAIFMT_BC_FC:	/* CODEC provider */95		ct |= PSC_I2SCFG_MS;	/* PSC I2S consumer mode */96		break;97	case SND_SOC_DAIFMT_BP_FP:	/* CODEC consumer */98		ct &= ~PSC_I2SCFG_MS;	/* PSC I2S provider mode */99		break;100	default:101		goto out;102	}103 104	pscdata->cfg = ct;105	ret = 0;106out:107	return ret;108}109 110static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream,111				 struct snd_pcm_hw_params *params,112				 struct snd_soc_dai *dai)113{114	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);115 116	int cfgbits;117	unsigned long stat;118 119	/* check if the PSC is already streaming data */120	stat = __raw_readl(I2S_STAT(pscdata));121	if (stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB)) {122		/* reject parameters not currently set up in hardware */123		cfgbits = __raw_readl(I2S_CFG(pscdata));124		if ((PSC_I2SCFG_GET_LEN(cfgbits) != params->msbits) ||125		    (params_rate(params) != pscdata->rate))126			return -EINVAL;127	} else {128		/* set sample bitdepth */129		pscdata->cfg &= ~(0x1f << 4);130		pscdata->cfg |= PSC_I2SCFG_SET_LEN(params->msbits);131		/* remember current rate for other stream */132		pscdata->rate = params_rate(params);133	}134	return 0;135}136 137/* Configure PSC late:  on my devel systems the codec  is I2S master and138 * supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit.  ASoC139 * uses aggressive PM and  switches the codec off  when it is not in use140 * which also means the PSC unit doesn't get any clocks and is therefore141 * dead. That's why this chunk here gets called from the trigger callback142 * because I can be reasonably certain the codec is driving the clocks.143 */144static int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata)145{146	unsigned long tmo;147 148	/* bring PSC out of sleep, and configure I2S unit */149	__raw_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));150	wmb(); /* drain writebuffer */151 152	tmo = 1000000;153	while (!(__raw_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_SR) && tmo)154		tmo--;155 156	if (!tmo)157		goto psc_err;158 159	__raw_writel(0, I2S_CFG(pscdata));160	wmb(); /* drain writebuffer */161	__raw_writel(pscdata->cfg | PSC_I2SCFG_DE_ENABLE, I2S_CFG(pscdata));162	wmb(); /* drain writebuffer */163 164	/* wait for I2S controller to become ready */165	tmo = 1000000;166	while (!(__raw_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_DR) && tmo)167		tmo--;168 169	if (tmo)170		return 0;171 172psc_err:173	__raw_writel(0, I2S_CFG(pscdata));174	__raw_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));175	wmb(); /* drain writebuffer */176	return -ETIMEDOUT;177}178 179static int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int stype)180{181	unsigned long tmo, stat;182	int ret;183 184	ret = 0;185 186	/* if both TX and RX are idle, configure the PSC  */187	stat = __raw_readl(I2S_STAT(pscdata));188	if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {189		ret = au1xpsc_i2s_configure(pscdata);190		if (ret)191			goto out;192	}193 194	__raw_writel(I2SPCR_CLRFIFO(stype), I2S_PCR(pscdata));195	wmb(); /* drain writebuffer */196	__raw_writel(I2SPCR_START(stype), I2S_PCR(pscdata));197	wmb(); /* drain writebuffer */198 199	/* wait for start confirmation */200	tmo = 1000000;201	while (!(__raw_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)202		tmo--;203 204	if (!tmo) {205		__raw_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));206		wmb(); /* drain writebuffer */207		ret = -ETIMEDOUT;208	}209out:210	return ret;211}212 213static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype)214{215	unsigned long tmo, stat;216 217	__raw_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata));218	wmb(); /* drain writebuffer */219 220	/* wait for stop confirmation */221	tmo = 1000000;222	while ((__raw_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo)223		tmo--;224 225	/* if both TX and RX are idle, disable PSC */226	stat = __raw_readl(I2S_STAT(pscdata));227	if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) {228		__raw_writel(0, I2S_CFG(pscdata));229		wmb(); /* drain writebuffer */230		__raw_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));231		wmb(); /* drain writebuffer */232	}233	return 0;234}235 236static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,237			       struct snd_soc_dai *dai)238{239	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);240	int ret, stype = substream->stream;241 242	switch (cmd) {243	case SNDRV_PCM_TRIGGER_START:244	case SNDRV_PCM_TRIGGER_RESUME:245		ret = au1xpsc_i2s_start(pscdata, stype);246		break;247	case SNDRV_PCM_TRIGGER_STOP:248	case SNDRV_PCM_TRIGGER_SUSPEND:249		ret = au1xpsc_i2s_stop(pscdata, stype);250		break;251	default:252		ret = -EINVAL;253	}254	return ret;255}256 257static int au1xpsc_i2s_startup(struct snd_pcm_substream *substream,258			       struct snd_soc_dai *dai)259{260	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);261	snd_soc_dai_set_dma_data(dai, substream, &pscdata->dmaids[0]);262	return 0;263}264 265static const struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = {266	.startup	= au1xpsc_i2s_startup,267	.trigger	= au1xpsc_i2s_trigger,268	.hw_params	= au1xpsc_i2s_hw_params,269	.set_fmt	= au1xpsc_i2s_set_fmt,270};271 272static const struct snd_soc_dai_driver au1xpsc_i2s_dai_template = {273	.playback = {274		.rates		= AU1XPSC_I2S_RATES,275		.formats	= AU1XPSC_I2S_FMTS,276		.channels_min	= 2,277		.channels_max	= 8,	/* 2 without external help */278	},279	.capture = {280		.rates		= AU1XPSC_I2S_RATES,281		.formats	= AU1XPSC_I2S_FMTS,282		.channels_min	= 2,283		.channels_max	= 8,	/* 2 without external help */284	},285	.ops = &au1xpsc_i2s_dai_ops,286};287 288static const struct snd_soc_component_driver au1xpsc_i2s_component = {289	.name			= "au1xpsc-i2s",290	.legacy_dai_naming	= 1,291};292 293static int au1xpsc_i2s_drvprobe(struct platform_device *pdev)294{295	struct resource *dmares;296	unsigned long sel;297	struct au1xpsc_audio_data *wd;298 299	wd = devm_kzalloc(&pdev->dev, sizeof(struct au1xpsc_audio_data),300			  GFP_KERNEL);301	if (!wd)302		return -ENOMEM;303 304	wd->mmio = devm_platform_ioremap_resource(pdev, 0);305	if (IS_ERR(wd->mmio))306		return PTR_ERR(wd->mmio);307 308	dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);309	if (!dmares)310		return -EBUSY;311	wd->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start;312 313	dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1);314	if (!dmares)315		return -EBUSY;316	wd->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start;317 318	/* preserve PSC clock source set up by platform (dev.platform_data319	 * is already occupied by soc layer)320	 */321	sel = __raw_readl(PSC_SEL(wd)) & PSC_SEL_CLK_MASK;322	__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));323	wmb(); /* drain writebuffer */324	__raw_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(wd));325	__raw_writel(0, I2S_CFG(wd));326	wmb(); /* drain writebuffer */327 328	/* preconfigure: set max rx/tx fifo depths */329	wd->cfg |= PSC_I2SCFG_RT_FIFO8 | PSC_I2SCFG_TT_FIFO8;330 331	/* don't wait for I2S core to become ready now; clocks may not332	 * be running yet; depending on clock input for PSC a wait might333	 * time out.334	 */335 336	/* name the DAI like this device instance ("au1xpsc-i2s.PSCINDEX") */337	memcpy(&wd->dai_drv, &au1xpsc_i2s_dai_template,338	       sizeof(struct snd_soc_dai_driver));339	wd->dai_drv.name = dev_name(&pdev->dev);340 341	platform_set_drvdata(pdev, wd);342 343	return devm_snd_soc_register_component(&pdev->dev,344				&au1xpsc_i2s_component, &wd->dai_drv, 1);345}346 347static void au1xpsc_i2s_drvremove(struct platform_device *pdev)348{349	struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);350 351	__raw_writel(0, I2S_CFG(wd));352	wmb(); /* drain writebuffer */353	__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));354	wmb(); /* drain writebuffer */355}356 357#ifdef CONFIG_PM358static int au1xpsc_i2s_drvsuspend(struct device *dev)359{360	struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);361 362	/* save interesting register and disable PSC */363	wd->pm[0] = __raw_readl(PSC_SEL(wd));364 365	__raw_writel(0, I2S_CFG(wd));366	wmb(); /* drain writebuffer */367	__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));368	wmb(); /* drain writebuffer */369 370	return 0;371}372 373static int au1xpsc_i2s_drvresume(struct device *dev)374{375	struct au1xpsc_audio_data *wd = dev_get_drvdata(dev);376 377	/* select I2S mode and PSC clock */378	__raw_writel(PSC_CTRL_DISABLE, PSC_CTRL(wd));379	wmb(); /* drain writebuffer */380	__raw_writel(0, PSC_SEL(wd));381	wmb(); /* drain writebuffer */382	__raw_writel(wd->pm[0], PSC_SEL(wd));383	wmb(); /* drain writebuffer */384 385	return 0;386}387 388static const struct dev_pm_ops au1xpsci2s_pmops = {389	.suspend	= au1xpsc_i2s_drvsuspend,390	.resume		= au1xpsc_i2s_drvresume,391};392 393#define AU1XPSCI2S_PMOPS &au1xpsci2s_pmops394 395#else396 397#define AU1XPSCI2S_PMOPS NULL398 399#endif400 401static struct platform_driver au1xpsc_i2s_driver = {402	.driver		= {403		.name	= "au1xpsc_i2s",404		.pm	= AU1XPSCI2S_PMOPS,405	},406	.probe		= au1xpsc_i2s_drvprobe,407	.remove		= au1xpsc_i2s_drvremove,408};409 410module_platform_driver(au1xpsc_i2s_driver);411 412MODULE_LICENSE("GPL");413MODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver");414MODULE_AUTHOR("Manuel Lauss");415