ASoC: rockchip: i2s: increase dma maxburst to 16
[firefly-linux-kernel-4.4.55.git] / sound / soc / rockchip / rockchip_i2s.c
index 58ee64594f075efb7327d1480113f1401e97f248..d7aa635e3693b684f32e1909d1bf5df7ed6fe52f 100644 (file)
  */
 
 #include <linux/module.h>
+#include <linux/mfd/syscon.h>
 #include <linux/delay.h>
 #include <linux/of_gpio.h>
+#include <linux/of_device.h>
 #include <linux/clk.h>
 #include <linux/pm_runtime.h>
 #include <linux/regmap.h>
 
 #define DRV_NAME "rockchip-i2s"
 
+struct rk_i2s_pins {
+       u32 reg_offset;
+       u32 shift;
+};
+
 struct rk_i2s_dev {
        struct device *dev;
 
@@ -33,6 +40,7 @@ struct rk_i2s_dev {
        struct snd_dmaengine_dai_dma_data playback_dma_data;
 
        struct regmap *regmap;
+       struct regmap *grf;
 
 /*
  * Used to indicate the tx/rx status.
@@ -41,12 +49,16 @@ struct rk_i2s_dev {
 */
        bool tx_start;
        bool rx_start;
+       bool is_master_mode;
+       const struct rk_i2s_pins *pins;
+       unsigned int bclk_fs;
 };
 
 static int i2s_runtime_suspend(struct device *dev)
 {
        struct rk_i2s_dev *i2s = dev_get_drvdata(dev);
 
+       regcache_cache_only(i2s->regmap, true);
        clk_disable_unprepare(i2s->mclk);
 
        return 0;
@@ -63,7 +75,14 @@ static int i2s_runtime_resume(struct device *dev)
                return ret;
        }
 
-       return 0;
+       regcache_cache_only(i2s->regmap, false);
+       regcache_mark_dirty(i2s->regmap);
+
+       ret = regcache_sync(i2s->regmap);
+       if (ret)
+               clk_disable_unprepare(i2s->mclk);
+
+       return ret;
 }
 
 static inline struct rk_i2s_dev *to_info(struct snd_soc_dai *dai)
@@ -98,6 +117,7 @@ static void rockchip_snd_txctrl(struct rk_i2s_dev *i2s, int on)
                                           I2S_XFER_TXS_STOP |
                                           I2S_XFER_RXS_STOP);
 
+                       udelay(150);
                        regmap_update_bits(i2s->regmap, I2S_CLR,
                                           I2S_CLR_TXC | I2S_CLR_RXC,
                                           I2S_CLR_TXC | I2S_CLR_RXC);
@@ -144,6 +164,7 @@ static void rockchip_snd_rxctrl(struct rk_i2s_dev *i2s, int on)
                                           I2S_XFER_TXS_STOP |
                                           I2S_XFER_RXS_STOP);
 
+                       udelay(150);
                        regmap_update_bits(i2s->regmap, I2S_CLR,
                                           I2S_CLR_TXC | I2S_CLR_RXC,
                                           I2S_CLR_TXC | I2S_CLR_RXC);
@@ -174,9 +195,11 @@ static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
        case SND_SOC_DAIFMT_CBS_CFS:
                /* Set source clock in Master mode */
                val = I2S_CKR_MSS_MASTER;
+               i2s->is_master_mode = true;
                break;
        case SND_SOC_DAIFMT_CBM_CFM:
                val = I2S_CKR_MSS_SLAVE;
+               i2s->is_master_mode = false;
                break;
        default:
                return -EINVAL;
@@ -184,7 +207,21 @@ static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
 
        regmap_update_bits(i2s->regmap, I2S_CKR, mask, val);
 
-       mask = I2S_TXCR_IBM_MASK;
+       mask = I2S_CKR_CKP_MASK;
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       case SND_SOC_DAIFMT_NB_NF:
+               val = I2S_CKR_CKP_NEG;
+               break;
+       case SND_SOC_DAIFMT_IB_NF:
+               val = I2S_CKR_CKP_POS;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       regmap_update_bits(i2s->regmap, I2S_CKR, mask, val);
+
+       mask = I2S_TXCR_IBM_MASK | I2S_TXCR_TFS_MASK | I2S_TXCR_PBM_MASK;
        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
        case SND_SOC_DAIFMT_RIGHT_J:
                val = I2S_TXCR_IBM_RSJM;
