blob: 7a75bc122de0e221fbdca5d4177e0662cdadbfda [file] [log] [blame]
/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <asm/setup.h>
#include <asm/errno.h>
#include <asm/sizes.h>
#include <asm/pgtable.h>
#include <linux/mutex.h>
#include <linux/memory.h>
#include <mach/msm_memtypes.h>
#include <mach/socinfo.h>
#include "smd_private.h"
#if defined(CONFIG_ARCH_MSM8960)
#include "rpm_resources.h"
#endif
static struct mem_region_t {
u64 start;
u64 size;
/* reserved for future use */
u64 num_partitions;
int state;
int mask;
struct mutex state_mutex;
} mem_regions[MAX_NR_REGIONS];
static unsigned int nr_mem_regions;
enum {
STATE_POWER_DOWN = 0x0,
STATE_ACTIVE = 0x2,
STATE_DEFAULT = STATE_ACTIVE
};
enum {
MEM_NO_CHANGE = 0x0,
MEM_DEEP_POWER_DOWN,
MEM_SELF_REFRESH,
};
static unsigned int dmm_mode;
static int default_mask = ~0x0;
/* Return the number of chipselects populated with a memory bank */
/* This is 7x30 only and will be re-implemented in the future */
#if defined(CONFIG_ARCH_MSM7X30)
unsigned int get_num_populated_chipselects()
{
/* Currently, Linux cannot determine the memory toplogy of a target */
/* This is a kludge until all this info is figured out from smem */
/* There is atleast one chipselect populated for hosting the 1st bank */
unsigned int num_chipselects = 1;
int i;
for (i = 0; i < meminfo.nr_banks; i++) {
struct membank *bank = &meminfo.bank[i];
if (bank->start == EBI1_PHYS_OFFSET)
num_chipselects++;
}
return num_chipselects;
}
#endif
#if defined(CONFIG_ARCH_MSM8960)
static int rpm_change_memory_state(int retention_mask,
int active_mask)
{
int ret;
struct msm_rpm_iv_pair cmd[2];
struct msm_rpm_iv_pair status[2];
cmd[0].id = MSM_RPM_ID_DDR_DMM_0;
cmd[1].id = MSM_RPM_ID_DDR_DMM_1;
status[0].id = MSM_RPM_STATUS_ID_DDR_DMM_0;
status[1].id = MSM_RPM_STATUS_ID_DDR_DMM_1;
cmd[0].value = retention_mask;
cmd[1].value = active_mask;
ret = msm_rpm_set(MSM_RPM_CTX_SET_0, cmd, 2);
if (ret < 0) {
pr_err("rpm set failed");
return -EINVAL;
}
ret = msm_rpm_get_status(status, 2);
if (ret < 0) {
pr_err("rpm status failed");
return -EINVAL;
}
if (status[0].value == retention_mask &&
status[1].value == active_mask)
return 0;
else {
pr_err("rpm failed to change memory state");
return -EINVAL;
}
}
static int switch_memory_state(int id, int new_state)
{
int mask;
int power_down_masks[MAX_NR_REGIONS] = { 0xFFFFFF00, 0xFFFF00FF,
0xFF00FFFF, 0x00FFFFFF };
int self_refresh_masks[MAX_NR_REGIONS] = { 0xFFFFFFF0, 0xFFFFFF0F,
0xFFFFF0FF, 0xFFFF0FFF };
mutex_lock(&mem_regions[id].state_mutex);
if (new_state == mem_regions[id].state)
goto no_change;
pr_info("request memory %d state switch (%d->%d) mode %d\n", id,
mem_regions[id].state, new_state, dmm_mode);
if (new_state == STATE_POWER_DOWN) {
if (dmm_mode == MEM_DEEP_POWER_DOWN)
mask = mem_regions[id].mask & power_down_masks[id];
else
mask = mem_regions[id].mask & self_refresh_masks[id];
} else if (new_state == STATE_ACTIVE) {
if (dmm_mode == MEM_DEEP_POWER_DOWN)
mask = mem_regions[id].mask | (~power_down_masks[id]);
else
mask = mem_regions[id].mask | (~self_refresh_masks[id]);
}
if (rpm_change_memory_state(mask, mask) == 0) {
mem_regions[id].state = new_state;
mem_regions[id].mask = mask;
pr_info("completed memory %d state switch to %d mode %d\n",
id, new_state, dmm_mode);
mutex_unlock(&mem_regions[id].state_mutex);
return 0;
}
pr_err("failed memory %d state switch (%d->%d) mode %d\n", id,
mem_regions[id].state, new_state, dmm_mode);
no_change:
mutex_unlock(&mem_regions[id].state_mutex);
return -EINVAL;
}
#else
static int switch_memory_state(int id, int new_state)
{
return -EINVAL;
}
#endif
/* The hotplug code expects the number of bytes that switched state successfully
* as the return value, so a return value of zero indicates an error
*/
int soc_change_memory_power(u64 start, u64 size, int change)
{
int i = 0;
int match = 0;
/* Find the memory region starting just below start */
for (i = 0; i < nr_mem_regions; i++) {
if (mem_regions[i].start <= start &&
mem_regions[i].start >= mem_regions[match].start) {
match = i;
}
}
if (start + size > mem_regions[match].start + mem_regions[match].size) {
pr_info("passed size exceeds size of memory bank\n");
return 0;
}
if (change != STATE_ACTIVE && change != STATE_POWER_DOWN) {
pr_info("requested state transition invalid\n");
return 0;
}
if (!switch_memory_state(match, change))
return size;
else
return 0;
}
unsigned int get_num_memory_banks(void)
{
return nr_mem_regions;
}
unsigned int get_memory_bank_size(unsigned int id)
{
BUG_ON(id >= nr_mem_regions);
return mem_regions[id].size;
}
unsigned int get_memory_bank_start(unsigned int id)
{
BUG_ON(id >= nr_mem_regions);
return mem_regions[id].start;
}
int __init meminfo_init(unsigned int type, unsigned int min_bank_size)
{
unsigned int i;
unsigned long bank_size;
unsigned long bank_start;
struct smem_ram_ptable *ram_ptable;
/* physical memory banks */
unsigned int nr_mem_banks = 0;
/* logical memory regions for dmm */
nr_mem_regions = 0;
ram_ptable = smem_alloc(SMEM_USABLE_RAM_PARTITION_TABLE,
sizeof(struct smem_ram_ptable));
if (!ram_ptable) {
pr_err("Could not read ram partition table\n");
return -EINVAL;
}
/* Determine power control mode based on the hw version */
/* This check will be removed when PASR is fully supported */
if (cpu_is_msm8960() &&
SOCINFO_VERSION_MAJOR(socinfo_get_version()) < 2)
dmm_mode = MEM_DEEP_POWER_DOWN;
else
dmm_mode = MEM_SELF_REFRESH;
pr_info("meminfo_init: smem ram ptable found: ver: %d len: %d\n",
ram_ptable->version, ram_ptable->len);
for (i = 0; i < ram_ptable->len; i++) {
if (ram_ptable->parts[i].type == type &&
ram_ptable->parts[i].size >= min_bank_size) {
bank_start = ram_ptable->parts[i].start;
bank_size = ram_ptable->parts[i].size;
/* Divide into logical memory regions of same size */
while (bank_size) {
mem_regions[nr_mem_regions].start =
bank_start;
mem_regions[nr_mem_regions].size =
MIN_MEMORY_BLOCK_SIZE;
mutex_init(&mem_regions[nr_mem_regions]
.state_mutex);
mem_regions[nr_mem_regions].state =
STATE_DEFAULT;
mem_regions[nr_mem_regions].mask = default_mask;
bank_start += MIN_MEMORY_BLOCK_SIZE;
bank_size -= MIN_MEMORY_BLOCK_SIZE;
nr_mem_regions++;
}
nr_mem_banks++;
}
}
pr_info("Found %d memory banks grouped into %d memory regions\n",
nr_mem_banks, nr_mem_regions);
return 0;
}