// 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 #include #include #include #include #include #include #include #include #include #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