clk: sunxi-ng: Unregister clocks/resets when unbinding
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
This commit is contained in:
parent
4abfc297b6
commit
9bec2b9c61
|
@ -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)
|
||||
|
|
|
@ -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[] = {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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[] = {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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[] = {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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[] = {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
@ -14,6 +15,11 @@
|
|||
#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,
|
||||
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,6 +112,9 @@ int sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
|
|||
continue;
|
||||
|
||||
name = hw->init->name;
|
||||
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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
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_ */
|
||||
|
|
Loading…
Reference in New Issue