|
5 | 5 | #include <linux/clk-provider.h>
|
6 | 6 | #include <linux/mfd/syscon.h>
|
7 | 7 | #include <linux/of_address.h>
|
| 8 | +#include <linux/of_device.h> |
| 9 | +#include <linux/platform_device.h> |
8 | 10 | #include <linux/regmap.h>
|
9 | 11 | #include <linux/slab.h>
|
10 | 12 | #include <linux/spinlock.h>
|
@@ -107,6 +109,18 @@ static const struct aspeed_gate_data aspeed_gates[] = {
|
107 | 109 | [ASPEED_CLK_GATE_LHCCLK] = { 28, -1, "lhclk-gate", "lhclk", 0 }, /* LPC master/LPC+ */
|
108 | 110 | };
|
109 | 111 |
|
| 112 | +static const struct clk_div_table ast2500_mac_div_table[] = { |
| 113 | + { 0x0, 4 }, /* Yep, really. Aspeed confirmed this is correct */ |
| 114 | + { 0x1, 4 }, |
| 115 | + { 0x2, 6 }, |
| 116 | + { 0x3, 8 }, |
| 117 | + { 0x4, 10 }, |
| 118 | + { 0x5, 12 }, |
| 119 | + { 0x6, 14 }, |
| 120 | + { 0x7, 16 }, |
| 121 | + { 0 } |
| 122 | +}; |
| 123 | + |
110 | 124 | static const struct clk_div_table ast2400_div_table[] = {
|
111 | 125 | { 0x0, 2 },
|
112 | 126 | { 0x1, 4 },
|
@@ -172,6 +186,122 @@ static struct clk_hw *aspeed_ast2500_calc_pll(const char *name, u32 val)
|
172 | 186 | mult, div);
|
173 | 187 | }
|
174 | 188 |
|
| 189 | +struct aspeed_clk_soc_data { |
| 190 | + const struct clk_div_table *div_table; |
| 191 | + const struct clk_div_table *mac_div_table; |
| 192 | + struct clk_hw *(*calc_pll)(const char *name, u32 val); |
| 193 | +}; |
| 194 | + |
| 195 | +static const struct aspeed_clk_soc_data ast2500_data = { |
| 196 | + .div_table = ast2500_div_table, |
| 197 | + .mac_div_table = ast2500_mac_div_table, |
| 198 | + .calc_pll = aspeed_ast2500_calc_pll, |
| 199 | +}; |
| 200 | + |
| 201 | +static const struct aspeed_clk_soc_data ast2400_data = { |
| 202 | + .div_table = ast2400_div_table, |
| 203 | + .mac_div_table = ast2400_div_table, |
| 204 | + .calc_pll = aspeed_ast2400_calc_pll, |
| 205 | +}; |
| 206 | + |
| 207 | +static int aspeed_clk_probe(struct platform_device *pdev) |
| 208 | +{ |
| 209 | + const struct aspeed_clk_soc_data *soc_data; |
| 210 | + struct device *dev = &pdev->dev; |
| 211 | + struct regmap *map; |
| 212 | + struct clk_hw *hw; |
| 213 | + u32 val, rate; |
| 214 | + |
| 215 | + map = syscon_node_to_regmap(dev->of_node); |
| 216 | + if (IS_ERR(map)) { |
| 217 | + dev_err(dev, "no syscon regmap\n"); |
| 218 | + return PTR_ERR(map); |
| 219 | + } |
| 220 | + |
| 221 | + /* SoC generations share common layouts but have different divisors */ |
| 222 | + soc_data = of_device_get_match_data(dev); |
| 223 | + if (!soc_data) { |
| 224 | + dev_err(dev, "no match data for platform\n"); |
| 225 | + return -EINVAL; |
| 226 | + } |
| 227 | + |
| 228 | + /* UART clock div13 setting */ |
| 229 | + regmap_read(map, ASPEED_MISC_CTRL, &val); |
| 230 | + if (val & UART_DIV13_EN) |
| 231 | + rate = 24000000 / 13; |
| 232 | + else |
| 233 | + rate = 24000000; |
| 234 | + /* TODO: Find the parent data for the uart clock */ |
| 235 | + hw = clk_hw_register_fixed_rate(dev, "uart", NULL, 0, rate); |
| 236 | + if (IS_ERR(hw)) |
| 237 | + return PTR_ERR(hw); |
| 238 | + aspeed_clk_data->hws[ASPEED_CLK_UART] = hw; |
| 239 | + |
| 240 | + /* |
| 241 | + * Memory controller (M-PLL) PLL. This clock is configured by the |
| 242 | + * bootloader, and is exposed to Linux as a read-only clock rate. |
| 243 | + */ |
| 244 | + regmap_read(map, ASPEED_MPLL_PARAM, &val); |
| 245 | + hw = soc_data->calc_pll("mpll", val); |
| 246 | + if (IS_ERR(hw)) |
| 247 | + return PTR_ERR(hw); |
| 248 | + aspeed_clk_data->hws[ASPEED_CLK_MPLL] = hw; |
| 249 | + |
| 250 | + /* SD/SDIO clock divider (TODO: There's a gate too) */ |
| 251 | + hw = clk_hw_register_divider_table(dev, "sdio", "hpll", 0, |
| 252 | + scu_base + ASPEED_CLK_SELECTION, 12, 3, 0, |
| 253 | + soc_data->div_table, |
| 254 | + &aspeed_clk_lock); |
| 255 | + if (IS_ERR(hw)) |
| 256 | + return PTR_ERR(hw); |
| 257 | + aspeed_clk_data->hws[ASPEED_CLK_SDIO] = hw; |
| 258 | + |
| 259 | + /* MAC AHB bus clock divider */ |
| 260 | + hw = clk_hw_register_divider_table(dev, "mac", "hpll", 0, |
| 261 | + scu_base + ASPEED_CLK_SELECTION, 16, 3, 0, |
| 262 | + soc_data->mac_div_table, |
| 263 | + &aspeed_clk_lock); |
| 264 | + if (IS_ERR(hw)) |
| 265 | + return PTR_ERR(hw); |
| 266 | + aspeed_clk_data->hws[ASPEED_CLK_MAC] = hw; |
| 267 | + |
| 268 | + /* LPC Host (LHCLK) clock divider */ |
| 269 | + hw = clk_hw_register_divider_table(dev, "lhclk", "hpll", 0, |
| 270 | + scu_base + ASPEED_CLK_SELECTION, 20, 3, 0, |
| 271 | + soc_data->div_table, |
| 272 | + &aspeed_clk_lock); |
| 273 | + if (IS_ERR(hw)) |
| 274 | + return PTR_ERR(hw); |
| 275 | + aspeed_clk_data->hws[ASPEED_CLK_LHCLK] = hw; |
| 276 | + |
| 277 | + /* P-Bus (BCLK) clock divider */ |
| 278 | + hw = clk_hw_register_divider_table(dev, "bclk", "hpll", 0, |
| 279 | + scu_base + ASPEED_CLK_SELECTION_2, 0, 2, 0, |
| 280 | + soc_data->div_table, |
| 281 | + &aspeed_clk_lock); |
| 282 | + if (IS_ERR(hw)) |
| 283 | + return PTR_ERR(hw); |
| 284 | + aspeed_clk_data->hws[ASPEED_CLK_BCLK] = hw; |
| 285 | + |
| 286 | + return 0; |
| 287 | +}; |
| 288 | + |
| 289 | +static const struct of_device_id aspeed_clk_dt_ids[] = { |
| 290 | + { .compatible = "aspeed,ast2400-scu", .data = &ast2400_data }, |
| 291 | + { .compatible = "aspeed,ast2500-scu", .data = &ast2500_data }, |
| 292 | + { } |
| 293 | +}; |
| 294 | + |
| 295 | +static struct platform_driver aspeed_clk_driver = { |
| 296 | + .probe = aspeed_clk_probe, |
| 297 | + .driver = { |
| 298 | + .name = "aspeed-clk", |
| 299 | + .of_match_table = aspeed_clk_dt_ids, |
| 300 | + .suppress_bind_attrs = true, |
| 301 | + }, |
| 302 | +}; |
| 303 | +builtin_platform_driver(aspeed_clk_driver); |
| 304 | + |
175 | 305 | static void __init aspeed_ast2400_cc(struct regmap *map)
|
176 | 306 | {
|
177 | 307 | struct clk_hw *hw;
|
|
0 commit comments