Merge branch 'for-5.0' of https://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[sfrench/cifs-2.6.git] / sound / soc / samsung / i2s.c
index d4bde4834ce5f1b581f4febe5fb0341ab609b321..2736209144710730fbfb6370a93b0ad020a61720 100644 (file)
@@ -34,6 +34,9 @@
 
 #define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
 
+#define SAMSUNG_I2S_ID_PRIMARY         1
+#define SAMSUNG_I2S_ID_SECONDARY       2
+
 struct samsung_i2s_variant_regs {
        unsigned int    bfs_off;
        unsigned int    rfs_off;
@@ -79,8 +82,10 @@ struct i2s_dai {
 #define DAI_OPENED     (1 << 0) /* Dai is opened */
 #define DAI_MANAGER    (1 << 1) /* Dai is the manager */
        unsigned mode;
+
        /* Driver for this DAI */
-       struct snd_soc_dai_driver i2s_dai_drv;
+       struct snd_soc_dai_driver *drv;
+
        /* DMA parameters */
        struct snd_dmaengine_dai_dma_data dma_playback;
        struct snd_dmaengine_dai_dma_data dma_capture;
@@ -92,8 +97,6 @@ struct i2s_dai {
        u32     suspend_i2spsr;
        const struct samsung_i2s_variant_regs *variant_regs;
 
-       /* Spinlock protecting access to the device's registers */
-       spinlock_t spinlock;
        spinlock_t *lock;
 
        /* Below fields are only valid if this is the primary FIFO */
@@ -104,10 +107,30 @@ struct i2s_dai {
 /* Lock for cross i/f checks */
 static DEFINE_SPINLOCK(lock);
 
-/* If this is the 'overlay' stereo DAI */
+struct samsung_i2s_priv {
+       struct platform_device *pdev;
+       struct platform_device *pdev_sec;
+
+       /* Spinlock protecting access to the device's registers */
+       spinlock_t spinlock;
+
+       /* CPU DAIs and their corresponding drivers */
+       struct i2s_dai *dai;
+       struct snd_soc_dai_driver *dai_drv;
+       int num_dais;
+};
+
+struct i2s_dai *samsung_i2s_get_pri_dai(struct device *dev)
+{
+       struct samsung_i2s_priv *priv = dev_get_drvdata(dev);
+
+       return &priv->dai[SAMSUNG_I2S_ID_PRIMARY - 1];
+}
+
+/* Returns true if this is the 'overlay' stereo DAI */
 static inline bool is_secondary(struct i2s_dai *i2s)
 {
-       return i2s->pri_dai ? true : false;
+       return i2s->drv->id == SAMSUNG_I2S_ID_SECONDARY;
 }
 
 /* If operating in SoC-Slave mode */
@@ -202,7 +225,9 @@ static inline bool any_active(struct i2s_dai *i2s)
 
 static inline struct i2s_dai *to_info(struct snd_soc_dai *dai)
 {
-       return snd_soc_dai_get_drvdata(dai);
+       struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+
+       return &priv->dai[dai->id - 1];
 }
 
 static inline bool is_opened(struct i2s_dai *i2s)
@@ -1065,7 +1090,7 @@ static int samsung_i2s_dai_probe(struct snd_soc_dai *dai)
 
 static int samsung_i2s_dai_remove(struct snd_soc_dai *dai)
 {
-       struct i2s_dai *i2s = snd_soc_dai_get_drvdata(dai);
+       struct i2s_dai *i2s = to_info(dai);
        unsigned long flags;
 
        pm_runtime_get_sync(dai->dev);
@@ -1102,47 +1127,63 @@ static const struct snd_soc_component_driver samsung_i2s_component = {
                                        SNDRV_PCM_FMTBIT_S16_LE | \
                                        SNDRV_PCM_FMTBIT_S24_LE)
 
-static struct i2s_dai *i2s_alloc_dai(struct platform_device *pdev,
-                               const struct samsung_i2s_dai_data *i2s_dai_data,
-                               bool sec)
+static int i2s_alloc_dais(struct samsung_i2s_priv *priv,
+                         const struct samsung_i2s_dai_data *i2s_dai_data,
+                         int num_dais)
 {
-       struct i2s_dai *i2s;
-
-       i2s = devm_kzalloc(&pdev->dev, sizeof(struct i2s_dai), GFP_KERNEL);
-       if (i2s == NULL)
-               return NULL;
-
-       i2s->pdev = pdev;
-       i2s->pri_dai = NULL;
-       i2s->sec_dai = NULL;
-       i2s->i2s_dai_drv.id = 1;
-       i2s->i2s_dai_drv.symmetric_rates = 1;
-       i2s->i2s_dai_drv.probe = samsung_i2s_dai_probe;
-       i2s->i2s_dai_drv.remove = samsung_i2s_dai_remove;
-       i2s->i2s_dai_drv.ops = &samsung_i2s_dai_ops;
-       i2s->i2s_dai_drv.suspend = i2s_suspend;
-       i2s->i2s_dai_drv.resume = i2s_resume;
-       i2s->i2s_dai_drv.playback.channels_min = 1;
-       i2s->i2s_dai_drv.playback.channels_max = 2;
-       i2s->i2s_dai_drv.playback.rates = i2s_dai_data->pcm_rates;
-       i2s->i2s_dai_drv.playback.formats = SAMSUNG_I2S_FMTS;
-
-       if (!sec) {
-               i2s->i2s_dai_drv.name = SAMSUNG_I2S_DAI;
-               i2s->i2s_dai_drv.capture.channels_min = 1;
-               i2s->i2s_dai_drv.capture.channels_max = 2;
-               i2s->i2s_dai_drv.capture.rates = i2s_dai_data->pcm_rates;
-               i2s->i2s_dai_drv.capture.formats = SAMSUNG_I2S_FMTS;
-       } else {
-               i2s->i2s_dai_drv.name = SAMSUNG_I2S_DAI_SEC;
+       static const char *dai_names[] = { "samsung-i2s", "samsung-i2s-sec" };
+       struct snd_soc_dai_driver *dai_drv;
+       struct i2s_dai *dai;
+       int i;
+
+       priv->dai = devm_kcalloc(&priv->pdev->dev, num_dais,
+                                    sizeof(*dai), GFP_KERNEL);
+       if (!priv->dai)
+               return -ENOMEM;
+
+       priv->dai_drv = devm_kcalloc(&priv->pdev->dev, num_dais,
+                                    sizeof(*dai_drv), GFP_KERNEL);
+       if (!priv->dai_drv)
+               return -ENOMEM;
+
+       for (i = 0; i < num_dais; i++) {
+               dai_drv = &priv->dai_drv[i];
+
+               dai_drv->probe = samsung_i2s_dai_probe;
+               dai_drv->remove = samsung_i2s_dai_remove;
+               dai_drv->suspend = i2s_suspend;
+               dai_drv->resume = i2s_resume;
+
+               dai_drv->symmetric_rates = 1;
+               dai_drv->ops = &samsung_i2s_dai_ops;
+
+               dai_drv->playback.channels_min = 1;
+               dai_drv->playback.channels_max = 2;
+               dai_drv->playback.rates = i2s_dai_data->pcm_rates;
+               dai_drv->playback.formats = SAMSUNG_I2S_FMTS;
+
+               dai_drv->id = i + 1;
+               dai_drv->name = dai_names[i];
+
+               priv->dai[i].drv = &priv->dai_drv[i];
+               priv->dai[i].pdev = priv->pdev;
        }
-       return i2s;
+
+       /* Initialize capture only for the primary DAI */
+       dai_drv = &priv->dai_drv[SAMSUNG_I2S_ID_PRIMARY - 1];
+
+       dai_drv->capture.channels_min = 1;
+       dai_drv->capture.channels_max = 2;
+       dai_drv->capture.rates = i2s_dai_data->pcm_rates;
+       dai_drv->capture.formats = SAMSUNG_I2S_FMTS;
+
+       return 0;
 }
 
 #ifdef CONFIG_PM
 static int i2s_runtime_suspend(struct device *dev)
 {
-       struct i2s_dai *i2s = dev_get_drvdata(dev);
+       struct i2s_dai *i2s = samsung_i2s_get_pri_dai(dev);
 
        i2s->suspend_i2smod = readl(i2s->addr + I2SMOD);
        i2s->suspend_i2scon = readl(i2s->addr + I2SCON);
@@ -1157,7 +1198,7 @@ static int i2s_runtime_suspend(struct device *dev)
 
 static int i2s_runtime_resume(struct device *dev)
 {
-       struct i2s_dai *i2s = dev_get_drvdata(dev);
+       struct i2s_dai *i2s = samsung_i2s_get_pri_dai(dev);
        int ret;
 
        ret = clk_prepare_enable(i2s->clk);
@@ -1192,7 +1233,7 @@ static void i2s_unregister_clocks(struct i2s_dai *i2s)
 
 static void i2s_unregister_clock_provider(struct platform_device *pdev)
 {
-       struct i2s_dai *i2s = dev_get_drvdata(&pdev->dev);
+       struct i2s_dai *i2s = samsung_i2s_get_pri_dai(&pdev->dev);
 
        of_clk_del_provider(pdev->dev.of_node);
        i2s_unregister_clocks(i2s);
@@ -1200,11 +1241,12 @@ static void i2s_unregister_clock_provider(struct platform_device *pdev)
 
 static int i2s_register_clock_provider(struct platform_device *pdev)
 {
+
        const char * const i2s_clk_desc[] = { "cdclk", "rclk_src", "prescaler" };
        const char *clk_name[2] = { "i2s_opclk0", "i2s_opclk1" };
        const char *p_names[2] = { NULL };
        struct device *dev = &pdev->dev;
-       struct i2s_dai *i2s = dev_get_drvdata(dev);
+       struct i2s_dai *i2s = samsung_i2s_get_pri_dai(dev);
        const struct samsung_i2s_variant_regs *reg_info = i2s->variant_regs;
        const char *i2s_clk_name[ARRAY_SIZE(i2s_clk_desc)];
        struct clk *rclksrc;
@@ -1271,6 +1313,34 @@ static int i2s_register_clock_provider(struct platform_device *pdev)
        return ret;
 }
 
+/* Create platform device for the secondary PCM */
+static int i2s_create_secondary_device(struct samsung_i2s_priv *priv)
+{
+       struct platform_device *pdev;
+       int ret;
+
+       pdev = platform_device_register_simple("samsung-i2s-sec", -1, NULL, 0);
+       if (!pdev)
+               return -ENOMEM;
+
+       ret = device_attach(&pdev->dev);
+       if (ret < 0) {
+               dev_info(&pdev->dev, "device_attach() failed\n");
+               return ret;
+       }
+
+       priv->pdev_sec = pdev;
+
+       return 0;
+}
+
+static void i2s_delete_secondary_device(struct samsung_i2s_priv *priv)
+{
+       if (priv->pdev_sec) {
+               platform_device_del(priv->pdev_sec);
+               priv->pdev_sec = NULL;
+       }
+}
 static int samsung_i2s_probe(struct platform_device *pdev)
 {
        struct i2s_dai *pri_dai, *sec_dai = NULL;
@@ -1279,7 +1349,8 @@ static int samsung_i2s_probe(struct platform_device *pdev)
        u32 regs_base, quirks = 0, idma_addr = 0;
        struct device_node *np = pdev->dev.of_node;
        const struct samsung_i2s_dai_data *i2s_dai_data;
-       int ret;
+       int num_dais, ret;
+       struct samsung_i2s_priv *priv;
 
        if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node)
                i2s_dai_data = of_device_get_match_data(&pdev->dev);
@@ -1287,14 +1358,26 @@ static int samsung_i2s_probe(struct platform_device *pdev)
                i2s_dai_data = (struct samsung_i2s_dai_data *)
                                platform_get_device_id(pdev)->driver_data;
 
-       pri_dai = i2s_alloc_dai(pdev, i2s_dai_data, false);
-       if (!pri_dai) {
-               dev_err(&pdev->dev, "Unable to alloc I2S_pri\n");
+       /* Nothing to do if it is the secondary device probe */
+       if (!i2s_dai_data)
+               return 0;
+
+       priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
                return -ENOMEM;
-       }
 
-       spin_lock_init(&pri_dai->spinlock);
-       pri_dai->lock = &pri_dai->spinlock;
+       quirks = np ? i2s_dai_data->quirks : i2s_pdata->type.quirks;
+       num_dais = (quirks & QUIRK_SEC_DAI) ? 2 : 1;
+       priv->pdev = pdev;
+
+       ret = i2s_alloc_dais(priv, i2s_dai_data, num_dais);
+       if (ret < 0)
+               return ret;
+
+       pri_dai = &priv->dai[SAMSUNG_I2S_ID_PRIMARY - 1];
+
+       spin_lock_init(&priv->spinlock);
+       pri_dai->lock = &priv->spinlock;
 
        if (!np) {
                if (i2s_pdata == NULL) {
@@ -1306,10 +1389,8 @@ static int samsung_i2s_probe(struct platform_device *pdev)
                pri_dai->dma_capture.filter_data = i2s_pdata->dma_capture;
                pri_dai->filter = i2s_pdata->dma_filter;
 
-               quirks = i2s_pdata->type.quirks;
                idma_addr = i2s_pdata->type.idma_addr;
        } else {
-               quirks = i2s_dai_data->quirks;
                if (of_property_read_u32(np, "samsung,idma-addr",
                                         &idma_addr)) {
                        if (quirks & QUIRK_SUPPORTS_IDMA) {
@@ -1318,7 +1399,6 @@ static int samsung_i2s_probe(struct platform_device *pdev)
                        }
                }
        }
-       quirks &= ~(QUIRK_SEC_DAI | QUIRK_SUPPORTS_IDMA);
 
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        pri_dai->addr = devm_ioremap_resource(&pdev->dev, res);
@@ -1348,28 +1428,17 @@ static int samsung_i2s_probe(struct platform_device *pdev)
        pri_dai->variant_regs = i2s_dai_data->i2s_variant_regs;
 
        if (quirks & QUIRK_PRI_6CHAN)
-               pri_dai->i2s_dai_drv.playback.channels_max = 6;
+               pri_dai->drv->playback.channels_max = 6;
 
        ret = samsung_asoc_dma_platform_register(&pdev->dev, pri_dai->filter,
-                                                NULL, NULL);
-       if (ret < 0)
-               goto err_disable_clk;
-
-       ret = devm_snd_soc_register_component(&pdev->dev,
-                                       &samsung_i2s_component,
-                                       &pri_dai->i2s_dai_drv, 1);
+                                                NULL, NULL, NULL);
        if (ret < 0)
                goto err_disable_clk;
 
        if (quirks & QUIRK_SEC_DAI) {
-               sec_dai = i2s_alloc_dai(pdev, i2s_dai_data, true);
-               if (!sec_dai) {
-                       dev_err(&pdev->dev, "Unable to alloc I2S_sec\n");
-                       ret = -ENOMEM;
-                       goto err_disable_clk;
-               }
+               sec_dai = &priv->dai[SAMSUNG_I2S_ID_SECONDARY - 1];
 
-               sec_dai->lock = &pri_dai->spinlock;
+               sec_dai->lock = &priv->spinlock;
                sec_dai->variant_regs = pri_dai->variant_regs;
                sec_dai->dma_playback.addr = regs_base + I2STXDS;
                sec_dai->dma_playback.chan_name = "tx-sec";
@@ -1387,16 +1456,16 @@ static int samsung_i2s_probe(struct platform_device *pdev)
                sec_dai->pri_dai = pri_dai;
                pri_dai->sec_dai = sec_dai;
 
-               ret = samsung_asoc_dma_platform_register(&pdev->dev,
-                                       sec_dai->filter, "tx-sec", NULL);
+               ret = i2s_create_secondary_device(priv);
                if (ret < 0)
                        goto err_disable_clk;
 
-               ret = devm_snd_soc_register_component(&pdev->dev,
-                                               &samsung_i2s_component,
-                                               &sec_dai->i2s_dai_drv, 1);
+               ret = samsung_asoc_dma_platform_register(&priv->pdev_sec->dev,
+                                               sec_dai->filter, "tx-sec", NULL,
+                                               &pdev->dev);
                if (ret < 0)
                        goto err_disable_clk;
+
        }
 
        if (i2s_pdata && i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) {
@@ -1405,7 +1474,13 @@ static int samsung_i2s_probe(struct platform_device *pdev)
                goto err_disable_clk;
        }
 
-       dev_set_drvdata(&pdev->dev, pri_dai);
+       dev_set_drvdata(&pdev->dev, priv);
+
+       ret = devm_snd_soc_register_component(&pdev->dev,
+                                       &samsung_i2s_component,
+                                       priv->dai_drv, num_dais);
+       if (ret < 0)
+               goto err_disable_clk;
 
        pm_runtime_set_active(&pdev->dev);
        pm_runtime_enable(&pdev->dev);
@@ -1422,14 +1497,18 @@ err_disable_pm:
        pm_runtime_disable(&pdev->dev);
 err_disable_clk:
        clk_disable_unprepare(pri_dai->clk);
+       i2s_delete_secondary_device(priv);
        return ret;
 }
 
 static int samsung_i2s_remove(struct platform_device *pdev)
 {
-       struct i2s_dai *pri_dai;
+       struct samsung_i2s_priv *priv = dev_get_drvdata(&pdev->dev);
+       struct i2s_dai *pri_dai = samsung_i2s_get_pri_dai(&pdev->dev);
 
-       pri_dai = dev_get_drvdata(&pdev->dev);
+       /* The secondary device has no driver data assigned */
+       if (!priv)
+               return 0;
 
        pm_runtime_get_sync(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
@@ -1437,6 +1516,7 @@ static int samsung_i2s_remove(struct platform_device *pdev)
        i2s_unregister_clock_provider(pdev);
        clk_disable_unprepare(pri_dai->clk);
        pm_runtime_put_noidle(&pdev->dev);
+       i2s_delete_secondary_device(priv);
 
        return 0;
 }
@@ -1534,6 +1614,8 @@ static const struct platform_device_id samsung_i2s_driver_ids[] = {
        {
                .name           = "samsung-i2s",
                .driver_data    = (kernel_ulong_t)&i2sv3_dai_type,
+       }, {
+               .name           = "samsung-i2s-sec",
        },
        {},
 };