While at it, fix two checkpatch errors.
Several non-const struct instances constified by this patch were added after
the introduction of platform_suspend_ops in checkpatch.pl's list of "should
be const" structs (79404849e9).
Patch against mainline.
Inspired by hunks of the grsecurity patch, updated for newer kernels.
Signed-off-by: Lionel Debroux <lionel_debroux@yahoo.fr>
Acked-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
		
	
			
		
			
				
	
	
		
			243 lines
		
	
	
	
		
			5.5 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
	
		
			5.5 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * AVR32 AP Power Management
 | 
						|
 *
 | 
						|
 * Copyright (C) 2008 Atmel Corporation
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or
 | 
						|
 * modify it under the terms of the GNU General Public License
 | 
						|
 * version 2 as published by the Free Software Foundation.
 | 
						|
 */
 | 
						|
#include <linux/io.h>
 | 
						|
#include <linux/suspend.h>
 | 
						|
#include <linux/vmalloc.h>
 | 
						|
 | 
						|
#include <asm/cacheflush.h>
 | 
						|
#include <asm/sysreg.h>
 | 
						|
 | 
						|
#include <mach/chip.h>
 | 
						|
#include <mach/pm.h>
 | 
						|
#include <mach/sram.h>
 | 
						|
 | 
						|
#include "sdramc.h"
 | 
						|
 | 
						|
#define SRAM_PAGE_FLAGS	(SYSREG_BIT(TLBELO_D) | SYSREG_BF(SZ, 1)	\
 | 
						|
				| SYSREG_BF(AP, 3) | SYSREG_BIT(G))
 | 
						|
 | 
						|
 | 
						|
static unsigned long	pm_sram_start;
 | 
						|
static size_t		pm_sram_size;
 | 
						|
static struct vm_struct	*pm_sram_area;
 | 
						|
 | 
						|
static void (*avr32_pm_enter_standby)(unsigned long sdramc_base);
 | 
						|
static void (*avr32_pm_enter_str)(unsigned long sdramc_base);
 | 
						|
 | 
						|
/*
 | 
						|
 * Must be called with interrupts disabled. Exceptions will be masked
 | 
						|
 * on return (i.e. all exceptions will be "unrecoverable".)
 | 
						|
 */
 | 
						|
static void *avr32_pm_map_sram(void)
 | 
						|
{
 | 
						|
	unsigned long	vaddr;
 | 
						|
	unsigned long	page_addr;
 | 
						|
	u32		tlbehi;
 | 
						|
	u32		mmucr;
 | 
						|
 | 
						|
	vaddr = (unsigned long)pm_sram_area->addr;
 | 
						|
	page_addr = pm_sram_start & PAGE_MASK;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Mask exceptions and grab the first TLB entry. We won't be
 | 
						|
	 * needing it while sleeping.
 | 
						|
	 */
 | 
						|
	asm volatile("ssrf	%0" : : "i"(SYSREG_EM_OFFSET) : "memory");
 | 
						|
 | 
						|
	mmucr = sysreg_read(MMUCR);
 | 
						|
	tlbehi = sysreg_read(TLBEHI);
 | 
						|
	sysreg_write(MMUCR, SYSREG_BFINS(DRP, 0, mmucr));
 | 
						|
 | 
						|
	tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi));
 | 
						|
	tlbehi |= vaddr & PAGE_MASK;
 | 
						|
	tlbehi |= SYSREG_BIT(TLBEHI_V);
 | 
						|
 | 
						|
	sysreg_write(TLBELO, page_addr | SRAM_PAGE_FLAGS);
 | 
						|
	sysreg_write(TLBEHI, tlbehi);
 | 
						|
	__builtin_tlbw();
 | 
						|
 | 
						|
	return (void *)(vaddr + pm_sram_start - page_addr);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Must be called with interrupts disabled. Exceptions will be
 | 
						|
 * unmasked on return.
 | 
						|
 */
 | 
						|
