linux/drivers/hwtracing/intel_th/gth.c
Alexander Shishkin ab1afed701 intel_th: Wait until port is in reset before programming it
Some devices don't drain their pipelines if we don't make sure that
the corresponding output port is in reset before programming it for
a new trace capture, resulting in bits of old trace appearing in the
new trace capture. Fix that by explicitly making sure the reset is
asserted before programming new trace capture.

Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Link: https://lore.kernel.org/r/20210621151246.31891-5-alexander.shishkin@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2021-06-24 15:49:32 +02:00

851 lines
21 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Intel(R) Trace Hub Global Trace Hub
*
* Copyright (C) 2014-2015 Intel Corporation.
*/
#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 <linux/pm_runtime.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 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 the port is active, program this setting,
* implies that runtime PM is on
*/
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, see above */
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 reg;
int port, i;
reg = ioread32(gth->base + REG_GTH_SCRPD0);
if (reg & SCRPD_DEBUGGER_IN_USE)
return -EBUSY;
/* Always save/restore STH and TU registers in S0ix entry/exit */
reg |= SCRPD_STH_IS_ENABLED | SCRPD_TRIGGER_IS_ENABLED;
iowrite32(reg, gth->base + REG_GTH_SCRPD0);
/* 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);
/* setup CTS for single trigger */
iowrite32(CTS_EVENT_ENABLE_IF_ANYTHING, gth->base + REG_CTS_C0S0_EN);
iowrite32(CTS_ACTION_CONTROL_SET_STATE(CTS_STATE_IDLE) |
CTS_ACTION_CONTROL_TRIGGER, gth->base + REG_CTS_C0S0_ACT);
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;
pm_runtime_get_sync(dev);
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);
pm_runtime_put(dev);
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;
pm_runtime_get_sync(dev);
spin_lock(&gth->gth_lock);
gth_output_parm_set(gth, oa->port, oa->parm, config);
spin_unlock(&gth->gth_lock);
pm_runtime_put(dev);
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_stop() - stop tracing to an output device
* @gth: GTH device
* @output: output device's descriptor
* @capture_done: set when no more traces will be captured
*
* This will stop tracing using force storeEn off signal and wait for the
* pipelines to be empty for the corresponding output port.
*/
static void intel_th_gth_stop(struct gth_device *gth,
struct intel_th_output *output,
bool capture_done)
{
struct intel_th_device *outdev =
container_of(output, struct intel_th_device, output);
struct intel_th_driver *outdrv =
to_intel_th_driver(outdev->dev.driver);
unsigned long count;
u32 reg;
u32 scr2 = 0xfc | (capture_done ? 1 : 0);
iowrite32(0, gth->base + REG_GTH_SCR);
iowrite32(scr2, 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();
}
if (!count)
dev_dbg(gth->dev, "timeout waiting for GTH[%d] PLE\n",
output->port);
/* wait on output piepline empty */
if (outdrv->wait_empty)
outdrv->wait_empty(outdev);
/* clear force capture done for next captures */
iowrite32(0xfc, gth->base + REG_GTH_SCR2);
}
/**
* intel_th_gth_start() - start tracing to an output device
* @gth: GTH device
* @output: output device's descriptor
*
* This will start tracing using force storeEn signal.
*/
static void intel_th_gth_start(struct gth_device *gth,
struct intel_th_output *output)
{
u32 scr = 0xfc0000;
if (output->multiblock)
scr |= 0xff;
iowrite32(scr, gth->base + REG_GTH_SCR);
iowrite32(0, gth->base + REG_GTH_SCR2);
}
/**
* intel_th_gth_disable() - disable 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);
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 + 1) {
gth_master_set(gth, master, -1);
}
spin_unlock(&gth->gth_lock);
intel_th_gth_stop(gth, output, true);
reg = ioread32(gth->base + REG_GTH_SCRPD0);
reg &= ~output->scratchpad;
iowrite32(reg, gth->base + REG_GTH_SCRPD0);
}
static void gth_tscu_resync(struct gth_device *gth)
{
u32 reg;
reg = ioread32(gth->base + REG_TSCU_TSUCTRL);
reg &= ~TSUCTRL_CTCRESYNC;
iowrite32(reg, gth->base + REG_TSCU_TSUCTRL);
}
static void intel_th_gth_prepare(struct intel_th_device *thdev,
struct intel_th_output *output)
{
struct gth_device *gth = dev_get_drvdata(&thdev->dev);
int count;
/*
* Wait until the output port is in reset before we start
* programming it.
*/
for (count = GTH_PLE_WAITLOOP_DEPTH;
count && !(gth_output_get(gth, output->port) & BIT(5)); count--)
cpu_relax();
}
/**
* 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);
struct intel_th *th = to_intel_th(thdev);
int master;
u32 scrpd;
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);
}
output->active = true;
spin_unlock(&gth->gth_lock);
if (INTEL_TH_CAP(th, tscu_enable))
gth_tscu_resync(gth);
scrpd = ioread32(gth->base + REG_GTH_SCRPD0);
scrpd |= output->scratchpad;
iowrite32(scrpd, gth->base + REG_GTH_SCRPD0);
intel_th_gth_start(gth, output);
}
/**
* intel_th_gth_switch() - execute a switch sequence
* @thdev: GTH device
* @output: output device's descriptor
*
* This will execute a switch sequence that will trigger a switch window
* when tracing to MSC in multi-block mode.
*/
static void intel_th_gth_switch(struct intel_th_device *thdev,
struct intel_th_output *output)
{
struct gth_device *gth = dev_get_drvdata(&thdev->dev);
unsigned long count;
u32 reg;
/* trigger */
iowrite32(0, gth->base + REG_CTS_CTL);
iowrite32(CTS_CTL_SEQUENCER_ENABLE, gth->base + REG_CTS_CTL);
/* wait on trigger status */
for (reg = 0, count = CTS_TRIG_WAITLOOP_DEPTH;
count && !(reg & BIT(4)); count--) {
reg = ioread32(gth->base + REG_CTS_STAT);
cpu_relax();
}
if (!count)
dev_dbg(&thdev->dev, "timeout waiting for CTS Trigger\n");
/* De-assert the trigger */
iowrite32(0, gth->base + REG_CTS_CTL);
intel_th_gth_stop(gth, output, false);
intel_th_gth_start(gth, output);
}
/**
* 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 (thdev->host_mode)
return -EBUSY;
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;
int master;
if (thdev->host_mode)
return;
spin_lock(&gth->gth_lock);
othdev->output.port = -1;
othdev->output.active = false;
gth->output[port].output = NULL;
for (master = 0; master < TH_CONFIGURABLE_MASTERS + 1; master++)
if (gth->master[master] == port)
gth->master[master] = -1;
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 intel_th *th = dev_get_drvdata(dev->parent);
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);
dev_set_drvdata(dev, gth);
/*
* Host mode can be signalled via SW means or via SCRPD_DEBUGGER_IN_USE
* bit. Either way, don't reset HW in this case, and don't export any
* capture configuration attributes. Also, refuse to assign output
* drivers to ports, see intel_th_gth_assign().
*/
if (thdev->host_mode)
return 0;
ret = intel_th_gth_reset(gth);
if (ret) {
if (ret != -EBUSY)
return ret;
thdev->host_mode = true;
return 0;
}
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 (gth->output[i].port_type == GTH_NONE)
continue;
ret = intel_th_output_enable(th, gth->output[i].port_type);
/* -ENODEV is ok, we just won't have that device enumerated */
if (ret && ret != -ENODEV)
return ret;
}
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;
}
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,
.prepare = intel_th_gth_prepare,
.enable = intel_th_gth_enable,
.trig_switch = intel_th_gth_switch,
.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>");