/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "spm.h" #include "lpm_resources.h" #include "rpm-notifier.h" #include "idle.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 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, true); 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); } 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); } 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); }