// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2018 Linaro Limited, All rights reserved. * Author: Mike Leach */ #include #include "coresight-cti.h" /** * CTI devices can be associated with a PE, or be connected to CoreSight * hardware. We have a list of all CTIs irrespective of CPU bound or * otherwise. * * We assume that the non-CPU CTIs are always powered as we do with sinks etc. * * We leave the client to figure out if all the CTIs are interconnected with * the same CTM, in general this is the case but does not always have to be. */ /* net of CTI devices connected via CTM */ LIST_HEAD(ect_net); /* protect the list */ static DEFINE_MUTEX(ect_mutex); #define csdev_to_cti_drvdata(csdev) \ dev_get_drvdata(csdev->dev.parent) /* * CTI naming. CTI bound to cores will have the name cti_cpu where * N is the CPU ID. System CTIs will have the name cti_sys where I * is an index allocated by order of discovery. * * CTI device name list - for CTI not bound to cores. */ DEFINE_CORESIGHT_DEVLIST(cti_sys_devs, "cti_sys"); /* write set of regs to hardware - call with spinlock claimed */ void cti_write_all_hw_regs(struct cti_drvdata *drvdata) { struct cti_config *config = &drvdata->config; int i; CS_UNLOCK(drvdata->base); /* disable CTI before writing registers */ writel_relaxed(0, drvdata->base + CTICONTROL); /* write the CTI trigger registers */ for (i = 0; i < config->nr_trig_max; i++) { writel_relaxed(config->ctiinen[i], drvdata->base + CTIINEN(i)); writel_relaxed(config->ctiouten[i], drvdata->base + CTIOUTEN(i)); } /* other regs */ writel_relaxed(config->ctigate, drvdata->base + CTIGATE); writel_relaxed(config->asicctl, drvdata->base + ASICCTL); writel_relaxed(config->ctiappset, drvdata->base + CTIAPPSET); /* re-enable CTI */ writel_relaxed(1, drvdata->base + CTICONTROL); CS_LOCK(drvdata->base); } static void cti_enable_hw_smp_call(void *info) { struct cti_drvdata *drvdata = info; cti_write_all_hw_regs(drvdata); } /* write regs to hardware and enable */ static int cti_enable_hw(struct cti_drvdata *drvdata) { struct cti_config *config = &drvdata->config; struct device *dev = &drvdata->csdev->dev; int rc = 0; pm_runtime_get_sync(dev->parent); spin_lock(&drvdata->spinlock); /* no need to do anything if enabled or unpowered*/ if (config->hw_enabled || !config->hw_powered) goto cti_state_unchanged; /* claim the device */ rc = coresight_claim_device(drvdata->base); if (rc) goto cti_err_not_enabled; if (drvdata->ctidev.cpu >= 0) { rc = smp_call_function_single(drvdata->ctidev.cpu, cti_enable_hw_smp_call, drvdata, 1); if (rc) goto cti_err_not_enabled; } else { cti_write_all_hw_regs(drvdata); } config->hw_enabled = true; atomic_inc(&drvdata->config.enable_req_count); spin_unlock(&drvdata->spinlock); return rc; cti_state_unchanged: atomic_inc(&drvdata->config.enable_req_count); /* cannot enable due to error */ cti_err_not_enabled: spin_unlock(&drvdata->spinlock); pm_runtime_put(dev->parent); return rc; } /* disable hardware */ static int cti_disable_hw(struct cti_drvdata *drvdata) { struct cti_config *config = &drvdata->config; struct device *dev = &drvdata->csdev->dev; spin_lock(&drvdata->spinlock); /* check refcount - disable on 0 */ if (atomic_dec_return(&drvdata->config.enable_req_count) > 0) goto cti_not_disabled; /* no need to do anything if disabled or cpu unpowered */ if (!config->hw_enabled || !config->hw_powered) goto cti_not_disabled; CS_UNLOCK(drvdata->base); /* disable CTI */ writel_relaxed(0, drvdata->base + CTICONTROL); config->hw_enabled = false; coresight_disclaim_device_unlocked(drvdata->base); CS_LOCK(drvdata->base); spin_unlock(&drvdata->spinlock); pm_runtime_put(dev); return 0; /* not disabled this call */ cti_not_disabled: spin_unlock(&drvdata->spinlock); return 0; } void cti_write_single_reg(struct cti_drvdata *drvdata, int offset, u32 value) { CS_UNLOCK(drvdata->base); writel_relaxed(value, drvdata->base + offset); CS_LOCK(drvdata->base); } void cti_write_intack(struct device *dev, u32 ackval) { struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); struct cti_config *config = &drvdata->config; spin_lock(&drvdata->spinlock); /* write if enabled */ if (cti_active(config)) cti_write_single_reg(drvdata, CTIINTACK, ackval); spin_unlock(&drvdata->spinlock); } /* * Look at the HW DEVID register for some of the HW settings. * DEVID[15:8] - max number of in / out triggers. */ #define CTI_DEVID_MAXTRIGS(devid_val) ((int) BMVAL(devid_val, 8, 15)) /* DEVID[19:16] - number of CTM channels */ #define CTI_DEVID_CTMCHANNELS(devid_val) ((int) BMVAL(devid_val, 16, 19)) static void cti_set_default_config(struct device *dev, struct cti_drvdata *drvdata) { struct cti_config *config = &drvdata->config; u32 devid; devid = readl_relaxed(drvdata->base + CORESIGHT_DEVID); config->nr_trig_max = CTI_DEVID_MAXTRIGS(devid); /* * no current hardware should exceed this, but protect the driver * in case of fault / out of spec hw */ if (config->nr_trig_max > CTIINOUTEN_MAX) { dev_warn_once(dev, "Limiting HW MaxTrig value(%d) to driver max(%d)\n", config->nr_trig_max, CTIINOUTEN_MAX); config->nr_trig_max = CTIINOUTEN_MAX; } config->nr_ctm_channels = CTI_DEVID_CTMCHANNELS(devid); /* Most regs default to 0 as zalloc'ed except...*/ config->trig_filter_enable = true; config->ctigate = GENMASK(config->nr_ctm_channels - 1, 0); atomic_set(&config->enable_req_count, 0); } /* * Add a connection entry to the list of connections for this * CTI device. */ int cti_add_connection_entry(struct device *dev, struct cti_drvdata *drvdata, struct cti_trig_con *tc, struct coresight_device *csdev, const char *assoc_dev_name) { struct cti_device *cti_dev = &drvdata->ctidev; tc->con_dev = csdev; /* * Prefer actual associated CS device dev name to supplied value - * which is likely to be node name / other conn name. */ if (csdev) tc->con_dev_name = dev_name(&csdev->dev); else if (assoc_dev_name != NULL) { tc->con_dev_name = devm_kstrdup(dev, assoc_dev_name, GFP_KERNEL); if (!tc->con_dev_name) return -ENOMEM; } list_add_tail(&tc->node, &cti_dev->trig_cons); cti_dev->nr_trig_con++; /* add connection usage bit info to overall info */ drvdata->config.trig_in_use |= tc->con_in->used_mask; drvdata->config.trig_out_use |= tc->con_out->used_mask; return 0; } /* create a trigger connection with appropriately sized signal groups */ struct cti_trig_con *cti_allocate_trig_con(struct device *dev, int in_sigs, int out_sigs) { struct cti_trig_con *tc = NULL; struct cti_trig_grp *in = NULL, *out = NULL; tc = devm_kzalloc(dev, sizeof(struct cti_trig_con), GFP_KERNEL); if (!tc) return tc; in = devm_kzalloc(dev, offsetof(struct cti_trig_grp, sig_types[in_sigs]), GFP_KERNEL); if (!in) return NULL; out = devm_kzalloc(dev, offsetof(struct cti_trig_grp, sig_types[out_sigs]), GFP_KERNEL); if (!out) return NULL; tc->con_in = in; tc->con_out = out; tc->con_in->nr_sigs = in_sigs; tc->con_out->nr_sigs = out_sigs; return tc; } /* * Add a default connection if nothing else is specified. * single connection based on max in/out info, no assoc device */ int cti_add_default_connection(struct device *dev, struct cti_drvdata *drvdata) { int ret = 0; int n_trigs = drvdata->config.nr_trig_max; u32 n_trig_mask = GENMASK(n_trigs - 1, 0); struct cti_trig_con *tc = NULL; /* * Assume max trigs for in and out, * all used, default sig types allocated */ tc = cti_allocate_trig_con(dev, n_trigs, n_trigs); if (!tc) return -ENOMEM; tc->con_in->used_mask = n_trig_mask; tc->con_out->used_mask = n_trig_mask; ret = cti_add_connection_entry(dev, drvdata, tc, NULL, "default"); return ret; } /** cti channel api **/ /* attach/detach channel from trigger - write through if enabled. */ int cti_channel_trig_op(struct device *dev, enum cti_chan_op op, enum cti_trig_dir direction, u32 channel_idx, u32 trigger_idx) { struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); struct cti_config *config = &drvdata->config; u32 trig_bitmask; u32 chan_bitmask; u32 reg_value; int reg_offset; /* ensure indexes in range */ if ((channel_idx >= config->nr_ctm_channels) || (trigger_idx >= config->nr_trig_max)) return -EINVAL; trig_bitmask = BIT(trigger_idx); /* ensure registered triggers and not out filtered */ if (direction == CTI_TRIG_IN) { if (!(trig_bitmask & config->trig_in_use)) return -EINVAL; } else { if (!(trig_bitmask & config->trig_out_use)) return -EINVAL; if ((config->trig_filter_enable) && (config->trig_out_filter & trig_bitmask)) return -EINVAL; } /* update the local register values */ chan_bitmask = BIT(channel_idx); reg_offset = (direction == CTI_TRIG_IN ? CTIINEN(trigger_idx) : CTIOUTEN(trigger_idx)); spin_lock(&drvdata->spinlock); /* read - modify write - the trigger / channel enable value */ reg_value = direction == CTI_TRIG_IN ? config->ctiinen[trigger_idx] : config->ctiouten[trigger_idx]; if (op == CTI_CHAN_ATTACH) reg_value |= chan_bitmask; else reg_value &= ~chan_bitmask; /* write local copy */ if (direction == CTI_TRIG_IN) config->ctiinen[trigger_idx] = reg_value; else config->ctiouten[trigger_idx] = reg_value; /* write through if enabled */ if (cti_active(config)) cti_write_single_reg(drvdata, reg_offset, reg_value); spin_unlock(&drvdata->spinlock); return 0; } int cti_channel_gate_op(struct device *dev, enum cti_chan_gate_op op, u32 channel_idx) { struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); struct cti_config *config = &drvdata->config; u32 chan_bitmask; u32 reg_value; int err = 0; if (channel_idx >= config->nr_ctm_channels) return -EINVAL; chan_bitmask = BIT(channel_idx); spin_lock(&drvdata->spinlock); reg_value = config->ctigate; switch (op) { case CTI_GATE_CHAN_ENABLE: reg_value |= chan_bitmask; break; case CTI_GATE_CHAN_DISABLE: reg_value &= ~chan_bitmask; break; default: err = -EINVAL; break; } if (err == 0) { config->ctigate = reg_value; if (cti_active(config)) cti_write_single_reg(drvdata, CTIGATE, reg_value); } spin_unlock(&drvdata->spinlock); return err; } int cti_channel_setop(struct device *dev, enum cti_chan_set_op op, u32 channel_idx) { struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); struct cti_config *config = &drvdata->config; u32 chan_bitmask; u32 reg_value; u32 reg_offset; int err = 0; if (channel_idx >= config->nr_ctm_channels) return -EINVAL; chan_bitmask = BIT(channel_idx); spin_lock(&drvdata->spinlock); reg_value = config->ctiappset; switch (op) { case CTI_CHAN_SET: config->ctiappset |= chan_bitmask; reg_value = config->ctiappset; reg_offset = CTIAPPSET; break; case CTI_CHAN_CLR: config->ctiappset &= ~chan_bitmask; reg_value = chan_bitmask; reg_offset = CTIAPPCLEAR; break; case CTI_CHAN_PULSE: config->ctiappset &= ~chan_bitmask; reg_value = chan_bitmask; reg_offset = CTIAPPPULSE; break; default: err = -EINVAL; break; } if ((err == 0) && cti_active(config)) cti_write_single_reg(drvdata, reg_offset, reg_value); spin_unlock(&drvdata->spinlock); return err; } /* * Look for a matching connection device name in the list of connections. * If found then swap in the csdev name, set trig con association pointer * and return found. */ static bool cti_match_fixup_csdev(struct cti_device *ctidev, const char *node_name, struct coresight_device *csdev) { struct cti_trig_con *tc; list_for_each_entry(tc, &ctidev->trig_cons, node) { if (tc->con_dev_name) { if (!strcmp(node_name, tc->con_dev_name)) { /* match: so swap in csdev name & dev */ tc->con_dev_name = dev_name(&csdev->dev); tc->con_dev = csdev; return true; } } } return false; } /* * Search the cti list to add an associated CTI into the supplied CS device * This will set the association if CTI declared before the CS device. * (called from coresight_register() with coresight_mutex locked). */ void cti_add_assoc_to_csdev(struct coresight_device *csdev) { struct cti_drvdata *ect_item; struct cti_device *ctidev; const char *node_name = NULL; /* protect the list */ mutex_lock(&ect_mutex); /* exit if current is an ECT device.*/ if ((csdev->type == CORESIGHT_DEV_TYPE_ECT) || list_empty(&ect_net)) goto cti_add_done; /* if we didn't find the csdev previously we used the fwnode name */ node_name = cti_plat_get_node_name(dev_fwnode(csdev->dev.parent)); if (!node_name) goto cti_add_done; /* for each CTI in list... */ list_for_each_entry(ect_item, &ect_net, node) { ctidev = &ect_item->ctidev; if (cti_match_fixup_csdev(ctidev, node_name, csdev)) { /* * if we found a matching csdev then update the ECT * association pointer for the device with this CTI. */ csdev->ect_dev = ect_item->csdev; break; } } cti_add_done: mutex_unlock(&ect_mutex); } EXPORT_SYMBOL_GPL(cti_add_assoc_to_csdev); /* * Removing the associated devices is easier. * A CTI will not have a value for csdev->ect_dev. */ void cti_remove_assoc_from_csdev(struct coresight_device *csdev) { struct cti_drvdata *ctidrv; struct cti_trig_con *tc; struct cti_device *ctidev; mutex_lock(&ect_mutex); if (csdev->ect_dev) { ctidrv = csdev_to_cti_drvdata(csdev->ect_dev); ctidev = &ctidrv->ctidev; list_for_each_entry(tc, &ctidev->trig_cons, node) { if (tc->con_dev == csdev->ect_dev) { tc->con_dev = NULL; break; } } csdev->ect_dev = NULL; } mutex_unlock(&ect_mutex); } EXPORT_SYMBOL_GPL(cti_remove_assoc_from_csdev); /* * Update the cross references where the associated device was found * while we were building the connection info. This will occur if the * assoc device was registered before the CTI. */ static void cti_update_conn_xrefs(struct cti_drvdata *drvdata) { struct cti_trig_con *tc; struct cti_device *ctidev = &drvdata->ctidev; list_for_each_entry(tc, &ctidev->trig_cons, node) { if (tc->con_dev) /* set tc->con_dev->ect_dev */ coresight_set_assoc_ectdev_mutex(tc->con_dev, drvdata->csdev); } } static void cti_remove_conn_xrefs(struct cti_drvdata *drvdata) { struct cti_trig_con *tc; struct cti_device *ctidev = &drvdata->ctidev; list_for_each_entry(tc, &ctidev->trig_cons, node) { if (tc->con_dev) { coresight_set_assoc_ectdev_mutex(tc->con_dev, NULL); } } } /** cti ect operations **/ int cti_enable(struct coresight_device *csdev) { struct cti_drvdata *drvdata = csdev_to_cti_drvdata(csdev); return cti_enable_hw(drvdata); } int cti_disable(struct coresight_device *csdev) { struct cti_drvdata *drvdata = csdev_to_cti_drvdata(csdev); return cti_disable_hw(drvdata); } const struct coresight_ops_ect cti_ops_ect = { .enable = cti_enable, .disable = cti_disable, }; const struct coresight_ops cti_ops = { .ect_ops = &cti_ops_ect, }; /* * Free up CTI specific resources * called by dev->release, need to call down to underlying csdev release. */ static void cti_device_release(struct device *dev) { struct cti_drvdata *drvdata = dev_get_drvdata(dev->parent); struct cti_drvdata *ect_item, *ect_tmp; mutex_lock(&ect_mutex); cti_remove_conn_xrefs(drvdata); /* remove from the list */ list_for_each_entry_safe(ect_item, ect_tmp, &ect_net, node) { if (ect_item == drvdata) { list_del(&ect_item->node); break; } } mutex_unlock(&ect_mutex); if (drvdata->csdev_release) drvdata->csdev_release(dev); } static int cti_probe(struct amba_device *adev, const struct amba_id *id) { int ret = 0; void __iomem *base; struct device *dev = &adev->dev; struct cti_drvdata *drvdata = NULL; struct coresight_desc cti_desc; struct coresight_platform_data *pdata = NULL; struct resource *res = &adev->res; /* driver data*/ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); if (!drvdata) { ret = -ENOMEM; dev_info(dev, "%s, mem err\n", __func__); goto err_out; } /* Validity for the resource is already checked by the AMBA core */ base = devm_ioremap_resource(dev, res); if (IS_ERR(base)) { ret = PTR_ERR(base); dev_err(dev, "%s, remap err\n", __func__); goto err_out; } drvdata->base = base; dev_set_drvdata(dev, drvdata); /* default CTI device info */ drvdata->ctidev.cpu = -1; drvdata->ctidev.nr_trig_con = 0; drvdata->ctidev.ctm_id = 0; INIT_LIST_HEAD(&drvdata->ctidev.trig_cons); spin_lock_init(&drvdata->spinlock); /* initialise CTI driver config values */ cti_set_default_config(dev, drvdata); pdata = coresight_cti_get_platform_data(dev); if (IS_ERR(pdata)) { dev_err(dev, "coresight_cti_get_platform_data err\n"); ret = PTR_ERR(pdata); goto err_out; } /* default to powered - could change on PM notifications */ drvdata->config.hw_powered = true; /* set up device name - will depend if cpu bound or otherwise */ if (drvdata->ctidev.cpu >= 0) cti_desc.name = devm_kasprintf(dev, GFP_KERNEL, "cti_cpu%d", drvdata->ctidev.cpu); else cti_desc.name = coresight_alloc_device_name(&cti_sys_devs, dev); if (!cti_desc.name) { ret = -ENOMEM; goto err_out; } /* set up coresight component description */ cti_desc.pdata = pdata; cti_desc.type = CORESIGHT_DEV_TYPE_ECT; cti_desc.subtype.ect_subtype = CORESIGHT_DEV_SUBTYPE_ECT_CTI; cti_desc.ops = &cti_ops; cti_desc.groups = coresight_cti_groups; cti_desc.dev = dev; drvdata->csdev = coresight_register(&cti_desc); if (IS_ERR(drvdata->csdev)) { ret = PTR_ERR(drvdata->csdev); goto err_out; } /* add to list of CTI devices */ mutex_lock(&ect_mutex); list_add(&drvdata->node, &ect_net); /* set any cross references */ cti_update_conn_xrefs(drvdata); mutex_unlock(&ect_mutex); /* set up release chain */ drvdata->csdev_release = drvdata->csdev->dev.release; drvdata->csdev->dev.release = cti_device_release; /* all done - dec pm refcount */ pm_runtime_put(&adev->dev); dev_info(&drvdata->csdev->dev, "CTI initialized\n"); return 0; err_out: return ret; } static struct amba_cs_uci_id uci_id_cti[] = { { /* CTI UCI data */ .devarch = 0x47701a14, /* CTI v2 */ .devarch_mask = 0xfff0ffff, .devtype = 0x00000014, /* maj(0x4-debug) min(0x1-ECT) */ } }; static const struct amba_id cti_ids[] = { CS_AMBA_ID(0x000bb906), /* Coresight CTI (SoC 400), C-A72, C-A57 */ CS_AMBA_ID(0x000bb922), /* CTI - C-A8 */ CS_AMBA_ID(0x000bb9a8), /* CTI - C-A53 */ CS_AMBA_ID(0x000bb9aa), /* CTI - C-A73 */ CS_AMBA_UCI_ID(0x000bb9da, uci_id_cti), /* CTI - C-A35 */ CS_AMBA_UCI_ID(0x000bb9ed, uci_id_cti), /* Coresight CTI (SoC 600) */ { 0, 0}, }; static struct amba_driver cti_driver = { .drv = { .name = "coresight-cti", .owner = THIS_MODULE, .suppress_bind_attrs = true, }, .probe = cti_probe, .id_table = cti_ids, }; builtin_amba_driver(cti_driver);