diff options
Diffstat (limited to 'drivers/hwtracing/intel_th/gth.c')
-rw-r--r-- | drivers/hwtracing/intel_th/gth.c | 706 |
1 files changed, 706 insertions, 0 deletions
diff --git a/drivers/hwtracing/intel_th/gth.c b/drivers/hwtracing/intel_th/gth.c new file mode 100644 index 000000000000..2dc5378ccd3a --- /dev/null +++ b/drivers/hwtracing/intel_th/gth.c @@ -0,0 +1,706 @@ +/* + * Intel(R) Trace Hub Global Trace Hub + * + * Copyright (C) 2014-2015 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 <linux/types.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/bitmap.h> + +#include "intel_th.h" +#include "gth.h" + +struct gth_device; + +/** + * struct gth_output - GTH view on an output port + * @gth: backlink to the GTH device + * @output: link to output device's output descriptor + * @index: output port number + * @port_type: one of GTH_* port type values + * @master: bitmap of masters configured for this output + */ +struct gth_output { + struct gth_device *gth; + struct intel_th_output *output; + unsigned int index; + unsigned int port_type; + DECLARE_BITMAP(master, TH_CONFIGURABLE_MASTERS + 1); +}; + +/** + * struct gth_device - GTH device + * @dev: driver core's device + * @base: register window base address + * @output_group: attributes describing output ports + * @master_group: attributes describing master assignments + * @output: output ports + * @master: master/output port assignments + * @gth_lock: serializes accesses to GTH bits + */ +struct gth_device { + struct device *dev; + void __iomem *base; + + struct attribute_group output_group; + struct attribute_group master_group; + struct gth_output output[TH_POSSIBLE_OUTPUTS]; + signed char master[TH_CONFIGURABLE_MASTERS + 1]; + spinlock_t gth_lock; +}; + +static void gth_output_set(struct gth_device *gth, int port, + unsigned int config) +{ + unsigned long reg = port & 4 ? REG_GTH_GTHOPT1 : REG_GTH_GTHOPT0; + u32 val; + int shift = (port & 3) * 8; + + val = ioread32(gth->base + reg); + val &= ~(0xff << shift); + val |= config << shift; + iowrite32(val, gth->base + reg); +} + +static unsigned int gth_output_get(struct gth_device *gth, int port) +{ + unsigned long reg = port & 4 ? REG_GTH_GTHOPT1 : REG_GTH_GTHOPT0; + u32 val; + int shift = (port & 3) * 8; + + val = ioread32(gth->base + reg); + val &= 0xff << shift; + val >>= shift; + + return val; +} + +static void gth_smcfreq_set(struct gth_device *gth, int port, + unsigned int freq) +{ + unsigned long reg = REG_GTH_SMCR0 + ((port / 2) * 4); + int shift = (port & 1) * 16; + u32 val; + + val = ioread32(gth->base + reg); + val &= ~(0xffff << shift); + val |= freq << shift; + iowrite32(val, gth->base + reg); +} + +static unsigned int gth_smcfreq_get(struct gth_device *gth, int port) +{ + unsigned long reg = REG_GTH_SMCR0 + ((port / 2) * 4); + int shift = (port & 1) * 16; + u32 val; + + val = ioread32(gth->base + reg); + val &= 0xffff << shift; + val >>= shift; + + return val; +} + +/* + * "masters" attribute group + */ + +struct master_attribute { + struct device_attribute attr; + struct gth_device *gth; + unsigned int master; +}; + +static void +gth_master_set(struct gth_device *gth, unsigned int master, int port) +{ + unsigned int reg = REG_GTH_SWDEST0 + ((master >> 1) & ~3u); + unsigned int shift = (master & 0x7) * 4; + u32 val; + + if (master >= 256) { + reg = REG_GTH_GSWTDEST; + shift = 0; + } + + val = ioread32(gth->base + reg); + val &= ~(0xf << shift); + if (port >= 0) + val |= (0x8 | port) << shift; + iowrite32(val, gth->base + reg); +} + +/*static int gth_master_get(struct gth_device *gth, unsigned int master) +{ + unsigned int reg = REG_GTH_SWDEST0 + ((master >> 1) & ~3u); + unsigned int shift = (master & 0x7) * 4; + u32 val; + + if (master >= 256) { + reg = REG_GTH_GSWTDEST; + shift = 0; + } + + val = ioread32(gth->base + reg); + val &= (0xf << shift); + val >>= shift; + + return val ? val & 0x7 : -1; + }*/ + +static ssize_t master_attr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct master_attribute *ma = + container_of(attr, struct master_attribute, attr); + struct gth_device *gth = ma->gth; + size_t count; + int port; + + spin_lock(>h->gth_lock); + port = gth->master[ma->master]; + spin_unlock(>h->gth_lock); + + if (port >= 0) + count = snprintf(buf, PAGE_SIZE, "%x\n", port); + else + count = snprintf(buf, PAGE_SIZE, "disabled\n"); + + return count; +} + +static ssize_t master_attr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct master_attribute *ma = + container_of(attr, struct master_attribute, attr); + struct gth_device *gth = ma->gth; + int old_port, port; + + if (kstrtoint(buf, 10, &port) < 0) + return -EINVAL; + + if (port >= TH_POSSIBLE_OUTPUTS || port < -1) + return -EINVAL; + + spin_lock(>h->gth_lock); + + /* disconnect from the previous output port, if any */ + old_port = gth->master[ma->master]; + if (old_port >= 0) { + gth->master[ma->master] = -1; + clear_bit(ma->master, gth->output[old_port].master); + if (gth->output[old_port].output->active) + gth_master_set(gth, ma->master, -1); + } + + /* connect to the new output port, if any */ + if (port >= 0) { + /* check if there's a driver for this port */ + if (!gth->output[port].output) { + count = -ENODEV; + goto unlock; + } + + set_bit(ma->master, gth->output[port].master); + + /* if the port is active, program this setting */ + if (gth->output[port].output->active) + gth_master_set(gth, ma->master, port); + } + + gth->master[ma->master] = port; + +unlock: + spin_unlock(>h->gth_lock); + + return count; +} + +struct output_attribute { + struct device_attribute attr; + struct gth_device *gth; + unsigned int port; + unsigned int parm; +}; + +#define OUTPUT_PARM(_name, _mask, _r, _w, _what) \ + [TH_OUTPUT_PARM(_name)] = { .name = __stringify(_name), \ + .get = gth_ ## _what ## _get, \ + .set = gth_ ## _what ## _set, \ + .mask = (_mask), \ + .readable = (_r), \ + .writable = (_w) } + +static const struct output_parm { + const char *name; + unsigned int (*get)(struct gth_device *gth, int port); + void (*set)(struct gth_device *gth, int port, + unsigned int val); + unsigned int mask; + unsigned int readable : 1, + writable : 1; +} output_parms[] = { + OUTPUT_PARM(port, 0x7, 1, 0, output), + OUTPUT_PARM(null, BIT(3), 1, 1, output), + OUTPUT_PARM(drop, BIT(4), 1, 1, output), + OUTPUT_PARM(reset, BIT(5), 1, 0, output), + OUTPUT_PARM(flush, BIT(7), 0, 1, output), + OUTPUT_PARM(smcfreq, 0xffff, 1, 1, smcfreq), +}; + +static void +gth_output_parm_set(struct gth_device *gth, int port, unsigned int parm, + unsigned int val) +{ + unsigned int config = output_parms[parm].get(gth, port); + unsigned int mask = output_parms[parm].mask; + unsigned int shift = __ffs(mask); + + config &= ~mask; + config |= (val << shift) & mask; + output_parms[parm].set(gth, port, config); +} + +static unsigned int +gth_output_parm_get(struct gth_device *gth, int port, unsigned int parm) +{ + unsigned int config = output_parms[parm].get(gth, port); + unsigned int mask = output_parms[parm].mask; + unsigned int shift = __ffs(mask); + + config &= mask; + config >>= shift; + return config; +} + +/* + * Reset outputs and sources + */ +static int intel_th_gth_reset(struct gth_device *gth) +{ + u32 scratchpad; + int port, i; + + scratchpad = ioread32(gth->base + REG_GTH_SCRPD0); + if (scratchpad & SCRPD_DEBUGGER_IN_USE) + return -EBUSY; + + /* output ports */ + for (port = 0; port < 8; port++) { + if (gth_output_parm_get(gth, port, TH_OUTPUT_PARM(port)) == + GTH_NONE) + continue; + + gth_output_set(gth, port, 0); + gth_smcfreq_set(gth, port, 16); + } + /* disable overrides */ + iowrite32(0, gth->base + REG_GTH_DESTOVR); + + /* masters swdest_0~31 and gswdest */ + for (i = 0; i < 33; i++) + iowrite32(0, gth->base + REG_GTH_SWDEST0 + i * 4); + + /* sources */ + iowrite32(0, gth->base + REG_GTH_SCR); + iowrite32(0xfc, gth->base + REG_GTH_SCR2); + + return 0; +} + +/* + * "outputs" attribute group + */ + +static ssize_t output_attr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct output_attribute *oa = + container_of(attr, struct output_attribute, attr); + struct gth_device *gth = oa->gth; + size_t count; + + spin_lock(>h->gth_lock); + count = snprintf(buf, PAGE_SIZE, "%x\n", + gth_output_parm_get(gth, oa->port, oa->parm)); + spin_unlock(>h->gth_lock); + + return count; +} + +static ssize_t output_attr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct output_attribute *oa = + container_of(attr, struct output_attribute, attr); + struct gth_device *gth = oa->gth; + unsigned int config; + + if (kstrtouint(buf, 16, &config) < 0) + return -EINVAL; + + spin_lock(>h->gth_lock); + gth_output_parm_set(gth, oa->port, oa->parm, config); + spin_unlock(>h->gth_lock); + + return count; +} + +static int intel_th_master_attributes(struct gth_device *gth) +{ + struct master_attribute *master_attrs; + struct attribute **attrs; + int i, nattrs = TH_CONFIGURABLE_MASTERS + 2; + + attrs = devm_kcalloc(gth->dev, nattrs, sizeof(void *), GFP_KERNEL); + if (!attrs) + return -ENOMEM; + + master_attrs = devm_kcalloc(gth->dev, nattrs, + sizeof(struct master_attribute), + GFP_KERNEL); + if (!master_attrs) + return -ENOMEM; + + for (i = 0; i < TH_CONFIGURABLE_MASTERS + 1; i++) { + char *name; + + name = devm_kasprintf(gth->dev, GFP_KERNEL, "%d%s", i, + i == TH_CONFIGURABLE_MASTERS ? "+" : ""); + if (!name) + return -ENOMEM; + + master_attrs[i].attr.attr.name = name; + master_attrs[i].attr.attr.mode = S_IRUGO | S_IWUSR; + master_attrs[i].attr.show = master_attr_show; + master_attrs[i].attr.store = master_attr_store; + + sysfs_attr_init(&master_attrs[i].attr.attr); + attrs[i] = &master_attrs[i].attr.attr; + + master_attrs[i].gth = gth; + master_attrs[i].master = i; + } + + gth->master_group.name = "masters"; + gth->master_group.attrs = attrs; + + return sysfs_create_group(>h->dev->kobj, >h->master_group); +} + +static int intel_th_output_attributes(struct gth_device *gth) +{ + struct output_attribute *out_attrs; + struct attribute **attrs; + int i, j, nouts = TH_POSSIBLE_OUTPUTS; + int nparms = ARRAY_SIZE(output_parms); + int nattrs = nouts * nparms + 1; + + attrs = devm_kcalloc(gth->dev, nattrs, sizeof(void *), GFP_KERNEL); + if (!attrs) + return -ENOMEM; + + out_attrs = devm_kcalloc(gth->dev, nattrs, + sizeof(struct output_attribute), + GFP_KERNEL); + if (!out_attrs) + return -ENOMEM; + + for (i = 0; i < nouts; i++) { + for (j = 0; j < nparms; j++) { + unsigned int idx = i * nparms + j; + char *name; + + name = devm_kasprintf(gth->dev, GFP_KERNEL, "%d_%s", i, + output_parms[j].name); + if (!name) + return -ENOMEM; + + out_attrs[idx].attr.attr.name = name; + + if (output_parms[j].readable) { + out_attrs[idx].attr.attr.mode |= S_IRUGO; + out_attrs[idx].attr.show = output_attr_show; + } + + if (output_parms[j].writable) { + out_attrs[idx].attr.attr.mode |= S_IWUSR; + out_attrs[idx].attr.store = output_attr_store; + } + + sysfs_attr_init(&out_attrs[idx].attr.attr); + attrs[idx] = &out_attrs[idx].attr.attr; + + out_attrs[idx].gth = gth; + out_attrs[idx].port = i; + out_attrs[idx].parm = j; + } + } + + gth->output_group.name = "outputs"; + gth->output_group.attrs = attrs; + + return sysfs_create_group(>h->dev->kobj, >h->output_group); +} + +/** + * intel_th_gth_disable() - enable tracing to an output device + * @thdev: GTH device + * @output: output device's descriptor + * + * This will deconfigure all masters set to output to this device, + * disable tracing using force storeEn off signal and wait for the + * "pipeline empty" bit for corresponding output port. + */ +static void intel_th_gth_disable(struct intel_th_device *thdev, + struct intel_th_output *output) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + unsigned long count; + int master; + u32 reg; + + spin_lock(>h->gth_lock); + output->active = false; + + for_each_set_bit(master, gth->output[output->port].master, + TH_CONFIGURABLE_MASTERS) { + gth_master_set(gth, master, -1); + } + spin_unlock(>h->gth_lock); + + iowrite32(0, gth->base + REG_GTH_SCR); + iowrite32(0xfd, gth->base + REG_GTH_SCR2); + + /* wait on pipeline empty for the given port */ + for (reg = 0, count = GTH_PLE_WAITLOOP_DEPTH; + count && !(reg & BIT(output->port)); count--) { + reg = ioread32(gth->base + REG_GTH_STAT); + cpu_relax(); + } + + /* clear force capture done for next captures */ + iowrite32(0xfc, gth->base + REG_GTH_SCR2); + + if (!count) + dev_dbg(&thdev->dev, "timeout waiting for GTH[%d] PLE\n", + output->port); +} + +/** + * intel_th_gth_enable() - enable tracing to an output device + * @thdev: GTH device + * @output: output device's descriptor + * + * This will configure all masters set to output to this device and + * enable tracing using force storeEn signal. + */ +static void intel_th_gth_enable(struct intel_th_device *thdev, + struct intel_th_output *output) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + u32 scr = 0xfc0000; + int master; + + spin_lock(>h->gth_lock); + for_each_set_bit(master, gth->output[output->port].master, + TH_CONFIGURABLE_MASTERS + 1) { + gth_master_set(gth, master, output->port); + } + + if (output->multiblock) + scr |= 0xff; + + output->active = true; + spin_unlock(>h->gth_lock); + + iowrite32(scr, gth->base + REG_GTH_SCR); + iowrite32(0, gth->base + REG_GTH_SCR2); +} + +/** + * intel_th_gth_assign() - assign output device to a GTH output port + * @thdev: GTH device + * @othdev: output device + * + * This will match a given output device parameters against present + * output ports on the GTH and fill out relevant bits in output device's + * descriptor. + * + * Return: 0 on success, -errno on error. + */ +static int intel_th_gth_assign(struct intel_th_device *thdev, + struct intel_th_device *othdev) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + int i, id; + + if (othdev->type != INTEL_TH_OUTPUT) + return -EINVAL; + + for (i = 0, id = 0; i < TH_POSSIBLE_OUTPUTS; i++) { + if (gth->output[i].port_type != othdev->output.type) + continue; + + if (othdev->id == -1 || othdev->id == id) + goto found; + + id++; + } + + return -ENOENT; + +found: + spin_lock(>h->gth_lock); + othdev->output.port = i; + othdev->output.active = false; + gth->output[i].output = &othdev->output; + spin_unlock(>h->gth_lock); + + return 0; +} + +/** + * intel_th_gth_unassign() - deassociate an output device from its output port + * @thdev: GTH device + * @othdev: output device + */ +static void intel_th_gth_unassign(struct intel_th_device *thdev, + struct intel_th_device *othdev) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + int port = othdev->output.port; + + spin_lock(>h->gth_lock); + othdev->output.port = -1; + othdev->output.active = false; + gth->output[port].output = NULL; + spin_unlock(>h->gth_lock); +} + +static int +intel_th_gth_set_output(struct intel_th_device *thdev, unsigned int master) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + int port = 0; /* FIXME: make default output configurable */ + + /* + * everything above TH_CONFIGURABLE_MASTERS is controlled by the + * same register + */ + if (master > TH_CONFIGURABLE_MASTERS) + master = TH_CONFIGURABLE_MASTERS; + + spin_lock(>h->gth_lock); + if (gth->master[master] == -1) { + set_bit(master, gth->output[port].master); + gth->master[master] = port; + } + spin_unlock(>h->gth_lock); + + return 0; +} + +static int intel_th_gth_probe(struct intel_th_device *thdev) +{ + struct device *dev = &thdev->dev; + struct gth_device *gth; + struct resource *res; + void __iomem *base; + int i, ret; + + 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; + + gth = devm_kzalloc(dev, sizeof(*gth), GFP_KERNEL); + if (!gth) + return -ENOMEM; + + gth->dev = dev; + gth->base = base; + spin_lock_init(>h->gth_lock); + + ret = intel_th_gth_reset(gth); + if (ret) + return ret; + + for (i = 0; i < TH_CONFIGURABLE_MASTERS + 1; i++) + gth->master[i] = -1; + + for (i = 0; i < TH_POSSIBLE_OUTPUTS; i++) { + gth->output[i].gth = gth; + gth->output[i].index = i; + gth->output[i].port_type = + gth_output_parm_get(gth, i, TH_OUTPUT_PARM(port)); + } + + if (intel_th_output_attributes(gth) || + intel_th_master_attributes(gth)) { + pr_warn("Can't initialize sysfs attributes\n"); + + if (gth->output_group.attrs) + sysfs_remove_group(>h->dev->kobj, >h->output_group); + return -ENOMEM; + } + + dev_set_drvdata(dev, gth); + + return 0; +} + +static void intel_th_gth_remove(struct intel_th_device *thdev) +{ + struct gth_device *gth = dev_get_drvdata(&thdev->dev); + + sysfs_remove_group(>h->dev->kobj, >h->output_group); + sysfs_remove_group(>h->dev->kobj, >h->master_group); +} + +static struct intel_th_driver intel_th_gth_driver = { + .probe = intel_th_gth_probe, + .remove = intel_th_gth_remove, + .assign = intel_th_gth_assign, + .unassign = intel_th_gth_unassign, + .set_output = intel_th_gth_set_output, + .enable = intel_th_gth_enable, + .disable = intel_th_gth_disable, + .driver = { + .name = "gth", + .owner = THIS_MODULE, + }, +}; + +module_driver(intel_th_gth_driver, + intel_th_driver_register, + intel_th_driver_unregister); + +MODULE_ALIAS("intel_th_switch"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel(R) Trace Hub Global Trace Hub driver"); +MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>"); |