| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * RZ/A1 Core CPG Clocks |
| * |
| * Copyright (C) 2013 Ideas On Board SPRL |
| * Copyright (C) 2014 Wolfram Sang, Sang Engineering <wsa@sang-engineering.com> |
| */ |
| |
| #include <linux/clk-provider.h> |
| #include <linux/clk/renesas.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/slab.h> |
| |
| struct rz_cpg { |
| struct clk_onecell_data data; |
| void __iomem *reg; |
| }; |
| |
| #define CPG_FRQCR 0x10 |
| #define CPG_FRQCR2 0x14 |
| |
| #define PPR0 0xFCFE3200 |
| #define PIBC0 0xFCFE7000 |
| |
| #define MD_CLK(x) ((x >> 2) & 1) /* P0_2 */ |
| |
| /* ----------------------------------------------------------------------------- |
| * Initialization |
| */ |
| |
| static u16 __init rz_cpg_read_mode_pins(void) |
| { |
| void __iomem *ppr0, *pibc0; |
| u16 modes; |
| |
| ppr0 = ioremap_nocache(PPR0, 2); |
| pibc0 = ioremap_nocache(PIBC0, 2); |
| BUG_ON(!ppr0 || !pibc0); |
| iowrite16(4, pibc0); /* enable input buffer */ |
| modes = ioread16(ppr0); |
| iounmap(ppr0); |
| iounmap(pibc0); |
| |
| return modes; |
| } |
| |
| static struct clk * __init |
| rz_cpg_register_clock(struct device_node *np, struct rz_cpg *cpg, const char *name) |
| { |
| u32 val; |
| unsigned mult; |
| static const unsigned frqcr_tab[4] = { 3, 2, 0, 1 }; |
| |
| if (strcmp(name, "pll") == 0) { |
| unsigned int cpg_mode = MD_CLK(rz_cpg_read_mode_pins()); |
| const char *parent_name = of_clk_get_parent_name(np, cpg_mode); |
| |
| mult = cpg_mode ? (32 / 4) : 30; |
| |
| return clk_register_fixed_factor(NULL, name, parent_name, 0, mult, 1); |
| } |
| |
| /* If mapping regs failed, skip non-pll clocks. System will boot anyhow */ |
| if (!cpg->reg) |
| return ERR_PTR(-ENXIO); |
| |
| /* FIXME:"i" and "g" are variable clocks with non-integer dividers (e.g. 2/3) |
| * and the constraint that always g <= i. To get the rz platform started, |
| * let them run at fixed current speed and implement the details later. |
| */ |
| if (strcmp(name, "i") == 0) |
| val = (readl(cpg->reg + CPG_FRQCR) >> 8) & 3; |
| else if (strcmp(name, "g") == 0) |
| val = readl(cpg->reg + CPG_FRQCR2) & 3; |
| else |
| return ERR_PTR(-EINVAL); |
| |
| mult = frqcr_tab[val]; |
| return clk_register_fixed_factor(NULL, name, "pll", 0, mult, 3); |
| } |
| |
| static void __init rz_cpg_clocks_init(struct device_node *np) |
| { |
| struct rz_cpg *cpg; |
| struct clk **clks; |
| unsigned i; |
| int num_clks; |
| |
| num_clks = of_property_count_strings(np, "clock-output-names"); |
| if (WARN(num_clks <= 0, "can't count CPG clocks\n")) |
| return; |
| |
| cpg = kzalloc(sizeof(*cpg), GFP_KERNEL); |
| clks = kcalloc(num_clks, sizeof(*clks), GFP_KERNEL); |
| BUG_ON(!cpg || !clks); |
| |
| cpg->data.clks = clks; |
| cpg->data.clk_num = num_clks; |
| |
| cpg->reg = of_iomap(np, 0); |
| |
| for (i = 0; i < num_clks; ++i) { |
| const char *name; |
| struct clk *clk; |
| |
| of_property_read_string_index(np, "clock-output-names", i, &name); |
| |
| clk = rz_cpg_register_clock(np, cpg, name); |
| if (IS_ERR(clk)) |
| pr_err("%s: failed to register %pOFn %s clock (%ld)\n", |
| __func__, np, name, PTR_ERR(clk)); |
| else |
| cpg->data.clks[i] = clk; |
| } |
| |
| of_clk_add_provider(np, of_clk_src_onecell_get, &cpg->data); |
| |
| cpg_mstp_add_clk_domain(np); |
| } |
| CLK_OF_DECLARE(rz_cpg_clks, "renesas,rz-cpg-clocks", rz_cpg_clocks_init); |