linux/drivers/clk/st/clk-flexgen.c
Maxime Ripard 36f8a30c0f clk: st: flexgen: Switch to determine_rate
The ST Flexgen clocks implements a mux with a set_parent hook, but
doesn't provide a determine_rate implementation.

This is a bit odd, since set_parent() is there to, as its name implies,
change the parent of a clock. However, the most likely candidate to
trigger that parent change is a call to clk_set_rate(), with
determine_rate() figuring out which parent is the best suited for a
given rate.

The other trigger would be a call to clk_set_parent(), but it's far less
used, and it doesn't look like there's any obvious user for that clock.

So, the set_parent hook is effectively unused, possibly because of an
oversight. However, it could also be an explicit decision by the
original author to avoid any reparenting but through an explicit call to
clk_set_parent().

The driver does implement round_rate() though, which means that we can
change the rate of the clock, but we will never get to change the
parent.

However, It's hard to tell whether it's been done on purpose or not.

Since we'll start mandating a determine_rate() implementation, let's
convert the round_rate() implementation to a determine_rate(), which
will also make the current behavior explicit. And if it was an
oversight, the clock behaviour can be adjusted later on.

Signed-off-by: Maxime Ripard <maxime@cerno.tech>
Link: https://lore.kernel.org/r/20221018-clk-range-checks-fixes-v4-62-971d5077e7d2@cerno.tech
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2023-06-08 18:39:35 -07:00

