18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/clk-provider.h>
78c2ecf20Sopenharmony_ci#include <linux/clkdev.h>
88c2ecf20Sopenharmony_ci#include <linux/clk/at91_pmc.h>
98c2ecf20Sopenharmony_ci#include <linux/of.h>
108c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
118c2ecf20Sopenharmony_ci#include <linux/regmap.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include "pmc.h"
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#define PROG_ID_MAX		7
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define PROG_STATUS_MASK(id)	(1 << ((id) + 8))
188c2ecf20Sopenharmony_ci#define PROG_PRES(layout, pckr)	((pckr >> layout->pres_shift) & layout->pres_mask)
198c2ecf20Sopenharmony_ci#define PROG_MAX_RM9200_CSS	3
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistruct clk_programmable {
228c2ecf20Sopenharmony_ci	struct clk_hw hw;
238c2ecf20Sopenharmony_ci	struct regmap *regmap;
248c2ecf20Sopenharmony_ci	u32 *mux_table;
258c2ecf20Sopenharmony_ci	u8 id;
268c2ecf20Sopenharmony_ci	const struct clk_programmable_layout *layout;
278c2ecf20Sopenharmony_ci};
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define to_clk_programmable(hw) container_of(hw, struct clk_programmable, hw)
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic unsigned long clk_programmable_recalc_rate(struct clk_hw *hw,
328c2ecf20Sopenharmony_ci						  unsigned long parent_rate)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	struct clk_programmable *prog = to_clk_programmable(hw);
358c2ecf20Sopenharmony_ci	const struct clk_programmable_layout *layout = prog->layout;
368c2ecf20Sopenharmony_ci	unsigned int pckr;
378c2ecf20Sopenharmony_ci	unsigned long rate;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr);
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	if (layout->is_pres_direct)
428c2ecf20Sopenharmony_ci		rate = parent_rate / (PROG_PRES(layout, pckr) + 1);
438c2ecf20Sopenharmony_ci	else
448c2ecf20Sopenharmony_ci		rate = parent_rate >> PROG_PRES(layout, pckr);
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	return rate;
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic int clk_programmable_determine_rate(struct clk_hw *hw,
508c2ecf20Sopenharmony_ci					   struct clk_rate_request *req)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	struct clk_programmable *prog = to_clk_programmable(hw);
538c2ecf20Sopenharmony_ci	const struct clk_programmable_layout *layout = prog->layout;
548c2ecf20Sopenharmony_ci	struct clk_hw *parent;
558c2ecf20Sopenharmony_ci	long best_rate = -EINVAL;
568c2ecf20Sopenharmony_ci	unsigned long parent_rate;
578c2ecf20Sopenharmony_ci	unsigned long tmp_rate = 0;
588c2ecf20Sopenharmony_ci	int shift;
598c2ecf20Sopenharmony_ci	int i;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
628c2ecf20Sopenharmony_ci		parent = clk_hw_get_parent_by_index(hw, i);
638c2ecf20Sopenharmony_ci		if (!parent)
648c2ecf20Sopenharmony_ci			continue;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci		parent_rate = clk_hw_get_rate(parent);
678c2ecf20Sopenharmony_ci		if (layout->is_pres_direct) {
688c2ecf20Sopenharmony_ci			for (shift = 0; shift <= layout->pres_mask; shift++) {
698c2ecf20Sopenharmony_ci				tmp_rate = parent_rate / (shift + 1);
708c2ecf20Sopenharmony_ci				if (tmp_rate <= req->rate)
718c2ecf20Sopenharmony_ci					break;
728c2ecf20Sopenharmony_ci			}
738c2ecf20Sopenharmony_ci		} else {
748c2ecf20Sopenharmony_ci			for (shift = 0; shift < layout->pres_mask; shift++) {
758c2ecf20Sopenharmony_ci				tmp_rate = parent_rate >> shift;
768c2ecf20Sopenharmony_ci				if (tmp_rate <= req->rate)
778c2ecf20Sopenharmony_ci					break;
788c2ecf20Sopenharmony_ci			}
798c2ecf20Sopenharmony_ci		}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci		if (tmp_rate > req->rate)
828c2ecf20Sopenharmony_ci			continue;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci		if (best_rate < 0 ||
858c2ecf20Sopenharmony_ci		    (req->rate - tmp_rate) < (req->rate - best_rate)) {
868c2ecf20Sopenharmony_ci			best_rate = tmp_rate;
878c2ecf20Sopenharmony_ci			req->best_parent_rate = parent_rate;
888c2ecf20Sopenharmony_ci			req->best_parent_hw = parent;
898c2ecf20Sopenharmony_ci		}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci		if (!best_rate)
928c2ecf20Sopenharmony_ci			break;
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	if (best_rate < 0)
968c2ecf20Sopenharmony_ci		return best_rate;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	req->rate = best_rate;
998c2ecf20Sopenharmony_ci	return 0;
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic int clk_programmable_set_parent(struct clk_hw *hw, u8 index)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	struct clk_programmable *prog = to_clk_programmable(hw);
1058c2ecf20Sopenharmony_ci	const struct clk_programmable_layout *layout = prog->layout;
1068c2ecf20Sopenharmony_ci	unsigned int mask = layout->css_mask;
1078c2ecf20Sopenharmony_ci	unsigned int pckr = index;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	if (layout->have_slck_mck)
1108c2ecf20Sopenharmony_ci		mask |= AT91_PMC_CSSMCK_MCK;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	if (prog->mux_table)
1138c2ecf20Sopenharmony_ci		pckr = clk_mux_index_to_val(prog->mux_table, 0, index);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	if (index > layout->css_mask) {
1168c2ecf20Sopenharmony_ci		if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck)
1178c2ecf20Sopenharmony_ci			return -EINVAL;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci		pckr |= AT91_PMC_CSSMCK_MCK;
1208c2ecf20Sopenharmony_ci	}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	regmap_update_bits(prog->regmap, AT91_PMC_PCKR(prog->id), mask, pckr);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	return 0;
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cistatic u8 clk_programmable_get_parent(struct clk_hw *hw)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	struct clk_programmable *prog = to_clk_programmable(hw);
1308c2ecf20Sopenharmony_ci	const struct clk_programmable_layout *layout = prog->layout;
1318c2ecf20Sopenharmony_ci	unsigned int pckr;
1328c2ecf20Sopenharmony_ci	u8 ret;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	ret = pckr & layout->css_mask;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	if (layout->have_slck_mck && (pckr & AT91_PMC_CSSMCK_MCK) && !ret)
1398c2ecf20Sopenharmony_ci		ret = PROG_MAX_RM9200_CSS + 1;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	if (prog->mux_table)
1428c2ecf20Sopenharmony_ci		ret = clk_mux_val_to_index(&prog->hw, prog->mux_table, 0, ret);
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	return ret;
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_cistatic int clk_programmable_set_rate(struct clk_hw *hw, unsigned long rate,
1488c2ecf20Sopenharmony_ci				     unsigned long parent_rate)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	struct clk_programmable *prog = to_clk_programmable(hw);
1518c2ecf20Sopenharmony_ci	const struct clk_programmable_layout *layout = prog->layout;
1528c2ecf20Sopenharmony_ci	unsigned long div = parent_rate / rate;
1538c2ecf20Sopenharmony_ci	int shift = 0;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	if (!div)
1568c2ecf20Sopenharmony_ci		return -EINVAL;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	if (layout->is_pres_direct) {
1598c2ecf20Sopenharmony_ci		shift = div - 1;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci		if (shift > layout->pres_mask)
1628c2ecf20Sopenharmony_ci			return -EINVAL;
1638c2ecf20Sopenharmony_ci	} else {
1648c2ecf20Sopenharmony_ci		shift = fls(div) - 1;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci		if (div != (1 << shift))
1678c2ecf20Sopenharmony_ci			return -EINVAL;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci		if (shift >= layout->pres_mask)
1708c2ecf20Sopenharmony_ci			return -EINVAL;
1718c2ecf20Sopenharmony_ci	}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	regmap_update_bits(prog->regmap, AT91_PMC_PCKR(prog->id),
1748c2ecf20Sopenharmony_ci			   layout->pres_mask << layout->pres_shift,
1758c2ecf20Sopenharmony_ci			   shift << layout->pres_shift);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	return 0;
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic const struct clk_ops programmable_ops = {
1818c2ecf20Sopenharmony_ci	.recalc_rate = clk_programmable_recalc_rate,
1828c2ecf20Sopenharmony_ci	.determine_rate = clk_programmable_determine_rate,
1838c2ecf20Sopenharmony_ci	.get_parent = clk_programmable_get_parent,
1848c2ecf20Sopenharmony_ci	.set_parent = clk_programmable_set_parent,
1858c2ecf20Sopenharmony_ci	.set_rate = clk_programmable_set_rate,
1868c2ecf20Sopenharmony_ci};
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_cistruct clk_hw * __init
1898c2ecf20Sopenharmony_ciat91_clk_register_programmable(struct regmap *regmap,
1908c2ecf20Sopenharmony_ci			       const char *name, const char **parent_names,
1918c2ecf20Sopenharmony_ci			       u8 num_parents, u8 id,
1928c2ecf20Sopenharmony_ci			       const struct clk_programmable_layout *layout,
1938c2ecf20Sopenharmony_ci			       u32 *mux_table)
1948c2ecf20Sopenharmony_ci{
1958c2ecf20Sopenharmony_ci	struct clk_programmable *prog;
1968c2ecf20Sopenharmony_ci	struct clk_hw *hw;
1978c2ecf20Sopenharmony_ci	struct clk_init_data init;
1988c2ecf20Sopenharmony_ci	int ret;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	if (id > PROG_ID_MAX)
2018c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	prog = kzalloc(sizeof(*prog), GFP_KERNEL);
2048c2ecf20Sopenharmony_ci	if (!prog)
2058c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	init.name = name;
2088c2ecf20Sopenharmony_ci	init.ops = &programmable_ops;
2098c2ecf20Sopenharmony_ci	init.parent_names = parent_names;
2108c2ecf20Sopenharmony_ci	init.num_parents = num_parents;
2118c2ecf20Sopenharmony_ci	init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	prog->id = id;
2148c2ecf20Sopenharmony_ci	prog->layout = layout;
2158c2ecf20Sopenharmony_ci	prog->hw.init = &init;
2168c2ecf20Sopenharmony_ci	prog->regmap = regmap;
2178c2ecf20Sopenharmony_ci	prog->mux_table = mux_table;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	hw = &prog->hw;
2208c2ecf20Sopenharmony_ci	ret = clk_hw_register(NULL, &prog->hw);
2218c2ecf20Sopenharmony_ci	if (ret) {
2228c2ecf20Sopenharmony_ci		kfree(prog);
2238c2ecf20Sopenharmony_ci		hw = ERR_PTR(ret);
2248c2ecf20Sopenharmony_ci	} else {
2258c2ecf20Sopenharmony_ci		pmc_register_pck(id);
2268c2ecf20Sopenharmony_ci	}
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci	return hw;
2298c2ecf20Sopenharmony_ci}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ciconst struct clk_programmable_layout at91rm9200_programmable_layout = {
2328c2ecf20Sopenharmony_ci	.pres_mask = 0x7,
2338c2ecf20Sopenharmony_ci	.pres_shift = 2,
2348c2ecf20Sopenharmony_ci	.css_mask = 0x3,
2358c2ecf20Sopenharmony_ci	.have_slck_mck = 0,
2368c2ecf20Sopenharmony_ci	.is_pres_direct = 0,
2378c2ecf20Sopenharmony_ci};
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ciconst struct clk_programmable_layout at91sam9g45_programmable_layout = {
2408c2ecf20Sopenharmony_ci	.pres_mask = 0x7,
2418c2ecf20Sopenharmony_ci	.pres_shift = 2,
2428c2ecf20Sopenharmony_ci	.css_mask = 0x3,
2438c2ecf20Sopenharmony_ci	.have_slck_mck = 1,
2448c2ecf20Sopenharmony_ci	.is_pres_direct = 0,
2458c2ecf20Sopenharmony_ci};
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ciconst struct clk_programmable_layout at91sam9x5_programmable_layout = {
2488c2ecf20Sopenharmony_ci	.pres_mask = 0x7,
2498c2ecf20Sopenharmony_ci	.pres_shift = 4,
2508c2ecf20Sopenharmony_ci	.css_mask = 0x7,
2518c2ecf20Sopenharmony_ci	.have_slck_mck = 0,
2528c2ecf20Sopenharmony_ci	.is_pres_direct = 0,
2538c2ecf20Sopenharmony_ci};
254