Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,87 @@
# SPDX-License-Identifier: GPL-2.0-only
config INTEL_TH
tristate "Intel(R) Trace Hub controller"
depends on HAS_DMA && HAS_IOMEM
help
Intel(R) Trace Hub (TH) is a set of hardware blocks (subdevices) that
produce, switch and output trace data from multiple hardware and
software sources over several types of trace output ports encoded
in System Trace Protocol (MIPI STPv2) and is intended to perform
full system debugging.
This option enables intel_th bus and common code used by TH
subdevices to interact with each other and hardware and for
platform glue layers to drive Intel TH devices.
Say Y here to enable Intel(R) Trace Hub controller support.
if INTEL_TH
config INTEL_TH_PCI
tristate "Intel(R) Trace Hub PCI controller"
depends on PCI
help
Intel(R) Trace Hub may exist as a PCI device. This option enables
support glue layer for PCI-based Intel TH.
Say Y here to enable PCI Intel TH support.
config INTEL_TH_ACPI
tristate "Intel(R) Trace Hub ACPI controller"
depends on ACPI
help
Intel(R) Trace Hub may exist as an ACPI device. This option enables
support glue layer for ACPI-based Intel TH. This typically implies
'host debugger' mode, that is, the trace configuration and capture
is handled by an external debug host and corresponding controls will
not be available on the target.
Say Y here to enable ACPI 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_STH
tristate "Intel(R) Trace Hub Software Trace Hub support"
depends on STM
help
Software Trace Hub (STH) enables trace data from software
trace sources to be sent out via Intel(R) Trace Hub. It
uses stm class device to interface with its sources.
Say Y here to enable STH subdevice of Intel(R) Trace Hub.
config INTEL_TH_MSU
tristate "Intel(R) Trace Hub Memory Storage Unit"
depends on MMU
help
Memory Storage Unit (MSU) trace output device enables
storing STP traces to system memory. It supports single
and multiblock modes of operation and provides read()
and mmap() access to the collected data.
Say Y here to enable MSU output device for Intel TH.
config INTEL_TH_PTI
tristate "Intel(R) Trace Hub PTI output"
help
Parallel Trace Interface unit (PTI) is a trace output device
of Intel TH architecture that facilitates STP trace output via
a PTI port.
Say Y to enable PTI output of Intel TH data.
config INTEL_TH_DEBUG
bool "Intel(R) Trace Hub debugging"
depends on DEBUG_FS
help
Say Y here to enable debugging.
endif
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_INTEL_TH) += intel_th.o
intel_th-y := core.o
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_ACPI) += intel_th_acpi.o
intel_th_acpi-y := acpi.o
obj-$(CONFIG_INTEL_TH_GTH) += intel_th_gth.o
intel_th_gth-y := gth.o
obj-$(CONFIG_INTEL_TH_STH) += intel_th_sth.o
intel_th_sth-y := sth.o
obj-$(CONFIG_INTEL_TH_MSU) += intel_th_msu.o
intel_th_msu-y := msu.o
obj-$(CONFIG_INTEL_TH_PTI) += intel_th_pti.o
intel_th_pti-y := pti.o
obj-$(CONFIG_INTEL_TH_MSU) += intel_th_msu_sink.o
intel_th_msu_sink-y := msu-sink.o
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel(R) Trace Hub ACPI driver
*
* Copyright (C) 2017 Intel Corporation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/types.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/sysfs.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include "intel_th.h"
#define DRIVER_NAME "intel_th_acpi"
static const struct intel_th_drvdata intel_th_acpi_pch = {
.host_mode_only = 1,
};
static const struct intel_th_drvdata intel_th_acpi_uncore = {
.host_mode_only = 1,
};
static const struct acpi_device_id intel_th_acpi_ids[] = {
{ "INTC1000", (kernel_ulong_t)&intel_th_acpi_uncore },
{ "INTC1001", (kernel_ulong_t)&intel_th_acpi_pch },
{ "", 0 },
};
MODULE_DEVICE_TABLE(acpi, intel_th_acpi_ids);
static int intel_th_acpi_probe(struct platform_device *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
struct resource resource[TH_MMIO_END];
const struct acpi_device_id *id;
struct intel_th *th;
int i, r;
id = acpi_match_device(intel_th_acpi_ids, &pdev->dev);
if (!id)
return -ENODEV;
for (i = 0, r = 0; i < pdev->num_resources && r < TH_MMIO_END; i++)
if (pdev->resource[i].flags &
(IORESOURCE_IRQ | IORESOURCE_MEM))
resource[r++] = pdev->resource[i];
th = intel_th_alloc(&pdev->dev, (void *)id->driver_data, resource, r);
if (IS_ERR(th))
return PTR_ERR(th);
adev->driver_data = th;
return 0;
}
static void intel_th_acpi_remove(struct platform_device *pdev)
{
struct intel_th *th = platform_get_drvdata(pdev);
intel_th_free(th);
}
static struct platform_driver intel_th_acpi_driver = {
.probe = intel_th_acpi_probe,
.remove = intel_th_acpi_remove,
.driver = {
.name = DRIVER_NAME,
.acpi_match_table = intel_th_acpi_ids,
},
};
module_platform_driver(intel_th_acpi_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Intel(R) Trace Hub ACPI controller driver");
MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@intel.com>");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel(R) Trace Hub driver debugging
*
* Copyright (C) 2014-2015 Intel Corporation.
*/
#include <linux/types.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include "intel_th.h"
#include "debug.h"
struct dentry *intel_th_dbg;
void intel_th_debug_init(void)
{
intel_th_dbg = debugfs_create_dir("intel_th", NULL);
if (IS_ERR(intel_th_dbg))
intel_th_dbg = NULL;
}
void intel_th_debug_done(void)
{
debugfs_remove(intel_th_dbg);
intel_th_dbg = NULL;
}
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Intel(R) Trace Hub driver debugging
*
* Copyright (C) 2014-2015 Intel Corporation.
*/
#ifndef __INTEL_TH_DEBUG_H__
#define __INTEL_TH_DEBUG_H__
#ifdef CONFIG_INTEL_TH_DEBUG
extern struct dentry *intel_th_dbg;
void intel_th_debug_init(void);
void intel_th_debug_done(void);
#else
static inline void intel_th_debug_init(void)
{
}
static inline void intel_th_debug_done(void)
{
}
#endif
#endif /* __INTEL_TH_DEBUG_H__ */
@@ -0,0 +1,850 @@
// 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 = sysfs_emit(buf, "%x\n", port);
else
count = sysfs_emit(buf, "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 = sysfs_emit(buf, "%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>");
@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Intel(R) Trace Hub Global Trace Hub (GTH) data structures
*
* Copyright (C) 2014-2015 Intel Corporation.
*/
#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] */
REG_TSCU_TSUCTRL = 0x2000, /* TSCU control register */
REG_TSCU_TSCUSTAT = 0x2004, /* TSCU status register */
/* Common Capture Sequencer (CTS) registers */
REG_CTS_C0S0_EN = 0x30c0, /* clause_event_enable_c0s0 */
REG_CTS_C0S0_ACT = 0x3180, /* clause_action_control_c0s0 */
REG_CTS_STAT = 0x32a0, /* cts_status */
REG_CTS_CTL = 0x32a4, /* cts_control */
};
/* waiting for Pipeline Empty bit(s) to assert for GTH */
#define GTH_PLE_WAITLOOP_DEPTH 10000
#define TSUCTRL_CTCRESYNC BIT(0)
#define TSCUSTAT_CTCSYNCING BIT(1)
/* waiting for Trigger status to assert for CTS */
#define CTS_TRIG_WAITLOOP_DEPTH 10000
#define CTS_EVENT_ENABLE_IF_ANYTHING BIT(31)
#define CTS_ACTION_CONTROL_STATE_OFF 27
#define CTS_ACTION_CONTROL_SET_STATE(x) \
(((x) & 0x1f) << CTS_ACTION_CONTROL_STATE_OFF)
#define CTS_ACTION_CONTROL_TRIGGER BIT(4)
#define CTS_STATE_IDLE 0x10u
#define CTS_CTL_SEQUENCER_ENABLE BIT(0)
#endif /* __INTEL_TH_GTH_H__ */
@@ -0,0 +1,384 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Intel(R) Trace Hub data structures
*
* Copyright (C) 2014-2015 Intel Corporation.
*/
#ifndef __INTEL_TH_H__
#define __INTEL_TH_H__
#include <linux/irqreturn.h>
/* intel_th_device device types */
enum {
/* Devices that generate trace data */
INTEL_TH_SOURCE = 0,
/* Output ports (MSC, PTI) */
INTEL_TH_OUTPUT,
/* Switch, the Global Trace Hub (GTH) */
INTEL_TH_SWITCH,
};
struct intel_th_device;
/**
* struct intel_th_output - descriptor INTEL_TH_OUTPUT type devices
* @port: output port number, assigned by the switch
* @type: GTH_{MSU,CTP,PTI}
* @scratchpad: scratchpad bits to flag when this output is enabled
* @multiblock: true for multiblock output configuration
* @active: true when this output is enabled
* @wait_empty: wait for device pipeline to be empty
*
* Output port descriptor, used by switch driver to tell which output
* port this output device corresponds to. Filled in at output device's
* probe time by switch::assign(). Passed from output device driver to
* switch related code to enable/disable its port.
*/
struct intel_th_output {
int port;
unsigned int type;
unsigned int scratchpad;
bool multiblock;
bool active;
};
/**
* struct intel_th_drvdata - describes hardware capabilities and quirks
* @tscu_enable: device needs SW to enable time stamping unit
* @multi_is_broken: device has multiblock mode is broken
* @has_mintctl: device has interrupt control (MINTCTL) register
* @host_mode_only: device can only operate in 'host debugger' mode
*/
struct intel_th_drvdata {
unsigned int tscu_enable : 1,
multi_is_broken : 1,
has_mintctl : 1,
host_mode_only : 1;
};
#define INTEL_TH_CAP(_th, _cap) ((_th)->drvdata ? (_th)->drvdata->_cap : 0)
/**
* struct intel_th_device - device on the intel_th bus
* @dev: device
* @drvdata: hardware capabilities/quirks
* @resource: array of resources available to this device
* @num_resources: number of resources in @resource array
* @type: INTEL_TH_{SOURCE,OUTPUT,SWITCH}
* @id: device instance or -1
* @host_mode: Intel TH is controlled by an external debug host
* @output: output descriptor for INTEL_TH_OUTPUT devices
* @name: device name to match the driver
*/
struct intel_th_device {
struct device dev;
const struct intel_th_drvdata *drvdata;
struct resource *resource;
unsigned int num_resources;
unsigned int type;
int id;
/* INTEL_TH_SWITCH specific */
bool host_mode;
/* INTEL_TH_OUTPUT specific */
struct intel_th_output output;
char name[];
};
#define to_intel_th_device(_d) \
container_of((_d), struct intel_th_device, dev)
/**
* intel_th_device_get_resource() - obtain @num'th resource of type @type
* @thdev: the device to search the resource for
* @type: resource type
* @num: number of the resource
*/
static inline struct resource *
intel_th_device_get_resource(struct intel_th_device *thdev, unsigned int type,
unsigned int num)
{
int i;
for (i = 0; i < thdev->num_resources; i++)
if (resource_type(&thdev->resource[i]) == type && !num--)
return &thdev->resource[i];
return NULL;
}
/*
* GTH, output ports configuration
*/
enum {
GTH_NONE = 0,
GTH_MSU, /* memory/usb */
GTH_CTP, /* Common Trace Port */
GTH_LPP, /* Low Power Path */
GTH_PTI, /* MIPI-PTI */
};
/**
* intel_th_output_assigned() - if an output device is assigned to a switch port
* @thdev: the output device
*
* Return: true if the device is INTEL_TH_OUTPUT *and* is assigned a port
*/
static inline bool
intel_th_output_assigned(struct intel_th_device *thdev)
{
return thdev->type == INTEL_TH_OUTPUT &&
(thdev->output.port >= 0 ||
thdev->output.type == GTH_NONE);
}
/**
* struct intel_th_driver - driver for an intel_th_device device
* @driver: generic driver
* @probe: probe method
* @remove: remove method
* @assign: match a given output type device against available outputs
* @unassign: deassociate an output type device from an output port
* @prepare: prepare output port for tracing
* @enable: enable tracing for a given output device
* @disable: disable tracing for a given output device
* @irq: interrupt callback
* @activate: enable tracing on the output's side
* @deactivate: disable tracing on the output's side
* @fops: file operations for device nodes
* @attr_group: attributes provided by the driver
*
* Callbacks @probe and @remove are required for all device types.
* Switch device driver needs to fill in @assign, @enable and @disable
* callbacks.
*/
struct intel_th_driver {
struct device_driver driver;
int (*probe)(struct intel_th_device *thdev);
void (*remove)(struct intel_th_device *thdev);
/* switch (GTH) ops */
int (*assign)(struct intel_th_device *thdev,
struct intel_th_device *othdev);
void (*unassign)(struct intel_th_device *thdev,
struct intel_th_device *othdev);
void (*prepare)(struct intel_th_device *thdev,
struct intel_th_output *output);
void (*enable)(struct intel_th_device *thdev,
struct intel_th_output *output);
void (*trig_switch)(struct intel_th_device *thdev,
struct intel_th_output *output);
void (*disable)(struct intel_th_device *thdev,
struct intel_th_output *output);
/* output ops */
irqreturn_t (*irq)(struct intel_th_device *thdev);
void (*wait_empty)(struct intel_th_device *thdev);
int (*activate)(struct intel_th_device *thdev);
void (*deactivate)(struct intel_th_device *thdev);
/* file_operations for those who want a device node */
const struct file_operations *fops;
/* optional attributes */
const struct attribute_group *attr_group;
/* source ops */
int (*set_output)(struct intel_th_device *thdev,
unsigned int master);
};
#define to_intel_th_driver(_d) \
container_of_const((_d), struct intel_th_driver, driver)
#define to_intel_th_driver_or_null(_d) \
((_d) ? to_intel_th_driver(_d) : NULL)
/*
* Subdevice tree structure is as follows:
* + struct intel_th device (pci; dev_{get,set}_drvdata()
* + struct intel_th_device INTEL_TH_SWITCH (GTH)
* + struct intel_th_device INTEL_TH_OUTPUT (MSU, PTI)
* + struct intel_th_device INTEL_TH_SOURCE (STH)
*
* In other words, INTEL_TH_OUTPUT devices are children of INTEL_TH_SWITCH;
* INTEL_TH_SWITCH and INTEL_TH_SOURCE are children of the intel_th device.
*/
static inline struct intel_th_device *
to_intel_th_parent(const struct intel_th_device *thdev)
{
struct device *parent = thdev->dev.parent;
if (!parent)
return NULL;
return to_intel_th_device(parent);
}
static inline struct intel_th *to_intel_th(const struct intel_th_device *thdev)
{
if (thdev->type == INTEL_TH_OUTPUT)
thdev = to_intel_th_parent(thdev);
if (WARN_ON_ONCE(!thdev || thdev->type == INTEL_TH_OUTPUT))
return NULL;
return dev_get_drvdata(thdev->dev.parent);
}
struct intel_th *
intel_th_alloc(struct device *dev, const struct intel_th_drvdata *drvdata,
struct resource *devres, unsigned int ndevres);
void intel_th_free(struct intel_th *th);
int intel_th_driver_register(struct intel_th_driver *thdrv);
void intel_th_driver_unregister(struct intel_th_driver *thdrv);
int intel_th_trace_enable(struct intel_th_device *thdev);
int intel_th_trace_switch(struct intel_th_device *thdev);
int intel_th_trace_disable(struct intel_th_device *thdev);
int intel_th_set_output(struct intel_th_device *thdev,
unsigned int master);
int intel_th_output_enable(struct intel_th *th, unsigned int otype);
enum th_mmio_idx {
TH_MMIO_CONFIG = 0,
TH_MMIO_SW = 1,
TH_MMIO_RTIT = 2,
TH_MMIO_END,
};
#define TH_POSSIBLE_OUTPUTS 8
/* Total number of possible subdevices: outputs + GTH + STH */
#define TH_SUBDEVICE_MAX (TH_POSSIBLE_OUTPUTS + 2)
#define TH_CONFIGURABLE_MASTERS 256
#define TH_MSC_MAX 2
/* Maximum IRQ vectors */
#define TH_NVEC_MAX 8
/**
* struct intel_th - Intel TH controller
* @dev: driver core's device
* @thdev: subdevices
* @hub: "switch" subdevice (GTH)
* @resource: resources of the entire controller
* @num_thdevs: number of devices in the @thdev array
* @num_resources: number of resources in the @resource array
* @irq: irq number
* @num_irqs: number of IRQs is use
* @id: this Intel TH controller's device ID in the system
* @major: device node major for output devices
*/
struct intel_th {
struct device *dev;
struct intel_th_device *thdev[TH_SUBDEVICE_MAX];
struct intel_th_device *hub;
const struct intel_th_drvdata *drvdata;
struct resource resource[TH_MMIO_END];
int (*activate)(struct intel_th *);
void (*deactivate)(struct intel_th *);
unsigned int num_thdevs;
unsigned int num_resources;
int irq;
int num_irqs;
int id;
int major;
#ifdef CONFIG_MODULES
struct work_struct request_module_work;
#endif /* CONFIG_MODULES */
#ifdef CONFIG_INTEL_TH_DEBUG
struct dentry *dbg;
#endif
};
static inline struct intel_th_device *
to_intel_th_hub(struct intel_th_device *thdev)
{
if (thdev->type == INTEL_TH_SWITCH)
return thdev;
else if (thdev->type == INTEL_TH_OUTPUT)
return to_intel_th_parent(thdev);
return to_intel_th(thdev)->hub;
}
/*
* Register windows
*/
enum {
/* Global Trace Hub (GTH) */
REG_GTH_OFFSET = 0x0000,
REG_GTH_LENGTH = 0x2000,
/* Timestamp counter unit (TSCU) */
REG_TSCU_OFFSET = 0x2000,
REG_TSCU_LENGTH = 0x1000,
REG_CTS_OFFSET = 0x3000,
REG_CTS_LENGTH = 0x1000,
/* Software Trace Hub (STH) [0x4000..0x4fff] */
REG_STH_OFFSET = 0x4000,
REG_STH_LENGTH = 0x2000,
/* Memory Storage Unit (MSU) [0xa0000..0xa1fff] */
REG_MSU_OFFSET = 0xa0000,
REG_MSU_LENGTH = 0x02000,
/* Internal MSU trace buffer [0x80000..0x9ffff] */
BUF_MSU_OFFSET = 0x80000,
BUF_MSU_LENGTH = 0x20000,
/* PTI output == same window as GTH */
REG_PTI_OFFSET = REG_GTH_OFFSET,
REG_PTI_LENGTH = REG_GTH_LENGTH,
/* DCI Handler (DCIH) == some window as MSU */
REG_DCIH_OFFSET = REG_MSU_OFFSET,
REG_DCIH_LENGTH = REG_MSU_LENGTH,
};
/*
* Scratchpad bits: tell firmware and external debuggers
* what we are up to.
*/
enum {
/* Memory is the primary destination */
SCRPD_MEM_IS_PRIM_DEST = BIT(0),
/* XHCI DbC is the primary destination */
SCRPD_DBC_IS_PRIM_DEST = BIT(1),
/* PTI is the primary destination */
SCRPD_PTI_IS_PRIM_DEST = BIT(2),
/* BSSB is the primary destination */
SCRPD_BSSB_IS_PRIM_DEST = BIT(3),
/* PTI is the alternate destination */
SCRPD_PTI_IS_ALT_DEST = BIT(4),
/* BSSB is the alternate destination */
SCRPD_BSSB_IS_ALT_DEST = BIT(5),
/* DeepSx exit occurred */
SCRPD_DEEPSX_EXIT = BIT(6),
/* S4 exit occurred */
SCRPD_S4_EXIT = BIT(7),
/* S5 exit occurred */
SCRPD_S5_EXIT = BIT(8),
/* MSU controller 0/1 is enabled */
SCRPD_MSC0_IS_ENABLED = BIT(9),
SCRPD_MSC1_IS_ENABLED = BIT(10),
/* Sx exit occurred */
SCRPD_SX_EXIT = BIT(11),
/* Trigger Unit is enabled */
SCRPD_TRIGGER_IS_ENABLED = BIT(12),
SCRPD_ODLA_IS_ENABLED = BIT(13),
SCRPD_SOCHAP_IS_ENABLED = BIT(14),
SCRPD_STH_IS_ENABLED = BIT(15),
SCRPD_DCIH_IS_ENABLED = BIT(16),
SCRPD_VER_IS_ENABLED = BIT(17),
/* External debugger is using Intel TH */
SCRPD_DEBUGGER_IN_USE = BIT(24),
};
#endif
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0
/*
* An example software sink buffer for Intel TH MSU.
*
* Copyright (C) 2019 Intel Corporation.
*/
#include <linux/intel_th.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#define MAX_SGTS 16
struct msu_sink_private {
struct device *dev;
struct sg_table **sgts;
unsigned int nr_sgts;
};
static void *msu_sink_assign(struct device *dev, int *mode)
{
struct msu_sink_private *priv;
priv = kzalloc_obj(*priv);
if (!priv)
return NULL;
priv->sgts = kcalloc(MAX_SGTS, sizeof(void *), GFP_KERNEL);
if (!priv->sgts) {
kfree(priv);
return NULL;
}
priv->dev = dev;
*mode = MSC_MODE_MULTI;
return priv;
}
static void msu_sink_unassign(void *data)
{
struct msu_sink_private *priv = data;
kfree(priv->sgts);
kfree(priv);
}
/* See also: msc.c: __msc_buffer_win_alloc() */
static int msu_sink_alloc_window(void *data, struct sg_table **sgt, size_t size)
{
struct msu_sink_private *priv = data;
unsigned int nents;
struct scatterlist *sg_ptr;
void *block;
int ret, i;
if (priv->nr_sgts == MAX_SGTS)
return -ENOMEM;
nents = DIV_ROUND_UP(size, PAGE_SIZE);
ret = sg_alloc_table(*sgt, nents, GFP_KERNEL);
if (ret)
return -ENOMEM;
priv->sgts[priv->nr_sgts++] = *sgt;
for_each_sg((*sgt)->sgl, sg_ptr, nents, i) {
block = dma_alloc_coherent(priv->dev->parent->parent,
PAGE_SIZE, &sg_dma_address(sg_ptr),
GFP_KERNEL);
if (!block)
return -ENOMEM;
sg_set_buf(sg_ptr, block, PAGE_SIZE);
}
return nents;
}
/* See also: msc.c: __msc_buffer_win_free() */
static void msu_sink_free_window(void *data, struct sg_table *sgt)
{
struct msu_sink_private *priv = data;
struct scatterlist *sg_ptr;
int i;
for_each_sg(sgt->sgl, sg_ptr, sgt->nents, i) {
dma_free_coherent(priv->dev->parent->parent, PAGE_SIZE,
sg_virt(sg_ptr), sg_dma_address(sg_ptr));
}
sg_free_table(sgt);
priv->nr_sgts--;
}
static int msu_sink_ready(void *data, struct sg_table *sgt, size_t bytes)
{
struct msu_sink_private *priv = data;
intel_th_msc_window_unlock(priv->dev, sgt);
return 0;
}
static const struct msu_buffer sink_mbuf = {
.name = "sink",
.assign = msu_sink_assign,
.unassign = msu_sink_unassign,
.alloc_window = msu_sink_alloc_window,
.free_window = msu_sink_free_window,
.ready = msu_sink_ready,
};
module_intel_th_msu_buffer(sink_mbuf);
MODULE_DESCRIPTION("example software sink buffer for Intel TH MSU");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,118 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Intel(R) Trace Hub Memory Storage Unit (MSU) data structures
*
* Copyright (C) 2014-2015 Intel Corporation.
*/
#ifndef __INTEL_TH_MSU_H__
#define __INTEL_TH_MSU_H__
enum {
REG_MSU_MSUPARAMS = 0x0000,
REG_MSU_MSUSTS = 0x0008,
REG_MSU_MINTCTL = 0x0004, /* MSU-global interrupt control */
REG_MSU_MSC0CTL = 0x0100, /* MSC0 control */
REG_MSU_MSC0STS = 0x0104, /* MSC0 status */
REG_MSU_MSC0BAR = 0x0108, /* MSC0 output base address */
REG_MSU_MSC0SIZE = 0x010c, /* MSC0 output size */
REG_MSU_MSC0MWP = 0x0110, /* MSC0 write pointer */
REG_MSU_MSC0NWSA = 0x011c, /* MSC0 next window start address */
REG_MSU_MSC1CTL = 0x0200, /* MSC1 control */
REG_MSU_MSC1STS = 0x0204, /* MSC1 status */
REG_MSU_MSC1BAR = 0x0208, /* MSC1 output base address */
REG_MSU_MSC1SIZE = 0x020c, /* MSC1 output size */
REG_MSU_MSC1MWP = 0x0210, /* MSC1 write pointer */
REG_MSU_MSC1NWSA = 0x021c, /* MSC1 next window start address */
};
/* MSUSTS bits */
#define MSUSTS_MSU_INT BIT(0)
#define MSUSTS_MSC0BLAST BIT(16)
#define MSUSTS_MSC1BLAST BIT(24)
/* MSCnCTL bits */
#define MSC_EN BIT(0)
#define MSC_WRAPEN BIT(1)
#define MSC_RD_HDR_OVRD BIT(2)
#define MSC_MODE (BIT(4) | BIT(5))
#define MSC_LEN (BIT(8) | BIT(9) | BIT(10))
/* MINTCTL bits */
#define MICDE BIT(0)
#define M0BLIE BIT(16)
#define M1BLIE BIT(24)
/* MSCnSTS bits */
#define MSCSTS_WRAPSTAT BIT(1) /* Wrap occurred */
#define MSCSTS_PLE BIT(2) /* Pipeline Empty */
/*
* Multiblock/multiwindow block descriptor
*/
struct msc_block_desc {
u32 sw_tag;
u32 block_sz;
u32 next_blk;
u32 next_win;
u32 res0[4];
u32 hw_tag;
u32 valid_dw;
u32 ts_low;
u32 ts_high;
u32 res1[4];
} __packed;
#define MSC_BDESC sizeof(struct msc_block_desc)
#define DATA_IN_PAGE (PAGE_SIZE - MSC_BDESC)
/* MSC multiblock sw tag bits */
#define MSC_SW_TAG_LASTBLK BIT(0)
#define MSC_SW_TAG_LASTWIN BIT(1)
/* MSC multiblock hw tag bits */
#define MSC_HW_TAG_TRIGGER BIT(0)
#define MSC_HW_TAG_BLOCKWRAP BIT(1)
#define MSC_HW_TAG_WINWRAP BIT(2)
#define MSC_HW_TAG_ENDBIT BIT(3)
static inline unsigned long msc_data_sz(struct msc_block_desc *bdesc)
{
if (!bdesc->valid_dw)
return 0;
return bdesc->valid_dw * 4 - MSC_BDESC;
}
static inline unsigned long msc_total_sz(struct msc_block_desc *bdesc)
{
return bdesc->valid_dw * 4;
}
static inline unsigned long msc_block_sz(struct msc_block_desc *bdesc)
{
return bdesc->block_sz * 64 - MSC_BDESC;
}
static inline bool msc_block_wrapped(struct msc_block_desc *bdesc)
{
if (bdesc->hw_tag & (MSC_HW_TAG_BLOCKWRAP | MSC_HW_TAG_WINWRAP))
return true;
return false;
}
static inline bool msc_block_last_written(struct msc_block_desc *bdesc)
{
if ((bdesc->hw_tag & MSC_HW_TAG_ENDBIT) ||
(msc_data_sz(bdesc) != msc_block_sz(bdesc)))
return true;
return false;
}
/* waiting for Pipeline Empty bit(s) to assert for MSC */
#define MSC_PLE_WAITLOOP_DEPTH 10000
#endif /* __INTEL_TH_MSU_H__ */
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel(R) Trace Hub pci driver
*
* 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/sysfs.h>
#include <linux/pci.h>
#include "intel_th.h"
#include "pci_ids.h"
#define DRIVER_NAME "intel_th_pci"
enum {
TH_PCI_CONFIG_BAR = 0,
TH_PCI_STH_SW_BAR = 2,
TH_PCI_RTIT_BAR = 4,
};
#define PCI_REG_NPKDSC 0x80
#define NPKDSC_TSACT BIT(5)
static int intel_th_pci_activate(struct intel_th *th)
{
struct pci_dev *pdev = to_pci_dev(th->dev);
u32 npkdsc;
int err;
if (!INTEL_TH_CAP(th, tscu_enable))
return 0;
err = pci_read_config_dword(pdev, PCI_REG_NPKDSC, &npkdsc);
if (!err) {
npkdsc |= NPKDSC_TSACT;
err = pci_write_config_dword(pdev, PCI_REG_NPKDSC, npkdsc);
}
if (err)
dev_err(&pdev->dev, "failed to read NPKDSC register\n");
return err;
}
static void intel_th_pci_deactivate(struct intel_th *th)
{
struct pci_dev *pdev = to_pci_dev(th->dev);
u32 npkdsc;
int err;
if (!INTEL_TH_CAP(th, tscu_enable))
return;
err = pci_read_config_dword(pdev, PCI_REG_NPKDSC, &npkdsc);
if (!err) {
npkdsc |= NPKDSC_TSACT;
err = pci_write_config_dword(pdev, PCI_REG_NPKDSC, npkdsc);
}
if (err)
dev_err(&pdev->dev, "failed to read NPKDSC register\n");
}
static int intel_th_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
const struct intel_th_drvdata *drvdata = (void *)id->driver_data;
struct resource resource[TH_MMIO_END + TH_NVEC_MAX] = {
[TH_MMIO_CONFIG] = pdev->resource[TH_PCI_CONFIG_BAR],
[TH_MMIO_SW] = pdev->resource[TH_PCI_STH_SW_BAR],
};
int err, r = TH_MMIO_SW + 1, i;
struct intel_th *th;
err = pcim_enable_device(pdev);
if (err)
return err;
err = pcim_request_all_regions(pdev, DRIVER_NAME);
if (err)
return err;
if (!pcim_iomap(pdev, TH_PCI_CONFIG_BAR, 0))
return -ENOMEM;
if (!pcim_iomap(pdev, TH_PCI_STH_SW_BAR, 0))
return -ENOMEM;
if (pdev->resource[TH_PCI_RTIT_BAR].start) {
resource[TH_MMIO_RTIT] = pdev->resource[TH_PCI_RTIT_BAR];
r++;
}
err = pci_alloc_irq_vectors(pdev, 1, 8, PCI_IRQ_ALL_TYPES);
if (err > 0)
for (i = 0; i < err; i++, r++) {
resource[r].flags = IORESOURCE_IRQ;
resource[r].start = pci_irq_vector(pdev, i);
}
th = intel_th_alloc(&pdev->dev, drvdata, resource, r);
if (IS_ERR(th)) {
err = PTR_ERR(th);
goto err_free_irq;
}
th->activate = intel_th_pci_activate;
th->deactivate = intel_th_pci_deactivate;
pci_set_master(pdev);
return 0;
err_free_irq:
pci_free_irq_vectors(pdev);
return err;
}
static void intel_th_pci_remove(struct pci_dev *pdev)
{
struct intel_th *th = pci_get_drvdata(pdev);
intel_th_free(th);
pci_free_irq_vectors(pdev);
}
static const struct intel_th_drvdata intel_th_1x_multi_is_broken = {
.multi_is_broken = 1,
};
static const struct intel_th_drvdata intel_th_2x = {
.tscu_enable = 1,
.has_mintctl = 1,
};
static const struct pci_device_id intel_th_pci_id_table[] = {
{ PCI_DEVICE_DATA(INTEL, NPK_CML, &intel_th_2x) }, /* Comet Lake */
{ PCI_DEVICE_DATA(INTEL, NPK_CML_PCH, &intel_th_2x) }, /* Comet Lake PCH */
{ PCI_DEVICE_DATA(INTEL, NPK_GNR, &intel_th_2x) }, /* Granite Rapids */
{ PCI_DEVICE_DATA(INTEL, NPK_BXT, NULL) }, /* Broxton */
{ PCI_DEVICE_DATA(INTEL, NPK_CDF, &intel_th_2x) }, /* Cedar Fork PCH */
{ PCI_DEVICE_DATA(INTEL, NPK_DNV, NULL) }, /* Denverton */
{ PCI_DEVICE_DATA(INTEL, NPK_BXT_B, NULL) }, /* Broxton B-step */
{ PCI_DEVICE_DATA(INTEL, NPK_EBG, &intel_th_2x) }, /* Emmitsburg PCH */
{ PCI_DEVICE_DATA(INTEL, NPK_GLK, &intel_th_2x) }, /* Gemini Lake */
{ PCI_DEVICE_DATA(INTEL, NPK_GNR_SOC, &intel_th_2x) }, /* Granite Rapids SOC */
{ PCI_DEVICE_DATA(INTEL, NPK_SPR, &intel_th_2x) }, /* Sapphire Rapids SOC */
{ PCI_DEVICE_DATA(INTEL, NPK_ICL_PCH, &intel_th_2x) }, /* Ice Lake PCH */
{ PCI_DEVICE_DATA(INTEL, NPK_TGL_PCH_H, &intel_th_2x) }, /* Tiger Lake PCH-H */
{ PCI_DEVICE_DATA(INTEL, NPK_EHL_CPU, &intel_th_2x) }, /* Elkhart Lake CPU */
{ PCI_DEVICE_DATA(INTEL, NPK_ICL_NNPI, &intel_th_2x) }, /* Ice Lake NNPI */
{ PCI_DEVICE_DATA(INTEL, NPK_ADL_CPU, &intel_th_2x) }, /* Alder Lake CPU */
{ PCI_DEVICE_DATA(INTEL, NPK_EHL, &intel_th_2x) }, /* Elkhart Lake */
{ PCI_DEVICE_DATA(INTEL, NPK_RKL, &intel_th_2x) }, /* Rocket Lake CPU */
{ PCI_DEVICE_DATA(INTEL, NPK_JSL_PCH, &intel_th_2x) }, /* Jasper Lake PCH */
{ PCI_DEVICE_DATA(INTEL, NPK_JSL_CPU, &intel_th_2x) }, /* Jasper Lake CPU */
{ PCI_DEVICE_DATA(INTEL, NPK_ADL_P, &intel_th_2x) }, /* Alder Lake-P */
{ PCI_DEVICE_DATA(INTEL, NPK_ADL_M, &intel_th_2x) }, /* Alder Lake-M */
{ PCI_DEVICE_DATA(INTEL, NPK_APL, NULL) }, /* Apollo Lake */
{ PCI_DEVICE_DATA(INTEL, NPK_NVL_PCH, &intel_th_2x) }, /* Nova Lake-PCH */
{ PCI_DEVICE_DATA(INTEL, NPK_ARL, &intel_th_2x) }, /* Arrow Lake */
{ PCI_DEVICE_DATA(INTEL, NPK_RPL_S, &intel_th_2x) }, /* Raptor Lake-S */
{ PCI_DEVICE_DATA(INTEL, NPK_ADL, &intel_th_2x) }, /* Alder Lake */
{ PCI_DEVICE_DATA(INTEL, NPK_MTL_P, &intel_th_2x) }, /* Meteor Lake-P */
{ PCI_DEVICE_DATA(INTEL, NPK_MTL_S, &intel_th_2x) }, /* Meteor Lake-S */
{ PCI_DEVICE_DATA(INTEL, NPK_ICL_CPU, &intel_th_2x) }, /* Ice Lake CPU */
{ PCI_DEVICE_DATA(INTEL, NPK_TGL_CPU, &intel_th_2x) }, /* Tiger Lake CPU */
{ PCI_DEVICE_DATA(INTEL, NPK_0, NULL) },
{ PCI_DEVICE_DATA(INTEL, NPK_CNL_LP, &intel_th_2x) }, /* Cannon Lake LP */
{ PCI_DEVICE_DATA(INTEL, NPK_TGL_PCH, &intel_th_2x) }, /* Tiger Lake PCH */
{ PCI_DEVICE_DATA(INTEL, NPK_1, NULL) },
{ PCI_DEVICE_DATA(INTEL, NPK_LBG_PCH, NULL) }, /* Lewisburg PCH */
{ PCI_DEVICE_DATA(INTEL, NPK_LBG_PCH_2, NULL) }, /* Lewisburg PCH */
{ PCI_DEVICE_DATA(INTEL, NPK_KBL_PCH, &intel_th_1x_multi_is_broken) }, /* Kaby Lake PCH-H */
{ PCI_DEVICE_DATA(INTEL, NPK_CNL_H, &intel_th_2x) }, /* Cannon Lake H */
{ PCI_DEVICE_DATA(INTEL, NPK_CML_PCH_V, &intel_th_1x_multi_is_broken) }, /* Comet Lake PCH-V */
{ PCI_DEVICE_DATA(INTEL, NPK_RPL_S_CPU, &intel_th_2x) }, /* Raptor Lake-S CPU */
{ PCI_DEVICE_DATA(INTEL, NPK_LNL, &intel_th_2x) }, /* Lunar Lake */
{ PCI_DEVICE_DATA(INTEL, NPK_MTL_S_CPU, &intel_th_2x) }, /* Meteor Lake-S CPU */
{ PCI_DEVICE_DATA(INTEL, NPK_NVL_P, &intel_th_2x) }, /* Nova Lake-P */
{ PCI_DEVICE_DATA(INTEL, NPK_NVL_H, &intel_th_2x) }, /* Nova Lake-H */
{ PCI_DEVICE_DATA(INTEL, NPK_NVL_S, &intel_th_2x) }, /* Nova Lake-S */
{ PCI_DEVICE_DATA(INTEL, NPK_PTL_H, &intel_th_2x) }, /* Panther Lake-H */
{ PCI_DEVICE_DATA(INTEL, NPK_PTL_PU, &intel_th_2x) }, /* Panther Lake-P/U */
{ }
};
MODULE_DEVICE_TABLE(pci, intel_th_pci_id_table);
static struct pci_driver intel_th_pci_driver = {
.name = DRIVER_NAME,
.id_table = intel_th_pci_id_table,
.probe = intel_th_pci_probe,
.remove = intel_th_pci_remove,
};
module_pci_driver(intel_th_pci_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Intel(R) Trace Hub PCI controller driver");
MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@intel.com>");
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Intel(R) Trace Hub driver debugging
*
* Copyright (C) 2025 Intel Corporation.
*/
#ifndef __INTEL_TH_PCI_IDS_H__
#define __INTEL_TH_PCI_IDS_H__
#define PCI_DEVICE_ID_INTEL_NPK_CML 0x02a6
#define PCI_DEVICE_ID_INTEL_NPK_CML_PCH 0x06a6
#define PCI_DEVICE_ID_INTEL_NPK_GNR 0x0963
#define PCI_DEVICE_ID_INTEL_NPK_BXT 0x0a80
#define PCI_DEVICE_ID_INTEL_NPK_CDF 0x18e1
#define PCI_DEVICE_ID_INTEL_NPK_DNV 0x19e1
#define PCI_DEVICE_ID_INTEL_NPK_BXT_B 0x1a8e
#define PCI_DEVICE_ID_INTEL_NPK_EBG 0x1bcc
#define PCI_DEVICE_ID_INTEL_NPK_GLK 0x318e
#define PCI_DEVICE_ID_INTEL_NPK_GNR_SOC 0x3256
#define PCI_DEVICE_ID_INTEL_NPK_SPR 0x3456
#define PCI_DEVICE_ID_INTEL_NPK_ICL_PCH 0x34a6
#define PCI_DEVICE_ID_INTEL_NPK_TGL_PCH_H 0x43a6
#define PCI_DEVICE_ID_INTEL_NPK_EHL_CPU 0x4529
#define PCI_DEVICE_ID_INTEL_NPK_ICL_NNPI 0x45c5
#define PCI_DEVICE_ID_INTEL_NPK_ADL_CPU 0x466f
#define PCI_DEVICE_ID_INTEL_NPK_EHL 0x4b26
#define PCI_DEVICE_ID_INTEL_NPK_RKL 0x4c19
#define PCI_DEVICE_ID_INTEL_NPK_JSL_PCH 0x4da6
#define PCI_DEVICE_ID_INTEL_NPK_JSL_CPU 0x4e29
#define PCI_DEVICE_ID_INTEL_NPK_ADL_P 0x51a6
#define PCI_DEVICE_ID_INTEL_NPK_ADL_M 0x54a6
#define PCI_DEVICE_ID_INTEL_NPK_APL 0x5a8e
#define PCI_DEVICE_ID_INTEL_NPK_NVL_PCH 0x6e26
#define PCI_DEVICE_ID_INTEL_NPK_ARL 0x7724
#define PCI_DEVICE_ID_INTEL_NPK_RPL_S 0x7a26
#define PCI_DEVICE_ID_INTEL_NPK_ADL 0x7aa6
#define PCI_DEVICE_ID_INTEL_NPK_MTL_P 0x7e24
#define PCI_DEVICE_ID_INTEL_NPK_MTL_S 0x7f26
#define PCI_DEVICE_ID_INTEL_NPK_ICL_CPU 0x8a29
#define PCI_DEVICE_ID_INTEL_NPK_TGL_CPU 0x9a33
#define PCI_DEVICE_ID_INTEL_NPK_0 0x9d26
#define PCI_DEVICE_ID_INTEL_NPK_CNL_LP 0x9da6
#define PCI_DEVICE_ID_INTEL_NPK_TGL_PCH 0xa0a6
#define PCI_DEVICE_ID_INTEL_NPK_1 0xa126
#define PCI_DEVICE_ID_INTEL_NPK_LBG_PCH 0xa1a6
#define PCI_DEVICE_ID_INTEL_NPK_LBG_PCH_2 0xa226
#define PCI_DEVICE_ID_INTEL_NPK_KBL_PCH 0xa2a6
#define PCI_DEVICE_ID_INTEL_NPK_CNL_H 0xa326
#define PCI_DEVICE_ID_INTEL_NPK_CML_PCH_V 0xa3a6
#define PCI_DEVICE_ID_INTEL_NPK_RPL_S_CPU 0xa76f
#define PCI_DEVICE_ID_INTEL_NPK_LNL 0xa824
#define PCI_DEVICE_ID_INTEL_NPK_MTL_S_CPU 0xae24
#define PCI_DEVICE_ID_INTEL_NPK_NVL_P 0xd224
#define PCI_DEVICE_ID_INTEL_NPK_NVL_H 0xd324
#define PCI_DEVICE_ID_INTEL_NPK_NVL_S 0xd424
#define PCI_DEVICE_ID_INTEL_NPK_PTL_H 0xe324
#define PCI_DEVICE_ID_INTEL_NPK_PTL_PU 0xe424
#endif /* __INTEL_TH_PCI_IDS_H__ */
@@ -0,0 +1,343 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel(R) Trace Hub PTI output driver
*
* Copyright (C) 2014-2016 Intel Corporation.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/types.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/sizes.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#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 const 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);
int i;
i = sysfs_match_string(lpp_dest_str, buf);
if (i < 0)
return i;
if (!(pti->lpp_dest_mask & BIT(i)))
return -EINVAL;
pti->lpp_dest = i;
return size;
}
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 const 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 <alexander.shishkin@linux.intel.com>");
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Intel(R) Trace Hub PTI output data structures
*
* Copyright (C) 2014-2015 Intel Corporation.
*/
#ifndef __INTEL_TH_STH_H__
#define __INTEL_TH_STH_H__
enum {
REG_PTI_CTL = 0x1c00,
};
#define PTI_EN BIT(0)
#define PTI_FCEN BIT(1)
#define PTI_MODE 0xf0
#define LPP_PTIPRESENT BIT(8)
#define LPP_BSSBPRESENT BIT(9)
#define PTI_CLKDIV 0x000f0000
#define PTI_PATGENMODE 0x00f00000
#define LPP_DEST BIT(25)
#define LPP_BSSBACT BIT(30)
#define LPP_LPPBUSY BIT(31)
#define LPP_DEST_PTI BIT(0)
#define LPP_DEST_EXI BIT(1)
#endif /* __INTEL_TH_STH_H__ */
@@ -0,0 +1,261 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Intel(R) Trace Hub Software Trace Hub support
*
* 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/stm.h>
#include "intel_th.h"
#include "sth.h"
struct sth_device {
void __iomem *base;
void __iomem *channels;
phys_addr_t channels_phys;
struct device *dev;
struct stm_data stm;
unsigned int sw_nmasters;
};
static struct intel_th_channel __iomem *
sth_channel(struct sth_device *sth, unsigned int master, unsigned int channel)
{
struct intel_th_channel __iomem *sw_map = sth->channels;
return &sw_map[(master - sth->stm.sw_start) * sth->stm.sw_nchannels +
channel];
}
static void sth_iowrite(void __iomem *dest, const unsigned char *payload,
unsigned int size)
{
switch (size) {
#ifdef CONFIG_64BIT
case 8:
writeq_relaxed(*(u64 *)payload, dest);
break;
#endif
case 4:
writel_relaxed(*(u32 *)payload, dest);
break;
case 2:
writew_relaxed(*(u16 *)payload, dest);
break;
case 1:
writeb_relaxed(*(u8 *)payload, dest);
break;
default:
break;
}
}
static ssize_t notrace sth_stm_packet(struct stm_data *stm_data,
unsigned int master,
unsigned int channel,
unsigned int packet,
unsigned int flags,
unsigned int size,
const unsigned char *payload)
{
struct sth_device *sth = container_of(stm_data, struct sth_device, stm);
struct intel_th_channel __iomem *out =
sth_channel(sth, master, channel);
unsigned long reg = REG_STH_TRIG;
u64 __iomem *outp;
#ifndef CONFIG_64BIT
if (size > 4)
size = 4;
#endif
size = rounddown_pow_of_two(size);
switch (packet) {
/* Global packets (GERR, XSYNC, TRIG) are sent with register writes */
case STP_PACKET_GERR:
reg += 4;
fallthrough;
case STP_PACKET_XSYNC:
reg += 8;
fallthrough;
case STP_PACKET_TRIG:
if (flags & STP_PACKET_TIMESTAMPED)
reg += 4;
writeb_relaxed(*payload, sth->base + reg);
break;
case STP_PACKET_MERR:
if (size > 4)
size = 4;
sth_iowrite(&out->MERR, payload, size);
break;
case STP_PACKET_FLAG:
if (flags & STP_PACKET_TIMESTAMPED)
outp = (u64 __iomem *)&out->FLAG_TS;
else
outp = (u64 __iomem *)&out->FLAG;
size = 0;
writeb_relaxed(0, outp);
break;
case STP_PACKET_USER:
if (flags & STP_PACKET_TIMESTAMPED)
outp = &out->USER_TS;
else
outp = &out->USER;
sth_iowrite(outp, payload, size);
break;
case STP_PACKET_DATA:
outp = &out->Dn;
if (flags & STP_PACKET_TIMESTAMPED)
outp += 2;
if (flags & STP_PACKET_MARKED)
outp++;
sth_iowrite(outp, payload, size);
break;
default:
return -ENOTSUPP;
}
return size;
}
static phys_addr_t
sth_stm_mmio_addr(struct stm_data *stm_data, unsigned int master,
unsigned int channel, unsigned int nr_chans)
{
struct sth_device *sth = container_of(stm_data, struct sth_device, stm);
phys_addr_t addr;
master -= sth->stm.sw_start;
addr = sth->channels_phys + (master * sth->stm.sw_nchannels + channel) *
sizeof(struct intel_th_channel);
if (offset_in_page(addr) ||
offset_in_page(nr_chans * sizeof(struct intel_th_channel)))
return 0;
return addr;
}
static int sth_stm_link(struct stm_data *stm_data, unsigned int master,
unsigned int channel)
{
struct sth_device *sth = container_of(stm_data, struct sth_device, stm);
return intel_th_set_output(to_intel_th_device(sth->dev), master);
}
static int intel_th_sw_init(struct sth_device *sth)
{
u32 reg;
reg = ioread32(sth->base + REG_STH_STHCAP1);
sth->stm.sw_nchannels = reg & 0xff;
reg = ioread32(sth->base + REG_STH_STHCAP0);
sth->stm.sw_start = reg & 0xffff;
sth->stm.sw_end = reg >> 16;
sth->sw_nmasters = sth->stm.sw_end - sth->stm.sw_start;
dev_dbg(sth->dev, "sw_start: %x sw_end: %x masters: %x nchannels: %x\n",
sth->stm.sw_start, sth->stm.sw_end, sth->sw_nmasters,
sth->stm.sw_nchannels);
return 0;
}
static int intel_th_sth_probe(struct intel_th_device *thdev)
{
struct device *dev = &thdev->dev;
struct sth_device *sth;
struct resource *res;
void __iomem *base, *channels;
int err;
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;
res = intel_th_device_get_resource(thdev, IORESOURCE_MEM, 1);
if (!res)
return -ENODEV;
channels = devm_ioremap(dev, res->start, resource_size(res));
if (!channels)
return -ENOMEM;
sth = devm_kzalloc(dev, sizeof(*sth), GFP_KERNEL);
if (!sth)
return -ENOMEM;
sth->dev = dev;
sth->base = base;
sth->channels = channels;
sth->channels_phys = res->start;
sth->stm.name = dev_name(dev);
sth->stm.packet = sth_stm_packet;
sth->stm.mmio_addr = sth_stm_mmio_addr;
sth->stm.sw_mmiosz = sizeof(struct intel_th_channel);
sth->stm.link = sth_stm_link;
err = intel_th_sw_init(sth);
if (err)
return err;
err = stm_register_device(dev, &sth->stm, THIS_MODULE);
if (err) {
dev_err(dev, "stm_register_device failed\n");
return err;
}
dev_set_drvdata(dev, sth);
return 0;
}
static void intel_th_sth_remove(struct intel_th_device *thdev)
{
struct sth_device *sth = dev_get_drvdata(&thdev->dev);
stm_unregister_device(&sth->stm);
}
static struct intel_th_driver intel_th_sth_driver = {
.probe = intel_th_sth_probe,
.remove = intel_th_sth_remove,
.driver = {
.name = "sth",
.owner = THIS_MODULE,
},
};
module_driver(intel_th_sth_driver,
intel_th_driver_register,
intel_th_driver_unregister);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Intel(R) Trace Hub Software Trace Hub driver");
MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@intel.com>");
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Intel(R) Trace Hub Software Trace Hub (STH) data structures
*
* Copyright (C) 2014-2015 Intel Corporation.
*/
#ifndef __INTEL_TH_STH_H__
#define __INTEL_TH_STH_H__
enum {
REG_STH_STHCAP0 = 0x0000, /* capabilities pt1 */
REG_STH_STHCAP1 = 0x0004, /* capabilities pt2 */
REG_STH_TRIG = 0x0008, /* TRIG packet payload */
REG_STH_TRIG_TS = 0x000c, /* TRIG_TS packet payload */
REG_STH_XSYNC = 0x0010, /* XSYNC packet payload */
REG_STH_XSYNC_TS = 0x0014, /* XSYNC_TS packet payload */
REG_STH_GERR = 0x0018, /* GERR packet payload */
};
struct intel_th_channel {
u64 Dn;
u64 DnM;
u64 DnTS;
u64 DnMTS;
u64 USER;
u64 USER_TS;
u32 FLAG;
u32 FLAG_TS;
u32 MERR;
u32 __unused;
} __packed;
#endif /* __INTEL_TH_STH_H__ */