diff options
Diffstat (limited to 'sound/soc/fsl/fsl_sai.c')
-rw-r--r-- | sound/soc/fsl/fsl_sai.c | 332 |
1 files changed, 212 insertions, 120 deletions
diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index cdd3fa830704..c4a423111673 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -15,6 +15,7 @@ #include <linux/dmaengine.h> #include <linux/module.h> #include <linux/of_address.h> +#include <linux/regmap.h> #include <linux/slab.h> #include <sound/core.h> #include <sound/dmaengine_pcm.h> @@ -22,34 +23,6 @@ #include "fsl_sai.h" -static inline u32 sai_readl(struct fsl_sai *sai, - const void __iomem *addr) -{ - u32 val; - - val = __raw_readl(addr); - - if (likely(sai->big_endian_regs)) - val = be32_to_cpu(val); - else - val = le32_to_cpu(val); - rmb(); - - return val; -} - -static inline void sai_writel(struct fsl_sai *sai, - u32 val, void __iomem *addr) -{ - wmb(); - if (likely(sai->big_endian_regs)) - val = cpu_to_be32(val); - else - val = cpu_to_le32(val); - - __raw_writel(val, addr); -} - static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int fsl_dir) { @@ -61,7 +34,8 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, else reg_cr2 = FSL_SAI_RCR2; - val_cr2 = sai_readl(sai, sai->base + reg_cr2); + regmap_read(sai->regmap, reg_cr2, &val_cr2); + val_cr2 &= ~FSL_SAI_CR2_MSEL_MASK; switch (clk_id) { @@ -81,7 +55,7 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, return -EINVAL; } - sai_writel(sai, val_cr2, sai->base + reg_cr2); + regmap_write(sai->regmap, reg_cr2, val_cr2); return 0; } @@ -89,32 +63,22 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, 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 = clk_prepare_enable(sai->clk); - if (ret) - return ret; - ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, FSL_FMT_TRANSMITTER); if (ret) { dev_err(cpu_dai->dev, "Cannot set tx sysclk: %d\n", ret); - goto err_clk; + return ret; } ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, FSL_FMT_RECEIVER); - if (ret) { + if (ret) dev_err(cpu_dai->dev, "Cannot set rx sysclk: %d\n", ret); - goto err_clk; - } - -err_clk: - clk_disable_unprepare(sai->clk); return ret; } @@ -133,43 +97,84 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, reg_cr4 = FSL_SAI_RCR4; } - val_cr2 = sai_readl(sai, sai->base + reg_cr2); - val_cr4 = sai_readl(sai, sai->base + reg_cr4); + regmap_read(sai->regmap, reg_cr2, &val_cr2); + regmap_read(sai->regmap, reg_cr4, &val_cr4); if (sai->big_endian_data) val_cr4 &= ~FSL_SAI_CR4_MF; else val_cr4 |= FSL_SAI_CR4_MF; + /* DAI mode */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: + /* + * Frame low, 1clk before data, one word length for frame sync, + * frame sync starts one serial clock cycle earlier, + * that is, together with the last bit of the previous + * data word. + */ + val_cr2 &= ~FSL_SAI_CR2_BCP; + val_cr4 |= FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* + * Frame high, one word length for frame sync, + * frame sync asserts with the first bit of the frame. + */ + val_cr2 &= ~FSL_SAI_CR2_BCP; + val_cr4 &= ~(FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP); + break; + case SND_SOC_DAIFMT_DSP_A: + /* + * Frame high, 1clk before data, one bit for frame sync, + * frame sync starts one serial clock cycle earlier, + * that is, together with the last bit of the previous + * data word. + */ + val_cr2 &= ~FSL_SAI_CR2_BCP; + val_cr4 &= ~FSL_SAI_CR4_FSP; val_cr4 |= FSL_SAI_CR4_FSE; + sai->is_dsp_mode = true; + break; + case SND_SOC_DAIFMT_DSP_B: + /* + * Frame high, one bit for frame sync, + * frame sync asserts with the first bit of the frame. + */ + val_cr2 &= ~FSL_SAI_CR2_BCP; + val_cr4 &= ~(FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP); + sai->is_dsp_mode = true; break; + case SND_SOC_DAIFMT_RIGHT_J: + /* To be done */ default: return -EINVAL; } + /* DAI clock inversion */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_IB_IF: - val_cr4 |= FSL_SAI_CR4_FSP; - val_cr2 &= ~FSL_SAI_CR2_BCP; + /* Invert both clocks */ + val_cr2 ^= FSL_SAI_CR2_BCP; + val_cr4 ^= FSL_SAI_CR4_FSP; break; case SND_SOC_DAIFMT_IB_NF: - val_cr4 &= ~FSL_SAI_CR4_FSP; - val_cr2 &= ~FSL_SAI_CR2_BCP; + /* Invert bit clock */ + val_cr2 ^= FSL_SAI_CR2_BCP; break; case SND_SOC_DAIFMT_NB_IF: - val_cr4 |= FSL_SAI_CR4_FSP; - val_cr2 |= FSL_SAI_CR2_BCP; + /* Invert frame clock */ + val_cr4 ^= FSL_SAI_CR4_FSP; break; case SND_SOC_DAIFMT_NB_NF: - val_cr4 &= ~FSL_SAI_CR4_FSP; - val_cr2 |= FSL_SAI_CR2_BCP; + /* Nothing to do for both normal cases */ break; default: return -EINVAL; } + /* DAI clock master masks */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; @@ -179,39 +184,37 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, val_cr2 &= ~FSL_SAI_CR2_BCD_MSTR; val_cr4 &= ~FSL_SAI_CR4_FSD_MSTR; break; + case SND_SOC_DAIFMT_CBS_CFM: + val_cr2 |= FSL_SAI_CR2_BCD_MSTR; + val_cr4 &= ~FSL_SAI_CR4_FSD_MSTR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + val_cr2 &= ~FSL_SAI_CR2_BCD_MSTR; + val_cr4 |= FSL_SAI_CR4_FSD_MSTR; + break; default: return -EINVAL; } - sai_writel(sai, val_cr2, sai->base + reg_cr2); - sai_writel(sai, val_cr4, sai->base + reg_cr4); + regmap_write(sai->regmap, reg_cr2, val_cr2); + regmap_write(sai->regmap, reg_cr4, val_cr4); return 0; } static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { - struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); int ret; - ret = clk_prepare_enable(sai->clk); - if (ret) - return ret; - ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER); if (ret) { dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret); - goto err_clk; + return ret; } ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER); - if (ret) { + if (ret) dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret); - goto err_clk; - } - -err_clk: - clk_disable_unprepare(sai->clk); return ret; } @@ -235,16 +238,19 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, reg_mr = FSL_SAI_RMR; } - val_cr4 = sai_readl(sai, sai->base + reg_cr4); + regmap_read(sai->regmap, reg_cr4, &val_cr4); + regmap_read(sai->regmap, reg_cr4, &val_cr5); + val_cr4 &= ~FSL_SAI_CR4_SYWD_MASK; val_cr4 &= ~FSL_SAI_CR4_FRSZ_MASK; - val_cr5 = sai_readl(sai, sai->base + reg_cr5); val_cr5 &= ~FSL_SAI_CR5_WNW_MASK; val_cr5 &= ~FSL_SAI_CR5_W0W_MASK; val_cr5 &= ~FSL_SAI_CR5_FBT_MASK; - val_cr4 |= FSL_SAI_CR4_SYWD(word_width); + if (!sai->is_dsp_mode) + val_cr4 |= FSL_SAI_CR4_SYWD(word_width); + val_cr5 |= FSL_SAI_CR5_WNW(word_width); val_cr5 |= FSL_SAI_CR5_W0W(word_width); @@ -257,9 +263,9 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, val_cr4 |= FSL_SAI_CR4_FRSZ(channels); val_mr = ~0UL - ((1 << channels) - 1); - sai_writel(sai, val_cr4, sai->base + reg_cr4); - sai_writel(sai, val_cr5, sai->base + reg_cr5); - sai_writel(sai, val_mr, sai->base + reg_mr); + regmap_write(sai->regmap, reg_cr4, val_cr4); + regmap_write(sai->regmap, reg_cr5, val_cr5); + regmap_write(sai->regmap, reg_mr, val_mr); return 0; } @@ -268,44 +274,42 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); - u32 tcsr, rcsr, val_cr2, val_cr3, reg_cr3; - - val_cr2 = sai_readl(sai, sai->base + FSL_SAI_TCR2); - val_cr2 &= ~FSL_SAI_CR2_SYNC; - sai_writel(sai, val_cr2, sai->base + FSL_SAI_TCR2); + u32 tcsr, rcsr; - val_cr2 = sai_readl(sai, sai->base + FSL_SAI_RCR2); - val_cr2 |= FSL_SAI_CR2_SYNC; - sai_writel(sai, val_cr2, sai->base + FSL_SAI_RCR2); + /* + * The transmitter bit clock and frame sync are to be + * used by both the transmitter and receiver. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC, + ~FSL_SAI_CR2_SYNC); + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC, + FSL_SAI_CR2_SYNC); - tcsr = sai_readl(sai, sai->base + FSL_SAI_TCSR); - rcsr = sai_readl(sai, sai->base + FSL_SAI_RCSR); + regmap_read(sai->regmap, FSL_SAI_TCSR, &tcsr); + regmap_read(sai->regmap, FSL_SAI_RCSR, &rcsr); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { tcsr |= FSL_SAI_CSR_FRDE; rcsr &= ~FSL_SAI_CSR_FRDE; - reg_cr3 = FSL_SAI_TCR3; } else { rcsr |= FSL_SAI_CSR_FRDE; tcsr &= ~FSL_SAI_CSR_FRDE; - reg_cr3 = FSL_SAI_RCR3; } - val_cr3 = sai_readl(sai, sai->base + reg_cr3); - + /* + * It is recommended that the transmitter is the last enabled + * and the first disabled. + */ switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: tcsr |= FSL_SAI_CSR_TERE; rcsr |= FSL_SAI_CSR_TERE; - val_cr3 |= FSL_SAI_CR3_TRCE; - sai_writel(sai, val_cr3, sai->base + reg_cr3); - sai_writel(sai, rcsr, sai->base + FSL_SAI_RCSR); - sai_writel(sai, tcsr, sai->base + FSL_SAI_TCSR); + regmap_write(sai->regmap, FSL_SAI_RCSR, rcsr); + regmap_write(sai->regmap, FSL_SAI_TCSR, tcsr); break; - case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: @@ -314,11 +318,8 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, rcsr &= ~FSL_SAI_CSR_TERE; } - val_cr3 &= ~FSL_SAI_CR3_TRCE; - - sai_writel(sai, tcsr, sai->base + FSL_SAI_TCSR); - sai_writel(sai, rcsr, sai->base + FSL_SAI_RCSR); - sai_writel(sai, val_cr3, sai->base + reg_cr3); + regmap_write(sai->regmap, FSL_SAI_TCSR, tcsr); + regmap_write(sai->regmap, FSL_SAI_RCSR, rcsr); break; default: return -EINVAL; @@ -331,16 +332,32 @@ static int fsl_sai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = FSL_SAI_TCR3; + else + reg = FSL_SAI_RCR3; + + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR3_TRCE, + FSL_SAI_CR3_TRCE); - return clk_prepare_enable(sai->clk); + return 0; } static void fsl_sai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = FSL_SAI_TCR3; + else + reg = FSL_SAI_RCR3; - clk_disable_unprepare(sai->clk); + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR3_TRCE, + ~FSL_SAI_CR3_TRCE); } static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { @@ -355,18 +372,13 @@ static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) { struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); - int ret; - ret = clk_prepare_enable(sai->clk); - if (ret) - return ret; - - sai_writel(sai, 0x0, sai->base + FSL_SAI_RCSR); - sai_writel(sai, 0x0, sai->base + FSL_SAI_TCSR); - sai_writel(sai, FSL_SAI_MAXBURST_TX * 2, sai->base + FSL_SAI_TCR1); - sai_writel(sai, FSL_SAI_MAXBURST_RX - 1, sai->base + FSL_SAI_RCR1); - - clk_disable_unprepare(sai->clk); + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, 0xffffffff, 0x0); + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, 0xffffffff, 0x0); + regmap_update_bits(sai->regmap, FSL_SAI_TCR1, FSL_SAI_CR1_RFW_MASK, + FSL_SAI_MAXBURST_TX * 2); + regmap_update_bits(sai->regmap, FSL_SAI_RCR1, FSL_SAI_CR1_RFW_MASK, + FSL_SAI_MAXBURST_RX - 1); snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx, &sai->dma_params_rx); @@ -397,26 +409,109 @@ static const struct snd_soc_component_driver fsl_component = { .name = "fsl-sai", }; +static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TFR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RDR: + case FSL_SAI_RFR: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TFR: + case FSL_SAI_RFR: + case FSL_SAI_TDR: + case FSL_SAI_RDR: + return true; + default: + return false; + } + +} + +static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TDR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static struct regmap_config fsl_sai_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FSL_SAI_RMR, + .readable_reg = fsl_sai_readable_reg, + .volatile_reg = fsl_sai_volatile_reg, + .writeable_reg = fsl_sai_writeable_reg, +}; + static int fsl_sai_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct fsl_sai *sai; struct resource *res; + void __iomem *base; int ret; sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); if (!sai) return -ENOMEM; + sai->big_endian_regs = of_property_read_bool(np, "big-endian-regs"); + if (sai->big_endian_regs) + fsl_sai_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG; + + sai->big_endian_data = of_property_read_bool(np, "big-endian-data"); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - sai->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(sai->base)) - return PTR_ERR(sai->base); - - sai->clk = devm_clk_get(&pdev->dev, "sai"); - if (IS_ERR(sai->clk)) { - dev_err(&pdev->dev, "Cannot get SAI's clock\n"); - return PTR_ERR(sai->clk); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "sai", base, &fsl_sai_regmap_config); + if (IS_ERR(sai->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(sai->regmap); } sai->dma_params_rx.addr = res->start + FSL_SAI_RDR; @@ -424,9 +519,6 @@ static int fsl_sai_probe(struct platform_device *pdev) sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX; sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX; - sai->big_endian_regs = of_property_read_bool(np, "big-endian-regs"); - sai->big_endian_data = of_property_read_bool(np, "big-endian-data"); - platform_set_drvdata(pdev, sai); ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, |