// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018 Linaro Limited, All rights reserved.
* Author: Mike Leach <mike.leach@linaro.org>
*/
#include <linux/amba/bus.h>
#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/coresight.h>
#include <linux/cpu_pm.h>
#include <linux/cpuhotplug.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/spinlock.h>
#include "coresight-priv.h"
#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 */
static LIST_HEAD(ect_net);
/* protect the list */
static DEFINE_MUTEX(ect_mutex);
#define csdev_to_cti_drvdata(csdev) \
dev_get_drvdata(csdev->dev.parent)
/* power management handling */
static int nr_cti_cpu;
/* quick lookup list for CPU bound CTIs when power handling */
static struct cti_drvdata *cti_cpu_drvdata[NR_CPUS];
/*
* CTI naming. CTI bound to cores will have the name cti_cpu<N> where
* N is the CPU ID. System CTIs will have the name cti_sys<I> 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;
}
/* re-enable CTI on CPU when using CPU hotplug */
static void cti_cpuhp_enable_hw(struct cti_drvdata *drvdata)
{
struct cti_config *config = &drvdata->config;
struct device *dev = &drvdata->csdev->dev;
pm_runtime_get_sync(dev->parent);
spin_lock(&drvdata->spinlock);
config->hw_powered = true;
/* no need to do anything if no enable request */
if (!atomic_read(&drvdata->config.enable_req_count))
goto cti_hp_not_enabled;
/* try to claim the device */
if (coresight_claim_device(drvdata->base))
goto cti_hp_not_enabled;
cti_write_all_hw_regs(drvdata);
config->hw_enabled = true;
spin_unlock(&drvdata->spinlock);
return;
/* did not re-enable due to no claim / no request */
cti_hp_not_enabled:
spin_unlock(&drvdata->spinlock);
pm_runtime_put(dev->parent);
}
/* 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