257 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (c) 2014 Marvell Technology Group Ltd.4 *5 * Alexandre Belloni <alexandre.belloni@free-electrons.com>6 * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>7 */8#include <linux/bitops.h>9#include <linux/clk-provider.h>10#include <linux/io.h>11#include <linux/of.h>12#include <linux/of_address.h>13#include <linux/slab.h>14#include <linux/spinlock.h>15 16#include "berlin2-div.h"17 18/*19 * Clock dividers in Berlin2 SoCs comprise a complex cell to select20 * input pll and divider. The virtual structure as it is used in Marvell21 * BSP code can be seen as:22 *23 * +---+24 * pll0 --------------->| 0 | +---+25 * +---+ |(B)|--+--------------->| 0 | +---+26 * pll1.0 -->| 0 | +-->| 1 | | +--------+ |(E)|----->| 0 | +---+27 * pll1.1 -->| 1 | | +---+ +-->|(C) 1:M |-->| 1 | |(F)|-->|(G)|->28 * ... -->|(A)|--+ | +--------+ +---+ +-->| 1 | +---+29 * ... -->| | +-->|(D) 1:3 |----------+ +---+30 * pll1.N -->| N | +---------31 * +---+32 *33 * (A) input pll clock mux controlled by <PllSelect[1:n]>34 * (B) input pll bypass mux controlled by <PllSwitch>35 * (C) programmable clock divider controlled by <Select[1:n]>36 * (D) constant div-by-3 clock divider37 * (E) programmable clock divider bypass controlled by <Switch>38 * (F) constant div-by-3 clock mux controlled by <D3Switch>39 * (G) clock gate controlled by <Enable>40 *41 * For whatever reason, above control signals come in two flavors:42 * - single register dividers with all bits in one register43 * - shared register dividers with bits spread over multiple registers44 * (including signals for the same cell spread over consecutive registers)45 *46 * Also, clock gate and pll mux is not available on every div cell, so47 * we have to deal with those, too. We reuse common clock composite driver48 * for it.49 */50 51#define PLL_SELECT_MASK 0x752#define DIV_SELECT_MASK 0x753 54struct berlin2_div {55 struct clk_hw hw;56 void __iomem *base;57 struct berlin2_div_map map;58 spinlock_t *lock;59};60 61#define to_berlin2_div(hw) container_of(hw, struct berlin2_div, hw)62 63static u8 clk_div[] = { 1, 2, 4, 6, 8, 12, 1, 1 };64 65static int berlin2_div_is_enabled(struct clk_hw *hw)66{67 struct berlin2_div *div = to_berlin2_div(hw);68 struct berlin2_div_map *map = &div->map;69 u32 reg;70 71 if (div->lock)72 spin_lock(div->lock);73 74 reg = readl_relaxed(div->base + map->gate_offs);75 reg >>= map->gate_shift;76 77 if (div->lock)78 spin_unlock(div->lock);79 80 return (reg & 0x1);81}82 83static int berlin2_div_enable(struct clk_hw *hw)84{85 struct berlin2_div *div = to_berlin2_div(hw);86 struct berlin2_div_map *map = &div->map;87 u32 reg;88 89 if (div->lock)90 spin_lock(div->lock);91 92 reg = readl_relaxed(div->base + map->gate_offs);93 reg |= BIT(map->gate_shift);94 writel_relaxed(reg, div->base + map->gate_offs);95 96 if (div->lock)97 spin_unlock(div->lock);98 99 return 0;100}101 102static void berlin2_div_disable(struct clk_hw *hw)103{104 struct berlin2_div *div = to_berlin2_div(hw);105 struct berlin2_div_map *map = &div->map;106 u32 reg;107 108 if (div->lock)109 spin_lock(div->lock);110 111 reg = readl_relaxed(div->base + map->gate_offs);112 reg &= ~BIT(map->gate_shift);113 writel_relaxed(reg, div->base + map->gate_offs);114 115 if (div->lock)116 spin_unlock(div->lock);117}118 119static int berlin2_div_set_parent(struct clk_hw *hw, u8 index)120{121 struct berlin2_div *div = to_berlin2_div(hw);122 struct berlin2_div_map *map = &div->map;123 u32 reg;124 125 if (div->lock)126 spin_lock(div->lock);127 128 /* index == 0 is PLL_SWITCH */129 reg = readl_relaxed(div->base + map->pll_switch_offs);130 if (index == 0)131 reg &= ~BIT(map->pll_switch_shift);132 else133 reg |= BIT(map->pll_switch_shift);134 writel_relaxed(reg, div->base + map->pll_switch_offs);135 136 /* index > 0 is PLL_SELECT */137 if (index > 0) {138 reg = readl_relaxed(div->base + map->pll_select_offs);139 reg &= ~(PLL_SELECT_MASK << map->pll_select_shift);140 reg |= (index - 1) << map->pll_select_shift;141 writel_relaxed(reg, div->base + map->pll_select_offs);142 }143 144 if (div->lock)145 spin_unlock(div->lock);146 147 return 0;148}149 150static u8 berlin2_div_get_parent(struct clk_hw *hw)151{152 struct berlin2_div *div = to_berlin2_div(hw);153 struct berlin2_div_map *map = &div->map;154 u32 reg;155 u8 index = 0;156 157 if (div->lock)158 spin_lock(div->lock);159 160 /* PLL_SWITCH == 0 is index 0 */161 reg = readl_relaxed(div->base + map->pll_switch_offs);162 reg &= BIT(map->pll_switch_shift);163 if (reg) {164 reg = readl_relaxed(div->base + map->pll_select_offs);165 reg >>= map->pll_select_shift;166 reg &= PLL_SELECT_MASK;167 index = 1 + reg;168 }169 170 if (div->lock)171 spin_unlock(div->lock);172 173 return index;174}175 176static unsigned long berlin2_div_recalc_rate(struct clk_hw *hw,177 unsigned long parent_rate)178{179 struct berlin2_div *div = to_berlin2_div(hw);180 struct berlin2_div_map *map = &div->map;181 u32 divsw, div3sw, divider = 1;182 183 if (div->lock)184 spin_lock(div->lock);185 186 divsw = readl_relaxed(div->base + map->div_switch_offs) &187 (1 << map->div_switch_shift);188 div3sw = readl_relaxed(div->base + map->div3_switch_offs) &189 (1 << map->div3_switch_shift);190 191 /* constant divide-by-3 (dominant) */192 if (div3sw != 0) {193 divider = 3;194 /* divider can be bypassed with DIV_SWITCH == 0 */195 } else if (divsw == 0) {196 divider = 1;197 /* clock divider determined by DIV_SELECT */198 } else {199 u32 reg;200 reg = readl_relaxed(div->base + map->div_select_offs);201 reg >>= map->div_select_shift;202 reg &= DIV_SELECT_MASK;203 divider = clk_div[reg];204 }205 206 if (div->lock)207 spin_unlock(div->lock);208 209 return parent_rate / divider;210}211 212static const struct clk_ops berlin2_div_rate_ops = {213 .determine_rate = clk_hw_determine_rate_no_reparent,214 .recalc_rate = berlin2_div_recalc_rate,215};216 217static const struct clk_ops berlin2_div_gate_ops = {218 .is_enabled = berlin2_div_is_enabled,219 .enable = berlin2_div_enable,220 .disable = berlin2_div_disable,221};222 223static const struct clk_ops berlin2_div_mux_ops = {224 .set_parent = berlin2_div_set_parent,225 .get_parent = berlin2_div_get_parent,226};227 228struct clk_hw * __init229berlin2_div_register(const struct berlin2_div_map *map,230 void __iomem *base, const char *name, u8 div_flags,231 const char **parent_names, int num_parents,232 unsigned long flags, spinlock_t *lock)233{234 const struct clk_ops *mux_ops = &berlin2_div_mux_ops;235 const struct clk_ops *rate_ops = &berlin2_div_rate_ops;236 const struct clk_ops *gate_ops = &berlin2_div_gate_ops;237 struct berlin2_div *div;238 239 div = kzalloc(sizeof(*div), GFP_KERNEL);240 if (!div)241 return ERR_PTR(-ENOMEM);242 243 /* copy div_map to allow __initconst */244 memcpy(&div->map, map, sizeof(*map));245 div->base = base;246 div->lock = lock;247 248 if ((div_flags & BERLIN2_DIV_HAS_GATE) == 0)249 gate_ops = NULL;250 if ((div_flags & BERLIN2_DIV_HAS_MUX) == 0)251 mux_ops = NULL;252 253 return clk_hw_register_composite(NULL, name, parent_names, num_parents,254 &div->hw, mux_ops, &div->hw, rate_ops,255 &div->hw, gate_ops, flags);256}257