aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/fsl/fsl_sai.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/fsl/fsl_sai.c')
-rw-r--r--sound/soc/fsl/fsl_sai.c581
1 files changed, 464 insertions, 117 deletions
diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c
index 10544fa27dc0..81f89f6767a2 100644
--- a/sound/soc/fsl/fsl_sai.c
+++ b/sound/soc/fsl/fsl_sai.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/pm_qos.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
@@ -22,6 +23,7 @@
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
#include "fsl_sai.h"
+#include "fsl_utils.h"
#include "imx-pcm.h"
#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\
@@ -30,7 +32,8 @@
static const unsigned int fsl_sai_rates[] = {
8000, 11025, 12000, 16000, 22050,
24000, 32000, 44100, 48000, 64000,
- 88200, 96000, 176400, 192000
+ 88200, 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000, 1411200, 2822400,
};
static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = {
@@ -56,13 +59,38 @@ static inline bool fsl_sai_dir_is_synced(struct fsl_sai *sai, int dir)
return !sai->synchronous[dir] && sai->synchronous[adir];
}
+static struct pinctrl_state *fsl_sai_get_pins_state(struct fsl_sai *sai, u32 bclk)
+{
+ struct pinctrl_state *state = NULL;
+
+ if (sai->is_pdm_mode) {
+ /* DSD512@44.1kHz, DSD512@48kHz */
+ if (bclk >= 22579200)
+ state = pinctrl_lookup_state(sai->pinctrl, "dsd512");
+
+ /* Get default DSD state */
+ if (IS_ERR_OR_NULL(state))
+ state = pinctrl_lookup_state(sai->pinctrl, "dsd");
+ } else {
+ /* 706k32b2c, 768k32b2c, etc */
+ if (bclk >= 45158400)
+ state = pinctrl_lookup_state(sai->pinctrl, "pcm_b2m");
+ }
+
+ /* Get default state */
+ if (IS_ERR_OR_NULL(state))
+ state = pinctrl_lookup_state(sai->pinctrl, "default");
+
+ return state;
+}
+
static irqreturn_t fsl_sai_isr(int irq, void *devid)
{
struct fsl_sai *sai = (struct fsl_sai *)devid;
unsigned int ofs = sai->soc_data->reg_offset;
struct device *dev = &sai->pdev->dev;
u32 flags, xcsr, mask;
- bool irq_none = true;
+ irqreturn_t iret = IRQ_NONE;
/*
* Both IRQ status bits and IRQ mask bits are in the xCSR but
@@ -76,7 +104,7 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid)
flags = xcsr & mask;
if (flags)
- irq_none = false;
+ iret = IRQ_HANDLED;
else
goto irq_rx;
@@ -86,11 +114,8 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid)
if (flags & FSL_SAI_CSR_SEF)
dev_dbg(dev, "isr: Tx Frame sync error detected\n");
- if (flags & FSL_SAI_CSR_FEF) {
+ if (flags & FSL_SAI_CSR_FEF)
dev_dbg(dev, "isr: Transmit underrun detected\n");
- /* FIFO reset for safety */
- xcsr |= FSL_SAI_CSR_FR;
- }
if (flags & FSL_SAI_CSR_FWF)
dev_dbg(dev, "isr: Enabled transmit FIFO is empty\n");
@@ -110,7 +135,7 @@ irq_rx:
flags = xcsr & mask;
if (flags)
- irq_none = false;
+ iret = IRQ_HANDLED;
else
goto out;
@@ -120,11 +145,8 @@ irq_rx:
if (flags & FSL_SAI_CSR_SEF)
dev_dbg(dev, "isr: Rx Frame sync error detected\n");
- if (flags & FSL_SAI_CSR_FEF) {
+ if (flags & FSL_SAI_CSR_FEF)
dev_dbg(dev, "isr: Receive overflow detected\n");
- /* FIFO reset for safety */
- xcsr |= FSL_SAI_CSR_FR;
- }
if (flags & FSL_SAI_CSR_FWF)
dev_dbg(dev, "isr: Enabled receive FIFO is full\n");
@@ -139,10 +161,7 @@ irq_rx:
regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), flags | xcsr);
out:
- if (irq_none)
- return IRQ_NONE;
- else
- return IRQ_HANDLED;
+ return iret;
}
static int fsl_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask,
@@ -167,11 +186,10 @@ static int fsl_sai_set_dai_bclk_ratio(struct snd_soc_dai *dai,
}
static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai,
- int clk_id, unsigned int freq, int fsl_dir)
+ int clk_id, unsigned int freq, bool tx)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
unsigned int ofs = sai->soc_data->reg_offset;
- bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
u32 val_cr2 = 0;
switch (clk_id) {
@@ -197,23 +215,55 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai,
return 0;
}
+static int fsl_sai_set_mclk_rate(struct snd_soc_dai *dai, int clk_id, unsigned int freq)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ fsl_asoc_reparent_pll_clocks(dai->dev, sai->mclk_clk[clk_id],
+ sai->pll8k_clk, sai->pll11k_clk, freq);
+
+ ret = clk_set_rate(sai->mclk_clk[clk_id], freq);
+ if (ret < 0)
+ dev_err(dai->dev, "failed to set clock rate (%u): %d\n", freq, ret);
+
+ return ret;
+}
+
static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
int ret;
if (dir == SND_SOC_CLOCK_IN)
return 0;
- ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq,
- FSL_FMT_TRANSMITTER);
+ if (freq > 0 && clk_id != FSL_SAI_CLK_BUS) {
+ if (clk_id < 0 || clk_id >= FSL_SAI_MCLK_MAX) {
+ dev_err(cpu_dai->dev, "Unknown clock id: %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ if (IS_ERR_OR_NULL(sai->mclk_clk[clk_id])) {
+ dev_err(cpu_dai->dev, "Unassigned clock: %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ if (sai->mclk_streams == 0) {
+ ret = fsl_sai_set_mclk_rate(cpu_dai, clk_id, freq);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, true);
if (ret) {
dev_err(cpu_dai->dev, "Cannot set tx sysclk: %d\n", ret);
return ret;
}
- ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq,
- FSL_FMT_RECEIVER);
+ ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, false);
if (ret)
dev_err(cpu_dai->dev, "Cannot set rx sysclk: %d\n", ret);
@@ -221,16 +271,16 @@ static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
}
static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
- unsigned int fmt, int fsl_dir)
+ unsigned int fmt, bool tx)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
unsigned int ofs = sai->soc_data->reg_offset;
- bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
u32 val_cr2 = 0, val_cr4 = 0;
if (!sai->is_lsb_first)
val_cr4 |= FSL_SAI_CR4_MF;
+ sai->is_pdm_mode = false;
/* DAI mode */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
@@ -269,6 +319,11 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
val_cr2 |= FSL_SAI_CR2_BCP;
sai->is_dsp_mode = true;
break;
+ case SND_SOC_DAIFMT_PDM:
+ val_cr2 |= FSL_SAI_CR2_BCP;
+ val_cr4 &= ~FSL_SAI_CR4_MF;
+ sai->is_pdm_mode = true;
+ break;
case SND_SOC_DAIFMT_RIGHT_J:
/* To be done */
default:
@@ -299,19 +354,19 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
/* DAI clock provider masks */
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
- case SND_SOC_DAIFMT_CBC_CFC:
+ case SND_SOC_DAIFMT_BP_FP:
val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
sai->is_consumer_mode = false;
break;
- case SND_SOC_DAIFMT_CBP_CFP:
+ case SND_SOC_DAIFMT_BC_FC:
sai->is_consumer_mode = true;
break;
- case SND_SOC_DAIFMT_CBC_CFP:
+ case SND_SOC_DAIFMT_BP_FC:
val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
sai->is_consumer_mode = false;
break;
- case SND_SOC_DAIFMT_CBP_CFC:
+ case SND_SOC_DAIFMT_BC_FP:
val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
sai->is_consumer_mode = true;
break;
@@ -332,13 +387,13 @@ static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
{
int ret;
- ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER);
+ ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, true);
if (ret) {
dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret);
return ret;
}
- ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER);
+ ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, false);
if (ret)
dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret);
@@ -348,13 +403,13 @@ static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai);
- unsigned int ofs = sai->soc_data->reg_offset;
+ unsigned int reg, ofs = sai->soc_data->reg_offset;
unsigned long clk_rate;
- u32 savediv = 0, ratio, savesub = freq;
+ u32 savediv = 0, ratio, bestdiff = freq;
int adir = tx ? RX : TX;
int dir = tx ? TX : RX;
u32 id;
- int ret = 0;
+ bool support_1_1_ratio = sai->verid.version >= 0x0301;
/* Don't apply to consumer mode */
if (sai->is_consumer_mode)
@@ -368,37 +423,41 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
id = sai->soc_data->mclk0_is_mclk1 ? 1 : 0;
for (; id < FSL_SAI_MCLK_MAX; id++) {
+ int diff;
+
clk_rate = clk_get_rate(sai->mclk_clk[id]);
if (!clk_rate)
continue;
- ratio = clk_rate / freq;
+ ratio = DIV_ROUND_CLOSEST(clk_rate, freq);
+ if (!ratio || ratio > 512)
+ continue;
+ if (ratio == 1 && !support_1_1_ratio)
+ continue;
+ if ((ratio & 1) && ratio > 1)
+ continue;
- ret = clk_rate - ratio * freq;
+ diff = abs((long)clk_rate - ratio * freq);
/*
* Drop the source that can not be
* divided into the required rate.
*/
- if (ret != 0 && clk_rate / ret < 1000)
+ if (diff != 0 && clk_rate / diff < 1000)
continue;
dev_dbg(dai->dev,
"ratio %d for freq %dHz based on clock %ldHz\n",
ratio, freq, clk_rate);
- if (ratio % 2 == 0 && ratio >= 2 && ratio <= 512)
- ratio /= 2;
- else
- continue;
- if (ret < savesub) {
+ if (diff < bestdiff) {
savediv = ratio;
sai->mclk_id[tx] = id;
- savesub = ret;
+ bestdiff = diff;
}
- if (ret == 0)
+ if (diff == 0)
break;
}
@@ -408,6 +467,9 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
return -EINVAL;
}
+ dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n",
+ sai->mclk_id[tx], savediv, bestdiff);
+
/*
* 1) For Asynchronous mode, we must set RCR2 register for capture, and
* set TCR2 register for playback.
@@ -418,22 +480,30 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
* 4) For Tx and Rx are both Synchronous with another SAI, we just
* ignore it.
*/
- if (fsl_sai_dir_is_synced(sai, adir)) {
- regmap_update_bits(sai->regmap, FSL_SAI_xCR2(!tx, ofs),
- FSL_SAI_CR2_MSEL_MASK,
- FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
- regmap_update_bits(sai->regmap, FSL_SAI_xCR2(!tx, ofs),
- FSL_SAI_CR2_DIV_MASK, savediv - 1);
- } else if (!sai->synchronous[dir]) {
- regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs),
- FSL_SAI_CR2_MSEL_MASK,
- FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
- regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs),
- FSL_SAI_CR2_DIV_MASK, savediv - 1);
- }
+ if (fsl_sai_dir_is_synced(sai, adir))
+ reg = FSL_SAI_xCR2(!tx, ofs);
+ else if (!sai->synchronous[dir])
+ reg = FSL_SAI_xCR2(tx, ofs);
+ else
+ return 0;
- dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n",
- sai->mclk_id[tx], savediv, savesub);
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_MSEL_MASK,
+ FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
+
+ if (savediv == 1)
+ regmap_update_bits(sai->regmap, reg,
+ FSL_SAI_CR2_DIV_MASK | FSL_SAI_CR2_BYP,
+ FSL_SAI_CR2_BYP);
+ else
+ regmap_update_bits(sai->regmap, reg,
+ FSL_SAI_CR2_DIV_MASK | FSL_SAI_CR2_BYP,
+ savediv / 2 - 1);
+
+ if (sai->soc_data->max_register >= FSL_SAI_MCTL) {
+ /* SAI is in master mode at this point, so enable MCLK */
+ regmap_update_bits(sai->regmap, FSL_SAI_MCTL,
+ FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN);
+ }
return 0;
}
@@ -446,31 +516,66 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
unsigned int ofs = sai->soc_data->reg_offset;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
unsigned int channels = params_channels(params);
+ struct snd_dmaengine_dai_dma_data *dma_params;
+ struct fsl_sai_dl_cfg *dl_cfg = sai->dl_cfg;
u32 word_width = params_width(params);
+ int trce_mask = 0, dl_cfg_idx = 0;
+ int dl_cfg_cnt = sai->dl_cfg_cnt;
+ u32 dl_type = FSL_SAI_DL_I2S;
u32 val_cr4 = 0, val_cr5 = 0;
u32 slots = (channels == 1) ? 2 : channels;
u32 slot_width = word_width;
int adir = tx ? RX : TX;
- u32 pins;
- int ret;
-
- if (sai->slots)
- slots = sai->slots;
+ u32 pins, bclk;
+ u32 watermark;
+ int ret, i;
if (sai->slot_width)
slot_width = sai->slot_width;
+ if (sai->slots)
+ slots = sai->slots;
+ else if (sai->bclk_ratio)
+ slots = sai->bclk_ratio / slot_width;
+
pins = DIV_ROUND_UP(channels, slots);
+ /*
+ * PDM mode, channels are independent
+ * each channels are on one dataline/FIFO.
+ */
+ if (sai->is_pdm_mode) {
+ pins = channels;
+ dl_type = FSL_SAI_DL_PDM;
+ }
+
+ for (i = 0; i < dl_cfg_cnt; i++) {
+ if (dl_cfg[i].type == dl_type && dl_cfg[i].pins[tx] == pins) {
+ dl_cfg_idx = i;
+ break;
+ }
+ }
+
+ if (hweight8(dl_cfg[dl_cfg_idx].mask[tx]) < pins) {
+ dev_err(cpu_dai->dev, "channel not supported\n");
+ return -EINVAL;
+ }
+
+ bclk = params_rate(params) * (sai->bclk_ratio ? sai->bclk_ratio : slots * slot_width);
+
+ if (!IS_ERR_OR_NULL(sai->pinctrl)) {
+ sai->pins_state = fsl_sai_get_pins_state(sai, bclk);
+ if (!IS_ERR_OR_NULL(sai->pins_state)) {
+ ret = pinctrl_select_state(sai->pinctrl, sai->pins_state);
+ if (ret) {
+ dev_err(cpu_dai->dev, "failed to set proper pins state: %d\n", ret);
+ return ret;
+ }
+ }
+ }
+
if (!sai->is_consumer_mode) {
- if (sai->bclk_ratio)
- ret = fsl_sai_set_bclk(cpu_dai, tx,
- sai->bclk_ratio *
- params_rate(params));
- else
- ret = fsl_sai_set_bclk(cpu_dai, tx,
- slots * slot_width *
- params_rate(params));
+ ret = fsl_sai_set_bclk(cpu_dai, tx, bclk);
if (ret)
return ret;
@@ -484,13 +589,13 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
}
}
- if (!sai->is_dsp_mode)
+ if (!sai->is_dsp_mode && !sai->is_pdm_mode)
val_cr4 |= FSL_SAI_CR4_SYWD(slot_width);
val_cr5 |= FSL_SAI_CR5_WNW(slot_width);
val_cr5 |= FSL_SAI_CR5_W0W(slot_width);
- if (sai->is_lsb_first)
+ if (sai->is_lsb_first || sai->is_pdm_mode)
val_cr5 |= FSL_SAI_CR5_FBT(0);
else
val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1);
@@ -517,9 +622,56 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
FSL_SAI_CR5_FBT_MASK, val_cr5);
}
+ /*
+ * Combine mode has limation:
+ * - Can't used for singel dataline/FIFO case except the FIFO0
+ * - Can't used for multi dataline/FIFO case except the enabled FIFOs
+ * are successive and start from FIFO0
+ *
+ * So for common usage, all multi fifo case disable the combine mode.
+ */
+ if (hweight8(dl_cfg[dl_cfg_idx].mask[tx]) <= 1 || sai->is_multi_fifo_dma)
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
+ FSL_SAI_CR4_FCOMB_MASK, 0);
+ else
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
+ FSL_SAI_CR4_FCOMB_MASK, FSL_SAI_CR4_FCOMB_SOFT);
+
+ dma_params = tx ? &sai->dma_params_tx : &sai->dma_params_rx;
+ dma_params->addr = sai->res->start + FSL_SAI_xDR0(tx) +
+ dl_cfg[dl_cfg_idx].start_off[tx] * 0x4;
+
+ if (sai->is_multi_fifo_dma) {
+ sai->audio_config[tx].words_per_fifo = min(slots, channels);
+ if (tx) {
+ sai->audio_config[tx].n_fifos_dst = pins;
+ sai->audio_config[tx].stride_fifos_dst = dl_cfg[dl_cfg_idx].next_off[tx];
+ } else {
+ sai->audio_config[tx].n_fifos_src = pins;
+ sai->audio_config[tx].stride_fifos_src = dl_cfg[dl_cfg_idx].next_off[tx];
+ }
+ dma_params->maxburst = sai->audio_config[tx].words_per_fifo * pins;
+ dma_params->peripheral_config = &sai->audio_config[tx];
+ dma_params->peripheral_size = sizeof(sai->audio_config[tx]);
+
+ watermark = tx ? (sai->soc_data->fifo_depth - dma_params->maxburst) :
+ (dma_params->maxburst - 1);
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR1(tx, ofs),
+ FSL_SAI_CR1_RFW_MASK(sai->soc_data->fifo_depth),
+ watermark);
+ }
+
+ /* Find a proper tcre setting */
+ for (i = 0; i < sai->soc_data->pins; i++) {
+ trce_mask = (1 << (i + 1)) - 1;
+ if (hweight8(dl_cfg[dl_cfg_idx].mask[tx] & trce_mask) == pins)
+ break;
+ }
+
regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs),
FSL_SAI_CR3_TRCE_MASK,
- FSL_SAI_CR3_TRCE((1 << pins) - 1));
+ FSL_SAI_CR3_TRCE((dl_cfg[dl_cfg_idx].mask[tx] & trce_mask)));
+
regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs),
FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
FSL_SAI_CR4_CHMOD_MASK,
@@ -731,6 +883,23 @@ static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai)
return 0;
}
+static int fsl_sai_dai_resume(struct snd_soc_component *component)
+{
+ struct fsl_sai *sai = snd_soc_component_get_drvdata(component);
+ struct device *dev = &sai->pdev->dev;
+ int ret;
+
+ if (!IS_ERR_OR_NULL(sai->pinctrl) && !IS_ERR_OR_NULL(sai->pins_state)) {
+ ret = pinctrl_select_state(sai->pinctrl, sai->pins_state);
+ if (ret) {
+ dev_err(dev, "failed to set proper pins state: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
static struct snd_soc_dai_driver fsl_sai_dai_template = {
.probe = fsl_sai_dai_probe,
.playback = {
@@ -738,7 +907,7 @@ static struct snd_soc_dai_driver fsl_sai_dai_template = {
.channels_min = 1,
.channels_max = 32,
.rate_min = 8000,
- .rate_max = 192000,
+ .rate_max = 2822400,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
@@ -747,7 +916,7 @@ static struct snd_soc_dai_driver fsl_sai_dai_template = {
.channels_min = 1,
.channels_max = 32,
.rate_min = 8000,
- .rate_max = 192000,
+ .rate_max = 2822400,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
@@ -755,7 +924,9 @@ static struct snd_soc_dai_driver fsl_sai_dai_template = {
};
static const struct snd_soc_component_driver fsl_component = {
- .name = "fsl-sai",
+ .name = "fsl-sai",
+ .resume = fsl_sai_dai_resume,
+ .legacy_dai_naming = 1,
};
static struct reg_default fsl_sai_reg_defaults_ofs0[] = {
@@ -968,10 +1139,8 @@ static int fsl_sai_check_version(struct device *dev)
dev_dbg(dev, "VERID: 0x%016X\n", val);
- sai->verid.major = (val & FSL_SAI_VERID_MAJOR_MASK) >>
- FSL_SAI_VERID_MAJOR_SHIFT;
- sai->verid.minor = (val & FSL_SAI_VERID_MINOR_MASK) >>
- FSL_SAI_VERID_MINOR_SHIFT;
+ sai->verid.version = val &
+ (FSL_SAI_VERID_MAJOR_MASK | FSL_SAI_VERID_MINOR_MASK);
sai->verid.feature = val & FSL_SAI_VERID_FEATURE_MASK;
ret = regmap_read(sai->regmap, FSL_SAI_PARAM, &val);
@@ -994,30 +1163,143 @@ static int fsl_sai_check_version(struct device *dev)
return 0;
}
+/*
+ * Calculate the offset between first two datalines, don't
+ * different offset in one case.
+ */
+static unsigned int fsl_sai_calc_dl_off(unsigned long dl_mask)
+{
+ int fbidx, nbidx, offset;
+
+ fbidx = find_first_bit(&dl_mask, FSL_SAI_DL_NUM);
+ nbidx = find_next_bit(&dl_mask, FSL_SAI_DL_NUM, fbidx + 1);
+ offset = nbidx - fbidx - 1;
+
+ return (offset < 0 || offset >= (FSL_SAI_DL_NUM - 1) ? 0 : offset);
+}
+
+/*
+ * read the fsl,dataline property from dts file.
+ * It has 3 value for each configuration, first one means the type:
+ * I2S(1) or PDM(2), second one is dataline mask for 'rx', third one is
+ * dataline mask for 'tx'. for example
+ *
+ * fsl,dataline = <1 0xff 0xff 2 0xff 0x11>,
+ *
+ * It means I2S type rx mask is 0xff, tx mask is 0xff, PDM type
+ * rx mask is 0xff, tx mask is 0x11 (dataline 1 and 4 enabled).
+ *
+ */
+static int fsl_sai_read_dlcfg(struct fsl_sai *sai)
+{
+ struct platform_device *pdev = sai->pdev;
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ int ret, elems, i, index, num_cfg;
+ char *propname = "fsl,dataline";
+ struct fsl_sai_dl_cfg *cfg;
+ unsigned long dl_mask;
+ unsigned int soc_dl;
+ u32 rx, tx, type;
+
+ elems = of_property_count_u32_elems(np, propname);
+
+ if (elems <= 0) {
+ elems = 0;
+ } else if (elems % 3) {
+ dev_err(dev, "Number of elements must be divisible to 3.\n");
+ return -EINVAL;
+ }
+
+ num_cfg = elems / 3;
+ /* Add one more for default value */
+ cfg = devm_kzalloc(&pdev->dev, (num_cfg + 1) * sizeof(*cfg), GFP_KERNEL);
+ if (!cfg)
+ return -ENOMEM;
+
+ /* Consider default value "0 0xFF 0xFF" if property is missing */
+ soc_dl = BIT(sai->soc_data->pins) - 1;
+ cfg[0].type = FSL_SAI_DL_DEFAULT;
+ cfg[0].pins[0] = sai->soc_data->pins;
+ cfg[0].mask[0] = soc_dl;
+ cfg[0].start_off[0] = 0;
+ cfg[0].next_off[0] = 0;
+
+ cfg[0].pins[1] = sai->soc_data->pins;
+ cfg[0].mask[1] = soc_dl;
+ cfg[0].start_off[1] = 0;
+ cfg[0].next_off[1] = 0;
+ for (i = 1, index = 0; i < num_cfg + 1; i++) {
+ /*
+ * type of dataline
+ * 0 means default mode
+ * 1 means I2S mode
+ * 2 means PDM mode
+ */
+ ret = of_property_read_u32_index(np, propname, index++, &type);
+ if (ret)
+ return -EINVAL;
+
+ ret = of_property_read_u32_index(np, propname, index++, &rx);
+ if (ret)
+ return -EINVAL;
+
+ ret = of_property_read_u32_index(np, propname, index++, &tx);
+ if (ret)
+ return -EINVAL;
+
+ if ((rx & ~soc_dl) || (tx & ~soc_dl)) {
+ dev_err(dev, "dataline cfg[%d] setting error, mask is 0x%x\n", i, soc_dl);
+ return -EINVAL;
+ }
+
+ rx = rx & soc_dl;
+ tx = tx & soc_dl;
+
+ cfg[i].type = type;
+ cfg[i].pins[0] = hweight8(rx);
+ cfg[i].mask[0] = rx;
+ dl_mask = rx;
+ cfg[i].start_off[0] = find_first_bit(&dl_mask, FSL_SAI_DL_NUM);
+ cfg[i].next_off[0] = fsl_sai_calc_dl_off(rx);
+
+ cfg[i].pins[1] = hweight8(tx);
+ cfg[i].mask[1] = tx;
+ dl_mask = tx;
+ cfg[i].start_off[1] = find_first_bit(&dl_mask, FSL_SAI_DL_NUM);
+ cfg[i].next_off[1] = fsl_sai_calc_dl_off(tx);
+ }
+
+ sai->dl_cfg = cfg;
+ sai->dl_cfg_cnt = num_cfg + 1;
+ return 0;
+}
+
static int fsl_sai_runtime_suspend(struct device *dev);
static int fsl_sai_runtime_resume(struct device *dev);
static int fsl_sai_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
struct fsl_sai *sai;
struct regmap *gpr;
- struct resource *res;
void __iomem *base;
char tmp[8];
int irq, ret, i;
int index;
+ u32 dmas[4];
- sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
+ sai = devm_kzalloc(dev, sizeof(*sai), GFP_KERNEL);
if (!sai)
return -ENOMEM;
sai->pdev = pdev;
- sai->soc_data = of_device_get_match_data(&pdev->dev);
+ sai->soc_data = of_device_get_match_data(dev);
sai->is_lsb_first = of_property_read_bool(np, "lsb-first");
- base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &sai->res);
if (IS_ERR(base))
return PTR_ERR(base);
@@ -1028,18 +1310,18 @@ static int fsl_sai_probe(struct platform_device *pdev)
ARRAY_SIZE(fsl_sai_reg_defaults_ofs8);
}
- sai->regmap = devm_regmap_init_mmio(&pdev->dev, base, &fsl_sai_regmap_config);
+ sai->regmap = devm_regmap_init_mmio(dev, base, &fsl_sai_regmap_config);
if (IS_ERR(sai->regmap)) {
- dev_err(&pdev->dev, "regmap init failed\n");
+ dev_err(dev, "regmap init failed\n");
return PTR_ERR(sai->regmap);
}
- sai->bus_clk = devm_clk_get(&pdev->dev, "bus");
+ sai->bus_clk = devm_clk_get(dev, "bus");
/* Compatible with old DTB cases */
if (IS_ERR(sai->bus_clk) && PTR_ERR(sai->bus_clk) != -EPROBE_DEFER)
- sai->bus_clk = devm_clk_get(&pdev->dev, "sai");
+ sai->bus_clk = devm_clk_get(dev, "sai");
if (IS_ERR(sai->bus_clk)) {
- dev_err(&pdev->dev, "failed to get bus clock: %ld\n",
+ dev_err(dev, "failed to get bus clock: %ld\n",
PTR_ERR(sai->bus_clk));
/* -EPROBE_DEFER */
return PTR_ERR(sai->bus_clk);
@@ -1047,10 +1329,10 @@ static int fsl_sai_probe(struct platform_device *pdev)
for (i = 1; i < FSL_SAI_MCLK_MAX; i++) {
sprintf(tmp, "mclk%d", i);
- sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp);
+ sai->mclk_clk[i] = devm_clk_get(dev, tmp);
if (IS_ERR(sai->mclk_clk[i])) {
- dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n",
- i + 1, PTR_ERR(sai->mclk_clk[i]));
+ dev_err(dev, "failed to get mclk%d clock: %ld\n",
+ i, PTR_ERR(sai->mclk_clk[i]));
sai->mclk_clk[i] = NULL;
}
}
@@ -1060,14 +1342,29 @@ static int fsl_sai_probe(struct platform_device *pdev)
else
sai->mclk_clk[0] = sai->bus_clk;
+ fsl_asoc_get_pll_clocks(&pdev->dev, &sai->pll8k_clk,
+ &sai->pll11k_clk);
+
+ /* Use Multi FIFO mode depending on the support from SDMA script */
+ ret = of_property_read_u32_array(np, "dmas", dmas, 4);
+ if (!sai->soc_data->use_edma && !ret && dmas[2] == IMX_DMATYPE_MULTI_SAI)
+ sai->is_multi_fifo_dma = true;
+
+ /* read dataline mask for rx and tx*/
+ ret = fsl_sai_read_dlcfg(sai);
+ if (ret < 0) {
+ dev_err(dev, "failed to read dlcfg %d\n", ret);
+ return ret;
+ }
+
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
- ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, IRQF_SHARED,
+ ret = devm_request_irq(dev, irq, fsl_sai_isr, IRQF_SHARED,
np->name, sai);
if (ret) {
- dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
+ dev_err(dev, "failed to claim irq %u\n", irq);
return ret;
}
@@ -1084,7 +1381,7 @@ static int fsl_sai_probe(struct platform_device *pdev)
if (of_find_property(np, "fsl,sai-synchronous-rx", NULL) &&
of_find_property(np, "fsl,sai-asynchronous", NULL)) {
/* error out if both synchronous and asynchronous are present */
- dev_err(&pdev->dev, "invalid binding for synchronous mode\n");
+ dev_err(dev, "invalid binding for synchronous mode\n");
return -EINVAL;
}
@@ -1105,7 +1402,7 @@ static int fsl_sai_probe(struct platform_device *pdev)
of_device_is_compatible(np, "fsl,imx6ul-sai")) {
gpr = syscon_regmap_lookup_by_compatible("fsl,imx6ul-iomuxc-gpr");
if (IS_ERR(gpr)) {
- dev_err(&pdev->dev, "cannot find iomuxc registers\n");
+ dev_err(dev, "cannot find iomuxc registers\n");
return PTR_ERR(gpr);
}
@@ -1117,38 +1414,38 @@ static int fsl_sai_probe(struct platform_device *pdev)
MCLK_DIR(index));
}
- sai->dma_params_rx.addr = res->start + FSL_SAI_RDR0;
- sai->dma_params_tx.addr = res->start + FSL_SAI_TDR0;
+ sai->dma_params_rx.addr = sai->res->start + FSL_SAI_RDR0;
+ sai->dma_params_tx.addr = sai->res->start + FSL_SAI_TDR0;
sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX;
sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX;
+ sai->pinctrl = devm_pinctrl_get(&pdev->dev);
+
platform_set_drvdata(pdev, sai);
- pm_runtime_enable(&pdev->dev);
- if (!pm_runtime_enabled(&pdev->dev)) {
- ret = fsl_sai_runtime_resume(&pdev->dev);
+ pm_runtime_enable(dev);
+ if (!pm_runtime_enabled(dev)) {
+ ret = fsl_sai_runtime_resume(dev);
if (ret)
goto err_pm_disable;
}
- ret = pm_runtime_get_sync(&pdev->dev);
- if (ret < 0) {
- pm_runtime_put_noidle(&pdev->dev);
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
goto err_pm_get_sync;
- }
/* Get sai version */
- ret = fsl_sai_check_version(&pdev->dev);
+ ret = fsl_sai_check_version(dev);
if (ret < 0)
- dev_warn(&pdev->dev, "Error reading SAI version: %d\n", ret);
+ dev_warn(dev, "Error reading SAI version: %d\n", ret);
/* Select MCLK direction */
if (of_find_property(np, "fsl,sai-mclk-direction-output", NULL) &&
- sai->verid.major >= 3 && sai->verid.minor >= 1) {
+ sai->soc_data->max_register >= FSL_SAI_MCTL) {
regmap_update_bits(sai->regmap, FSL_SAI_MCTL,
FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN);
}
- ret = pm_runtime_put_sync(&pdev->dev);
+ ret = pm_runtime_put_sync(dev);
if (ret < 0)
goto err_pm_get_sync;
@@ -1157,16 +1454,19 @@ static int fsl_sai_probe(struct platform_device *pdev)
* is not defer probe for platform component in snd_soc_add_pcm_runtime().
*/
if (sai->soc_data->use_imx_pcm) {
- ret = imx_pcm_dma_init(pdev, IMX_SAI_DMABUF_SIZE);
- if (ret)
+ ret = imx_pcm_dma_init(pdev);
+ if (ret) {
+ if (!IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA))
+ dev_err(dev, "Error: You must enable the imx-pcm-dma support!\n");
goto err_pm_get_sync;
+ }
} else {
- ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+ ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0);
if (ret)
goto err_pm_get_sync;
}
- ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
+ ret = devm_snd_soc_register_component(dev, &fsl_component,
&sai->cpu_dai_drv, 1);
if (ret)
goto err_pm_get_sync;
@@ -1174,10 +1474,10 @@ static int fsl_sai_probe(struct platform_device *pdev)
return ret;
err_pm_get_sync:
- if (!pm_runtime_status_suspended(&pdev->dev))
- fsl_sai_runtime_suspend(&pdev->dev);
+ if (!pm_runtime_status_suspended(dev))
+ fsl_sai_runtime_suspend(dev);
err_pm_disable:
- pm_runtime_disable(&pdev->dev);
+ pm_runtime_disable(dev);
return ret;
}
@@ -1195,45 +1495,88 @@ static const struct fsl_sai_soc_data fsl_sai_vf610_data = {
.use_imx_pcm = false,
.use_edma = false,
.fifo_depth = 32,
+ .pins = 1,
.reg_offset = 0,
.mclk0_is_mclk1 = false,
.flags = 0,
+ .max_register = FSL_SAI_RMR,
};
static const struct fsl_sai_soc_data fsl_sai_imx6sx_data = {
.use_imx_pcm = true,
.use_edma = false,
.fifo_depth = 32,
+ .pins = 1,
.reg_offset = 0,
.mclk0_is_mclk1 = true,
.flags = 0,
+ .max_register = FSL_SAI_RMR,
};
static const struct fsl_sai_soc_data fsl_sai_imx7ulp_data = {
.use_imx_pcm = true,
.use_edma = false,
.fifo_depth = 16,
+ .pins = 2,
.reg_offset = 8,
.mclk0_is_mclk1 = false,
.flags = PMQOS_CPU_LATENCY,
+ .max_register = FSL_SAI_RMR,
};
static const struct fsl_sai_soc_data fsl_sai_imx8mq_data = {
.use_imx_pcm = true,
.use_edma = false,
.fifo_depth = 128,
+ .pins = 8,
.reg_offset = 8,
.mclk0_is_mclk1 = false,
.flags = 0,
+ .max_register = FSL_SAI_RMR,
};
static const struct fsl_sai_soc_data fsl_sai_imx8qm_data = {
.use_imx_pcm = true,
.use_edma = true,
.fifo_depth = 64,
+ .pins = 1,
.reg_offset = 0,
.mclk0_is_mclk1 = false,
.flags = 0,
+ .max_register = FSL_SAI_RMR,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx8mm_data = {
+ .use_imx_pcm = true,
+ .use_edma = false,
+ .fifo_depth = 128,
+ .reg_offset = 8,
+ .mclk0_is_mclk1 = false,
+ .pins = 8,
+ .flags = 0,
+ .max_register = FSL_SAI_MCTL,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx8mp_data = {
+ .use_imx_pcm = true,
+ .use_edma = false,
+ .fifo_depth = 128,
+ .reg_offset = 8,
+ .mclk0_is_mclk1 = false,
+ .pins = 8,
+ .flags = 0,
+ .max_register = FSL_SAI_MDIV,
+};
+
+static const struct fsl_sai_soc_data fsl_sai_imx8ulp_data = {
+ .use_imx_pcm = true,
+ .use_edma = true,
+ .fifo_depth = 16,
+ .reg_offset = 8,
+ .mclk0_is_mclk1 = false,
+ .pins = 4,
+ .flags = PMQOS_CPU_LATENCY,
+ .max_register = FSL_SAI_RTCAP,
};
static const struct of_device_id fsl_sai_ids[] = {
@@ -1243,6 +1586,10 @@ static const struct of_device_id fsl_sai_ids[] = {
{ .compatible = "fsl,imx7ulp-sai", .data = &fsl_sai_imx7ulp_data },
{ .compatible = "fsl,imx8mq-sai", .data = &fsl_sai_imx8mq_data },
{ .compatible = "fsl,imx8qm-sai", .data = &fsl_sai_imx8qm_data },
+ { .compatible = "fsl,imx8mm-sai", .data = &fsl_sai_imx8mm_data },
+ { .compatible = "fsl,imx8mp-sai", .data = &fsl_sai_imx8mp_data },
+ { .compatible = "fsl,imx8ulp-sai", .data = &fsl_sai_imx8ulp_data },
+ { .compatible = "fsl,imx8mn-sai", .data = &fsl_sai_imx8mp_data },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsl_sai_ids);