brintos

brintos / linux-shallow public Read only

0
0
Text · 6.8 KiB · 0a248bf Raw
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