aboutsummaryrefslogblamecommitdiffstats
path: root/arch/arm/mach-imx/pm-imx6.c
blob: f3675cccbb62833bc8f08495e6a63290bdbfd116 (plain) (tree)
1
2
  
                                                    














                                                                     
                         


                           


                                              



                           
                    





































                                                    








                                                   


                                 
                               
                                



                                                         
                     
 
                                 


                         


                                                                  



















                                             




                                                            
                                                            










                                                        



























                                                      


                                        
 


























                                                       




                                       











                                                   

























                                                                     



                                                   

                                       
                                                       


                                                            




                                           
                                    
                                                          
                                                         



                                                            










                                                  
                                    
                                                          
                                                         



                                                            

















                                                   


                             
                                                                    
                             




                                               

                         





                                                                                     





                                                                    
                                                              








                                                                               




                                                 

                                                  

                              

                                                   



                                           
                                     




                                                   



                                                                           


                                          

                                                    


                                             




                                                                          
                                          

                                         

                                       






                                                 





                                                                    
                                                              




                                                                               



                                                                           











                                                       





                                                      

  





























                                                                               

                                

                        
                                                                   

          

                                                                 
           




                                                                           

                                   
                                                                  



                                                           


                                                            
           
                                                  
                                                    
                                                                               



                                                            
                                                                
                                                    
                                                                                  



                                                           
                                                                
                                                    
                                                                                  



                                                            
                                                             
                                                    
                                                                               
 




















                                                                        

                                        





                                                                                  
                                                                       
                                                          

                                                                    
 
                                                                        
                                                             


                                                                                     







                                          
                                 
                                          
























                                                                               
 
/*
 * Copyright 2011-2014 Freescale Semiconductor, Inc.
 * Copyright 2011 Linaro Ltd.
 *
 * 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
 */

#include <linux/delay.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_fdt.h>
#include <linux/of_irq.h>
#include <linux/suspend.h>
#include <linux/genalloc.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
#include <asm/cacheflush.h>
#include <asm/fncpy.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <asm/tlb.h>
#include <asm/hardware/cache-l2x0.h>
#include <asm/mach/map.h>

#include "common.h"
#include "hardware.h"

#define CCR				0x0
#define BM_CCR_WB_COUNT			(0x7 << 16)
#define BM_CCR_RBC_BYPASS_COUNT		(0x3f << 21)
#define BM_CCR_RBC_EN			(0x1 << 27)

#define CLPCR				0x54
#define BP_CLPCR_LPM			0
#define BM_CLPCR_LPM			(0x3 << 0)
#define BM_CLPCR_BYPASS_PMIC_READY	(0x1 << 2)
#define BM_CLPCR_ARM_CLK_DIS_ON_LPM	(0x1 << 5)
#define BM_CLPCR_SBYOS			(0x1 << 6)
#define BM_CLPCR_DIS_REF_OSC		(0x1 << 7)
#define BM_CLPCR_VSTBY			(0x1 << 8)
#define BP_CLPCR_STBY_COUNT		9
#define BM_CLPCR_STBY_COUNT		(0x3 << 9)
#define BM_CLPCR_COSC_PWRDOWN		(0x1 << 11)
#define BM_CLPCR_WB_PER_AT_LPM		(0x1 << 16)
#define BM_CLPCR_WB_CORE_AT_LPM		(0x1 << 17)
#define BM_CLPCR_BYP_MMDC_CH0_LPM_HS	(0x1 << 19)
#define BM_CLPCR_BYP_MMDC_CH1_LPM_HS	(0x1 << 21)
#define BM_CLPCR_MASK_CORE0_WFI		(0x1 << 22)
#define BM_CLPCR_MASK_CORE1_WFI		(0x1 << 23)
#define BM_CLPCR_MASK_CORE2_WFI		(0x1 << 24)
#define BM_CLPCR_MASK_CORE3_WFI		(0x1 << 25)
#define BM_CLPCR_MASK_SCU_IDLE		(0x1 << 26)
#define BM_CLPCR_MASK_L2CC_IDLE		(0x1 << 27)

