diff options
Diffstat (limited to 'drivers/platform/chrome/cros_ec_lightbar.c')
| -rw-r--r-- | drivers/platform/chrome/cros_ec_lightbar.c | 252 |
1 files changed, 127 insertions, 125 deletions
diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index fd2b047a2748..8352e9732791 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -1,39 +1,25 @@ -/* - * cros_ec_lightbar - expose the Chromebook Pixel lightbar to userspace - * - * Copyright (C) 2014 Google, Inc. - * - * 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, see <http://www.gnu.org/licenses/>. - */ - -#define pr_fmt(fmt) "cros_ec_lightbar: " fmt +// SPDX-License-Identifier: GPL-2.0+ +// Expose the Chromebook Pixel lightbar to userspace +// +// Copyright (C) 2014 Google, Inc. #include <linux/ctype.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/fs.h> #include <linux/kobject.h> -#include <linux/mfd/cros_ec.h> -#include <linux/mfd/cros_ec_commands.h> +#include <linux/kstrtox.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_device.h> #include <linux/sched.h> #include <linux/types.h> #include <linux/uaccess.h> #include <linux/slab.h> -#include "cros_ec_dev.h" +#define DRV_NAME "cros-ec-lightbar" /* Rate-limit the lightbar interface to prevent DoS. */ static unsigned long lb_interval_jiffies = 50 * HZ / 1000; @@ -43,14 +29,20 @@ static unsigned long lb_interval_jiffies = 50 * HZ / 1000; * If this is true, we won't do anything during suspend/resume. */ static bool userspace_control; -static struct cros_ec_dev *ec_with_lightbar; + +/* + * Whether or not the lightbar supports the manual suspend commands. + * The Pixel 2013 (Link) does not while all other devices with a + * lightbar do. + */ +static bool has_manual_suspend; static ssize_t interval_msec_show(struct device *dev, struct device_attribute *attr, char *buf) { unsigned long msec = lb_interval_jiffies * 1000 / HZ; - return scnprintf(buf, PAGE_SIZE, "%lu\n", msec); + return sysfs_emit(buf, "%lu\n", msec); } static ssize_t interval_msec_store(struct device *dev, @@ -133,8 +125,10 @@ static int get_lightbar_version(struct cros_ec_dev *ec, param = (struct ec_params_lightbar *)msg->data; param->cmd = LIGHTBAR_CMD_VERSION; - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); - if (ret < 0) { + msg->outsize = sizeof(param->cmd); + msg->result = sizeof(resp->version); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); + if (ret < 0 && ret != -EINVAL) { ret = 0; goto exit; } @@ -172,8 +166,7 @@ static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) { uint32_t version = 0, flags = 0; - struct cros_ec_dev *ec = container_of(dev, - struct cros_ec_dev, class_dev); + struct cros_ec_dev *ec = to_cros_ec_dev(dev); int ret; ret = lb_throttle(); @@ -184,7 +177,7 @@ static ssize_t version_show(struct device *dev, if (!get_lightbar_version(ec, &version, &flags)) return -EIO; - return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags); + return sysfs_emit(buf, "%d %d\n", version, flags); } static ssize_t brightness_store(struct device *dev, @@ -195,8 +188,7 @@ static ssize_t brightness_store(struct device *dev, struct cros_ec_command *msg; int ret; unsigned int val; - struct cros_ec_dev *ec = container_of(dev, - struct cros_ec_dev, class_dev); + struct cros_ec_dev *ec = to_cros_ec_dev(dev); if (kstrtouint(buf, 0, &val)) return -EINVAL; @@ -212,15 +204,10 @@ static ssize_t brightness_store(struct device *dev, if (ret) goto exit; - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); if (ret < 0) goto exit; - if (msg->result != EC_RES_SUCCESS) { - ret = -EINVAL; - goto exit; - } - ret = count; exit: kfree(msg); @@ -240,8 +227,7 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, { struct ec_params_lightbar *param; struct cros_ec_command *msg; - struct cros_ec_dev *ec = container_of(dev, - struct cros_ec_dev, class_dev); + struct cros_ec_dev *ec = to_cros_ec_dev(dev); unsigned int val[4]; int ret, i = 0, j = 0, ok = 0; @@ -278,13 +264,10 @@ static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, goto exit; } - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); if (ret < 0) goto exit; - if (msg->result != EC_RES_SUCCESS) - goto exit; - i = 0; ok = 1; } @@ -313,8 +296,7 @@ static ssize_t sequence_show(struct device *dev, struct ec_response_lightbar *resp; struct cros_ec_command *msg; int ret; - struct cros_ec_dev *ec = container_of(dev, - struct cros_ec_dev, class_dev); + struct cros_ec_dev *ec = to_cros_ec_dev(dev); msg = alloc_lightbar_cmd_msg(ec); if (!msg) @@ -326,22 +308,17 @@ static ssize_t sequence_show(struct device *dev, if (ret) goto exit; - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); - if (ret < 0) - goto exit; - - if (msg->result != EC_RES_SUCCESS) { - ret = scnprintf(buf, PAGE_SIZE, - "ERROR: EC returned %d\n", msg->result); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); + if (ret < 0) { + ret = sysfs_emit(buf, "XFER / EC ERROR %d / %d\n", ret, msg->result); goto exit; } resp = (struct ec_response_lightbar *)msg->data; if (resp->get_seq.num >= ARRAY_SIZE(seqname)) - ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); + ret = sysfs_emit(buf, "%d\n", resp->get_seq.num); else - ret = scnprintf(buf, PAGE_SIZE, "%s\n", - seqname[resp->get_seq.num]); + ret = sysfs_emit(buf, "%s\n", seqname[resp->get_seq.num]); exit: kfree(msg); @@ -365,13 +342,10 @@ static int lb_send_empty_cmd(struct cros_ec_dev *ec, uint8_t cmd) if (ret) goto error; - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); if (ret < 0) goto error; - if (msg->result != EC_RES_SUCCESS) { - ret = -EINVAL; - goto error; - } + ret = 0; error: kfree(msg); @@ -379,15 +353,12 @@ error: return ret; } -int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable) +static int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable) { struct ec_params_lightbar *param; struct cros_ec_command *msg; int ret; - if (ec != ec_with_lightbar) - return 0; - msg = alloc_lightbar_cmd_msg(ec); if (!msg) return -ENOMEM; @@ -401,13 +372,10 @@ int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable) if (ret) goto error; - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); if (ret < 0) goto error; - if (msg->result != EC_RES_SUCCESS) { - ret = -EINVAL; - goto error; - } + ret = 0; error: kfree(msg); @@ -415,22 +383,6 @@ error: return ret; } -int lb_suspend(struct cros_ec_dev *ec) -{ - if (userspace_control || ec != ec_with_lightbar) - return 0; - - return lb_send_empty_cmd(ec, LIGHTBAR_CMD_SUSPEND); -} - -int lb_resume(struct cros_ec_dev *ec) -{ - if (userspace_control || ec != ec_with_lightbar) - return 0; - - return lb_send_empty_cmd(ec, LIGHTBAR_CMD_RESUME); -} - static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -438,8 +390,7 @@ static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, struct cros_ec_command *msg; unsigned int num; int ret, len; - struct cros_ec_dev *ec = container_of(dev, - struct cros_ec_dev, class_dev); + struct cros_ec_dev *ec = to_cros_ec_dev(dev); for (len = 0; len < count; len++) if (!isalnum(buf[len])) @@ -466,15 +417,10 @@ static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, if (ret) goto exit; - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); if (ret < 0) goto exit; - if (msg->result != EC_RES_SUCCESS) { - ret = -EINVAL; - goto exit; - } - ret = count; exit: kfree(msg); @@ -487,8 +433,7 @@ static ssize_t program_store(struct device *dev, struct device_attribute *attr, int extra_bytes, max_size, ret; struct ec_params_lightbar *param; struct cros_ec_command *msg; - struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev, - class_dev); + struct cros_ec_dev *ec = to_cros_ec_dev(dev); /* * We might need to reject the program for size reasons. The EC @@ -529,13 +474,9 @@ static ssize_t program_store(struct device *dev, struct device_attribute *attr, */ msg->outsize = count + extra_bytes; - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); if (ret < 0) goto exit; - if (msg->result != EC_RES_SUCCESS) { - ret = -EINVAL; - goto exit; - } ret = count; exit: @@ -548,7 +489,7 @@ static ssize_t userspace_control_show(struct device *dev, struct device_attribute *attr, char *buf) { - return scnprintf(buf, PAGE_SIZE, "%d\n", userspace_control); + return sysfs_emit(buf, "%d\n", userspace_control); } static ssize_t userspace_control_store(struct device *dev, @@ -559,7 +500,7 @@ static ssize_t userspace_control_store(struct device *dev, bool enable; int ret; - ret = strtobool(buf, &enable); + ret = kstrtobool(buf, &enable); if (ret < 0) return ret; @@ -589,36 +530,97 @@ static struct attribute *__lb_cmds_attrs[] = { NULL, }; -bool ec_has_lightbar(struct cros_ec_dev *ec) +static const struct attribute_group cros_ec_lightbar_attr_group = { + .name = "lightbar", + .attrs = __lb_cmds_attrs, +}; + +static int cros_ec_lightbar_probe(struct platform_device *pd) +{ + struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); + struct cros_ec_platform *pdata = dev_get_platdata(ec_dev->dev); + struct device *dev = &pd->dev; + int ret; + + /* + * Only instantiate the lightbar if the EC name is 'cros_ec'. Other EC + * devices like 'cros_pd' doesn't have a lightbar. + */ + if (strcmp(pdata->ec_name, CROS_EC_DEV_NAME) != 0) + return -ENODEV; + + /* + * Ask then for the lightbar version, if it's 0 then the 'cros_ec' + * doesn't have a lightbar. + */ + if (!get_lightbar_version(ec_dev, NULL, NULL)) + return -ENODEV; + + /* Take control of the lightbar from the EC. */ + has_manual_suspend = (lb_manual_suspend_ctrl(ec_dev, 1) != -EINVAL); + + ret = sysfs_create_group(&ec_dev->class_dev.kobj, + &cros_ec_lightbar_attr_group); + if (ret < 0) + dev_err(dev, "failed to create %s attributes. err=%d\n", + cros_ec_lightbar_attr_group.name, ret); + + return ret; +} + +static void cros_ec_lightbar_remove(struct platform_device *pd) { - return !!get_lightbar_version(ec, NULL, NULL); + struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); + + sysfs_remove_group(&ec_dev->class_dev.kobj, + &cros_ec_lightbar_attr_group); + + /* Let the EC take over the lightbar again. */ + if (has_manual_suspend) + lb_manual_suspend_ctrl(ec_dev, 0); } -static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, - struct attribute *a, int n) +static int __maybe_unused cros_ec_lightbar_resume(struct device *dev) { - struct device *dev = container_of(kobj, struct device, kobj); - struct cros_ec_dev *ec = container_of(dev, - struct cros_ec_dev, class_dev); - struct platform_device *pdev = to_platform_device(ec->dev); - struct cros_ec_platform *pdata = pdev->dev.platform_data; - int is_cros_ec; + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); + + if (userspace_control || !has_manual_suspend) + return 0; + + return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_RESUME); +} - is_cros_ec = strcmp(pdata->ec_name, CROS_EC_DEV_NAME); +static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev) +{ + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); - if (is_cros_ec != 0) + if (userspace_control || !has_manual_suspend) return 0; - /* Only instantiate this stuff if the EC has a lightbar */ - if (ec_has_lightbar(ec)) { - ec_with_lightbar = ec; - return a->mode; - } - return 0; + return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_SUSPEND); } -struct attribute_group cros_ec_lightbar_attr_group = { - .name = "lightbar", - .attrs = __lb_cmds_attrs, - .is_visible = cros_ec_lightbar_attrs_are_visible, +static SIMPLE_DEV_PM_OPS(cros_ec_lightbar_pm_ops, + cros_ec_lightbar_suspend, cros_ec_lightbar_resume); + +static const struct platform_device_id cros_ec_lightbar_id[] = { + { DRV_NAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(platform, cros_ec_lightbar_id); + +static struct platform_driver cros_ec_lightbar_driver = { + .driver = { + .name = DRV_NAME, + .pm = &cros_ec_lightbar_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = cros_ec_lightbar_probe, + .remove = cros_ec_lightbar_remove, + .id_table = cros_ec_lightbar_id, }; + +module_platform_driver(cros_ec_lightbar_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Expose the Chromebook Pixel's lightbar to userspace"); |
