| /* Copyright (c) 2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #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; |
| }; |
| |
| static ssize_t enable_tgu(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) |
| 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); |
| TGU_LOCK(drvdata); |
| spin_unlock(&drvdata->spinlock); |
| |
| pm_runtime_put(drvdata->dev); |
| dev_dbg(dev, "Coresight-TGU disabled\n"); |
| } |
| |
| TGU_LOCK(drvdata); |
| spin_unlock(&drvdata->spinlock); |
| return size; |
| } |
| static DEVICE_ATTR(enable_tgu, 0200, NULL, enable_tgu); |
| |
| static ssize_t reset_tgu(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) |
| 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(reset_tgu, 0200, NULL, reset_tgu); |
| |
| static ssize_t set_group(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"); |
| 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(set_group, 0200, NULL, set_group); |
| |
| static ssize_t tgu_set_condition(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"); |
| 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(set_condition, 0200, NULL, tgu_set_condition); |
| |
| static ssize_t tgu_set_select(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"); |
| 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(set_select, 0200, NULL, tgu_set_select); |
| |
| static ssize_t tgu_set_timers(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->select_refcnt >= MAX_TIMER_COUNTER_SETS) { |
| dev_err(drvdata->dev, " Too many groups are being configured"); |
| 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(set_timer, 0200, NULL, tgu_set_timers); |
| |
| static ssize_t tgu_set_counters(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"); |
| 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(set_counter, 0200, NULL, tgu_set_counters); |
| |
| 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; |
| |
| pdata = of_get_coresight_platform_data(dev, adev->dev.of_node); |
| 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 = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); |
| if (!desc) { |
| ret = -ENOMEM; |
| goto err; |
| } |
| desc->type = CORESIGHT_DEV_TYPE_NONE; |
| 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 v2"); |
| MODULE_DESCRIPTION("CoreSight TGU driver"); |