clk: sunxi-ng: Unregister clocks/resets when unbinding
authorSamuel Holland <samuel@sholland.org>
Wed, 1 Sep 2021 05:05:19 +0000 (00:05 -0500)
committerMaxime Ripard <maxime@cerno.tech>
Mon, 13 Sep 2021 07:03:20 +0000 (09:03 +0200)
Currently, unbinding a CCU driver unmaps the device's MMIO region, while
leaving its clocks/resets and their providers registered. This can cause
a page fault later when some clock operation tries to perform MMIO. Fix
this by separating the CCU initialization from the memory allocation,
and then using a devres callback to unregister the clocks and resets.

This also fixes a memory leak of the `struct ccu_reset`, and uses the
correct owner (the specific platform driver) for the clocks and resets.

Early OF clock providers are never unregistered, and limited error
handling is possible, so they are mostly unchanged. The error reporting
is made more consistent by moving the message inside of_sunxi_ccu_probe.

Signed-off-by: Samuel Holland <samuel@sholland.org>
Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Link: https://lore.kernel.org/r/20210901050526.45673-2-samuel@sholland.org
23 files changed:
drivers/clk/sunxi-ng/ccu-sun4i-a10.c
drivers/clk/sunxi-ng/ccu-sun50i-a100-r.c
drivers/clk/sunxi-ng/ccu-sun50i-a100.c
drivers/clk/sunxi-ng/ccu-sun50i-a64.c
drivers/clk/sunxi-ng/ccu-sun50i-h6-r.c
drivers/clk/sunxi-ng/ccu-sun50i-h6.c
drivers/clk/sunxi-ng/ccu-sun50i-h616.c
drivers/clk/sunxi-ng/ccu-sun5i.c
drivers/clk/sunxi-ng/ccu-sun6i-a31.c
drivers/clk/sunxi-ng/ccu-sun8i-a23.c
drivers/clk/sunxi-ng/ccu-sun8i-a33.c
drivers/clk/sunxi-ng/ccu-sun8i-a83t.c
drivers/clk/sunxi-ng/ccu-sun8i-de2.c
drivers/clk/sunxi-ng/ccu-sun8i-h3.c
drivers/clk/sunxi-ng/ccu-sun8i-r.c
drivers/clk/sunxi-ng/ccu-sun8i-r40.c
drivers/clk/sunxi-ng/ccu-sun8i-v3s.c
drivers/clk/sunxi-ng/ccu-sun9i-a80-de.c
drivers/clk/sunxi-ng/ccu-sun9i-a80-usb.c
drivers/clk/sunxi-ng/ccu-sun9i-a80.c
drivers/clk/sunxi-ng/ccu-suniv-f1c100s.c
drivers/clk/sunxi-ng/ccu_common.c
drivers/clk/sunxi-ng/ccu_common.h

index f32366d9336e7bd020af7de00844a5ba4b22a348..bd9a8782fec3d41d76f49c1c7bc0b4f40a85baa0 100644 (file)
@@ -1464,7 +1464,7 @@ static void __init sun4i_ccu_init(struct device_node *node,
        val &= ~GENMASK(7, 6);
        writel(val | (2 << 6), reg + SUN4I_AHB_REG);
 
-       sunxi_ccu_probe(node, reg, desc);
+       of_sunxi_ccu_probe(node, reg, desc);
 }
 
 static void __init sun4i_a10_ccu_setup(struct device_node *node)
index a56142b90993830557e99a8629a48dec38f2c7ad..6f2a589705561007490372040d8d5c0ab7521ca8 100644 (file)
@@ -196,7 +196,7 @@ static int sun50i_a100_r_ccu_probe(struct platform_device *pdev)
        if (IS_ERR(reg))
                return PTR_ERR(reg);
 
-       return sunxi_ccu_probe(pdev->dev.of_node, reg, &sun50i_a100_r_ccu_desc);
+       return devm_sunxi_ccu_probe(&pdev->dev, reg, &sun50i_a100_r_ccu_desc);
 }
 
 static const struct of_device_id sun50i_a100_r_ccu_ids[] = {
index 81b48c73d389fdfa5d1b4d68e3933eeb01cc3e8f..913bb08e6dee8bff50234bc812b29e640a801547 100644 (file)
@@ -1247,7 +1247,7 @@ static int sun50i_a100_ccu_probe(struct platform_device *pdev)
                writel(val, reg + sun50i_a100_usb2_clk_regs[i]);
        }
 
