busfreq_lpddr2.c 10 KB
/*
 * 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 <asm/cacheflush.h>
#include <asm/fncpy.h>
#include <asm/io.h>
#include <asm/mach/map.h>
#include <asm/mach-types.h>
#include <asm/tlb.h>
#include <linux/busfreq-imx.h>
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/genalloc.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/slab.h>

#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 *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() ||
	    cpu_is_imx6ulz())
		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(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(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(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;
}