@@ -195,13 +232,19 @@ static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
        case SND_SOC_DAIFMT_I2S:
                val = I2S_TXCR_IBM_NORMAL;
                break;
+       case SND_SOC_DAIFMT_DSP_A: /* PCM no delay mode */
+               val = I2S_TXCR_TFS_PCM;
+               break;
+       case SND_SOC_DAIFMT_DSP_B: /* PCM delay 1 mode */
+               val = I2S_TXCR_TFS_PCM | I2S_TXCR_PBM_MODE(1);
+               break;
        default:
                return -EINVAL;
        }
 
        regmap_update_bits(i2s->regmap, I2S_TXCR, mask, val);
 
-       mask = I2S_RXCR_IBM_MASK;
+       mask = I2S_RXCR_IBM_MASK | I2S_RXCR_TFS_MASK | I2S_RXCR_PBM_MASK;
        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
        case SND_SOC_DAIFMT_RIGHT_J:
                val = I2S_RXCR_IBM_RSJM;
@@ -212,6 +255,12 @@ static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
        case SND_SOC_DAIFMT_I2S:
                val = I2S_RXCR_IBM_NORMAL;
                break;
+       case SND_SOC_DAIFMT_DSP_A: /* PCM no delay mode */
+               val = I2S_RXCR_TFS_PCM;
+               break;
+       case SND_SOC_DAIFMT_DSP_B: /* PCM delay 1 mode */
+               val = I2S_RXCR_TFS_PCM | I2S_RXCR_PBM_MODE(1);
+               break;
        default:
                return -EINVAL;
        }
@@ -228,6 +277,26 @@ static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream,
        struct rk_i2s_dev *i2s = to_info(dai);
        struct snd_soc_pcm_runtime *rtd = substream->private_data;
        unsigned int val = 0;
+       unsigned int mclk_rate, bclk_rate, div_bclk, div_lrck;
+
+       if (i2s->is_master_mode) {
+               mclk_rate = clk_get_rate(i2s->mclk);
+               bclk_rate = i2s->bclk_fs * params_rate(params);
+               if (bclk_rate && mclk_rate % bclk_rate)
+                       return -EINVAL;
+
+               div_bclk = mclk_rate / bclk_rate;
+               div_lrck = bclk_rate / params_rate(params);
+               regmap_update_bits(i2s->regmap, I2S_CKR,
+                                  I2S_CKR_MDIV_MASK,
+                                  I2S_CKR_MDIV(div_bclk));
+
+               regmap_update_bits(i2s->regmap, I2S_CKR,
+                                  I2S_CKR_TSD_MASK |
+                                  I2S_CKR_RSD_MASK,
+                                  I2S_CKR_TSD(div_lrck) |
+                                  I2S_CKR_RSD(div_lrck));
+       }
 
        switch (params_format(params)) {
        case SNDRV_PCM_FORMAT_S8:
@@ -242,6 +311,9 @@ static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream,
        case SNDRV_PCM_FORMAT_S24_LE:
                val |= I2S_TXCR_VDW(24);
                break;
+       case SNDRV_PCM_FORMAT_S32_LE:
+               val |= I2S_TXCR_VDW(32);
+               break;
        default:
                return -EINVAL;
        }
@@ -274,14 +346,38 @@ static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream,
                                   I2S_TXCR_VDW_MASK | I2S_TXCR_CSR_MASK,
                                   val);
 
+       if (!IS_ERR(i2s->grf) && i2s->pins) {
+               regmap_read(i2s->regmap, I2S_TXCR, &val);
+               val &= I2S_TXCR_CSR_MASK;
+
+               switch (val) {
+               case I2S_CHN_4:
+                       val = I2S_IO_4CH_OUT_6CH_IN;
+                       break;
+               case I2S_CHN_6:
+                       val = I2S_IO_6CH_OUT_4CH_IN;
+                       break;
+               case I2S_CHN_8:
+                       val = I2S_IO_8CH_OUT_2CH_IN;
+                       break;
+               default:
+                       val = I2S_IO_2CH_OUT_8CH_IN;
+                       break;
+               }
+
+               val <<= i2s->pins->shift;
+               val |= (I2S_IO_DIRECTION_MASK << i2s->pins->shift) << 16;
+               regmap_write(i2s->grf, i2s->pins->reg_offset, val);
+       }
+
        regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_TDL_MASK,
                           I2S_DMACR_TDL(16));
        regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_RDL_MASK,
                           I2S_DMACR_RDL(16));
 
        val = I2S_CKR_TRCM_TXRX;
