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