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