busfreq_optee.c 6.71 KB
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright 2018 NXP
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

/*!
 * @file   busfreq_optee.c
 *
 * @brief  iMX.6 and i.MX7 Bus Frequency change.\n
 *         Call OPTEE busfreq function regardless memory type and device.
 *
 * @ingroup PM
 */
#include <asm/fncpy.h>
#include <linux/busfreq-imx.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/platform_device.h>

#include "hardware.h"
#include "smc_sip.h"


extern unsigned int ddr_normal_rate;
static int curr_ddr_rate;

#ifdef CONFIG_SMP
/*
 * External declaration
 */
extern void imx_smp_wfe_optee(u32 cpuid, u32 status_addr);
extern unsigned long imx_smp_wfe_start asm("imx_smp_wfe_optee");
extern unsigned long imx_smp_wfe_end asm("imx_smp_wfe_optee_end");

extern unsigned long ddr_freq_change_iram_base;


/**
 * @brief  Definition of the synchronization status
 *         structure used to control to CPUs status
 *         and on-going frequency change
 */
struct busfreq_sync {
	uint32_t change_ongoing;
	uint32_t wfe_status[NR_CPUS];
} __aligned(8);

static struct busfreq_sync *pSync;

static void (*wfe_change_freq)(uint32_t *wfe_status, uint32_t *freq_done);

static uint32_t *irqs_for_wfe;
static void __iomem *gic_dist_base;

/**
 * @brief  Switch all active cores, except the one changing the
 *         bus frequency, in WFE mode until completion of the
 *         frequency change
 *
 * @param[in]  irq     Interrupt ID - not used
 * @param[in]  dev_id  Client data - not used
 *
 * @retval IRQ_HANDLED  Interrupt handled
 */
static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id)
{
	uint32_t me;

	me = smp_processor_id();
#ifdef CONFIG_LOCAL_TIMERS
	clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER,
		&me);
#endif

	wfe_change_freq(&pSync->wfe_status[me], &pSync->change_ongoing);

#ifdef CONFIG_LOCAL_TIMERS
	clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT,
		&me);
#endif

	return IRQ_HANDLED;
}
#endif

/**
 * @brief   Request OPTEE OS to change the memory bus frequency
 *          to \a ddr_rate value
 *
 * @param[in]  rate  Bus Frequency
 *
 * @retval 0  Success
 */
int update_freq_optee(int ddr_rate)
{
	struct arm_smccc_res res;

	uint32_t me      = 0;
	uint32_t dll_off = 0;
	int      mode    = get_bus_freq_mode();

#ifdef CONFIG_SMP
	uint32_t reg         = 0;
	uint32_t cpu         = 0;
	uint32_t online_cpus = 0;
	uint32_t all_cpus    = 0;
#endif

	pr_info("\nBusfreq OPTEE set from %d to %d start...\n",
			curr_ddr_rate, ddr_rate);

	if (ddr_rate == curr_ddr_rate)
		return 0;

	if (cpu_is_imx6()) {
		if ((mode == BUS_FREQ_LOW) || (mode == BUS_FREQ_AUDIO))
			dll_off = 1;
	}

	local_irq_disable();

#ifdef CONFIG_SMP
	me = smp_processor_id();

	/* Make sure all the online cores to be active */
	do {
		all_cpus = 0;

		for_each_online_cpu(cpu)
			all_cpus |= (pSync->wfe_status[cpu] << cpu);
	} while (all_cpus);

	pSync->change_ongoing = 1;
	dsb();

	for_each_online_cpu(cpu) {
		if (cpu != me) {
			online_cpus |= (1 << cpu);
			/* Set the interrupt to be pending in the GIC. */
			reg = 1 << (irqs_for_wfe[cpu] % 32);
			writel_relaxed(reg, gic_dist_base + GIC_DIST_PENDING_SET
				+ (irqs_for_wfe[cpu] / 32) * 4);
		}
	}

	/* Wait for all active CPUs to be in WFE */
	do {
		all_cpus = 0;

		for_each_online_cpu(cpu)
			all_cpus |= (pSync->wfe_status[cpu] << cpu);
	} while (all_cpus != online_cpus);

#endif

	/* Now we can change the DDR frequency. */
	/* Call the TEE SiP */
	arm_smccc_smc(OPTEE_SMC_FAST_CALL_SIP_VAL(IMX_SIP_BUSFREQ_CHANGE),
				ddr_rate, dll_off, 0, 0, 0, 0, 0, &res);

	curr_ddr_rate = ddr_rate;

#ifdef CONFIG_SMP
	/* DDR frequency change is done */
	pSync->change_ongoing = 0;
	dsb();

	/* wake up all the cores. */
	sev();
#endif

	local_irq_enable();

	pr_info("Busfreq OPTEE set to %d done! cpu=%d\n",
			ddr_rate, me);

	return 0;
}

