| /* |
| * Copyright (c) Quantenna Communications Incorporated 2012. |
| * |
| * ######################################################################## |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| * ######################################################################## |
| * |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/proc_fs.h> |
| #include <linux/pm_qos_params.h> |
| |
| #include <asm/uaccess.h> |
| #include <asm/board/pm.h> |
| |
| #define STR_HELPER(x) #x |
| #define STR(x) STR_HELPER(x) |
| |
| #define PM_PROC_FILE_NAME "soc_pm" |
| |
| #define PM_PROC_ADD_CMD "add" |
| #define PM_PROC_UPDATE_CMD "update" |
| #define PM_PROC_REMOVE_CMD "remove" |
| |
| #define PM_MAX_NAME_LEN 64 |
| |
| #define PM_PROC_PARSE_ADD_CMD PM_PROC_ADD_CMD" %"STR(PM_MAX_NAME_LEN)"s %d" /* syntax: "add who_ask NNN" */ |
| #define PM_PROC_PARSE_UPDATE_CMD PM_PROC_UPDATE_CMD" %"STR(PM_MAX_NAME_LEN)"s %d" /* syntax: "update who_ask NNN" */ |
| #define PM_PROC_PARSE_REMOVE_CMD PM_PROC_REMOVE_CMD" %"STR(PM_MAX_NAME_LEN)"s" /* syntax: "remove who_ask" */ |
| |
| static struct workqueue_struct *pm_wq; |
| static DEFINE_SPINLOCK(pm_wq_lock); |
| |
| static int pm_write_proc(struct file *file, const char __user *buffer, unsigned long count, void *data) |
| { |
| char cmd[PM_MAX_NAME_LEN + 32]; |
| char name[PM_MAX_NAME_LEN + 1]; |
| int val; |
| int ret = 0; |
| |
| if (!count) { |
| return -EINVAL; |
| } else if (count > sizeof(cmd) - 1) { |
| return -EINVAL; |
| } else if (copy_from_user(cmd, buffer, count)) { |
| return -EFAULT; |
| } |
| cmd[count] = '\0'; |
| |
| if (sscanf(cmd, PM_PROC_PARSE_ADD_CMD, name, &val) == 2) { |
| ret = pm_qos_add_requirement(PM_QOS_POWER_SAVE, name, val); |
| } else if (sscanf(cmd, PM_PROC_PARSE_UPDATE_CMD, name, &val) == 2) { |
| ret = pm_qos_update_requirement(PM_QOS_POWER_SAVE, name, val); |
| } else if (sscanf(cmd, PM_PROC_PARSE_REMOVE_CMD, name) == 1) { |
| pm_qos_remove_requirement(PM_QOS_POWER_SAVE, name); |
| } else { |
| ret = -EINVAL; |
| } |
| |
| return ret ? ret : count; |
| } |
| |
| static int pm_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data) |
| { |
| return sprintf(page, "%d", pm_qos_requirement(PM_QOS_POWER_SAVE)); |
| } |
| |
| static void __init pm_create_wq(void) |
| { |
| pm_wq = create_rt_workqueue("ruby_pm"); |
| } |
| |
| static int __init pm_create_proc(void) |
| { |
| /* |
| * Proc interface to change power save levels. |
| * Main purpose is debugging. |
| * Other kernel modules can directly use pm_qos_*() functions. |
| * User-space application (safe way) can open misc character device (major number 10, minor see by "cat /proc/misc") |
| * provided by PM QoS kernel module and control through it. It is safe because 'name' is chosen based on PID, |
| * and when application quit all its requests are removed. |
| */ |
| |
| struct proc_dir_entry *entry = create_proc_entry(PM_PROC_FILE_NAME, 0600, NULL); |
| if (!entry) { |
| return -ENOMEM; |
| } |
| |
| entry->write_proc = pm_write_proc; |
| entry->read_proc = pm_read_proc; |
| |
| return 0; |
| } |
| |
| static int __init pm_init(void) |
| { |
| pm_qos_add_requirement(PM_QOS_POWER_SAVE, BOARD_PM_GOVERNOR_QCSAPI, BOARD_PM_LEVEL_INIT); |
| pm_create_wq(); |
| return pm_create_proc(); |
| } |
| arch_initcall(pm_init); |
| |
| static int __pm_cancel_work(struct delayed_work *dwork) |
| { |
| int ret = 0; |
| |
| if (delayed_work_pending(dwork)) { |
| cancel_delayed_work(dwork); |
| ret = 1; |
| } else if (work_pending(&dwork->work)) { |
| cancel_work_sync(&dwork->work); |
| ret = 1; |
| } |
| |
| return ret; |
| } |
| |
| int pm_queue_work(struct delayed_work *dwork, unsigned long delay) |
| { |
| unsigned long flags; |
| int ret = 0; |
| |
| spin_lock_irqsave(&pm_wq_lock, flags); |
| |
| ret = __pm_cancel_work(dwork); |
| queue_delayed_work(pm_wq, dwork, delay); |
| |
| spin_unlock_irqrestore(&pm_wq_lock, flags); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(pm_queue_work); |
| |
| int pm_cancel_work(struct delayed_work *dwork) |
| { |
| unsigned long flags; |
| int ret = 0; |
| |
| spin_lock_irqsave(&pm_wq_lock, flags); |
| |
| ret = __pm_cancel_work(dwork); |
| |
| spin_unlock_irqrestore(&pm_wq_lock, flags); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(pm_cancel_work); |
| |
| int pm_flush_work(struct delayed_work *dwork) |
| { |
| might_sleep(); |
| return cancel_delayed_work_sync(dwork); |
| } |
| EXPORT_SYMBOL(pm_flush_work); |
| |
| MODULE_AUTHOR("Quantenna"); |
| MODULE_LICENSE("GPL"); |
| |