750 lines
20 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* clk-flexgen.c
*
* Copyright (C) ST-Microelectronics SA 2013
* Author: Maxime Coquelin <maxime.coquelin@st.com> for ST-Microelectronics.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/of.h>
#include <linux/of_address.h>
struct clkgen_clk_out {
const char *name;
unsigned long flags;
};
struct clkgen_data {
unsigned long flags;
bool mode;
const struct clkgen_clk_out *outputs;
const unsigned int outputs_nb;
};
struct flexgen {
struct clk_hw hw;
/* Crossbar */
struct clk_mux mux;
/* Pre-divisor's gate */
struct clk_gate pgate;
/* Pre-divisor */
struct clk_divider pdiv;
/* Final divisor's gate */
struct clk_gate fgate;
/* Final divisor */
struct clk_divider fdiv;
/* Asynchronous mode control */
struct clk_gate sync;
/* hw control flags */
bool control_mode;
};
#define to_flexgen(_hw) container_of(_hw, struct flexgen, hw)
#define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw)
static int flexgen_enable(struct clk_hw *hw)
{
struct flexgen *flexgen = to_flexgen(hw);
struct clk_hw *pgate_hw = &flexgen->pgate.hw;
struct clk_hw *fgate_hw = &flexgen->fgate.hw;
__clk_hw_set_clk(pgate_hw, hw);
__clk_hw_set_clk(fgate_hw, hw);
clk_gate_ops.enable(pgate_hw);
clk_gate_ops.enable(fgate_hw);
pr_debug("%s: flexgen output enabled\n", clk_hw_get_name(hw));
return 0;
}
static void flexgen_disable(struct clk_hw *hw)
{
struct flexgen *flexgen = to_flexgen(hw);
struct clk_hw *fgate_hw = &flexgen->fgate.hw;
/* disable only the final gate */
__clk_hw_set_clk(fgate_hw, hw);
clk_gate_ops.disable(fgate_hw);
pr_debug("%s: flexgen output disabled\n", clk_hw_get_name(hw));
}
static int flexgen_is_enabled(struct clk_hw *hw)
{
struct flexgen *flexgen = to_flexgen(hw);
struct clk_hw *fgate_hw = &flexgen->fgate.hw;
__clk_hw_set_clk(fgate_hw, hw);
if (!clk_gate_ops.is_enabled(fgate_hw))
return 0;
return 1;
}
static u8 flexgen_get_parent(struct clk_hw *hw)
{
struct flexgen *flexgen = to_flexgen(hw);
struct clk_hw *mux_hw = &flexgen->mux.hw;
__clk_hw_set_clk(mux_hw, hw);
return clk_mux_ops.get_parent(mux_hw);
}
static int flexgen_set_parent(struct clk_hw *hw, u8 index)
{
struct flexgen *flexgen = to_flexgen(hw);
struct clk_hw *mux_hw = &flexgen->mux.hw;
__clk_hw_set_clk(mux_hw, hw);
return clk_mux_ops.set_parent(mux_hw, index);
}
static inline unsigned long
clk_best_div(unsigned long parent_rate, unsigned long rate)
{
return parent_rate / rate + ((rate > (2*(parent_rate % rate))) ? 0 : 1);
}
static int flexgen_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
unsigned long div;
/* Round div according to exact prate and wished rate */
div = clk_best_div(req->best_parent_rate, req->rate);
if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
req->best_parent_rate = req->rate * div;
return 0;
}
req->rate = req->best_parent_rate / div;
return 0;
}
static unsigned long flexgen_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct flexgen *flexgen = to_flexgen(hw);
struct clk_hw *pdiv_hw = &flexgen->pdiv.hw;
struct clk_hw *fdiv_hw = &flexgen->fdiv.hw;
unsigned long mid_rate;
__clk_hw_set_clk(pdiv_hw, hw);
__clk_hw_set_clk(fdiv_hw, hw);
mid_rate = clk_divider_ops.recalc_rate(pdiv_hw, parent_rate);
return clk_divider_ops.recalc_rate(fdiv_hw, mid_rate);
}
static int flexgen_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct flexgen *flexgen = to_flexgen(hw);
struct clk_hw *pdiv_hw = &flexgen->pdiv.hw;
struct clk_hw *fdiv_hw = &flexgen->fdiv.hw;
struct clk_hw *sync_hw = &flexgen->sync.hw;
struct clk_gate *config = to_clk_gate(sync_hw);
unsigned long div = 0;
int ret = 0;
u32 reg;
__clk_hw_set_clk(pdiv_hw, hw);
__clk_hw_set_clk(fdiv_hw, hw);
if (flexgen->control_mode) {
reg = readl(config->reg);
reg &= ~BIT(config->bit_idx);
writel(reg, config->reg);
}
div = clk_best_div(parent_rate, rate);
/*
* pdiv is mainly targeted for low freq results, while fdiv
* should be used for div <= 64. The other way round can
* lead to 'duty cycle' issues.
*/
if (div <= 64) {
clk_divider_ops.set_rate(pdiv_hw, parent_rate, parent_rate);
ret = clk_divider_ops.set_rate(fdiv_hw, rate, rate * div);
} else {
clk_divider_ops.set_rate(fdiv_hw, parent_rate, parent_rate);
ret = clk_divider_ops.set_rate(pdiv_hw, rate, rate * div);
}
return ret;
}
static const struct clk_ops flexgen_ops = {
.enable = flexgen_enable,
.disable = flexgen_disable,
.is_enabled = flexgen_is_enabled,
.get_parent = flexgen_get_parent,
.set_parent = flexgen_set_parent,
.determine_rate = flexgen_determine_rate,
.recalc_rate = flexgen_recalc_rate,
.set_rate = flexgen_set_rate,
};
static struct clk *clk_register_flexgen(const char *name,
const char **parent_names, u8 num_parents,
void __iomem *reg, spinlock_t *lock, u32 idx,
unsigned long flexgen_flags, bool mode) {
struct flexgen *fgxbar;
struct clk *clk;
struct clk_init_data init;
u32 xbar_shift;
void __iomem *xbar_reg, *fdiv_reg;
fgxbar = kzalloc(sizeof(struct flexgen), GFP_KERNEL);
if (!fgxbar)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &flexgen_ops;
init.flags = CLK_GET_RATE_NOCACHE | flexgen_flags;
init.parent_names = parent_names;
init.num_parents = num_parents;
xbar_reg = reg + 0x18 + (idx & ~0x3);
xbar_shift = (idx % 4) * 0x8;
fdiv_reg = reg + 0x164 + idx * 4;
/* Crossbar element config */
fgxbar->mux.lock = lock;
fgxbar->mux.mask = BIT(6) - 1;
fgxbar->mux.reg = xbar_reg;
fgxbar->mux.shift = xbar_shift;
fgxbar->mux.table = NULL;
/* Pre-divider's gate config (in xbar register)*/
fgxbar->pgate.lock = lock;
fgxbar->pgate.reg = xbar_reg;
fgxbar->pgate.bit_idx = xbar_shift + 6;
/* Pre-divider config */
fgxbar->pdiv.lock = lock;
fgxbar->pdiv.reg = reg + 0x58 + idx * 4;
fgxbar->pdiv.width = 10;
/* Final divider's gate config */
fgxbar->fgate.lock = lock;
fgxbar->fgate.reg = fdiv_reg;
fgxbar->fgate.bit_idx = 6;
/* Final divider config */
fgxbar->fdiv.lock = lock;
fgxbar->fdiv.reg = fdiv_reg;
fgxbar->fdiv.width = 6;
/* Final divider sync config */
fgxbar->sync.lock = lock;
fgxbar->sync.reg = fdiv_reg;
fgxbar->sync.bit_idx = 7;
fgxbar->control_mode = mode;
fgxbar->hw.init = &init;
clk = clk_register(NULL, &fgxbar->hw);
if (IS_ERR(clk))
kfree(fgxbar);
else
pr_debug("%s: parent %s rate %u\n",
__clk_get_name(clk),
__clk_get_name(clk_get_parent(clk)),
(unsigned int)clk_get_rate(clk));
return clk;
}
static const char ** __init flexgen_get_parents(struct device_node *np,
int *num_parents)
{
const char **parents;
unsigned int nparents;
nparents = of_clk_get_parent_count(np);
if (WARN_ON(!nparents))
return NULL;
parents = kcalloc(nparents, sizeof(const char *), GFP_KERNEL);
if (!parents)
return NULL;
*num_parents = of_clk_parent_fill(np, parents, nparents);
return parents;
}
static const struct clkgen_data clkgen_audio = {
.flags = CLK_SET_RATE_PARENT,
};
static const struct clkgen_data clkgen_video = {
.flags = CLK_SET_RATE_PARENT,
.mode = 1,
};
static const struct clkgen_clk_out clkgen_stih407_a0_clk_out[] = {
/* This clk needs to be on so that memory interface is accessible */
{ .name = "clk-ic-lmi0", .flags = CLK_IS_CRITICAL },
};
static const struct clkgen_data clkgen_stih407_a0 = {
.outputs = clkgen_stih407_a0_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih407_a0_clk_out),
};
static const struct clkgen_clk_out clkgen_stih410_a0_clk_out[] = {
/* Those clks need to be on so that memory interface is accessible */
{ .name = "clk-ic-lmi0", .flags = CLK_IS_CRITICAL },
{ .name = "clk-ic-lmi1", .flags = CLK_IS_CRITICAL },
};
static const struct clkgen_data clkgen_stih410_a0 = {
.outputs = clkgen_stih410_a0_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih410_a0_clk_out),
};
static const struct clkgen_clk_out clkgen_stih407_c0_clk_out[] = {
{ .name = "clk-icn-gpu", },
{ .name = "clk-fdma", },
{ .name = "clk-nand", },
{ .name = "clk-hva", },
{ .name = "clk-proc-stfe", },
{ .name = "clk-proc-tp", },
{ .name = "clk-rx-icn-dmu", },
{ .name = "clk-rx-icn-hva", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-cpu", .flags = CLK_IS_CRITICAL },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-tx-icn-dmu", .flags = CLK_IS_CRITICAL },
{ .name = "clk-mmc-0", },
{ .name = "clk-mmc-1", },
{ .name = "clk-jpegdec", },
/* This clk needs to be on to keep A9 running */
{ .name = "clk-ext2fa9", .flags = CLK_IS_CRITICAL },
{ .name = "clk-ic-bdisp-0", },
{ .name = "clk-ic-bdisp-1", },
{ .name = "clk-pp-dmu", },
{ .name = "clk-vid-dmu", },
{ .name = "clk-dss-lpc", },
{ .name = "clk-st231-aud-0", },
{ .name = "clk-st231-gp-1", },
{ .name = "clk-st231-dmu", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-lmi", .flags = CLK_IS_CRITICAL },
{ .name = "clk-tx-icn-disp-1", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-sbc", .flags = CLK_IS_CRITICAL },
{ .name = "clk-stfe-frc2", },
{ .name = "clk-eth-phy", },
{ .name = "clk-eth-ref-phyclk", },
{ .name = "clk-flash-promip", },
{ .name = "clk-main-disp", },
{ .name = "clk-aux-disp", },
{ .name = "clk-compo-dvp", },
};
static const struct clkgen_data clkgen_stih407_c0 = {
.outputs = clkgen_stih407_c0_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih407_c0_clk_out),
};
static const struct clkgen_clk_out clkgen_stih410_c0_clk_out[] = {
{ .name = "clk-icn-gpu", },
{ .name = "clk-fdma", },
{ .name = "clk-nand", },
{ .name = "clk-hva", },
{ .name = "clk-proc-stfe", },
{ .name = "clk-proc-tp", },
{ .name = "clk-rx-icn-dmu", },
{ .name = "clk-rx-icn-hva", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-cpu", .flags = CLK_IS_CRITICAL },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-tx-icn-dmu", .flags = CLK_IS_CRITICAL },
{ .name = "clk-mmc-0", },
{ .name = "clk-mmc-1", },
{ .name = "clk-jpegdec", },
/* This clk needs to be on to keep A9 running */
{ .name = "clk-ext2fa9", .flags = CLK_IS_CRITICAL },
{ .name = "clk-ic-bdisp-0", },
{ .name = "clk-ic-bdisp-1", },
{ .name = "clk-pp-dmu", },
{ .name = "clk-vid-dmu", },
{ .name = "clk-dss-lpc", },
{ .name = "clk-st231-aud-0", },
{ .name = "clk-st231-gp-1", },
{ .name = "clk-st231-dmu", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-lmi", .flags = CLK_IS_CRITICAL },
{ .name = "clk-tx-icn-disp-1", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-sbc", .flags = CLK_IS_CRITICAL },
{ .name = "clk-stfe-frc2", },
{ .name = "clk-eth-phy", },
{ .name = "clk-eth-ref-phyclk", },
{ .name = "clk-flash-promip", },
{ .name = "clk-main-disp", },
{ .name = "clk-aux-disp", },
{ .name = "clk-compo-dvp", },
{ .name = "clk-tx-icn-hades", },
{ .name = "clk-rx-icn-hades", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-reg-16", .flags = CLK_IS_CRITICAL },
{ .name = "clk-pp-hades", },
{ .name = "clk-clust-hades", },
{ .name = "clk-hwpe-hades", },
{ .name = "clk-fc-hades", },
};
static const struct clkgen_data clkgen_stih410_c0 = {
.outputs = clkgen_stih410_c0_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih410_c0_clk_out),
};
static const struct clkgen_clk_out clkgen_stih418_c0_clk_out[] = {
{ .name = "clk-icn-gpu", },
{ .name = "clk-fdma", },
{ .name = "clk-nand", },
{ .name = "clk-hva", },
{ .name = "clk-proc-stfe", },
{ .name = "clk-tp", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-rx-icn-dmu", .flags = CLK_IS_CRITICAL },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-rx-icn-hva", .flags = CLK_IS_CRITICAL },
{ .name = "clk-icn-cpu", .flags = CLK_IS_CRITICAL },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-tx-icn-dmu", .flags = CLK_IS_CRITICAL },
{ .name = "clk-mmc-0", },
{ .name = "clk-mmc-1", },
{ .name = "clk-jpegdec", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-reg", .flags = CLK_IS_CRITICAL },
{ .name = "clk-proc-bdisp-0", },
{ .name = "clk-proc-bdisp-1", },
{ .name = "clk-pp-dmu", },
{ .name = "clk-vid-dmu", },
{ .name = "clk-dss-lpc", },
{ .name = "clk-st231-aud-0", },
{ .name = "clk-st231-gp-1", },
{ .name = "clk-st231-dmu", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-lmi", .flags = CLK_IS_CRITICAL },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-tx-icn-1", .flags = CLK_IS_CRITICAL },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-sbc", .flags = CLK_IS_CRITICAL },
{ .name = "clk-stfe-frc2", },
{ .name = "clk-eth-phyref", },
{ .name = "clk-eth-ref-phyclk", },
{ .name = "clk-flash-promip", },
{ .name = "clk-main-disp", },
{ .name = "clk-aux-disp", },
{ .name = "clk-compo-dvp", },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-tx-icn-hades", .flags = CLK_IS_CRITICAL },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-rx-icn-hades", .flags = CLK_IS_CRITICAL },
/* This clk needs to be on to keep bus interconnect alive */
{ .name = "clk-icn-reg-16", .flags = CLK_IS_CRITICAL },
{ .name = "clk-pp-hevc", },
{ .name = "clk-clust-hevc", },
{ .name = "clk-hwpe-hevc", },
{ .name = "clk-fc-hevc", },
{ .name = "clk-proc-mixer", },
{ .name = "clk-proc-sc", },
{ .name = "clk-avsp-hevc", },
};
static const struct clkgen_data clkgen_stih418_c0 = {
.outputs = clkgen_stih418_c0_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih418_c0_clk_out),
};
static const struct clkgen_clk_out clkgen_stih407_d0_clk_out[] = {
{ .name = "clk-pcm-0", },
{ .name = "clk-pcm-1", },
{ .name = "clk-pcm-2", },
{ .name = "clk-spdiff", },
};
static const struct clkgen_data clkgen_stih407_d0 = {
.flags = CLK_SET_RATE_PARENT,
.outputs = clkgen_stih407_d0_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih407_d0_clk_out),
};
static const struct clkgen_clk_out clkgen_stih410_d0_clk_out[] = {
{ .name = "clk-pcm-0", },
{ .name = "clk-pcm-1", },
{ .name = "clk-pcm-2", },
{ .name = "clk-spdiff", },
{ .name = "clk-pcmr10-master", },
{ .name = "clk-usb2-phy", },
};
static const struct clkgen_data clkgen_stih410_d0 = {
.flags = CLK_SET_RATE_PARENT,
.outputs = clkgen_stih410_d0_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih410_d0_clk_out),
};
static const struct clkgen_clk_out clkgen_stih407_d2_clk_out[] = {
{ .name = "clk-pix-main-disp", },
{ .name = "clk-pix-pip", },
{ .name = "clk-pix-gdp1", },
{ .name = "clk-pix-gdp2", },
{ .name = "clk-pix-gdp3", },
{ .name = "clk-pix-gdp4", },
{ .name = "clk-pix-aux-disp", },
{ .name = "clk-denc", },
{ .name = "clk-pix-hddac", },
{ .name = "clk-hddac", },
{ .name = "clk-sddac", },
{ .name = "clk-pix-dvo", },
{ .name = "clk-dvo", },
{ .name = "clk-pix-hdmi", },
{ .name = "clk-tmds-hdmi", },
{ .name = "clk-ref-hdmiphy", },
};
static const struct clkgen_data clkgen_stih407_d2 = {
.outputs = clkgen_stih407_d2_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih407_d2_clk_out),
.flags = CLK_SET_RATE_PARENT,
.mode = 1,
};
static const struct clkgen_clk_out clkgen_stih418_d2_clk_out[] = {
{ .name = "clk-pix-main-disp", },
{ .name = "", },
{ .name = "", },
{ .name = "", },
{ .name = "", },
{ .name = "clk-tmds-hdmi-div2", },
{ .name = "clk-pix-aux-disp", },
{ .name = "clk-denc", },
{ .name = "clk-pix-hddac", },
{ .name = "clk-hddac", },
{ .name = "clk-sddac", },
{ .name = "clk-pix-dvo", },
{ .name = "clk-dvo", },
{ .name = "clk-pix-hdmi", },
{ .name = "clk-tmds-hdmi", },
{ .name = "clk-ref-hdmiphy", },
{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
{ .name = "", }, { .name = "", }, { .name = "", }, { .name = "", },
{ .name = "", }, { .name = "", }, { .name = "", },
{ .name = "clk-vp9", },
};
static const struct clkgen_data clkgen_stih418_d2 = {
.outputs = clkgen_stih418_d2_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih418_d2_clk_out),
.flags = CLK_SET_RATE_PARENT,
.mode = 1,
};
static const struct clkgen_clk_out clkgen_stih407_d3_clk_out[] = {
{ .name = "clk-stfe-frc1", },
{ .name = "clk-tsout-0", },
{ .name = "clk-tsout-1", },
{ .name = "clk-mchi", },
{ .name = "clk-vsens-compo", },
{ .name = "clk-frc1-remote", },
{ .name = "clk-lpc-0", },
{ .name = "clk-lpc-1", },
};
static const struct clkgen_data clkgen_stih407_d3 = {
.outputs = clkgen_stih407_d3_clk_out,
.outputs_nb = ARRAY_SIZE(clkgen_stih407_d3_clk_out),
};
static const struct of_device_id flexgen_of_match[] = {
{
.compatible = "st,flexgen-audio",
.data = &clkgen_audio,
},
{
.compatible = "st,flexgen-video",
.data = &clkgen_video,
},
{
.compatible = "st,flexgen-stih407-a0",
.data = &clkgen_stih407_a0,
},
{
.compatible = "st,flexgen-stih410-a0",
.data = &clkgen_stih410_a0,
},
{
.compatible = "st,flexgen-stih407-c0",
.data = &clkgen_stih407_c0,
},
{
.compatible = "st,flexgen-stih410-c0",
.data = &clkgen_stih410_c0,
},
{
.compatible = "st,flexgen-stih418-c0",
.data = &clkgen_stih418_c0,
},
{
.compatible = "st,flexgen-stih407-d0",
.data = &clkgen_stih407_d0,
},
{
.compatible = "st,flexgen-stih410-d0",
.data = &clkgen_stih410_d0,
},
{
.compatible = "st,flexgen-stih407-d2",
.data = &clkgen_stih407_d2,
},
{
.compatible = "st,flexgen-stih418-d2",
.data = &clkgen_stih418_d2,
},
{
.compatible = "st,flexgen-stih407-d3",
.data = &clkgen_stih407_d3,
},
{}
};
static void __init st_of_flexgen_setup(struct device_node *np)
{
struct device_node *pnode;
void __iomem *reg;
struct clk_onecell_data *clk_data;
const char **parents;
int num_parents, i;
spinlock_t *rlock = NULL;
const struct of_device_id *match;
struct clkgen_data *data = NULL;
unsigned long flex_flags = 0;
int ret;
bool clk_mode = 0;
const char *clk_name;
pnode = of_get_parent(np);
if (!pnode)
return;
reg = of_iomap(pnode, 0);
of_node_put(pnode);
if (!reg)
return;
parents = flexgen_get_parents(np, &num_parents);
if (!parents) {
iounmap(reg);
return;
}
match = of_match_node(flexgen_of_match, np);
if (match) {
data = (struct clkgen_data *)match->data;
flex_flags = data->flags;
clk_mode = data->mode;
}
clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
if (!clk_data)
goto err;
/* First try to get output information from the compatible data */
if (!data || !data->outputs_nb || !data->outputs) {
ret = of_property_count_strings(np, "clock-output-names");
if (ret <= 0) {
pr_err("%s: Failed to get number of output clocks (%d)",
__func__, clk_data->clk_num);
goto err;
}
clk_data->clk_num = ret;
} else
clk_data->clk_num = data->outputs_nb;
clk_data->clks = kcalloc(clk_data->clk_num, sizeof(struct clk *),
GFP_KERNEL);
if (!clk_data->clks)
goto err;
rlock = kzalloc(sizeof(spinlock_t), GFP_KERNEL);
if (!rlock)
goto err;
spin_lock_init(rlock);
for (i = 0; i < clk_data->clk_num; i++) {
struct clk *clk;
if (!data || !data->outputs_nb || !data->outputs) {
if (of_property_read_string_index(np,
"clock-output-names",
i, &clk_name))
break;
flex_flags &= ~CLK_IS_CRITICAL;
of_clk_detect_critical(np, i, &flex_flags);
} else {
clk_name = data->outputs[i].name;
flex_flags = data->flags | data->outputs[i].flags;
}
/*
* If we read an empty clock name then the output is unused
*/
if (*clk_name == '\0')
continue;
clk = clk_register_flexgen(clk_name, parents, num_parents,
reg, rlock, i, flex_flags, clk_mode);
if (IS_ERR(clk))
goto err;
clk_data->clks[i] = clk;
}
kfree(parents);
of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
return;
err:
iounmap(reg);
if (clk_data)
kfree(clk_data->clks);
kfree(clk_data);
kfree(parents);
kfree(rlock);
}
CLK_OF_DECLARE(flexgen, "st,flexgen", st_of_flexgen_setup);