Commit 76067540 authored by Dong Aisheng's avatar Dong Aisheng Committed by Mark Brown

ASoC: mxs-saif: add record function

1. add different clkmux mode handling
SAIF can use two instances to implement full duplex (playback &
recording) and record saif may work on EXTMASTER mode which is
using other saif's BITCLK&LRCLK.

The clkmux mode could be set in pdata->init() in mach-specific code.
For generic saif driver, it only needs to know who is his master
and the master id is also provided in mach-specific code.

2. support playback and capture simutaneously however the sample
rates can not be different due to hw limitation.
Signed-off-by: default avatarDong Aisheng <b29396@freescale.com>
Acked-by: default avatarWolfram Sang <w.sang@pengutronix.de>
Acked-by: default avatarLiam Girdwood <lrg@ti.com>
Signed-off-by: default avatarMark Brown <broonie@opensource.wolfsonmicro.com>
parent 5d42940c
/*
* Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved.
*
* 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.
*/
#ifndef __SOUND_SAIF_H__
#define __SOUND_SAIF_H__
struct mxs_saif_platform_data {
int (*init) (void);
int (*get_master_id) (unsigned int saif_id);
};
#endif
...@@ -23,10 +23,12 @@ ...@@ -23,10 +23,12 @@
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/time.h>
#include <sound/core.h> #include <sound/core.h>
#include <sound/pcm.h> #include <sound/pcm.h>
#include <sound/pcm_params.h> #include <sound/pcm_params.h>
#include <sound/soc.h> #include <sound/soc.h>
#include <sound/saif.h>
#include <mach/dma.h> #include <mach/dma.h>
#include <asm/mach-types.h> #include <asm/mach-types.h>
#include <mach/hardware.h> #include <mach/hardware.h>
...@@ -36,6 +38,24 @@ ...@@ -36,6 +38,24 @@
static struct mxs_saif *mxs_saif[2]; static struct mxs_saif *mxs_saif[2];
/*
* SAIF is a little different with other normal SOC DAIs on clock using.
*
* For MXS, two SAIF modules are instantiated on-chip.
* Each SAIF has a set of clock pins and can be operating in master
* mode simultaneously if they are connected to different off-chip codecs.
* Also, one of the two SAIFs can master or drive the clock pins while the
* other SAIF, in slave mode, receives clocking from the master SAIF.
* This also means that both SAIFs must operate at the same sample rate.
*
* We abstract this as each saif has a master, the master could be
* himself or other saifs. In the generic saif driver, saif does not need
* to know the different clkmux. Saif only needs to know who is his master
* and operating his master to generate the proper clock rate for him.
* The master id is provided in mach-specific layer according to different
* clkmux setting.
*/
static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir) int clk_id, unsigned int freq, int dir)
{ {
...@@ -51,6 +71,17 @@ static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, ...@@ -51,6 +71,17 @@ static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
return 0; return 0;
} }
/*
* Since SAIF may work on EXTMASTER mode, IOW, it's working BITCLK&LRCLK
* is provided by other SAIF, we provide a interface here to get its master
* from its master_id.
* Note that the master could be himself.
*/
static inline struct mxs_saif *mxs_saif_get_master(struct mxs_saif * saif)
{
return mxs_saif[saif->master_id];
}
/* /*
* Set SAIF clock and MCLK * Set SAIF clock and MCLK
*/ */
...@@ -60,8 +91,26 @@ static int mxs_saif_set_clk(struct mxs_saif *saif, ...@@ -60,8 +91,26 @@ static int mxs_saif_set_clk(struct mxs_saif *saif,
{ {
u32 scr; u32 scr;
int ret; int ret;
struct mxs_saif *master_saif;
scr = __raw_readl(saif->base + SAIF_CTRL); dev_dbg(saif->dev, "mclk %d rate %d\n", mclk, rate);
/* Set master saif to generate proper clock */
master_saif = mxs_saif_get_master(saif);
if (!master_saif)
return -EINVAL;
dev_dbg(saif->dev, "master saif%d\n", master_saif->id);
/* Checking if can playback and capture simutaneously */
if (master_saif->ongoing && rate != master_saif->cur_rate) {
dev_err(saif->dev,
"can not change clock, master saif%d(rate %d) is ongoing\n",
master_saif->id, master_saif->cur_rate);
return -EINVAL;
}
scr = __raw_readl(master_saif->base + SAIF_CTRL);
scr &= ~BM_SAIF_CTRL_BITCLK_MULT_RATE; scr &= ~BM_SAIF_CTRL_BITCLK_MULT_RATE;
scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
...@@ -75,27 +124,29 @@ static int mxs_saif_set_clk(struct mxs_saif *saif, ...@@ -75,27 +124,29 @@ static int mxs_saif_set_clk(struct mxs_saif *saif,
* *
* If MCLK is not used, we just set saif clk to 512*fs. * If MCLK is not used, we just set saif clk to 512*fs.
*/ */
if (saif->mclk_in_use) { if (master_saif->mclk_in_use) {
if (mclk % 32 == 0) { if (mclk % 32 == 0) {
scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
ret = clk_set_rate(saif->clk, 512 * rate); ret = clk_set_rate(master_saif->clk, 512 * rate);
} else if (mclk % 48 == 0) { } else if (mclk % 48 == 0) {
scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE; scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE;
ret = clk_set_rate(saif->clk, 384 * rate); ret = clk_set_rate(master_saif->clk, 384 * rate);
} else { } else {
/* SAIF MCLK should be either 32x or 48x */ /* SAIF MCLK should be either 32x or 48x */
return -EINVAL; return -EINVAL;
} }
} else { } else {
ret = clk_set_rate(saif->clk, 512 * rate); ret = clk_set_rate(master_saif->clk, 512 * rate);
scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
} }
if (ret) if (ret)
return ret; return ret;
if (!saif->mclk_in_use) { master_saif->cur_rate = rate;
__raw_writel(scr, saif->base + SAIF_CTRL);
if (!master_saif->mclk_in_use) {
__raw_writel(scr, master_saif->base + SAIF_CTRL);
return 0; return 0;
} }
...@@ -137,7 +188,7 @@ static int mxs_saif_set_clk(struct mxs_saif *saif, ...@@ -137,7 +188,7 @@ static int mxs_saif_set_clk(struct mxs_saif *saif,
return -EINVAL; return -EINVAL;
} }
__raw_writel(scr, saif->base + SAIF_CTRL); __raw_writel(scr, master_saif->base + SAIF_CTRL);
return 0; return 0;
} }
...@@ -183,6 +234,7 @@ int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk, ...@@ -183,6 +234,7 @@ int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk,
struct mxs_saif *saif = mxs_saif[saif_id]; struct mxs_saif *saif = mxs_saif[saif_id];
u32 stat; u32 stat;
int ret; int ret;
struct mxs_saif *master_saif;
if (!saif) if (!saif)
return -EINVAL; return -EINVAL;
...@@ -195,6 +247,12 @@ int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk, ...@@ -195,6 +247,12 @@ int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk,
__raw_writel(BM_SAIF_CTRL_CLKGATE, __raw_writel(BM_SAIF_CTRL_CLKGATE,
saif->base + SAIF_CTRL + MXS_CLR_ADDR); saif->base + SAIF_CTRL + MXS_CLR_ADDR);
master_saif = mxs_saif_get_master(saif);
if (saif != master_saif) {
dev_err(saif->dev, "can not get mclk from a non-master saif\n");
return -EINVAL;
}
stat = __raw_readl(saif->base + SAIF_STAT); stat = __raw_readl(saif->base + SAIF_STAT);
if (stat & BM_SAIF_STAT_BUSY) { if (stat & BM_SAIF_STAT_BUSY) {
dev_err(saif->dev, "error: busy\n"); dev_err(saif->dev, "error: busy\n");
...@@ -278,10 +336,17 @@ static int mxs_saif_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) ...@@ -278,10 +336,17 @@ static int mxs_saif_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
/* /*
* Note: We simply just support master mode since SAIF TX can only * Note: We simply just support master mode since SAIF TX can only
* work as master. * work as master.
* Here the master is relative to codec side.
* Saif internally could be slave when working on EXTMASTER mode.
* We just hide this to machine driver.
*/ */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS: case SND_SOC_DAIFMT_CBS_CFS:
scr &= ~BM_SAIF_CTRL_SLAVE_MODE; if (saif->id == saif->master_id)
scr &= ~BM_SAIF_CTRL_SLAVE_MODE;
else
scr |= BM_SAIF_CTRL_SLAVE_MODE;
__raw_writel(scr | scr0, saif->base + SAIF_CTRL); __raw_writel(scr | scr0, saif->base + SAIF_CTRL);
break; break;
default: default:
...@@ -396,6 +461,12 @@ static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd, ...@@ -396,6 +461,12 @@ static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *cpu_dai) struct snd_soc_dai *cpu_dai)
{ {
struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai);
struct mxs_saif *master_saif;
u32 delay;
master_saif = mxs_saif_get_master(saif);
if (!master_saif)
return -EINVAL;
switch (cmd) { switch (cmd) {
case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_START:
...@@ -403,10 +474,20 @@ static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd, ...@@ -403,10 +474,20 @@ static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd,
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
dev_dbg(cpu_dai->dev, "start\n"); dev_dbg(cpu_dai->dev, "start\n");
clk_enable(saif->clk); clk_enable(master_saif->clk);
if (!saif->mclk_in_use) if (!master_saif->mclk_in_use)
__raw_writel(BM_SAIF_CTRL_RUN,
master_saif->base + SAIF_CTRL + MXS_SET_ADDR);
/*
* If the saif's master is not himself, we also need to enable
* itself clk for its internal basic logic to work.
*/
if (saif != master_saif) {
clk_enable(saif->clk);
__raw_writel(BM_SAIF_CTRL_RUN, __raw_writel(BM_SAIF_CTRL_RUN,
saif->base + SAIF_CTRL + MXS_SET_ADDR); saif->base + SAIF_CTRL + MXS_SET_ADDR);
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* /*
...@@ -422,20 +503,39 @@ static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd, ...@@ -422,20 +503,39 @@ static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd,
__raw_readl(saif->base + SAIF_DATA); __raw_readl(saif->base + SAIF_DATA);
} }
dev_dbg(cpu_dai->dev, "CTRL 0x%x STAT 0x%x\n", master_saif->ongoing = 1;
dev_dbg(saif->dev, "CTRL 0x%x STAT 0x%x\n",
__raw_readl(saif->base + SAIF_CTRL), __raw_readl(saif->base + SAIF_CTRL),
__raw_readl(saif->base + SAIF_STAT)); __raw_readl(saif->base + SAIF_STAT));
dev_dbg(master_saif->dev, "CTRL 0x%x STAT 0x%x\n",
__raw_readl(master_saif->base + SAIF_CTRL),
__raw_readl(master_saif->base + SAIF_STAT));
break; break;
case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
dev_dbg(cpu_dai->dev, "stop\n"); dev_dbg(cpu_dai->dev, "stop\n");
clk_disable(saif->clk); /* wait a while for the current sample to complete */
if (!saif->mclk_in_use) delay = USEC_PER_SEC / master_saif->cur_rate;
if (!master_saif->mclk_in_use) {
__raw_writel(BM_SAIF_CTRL_RUN,
master_saif->base + SAIF_CTRL + MXS_CLR_ADDR);
udelay(delay);
}
clk_disable(master_saif->clk);
if (saif != master_saif) {
__raw_writel(BM_SAIF_CTRL_RUN, __raw_writel(BM_SAIF_CTRL_RUN,
saif->base + SAIF_CTRL + MXS_CLR_ADDR); saif->base + SAIF_CTRL + MXS_CLR_ADDR);
udelay(delay);
clk_disable(saif->clk);
}
master_saif->ongoing = 0;
break; break;
default: default:
...@@ -519,16 +619,33 @@ static int mxs_saif_probe(struct platform_device *pdev) ...@@ -519,16 +619,33 @@ static int mxs_saif_probe(struct platform_device *pdev)
{ {
struct resource *res; struct resource *res;
struct mxs_saif *saif; struct mxs_saif *saif;
struct mxs_saif_platform_data *pdata;
int ret = 0; int ret = 0;
if (pdev->id >= ARRAY_SIZE(mxs_saif)) if (pdev->id >= ARRAY_SIZE(mxs_saif))
return -EINVAL; return -EINVAL;
pdata = pdev->dev.platform_data;
if (pdata && pdata->init) {
ret = pdata->init();
if (ret)
return ret;
}
saif = kzalloc(sizeof(*saif), GFP_KERNEL); saif = kzalloc(sizeof(*saif), GFP_KERNEL);
if (!saif) if (!saif)
return -ENOMEM; return -ENOMEM;
mxs_saif[pdev->id] = saif; mxs_saif[pdev->id] = saif;
saif->id = pdev->id;
saif->master_id = saif->id;
if (pdata && pdata->get_master_id) {
saif->master_id = pdata->get_master_id(saif->id);
if (saif->master_id < 0 ||
saif->master_id >= ARRAY_SIZE(mxs_saif))
return -EINVAL;
}
saif->clk = clk_get(&pdev->dev, NULL); saif->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(saif->clk)) { if (IS_ERR(saif->clk)) {
......
...@@ -118,6 +118,10 @@ struct mxs_saif { ...@@ -118,6 +118,10 @@ struct mxs_saif {
void __iomem *base; void __iomem *base;
int irq; int irq;
struct mxs_pcm_dma_params dma_param; struct mxs_pcm_dma_params dma_param;
unsigned int id;
unsigned int master_id;
unsigned int cur_rate;
unsigned int ongoing;
struct platform_device *soc_platform_pdev; struct platform_device *soc_platform_pdev;
u32 fifo_underrun; u32 fifo_underrun;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment