1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2016-2017 Imagination Technologies |
4 | * Author: Paul Burton <paul.burton@mips.com> |
5 | */ |
6 | |
7 | #define pr_fmt(fmt) "clk-boston: " fmt |
8 | |
9 | #include <linux/clk-provider.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/of.h> |
12 | #include <linux/regmap.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/mfd/syscon.h> |
15 | |
16 | #include <dt-bindings/clock/boston-clock.h> |
17 | |
18 | #define BOSTON_PLAT_MMCMDIV 0x30 |
19 | # define BOSTON_PLAT_MMCMDIV_CLK0DIV (0xff << 0) |
20 | # define BOSTON_PLAT_MMCMDIV_INPUT (0xff << 8) |
21 | # define BOSTON_PLAT_MMCMDIV_MUL (0xff << 16) |
22 | # define BOSTON_PLAT_MMCMDIV_CLK1DIV (0xff << 24) |
23 | |
24 | #define BOSTON_CLK_COUNT 3 |
25 | |
26 | static u32 ext_field(u32 val, u32 mask) |
27 | { |
28 | return (val & mask) >> (ffs(mask) - 1); |
29 | } |
30 | |
31 | static void __init clk_boston_setup(struct device_node *np) |
32 | { |
33 | unsigned long in_freq, cpu_freq, sys_freq; |
34 | uint mmcmdiv, mul, cpu_div, sys_div; |
35 | struct clk_hw_onecell_data *onecell; |
36 | struct regmap *regmap; |
37 | struct clk_hw *hw; |
38 | int err; |
39 | |
40 | regmap = syscon_node_to_regmap(np: np->parent); |
41 | if (IS_ERR(ptr: regmap)) { |
42 | pr_err("failed to find regmap\n" ); |
43 | return; |
44 | } |
45 | |
46 | err = regmap_read(map: regmap, BOSTON_PLAT_MMCMDIV, val: &mmcmdiv); |
47 | if (err) { |
48 | pr_err("failed to read mmcm_div register: %d\n" , err); |
49 | return; |
50 | } |
51 | |
52 | in_freq = ext_field(val: mmcmdiv, BOSTON_PLAT_MMCMDIV_INPUT) * 1000000; |
53 | mul = ext_field(val: mmcmdiv, BOSTON_PLAT_MMCMDIV_MUL); |
54 | |
55 | sys_div = ext_field(val: mmcmdiv, BOSTON_PLAT_MMCMDIV_CLK0DIV); |
56 | sys_freq = mult_frac(in_freq, mul, sys_div); |
57 | |
58 | cpu_div = ext_field(val: mmcmdiv, BOSTON_PLAT_MMCMDIV_CLK1DIV); |
59 | cpu_freq = mult_frac(in_freq, mul, cpu_div); |
60 | |
61 | onecell = kzalloc(struct_size(onecell, hws, BOSTON_CLK_COUNT), |
62 | GFP_KERNEL); |
63 | if (!onecell) |
64 | return; |
65 | |
66 | onecell->num = BOSTON_CLK_COUNT; |
67 | |
68 | hw = clk_hw_register_fixed_rate(NULL, "input" , NULL, 0, in_freq); |
69 | if (IS_ERR(ptr: hw)) { |
70 | pr_err("failed to register input clock: %ld\n" , PTR_ERR(hw)); |
71 | goto fail_input; |
72 | } |
73 | onecell->hws[BOSTON_CLK_INPUT] = hw; |
74 | |
75 | hw = clk_hw_register_fixed_rate(NULL, "sys" , "input" , 0, sys_freq); |
76 | if (IS_ERR(ptr: hw)) { |
77 | pr_err("failed to register sys clock: %ld\n" , PTR_ERR(hw)); |
78 | goto fail_sys; |
79 | } |
80 | onecell->hws[BOSTON_CLK_SYS] = hw; |
81 | |
82 | hw = clk_hw_register_fixed_rate(NULL, "cpu" , "input" , 0, cpu_freq); |
83 | if (IS_ERR(ptr: hw)) { |
84 | pr_err("failed to register cpu clock: %ld\n" , PTR_ERR(hw)); |
85 | goto fail_cpu; |
86 | } |
87 | onecell->hws[BOSTON_CLK_CPU] = hw; |
88 | |
89 | err = of_clk_add_hw_provider(np, get: of_clk_hw_onecell_get, data: onecell); |
90 | if (err) { |
91 | pr_err("failed to add DT provider: %d\n" , err); |
92 | goto fail_clk_add; |
93 | } |
94 | |
95 | return; |
96 | |
97 | fail_clk_add: |
98 | clk_hw_unregister_fixed_rate(hw: onecell->hws[BOSTON_CLK_CPU]); |
99 | fail_cpu: |
100 | clk_hw_unregister_fixed_rate(hw: onecell->hws[BOSTON_CLK_SYS]); |
101 | fail_sys: |
102 | clk_hw_unregister_fixed_rate(hw: onecell->hws[BOSTON_CLK_INPUT]); |
103 | fail_input: |
104 | kfree(objp: onecell); |
105 | } |
106 | |
107 | /* |
108 | * Use CLK_OF_DECLARE so that this driver is probed early enough to provide the |
109 | * CPU frequency for use with the GIC or cop0 counters/timers. |
110 | */ |
111 | CLK_OF_DECLARE(clk_boston, "img,boston-clock" , clk_boston_setup); |
112 | |