| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com> |
|---|
| 3 | | - * |
|---|
| 4 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 5 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 6 | | - * the Free Software Foundation; either version 2 of the License, or |
|---|
| 7 | | - * (at your option) any later version. |
|---|
| 8 | | - * |
|---|
| 9 | 4 | */ |
|---|
| 10 | 5 | |
|---|
| 11 | 6 | #include <linux/clk-provider.h> |
|---|
| .. | .. |
|---|
| 17 | 12 | |
|---|
| 18 | 13 | #include "pmc.h" |
|---|
| 19 | 14 | |
|---|
| 20 | | -#define PROG_SOURCE_MAX 5 |
|---|
| 21 | 15 | #define PROG_ID_MAX 7 |
|---|
| 22 | 16 | |
|---|
| 23 | 17 | #define PROG_STATUS_MASK(id) (1 << ((id) + 8)) |
|---|
| 24 | | -#define PROG_PRES_MASK 0x7 |
|---|
| 25 | | -#define PROG_PRES(layout, pckr) ((pckr >> layout->pres_shift) & PROG_PRES_MASK) |
|---|
| 18 | +#define PROG_PRES(layout, pckr) ((pckr >> layout->pres_shift) & layout->pres_mask) |
|---|
| 26 | 19 | #define PROG_MAX_RM9200_CSS 3 |
|---|
| 27 | | - |
|---|
| 28 | | -struct clk_programmable_layout { |
|---|
| 29 | | - u8 pres_shift; |
|---|
| 30 | | - u8 css_mask; |
|---|
| 31 | | - u8 have_slck_mck; |
|---|
| 32 | | -}; |
|---|
| 33 | 20 | |
|---|
| 34 | 21 | struct clk_programmable { |
|---|
| 35 | 22 | struct clk_hw hw; |
|---|
| 36 | 23 | struct regmap *regmap; |
|---|
| 24 | + u32 *mux_table; |
|---|
| 37 | 25 | u8 id; |
|---|
| 38 | 26 | const struct clk_programmable_layout *layout; |
|---|
| 39 | 27 | }; |
|---|
| .. | .. |
|---|
| 44 | 32 | unsigned long parent_rate) |
|---|
| 45 | 33 | { |
|---|
| 46 | 34 | struct clk_programmable *prog = to_clk_programmable(hw); |
|---|
| 35 | + const struct clk_programmable_layout *layout = prog->layout; |
|---|
| 47 | 36 | unsigned int pckr; |
|---|
| 37 | + unsigned long rate; |
|---|
| 48 | 38 | |
|---|
| 49 | 39 | regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr); |
|---|
| 50 | 40 | |
|---|
| 51 | | - return parent_rate >> PROG_PRES(prog->layout, pckr); |
|---|
| 41 | + if (layout->is_pres_direct) |
|---|
| 42 | + rate = parent_rate / (PROG_PRES(layout, pckr) + 1); |
|---|
| 43 | + else |
|---|
| 44 | + rate = parent_rate >> PROG_PRES(layout, pckr); |
|---|
| 45 | + |
|---|
| 46 | + return rate; |
|---|
| 52 | 47 | } |
|---|
| 53 | 48 | |
|---|
| 54 | 49 | static int clk_programmable_determine_rate(struct clk_hw *hw, |
|---|
| 55 | 50 | struct clk_rate_request *req) |
|---|
| 56 | 51 | { |
|---|
| 52 | + struct clk_programmable *prog = to_clk_programmable(hw); |
|---|
| 53 | + const struct clk_programmable_layout *layout = prog->layout; |
|---|
| 57 | 54 | struct clk_hw *parent; |
|---|
| 58 | 55 | long best_rate = -EINVAL; |
|---|
| 59 | 56 | unsigned long parent_rate; |
|---|
| 60 | | - unsigned long tmp_rate; |
|---|
| 57 | + unsigned long tmp_rate = 0; |
|---|
| 61 | 58 | int shift; |
|---|
| 62 | 59 | int i; |
|---|
| 63 | 60 | |
|---|
| .. | .. |
|---|
| 67 | 64 | continue; |
|---|
| 68 | 65 | |
|---|
| 69 | 66 | parent_rate = clk_hw_get_rate(parent); |
|---|
| 70 | | - for (shift = 0; shift < PROG_PRES_MASK; shift++) { |
|---|
| 71 | | - tmp_rate = parent_rate >> shift; |
|---|
| 72 | | - if (tmp_rate <= req->rate) |
|---|
| 73 | | - break; |
|---|
| 67 | + if (layout->is_pres_direct) { |
|---|
| 68 | + for (shift = 0; shift <= layout->pres_mask; shift++) { |
|---|
| 69 | + tmp_rate = parent_rate / (shift + 1); |
|---|
| 70 | + if (tmp_rate <= req->rate) |
|---|
| 71 | + break; |
|---|
| 72 | + } |
|---|
| 73 | + } else { |
|---|
| 74 | + for (shift = 0; shift < layout->pres_mask; shift++) { |
|---|
| 75 | + tmp_rate = parent_rate >> shift; |
|---|
| 76 | + if (tmp_rate <= req->rate) |
|---|
| 77 | + break; |
|---|
| 78 | + } |
|---|
| 74 | 79 | } |
|---|
| 75 | 80 | |
|---|
| 76 | 81 | if (tmp_rate > req->rate) |
|---|
| .. | .. |
|---|
| 104 | 109 | if (layout->have_slck_mck) |
|---|
| 105 | 110 | mask |= AT91_PMC_CSSMCK_MCK; |
|---|
| 106 | 111 | |
|---|
| 112 | + if (prog->mux_table) |
|---|
| 113 | + pckr = clk_mux_index_to_val(prog->mux_table, 0, index); |
|---|
| 114 | + |
|---|
| 107 | 115 | if (index > layout->css_mask) { |
|---|
| 108 | 116 | if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck) |
|---|
| 109 | 117 | return -EINVAL; |
|---|
| .. | .. |
|---|
| 130 | 138 | if (layout->have_slck_mck && (pckr & AT91_PMC_CSSMCK_MCK) && !ret) |
|---|
| 131 | 139 | ret = PROG_MAX_RM9200_CSS + 1; |
|---|
| 132 | 140 | |
|---|
| 141 | + if (prog->mux_table) |
|---|
| 142 | + ret = clk_mux_val_to_index(&prog->hw, prog->mux_table, 0, ret); |
|---|
| 143 | + |
|---|
| 133 | 144 | return ret; |
|---|
| 134 | 145 | } |
|---|
| 135 | 146 | |
|---|
| .. | .. |
|---|
| 139 | 150 | struct clk_programmable *prog = to_clk_programmable(hw); |
|---|
| 140 | 151 | const struct clk_programmable_layout *layout = prog->layout; |
|---|
| 141 | 152 | unsigned long div = parent_rate / rate; |
|---|
| 142 | | - unsigned int pckr; |
|---|
| 143 | 153 | int shift = 0; |
|---|
| 144 | | - |
|---|
| 145 | | - regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr); |
|---|
| 146 | 154 | |
|---|
| 147 | 155 | if (!div) |
|---|
| 148 | 156 | return -EINVAL; |
|---|
| 149 | 157 | |
|---|
| 150 | | - shift = fls(div) - 1; |
|---|
| 158 | + if (layout->is_pres_direct) { |
|---|
| 159 | + shift = div - 1; |
|---|
| 151 | 160 | |
|---|
| 152 | | - if (div != (1 << shift)) |
|---|
| 153 | | - return -EINVAL; |
|---|
| 161 | + if (shift > layout->pres_mask) |
|---|
| 162 | + return -EINVAL; |
|---|
| 163 | + } else { |
|---|
| 164 | + shift = fls(div) - 1; |
|---|
| 154 | 165 | |
|---|
| 155 | | - if (shift >= PROG_PRES_MASK) |
|---|
| 156 | | - return -EINVAL; |
|---|
| 166 | + if (div != (1 << shift)) |
|---|
| 167 | + return -EINVAL; |
|---|
| 168 | + |
|---|
| 169 | + if (shift >= layout->pres_mask) |
|---|
| 170 | + return -EINVAL; |
|---|
| 171 | + } |
|---|
| 157 | 172 | |
|---|
| 158 | 173 | regmap_update_bits(prog->regmap, AT91_PMC_PCKR(prog->id), |
|---|
| 159 | | - PROG_PRES_MASK << layout->pres_shift, |
|---|
| 174 | + layout->pres_mask << layout->pres_shift, |
|---|
| 160 | 175 | shift << layout->pres_shift); |
|---|
| 161 | 176 | |
|---|
| 162 | 177 | return 0; |
|---|
| .. | .. |
|---|
| 170 | 185 | .set_rate = clk_programmable_set_rate, |
|---|
| 171 | 186 | }; |
|---|
| 172 | 187 | |
|---|
| 173 | | -static struct clk_hw * __init |
|---|
| 188 | +struct clk_hw * __init |
|---|
| 174 | 189 | at91_clk_register_programmable(struct regmap *regmap, |
|---|
| 175 | 190 | const char *name, const char **parent_names, |
|---|
| 176 | 191 | u8 num_parents, u8 id, |
|---|
| 177 | | - const struct clk_programmable_layout *layout) |
|---|
| 192 | + const struct clk_programmable_layout *layout, |
|---|
| 193 | + u32 *mux_table) |
|---|
| 178 | 194 | { |
|---|
| 179 | 195 | struct clk_programmable *prog; |
|---|
| 180 | 196 | struct clk_hw *hw; |
|---|
| 181 | | - struct clk_init_data init = {}; |
|---|
| 197 | + struct clk_init_data init; |
|---|
| 182 | 198 | int ret; |
|---|
| 183 | 199 | |
|---|
| 184 | 200 | if (id > PROG_ID_MAX) |
|---|
| .. | .. |
|---|
| 198 | 214 | prog->layout = layout; |
|---|
| 199 | 215 | prog->hw.init = &init; |
|---|
| 200 | 216 | prog->regmap = regmap; |
|---|
| 217 | + prog->mux_table = mux_table; |
|---|
| 201 | 218 | |
|---|
| 202 | 219 | hw = &prog->hw; |
|---|
| 203 | 220 | ret = clk_hw_register(NULL, &prog->hw); |
|---|
| .. | .. |
|---|
| 211 | 228 | return hw; |
|---|
| 212 | 229 | } |
|---|
| 213 | 230 | |
|---|
| 214 | | -static const struct clk_programmable_layout at91rm9200_programmable_layout = { |
|---|
| 231 | +const struct clk_programmable_layout at91rm9200_programmable_layout = { |
|---|
| 232 | + .pres_mask = 0x7, |
|---|
| 215 | 233 | .pres_shift = 2, |
|---|
| 216 | 234 | .css_mask = 0x3, |
|---|
| 217 | 235 | .have_slck_mck = 0, |
|---|
| 236 | + .is_pres_direct = 0, |
|---|
| 218 | 237 | }; |
|---|
| 219 | 238 | |
|---|
| 220 | | -static const struct clk_programmable_layout at91sam9g45_programmable_layout = { |
|---|
| 239 | +const struct clk_programmable_layout at91sam9g45_programmable_layout = { |
|---|
| 240 | + .pres_mask = 0x7, |
|---|
| 221 | 241 | .pres_shift = 2, |
|---|
| 222 | 242 | .css_mask = 0x3, |
|---|
| 223 | 243 | .have_slck_mck = 1, |
|---|
| 244 | + .is_pres_direct = 0, |
|---|
| 224 | 245 | }; |
|---|
| 225 | 246 | |
|---|
| 226 | | -static const struct clk_programmable_layout at91sam9x5_programmable_layout = { |
|---|
| 247 | +const struct clk_programmable_layout at91sam9x5_programmable_layout = { |
|---|
| 248 | + .pres_mask = 0x7, |
|---|
| 227 | 249 | .pres_shift = 4, |
|---|
| 228 | 250 | .css_mask = 0x7, |
|---|
| 229 | 251 | .have_slck_mck = 0, |
|---|
| 252 | + .is_pres_direct = 0, |
|---|
| 230 | 253 | }; |
|---|
| 231 | | - |
|---|
| 232 | | -static void __init |
|---|
| 233 | | -of_at91_clk_prog_setup(struct device_node *np, |
|---|
| 234 | | - const struct clk_programmable_layout *layout) |
|---|
| 235 | | -{ |
|---|
| 236 | | - int num; |
|---|
| 237 | | - u32 id; |
|---|
| 238 | | - struct clk_hw *hw; |
|---|
| 239 | | - unsigned int num_parents; |
|---|
| 240 | | - const char *parent_names[PROG_SOURCE_MAX]; |
|---|
| 241 | | - const char *name; |
|---|
| 242 | | - struct device_node *progclknp; |
|---|
| 243 | | - struct regmap *regmap; |
|---|
| 244 | | - |
|---|
| 245 | | - num_parents = of_clk_get_parent_count(np); |
|---|
| 246 | | - if (num_parents == 0 || num_parents > PROG_SOURCE_MAX) |
|---|
| 247 | | - return; |
|---|
| 248 | | - |
|---|
| 249 | | - of_clk_parent_fill(np, parent_names, num_parents); |
|---|
| 250 | | - |
|---|
| 251 | | - num = of_get_child_count(np); |
|---|
| 252 | | - if (!num || num > (PROG_ID_MAX + 1)) |
|---|
| 253 | | - return; |
|---|
| 254 | | - |
|---|
| 255 | | - regmap = syscon_node_to_regmap(of_get_parent(np)); |
|---|
| 256 | | - if (IS_ERR(regmap)) |
|---|
| 257 | | - return; |
|---|
| 258 | | - |
|---|
| 259 | | - for_each_child_of_node(np, progclknp) { |
|---|
| 260 | | - if (of_property_read_u32(progclknp, "reg", &id)) |
|---|
| 261 | | - continue; |
|---|
| 262 | | - |
|---|
| 263 | | - if (of_property_read_string(np, "clock-output-names", &name)) |
|---|
| 264 | | - name = progclknp->name; |
|---|
| 265 | | - |
|---|
| 266 | | - hw = at91_clk_register_programmable(regmap, name, |
|---|
| 267 | | - parent_names, num_parents, |
|---|
| 268 | | - id, layout); |
|---|
| 269 | | - if (IS_ERR(hw)) |
|---|
| 270 | | - continue; |
|---|
| 271 | | - |
|---|
| 272 | | - of_clk_add_hw_provider(progclknp, of_clk_hw_simple_get, hw); |
|---|
| 273 | | - } |
|---|
| 274 | | -} |
|---|
| 275 | | - |
|---|
| 276 | | - |
|---|
| 277 | | -static void __init of_at91rm9200_clk_prog_setup(struct device_node *np) |
|---|
| 278 | | -{ |
|---|
| 279 | | - of_at91_clk_prog_setup(np, &at91rm9200_programmable_layout); |
|---|
| 280 | | -} |
|---|
| 281 | | -CLK_OF_DECLARE(at91rm9200_clk_prog, "atmel,at91rm9200-clk-programmable", |
|---|
| 282 | | - of_at91rm9200_clk_prog_setup); |
|---|
| 283 | | - |
|---|
| 284 | | -static void __init of_at91sam9g45_clk_prog_setup(struct device_node *np) |
|---|
| 285 | | -{ |
|---|
| 286 | | - of_at91_clk_prog_setup(np, &at91sam9g45_programmable_layout); |
|---|
| 287 | | -} |
|---|
| 288 | | -CLK_OF_DECLARE(at91sam9g45_clk_prog, "atmel,at91sam9g45-clk-programmable", |
|---|
| 289 | | - of_at91sam9g45_clk_prog_setup); |
|---|
| 290 | | - |
|---|
| 291 | | -static void __init of_at91sam9x5_clk_prog_setup(struct device_node *np) |
|---|
| 292 | | -{ |
|---|
| 293 | | - of_at91_clk_prog_setup(np, &at91sam9x5_programmable_layout); |
|---|
| 294 | | -} |
|---|
| 295 | | -CLK_OF_DECLARE(at91sam9x5_clk_prog, "atmel,at91sam9x5-clk-programmable", |
|---|
| 296 | | - of_at91sam9x5_clk_prog_setup); |
|---|