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 
27 struct 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 
clk_programmable_get_rate(struct clk * clk)39 static 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 
clk_programmable_set_parent(struct clk * clk,struct clk * parent)56 static 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 
clk_programmable_set_rate(struct clk * clk,ulong rate)88 static 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 
124 static 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 
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)130 struct 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 
179 U_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 
186 const 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 
194 const 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 
202 const 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