// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #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");