Contributors: 3
Author Tokens Token Proportion Commits Commit Proportion
Yuanfang Zhang 954 98.55% 1 33.33%
Dan Carpenter 9 0.93% 1 33.33%
Unknown 5 0.52% 1 33.33%
Total 968 3


// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
 */

 #include <linux/amba/bus.h>
 #include <linux/coresight.h>
 #include <linux/device.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>

#include "coresight-priv.h"
#include "coresight-trace-id.h"

#define TRACE_NOC_CTRL      0x008
#define TRACE_NOC_XLD       0x010
#define TRACE_NOC_FREQVAL   0x018
#define TRACE_NOC_SYNCR     0x020

/* Enable generation of output ATB traffic.*/
#define TRACE_NOC_CTRL_PORTEN   BIT(0)
/* Sets the type of issued ATB FLAG packets.*/
#define TRACE_NOC_CTRL_FLAGTYPE BIT(7)
/* Sets the type of issued ATB FREQ packet*/
#define TRACE_NOC_CTRL_FREQTYPE BIT(8)

#define TRACE_NOC_SYNC_INTERVAL	0xFFFF

/*
 * struct trace_noc_drvdata - specifics associated to a trace noc component
 * @base:      memory mapped base address for this component.
 * @dev:       device node for trace_noc_drvdata.
 * @csdev:     component vitals needed by the framework.
 * @spinlock:  serialize enable/disable operation.
 * @atid:      id for the trace packet.
 */
struct trace_noc_drvdata {
	void __iomem		*base;
	struct device		*dev;
	struct coresight_device	*csdev;
	spinlock_t		spinlock;
	u32			atid;
};

DEFINE_CORESIGHT_DEVLIST(trace_noc_devs, "traceNoc");

static void trace_noc_enable_hw(struct trace_noc_drvdata *drvdata)
{
	u32 val;

	/* Set ATID */
	writel_relaxed(drvdata->atid, drvdata->base + TRACE_NOC_XLD);

	/* Set the data word count between 'SYNC' packets */
	writel_relaxed(TRACE_NOC_SYNC_INTERVAL, drvdata->base + TRACE_NOC_SYNCR);

	/* Set the Control register:
	 * - Set the FLAG packets to 'FLAG' packets
	 * - Set the FREQ packets to 'FREQ_TS' packets
	 * - Enable generation of output ATB traffic
	 */

	val = readl_relaxed(drvdata->base + TRACE_NOC_CTRL);

	val &= ~TRACE_NOC_CTRL_FLAGTYPE;
	val |= TRACE_NOC_CTRL_FREQTYPE;
	val |= TRACE_NOC_CTRL_PORTEN;

	writel(val, drvdata->base + TRACE_NOC_CTRL);
}

static int trace_noc_enable(struct coresight_device *csdev, struct coresight_connection *inport,
			    struct coresight_connection *outport)
{
	struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);

	scoped_guard(spinlock, &drvdata->spinlock) {
		if (csdev->refcnt == 0)
			trace_noc_enable_hw(drvdata);

		csdev->refcnt++;
	}

	dev_dbg(drvdata->dev, "Trace NOC is enabled\n");
	return 0;
}

static void trace_noc_disable(struct coresight_device *csdev, struct coresight_connection *inport,
			      struct coresight_connection *outport)
{
	struct trace_noc_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);

	scoped_guard(spinlock, &drvdata->spinlock) {
		if (--csdev->refcnt == 0)
			writel(0x0, drvdata->base + TRACE_NOC_CTRL);
	}
	dev_dbg(drvdata->dev, "Trace NOC is disabled\n");
}

static int trace_noc_id(struct coresight_device *csdev, __maybe_unused enum cs_mode mode,
			__maybe_unused struct coresight_device *sink)
{
	struct trace_noc_drvdata *drvdata;

	drvdata = dev_get_drvdata(csdev->dev.parent);

	return drvdata->atid;
}

static const struct coresight_ops_link trace_noc_link_ops = {
	.enable		= trace_noc_enable,
	.disable	= trace_noc_disable,
};

static const struct coresight_ops trace_noc_cs_ops = {
	.trace_id	= trace_noc_id,
	.link_ops	= &trace_noc_link_ops,
};

static int trace_noc_init_default_data(struct trace_noc_drvdata *drvdata)
{
	int atid;

	atid = coresight_trace_id_get_system_id();
	if (atid < 0)
		return atid;

	drvdata->atid = atid;

	return 0;
}

static ssize_t traceid_show(struct device *dev,
			    struct device_attribute *attr, char *buf)
{
	unsigned long val;
	struct trace_noc_drvdata *drvdata = dev_get_drvdata(dev->parent);

	val = drvdata->atid;
	return sprintf(buf, "%#lx\n", val);
}
static DEVICE_ATTR_RO(traceid);

static struct attribute *coresight_tnoc_attrs[] = {
	&dev_attr_traceid.attr,
	NULL,
};

static const struct attribute_group coresight_tnoc_group = {
	.attrs = coresight_tnoc_attrs,
};

static const struct attribute_group *coresight_tnoc_groups[] = {
	&coresight_tnoc_group,
	NULL,
};

static int trace_noc_probe(struct amba_device *adev, const struct amba_id *id)
{
	struct device *dev = &adev->dev;
	struct coresight_platform_data *pdata;
	struct trace_noc_drvdata *drvdata;
	struct coresight_desc desc = { 0 };
	int ret;

	desc.name = coresight_alloc_device_name(&trace_noc_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 (IS_ERR(drvdata->base))
		return PTR_ERR(drvdata->base);

	spin_lock_init(&drvdata->spinlock);

	ret = trace_noc_init_default_data(drvdata);
	if (ret)
		return ret;

	desc.ops = &trace_noc_cs_ops;
	desc.type = CORESIGHT_DEV_TYPE_LINK;
	desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG;
	desc.pdata = adev->dev.platform_data;
	desc.dev = &adev->dev;
	desc.access = CSDEV_ACCESS_IOMEM(drvdata->base);
	desc.groups = coresight_tnoc_groups;
	drvdata->csdev = coresight_register(&desc);
	if (IS_ERR(drvdata->csdev)) {
		coresight_trace_id_put_system_id(drvdata->atid);
		return PTR_ERR(drvdata->csdev);
	}
	pm_runtime_put(&adev->dev);

	return 0;
}

static void trace_noc_remove(struct amba_device *adev)
{
	struct trace_noc_drvdata *drvdata = dev_get_drvdata(&adev->dev);

	coresight_unregister(drvdata->csdev);
	coresight_trace_id_put_system_id(drvdata->atid);
}

static struct amba_id trace_noc_ids[] = {
	{
		.id     = 0x000f0c00,
		.mask   = 0x00ffff00,
	},
	{
		.id     = 0x001f0c00,
		.mask   = 0x00ffff00,
	},
	{},
};
MODULE_DEVICE_TABLE(amba, trace_noc_ids);

static struct amba_driver trace_noc_driver = {
	.drv = {
		.name   = "coresight-trace-noc",
		.suppress_bind_attrs = true,
	},
	.probe          = trace_noc_probe,
	.remove		= trace_noc_remove,
	.id_table	= trace_noc_ids,
};

module_amba_driver(trace_noc_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Trace NOC driver");