brintos

brintos / linux-shallow public Read only

0
0
Text · 7.8 KiB · a3467aa Raw
304 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Clock driver for Loongson-1 SoC4 *5 * Copyright (C) 2012-2023 Keguang Zhang <keguang.zhang@gmail.com>6 */7 8#include <linux/bits.h>9#include <linux/clk-provider.h>10#include <linux/container_of.h>11#include <linux/io.h>12#include <linux/of_address.h>13#include <linux/slab.h>14#include <linux/spinlock.h>15#include <linux/printk.h>16 17#include <dt-bindings/clock/loongson,ls1x-clk.h>18 19/* Loongson 1 Clock Register Definitions */20#define CLK_PLL_FREQ		0x021#define CLK_PLL_DIV		0x422 23static DEFINE_SPINLOCK(ls1x_clk_div_lock);24 25struct ls1x_clk_pll_data {26	u32 fixed;27	u8 shift;28	u8 int_shift;29	u8 int_width;30	u8 frac_shift;31	u8 frac_width;32};33 34struct ls1x_clk_div_data {35	u8 shift;36	u8 width;37	unsigned long flags;38	const struct clk_div_table *table;39	u8 bypass_shift;40	u8 bypass_inv;41	spinlock_t *lock;	/* protect access to DIV registers */42};43 44struct ls1x_clk {45	void __iomem *reg;46	unsigned int offset;47	struct clk_hw hw;48	const void *data;49};50 51#define to_ls1x_clk(_hw) container_of(_hw, struct ls1x_clk, hw)52 53static inline unsigned long ls1x_pll_rate_part(unsigned int val,54					       unsigned int shift,55					       unsigned int width)56{57	return (val & GENMASK(shift + width, shift)) >> shift;58}59 60static unsigned long ls1x_pll_recalc_rate(struct clk_hw *hw,61					  unsigned long parent_rate)62{63	struct ls1x_clk *ls1x_clk = to_ls1x_clk(hw);64	const struct ls1x_clk_pll_data *d = ls1x_clk->data;65	u32 val, rate;66 67	val = readl(ls1x_clk->reg);68	rate = d->fixed;69	rate += ls1x_pll_rate_part(val, d->int_shift, d->int_width);70	if (d->frac_width)71		rate += ls1x_pll_rate_part(val, d->frac_shift, d->frac_width);72	rate *= parent_rate;73	rate >>= d->shift;74 75	return rate;76}77 78static const struct clk_ops ls1x_pll_clk_ops = {79	.recalc_rate = ls1x_pll_recalc_rate,80};81 82static unsigned long ls1x_divider_recalc_rate(struct clk_hw *hw,83					      unsigned long parent_rate)84{85	struct ls1x_clk *ls1x_clk = to_ls1x_clk(hw);86	const struct ls1x_clk_div_data *d = ls1x_clk->data;87	unsigned int val;88 89	val = readl(ls1x_clk->reg) >> d->shift;90	val &= clk_div_mask(d->width);91 92	return divider_recalc_rate(hw, parent_rate, val, d->table,93				   d->flags, d->width);94}95 96static long ls1x_divider_round_rate(struct clk_hw *hw, unsigned long rate,97				    unsigned long *prate)98{99	struct ls1x_clk *ls1x_clk = to_ls1x_clk(hw);100	const struct ls1x_clk_div_data *d = ls1x_clk->data;101 102	return divider_round_rate(hw, rate, prate, d->table,103				  d->width, d->flags);104}105 106static int ls1x_divider_set_rate(struct clk_hw *hw, unsigned long rate,107				 unsigned long parent_rate)108{109	struct ls1x_clk *ls1x_clk = to_ls1x_clk(hw);110	const struct ls1x_clk_div_data *d = ls1x_clk->data;111	int val, div_val;112	unsigned long flags = 0;113 114	div_val = divider_get_val(rate, parent_rate, d->table,115				  d->width, d->flags);116	if (div_val < 0)117		return div_val;118 119	spin_lock_irqsave(d->lock, flags);120 121	/* Bypass the clock */122	val = readl(ls1x_clk->reg);123	if (d->bypass_inv)124		val &= ~BIT(d->bypass_shift);125	else126		val |= BIT(d->bypass_shift);127	writel(val, ls1x_clk->reg);128 129	val = readl(ls1x_clk->reg);130	val &= ~(clk_div_mask(d->width) << d->shift);131	val |= (u32)div_val << d->shift;132	writel(val, ls1x_clk->reg);133 134	/* Restore the clock */135	val = readl(ls1x_clk->reg);136	if (d->bypass_inv)137		val |= BIT(d->bypass_shift);138	else139		val &= ~BIT(d->bypass_shift);140	writel(val, ls1x_clk->reg);141 142	spin_unlock_irqrestore(d->lock, flags);143 144	return 0;145}146 147static const struct clk_ops ls1x_clk_divider_ops = {148	.recalc_rate = ls1x_divider_recalc_rate,149	.round_rate = ls1x_divider_round_rate,150	.set_rate = ls1x_divider_set_rate,151};152 153#define LS1X_CLK_PLL(_name, _offset, _fixed, _shift,			\154		     f_shift, f_width, i_shift, i_width)		\155struct ls1x_clk _name = {						\156	.offset = (_offset),						\157	.data = &(const struct ls1x_clk_pll_data) {			\158		.fixed = (_fixed),					\159		.shift = (_shift),					\160		.int_shift = (i_shift),					\161		.int_width = (i_width),					\162		.frac_shift = (f_shift),				\163		.frac_width = (f_width),				\164	},								\165	.hw.init = &(const struct clk_init_data) {			\166		.name = #_name,						\167		.ops = &ls1x_pll_clk_ops,				\168		.parent_data = &(const struct clk_parent_data) {	\169			.fw_name = "xtal",				\170			.name = "xtal",					\171			.index = -1,					\172		},							\173		.num_parents = 1,					\174	},								\175}176 177#define LS1X_CLK_DIV(_name, _pname, _offset, _shift, _width,		\178		     _table, _bypass_shift, _bypass_inv, _flags)	\179struct ls1x_clk _name = {						\180	.offset = (_offset),						\181	.data = &(const struct ls1x_clk_div_data){			\182		.shift = (_shift),					\183		.width = (_width),					\184		.table = (_table),					\185		.flags = (_flags),					\186		.bypass_shift = (_bypass_shift),			\187		.bypass_inv = (_bypass_inv),				\188		.lock = &ls1x_clk_div_lock,				\189	},								\190	.hw.init = &(const struct clk_init_data) {			\191		.name = #_name,						\192		.ops = &ls1x_clk_divider_ops,				\193		.parent_hws = (const struct clk_hw *[]) { _pname },	\194		.num_parents = 1,					\195		.flags = CLK_GET_RATE_NOCACHE,				\196	},								\197}198 199static LS1X_CLK_PLL(ls1b_clk_pll, CLK_PLL_FREQ, 12, 1, 0, 5, 0, 0);200static LS1X_CLK_DIV(ls1b_clk_cpu, &ls1b_clk_pll.hw, CLK_PLL_DIV,201		    20, 4, NULL, 8, 0,202		    CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ROUND_CLOSEST);203static LS1X_CLK_DIV(ls1b_clk_dc, &ls1b_clk_pll.hw, CLK_PLL_DIV,204		    26, 4, NULL, 12, 0, CLK_DIVIDER_ONE_BASED);205static LS1X_CLK_DIV(ls1b_clk_ahb, &ls1b_clk_pll.hw, CLK_PLL_DIV,206		    14, 4, NULL, 10, 0, CLK_DIVIDER_ONE_BASED);207static CLK_FIXED_FACTOR(ls1b_clk_apb, "ls1b_clk_apb", "ls1b_clk_ahb", 2, 1,208			CLK_SET_RATE_PARENT);209 210static struct clk_hw_onecell_data ls1b_clk_hw_data = {211	.hws = {212		[LS1X_CLKID_PLL] = &ls1b_clk_pll.hw,213		[LS1X_CLKID_CPU] = &ls1b_clk_cpu.hw,214		[LS1X_CLKID_DC] = &ls1b_clk_dc.hw,215		[LS1X_CLKID_AHB] = &ls1b_clk_ahb.hw,216		[LS1X_CLKID_APB] = &ls1b_clk_apb.hw,217	},218	.num = CLK_NR_CLKS,219};220 221static const struct clk_div_table ls1c_ahb_div_table[] = {222	[0] = { .val = 0, .div = 2 },223	[1] = { .val = 1, .div = 4 },224	[2] = { .val = 2, .div = 3 },225	[3] = { .val = 3, .div = 3 },226	[4] = { /* sentinel */ }227};228 229static LS1X_CLK_PLL(ls1c_clk_pll, CLK_PLL_FREQ, 0, 2, 8, 8, 16, 8);230static LS1X_CLK_DIV(ls1c_clk_cpu, &ls1c_clk_pll.hw, CLK_PLL_DIV,231		    8, 7, NULL, 0, 1,232		    CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ROUND_CLOSEST);233static LS1X_CLK_DIV(ls1c_clk_dc, &ls1c_clk_pll.hw, CLK_PLL_DIV,234		    24, 7, NULL, 4, 1, CLK_DIVIDER_ONE_BASED);235static LS1X_CLK_DIV(ls1c_clk_ahb, &ls1c_clk_cpu.hw, CLK_PLL_FREQ,236		    0, 2, ls1c_ahb_div_table, 0, 0, CLK_DIVIDER_ALLOW_ZERO);237static CLK_FIXED_FACTOR(ls1c_clk_apb, "ls1c_clk_apb", "ls1c_clk_ahb", 1, 1,238			CLK_SET_RATE_PARENT);239 240static struct clk_hw_onecell_data ls1c_clk_hw_data = {241	.hws = {242		[LS1X_CLKID_PLL] = &ls1c_clk_pll.hw,243		[LS1X_CLKID_CPU] = &ls1c_clk_cpu.hw,244		[LS1X_CLKID_DC] = &ls1c_clk_dc.hw,245		[LS1X_CLKID_AHB] = &ls1c_clk_ahb.hw,246		[LS1X_CLKID_APB] = &ls1c_clk_apb.hw,247	},248	.num = CLK_NR_CLKS,249};250 251static void __init ls1x_clk_init(struct device_node *np,252				 struct clk_hw_onecell_data *hw_data)253{254	struct ls1x_clk *ls1x_clk;255	void __iomem *reg;256	int i, ret;257 258	reg = of_iomap(np, 0);259	if (!reg) {260		pr_err("Unable to map base for %pOF\n", np);261		return;262	}263 264	for (i = 0; i < hw_data->num; i++) {265		/* array might be sparse */266		if (!hw_data->hws[i])267			continue;268 269		if (i != LS1X_CLKID_APB) {270			ls1x_clk = to_ls1x_clk(hw_data->hws[i]);271			ls1x_clk->reg = reg + ls1x_clk->offset;272		}273 274		ret = of_clk_hw_register(np, hw_data->hws[i]);275		if (ret)276			goto err;277	}278 279	ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, hw_data);280	if (!ret)281		return;282 283err:284	pr_err("Failed to register %pOF\n", np);285 286	while (--i >= 0)287		clk_hw_unregister(hw_data->hws[i]);288 289	iounmap(reg);290}291 292static void __init ls1b_clk_init(struct device_node *np)293{294	return ls1x_clk_init(np, &ls1b_clk_hw_data);295}296 297static void __init ls1c_clk_init(struct device_node *np)298{299	return ls1x_clk_init(np, &ls1c_clk_hw_data);300}301 302CLK_OF_DECLARE(ls1b_clk, "loongson,ls1b-clk", ls1b_clk_init);303CLK_OF_DECLARE(ls1c_clk, "loongson,ls1c-clk", ls1c_clk_init);304