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