static void avr32_pm_unmap_sram(void)
 | 
						|
{
 | 
						|
	u32	mmucr;
 | 
						|
	u32	tlbehi;
 | 
						|
	u32	tlbarlo;
 | 
						|
 | 
						|
	/* Going to update TLB entry at index 0 */
 | 
						|
	mmucr = sysreg_read(MMUCR);
 | 
						|
	tlbehi = sysreg_read(TLBEHI);
 | 
						|
	sysreg_write(MMUCR, SYSREG_BFINS(DRP, 0, mmucr));
 | 
						|
 | 
						|
	/* Clear the "valid" bit */
 | 
						|
	tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi));
 | 
						|
	sysreg_write(TLBEHI, tlbehi);
 | 
						|
 | 
						|
	/* Mark it as "not accessed" */
 | 
						|
	tlbarlo = sysreg_read(TLBARLO);
 | 
						|
	sysreg_write(TLBARLO, tlbarlo | 0x80000000U);
 | 
						|
 | 
						|
	/* Update the TLB */
 | 
						|
	__builtin_tlbw();
 | 
						|
 | 
						|
	/* Unmask exceptions */
 | 
						|
	asm volatile("csrf	%0" : : "i"(SYSREG_EM_OFFSET) : "memory");
 | 
						|
}
 | 
						|
 | 
						|
