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:
Samuel Holland 2021-09-01 00:05:19 -05:00 committed by Maxime Ripard
parent 4abfc297b6
commit 9bec2b9c61
No known key found for this signature in database
GPG Key ID: E3EF0D6F671851C5
23 changed files with 100 additions and 41 deletions

View 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)

View 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[] = {

View 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;

View 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;

View 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)

View 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[] = {

View 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",

View 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)

View 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);

View 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);

View 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);

View 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[] = {

View 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;

View 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);

View 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)

View 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;

View 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)

View 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;

View 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;

View 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[] = {

View 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);

View 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>
@ -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,
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);
}
}

View 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_ */