-       if (dai->driver->symmetric_rates || rtd->dai_link->symmetric_rates)
-               val = I2S_CKR_TRCM_TXSHARE;
+       if (dai->driver->symmetric_rates && rtd->dai_link->symmetric_rates)
+               val = I2S_CKR_TRCM_TXONLY;
 
        regmap_update_bits(i2s->regmap, I2S_CKR,
                           I2S_CKR_TRCM_MASK,
@@ -360,7 +456,8 @@ static struct snd_soc_dai_driver rockchip_i2s_dai = {
                .formats = (SNDRV_PCM_FMTBIT_S8 |
                            SNDRV_PCM_FMTBIT_S16_LE |
                            SNDRV_PCM_FMTBIT_S20_3LE |
-                           SNDRV_PCM_FMTBIT_S24_LE),
+                           SNDRV_PCM_FMTBIT_S24_LE |
+                           SNDRV_PCM_FMTBIT_S32_LE),
        },
        .capture = {
                .stream_name = "Capture",
@@ -370,7 +467,8 @@ static struct snd_soc_dai_driver rockchip_i2s_dai = {
                .formats = (SNDRV_PCM_FMTBIT_S8 |
                            SNDRV_PCM_FMTBIT_S16_LE |
                            SNDRV_PCM_FMTBIT_S20_3LE |
-                           SNDRV_PCM_FMTBIT_S24_LE),
+                           SNDRV_PCM_FMTBIT_S24_LE |
+                           SNDRV_PCM_FMTBIT_S32_LE),
        },
        .ops = &rockchip_i2s_dai_ops,
        .symmetric_rates = 1,
@@ -435,11 +533,21 @@ static bool rockchip_i2s_precious_reg(struct device *dev, unsigned int reg)
        }
 }
 
