These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / hwtracing / intel_th / gth.c
diff --git a/kernel/drivers/hwtracing/intel_th/gth.c b/kernel/drivers/hwtracing/intel_th/gth.c
new file mode 100644 (file)
index 0000000..2dc5378
--- /dev/null
@@ -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(&gth->gth_lock);
+       port = gth->master[ma->master];
+       spin_unlock(&gth->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(&gth->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(&gth->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(&gth->gth_lock);
+       count = snprintf(buf, PAGE_SIZE, "%x\n",
+                        gth_output_parm_get(gth, oa->port, oa->parm));
+       spin_unlock(&gth->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(&gth->gth_lock);
+       gth_output_parm_set(gth, oa->port, oa->parm, config);
+       spin_unlock(&gth->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(&gth->dev->kobj, &gth->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(&gth->dev->kobj, &gth->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(&gth->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(&gth->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(&gth->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(&gth->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(&gth->gth_lock);
+       othdev->output.port = i;
+       othdev->output.active = false;
+       gth->output[i].output = &othdev->output;
+       spin_unlock(&gth->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(&gth->gth_lock);
+       othdev->output.port = -1;
+       othdev->output.active = false;
+       gth->output[port].output = NULL;
+       spin_unlock(&gth->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(&gth->gth_lock);
+       if (gth->master[master] == -1) {
+               set_bit(master, gth->output[port].master);
+               gth->master[master] = port;
+       }
+       spin_unlock(&gth->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(&gth->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(&gth->dev->kobj, &gth->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(&gth->dev->kobj, &gth->output_group);
+       sysfs_remove_group(&gth->dev->kobj, &gth->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>");