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
41struct hi3660_stub_clk_chan {
42 struct mbox_client cl;
43 struct mbox_chan *mbox;
44};
45
46struct 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
54static void __iomem *freq_reg;
55static struct hi3660_stub_clk_chan stub_clk_chan;
56
57static 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
70static 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
80static 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
98static 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
104static 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
111static 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
124static 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
161static const struct of_device_id hi3660_stub_clk_of_match[] = {
162 { .compatible = "hisilicon,hi3660-stub-clk", },
163 {}
164};
165
166static 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
174static int __init hi3660_stub_clk_init(void)
175{
176 return platform_driver_register(&hi3660_stub_clk_driver);
177}
178subsys_initcall(hi3660_stub_clk_init);
179

source code of linux/drivers/clk/hisilicon/clk-hi3660-stub.c