|
@@ -18,6 +18,7 @@
|
|
|
#include <linux/clk-provider.h>
|
|
|
|
|
|
static DEFINE_SPINLOCK(clkgena_divmux_lock);
|
|
|
+static DEFINE_SPINLOCK(clkgenf_lock);
|
|
|
|
|
|
static const char ** __init clkgen_mux_get_parents(struct device_node *np,
|
|
|
int *num_parents)
|
|
@@ -527,3 +528,274 @@ void __init st_of_clkgena_prediv_setup(struct device_node *np)
|
|
|
return;
|
|
|
}
|
|
|
CLK_OF_DECLARE(clkgenaprediv, "st,clkgena-prediv", st_of_clkgena_prediv_setup);
|
|
|
+
|
|
|
+struct clkgen_mux_data {
|
|
|
+ u32 offset;
|
|
|
+ u8 shift;
|
|
|
+ u8 width;
|
|
|
+ spinlock_t *lock;
|
|
|
+ unsigned long clk_flags;
|
|
|
+ u8 mux_flags;
|
|
|
+};
|
|
|
+
|
|
|
+static struct clkgen_mux_data clkgen_mux_c_vcc_hd_416 = {
|
|
|
+ .offset = 0,
|
|
|
+ .shift = 0,
|
|
|
+ .width = 1,
|
|
|
+};
|
|
|
+
|
|
|
+static struct clkgen_mux_data clkgen_mux_f_vcc_fvdp_416 = {
|
|
|
+ .offset = 0,
|
|
|
+ .shift = 0,
|
|
|
+ .width = 1,
|
|
|
+};
|
|
|
+
|
|
|
+static struct clkgen_mux_data clkgen_mux_f_vcc_hva_416 = {
|
|
|
+ .offset = 0,
|
|
|
+ .shift = 0,
|
|
|
+ .width = 1,
|
|
|
+};
|
|
|
+
|
|
|
+static struct clkgen_mux_data clkgen_mux_f_vcc_hd_416 = {
|
|
|
+ .offset = 0,
|
|
|
+ .shift = 16,
|
|
|
+ .width = 1,
|
|
|
+ .lock = &clkgenf_lock,
|
|
|
+};
|
|
|
+
|
|
|
+static struct clkgen_mux_data clkgen_mux_c_vcc_sd_416 = {
|
|
|
+ .offset = 0,
|
|
|
+ .shift = 17,
|
|
|
+ .width = 1,
|
|
|
+ .lock = &clkgenf_lock,
|
|
|
+};
|
|
|
+
|
|
|
+static struct of_device_id mux_of_match[] = {
|
|
|
+ {
|
|
|
+ .compatible = "st,stih416-clkgenc-vcc-hd",
|
|
|
+ .data = &clkgen_mux_c_vcc_hd_416,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .compatible = "st,stih416-clkgenf-vcc-fvdp",
|
|
|
+ .data = &clkgen_mux_f_vcc_fvdp_416,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .compatible = "st,stih416-clkgenf-vcc-hva",
|
|
|
+ .data = &clkgen_mux_f_vcc_hva_416,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .compatible = "st,stih416-clkgenf-vcc-hd",
|
|
|
+ .data = &clkgen_mux_f_vcc_hd_416,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .compatible = "st,stih416-clkgenf-vcc-sd",
|
|
|
+ .data = &clkgen_mux_c_vcc_sd_416,
|
|
|
+ },
|
|
|
+ {}
|
|
|
+};
|
|
|
+
|
|
|
+void __init st_of_clkgen_mux_setup(struct device_node *np)
|
|
|
+{
|
|
|
+ const struct of_device_id *match;
|
|
|
+ struct clk *clk;
|
|
|
+ void __iomem *reg;
|
|
|
+ const char **parents;
|
|
|
+ int num_parents;
|
|
|
+ struct clkgen_mux_data *data;
|
|
|
+
|
|
|
+ match = of_match_node(mux_of_match, np);
|
|
|
+ if (!match) {
|
|
|
+ pr_err("%s: No matching data\n", __func__);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ data = (struct clkgen_mux_data *)match->data;
|
|
|
+
|
|
|
+ reg = of_iomap(np, 0);
|
|
|
+ if (!reg) {
|
|
|
+ pr_err("%s: Failed to get base address\n", __func__);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ parents = clkgen_mux_get_parents(np, &num_parents);
|
|
|
+ if (IS_ERR(parents)) {
|
|
|
+ pr_err("%s: Failed to get parents (%ld)\n",
|
|
|
+ __func__, PTR_ERR(parents));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ clk = clk_register_mux(NULL, np->name, parents, num_parents,
|
|
|
+ data->clk_flags | CLK_SET_RATE_PARENT,
|
|
|
+ reg + data->offset,
|
|
|
+ data->shift, data->width, data->mux_flags,
|
|
|
+ data->lock);
|
|
|
+ if (IS_ERR(clk))
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ 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));
|
|
|
+
|
|
|
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
|
|
|
+
|
|
|
+err:
|
|
|
+ kfree(parents);
|
|
|
+
|
|
|
+ return;
|
|
|
+}
|
|
|
+CLK_OF_DECLARE(clkgen_mux, "st,clkgen-mux", st_of_clkgen_mux_setup);
|
|
|
+
|
|
|
+#define VCC_MAX_CHANNELS 16
|
|
|
+
|
|
|
+#define VCC_GATE_OFFSET 0x0
|
|
|
+#define VCC_MUX_OFFSET 0x4
|
|
|
+#define VCC_DIV_OFFSET 0x8
|
|
|
+
|
|
|
+struct clkgen_vcc_data {
|
|
|
+ spinlock_t *lock;
|
|
|
+ unsigned long clk_flags;
|
|
|
+};
|
|
|
+
|
|
|
+static struct clkgen_vcc_data st_clkgenc_vcc_416 = {
|
|
|
+ .clk_flags = CLK_SET_RATE_PARENT,
|
|
|
+};
|
|
|
+
|
|
|
+static struct clkgen_vcc_data st_clkgenf_vcc_416 = {
|
|
|
+ .lock = &clkgenf_lock,
|
|
|
+};
|
|
|
+
|
|
|
+static struct of_device_id vcc_of_match[] = {
|
|
|
+ { .compatible = "st,stih416-clkgenc", .data = &st_clkgenc_vcc_416 },
|
|
|
+ { .compatible = "st,stih416-clkgenf", .data = &st_clkgenf_vcc_416 },
|
|
|
+ {}
|
|
|
+};
|
|
|
+
|
|
|
+void __init st_of_clkgen_vcc_setup(struct device_node *np)
|
|
|
+{
|
|
|
+ const struct of_device_id *match;
|
|
|
+ void __iomem *reg;
|
|
|
+ const char **parents;
|
|
|
+ int num_parents, i;
|
|
|
+ struct clk_onecell_data *clk_data;
|
|
|
+ struct clkgen_vcc_data *data;
|
|
|
+
|
|
|
+ match = of_match_node(vcc_of_match, np);
|
|
|
+ if (WARN_ON(!match))
|
|
|
+ return;
|
|
|
+ data = (struct clkgen_vcc_data *)match->data;
|
|
|
+
|
|
|
+ reg = of_iomap(np, 0);
|
|
|
+ if (!reg)
|
|
|
+ return;
|
|
|
+
|
|
|
+ parents = clkgen_mux_get_parents(np, &num_parents);
|
|
|
+ if (IS_ERR(parents))
|
|
|
+ return;
|
|
|
+
|
|
|
+ clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
|
|
|
+ if (!clk_data)
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ clk_data->clk_num = VCC_MAX_CHANNELS;
|
|
|
+ clk_data->clks = kzalloc(clk_data->clk_num * sizeof(struct clk *),
|
|
|
+ GFP_KERNEL);
|
|
|
+
|
|
|
+ if (!clk_data->clks)
|
|
|
+ goto err;
|
|
|
+
|
|
|
+ for (i = 0; i < clk_data->clk_num; i++) {
|
|
|
+ struct clk *clk;
|
|
|
+ const char *clk_name;
|
|
|
+ struct clk_gate *gate;
|
|
|
+ struct clk_divider *div;
|
|
|
+ struct clk_mux *mux;
|
|
|
+
|
|
|
+ if (of_property_read_string_index(np, "clock-output-names",
|
|
|
+ i, &clk_name))
|
|
|
+ break;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If we read an empty clock name then the output is unused
|
|
|
+ */
|
|
|
+ if (*clk_name == '\0')
|
|
|
+ continue;
|
|
|
+
|
|
|
+ gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL);
|
|
|
+ if (!gate)
|
|
|
+ break;
|
|
|
+
|
|
|
+ div = kzalloc(sizeof(struct clk_divider), GFP_KERNEL);
|
|
|
+ if (!div) {
|
|
|
+ kfree(gate);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL);
|
|
|
+ if (!mux) {
|
|
|
+ kfree(gate);
|
|
|
+ kfree(div);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ gate->reg = reg + VCC_GATE_OFFSET;
|
|
|
+ gate->bit_idx = i;
|
|
|
+ gate->flags = CLK_GATE_SET_TO_DISABLE;
|
|
|
+ gate->lock = data->lock;
|
|
|
+
|
|
|
+ div->reg = reg + VCC_DIV_OFFSET;
|
|
|
+ div->shift = 2 * i;
|
|
|
+ div->width = 2;
|
|
|
+ div->flags = CLK_DIVIDER_POWER_OF_TWO;
|
|
|
+
|
|
|
+ mux->reg = reg + VCC_MUX_OFFSET;
|
|
|
+ mux->shift = 2 * i;
|
|
|
+ mux->mask = 0x3;
|
|
|
+
|
|
|
+ clk = clk_register_composite(NULL, clk_name, parents,
|
|
|
+ num_parents,
|
|
|
+ &mux->hw, &clk_mux_ops,
|
|
|
+ &div->hw, &clk_divider_ops,
|
|
|
+ &gate->hw, &clk_gate_ops,
|
|
|
+ data->clk_flags);
|
|
|
+ if (IS_ERR(clk)) {
|
|
|
+ kfree(gate);
|
|
|
+ kfree(div);
|
|
|
+ kfree(mux);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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));
|
|
|
+
|
|
|
+ clk_data->clks[i] = clk;
|
|
|
+ }
|
|
|
+
|
|
|
+ kfree(parents);
|
|
|
+
|
|
|
+ of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
|
|
|
+ return;
|
|
|
+
|
|
|
+err:
|
|
|
+ for (i = 0; i < clk_data->clk_num; i++) {
|
|
|
+ struct clk_composite *composite;
|
|
|
+
|
|
|
+ if (!clk_data->clks[i])
|
|
|
+ continue;
|
|
|
+
|
|
|
+ composite = container_of(__clk_get_hw(clk_data->clks[i]),
|
|
|
+ struct clk_composite, hw);
|
|
|
+ kfree(container_of(composite->gate_hw, struct clk_gate, hw));
|
|
|
+ kfree(container_of(composite->rate_hw, struct clk_divider, hw));
|
|
|
+ kfree(container_of(composite->mux_hw, struct clk_mux, hw));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (clk_data)
|
|
|
+ kfree(clk_data->clks);
|
|
|
+
|
|
|
+ kfree(clk_data);
|
|
|
+ kfree(parents);
|
|
|
+}
|
|
|
+CLK_OF_DECLARE(clkgen_vcc, "st,clkgen-vcc", st_of_clkgen_vcc_setup);
|