/* * Copyright 2018-2019 Arkmicro, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "clk-ark.h" static inline int get_div(int div, int divmode) { switch(divmode) { case ARK_CLK_DIVMODE_NOZERO: div = div ? div : 1; break; case ARK_CLK_DIVMODE_PLUSONE: div = div + 1; break; case ARK_CLK_DIVMODE_DOUBLE: div *= 2; break; case ARK_CLK_DIVMODE_EXPONENT: div = 1 << div; break; case ARK_CLK_DIVMODE_PONEDOUBLE: div = (div + 1) * 2; break; } return div; } static inline int set_div(int div, int divmode) { switch(divmode) { case ARK_CLK_DIVMODE_PLUSONE: div = div - 1; break; case ARK_CLK_DIVMODE_DOUBLE: div /= 2; break; case ARK_CLK_DIVMODE_EXPONENT: div = ilog2(div); break; case ARK_CLK_DIVMODE_PONEDOUBLE: div = div / 2 - 1; break; } return div; } static unsigned long clk_sys_recalc_rate(struct clk_hw *hwclk, unsigned long parent_rate) { struct ark_clk *clk = to_ark_clk(hwclk); pr_info("clk %s rate %lu.\n", clk_hw_get_name(hwclk), parent_rate / clk->div); return parent_rate / clk->div; } static int clk_sys_enable(struct clk_hw *hwclk) { struct ark_clk *clk = to_ark_clk(hwclk); int i; for (i = 0; i < clk->enable_num; i++) { writel(readl(clk->enable_reg[i]) | (1 << clk->enable_offset[i]), clk->enable_reg[i]); } return 0; } static void clk_sys_disable(struct clk_hw *hwclk) { struct ark_clk *clk = to_ark_clk(hwclk); int i; for (i = 0; i < clk->enable_num; i++) { writel(readl(clk->enable_reg[i]) & ~(1 << clk->enable_offset[i]), clk->enable_reg[i]); } } static int clk_sys_is_enabled(struct clk_hw *hwclk) { struct ark_clk *clk = to_ark_clk(hwclk); int i, enable = 0; for (i = 0; i < clk->enable_num; i++) { enable |= !!(readl(clk->enable_reg[i]) & (1 << clk->enable_offset[i])); } return enable; } static int clk_sys_set_rate(struct clk_hw *hwclk, unsigned long rate, unsigned long parent_rate) { struct ark_clk *clk = to_ark_clk(hwclk); int div; u32 reg; if (clk->can_change) { div = DIV_ROUND_UP(parent_rate, rate); clk->div = div; div = set_div(div, clk->divmode); reg = readl(clk->reg); reg &= ~(clk->divmask << clk->divoffset); reg |= ((div & clk->divmask) << clk->divoffset); writel(reg, clk->reg); } return 0; } static long clk_sys_round_rate(struct clk_hw *hwclk, unsigned long rate, unsigned long *parent_rate) { struct ark_clk *clk = to_ark_clk(hwclk); int div; if (clk->can_change) { div = DIV_ROUND_UP(*parent_rate, rate); div = set_div(div, clk->divmode); div = get_div(div, clk->divmode); return DIV_ROUND_UP(*parent_rate, div); } return rate; } static const struct clk_ops clk_sys_ops = { .recalc_rate = clk_sys_recalc_rate, .enable = clk_sys_enable, .disable = clk_sys_disable, .is_enabled = clk_sys_is_enabled, .set_rate = clk_sys_set_rate, .round_rate = clk_sys_round_rate, }; static __init struct clk *ark_sys_clk_init(struct device_node *node, const struct clk_ops *ops) { u32 reg, mask, offset, divmode = 0; void __iomem *reg_base; struct ark_clk *ark_clk; const char *clk_name = node->name; const char *parent_name; struct clk_init_data init; struct device_node *srnp; int rc, index = 0, div = 1; const __be32 *enable_list, *enable_offset_list; int enable_size, enable_offset_size; u32 val, inv_reg; int i; rc = of_property_read_u32(node, "reg", ®); if (WARN_ON(rc)) return NULL; ark_clk = kzalloc(sizeof(*ark_clk), GFP_KERNEL); if (WARN_ON(!ark_clk)) return NULL; /* Map system registers */ srnp = of_find_compatible_node(NULL, NULL, "arkmicro,ark-sregs"); reg_base = of_iomap(srnp, 0); BUG_ON(!reg_base); ark_clk->reg = reg_base + reg; reg = readl(ark_clk->reg); of_property_read_string(node, "clock-output-names", &clk_name); init.name = clk_name; init.ops = ops; init.flags = 0; if (!of_property_read_u32(node, "index-mask", &mask) && !of_property_read_u32(node, "index-offset", &offset)) { if (!of_property_read_u32(node, "index-value", &val)) { index = val; if (val) val = 1 << (val - 1); reg &= ~(mask << offset); reg |= ((val & mask) << offset); writel(reg, ark_clk->reg); } else { index = (reg >> offset) & mask; if (index) index = ilog2(index) + 1; } } of_property_read_u32(node, "div-mode", &divmode); if (!of_property_read_u32(node, "div-mask", &mask) && !of_property_read_u32(node, "div-offset", &offset)) { ark_clk->divmode = divmode; ark_clk->divmask = mask; ark_clk->divoffset = offset; if (!of_property_read_u32(node, "div-value", &val)) { val = set_div(val, divmode); div = get_div(val, divmode); reg &= ~(mask << offset); reg |= ((val & mask) << offset); writel(reg, ark_clk->reg); } else { div = (reg >> offset) & mask; div = get_div(div, divmode); } } ark_clk->can_change = of_property_read_bool(node, "clk-can-change"); enable_list = of_get_property(node, "enable-reg", &enable_size); enable_offset_list = of_get_property(node, "enable-offset", &enable_offset_size); if (enable_list && enable_offset_list && enable_size == enable_offset_size) { ark_clk->enable_num = min(ARK_CLK_MAX_ENABLE_NUM, (enable_size / sizeof(*enable_list))); for (i = 0; i < ark_clk->enable_num; i++) { ark_clk->enable_reg[i] = reg_base + be32_to_cpu(*enable_list++); ark_clk->enable_offset[i] = be32_to_cpu(*enable_offset_list++); } } if (!of_property_read_u32(node, "inv-reg", &inv_reg) && !of_property_read_u32(node, "inv-offset", &offset) && !of_property_read_u32(node, "inv-mask", &mask) && !of_property_read_u32(node, "inv-value", &val)) { reg = readl(reg_base + inv_reg); reg &= ~(mask << offset); reg |= ((val & mask) << offset); writel(reg, reg_base + inv_reg); } parent_name = of_clk_get_parent_name(node, index); init.parent_names = &parent_name; init.num_parents = 1; ark_clk->div = div; ark_clk->hw.init = &init; rc = clk_hw_register(NULL, &ark_clk->hw); if (WARN_ON(rc)) { kfree(ark_clk); return NULL; } rc = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &ark_clk->hw); return ark_clk->hw.clk; } static void __init of_ark_clk_sys_setup(struct device_node *np) { ark_sys_clk_init(np, &clk_sys_ops); } CLK_OF_DECLARE(ark_clk_sys, "arkmiro,ark-clk-sys", of_ark_clk_sys_setup);