Merge "coresight: Add driver to support for hwevent"

This commit is contained in:
qctecmdr 2022-08-17 20:03:55 -07:00 committed by Gerrit - the friendly Code Review server
commit 7304dc66c3
6 changed files with 1428 additions and 0 deletions

View File

@ -188,6 +188,44 @@ 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_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_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_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

View File

@ -27,3 +27,7 @@ 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
obj-$(CONFIG_CORESIGHT_REMOTE_ETM) += coresight-remote-etm.o
obj-$(CONFIG_CORESIGHT_TGU) += coresight-tgu.o
obj-$(CONFIG_CORESIGHT_HWEVENT) += coresight-hwevent.o

View File

@ -0,0 +1,171 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/coresight.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#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");

View File

@ -0,0 +1,350 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/clk.h>
#include <linux/coresight.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#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");

View File

@ -0,0 +1,336 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/sysfs.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/coresight.h>
#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");

View File

@ -0,0 +1,529 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/amba/bus.h>
#include <linux/topology.h>
#include <linux/of.h>
#include <linux/coresight.h>
#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, &reg, &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");