Merge tag 'tty-5.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty
[linux-2.6-microblaze.git] / drivers / hwtracing / intel_th / pti.c
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, "%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(buf, 10, &val);
65         if (ret)
66                 return ret;
67
68         ret = pti_width_mode(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, "%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(buf, 10, &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, "%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(buf, 10, &val);
125         if (ret)
126                 return ret;
127
128         if (!is_power_of_2(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(&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(&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(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, 0);
209         if (!res)
210                 return -ENODEV;
211
212         base = devm_ioremap(dev, res->start, resource_size(res));
213         if (!base)
214                 return -ENOMEM;
215
216         pti = devm_kzalloc(dev, 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, 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 + 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(&intel_th_pti_driver);
319         if (err)
320                 return err;
321
322         err = intel_th_driver_register(&intel_th_lpp_driver);
323         if (err) {
324                 intel_th_driver_unregister(&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(&intel_th_pti_driver);
336         intel_th_driver_unregister(&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>");