diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 9699778ed5b5..47638650c2c4 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -221,6 +221,14 @@ config CORESIGHT_TGU provide a trigger point for centering data around a specific event within the trace data buffer. +config CORESIGHT_DBGUI + bool "CoreSight DebugUI driver" + help + This driver provides support for DebugUI that helps to capture + the value at a specified address. It allows configuring DebugUI + to select specified address and trigger mode based on user + input. + config CORESIGHT_OST bool "CoreSight OST framework" depends on CORESIGHT_STM && CORESIGHT_QGKI diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 01ad6fe5315c..8ad9bd815f9e 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -26,5 +26,6 @@ obj-$(CONFIG_CORESIGHT_DUMMY) += coresight-dummy.o obj-$(CONFIG_CORESIGHT_FUSE) += coresight-fuse.o obj-$(CONFIG_CORESIGHT_REMOTE_ETM) += coresight-remote-etm.o obj-$(CONFIG_CORESIGHT_CSR) += coresight-csr.o +obj-$(CONFIG_CORESIGHT_DBGUI) += coresight-dbgui.o obj-$(CONFIG_CORESIGHT_TGU) += coresight-tgu.o apss_tgu.o obj-$(CONFIG_CORESIGHT_OST) += coresight-ost.o diff --git a/drivers/hwtracing/coresight/coresight-dbgui.c b/drivers/hwtracing/coresight/coresight-dbgui.c new file mode 100644 index 000000000000..ea18ca1160ff --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-dbgui.c @@ -0,0 +1,948 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2018, 2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-priv.h" + +#define dbgui_writel(drvdata, val, off) \ + __raw_writel((val), drvdata->base + off) +#define dbgui_readl(drvdata, off) __raw_readl(drvdata->base + off) + +#define DBGUI_LOCK(drvdata) \ +do { \ + mb(); /* ensure configuration take effect before we lock it */ \ + dbgui_writel(drvdata, 0x0, CORESIGHT_LAR); \ +} while (0) + +#define DBGUI_UNLOCK(drvdata) \ +do { \ + dbgui_writel(drvdata, CORESIGHT_UNLOCK, CORESIGHT_LAR); \ + mb(); /* ensure unlock take effect before we configure */ \ +} while (0) + +/* DBGUI registers */ +#define DBGUI_SECURE (0x000) +#define DBGUI_CTL (0x004) +#define DBGUI_CTL_MASK (0x008) +#define DBGUI_SWTRIG (0x00C) +#define DBGUI_STATUS (0x010) +#define DBGUI_HWE_MASK (0x014) +#define DBGUI_CTR_VAL (0x018) +#define DBGUI_CTR_EN (0x01C) +#define DBGUI_NUM_REGS_RD (0x020) +#define DBGUI_ATB_REG (0x024) + +#define DBGUI_ADDRn(drvdata, n) (drvdata->addr_offset + 4*n) +#define DBGUI_DATAn(drvdata, n) (drvdata->data_offset + 4*n) + +#define DBGUI_TRIG_MASK 0xF0001 +#define DBGUI_MAX_ADDR_VAL 64 +#define DBGUI_TS_VALID BIT(15) +#define DBGUI_ATB_TRACE_EN BIT(0) +#define DBGUI_TIMER_CTR_OVERRIDE BIT(1) +#define DBGUI_TIMER_CTR_EN BIT(0) + +/* ATID for DBGUI */ +#define APB_ATID 50 +#define AHB_ATID 52 + +enum dbgui_trig_type { + DBGUI_TRIG_SW = BIT(0), + DBGUI_TRIG_TIMER = BIT(16), + DBGUI_TRIG_HWE = BIT(17), + DBGUI_TRIG_WDOG = BIT(18), + DBGUI_TRIG_CTI = BIT(19), +}; + +struct dbgui_drvdata { + void __iomem *base; + bool enable; + uint32_t addr_offset; + uint32_t data_offset; + uint32_t size; + struct device *dev; + struct coresight_device *csdev; + struct clk *clk; + struct mutex mutex; + uint32_t trig_mask; + bool capture_enable; + bool ts_enable; + bool timer_override_enable; + bool handoff_enable; + uint32_t nr_apb_regs; + uint32_t nr_ahb_regs; + uint32_t hwe_mask; + uint32_t addr_idx; + uint32_t timeout_val; + uint32_t addr_val[DBGUI_MAX_ADDR_VAL]; + uint32_t data_val[DBGUI_MAX_ADDR_VAL]; + struct msm_dump_data reg_data; +}; + +static struct dbgui_drvdata *dbgui_drvdata; + +static void dbgui_enable_atb_trace(struct dbgui_drvdata *drvdata) +{ + uint32_t reg; + + reg = dbgui_readl(drvdata, DBGUI_ATB_REG); + reg |= DBGUI_ATB_TRACE_EN | APB_ATID << 8 | AHB_ATID << 1; + dbgui_writel(drvdata, reg, DBGUI_ATB_REG); +} + +static void dbgui_disable_atb_trace(struct dbgui_drvdata *drvdata) +{ + uint32_t reg; + + reg = dbgui_readl(drvdata, DBGUI_ATB_REG); + reg &= ~DBGUI_ATB_TRACE_EN; + dbgui_writel(drvdata, reg, DBGUI_ATB_REG); +} + +static void dbgui_enable_timestamp(struct dbgui_drvdata *drvdata) +{ + uint32_t reg; + + reg = dbgui_readl(drvdata, DBGUI_ATB_REG); + reg |= DBGUI_TS_VALID; + dbgui_writel(drvdata, reg, DBGUI_ATB_REG); +} + +static void dbgui_disable_timestamp(struct dbgui_drvdata *drvdata) +{ + uint32_t reg; + + reg = dbgui_readl(drvdata, DBGUI_ATB_REG); + reg &= ~DBGUI_TS_VALID; + dbgui_writel(drvdata, reg, DBGUI_ATB_REG); +} + +static void dbgui_wait_for_pending_actions(struct dbgui_drvdata *drvdata) +{ + int count; + uint32_t reg_val; + + for (count = TIMEOUT_US; reg_val = + dbgui_readl(drvdata, DBGUI_STATUS), + BMVAL(reg_val, 4, 7) != 0 + && BVAL(reg_val, 0) != 0 && count > 0; count--) + udelay(1); + + WARN(count == 0, + "timeout while waiting for pending action: STATUS %#x\n", + dbgui_readl(drvdata, DBGUI_STATUS)); +} + +static void __dbgui_capture_enable(struct dbgui_drvdata *drvdata) +{ + int i; + uint32_t reg_val; + + DBGUI_UNLOCK(drvdata); + + dbgui_wait_for_pending_actions(drvdata); + dbgui_writel(drvdata, 0x1, DBGUI_SECURE); + dbgui_writel(drvdata, 0x1, DBGUI_CTL); + + reg_val = dbgui_readl(drvdata, DBGUI_NUM_REGS_RD); + reg_val &= ~0xFF; + reg_val |= (drvdata->nr_apb_regs | drvdata->nr_ahb_regs << 8); + dbgui_writel(drvdata, reg_val, DBGUI_NUM_REGS_RD); + + for (i = 0; i < drvdata->size; i++) { + if (drvdata->addr_val[i]) + dbgui_writel(drvdata, drvdata->addr_val[i], + DBGUI_ADDRn(drvdata, i)); + } + + if (!(drvdata->trig_mask & DBGUI_TRIG_TIMER) && drvdata->timeout_val) { + dbgui_writel(drvdata, drvdata->timeout_val, DBGUI_CTR_VAL); + + reg_val = dbgui_readl(drvdata, DBGUI_CTR_EN); + if (drvdata->timer_override_enable) + reg_val |= DBGUI_TIMER_CTR_OVERRIDE; + + reg_val |= DBGUI_TIMER_CTR_EN; + dbgui_writel(drvdata, reg_val, DBGUI_CTR_EN); + } + + if (!(drvdata->trig_mask & DBGUI_TRIG_HWE)) + dbgui_writel(drvdata, drvdata->hwe_mask, DBGUI_HWE_MASK); + + dbgui_writel(drvdata, drvdata->trig_mask, DBGUI_CTL_MASK); + + DBGUI_LOCK(drvdata); +}; + +static int dbgui_capture_enable(struct dbgui_drvdata *drvdata) +{ + int ret = 0; + + mutex_lock(&drvdata->mutex); + if (drvdata->capture_enable) + goto out; + + if (drvdata->trig_mask == DBGUI_TRIG_MASK) { + ret = -EINVAL; + goto out; + } + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + goto out; + + if (!drvdata->handoff_enable) + __dbgui_capture_enable(drvdata); + drvdata->capture_enable = true; + mutex_unlock(&drvdata->mutex); + + dev_info(drvdata->dev, "DebugUI capture enabled\n"); + return 0; +out: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static void __dbgui_capture_disable(struct dbgui_drvdata *drvdata) +{ + DBGUI_UNLOCK(drvdata); + + dbgui_wait_for_pending_actions(drvdata); + + /* mask all the triggers */ + dbgui_writel(drvdata, DBGUI_TRIG_MASK, DBGUI_CTL_MASK); + + DBGUI_LOCK(drvdata); +} + +static int dbgui_capture_disable(struct dbgui_drvdata *drvdata) +{ + int ret = 0; + + mutex_lock(&drvdata->mutex); + if (!drvdata->capture_enable) + goto out; + + /* don't allow capture disable while its enabled as a trace source */ + if (drvdata->enable) { + ret = -EPERM; + goto out; + } + + __dbgui_capture_disable(drvdata); + clk_disable_unprepare(drvdata->clk); + drvdata->capture_enable = false; + mutex_unlock(&drvdata->mutex); + + dev_info(drvdata->dev, "DebugUI capture disabled\n"); + return 0; +out: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static int __dbgui_enable(struct dbgui_drvdata *drvdata) +{ + DBGUI_UNLOCK(drvdata); + + dbgui_enable_atb_trace(drvdata); + if (drvdata->ts_enable) + dbgui_enable_timestamp(drvdata); + + DBGUI_LOCK(drvdata); + return 0; +} + +static int dbgui_enable(struct coresight_device *csdev, + struct perf_event *event, u32 mode) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + mutex_lock(&drvdata->mutex); + + if (!drvdata->capture_enable) { + mutex_unlock(&drvdata->mutex); + return -EPERM; + } + + __dbgui_enable(drvdata); + drvdata->enable = true; + mutex_unlock(&drvdata->mutex); + + dev_info(drvdata->dev, "DebugUI tracing enabled\n"); + return 0; +} + +static void __dbgui_disable(struct dbgui_drvdata *drvdata) +{ + DBGUI_UNLOCK(drvdata); + + dbgui_disable_atb_trace(drvdata); + if (drvdata->ts_enable) + dbgui_disable_timestamp(drvdata); + + DBGUI_LOCK(drvdata); +} + +static void dbgui_disable(struct coresight_device *csdev, + struct perf_event *event) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + mutex_lock(&drvdata->mutex); + __dbgui_disable(drvdata); + drvdata->enable = false; + mutex_unlock(&drvdata->mutex); + + dev_info(drvdata->dev, "DebugUI tracing disabled\n"); +} + +static int dbgui_trace_id(struct coresight_device *csdev) +{ + return 0; +} + +static const struct coresight_ops_source dbgui_source_ops = { + .trace_id = dbgui_trace_id, + .enable = dbgui_enable, + .disable = dbgui_disable, +}; + +/* DebugUI may already be configured for capture, so retrieve current state */ +static void dbgui_handoff(struct dbgui_drvdata *drvdata) +{ + uint32_t val; + int i; + + drvdata->handoff_enable = true; + + drvdata->trig_mask = dbgui_readl(drvdata, DBGUI_CTL_MASK); + drvdata->hwe_mask = dbgui_readl(drvdata, DBGUI_HWE_MASK); + drvdata->timeout_val = dbgui_readl(drvdata, DBGUI_CTR_VAL); + + val = dbgui_readl(drvdata, DBGUI_NUM_REGS_RD); + drvdata->nr_ahb_regs = (val >> 8) & 0xF; + drvdata->nr_apb_regs = val & 0xF; + + val = dbgui_readl(drvdata, DBGUI_ATB_REG); + if (val & DBGUI_TS_VALID) + drvdata->ts_enable = true; + + val = dbgui_readl(drvdata, DBGUI_CTR_EN); + if (val & DBGUI_TIMER_CTR_OVERRIDE) + drvdata->timer_override_enable = true; + + for (i = 0; i < drvdata->size; i++) + drvdata->addr_val[i] = dbgui_readl(drvdata, + DBGUI_ADDRn(drvdata, i)); + + if (drvdata->trig_mask != DBGUI_TRIG_MASK) + dbgui_capture_enable(drvdata); + + drvdata->handoff_enable = false; +} + +static const struct coresight_ops dbgui_cs_ops = { + .source_ops = &dbgui_source_ops, +}; + +static ssize_t trig_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->trig_mask = val & DBGUI_TRIG_MASK; + mutex_unlock(&drvdata->mutex); + + return size; +} + +static ssize_t trig_mask_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", drvdata->trig_mask); +}; +static DEVICE_ATTR_RW(trig_mask); + +static ssize_t timer_override_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->timer_override_enable = val ? true : false; + mutex_unlock(&drvdata->mutex); + + return size; +} + +static ssize_t timer_override_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", + drvdata->timer_override_enable); +}; +static DEVICE_ATTR_RW(timer_override_enable); + +static ssize_t ts_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->ts_enable = val ? true : false; + mutex_unlock(&drvdata->mutex); + + return size; +} + +static ssize_t ts_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", drvdata->ts_enable); +}; +static DEVICE_ATTR_RW(ts_enable); + +static ssize_t hwe_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->hwe_mask = val; + mutex_unlock(&drvdata->mutex); + + return size; +} + +static ssize_t hwe_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", drvdata->hwe_mask); +}; +static DEVICE_ATTR_RW(hwe_mask); + +static ssize_t sw_trig_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + if (!val) + return 0; + + mutex_lock(&drvdata->mutex); + if (!drvdata->capture_enable) { + mutex_unlock(&drvdata->mutex); + return -EINVAL; + } + + dbgui_wait_for_pending_actions(drvdata); + DBGUI_UNLOCK(drvdata); + + /* clear status register and free the sequencer */ + dbgui_writel(drvdata, 0x1, DBGUI_CTL); + + /* fire a software trigger */ + dbgui_writel(drvdata, 0x1, DBGUI_SWTRIG); + + DBGUI_LOCK(drvdata); + mutex_unlock(&drvdata->mutex); + + return size; +} +static DEVICE_ATTR_WO(sw_trig); + +static ssize_t nr_ahb_regs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + if (val > drvdata->size) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->nr_ahb_regs = val; + + /* + * Please make sure nr_ahb_regs + nr_apb_regs isn't greater than + * drvdata->size. If sum is greater than size, The last setting + * of nr_ahb_regs or nr_apb_regs takes high priority. + */ + if (drvdata->nr_apb_regs + drvdata->nr_ahb_regs > drvdata->size) + drvdata->nr_apb_regs = drvdata->size - + drvdata->nr_ahb_regs; + + mutex_unlock(&drvdata->mutex); + + return size; +} + +static ssize_t nr_ahb_regs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", drvdata->nr_ahb_regs); +}; +static DEVICE_ATTR_RW(nr_ahb_regs); + +static ssize_t nr_apb_regs_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + if (val > drvdata->size) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->nr_apb_regs = val; + + if (drvdata->nr_apb_regs + drvdata->nr_ahb_regs > drvdata->size) + drvdata->nr_ahb_regs = drvdata->size - + drvdata->nr_apb_regs; + + mutex_unlock(&drvdata->mutex); + + return size; +} + +static ssize_t nr_apb_regs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", drvdata->nr_apb_regs); +}; +static DEVICE_ATTR_RW(nr_apb_regs); + +static ssize_t size_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", drvdata->size); +}; +static DEVICE_ATTR_RO(size); + +static ssize_t timeout_val_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->timeout_val = val; + mutex_unlock(&drvdata->mutex); + + return size; +} + +static ssize_t timeout_val_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", drvdata->timeout_val); +}; +static DEVICE_ATTR_RW(timeout_val); + +static ssize_t addr_idx_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + if (val >= drvdata->size) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->addr_idx = val; + mutex_unlock(&drvdata->mutex); + + return size; +} + +static ssize_t addr_idx_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", drvdata->addr_idx); +}; +static DEVICE_ATTR_RW(addr_idx); + +static ssize_t addr_val_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + drvdata->addr_val[drvdata->addr_idx] = val; + mutex_unlock(&drvdata->mutex); + + return size; +} + +static ssize_t addr_val_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + ssize_t len = 0; + int i; + + mutex_lock(&drvdata->mutex); + for (i = 0; i < drvdata->size; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "[%02d]:0x%08x%s\n", + i, drvdata->addr_val[i], + drvdata->addr_idx == i ? + " *" : ""); + mutex_unlock(&drvdata->mutex); + + return len; +}; +static DEVICE_ATTR_RW(addr_val); + +static ssize_t data_val_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + ssize_t len = 0; + int i; + uint32_t val, trig_mask; + + if (!drvdata->capture_enable) + return 0; + + dbgui_wait_for_pending_actions(drvdata); + + mutex_lock(&drvdata->mutex); + + DBGUI_UNLOCK(drvdata); + + /* + * If the timer trigger is enabled, data might change while we read it. + * We mask all the trggers here to avoid this. + */ + trig_mask = dbgui_readl(drvdata, DBGUI_CTL_MASK); + dbgui_writel(drvdata, DBGUI_TRIG_MASK, DBGUI_CTL_MASK); + + for (i = 0; i < drvdata->size; i++) { + val = dbgui_readl(drvdata, DBGUI_DATAn(drvdata, i)); + drvdata->data_val[i] = val; + len += scnprintf(buf + len, PAGE_SIZE - len, + "[%02d]:0x%08x\n", + i, drvdata->data_val[i]); + } + dbgui_writel(drvdata, trig_mask, DBGUI_CTL_MASK); + + DBGUI_LOCK(drvdata); + + mutex_unlock(&drvdata->mutex); + + return len; +}; +static DEVICE_ATTR_RO(data_val); + +static ssize_t capture_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + uint32_t ret; + unsigned long val; + + if (kstrtoul(buf, 16, &val)) + return -EINVAL; + + if (val) + ret = dbgui_capture_enable(drvdata); + else + ret = dbgui_capture_disable(drvdata); + + if (ret) + return ret; + return size; +} + +static ssize_t capture_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dbgui_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", drvdata->capture_enable); +}; +static DEVICE_ATTR_RW(capture_enable); + +static struct attribute *dbgui_attrs[] = { + &dev_attr_sw_trig.attr, + &dev_attr_trig_mask.attr, + &dev_attr_capture_enable.attr, + &dev_attr_ts_enable.attr, + &dev_attr_hwe_mask.attr, + &dev_attr_timer_override_enable.attr, + &dev_attr_size.attr, + &dev_attr_nr_ahb_regs.attr, + &dev_attr_nr_apb_regs.attr, + &dev_attr_timeout_val.attr, + &dev_attr_addr_idx.attr, + &dev_attr_addr_val.attr, + &dev_attr_data_val.attr, + NULL, +}; + +static struct attribute_group dbgui_attr_grp = { + .attrs = dbgui_attrs, +}; + +static const struct attribute_group *dbgui_attr_grps[] = { + &dbgui_attr_grp, + NULL, +}; + +static bool coresight_authstatus_enabled(void __iomem *addr) +{ + int ret; + unsigned int auth_val; + + if (!addr) + return false; + + auth_val = readl_relaxed(addr + CORESIGHT_AUTHSTATUS); + + if ((BMVAL(auth_val, 0, 1) == 0x2) || + (BMVAL(auth_val, 2, 3) == 0x2) || + (BMVAL(auth_val, 4, 5) == 0x2) || + (BMVAL(auth_val, 6, 7) == 0x2)) + ret = false; + else + ret = true; + + return ret; +} + +static int dbgui_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct coresight_platform_data *pdata; + struct dbgui_drvdata *drvdata; + struct resource *res; + struct coresight_desc desc = { 0 }; + + pdata = coresight_get_platform_data(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + pdev->dev.platform_data = pdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->dev = &pdev->dev; + platform_set_drvdata(pdev, drvdata); + mutex_init(&drvdata->mutex); + + drvdata->clk = devm_clk_get(dev, "apb_pclk"); + if (IS_ERR(drvdata->clk)) + return PTR_ERR(drvdata->clk); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbgui-base"); + if (!res) { + dev_info(dev, "DBGUI base not specified\n"); + return -ENODEV; + } + + drvdata->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!drvdata->base) + return -ENOMEM; + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + if (!coresight_authstatus_enabled(drvdata->base)) + goto err; + + clk_disable_unprepare(drvdata->clk); + + ret = of_property_read_u32(pdev->dev.of_node, + "qcom,dbgui-addr-offset", + &drvdata->addr_offset); + if (ret) + return -EINVAL; + + ret = of_property_read_u32(pdev->dev.of_node, + "qcom,dbgui-data-offset", + &drvdata->data_offset); + if (ret) + return -EINVAL; + + if (drvdata->addr_offset >= resource_size(res) + || drvdata->data_offset >= resource_size(res)) { + dev_err(dev, "Invalid address or data offset\n"); + return -EINVAL; + } + + ret = of_property_read_u32(pdev->dev.of_node, + "qcom,dbgui-size", + &drvdata->size); + if (ret || drvdata->size > DBGUI_MAX_ADDR_VAL) + return -EINVAL; + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + dbgui_handoff(drvdata); + clk_disable_unprepare(drvdata->clk); + /* + * To provide addr_offset, data_offset and size via a global variable. + * NOTE: Only single dbgui device is supported now. + */ + if (of_property_read_string(dev->of_node, "coresight-name", &desc.name)) + desc.name = "coresight-dbgui"; + + dbgui_drvdata = drvdata; + desc.type = CORESIGHT_DEV_TYPE_SOURCE; + desc.subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_PROC; + desc.ops = &dbgui_cs_ops; + desc.pdata = pdev->dev.platform_data; + desc.dev = &pdev->dev; + desc.groups = dbgui_attr_grps; + drvdata->csdev = coresight_register(&desc); + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); + + dev_info(dev, "DebugUI initializaed\n"); + return 0; +err: + clk_disable_unprepare(drvdata->clk); + return -EPERM; +} + +static int dbgui_remove(struct platform_device *pdev) +{ + struct dbgui_drvdata *drvdata = platform_get_drvdata(pdev); + + coresight_unregister(drvdata->csdev); + return 0; +} + +static const struct of_device_id dbgui_match[] = { + {.compatible = "qcom,coresight-dbgui"}, + {} +}; + +static struct platform_driver dbgui_driver = { + .probe = dbgui_probe, + .remove = dbgui_remove, + .driver = { + .name = "coresight-dbgui", + .of_match_table = dbgui_match, + }, +}; + +static int __init dbgui_init(void) +{ + return platform_driver_register(&dbgui_driver); +} +module_init(dbgui_init); + +static void __exit dbgui_exit(void) +{ + return platform_driver_unregister(&dbgui_driver); +} +module_exit(dbgui_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CoreSight DebugUI driver");