blob: 99778bd3a2107c84918fb4d2ee5f502043548fc8 [file] [log] [blame]
/*
* 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 <asm/uaccess.h>
#include <asm/board/pm.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include "pm.h"
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#define PM_PROC_FILE_NAME "soc_pm"
#define PM_EMAC_PROC_FILE_NAME "emac_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);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
struct qtn_pm_req_list {
struct list_head list;
int class_id;
char req_name[16];
struct pm_qos_request req;
};
static LIST_HEAD(qtn_pm_req_list_head);
static struct pm_qos_request *pm_emac_req;
#else
static struct pm_qos_request_list *pm_emac_req;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
static ssize_t pm_write_proc(struct file *file, const char __user *buffer,
size_t count, loff_t *f_pos)
#else
static int pm_write_proc(struct file *file, const char __user *buffer,
unsigned long count, void *data)
#endif
{
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 LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
switch ((int)PDE_DATA(file_inode(file))) {
#else
switch ((int)data) {
#endif
case PM_QOS_POWER_SAVE:
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;
}
break;
case PM_QOS_POWER_EMAC:
if (sscanf(cmd, "%d", &val) == 1) {
pm_qos_update_request(pm_emac_req, val);
} else {
ret = -EINVAL;
}
break;
default:
ret = -EINVAL;
break;
}
return ret ? ret : count;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
static int pm_show(struct seq_file *seq, void *v)
{
int class_id = (int)seq->private;
seq_printf(seq, "class=%d, level=%d\n", class_id, pm_qos_requirement(class_id));
return 0;
}
static int pm_open_proc(struct inode *inode, struct file *file)
{
return single_open(file, pm_show, PDE_DATA(inode));
}
static const struct file_operations pm_save_fops = {
.owner = THIS_MODULE,
.open = pm_open_proc,
.write = pm_write_proc,
.read = seq_read,
};
#else
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((int)data));
}
#endif
static int __init pm_create_proc(void)
{
struct proc_dir_entry *entry;
struct proc_dir_entry *emac_proc_entry;
/*
* 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.
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
entry = proc_create_data(PM_PROC_FILE_NAME, 0600, NULL, &pm_save_fops, (void *)PM_QOS_POWER_SAVE);
if (!entry) {
return -ENODEV;
}
emac_proc_entry = proc_create_data(PM_EMAC_PROC_FILE_NAME, 0600, NULL, &pm_save_fops, (void *)PM_QOS_POWER_EMAC);
if (!emac_proc_entry) {
return -ENODEV;
}
#else
entry = create_proc_entry(PM_PROC_FILE_NAME, 0600, NULL);
if (!entry) {
return -ENODEV;
}
entry->write_proc = pm_write_proc;
entry->read_proc = pm_read_proc;
entry->data = (void *)PM_QOS_POWER_SAVE;
emac_proc_entry = create_proc_entry(PM_EMAC_PROC_FILE_NAME, 0600, NULL);
if (!emac_proc_entry) {
return -ENODEV;
}
emac_proc_entry->write_proc = pm_write_proc;
emac_proc_entry->read_proc = pm_read_proc;
emac_proc_entry->data = (void *)PM_QOS_POWER_EMAC;
#endif
return 0;
}
static void __init pm_create_wq(void)
{
pm_wq = create_workqueue("ruby_pm");
}
static int __init pm_init(void)
{
pm_qos_add_requirement(PM_QOS_POWER_SAVE, BOARD_PM_GOVERNOR_QCSAPI, BOARD_PM_LEVEL_INIT);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0)
if(NULL == (pm_emac_req = kzalloc(sizeof(*pm_emac_req), GFP_KERNEL))) {
return -ENOMEM;
}
pm_qos_add_request(pm_emac_req, PM_QOS_POWER_EMAC, BOARD_PM_LEVEL_NO);
#else
pm_emac_req = pm_qos_add_request(PM_QOS_POWER_EMAC, BOARD_PM_LEVEL_NO);
if (!pm_emac_req)
return -ENOMEM;
#endif
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);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,24)
int pm_qos_add_requirement(int pm_qos_class, const char *name, s32 value)
{
struct qtn_pm_req_list *req_list = NULL;
if ((NULL == name) || (pm_qos_class < PM_QOS_POWER_SAVE)
|| (pm_qos_class > PM_QOS_POWER_EMAC)) {
return -EINVAL;
}
list_for_each_entry(req_list, &qtn_pm_req_list_head, list) {
if ((pm_qos_class == req_list->class_id)
&& !strncmp(name, req_list->req_name, sizeof(req_list->req_name))) {
return -EEXIST;
}
}
if(NULL == (req_list = kzalloc(sizeof(*req_list), GFP_KERNEL))) {
return -ENOMEM;
}
req_list->class_id = pm_qos_class;
strncpy(req_list->req_name, name, sizeof(req_list->req_name));
pm_qos_add_request(&req_list->req, pm_qos_class, value);
list_add(&req_list->list, &qtn_pm_req_list_head);
return 0;
}
EXPORT_SYMBOL(pm_qos_add_requirement);
int pm_qos_update_requirement(int pm_qos_class, const char *name, s32 new_value)
{
struct qtn_pm_req_list *req_list = NULL;
if ((NULL == name) || (pm_qos_class < PM_QOS_POWER_SAVE)
|| (pm_qos_class > PM_QOS_POWER_EMAC)) {
return -EINVAL;
}
printk("%s: name=%s, value=%d\n", __func__, name, new_value);
list_for_each_entry(req_list, &qtn_pm_req_list_head, list) {
if ((pm_qos_class == req_list->class_id)
&& !strncmp(name, req_list->req_name, sizeof(req_list->req_name))) {
pm_qos_update_request(&req_list->req, new_value);
return 0;
}
}
return 0;
}
EXPORT_SYMBOL(pm_qos_update_requirement);
void pm_qos_remove_requirement(int pm_qos_class, const char *name)
{
struct qtn_pm_req_list *req_list = NULL;
if ((NULL == name) || (pm_qos_class < PM_QOS_POWER_SAVE)
|| (pm_qos_class > PM_QOS_POWER_EMAC)) {
return ;
}
list_for_each_entry(req_list, &qtn_pm_req_list_head, list) {
if ((pm_qos_class == req_list->class_id)
&& !strncmp(name, req_list->req_name, sizeof(req_list->req_name))) {
break;
}
}
pm_qos_remove_request(&req_list->req);
list_del(&req_list->list);
kfree(req_list);
}
EXPORT_SYMBOL(pm_qos_remove_requirement);
int pm_qos_requirement(int pm_qos_class)
{
return pm_qos_request(pm_qos_class);
}
EXPORT_SYMBOL(pm_qos_requirement);
#endif
MODULE_AUTHOR("Quantenna");
MODULE_LICENSE("GPL");