Logo Search packages:      
Sourcecode: linux version File versions

pm.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 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);

Generated by  Doxygen 1.6.0   Back to index