-       ret = sunxi_ccu_probe(pdev->dev.of_node, reg, &sun50i_a100_ccu_desc);
+       ret = devm_sunxi_ccu_probe(&pdev->dev, reg, &sun50i_a100_ccu_desc);
        if (ret)
                return ret;
 
index 149cfde817cba7f2479b6bcf4829f5c8fd09851f..54f25c624f020baf6ab68bf61270e61c4b6232ca 100644 (file)
@@ -955,7 +955,7 @@ static int sun50i_a64_ccu_probe(struct platform_device *pdev)
 
        writel(0x515, reg + SUN50I_A64_PLL_MIPI_REG);
 
-       ret = sunxi_ccu_probe(pdev->dev.of_node, reg, &sun50i_a64_ccu_desc);
+       ret = devm_sunxi_ccu_probe(&pdev->dev, reg, &sun50i_a64_ccu_desc);
        if (ret)
                return ret;
 
index f8909a7ed5539a0f73eea5e7c14f06531e394900..f30d7eb5424d8a8acc31cb649332b9b2d81bb5b9 100644 (file)
@@ -232,7 +232,7 @@ static void __init sunxi_r_ccu_init(struct device_node *node,
                return;
        }
 
-       sunxi_ccu_probe(node, reg, desc);
+       of_sunxi_ccu_probe(node, reg, desc);
 }
 
 static void __init sun50i_h6_r_ccu_setup(struct device_node *node)
index bff446b7829071df891d74dd4a5eda8740cc1c42..c0800da2fa3d70bb0fca2298df1ca84948605e83 100644 (file)
@@ -1240,7 +1240,7 @@ static int sun50i_h6_ccu_probe(struct platform_device *pdev)
        val |= BIT(24);
        writel(val, reg + SUN50I_H6_HDMI_CEC_CLK_REG);
 
-       return sunxi_ccu_probe(pdev->dev.of_node, reg, &sun50i_h6_ccu_desc);
+       return devm_sunxi_ccu_probe(&pdev->dev, reg, &sun50i_h6_ccu_desc);
 }
 
 static const struct of_device_id sun50i_h6_ccu_ids[] = {
index 225307305880e25054ad57ac9f1b75cb556dbde8..22eb18079a154e711ed6347d1d69245485026ded 100644 (file)
@@ -1141,9 +1141,7 @@ static void __init sun50i_h616_ccu_setup(struct device_node *node)
        val |= BIT(24);
        writel(val, reg + SUN50I_H616_HDMI_CEC_CLK_REG);
 
-       i = sunxi_ccu_probe(node, reg, &sun50i_h616_ccu_desc);
-       if (i)
-               pr_err("%pOF: probing clocks fails: %d\n", node, i);
+       of_sunxi_ccu_probe(node, reg, &sun50i_h616_ccu_desc);
 }
 
 CLK_OF_DECLARE(sun50i_h616_ccu, "allwinner,sun50i-h616-ccu",
index b78e9b507c1c68895db8ecaf3946e5317c0bf40d..1f4bc0e773a7ed546d31daa96ff89153712fbe40 100644 (file)
@@ -1012,7 +1012,7 @@ static void __init sun5i_ccu_init(struct device_node *node,
        val &= ~GENMASK(7, 6);
        writel(val | (2 << 6), reg + SUN5I_AHB_REG);
 
-       sunxi_ccu_probe(node, reg, desc);
+       of_sunxi_ccu_probe(node, reg, desc);
 }
 
 static void __init sun5i_a10s_ccu_setup(struct device_node *node)
index 9b40d53266a3f4138186404160928978c05ed880..3df5c0b4158046bcf4cec1f8a614fe8598289feb 100644 (file)
@@ -1257,7 +1257,7 @@ static void __init sun6i_a31_ccu_setup(struct device_node *node)
        val |= 0x3 << 12;
        writel(val, reg + SUN6I_A31_AHB1_REG);
 
-       sunxi_ccu_probe(node, reg, &sun6i_a31_ccu_desc);
+       of_sunxi_ccu_probe(node, reg, &sun6i_a31_ccu_desc);
 
        ccu_mux_notifier_register(pll_cpu_clk.common.hw.clk,
                                  &sun6i_a31_cpu_nb);
index 103aa504f6c8a6368094b72f531876291c90db06..577bb235d6584184d444d888dcadbea72d398358 100644 (file)
@@ -745,7 +745,7 @@ static void __init sun8i_a23_ccu_setup(struct device_node *node)
        val &= ~BIT(16);
        writel(val, reg + SUN8I_A23_PLL_MIPI_REG);
 
-       sunxi_ccu_probe(node, reg, &sun8i_a23_ccu_desc);
+       of_sunxi_ccu_probe(node, reg, &sun8i_a23_ccu_desc);
 }
 CLK_OF_DECLARE(sun8i_a23_ccu, "allwinner,sun8i-a23-ccu",
               sun8i_a23_ccu_setup);
