/* * ALSA SoC McASP Audio Layer for TI OMAP processor * * Multi-channel Audio Serial Port Driver * * Author: Jon Hunter , * Dan Milea , * * Based upon McASP driver written for TI DaVinci * * Copyright: (C) 2011 Texas Instruments * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "omap-pcm.h" #include "omap-mcasp.h" /* * McASP register definitions */ #define OMAP_MCASP_PID_REG 0x00 #define OMAP_MCASP_SYSCONFIG_REG 0x04 #define OMAP_MCASP_PFUNC_REG 0x10 #define OMAP_MCASP_PDIR_REG 0x14 #define OMAP_MCASP_PDOUT_REG 0x18 #define OMAP_MCASP_PDIN_REG 0x1c #define OMAP_MCASP_PDSET_REG 0x1c #define OMAP_MCASP_PDCLR_REG 0x20 #define OMAP_MCASP_GBLCTL_REG 0x44 #define OMAP_MCASP_AMUTE_REG 0x48 #define OMAP_MCASP_TXDITCTL_REG 0x50 #define OMAP_MCASP_TXMASK_REG 0xa4 #define OMAP_MCASP_TXFMT_REG 0xa8 #define OMAP_MCASP_TXFMCTL_REG 0xac #define OMAP_MCASP_ACLKXCTL_REG 0xb0 #define OMAP_MCASP_AHCLKXCTL_REG 0xb4 #define OMAP_MCASP_TXTDM_REG 0xb8 #define OMAP_MCASP_EVTCTLX_REG 0xbc #define OMAP_MCASP_TXSTAT_REG 0xc0 #define OMAP_MCASP_TXSTAT_MASK 0x1ff #define OMAP_MCASP_TXTDMSLOT_REG 0xc4 #define OMAP_MCASP_TXCLKCHK_REG 0xc8 #define OMAP_MCASP_TXEVTCTL_REG 0xcc /* Left(even TDM Slot) Channel Status Register File */ #define OMAP_MCASP_DITCSRA_REG 0x100 /* Right(odd TDM slot) Channel Status Register File */ #define OMAP_MCASP_DITCSRB_REG 0x118 /* Left(even TDM slot) User Data Register File */ #define OMAP_MCASP_DITUDRA_REG 0x130 /* Right(odd TDM Slot) User Data Register File */ #define OMAP_MCASP_DITUDRB_REG 0x148 /* Serializer n Control Register */ #define OMAP_MCASP_XRSRCTL0_REG 0x180 /* Transmit Buffer for Serializer */ #define OMAP_MCASP_TXBUF0_REG 0x200 /* * OMAP_MCASP_PFUNC_REG - Pin Function / GPIO Enable Register Bits */ #define AXR0 BIT(0) #define PFUNC_AMUTE BIT(25) #define ACLKX BIT(26) #define AHCLKX BIT(27) #define AFSX BIT(28) /* * OMAP_MCASP_PDIR_REG - Pin Direction Register Bits */ #define AXR0 BIT(0) #define PDIR_AMUTE BIT(25) #define ACLKX BIT(26) #define AHCLKX BIT(27) #define AFSX BIT(28) /* * OMAP_MCASP_TXDITCTL_REG - Transmit DIT Control Register Bits */ #define DITEN BIT(0) /* Transmit DIT mode enable/disable */ #define VA BIT(2) #define VB BIT(3) /* * OMAP_MCASP_TXFMT_REG - Transmit Bitstream Format Register Bits */ #define TXROT(val) (val) #define TXROT_MASK TXROT(0x7) #define TXSEL BIT(3) #define TXSSZ(val) (val<<4) #define TXSSZ_MASK TXSSZ(0xf<<4) #define TXPAD(val) (val<<13) #define TXORD BIT(15) #define FSXDLY(val) (val<<16) #define ROTATE_24 0x6 #define SLOTSIZE_32 0xf /* * OMAP_MCASP_TXFMCTL_REG - Transmit Frame Control Register Bits */ #define FSXPOL BIT(0) #define AFSXE BIT(1) #define FSXDUR BIT(4) #define FSXMOD(val) (val<<7) /* * OMAP_MCASP_ACLKXCTL_REG - Transmit Clock Control Register Bits */ #define ACLKXDIV(val) (val) #define ACLKXE BIT(5) #define TX_ASYNC BIT(6) /* * OMAP_MCASP_AHCLKXCTL_REG - High Frequency Transmit Clock Control * Register Bits */ #define AHCLKXDIV(val) (val) #define AHCLKXE BIT(15) /* * OMAP_MCASP_EVTCTLX_REG - Transmitter Interrupt Control Register bits */ #define EVTCTLX_XUNDRN BIT(0) /* * OMAP_MCASP_TXSTAT_REG - Transmit Status Register Bits */ #define TXSTAT_XUNDRN (0x1 << 0) #define TXSTAT_XSYNCERR (0x1 << 1) #define TXSTAT_XCKFAIL (0x1 << 2) #define TXSTAT_XDMSLOT (0x1 << 3) #define TXSTAT_XLAST (0x1 << 4) #define TXSTAT_XDATA (0x1 << 5) #define TXSTAT_XSTAFRM (0x1 << 6) #define TXSTAT_XDMAERR (0x1 << 7) #define TXSTAT_XERR (0x1 << 8) /* * OMAP_MCASP_XRSRCTL_BASE_REG - Serializer Control Register Bits */ #define MODE(val) (val) #define TXSTATE BIT(4) /* * OMAP_MCASP_TXTDMSLOT_REG - Transmit TDM Slot Register configuration */ #define TXTDMS(n) (1<> 7) / divisor; /* ppm calculation in two steps to avoid overflow */ ppm = abs(tgt_sample_rate - sample_rate); ppm = (1000000 * ppm) / tgt_sample_rate; if (ppm > MCASP_ALLOWED_PPM) return -EINVAL; /* At this point, divisor holds the product of the two divider values we * need to use for ACLKXCTL and AHCLKXCTL. ACLKXCTL holds a 5 bit * divider [1, 32], while AHCLKXCTL holds a 12 bit divider [1, 4096]. * We need to make sure that we can factor divisor into two integers * which will fit into these divider registers. Find the largest 5-bit * + 1 value which divides divisor and use that as our smaller divider. * After removing this factor from divisor, if the result is <= 4096, * then we have succeeded and will be able to produce the target sample * rate. */ for (i = 32; (i > 1) && (divisor % i); --i) ; /* no body */ /* Make sure to subtract one, registers hold the value of the divider * minus one (IOW, to divide by 5, the register gets programmed with the * value 4. */ *out_div_lo = i - 1; *out_div_hi = (divisor / i) - 1; return (*out_div_hi <= 4096) ? 0 : -EINVAL; } static int omap_mcasp_start(struct omap_mcasp *mcasp) { int i; mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXHCLKRST); mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXCLKRST); mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXSERCLR); /* Wait until the DMA has loaded the first sample into TXBUF before we * let the TX state machine and frame sync generator out of reset. */ i = 0; while (1) { u32 reg = mcasp_get_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG); if (!(reg & TXSTAT_XDATA)) break; if (++i > 1000) { printk(KERN_ERR "Timeout waiting for DMA to load first" " sample of audio.\n"); return -ETIMEDOUT; } udelay(1); } mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXSMRST); mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXFSRST); mcasp_clr_bits(mcasp->base + OMAP_MCASP_TXEVTCTL_REG, TXDATADMADIS); /* enable IRQ sources */ mcasp_set_bits(mcasp->base + OMAP_MCASP_EVTCTLX_REG, EVTCTLX_XUNDRN); return 0; } static void omap_mcasp_stop(struct omap_mcasp *mcasp) { /* disable IRQ sources */ mcasp_set_reg(mcasp->base + OMAP_MCASP_EVTCTLX_REG, 0); mcasp_set_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, 0); mcasp_set_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG, OMAP_MCASP_TXSTAT_MASK); } /* S/PDIF */ static int omap_mcasp_setup(struct omap_mcasp *mcasp, unsigned int rate) { u32 aclkxdiv, ahclkxdiv, ditcsr; int res; /* Set TX frame synch : DIT Mode, 1 bit width, internal, rising edge */ mcasp_set_reg(mcasp->base + OMAP_MCASP_TXFMCTL_REG, AFSXE | FSXMOD(0x180)); /* Set the TX clock controls : div = 1 and internal */ mcasp_set_reg(mcasp->base + OMAP_MCASP_ACLKXCTL_REG, ACLKXE | TX_ASYNC); /* Set the HS TX clock controls : div = 1 and internal */ mcasp_set_reg(mcasp->base + OMAP_MCASP_AHCLKXCTL_REG, AHCLKXE); /* The SPDIF bit clock is derived from the McASP functional clock. * The McASP has two programmable clock dividers (aclkxdiv and * ahclkxdiv) that are configured via the registers MCASP_ACLKXCTL * and MCASP_AHCLKXCTL. For SPDIF the bit clock frequency should be * 128 * sample rate freq. The dividers are defined as part of * platform data as they are dependent upon the functional clock * setting. Lookup the appropriate dividers for the sampling * frequency that we are playing. */ res = mcasp_compute_clock_dividers(clk_get_rate(mcasp->fclk), rate, &aclkxdiv, &ahclkxdiv); if (res) { dev_err(mcasp->dev, "%s: No valid McASP config for sampling rate (%d)!\n", __func__, rate); return res; } switch (rate) { case 22050: ditcsr = OMAP_MCASP_DITCSR_22050HZ; break; case 24000: ditcsr = OMAP_MCASP_DITCSR_24000HZ; break; case 32000: ditcsr = OMAP_MCASP_DITCSR_32000HZ; break; case 44100: ditcsr = OMAP_MCASP_DITCSR_44100HZ; break; case 48000: ditcsr = OMAP_MCASP_DITCSR_48000HZ; break; case 88200: ditcsr = OMAP_MCASP_DITCSR_88200HZ; break; case 96000: ditcsr = OMAP_MCASP_DITCSR_96000HZ; break; case 176400: ditcsr = OMAP_MCASP_DITCSR_176400HZ; break; case 192000: ditcsr = OMAP_MCASP_DITCSR_192000HZ; break; default: dev_err(mcasp->dev, "%s: Invalid sampling rate: %d\n", __func__, rate); return -EINVAL; } mcasp_set_reg(mcasp->base + OMAP_MCASP_DITCSRA_REG, ditcsr); mcasp_set_reg(mcasp->base + OMAP_MCASP_DITCSRB_REG, ditcsr); mcasp_set_bits(mcasp->base + OMAP_MCASP_AHCLKXCTL_REG, AHCLKXDIV(ahclkxdiv)); mcasp_set_bits(mcasp->base + OMAP_MCASP_ACLKXCTL_REG, AHCLKXDIV(aclkxdiv)); /* Configure McASP formatter */ mcasp_mod_bits(mcasp->base + OMAP_MCASP_TXFMT_REG, TXSSZ(SLOTSIZE_32), TXSSZ_MASK); mcasp_mod_bits(mcasp->base + OMAP_MCASP_TXFMT_REG, TXROT(ROTATE_24), TXROT_MASK); mcasp_set_reg(mcasp->base + OMAP_MCASP_TXMASK_REG, 0xFFFF); /* Set the TX tdm : for all the slots */ mcasp_set_reg(mcasp->base + OMAP_MCASP_TXTDM_REG, 0xFFFFFFFF); /* configure the serializer for transmit mode operation */ mcasp_set_bits(mcasp->base + OMAP_MCASP_XRSRCTL0_REG, MODE(1)); /* All PINS as McASP */ mcasp_set_reg(mcasp->base + OMAP_MCASP_PFUNC_REG, 0); mcasp_set_bits(mcasp->base + OMAP_MCASP_PDIR_REG, AXR0); /* Enable the DIT */ mcasp_set_bits(mcasp->base + OMAP_MCASP_TXDITCTL_REG, DITEN); mcasp_set_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG, 0xFF); return 0; } static irqreturn_t omap_mcasp_irq_handler(int irq, void *data) { struct omap_mcasp *mcasp = data; u32 txstat; txstat = mcasp_get_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG); if (txstat & TXSTAT_XUNDRN) { dev_err(mcasp->dev, "%s: Underrun (0x%08x)\n", __func__, txstat); /* Try to recover from this state */ spin_lock(&mcasp->lock); if (likely(mcasp->stream_rate)) { dev_err(mcasp->dev, "%s: Trying to recover\n", __func__); omap_mcasp_stop(mcasp); omap_mcasp_setup(mcasp, mcasp->stream_rate); omap_mcasp_start(mcasp); } spin_unlock(&mcasp->lock); } mcasp_set_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG, txstat); return IRQ_HANDLED; } static int omap_mcasp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); /* HACK: Only allow C2 state */ pm_qos_add_request(mcasp->pm_qos, PM_QOS_CPU_DMA_LATENCY, 1150); pm_runtime_get_sync(mcasp->dev); return 0; } static void omap_mcasp_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); pm_runtime_put_sync(mcasp->dev); /* HACK: remove qos */ pm_qos_remove_request(mcasp->pm_qos); } static int omap_mcasp_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(dai); int stream = substream->stream; omap_mcasp_stop(mcasp); if (omap_mcasp_setup(mcasp, params_rate(params)) < 0) return -EPERM; snd_soc_dai_set_dma_data(dai, substream, &omap_mcasp_dai_dma_params[stream]); return 0; } static int omap_mcasp_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *cpu_dai) { struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai); unsigned long flags; int ret = 0; spin_lock_irqsave(&mcasp->lock, flags); switch (cmd) { case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: mcasp->stream_rate = substream->runtime->rate; ret = omap_mcasp_start(mcasp); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: mcasp->stream_rate = 0; omap_mcasp_stop(mcasp); break; default: ret = -EINVAL; } spin_unlock_irqrestore(&mcasp->lock, flags); return ret; } static struct snd_soc_dai_ops omap_mcasp_dai_ops = { .startup = omap_mcasp_startup, .shutdown = omap_mcasp_shutdown, .trigger = omap_mcasp_trigger, .hw_params = omap_mcasp_hw_params, }; #define MCASP_RATES (SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \ SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) static struct snd_soc_dai_driver omap_mcasp_dai[] = { { .name = "omap-mcasp-dai", .playback = { .channels_min = 1, .channels_max = 384, .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = MCASP_RATES, }, .ops = &omap_mcasp_dai_ops, }, }; static __devinit int omap_mcasp_probe(struct platform_device *pdev) { struct omap_mcasp *mcasp; struct resource *res; long fclk_rate; int ret = 0; mcasp = kzalloc(sizeof(struct omap_mcasp), GFP_KERNEL); if (!mcasp) return -ENOMEM; spin_lock_init(&mcasp->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "no resource\n"); ret = -ENODEV; goto err_res; } mcasp->base = ioremap(res->start, resource_size(res)); if (!mcasp->base) { ret = -ENOMEM; goto err_res; } mcasp->irq = platform_get_irq(pdev, 0); if (mcasp->irq < 0) { ret = mcasp->irq; goto err_irq; } ret = request_threaded_irq(mcasp->irq, NULL, omap_mcasp_irq_handler, 0, "McASP", mcasp); if (ret) { dev_err(mcasp->dev, "IRQ request failed\n"); goto err_irq; } mcasp->fclk = clk_get(&pdev->dev, "mcasp_fck"); if (!mcasp->fclk) { ret = -ENODEV; goto err_clk; } pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); fclk_rate = clk_get_rate(mcasp->fclk); platform_set_drvdata(pdev, mcasp); mcasp->dev = &pdev->dev; ret = snd_soc_register_dai(&pdev->dev, omap_mcasp_dai); if (ret < 0) goto err_dai; /* HACK: qos */ mcasp->pm_qos = kzalloc(sizeof(struct pm_qos_request_list), GFP_KERNEL); if (!mcasp->pm_qos) { ret = -ENOMEM; goto err_dai; } pm_runtime_put_sync(&pdev->dev); return 0; err_dai: pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); err_clk: free_irq(mcasp->irq, (void *)mcasp); err_irq: iounmap(mcasp->base); err_res: kfree(mcasp); return ret; } static __devexit int omap_mcasp_remove(struct platform_device *pdev) { struct omap_mcasp *mcasp = dev_get_drvdata(&pdev->dev); snd_soc_unregister_dai(&pdev->dev); pm_runtime_disable(&pdev->dev); clk_put(mcasp->fclk); free_irq(mcasp->irq, (void *)mcasp); iounmap(mcasp->base); /* HACK: qos */ kfree(mcasp->pm_qos); kfree(mcasp); return 0; } static struct platform_driver omap_mcasp_driver = { .probe = omap_mcasp_probe, .remove = omap_mcasp_remove, .driver = { .name = "omap-mcasp-dai", .owner = THIS_MODULE, }, }; static int __init omap_mcasp_init(void) { return platform_driver_register(&omap_mcasp_driver); } module_init(omap_mcasp_init); static void __exit omap_mcasp_exit(void) { platform_driver_unregister(&omap_mcasp_driver); } module_exit(omap_mcasp_exit); MODULE_AUTHOR("Jon Hunter "); MODULE_DESCRIPTION("TI OMAP McASP SoC Interface"); MODULE_LICENSE("GPL");