/* * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2017 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_lpddr2.c * * @brief iMX6 LPDDR2 frequency change specific file. * * @ingroup PM */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "hardware.h" static struct device *busfreq_dev; static int curr_ddr_rate; static DEFINE_SPINLOCK(freq_lock); void (*mx6_change_lpddr2_freq)(u32 ddr_freq, int bus_freq_mode) = NULL; extern unsigned int ddr_normal_rate; extern void mx6_lpddr2_freq_change(u32 freq, int bus_freq_mode); extern void imx6_up_lpddr2_freq_change(u32 freq, int bus_freq_mode); extern void imx6sll_lpddr2_freq_change(u32 freq, int bus_freq_mode); extern unsigned long save_ttbr1(void); extern void restore_ttbr1(unsigned long ttbr1); extern void mx6q_lpddr2_freq_change(u32 freq, void *ddr_settings); extern unsigned long ddr_freq_change_iram_base; extern unsigned long imx6_lpddr2_freq_change_start asm("imx6_lpddr2_freq_change_start"); extern unsigned long imx6_lpddr2_freq_change_end asm("imx6_lpddr2_freq_change_end"); extern unsigned long mx6q_lpddr2_freq_change_start asm("mx6q_lpddr2_freq_change_start"); extern unsigned long mx6q_lpddr2_freq_change_end asm("mx6q_lpddr2_freq_change_end"); extern unsigned long iram_tlb_phys_addr; struct mmdc_settings_info { u32 size; void *settings; int freq; } __aligned(8); static struct mmdc_settings_info *mmdc_settings_info; void (*mx6_change_lpddr2_freq_smp)(u32 ddr_freq, struct mmdc_settings_info *mmdc_settings_info) = NULL; static int mmdc_settings_size; static unsigned long (*mmdc_settings)[2]; static unsigned long (*iram_mmdc_settings)[2]; static unsigned long *iram_settings_size; static unsigned long *iram_ddr_freq_chage; unsigned long mmdc_timing_settings[][2] = { {0x0C, 0x0}, /* mmdc_mdcfg0 */ {0x10, 0x0}, /* mmdc_mdcfg1 */ {0x14, 0x0}, /* mmdc_mdcfg2 */ {0x18, 0x0}, /* mmdc_mdmisc */ {0x38, 0x0}, /* mmdc_mdcfg3lp */ }; #ifdef CONFIG_SMP volatile u32 *wait_for_lpddr2_freq_update; static unsigned int online_cpus; static u32 *irqs_used; void (*wfe_change_lpddr2_freq)(u32 cpuid, u32 *ddr_freq_change_done); extern void wfe_smp_freq_change(u32 cpuid, u32 *ddr_freq_change_done); extern unsigned long wfe_smp_freq_change_start asm("wfe_smp_freq_change_start"); extern unsigned long wfe_smp_freq_change_end asm("wfe_smp_freq_change_end"); extern void __iomem *imx_scu_base; static void __iomem *gic_dist_base; #endif #ifdef CONFIG_SMP static irqreturn_t wait_in_wfe_irq(int irq, void *dev_id) { u32 me; me = smp_processor_id(); #ifdef CONFIG_LOCAL_TIMERS clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &me); #endif wfe_change_lpddr2_freq(0xff << (me * 8), (u32 *)ddr_freq_change_iram_base); #ifdef CONFIG_LOCAL_TIMERS clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &me); #endif return IRQ_HANDLED; } #endif /* change the DDR frequency. */ int update_lpddr2_freq(int ddr_rate) { unsigned long ttbr1, flags; int mode = get_bus_freq_mode(); if (ddr_rate == curr_ddr_rate) return 0; printk(KERN_DEBUG "\nBus freq set to %d start...\n", ddr_rate); spin_lock_irqsave(&freq_lock, flags); /* * Flush the TLB, to ensure no TLB maintenance occurs * when DDR is in self-refresh. */ ttbr1 = save_ttbr1(); /* Now change DDR frequency. */ if (cpu_is_imx6sl()) mx6_change_lpddr2_freq(ddr_rate, (mode == BUS_FREQ_LOW || mode == BUS_FREQ_ULTRA_LOW) ? 1 : 0); else mx6_change_lpddr2_freq(ddr_rate, (mode == BUS_FREQ_LOW || mode == BUS_FREQ_AUDIO) ? 1 : 0); restore_ttbr1(ttbr1); curr_ddr_rate = ddr_rate; spin_unlock_irqrestore(&freq_lock, flags); printk(KERN_DEBUG "\nBus freq set to %d done...\n", ddr_rate); return 0; } int init_mmdc_lpddr2_settings(struct platform_device *busfreq_pdev) { unsigned long ddr_code_size; busfreq_dev = &busfreq_pdev->dev; ddr_code_size = SZ_4K; if (cpu_is_imx6sl()) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &mx6_lpddr2_freq_change, ddr_code_size); if (cpu_is_imx6sx() || cpu_is_imx6ul() || cpu_is_imx6ull()) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &imx6_up_lpddr2_freq_change, ddr_code_size); if (cpu_is_imx6sll()) mx6_change_lpddr2_freq = (void *)fncpy( (void *)ddr_freq_change_iram_base, &imx6sll_lpddr2_freq_change, ddr_code_size); curr_ddr_rate = ddr_normal_rate; return 0; } int update_lpddr2_freq_smp(int ddr_rate) { unsigned long ttbr1; int i, me = 0; #ifdef CONFIG_SMP int cpu = 0; u32 reg = 0; #endif if (ddr_rate == curr_ddr_rate) return 0; printk(KERN_DEBUG "Bus freq set to %d start...\n", ddr_rate); for (i=0; i < mmdc_settings_size; i++) { iram_mmdc_settings[i][0] = mmdc_settings[i][0]; iram_mmdc_settings[i][1] = mmdc_settings[i][1]; } mmdc_settings_info->size = mmdc_settings_size; mmdc_settings_info->settings = iram_mmdc_settings; mmdc_settings_info->freq = curr_ddr_rate; /* ensure that all Cores are in WFE. */ local_irq_disable(); #ifdef CONFIG_SMP me = smp_processor_id(); /* Make sure all the online cores are active */ while (1) { bool not_exited_busfreq = false; for_each_online_cpu(cpu) { reg = __raw_readl(imx_scu_base + 0x08); if (reg & (0x02 << (cpu * 8))) not_exited_busfreq = true; } if (!not_exited_busfreq) break; } wmb(); *wait_for_lpddr2_freq_update = 1; dsb(); online_cpus = readl_relaxed(imx_scu_base + 0x08); for_each_online_cpu(cpu) { *((char *)(&online_cpus) + (u8)cpu) = 0x02; if (cpu != me) { reg = 1 << (irqs_used[cpu] % 32); writel_relaxed(reg, gic_dist_base + GIC_DIST_PENDING_SET + (irqs_used[cpu] / 32) * 4); } } /* Wait for the other active CPUs to idle */ while (1) { reg = 0; reg = readl_relaxed(imx_scu_base + 0x08); reg |= (0x02 << (me * 8)); if (reg == online_cpus) break; } #endif /* Ensure iram_tlb_phys_addr is flushed to DDR. */ __cpuc_flush_dcache_area(&iram_tlb_phys_addr, sizeof(iram_tlb_phys_addr)); outer_clean_range(__pa(&iram_tlb_phys_addr), __pa(&iram_tlb_phys_addr + 1)); /* * Flush the TLB, to ensure no TLB maintenance occurs * when DDR is in self-refresh. */ ttbr1 = save_ttbr1(); curr_ddr_rate = ddr_rate; /* Now change DDR frequency. */ mx6_change_lpddr2_freq_smp(ddr_rate, mmdc_settings_info); restore_ttbr1(ttbr1); #ifdef CONFIG_SMP wmb(); /* DDR frequency change is done . */ *wait_for_lpddr2_freq_update = 0; dsb(); /* wake up all the cores. */ sev(); #endif local_irq_enable(); printk(KERN_DEBUG "Bus freq set to %d done! cpu=%d\n", ddr_rate, me); return 0; } int init_mmdc_lpddr2_settings_mx6q(struct platform_device *busfreq_pdev) { struct device *dev = &busfreq_pdev->dev; unsigned long ddr_code_size = 0; unsigned long wfe_code_size = 0; struct device_node *node; void __iomem *mmdc_base; int i; #ifdef CONFIG_SMP struct irq_data *d; u32 cpu; int err; #endif node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc"); if (!node) { printk(KERN_ERR "failed to find mmdc device tree data!\n"); return -EINVAL; } mmdc_base = of_iomap(node, 0); if (!mmdc_base) { dev_err(dev, "unable to map mmdc registers\n"); return -EINVAL; } mmdc_settings_size = ARRAY_SIZE(mmdc_timing_settings); mmdc_settings = kmalloc((mmdc_settings_size * 8), GFP_KERNEL); memcpy(mmdc_settings, mmdc_timing_settings, sizeof(mmdc_timing_settings)); #ifdef CONFIG_SMP node = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-gic"); if (!node) { printk(KERN_ERR "failed to find imx6q-a9-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_used = devm_kzalloc(dev, sizeof(u32) * num_present_cpus(), GFP_KERNEL); for_each_online_cpu(cpu) { int irq = platform_get_irq(busfreq_pdev, cpu); err = request_irq(irq, wait_in_wfe_irq, IRQF_PERCPU, "mmdc_1", 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; } d = irq_get_irq_data(irq); irqs_used[cpu] = d->hwirq + 32; } /* Stoange_iram_basee the variable used to communicate between cores in * a non-cacheable IRAM area */ wait_for_lpddr2_freq_update = (u32 *)ddr_freq_change_iram_base; wfe_code_size = (&wfe_smp_freq_change_end - &wfe_smp_freq_change_start) *4; wfe_change_lpddr2_freq = (void *)fncpy((void *)ddr_freq_change_iram_base + 0x8, &wfe_smp_freq_change, wfe_code_size); #endif iram_settings_size = (void *)ddr_freq_change_iram_base + wfe_code_size + 0x8; iram_mmdc_settings = (void *)iram_settings_size + sizeof(*mmdc_settings_info); iram_ddr_freq_chage = (void *)iram_mmdc_settings + (mmdc_settings_size * 8) + 0x8; mmdc_settings_info = (struct mmdc_settings_info *)iram_settings_size; ddr_code_size = (&mx6q_lpddr2_freq_change_end -&mx6q_lpddr2_freq_change_start) *4; mx6_change_lpddr2_freq_smp = (void *)fncpy(iram_ddr_freq_chage, &mx6q_lpddr2_freq_change, ddr_code_size); /* save initial mmdc boot timing settings */ for (i=0; i < mmdc_settings_size; i++) mmdc_settings[i][1] = readl_relaxed(mmdc_base + mmdc_settings[i][0]); curr_ddr_rate = ddr_normal_rate; return 0; }