#define CGPR				0x64
#define BM_CGPR_INT_MEM_CLK_LPM		(0x1 << 17)

#define MX6_INT_IOMUXC			32

#define ROMC_ROMPATCH0D			0xf0
#define ROMC_ROMPATCHCNTL		0xf4
#define ROMC_ROMPATCHENL		0xfc
#define ROMC_ROMPATCH0A			0x100
#define BM_ROMPATCHCNTL_0D		(0x1 << 0)
#define BM_ROMPATCHCNTL_DIS		(0x1 << 29)
#define BM_ROMPATCHENL_0D		(0x1 << 0)
#define ROM_ADDR_FOR_INTERNAL_RAM_BASE	0x10d7c

unsigned long iram_tlb_base_addr;
unsigned long iram_tlb_phys_addr;

static void *suspend_iram_base;
static unsigned long iram_paddr;
static int (*suspend_in_iram_fn)(void *iram_vbase,
	unsigned long iram_pbase, unsigned int cpu_type);
static unsigned int cpu_type;
static void __iomem *ccm_base;
struct regmap *romcp;

unsigned long total_suspend_size;
static unsigned long dcr;
static unsigned long pcr;

extern unsigned long imx6_suspend_start asm("imx6_suspend_start");
extern unsigned long imx6_suspend_end asm("imx6_suspend_end");

unsigned long save_ttbr1(void)
{
	unsigned long lttbr1;
	asm volatile(
		".align 4\n"
		"mrc p15, 0, %0, c2, c0, 1\n"
	: "=r" (lttbr1)
	);
	return lttbr1;
}

void restore_ttbr1(unsigned long ttbr1)
{
	asm volatile(
		".align 4\n"
		"mcr p15, 0, %0, c2, c0, 1\n"
	: : "r" (ttbr1)
	);
}

void imx6_set_cache_lpm_in_wait(bool enable)
{
	if ((cpu_is_imx6q() && imx_get_soc_revision() >
		IMX_CHIP_REVISION_1_1) ||
		(cpu_is_imx6dl() && imx_get_soc_revision() >
		IMX_CHIP_REVISION_1_0) || cpu_is_imx6sx()) {
		u32 val;

		val = readl_relaxed(ccm_base + CGPR);
		if (enable)
			val |= BM_CGPR_INT_MEM_CLK_LPM;
		else
			val &= ~BM_CGPR_INT_MEM_CLK_LPM;
		writel_relaxed(val, ccm_base + CGPR);
	}
}

static void imx6_save_cpu_arch_regs(void)
{
	/* Save the Diagnostic Control Register. */
	asm volatile(
		"mrc p15, 0, %0, c15, c0, 1\n"
	: "=r" (dcr)
	);
	/* Save the Power Control Register. */
	asm volatile(
		"mrc p15, 0, %0, c15, c0, 0\n"
	: "=r" (pcr)
	);
}

static void imx6_restore_cpu_arch_regs(void)
{
	/* Restore the diagnostic Control Register. */
	asm volatile(
		"mcr p15, 0, %0, c15, c0, 1\n"
	: : "r" (dcr)
	);
	/* Restore the Power Control Register. */
	asm volatile(
		"mcr p15, 0, %0, c15, c0, 0\n"
	: : "r" (pcr)
	);
}

static void imx6_enable_rbc(bool enable)
{
	u32 val;

	/*
	 * need to mask all interrupts in GPC before
	 * operating RBC configurations
	 */
	imx_gpc_mask_all();

	/* configure RBC enable bit */
	val = readl_relaxed(ccm_base + CCR);
	val &= ~BM_CCR_RBC_EN;
	val |= enable ? BM_CCR_RBC_EN : 0;
	writel_relaxed(val, ccm_base + CCR);

	/* configure RBC count */
	val = readl_relaxed(ccm_base + CCR);
	val &= ~BM_CCR_RBC_BYPASS_COUNT;
	val |= enable ? BM_CCR_RBC_BYPASS_COUNT : 0;
	writel(val, ccm_base + CCR);

	/*
	 * need to delay at least 2 cycles of CKIL(32K)
	 * due to hardware design requirement, which is
	 * ~61us, here we use 65us for safe
	 */
	udelay(65);

	/* restore GPC interrupt mask settings */
	imx_gpc_restore_all();
}

