| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. |
| */ |
| |
| #include <linux/amba/bus.h> |
| #include <linux/bitmap.h> |
| #include <linux/coresight.h> |
| #include <linux/coresight-pmu.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/fs.h> |
| #include <linux/io.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| |
| #include "coresight-priv.h" |
| #include "coresight-tpdm.h" |
| |
| DEFINE_CORESIGHT_DEVLIST(tpdm_devs, "tpdm"); |
| |
| static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) |
| { |
| u32 val; |
| |
| /* Set the enable bit of DSB control register to 1 */ |
| val = readl_relaxed(drvdata->base + TPDM_DSB_CR); |
| val |= TPDM_DSB_CR_ENA; |
| writel_relaxed(val, drvdata->base + TPDM_DSB_CR); |
| } |
| |
| /* TPDM enable operations */ |
| static void __tpdm_enable(struct tpdm_drvdata *drvdata) |
| { |
| CS_UNLOCK(drvdata->base); |
| |
| /* Check if DSB datasets is present for TPDM. */ |
| if (drvdata->datasets & TPDM_PIDR0_DS_DSB) |
| tpdm_enable_dsb(drvdata); |
| |
| CS_LOCK(drvdata->base); |
| } |
| |
| static int tpdm_enable(struct coresight_device *csdev, |
| struct perf_event *event, u32 mode) |
| { |
| struct tpdm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); |
| |
| spin_lock(&drvdata->spinlock); |
| if (drvdata->enable) { |
| spin_unlock(&drvdata->spinlock); |
| return -EBUSY; |
| } |
| |
| __tpdm_enable(drvdata); |
| drvdata->enable = true; |
| spin_unlock(&drvdata->spinlock); |
| |
| dev_dbg(drvdata->dev, "TPDM tracing enabled\n"); |
| return 0; |
| } |
| |
| static void tpdm_disable_dsb(struct tpdm_drvdata *drvdata) |
| { |
| u32 val; |
| |
| /* Set the enable bit of DSB control register to 0 */ |
| val = readl_relaxed(drvdata->base + TPDM_DSB_CR); |
| val &= ~TPDM_DSB_CR_ENA; |
| writel_relaxed(val, drvdata->base + TPDM_DSB_CR); |
| } |
| |
| /* TPDM disable operations */ |
| static void __tpdm_disable(struct tpdm_drvdata *drvdata) |
| { |
| CS_UNLOCK(drvdata->base); |
| |
| /* Check if DSB datasets is present for TPDM. */ |
| if (drvdata->datasets & TPDM_PIDR0_DS_DSB) |
| tpdm_disable_dsb(drvdata); |
| |
| CS_LOCK(drvdata->base); |
| } |
| |
| static void tpdm_disable(struct coresight_device *csdev, |
| struct perf_event *event) |
| { |
| struct tpdm_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent); |
| |
| spin_lock(&drvdata->spinlock); |
| if (!drvdata->enable) { |
| spin_unlock(&drvdata->spinlock); |
| return; |
| } |
| |
| __tpdm_disable(drvdata); |
| drvdata->enable = false; |
| spin_unlock(&drvdata->spinlock); |
| |
| dev_dbg(drvdata->dev, "TPDM tracing disabled\n"); |
| } |
| |
| static const struct coresight_ops_source tpdm_source_ops = { |
| .enable = tpdm_enable, |
| .disable = tpdm_disable, |
| }; |
| |
| static const struct coresight_ops tpdm_cs_ops = { |
| .source_ops = &tpdm_source_ops, |
| }; |
| |
| static void tpdm_init_default_data(struct tpdm_drvdata *drvdata) |
| { |
| u32 pidr; |
| |
| CS_UNLOCK(drvdata->base); |
| /* Get the datasets present on the TPDM. */ |
| pidr = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR0); |
| drvdata->datasets |= pidr & GENMASK(TPDM_DATASETS - 1, 0); |
| CS_LOCK(drvdata->base); |
| } |
| |
| /* |
| * value 1: 64 bits test data |
| * value 2: 32 bits test data |
| */ |
| static ssize_t integration_test_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, |
| size_t size) |
| { |
| int i, ret = 0; |
| unsigned long val; |
| struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); |
| |
| ret = kstrtoul(buf, 10, &val); |
| if (ret) |
| return ret; |
| |
| if (val != 1 && val != 2) |
| return -EINVAL; |
| |
| if (!drvdata->enable) |
| return -EINVAL; |
| |
| if (val == 1) |
| val = ATBCNTRL_VAL_64; |
| else |
| val = ATBCNTRL_VAL_32; |
| CS_UNLOCK(drvdata->base); |
| writel_relaxed(0x1, drvdata->base + TPDM_ITCNTRL); |
| |
| for (i = 0; i < INTEGRATION_TEST_CYCLE; i++) |
| writel_relaxed(val, drvdata->base + TPDM_ITATBCNTRL); |
| |
| writel_relaxed(0, drvdata->base + TPDM_ITCNTRL); |
| CS_LOCK(drvdata->base); |
| return size; |
| } |
| static DEVICE_ATTR_WO(integration_test); |
| |
| static struct attribute *tpdm_attrs[] = { |
| &dev_attr_integration_test.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group tpdm_attr_grp = { |
| .attrs = tpdm_attrs, |
| }; |
| |
| static const struct attribute_group *tpdm_attr_grps[] = { |
| &tpdm_attr_grp, |
| NULL, |
| }; |
| |
| static int tpdm_probe(struct amba_device *adev, const struct amba_id *id) |
| { |
| void __iomem *base; |
| struct device *dev = &adev->dev; |
| struct coresight_platform_data *pdata; |
| struct tpdm_drvdata *drvdata; |
| struct coresight_desc desc = { 0 }; |
| |
| pdata = coresight_get_platform_data(dev); |
| if (IS_ERR(pdata)) |
| return PTR_ERR(pdata); |
| adev->dev.platform_data = pdata; |
| |
| /* driver data*/ |
| drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); |
| if (!drvdata) |
| return -ENOMEM; |
| drvdata->dev = &adev->dev; |
| dev_set_drvdata(dev, drvdata); |
| |
| base = devm_ioremap_resource(dev, &adev->res); |
| if (IS_ERR(base)) |
| return PTR_ERR(base); |
| |
| drvdata->base = base; |
| |
| /* Set up coresight component description */ |
| desc.name = coresight_alloc_device_name(&tpdm_devs, dev); |
| if (!desc.name) |
| return -ENOMEM; |
| desc.type = CORESIGHT_DEV_TYPE_SOURCE; |
| desc.subtype.source_subtype = CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS; |
| desc.ops = &tpdm_cs_ops; |
| desc.pdata = adev->dev.platform_data; |
| desc.dev = &adev->dev; |
| desc.access = CSDEV_ACCESS_IOMEM(base); |
| desc.groups = tpdm_attr_grps; |
| drvdata->csdev = coresight_register(&desc); |
| if (IS_ERR(drvdata->csdev)) |
| return PTR_ERR(drvdata->csdev); |
| |
| spin_lock_init(&drvdata->spinlock); |
| tpdm_init_default_data(drvdata); |
| /* Decrease pm refcount when probe is done.*/ |
| pm_runtime_put(&adev->dev); |
| |
| return 0; |
| } |
| |
| static void tpdm_remove(struct amba_device *adev) |
| { |
| struct tpdm_drvdata *drvdata = dev_get_drvdata(&adev->dev); |
| |
| coresight_unregister(drvdata->csdev); |
| } |
| |
| /* |
| * Different TPDM has different periph id. |
| * The difference is 0-7 bits' value. So ignore 0-7 bits. |
| */ |
| static struct amba_id tpdm_ids[] = { |
| { |
| .id = 0x000f0e00, |
| .mask = 0x000fff00, |
| }, |
| { 0, 0}, |
| }; |
| |
| static struct amba_driver tpdm_driver = { |
| .drv = { |
| .name = "coresight-tpdm", |
| .owner = THIS_MODULE, |
| .suppress_bind_attrs = true, |
| }, |
| .probe = tpdm_probe, |
| .id_table = tpdm_ids, |
| .remove = tpdm_remove, |
| }; |
| |
| module_amba_driver(tpdm_driver); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Trace, Profiling & Diagnostic Monitor driver"); |