blob: f0e5ebdd06812a77de696eb92a55ee7cc37a2c72 [file] [log] [blame]
/* Copyright (c) 2012-2013, The Linux Foundation. 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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/cpu.h>
#include <linux/notifier.h>
#include <linux/hrtimer.h>
#include <linux/tick.h>
#include <mach/mpm.h>
#include <mach/rpm-smd.h>
#include "spm.h"
#include "lpm_resources.h"
#include "rpm-notifier.h"
#include "idle.h"
#include "trace_msm_low_power.h"
/*Debug Definitions*/
enum {
MSM_LPMRS_DEBUG_RPM = BIT(0),
MSM_LPMRS_DEBUG_PXO = BIT(1),
MSM_LPMRS_DEBUG_VDD_DIG = BIT(2),
MSM_LPMRS_DEBUG_VDD_MEM = BIT(3),
MSM_LPMRS_DEBUG_L2 = BIT(4),
MSM_LPMRS_DEBUG_LVLS = BIT(5),
};
static int msm_lpm_debug_mask;
module_param_named(
debug_mask, msm_lpm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
);
static bool msm_lpm_get_rpm_notif = true;
/*Macros*/
#define MAX_RS_NAME (16)
#define MAX_RS_SIZE (4)
#define IS_RPM_CTL(rs) \
(!strncmp(rs->name, "rpm_ctl", MAX_RS_NAME))
#define MAX_STR_LEN 30
static bool msm_lpm_beyond_limits_vdd_dig(struct msm_rpmrs_limits *limits);
static void msm_lpm_aggregate_vdd_dig(struct msm_rpmrs_limits *limits);
static void msm_lpm_flush_vdd_dig(int notify_rpm);
static void msm_lpm_notify_vdd_dig(struct msm_rpm_notifier_data
*rpm_notifier_cb);
static int msm_lpm_init_value_vdd_dig(struct device_node *node,
char *key, uint32_t *default_value);
static bool msm_lpm_beyond_limits_vdd_mem(struct msm_rpmrs_limits *limits);
static void msm_lpm_aggregate_vdd_mem(struct msm_rpmrs_limits *limits);
static void msm_lpm_flush_vdd_mem(int notify_rpm);
static void msm_lpm_notify_vdd_mem(struct msm_rpm_notifier_data
*rpm_notifier_cb);
static int msm_lpm_init_value_vdd_mem(struct device_node *node,
char *key, uint32_t *default_value);
static bool msm_lpm_beyond_limits_pxo(struct msm_rpmrs_limits *limits);
static void msm_lpm_aggregate_pxo(struct msm_rpmrs_limits *limits);
static void msm_lpm_flush_pxo(int notify_rpm);
static void msm_lpm_notify_pxo(struct msm_rpm_notifier_data
*rpm_notifier_cb);
static int msm_lpm_init_value_pxo(struct device_node *node,
char *key, uint32_t *default_value);
static bool msm_lpm_beyond_limits_l2(struct msm_rpmrs_limits *limits);
static void msm_lpm_flush_l2(int notify_rpm);
static void msm_lpm_aggregate_l2(struct msm_rpmrs_limits *limits);
static int msm_lpm_init_value_l2(struct device_node *node,
char *key, uint32_t *default_value);
static void msm_lpm_flush_rpm_ctl(int notify_rpm);
static int msm_lpm_rpm_callback(struct notifier_block *rpm_nb,
unsigned long action, void *rpm_notif);
static int msm_lpm_cpu_callback(struct notifier_block *cpu_nb,
unsigned long action, void *hcpu);
static ssize_t msm_lpm_resource_attr_show(
struct kobject *kobj, struct kobj_attribute *attr, char *buf);
static ssize_t msm_lpm_resource_attr_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count);
#define RPMRS_ATTR(_name) \
__ATTR(_name, S_IRUGO|S_IWUSR, \
msm_lpm_resource_attr_show, msm_lpm_resource_attr_store)
/*Data structures*/
struct msm_lpm_rs_data {
uint32_t type;
uint32_t id;
uint32_t key;
uint32_t value;
uint32_t default_value;
struct msm_rpm_request *handle;
};
enum {
MSM_LPM_RPM_RS_TYPE = 0,
MSM_LPM_LOCAL_RS_TYPE = 1,
};
enum {
MSM_SCM_L2_ON = 0,
MSM_SCM_L2_OFF = 1,
MSM_SCM_L2_GDHS = 3,
};
struct msm_lpm_resource {
struct msm_lpm_rs_data rs_data;
uint32_t sleep_value;
char name[MAX_RS_NAME];
uint32_t enable_low_power;
bool valid;
bool (*beyond_limits)(struct msm_rpmrs_limits *limits);
void (*aggregate)(struct msm_rpmrs_limits *limits);
void (*flush)(int notify_rpm);
void (*notify)(struct msm_rpm_notifier_data *rpm_notifier_cb);
struct kobj_attribute ko_attr;
int (*init_value)(struct device_node *node,
char *key, uint32_t *default_value);
};
struct lpm_lookup_table {
uint32_t modes;
const char *mode_name;
};
static struct msm_lpm_resource msm_lpm_l2 = {
.name = "l2",
.beyond_limits = msm_lpm_beyond_limits_l2,
.aggregate = msm_lpm_aggregate_l2,
.flush = msm_lpm_flush_l2,
.notify = NULL,
.valid = false,
.ko_attr = RPMRS_ATTR(l2),
.init_value = msm_lpm_init_value_l2,
};
static struct msm_lpm_resource msm_lpm_vdd_dig = {
.name = "vdd-dig",
.beyond_limits = msm_lpm_beyond_limits_vdd_dig,
.aggregate = msm_lpm_aggregate_vdd_dig,
.flush = msm_lpm_flush_vdd_dig,
.notify = msm_lpm_notify_vdd_dig,
.valid = false,
.ko_attr = RPMRS_ATTR(vdd_dig),
.init_value = msm_lpm_init_value_vdd_dig,
};
static struct msm_lpm_resource msm_lpm_vdd_mem = {
.name = "vdd-mem",
.beyond_limits = msm_lpm_beyond_limits_vdd_mem,
.aggregate = msm_lpm_aggregate_vdd_mem,
.flush = msm_lpm_flush_vdd_mem,
.notify = msm_lpm_notify_vdd_mem,
.valid = false,
.ko_attr = RPMRS_ATTR(vdd_mem),
.init_value = msm_lpm_init_value_vdd_mem,
};
static struct msm_lpm_resource msm_lpm_pxo = {
.name = "pxo",
.beyond_limits = msm_lpm_beyond_limits_pxo,
.aggregate = msm_lpm_aggregate_pxo,
.flush = msm_lpm_flush_pxo,
.notify = msm_lpm_notify_pxo,
.valid = false,
.ko_attr = RPMRS_ATTR(pxo),
.init_value = msm_lpm_init_value_pxo,
};
static struct msm_lpm_resource *msm_lpm_resources[] = {
&msm_lpm_vdd_dig,
&msm_lpm_vdd_mem,
&msm_lpm_pxo,
&msm_lpm_l2,
};
static struct msm_lpm_resource msm_lpm_rpm_ctl = {
.name = "rpm_ctl",
.beyond_limits = NULL,
.aggregate = NULL,
.flush = msm_lpm_flush_rpm_ctl,
.valid = true,
.ko_attr = RPMRS_ATTR(rpm_ctl),
};
static struct notifier_block msm_lpm_rpm_nblk = {
.notifier_call = msm_lpm_rpm_callback,
};
static struct notifier_block __refdata msm_lpm_cpu_nblk = {
.notifier_call = msm_lpm_cpu_callback,
};
static DEFINE_SPINLOCK(msm_lpm_sysfs_lock);
/* Attribute Definitions */
static struct attribute *msm_lpm_attributes[] = {
&msm_lpm_vdd_dig.ko_attr.attr,
&msm_lpm_vdd_mem.ko_attr.attr,
&msm_lpm_pxo.ko_attr.attr,
&msm_lpm_l2.ko_attr.attr,
NULL,
};
static struct attribute_group msm_lpm_attribute_group = {
.attrs = msm_lpm_attributes,
};
static struct attribute *msm_lpm_rpm_ctl_attribute[] = {
&msm_lpm_rpm_ctl.ko_attr.attr,
NULL,
};
static struct attribute_group msm_lpm_rpm_ctl_attr_group = {
.attrs = msm_lpm_rpm_ctl_attribute,
};
#define GET_RS_FROM_ATTR(attr) \
(container_of(attr, struct msm_lpm_resource, ko_attr))
/* RPM */
static struct msm_rpm_request *msm_lpm_create_rpm_request
(uint32_t rsc_type, uint32_t rsc_id)
{
struct msm_rpm_request *handle = NULL;
handle = msm_rpm_create_request(MSM_RPM_CTX_SLEEP_SET,
rsc_type,
rsc_id, 1);
return handle;
}
static int msm_lpm_send_sleep_data(struct msm_rpm_request *handle,
uint32_t key, uint8_t *value)
{
int ret = 0;
int msg_id;
if (!handle)
return ret;
ret = msm_rpm_add_kvp_data_noirq(handle, key, value, MAX_RS_SIZE);
if (ret < 0) {
pr_err("%s: Error adding kvp data key %u, size %d\n",
__func__, key, MAX_RS_SIZE);
return ret;
}
msg_id = msm_rpm_send_request_noirq(handle);
if (!msg_id) {
pr_err("%s: Error sending RPM request key %u, handle 0x%x\n",
__func__, key, (unsigned int)handle);
ret = -EIO;
return ret;
}
ret = msm_rpm_wait_for_ack_noirq(msg_id);
if (ret < 0) {
pr_err("%s: Couldn't get ACK from RPM for Msg %d Error %d",
__func__, msg_id, ret);
return ret;
}
if (msm_lpm_debug_mask & MSM_LPMRS_DEBUG_RPM)
pr_info("Rs key %u, value %u, size %d\n", key,
*(unsigned int *)value, MAX_RS_SIZE);
return ret;
}
/* RPM Notifier */
static int msm_lpm_rpm_callback(struct notifier_block *rpm_nb,
unsigned long action,
void *rpm_notif)
{
int i;
struct msm_lpm_resource *rs = NULL;
struct msm_rpm_notifier_data *rpm_notifier_cb =
(struct msm_rpm_notifier_data *)rpm_notif;
if (!msm_lpm_get_rpm_notif)
return NOTIFY_DONE;
if (!(rpm_nb && rpm_notif))
return NOTIFY_BAD;
for (i = 0; i < ARRAY_SIZE(msm_lpm_resources); i++) {
rs = msm_lpm_resources[i];
if (rs && rs->valid && rs->notify)
rs->notify(rpm_notifier_cb);
}
return NOTIFY_OK;
}
/* SYSFS */
static ssize_t msm_lpm_resource_attr_show(
struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
struct kernel_param kp;
unsigned long flags;
unsigned int temp;
int rc;
spin_lock_irqsave(&msm_lpm_sysfs_lock, flags);
temp = GET_RS_FROM_ATTR(attr)->enable_low_power;
spin_unlock_irqrestore(&msm_lpm_sysfs_lock, flags);
kp.arg = &temp;
rc = param_get_uint(buf, &kp);
if (rc > 0) {
strlcat(buf, "\n", PAGE_SIZE);
rc++;
}
return rc;
}
static ssize_t msm_lpm_resource_attr_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
struct kernel_param kp;
unsigned long flags;
unsigned int temp;
int rc;
kp.arg = &temp;
rc = param_set_uint(buf, &kp);
if (rc)
return rc;
spin_lock_irqsave(&msm_lpm_sysfs_lock, flags);
GET_RS_FROM_ATTR(attr)->enable_low_power = temp;
if (IS_RPM_CTL(GET_RS_FROM_ATTR(attr))) {
struct msm_lpm_resource *rs = GET_RS_FROM_ATTR(attr);
rs->flush(false);
}
spin_unlock_irqrestore(&msm_lpm_sysfs_lock, flags);
return count;
}
/* lpm resource handling functions */
/* Common */
static void msm_lpm_notify_common(struct msm_rpm_notifier_data *cb,
struct msm_lpm_resource *rs)
{
if ((cb->rsc_type == rs->rs_data.type) &&
(cb->rsc_id == rs->rs_data.id) &&
(cb->key == rs->rs_data.key)) {
BUG_ON(cb->size > MAX_RS_SIZE);
if (rs->valid) {
if (cb->value) {
memcpy(&rs->rs_data.value, cb->value, cb->size);
msm_rpm_add_kvp_data_noirq(rs->rs_data.handle,
cb->key, cb->value, cb->size);
}
else
rs->rs_data.value = rs->rs_data.default_value;
if (msm_lpm_debug_mask & MSM_LPMRS_DEBUG_RPM)
pr_info("Notification received Rs %s value %u\n",
rs->name, rs->rs_data.value);
}
}
}
/* L2 */
static bool msm_lpm_beyond_limits_l2(struct msm_rpmrs_limits *limits)
{
uint32_t l2;
bool ret = false;
struct msm_lpm_resource *rs = &msm_lpm_l2;
if (rs->valid) {
uint32_t l2_buf = rs->rs_data.value;
if (rs->enable_low_power == 1)
l2 = MSM_LPM_L2_CACHE_GDHS;
else if (rs->enable_low_power == 2)
l2 = MSM_LPM_L2_CACHE_HSFS_OPEN;
else
l2 = MSM_LPM_L2_CACHE_ACTIVE ;
if (l2_buf > l2)
l2 = l2_buf;
ret = (l2 > limits->l2_cache);
if (msm_lpm_debug_mask & MSM_LPMRS_DEBUG_L2)
pr_info("%s: l2 buf %u, l2 %u, limits %u\n",
__func__, l2_buf, l2, limits->l2_cache);
}
return ret;
}
static void msm_lpm_aggregate_l2(struct msm_rpmrs_limits *limits)
{
struct msm_lpm_resource *rs = &msm_lpm_l2;
if (rs->valid)
rs->sleep_value = limits->l2_cache;
trace_lpm_resources(rs->sleep_value, rs->name);
}
static void msm_lpm_set_l2_mode(int sleep_mode, int notify_rpm)
{
int lpm, rc;
msm_pm_set_l2_flush_flag(MSM_SCM_L2_ON);
switch (sleep_mode) {
case MSM_LPM_L2_CACHE_HSFS_OPEN:
lpm = MSM_SPM_L2_MODE_POWER_COLLAPSE;
msm_pm_set_l2_flush_flag(MSM_SCM_L2_OFF);
break;
case MSM_LPM_L2_CACHE_GDHS:
lpm = MSM_SPM_L2_MODE_GDHS;
msm_pm_set_l2_flush_flag(MSM_SCM_L2_GDHS);
break;
case MSM_LPM_L2_CACHE_RETENTION:
lpm = MSM_SPM_L2_MODE_RETENTION;
break;
default:
case MSM_LPM_L2_CACHE_ACTIVE:
lpm = MSM_SPM_L2_MODE_DISABLED;
break;
}
rc = msm_spm_l2_set_low_power_mode(lpm, notify_rpm);
if (rc < 0)
pr_err("%s: Failed to set L2 low power mode %d",
__func__, lpm);
if (msm_lpm_debug_mask & MSM_LPMRS_DEBUG_L2)
pr_info("%s: Requesting low power mode %d\n",
__func__, lpm);
}
static int msm_lpm_init_value_l2(struct device_node *node,
char *key, uint32_t *default_value)
{
return msm_lpm_get_l2_cache_value(node, key, default_value);
}
static void msm_lpm_flush_l2(int notify_rpm)
{
struct msm_lpm_resource *rs = &msm_lpm_l2;
msm_lpm_set_l2_mode(rs->sleep_value, notify_rpm);
}
int msm_lpm_get_l2_cache_value(struct device_node *node,
char *key, uint32_t *l2_val)
{
int i;
struct lpm_lookup_table l2_mode_lookup[] = {
{MSM_LPM_L2_CACHE_HSFS_OPEN, "l2_cache_pc"},
{MSM_LPM_L2_CACHE_GDHS, "l2_cache_gdhs"},
{MSM_LPM_L2_CACHE_RETENTION, "l2_cache_retention"},
{MSM_LPM_L2_CACHE_ACTIVE, "l2_cache_active"}
};
const char *l2_str;
int ret;
ret = of_property_read_string(node, key, &l2_str);
if (!ret) {
ret = -EINVAL;
for (i = 0; i < ARRAY_SIZE(l2_mode_lookup); i++) {
if (!strncmp(l2_str, l2_mode_lookup[i].mode_name,
MAX_STR_LEN)) {
*l2_val = l2_mode_lookup[i].modes;
ret = 0;
break;
}
}
}
return ret;
}
/* RPM CTL */
static void msm_lpm_flush_rpm_ctl(int notify_rpm)
{
struct msm_lpm_resource *rs = &msm_lpm_rpm_ctl;
msm_lpm_send_sleep_data(rs->rs_data.handle,
rs->rs_data.key,
(uint8_t *)&rs->sleep_value);
}
/*VDD Dig*/
static bool msm_lpm_beyond_limits_vdd_dig(struct msm_rpmrs_limits *limits)
{
bool ret = true;
struct msm_lpm_resource *rs = &msm_lpm_vdd_dig;
if (rs->valid) {
uint32_t vdd_buf = rs->rs_data.value;
uint32_t vdd_dig = rs->enable_low_power ? rs->enable_low_power :
rs->rs_data.default_value;
if (vdd_buf > vdd_dig)
vdd_dig = vdd_buf;
ret = (vdd_dig > limits->vdd_dig_upper_bound);
if (msm_lpm_debug_mask & MSM_LPMRS_DEBUG_VDD_DIG)
pr_info("%s:buf %d vdd dig %d limits%d\n",
__func__, vdd_buf, vdd_dig,
limits->vdd_dig_upper_bound);
}
return ret;
}
static int msm_lpm_init_value_vdd_dig(struct device_node *node,
char *key, uint32_t *default_value)
{
return of_property_read_u32(node, key, default_value);
}
static void msm_lpm_aggregate_vdd_dig(struct msm_rpmrs_limits *limits)
{
struct msm_lpm_resource *rs = &msm_lpm_vdd_dig;
if (rs->valid) {
uint32_t vdd_buf = rs->rs_data.value;
if (limits->vdd_dig_lower_bound > vdd_buf)
rs->sleep_value = limits->vdd_dig_lower_bound;
else
rs->sleep_value = vdd_buf;
}
trace_lpm_resources(rs->sleep_value, rs->name);
}
static void msm_lpm_flush_vdd_dig(int notify_rpm)
{
if (notify_rpm) {
struct msm_lpm_resource *rs = &msm_lpm_vdd_dig;
msm_lpm_send_sleep_data(rs->rs_data.handle,
rs->rs_data.key,
(uint8_t *)&rs->sleep_value);
}
}
static void msm_lpm_notify_vdd_dig(struct msm_rpm_notifier_data
*rpm_notifier_cb)
{
struct msm_lpm_resource *rs = &msm_lpm_vdd_dig;
msm_lpm_notify_common(rpm_notifier_cb, rs);
}
/*VDD Mem*/
static bool msm_lpm_beyond_limits_vdd_mem(struct msm_rpmrs_limits *limits)
{
bool ret = true;
struct msm_lpm_resource *rs = &msm_lpm_vdd_mem;
if (rs->valid) {
uint32_t vdd_buf = rs->rs_data.value;
uint32_t vdd_mem = rs->enable_low_power ? rs->enable_low_power :
rs->rs_data.default_value;
if (vdd_buf > vdd_mem)
vdd_mem = vdd_buf;
ret = (vdd_mem > limits->vdd_mem_upper_bound);
if (msm_lpm_debug_mask & MSM_LPMRS_DEBUG_VDD_MEM)
pr_info("%s:buf %d vdd mem %d limits%d\n",
__func__, vdd_buf, vdd_mem,
limits->vdd_mem_upper_bound);
}
return ret;
}
static void msm_lpm_aggregate_vdd_mem(struct msm_rpmrs_limits *limits)
{
struct msm_lpm_resource *rs = &msm_lpm_vdd_mem;
if (rs->valid) {
uint32_t vdd_buf = rs->rs_data.value;
if (limits->vdd_mem_lower_bound > vdd_buf)
rs->sleep_value = limits->vdd_mem_lower_bound;
else
rs->sleep_value = vdd_buf;
}
trace_lpm_resources(rs->sleep_value, rs->name);
}
static void msm_lpm_flush_vdd_mem(int notify_rpm)
{
if (notify_rpm) {
struct msm_lpm_resource *rs = &msm_lpm_vdd_mem;
msm_lpm_send_sleep_data(rs->rs_data.handle,
rs->rs_data.key,
(uint8_t *)&rs->sleep_value);
}
}
static void msm_lpm_notify_vdd_mem(struct msm_rpm_notifier_data
*rpm_notifier_cb)
{
struct msm_lpm_resource *rs = &msm_lpm_vdd_mem;
msm_lpm_notify_common(rpm_notifier_cb, rs);
}
static int msm_lpm_init_value_vdd_mem(struct device_node *node,
char *key, uint32_t *default_value)
{
return of_property_read_u32(node, key, default_value);
}
/*PXO*/
static bool msm_lpm_beyond_limits_pxo(struct msm_rpmrs_limits *limits)
{
bool ret = true;
struct msm_lpm_resource *rs = &msm_lpm_pxo;
if (rs->valid) {
uint32_t pxo_buf = rs->rs_data.value;
uint32_t pxo = rs->enable_low_power ? MSM_LPM_PXO_OFF :
rs->rs_data.default_value;
if (pxo_buf > pxo)
pxo = pxo_buf;
ret = (pxo > limits->pxo);
if (msm_lpm_debug_mask & MSM_LPMRS_DEBUG_PXO)
pr_info("%s:pxo buf %d pxo %d limits pxo %d\n",
__func__, pxo_buf, pxo, limits->pxo);
}
return ret;
}
static void msm_lpm_aggregate_pxo(struct msm_rpmrs_limits *limits)
{
struct msm_lpm_resource *rs = &msm_lpm_pxo;
if (rs->valid) {
uint32_t pxo_buf = rs->rs_data.value;
if (limits->pxo > pxo_buf)
rs->sleep_value = limits->pxo;
else
rs->sleep_value = pxo_buf;
if (msm_lpm_debug_mask & MSM_LPMRS_DEBUG_PXO)
pr_info("%s: pxo buf %d sleep value %d\n",
__func__, pxo_buf, rs->sleep_value);
}
trace_lpm_resources(rs->sleep_value, rs->name);
}
static void msm_lpm_flush_pxo(int notify_rpm)
{
if (notify_rpm) {
struct msm_lpm_resource *rs = &msm_lpm_pxo;
msm_lpm_send_sleep_data(rs->rs_data.handle,
rs->rs_data.key,
(uint8_t *)&rs->sleep_value);
}
}
static void msm_lpm_notify_pxo(struct msm_rpm_notifier_data
*rpm_notifier_cb)
{
struct msm_lpm_resource *rs = &msm_lpm_pxo;
msm_lpm_notify_common(rpm_notifier_cb, rs);
}
static int msm_lpm_init_value_pxo(struct device_node *node,
char *key, uint32_t *default_value)
{
return msm_lpm_get_xo_value(node, key, default_value);
}
static inline bool msm_lpm_use_mpm(struct msm_rpmrs_limits *limits)
{
return (limits->pxo == MSM_LPM_PXO_OFF);
}
int msm_lpm_get_xo_value(struct device_node *node,
char *key, uint32_t *xo_val)
{
int i;
struct lpm_lookup_table pxo_mode_lookup[] = {
{MSM_LPM_PXO_OFF, "xo_off"},
{MSM_LPM_PXO_ON, "xo_on"}
};
const char *xo_str;
int ret;
ret = of_property_read_string(node, key, &xo_str);
if (!ret) {
ret = -EINVAL;
for (i = 0; i < ARRAY_SIZE(pxo_mode_lookup); i++) {
if (!strncmp(xo_str, pxo_mode_lookup[i].mode_name,
MAX_STR_LEN)) {
*xo_val = pxo_mode_lookup[i].modes;
ret = 0;
break;
}
}
}
return ret;
}
/* LPM levels interface */
bool msm_lpm_level_beyond_limit(struct msm_rpmrs_limits *limits)
{
int i;
struct msm_lpm_resource *rs;
bool beyond_limit = false;
for (i = 0; i < ARRAY_SIZE(msm_lpm_resources); i++) {
rs = msm_lpm_resources[i];
if (rs->beyond_limits && rs->beyond_limits(limits)) {
beyond_limit = true;
if (msm_lpm_debug_mask & MSM_LPMRS_DEBUG_LVLS)
pr_info("%s: %s beyond limit", __func__,
rs->name);
break;
}
}
return beyond_limit;
}
int msm_lpmrs_enter_sleep(uint32_t sclk_count, struct msm_rpmrs_limits *limits,
bool from_idle, bool notify_rpm)
{
int ret = 0;
int i;
struct msm_lpm_resource *rs = NULL;
for (i = 0; i < ARRAY_SIZE(msm_lpm_resources); i++) {
rs = msm_lpm_resources[i];
if (rs->aggregate)
rs->aggregate(limits);
}
msm_lpm_get_rpm_notif = false;
for (i = 0; i < ARRAY_SIZE(msm_lpm_resources); i++) {
rs = msm_lpm_resources[i];
if (rs->valid && rs->flush)
rs->flush(notify_rpm);
}
msm_lpm_get_rpm_notif = true;
if (notify_rpm)
msm_mpm_enter_sleep(sclk_count, from_idle);
return ret;
}
void msm_lpmrs_exit_sleep(struct msm_rpmrs_limits *limits,
bool from_idle, bool notify_rpm, bool collapsed)
{
if (msm_lpm_use_mpm(limits))
msm_mpm_exit_sleep(from_idle);
if (msm_lpm_l2.valid)
msm_lpm_set_l2_mode(msm_lpm_l2.rs_data.default_value, false);
}
static int msm_lpm_cpu_callback(struct notifier_block *cpu_nb,
unsigned long action, void *hcpu)
{
struct msm_lpm_resource *rs = &msm_lpm_l2;
switch (action) {
case CPU_UP_PREPARE:
case CPU_UP_PREPARE_FROZEN:
rs->rs_data.value = rs->rs_data.default_value;
break;
case CPU_ONLINE_FROZEN:
case CPU_ONLINE:
if (num_online_cpus() > 1)
rs->rs_data.value = rs->rs_data.default_value;
break;
case CPU_DEAD_FROZEN:
case CPU_DEAD:
if (num_online_cpus() == 1)
rs->rs_data.value = MSM_LPM_L2_CACHE_HSFS_OPEN;
break;
}
return NOTIFY_OK;
}
/* RPM CTL */
static int __devinit msm_lpm_init_rpm_ctl(void)
{
struct msm_lpm_resource *rs = &msm_lpm_rpm_ctl;
rs->rs_data.handle = msm_rpm_create_request(
MSM_RPM_CTX_ACTIVE_SET,
rs->rs_data.type,
rs->rs_data.id, 1);
if (!rs->rs_data.handle)
return -EIO;
rs->valid = true;
return 0;
}
static int __devinit msm_lpm_resource_sysfs_add(void)
{
struct kobject *module_kobj = NULL;
struct kobject *low_power_kobj = NULL;
struct kobject *mode_kobj = NULL;
int rc = 0;
module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME);
if (!module_kobj) {
pr_err("%s: cannot find kobject for module %s\n",
__func__, KBUILD_MODNAME);
rc = -ENOENT;
goto resource_sysfs_add_exit;
}
low_power_kobj = kobject_create_and_add(
"enable_low_power", module_kobj);
if (!low_power_kobj) {
pr_err("%s: cannot create kobject\n", __func__);
rc = -ENOMEM;
goto resource_sysfs_add_exit;
}
mode_kobj = kobject_create_and_add(
"mode", module_kobj);
if (!mode_kobj) {
pr_err("%s: cannot create kobject\n", __func__);
rc = -ENOMEM;
goto resource_sysfs_add_exit;
}
rc = sysfs_create_group(low_power_kobj, &msm_lpm_attribute_group);
if (rc) {
pr_err("%s: cannot create kobject attribute group\n", __func__);
goto resource_sysfs_add_exit;
}
rc = sysfs_create_group(mode_kobj, &msm_lpm_rpm_ctl_attr_group);
if (rc) {
pr_err("%s: cannot create kobject attribute group\n", __func__);
goto resource_sysfs_add_exit;
}
resource_sysfs_add_exit:
if (rc) {
if (low_power_kobj)
sysfs_remove_group(low_power_kobj,
&msm_lpm_attribute_group);
kobject_del(low_power_kobj);
kobject_del(mode_kobj);
}
return rc;
}
late_initcall(msm_lpm_resource_sysfs_add);
static int __devinit msm_lpmrs_probe(struct platform_device *pdev)
{
struct device_node *node = NULL;
char *key = NULL;
int ret = 0;
for_each_child_of_node(pdev->dev.of_node, node) {
struct msm_lpm_resource *rs = NULL;
const char *val;
int i;
bool local_resource;
key = "qcom,name";
ret = of_property_read_string(node, key, &val);
if (ret) {
pr_err("Cannot read string\n");
goto fail;
}
for (i = 0; i < ARRAY_SIZE(msm_lpm_resources); i++) {
char *lpmrs_name = msm_lpm_resources[i]->name;
if (!msm_lpm_resources[i]->valid &&
!strncmp(val, lpmrs_name, strnlen(lpmrs_name,
MAX_RS_NAME))) {
rs = msm_lpm_resources[i];
break;
}
}
if (!rs) {
pr_err("LPM resource not found\n");
continue;
}
key = "qcom,init-value";
ret = rs->init_value(node, key, &rs->rs_data.default_value);
if (ret) {
pr_err("%s():Failed to read %s\n", __func__, key);
goto fail;
}
rs->rs_data.value = rs->rs_data.default_value;
key = "qcom,local-resource-type";
local_resource = of_property_read_bool(node, key);
if (!local_resource) {
key = "qcom,type";
ret = of_property_read_u32(node, key,
&rs->rs_data.type);
if (ret) {
pr_err("Failed to read type\n");
goto fail;
}
key = "qcom,id";
ret = of_property_read_u32(node, key, &rs->rs_data.id);
if (ret) {
pr_err("Failed to read id\n");
goto fail;
}
key = "qcom,key";
ret = of_property_read_u32(node, key, &rs->rs_data.key);
if (ret) {
pr_err("Failed to read key\n");
goto fail;
}
rs->rs_data.handle = msm_lpm_create_rpm_request(
rs->rs_data.type,
rs->rs_data.id);
if (!rs->rs_data.handle) {
pr_err("%s: Failed to allocate handle for %s\n",
__func__, rs->name);
ret = -1;
goto fail;
}
/* fall through */
}
rs->valid = true;
}
msm_rpm_register_notifier(&msm_lpm_rpm_nblk);
msm_lpm_init_rpm_ctl();
if (msm_lpm_l2.valid) {
register_hotcpu_notifier(&msm_lpm_cpu_nblk);
/* For UP mode, set the default to HSFS OPEN*/
if (num_possible_cpus() == 1) {
msm_lpm_l2.rs_data.default_value =
MSM_LPM_L2_CACHE_HSFS_OPEN;
msm_lpm_l2.rs_data.value = MSM_LPM_L2_CACHE_HSFS_OPEN;
}
msm_pm_set_l2_flush_flag(0);
} else
msm_pm_set_l2_flush_flag(1);
fail:
return ret;
}
static struct of_device_id msm_lpmrs_match_table[] = {
{.compatible = "qcom,lpm-resources"},
{},
};
static struct platform_driver msm_lpmrs_driver = {
.probe = msm_lpmrs_probe,
.driver = {
.name = "lpm-resources",
.owner = THIS_MODULE,
.of_match_table = msm_lpmrs_match_table,
},
};
int __init msm_lpmrs_module_init(void)
{
return platform_driver_register(&msm_lpmrs_driver);
}