/* * Intel(R) Trace Hub PTI output driver * * Copyright (C) 2014-2016 Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #include #include #include "intel_th.h" #include "pti.h" struct pti_device { void __iomem *base; struct intel_th_device *thdev; unsigned int mode; unsigned int freeclk; unsigned int clkdiv; unsigned int patgen; unsigned int lpp_dest_mask; unsigned int lpp_dest; }; /* map PTI widths to MODE settings of PTI_CTL register */ static const unsigned int pti_mode[] = { 0, 4, 8, 0, 12, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, }; static int pti_width_mode(unsigned int width) { int i; for (i = 0; i < ARRAY_SIZE(pti_mode); i++) if (pti_mode[i] == width) return i; return -EINVAL; } static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct pti_device *pti = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%d\n", pti_mode[pti->mode]); } static ssize_t mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct pti_device *pti = dev_get_drvdata(dev); unsigned long val; int ret; ret = kstrtoul(buf, 10, &val); if (ret) return ret; ret = pti_width_mode(val); if (ret < 0) return ret; pti->mode = ret; return size; } static DEVICE_ATTR_RW(mode); static ssize_t freerunning_clock_show(struct device *dev, struct device_attribute *attr, char *buf) { struct pti_device *pti = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%d\n", pti->freeclk); } static ssize_t freerunning_clock_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct pti_device *pti = dev_get_drvdata(dev); unsigned long val; int ret; ret = kstrtoul(buf, 10, &val); if (ret) return ret; pti->freeclk = !!val; return size; } static DEVICE_ATTR_RW(freerunning_clock); static ssize_t clock_divider_show(struct device *dev, struct device_attribute *attr, char *buf) { struct pti_device *pti = dev_get_drvdata(dev); return scnprintf(buf, PAGE_SIZE, "%d\n", 1u << pti->clkdiv); } static ssize_t clock_divider_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct pti_device *pti = dev_get_drvdata(dev); unsigned long val; int ret; ret = kstrtoul(buf, 10, &val); if (ret) return ret; if (!is_power_of_2(val) || val > 8 || !val) return -EINVAL; pti->clkdiv = val; return size; } static DEVICE_ATTR_RW(clock_divider); static struct attribute *pti_output_attrs[] = { &dev_attr_mode.attr, &dev_attr_freerunning_clock.attr, &dev_attr_clock_divider.attr, NULL, }; static struct attribute_group pti_output_group = { .attrs = pti_output_attrs, }; static int intel_th_pti_activate(struct intel_th_device *thdev) { struct pti_device *pti = dev_get_drvdata(&thdev->dev); u32 ctl = PTI_EN; if (pti->patgen) ctl |= pti->patgen << __ffs(PTI_PATGENMODE); if (pti->freeclk) ctl |= PTI_FCEN; ctl |= pti->mode << __ffs(PTI_MODE); ctl |= pti->clkdiv << __ffs(PTI_CLKDIV); ctl |= pti->lpp_dest << __ffs(LPP_DEST); iowrite32(ctl, pti->base + REG_PTI_CTL); intel_th_trace_enable(thdev); return 0; } static void intel_th_pti_deactivate(struct intel_th_device *thdev) { struct pti_device *pti = dev_get_drvdata(&thdev->dev); intel_th_trace_disable(thdev); iowrite32(0, pti->base + REG_PTI_CTL); } static void read_hw_config(struct pti_device *pti) { u32 ctl = ioread32(pti->base + REG_PTI_CTL); pti->mode = (ctl & PTI_MODE) >> __ffs(PTI_MODE); pti->clkdiv = (ctl & PTI_CLKDIV) >> __ffs(PTI_CLKDIV); pti->freeclk = !!(ctl & PTI_FCEN); if (!pti_mode[pti->mode]) pti->mode = pti_width_mode(4); if (!pti->clkdiv) pti->clkdiv = 1; if (pti->thdev->output.type == GTH_LPP) { if (ctl & LPP_PTIPRESENT) pti->lpp_dest_mask |= LPP_DEST_PTI; if (ctl & LPP_BSSBPRESENT) pti->lpp_dest_mask |= LPP_DEST_EXI; if (ctl & LPP_DEST) pti->lpp_dest = 1; } } static int intel_th_pti_probe(struct intel_th_device *thdev) { struct device *dev = &thdev->dev; struct resource *res; struct pti_device *pti; void __iomem *base; res = intel_th_device_get_resource(thdev, IORESOURCE_MEM, 0); if (!res) return -ENODEV; base = devm_ioremap(dev, res->start, resource_size(res)); if (!base) return -ENOMEM; pti = devm_kzalloc(dev, sizeof(*pti), GFP_KERNEL); if (!pti) return -ENOMEM; pti->thdev = thdev; pti->base = base; read_hw_config(pti); dev_set_drvdata(dev, pti); return 0; } static void intel_th_pti_remove(struct intel_th_device *thdev) { } static struct intel_th_driver intel_th_pti_driver = { .probe = intel_th_pti_probe, .remove = intel_th_pti_remove, .activate = intel_th_pti_activate, .deactivate = intel_th_pti_deactivate, .attr_group = &pti_output_group, .driver = { .name = "pti", .owner = THIS_MODULE, }, }; static const char * const lpp_dest_str[] = { "pti", "exi" }; static ssize_t lpp_dest_show(struct device *dev, struct device_attribute *attr, char *buf) { struct pti_device *pti = dev_get_drvdata(dev); ssize_t ret = 0; int i; for (i = ARRAY_SIZE(lpp_dest_str) - 1; i >= 0; i--) { const char *fmt = pti->lpp_dest == i ? "[%s] " : "%s "; if (!(pti->lpp_dest_mask & BIT(i))) continue; ret += scnprintf(buf + ret, PAGE_SIZE - ret, fmt, lpp_dest_str[i]); } if (ret) buf[ret - 1] = '\n'; return ret; } static ssize_t lpp_dest_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct pti_device *pti = dev_get_drvdata(dev); ssize_t ret = -EINVAL; int i; for (i = 0; i < ARRAY_SIZE(lpp_dest_str); i++) if (sysfs_streq(buf, lpp_dest_str[i])) break; if (i < ARRAY_SIZE(lpp_dest_str) && pti->lpp_dest_mask & BIT(i)) { pti->lpp_dest = i; ret = size; } return ret; } static DEVICE_ATTR_RW(lpp_dest); static struct attribute *lpp_output_attrs[] = { &dev_attr_mode.attr, &dev_attr_freerunning_clock.attr, &dev_attr_clock_divider.attr, &dev_attr_lpp_dest.attr, NULL, }; static struct attribute_group lpp_output_group = { .attrs = lpp_output_attrs, }; static struct intel_th_driver intel_th_lpp_driver = { .probe = intel_th_pti_probe, .remove = intel_th_pti_remove, .activate = intel_th_pti_activate, .deactivate = intel_th_pti_deactivate, .attr_group = &lpp_output_group, .driver = { .name = "lpp", .owner = THIS_MODULE, }, }; static int __init intel_th_pti_lpp_init(void) { int err; err = intel_th_driver_register(&intel_th_pti_driver); if (err) return err; err = intel_th_driver_register(&intel_th_lpp_driver); if (err) { intel_th_driver_unregister(&intel_th_pti_driver); return err; } return 0; } module_init(intel_th_pti_lpp_init); static void __exit intel_th_pti_lpp_exit(void) { intel_th_driver_unregister(&intel_th_pti_driver); intel_th_driver_unregister(&intel_th_lpp_driver); } module_exit(intel_th_pti_lpp_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel(R) Trace Hub PTI/LPP output driver"); MODULE_AUTHOR("Alexander Shishkin ");