1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * MStar MSC313 MPLL driver |
4 | * |
5 | * Copyright (C) 2020 Daniel Palmer <daniel@thingy.jp> |
6 | */ |
7 | |
8 | #include <linux/platform_device.h> |
9 | #include <linux/of_address.h> |
10 | #include <linux/clk-provider.h> |
11 | #include <linux/regmap.h> |
12 | |
13 | #define REG_CONFIG1 0x8 |
14 | #define REG_CONFIG2 0xc |
15 | |
16 | static const struct regmap_config msc313_mpll_regmap_config = { |
17 | .reg_bits = 16, |
18 | .val_bits = 16, |
19 | .reg_stride = 4, |
20 | }; |
21 | |
22 | static const struct reg_field config1_loop_div_first = REG_FIELD(REG_CONFIG1, 8, 9); |
23 | static const struct reg_field config1_input_div_first = REG_FIELD(REG_CONFIG1, 4, 5); |
24 | static const struct reg_field config2_output_div_first = REG_FIELD(REG_CONFIG2, 12, 13); |
25 | static const struct reg_field config2_loop_div_second = REG_FIELD(REG_CONFIG2, 0, 7); |
26 | |
27 | static const unsigned int output_dividers[] = { |
28 | 2, 3, 4, 5, 6, 7, 10 |
29 | }; |
30 | |
31 | #define NUMOUTPUTS (ARRAY_SIZE(output_dividers) + 1) |
32 | |
33 | struct msc313_mpll { |
34 | struct clk_hw clk_hw; |
35 | struct regmap_field *input_div; |
36 | struct regmap_field *loop_div_first; |
37 | struct regmap_field *loop_div_second; |
38 | struct regmap_field *output_div; |
39 | struct clk_hw_onecell_data *clk_data; |
40 | }; |
41 | |
42 | #define to_mpll(_hw) container_of(_hw, struct msc313_mpll, clk_hw) |
43 | |
44 | static unsigned long msc313_mpll_recalc_rate(struct clk_hw *hw, |
45 | unsigned long parent_rate) |
46 | { |
47 | struct msc313_mpll *mpll = to_mpll(hw); |
48 | unsigned int input_div, output_div, loop_first, loop_second; |
49 | unsigned long output_rate; |
50 | |
51 | regmap_field_read(field: mpll->input_div, val: &input_div); |
52 | regmap_field_read(field: mpll->output_div, val: &output_div); |
53 | regmap_field_read(field: mpll->loop_div_first, val: &loop_first); |
54 | regmap_field_read(field: mpll->loop_div_second, val: &loop_second); |
55 | |
56 | output_rate = parent_rate / (1 << input_div); |
57 | output_rate *= (1 << loop_first) * max(loop_second, 1U); |
58 | output_rate /= max(output_div, 1U); |
59 | |
60 | return output_rate; |
61 | } |
62 | |
63 | static const struct clk_ops msc313_mpll_ops = { |
64 | .recalc_rate = msc313_mpll_recalc_rate, |
65 | }; |
66 | |
67 | static const struct clk_parent_data mpll_parent = { |
68 | .index = 0, |
69 | }; |
70 | |
71 | static int msc313_mpll_probe(struct platform_device *pdev) |
72 | { |
73 | void __iomem *base; |
74 | struct msc313_mpll *mpll; |
75 | struct clk_init_data clk_init = { }; |
76 | struct device *dev = &pdev->dev; |
77 | struct regmap *regmap; |
78 | char *outputname; |
79 | struct clk_hw *divhw; |
80 | int ret, i; |
81 | |
82 | mpll = devm_kzalloc(dev, size: sizeof(*mpll), GFP_KERNEL); |
83 | if (!mpll) |
84 | return -ENOMEM; |
85 | |
86 | base = devm_platform_ioremap_resource(pdev, index: 0); |
87 | if (IS_ERR(ptr: base)) |
88 | return PTR_ERR(ptr: base); |
89 | |
90 | regmap = devm_regmap_init_mmio(dev, base, &msc313_mpll_regmap_config); |
91 | if (IS_ERR(ptr: regmap)) |
92 | return PTR_ERR(ptr: regmap); |
93 | |
94 | mpll->input_div = devm_regmap_field_alloc(dev, regmap, reg_field: config1_input_div_first); |
95 | if (IS_ERR(ptr: mpll->input_div)) |
96 | return PTR_ERR(ptr: mpll->input_div); |
97 | mpll->output_div = devm_regmap_field_alloc(dev, regmap, reg_field: config2_output_div_first); |
98 | if (IS_ERR(ptr: mpll->output_div)) |
99 | return PTR_ERR(ptr: mpll->output_div); |
100 | mpll->loop_div_first = devm_regmap_field_alloc(dev, regmap, reg_field: config1_loop_div_first); |
101 | if (IS_ERR(ptr: mpll->loop_div_first)) |
102 | return PTR_ERR(ptr: mpll->loop_div_first); |
103 | mpll->loop_div_second = devm_regmap_field_alloc(dev, regmap, reg_field: config2_loop_div_second); |
104 | if (IS_ERR(ptr: mpll->loop_div_second)) |
105 | return PTR_ERR(ptr: mpll->loop_div_second); |
106 | |
107 | mpll->clk_data = devm_kzalloc(dev, struct_size(mpll->clk_data, hws, |
108 | ARRAY_SIZE(output_dividers)), GFP_KERNEL); |
109 | if (!mpll->clk_data) |
110 | return -ENOMEM; |
111 | |
112 | clk_init.name = dev_name(dev); |
113 | clk_init.ops = &msc313_mpll_ops; |
114 | clk_init.parent_data = &mpll_parent; |
115 | clk_init.num_parents = 1; |
116 | mpll->clk_hw.init = &clk_init; |
117 | |
118 | ret = devm_clk_hw_register(dev, hw: &mpll->clk_hw); |
119 | if (ret) |
120 | return ret; |
121 | |
122 | mpll->clk_data->num = NUMOUTPUTS; |
123 | mpll->clk_data->hws[0] = &mpll->clk_hw; |
124 | |
125 | for (i = 0; i < ARRAY_SIZE(output_dividers); i++) { |
126 | outputname = devm_kasprintf(dev, GFP_KERNEL, fmt: "%s_div_%u" , |
127 | clk_init.name, output_dividers[i]); |
128 | if (!outputname) |
129 | return -ENOMEM; |
130 | divhw = devm_clk_hw_register_fixed_factor(dev, name: outputname, |
131 | parent_name: clk_init.name, flags: 0, mult: 1, div: output_dividers[i]); |
132 | if (IS_ERR(ptr: divhw)) |
133 | return PTR_ERR(ptr: divhw); |
134 | mpll->clk_data->hws[i + 1] = divhw; |
135 | } |
136 | |
137 | platform_set_drvdata(pdev, data: mpll); |
138 | |
139 | return devm_of_clk_add_hw_provider(dev: &pdev->dev, get: of_clk_hw_onecell_get, |
140 | data: mpll->clk_data); |
141 | } |
142 | |
143 | static const struct of_device_id msc313_mpll_of_match[] = { |
144 | { .compatible = "mstar,msc313-mpll" , }, |
145 | {} |
146 | }; |
147 | |
148 | static struct platform_driver msc313_mpll_driver = { |
149 | .driver = { |
150 | .name = "mstar-msc313-mpll" , |
151 | .of_match_table = msc313_mpll_of_match, |
152 | }, |
153 | .probe = msc313_mpll_probe, |
154 | }; |
155 | builtin_platform_driver(msc313_mpll_driver); |
156 | |