162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// ALSA SoC Audio Layer - S3C PCM-Controller driver 462306a36Sopenharmony_ci// 562306a36Sopenharmony_ci// Copyright (c) 2009 Samsung Electronics Co. Ltd 662306a36Sopenharmony_ci// Author: Jaswinder Singh <jassisinghbrar@gmail.com> 762306a36Sopenharmony_ci// based upon I2S drivers by Ben Dooks. 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/clk.h> 1062306a36Sopenharmony_ci#include <linux/io.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <sound/soc.h> 1562306a36Sopenharmony_ci#include <sound/pcm_params.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <linux/platform_data/asoc-s3c.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include "dma.h" 2062306a36Sopenharmony_ci#include "pcm.h" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/*Register Offsets */ 2362306a36Sopenharmony_ci#define S3C_PCM_CTL 0x00 2462306a36Sopenharmony_ci#define S3C_PCM_CLKCTL 0x04 2562306a36Sopenharmony_ci#define S3C_PCM_TXFIFO 0x08 2662306a36Sopenharmony_ci#define S3C_PCM_RXFIFO 0x0C 2762306a36Sopenharmony_ci#define S3C_PCM_IRQCTL 0x10 2862306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT 0x14 2962306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT 0x18 3062306a36Sopenharmony_ci#define S3C_PCM_CLRINT 0x20 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* PCM_CTL Bit-Fields */ 3362306a36Sopenharmony_ci#define S3C_PCM_CTL_TXDIPSTICK_MASK 0x3f 3462306a36Sopenharmony_ci#define S3C_PCM_CTL_TXDIPSTICK_SHIFT 13 3562306a36Sopenharmony_ci#define S3C_PCM_CTL_RXDIPSTICK_MASK 0x3f 3662306a36Sopenharmony_ci#define S3C_PCM_CTL_RXDIPSTICK_SHIFT 7 3762306a36Sopenharmony_ci#define S3C_PCM_CTL_TXDMA_EN (0x1 << 6) 3862306a36Sopenharmony_ci#define S3C_PCM_CTL_RXDMA_EN (0x1 << 5) 3962306a36Sopenharmony_ci#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1 << 4) 4062306a36Sopenharmony_ci#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1 << 3) 4162306a36Sopenharmony_ci#define S3C_PCM_CTL_TXFIFO_EN (0x1 << 2) 4262306a36Sopenharmony_ci#define S3C_PCM_CTL_RXFIFO_EN (0x1 << 1) 4362306a36Sopenharmony_ci#define S3C_PCM_CTL_ENABLE (0x1 << 0) 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci/* PCM_CLKCTL Bit-Fields */ 4662306a36Sopenharmony_ci#define S3C_PCM_CLKCTL_SERCLK_EN (0x1 << 19) 4762306a36Sopenharmony_ci#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1 << 18) 4862306a36Sopenharmony_ci#define S3C_PCM_CLKCTL_SCLKDIV_MASK 0x1ff 4962306a36Sopenharmony_ci#define S3C_PCM_CLKCTL_SYNCDIV_MASK 0x1ff 5062306a36Sopenharmony_ci#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT 9 5162306a36Sopenharmony_ci#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT 0 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci/* PCM_TXFIFO Bit-Fields */ 5462306a36Sopenharmony_ci#define S3C_PCM_TXFIFO_DVALID (0x1 << 16) 5562306a36Sopenharmony_ci#define S3C_PCM_TXFIFO_DATA_MSK (0xffff << 0) 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci/* PCM_RXFIFO Bit-Fields */ 5862306a36Sopenharmony_ci#define S3C_PCM_RXFIFO_DVALID (0x1 << 16) 5962306a36Sopenharmony_ci#define S3C_PCM_RXFIFO_DATA_MSK (0xffff << 0) 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci/* PCM_IRQCTL Bit-Fields */ 6262306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_IRQEN (0x1 << 14) 6362306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_WRDEN (0x1 << 12) 6462306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1 << 11) 6562306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1 << 10) 6662306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_TXFULLEN (0x1 << 9) 6762306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1 << 8) 6862306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_TXSTARVEN (0x1 << 7) 6962306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1 << 6) 7062306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_RXEMPTEN (0x1 << 5) 7162306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1 << 4) 7262306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_RXFULLEN (0x1 << 3) 7362306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1 << 2) 7462306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_RXSTARVEN (0x1 << 1) 7562306a36Sopenharmony_ci#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1 << 0) 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci/* PCM_IRQSTAT Bit-Fields */ 7862306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_IRQPND (0x1 << 13) 7962306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_WRD_XFER (0x1 << 12) 8062306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_TXEMPTY (0x1 << 11) 8162306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1 << 10) 8262306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_TXFULL (0x1 << 9) 8362306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1 << 8) 8462306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_TXSTARV (0x1 << 7) 8562306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1 << 6) 8662306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_RXEMPT (0x1 << 5) 8762306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1 << 4) 8862306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_RXFULL (0x1 << 3) 8962306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1 << 2) 9062306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_RXSTARV (0x1 << 1) 9162306a36Sopenharmony_ci#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1 << 0) 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/* PCM_FIFOSTAT Bit-Fields */ 9462306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f << 14) 9562306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1 << 13) 9662306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1 << 12) 9762306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1 << 11) 9862306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1 << 10) 9962306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f << 4) 10062306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1 << 3) 10162306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1 << 2) 10262306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1 << 1) 10362306a36Sopenharmony_ci#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1 << 0) 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci/** 10662306a36Sopenharmony_ci * struct s3c_pcm_info - S3C PCM Controller information 10762306a36Sopenharmony_ci * @lock: Spin lock 10862306a36Sopenharmony_ci * @dev: The parent device passed to use from the probe. 10962306a36Sopenharmony_ci * @regs: The pointer to the device register block. 11062306a36Sopenharmony_ci * @sclk_per_fs: number of sclk per frame sync 11162306a36Sopenharmony_ci * @idleclk: Whether to keep PCMSCLK enabled even when idle (no active xfer) 11262306a36Sopenharmony_ci * @pclk: the PCLK_PCM (pcm) clock pointer 11362306a36Sopenharmony_ci * @cclk: the SCLK_AUDIO (audio-bus) clock pointer 11462306a36Sopenharmony_ci * @dma_playback: DMA information for playback channel. 11562306a36Sopenharmony_ci * @dma_capture: DMA information for capture channel. 11662306a36Sopenharmony_ci */ 11762306a36Sopenharmony_cistruct s3c_pcm_info { 11862306a36Sopenharmony_ci spinlock_t lock; 11962306a36Sopenharmony_ci struct device *dev; 12062306a36Sopenharmony_ci void __iomem *regs; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci unsigned int sclk_per_fs; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */ 12562306a36Sopenharmony_ci unsigned int idleclk; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci struct clk *pclk; 12862306a36Sopenharmony_ci struct clk *cclk; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci struct snd_dmaengine_dai_dma_data *dma_playback; 13162306a36Sopenharmony_ci struct snd_dmaengine_dai_dma_data *dma_capture; 13262306a36Sopenharmony_ci}; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic struct snd_dmaengine_dai_dma_data s3c_pcm_stereo_out[] = { 13562306a36Sopenharmony_ci [0] = { 13662306a36Sopenharmony_ci .addr_width = 4, 13762306a36Sopenharmony_ci }, 13862306a36Sopenharmony_ci [1] = { 13962306a36Sopenharmony_ci .addr_width = 4, 14062306a36Sopenharmony_ci }, 14162306a36Sopenharmony_ci}; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic struct snd_dmaengine_dai_dma_data s3c_pcm_stereo_in[] = { 14462306a36Sopenharmony_ci [0] = { 14562306a36Sopenharmony_ci .addr_width = 4, 14662306a36Sopenharmony_ci }, 14762306a36Sopenharmony_ci [1] = { 14862306a36Sopenharmony_ci .addr_width = 4, 14962306a36Sopenharmony_ci }, 15062306a36Sopenharmony_ci}; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic struct s3c_pcm_info s3c_pcm[2]; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci void __iomem *regs = pcm->regs; 15762306a36Sopenharmony_ci u32 ctl, clkctl; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci clkctl = readl(regs + S3C_PCM_CLKCTL); 16062306a36Sopenharmony_ci ctl = readl(regs + S3C_PCM_CTL); 16162306a36Sopenharmony_ci ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK 16262306a36Sopenharmony_ci << S3C_PCM_CTL_TXDIPSTICK_SHIFT); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci if (on) { 16562306a36Sopenharmony_ci ctl |= S3C_PCM_CTL_TXDMA_EN; 16662306a36Sopenharmony_ci ctl |= S3C_PCM_CTL_TXFIFO_EN; 16762306a36Sopenharmony_ci ctl |= S3C_PCM_CTL_ENABLE; 16862306a36Sopenharmony_ci ctl |= (0x4<<S3C_PCM_CTL_TXDIPSTICK_SHIFT); 16962306a36Sopenharmony_ci clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; 17062306a36Sopenharmony_ci } else { 17162306a36Sopenharmony_ci ctl &= ~S3C_PCM_CTL_TXDMA_EN; 17262306a36Sopenharmony_ci ctl &= ~S3C_PCM_CTL_TXFIFO_EN; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (!(ctl & S3C_PCM_CTL_RXFIFO_EN)) { 17562306a36Sopenharmony_ci ctl &= ~S3C_PCM_CTL_ENABLE; 17662306a36Sopenharmony_ci if (!pcm->idleclk) 17762306a36Sopenharmony_ci clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci writel(clkctl, regs + S3C_PCM_CLKCTL); 18262306a36Sopenharmony_ci writel(ctl, regs + S3C_PCM_CTL); 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci void __iomem *regs = pcm->regs; 18862306a36Sopenharmony_ci u32 ctl, clkctl; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci ctl = readl(regs + S3C_PCM_CTL); 19162306a36Sopenharmony_ci clkctl = readl(regs + S3C_PCM_CLKCTL); 19262306a36Sopenharmony_ci ctl &= ~(S3C_PCM_CTL_RXDIPSTICK_MASK 19362306a36Sopenharmony_ci << S3C_PCM_CTL_RXDIPSTICK_SHIFT); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (on) { 19662306a36Sopenharmony_ci ctl |= S3C_PCM_CTL_RXDMA_EN; 19762306a36Sopenharmony_ci ctl |= S3C_PCM_CTL_RXFIFO_EN; 19862306a36Sopenharmony_ci ctl |= S3C_PCM_CTL_ENABLE; 19962306a36Sopenharmony_ci ctl |= (0x20<<S3C_PCM_CTL_RXDIPSTICK_SHIFT); 20062306a36Sopenharmony_ci clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; 20162306a36Sopenharmony_ci } else { 20262306a36Sopenharmony_ci ctl &= ~S3C_PCM_CTL_RXDMA_EN; 20362306a36Sopenharmony_ci ctl &= ~S3C_PCM_CTL_RXFIFO_EN; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci if (!(ctl & S3C_PCM_CTL_TXFIFO_EN)) { 20662306a36Sopenharmony_ci ctl &= ~S3C_PCM_CTL_ENABLE; 20762306a36Sopenharmony_ci if (!pcm->idleclk) 20862306a36Sopenharmony_ci clkctl |= S3C_PCM_CLKCTL_SERCLK_EN; 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci writel(clkctl, regs + S3C_PCM_CLKCTL); 21362306a36Sopenharmony_ci writel(ctl, regs + S3C_PCM_CTL); 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_cistatic int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd, 21762306a36Sopenharmony_ci struct snd_soc_dai *dai) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 22062306a36Sopenharmony_ci struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); 22162306a36Sopenharmony_ci unsigned long flags; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci dev_dbg(pcm->dev, "Entered %s\n", __func__); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci switch (cmd) { 22662306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 22762306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_RESUME: 22862306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 22962306a36Sopenharmony_ci spin_lock_irqsave(&pcm->lock, flags); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 23262306a36Sopenharmony_ci s3c_pcm_snd_rxctrl(pcm, 1); 23362306a36Sopenharmony_ci else 23462306a36Sopenharmony_ci s3c_pcm_snd_txctrl(pcm, 1); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci spin_unlock_irqrestore(&pcm->lock, flags); 23762306a36Sopenharmony_ci break; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 24062306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_SUSPEND: 24162306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 24262306a36Sopenharmony_ci spin_lock_irqsave(&pcm->lock, flags); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 24562306a36Sopenharmony_ci s3c_pcm_snd_rxctrl(pcm, 0); 24662306a36Sopenharmony_ci else 24762306a36Sopenharmony_ci s3c_pcm_snd_txctrl(pcm, 0); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci spin_unlock_irqrestore(&pcm->lock, flags); 25062306a36Sopenharmony_ci break; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci default: 25362306a36Sopenharmony_ci return -EINVAL; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci return 0; 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic int s3c_pcm_hw_params(struct snd_pcm_substream *substream, 26062306a36Sopenharmony_ci struct snd_pcm_hw_params *params, 26162306a36Sopenharmony_ci struct snd_soc_dai *socdai) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); 26462306a36Sopenharmony_ci struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); 26562306a36Sopenharmony_ci void __iomem *regs = pcm->regs; 26662306a36Sopenharmony_ci struct clk *clk; 26762306a36Sopenharmony_ci int sclk_div, sync_div; 26862306a36Sopenharmony_ci unsigned long flags; 26962306a36Sopenharmony_ci u32 clkctl; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci dev_dbg(pcm->dev, "Entered %s\n", __func__); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci /* Strictly check for sample size */ 27462306a36Sopenharmony_ci switch (params_width(params)) { 27562306a36Sopenharmony_ci case 16: 27662306a36Sopenharmony_ci break; 27762306a36Sopenharmony_ci default: 27862306a36Sopenharmony_ci return -EINVAL; 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci spin_lock_irqsave(&pcm->lock, flags); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci /* Get hold of the PCMSOURCE_CLK */ 28462306a36Sopenharmony_ci clkctl = readl(regs + S3C_PCM_CLKCTL); 28562306a36Sopenharmony_ci if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK) 28662306a36Sopenharmony_ci clk = pcm->pclk; 28762306a36Sopenharmony_ci else 28862306a36Sopenharmony_ci clk = pcm->cclk; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* Set the SCLK divider */ 29162306a36Sopenharmony_ci sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs / 29262306a36Sopenharmony_ci params_rate(params) / 2 - 1; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK 29562306a36Sopenharmony_ci << S3C_PCM_CLKCTL_SCLKDIV_SHIFT); 29662306a36Sopenharmony_ci clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK) 29762306a36Sopenharmony_ci << S3C_PCM_CLKCTL_SCLKDIV_SHIFT); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci /* Set the SYNC divider */ 30062306a36Sopenharmony_ci sync_div = pcm->sclk_per_fs - 1; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK 30362306a36Sopenharmony_ci << S3C_PCM_CLKCTL_SYNCDIV_SHIFT); 30462306a36Sopenharmony_ci clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK) 30562306a36Sopenharmony_ci << S3C_PCM_CLKCTL_SYNCDIV_SHIFT); 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci writel(clkctl, regs + S3C_PCM_CLKCTL); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci spin_unlock_irqrestore(&pcm->lock, flags); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n", 31262306a36Sopenharmony_ci clk_get_rate(clk), pcm->sclk_per_fs, 31362306a36Sopenharmony_ci sclk_div, sync_div); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci return 0; 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_cistatic int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai, 31962306a36Sopenharmony_ci unsigned int fmt) 32062306a36Sopenharmony_ci{ 32162306a36Sopenharmony_ci struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai); 32262306a36Sopenharmony_ci void __iomem *regs = pcm->regs; 32362306a36Sopenharmony_ci unsigned long flags; 32462306a36Sopenharmony_ci int ret = 0; 32562306a36Sopenharmony_ci u32 ctl; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci dev_dbg(pcm->dev, "Entered %s\n", __func__); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci spin_lock_irqsave(&pcm->lock, flags); 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci ctl = readl(regs + S3C_PCM_CTL); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 33462306a36Sopenharmony_ci case SND_SOC_DAIFMT_IB_NF: 33562306a36Sopenharmony_ci /* Nothing to do, IB_NF by default */ 33662306a36Sopenharmony_ci break; 33762306a36Sopenharmony_ci default: 33862306a36Sopenharmony_ci dev_err(pcm->dev, "Unsupported clock inversion!\n"); 33962306a36Sopenharmony_ci ret = -EINVAL; 34062306a36Sopenharmony_ci goto exit; 34162306a36Sopenharmony_ci } 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { 34462306a36Sopenharmony_ci case SND_SOC_DAIFMT_BP_FP: 34562306a36Sopenharmony_ci /* Nothing to do, Master by default */ 34662306a36Sopenharmony_ci break; 34762306a36Sopenharmony_ci default: 34862306a36Sopenharmony_ci dev_err(pcm->dev, "Unsupported master/slave format!\n"); 34962306a36Sopenharmony_ci ret = -EINVAL; 35062306a36Sopenharmony_ci goto exit; 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) { 35462306a36Sopenharmony_ci case SND_SOC_DAIFMT_CONT: 35562306a36Sopenharmony_ci pcm->idleclk = 1; 35662306a36Sopenharmony_ci break; 35762306a36Sopenharmony_ci case SND_SOC_DAIFMT_GATED: 35862306a36Sopenharmony_ci pcm->idleclk = 0; 35962306a36Sopenharmony_ci break; 36062306a36Sopenharmony_ci default: 36162306a36Sopenharmony_ci dev_err(pcm->dev, "Invalid Clock gating request!\n"); 36262306a36Sopenharmony_ci ret = -EINVAL; 36362306a36Sopenharmony_ci goto exit; 36462306a36Sopenharmony_ci } 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 36762306a36Sopenharmony_ci case SND_SOC_DAIFMT_DSP_A: 36862306a36Sopenharmony_ci ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC; 36962306a36Sopenharmony_ci ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC; 37062306a36Sopenharmony_ci break; 37162306a36Sopenharmony_ci case SND_SOC_DAIFMT_DSP_B: 37262306a36Sopenharmony_ci ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC; 37362306a36Sopenharmony_ci ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC; 37462306a36Sopenharmony_ci break; 37562306a36Sopenharmony_ci default: 37662306a36Sopenharmony_ci dev_err(pcm->dev, "Unsupported data format!\n"); 37762306a36Sopenharmony_ci ret = -EINVAL; 37862306a36Sopenharmony_ci goto exit; 37962306a36Sopenharmony_ci } 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci writel(ctl, regs + S3C_PCM_CTL); 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ciexit: 38462306a36Sopenharmony_ci spin_unlock_irqrestore(&pcm->lock, flags); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci return ret; 38762306a36Sopenharmony_ci} 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_cistatic int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai, 39062306a36Sopenharmony_ci int div_id, int div) 39162306a36Sopenharmony_ci{ 39262306a36Sopenharmony_ci struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai); 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci switch (div_id) { 39562306a36Sopenharmony_ci case S3C_PCM_SCLK_PER_FS: 39662306a36Sopenharmony_ci pcm->sclk_per_fs = div; 39762306a36Sopenharmony_ci break; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci default: 40062306a36Sopenharmony_ci return -EINVAL; 40162306a36Sopenharmony_ci } 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci return 0; 40462306a36Sopenharmony_ci} 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_cistatic int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai, 40762306a36Sopenharmony_ci int clk_id, unsigned int freq, int dir) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai); 41062306a36Sopenharmony_ci void __iomem *regs = pcm->regs; 41162306a36Sopenharmony_ci u32 clkctl = readl(regs + S3C_PCM_CLKCTL); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci switch (clk_id) { 41462306a36Sopenharmony_ci case S3C_PCM_CLKSRC_PCLK: 41562306a36Sopenharmony_ci clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK; 41662306a36Sopenharmony_ci break; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci case S3C_PCM_CLKSRC_MUX: 41962306a36Sopenharmony_ci clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci if (clk_get_rate(pcm->cclk) != freq) 42262306a36Sopenharmony_ci clk_set_rate(pcm->cclk, freq); 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci break; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci default: 42762306a36Sopenharmony_ci return -EINVAL; 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci writel(clkctl, regs + S3C_PCM_CLKCTL); 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci return 0; 43362306a36Sopenharmony_ci} 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_cistatic int s3c_pcm_dai_probe(struct snd_soc_dai *dai) 43662306a36Sopenharmony_ci{ 43762306a36Sopenharmony_ci struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(dai); 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci snd_soc_dai_init_dma_data(dai, pcm->dma_playback, pcm->dma_capture); 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci return 0; 44262306a36Sopenharmony_ci} 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_cistatic const struct snd_soc_dai_ops s3c_pcm_dai_ops = { 44562306a36Sopenharmony_ci .probe = s3c_pcm_dai_probe, 44662306a36Sopenharmony_ci .set_sysclk = s3c_pcm_set_sysclk, 44762306a36Sopenharmony_ci .set_clkdiv = s3c_pcm_set_clkdiv, 44862306a36Sopenharmony_ci .trigger = s3c_pcm_trigger, 44962306a36Sopenharmony_ci .hw_params = s3c_pcm_hw_params, 45062306a36Sopenharmony_ci .set_fmt = s3c_pcm_set_fmt, 45162306a36Sopenharmony_ci}; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci#define S3C_PCM_RATES SNDRV_PCM_RATE_8000_96000 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci#define S3C_PCM_DAI_DECLARE \ 45662306a36Sopenharmony_ci .symmetric_rate = 1, \ 45762306a36Sopenharmony_ci .ops = &s3c_pcm_dai_ops, \ 45862306a36Sopenharmony_ci .playback = { \ 45962306a36Sopenharmony_ci .channels_min = 2, \ 46062306a36Sopenharmony_ci .channels_max = 2, \ 46162306a36Sopenharmony_ci .rates = S3C_PCM_RATES, \ 46262306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, \ 46362306a36Sopenharmony_ci }, \ 46462306a36Sopenharmony_ci .capture = { \ 46562306a36Sopenharmony_ci .channels_min = 2, \ 46662306a36Sopenharmony_ci .channels_max = 2, \ 46762306a36Sopenharmony_ci .rates = S3C_PCM_RATES, \ 46862306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_LE, \ 46962306a36Sopenharmony_ci } 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_cistatic struct snd_soc_dai_driver s3c_pcm_dai[] = { 47262306a36Sopenharmony_ci [0] = { 47362306a36Sopenharmony_ci .name = "samsung-pcm.0", 47462306a36Sopenharmony_ci S3C_PCM_DAI_DECLARE, 47562306a36Sopenharmony_ci }, 47662306a36Sopenharmony_ci [1] = { 47762306a36Sopenharmony_ci .name = "samsung-pcm.1", 47862306a36Sopenharmony_ci S3C_PCM_DAI_DECLARE, 47962306a36Sopenharmony_ci }, 48062306a36Sopenharmony_ci}; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_cistatic const struct snd_soc_component_driver s3c_pcm_component = { 48362306a36Sopenharmony_ci .name = "s3c-pcm", 48462306a36Sopenharmony_ci .legacy_dai_naming = 1, 48562306a36Sopenharmony_ci}; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_cistatic int s3c_pcm_dev_probe(struct platform_device *pdev) 48862306a36Sopenharmony_ci{ 48962306a36Sopenharmony_ci struct s3c_pcm_info *pcm; 49062306a36Sopenharmony_ci struct resource *mem_res; 49162306a36Sopenharmony_ci struct s3c_audio_pdata *pcm_pdata; 49262306a36Sopenharmony_ci dma_filter_fn filter; 49362306a36Sopenharmony_ci int ret; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* Check for valid device index */ 49662306a36Sopenharmony_ci if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) { 49762306a36Sopenharmony_ci dev_err(&pdev->dev, "id %d out of range\n", pdev->id); 49862306a36Sopenharmony_ci return -EINVAL; 49962306a36Sopenharmony_ci } 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci pcm_pdata = pdev->dev.platform_data; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) { 50462306a36Sopenharmony_ci dev_err(&pdev->dev, "Unable to configure gpio\n"); 50562306a36Sopenharmony_ci return -EINVAL; 50662306a36Sopenharmony_ci } 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci pcm = &s3c_pcm[pdev->id]; 50962306a36Sopenharmony_ci pcm->dev = &pdev->dev; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci spin_lock_init(&pcm->lock); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci /* Default is 128fs */ 51462306a36Sopenharmony_ci pcm->sclk_per_fs = 128; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci pcm->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem_res); 51762306a36Sopenharmony_ci if (IS_ERR(pcm->regs)) 51862306a36Sopenharmony_ci return PTR_ERR(pcm->regs); 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci pcm->cclk = devm_clk_get(&pdev->dev, "audio-bus"); 52162306a36Sopenharmony_ci if (IS_ERR(pcm->cclk)) { 52262306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to get audio-bus clock\n"); 52362306a36Sopenharmony_ci return PTR_ERR(pcm->cclk); 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci ret = clk_prepare_enable(pcm->cclk); 52662306a36Sopenharmony_ci if (ret) 52762306a36Sopenharmony_ci return ret; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci /* record our pcm structure for later use in the callbacks */ 53062306a36Sopenharmony_ci dev_set_drvdata(&pdev->dev, pcm); 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci pcm->pclk = devm_clk_get(&pdev->dev, "pcm"); 53362306a36Sopenharmony_ci if (IS_ERR(pcm->pclk)) { 53462306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to get pcm clock\n"); 53562306a36Sopenharmony_ci ret = PTR_ERR(pcm->pclk); 53662306a36Sopenharmony_ci goto err_dis_cclk; 53762306a36Sopenharmony_ci } 53862306a36Sopenharmony_ci ret = clk_prepare_enable(pcm->pclk); 53962306a36Sopenharmony_ci if (ret) 54062306a36Sopenharmony_ci goto err_dis_cclk; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci s3c_pcm_stereo_in[pdev->id].addr = mem_res->start + S3C_PCM_RXFIFO; 54362306a36Sopenharmony_ci s3c_pcm_stereo_out[pdev->id].addr = mem_res->start + S3C_PCM_TXFIFO; 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci filter = NULL; 54662306a36Sopenharmony_ci if (pcm_pdata) { 54762306a36Sopenharmony_ci s3c_pcm_stereo_in[pdev->id].filter_data = pcm_pdata->dma_capture; 54862306a36Sopenharmony_ci s3c_pcm_stereo_out[pdev->id].filter_data = pcm_pdata->dma_playback; 54962306a36Sopenharmony_ci filter = pcm_pdata->dma_filter; 55062306a36Sopenharmony_ci } 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id]; 55362306a36Sopenharmony_ci pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id]; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci ret = samsung_asoc_dma_platform_register(&pdev->dev, filter, 55662306a36Sopenharmony_ci NULL, NULL, NULL); 55762306a36Sopenharmony_ci if (ret) { 55862306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret); 55962306a36Sopenharmony_ci goto err_dis_pclk; 56062306a36Sopenharmony_ci } 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci pm_runtime_enable(&pdev->dev); 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci ret = devm_snd_soc_register_component(&pdev->dev, &s3c_pcm_component, 56562306a36Sopenharmony_ci &s3c_pcm_dai[pdev->id], 1); 56662306a36Sopenharmony_ci if (ret != 0) { 56762306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to get register DAI: %d\n", ret); 56862306a36Sopenharmony_ci goto err_dis_pm; 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci return 0; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_cierr_dis_pm: 57462306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 57562306a36Sopenharmony_cierr_dis_pclk: 57662306a36Sopenharmony_ci clk_disable_unprepare(pcm->pclk); 57762306a36Sopenharmony_cierr_dis_cclk: 57862306a36Sopenharmony_ci clk_disable_unprepare(pcm->cclk); 57962306a36Sopenharmony_ci return ret; 58062306a36Sopenharmony_ci} 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_cistatic void s3c_pcm_dev_remove(struct platform_device *pdev) 58362306a36Sopenharmony_ci{ 58462306a36Sopenharmony_ci struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id]; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 58762306a36Sopenharmony_ci clk_disable_unprepare(pcm->cclk); 58862306a36Sopenharmony_ci clk_disable_unprepare(pcm->pclk); 58962306a36Sopenharmony_ci} 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_cistatic struct platform_driver s3c_pcm_driver = { 59262306a36Sopenharmony_ci .probe = s3c_pcm_dev_probe, 59362306a36Sopenharmony_ci .remove_new = s3c_pcm_dev_remove, 59462306a36Sopenharmony_ci .driver = { 59562306a36Sopenharmony_ci .name = "samsung-pcm", 59662306a36Sopenharmony_ci }, 59762306a36Sopenharmony_ci}; 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_cimodule_platform_driver(s3c_pcm_driver); 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci/* Module information */ 60262306a36Sopenharmony_ciMODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>"); 60362306a36Sopenharmony_ciMODULE_DESCRIPTION("S3C PCM Controller Driver"); 60462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 60562306a36Sopenharmony_ciMODULE_ALIAS("platform:samsung-pcm"); 606