static void imx6_enable_wb(bool enable)
{
	u32 val;

	/* configure well bias enable bit */
	val = readl_relaxed(ccm_base + CLPCR);
	val &= ~BM_CLPCR_WB_PER_AT_LPM;
	val |= enable ? BM_CLPCR_WB_PER_AT_LPM : 0;
	writel_relaxed(val, ccm_base + CLPCR);

	/* configure well bias count */
	val = readl_relaxed(ccm_base + CCR);
	val &= ~BM_CCR_WB_COUNT;
	val |= enable ? BM_CCR_WB_COUNT : 0;
	writel_relaxed(val, ccm_base + CCR);
}

int imx6_set_lpm(enum mxc_cpu_pwr_mode mode)
{
	u32 val = readl_relaxed(ccm_base + CLPCR);
	struct irq_desc *desc = irq_to_desc(MX6_INT_IOMUXC);

	/*
	 * CCM state machine has restriction, before enabling
	 * LPM mode, need to make sure last LPM mode is waked up
	 * by dsm_wakeup_signal, which means the wakeup source
	 * must be seen by GPC, then CCM will clean its state machine
	 * and re-sample necessary signal to decide whether it can
	 * enter LPM mode. We force irq #32 to be always pending,
	 * unmask it before we enable LPM mode and mask it after LPM
	 * is enabled, this flow will make sure CCM state machine in
	 * reliable status before entering LPM mode. Otherwise, CCM
	 * may enter LPM mode by mistake which will cause system bus
	 * locked by CPU access not finished, as when CCM enter
	 * LPM mode, CPU will stop running.
	 */
	imx_gpc_irq_unmask(&desc->irq_data);

	val &= ~BM_CLPCR_LPM;
	switch (mode) {
	case WAIT_CLOCKED:
		break;
	case WAIT_UNCLOCKED:
		val |= 0x1 << BP_CLPCR_LPM;
		val |= BM_CLPCR_ARM_CLK_DIS_ON_LPM;
		val &= ~BM_CLPCR_VSTBY;
		val &= ~BM_CLPCR_SBYOS;
		if (cpu_is_imx6sl() || cpu_is_imx6sx())
			val |= BM_CLPCR_BYP_MMDC_CH0_LPM_HS;
		else
			val |= BM_CLPCR_BYP_MMDC_CH1_LPM_HS;
		break;
	case STOP_POWER_ON:
		val |= 0x2 << BP_CLPCR_LPM;
		val &= ~BM_CLPCR_VSTBY;
		val &= ~BM_CLPCR_SBYOS;
		if (cpu_is_imx6sl())
			val |= BM_CLPCR_BYPASS_PMIC_READY;
		if (cpu_is_imx6sl() || cpu_is_imx6sx()) {
			val |= BM_CLPCR_BYP_MMDC_CH0_LPM_HS;
		} else {
			val |= BM_CLPCR_BYP_MMDC_CH1_LPM_HS;
		}
		break;
	case WAIT_UNCLOCKED_POWER_OFF:
		val |= 0x1 << BP_CLPCR_LPM;
		val &= ~BM_CLPCR_VSTBY;
		val &= ~BM_CLPCR_SBYOS;
		break;
	case STOP_POWER_OFF:
		val |= 0x2 << BP_CLPCR_LPM;
		val |= 0x3 << BP_CLPCR_STBY_COUNT;
		val |= BM_CLPCR_VSTBY;
		val |= BM_CLPCR_SBYOS;
		if (cpu_is_imx6sl())
			val |= BM_CLPCR_BYPASS_PMIC_READY;
		if (cpu_is_imx6sl() || cpu_is_imx6sx()) {
			val |= BM_CLPCR_BYP_MMDC_CH0_LPM_HS;
		} else {
			val |= BM_CLPCR_BYP_MMDC_CH1_LPM_HS;
		}
		break;
	default:
		imx_gpc_irq_mask(&desc->irq_data);
		return -EINVAL;
	}

	writel_relaxed(val, ccm_base + CLPCR);
	imx_gpc_irq_mask(&desc->irq_data);

	return 0;
}