index 91838cd110377cb4e8dab7df3238f5288e66a65c..8f65cd03f5acc0b247fdb4040934dfe221252c12 100644 (file)
@@ -805,7 +805,7 @@ static void __init sun8i_a33_ccu_setup(struct device_node *node)
        val &= ~BIT(16);
        writel(val, reg + SUN8I_A33_PLL_MIPI_REG);
 
-       sunxi_ccu_probe(node, reg, &sun8i_a33_ccu_desc);
+       of_sunxi_ccu_probe(node, reg, &sun8i_a33_ccu_desc);
 
        /* Gate then ungate PLL CPU after any rate changes */
        ccu_pll_notifier_register(&sun8i_a33_pll_cpu_nb);
index 2b434521c5ccfc07b2198c5dfd6bb4d023d0a34c..c2ddcd2ddab4ec977d453a0500b18c3e1b2b2c56 100644 (file)
@@ -906,7 +906,7 @@ static int sun8i_a83t_ccu_probe(struct platform_device *pdev)
        sun8i_a83t_cpu_pll_fixup(reg + SUN8I_A83T_PLL_C0CPUX_REG);
        sun8i_a83t_cpu_pll_fixup(reg + SUN8I_A83T_PLL_C1CPUX_REG);
 
-       return sunxi_ccu_probe(pdev->dev.of_node, reg, &sun8i_a83t_ccu_desc);
+       return devm_sunxi_ccu_probe(&pdev->dev, reg, &sun8i_a83t_ccu_desc);
 }
 
 static const struct of_device_id sun8i_a83t_ccu_ids[] = {
index 524f33275bc73c1661921358815cf3dd9e2fa968..4b94b6041b271ac7a15cbffd366d073e7d18da89 100644 (file)
@@ -342,7 +342,7 @@ static int sunxi_de2_clk_probe(struct platform_device *pdev)
                goto err_disable_mod_clk;
        }
 
-       ret = sunxi_ccu_probe(pdev->dev.of_node, reg, ccu_desc);
+       ret = devm_sunxi_ccu_probe(&pdev->dev, reg, ccu_desc);
        if (ret)
                goto err_assert_reset;
 
index 7e629a4493afde1be2053300dac1a9fb08cdd33c..d2fc2903787d8f838dcb5518a4a0dd77006e8386 100644 (file)
@@ -1154,7 +1154,7 @@ static void __init sunxi_h3_h5_ccu_init(struct device_node *node,
        val &= ~GENMASK(19, 16);
        writel(val | (0 << 16), reg + SUN8I_H3_PLL_AUDIO_REG);
 
-       sunxi_ccu_probe(node, reg, desc);
+       of_sunxi_ccu_probe(node, reg, desc);
 
        /* Gate then ungate PLL CPU after any rate changes */
        ccu_pll_notifier_register(&sun8i_h3_pll_cpu_nb);
index 4c8c491b87c2777d636cdd15b603e0c45c72cc71..9e754d1f754a134d57427fbfaf22526f4a22a1d6 100644 (file)
@@ -265,7 +265,7 @@ static void __init sunxi_r_ccu_init(struct device_node *node,
                return;
        }
 
-       sunxi_ccu_probe(node, reg, desc);
+       of_sunxi_ccu_probe(node, reg, desc);
 }
 
 static void __init sun8i_a83t_r_ccu_setup(struct device_node *node)
index 84153418453f41fa504ac2d3318646bf3997a073..002e0c3a04dbeab502272b2190a578dd5a89eb1f 100644 (file)
@@ -1346,7 +1346,7 @@ static int sun8i_r40_ccu_probe(struct platform_device *pdev)
        if (IS_ERR(regmap))
                return PTR_ERR(regmap);
 
-       ret = sunxi_ccu_probe(pdev->dev.of_node, reg, &sun8i_r40_ccu_desc);
+       ret = devm_sunxi_ccu_probe(&pdev->dev, reg, &sun8i_r40_ccu_desc);
        if (ret)
                return ret;
 