+static const struct reg_default rockchip_i2s_reg_defaults[] = {
+       {0x00, 0x0000000f},
+       {0x04, 0x0000000f},
+       {0x08, 0x00071f1f},
+       {0x10, 0x001f0000},
+       {0x14, 0x01f00000},
+};
+
 static const struct regmap_config rockchip_i2s_regmap_config = {
        .reg_bits = 32,
        .reg_stride = 4,
        .val_bits = 32,
        .max_register = I2S_RXDR,
+       .reg_defaults = rockchip_i2s_reg_defaults,
+       .num_reg_defaults = ARRAY_SIZE(rockchip_i2s_reg_defaults),
        .writeable_reg = rockchip_i2s_wr_reg,
        .readable_reg = rockchip_i2s_rd_reg,
        .volatile_reg = rockchip_i2s_volatile_reg,
@@ -447,10 +555,25 @@ static const struct regmap_config rockchip_i2s_regmap_config = {
        .cache_type = REGCACHE_FLAT,
 };
 
+static const struct rk_i2s_pins rk3399_i2s_pins = {
+       .reg_offset = 0xe220,
+       .shift = 11,
+};
+
+static const struct of_device_id rockchip_i2s_match[] = {
+       { .compatible = "rockchip,rk3066-i2s", },
+       { .compatible = "rockchip,rk3188-i2s", },
+       { .compatible = "rockchip,rk3288-i2s", },
+       { .compatible = "rockchip,rk3399-i2s", .data = &rk3399_i2s_pins },
+       {},
+};
+
 static int rockchip_i2s_probe(struct platform_device *pdev)
 {
        struct device_node *node = pdev->dev.of_node;
+       const struct of_device_id *of_id;
        struct rk_i2s_dev *i2s;
+       struct snd_soc_dai_driver *soc_dai;
        struct resource *res;
        void __iomem *regs;
        int ret;
@@ -462,6 +585,17 @@ static int rockchip_i2s_probe(struct platform_device *pdev)
                return -ENOMEM;
        }
 
+       i2s->dev = &pdev->dev;
+
+       i2s->grf = syscon_regmap_lookup_by_phandle(node, "rockchip,grf");
+       if (!IS_ERR(i2s->grf)) {
+               of_id = of_match_device(rockchip_i2s_match, &pdev->dev);
+               if (!of_id || !of_id->data)
+                       return -EINVAL;
+
+               i2s->pins = of_id->data;
+       }
+
        /* try to prepare related clocks */
        i2s->hclk = devm_clk_get(&pdev->dev, "i2s_hclk");
        if (IS_ERR(i2s->hclk)) {
@@ -495,13 +629,12 @@ static int rockchip_i2s_probe(struct platform_device *pdev)
 
        i2s->playback_dma_data.addr = res->start + I2S_TXDR;
        i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
-       i2s->playback_dma_data.maxburst = 4;
+       i2s->playback_dma_data.maxburst = 16;
 
        i2s->capture_dma_data.addr = res->start + I2S_RXDR;
        i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
-       i2s->capture_dma_data.maxburst = 4;
+       i2s->capture_dma_data.maxburst = 16;
 
-       i2s->dev = &pdev->dev;
        dev_set_drvdata(&pdev->dev, i2s);
 
        pm_runtime_enable(&pdev->dev);
@@ -511,17 +644,32 @@ static int rockchip_i2s_probe(struct platform_device *pdev)
                        goto err_pm_disable;
        }
 
-       /* refine capture channels */
+       soc_dai = devm_kzalloc(&pdev->dev,
+                              sizeof(*soc_dai), GFP_KERNEL);
+       if (!soc_dai)
+               return -ENOMEM;
+
+       memcpy(soc_dai, &rockchip_i2s_dai, sizeof(*soc_dai));
+       if (!of_property_read_u32(node, "rockchip,playback-channels", &val)) {
+               if (val >= 2 && val <= 8)
+                       soc_dai->playback.channels_max = val;
+       }
+
        if (!of_property_read_u32(node, "rockchip,capture-channels", &val)) {
                if (val >= 2 && val <= 8)
-                       rockchip_i2s_dai.capture.channels_max = val;
-               else
-                       rockchip_i2s_dai.capture.channels_max = 2;
+                       soc_dai->capture.channels_max = val;
+       }
+
+       i2s->bclk_fs = 64;
+       if (!of_property_read_u32(node, "rockchip,bclk-fs", &val)) {
+               if ((val >= 32) && (val % 2 == 0))
+                       i2s->bclk_fs = val;
        }
 
        ret = devm_snd_soc_register_component(&pdev->dev,
                                              &rockchip_i2s_component,
-                                             &rockchip_i2s_dai, 1);
+                                             soc_dai, 1);
+
        if (ret) {
                dev_err(&pdev->dev, "Could not register DAI\n");
                goto err_suspend;
@@ -558,14 +706,35 @@ static int rockchip_i2s_remove(struct platform_device *pdev)
        return 0;
 }
 
-static const struct of_device_id rockchip_i2s_match[] = {
-       { .compatible = "rockchip,rk3066-i2s", },
-       {},
-};
+#ifdef CONFIG_PM_SLEEP
+static int rockchip_i2s_suspend(struct device *dev)
+{
+       struct rk_i2s_dev *i2s = dev_get_drvdata(dev);
+
+       regcache_mark_dirty(i2s->regmap);
+
+       return 0;
+}
+
+static int rockchip_i2s_resume(struct device *dev)
+{
+       struct rk_i2s_dev *i2s = dev_get_drvdata(dev);
+       int ret;
+
+       ret = pm_runtime_get_sync(dev);
+       if (ret < 0)
+               return ret;
+       ret = regcache_sync(i2s->regmap);
+       pm_runtime_put(dev);
+
+       return ret;
+}
+#endif
 
 static const struct dev_pm_ops rockchip_i2s_pm_ops = {
        SET_RUNTIME_PM_OPS(i2s_runtime_suspend, i2s_runtime_resume,
                           NULL)
+       SET_SYSTEM_SLEEP_PM_OPS(rockchip_i2s_suspend, rockchip_i2s_resume)
 };
 
 static struct platform_driver rockchip_i2s_driver = {