#ifdef CONFIG_SMP
static int init_freq_optee_smp(struct platform_device *busfreq_pdev)
{
	struct device_node *node = 0;
	struct device *dev = &busfreq_pdev->dev;
	uint32_t cpu;
	int err;
	int irq;
	struct irq_data *irq_data;
	unsigned long wfe_iram_base;

	if (cpu_is_imx6()) {
		node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-gic");
		if (!node) {
			if (cpu_is_imx6q())
				pr_debug("failed to find imx6q-a9-gic device tree data!\n");

			return -EINVAL;
		}
	} else {
		node = of_find_compatible_node(NULL, NULL, "arm,cortex-a7-gic");
		if (!node) {
			pr_debug("failed to find imx7d-a7-gic device tree data!\n");
			return -EINVAL;
		}
	}

	gic_dist_base = of_iomap(node, 0);
	WARN(!gic_dist_base, "unable to map gic dist registers\n");

	irqs_for_wfe = devm_kzalloc(dev, sizeof(uint32_t) * num_present_cpus(),
					GFP_KERNEL);

	for_each_online_cpu(cpu) {
		/*
		 * set up a reserved interrupt to get all
		 * the active cores into a WFE state
		 * before changing the DDR frequency.
		 */
		irq = platform_get_irq(busfreq_pdev, cpu);

		if (cpu_is_imx6()) {
			err = request_irq(irq, wait_in_wfe_irq,
				IRQF_PERCPU, "mmdc_1", NULL);
		} else {
			err = request_irq(irq, wait_in_wfe_irq,
				IRQF_PERCPU, "ddrc", NULL);
		}

		if (err) {
			dev_err(dev,
				"Busfreq:request_irq failed %d, err = %d\n",
				irq, err);
			return err;
		}

		err = irq_set_affinity(irq, cpumask_of(cpu));
		if (err) {
			dev_err(dev,
				"Busfreq: Cannot set irq affinity irq=%d,\n",
				irq);
			return err;
		}

		irq_data = irq_get_irq_data(irq);
		irqs_for_wfe[cpu] = irq_data->hwirq + 32;
	}

	/* Store the variable used to communicate between cores */
	pSync = (void *)ddr_freq_change_iram_base;

	memset(pSync, 0, sizeof(*pSync));

	wfe_iram_base = ddr_freq_change_iram_base + sizeof(*pSync);

	if (wfe_iram_base & (FNCPY_ALIGN - 1))
		wfe_iram_base += FNCPY_ALIGN -
				((uintptr_t)wfe_iram_base % (FNCPY_ALIGN));

	wfe_change_freq = (void *)fncpy((void *)wfe_iram_base,
				&imx_smp_wfe_optee,
				((&imx_smp_wfe_end -&imx_smp_wfe_start) *4));

	return 0;

}

int init_freq_optee(struct platform_device *busfreq_pdev)
{
	int err = -EINVAL;
	struct device *dev = &busfreq_pdev->dev;

	if (num_present_cpus() <= 1) {
		wfe_change_freq = NULL;

		/* Allocate the cores synchronization variables (not used) */
		pSync = devm_kzalloc(dev, sizeof(*pSync), GFP_KERNEL);

		if (pSync)
			err = 0;
	} else {
		err = init_freq_optee_smp(busfreq_pdev);
	}

	if (err == 0)
		curr_ddr_rate = ddr_normal_rate;

	return err;
}
#else
int init_freq_optee(struct platform_device *busfreq_pdev)
{
	curr_ddr_rate = ddr_normal_rate;
	return 0;
}
#endif