static int imx6_suspend_finish(unsigned long val)
{
	/*
	 * call low level suspend function in iram,
	 * as we need to float DDR IO.
	 */
	u32 ttbr1;

	ttbr1 = save_ttbr1();
	suspend_in_iram_fn(suspend_iram_base, iram_paddr, cpu_type);
	restore_ttbr1(ttbr1);
	return 0;
}

static int imx6_pm_enter(suspend_state_t state)
{
	struct regmap *g;

	if (!iram_tlb_base_addr) {
		pr_warn("No IRAM/OCRAM memory allocated for suspend/resume code. \
			Please ensure device tree has an entry for fsl,lpm-sram.\n");
			return -EINVAL;
	}

	/*
	 * L2 can exit by 'reset' or Inband beacon (from remote EP)
	 * toggling phy_powerdown has same effect as 'inband beacon'
	 * So, toggle bit18 of GPR1, used as a workaround of errata
	 * "PCIe PCIe does not support L2 Power Down"
	 */
	if (IS_ENABLED(CONFIG_PCI_IMX6) && !cpu_is_imx6sx()) {
		g = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr");
		if (IS_ERR(g)) {
			pr_err("failed to find fsl,imx6q-iomux-gpr regmap\n");
			return PTR_ERR(g);
		}
		regmap_update_bits(g, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_TEST_PD,
				IMX6Q_GPR1_PCIE_TEST_PD);
	}

	switch (state) {
	case PM_SUSPEND_STANDBY:
		imx6_set_lpm(STOP_POWER_ON);
		imx6_set_cache_lpm_in_wait(true);
		imx_gpc_pre_suspend(false);
		if (cpu_is_imx6sl())
			imx6sl_set_wait_clk(true);
		/* Zzz ... */
		cpu_do_idle();
		if (cpu_is_imx6sl())
			imx6sl_set_wait_clk(false);
		imx_gpc_post_resume();
		imx6_set_lpm(WAIT_CLOCKED);
		break;
	case PM_SUSPEND_MEM:
		imx6_enable_wb(true);
		imx6_set_cache_lpm_in_wait(false);
		imx6_set_lpm(STOP_POWER_OFF);
		imx_gpc_pre_suspend(true);
		imx_anatop_pre_suspend();
		imx_set_cpu_jump(0, v7_cpu_resume);
		/* Enable ROM patch for i.MX6SX */
		if (cpu_is_imx6sx())
			regmap_update_bits(romcp, ROMC_ROMPATCHCNTL,
				BM_ROMPATCHCNTL_DIS, ~BM_ROMPATCHCNTL_DIS);

		imx6_save_cpu_arch_regs();

		/* Zzz ... */
		cpu_suspend(0, imx6_suspend_finish);

		imx6_restore_cpu_arch_regs();

		/* Disable ROM patch for i.MX6SX */
		if (cpu_is_imx6sx())
			regmap_update_bits(romcp, ROMC_ROMPATCHCNTL,
				BM_ROMPATCHCNTL_DIS, BM_ROMPATCHCNTL_DIS);
		if (!cpu_is_imx6sl() && !cpu_is_imx6sx())
			imx_smp_prepare();
		imx_anatop_post_resume();
		imx_gpc_post_resume();
		imx6_enable_rbc(false);
		imx6_enable_wb(false);
		imx6_set_cache_lpm_in_wait(true);
		imx6_set_lpm(WAIT_CLOCKED);
		break;
	default:
		return -EINVAL;
	}

	/*
	 * L2 can exit by 'reset' or Inband beacon (from remote EP)
	 * toggling phy_powerdown has same effect as 'inband beacon'
	 * So, toggle bit18 of GPR1, used as a workaround of errata
	 * "PCIe PCIe does not support L2 Power Down"
	 */
	if (IS_ENABLED(CONFIG_PCI_IMX6) && !cpu_is_imx6sx()) {
		g = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr");
		if (IS_ERR(g)) {
			pr_err("failed to find fsl,imx6q-iomux-gpr regmap\n");
			return PTR_ERR(g);
		}
		regmap_update_bits(g, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_TEST_PD,
				!IMX6Q_GPR1_PCIE_TEST_PD);
	}

	return 0;
}

