18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * linux/drivers/devfreq/governor_userspace.c 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2011 Samsung Electronics 68c2ecf20Sopenharmony_ci * MyungJoo Ham <myungjoo.ham@samsung.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/slab.h> 108c2ecf20Sopenharmony_ci#include <linux/device.h> 118c2ecf20Sopenharmony_ci#include <linux/devfreq.h> 128c2ecf20Sopenharmony_ci#include <linux/pm.h> 138c2ecf20Sopenharmony_ci#include <linux/mutex.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include "governor.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistruct userspace_data { 188c2ecf20Sopenharmony_ci unsigned long user_frequency; 198c2ecf20Sopenharmony_ci bool valid; 208c2ecf20Sopenharmony_ci}; 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic int devfreq_userspace_func(struct devfreq *df, unsigned long *freq) 238c2ecf20Sopenharmony_ci{ 248c2ecf20Sopenharmony_ci struct userspace_data *data = df->governor_data; 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci if (data->valid) 278c2ecf20Sopenharmony_ci *freq = data->user_frequency; 288c2ecf20Sopenharmony_ci else 298c2ecf20Sopenharmony_ci *freq = df->previous_freq; /* No user freq specified yet */ 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci return 0; 328c2ecf20Sopenharmony_ci} 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic ssize_t store_freq(struct device *dev, struct device_attribute *attr, 358c2ecf20Sopenharmony_ci const char *buf, size_t count) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci struct devfreq *devfreq = to_devfreq(dev); 388c2ecf20Sopenharmony_ci struct userspace_data *data; 398c2ecf20Sopenharmony_ci unsigned long wanted; 408c2ecf20Sopenharmony_ci int err = 0; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci mutex_lock(&devfreq->lock); 438c2ecf20Sopenharmony_ci data = devfreq->governor_data; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci sscanf(buf, "%lu", &wanted); 468c2ecf20Sopenharmony_ci data->user_frequency = wanted; 478c2ecf20Sopenharmony_ci data->valid = true; 488c2ecf20Sopenharmony_ci err = update_devfreq(devfreq); 498c2ecf20Sopenharmony_ci if (err == 0) 508c2ecf20Sopenharmony_ci err = count; 518c2ecf20Sopenharmony_ci mutex_unlock(&devfreq->lock); 528c2ecf20Sopenharmony_ci return err; 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic ssize_t show_freq(struct device *dev, struct device_attribute *attr, 568c2ecf20Sopenharmony_ci char *buf) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci struct devfreq *devfreq = to_devfreq(dev); 598c2ecf20Sopenharmony_ci struct userspace_data *data; 608c2ecf20Sopenharmony_ci int err = 0; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci mutex_lock(&devfreq->lock); 638c2ecf20Sopenharmony_ci data = devfreq->governor_data; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci if (data->valid) 668c2ecf20Sopenharmony_ci err = sprintf(buf, "%lu\n", data->user_frequency); 678c2ecf20Sopenharmony_ci else 688c2ecf20Sopenharmony_ci err = sprintf(buf, "undefined\n"); 698c2ecf20Sopenharmony_ci mutex_unlock(&devfreq->lock); 708c2ecf20Sopenharmony_ci return err; 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic DEVICE_ATTR(set_freq, 0644, show_freq, store_freq); 748c2ecf20Sopenharmony_cistatic struct attribute *dev_entries[] = { 758c2ecf20Sopenharmony_ci &dev_attr_set_freq.attr, 768c2ecf20Sopenharmony_ci NULL, 778c2ecf20Sopenharmony_ci}; 788c2ecf20Sopenharmony_cistatic const struct attribute_group dev_attr_group = { 798c2ecf20Sopenharmony_ci .name = DEVFREQ_GOV_USERSPACE, 808c2ecf20Sopenharmony_ci .attrs = dev_entries, 818c2ecf20Sopenharmony_ci}; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_cistatic int userspace_init(struct devfreq *devfreq) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci int err = 0; 868c2ecf20Sopenharmony_ci struct userspace_data *data = kzalloc(sizeof(struct userspace_data), 878c2ecf20Sopenharmony_ci GFP_KERNEL); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (!data) { 908c2ecf20Sopenharmony_ci err = -ENOMEM; 918c2ecf20Sopenharmony_ci goto out; 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci data->valid = false; 948c2ecf20Sopenharmony_ci devfreq->governor_data = data; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci err = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group); 978c2ecf20Sopenharmony_ciout: 988c2ecf20Sopenharmony_ci return err; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic void userspace_exit(struct devfreq *devfreq) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci /* 1048c2ecf20Sopenharmony_ci * Remove the sysfs entry, unless this is being called after 1058c2ecf20Sopenharmony_ci * device_del(), which should have done this already via kobject_del(). 1068c2ecf20Sopenharmony_ci */ 1078c2ecf20Sopenharmony_ci if (devfreq->dev.kobj.sd) 1088c2ecf20Sopenharmony_ci sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci kfree(devfreq->governor_data); 1118c2ecf20Sopenharmony_ci devfreq->governor_data = NULL; 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic int devfreq_userspace_handler(struct devfreq *devfreq, 1158c2ecf20Sopenharmony_ci unsigned int event, void *data) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci int ret = 0; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci switch (event) { 1208c2ecf20Sopenharmony_ci case DEVFREQ_GOV_START: 1218c2ecf20Sopenharmony_ci ret = userspace_init(devfreq); 1228c2ecf20Sopenharmony_ci break; 1238c2ecf20Sopenharmony_ci case DEVFREQ_GOV_STOP: 1248c2ecf20Sopenharmony_ci userspace_exit(devfreq); 1258c2ecf20Sopenharmony_ci break; 1268c2ecf20Sopenharmony_ci default: 1278c2ecf20Sopenharmony_ci break; 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci return ret; 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic struct devfreq_governor devfreq_userspace = { 1348c2ecf20Sopenharmony_ci .name = DEVFREQ_GOV_USERSPACE, 1358c2ecf20Sopenharmony_ci .get_target_freq = devfreq_userspace_func, 1368c2ecf20Sopenharmony_ci .event_handler = devfreq_userspace_handler, 1378c2ecf20Sopenharmony_ci}; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistatic int __init devfreq_userspace_init(void) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci return devfreq_add_governor(&devfreq_userspace); 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_cisubsys_initcall(devfreq_userspace_init); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cistatic void __exit devfreq_userspace_exit(void) 1468c2ecf20Sopenharmony_ci{ 1478c2ecf20Sopenharmony_ci int ret; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci ret = devfreq_remove_governor(&devfreq_userspace); 1508c2ecf20Sopenharmony_ci if (ret) 1518c2ecf20Sopenharmony_ci pr_err("%s: failed remove governor %d\n", __func__, ret); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci return; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_cimodule_exit(devfreq_userspace_exit); 1568c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 157