1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Programmable clock support for AT91 architectures.
4 *
5 * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
6 *
7 * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
8 *
9 * Based on drivers/clk/at91/clk-programmable.c from Linux.
10 */
11#include <common.h>
12#include <clk-uclass.h>
13#include <dm.h>
14#include <linux/clk-provider.h>
15#include <linux/clk/at91_pmc.h>
16
17#include "pmc.h"
18
19#define UBOOT_DM_CLK_AT91_PROG		"at91-prog-clk"
20
21#define PROG_ID_MAX		7
22
23#define PROG_STATUS_MASK(id)	(1 << ((id) + 8))
24#define PROG_PRES(_l, _p)	(((_p) >> (_l)->pres_shift) & (_l)->pres_mask)
25#define PROG_MAX_RM9200_CSS	3
26
27struct clk_programmable {
28	void __iomem *base;
29	const u32 *clk_mux_table;
30	const u32 *mux_table;
31	const struct clk_programmable_layout *layout;
32	u32 num_parents;
33	struct clk clk;
34	u8 id;
35};
36
37#define to_clk_programmable(_c) container_of(_c, struct clk_programmable, clk)
38
39static ulong clk_programmable_get_rate(struct clk *clk)
40{
41	struct clk_programmable *prog = to_clk_programmable(clk);
42	const struct clk_programmable_layout *layout = prog->layout;
43	ulong rate, parent_rate = clk_get_parent_rate(clk);
44	unsigned int pckr;
45
46	pmc_read(prog->base, AT91_PMC_PCKR(prog->id), &pckr);
47
48	if (layout->is_pres_direct)
49		rate = parent_rate / (PROG_PRES(layout, pckr) + 1);
50	else
51		rate = parent_rate >> PROG_PRES(layout, pckr);
52
53	return rate;
54}
55
56static int clk_programmable_set_parent(struct clk *clk, struct clk *parent)
57{
58	struct clk_programmable *prog = to_clk_programmable(clk);
59	const struct clk_programmable_layout *layout = prog->layout;
60	unsigned int mask = layout->css_mask;
61	int index;
62
63	index = at91_clk_mux_val_to_index(prog->clk_mux_table,
64					  prog->num_parents, parent->id);
65	if (index < 0)
66		return index;
67
68	index = at91_clk_mux_index_to_val(prog->mux_table, prog->num_parents,
69					  index);
70	if (index < 0)
71		return index;
72
73	if (layout->have_slck_mck)
74		mask |= AT91_PMC_CSSMCK_MCK;
75
76	if (index > layout->css_mask) {
77		if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck)
78			return -EINVAL;
79
80		index |= AT91_PMC_CSSMCK_MCK;
81	}
82
83	pmc_update_bits(prog->base, AT91_PMC_PCKR(prog->id), mask, index);
84
85	return 0;
86}
87
88static ulong clk_programmable_set_rate(struct clk *clk, ulong rate)
89{
90	struct clk_programmable *prog = to_clk_programmable(clk);
91	const struct clk_programmable_layout *layout = prog->layout;
92	ulong parent_rate = clk_get_parent_rate(clk);
93	ulong div = parent_rate / rate;
94	int shift = 0;
95
96	if (!parent_rate || !div)
97		return -EINVAL;
98
99	if (layout->is_pres_direct) {
100		shift = div - 1;
101
102		if (shift > layout->pres_mask)
103			return -EINVAL;
104	} else {
105		shift = fls(div) - 1;
106
107		if (div != (1 << shift))
108			return -EINVAL;
109
110		if (shift >= layout->pres_mask)
111			return -EINVAL;
112	}
113
114	pmc_update_bits(prog->base, AT91_PMC_PCKR(prog->id),
115			layout->pres_mask << layout->pres_shift,
116			shift << layout->pres_shift);
117
118	if (layout->is_pres_direct)
119		return (parent_rate / shift + 1);
120
121	return parent_rate >> shift;
122}
123
124static const struct clk_ops programmable_ops = {
125	.get_rate = clk_programmable_get_rate,
126	.set_parent = clk_programmable_set_parent,
127	.set_rate = clk_programmable_set_rate,
128};
129
130struct clk *at91_clk_register_programmable(void __iomem *base, const char *name,
131			const char *const *parent_names, u8 num_parents, u8 id,
132			const struct clk_programmable_layout *layout,
133			const u32 *clk_mux_table, const u32 *mux_table)
134{
135	struct clk_programmable *prog;
136	struct clk *clk;
137	u32 val, tmp;
138	int ret;
139
140	if (!base || !name || !parent_names || !num_parents ||
141	    !layout || !clk_mux_table || !mux_table || id > PROG_ID_MAX)
142		return ERR_PTR(-EINVAL);
143
144	prog = kzalloc(sizeof(*prog), GFP_KERNEL);
145	if (!prog)
146		return ERR_PTR(-ENOMEM);
147
148	prog->id = id;
149	prog->layout = layout;
150	prog->base = base;
151	prog->clk_mux_table = clk_mux_table;
152	prog->mux_table = mux_table;
153	prog->num_parents = num_parents;
154
155	pmc_read(prog->base, AT91_PMC_PCKR(prog->id), &tmp);
156	val = tmp & prog->layout->css_mask;
157	if (layout->have_slck_mck && (tmp & AT91_PMC_CSSMCK_MCK) && !val)
158		ret = PROG_MAX_RM9200_CSS + 1;
159	else
160		ret = at91_clk_mux_val_to_index(prog->mux_table,
161						prog->num_parents, val);
162	if (ret < 0) {
163		kfree(prog);
164		return ERR_PTR(ret);
165	}
166
167	clk = &prog->clk;
168	clk->flags = CLK_GET_RATE_NOCACHE;
169	ret = clk_register(clk, UBOOT_DM_CLK_AT91_PROG, name,
170			   parent_names[ret]);
171	if (ret) {
172		kfree(prog);
173		clk = ERR_PTR(ret);
174	}
175
176	return clk;
177}
178
179U_BOOT_DRIVER(at91_prog_clk) = {
180	.name = UBOOT_DM_CLK_AT91_PROG,
181	.id = UCLASS_CLK,
182	.ops = &programmable_ops,
183	.flags = DM_FLAG_PRE_RELOC,
184};
185
186const struct clk_programmable_layout at91rm9200_programmable_layout = {
187	.pres_mask = 0x7,
188	.pres_shift = 2,
189	.css_mask = 0x3,
190	.have_slck_mck = 0,
191	.is_pres_direct = 0,
192};
193
194const struct clk_programmable_layout at91sam9g45_programmable_layout = {
195	.pres_mask = 0x7,
196	.pres_shift = 2,
197	.css_mask = 0x3,
198	.have_slck_mck = 1,
199	.is_pres_direct = 0,
200};
201
202const struct clk_programmable_layout at91sam9x5_programmable_layout = {
203	.pres_mask = 0x7,
204	.pres_shift = 4,
205	.css_mask = 0x7,
206	.have_slck_mck = 0,
207	.is_pres_direct = 0,
208};
209