1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Hisilicon clock driver |
4 | * |
5 | * Copyright (c) 2013-2017 Hisilicon Limited. |
6 | * Copyright (c) 2017 Linaro Limited. |
7 | * |
8 | * Author: Kai Zhao <zhaokai1@hisilicon.com> |
9 | * Tao Wang <kevin.wangtao@hisilicon.com> |
10 | * Leo Yan <leo.yan@linaro.org> |
11 | */ |
12 | |
13 | #include <linux/clk-provider.h> |
14 | #include <linux/device.h> |
15 | #include <linux/err.h> |
16 | #include <linux/init.h> |
17 | #include <linux/io.h> |
18 | #include <linux/mailbox_client.h> |
19 | #include <linux/module.h> |
20 | #include <linux/of.h> |
21 | #include <linux/platform_device.h> |
22 | #include <dt-bindings/clock/hi3660-clock.h> |
23 | |
24 | #define HI3660_STUB_CLOCK_DATA (0x70) |
25 | #define MHZ (1000 * 1000) |
26 | |
27 | #define DEFINE_CLK_STUB(_id, _cmd, _name) \ |
28 | { \ |
29 | .id = (_id), \ |
30 | .cmd = (_cmd), \ |
31 | .hw.init = &(struct clk_init_data) { \ |
32 | .name = #_name, \ |
33 | .ops = &hi3660_stub_clk_ops, \ |
34 | .num_parents = 0, \ |
35 | .flags = CLK_GET_RATE_NOCACHE, \ |
36 | }, \ |
37 | }, |
38 | |
39 | #define to_stub_clk(_hw) container_of(_hw, struct hi3660_stub_clk, hw) |
40 | |
41 | struct hi3660_stub_clk_chan { |
42 | struct mbox_client cl; |
43 | struct mbox_chan *mbox; |
44 | }; |
45 | |
46 | struct hi3660_stub_clk { |
47 | unsigned int id; |
48 | struct clk_hw hw; |
49 | unsigned int cmd; |
50 | unsigned int msg[8]; |
51 | unsigned int rate; |
52 | }; |
53 | |
54 | static void __iomem *freq_reg; |
55 | static struct hi3660_stub_clk_chan stub_clk_chan; |
56 | |
57 | static unsigned long hi3660_stub_clk_recalc_rate(struct clk_hw *hw, |
58 | unsigned long parent_rate) |
59 | { |
60 | struct hi3660_stub_clk *stub_clk = to_stub_clk(hw); |
61 | |
62 | /* |
63 | * LPM3 writes back the CPU frequency in shared SRAM so read |
64 | * back the frequency. |
65 | */ |
66 | stub_clk->rate = readl(addr: freq_reg + (stub_clk->id << 2)) * MHZ; |
67 | return stub_clk->rate; |
68 | } |
69 | |
70 | static long hi3660_stub_clk_round_rate(struct clk_hw *hw, unsigned long rate, |
71 | unsigned long *prate) |
72 | { |
73 | /* |
74 | * LPM3 handles rate rounding so just return whatever |
75 | * rate is requested. |
76 | */ |
77 | return rate; |
78 | } |
79 | |
80 | static int hi3660_stub_clk_set_rate(struct clk_hw *hw, unsigned long rate, |
81 | unsigned long parent_rate) |
82 | { |
83 | struct hi3660_stub_clk *stub_clk = to_stub_clk(hw); |
84 | |
85 | stub_clk->msg[0] = stub_clk->cmd; |
86 | stub_clk->msg[1] = rate / MHZ; |
87 | |
88 | dev_dbg(stub_clk_chan.cl.dev, "set rate msg[0]=0x%x msg[1]=0x%x\n" , |
89 | stub_clk->msg[0], stub_clk->msg[1]); |
90 | |
91 | mbox_send_message(chan: stub_clk_chan.mbox, mssg: stub_clk->msg); |
92 | mbox_client_txdone(chan: stub_clk_chan.mbox, r: 0); |
93 | |
94 | stub_clk->rate = rate; |
95 | return 0; |
96 | } |
97 | |
98 | static const struct clk_ops hi3660_stub_clk_ops = { |
99 | .recalc_rate = hi3660_stub_clk_recalc_rate, |
100 | .round_rate = hi3660_stub_clk_round_rate, |
101 | .set_rate = hi3660_stub_clk_set_rate, |
102 | }; |
103 | |
104 | static struct hi3660_stub_clk hi3660_stub_clks[HI3660_CLK_STUB_NUM] = { |
105 | DEFINE_CLK_STUB(HI3660_CLK_STUB_CLUSTER0, 0x0001030A, "cpu-cluster.0" ) |
106 | DEFINE_CLK_STUB(HI3660_CLK_STUB_CLUSTER1, 0x0002030A, "cpu-cluster.1" ) |
107 | DEFINE_CLK_STUB(HI3660_CLK_STUB_GPU, 0x0003030A, "clk-g3d" ) |
108 | DEFINE_CLK_STUB(HI3660_CLK_STUB_DDR, 0x00040309, "clk-ddrc" ) |
109 | }; |
110 | |
111 | static struct clk_hw *hi3660_stub_clk_hw_get(struct of_phandle_args *clkspec, |
112 | void *data) |
113 | { |
114 | unsigned int idx = clkspec->args[0]; |
115 | |
116 | if (idx >= HI3660_CLK_STUB_NUM) { |
117 | pr_err("%s: invalid index %u\n" , __func__, idx); |
118 | return ERR_PTR(error: -EINVAL); |
119 | } |
120 | |
121 | return &hi3660_stub_clks[idx].hw; |
122 | } |
123 | |
124 | static int hi3660_stub_clk_probe(struct platform_device *pdev) |
125 | { |
126 | struct device *dev = &pdev->dev; |
127 | struct resource *res; |
128 | unsigned int i; |
129 | int ret; |
130 | |
131 | /* Use mailbox client without blocking */ |
132 | stub_clk_chan.cl.dev = dev; |
133 | stub_clk_chan.cl.tx_done = NULL; |
134 | stub_clk_chan.cl.tx_block = false; |
135 | stub_clk_chan.cl.knows_txdone = false; |
136 | |
137 | /* Allocate mailbox channel */ |
138 | stub_clk_chan.mbox = mbox_request_channel(cl: &stub_clk_chan.cl, index: 0); |
139 | if (IS_ERR(ptr: stub_clk_chan.mbox)) |
140 | return PTR_ERR(ptr: stub_clk_chan.mbox); |
141 | |
142 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
143 | if (!res) |
144 | return -EINVAL; |
145 | freq_reg = devm_ioremap(dev, offset: res->start, size: resource_size(res)); |
146 | if (!freq_reg) |
147 | return -ENOMEM; |
148 | |
149 | freq_reg += HI3660_STUB_CLOCK_DATA; |
150 | |
151 | for (i = 0; i < HI3660_CLK_STUB_NUM; i++) { |
152 | ret = devm_clk_hw_register(dev: &pdev->dev, hw: &hi3660_stub_clks[i].hw); |
153 | if (ret) |
154 | return ret; |
155 | } |
156 | |
157 | return devm_of_clk_add_hw_provider(dev: &pdev->dev, get: hi3660_stub_clk_hw_get, |
158 | data: hi3660_stub_clks); |
159 | } |
160 | |
161 | static const struct of_device_id hi3660_stub_clk_of_match[] = { |
162 | { .compatible = "hisilicon,hi3660-stub-clk" , }, |
163 | {} |
164 | }; |
165 | |
166 | static struct platform_driver hi3660_stub_clk_driver = { |
167 | .probe = hi3660_stub_clk_probe, |
168 | .driver = { |
169 | .name = "hi3660-stub-clk" , |
170 | .of_match_table = hi3660_stub_clk_of_match, |
171 | }, |
172 | }; |
173 | |
174 | static int __init hi3660_stub_clk_init(void) |
175 | { |
176 | return platform_driver_register(&hi3660_stub_clk_driver); |
177 | } |
178 | subsys_initcall(hi3660_stub_clk_init); |
179 | |