From patchwork Mon May 22 12:48:32 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shaokun Zhang X-Patchwork-Id: 9740113 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id B3F76601C2 for ; Mon, 22 May 2017 12:19:10 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9CC27284C0 for ; Mon, 22 May 2017 12:19:10 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 8FEF128697; Mon, 22 May 2017 12:19:10 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 4305D284C0 for ; Mon, 22 May 2017 12:19:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:Message-ID:Date:Subject:To :From:Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References: List-Owner; bh=SGKtbmnlMpWtJ0fabbMbn/W2ojcNXmrQaelKqkEZv3I=; b=R6BcS5kRM/34Gx mPzwqn3Hk3fzbmJunF3O6JuC+H5nsri/Fy2ObWjxuxE+D9xZFlkAzQvUAnftSkGuizhNHfRKd5RN5 rEuuWv6Y8erc2mKL5XjNdLLCzOnainrqKZvJQt2zkZBLhSbfIw8xUmERWPgrYeVLNQvAzEv+BjUO2 JEUbWPugIa7HxTlwUcRaFD9WmXo/Wlux/o+3oANt3CPu9gE+DPENrZYcP++TXBZc2a+qSRNjP+CK4 zN3oITI2k/7T9TaZpcxX4dFJQ1vpfIRFaViAlLehIdvjgNh63IaszEi10pFR+yFtoknuVncEccFrA pdwoPl7IbLlB2N6eJh7A==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1dCmIl-0005j9-Bs; Mon, 22 May 2017 12:19:07 +0000 Received: from szxga01-in.huawei.com ([45.249.212.187]) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1dCmHj-00044c-Br for linux-arm-kernel@lists.infradead.org; Mon, 22 May 2017 12:18:08 +0000 Received: from 172.30.72.53 (EHLO dggeml405-hub.china.huawei.com) ([172.30.72.53]) by dggrg01-dlp.huawei.com (MOS 4.4.6-GA FastPath queued) with ESMTP id APA29857; Mon, 22 May 2017 20:17:31 +0800 (CST) Received: from localhost.localdomain (10.67.212.75) by dggeml405-hub.china.huawei.com (10.3.17.49) with Microsoft SMTP Server id 14.3.301.0; Mon, 22 May 2017 20:17:22 +0800 From: Shaokun Zhang To: , Subject: [PATCH v8 6/9] drivers: perf: hisi: Add support for Hisilicon Djtag driver Date: Mon, 22 May 2017 20:48:32 +0800 Message-ID: <1495457312-237127-1-git-send-email-zhangshaokun@hisilicon.com> X-Mailer: git-send-email 1.9.1 MIME-Version: 1.0 X-Originating-IP: [10.67.212.75] X-CFilter-Loop: Reflected X-Mirapoint-Virus-RAPID-Raw: score=unknown(0), refid=str=0001.0A090205.5922D6DC.001D, ss=1, re=0.000, recu=0.000, reip=0.000, cl=1, cld=1, fgs=0, ip=0.0.0.0, so=2014-11-16 11:51:01, dmn=2013-03-21 17:37:32 X-Mirapoint-Loop-Id: 9753c71b0bf36918dcdc5857910b78c0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20170522_051804_524617_3BC3DFFF X-CRM114-Status: GOOD ( 20.66 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: dikshit.n@huawei.com, shyju.pv@huawei.com, anurupvasu@gmail.com, gabriele.paoloni@huawei.com, huangdaode@hisilicon.com, john.garry@huawei.com, linux-kernel@vger.kernel.org, xuwei5@hisilicon.com, linuxarm@huawei.com, zhangshaokun@hisilicon.com, sanil.kumar@hisilicon.com, linux-arm-kernel@lists.infradead.org, shiju.jose@huawei.com, tanxiaojun@huawei.com, anurup.m@huawei.com Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP From: Tan Xiaojun 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 Signed-off-by: John Garry Signed-off-by: Anurup M --- 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 + * Anurup M + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * Anurup M + * + * 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 + +#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 */