The audio subsystem on Exynos 5420 has separate clocks and GPIO. To
operate properly on GPIOs the main block clock 'mau_epll' must be
enabled.

This was observed on Peach Pi/Pit and Arndale Octa (after enabling i2s0)
after introducing runtime PM to pl330 DMA driver. After that commit the
'mau_epll' was gated, because the "amba" clock was disabled and there
were no more users of mau_epll.

The system hang just before probing i2s0 because
samsung_pinmux_setup() tried to access memory from audss block which was
gated.

Add a clock property to the pinctrl driver and enable the clock during
GPIO setup. During normal GPIO operations (set, get, set_direction) the
clock is not enabled.

Signed-off-by: Krzysztof Kozlowski <k.kozlow...@samsung.com>
Tested-by: Javier Martinez Canillas <javier.marti...@collabora.co.uk>
---
 .../bindings/pinctrl/samsung-pinctrl.txt           |   6 ++
 drivers/pinctrl/samsung/pinctrl-samsung.c          | 111 +++++++++++++++++++--
 drivers/pinctrl/samsung/pinctrl-samsung.h          |   2 +
 3 files changed, 112 insertions(+), 7 deletions(-)

diff --git a/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt 
b/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
index 8425838a6dff..eb121daabe9d 100644
--- a/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
+++ b/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
@@ -93,6 +93,12 @@ Required Properties:
   pin configuration should use the bindings listed in the 
"pinctrl-bindings.txt"
   file.
 
+Optional Properties:
+- clocks: Optional clock needed to access the block. Will be enabled/disabled
+  during GPIO configuration, suspend and resume but not during GPIO operations
+  (like set, get, set direction).
+- clock-names: Must be "block".
+
 External GPIO and Wakeup Interrupts:
 
 The controller supports two types of external interrupts over gpio. The first
diff --git a/drivers/pinctrl/samsung/pinctrl-samsung.c 
b/drivers/pinctrl/samsung/pinctrl-samsung.c
index ec580af35856..85e487fe43ec 100644
--- a/drivers/pinctrl/samsung/pinctrl-samsung.c
+++ b/drivers/pinctrl/samsung/pinctrl-samsung.c
@@ -24,6 +24,7 @@
 #include <linux/platform_device.h>
 #include <linux/io.h>
 #include <linux/slab.h>
+#include <linux/clk.h>
 #include <linux/err.h>
 #include <linux/gpio.h>
 #include <linux/irqdomain.h>
@@ -55,6 +56,32 @@ static LIST_HEAD(drvdata_list);
 
 static unsigned int pin_base;
 
+static int pctl_clk_enable(struct pinctrl_dev *pctldev)
+{
+       struct samsung_pinctrl_drv_data *drvdata;
+       int ret;
+
+       drvdata = pinctrl_dev_get_drvdata(pctldev);
+       if (!drvdata->clk)
+               return 0;
+
+       ret = clk_enable(drvdata->clk);
+       if (ret)
+               dev_err(pctldev->dev, "failed to enable clock: %d\n", ret);
+
+       return ret;
+}
+
+static void pctl_clk_disable(struct pinctrl_dev *pctldev)
+{
+       struct samsung_pinctrl_drv_data *drvdata;
+
+       drvdata = pinctrl_dev_get_drvdata(pctldev);
+
+       /* clk/core.c does the check if clk != NULL */
+       clk_disable(drvdata->clk);
+}
+
 static inline struct samsung_pin_bank *gc_to_pin_bank(struct gpio_chip *gc)
 {
        return container_of(gc, struct samsung_pin_bank, gpio_chip);
@@ -374,7 +401,9 @@ static void samsung_pinmux_setup(struct pinctrl_dev 
*pctldev, unsigned selector,
        const struct samsung_pmx_func *func;
        const struct samsung_pin_group *grp;
 
+       pctl_clk_enable(pctldev);
        drvdata = pinctrl_dev_get_drvdata(pctldev);
+
        func = &drvdata->pmx_functions[selector];
        grp = &drvdata->pin_groups[group];
 
@@ -398,6 +427,8 @@ static void samsung_pinmux_setup(struct pinctrl_dev 
*pctldev, unsigned selector,
        writel(data, reg + type->reg_offset[PINCFG_TYPE_FUNC]);
 
        spin_unlock_irqrestore(&bank->slock, flags);
+
+       pctl_clk_disable(pctldev);
 }
 
 /* enable a specified pinmux by writing to registers */
@@ -469,20 +500,37 @@ static int samsung_pinconf_set(struct pinctrl_dev 
*pctldev, unsigned int pin,
 {
        int i, ret;
 
+       ret = pctl_clk_enable(pctldev);
+       if (ret)
+               goto out;
+
        for (i = 0; i < num_configs; i++) {
                ret = samsung_pinconf_rw(pctldev, pin, &configs[i], true);
                if (ret < 0)
-                       return ret;
+                       goto out;
        } /* for each config */
 
-       return 0;
+out:
+       pctl_clk_disable(pctldev);
+
+       return ret;
 }
 
 /* get the pin config settings for a specified pin */
 static int samsung_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin,
                                        unsigned long *config)
 {
-       return samsung_pinconf_rw(pctldev, pin, config, false);
+       int ret;
+
+       ret = pctl_clk_enable(pctldev);
+       if (ret)
+               return ret;
+
+       ret = samsung_pinconf_rw(pctldev, pin, config, false);
+
+       pctl_clk_disable(pctldev);
+
+       return ret;
 }
 
 /* set the pin config settings for a specified pin group */
@@ -1057,10 +1105,23 @@ static int samsung_pinctrl_probe(struct platform_device 
*pdev)
        }
        drvdata->dev = dev;
 
+       drvdata->clk = clk_get(&pdev->dev, "block");
+       if (!IS_ERR(drvdata->clk)) {
+               ret = clk_prepare_enable(drvdata->clk);
+               if (ret) {
+                       dev_err(dev, "failed to enable clk: %d\n", ret);
+                       return ret;
+               }
+       } else {
+               drvdata->clk = NULL;
+       }
+
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        drvdata->virt_base = devm_ioremap_resource(&pdev->dev, res);
-       if (IS_ERR(drvdata->virt_base))
-               return PTR_ERR(drvdata->virt_base);
+       if (IS_ERR(drvdata->virt_base)) {
+               ret = PTR_ERR(drvdata->virt_base);
+               goto err;
+       }
 
        res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
        if (res)
@@ -1068,12 +1129,12 @@ static int samsung_pinctrl_probe(struct platform_device 
*pdev)
 
        ret = samsung_gpiolib_register(pdev, drvdata);
        if (ret)
-               return ret;
+               goto err;
 
        ret = samsung_pinctrl_register(pdev, drvdata);
        if (ret) {
                samsung_gpiolib_unregister(pdev, drvdata);
-               return ret;
+               goto err;
        }
 
        if (ctrl->eint_gpio_init)
@@ -1085,6 +1146,23 @@ static int samsung_pinctrl_probe(struct platform_device 
*pdev)
 
        /* Add to the global list */
        list_add_tail(&drvdata->node, &drvdata_list);
+       clk_disable(drvdata->clk); /* Leave prepared */
+
+       return 0;
+
+err:
+       if (drvdata->clk)
+               clk_disable_unprepare(drvdata->clk);
+
+       return ret;
+}
+
+static int samsung_pinctrl_remove(struct platform_device *pdev)
+{
+       struct samsung_pinctrl_drv_data *drvdata = platform_get_drvdata(pdev);
+
+       if (drvdata->clk)
+               clk_unprepare(drvdata->clk);
 
        return 0;
 }
