263 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Marvell Dove PMU Core PLL divider driver4 *5 * Cleaned up by substantially rewriting, and converted to DT by6 * Russell King. Origin is not known.7 */8#include <linux/clk-provider.h>9#include <linux/delay.h>10#include <linux/io.h>11#include <linux/kernel.h>12#include <linux/of.h>13#include <linux/of_address.h>14 15#include "dove-divider.h"16 17struct dove_clk {18 const char *name;19 struct clk_hw hw;20 void __iomem *base;21 spinlock_t *lock;22 u8 div_bit_start;23 u8 div_bit_end;24 u8 div_bit_load;25 u8 div_bit_size;26 u32 *divider_table;27};28 29enum {30 DIV_CTRL0 = 0,31 DIV_CTRL1 = 4,32 DIV_CTRL1_N_RESET_MASK = BIT(10),33};34 35#define to_dove_clk(hw) container_of(hw, struct dove_clk, hw)36 37static void dove_load_divider(void __iomem *base, u32 val, u32 mask, u32 load)38{39 u32 v;40 41 v = readl_relaxed(base + DIV_CTRL1) | DIV_CTRL1_N_RESET_MASK;42 writel_relaxed(v, base + DIV_CTRL1);43 44 v = (readl_relaxed(base + DIV_CTRL0) & ~(mask | load)) | val;45 writel_relaxed(v, base + DIV_CTRL0);46 writel_relaxed(v | load, base + DIV_CTRL0);47 ndelay(250);48 writel_relaxed(v, base + DIV_CTRL0);49}50 51static unsigned int dove_get_divider(struct dove_clk *dc)52{53 unsigned int divider;54 u32 val;55 56 val = readl_relaxed(dc->base + DIV_CTRL0);57 val >>= dc->div_bit_start;58 59 divider = val & ~(~0 << dc->div_bit_size);60 61 if (dc->divider_table)62 divider = dc->divider_table[divider];63 64 return divider;65}66 67static int dove_calc_divider(const struct dove_clk *dc, unsigned long rate,68 unsigned long parent_rate, bool set)69{70 unsigned int divider, max;71 72 divider = DIV_ROUND_CLOSEST(parent_rate, rate);73 74 if (dc->divider_table) {75 unsigned int i;76 77 for (i = 0; dc->divider_table[i]; i++)78 if (divider == dc->divider_table[i]) {79 divider = i;80 break;81 }82 83 if (!dc->divider_table[i])84 return -EINVAL;85 } else {86 max = 1 << dc->div_bit_size;87 88 if (set && (divider == 0 || divider >= max))89 return -EINVAL;90 if (divider >= max)91 divider = max - 1;92 else if (divider == 0)93 divider = 1;94 }95 96 return divider;97}98 99static unsigned long dove_recalc_rate(struct clk_hw *hw, unsigned long parent)100{101 struct dove_clk *dc = to_dove_clk(hw);102 unsigned int divider = dove_get_divider(dc);103 unsigned long rate = DIV_ROUND_CLOSEST(parent, divider);104 105 pr_debug("%s(): %s divider=%u parent=%lu rate=%lu\n",106 __func__, dc->name, divider, parent, rate);107 108 return rate;109}110 111static long dove_round_rate(struct clk_hw *hw, unsigned long rate,112 unsigned long *parent)113{114 struct dove_clk *dc = to_dove_clk(hw);115 unsigned long parent_rate = *parent;116 int divider;117 118 divider = dove_calc_divider(dc, rate, parent_rate, false);119 if (divider < 0)120 return divider;121 122 rate = DIV_ROUND_CLOSEST(parent_rate, divider);123 124 pr_debug("%s(): %s divider=%u parent=%lu rate=%lu\n",125 __func__, dc->name, divider, parent_rate, rate);126 127 return rate;128}129 130static int dove_set_clock(struct clk_hw *hw, unsigned long rate,131 unsigned long parent_rate)132{133 struct dove_clk *dc = to_dove_clk(hw);134 u32 mask, load, div;135 int divider;136 137 divider = dove_calc_divider(dc, rate, parent_rate, true);138 if (divider < 0)139 return divider;140 141 pr_debug("%s(): %s divider=%u parent=%lu rate=%lu\n",142 __func__, dc->name, divider, parent_rate, rate);143 144 div = (u32)divider << dc->div_bit_start;145 mask = ~(~0 << dc->div_bit_size) << dc->div_bit_start;146 load = BIT(dc->div_bit_load);147 148 spin_lock(dc->lock);149 dove_load_divider(dc->base, div, mask, load);150 spin_unlock(dc->lock);151 152 return 0;153}154 155static const struct clk_ops dove_divider_ops = {156 .set_rate = dove_set_clock,157 .round_rate = dove_round_rate,158 .recalc_rate = dove_recalc_rate,159};160 161static struct clk *clk_register_dove_divider(struct device *dev,162 struct dove_clk *dc, const char **parent_names, size_t num_parents,163 void __iomem *base)164{165 char name[32];166 struct clk_init_data init = {167 .name = name,168 .ops = &dove_divider_ops,169 .parent_names = parent_names,170 .num_parents = num_parents,171 };172 173 strscpy(name, dc->name, sizeof(name));174 175 dc->hw.init = &init;176 dc->base = base;177 dc->div_bit_size = dc->div_bit_end - dc->div_bit_start + 1;178 179 return clk_register(dev, &dc->hw);180}181 182static DEFINE_SPINLOCK(dove_divider_lock);183 184static u32 axi_divider[] = {-1, 2, 1, 3, 4, 6, 5, 7, 8, 10, 9, 0};185 186static struct dove_clk dove_hw_clocks[4] = {187 {188 .name = "axi",189 .lock = &dove_divider_lock,190 .div_bit_start = 1,191 .div_bit_end = 6,192 .div_bit_load = 7,193 .divider_table = axi_divider,194 }, {195 .name = "gpu",196 .lock = &dove_divider_lock,197 .div_bit_start = 8,198 .div_bit_end = 13,199 .div_bit_load = 14,200 }, {201 .name = "vmeta",202 .lock = &dove_divider_lock,203 .div_bit_start = 15,204 .div_bit_end = 20,205 .div_bit_load = 21,206 }, {207 .name = "lcd",208 .lock = &dove_divider_lock,209 .div_bit_start = 22,210 .div_bit_end = 27,211 .div_bit_load = 28,212 },213};214 215static const char *core_pll[] = {216 "core-pll",217};218 219static int dove_divider_init(struct device *dev, void __iomem *base,220 struct clk **clks)221{222 struct clk *clk;223 int i;224 225 /*226 * Create the core PLL clock. We treat this as a fixed rate227 * clock as we don't know any better, and documentation is sparse.228 */229 clk = clk_register_fixed_rate(dev, core_pll[0], NULL, 0, 2000000000UL);230 if (IS_ERR(clk))231 return PTR_ERR(clk);232 233 for (i = 0; i < ARRAY_SIZE(dove_hw_clocks); i++)234 clks[i] = clk_register_dove_divider(dev, &dove_hw_clocks[i],235 core_pll,236 ARRAY_SIZE(core_pll), base);237 238 return 0;239}240 241static struct clk *dove_divider_clocks[4];242 243static struct clk_onecell_data dove_divider_data = {244 .clks = dove_divider_clocks,245 .clk_num = ARRAY_SIZE(dove_divider_clocks),246};247 248void __init dove_divider_clk_init(struct device_node *np)249{250 void __iomem *base;251 252 base = of_iomap(np, 0);253 if (WARN_ON(!base))254 return;255 256 if (WARN_ON(dove_divider_init(NULL, base, dove_divider_clocks))) {257 iounmap(base);258 return;259 }260 261 of_clk_add_provider(np, of_clk_src_onecell_get, &dove_divider_data);262}263