index f49724a22540e5923127d64afc066499dfb690ec..ce150f83ab54e2f018f97244be73d412be7e9c7b 100644 (file)
@@ -822,7 +822,7 @@ static void __init sun8i_v3_v3s_ccu_init(struct device_node *node,
        val &= ~GENMASK(19, 16);
        writel(val, reg + SUN8I_V3S_PLL_AUDIO_REG);
 
-       sunxi_ccu_probe(node, reg, ccu_desc);
+       of_sunxi_ccu_probe(node, reg, ccu_desc);
 }
 
 static void __init sun8i_v3s_ccu_setup(struct device_node *node)
index 6616e8114f62385e6150aef5083a6eec4bd1279f..261e64416f26a8d33e41567e6bfc1b48b6e315d6 100644 (file)
@@ -246,8 +246,7 @@ static int sun9i_a80_de_clk_probe(struct platform_device *pdev)
                goto err_disable_clk;
        }
 
-       ret = sunxi_ccu_probe(pdev->dev.of_node, reg,
-                             &sun9i_a80_de_clk_desc);
+       ret = devm_sunxi_ccu_probe(&pdev->dev, reg, &sun9i_a80_de_clk_desc);
        if (ret)
                goto err_assert_reset;
 
index 4b4a507d04edfaa5e056b32e1c868fcbf2027185..596243b3e0fa3e686ab9268dcfd5f96abf0624e1 100644 (file)
@@ -117,8 +117,7 @@ static int sun9i_a80_usb_clk_probe(struct platform_device *pdev)
                return ret;
        }
 
-       ret = sunxi_ccu_probe(pdev->dev.of_node, reg,
-                             &sun9i_a80_usb_clk_desc);
+       ret = devm_sunxi_ccu_probe(&pdev->dev, reg, &sun9i_a80_usb_clk_desc);
        if (ret)
                goto err_disable_clk;
 
index ef29582676f6eee6a5aeed679730ebd5c942ecae..97aaed0e685000005d9002bbb08f97d18ccfdcfd 100644 (file)
@@ -1231,7 +1231,7 @@ static int sun9i_a80_ccu_probe(struct platform_device *pdev)
        sun9i_a80_cpu_pll_fixup(reg + SUN9I_A80_PLL_C0CPUX_REG);
        sun9i_a80_cpu_pll_fixup(reg + SUN9I_A80_PLL_C1CPUX_REG);
 
-       return sunxi_ccu_probe(pdev->dev.of_node, reg, &sun9i_a80_ccu_desc);
+       return devm_sunxi_ccu_probe(&pdev->dev, reg, &sun9i_a80_ccu_desc);
 }
 
 static const struct of_device_id sun9i_a80_ccu_ids[] = {
index 7ecc3a5a5b5e1a304a8773ac534e2bb9a74ffa1f..61ad7ee91c11401d32bee9721f318efed1be2e04 100644 (file)
@@ -538,7 +538,7 @@ static void __init suniv_f1c100s_ccu_setup(struct device_node *node)
        val &= ~GENMASK(19, 16);
        writel(val | (3 << 16), reg + SUNIV_PLL_AUDIO_REG);
 
-       sunxi_ccu_probe(node, reg, &suniv_ccu_desc);
+       of_sunxi_ccu_probe(node, reg, &suniv_ccu_desc);
 
        /* Gate then ungate PLL CPU after any rate changes */
        ccu_pll_notifier_register(&suniv_pll_cpu_nb);
index 2e20e650b6c01b209e26a84e28c9df3bae990eca..88cb569e58358d893c68bc94494ec081531a916e 100644 (file)
@@ -7,6 +7,7 @@
 
 #include <linux/clk.h>
 #include <linux/clk-provider.h>
+#include <linux/device.h>
 #include <linux/iopoll.h>
 #include <linux/slab.h>
 
 #include "ccu_gate.h"
 #include "ccu_reset.h"
 
+struct sunxi_ccu {
+       const struct sunxi_ccu_desc     *desc;
+       struct ccu_reset                reset;
+};
+
 static DEFINE_SPINLOCK(ccu_lock);
 
 void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock)
@@ -79,12 +85,15 @@ int ccu_pll_notifier_register(struct ccu_pll_nb *pll_nb)
                                     &pll_nb->clk_nb);
 }
 
