1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Intel(R) Trace Hub PTI output driver |
4 | * |
5 | * Copyright (C) 2014-2016 Intel Corporation. |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | |
10 | #include <linux/types.h> |
11 | #include <linux/module.h> |
12 | #include <linux/device.h> |
13 | #include <linux/sizes.h> |
14 | #include <linux/printk.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/mm.h> |
17 | #include <linux/io.h> |
18 | |
19 | #include "intel_th.h" |
20 | #include "pti.h" |
21 | |
22 | struct pti_device { |
23 | void __iomem *base; |
24 | struct intel_th_device *thdev; |
25 | unsigned int mode; |
26 | unsigned int freeclk; |
27 | unsigned int clkdiv; |
28 | unsigned int patgen; |
29 | unsigned int lpp_dest_mask; |
30 | unsigned int lpp_dest; |
31 | }; |
32 | |
33 | /* map PTI widths to MODE settings of PTI_CTL register */ |
34 | static const unsigned int pti_mode[] = { |
35 | 0, 4, 8, 0, 12, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, |
36 | }; |
37 | |
38 | static int pti_width_mode(unsigned int width) |
39 | { |
40 | int i; |
41 | |
42 | for (i = 0; i < ARRAY_SIZE(pti_mode); i++) |
43 | if (pti_mode[i] == width) |
44 | return i; |
45 | |
46 | return -EINVAL; |
47 | } |
48 | |
49 | static ssize_t mode_show(struct device *dev, struct device_attribute *attr, |
50 | char *buf) |
51 | { |
52 | struct pti_device *pti = dev_get_drvdata(dev); |
53 | |
54 | return scnprintf(buf, PAGE_SIZE, fmt: "%d\n" , pti_mode[pti->mode]); |
55 | } |
56 | |
57 | static ssize_t mode_store(struct device *dev, struct device_attribute *attr, |
58 | const char *buf, size_t size) |
59 | { |
60 | struct pti_device *pti = dev_get_drvdata(dev); |
61 | unsigned long val; |
62 | int ret; |
63 | |
64 | ret = kstrtoul(s: buf, base: 10, res: &val); |
65 | if (ret) |
66 | return ret; |
67 | |
68 | ret = pti_width_mode(width: val); |
69 | if (ret < 0) |
70 | return ret; |
71 | |
72 | pti->mode = ret; |
73 | |
74 | return size; |
75 | } |
76 | |
77 | static DEVICE_ATTR_RW(mode); |
78 | |
79 | static ssize_t |
80 | freerunning_clock_show(struct device *dev, struct device_attribute *attr, |
81 | char *buf) |
82 | { |
83 | struct pti_device *pti = dev_get_drvdata(dev); |
84 | |
85 | return scnprintf(buf, PAGE_SIZE, fmt: "%d\n" , pti->freeclk); |
86 | } |
87 | |
88 | static ssize_t |
89 | freerunning_clock_store(struct device *dev, struct device_attribute *attr, |
90 | const char *buf, size_t size) |
91 | { |
92 | struct pti_device *pti = dev_get_drvdata(dev); |
93 | unsigned long val; |
94 | int ret; |
95 | |
96 | ret = kstrtoul(s: buf, base: 10, res: &val); |
97 | if (ret) |
98 | return ret; |
99 | |
100 | pti->freeclk = !!val; |
101 | |
102 | return size; |
103 | } |
104 | |
105 | static DEVICE_ATTR_RW(freerunning_clock); |
106 | |
107 | static ssize_t |
108 | clock_divider_show(struct device *dev, struct device_attribute *attr, |
109 | char *buf) |
110 | { |
111 | struct pti_device *pti = dev_get_drvdata(dev); |
112 | |
113 | return scnprintf(buf, PAGE_SIZE, fmt: "%d\n" , 1u << pti->clkdiv); |
114 | } |
115 | |
116 | static ssize_t |
117 | clock_divider_store(struct device *dev, struct device_attribute *attr, |
118 | const char *buf, size_t size) |
119 | { |
120 | struct pti_device *pti = dev_get_drvdata(dev); |
121 | unsigned long val; |
122 | int ret; |
123 | |
124 | ret = kstrtoul(s: buf, base: 10, res: &val); |
125 | if (ret) |
126 | return ret; |
127 | |
128 | if (!is_power_of_2(n: val) || val > 8 || !val) |
129 | return -EINVAL; |
130 | |
131 | pti->clkdiv = val; |
132 | |
133 | return size; |
134 | } |
135 | |
136 | static DEVICE_ATTR_RW(clock_divider); |
137 | |
138 | static struct attribute *pti_output_attrs[] = { |
139 | &dev_attr_mode.attr, |
140 | &dev_attr_freerunning_clock.attr, |
141 | &dev_attr_clock_divider.attr, |
142 | NULL, |
143 | }; |
144 | |
145 | static const struct attribute_group pti_output_group = { |
146 | .attrs = pti_output_attrs, |
147 | }; |
148 | |
149 | static int intel_th_pti_activate(struct intel_th_device *thdev) |
150 | { |
151 | struct pti_device *pti = dev_get_drvdata(dev: &thdev->dev); |
152 | u32 ctl = PTI_EN; |
153 | |
154 | if (pti->patgen) |
155 | ctl |= pti->patgen << __ffs(PTI_PATGENMODE); |
156 | if (pti->freeclk) |
157 | ctl |= PTI_FCEN; |
158 | ctl |= pti->mode << __ffs(PTI_MODE); |
159 | ctl |= pti->clkdiv << __ffs(PTI_CLKDIV); |
160 | ctl |= pti->lpp_dest << __ffs(LPP_DEST); |
161 | |
162 | iowrite32(ctl, pti->base + REG_PTI_CTL); |
163 | |
164 | intel_th_trace_enable(thdev); |
165 | |
166 | return 0; |
167 | } |
168 | |
169 | static void intel_th_pti_deactivate(struct intel_th_device *thdev) |
170 | { |
171 | struct pti_device *pti = dev_get_drvdata(dev: &thdev->dev); |
172 | |
173 | intel_th_trace_disable(thdev); |
174 | |
175 | iowrite32(0, pti->base + REG_PTI_CTL); |
176 | } |
177 | |
178 | static void read_hw_config(struct pti_device *pti) |
179 | { |
180 | u32 ctl = ioread32(pti->base + REG_PTI_CTL); |
181 | |
182 | pti->mode = (ctl & PTI_MODE) >> __ffs(PTI_MODE); |
183 | pti->clkdiv = (ctl & PTI_CLKDIV) >> __ffs(PTI_CLKDIV); |
184 | pti->freeclk = !!(ctl & PTI_FCEN); |
185 | |
186 | if (!pti_mode[pti->mode]) |
187 | pti->mode = pti_width_mode(width: 4); |
188 | if (!pti->clkdiv) |
189 | pti->clkdiv = 1; |
190 | |
191 | if (pti->thdev->output.type == GTH_LPP) { |
192 | if (ctl & LPP_PTIPRESENT) |
193 | pti->lpp_dest_mask |= LPP_DEST_PTI; |
194 | if (ctl & LPP_BSSBPRESENT) |
195 | pti->lpp_dest_mask |= LPP_DEST_EXI; |
196 | if (ctl & LPP_DEST) |
197 | pti->lpp_dest = 1; |
198 | } |
199 | } |
200 | |
201 | static int intel_th_pti_probe(struct intel_th_device *thdev) |
202 | { |
203 | struct device *dev = &thdev->dev; |
204 | struct resource *res; |
205 | struct pti_device *pti; |
206 | void __iomem *base; |
207 | |
208 | res = intel_th_device_get_resource(thdev, IORESOURCE_MEM, num: 0); |
209 | if (!res) |
210 | return -ENODEV; |
211 | |
212 | base = devm_ioremap(dev, offset: res->start, size: resource_size(res)); |
213 | if (!base) |
214 | return -ENOMEM; |
215 | |
216 | pti = devm_kzalloc(dev, size: sizeof(*pti), GFP_KERNEL); |
217 | if (!pti) |
218 | return -ENOMEM; |
219 | |
220 | pti->thdev = thdev; |
221 | pti->base = base; |
222 | |
223 | read_hw_config(pti); |
224 | |
225 | dev_set_drvdata(dev, data: pti); |
226 | |
227 | return 0; |
228 | } |
229 | |
230 | static void intel_th_pti_remove(struct intel_th_device *thdev) |
231 | { |
232 | } |
233 | |
234 | static struct intel_th_driver intel_th_pti_driver = { |
235 | .probe = intel_th_pti_probe, |
236 | .remove = intel_th_pti_remove, |
237 | .activate = intel_th_pti_activate, |
238 | .deactivate = intel_th_pti_deactivate, |
239 | .attr_group = &pti_output_group, |
240 | .driver = { |
241 | .name = "pti" , |
242 | .owner = THIS_MODULE, |
243 | }, |
244 | }; |
245 | |
246 | static const char * const lpp_dest_str[] = { "pti" , "exi" }; |
247 | |
248 | static ssize_t lpp_dest_show(struct device *dev, struct device_attribute *attr, |
249 | char *buf) |
250 | { |
251 | struct pti_device *pti = dev_get_drvdata(dev); |
252 | ssize_t ret = 0; |
253 | int i; |
254 | |
255 | for (i = ARRAY_SIZE(lpp_dest_str) - 1; i >= 0; i--) { |
256 | const char *fmt = pti->lpp_dest == i ? "[%s] " : "%s " ; |
257 | |
258 | if (!(pti->lpp_dest_mask & BIT(i))) |
259 | continue; |
260 | |
261 | ret += scnprintf(buf: buf + ret, PAGE_SIZE - ret, |
262 | fmt, lpp_dest_str[i]); |
263 | } |
264 | |
265 | if (ret) |
266 | buf[ret - 1] = '\n'; |
267 | |
268 | return ret; |
269 | } |
270 | |
271 | static ssize_t lpp_dest_store(struct device *dev, struct device_attribute *attr, |
272 | const char *buf, size_t size) |
273 | { |
274 | struct pti_device *pti = dev_get_drvdata(dev); |
275 | int i; |
276 | |
277 | i = sysfs_match_string(lpp_dest_str, buf); |
278 | if (i < 0) |
279 | return i; |
280 | |
281 | if (!(pti->lpp_dest_mask & BIT(i))) |
282 | return -EINVAL; |
283 | |
284 | pti->lpp_dest = i; |
285 | return size; |
286 | } |
287 | |
288 | static DEVICE_ATTR_RW(lpp_dest); |
289 | |
290 | static struct attribute *lpp_output_attrs[] = { |
291 | &dev_attr_mode.attr, |
292 | &dev_attr_freerunning_clock.attr, |
293 | &dev_attr_clock_divider.attr, |
294 | &dev_attr_lpp_dest.attr, |
295 | NULL, |
296 | }; |
297 | |
298 | static const struct attribute_group lpp_output_group = { |
299 | .attrs = lpp_output_attrs, |
300 | }; |
301 | |
302 | static struct intel_th_driver intel_th_lpp_driver = { |
303 | .probe = intel_th_pti_probe, |
304 | .remove = intel_th_pti_remove, |
305 | .activate = intel_th_pti_activate, |
306 | .deactivate = intel_th_pti_deactivate, |
307 | .attr_group = &lpp_output_group, |
308 | .driver = { |
309 | .name = "lpp" , |
310 | .owner = THIS_MODULE, |
311 | }, |
312 | }; |
313 | |
314 | static int __init intel_th_pti_lpp_init(void) |
315 | { |
316 | int err; |
317 | |
318 | err = intel_th_driver_register(thdrv: &intel_th_pti_driver); |
319 | if (err) |
320 | return err; |
321 | |
322 | err = intel_th_driver_register(thdrv: &intel_th_lpp_driver); |
323 | if (err) { |
324 | intel_th_driver_unregister(thdrv: &intel_th_pti_driver); |
325 | return err; |
326 | } |
327 | |
328 | return 0; |
329 | } |
330 | |
331 | module_init(intel_th_pti_lpp_init); |
332 | |
333 | static void __exit intel_th_pti_lpp_exit(void) |
334 | { |
335 | intel_th_driver_unregister(thdrv: &intel_th_pti_driver); |
336 | intel_th_driver_unregister(thdrv: &intel_th_lpp_driver); |
337 | } |
338 | |
339 | module_exit(intel_th_pti_lpp_exit); |
340 | |
341 | MODULE_LICENSE("GPL v2" ); |
342 | MODULE_DESCRIPTION("Intel(R) Trace Hub PTI/LPP output driver" ); |
343 | MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>" ); |
344 | |