@@ -1102,6 +1180,13 @@ static void samsung_pinctrl_suspend_dev(
        void __iomem *virt_base = drvdata->virt_base;
        int i;
 
+       if (drvdata->clk) {
+               if (clk_enable(drvdata->clk)) {
+                       dev_err(drvdata->dev, "failed to enable clock\n");
+                       return;
+               }
+       }
+
        for (i = 0; i < drvdata->nr_banks; i++) {
                struct samsung_pin_bank *bank = &drvdata->pin_banks[i];
                void __iomem *reg = virt_base + bank->pctl_offset;
@@ -1133,6 +1218,8 @@ static void samsung_pinctrl_suspend_dev(
 
        if (drvdata->suspend)
                drvdata->suspend(drvdata);
+
+       clk_disable(drvdata->clk);
 }
 
 /**
@@ -1148,6 +1235,13 @@ static void samsung_pinctrl_resume_dev(struct 
samsung_pinctrl_drv_data *drvdata)
        void __iomem *virt_base = drvdata->virt_base;
        int i;
 
+       if (drvdata->clk) {
+               if (clk_enable(drvdata->clk)) {
+                       dev_err(drvdata->dev, "failed to enable clock\n");
+                       return;
+               }
+       }
+
        if (drvdata->resume)
                drvdata->resume(drvdata);
 
@@ -1181,6 +1275,8 @@ static void samsung_pinctrl_resume_dev(struct 
samsung_pinctrl_drv_data *drvdata)
                        if (widths[type])
                                writel(bank->pm_save[type], reg + offs[type]);
        }
+
+       clk_disable(drvdata->clk);
 }
 
 /**
@@ -1264,6 +1360,7 @@ MODULE_DEVICE_TABLE(of, samsung_pinctrl_dt_match);
 
 static struct platform_driver samsung_pinctrl_driver = {
        .probe          = samsung_pinctrl_probe,
+       .remove         = samsung_pinctrl_remove,
        .driver = {
                .name   = "samsung-pinctrl",
                .of_match_table = samsung_pinctrl_dt_match,
diff --git a/drivers/pinctrl/samsung/pinctrl-samsung.h 
b/drivers/pinctrl/samsung/pinctrl-samsung.h
index 1b8c0139d604..666cb23eb9f2 100644
--- a/drivers/pinctrl/samsung/pinctrl-samsung.h
+++ b/drivers/pinctrl/samsung/pinctrl-samsung.h
@@ -201,6 +201,7 @@ struct samsung_pin_ctrl {
  * struct samsung_pinctrl_drv_data: wrapper for holding driver data together.
  * @node: global list node
  * @virt_base: register base address of the controller.
+ * @clk: Optional clock to enable/disable during setup. May be NULL.
  * @dev: device instance representing the controller.
  * @irq: interrpt number used by the controller to notify gpio interrupts.
  * @ctrl: pin controller instance managed by the driver.
@@ -218,6 +219,7 @@ struct samsung_pinctrl_drv_data {
        void __iomem                    *virt_base;
        struct device                   *dev;
        int                             irq;
+       struct clk                      *clk;
 
        struct pinctrl_desc             pctl;
        struct pinctrl_dev              *pctl_dev;
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to