static struct map_desc imx6_pm_io_desc[] __initdata = {
	imx_map_entry(MX6Q, MMDC_P0, MT_DEVICE),
	imx_map_entry(MX6Q, MMDC_P1, MT_DEVICE),
	imx_map_entry(MX6Q, SRC, MT_DEVICE),
	imx_map_entry(MX6Q, IOMUXC, MT_DEVICE),
	imx_map_entry(MX6Q, CCM, MT_DEVICE),
	imx_map_entry(MX6Q, ANATOP, MT_DEVICE),
	imx_map_entry(MX6Q, GPC, MT_DEVICE),
	imx_map_entry(MX6Q, L2, MT_DEVICE),
};

static struct map_desc iram_tlb_io_desc __initdata = {
	/* .virtual and .pfn are run-time assigned */
	.length		= SZ_1M,
	.type		= MT_MEMORY_NONCACHED,
};

const static char *low_power_ocram_match[] __initconst = {
	"fsl,lpm-sram",
	NULL
};

static int __init imx6_dt_find_lpsram(unsigned long node,
		const char *uname, int depth, void *data)
{
	unsigned long lpram_addr;
	__be32 *prop;

	if (of_flat_dt_match(node, low_power_ocram_match)) {
		prop = of_get_flat_dt_prop(node, "reg", NULL);
		if (!prop)
			return -EINVAL;

		lpram_addr = be32_to_cpup(prop);

		/* We need to create a 1M page table entry. */
		iram_tlb_io_desc.virtual = IMX_IO_P2V(lpram_addr & 0xFFF00000);
		iram_tlb_io_desc.pfn = __phys_to_pfn(lpram_addr & 0xFFF00000);
		iram_tlb_phys_addr = lpram_addr;
		iram_tlb_base_addr = IMX_IO_P2V(lpram_addr);

		iotable_init(&iram_tlb_io_desc, 1);
	}
	return 0;

}

