From 2db12fd9e99cce1cda4886d0f94369d90692b694 Mon Sep 17 00:00:00 2001 From: Mao Jinlong Date: Wed, 27 Jul 2022 05:09:22 -0700 Subject: [PATCH 1/4] coresight: Add driver to support for dummy coresight device Some Coresight devices that HLOS does not have permission to access or configure. Such as Coresight sink EUD, some TPDMs etc. So there needs driver to register dummy devices as Coresight devices. Provide Coresight API for dummy devices operations, such as enable and disable dummy devices. Build the Coresight path for dummy sink or dummy source for debugging. Change-Id: I93b24a84f0b463f92c5e5cb69b53a86bf873e7a6 Signed-off-by: Tao Zhang Signed-off-by: Mao Jinlong --- drivers/hwtracing/coresight/Kconfig | 8 + drivers/hwtracing/coresight/Makefile | 1 + drivers/hwtracing/coresight/coresight-dummy.c | 171 ++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 drivers/hwtracing/coresight/coresight-dummy.c diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 514a9b8086e3..40b7bd19d2ad 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -188,6 +188,14 @@ config CORESIGHT_CTI_INTEGRATION_REGS registers are not used in normal operation and can leave devices in an inconsistent state. +config CORESIGHT_DUMMY + tristate "Dummy driver support" + help + Enables support for dummy driver. Dummy driver can be used for + CoreSight sources/sinks that are owned and configured by some + other subsystem and use Linux drivers to configure rest of trace + path. + config CORESIGHT_TRBE tristate "Trace Buffer Extension (TRBE) driver" depends on ARM64 && CORESIGHT_SOURCE_ETM4X diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index b6c4a48140ec..0e722261b47e 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -27,3 +27,4 @@ obj-$(CONFIG_CORESIGHT_CTI) += coresight-cti.o obj-$(CONFIG_CORESIGHT_TRBE) += coresight-trbe.o coresight-cti-y := coresight-cti-core.o coresight-cti-platform.o \ coresight-cti-sysfs.o +obj-$(CONFIG_CORESIGHT_DUMMY) += coresight-dummy.o diff --git a/drivers/hwtracing/coresight/coresight-dummy.c b/drivers/hwtracing/coresight/coresight-dummy.c new file mode 100644 index 000000000000..5e73b506d4e3 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-dummy.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#define DUMMY_TRACE_ID_START 256 + +struct dummy_drvdata { + struct device *dev; + struct coresight_device *csdev; + int traceid; +}; + +DEFINE_CORESIGHT_DEVLIST(dummy_devs, "dummy"); + +static int dummy_source_enable(struct coresight_device *csdev, + struct perf_event *event, u32 mode) +{ + struct dummy_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + dev_info(drvdata->dev, "Dummy source enabled\n"); + + return 0; +} + +static void dummy_source_disable(struct coresight_device *csdev, + struct perf_event *event) +{ + struct dummy_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + dev_info(drvdata->dev, "Dummy source disabled\n"); +} + +static int dummy_sink_enable(struct coresight_device *csdev, u32 mode, + void *data) +{ + struct dummy_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + dev_info(drvdata->dev, "Dummy sink enabled\n"); + + return 0; +} + +static int dummy_sink_disable(struct coresight_device *csdev) +{ + struct dummy_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + dev_info(drvdata->dev, "Dummy sink disabled\n"); + + return 0; +} + +static int dummy_trace_id(struct coresight_device *csdev) +{ + struct dummy_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + return drvdata->traceid; +} + +static const struct coresight_ops_source dummy_source_ops = { + .trace_id = dummy_trace_id, + .enable = dummy_source_enable, + .disable = dummy_source_disable, +}; + +static const struct coresight_ops_sink dummy_sink_ops = { + .enable = dummy_sink_enable, + .disable = dummy_sink_disable, +}; + +static const struct coresight_ops dummy_cs_ops = { + .source_ops = &dummy_source_ops, + .sink_ops = &dummy_sink_ops, +}; + +static int dummy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct coresight_platform_data *pdata; + struct dummy_drvdata *drvdata; + struct coresight_desc desc = { 0 }; + static int traceid = DUMMY_TRACE_ID_START; + + desc.name = coresight_alloc_device_name(&dummy_devs, dev); + if (!desc.name) + return -ENOMEM; + 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); + + drvdata->traceid = traceid++; + + if (of_property_read_bool(pdev->dev.of_node, "qcom,dummy-source")) { + desc.type = CORESIGHT_DEV_TYPE_SOURCE; + desc.subtype.source_subtype = + CORESIGHT_DEV_SUBTYPE_SOURCE_PROC; + } else if (of_property_read_bool(pdev->dev.of_node, + "qcom,dummy-sink")) { + desc.type = CORESIGHT_DEV_TYPE_SINK; + desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER; + } else { + dev_info(dev, "Device type not set\n"); + return -EINVAL; + } + + desc.ops = &dummy_cs_ops; + desc.pdata = pdev->dev.platform_data; + desc.dev = &pdev->dev; + drvdata->csdev = coresight_register(&desc); + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); + + pm_runtime_enable(dev); + dev_info(dev, "Dummy device initialized\n"); + + return 0; +} + +static int dummy_remove(struct platform_device *pdev) +{ + struct dummy_drvdata *drvdata = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + pm_runtime_disable(dev); + coresight_unregister(drvdata->csdev); + return 0; +} + +static const struct of_device_id dummy_match[] = { + {.compatible = "qcom,coresight-dummy"}, + {}, +}; + +static struct platform_driver dummy_driver = { + .probe = dummy_probe, + .remove = dummy_remove, + .driver = { + .name = "coresight-dummy", + .of_match_table = dummy_match, + }, +}; + +int __init dummy_init(void) +{ + return platform_driver_register(&dummy_driver); +} +module_init(dummy_init); + +void __exit dummy_exit(void) +{ + platform_driver_unregister(&dummy_driver); +} +module_exit(dummy_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CoreSight dummy source driver"); From b09f9eef630b21fd785b18d534706994969f8f64 Mon Sep 17 00:00:00 2001 From: Mao Jinlong Date: Wed, 27 Jul 2022 05:14:54 -0700 Subject: [PATCH 2/4] coresight: Add driver to support for remote etm Enables support for ETM trace collection on remote processor using CoreSight framework. Enabling this will allow turning on ETM tracing on remote processor via sysfs by configuring the required CoreSight components. Change-Id: I46b7c31eb81bd3b6623b71cf6c4811ba2d99d360 Signed-off-by: Tao Zhang Signed-off-by: Mao Jinlong --- drivers/hwtracing/coresight/Kconfig | 9 + drivers/hwtracing/coresight/Makefile | 1 + .../coresight/coresight-remote-etm.c | 336 ++++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 drivers/hwtracing/coresight/coresight-remote-etm.c diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 40b7bd19d2ad..c07b8d846bbf 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -196,6 +196,15 @@ config CORESIGHT_DUMMY other subsystem and use Linux drivers to configure rest of trace path. +config CORESIGHT_REMOTE_ETM + tristate "Remote processor ETM trace support" + depends on QCOM_QMI_HELPERS + help + Enables support for ETM trace collection on remote processor using + CoreSight framework. Enabling this will allow turning on ETM + tracing on remote processor via sysfs by configuring the required + CoreSight components. + config CORESIGHT_TRBE tristate "Trace Buffer Extension (TRBE) driver" depends on ARM64 && CORESIGHT_SOURCE_ETM4X diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 0e722261b47e..87a6163e4adc 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_CORESIGHT_TRBE) += coresight-trbe.o coresight-cti-y := coresight-cti-core.o coresight-cti-platform.o \ coresight-cti-sysfs.o obj-$(CONFIG_CORESIGHT_DUMMY) += coresight-dummy.o +obj-$(CONFIG_CORESIGHT_REMOTE_ETM) += coresight-remote-etm.o diff --git a/drivers/hwtracing/coresight/coresight-remote-etm.c b/drivers/hwtracing/coresight/coresight-remote-etm.c new file mode 100644 index 000000000000..46d4c1e5afb4 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-remote-etm.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "coresight-qmi.h" + +#define REMOTE_ETM_TRACE_ID_START 192 + +#ifdef CONFIG_CORESIGHT_REMOTE_ETM_DEFAULT_ENABLE +static int boot_enable = CONFIG_CORESIGHT_REMOTE_ETM_DEFAULT_ENABLE; +#else +static int boot_enable; +#endif + +DEFINE_CORESIGHT_DEVLIST(remote_etm_devs, "remote-etm"); + +struct remote_etm_drvdata { + struct device *dev; + struct coresight_device *csdev; + struct mutex mutex; + struct qmi_handle handle; + uint32_t inst_id; + bool enable; + int traceid; + bool service_connected; + struct sockaddr_qrtr s_addr; +}; + +static int service_remote_etm_new_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + struct remote_etm_drvdata *drvdata = container_of(qmi, + struct remote_etm_drvdata, handle); + + drvdata->s_addr.sq_family = AF_QIPCRTR; + drvdata->s_addr.sq_node = svc->node; + drvdata->s_addr.sq_port = svc->port; + drvdata->service_connected = true; + dev_info(drvdata->dev, + "Connection established between QMI handle and %d service\n", + drvdata->inst_id); + + return 0; +} + +static void service_remote_etm_del_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + struct remote_etm_drvdata *drvdata = container_of(qmi, + struct remote_etm_drvdata, handle); + drvdata->service_connected = false; + dev_info(drvdata->dev, + "Connection disconnected between QMI handle and %d service\n", + drvdata->inst_id); +} + +static struct qmi_ops server_ops = { + .new_server = service_remote_etm_new_server, + .del_server = service_remote_etm_del_server, +}; + +static int remote_etm_enable(struct coresight_device *csdev, + struct perf_event *event, u32 mode) +{ + struct remote_etm_drvdata *drvdata = + dev_get_drvdata(csdev->dev.parent); + struct coresight_set_etm_req_msg_v01 req; + struct coresight_set_etm_resp_msg_v01 resp = { { 0, 0 } }; + struct qmi_txn txn; + int ret; + + mutex_lock(&drvdata->mutex); + + if (!drvdata->service_connected) { + dev_err(drvdata->dev, "QMI service not connected!\n"); + ret = EINVAL; + goto err; + } + /* + * The QMI handle may be NULL in the following scenarios: + * 1. QMI service is not present + * 2. QMI service is present but attempt to enable remote ETM is earlier + * than service is ready to handle request + * 3. Connection between QMI client and QMI service failed + * + * Enable CoreSight without processing further QMI commands which + * provides the option to enable remote ETM by other means. + */ + req.state = CORESIGHT_ETM_STATE_ENABLED_V01; + + ret = qmi_txn_init(&drvdata->handle, &txn, + coresight_set_etm_resp_msg_v01_ei, + &resp); + + if (ret < 0) { + dev_err(drvdata->dev, "QMI tx init failed , ret:%d\n", + ret); + goto err; + } + + ret = qmi_send_request(&drvdata->handle, &drvdata->s_addr, + &txn, CORESIGHT_QMI_SET_ETM_REQ_V01, + CORESIGHT_QMI_SET_ETM_REQ_MAX_LEN, + coresight_set_etm_req_msg_v01_ei, + &req); + if (ret < 0) { + dev_err(drvdata->dev, "QMI send ACK failed, ret:%d\n", + ret); + qmi_txn_cancel(&txn); + goto err; + } + + ret = qmi_txn_wait(&txn, msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + dev_err(drvdata->dev, "QMI qmi txn wait failed, ret:%d\n", + ret); + goto err; + } + + /* Check the response */ + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) + dev_err(drvdata->dev, "QMI request failed 0x%x\n", + resp.resp.error); + + drvdata->enable = true; + mutex_unlock(&drvdata->mutex); + + dev_info(drvdata->dev, "Remote ETM tracing enabled for instance %d\n", + drvdata->inst_id); + return 0; +err: + mutex_unlock(&drvdata->mutex); + return ret; +} + +static void remote_etm_disable(struct coresight_device *csdev, + struct perf_event *event) +{ + struct remote_etm_drvdata *drvdata = + dev_get_drvdata(csdev->dev.parent); + struct coresight_set_etm_req_msg_v01 req; + struct coresight_set_etm_resp_msg_v01 resp = { { 0, 0 } }; + struct qmi_txn txn; + int ret; + + mutex_lock(&drvdata->mutex); + if (!drvdata->service_connected) { + dev_err(drvdata->dev, "QMI service not connected!\n"); + goto err; + } + + req.state = CORESIGHT_ETM_STATE_DISABLED_V01; + + ret = qmi_txn_init(&drvdata->handle, &txn, + coresight_set_etm_resp_msg_v01_ei, + &resp); + + if (ret < 0) { + dev_err(drvdata->dev, "QMI tx init failed , ret:%d\n", + ret); + goto err; + } + + ret = qmi_send_request(&drvdata->handle, &drvdata->s_addr, + &txn, CORESIGHT_QMI_SET_ETM_REQ_V01, + CORESIGHT_QMI_SET_ETM_REQ_MAX_LEN, + coresight_set_etm_req_msg_v01_ei, + &req); + if (ret < 0) { + dev_err(drvdata->dev, "QMI send req failed, ret:%d\n", + ret); + qmi_txn_cancel(&txn); + goto err; + } + + ret = qmi_txn_wait(&txn, msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + dev_err(drvdata->dev, "QMI qmi txn wait failed, ret:%d\n", + ret); + goto err; + } + + /* Check the response */ + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + dev_err(drvdata->dev, "QMI request failed 0x%x\n", + resp.resp.error); + goto err; + } + + drvdata->enable = false; + dev_info(drvdata->dev, "Remote ETM tracing disabled for instance %d\n", + drvdata->inst_id); +err: + mutex_unlock(&drvdata->mutex); +} + +static int remote_etm_trace_id(struct coresight_device *csdev) +{ + struct remote_etm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); + + return drvdata->traceid; +} + +static const struct coresight_ops_source remote_etm_source_ops = { + .trace_id = remote_etm_trace_id, + .enable = remote_etm_enable, + .disable = remote_etm_disable, +}; + +static const struct coresight_ops remote_cs_ops = { + .source_ops = &remote_etm_source_ops, +}; + +static int remote_etm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct coresight_platform_data *pdata; + struct remote_etm_drvdata *drvdata; + struct coresight_desc desc = {0 }; + int ret; + static int traceid = REMOTE_ETM_TRACE_ID_START; + + desc.name = coresight_alloc_device_name(&remote_etm_devs, dev); + if (!desc.name) + return -ENOMEM; + 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); + + ret = of_property_read_u32(pdev->dev.of_node, "qcom,inst-id", + &drvdata->inst_id); + if (ret) + return ret; + + mutex_init(&drvdata->mutex); + + ret = qmi_handle_init(&drvdata->handle, + CORESIGHT_QMI_SET_ETM_REQ_MAX_LEN, + &server_ops, NULL); + if (ret < 0) { + dev_err(dev, "Remote ETM client init failed ret:%d\n", ret); + return ret; + } + + qmi_add_lookup(&drvdata->handle, + CORESIGHT_QMI_SVC_ID, + CORESIGHT_QMI_VERSION, + drvdata->inst_id); + + drvdata->traceid = traceid++; + + desc.type = CORESIGHT_DEV_TYPE_SOURCE; + desc.subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_PROC; + desc.ops = &remote_cs_ops; + desc.pdata = pdev->dev.platform_data; + desc.dev = &pdev->dev; + drvdata->csdev = coresight_register(&desc); + if (IS_ERR(drvdata->csdev)) { + ret = PTR_ERR(drvdata->csdev); + goto err; + } + dev_info(dev, "Remote ETM initialized\n"); + + pm_runtime_enable(dev); + if (drvdata->inst_id >= sizeof(int)*BITS_PER_BYTE) + dev_err(dev, "inst_id greater than boot_enable bit mask\n"); + else if (boot_enable & BIT(drvdata->inst_id)) + coresight_enable(drvdata->csdev); + + return 0; +err: + qmi_handle_release(&drvdata->handle); + return ret; +} + +static int remote_etm_remove(struct platform_device *pdev) +{ + struct remote_etm_drvdata *drvdata = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + pm_runtime_disable(dev); + qmi_handle_release(&drvdata->handle); + coresight_unregister(drvdata->csdev); + return 0; +} + +static const struct of_device_id remote_etm_match[] = { + {.compatible = "qcom,coresight-remote-etm"}, + {} +}; + +static struct platform_driver remote_etm_driver = { + .probe = remote_etm_probe, + .remove = remote_etm_remove, + .driver = { + .name = "coresight-remote-etm", + .of_match_table = remote_etm_match, + }, +}; + +int __init remote_etm_init(void) +{ + return platform_driver_register(&remote_etm_driver); +} +module_init(remote_etm_init); + +void __exit remote_etm_exit(void) +{ + platform_driver_unregister(&remote_etm_driver); +} +module_exit(remote_etm_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CoreSight Remote ETM driver"); From c507c2b1000b6ebde055e0114f736169cacaab32 Mon Sep 17 00:00:00 2001 From: Mao Jinlong Date: Wed, 27 Jul 2022 08:23:51 -0700 Subject: [PATCH 3/4] coresight: Add driver to support for tgu This driver provides support for Trigger Generation Unit that is used to detect patterns or sequences on a given set of signals. TGU is used to monitor a particular bus within a given region to detect illegal transaction sequences or slave responses. It is also used to monitor a data stream to detect protocol violations and to provide a trigger point for centering data around a specific event within the trace data buffer. Change-Id: I84378e6134eb0e6eefb483004acabc73736200a2 Signed-off-by: Tao Zhang Signed-off-by: Mao Jinlong --- drivers/hwtracing/coresight/Kconfig | 11 + drivers/hwtracing/coresight/Makefile | 1 + drivers/hwtracing/coresight/coresight-tgu.c | 529 ++++++++++++++++++++ 3 files changed, 541 insertions(+) create mode 100644 drivers/hwtracing/coresight/coresight-tgu.c diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index c07b8d846bbf..907e9975090d 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -205,6 +205,17 @@ config CORESIGHT_REMOTE_ETM tracing on remote processor via sysfs by configuring the required CoreSight components. +config CORESIGHT_TGU + tristate "CoreSight Trigger Generation Unit driver" + help + This driver provides support for Trigger Generation Unit that is + used to detect patterns or sequences on a given set of signals. + TGU is used to monitor a particular bus within a given region to + detect illegal transaction sequences or slave responses. It is also + used to monitor a data stream to detect protocol violations and to + provide a trigger point for centering data around a specific event + within the trace data buffer. + config CORESIGHT_TRBE tristate "Trace Buffer Extension (TRBE) driver" depends on ARM64 && CORESIGHT_SOURCE_ETM4X diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 87a6163e4adc..a4beb8972acd 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -29,3 +29,4 @@ coresight-cti-y := coresight-cti-core.o coresight-cti-platform.o \ coresight-cti-sysfs.o obj-$(CONFIG_CORESIGHT_DUMMY) += coresight-dummy.o obj-$(CONFIG_CORESIGHT_REMOTE_ETM) += coresight-remote-etm.o +obj-$(CONFIG_CORESIGHT_TGU) += coresight-tgu.o diff --git a/drivers/hwtracing/coresight/coresight-tgu.c b/drivers/hwtracing/coresight/coresight-tgu.c new file mode 100644 index 000000000000..34df8278a915 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-tgu.c @@ -0,0 +1,529 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-priv.h" + +#define tgu_writel(drvdata, val, off) __raw_writel((val), drvdata->base + off) +#define tgu_readl(drvdata, off) __raw_readl(drvdata->base + off) + +#define TGU_LOCK(drvdata) \ +do { \ + mb(); /* ensure configuration take effect before we lock it */ \ + tgu_writel(drvdata, 0x0, CORESIGHT_LAR); \ +} while (0) +#define TGU_UNLOCK(drvdata) \ +do { \ + tgu_writel(drvdata, CORESIGHT_UNLOCK, CORESIGHT_LAR); \ + mb(); /* ensure unlock take effect before we configure */ \ +} while (0) + +#define TGU_CONTROL 0x0000 +#define TIMER0_STATUS 0x0004 +#define COUNTER0_STATUS 0x000C +#define TGU_STATUS 0x0014 +#define TIMER0_COMPARE_STEP(n) (0x0040 + 0x1D8 * n) +#define COUNTER0_COMPARE_STEP(n) (0x0048 + 0x1D8 * n) +#define GROUP_REG_STEP(grp, reg, step) (0x0074 + 0x60 * grp + 0x4 * reg + \ + 0x1D8 * step) +#define CONDITION_DECODE_STEP(m, n) (0x0050 + 0x4 * m + 0x1D8 * n) +#define CONDITION_SELECT_STEP(m, n) (0x0060 + 0x4 * m + 0x1D8 * n) +#define GROUP0 0x0074 +#define GROUP1 0x00D4 +#define GROUP2 0x0134 +#define GROUP3 0x0194 +#define TGU_LAR 0x0FB0 + +#define MAX_GROUP_SETS 256 +#define MAX_GROUPS 4 +#define MAX_CONDITION_SETS 64 +#define MAX_TIMER_COUNTER_SETS 8 + +#define to_tgu_drvdata(c) container_of(c, struct tgu_drvdata, tgu) + +struct Trigger_group_data { + unsigned long grpaddr; + unsigned long value; +}; + +struct Trigger_condition_data { + unsigned long condaddr; + unsigned long value; +}; + +struct Trigger_select_data { + unsigned long selectaddr; + unsigned long value; +}; + +struct Trigger_timer_data { + unsigned long timeraddr; + unsigned long value; +}; + +struct Trigger_counter_data { + unsigned long counteraddr; + unsigned long value; +}; +struct tgu_drvdata { + void __iomem *base; + struct device *dev; + struct coresight_device *csdev; + struct clk *clk; + spinlock_t spinlock; + int max_steps; + int max_conditions; + int max_regs; + int max_timer_counter; + struct Trigger_group_data *grp_data; + struct Trigger_condition_data *condition_data; + struct Trigger_select_data *select_data; + struct Trigger_timer_data *timer_data; + struct Trigger_counter_data *counter_data; + int grp_refcnt; + int cond_refcnt; + int select_refcnt; + int timer_refcnt; + int counter_refcnt; + bool enable; +}; + +DEFINE_CORESIGHT_DEVLIST(tgu_devs, "tgu"); + +static ssize_t enable_tgu_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long value; + struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent); + int ret, i, j; + + if (kstrtoul(buf, 16, &value)) + return -EINVAL; + + /* Enable clock */ + ret = pm_runtime_get_sync(drvdata->dev); + if (ret < 0) { + pm_runtime_put(drvdata->dev); + return ret; + } + + spin_lock(&drvdata->spinlock); + /* Unlock the TGU LAR */ + TGU_UNLOCK(drvdata); + + if (value) { + + /* Disable TGU to program the triggers */ + tgu_writel(drvdata, 0, TGU_CONTROL); + + /* program the TGU Group data for the desired use case*/ + + for (i = 0; i <= drvdata->grp_refcnt; i++) + tgu_writel(drvdata, drvdata->grp_data[i].value, + drvdata->grp_data[i].grpaddr); + + /* program the unused Condition Decode registers NOT bits to 1*/ + for (i = 0; i <= drvdata->max_conditions; i++) { + for (j = 0; j <= drvdata->max_steps; j++) + tgu_writel(drvdata, 0x1000000, + CONDITION_DECODE_STEP(i, j)); + } + /* program the TGU Condition Decode for the desired use case*/ + for (i = 0; i <= drvdata->cond_refcnt; i++) + tgu_writel(drvdata, drvdata->condition_data[i].value, + drvdata->condition_data[i].condaddr); + + /* program the TGU Condition Select for the desired use case*/ + for (i = 0; i <= drvdata->select_refcnt; i++) + tgu_writel(drvdata, drvdata->select_data[i].value, + drvdata->select_data[i].selectaddr); + + /* Timer and Counter Check */ + for (i = 0; i <= drvdata->timer_refcnt; i++) + tgu_writel(drvdata, drvdata->timer_data[i].value, + drvdata->timer_data[i].timeraddr); + + for (i = 0; i <= drvdata->counter_refcnt; i++) + tgu_writel(drvdata, drvdata->counter_data[i].value, + drvdata->counter_data[i].counteraddr); + + /* Enable TGU to program the triggers */ + tgu_writel(drvdata, 1, TGU_CONTROL); + + drvdata->enable = true; + dev_dbg(dev, "Coresight-TGU enabled\n"); + + } else { + /* Disable TGU to program the triggers */ + tgu_writel(drvdata, 0, TGU_CONTROL); + + pm_runtime_put(drvdata->dev); + dev_dbg(dev, "Coresight-TGU disabled\n"); + } + + TGU_LOCK(drvdata); + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_WO(enable_tgu); + +static ssize_t reset_tgu_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long value; + struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent); + int ret; + + if (kstrtoul(buf, 16, &value)) + return -EINVAL; + + if (!drvdata->enable) { + /* Enable clock */ + ret = pm_runtime_get_sync(drvdata->dev); + if (ret < 0) { + pm_runtime_put(drvdata->dev); + return ret; + } + } + + spin_lock(&drvdata->spinlock); + /* Unlock the TGU LAR */ + TGU_UNLOCK(drvdata); + + if (value) { + /* Disable TGU to program the triggers */ + tgu_writel(drvdata, 0, TGU_CONTROL); + + /* Reset the Reference counters*/ + drvdata->grp_refcnt = 0; + drvdata->cond_refcnt = 0; + drvdata->select_refcnt = 0; + drvdata->timer_refcnt = 0; + drvdata->counter_refcnt = 0; + + dev_dbg(dev, "Coresight-TGU disabled\n"); + } else + dev_dbg(dev, "Invalid input to reset the TGU\n"); + + TGU_LOCK(drvdata); + spin_unlock(&drvdata->spinlock); + pm_runtime_put(drvdata->dev); + return size; +} +static DEVICE_ATTR_WO(reset_tgu); + +static ssize_t set_group_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t size) +{ + struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent); + int grp, reg, step; + unsigned long value; + + if (drvdata->grp_refcnt >= MAX_GROUP_SETS) { + dev_err(drvdata->dev, " Too many groups are being configured\n"); + return -EINVAL; + } + + if (sscanf(buf, "%d %d %d %lx", &grp, ®, &step, &value) != 4) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + if ((grp <= MAX_GROUPS) && (reg <= drvdata->max_regs)) { + drvdata->grp_data[drvdata->grp_refcnt].grpaddr = + GROUP_REG_STEP(grp, reg, step); + drvdata->grp_data[drvdata->grp_refcnt].value = value; + drvdata->grp_refcnt++; + } else + dev_err(drvdata->dev, "Invalid group data\n"); + + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_WO(set_group); + +static ssize_t set_condition_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t size) +{ + struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long value; + int cond, step; + + if (drvdata->cond_refcnt >= MAX_CONDITION_SETS) { + dev_err(drvdata->dev, " Too many groups are being configured\n"); + return -EINVAL; + } + + if (sscanf(buf, "%d %d %lx", &cond, &step, &value) != 3) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + if ((cond <= drvdata->max_conditions) && (step <= + drvdata->max_steps)) { + drvdata->condition_data[drvdata->cond_refcnt].condaddr = + CONDITION_DECODE_STEP(cond, step); + drvdata->condition_data[drvdata->cond_refcnt].value = value; + drvdata->cond_refcnt++; + } else + dev_err(drvdata->dev, "Invalid condition decode data\n"); + + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_WO(set_condition); + +static ssize_t set_select_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t size) +{ + struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long value; + int select, step; + + if (drvdata->select_refcnt >= MAX_CONDITION_SETS) { + dev_err(drvdata->dev, " Too many groups are being configured\n"); + return -EINVAL; + } + + if (sscanf(buf, "%d %d %lx", &select, &step, &value) != 3) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + + if ((select <= drvdata->max_conditions) && (step <= + drvdata->max_steps)) { + drvdata->select_data[drvdata->select_refcnt].selectaddr = + CONDITION_SELECT_STEP(select, step); + drvdata->select_data[drvdata->select_refcnt].value = value; + drvdata->select_refcnt++; + } else + dev_err(drvdata->dev, "Invalid select decode data\n"); + + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_WO(set_select); + +static ssize_t set_timer_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t size) +{ + struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long value; + int step; + + if (drvdata->timer_refcnt >= MAX_TIMER_COUNTER_SETS) { + dev_err(drvdata->dev, " Too many groups are being configured\n"); + return -EINVAL; + } + + if (sscanf(buf, "%d %lx", &step, &value) != 2) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + if (step <= drvdata->max_timer_counter) { + drvdata->timer_data[drvdata->timer_refcnt].timeraddr = + TIMER0_COMPARE_STEP(step); + drvdata->timer_data[drvdata->timer_refcnt].value = value; + drvdata->timer_refcnt++; + } else + dev_err(drvdata->dev, "Invalid TGU timer data\n"); + + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_WO(set_timer); + +static ssize_t set_counter_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t size) +{ + struct tgu_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long value; + int step; + + if (drvdata->counter_refcnt >= MAX_TIMER_COUNTER_SETS) { + dev_err(drvdata->dev, " Too many groups are being configured\n"); + return -EINVAL; + } + + if (sscanf(buf, "%d %lx", &step, &value) != 2) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + if (step <= drvdata->max_timer_counter) { + drvdata->counter_data[drvdata->counter_refcnt].counteraddr = + COUNTER0_COMPARE_STEP(step); + drvdata->counter_data[drvdata->counter_refcnt].value = value; + drvdata->counter_refcnt++; + } else + dev_err(drvdata->dev, "Invalid TGU counter data\n"); + + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_WO(set_counter); + +static struct attribute *tgu_attrs[] = { + &dev_attr_enable_tgu.attr, + &dev_attr_reset_tgu.attr, + &dev_attr_set_group.attr, + &dev_attr_set_condition.attr, + &dev_attr_set_select.attr, + &dev_attr_set_timer.attr, + &dev_attr_set_counter.attr, + NULL, +}; + +static struct attribute_group tgu_attr_grp = { + .attrs = tgu_attrs, +}; + +static const struct attribute_group *tgu_attr_grps[] = { + &tgu_attr_grp, + NULL, +}; + +static int tgu_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret = 0; + struct device *dev = &adev->dev; + struct coresight_platform_data *pdata; + struct tgu_drvdata *drvdata; + struct coresight_desc desc = { 0 }; + + desc.name = coresight_alloc_device_name(&tgu_devs, dev); + if (!desc.name) + return -ENOMEM; + pdata = coresight_get_platform_data(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + adev->dev.platform_data = pdata; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->dev = &adev->dev; + + dev_set_drvdata(dev, drvdata); + + drvdata->base = devm_ioremap_resource(dev, &adev->res); + if (!drvdata->base) + return -ENOMEM; + + spin_lock_init(&drvdata->spinlock); + + ret = of_property_read_u32(adev->dev.of_node, "tgu-steps", + &drvdata->max_steps); + if (ret) + return -EINVAL; + + ret = of_property_read_u32(adev->dev.of_node, "tgu-conditions", + &drvdata->max_conditions); + if (ret) + return -EINVAL; + + ret = of_property_read_u32(adev->dev.of_node, "tgu-regs", + &drvdata->max_regs); + if (ret) + return -EINVAL; + + ret = of_property_read_u32(adev->dev.of_node, "tgu-timer-counters", + &drvdata->max_timer_counter); + if (ret) + return -EINVAL; + + /* Alloc memory for Grps, Conditions and Steps */ + drvdata->grp_data = devm_kzalloc(dev, MAX_GROUP_SETS * + sizeof(*drvdata->grp_data), + GFP_KERNEL); + if (!drvdata->grp_data) + return -ENOMEM; + + drvdata->condition_data = devm_kzalloc(dev, MAX_CONDITION_SETS * + sizeof(*drvdata->condition_data), + GFP_KERNEL); + + if (!drvdata->condition_data) + return -ENOMEM; + + drvdata->select_data = devm_kzalloc(dev, MAX_CONDITION_SETS * + sizeof(*drvdata->select_data), + GFP_KERNEL); + if (!drvdata->select_data) + return -ENOMEM; + + drvdata->timer_data = devm_kzalloc(dev, MAX_TIMER_COUNTER_SETS * + sizeof(*drvdata->timer_data), + GFP_KERNEL); + if (!drvdata->timer_data) + return -ENOMEM; + + drvdata->counter_data = devm_kzalloc(dev, MAX_TIMER_COUNTER_SETS * + sizeof(*drvdata->counter_data), + GFP_KERNEL); + if (!drvdata->counter_data) + return -ENOMEM; + + drvdata->enable = false; + + desc.type = CORESIGHT_DEV_TYPE_HELPER; + desc.pdata = adev->dev.platform_data; + desc.dev = &adev->dev; + desc.groups = tgu_attr_grps; + drvdata->csdev = coresight_register(&desc); + if (IS_ERR(drvdata->csdev)) { + ret = PTR_ERR(drvdata->csdev); + goto err; + } + + pm_runtime_put(&adev->dev); + dev_dbg(dev, "TGU initialized\n"); + return 0; +err: + pm_runtime_put(&adev->dev); + return ret; +} + +static struct amba_id tgu_ids[] = { + { + .id = 0x0003b999, + .mask = 0x0003ffff, + .data = "TGU", + }, + { 0, 0}, +}; + +static struct amba_driver tgu_driver = { + .drv = { + .name = "coresight-tgu", + .owner = THIS_MODULE, + .suppress_bind_attrs = true, + }, + .probe = tgu_probe, + .id_table = tgu_ids, +}; + +builtin_amba_driver(tgu_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CoreSight TGU driver"); From b7e422e142c58e041bf24e0d610188b2fddcedbf Mon Sep 17 00:00:00 2001 From: Mao Jinlong Date: Wed, 27 Jul 2022 08:34:56 -0700 Subject: [PATCH 4/4] coresight: Add driver to support for hwevent This driver provides support for monitoring and tracing Coresight Hardware Event across STM interface. It configures Coresight Hardware Event mux control registers to select hardware events based on user input. Change-Id: I5c3c5ca70015abe7448b25777aedeeb289b8e2d3 Signed-off-by: Tao Zhang Signed-off-by: Mao Jinlong --- drivers/hwtracing/coresight/Kconfig | 10 + drivers/hwtracing/coresight/Makefile | 1 + .../hwtracing/coresight/coresight-hwevent.c | 350 ++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 drivers/hwtracing/coresight/coresight-hwevent.c diff --git a/drivers/hwtracing/coresight/Kconfig b/drivers/hwtracing/coresight/Kconfig index 907e9975090d..f112c67d874b 100644 --- a/drivers/hwtracing/coresight/Kconfig +++ b/drivers/hwtracing/coresight/Kconfig @@ -216,6 +216,16 @@ config CORESIGHT_TGU provide a trigger point for centering data around a specific event within the trace data buffer. +config CORESIGHT_HWEVENT + tristate "CoreSight Hardware Event driver" + depends on CORESIGHT_STM + select CORESIGHT_CSR + help + This driver provides support for monitoring and tracing CoreSight + Hardware Event across STM interface. It configures Coresight + Hardware Event mux control registers to select hardware events + based on user input. + config CORESIGHT_TRBE tristate "Trace Buffer Extension (TRBE) driver" depends on ARM64 && CORESIGHT_SOURCE_ETM4X diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index a4beb8972acd..416e19d984f6 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -30,3 +30,4 @@ coresight-cti-y := coresight-cti-core.o coresight-cti-platform.o \ obj-$(CONFIG_CORESIGHT_DUMMY) += coresight-dummy.o obj-$(CONFIG_CORESIGHT_REMOTE_ETM) += coresight-remote-etm.o obj-$(CONFIG_CORESIGHT_TGU) += coresight-tgu.o +obj-$(CONFIG_CORESIGHT_HWEVENT) += coresight-hwevent.o diff --git a/drivers/hwtracing/coresight/coresight-hwevent.c b/drivers/hwtracing/coresight/coresight-hwevent.c new file mode 100644 index 000000000000..8249d6669dec --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-hwevent.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coresight-common.h" + +struct hwevent_mux { + phys_addr_t start; + phys_addr_t end; +}; + +struct hwevent_drvdata { + struct device *dev; + struct coresight_device *csdev; + struct clk *clk; + struct mutex mutex; + int nr_hclk; + struct clk **hclk; + int nr_hreg; + struct regulator **hreg; + int nr_hmux; + struct hwevent_mux *hmux; + struct coresight_csr *csr; + const char *csr_name; +}; + +DEFINE_CORESIGHT_DEVLIST(hwevent_devs, "hwevent"); + +static int hwevent_enable(struct hwevent_drvdata *drvdata) +{ + int ret, i, j; + + ret = clk_prepare_enable(drvdata->clk); + if (ret) + return ret; + + for (i = 0; i < drvdata->nr_hreg; i++) { + ret = regulator_enable(drvdata->hreg[i]); + if (ret) + goto err0; + } + + for (j = 0; j < drvdata->nr_hclk; j++) { + ret = clk_prepare_enable(drvdata->hclk[j]); + if (ret) + goto err1; + } + return 0; +err1: + for (j--; j >= 0; j--) + clk_disable_unprepare(drvdata->hclk[j]); +err0: + for (i--; i >= 0; i--) + regulator_disable(drvdata->hreg[i]); + + clk_disable_unprepare(drvdata->clk); + return ret; +} + +static void hwevent_disable(struct hwevent_drvdata *drvdata) +{ + int i; + + clk_disable_unprepare(drvdata->clk); + for (i = 0; i < drvdata->nr_hclk; i++) + clk_disable_unprepare(drvdata->hclk[i]); + for (i = 0; i < drvdata->nr_hreg; i++) + regulator_disable(drvdata->hreg[i]); +} + +static ssize_t setreg_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct hwevent_drvdata *drvdata = dev_get_drvdata(dev->parent); + void *hwereg; + unsigned long long addr; + unsigned long val; + int ret, i; + + if (sscanf(buf, "%llx %lx", &addr, &val) != 2) + return -EINVAL; + + mutex_lock(&drvdata->mutex); + ret = hwevent_enable(drvdata); + if (ret) { + mutex_unlock(&drvdata->mutex); + return ret; + } + + for (i = 0; i < drvdata->nr_hmux; i++) { + if ((addr >= drvdata->hmux[i].start) && + (addr < drvdata->hmux[i].end)) { + hwereg = ioremap(drvdata->hmux[i].start, + drvdata->hmux[i].end - + drvdata->hmux[i].start); + if (!hwereg) { + dev_err(dev, "unable to map address 0x%llx\n", + addr); + ret = -ENOMEM; + goto err; + } + writel_relaxed(val, hwereg + addr - + drvdata->hmux[i].start); + /* + * Ensure writes to hwevent control registers + * are completed before unmapping the address + */ + mb(); + iounmap(hwereg); + break; + } + } + + if (i == drvdata->nr_hmux) { + ret = coresight_csr_hwctrl_set(drvdata->csr, addr, val); + if (ret) { + dev_err(dev, "invalid mux control register address\n"); + ret = -EINVAL; + goto err; + } + } + + hwevent_disable(drvdata); + mutex_unlock(&drvdata->mutex); + return size; +err: + hwevent_disable(drvdata); + mutex_unlock(&drvdata->mutex); + return ret; +} +static DEVICE_ATTR_WO(setreg); + +static struct attribute *hwevent_attrs[] = { + &dev_attr_setreg.attr, + NULL, +}; + +static struct attribute_group hwevent_attr_grp = { + .attrs = hwevent_attrs, +}; + +static const struct attribute_group *hwevent_attr_grps[] = { + &hwevent_attr_grp, + NULL, +}; + +static int hwevent_init_hmux(struct hwevent_drvdata *drvdata, + struct platform_device *pdev) +{ + int ret, i; + struct resource *res; + const char *hmux_name; + struct device_node *node = drvdata->dev->of_node; + + drvdata->nr_hmux = of_property_count_strings(node, + "reg-names"); + + if (drvdata->nr_hmux < 0) + drvdata->nr_hmux = 0; + + if (drvdata->nr_hmux > 0) { + drvdata->hmux = devm_kzalloc(drvdata->dev, drvdata->nr_hmux * + sizeof(*drvdata->hmux), + GFP_KERNEL); + if (!drvdata->hmux) + return -ENOMEM; + for (i = 0; i < drvdata->nr_hmux; i++) { + ret = of_property_read_string_index(node, + "reg-names", i, + &hmux_name); + if (ret) + return ret; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + hmux_name); + if (!res) + return -ENODEV; + drvdata->hmux[i].start = res->start; + drvdata->hmux[i].end = res->end; + + } + } + + return 0; +} + +static int hwevent_init_clk(struct hwevent_drvdata *drvdata) +{ + int ret, i; + const char *hclk_name, *hreg_name; + struct device_node *node = drvdata->dev->of_node; + + drvdata->nr_hclk = of_property_count_strings(node, + "qcom,hwevent-clks"); + drvdata->nr_hreg = of_property_count_strings(node, + "qcom,hwevent-regs"); + + if (drvdata->nr_hclk > 0) { + drvdata->hclk = devm_kzalloc(drvdata->dev, drvdata->nr_hclk * + sizeof(*drvdata->hclk), + GFP_KERNEL); + if (!drvdata->hclk) + return -ENOMEM; + + for (i = 0; i < drvdata->nr_hclk; i++) { + ret = of_property_read_string_index(node, + "qcom,hwevent-clks", + i, &hclk_name); + if (ret) + return ret; + + drvdata->hclk[i] = devm_clk_get(drvdata->dev, + hclk_name); + if (IS_ERR(drvdata->hclk[i])) + return PTR_ERR(drvdata->hclk[i]); + } + } + if (drvdata->nr_hreg > 0) { + drvdata->hreg = devm_kzalloc(drvdata->dev, drvdata->nr_hreg * + sizeof(*drvdata->hreg), + GFP_KERNEL); + if (!drvdata->hreg) + return -ENOMEM; + + for (i = 0; i < drvdata->nr_hreg; i++) { + ret = of_property_read_string_index(node, + "qcom,hwevent-regs", + i, &hreg_name); + if (ret) + return ret; + + drvdata->hreg[i] = devm_regulator_get(drvdata->dev, + hreg_name); + if (IS_ERR(drvdata->hreg[i])) + return PTR_ERR(drvdata->hreg[i]); + } + } + + return 0; +} + +static int hwevent_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hwevent_drvdata *drvdata; + struct coresight_desc desc = { 0 }; + struct coresight_platform_data *pdata; + int ret; + + desc.name = coresight_alloc_device_name(&hwevent_devs, dev); + if (!desc.name) + return -ENOMEM; + 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); + + ret = of_get_coresight_csr_name(dev->of_node, &drvdata->csr_name); + if (ret) + dev_err(dev, "No csr data\n"); + else { + drvdata->csr = coresight_csr_get(drvdata->csr_name); + if (IS_ERR(drvdata->csr)) { + dev_dbg(dev, "failed to get csr, defer probe\n"); + return -EPROBE_DEFER; + } + } + + drvdata->clk = devm_clk_get(dev, "apb_pclk"); + if (IS_ERR(drvdata->clk)) + return PTR_ERR(drvdata->clk); + + ret = hwevent_init_hmux(drvdata, pdev); + if (ret) + return ret; + + ret = hwevent_init_clk(drvdata); + if (ret) + return ret; + + desc.type = CORESIGHT_DEV_TYPE_HELPER; + desc.pdata = pdev->dev.platform_data; + desc.dev = &pdev->dev; + desc.groups = hwevent_attr_grps; + drvdata->csdev = coresight_register(&desc); + if (IS_ERR(drvdata->csdev)) + return PTR_ERR(drvdata->csdev); + + dev_dbg(dev, "Hardware Event driver initialized\n"); + return 0; +} + +static int hwevent_remove(struct platform_device *pdev) +{ + struct hwevent_drvdata *drvdata = platform_get_drvdata(pdev); + + coresight_unregister(drvdata->csdev); + return 0; +} + +static const struct of_device_id hwevent_match[] = { + {.compatible = "qcom,coresight-hwevent"}, + {}, +}; + +static struct platform_driver hwevent_driver = { + .probe = hwevent_probe, + .remove = hwevent_remove, + .driver = { + .name = "coresight-hwevent", + .of_match_table = hwevent_match, + }, +}; + +static int __init hwevent_init(void) +{ + return platform_driver_register(&hwevent_driver); +} +module_init(hwevent_init); + +static void __exit hwevent_exit(void) +{ + platform_driver_unregister(&hwevent_driver); +} +module_exit(hwevent_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CoreSight Hardware Event driver");