Skip to content

Commit

Permalink
[WIP] Convert to a platform driver.
Browse files Browse the repository at this point in the history
This has a few advantages: (1) being able to use .fw_name instead of
.anme to refer to clocks, which eliminates the need to set
/clock-48mhz/clock-output-names = "ref" in the WPCM450 devicetree.
(2) Being able to use devm_ functions to simplify/improve deallocation.
  • Loading branch information
neuschaefer committed Jul 22, 2023
1 parent a9edc8b commit c4a3495
Showing 1 changed file with 74 additions and 57 deletions.
131 changes: 74 additions & 57 deletions drivers/clk/nuvoton/clk-wpcm450.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <linux/bitfield.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
Expand All @@ -17,6 +18,7 @@
#include <linux/reset-controller.h>
#include <linux/reset/reset-simple.h>
#include <linux/slab.h>
#include <linux/platform_device.h>

#include <dt-bindings/clock/nuvoton,wpcm450-clk.h>

Expand Down Expand Up @@ -84,14 +86,14 @@ static const struct clk_ops wpcm450_clk_pll_ops = {
};

static struct clk_hw *
wpcm450_clk_register_pll(void __iomem *pllcon, const char *name,
wpcm450_clk_register_pll(struct device *dev, void __iomem *pllcon, const char *name,
const struct clk_parent_data *parent, unsigned long flags)
{
struct wpcm450_clk_pll *pll;
struct clk_init_data init = {};
int ret;

pll = kzalloc(sizeof(*pll), GFP_KERNEL);
pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);

Expand All @@ -104,9 +106,9 @@ wpcm450_clk_register_pll(void __iomem *pllcon, const char *name,
pll->pllcon = pllcon;
pll->hw.init = &init;

ret = clk_hw_register(NULL, &pll->hw);
ret = devm_clk_hw_register(dev, &pll->hw);
if (ret) {
kfree(pll);
devm_kfree(dev, pll);
return ERR_PTR(ret);
}

Expand Down Expand Up @@ -243,32 +245,27 @@ static const struct wpcm450_clken_data clken_data[] = {

static DEFINE_SPINLOCK(wpcm450_clk_lock);

/*
* NOTE: Error handling is very rudimentary here. If the clock driver initial-
* ization fails, the system is probably in bigger trouble than what is caused
* by a few leaked resources.
*/

static void __init wpcm450_clk_init(struct device_node *clk_np)
static int wpcm450_clk_probe(struct platform_device *pdev)
{
struct clk_hw_onecell_data *clk_data;
static struct clk_hw **hws;
static struct clk_hw *hw;
void __iomem *clk_base;
int i, ret;
struct reset_simple_data *reset;

clk_base = of_iomap(clk_np, 0);
if (!clk_base) {
pr_err("%pOFP: failed to map registers\n", clk_np);
of_node_put(clk_np);
return;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
resource_size_t map_size;

clk_base = devm_of_iomap(dev, np, 0, &map_size);
if (IS_ERR(clk_base)) {
pr_err("%pOFP: failed to map registers\n", np); // TODO: check, dev_err
return PTR_ERR(clk_base);
}
of_node_put(clk_np);

clk_data = kzalloc(struct_size(clk_data, hws, WPCM450_NUM_CLKS), GFP_KERNEL);
clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, WPCM450_NUM_CLKS), GFP_KERNEL);
if (!clk_data)
return;
return -ENOMEM;

clk_data->num = WPCM450_NUM_CLKS;
hws = clk_data->hws;
Expand All @@ -280,41 +277,43 @@ static void __init wpcm450_clk_init(struct device_node *clk_np)
for (i = 0; i < ARRAY_SIZE(pll_data); i++) {
const struct wpcm450_pll_data *data = &pll_data[i];

hw = wpcm450_clk_register_pll(clk_base + data->reg, data->name,
hw = wpcm450_clk_register_pll(dev, clk_base + data->reg, data->name,
&data->parent, data->flags);
if (IS_ERR(hw)) {
pr_info("Failed to register PLL: %pe\n", hw);
return;
return PTR_ERR(hw);
}
}

/* Early divisors (REF/2) */
for (i = 0; i < ARRAY_SIZE(clkdiv_data_early); i++) {
const struct wpcm450_clkdiv_data *data = &clkdiv_data_early[i];

hw = clk_hw_register_divider_table_parent_data(NULL, data->name, &data->parent,
data->flags, clk_base + REG_CLKDIV,
data->shift, data->width,
data->div_flags, data->table,
&wpcm450_clk_lock);
hw = devm_clk_hw_register_divider_table_parent_data(dev, data->name, &data->parent,
data->flags, clk_base + REG_CLKDIV,
data->shift, data->width,
data->div_flags, data->table,
&wpcm450_clk_lock);

if (IS_ERR(hw)) {
pr_err("Failed to register div table: %pe\n", hw);
return;
return PTR_ERR(hw);
}
}

/* Selects/muxes */
for (i = 0; i < ARRAY_SIZE(clksel_data); i++) {
const struct wpcm450_clksel_data *data = &clksel_data[i];

hw = clk_hw_register_mux_parent_data(NULL, data->name, data->parents,
data->num_parents, data->flags,
clk_base + REG_CLKSEL, data->shift,
data->width, 0,
&wpcm450_clk_lock);
hw = devm_clk_hw_register_mux_parent_data_table(dev, data->name, data->parents,
data->num_parents, data->flags,
clk_base + REG_CLKSEL, data->shift,
data->width, 0, NULL,
&wpcm450_clk_lock);

if (IS_ERR(hw)) {
pr_err("Failed to register mux: %pe\n", hw);
return;
return PTR_ERR(hw);
}
if (data->index >= 0)
clk_data->hws[data->index] = hw;
Expand All @@ -324,49 +323,67 @@ static void __init wpcm450_clk_init(struct device_node *clk_np)
for (i = 0; i < ARRAY_SIZE(clkdiv_data); i++) {
const struct wpcm450_clkdiv_data *data = &clkdiv_data[i];

hw = clk_hw_register_divider_table_parent_data(NULL, data->name, &data->parent,
data->flags, clk_base + REG_CLKDIV,
data->shift, data->width,
data->div_flags, data->table,
&wpcm450_clk_lock);
hw = devm_clk_hw_register_divider_table_parent_data(dev, data->name, &data->parent,
data->flags, clk_base + REG_CLKDIV,
data->shift, data->width,
data->div_flags, data->table,
&wpcm450_clk_lock);

if (IS_ERR(hw)) {
pr_err("Failed to register divider: %pe\n", hw);
return;
return PTR_ERR(hw);
}
}

/* Enables/gates */
for (i = 0; i < ARRAY_SIZE(clken_data); i++) {
const struct wpcm450_clken_data *data = &clken_data[i];

hw = clk_hw_register_gate_parent_data(NULL, data->name, &data->parent, data->flags,
clk_base + REG_CLKEN, data->bitnum,
data->flags, &wpcm450_clk_lock);
hw = devm_clk_hw_register_gate_parent_data(dev, data->name, &data->parent, data->flags,
clk_base + REG_CLKEN, data->bitnum,
data->flags, &wpcm450_clk_lock);
if (IS_ERR(hw)) {
pr_err("Failed to register gate: %pe\n", hw);
return;
return PTR_ERR(hw);
}
clk_data->hws[data->bitnum] = hw;
}

ret = of_clk_add_hw_provider(clk_np, of_clk_hw_onecell_get, clk_data);
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
if (ret)
pr_err("Failed to add DT provider: %pe\n", ERR_PTR(ret));

/* Reset controller */
reset = kzalloc(sizeof(*reset), GFP_KERNEL);
if (!reset)
return;
reset->rcdev.owner = THIS_MODULE;
reset->rcdev.nr_resets = WPCM450_NUM_RESETS;
reset->rcdev.ops = &reset_simple_ops;
reset->rcdev.of_node = clk_np;
reset->membase = clk_base + REG_IPSRST;
ret = reset_controller_register(&reset->rcdev);
if (ret)
pr_err("Failed to register reset controller: %pe\n", ERR_PTR(ret));
if (reset) {
reset->rcdev.owner = THIS_MODULE;
reset->rcdev.nr_resets = WPCM450_NUM_RESETS;
reset->rcdev.ops = &reset_simple_ops;
reset->rcdev.of_node = np;
reset->membase = clk_base + REG_IPSRST;
ret = devm_reset_controller_register(dev, &reset->rcdev);
if (ret)
pr_err("Failed to register reset controller: %pe\n", ERR_PTR(ret));
}

of_node_put(clk_np);
return 0;
}

CLK_OF_DECLARE(wpcm450_clk_init, "nuvoton,wpcm450-clk", wpcm450_clk_init);
static const struct of_device_id wpcm450_of_match[] = {
{ .compatible = "nuvoton,wpcm450-clk" },
{}
};
MODULE_DEVICE_TABLE(of, wpcm450_of_match);

static struct platform_driver wpcm450_clk_driver = {
.driver = {
.name = "wpcm450-clk",
.of_match_table = wpcm450_of_match,
},
.probe = wpcm450_clk_probe,
};
module_platform_driver(wpcm450_clk_driver);

MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>");
MODULE_DESCRIPTION("Nuvoton WPCM450 clock and reset controller driver");
MODULE_LICENSE("GPL v2");

0 comments on commit c4a3495

Please sign in to comment.