intel_th: Add Global Trace Hub driver
Global Trace Hub (GTH) is the central component of Intel TH architecture; it carries out switching between the trace sources and trace outputs, can enable/disable tracing, perform STP encoding, internal buffering, control backpressure from outputs to sources and so on. This property is also reflected in the software model; GTH (switch) driver is required for the other subdevices to probe, because it matches trace output devices against its output ports and configures them accordingly. It also implements an interface for output ports to request trace enabling or disabling and a few other useful things. For userspace, it provides an attribute group "masters", which allows configuration of per-master trace output destinations for up to master 255 and "256+" meaning "masters 256 and above". It also provides an attribute group to discover and configure some of the parameters of its output ports, called "outputs". Via these the user can set up data retention policy for an individual output port or check if it is in reset state. Signed-off-by: Laurent Fert <laurent.fert@intel.com> Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
2b0b16d329
commit
b27a6a3f97
|
@ -0,0 +1,49 @@
|
|||
What: /sys/bus/intel_th/devices/<intel_th_id>-gth/masters/*
|
||||
Date: June 2015
|
||||
KernelVersion: 4.3
|
||||
Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
Description: (RW) Configure output ports for STP masters. Writing -1
|
||||
disables a master; any
|
||||
|
||||
What: /sys/bus/intel_th/devices/<intel_th_id>-gth/outputs/[0-7]_port
|
||||
Date: June 2015
|
||||
KernelVersion: 4.3
|
||||
Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
Description: (RO) Output port type:
|
||||
0: not present,
|
||||
1: MSU (Memory Storage Unit)
|
||||
2: CTP (Common Trace Port)
|
||||
4: PTI (MIPI PTI).
|
||||
|
||||
What: /sys/bus/intel_th/devices/<intel_th_id>-gth/outputs/[0-7]_drop
|
||||
Date: June 2015
|
||||
KernelVersion: 4.3
|
||||
Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
Description: (RW) Data retention policy setting: keep (0) or drop (1)
|
||||
incoming data while output port is in reset.
|
||||
|
||||
What: /sys/bus/intel_th/devices/<intel_th_id>-gth/outputs/[0-7]_null
|
||||
Date: June 2015
|
||||
KernelVersion: 4.3
|
||||
Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
Description: (RW) STP NULL packet generation: enabled (1) or disabled (0).
|
||||
|
||||
What: /sys/bus/intel_th/devices/<intel_th_id>-gth/outputs/[0-7]_flush
|
||||
Date: June 2015
|
||||
KernelVersion: 4.3
|
||||
Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
Description: (RW) Force flush data from byte packing buffer for the output
|
||||
port.
|
||||
|
||||
What: /sys/bus/intel_th/devices/<intel_th_id>-gth/outputs/[0-7]_reset
|
||||
Date: June 2015
|
||||
KernelVersion: 4.3
|
||||
Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
Description: (RO) Output port is in reset (1).
|
||||
|
||||
What: /sys/bus/intel_th/devices/<intel_th_id>-gth/outputs/[0-7]_smcfreq
|
||||
Date: June 2015
|
||||
KernelVersion: 4.3
|
||||
Contact: Alexander Shishkin <alexander.shishkin@linux.intel.com>
|
||||
Description: (RW) STP sync packet frequency for the port. Specifies the
|
||||
number of clocks between mainenance packets.
|
|
@ -24,6 +24,16 @@ config INTEL_TH_PCI
|
|||
|
||||
Say Y here to enable PCI Intel TH support.
|
||||
|
||||
config INTEL_TH_GTH
|
||||
tristate "Intel(R) Trace Hub Global Trace Hub"
|
||||
help
|
||||
Global Trace Hub (GTH) is the central component of the
|
||||
Intel TH infrastructure and acts as a switch for source
|
||||
and output devices. This driver is required for other
|
||||
Intel TH subdevices to initialize.
|
||||
|
||||
Say Y here to enable GTH subdevice of Intel(R) Trace Hub.
|
||||
|
||||
config INTEL_TH_DEBUG
|
||||
bool "Intel(R) Trace Hub debugging"
|
||||
depends on DEBUG_FS
|
||||
|
|
|
@ -4,3 +4,6 @@ intel_th-$(CONFIG_INTEL_TH_DEBUG) += debug.o
|
|||
|
||||
obj-$(CONFIG_INTEL_TH_PCI) += intel_th_pci.o
|
||||
intel_th_pci-y := pci.o
|
||||
|
||||
obj-$(CONFIG_INTEL_TH_GTH) += intel_th_gth.o
|
||||
intel_th_gth-y := gth.o
|
||||
|
|
|
@ -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 (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
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>");
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Intel(R) Trace Hub Global Trace Hub (GTH) data structures
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __INTEL_TH_GTH_H__
|
||||
#define __INTEL_TH_GTH_H__
|
||||
|
||||
/* Map output port parameter bits to symbolic names */
|
||||
#define TH_OUTPUT_PARM(name) \
|
||||
TH_OUTPUT_ ## name
|
||||
|
||||
enum intel_th_output_parm {
|
||||
/* output port type */
|
||||
TH_OUTPUT_PARM(port),
|
||||
/* generate NULL packet */
|
||||
TH_OUTPUT_PARM(null),
|
||||
/* packet drop */
|
||||
TH_OUTPUT_PARM(drop),
|
||||
/* port in reset state */
|
||||
TH_OUTPUT_PARM(reset),
|
||||
/* flush out data */
|
||||
TH_OUTPUT_PARM(flush),
|
||||
/* mainenance packet frequency */
|
||||
TH_OUTPUT_PARM(smcfreq),
|
||||
};
|
||||
|
||||
/*
|
||||
* Register offsets
|
||||
*/
|
||||
enum {
|
||||
REG_GTH_GTHOPT0 = 0x00, /* Output ports 0..3 config */
|
||||
REG_GTH_GTHOPT1 = 0x04, /* Output ports 4..7 config */
|
||||
REG_GTH_SWDEST0 = 0x08, /* Switching destination masters 0..7 */
|
||||
REG_GTH_GSWTDEST = 0x88, /* Global sw trace destination */
|
||||
REG_GTH_SMCR0 = 0x9c, /* STP mainenance for ports 0/1 */
|
||||
REG_GTH_SMCR1 = 0xa0, /* STP mainenance for ports 2/3 */
|
||||
REG_GTH_SMCR2 = 0xa4, /* STP mainenance for ports 4/5 */
|
||||
REG_GTH_SMCR3 = 0xa8, /* STP mainenance for ports 6/7 */
|
||||
REG_GTH_SCR = 0xc8, /* Source control (storeEn override) */
|
||||
REG_GTH_STAT = 0xd4, /* GTH status */
|
||||
REG_GTH_SCR2 = 0xd8, /* Source control (force storeEn off) */
|
||||
REG_GTH_DESTOVR = 0xdc, /* Destination override */
|
||||
REG_GTH_SCRPD0 = 0xe0, /* ScratchPad[0] */
|
||||
REG_GTH_SCRPD1 = 0xe4, /* ScratchPad[1] */
|
||||
REG_GTH_SCRPD2 = 0xe8, /* ScratchPad[2] */
|
||||
REG_GTH_SCRPD3 = 0xec, /* ScratchPad[3] */
|
||||
};
|
||||
|
||||
/* Externall debugger is using Intel TH */
|
||||
#define SCRPD_DEBUGGER_IN_USE BIT(24)
|
||||
|
||||
/* waiting for Pipeline Empty bit(s) to assert for GTH */
|
||||
#define GTH_PLE_WAITLOOP_DEPTH 10000
|
||||
|
||||
#endif /* __INTEL_TH_GTH_H__ */
|
Loading…
Reference in New Issue