xref: /linux/drivers/devfreq/event/exynos-nocp.c (revision 9f2c9170934eace462499ba0bfe042cc72900173)
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(nocp->regmap, NOCP_MAIN_CTL,
39 				NOCP_MAIN_CTL_STATEN_MASK, 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(nocp->regmap, NOCP_STAT_PERIOD, 0x0);
47 	if (ret < 0)
48 		goto out;
49 
50 	/* Set the IntEvent fields of *_SRC */
51 	ret = regmap_update_bits(nocp->regmap, 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(nocp->regmap, 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(nocp->regmap, 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(nocp->regmap, 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(nocp->regmap, NOCP_STAT_ALARM_MIN, 0x0);
78 	if (ret < 0)
79 		goto out;
80 
81 	ret = regmap_write(nocp->regmap, NOCP_STAT_ALARM_MAX, 0x0);
82 	if (ret < 0)
83 		goto out;
84 
85 	/* Set AlarmMode */
86 	ret = regmap_update_bits(nocp->regmap, 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(nocp->regmap, 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(nocp->regmap, 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(nocp->regmap, 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(nocp->regmap, 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(nocp->regmap, 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(nocp->regmap, 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(nocp->regmap, NOCP_MAIN_CTL,
136 				NOCP_MAIN_CTL_STATEN_MASK, 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(nocp->regmap, NOCP_COUNTERS_0_VAL, &counter[0]);
152 	if (ret < 0)
153 		goto out;
154 
155 	ret = regmap_read(nocp->regmap, NOCP_COUNTERS_1_VAL, &counter[1]);
156 	if (ret < 0)
157 		goto out;
158 
159 	ret = regmap_read(nocp->regmap, NOCP_COUNTERS_2_VAL, &counter[2]);
160 	if (ret < 0)
161 		goto out;
162 
163 	ret = regmap_read(nocp->regmap, NOCP_COUNTERS_3_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, "nocp");
213 	if (IS_ERR(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, 0, &res);
218 	if (IS_ERR(base))
219 		return PTR_ERR(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(nocp->regmap)) {
226 		dev_err(dev, "failed to initialize regmap\n");
227 		return PTR_ERR(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(&pdev->dev, 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(&pdev->dev, &nocp->desc);
259 	if (IS_ERR(nocp->edev)) {
260 		dev_err(&pdev->dev,
261 			"failed to add devfreq-event device\n");
262 		return PTR_ERR(nocp->edev);
263 	}
264 	platform_set_drvdata(pdev, nocp);
265 
266 	ret = clk_prepare_enable(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(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