static int avr32_pm_valid_state(suspend_state_t state)
 | 
						|
{
 | 
						|
	switch (state) {
 | 
						|
	case PM_SUSPEND_ON:
 | 
						|
	case PM_SUSPEND_STANDBY:
 | 
						|
	case PM_SUSPEND_MEM:
 | 
						|
		return 1;
 | 
						|
 | 
						|
	default:
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static int avr32_pm_enter(suspend_state_t state)
 | 
						|
{
 | 
						|
	u32		lpr_saved;
 | 
						|
	u32		evba_saved;
 | 
						|
	void		*sram;
 | 
						|
 | 
						|
	switch (state) {
 | 
						|
	case PM_SUSPEND_STANDBY:
 | 
						|
		sram = avr32_pm_map_sram();
 | 
						|
 | 
						|
		/* Switch to in-sram exception handlers */
 | 
						|
		evba_saved = sysreg_read(EVBA);
 | 
						|
		sysreg_write(EVBA, (unsigned long)sram);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Save the LPR register so that we can re-enable
 | 
						|
		 * SDRAM Low Power mode on resume.
 | 
						|
		 */
 | 
						|
		lpr_saved = sdramc_readl(LPR);
 | 
						|
		pr_debug("%s: Entering standby...\n", __func__);
 | 
						|
		avr32_pm_enter_standby(SDRAMC_BASE);
 | 
						|
		sdramc_writel(LPR, lpr_saved);
 | 
						|
 | 
						|
		/* Switch back to regular exception handlers */
 | 
						|
		sysreg_write(EVBA, evba_saved);
 | 
						|
 | 
						|
		avr32_pm_unmap_sram();
 | 
						|
		break;
 | 
						|
 | 
						|
	case PM_SUSPEND_MEM:
 | 
						|
		sram = avr32_pm_map_sram();
 | 
						|
 | 
						|
		/* Switch to in-sram exception handlers */
 | 
						|
		evba_saved = sysreg_read(EVBA);
 | 
						|
		sysreg_write(EVBA, (unsigned long)sram);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Save the LPR register so that we can re-enable
 | 
						|
		 * SDRAM Low Power mode on resume.
 | 
						|
		 */
 | 
						|
		lpr_saved = sdramc_readl(LPR);
 | 
						|
		pr_debug("%s: Entering suspend-to-ram...\n", __func__);
 | 
						|
		avr32_pm_enter_str(SDRAMC_BASE);
 | 
						|
		sdramc_writel(LPR, lpr_saved);
 | 
						|
 | 
						|
		/* Switch back to regular exception handlers */
 | 
						|
		sysreg_write(EVBA, evba_saved);
 | 
						|
 | 
						|
		avr32_pm_unmap_sram();
 | 
						|
		break;
 | 
						|
 | 
						|
	case PM_SUSPEND_ON:
 | 
						|
		pr_debug("%s: Entering idle...\n", __func__);
 | 
						|
		cpu_enter_idle();
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		pr_debug("%s: Invalid suspend state %d\n", __func__, state);
 | 
						|
		goto out;
 | 
						|
	}
 | 
						|
 | 
						|
	pr_debug("%s: wakeup\n", __func__);
 | 
						|
 | 
						|
out:
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static const struct platform_suspend_ops avr32_pm_ops = {
 | 
						|
	.valid	= avr32_pm_valid_state,
 | 
						|
	.enter	= avr32_pm_enter,
 | 
						|
};
 | 
						|
 | 
						|
static unsigned long avr32_pm_offset(void *symbol)
 | 
						|
{
 | 
						|
	extern u8 pm_exception[];
 | 
						|
 | 
						|
	return (unsigned long)symbol - (unsigned long)pm_exception;
 | 
						|
}
 | 
						|
 | 
						|
static int __init avr32_pm_init(void)
 | 
						|
{
 | 
						|
	extern u8 pm_exception[];
 | 
						|
	extern u8 pm_irq0[];
 | 
						|
	extern u8 pm_standby[];
 | 
						|
	extern u8 pm_suspend_to_ram[];
 | 
						|
	extern u8 pm_sram_end[];
 | 
						|
	void *dst;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * To keep things simple, we depend on not needing more than a
 | 
						|
	 * single page.
 | 
						|
	 */
 | 
						|
	pm_sram_size = avr32_pm_offset(pm_sram_end);
 | 
						|
	if (pm_sram_size > PAGE_SIZE)
 | 
						|
		goto err;
 | 
						|
 | 
						|
	pm_sram_start = sram_alloc(pm_sram_size);
 | 
						|
	if (!pm_sram_start)
 | 
						|
		goto err_alloc_sram;
 | 
						|
 | 
						|
	/* Grab a virtual area we can use later on. */
 | 
						|
	pm_sram_area = get_vm_area(pm_sram_size, VM_IOREMAP);
 | 
						|
	if (!pm_sram_area)
 | 
						|
		goto err_vm_area;
 | 
						|
	pm_sram_area->phys_addr = pm_sram_start;
 | 
						|
 | 
						|
	local_irq_disable();
 | 
						|
	dst = avr32_pm_map_sram();
 | 
						|
	memcpy(dst, pm_exception, pm_sram_size);
 | 
						|
	flush_dcache_region(dst, pm_sram_size);
 | 
						|
	invalidate_icache_region(dst, pm_sram_size);
 | 
						|
	avr32_pm_unmap_sram();
 | 
						|
	local_irq_enable();
 | 
						|
 | 
						|
	avr32_pm_enter_standby = dst + avr32_pm_offset(pm_standby);
 | 
						|
	avr32_pm_enter_str = dst + avr32_pm_offset(pm_suspend_to_ram);
 | 
						|
	intc_set_suspend_handler(avr32_pm_offset(pm_irq0));
 | 
						|
 | 
						|
	suspend_set_ops(&avr32_pm_ops);
 | 
						|
 | 
						|
	printk("AVR32 AP Power Management enabled\n");
 | 
						|
 | 
						|
	return 0;
 | 
						|
 | 
						|
err_vm_area:
 | 
						|
	sram_free(pm_sram_start, pm_sram_size);
 | 
						|
err_alloc_sram:
 | 
						|
err:
 | 
						|
	pr_err("AVR32 Power Management initialization failed\n");
 | 
						|
	return -ENOMEM;
 | 
						|
}
 | 
						|
arch_initcall(avr32_pm_init);
 |