diff options
Diffstat (limited to 'drivers/platform/chrome/cros_ec_lightbar.c')
| -rw-r--r-- | drivers/platform/chrome/cros_ec_lightbar.c | 237 |
1 files changed, 122 insertions, 115 deletions
diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index 68193bb53383..8352e9732791 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -1,38 +1,26 @@ -/* - * 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> +#define DRV_NAME "cros-ec-lightbar" + /* Rate-limit the lightbar interface to prevent DoS. */ static unsigned long lb_interval_jiffies = 50 * HZ / 1000; @@ -41,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, @@ -131,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; } @@ -181,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, @@ -208,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); @@ -273,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; } @@ -320,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); @@ -359,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); @@ -373,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; @@ -395,38 +372,16 @@ 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); return ret; } -EXPORT_SYMBOL(lb_manual_suspend_ctrl); - -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); -} -EXPORT_SYMBOL(lb_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); -} -EXPORT_SYMBOL(lb_resume); static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -462,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); @@ -524,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: @@ -543,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, @@ -554,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; @@ -584,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) { - return !!get_lightbar_version(ec, NULL, NULL); + 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 umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, - struct attribute *a, int n) +static void cros_ec_lightbar_remove(struct platform_device *pd) { - struct device *dev = container_of(kobj, struct device, kobj); - struct cros_ec_dev *ec = to_cros_ec_dev(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(pd->dev.parent); + + sysfs_remove_group(&ec_dev->class_dev.kobj, + &cros_ec_lightbar_attr_group); - is_cros_ec = strcmp(pdata->ec_name, CROS_EC_DEV_NAME); + /* Let the EC take over the lightbar again. */ + if (has_manual_suspend) + lb_manual_suspend_ctrl(ec_dev, 0); +} + +static int __maybe_unused cros_ec_lightbar_resume(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_RESUME); } -struct attribute_group cros_ec_lightbar_attr_group = { - .name = "lightbar", - .attrs = __lb_cmds_attrs, - .is_visible = cros_ec_lightbar_attrs_are_visible, +static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev) +{ + 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_SUSPEND); +} + +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, }; -EXPORT_SYMBOL(cros_ec_lightbar_attr_group); + +module_platform_driver(cros_ec_lightbar_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Expose the Chromebook Pixel's lightbar to userspace"); |
