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 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 ssize_t ret = -EINVAL; 276 int i; 277 278 for (i = 0; i < ARRAY_SIZE(lpp_dest_str); i++) 279 if (sysfs_streq(buf, lpp_dest_str[i])) 280 break; 281 282 if (i < ARRAY_SIZE(lpp_dest_str) && pti->lpp_dest_mask & BIT(i)) { 283 pti->lpp_dest = i; 284 ret = size; 285 } 286 287 return ret; 288 } 289 290 static DEVICE_ATTR_RW(lpp_dest); 291 292 static struct attribute *lpp_output_attrs[] = { 293 &dev_attr_mode.attr, 294 &dev_attr_freerunning_clock.attr, 295 &dev_attr_clock_divider.attr, 296 &dev_attr_lpp_dest.attr, 297 NULL, 298 }; 299 300 static struct attribute_group lpp_output_group = { 301 .attrs = lpp_output_attrs, 302 }; 303 304 static struct intel_th_driver intel_th_lpp_driver = { 305 .probe = intel_th_pti_probe, 306 .remove = intel_th_pti_remove, 307 .activate = intel_th_pti_activate, 308 .deactivate = intel_th_pti_deactivate, 309 .attr_group = &lpp_output_group, 310 .driver = { 311 .name = "lpp", 312 .owner = THIS_MODULE, 313 }, 314 }; 315 316 static int __init intel_th_pti_lpp_init(void) 317 { 318 int err; 319 320 err = intel_th_driver_register(&intel_th_pti_driver); 321 if (err) 322 return err; 323 324 err = intel_th_driver_register(&intel_th_lpp_driver); 325 if (err) { 326 intel_th_driver_unregister(&intel_th_pti_driver); 327 return err; 328 } 329 330 return 0; 331 } 332 333 module_init(intel_th_pti_lpp_init); 334 335 static void __exit intel_th_pti_lpp_exit(void) 336 { 337 intel_th_driver_unregister(&intel_th_pti_driver); 338 intel_th_driver_unregister(&intel_th_lpp_driver); 339 } 340 341 module_exit(intel_th_pti_lpp_exit); 342 343 MODULE_LICENSE("GPL v2"); 344 MODULE_DESCRIPTION("Intel(R) Trace Hub PTI/LPP output driver"); 345 MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>"); 346