-int sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
-                   const struct sunxi_ccu_desc *desc)
+static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev,
+                          struct device_node *node, void __iomem *reg,
+                          const struct sunxi_ccu_desc *desc)
 {
        struct ccu_reset *reset;
        int i, ret;
 
+       ccu->desc = desc;
+
        for (i = 0; i < desc->num_ccu_clks; i++) {
                struct ccu_common *cclk = desc->ccu_clks[i];
 
@@ -103,7 +112,10 @@ int sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
                        continue;
 
                name = hw->init->name;
-               ret = of_clk_hw_register(node, hw);
+               if (dev)
+                       ret = clk_hw_register(dev, hw);
+               else
+                       ret = of_clk_hw_register(node, hw);
                if (ret) {
                        pr_err("Couldn't register clock %d - %s\n", i, name);
                        goto err_clk_unreg;
@@ -115,15 +127,10 @@ int sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
        if (ret)
                goto err_clk_unreg;
 
-       reset = kzalloc(sizeof(*reset), GFP_KERNEL);
-       if (!reset) {
-               ret = -ENOMEM;
-               goto err_alloc_reset;
-       }
-
+       reset = &ccu->reset;
        reset->rcdev.of_node = node;
        reset->rcdev.ops = &ccu_reset_ops;
-       reset->rcdev.owner = THIS_MODULE;
+       reset->rcdev.owner = dev ? dev->driver->owner : THIS_MODULE;
        reset->rcdev.nr_resets = desc->num_resets;
        reset->base = reg;
        reset->lock = &ccu_lock;
@@ -131,13 +138,11 @@ int sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
 
        ret = reset_controller_register(&reset->rcdev);
        if (ret)
-               goto err_of_clk_unreg;
+               goto err_del_provider;
 
        return 0;
 
-err_of_clk_unreg:
-       kfree(reset);
-err_alloc_reset:
+err_del_provider:
        of_clk_del_provider(node);
 err_clk_unreg:
        while (--i >= 0) {
@@ -149,3 +154,59 @@ err_clk_unreg:
        }
        return ret;
 }
+
+static void devm_sunxi_ccu_release(struct device *dev, void *res)
+{
+       struct sunxi_ccu *ccu = res;
+       const struct sunxi_ccu_desc *desc = ccu->desc;
+       int i;
+
+       reset_controller_unregister(&ccu->reset.rcdev);
+       of_clk_del_provider(dev->of_node);
+
+       for (i = 0; i < desc->hw_clks->num; i++) {
+               struct clk_hw *hw = desc->hw_clks->hws[i];
+
+               if (!hw)
+                       continue;
+               clk_hw_unregister(hw);
+       }
+}
+
+int devm_sunxi_ccu_probe(struct device *dev, void __iomem *reg,
+                        const struct sunxi_ccu_desc *desc)
+{
+       struct sunxi_ccu *ccu;
+       int ret;
+
+       ccu = devres_alloc(devm_sunxi_ccu_release, sizeof(*ccu), GFP_KERNEL);
+       if (!ccu)
+               return -ENOMEM;
+
+       ret = sunxi_ccu_probe(ccu, dev, dev->of_node, reg, desc);
+       if (ret) {
+               devres_free(ccu);
+               return ret;
+       }
+
+       devres_add(dev, ccu);
+
+       return 0;
+}
+
+void of_sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
+                       const struct sunxi_ccu_desc *desc)
+{
+       struct sunxi_ccu *ccu;
+       int ret;
+
+       ccu = kzalloc(sizeof(*ccu), GFP_KERNEL);
+       if (!ccu)
+               return;
+
+       ret = sunxi_ccu_probe(ccu, NULL, node, reg, desc);
+       if (ret) {
+               pr_err("%pOF: probing clocks failed: %d\n", node, ret);
+               kfree(ccu);
+       }
+}
index 04e7a12200a2123f669b2ded71ed39b03348af2b..98a1834b58bb4f6b545a6a9877d2d98680cb8dcb 100644 (file)
@@ -63,7 +63,9 @@ struct ccu_pll_nb {
 
 int ccu_pll_notifier_register(struct ccu_pll_nb *pll_nb);
 
-int sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
-                   const struct sunxi_ccu_desc *desc);
+int devm_sunxi_ccu_probe(struct device *dev, void __iomem *reg,
+                        const struct sunxi_ccu_desc *desc);
+void of_sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
+                       const struct sunxi_ccu_desc *desc);
 
 #endif /* _COMMON_H_ */