[PATCH v8 6/9] drivers: perf: hisi: Add support for Hisilicon Djtag driver
From: Shaokun Zhang
Date: Mon May 22 2017 - 08:17:49 EST
From: Tan Xiaojun <tanxiaojun@xxxxxxxxxx>
The Hisilicon Djtag is an independent component which connects
with some other components in the SoC by Debug Bus. This driver
can be configured to access the registers of connecting components
(like L3 cache) during real time debugging and it supports DT and
ACPI mode.
Signed-off-by: Tan Xiaojun <tanxiaojun@xxxxxxxxxx>
Signed-off-by: John Garry <john.garry@xxxxxxxxxx>
Signed-off-by: Anurup M <anurup.m@xxxxxxxxxx>
---
drivers/perf/Makefile | 1 +
drivers/perf/hisilicon/Makefile | 1 +
drivers/perf/hisilicon/djtag.c | 882 ++++++++++++++++++++++++++++++++++++++++
drivers/perf/hisilicon/djtag.h | 46 +++
4 files changed, 930 insertions(+)
create mode 100644 drivers/perf/hisilicon/Makefile
create mode 100644 drivers/perf/hisilicon/djtag.c
create mode 100644 drivers/perf/hisilicon/djtag.h
diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
index 6420bd4..41d3342 100644
--- a/drivers/perf/Makefile
+++ b/drivers/perf/Makefile
@@ -1,5 +1,6 @@
obj-$(CONFIG_ARM_PMU) += arm_pmu.o arm_pmu_platform.o
obj-$(CONFIG_ARM_PMU_ACPI) += arm_pmu_acpi.o
+obj-$(CONFIG_HISI_PMU) += hisilicon/
obj-$(CONFIG_QCOM_L2_PMU) += qcom_l2_pmu.o
obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o
obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o
diff --git a/drivers/perf/hisilicon/Makefile b/drivers/perf/hisilicon/Makefile
new file mode 100644
index 0000000..be8f093
--- /dev/null
+++ b/drivers/perf/hisilicon/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_HISI_PMU) += djtag.o
diff --git a/drivers/perf/hisilicon/djtag.c b/drivers/perf/hisilicon/djtag.c
new file mode 100644
index 0000000..3b655fe
--- /dev/null
+++ b/drivers/perf/hisilicon/djtag.c
@@ -0,0 +1,882 @@
+/*
+ * Driver for Hisilicon Djtag r/w which use CPU sysctrl.
+ *
+ * Copyright (C) 2017 Hisilicon Limited
+ * Author: Tan Xiaojun <tanxiaojun@xxxxxxxxxx>
+ * Anurup M <anurup.m@xxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/idr.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/time64.h>
+
+#include "djtag.h"
+
+/*
+ * Timeout of 100ms is used to retry on djtag access busy condition.
+ * If djtag unavailable event after timeout, it means some serious
+ * hardware condition where we would BUG_ON, as subsequent access
+ * are also likely to FAIL.
+ */
+#define SC_DJTAG_TIMEOUT_US (100 * USEC_PER_MSEC)
+
+#define DJTAG_PREFIX "hisi-djtag-dev-"
+
+/* for djtag v1 */
+#define SC_DJTAG_MSTR_EN 0x6800
+#define DJTAG_NOR_CFG BIT(1) /* normal RW mode */
+#define DJTAG_MSTR_EN BIT(0)
+#define SC_DJTAG_MSTR_START_EN 0x6804
+#define DJTAG_MSTR_START_EN 0x1
+#define SC_DJTAG_DEBUG_MODULE_SEL 0x680c
+#define SC_DJTAG_MSTR_WR 0x6810
+#define DJTAG_MSTR_W 0x1
+#define DJTAG_MSTR_R 0x0
+#define SC_DJTAG_CHAIN_UNIT_CFG_EN 0x6814
+#define CHAIN_UNIT_CFG_EN 0xFFFF
+#define SC_DJTAG_MSTR_ADDR 0x6818
+#define SC_DJTAG_MSTR_DATA 0x681c
+#define SC_DJTAG_RD_DATA_BASE 0xe800
+
+#define SC_DJTAG_V1_UNLOCK 0x3B
+#define SC_DJTAG_V1_KERNEL_LOCK 0x3C
+
+/* for djtag v2 */
+#define SC_DJTAG_SEC_ACC_EN_EX 0xd800
+#define DJTAG_SEC_ACC_EN_EX 0x1
+#define SC_DJTAG_MSTR_CFG_EX 0xd818
+#define DJTAG_MSTR_RW_SHIFT_EX 29
+#define DJTAG_MSTR_RD_EX (0x0 << DJTAG_MSTR_RW_SHIFT_EX)
+#define DJTAG_MSTR_WR_EX (0x1 << DJTAG_MSTR_RW_SHIFT_EX)
+#define DEBUG_MODULE_SEL_SHIFT_EX 16
+#define CHAIN_UNIT_CFG_EN_EX 0xFFFF
+#define SC_DJTAG_MSTR_ADDR_EX 0xd810
+#define SC_DJTAG_MSTR_DATA_EX 0xd814
+#define SC_DJTAG_MSTR_START_EN_EX 0xd81c
+#define DJTAG_MSTR_START_EN_EX 0x1
+#define SC_DJTAG_RD_DATA_BASE_EX 0xe800
+#define SC_DJTAG_OP_ST_EX 0xe828
+#define DJTAG_OP_DONE_EX BIT(8)
+
+#define SC_DJTAG_V2_UNLOCK 0xAA
+#define SC_DJTAG_V2_KERNEL_LOCK 0xAB
+#define SC_DJTAG_V2_MODULE_SEL_MASK 0xFF00FFFF
+
+#define DJTAG_SYSCTRL_ADDR_SIZE 0x10000
+
+static DEFINE_IDR(djtag_hosts_idr);
+static DEFINE_IDR(djtag_clients_idr);
+
+struct hisi_djtag_ops {
+ void (*djtag_read)(void __iomem *regs_base, u32 offset,
+ u32 mod_sel, u32 mod_mask, int chain_id, u32 *rval);
+ void (*djtag_write)(void __iomem *regs_base, u32 offset,
+ u32 mod_sel, u32 mod_mask, u32 wval, int chain_id);
+};
+
+struct hisi_djtag_host {
+ spinlock_t lock;
+ int id;
+ u32 scl_id;
+ struct device dev;
+ struct list_head client_list;
+ void __iomem *sysctl_reg_map;
+ struct device_node *of_node;
+ acpi_handle acpi_handle;
+ const struct hisi_djtag_ops *djtag_ops;
+};
+
+#define to_hisi_djtag_client(d) container_of(d, struct hisi_djtag_client, dev)
+#define to_hisi_djtag_driver(d) container_of(d, struct hisi_djtag_driver, \
+ driver)
+#define MODULE_PREFIX "hisi_djtag:"
+
+/*
+ * hisi_djtag_lock_v1: djtag lock to avoid djtag access conflict
+ * @reg_base: djtag register base address
+ *
+ */
+static void hisi_djtag_lock_v1(void __iomem *regs_base)
+{
+ u32 rd;
+
+ /* Continuously poll to ensure the djtag is free */
+ while (1) {
+ rd = readl(regs_base + SC_DJTAG_DEBUG_MODULE_SEL);
+ if (rd == SC_DJTAG_V1_UNLOCK) {
+ writel(SC_DJTAG_V1_KERNEL_LOCK,
+ regs_base + SC_DJTAG_DEBUG_MODULE_SEL);
+ udelay(10);
+ rd = readl(regs_base + SC_DJTAG_DEBUG_MODULE_SEL);
+ if (rd == SC_DJTAG_V1_KERNEL_LOCK)
+ break;
+ }
+ udelay(10); /* 10us */
+ }
+}
+
+static void hisi_djtag_prepare_v1(void __iomem *regs_base, u32 offset,
+ u32 mod_sel, u32 mod_mask)
+{
+ /* djtag master enable and support normal mode for R,W */
+ writel(DJTAG_NOR_CFG | DJTAG_MSTR_EN, regs_base + SC_DJTAG_MSTR_EN);
+
+ /* select module */
+ writel(mod_sel, regs_base + SC_DJTAG_DEBUG_MODULE_SEL);
+ writel(mod_mask & CHAIN_UNIT_CFG_EN,
+ regs_base + SC_DJTAG_CHAIN_UNIT_CFG_EN);
+
+ /* address offset */
+ writel(offset, regs_base + SC_DJTAG_MSTR_ADDR);
+}
+
+static void hisi_djtag_do_operation_v1(void __iomem *regs_base)
+{
+ u32 val;
+ int ret;
+
+ /* start to write to djtag register */
+ writel(DJTAG_MSTR_START_EN, regs_base + SC_DJTAG_MSTR_START_EN);
+
+ /* ensure the djtag operation is done */
+ ret = readl_poll_timeout_atomic(regs_base + SC_DJTAG_MSTR_START_EN,
+ val, !(val & DJTAG_MSTR_EN), 1,
+ SC_DJTAG_TIMEOUT_US);
+ /*
+ * Djtag access busy even after timeout indicate a serious hardware
+ * condition. BUG_ON as subsequent access are also likely to go wrong.
+ */
+ BUG_ON(ret == -ETIMEDOUT);
+}
+
+/*
+ * hisi_djtag_lock_v2: djtag lock to avoid djtag access conflict b/w kernel
+ * and UEFI.
+ * @reg_base: djtag register base address
+ *
+ * Return - none.
+ */
+static void hisi_djtag_lock_v2(void __iomem *regs_base)
+{
+ u32 rd, wr, mod_sel;
+
+ /* Continuously poll to ensure the djtag is free */
+ while (1) {
+ rd = readl(regs_base + SC_DJTAG_MSTR_CFG_EX);
+ mod_sel = ((rd >> DEBUG_MODULE_SEL_SHIFT_EX) & 0xFF);
+ if (mod_sel == SC_DJTAG_V2_UNLOCK) {
+ wr = ((rd & SC_DJTAG_V2_MODULE_SEL_MASK) |
+ (SC_DJTAG_V2_KERNEL_LOCK <<
+ DEBUG_MODULE_SEL_SHIFT_EX));
+ writel(wr, regs_base + SC_DJTAG_MSTR_CFG_EX);
+ udelay(10); /* 10us */
+
+ rd = readl(regs_base + SC_DJTAG_MSTR_CFG_EX);
+ mod_sel = ((rd >> DEBUG_MODULE_SEL_SHIFT_EX) & 0xFF);
+ if (mod_sel == SC_DJTAG_V2_KERNEL_LOCK)
+ break;
+ }
+ udelay(10); /* 10us */
+ }
+}
+
+/*
+ * hisi_djtag_unlock_v2: djtag unlock
+ * @reg_base: djtag register base address
+ *
+ * Return - none.
+ */
+static void hisi_djtag_unlock_v2(void __iomem *regs_base)
+{
+ u32 rd, wr;
+
+ rd = readl(regs_base + SC_DJTAG_MSTR_CFG_EX);
+
+ wr = ((rd & SC_DJTAG_V2_MODULE_SEL_MASK) |
+ (SC_DJTAG_V2_UNLOCK << DEBUG_MODULE_SEL_SHIFT_EX));
+ /*
+ * Release djtag module by writing to module
+ * selection bits of DJTAG_MSTR_CFG register
+ */
+ writel(wr, regs_base + SC_DJTAG_MSTR_CFG_EX);
+}
+
+static void hisi_djtag_prepare_v2(void __iomem *regs_base, u32 offset,
+ u32 mod_sel, u32 mod_mask)
+{
+ /* djtag mster enable */
+ writel(DJTAG_SEC_ACC_EN_EX, regs_base + SC_DJTAG_SEC_ACC_EN_EX);
+
+ /* address offset */
+ writel(offset, regs_base + SC_DJTAG_MSTR_ADDR_EX);
+}
+
+static void hisi_djtag_do_operation_v2(void __iomem *regs_base)
+{
+ u32 val;
+ int ret;
+
+ /* start to write to djtag register */
+ writel(DJTAG_MSTR_START_EN_EX, regs_base + SC_DJTAG_MSTR_START_EN_EX);
+
+ /* ensure the djtag operation is done */
+ ret = readl_poll_timeout_atomic(regs_base + SC_DJTAG_MSTR_START_EN_EX,
+ val, !(val & DJTAG_MSTR_START_EN_EX), 1,
+ SC_DJTAG_TIMEOUT_US);
+ /*
+ * Djtag access busy even after timeout indicate a serious hardware
+ * condition. BUG_ON as subsequent access are also likely to go wrong.
+ */
+ BUG_ON(ret == -ETIMEDOUT);
+
+ /* wait for the operation to complete */
+ ret = readl_poll_timeout_atomic(regs_base + SC_DJTAG_OP_ST_EX,
+ val, (val & DJTAG_OP_DONE_EX), 1,
+ SC_DJTAG_TIMEOUT_US);
+
+ /*
+ * Djtag access busy even after timeout indicate a serious hardware
+ * condition. BUG_ON as subsequent access are also likely to go wrong.
+ */
+ BUG_ON(ret == -ETIMEDOUT);
+}
+
+/*
+ * hisi_djtag_read_v1/v2: djtag read interface
+ * @reg_base: djtag register base address
+ * @offset: register's offset
+ * @mod_sel: module selection
+ * @mod_mask: mask to select specific modules for read
+ * @chain_id: which sub module to read
+ * @rval: value read from register
+ *
+ * Return - none.
+ */
+static void hisi_djtag_read_v1(void __iomem *regs_base, u32 offset, u32 mod_sel,
+ u32 mod_mask, int chain_id, u32 *rval)
+{
+ hisi_djtag_lock_v1(regs_base);
+
+ hisi_djtag_prepare_v1(regs_base, offset, mod_sel, mod_mask);
+
+ writel(DJTAG_MSTR_R, regs_base + SC_DJTAG_MSTR_WR);
+
+ hisi_djtag_do_operation_v1(regs_base);
+
+ *rval = readl(regs_base + SC_DJTAG_RD_DATA_BASE + chain_id * 0x4);
+
+ /* Unlock djtag by setting module selection register to 0x3A */
+ writel(SC_DJTAG_V1_UNLOCK, regs_base + SC_DJTAG_DEBUG_MODULE_SEL);
+}
+
+static void hisi_djtag_read_v2(void __iomem *regs_base, u32 offset, u32 mod_sel,
+ u32 mod_mask, int chain_id, u32 *rval)
+{
+ u32 val = DJTAG_MSTR_RD_EX |
+ (mod_sel << DEBUG_MODULE_SEL_SHIFT_EX) |
+ (mod_mask & CHAIN_UNIT_CFG_EN_EX);
+
+ hisi_djtag_lock_v2(regs_base);
+
+ hisi_djtag_prepare_v2(regs_base, offset, mod_sel, mod_mask);
+
+ writel(val, regs_base + SC_DJTAG_MSTR_CFG_EX);
+
+ hisi_djtag_do_operation_v2(regs_base);
+
+ *rval = readl(regs_base + SC_DJTAG_RD_DATA_BASE_EX + chain_id * 0x4);
+
+ hisi_djtag_unlock_v2(regs_base);
+}
+
+/*
+ * hisi_djtag_write_v1/v2: djtag write interface
+ * @reg_base: djtag register base address
+ * @offset: register's offset
+ * @mod_sel: module selection
+ * @mod_mask: mask to select specific modules for write
+ * @wval: value to write to register
+ * @chain_id: which sub module to write
+ *
+ * Return - none.
+ */
+static void hisi_djtag_write_v1(void __iomem *regs_base, u32 offset,
+ u32 mod_sel, u32 mod_mask, u32 wval,
+ int chain_id)
+{
+ hisi_djtag_lock_v1(regs_base);
+
+ hisi_djtag_prepare_v1(regs_base, offset, mod_sel, mod_mask);
+
+ writel(DJTAG_MSTR_W, regs_base + SC_DJTAG_MSTR_WR);
+ writel(wval, regs_base + SC_DJTAG_MSTR_DATA);
+
+ hisi_djtag_do_operation_v1(regs_base);
+
+ /* Unlock djtag by setting module selection register to 0x3A */
+ writel(SC_DJTAG_V1_UNLOCK, regs_base + SC_DJTAG_DEBUG_MODULE_SEL);
+}
+
+static void hisi_djtag_write_v2(void __iomem *regs_base, u32 offset,
+ u32 mod_sel, u32 mod_mask, u32 wval,
+ int chain_id)
+{
+ u32 val = DJTAG_MSTR_WR_EX |
+ (mod_sel << DEBUG_MODULE_SEL_SHIFT_EX) |
+ (mod_mask & CHAIN_UNIT_CFG_EN_EX);
+ hisi_djtag_lock_v2(regs_base);
+
+ hisi_djtag_prepare_v2(regs_base, offset, mod_sel, mod_mask);
+
+ writel(val, regs_base + SC_DJTAG_MSTR_CFG_EX);
+ writel(wval, regs_base + SC_DJTAG_MSTR_DATA_EX);
+
+ hisi_djtag_do_operation_v2(regs_base);
+
+ hisi_djtag_unlock_v2(regs_base);
+}
+
+/*
+ * hisi_djtag_writel - write registers via djtag
+ * @client: djtag client handle
+ * @offset: register's offset
+ * @mod_sel: module selection
+ * @mod_mask: mask to select specific modules
+ * @val: value to write to register
+ *
+ * Return - none.
+ */
+void hisi_djtag_writel(struct hisi_djtag_client *client, u32 offset,
+ u32 mod_sel, u32 mod_mask, u32 val)
+{
+ struct hisi_djtag_host *host = client->host;
+ void __iomem *reg_map = host->sysctl_reg_map;
+ const struct hisi_djtag_ops *ops = host->djtag_ops;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ ops->djtag_write(reg_map, offset, mod_sel, mod_mask, val, 0);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/*
+ * hisi_djtag_readl - read registers via djtag
+ * @client: djtag client handle
+ * @offset: register's offset
+ * @mod_sel: module type selection
+ * @chain_id: chain_id number, mostly is 0
+ * @val: register's value
+ *
+ * Return - none.
+ */
+void hisi_djtag_readl(struct hisi_djtag_client *client, u32 offset,
+ u32 mod_sel, int chain_id, u32 *val)
+{
+ struct hisi_djtag_host *host = client->host;
+ void __iomem *reg_map = host->sysctl_reg_map;
+ const struct hisi_djtag_ops *ops = host->djtag_ops;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ ops->djtag_read(reg_map, offset, mod_sel, 0xffff, chain_id, val);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+u32 hisi_djtag_get_sclid(struct hisi_djtag_client *client)
+{
+ return client->host->scl_id;
+}
+
+static const struct hisi_djtag_ops djtag_v1_ops = {
+ .djtag_read = hisi_djtag_read_v1,
+ .djtag_write = hisi_djtag_write_v1,
+};
+
+static const struct hisi_djtag_ops djtag_v2_ops = {
+ .djtag_read = hisi_djtag_read_v2,
+ .djtag_write = hisi_djtag_write_v2,
+};
+
+static const struct of_device_id djtag_of_match[] = {
+ /* for hip05 CPU die */
+ { .compatible = "hisilicon,hip05-cpu-djtag-v1", .data = &djtag_v1_ops },
+ /* for hip05 IO die */
+ { .compatible = "hisilicon,hip05-io-djtag-v1", .data = &djtag_v1_ops },
+ /* for hip06 CPU die */
+ { .compatible = "hisilicon,hip06-cpu-djtag-v1", .data = &djtag_v1_ops },
+ /* for hip06 IO die */
+ { .compatible = "hisilicon,hip06-io-djtag-v2", .data = &djtag_v2_ops },
+ /* for hip07 CPU die */
+ { .compatible = "hisilicon,hip07-cpu-djtag-v2", .data = &djtag_v2_ops },
+ /* for hip07 IO die */
+ { .compatible = "hisilicon,hip07-io-djtag-v2", .data = &djtag_v2_ops },
+ {},
+};
+MODULE_DEVICE_TABLE(of, djtag_of_match);
+
+static const struct acpi_device_id djtag_acpi_match[] = {
+ { "HISI0201", (kernel_ulong_t)&djtag_v1_ops},
+ { "HISI0202", (kernel_ulong_t)&djtag_v2_ops},
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, djtag_acpi_match);
+
+static ssize_t show_modalias(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct hisi_djtag_client *client = to_hisi_djtag_client(dev);
+
+ return sprintf(buf, "%s%s\n", MODULE_PREFIX, dev_name(&client->dev));
+}
+static DEVICE_ATTR(modalias, 0444, show_modalias, NULL);
+
+static struct attribute *hisi_djtag_dev_attrs[] = {
+ /* modalias helps coldplug: modprobe $(cat .../modalias) */
+ &dev_attr_modalias.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(hisi_djtag_dev);
+
+static struct device_type hisi_djtag_client_type = {
+ .groups = hisi_djtag_dev_groups,
+};
+
+static int hisi_djtag_device_probe(struct device *dev)
+{
+ struct hisi_djtag_driver *driver;
+ struct hisi_djtag_client *client;
+ int ret;
+
+ client = to_hisi_djtag_client(dev);
+ if (!client) {
+ dev_err(dev, "could not find client\n");
+ return -ENODEV;
+ }
+
+ driver = to_hisi_djtag_driver(dev->driver);
+ if (!driver) {
+ dev_err(dev, "could not find driver\n");
+ return -ENODEV;
+ }
+
+ ret = driver->probe(client);
+ if (ret < 0) {
+ dev_err(dev, "client probe failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int hisi_djtag_device_remove(struct device *dev)
+{
+ struct hisi_djtag_driver *driver;
+ struct hisi_djtag_client *client;
+ int ret;
+
+ client = to_hisi_djtag_client(dev);
+ if (!client) {
+ dev_err(dev, "could not find client\n");
+ return -ENODEV;
+ }
+
+ driver = to_hisi_djtag_driver(dev->driver);
+ if (!driver) {
+ dev_err(dev, "could not find driver\n");
+ return -ENODEV;
+ }
+
+ ret = driver->remove(client);
+ if (ret < 0) {
+ dev_err(dev, "client probe failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int hisi_djtag_device_match(struct device *dev,
+ struct device_driver *drv)
+{
+ /* Attempt an OF style match */
+ if (of_driver_match_device(dev, drv))
+ return true;
+
+#ifdef CONFIG_ACPI
+ if (acpi_driver_match_device(dev, drv))
+ return true;
+#endif
+ return false;
+}
+
+struct bus_type hisi_djtag_bus = {
+ .name = "hisi-djtag",
+ .match = hisi_djtag_device_match,
+ .probe = hisi_djtag_device_probe,
+ .remove = hisi_djtag_device_remove,
+};
+
+static struct hisi_djtag_client *
+hisi_djtag_client_alloc(struct hisi_djtag_host *host)
+{
+ struct hisi_djtag_client *client;
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return NULL;
+
+ client->host = host;
+ client->dev.parent = &client->host->dev;
+ client->dev.bus = &hisi_djtag_bus;
+ client->dev.type = &hisi_djtag_client_type;
+
+ return client;
+}
+
+static int hisi_djtag_get_client_id(struct hisi_djtag_client *client)
+{
+ return idr_alloc(&djtag_clients_idr, client, 0, 0, GFP_KERNEL);
+}
+
+static int hisi_djtag_set_client_name(struct hisi_djtag_client *client,
+ const char *device_name)
+{
+ char name[DJTAG_CLIENT_NAME_LEN];
+ int id;
+
+ id = hisi_djtag_get_client_id(client);
+ if (id < 0)
+ return id;
+
+ client->id = id;
+ snprintf(name, DJTAG_CLIENT_NAME_LEN, "%s%s_%d", DJTAG_PREFIX,
+ device_name, client->id);
+ dev_set_name(&client->dev, "%s", name);
+
+ return 0;
+}
+
+static int hisi_djtag_new_of_device(struct hisi_djtag_host *host,
+ struct device_node *node)
+{
+ struct hisi_djtag_client *client;
+ int ret;
+
+ client = hisi_djtag_client_alloc(host);
+ if (!client) {
+ dev_err(&host->dev, "DT: Client alloc fail!\n");
+ return -ENOMEM;
+ }
+
+ client->dev.of_node = of_node_get(node);
+
+ ret = hisi_djtag_set_client_name(client, node->name);
+ if (ret < 0) {
+ dev_err(&host->dev, "DT: Client set name fail!\n");
+ goto fail;
+ }
+
+ ret = device_register(&client->dev);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "DT: error adding new device, ret=%d\n", ret);
+ idr_remove(&djtag_clients_idr, client->id);
+ goto fail;
+ }
+
+ list_add(&client->next, &host->client_list);
+
+ return 0;
+fail:
+ of_node_put(client->dev.of_node);
+ kfree(client);
+ return ret;
+}
+
+static acpi_status djtag_add_new_acpi_device(acpi_handle handle, u32 level,
+ void *data, void **return_value)
+{
+ struct acpi_device *acpi_dev;
+ struct hisi_djtag_client *client;
+ struct hisi_djtag_host *host = data;
+ struct device *dev;
+ const char *cid = NULL;
+ int ret;
+
+ if (acpi_bus_get_device(handle, &acpi_dev))
+ return -ENODEV;
+
+ dev = &acpi_dev->dev;
+ client = hisi_djtag_client_alloc(host);
+ if (!client) {
+ dev_err(dev, "ACPI: Client alloc fail!\n");
+ return -ENOMEM;
+ }
+
+ client->dev.fwnode = acpi_fwnode_handle(acpi_dev);
+
+ cid = acpi_device_hid(acpi_dev);
+ if (!cid) {
+ dev_err(dev, "ACPI: could not read _CID!\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ret = hisi_djtag_set_client_name(client, cid);
+ if (ret < 0)
+ goto fail;
+
+ ret = device_register(&client->dev);
+ if (ret < 0) {
+ dev_err(dev, "ACPI: Error adding new device, ret=%d\n", ret);
+ idr_remove(&djtag_clients_idr, client->id);
+ goto fail;
+ }
+
+ list_add(&client->next, &host->client_list);
+
+ return 0;
+
+fail:
+ kfree(client);
+ return ret;
+}
+
+static void djtag_register_devices(struct hisi_djtag_host *host)
+{
+ struct device_node *node;
+
+ if (host->of_node) {
+ for_each_available_child_of_node(host->of_node, node) {
+ if (of_node_test_and_set_flag(node, OF_POPULATED))
+ continue;
+ if (hisi_djtag_new_of_device(host, node))
+ break;
+ }
+ } else if (host->acpi_handle) {
+ acpi_handle handle = host->acpi_handle;
+ acpi_status status;
+
+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1,
+ djtag_add_new_acpi_device, NULL,
+ host, NULL);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&host->dev,
+ "ACPI: Fail to read client devices!\n");
+ return;
+ }
+ }
+}
+
+static int hisi_djtag_add_host(struct hisi_djtag_host *host)
+{
+ int ret;
+
+ host->dev.bus = &hisi_djtag_bus;
+
+ ret = idr_alloc(&djtag_hosts_idr, host, 0, 0, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(&host->dev, "No available djtag host ID'!s\n");
+ return ret;
+ }
+ host->id = ret;
+
+ /* Suffix the unique ID and set djtag hostname */
+ dev_set_name(&host->dev, "djtag-host-%d", host->id);
+ ret = device_register(&host->dev);
+ if (ret < 0) {
+ dev_err(&host->dev,
+ "add_host dev register failed, ret=%d\n", ret);
+ idr_remove(&djtag_hosts_idr, host->id);
+ return ret;
+ }
+
+ djtag_register_devices(host);
+
+ return 0;
+}
+
+static int djtag_host_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct hisi_djtag_host *host;
+ struct resource *res;
+ int ret;
+
+ host = kzalloc(sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ if (dev->of_node) {
+ const struct of_device_id *of_id;
+
+ of_id = of_match_device(djtag_of_match, dev);
+ if (!of_id) {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ host->djtag_ops = of_id->data;
+ host->of_node = of_node_get(dev->of_node);
+ } else if (ACPI_COMPANION(dev)) {
+ const struct acpi_device_id *acpi_id;
+
+ acpi_id = acpi_match_device(djtag_acpi_match, dev);
+ if (!acpi_id) {
+ ret = -EINVAL;
+ goto fail;
+ }
+ host->djtag_ops = (struct hisi_djtag_ops *)acpi_id->driver_data;
+
+ host->acpi_handle = ACPI_HANDLE(dev);
+ if (!host->acpi_handle) {
+ ret = -EINVAL;
+ goto fail;
+ }
+ } else {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ /* Find the SCL ID */
+ ret = device_property_read_u32(dev, "hisilicon,scl-id", &host->scl_id);
+ if (ret < 0)
+ goto fail;
+
+ spin_lock_init(&host->lock);
+ INIT_LIST_HEAD(&host->client_list);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "No reg resorces!\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (resource_size(res) != DJTAG_SYSCTRL_ADDR_SIZE) {
+ dev_err(dev, "Incorrect djtag sysctrl address range!\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ host->sysctl_reg_map = devm_ioremap_resource(dev, res);
+ if (IS_ERR(host->sysctl_reg_map)) {
+ dev_err(dev, "Unable to map sysctl registers!\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ platform_set_drvdata(pdev, host);
+
+ ret = hisi_djtag_add_host(host);
+ if (ret) {
+ dev_err(dev, "add host failed, ret=%d\n", ret);
+ goto fail;
+ }
+
+ return 0;
+fail:
+ of_node_put(dev->of_node);
+ kfree(host);
+ return ret;
+}
+
+static int djtag_host_remove(struct platform_device *pdev)
+{
+ struct hisi_djtag_host *host;
+ struct hisi_djtag_client *client, *tmp;
+ struct list_head *client_list;
+
+ host = platform_get_drvdata(pdev);
+ client_list = &host->client_list;
+
+ list_for_each_entry_safe(client, tmp, client_list, next) {
+ list_del(&client->next);
+ device_unregister(&client->dev);
+ idr_remove(&djtag_clients_idr, client->id);
+ of_node_put(client->dev.of_node);
+ kfree(client);
+ }
+
+ device_unregister(&host->dev);
+ idr_remove(&djtag_hosts_idr, host->id);
+ of_node_put(host->of_node);
+ kfree(host);
+
+ return 0;
+}
+
+static struct platform_driver djtag_dev_driver = {
+ .driver = {
+ .name = "hisi-djtag",
+ .of_match_table = djtag_of_match,
+ .acpi_match_table = ACPI_PTR(djtag_acpi_match),
+ },
+ .probe = djtag_host_probe,
+ .remove = djtag_host_remove,
+};
+module_platform_driver(djtag_dev_driver);
+
+int hisi_djtag_register_driver(struct module *owner,
+ struct hisi_djtag_driver *driver)
+{
+ int ret;
+
+ driver->driver.owner = owner;
+ driver->driver.bus = &hisi_djtag_bus;
+
+ ret = driver_register(&driver->driver);
+ if (ret < 0)
+ pr_err("%s register failed, ret=%d\n", __func__, ret);
+
+ return ret;
+}
+
+void hisi_djtag_unregister_driver(struct hisi_djtag_driver *driver)
+{
+ driver->driver.bus = &hisi_djtag_bus;
+ driver_unregister(&driver->driver);
+}
+
+static int __init hisi_djtag_init(void)
+{
+ int ret;
+
+ ret = bus_register(&hisi_djtag_bus);
+ if (ret) {
+ pr_err("hisi djtag init failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+module_init(hisi_djtag_init);
+
+static void __exit hisi_djtag_exit(void)
+{
+ bus_unregister(&hisi_djtag_bus);
+}
+module_exit(hisi_djtag_exit);
+
+MODULE_DESCRIPTION("Hisilicon djtag driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
diff --git a/drivers/perf/hisilicon/djtag.h b/drivers/perf/hisilicon/djtag.h
new file mode 100644
index 0000000..7c8219a
--- /dev/null
+++ b/drivers/perf/hisilicon/djtag.h
@@ -0,0 +1,46 @@
+/*
+ * Driver for Hisilicon djtag r/w via System Controller.
+ *
+ * Copyright (c) 2017 Hisilicon Limited
+ * Author: Tan Xiaojun <tanxiaojun@xxxxxxxxxx>
+ * Anurup M <anurup.m@xxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __HISI_DJTAG_H
+#define __HISI_DJTAG_H
+
+#include <linux/bitops.h>
+
+#define DJTAG_CLIENT_NAME_LEN 32
+
+/* Get djtag instance or sub-module ID */
+#define DJTAG_GET_CHAIN_ID(x) (ilog2(x))
+
+struct hisi_djtag_client {
+ int id; /* Unique identifier to sufix in client->dev.name */
+ struct hisi_djtag_host *host;
+ struct list_head next;
+ struct device dev;
+};
+
+struct hisi_djtag_driver {
+ struct device_driver driver;
+ int (*probe)(struct hisi_djtag_client *);
+ int (*remove)(struct hisi_djtag_client *);
+};
+
+extern struct bus_type hisi_djtag_bus;
+
+int hisi_djtag_register_driver(struct module *owner,
+ struct hisi_djtag_driver *driver);
+void hisi_djtag_unregister_driver(struct hisi_djtag_driver *driver);
+void hisi_djtag_readl(struct hisi_djtag_client *client, u32 offset,
+ u32 mod_sel, int chain_id, u32 *val);
+void hisi_djtag_writel(struct hisi_djtag_client *client, u32 offset,
+ u32 mod_sel, u32 mod_mask, u32 val);
+u32 hisi_djtag_get_sclid(struct hisi_djtag_client *client);
+#endif /* __HISI_DJTAG_H */
--
1.9.1