void __init imx6_pm_map_io(void)
{
	unsigned long i;

	iotable_init(imx6_pm_io_desc, ARRAY_SIZE(imx6_pm_io_desc));

	/*
	 * Get the address of IRAM or OCRAM to be used by the low
	 * power code from the device tree.
	 */
	WARN_ON(of_scan_flat_dt(imx6_dt_find_lpsram, NULL));

	/* Return if no IRAM space is allocated for suspend/resume code. */
	if (!iram_tlb_base_addr)
		return;

	/* Set all entries to 0. */
	memset((void *)iram_tlb_base_addr, 0, MX6Q_IRAM_TLB_SIZE);

	/*
	 * Make sure the IRAM virtual address has a mapping
	 * in the IRAM page table.
	 * Only use the top 11 bits [31-20] when storing the
	 * physical address in the page table as only these
	 * bits are required for 1M mapping.
	 */
	i = ((iram_tlb_base_addr >> 20) << 2) / 4;
	*((unsigned long *)iram_tlb_base_addr + i) =
		(iram_tlb_phys_addr & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M;
	/*
	 * Make sure the AIPS1 virtual address has a mapping
	 * in the IRAM page table.
	 */
	i = ((IMX_IO_P2V(MX6Q_AIPS1_BASE_ADDR) >> 20) << 2) / 4;
	*((unsigned long *)iram_tlb_base_addr + i) =
		(MX6Q_AIPS1_BASE_ADDR  & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M;
	/*
	* Make sure the AIPS2 virtual address has a mapping
	* in the IRAM page table.
	*/
	i = ((IMX_IO_P2V(MX6Q_AIPS2_BASE_ADDR) >> 20) << 2) / 4;
	*((unsigned long *)iram_tlb_base_addr + i) =
		(MX6Q_AIPS2_BASE_ADDR  & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M;
	/*
	 * Make sure the AIPS2 virtual address has a mapping
	 * in the IRAM page table.
	 */
	i = ((IMX_IO_P2V(MX6Q_L2_BASE_ADDR) >> 20) << 2) / 4;
	*((unsigned long *)iram_tlb_base_addr + i) =
		(MX6Q_L2_BASE_ADDR  & 0xFFF00000) | TT_ATTRIB_NON_CACHEABLE_1M;

}

static int imx6_pm_valid(suspend_state_t state)
{
	return (state == PM_SUSPEND_STANDBY || state == PM_SUSPEND_MEM);
}

static const struct platform_suspend_ops imx6_pm_ops = {
	.enter = imx6_pm_enter,
	.valid = imx6_pm_valid,
};

void imx6_pm_set_ccm_base(void __iomem *base)
{
	if (!base)
		pr_warn("ccm base is NULL!\n");
	ccm_base = base;
}

void __init imx6_pm_init(void)
{
	unsigned long suspend_code_size;

	if (!iram_tlb_base_addr) {
		pr_warn("No IRAM/OCRAM memory allocated for suspend/resume code. \
Please ensure device tree has an entry fsl,lpm-sram\n");
			return;
	}

	iram_paddr = iram_tlb_phys_addr + MX6_SUSPEND_IRAM_ADDR_OFFSET;
	/* Get the virtual address of the suspend code. */
	suspend_iram_base = (void *)IMX_IO_P2V(iram_tlb_phys_addr) +
			MX6_SUSPEND_IRAM_ADDR_OFFSET;

	suspend_code_size = (&imx6_suspend_end -&imx6_suspend_start) *4;
	suspend_in_iram_fn = (void *)fncpy(suspend_iram_base,
		&imx6_suspend, suspend_code_size);
	/* Now add the space used for storing various registers and IO in suspend. */
	total_suspend_size = suspend_code_size + MX6_SUSPEND_IRAM_DATA_SIZE;

	suspend_set_ops(&imx6_pm_ops);

	/* Set cpu_type for DSM */
	if (cpu_is_imx6q())
		cpu_type = MXC_CPU_IMX6Q;
	else if (cpu_is_imx6dl())
		cpu_type = MXC_CPU_IMX6DL;
	else if (cpu_is_imx6sl())
		cpu_type = MXC_CPU_IMX6SL;
	else {
		cpu_type = MXC_CPU_IMX6SX;
		/*
		 * As there is a 16K OCRAM(start from 0x8f8000)
		 * dedicated for low power function on i.MX6SX,
		 * but ROM did NOT do the ocram address change
		 * accordingly, so we need to add a data patch
		 * to workaround this issue, otherwise, system
		 * will fail to resume from DSM mode.
		 */
		romcp = syscon_regmap_lookup_by_compatible("fsl,imx6sx-romcp");
		if (IS_ERR(romcp)) {
			pr_err("failed to find fsl,imx6sx-romcp regmap\n");
			return;
		}
		regmap_write(romcp, ROMC_ROMPATCH0D, iram_paddr);
		regmap_update_bits(romcp, ROMC_ROMPATCHCNTL,
			BM_ROMPATCHCNTL_DIS, BM_ROMPATCHCNTL_DIS);
		regmap_update_bits(romcp, ROMC_ROMPATCHCNTL,
			BM_ROMPATCHCNTL_0D, BM_ROMPATCHCNTL_0D);
		regmap_update_bits(romcp, ROMC_ROMPATCHENL,
			BM_ROMPATCHENL_0D, BM_ROMPATCHENL_0D);
		regmap_write(romcp, ROMC_ROMPATCH0A,
			ROM_ADDR_FOR_INTERNAL_RAM_BASE);
	}
}