1188 lines
31 KiB
C
1188 lines
31 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 Linaro Limited, All rights reserved.
|
|
* Author: Mike Leach <mike.leach@linaro.org>
|
|
*/
|
|
|
|
#include <linux/atomic.h>
|
|
#include <linux/coresight.h>
|
|
#include <linux/device.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/sysfs.h>
|
|
|
|
#include "coresight-cti.h"
|
|
|
|
/*
|
|
* Declare the number of static declared attribute groups
|
|
* Value includes groups + NULL value at end of table.
|
|
*/
|
|
#define CORESIGHT_CTI_STATIC_GROUPS_MAX 5
|
|
|
|
/*
|
|
* List of trigger signal type names. Match the constants declared in
|
|
* include\dt-bindings\arm\coresight-cti-dt.h
|
|
*/
|
|
static const char * const sig_type_names[] = {
|
|
"genio", /* GEN_IO */
|
|
"intreq", /* GEN_INTREQ */
|
|
"intack", /* GEN_INTACK */
|
|
"haltreq", /* GEN_HALTREQ */
|
|
"restartreq", /* GEN_RESTARTREQ */
|
|
"pe_edbgreq", /* PE_EDBGREQ */
|
|
"pe_dbgrestart",/* PE_DBGRESTART */
|
|
"pe_ctiirq", /* PE_CTIIRQ */
|
|
"pe_pmuirq", /* PE_PMUIRQ */
|
|
"pe_dbgtrigger",/* PE_DBGTRIGGER */
|
|
"etm_extout", /* ETM_EXTOUT */
|
|
"etm_extin", /* ETM_EXTIN */
|
|
"snk_full", /* SNK_FULL */
|
|
"snk_acqcomp", /* SNK_ACQCOMP */
|
|
"snk_flushcomp",/* SNK_FLUSHCOMP */
|
|
"snk_flushin", /* SNK_FLUSHIN */
|
|
"snk_trigin", /* SNK_TRIGIN */
|
|
"stm_asyncout", /* STM_ASYNCOUT */
|
|
"stm_tout_spte",/* STM_TOUT_SPTE */
|
|
"stm_tout_sw", /* STM_TOUT_SW */
|
|
"stm_tout_hete",/* STM_TOUT_HETE */
|
|
"stm_hwevent", /* STM_HWEVENT */
|
|
"ela_tstart", /* ELA_TSTART */
|
|
"ela_tstop", /* ELA_TSTOP */
|
|
"ela_dbgreq", /* ELA_DBGREQ */
|
|
};
|
|
|
|
/* Show function pointer used in the connections dynamic declared attributes*/
|
|
typedef ssize_t (*p_show_fn)(struct device *dev, struct device_attribute *attr,
|
|
char *buf);
|
|
|
|
/* Connection attribute types */
|
|
enum cti_conn_attr_type {
|
|
CTI_CON_ATTR_NAME,
|
|
CTI_CON_ATTR_TRIGIN_SIG,
|
|
CTI_CON_ATTR_TRIGOUT_SIG,
|
|
CTI_CON_ATTR_TRIGIN_TYPES,
|
|
CTI_CON_ATTR_TRIGOUT_TYPES,
|
|
CTI_CON_ATTR_MAX,
|
|
};
|
|
|
|
/* Names for the connection attributes */
|
|
static const char * const con_attr_names[CTI_CON_ATTR_MAX] = {
|
|
"name",
|
|
"in_signals",
|
|
"out_signals",
|
|
"in_types",
|
|
"out_types",
|
|
};
|
|
|
|
/* basic attributes */
|
|
static ssize_t enable_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int enable_req;
|
|
bool enabled, powered;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
enable_req = atomic_read(&drvdata->config.enable_req_count);
|
|
spin_lock(&drvdata->spinlock);
|
|
powered = drvdata->config.hw_powered;
|
|
enabled = drvdata->config.hw_enabled;
|
|
spin_unlock(&drvdata->spinlock);
|
|
|
|
if (powered)
|
|
return sprintf(buf, "%d\n", enabled);
|
|
else
|
|
return sprintf(buf, "%d\n", !!enable_req);
|
|
}
|
|
|
|
static ssize_t enable_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int ret = 0;
|
|
unsigned long val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
ret = kstrtoul(buf, 0, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (val) {
|
|
ret = pm_runtime_resume_and_get(dev->parent);
|
|
if (ret)
|
|
return ret;
|
|
ret = cti_enable(drvdata->csdev);
|
|
if (ret)
|
|
pm_runtime_put(dev->parent);
|
|
} else {
|
|
ret = cti_disable(drvdata->csdev);
|
|
if (!ret)
|
|
pm_runtime_put(dev->parent);
|
|
}
|
|
|
|
if (ret)
|
|
return ret;
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_RW(enable);
|
|
|
|
static ssize_t powered_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
bool powered;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
powered = drvdata->config.hw_powered;
|
|
spin_unlock(&drvdata->spinlock);
|
|
|
|
return sprintf(buf, "%d\n", powered);
|
|
}
|
|
static DEVICE_ATTR_RO(powered);
|
|
|
|
static ssize_t ctmid_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
return sprintf(buf, "%d\n", drvdata->ctidev.ctm_id);
|
|
}
|
|
static DEVICE_ATTR_RO(ctmid);
|
|
|
|
static ssize_t nr_trigger_cons_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
return sprintf(buf, "%d\n", drvdata->ctidev.nr_trig_con);
|
|
}
|
|
static DEVICE_ATTR_RO(nr_trigger_cons);
|
|
|
|
/* attribute and group sysfs tables. */
|
|
static struct attribute *coresight_cti_attrs[] = {
|
|
&dev_attr_enable.attr,
|
|
&dev_attr_powered.attr,
|
|
&dev_attr_ctmid.attr,
|
|
&dev_attr_nr_trigger_cons.attr,
|
|
NULL,
|
|
};
|
|
|
|
/* register based attributes */
|
|
|
|
/* Read registers with power check only (no enable check). */
|
|
static ssize_t coresight_cti_reg_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cs_off_attribute *cti_attr = container_of(attr, struct cs_off_attribute, attr);
|
|
u32 val = 0;
|
|
|
|
pm_runtime_get_sync(dev->parent);
|
|
spin_lock(&drvdata->spinlock);
|
|
if (drvdata->config.hw_powered)
|
|
val = readl_relaxed(drvdata->base + cti_attr->off);
|
|
spin_unlock(&drvdata->spinlock);
|
|
pm_runtime_put_sync(dev->parent);
|
|
return sysfs_emit(buf, "0x%x\n", val);
|
|
}
|
|
|
|
/* Write registers with power check only (no enable check). */
|
|
static __maybe_unused ssize_t coresight_cti_reg_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cs_off_attribute *cti_attr = container_of(attr, struct cs_off_attribute, attr);
|
|
unsigned long val = 0;
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
|
|
pm_runtime_get_sync(dev->parent);
|
|
spin_lock(&drvdata->spinlock);
|
|
if (drvdata->config.hw_powered)
|
|
cti_write_single_reg(drvdata, cti_attr->off, val);
|
|
spin_unlock(&drvdata->spinlock);
|
|
pm_runtime_put_sync(dev->parent);
|
|
return size;
|
|
}
|
|
|
|
#define coresight_cti_reg(name, offset) \
|
|
(&((struct cs_off_attribute[]) { \
|
|
{ \
|
|
__ATTR(name, 0444, coresight_cti_reg_show, NULL), \
|
|
offset \
|
|
} \
|
|
})[0].attr.attr)
|
|
|
|
#define coresight_cti_reg_rw(name, offset) \
|
|
(&((struct cs_off_attribute[]) { \
|
|
{ \
|
|
__ATTR(name, 0644, coresight_cti_reg_show, \
|
|
coresight_cti_reg_store), \
|
|
offset \
|
|
} \
|
|
})[0].attr.attr)
|
|
|
|
#define coresight_cti_reg_wo(name, offset) \
|
|
(&((struct cs_off_attribute[]) { \
|
|
{ \
|
|
__ATTR(name, 0200, NULL, coresight_cti_reg_store), \
|
|
offset \
|
|
} \
|
|
})[0].attr.attr)
|
|
|
|
/* coresight management registers */
|
|
static struct attribute *coresight_cti_mgmt_attrs[] = {
|
|
coresight_cti_reg(devaff0, CTIDEVAFF0),
|
|
coresight_cti_reg(devaff1, CTIDEVAFF1),
|
|
coresight_cti_reg(authstatus, CORESIGHT_AUTHSTATUS),
|
|
coresight_cti_reg(devarch, CORESIGHT_DEVARCH),
|
|
coresight_cti_reg(devid, CORESIGHT_DEVID),
|
|
coresight_cti_reg(devtype, CORESIGHT_DEVTYPE),
|
|
coresight_cti_reg(pidr0, CORESIGHT_PERIPHIDR0),
|
|
coresight_cti_reg(pidr1, CORESIGHT_PERIPHIDR1),
|
|
coresight_cti_reg(pidr2, CORESIGHT_PERIPHIDR2),
|
|
coresight_cti_reg(pidr3, CORESIGHT_PERIPHIDR3),
|
|
coresight_cti_reg(pidr4, CORESIGHT_PERIPHIDR4),
|
|
NULL,
|
|
};
|
|
|
|
/* CTI low level programming registers */
|
|
|
|
/*
|
|
* Show a simple 32 bit value if enabled and powered.
|
|
* If inaccessible & pcached_val not NULL then show cached value.
|
|
*/
|
|
static ssize_t cti_reg32_show(struct device *dev, char *buf,
|
|
u32 *pcached_val, int reg_offset)
|
|
{
|
|
u32 val = 0;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *config = &drvdata->config;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
if ((reg_offset >= 0) && cti_active(config)) {
|
|
CS_UNLOCK(drvdata->base);
|
|
val = readl_relaxed(drvdata->base + reg_offset);
|
|
if (pcached_val)
|
|
*pcached_val = val;
|
|
CS_LOCK(drvdata->base);
|
|
} else if (pcached_val) {
|
|
val = *pcached_val;
|
|
}
|
|
spin_unlock(&drvdata->spinlock);
|
|
return sprintf(buf, "%#x\n", val);
|
|
}
|
|
|
|
/*
|
|
* Store a simple 32 bit value.
|
|
* If pcached_val not NULL, then copy to here too,
|
|
* if reg_offset >= 0 then write through if enabled.
|
|
*/
|
|
static ssize_t cti_reg32_store(struct device *dev, const char *buf,
|
|
size_t size, u32 *pcached_val, int reg_offset)
|
|
{
|
|
unsigned long val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *config = &drvdata->config;
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
/* local store */
|
|
if (pcached_val)
|
|
*pcached_val = (u32)val;
|
|
|
|
/* write through if offset and enabled */
|
|
if ((reg_offset >= 0) && cti_active(config))
|
|
cti_write_single_reg(drvdata, reg_offset, val);
|
|
spin_unlock(&drvdata->spinlock);
|
|
return size;
|
|
}
|
|
|
|
/* Standard macro for simple rw cti config registers */
|
|
#define cti_config_reg32_rw(name, cfgname, offset) \
|
|
static ssize_t name##_show(struct device *dev, \
|
|
struct device_attribute *attr, \
|
|
char *buf) \
|
|
{ \
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \
|
|
return cti_reg32_show(dev, buf, \
|
|
&drvdata->config.cfgname, offset); \
|
|
} \
|
|
\
|
|
static ssize_t name##_store(struct device *dev, \
|
|
struct device_attribute *attr, \
|
|
const char *buf, size_t size) \
|
|
{ \
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); \
|
|
return cti_reg32_store(dev, buf, size, \
|
|
&drvdata->config.cfgname, offset); \
|
|
} \
|
|
static DEVICE_ATTR_RW(name)
|
|
|
|
static ssize_t inout_sel_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
u32 val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
val = (u32)drvdata->config.ctiinout_sel;
|
|
return sprintf(buf, "%d\n", val);
|
|
}
|
|
|
|
static ssize_t inout_sel_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
unsigned long val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
if (val > (CTIINOUTEN_MAX - 1))
|
|
return -EINVAL;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
drvdata->config.ctiinout_sel = val;
|
|
spin_unlock(&drvdata->spinlock);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_RW(inout_sel);
|
|
|
|
static ssize_t inen_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
unsigned long val;
|
|
int index;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
index = drvdata->config.ctiinout_sel;
|
|
val = drvdata->config.ctiinen[index];
|
|
spin_unlock(&drvdata->spinlock);
|
|
return sprintf(buf, "%#lx\n", val);
|
|
}
|
|
|
|
static ssize_t inen_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
unsigned long val;
|
|
int index;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *config = &drvdata->config;
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
index = config->ctiinout_sel;
|
|
config->ctiinen[index] = val;
|
|
|
|
/* write through if enabled */
|
|
if (cti_active(config))
|
|
cti_write_single_reg(drvdata, CTIINEN(index), val);
|
|
spin_unlock(&drvdata->spinlock);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_RW(inen);
|
|
|
|
static ssize_t outen_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
unsigned long val;
|
|
int index;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
index = drvdata->config.ctiinout_sel;
|
|
val = drvdata->config.ctiouten[index];
|
|
spin_unlock(&drvdata->spinlock);
|
|
return sprintf(buf, "%#lx\n", val);
|
|
}
|
|
|
|
static ssize_t outen_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
unsigned long val;
|
|
int index;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *config = &drvdata->config;
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
index = config->ctiinout_sel;
|
|
config->ctiouten[index] = val;
|
|
|
|
/* write through if enabled */
|
|
if (cti_active(config))
|
|
cti_write_single_reg(drvdata, CTIOUTEN(index), val);
|
|
spin_unlock(&drvdata->spinlock);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_RW(outen);
|
|
|
|
static ssize_t intack_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
unsigned long val;
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
|
|
cti_write_intack(dev, val);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_WO(intack);
|
|
|
|
cti_config_reg32_rw(gate, ctigate, CTIGATE);
|
|
cti_config_reg32_rw(asicctl, asicctl, ASICCTL);
|
|
cti_config_reg32_rw(appset, ctiappset, CTIAPPSET);
|
|
|
|
static ssize_t appclear_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
unsigned long val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *config = &drvdata->config;
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
|
|
/* a 1'b1 in appclr clears down the same bit in appset*/
|
|
config->ctiappset &= ~val;
|
|
|
|
/* write through if enabled */
|
|
if (cti_active(config))
|
|
cti_write_single_reg(drvdata, CTIAPPCLEAR, val);
|
|
spin_unlock(&drvdata->spinlock);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_WO(appclear);
|
|
|
|
static ssize_t apppulse_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
unsigned long val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *config = &drvdata->config;
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
|
|
/* write through if enabled */
|
|
if (cti_active(config))
|
|
cti_write_single_reg(drvdata, CTIAPPPULSE, val);
|
|
spin_unlock(&drvdata->spinlock);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_WO(apppulse);
|
|
|
|
/*
|
|
* Define CONFIG_CORESIGHT_CTI_INTEGRATION_REGS to enable the access to the
|
|
* integration control registers. Normally only used to investigate connection
|
|
* data.
|
|
*/
|
|
static struct attribute *coresight_cti_regs_attrs[] = {
|
|
&dev_attr_inout_sel.attr,
|
|
&dev_attr_inen.attr,
|
|
&dev_attr_outen.attr,
|
|
&dev_attr_gate.attr,
|
|
&dev_attr_asicctl.attr,
|
|
&dev_attr_intack.attr,
|
|
&dev_attr_appset.attr,
|
|
&dev_attr_appclear.attr,
|
|
&dev_attr_apppulse.attr,
|
|
coresight_cti_reg(triginstatus, CTITRIGINSTATUS),
|
|
coresight_cti_reg(trigoutstatus, CTITRIGOUTSTATUS),
|
|
coresight_cti_reg(chinstatus, CTICHINSTATUS),
|
|
coresight_cti_reg(choutstatus, CTICHOUTSTATUS),
|
|
#ifdef CONFIG_CORESIGHT_CTI_INTEGRATION_REGS
|
|
coresight_cti_reg_rw(itctrl, CORESIGHT_ITCTRL),
|
|
coresight_cti_reg(ittrigin, ITTRIGIN),
|
|
coresight_cti_reg(itchin, ITCHIN),
|
|
coresight_cti_reg_rw(ittrigout, ITTRIGOUT),
|
|
coresight_cti_reg_rw(itchout, ITCHOUT),
|
|
coresight_cti_reg(itchoutack, ITCHOUTACK),
|
|
coresight_cti_reg(ittrigoutack, ITTRIGOUTACK),
|
|
coresight_cti_reg_wo(ittriginack, ITTRIGINACK),
|
|
coresight_cti_reg_wo(itchinack, ITCHINACK),
|
|
#endif
|
|
NULL,
|
|
};
|
|
|
|
/* CTI channel x-trigger programming */
|
|
static int
|
|
cti_trig_op_parse(struct device *dev, enum cti_chan_op op,
|
|
enum cti_trig_dir dir, const char *buf, size_t size)
|
|
{
|
|
u32 chan_idx;
|
|
u32 trig_idx;
|
|
int items, err = -EINVAL;
|
|
|
|
/* extract chan idx and trigger idx */
|
|
items = sscanf(buf, "%d %d", &chan_idx, &trig_idx);
|
|
if (items == 2) {
|
|
err = cti_channel_trig_op(dev, op, dir, chan_idx, trig_idx);
|
|
if (!err)
|
|
err = size;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static ssize_t trigin_attach_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
return cti_trig_op_parse(dev, CTI_CHAN_ATTACH, CTI_TRIG_IN,
|
|
buf, size);
|
|
}
|
|
static DEVICE_ATTR_WO(trigin_attach);
|
|
|
|
static ssize_t trigin_detach_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
return cti_trig_op_parse(dev, CTI_CHAN_DETACH, CTI_TRIG_IN,
|
|
buf, size);
|
|
}
|
|
static DEVICE_ATTR_WO(trigin_detach);
|
|
|
|
static ssize_t trigout_attach_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
return cti_trig_op_parse(dev, CTI_CHAN_ATTACH, CTI_TRIG_OUT,
|
|
buf, size);
|
|
}
|
|
static DEVICE_ATTR_WO(trigout_attach);
|
|
|
|
static ssize_t trigout_detach_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
return cti_trig_op_parse(dev, CTI_CHAN_DETACH, CTI_TRIG_OUT,
|
|
buf, size);
|
|
}
|
|
static DEVICE_ATTR_WO(trigout_detach);
|
|
|
|
|
|
static ssize_t chan_gate_enable_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int err = 0, channel = 0;
|
|
|
|
if (kstrtoint(buf, 0, &channel))
|
|
return -EINVAL;
|
|
|
|
err = cti_channel_gate_op(dev, CTI_GATE_CHAN_ENABLE, channel);
|
|
return err ? err : size;
|
|
}
|
|
|
|
static ssize_t chan_gate_enable_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *cfg = &drvdata->config;
|
|
unsigned long ctigate_bitmask = cfg->ctigate;
|
|
int size = 0;
|
|
|
|
if (cfg->ctigate == 0)
|
|
size = sprintf(buf, "\n");
|
|
else
|
|
size = bitmap_print_to_pagebuf(true, buf, &ctigate_bitmask,
|
|
cfg->nr_ctm_channels);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_RW(chan_gate_enable);
|
|
|
|
static ssize_t chan_gate_disable_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int err = 0, channel = 0;
|
|
|
|
if (kstrtoint(buf, 0, &channel))
|
|
return -EINVAL;
|
|
|
|
err = cti_channel_gate_op(dev, CTI_GATE_CHAN_DISABLE, channel);
|
|
return err ? err : size;
|
|
}
|
|
static DEVICE_ATTR_WO(chan_gate_disable);
|
|
|
|
static int
|
|
chan_op_parse(struct device *dev, enum cti_chan_set_op op, const char *buf)
|
|
{
|
|
int err = 0, channel = 0;
|
|
|
|
if (kstrtoint(buf, 0, &channel))
|
|
return -EINVAL;
|
|
|
|
err = cti_channel_setop(dev, op, channel);
|
|
return err;
|
|
|
|
}
|
|
|
|
static ssize_t chan_set_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int err = chan_op_parse(dev, CTI_CHAN_SET, buf);
|
|
|
|
return err ? err : size;
|
|
}
|
|
static DEVICE_ATTR_WO(chan_set);
|
|
|
|
static ssize_t chan_clear_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int err = chan_op_parse(dev, CTI_CHAN_CLR, buf);
|
|
|
|
return err ? err : size;
|
|
}
|
|
static DEVICE_ATTR_WO(chan_clear);
|
|
|
|
static ssize_t chan_pulse_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int err = chan_op_parse(dev, CTI_CHAN_PULSE, buf);
|
|
|
|
return err ? err : size;
|
|
}
|
|
static DEVICE_ATTR_WO(chan_pulse);
|
|
|
|
static ssize_t trig_filter_enable_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
u32 val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
val = drvdata->config.trig_filter_enable;
|
|
spin_unlock(&drvdata->spinlock);
|
|
return sprintf(buf, "%d\n", val);
|
|
}
|
|
|
|
static ssize_t trig_filter_enable_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
unsigned long val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
drvdata->config.trig_filter_enable = !!val;
|
|
spin_unlock(&drvdata->spinlock);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_RW(trig_filter_enable);
|
|
|
|
static ssize_t trigout_filtered_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *cfg = &drvdata->config;
|
|
int size = 0, nr_trig_max = cfg->nr_trig_max;
|
|
unsigned long mask = cfg->trig_out_filter;
|
|
|
|
if (mask)
|
|
size = bitmap_print_to_pagebuf(true, buf, &mask, nr_trig_max);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_RO(trigout_filtered);
|
|
|
|
/* clear all xtrigger / channel programming */
|
|
static ssize_t chan_xtrigs_reset_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int i;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *config = &drvdata->config;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
|
|
/* clear the CTI trigger / channel programming registers */
|
|
for (i = 0; i < config->nr_trig_max; i++) {
|
|
config->ctiinen[i] = 0;
|
|
config->ctiouten[i] = 0;
|
|
}
|
|
|
|
/* clear the other regs */
|
|
config->ctigate = GENMASK(config->nr_ctm_channels - 1, 0);
|
|
config->asicctl = 0;
|
|
config->ctiappset = 0;
|
|
config->ctiinout_sel = 0;
|
|
config->xtrig_rchan_sel = 0;
|
|
|
|
/* if enabled then write through */
|
|
if (cti_active(config))
|
|
cti_write_all_hw_regs(drvdata);
|
|
|
|
spin_unlock(&drvdata->spinlock);
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_WO(chan_xtrigs_reset);
|
|
|
|
/*
|
|
* Write to select a channel to view, read to display the
|
|
* cross triggers for the selected channel.
|
|
*/
|
|
static ssize_t chan_xtrigs_sel_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
unsigned long val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
if (kstrtoul(buf, 0, &val))
|
|
return -EINVAL;
|
|
if (val > (drvdata->config.nr_ctm_channels - 1))
|
|
return -EINVAL;
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
drvdata->config.xtrig_rchan_sel = val;
|
|
spin_unlock(&drvdata->spinlock);
|
|
return size;
|
|
}
|
|
|
|
static ssize_t chan_xtrigs_sel_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
unsigned long val;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
|
|
spin_lock(&drvdata->spinlock);
|
|
val = drvdata->config.xtrig_rchan_sel;
|
|
spin_unlock(&drvdata->spinlock);
|
|
|
|
return sprintf(buf, "%ld\n", val);
|
|
}
|
|
static DEVICE_ATTR_RW(chan_xtrigs_sel);
|
|
|
|
static ssize_t chan_xtrigs_in_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *cfg = &drvdata->config;
|
|
int used = 0, reg_idx;
|
|
int nr_trig_max = drvdata->config.nr_trig_max;
|
|
u32 chan_mask = BIT(cfg->xtrig_rchan_sel);
|
|
|
|
for (reg_idx = 0; reg_idx < nr_trig_max; reg_idx++) {
|
|
if (chan_mask & cfg->ctiinen[reg_idx])
|
|
used += sprintf(buf + used, "%d ", reg_idx);
|
|
}
|
|
|
|
used += sprintf(buf + used, "\n");
|
|
return used;
|
|
}
|
|
static DEVICE_ATTR_RO(chan_xtrigs_in);
|
|
|
|
static ssize_t chan_xtrigs_out_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *cfg = &drvdata->config;
|
|
int used = 0, reg_idx;
|
|
int nr_trig_max = drvdata->config.nr_trig_max;
|
|
u32 chan_mask = BIT(cfg->xtrig_rchan_sel);
|
|
|
|
for (reg_idx = 0; reg_idx < nr_trig_max; reg_idx++) {
|
|
if (chan_mask & cfg->ctiouten[reg_idx])
|
|
used += sprintf(buf + used, "%d ", reg_idx);
|
|
}
|
|
|
|
used += sprintf(buf + used, "\n");
|
|
return used;
|
|
}
|
|
static DEVICE_ATTR_RO(chan_xtrigs_out);
|
|
|
|
static ssize_t print_chan_list(struct device *dev,
|
|
char *buf, bool inuse)
|
|
{
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *config = &drvdata->config;
|
|
int size, i;
|
|
unsigned long inuse_bits = 0, chan_mask;
|
|
|
|
/* scan regs to get bitmap of channels in use. */
|
|
spin_lock(&drvdata->spinlock);
|
|
for (i = 0; i < config->nr_trig_max; i++) {
|
|
inuse_bits |= config->ctiinen[i];
|
|
inuse_bits |= config->ctiouten[i];
|
|
}
|
|
spin_unlock(&drvdata->spinlock);
|
|
|
|
/* inverse bits if printing free channels */
|
|
if (!inuse)
|
|
inuse_bits = ~inuse_bits;
|
|
|
|
/* list of channels, or 'none' */
|
|
chan_mask = GENMASK(config->nr_ctm_channels - 1, 0);
|
|
if (inuse_bits & chan_mask)
|
|
size = bitmap_print_to_pagebuf(true, buf, &inuse_bits,
|
|
config->nr_ctm_channels);
|
|
else
|
|
size = sprintf(buf, "\n");
|
|
return size;
|
|
}
|
|
|
|
static ssize_t chan_inuse_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return print_chan_list(dev, buf, true);
|
|
}
|
|
static DEVICE_ATTR_RO(chan_inuse);
|
|
|
|
static ssize_t chan_free_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return print_chan_list(dev, buf, false);
|
|
}
|
|
static DEVICE_ATTR_RO(chan_free);
|
|
|
|
static struct attribute *coresight_cti_channel_attrs[] = {
|
|
&dev_attr_trigin_attach.attr,
|
|
&dev_attr_trigin_detach.attr,
|
|
&dev_attr_trigout_attach.attr,
|
|
&dev_attr_trigout_detach.attr,
|
|
&dev_attr_trig_filter_enable.attr,
|
|
&dev_attr_trigout_filtered.attr,
|
|
&dev_attr_chan_gate_enable.attr,
|
|
&dev_attr_chan_gate_disable.attr,
|
|
&dev_attr_chan_set.attr,
|
|
&dev_attr_chan_clear.attr,
|
|
&dev_attr_chan_pulse.attr,
|
|
&dev_attr_chan_inuse.attr,
|
|
&dev_attr_chan_free.attr,
|
|
&dev_attr_chan_xtrigs_sel.attr,
|
|
&dev_attr_chan_xtrigs_in.attr,
|
|
&dev_attr_chan_xtrigs_out.attr,
|
|
&dev_attr_chan_xtrigs_reset.attr,
|
|
NULL,
|
|
};
|
|
|
|
/* Create the connections trigger groups and attrs dynamically */
|
|
/*
|
|
* Each connection has dynamic group triggers<N> + name, trigin/out sigs/types
|
|
* attributes, + each device has static nr_trigger_cons giving the number
|
|
* of groups. e.g. in sysfs:-
|
|
* /cti_<name>/triggers0
|
|
* /cti_<name>/triggers1
|
|
* /cti_<name>/nr_trigger_cons
|
|
* where nr_trigger_cons = 2
|
|
*/
|
|
static ssize_t con_name_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct dev_ext_attribute *ext_attr =
|
|
container_of(attr, struct dev_ext_attribute, attr);
|
|
struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var;
|
|
|
|
return sprintf(buf, "%s\n", con->con_dev_name);
|
|
}
|
|
|
|
static ssize_t trigin_sig_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct dev_ext_attribute *ext_attr =
|
|
container_of(attr, struct dev_ext_attribute, attr);
|
|
struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *cfg = &drvdata->config;
|
|
unsigned long mask = con->con_in->used_mask;
|
|
|
|
return bitmap_print_to_pagebuf(true, buf, &mask, cfg->nr_trig_max);
|
|
}
|
|
|
|
static ssize_t trigout_sig_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct dev_ext_attribute *ext_attr =
|
|
container_of(attr, struct dev_ext_attribute, attr);
|
|
struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var;
|
|
struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent);
|
|
struct cti_config *cfg = &drvdata->config;
|
|
unsigned long mask = con->con_out->used_mask;
|
|
|
|
return bitmap_print_to_pagebuf(true, buf, &mask, cfg->nr_trig_max);
|
|
}
|
|
|
|
/* convert a sig type id to a name */
|
|
static const char *
|
|
cti_sig_type_name(struct cti_trig_con *con, int used_count, bool in)
|
|
{
|
|
int idx = 0;
|
|
struct cti_trig_grp *grp = in ? con->con_in : con->con_out;
|
|
|
|
if (used_count < grp->nr_sigs)
|
|
idx = grp->sig_types[used_count];
|
|
return sig_type_names[idx];
|
|
}
|
|
|
|
static ssize_t trigin_type_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct dev_ext_attribute *ext_attr =
|
|
container_of(attr, struct dev_ext_attribute, attr);
|
|
struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var;
|
|
int sig_idx, used = 0;
|
|
const char *name;
|
|
|
|
for (sig_idx = 0; sig_idx < con->con_in->nr_sigs; sig_idx++) {
|
|
name = cti_sig_type_name(con, sig_idx, true);
|
|
used += sprintf(buf + used, "%s ", name);
|
|
}
|
|
used += sprintf(buf + used, "\n");
|
|
return used;
|
|
}
|
|
|
|
static ssize_t trigout_type_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct dev_ext_attribute *ext_attr =
|
|
container_of(attr, struct dev_ext_attribute, attr);
|
|
struct cti_trig_con *con = (struct cti_trig_con *)ext_attr->var;
|
|
int sig_idx, used = 0;
|
|
const char *name;
|
|
|
|
for (sig_idx = 0; sig_idx < con->con_out->nr_sigs; sig_idx++) {
|
|
name = cti_sig_type_name(con, sig_idx, false);
|
|
used += sprintf(buf + used, "%s ", name);
|
|
}
|
|
used += sprintf(buf + used, "\n");
|
|
return used;
|
|
}
|
|
|
|
/*
|
|
* Array of show function names declared above to allow selection
|
|
* for the connection attributes
|
|
*/
|
|
static p_show_fn show_fns[CTI_CON_ATTR_MAX] = {
|
|
con_name_show,
|
|
trigin_sig_show,
|
|
trigout_sig_show,
|
|
trigin_type_show,
|
|
trigout_type_show,
|
|
};
|
|
|
|
static int cti_create_con_sysfs_attr(struct device *dev,
|
|
struct cti_trig_con *con,
|
|
enum cti_conn_attr_type attr_type,
|
|
int attr_idx)
|
|
{
|
|
struct dev_ext_attribute *eattr;
|
|
char *name;
|
|
|
|
eattr = devm_kzalloc(dev, sizeof(struct dev_ext_attribute),
|
|
GFP_KERNEL);
|
|
if (eattr) {
|
|
name = devm_kstrdup(dev, con_attr_names[attr_type],
|
|
GFP_KERNEL);
|
|
if (name) {
|
|
/* fill out the underlying attribute struct */
|
|
eattr->attr.attr.name = name;
|
|
eattr->attr.attr.mode = 0444;
|
|
|
|
/* now the device_attribute struct */
|
|
eattr->attr.show = show_fns[attr_type];
|
|
} else {
|
|
return -ENOMEM;
|
|
}
|
|
} else {
|
|
return -ENOMEM;
|
|
}
|
|
eattr->var = con;
|
|
con->con_attrs[attr_idx] = &eattr->attr.attr;
|
|
/*
|
|
* Initialize the dynamically allocated attribute
|
|
* to avoid LOCKDEP splat. See include/linux/sysfs.h
|
|
* for more details.
|
|
*/
|
|
sysfs_attr_init(con->con_attrs[attr_idx]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct attribute_group *
|
|
cti_create_con_sysfs_group(struct device *dev, struct cti_device *ctidev,
|
|
int con_idx, struct cti_trig_con *tc)
|
|
{
|
|
struct attribute_group *group = NULL;
|
|
int grp_idx;
|
|
|
|
group = devm_kzalloc(dev, sizeof(struct attribute_group), GFP_KERNEL);
|
|
if (!group)
|
|
return NULL;
|
|
|
|
group->name = devm_kasprintf(dev, GFP_KERNEL, "triggers%d", con_idx);
|
|
if (!group->name)
|
|
return NULL;
|
|
|
|
grp_idx = con_idx + CORESIGHT_CTI_STATIC_GROUPS_MAX - 1;
|
|
ctidev->con_groups[grp_idx] = group;
|
|
tc->attr_group = group;
|
|
return group;
|
|
}
|
|
|
|
/* create a triggers connection group and the attributes for that group */
|
|
static int cti_create_con_attr_set(struct device *dev, int con_idx,
|
|
struct cti_device *ctidev,
|
|
struct cti_trig_con *tc)
|
|
{
|
|
struct attribute_group *attr_group = NULL;
|
|
int attr_idx = 0;
|
|
int err = -ENOMEM;
|
|
|
|
attr_group = cti_create_con_sysfs_group(dev, ctidev, con_idx, tc);
|
|
if (!attr_group)
|
|
return -ENOMEM;
|
|
|
|
/* allocate NULL terminated array of attributes */
|
|
tc->con_attrs = devm_kcalloc(dev, CTI_CON_ATTR_MAX + 1,
|
|
sizeof(struct attribute *), GFP_KERNEL);
|
|
if (!tc->con_attrs)
|
|
return -ENOMEM;
|
|
|
|
err = cti_create_con_sysfs_attr(dev, tc, CTI_CON_ATTR_NAME,
|
|
attr_idx++);
|
|
if (err)
|
|
return err;
|
|
|
|
if (tc->con_in->nr_sigs > 0) {
|
|
err = cti_create_con_sysfs_attr(dev, tc,
|
|
CTI_CON_ATTR_TRIGIN_SIG,
|
|
attr_idx++);
|
|
if (err)
|
|
return err;
|
|
|
|
err = cti_create_con_sysfs_attr(dev, tc,
|
|
CTI_CON_ATTR_TRIGIN_TYPES,
|
|
attr_idx++);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
if (tc->con_out->nr_sigs > 0) {
|
|
err = cti_create_con_sysfs_attr(dev, tc,
|
|
CTI_CON_ATTR_TRIGOUT_SIG,
|
|
attr_idx++);
|
|
if (err)
|
|
return err;
|
|
|
|
err = cti_create_con_sysfs_attr(dev, tc,
|
|
CTI_CON_ATTR_TRIGOUT_TYPES,
|
|
attr_idx++);
|
|
if (err)
|
|
return err;
|
|
}
|
|
attr_group->attrs = tc->con_attrs;
|
|
return 0;
|
|
}
|
|
|
|
/* create the array of group pointers for the CTI sysfs groups */
|
|
static int cti_create_cons_groups(struct device *dev, struct cti_device *ctidev)
|
|
{
|
|
int nr_groups;
|
|
|
|
/* nr groups = dynamic + static + NULL terminator */
|
|
nr_groups = ctidev->nr_trig_con + CORESIGHT_CTI_STATIC_GROUPS_MAX;
|
|
ctidev->con_groups = devm_kcalloc(dev, nr_groups,
|
|
sizeof(struct attribute_group *),
|
|
GFP_KERNEL);
|
|
if (!ctidev->con_groups)
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
int cti_create_cons_sysfs(struct device *dev, struct cti_drvdata *drvdata)
|
|
{
|
|
struct cti_device *ctidev = &drvdata->ctidev;
|
|
int err, con_idx = 0, i;
|
|
struct cti_trig_con *tc;
|
|
|
|
err = cti_create_cons_groups(dev, ctidev);
|
|
if (err)
|
|
return err;
|
|
|
|
/* populate first locations with the static set of groups */
|
|
for (i = 0; i < (CORESIGHT_CTI_STATIC_GROUPS_MAX - 1); i++)
|
|
ctidev->con_groups[i] = coresight_cti_groups[i];
|
|
|
|
/* add dynamic set for each connection */
|
|
list_for_each_entry(tc, &ctidev->trig_cons, node) {
|
|
err = cti_create_con_attr_set(dev, con_idx++, ctidev, tc);
|
|
if (err)
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/* attribute and group sysfs tables. */
|
|
static const struct attribute_group coresight_cti_group = {
|
|
.attrs = coresight_cti_attrs,
|
|
};
|
|
|
|
static const struct attribute_group coresight_cti_mgmt_group = {
|
|
.attrs = coresight_cti_mgmt_attrs,
|
|
.name = "mgmt",
|
|
};
|
|
|
|
static const struct attribute_group coresight_cti_regs_group = {
|
|
.attrs = coresight_cti_regs_attrs,
|
|
.name = "regs",
|
|
};
|
|
|
|
static const struct attribute_group coresight_cti_channels_group = {
|
|
.attrs = coresight_cti_channel_attrs,
|
|
.name = "channels",
|
|
};
|
|
|
|
const struct attribute_group *
|
|
coresight_cti_groups[CORESIGHT_CTI_STATIC_GROUPS_MAX] = {
|
|
&coresight_cti_group,
|
|
&coresight_cti_mgmt_group,
|
|
&coresight_cti_regs_group,
|
|
&coresight_cti_channels_group,
|
|
NULL,
|
|
};
|