1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * exynos-nocp.c - Exynos NoC (Network On Chip) Probe support |
4 | * |
5 | * Copyright (c) 2016 Samsung Electronics Co., Ltd. |
6 | * Author : Chanwoo Choi <cw00.choi@samsung.com> |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/module.h> |
11 | #include <linux/devfreq-event.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/of_address.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/regmap.h> |
16 | |
17 | #include "exynos-nocp.h" |
18 | |
19 | struct exynos_nocp { |
20 | struct devfreq_event_dev *edev; |
21 | struct devfreq_event_desc desc; |
22 | |
23 | struct device *dev; |
24 | |
25 | struct regmap *regmap; |
26 | struct clk *clk; |
27 | }; |
28 | |
29 | /* |
30 | * The devfreq-event ops structure for nocp probe. |
31 | */ |
32 | static int exynos_nocp_set_event(struct devfreq_event_dev *edev) |
33 | { |
34 | struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev); |
35 | int ret; |
36 | |
37 | /* Disable NoC probe */ |
38 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_MAIN_CTL, |
39 | NOCP_MAIN_CTL_STATEN_MASK, val: 0); |
40 | if (ret < 0) { |
41 | dev_err(nocp->dev, "failed to disable the NoC probe device\n" ); |
42 | return ret; |
43 | } |
44 | |
45 | /* Set a statistics dump period to 0 */ |
46 | ret = regmap_write(map: nocp->regmap, reg: NOCP_STAT_PERIOD, val: 0x0); |
47 | if (ret < 0) |
48 | goto out; |
49 | |
50 | /* Set the IntEvent fields of *_SRC */ |
51 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_COUNTERS_0_SRC, |
52 | NOCP_CNT_SRC_INTEVENT_MASK, |
53 | NOCP_CNT_SRC_INTEVENT_BYTE_MASK); |
54 | if (ret < 0) |
55 | goto out; |
56 | |
57 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_COUNTERS_1_SRC, |
58 | NOCP_CNT_SRC_INTEVENT_MASK, |
59 | NOCP_CNT_SRC_INTEVENT_CHAIN_MASK); |
60 | if (ret < 0) |
61 | goto out; |
62 | |
63 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_COUNTERS_2_SRC, |
64 | NOCP_CNT_SRC_INTEVENT_MASK, |
65 | NOCP_CNT_SRC_INTEVENT_CYCLE_MASK); |
66 | if (ret < 0) |
67 | goto out; |
68 | |
69 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_COUNTERS_3_SRC, |
70 | NOCP_CNT_SRC_INTEVENT_MASK, |
71 | NOCP_CNT_SRC_INTEVENT_CHAIN_MASK); |
72 | if (ret < 0) |
73 | goto out; |
74 | |
75 | |
76 | /* Set an alarm with a max/min value of 0 to generate StatALARM */ |
77 | ret = regmap_write(map: nocp->regmap, reg: NOCP_STAT_ALARM_MIN, val: 0x0); |
78 | if (ret < 0) |
79 | goto out; |
80 | |
81 | ret = regmap_write(map: nocp->regmap, reg: NOCP_STAT_ALARM_MAX, val: 0x0); |
82 | if (ret < 0) |
83 | goto out; |
84 | |
85 | /* Set AlarmMode */ |
86 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_COUNTERS_0_ALARM_MODE, |
87 | NOCP_CNT_ALARM_MODE_MASK, |
88 | NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); |
89 | if (ret < 0) |
90 | goto out; |
91 | |
92 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_COUNTERS_1_ALARM_MODE, |
93 | NOCP_CNT_ALARM_MODE_MASK, |
94 | NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); |
95 | if (ret < 0) |
96 | goto out; |
97 | |
98 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_COUNTERS_2_ALARM_MODE, |
99 | NOCP_CNT_ALARM_MODE_MASK, |
100 | NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); |
101 | if (ret < 0) |
102 | goto out; |
103 | |
104 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_COUNTERS_3_ALARM_MODE, |
105 | NOCP_CNT_ALARM_MODE_MASK, |
106 | NOCP_CNT_ALARM_MODE_MIN_MAX_MASK); |
107 | if (ret < 0) |
108 | goto out; |
109 | |
110 | /* Enable the measurements by setting AlarmEn and StatEn */ |
111 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_MAIN_CTL, |
112 | NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK, |
113 | NOCP_MAIN_CTL_STATEN_MASK | NOCP_MAIN_CTL_ALARMEN_MASK); |
114 | if (ret < 0) |
115 | goto out; |
116 | |
117 | /* Set GlobalEN */ |
118 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_CFG_CTL, |
119 | NOCP_CFG_CTL_GLOBALEN_MASK, |
120 | NOCP_CFG_CTL_GLOBALEN_MASK); |
121 | if (ret < 0) |
122 | goto out; |
123 | |
124 | /* Enable NoC probe */ |
125 | ret = regmap_update_bits(map: nocp->regmap, reg: NOCP_MAIN_CTL, |
126 | NOCP_MAIN_CTL_STATEN_MASK, |
127 | NOCP_MAIN_CTL_STATEN_MASK); |
128 | if (ret < 0) |
129 | goto out; |
130 | |
131 | return 0; |
132 | |
133 | out: |
134 | /* Reset NoC probe */ |
135 | if (regmap_update_bits(map: nocp->regmap, reg: NOCP_MAIN_CTL, |
136 | NOCP_MAIN_CTL_STATEN_MASK, val: 0)) { |
137 | dev_err(nocp->dev, "Failed to reset NoC probe device\n" ); |
138 | } |
139 | |
140 | return ret; |
141 | } |
142 | |
143 | static int exynos_nocp_get_event(struct devfreq_event_dev *edev, |
144 | struct devfreq_event_data *edata) |
145 | { |
146 | struct exynos_nocp *nocp = devfreq_event_get_drvdata(edev); |
147 | unsigned int counter[4]; |
148 | int ret; |
149 | |
150 | /* Read cycle count */ |
151 | ret = regmap_read(map: nocp->regmap, reg: NOCP_COUNTERS_0_VAL, val: &counter[0]); |
152 | if (ret < 0) |
153 | goto out; |
154 | |
155 | ret = regmap_read(map: nocp->regmap, reg: NOCP_COUNTERS_1_VAL, val: &counter[1]); |
156 | if (ret < 0) |
157 | goto out; |
158 | |
159 | ret = regmap_read(map: nocp->regmap, reg: NOCP_COUNTERS_2_VAL, val: &counter[2]); |
160 | if (ret < 0) |
161 | goto out; |
162 | |
163 | ret = regmap_read(map: nocp->regmap, reg: NOCP_COUNTERS_3_VAL, val: &counter[3]); |
164 | if (ret < 0) |
165 | goto out; |
166 | |
167 | edata->load_count = ((counter[1] << 16) | counter[0]); |
168 | edata->total_count = ((counter[3] << 16) | counter[2]); |
169 | |
170 | dev_dbg(&edev->dev, "%s (event: %ld/%ld)\n" , edev->desc->name, |
171 | edata->load_count, edata->total_count); |
172 | |
173 | return 0; |
174 | |
175 | out: |
176 | dev_err(nocp->dev, "Failed to read the counter of NoC probe device\n" ); |
177 | |
178 | return ret; |
179 | } |
180 | |
181 | static const struct devfreq_event_ops exynos_nocp_ops = { |
182 | .set_event = exynos_nocp_set_event, |
183 | .get_event = exynos_nocp_get_event, |
184 | }; |
185 | |
186 | static const struct of_device_id exynos_nocp_id_match[] = { |
187 | { .compatible = "samsung,exynos5420-nocp" , }, |
188 | { /* sentinel */ }, |
189 | }; |
190 | MODULE_DEVICE_TABLE(of, exynos_nocp_id_match); |
191 | |
192 | static struct regmap_config exynos_nocp_regmap_config = { |
193 | .reg_bits = 32, |
194 | .val_bits = 32, |
195 | .reg_stride = 4, |
196 | .max_register = NOCP_COUNTERS_3_VAL, |
197 | }; |
198 | |
199 | static int exynos_nocp_parse_dt(struct platform_device *pdev, |
200 | struct exynos_nocp *nocp) |
201 | { |
202 | struct device *dev = nocp->dev; |
203 | struct device_node *np = dev->of_node; |
204 | struct resource *res; |
205 | void __iomem *base; |
206 | |
207 | if (!np) { |
208 | dev_err(dev, "failed to find devicetree node\n" ); |
209 | return -EINVAL; |
210 | } |
211 | |
212 | nocp->clk = devm_clk_get(dev, id: "nocp" ); |
213 | if (IS_ERR(ptr: nocp->clk)) |
214 | nocp->clk = NULL; |
215 | |
216 | /* Maps the memory mapped IO to control nocp register */ |
217 | base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
218 | if (IS_ERR(ptr: base)) |
219 | return PTR_ERR(ptr: base); |
220 | |
221 | exynos_nocp_regmap_config.max_register = resource_size(res) - 4; |
222 | |
223 | nocp->regmap = devm_regmap_init_mmio(dev, base, |
224 | &exynos_nocp_regmap_config); |
225 | if (IS_ERR(ptr: nocp->regmap)) { |
226 | dev_err(dev, "failed to initialize regmap\n" ); |
227 | return PTR_ERR(ptr: nocp->regmap); |
228 | } |
229 | |
230 | return 0; |
231 | } |
232 | |
233 | static int exynos_nocp_probe(struct platform_device *pdev) |
234 | { |
235 | struct device *dev = &pdev->dev; |
236 | struct device_node *np = dev->of_node; |
237 | struct exynos_nocp *nocp; |
238 | int ret; |
239 | |
240 | nocp = devm_kzalloc(dev: &pdev->dev, size: sizeof(*nocp), GFP_KERNEL); |
241 | if (!nocp) |
242 | return -ENOMEM; |
243 | |
244 | nocp->dev = &pdev->dev; |
245 | |
246 | /* Parse dt data to get resource */ |
247 | ret = exynos_nocp_parse_dt(pdev, nocp); |
248 | if (ret < 0) { |
249 | dev_err(&pdev->dev, |
250 | "failed to parse devicetree for resource\n" ); |
251 | return ret; |
252 | } |
253 | |
254 | /* Add devfreq-event device to measure the bandwidth of NoC */ |
255 | nocp->desc.ops = &exynos_nocp_ops; |
256 | nocp->desc.driver_data = nocp; |
257 | nocp->desc.name = np->full_name; |
258 | nocp->edev = devm_devfreq_event_add_edev(dev: &pdev->dev, desc: &nocp->desc); |
259 | if (IS_ERR(ptr: nocp->edev)) { |
260 | dev_err(&pdev->dev, |
261 | "failed to add devfreq-event device\n" ); |
262 | return PTR_ERR(ptr: nocp->edev); |
263 | } |
264 | platform_set_drvdata(pdev, data: nocp); |
265 | |
266 | ret = clk_prepare_enable(clk: nocp->clk); |
267 | if (ret) { |
268 | dev_err(&pdev->dev, "failed to prepare ppmu clock\n" ); |
269 | return ret; |
270 | } |
271 | |
272 | pr_info("exynos-nocp: new NoC Probe device registered: %s\n" , |
273 | dev_name(dev)); |
274 | |
275 | return 0; |
276 | } |
277 | |
278 | static int exynos_nocp_remove(struct platform_device *pdev) |
279 | { |
280 | struct exynos_nocp *nocp = platform_get_drvdata(pdev); |
281 | |
282 | clk_disable_unprepare(clk: nocp->clk); |
283 | |
284 | return 0; |
285 | } |
286 | |
287 | static struct platform_driver exynos_nocp_driver = { |
288 | .probe = exynos_nocp_probe, |
289 | .remove = exynos_nocp_remove, |
290 | .driver = { |
291 | .name = "exynos-nocp" , |
292 | .of_match_table = exynos_nocp_id_match, |
293 | }, |
294 | }; |
295 | module_platform_driver(exynos_nocp_driver); |
296 | |
297 | MODULE_DESCRIPTION("Exynos NoC (Network on Chip) Probe driver" ); |
298 | MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>" ); |
299 | MODULE_LICENSE("GPL" ); |
300 | |