aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Harkin <ryan.harkin@linaro.org>2017-07-27 09:48:33 +0100
committerRyan Harkin <ryan.harkin@linaro.org>2017-07-27 09:48:33 +0100
commitf1f404eafaddc479b218357d6547259fe5a475c5 (patch)
tree53fe7b59d34a800272801a1740f209b1a6ede414
parent4b8c31fd3f1101587f55c9d15985d721c1094603 (diff)
parent4650947c75770b946b7b08e156cfec481bfc3304 (diff)
Merge branch '4.4-armlt-scpi' into 4.4-armlt
-rw-r--r--drivers/firmware/Kconfig14
-rw-r--r--drivers/firmware/Makefile2
-rw-r--r--drivers/firmware/arm_scpi.c51
-rw-r--r--drivers/firmware/arm_scpi_test.c568
-rw-r--r--drivers/firmware/scpi_pm_domain.c163
-rw-r--r--drivers/hwmon/scpi-hwmon.c14
-rw-r--r--include/linux/scpi_protocol.h5
7 files changed, 802 insertions, 15 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 49a3a1185bb6..14bdb5a538b9 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -27,6 +27,20 @@ config ARM_SCPI_PROTOCOL
This protocol library provides interface for all the client drivers
making use of the features offered by the SCP.
+config ARM_SCPI_POWER_DOMAIN
+ tristate "SCPI power domain driver"
+ depends on ARM_SCPI_PROTOCOL || COMPILE_TEST
+ default y
+ select PM_GENERIC_DOMAINS if PM
+ select PM_GENERIC_DOMAINS_OF if PM
+ help
+ This enables support for the SCPI power domains which can be
+ enabled or disabled via the SCP firmware
+
+config ARM_SCPI_PROTOCOL_TEST
+ tristate "Test code for SCPI Message Protocol"
+ default ARM_SCPI_PROTOCOL
+
config EDD
tristate "BIOS Enhanced Disk Drive calls determine boot disk"
depends on X86
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 48dd4175297e..af4e23c4c883 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -3,6 +3,8 @@
#
obj-$(CONFIG_ARM_PSCI_FW) += psci.o
obj-$(CONFIG_ARM_SCPI_PROTOCOL) += arm_scpi.o
+obj-$(CONFIG_ARM_SCPI_POWER_DOMAIN) += scpi_pm_domain.o
+obj-$(CONFIG_ARM_SCPI_PROTOCOL_TEST) += arm_scpi_test.o
obj-$(CONFIG_DMI) += dmi_scan.o
obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o
obj-$(CONFIG_EDD) += edd.o
diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c
index 1151b5b12839..c1d25bf3296c 100644
--- a/drivers/firmware/arm_scpi.c
+++ b/drivers/firmware/arm_scpi.c
@@ -208,10 +208,6 @@ struct dvfs_info {
} opps[MAX_DVFS_OPPS];
} __packed;
-struct dvfs_get {
- u8 index;
-} __packed;
-
struct dvfs_set {
u8 domain;
u8 index;
@@ -229,7 +225,13 @@ struct _scpi_sensor_info {
};
struct sensor_value {
- __le32 val;
+ __le32 lo_val;
+ __le32 hi_val;
+} __packed;
+
+struct dev_pstate_set {
+ u16 dev_id;
+ u8 pstate;
} __packed;
static struct scpi_drvinfo *scpi_info;
@@ -447,11 +449,11 @@ static int scpi_clk_set_val(u16 clk_id, unsigned long rate)
static int scpi_dvfs_get_idx(u8 domain)
{
int ret;
- struct dvfs_get dvfs;
+ u8 dvfs_idx;
ret = scpi_send_message(SCPI_CMD_GET_DVFS, &domain, sizeof(domain),
- &dvfs, sizeof(dvfs));
- return ret ? ret : dvfs.index;
+ &dvfs_idx, sizeof(dvfs_idx));
+ return ret ? ret : dvfs_idx;
}
static int scpi_dvfs_set_idx(u8 domain, u8 index)
@@ -552,19 +554,44 @@ static int scpi_sensor_get_info(u16 sensor_id, struct scpi_sensor_info *info)
return ret;
}
-int scpi_sensor_get_value(u16 sensor, u32 *val)
+static int scpi_sensor_get_value(u16 sensor, u64 *val)
{
+ __le16 id = cpu_to_le16(sensor);
struct sensor_value buf;
int ret;
- ret = scpi_send_message(SCPI_CMD_SENSOR_VALUE, &sensor, sizeof(sensor),
+ ret = scpi_send_message(SCPI_CMD_SENSOR_VALUE, &id, sizeof(id),
&buf, sizeof(buf));
if (!ret)
- *val = le32_to_cpu(buf.val);
+ *val = (u64)le32_to_cpu(buf.hi_val) << 32 |
+ le32_to_cpu(buf.lo_val);
return ret;
}
+static int scpi_device_get_power_state(u16 dev_id)
+{
+ int ret;
+ u8 pstate;
+ __le16 id = cpu_to_le16(dev_id);
+
+ ret = scpi_send_message(SCPI_CMD_GET_DEVICE_PWR_STATE, &id,
+ sizeof(id), &pstate, sizeof(pstate));
+ return ret ? ret : pstate;
+}
+
+static int scpi_device_set_power_state(u16 dev_id, u8 pstate)
+{
+ int stat;
+ struct dev_pstate_set dev_set = {
+ .dev_id = cpu_to_le16(dev_id),
+ .pstate = pstate,
+ };
+
+ return scpi_send_message(SCPI_CMD_SET_DEVICE_PWR_STATE, &dev_set,
+ sizeof(dev_set), &stat, sizeof(stat));
+}
+
static struct scpi_ops scpi_ops = {
.get_version = scpi_get_version,
.clk_get_range = scpi_clk_get_range,
@@ -576,6 +603,8 @@ static struct scpi_ops scpi_ops = {
.sensor_get_capability = scpi_sensor_get_capability,
.sensor_get_info = scpi_sensor_get_info,
.sensor_get_value = scpi_sensor_get_value,
+ .device_get_power_state = scpi_device_get_power_state,
+ .device_set_power_state = scpi_device_set_power_state,
};
struct scpi_ops *get_scpi_ops(void)
diff --git a/drivers/firmware/arm_scpi_test.c b/drivers/firmware/arm_scpi_test.c
new file mode 100644
index 000000000000..c21b35e6f4b8
--- /dev/null
+++ b/drivers/firmware/arm_scpi_test.c
@@ -0,0 +1,568 @@
+/*
+ * Test code for System Control and Power Interface (SCPI)
+ *
+ * Copyright (C) 2015 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/scpi_protocol.h>
+
+
+static int stress_time;
+module_param(stress_time, int, 0644);
+MODULE_PARM_DESC(stress_time, "Number of seconds to run each stress test for, overides each test's default.");
+
+static int run;
+static struct kernel_param_ops run_ops;
+module_param_cb(run, &run_ops, &run, 0644);
+MODULE_PARM_DESC(run, "The number of the test case to run, or -1 for all, 0 to stop tests.");
+
+
+static struct scpi_ops *scpi;
+
+static struct task_struct *main_thread;
+
+static DEFINE_MUTEX(main_thread_lock);
+
+
+#define MAX_TEST_THREADS 4
+
+static struct test_thread {
+ struct task_struct *task;
+ int thread_num;
+} test_threads[MAX_TEST_THREADS];
+
+static DEFINE_MUTEX(thread_lock);
+
+
+#define MAX_POWER_DOMAINS 8
+
+static u16 num_sensors;
+static u8 num_pd;
+static u8 num_opps[MAX_POWER_DOMAINS];
+static struct mutex pd_lock[MAX_POWER_DOMAINS];
+static u8 num_devices_with_power_states;
+
+
+#define FLAG_SERIAL_DVFS (1<<0)
+#define FLAG_SERIAL_PD (1<<1)
+
+static int test_flags;
+
+
+static int sensor_pmic = -1;
+
+
+static u32 random_seed;
+
+static u32 random(u32 range)
+{
+ random_seed = random_seed*69069+1;
+
+ return ((u64)random_seed * (u64)range) >> 32;
+}
+
+
+static atomic_t passes;
+static atomic_t failures;
+
+static bool fail_on(bool fail)
+{
+ if (fail)
+ atomic_inc(&failures);
+ else
+ atomic_inc(&passes);
+ return fail;
+}
+
+static void show_results(const char *title)
+{
+ int fail = atomic_xchg(&failures, 0);
+ int pass = atomic_xchg(&passes, 0);
+
+ if (fail)
+ pr_err("Results for '%s' is %d/%d (pass/fail)\n", title, pass, fail);
+ else
+ pr_info("Results for '%s' is %d/%d (pass/fail)\n", title, pass, fail);
+}
+
+
+static bool check_name(const char *name)
+{
+ char c;
+
+ if (!isalpha(*name++))
+ return false;
+
+ while ((c = *name++))
+ if (!isalnum(c) && c != '_')
+ return false;
+
+ return true;
+}
+
+static u64 get_sensor(u16 id)
+{
+ u64 val;
+ int ret;
+
+ ret = scpi->sensor_get_value(id, &val);
+ if (fail_on(ret < 0))
+ pr_err("FAILED sensor_get_value %d (%d)\n", id, ret);
+
+ return val;
+}
+
+static void init_sensors(void)
+{
+ u16 id;
+ int ret;
+
+ ret = scpi->sensor_get_capability(&num_sensors);
+ if (fail_on(ret))
+ pr_err("FAILED sensor_get_capability (%d)\n", ret);
+
+ pr_info("num_sensors: %d\n", num_sensors);
+
+ for (id = 0; id < num_sensors; id++) {
+
+ struct scpi_sensor_info info;
+ char name[sizeof(info.name) + 1];
+
+ ret = scpi->sensor_get_info(id, &info);
+ if (fail_on(ret)) {
+ pr_err("FAILED sensor_get_info (%d)\n", ret);
+ continue;
+ }
+
+ /* Get sensor name, guarding against missing NUL terminator */
+ memcpy(name, info.name, sizeof(info.name));
+ name[sizeof(info.name)] = 0;
+
+ pr_info("sensor[%d] id=%d class=%d trigger=%d name=%s\n", id,
+ info.sensor_id, info.class, info.trigger_type, name);
+
+ if (fail_on(id != info.sensor_id))
+ pr_err("FAILED bad sensor id\n");
+ if (fail_on(info.class > 4))
+ pr_err("FAILED bad sensor class\n");
+ if (fail_on(info.trigger_type > 3))
+ pr_err("FAILED bad sensor trigger type\n");
+ if (fail_on(strlen(name) >= sizeof(info.name) || !check_name(name)))
+ pr_err("FAILED bad name\n");
+
+ pr_info("sensor[%d] value is %llu\n", id, get_sensor(id));
+ if (strstr(name, "PMIC"))
+ sensor_pmic = id;
+ }
+}
+
+static int get_dvfs(u8 pd)
+{
+ int ret = scpi->dvfs_get_idx(pd);
+
+ if (fail_on(ret < 0))
+ pr_err("FAILED get_dvfs %d (%d)\n", pd, ret);
+ else if (fail_on(ret >= num_opps[pd]))
+ pr_err("FAILED get_dvfs %d returned out of range index (%d)\n", pd, ret);
+
+ return ret;
+}
+
+static int set_dvfs(u8 pd, u8 opp)
+{
+ int ret;
+
+ if (test_flags & FLAG_SERIAL_DVFS)
+ mutex_lock(&pd_lock[0]);
+ else if (test_flags & FLAG_SERIAL_PD)
+ mutex_lock(&pd_lock[pd]);
+
+ ret = scpi->dvfs_set_idx(pd, opp);
+
+ if (test_flags & FLAG_SERIAL_DVFS)
+ mutex_unlock(&pd_lock[0]);
+ else if (test_flags & FLAG_SERIAL_PD)
+ mutex_unlock(&pd_lock[pd]);
+
+ if (fail_on(ret < 0))
+ pr_err("FAILED set_dvfs %d %d (%d)\n", pd, opp, ret);
+
+ return ret;
+}
+
+static void init_dvfs(void)
+{
+ u8 pd;
+
+ for (pd = 0; pd < MAX_POWER_DOMAINS; ++pd) {
+ struct scpi_dvfs_info *info;
+ int opp;
+
+ info = scpi->dvfs_get_info(pd);
+ if (IS_ERR(info)) {
+ pr_info("dvfs_get_info %d failed with %d assume because no more power domains\n",
+ pd, (int)PTR_ERR(info));
+ break;
+ }
+
+ num_opps[pd] = info->count;
+ mutex_init(&pd_lock[pd]);
+ pr_info("pd[%d] count=%u latency=%u\n",
+ pd, info->count, info->latency);
+
+ opp = get_dvfs(pd);
+ pr_info("pd[%d] current opp=%d\n", pd, opp);
+
+ for (opp = 0; opp < info->count; ++opp) {
+ pr_info("pd[%d].opp[%d] freq=%u m_volt=%u\n", pd, opp,
+ info->opps[opp].freq, info->opps[opp].m_volt);
+ /*
+ * Try setting each opp. Note, failure is not necessarily
+ * an error because cpufreq may be setting values too.
+ */
+ set_dvfs(pd, opp);
+ if (get_dvfs(pd) == opp)
+ pr_info("pd[%d] set to opp %d OK\n", pd, opp);
+ else
+ pr_warn("pd[%d] failed to set opp to %d\n", pd, opp);
+ }
+ }
+
+ if (!pd) {
+ /* Assume device should have at least one DVFS power domain */
+ pr_err("FAILED no power domains\n");
+ fail_on(true);
+ }
+ num_pd = pd;
+}
+
+static int device_get_power_state(u16 dev_id)
+{
+ int ret;
+
+ ret = scpi->device_get_power_state(dev_id);
+ if (fail_on(ret < 0))
+ pr_err("FAILED device_get_power_state %d (%d)\n", dev_id, ret);
+
+ return ret;
+}
+
+static int device_set_power_state(u16 dev_id, u8 pstate)
+{
+ int ret;
+
+ ret = scpi->device_set_power_state(dev_id, pstate);
+ if (fail_on(ret < 0))
+ pr_err("FAILED device_get_power_state %d (%d)\n", dev_id, ret);
+
+ return ret;
+}
+
+static void init_device_power_states(void)
+{
+ u16 dev_id;
+
+ for (dev_id = 0; dev_id < 0xffff; ++dev_id) {
+ int state = scpi->device_get_power_state(dev_id);
+
+ if (state < 0) {
+ pr_info("device_get_power_state %d failed with %d assume because no more devices\n",
+ dev_id, state);
+ break;
+ }
+
+ pr_info("device[%d] current state=%d\n", dev_id, state);
+
+ device_set_power_state(dev_id, state);
+ if (device_get_power_state(dev_id) == state)
+ pr_info("device[%d] set state to %d OK\n", dev_id, state);
+ else
+ pr_warn("device[%d] failed set state to %d\n", dev_id, state);
+ }
+
+ if (!dev_id) {
+ /* Assume device should have at least one device power state */
+ pr_err("FAILED no devices with power states\n");
+ fail_on(true);
+ }
+ num_devices_with_power_states = dev_id;
+}
+
+static int stress_pmic(void *data)
+{
+ int sensor, pd, opp;
+
+ while (!kthread_should_stop()) {
+ sensor = sensor_pmic;
+ pd = random(num_pd);
+ opp = random(num_opps[pd]);
+
+ switch (random(3)) {
+ case 0:
+ if (sensor >= 0) {
+ get_sensor(sensor);
+ break;
+ }
+ /* If no sensor, do DFVS... */
+ case 1:
+ set_dvfs(pd, opp);
+ break;
+ default:
+ msleep(random(20));
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int stress_all(void *data)
+{
+ int sensor, pd, opp;
+
+ while (!kthread_should_stop()) {
+ sensor = random(num_sensors);
+ pd = random(num_pd);
+ opp = random(num_opps[pd]);
+
+ switch (random(4)) {
+ case 0:
+ set_dvfs(pd, opp);
+ break;
+ case 1:
+ opp = get_dvfs(pd);
+ break;
+ case 2:
+ get_sensor(sensor);
+ break;
+ default:
+ msleep(random(20));
+ break;
+ }
+ }
+
+ return 0;
+}
+
+struct test {
+ const char *title;
+ int (*thread_fn)(void *);
+ int flags;
+ int num_threads;
+ int duration;
+};
+
+static void stop_test_threads(void)
+{
+ int t, ret;
+
+ for (t = 0; t < MAX_TEST_THREADS; ++t) {
+ struct test_thread *thread = &test_threads[t];
+
+ mutex_lock(&thread_lock);
+ if (thread->task) {
+ ret = kthread_stop(thread->task);
+ thread->task = NULL;
+ if (ret)
+ pr_warn("Test thread %d exited with status %d\n", t, ret);
+ }
+ mutex_unlock(&thread_lock);
+ }
+}
+
+static void run_test(struct test *test)
+{
+ int num_threads = min(test->num_threads, MAX_TEST_THREADS);
+ int duration = stress_time;
+ int t;
+
+ if (test->duration <= 0)
+ duration = 0;
+ else if (duration <= 0)
+ duration = test->duration;
+
+ pr_info("Running test '%s' for %d seconds\n", test->title, duration);
+
+ test_flags = test->flags;
+
+ for (t = 0; t < num_threads; ++t) {
+ struct test_thread *thread = &test_threads[t];
+ struct task_struct *task;
+
+ mutex_lock(&thread_lock);
+ thread->thread_num = t;
+ task = kthread_run(test->thread_fn, thread, "scpi-test-%d", t);
+ if (IS_ERR(task))
+ pr_warn("Failed to create test thread %d\n", t);
+ else
+ thread->task = task;
+ mutex_unlock(&thread_lock);
+ }
+
+ schedule_timeout_interruptible(msecs_to_jiffies(duration * 1000));
+
+ stop_test_threads();
+
+ show_results(test->title);
+}
+
+static struct test tests[] = {
+ {"Stress All, concurrent DVFS",
+ stress_all, 0, MAX_TEST_THREADS, 60},
+ {"Stress All, concurrent DVFS on different PDs",
+ stress_all, FLAG_SERIAL_PD, MAX_TEST_THREADS, 60},
+ {"Stress All, no concurrent DVFS",
+ stress_all, FLAG_SERIAL_DVFS, MAX_TEST_THREADS, 60},
+ {"Stress PMIC, concurrent DVFS",
+ stress_pmic, 0, MAX_TEST_THREADS, 60},
+ {"Stress PMIC, concurrent DVFS on different PDs",
+ stress_pmic, FLAG_SERIAL_PD, MAX_TEST_THREADS, 60},
+ {"Stress PMIC, no concurrent DVFS",
+ stress_pmic, FLAG_SERIAL_DVFS, MAX_TEST_THREADS, 60},
+ {}
+};
+
+static int main_thread_fn(void *data)
+{
+ struct test *test = tests;
+ int i = 1;
+
+ for (; test->title && !kthread_should_stop(); ++test, ++i)
+ if (run < 0 || run == i)
+ run_test(test);
+
+ run = 0;
+ return 0;
+}
+
+static DEFINE_MUTEX(setup_lock);
+
+static int setup(void)
+{
+ int ret = 0;
+
+ mutex_lock(&setup_lock);
+
+ if (!scpi) {
+ int tries = 12;
+
+ pr_info("Initial setup\n");
+ while ((scpi = get_scpi_ops()) == 0 && --tries) {
+ pr_info("Waiting for get_scpi_ops\n");
+ msleep(5000);
+ }
+
+ if (scpi) {
+ init_sensors();
+ init_dvfs();
+ init_device_power_states();
+ show_results("Initial setup");
+ } else {
+ pr_err("Given up on get_scpi_ops\n");
+ ret = -ENODEV;
+ }
+ }
+
+ mutex_unlock(&setup_lock);
+
+ return ret;
+}
+
+static int start_tests(void)
+{
+ struct task_struct *task;
+ int ret;
+
+ ret = setup();
+ if (ret) {
+ run = 0;
+ return ret;
+ }
+
+ pr_info("Creating main thread\n");
+ mutex_lock(&main_thread_lock);
+ if (main_thread) {
+ ret = -EBUSY;
+ } else {
+ task = kthread_run(main_thread_fn, 0, "scpi-test-main");
+ if (IS_ERR(task))
+ ret = PTR_ERR(task);
+ else
+ main_thread = task;
+ }
+ mutex_unlock(&main_thread_lock);
+
+ if (ret) {
+ pr_err("Failed to create main thread (%d)\n", ret);
+ run = 0;
+ }
+
+ return ret;
+}
+
+static void stop_tests(void)
+{
+ pr_info("Stopping tests\n");
+ mutex_lock(&main_thread_lock);
+ if (main_thread)
+ kthread_stop(main_thread);
+ main_thread = NULL;
+ mutex_unlock(&main_thread_lock);
+}
+
+static int param_set_running(const char *val, const struct kernel_param *kp)
+{
+ int ret;
+
+ ret = param_set_int(val, kp);
+ if (!ret) {
+ if (run)
+ ret = start_tests();
+ else
+ stop_tests();
+ }
+
+ return ret;
+}
+
+static struct kernel_param_ops run_ops = {
+ .set = param_set_running,
+ .get = param_get_int,
+};
+
+
+static int scpi_test_init(void)
+{
+ return 0;
+}
+
+static void scpi_test_exit(void)
+{
+ stop_tests();
+}
+
+module_init(scpi_test_init);
+module_exit(scpi_test_exit);
+
+
+MODULE_AUTHOR("Jon Medhurst (Tixy) <tixy@linaro.org>");
+MODULE_DESCRIPTION("ARM SCPI driver tests");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/scpi_pm_domain.c b/drivers/firmware/scpi_pm_domain.c
new file mode 100644
index 000000000000..f395dec27113
--- /dev/null
+++ b/drivers/firmware/scpi_pm_domain.c
@@ -0,0 +1,163 @@
+/*
+ * SCPI Generic power domain support.
+ *
+ * Copyright (C) 2016 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pm_domain.h>
+#include <linux/scpi_protocol.h>
+
+struct scpi_pm_domain {
+ struct generic_pm_domain genpd;
+ struct scpi_ops *ops;
+ u32 domain;
+ char name[30];
+};
+
+/*
+ * These device power state values are not well-defined in the specification.
+ * In case, different implementations use different values, we can make these
+ * specific to compatibles rather than getting these values from device tree.
+ */
+enum scpi_power_domain_state {
+ SCPI_PD_STATE_ON = 0,
+ SCPI_PD_STATE_OFF = 3,
+};
+
+#define to_scpi_pd(gpd) container_of(gpd, struct scpi_pm_domain, genpd)
+
+static int scpi_pd_power(struct scpi_pm_domain *pd, bool power_on)
+{
+ int ret;
+ enum scpi_power_domain_state state;
+
+ if (power_on)
+ state = SCPI_PD_STATE_ON;
+ else
+ state = SCPI_PD_STATE_OFF;
+
+ ret = pd->ops->device_set_power_state(pd->domain, state);
+ if (ret)
+ return ret;
+
+ return !(state == pd->ops->device_get_power_state(pd->domain));
+}
+
+static int scpi_pd_power_on(struct generic_pm_domain *domain)
+{
+ struct scpi_pm_domain *pd = to_scpi_pd(domain);
+
+ return scpi_pd_power(pd, true);
+}
+
+static int scpi_pd_power_off(struct generic_pm_domain *domain)
+{
+ struct scpi_pm_domain *pd = to_scpi_pd(domain);
+
+ return scpi_pd_power(pd, false);
+}
+
+static int scpi_pm_domain_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct scpi_pm_domain *scpi_pd;
+ struct genpd_onecell_data *scpi_pd_data;
+ struct generic_pm_domain **domains;
+ struct scpi_ops *scpi_ops;
+ int ret, num_domains, i;
+
+ scpi_ops = get_scpi_ops();
+ if (!scpi_ops)
+ return -EPROBE_DEFER;
+
+ if (!np) {
+ dev_err(dev, "device tree node not found\n");
+ return -ENODEV;
+ }
+
+ if (!scpi_ops->device_set_power_state ||
+ !scpi_ops->device_get_power_state) {
+ dev_err(dev, "power domains not supported in the firmware\n");
+ return -ENODEV;
+ }
+
+ ret = of_property_read_u32(np, "num-domains", &num_domains);
+ if (ret) {
+ dev_err(dev, "number of domains not found\n");
+ return -EINVAL;
+ }
+
+ scpi_pd = devm_kcalloc(dev, num_domains, sizeof(*scpi_pd), GFP_KERNEL);
+ if (!scpi_pd)
+ return -ENOMEM;
+
+ scpi_pd_data = devm_kzalloc(dev, sizeof(*scpi_pd_data), GFP_KERNEL);
+ if (!scpi_pd_data)
+ return -ENOMEM;
+
+ domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
+ if (!domains)
+ return -ENOMEM;
+
+ for (i = 0; i < num_domains; i++, scpi_pd++) {
+ domains[i] = &scpi_pd->genpd;
+
+ scpi_pd->domain = i;
+ scpi_pd->ops = scpi_ops;
+ sprintf(scpi_pd->name, "%s.%d", np->name, i);
+ scpi_pd->genpd.name = scpi_pd->name;
+ scpi_pd->genpd.power_off = scpi_pd_power_off;
+ scpi_pd->genpd.power_on = scpi_pd_power_on;
+
+ /*
+ * Treat all power domains as off at boot.
+ *
+ * The SCP firmware itself may have switched on some domains,
+ * but for reference counting purpose, keep it this way.
+ */
+ pm_genpd_init(&scpi_pd->genpd, NULL, true);
+ }
+
+ scpi_pd_data->domains = domains;
+ scpi_pd_data->num_domains = num_domains;
+
+ of_genpd_add_provider_onecell(np, scpi_pd_data);
+
+ return 0;
+}
+
+static const struct of_device_id scpi_power_domain_ids[] = {
+ { .compatible = "arm,scpi-power-domains", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, scpi_power_domain_ids);
+
+static struct platform_driver scpi_power_domain_driver = {
+ .driver = {
+ .name = "scpi_power_domain",
+ .of_match_table = scpi_power_domain_ids,
+ },
+ .probe = scpi_pm_domain_probe,
+};
+module_platform_driver(scpi_power_domain_driver);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCPI power domain driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hwmon/scpi-hwmon.c b/drivers/hwmon/scpi-hwmon.c
index 7e20567bc369..912b449c8303 100644
--- a/drivers/hwmon/scpi-hwmon.c
+++ b/drivers/hwmon/scpi-hwmon.c
@@ -52,7 +52,7 @@ static int scpi_read_temp(void *dev, int *temp)
struct scpi_sensors *scpi_sensors = zone->scpi_sensors;
struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops;
struct sensor_data *sensor = &scpi_sensors->data[zone->sensor_id];
- u32 value;
+ u64 value;
int ret;
ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value);
@@ -70,7 +70,7 @@ scpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
struct scpi_sensors *scpi_sensors = dev_get_drvdata(dev);
struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops;
struct sensor_data *sensor;
- u32 value;
+ u64 value;
int ret;
sensor = container_of(attr, struct sensor_data, dev_attr_input);
@@ -79,7 +79,7 @@ scpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
if (ret)
return ret;
- return sprintf(buf, "%u\n", value);
+ return sprintf(buf, "%llu\n", value);
}
static ssize_t
@@ -114,6 +114,7 @@ static int scpi_hwmon_probe(struct platform_device *pdev)
{
u16 nr_sensors, i;
int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0;
+ int num_energy = 0;
struct scpi_ops *scpi_ops;
struct device *hwdev, *dev = &pdev->dev;
struct scpi_sensors *scpi_sensors;
@@ -182,6 +183,13 @@ static int scpi_hwmon_probe(struct platform_device *pdev)
"power%d_label", num_power + 1);
num_power++;
break;
+ case ENERGY:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "energy%d_input", num_energy + 1);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "energy%d_label", num_energy + 1);
+ num_energy++;
+ break;
default:
continue;
}
diff --git a/include/linux/scpi_protocol.h b/include/linux/scpi_protocol.h
index 72ce932c69b2..dc5f989be226 100644
--- a/include/linux/scpi_protocol.h
+++ b/include/linux/scpi_protocol.h
@@ -33,6 +33,7 @@ enum scpi_sensor_class {
VOLTAGE,
CURRENT,
POWER,
+ ENERGY,
};
struct scpi_sensor_info {
@@ -68,7 +69,9 @@ struct scpi_ops {
struct scpi_dvfs_info *(*dvfs_get_info)(u8);
int (*sensor_get_capability)(u16 *sensors);
int (*sensor_get_info)(u16 sensor_id, struct scpi_sensor_info *);
- int (*sensor_get_value)(u16, u32 *);
+ int (*sensor_get_value)(u16, u64 *);
+ int (*device_get_power_state)(u16);
+ int (*device_set_power_state)(u16, u8);
};
#if IS_REACHABLE(CONFIG_ARM_SCPI_PROTOCOL)