| /* 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); |
| } |