| .. | .. |
|---|
| 1 | | -/* |
|---|
| 2 | | - * cros_ec_lightbar - expose the Chromebook Pixel lightbar to userspace |
|---|
| 3 | | - * |
|---|
| 4 | | - * Copyright (C) 2014 Google, Inc. |
|---|
| 5 | | - * |
|---|
| 6 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 7 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 8 | | - * the Free Software Foundation; either version 2 of the License, or |
|---|
| 9 | | - * (at your option) any later version. |
|---|
| 10 | | - * |
|---|
| 11 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 12 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 13 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 14 | | - * GNU General Public License for more details. |
|---|
| 15 | | - * |
|---|
| 16 | | - * You should have received a copy of the GNU General Public License |
|---|
| 17 | | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 18 | | - */ |
|---|
| 19 | | - |
|---|
| 20 | | -#define pr_fmt(fmt) "cros_ec_lightbar: " fmt |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0+ |
|---|
| 2 | +// Expose the Chromebook Pixel lightbar to userspace |
|---|
| 3 | +// |
|---|
| 4 | +// Copyright (C) 2014 Google, Inc. |
|---|
| 21 | 5 | |
|---|
| 22 | 6 | #include <linux/ctype.h> |
|---|
| 23 | 7 | #include <linux/delay.h> |
|---|
| 24 | 8 | #include <linux/device.h> |
|---|
| 25 | 9 | #include <linux/fs.h> |
|---|
| 26 | 10 | #include <linux/kobject.h> |
|---|
| 27 | | -#include <linux/mfd/cros_ec.h> |
|---|
| 28 | | -#include <linux/mfd/cros_ec_commands.h> |
|---|
| 29 | 11 | #include <linux/module.h> |
|---|
| 12 | +#include <linux/platform_data/cros_ec_commands.h> |
|---|
| 13 | +#include <linux/platform_data/cros_ec_proto.h> |
|---|
| 30 | 14 | #include <linux/platform_device.h> |
|---|
| 31 | 15 | #include <linux/sched.h> |
|---|
| 32 | 16 | #include <linux/types.h> |
|---|
| 33 | 17 | #include <linux/uaccess.h> |
|---|
| 34 | 18 | #include <linux/slab.h> |
|---|
| 19 | + |
|---|
| 20 | +#define DRV_NAME "cros-ec-lightbar" |
|---|
| 35 | 21 | |
|---|
| 36 | 22 | /* Rate-limit the lightbar interface to prevent DoS. */ |
|---|
| 37 | 23 | static unsigned long lb_interval_jiffies = 50 * HZ / 1000; |
|---|
| .. | .. |
|---|
| 41 | 27 | * If this is true, we won't do anything during suspend/resume. |
|---|
| 42 | 28 | */ |
|---|
| 43 | 29 | static bool userspace_control; |
|---|
| 44 | | -static struct cros_ec_dev *ec_with_lightbar; |
|---|
| 45 | 30 | |
|---|
| 46 | 31 | static ssize_t interval_msec_show(struct device *dev, |
|---|
| 47 | 32 | struct device_attribute *attr, char *buf) |
|---|
| .. | .. |
|---|
| 131 | 116 | |
|---|
| 132 | 117 | param = (struct ec_params_lightbar *)msg->data; |
|---|
| 133 | 118 | param->cmd = LIGHTBAR_CMD_VERSION; |
|---|
| 134 | | - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
|---|
| 135 | | - if (ret < 0) { |
|---|
| 119 | + msg->outsize = sizeof(param->cmd); |
|---|
| 120 | + msg->result = sizeof(resp->version); |
|---|
| 121 | + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); |
|---|
| 122 | + if (ret < 0 && ret != -EINVAL) { |
|---|
| 136 | 123 | ret = 0; |
|---|
| 137 | 124 | goto exit; |
|---|
| 138 | 125 | } |
|---|
| .. | .. |
|---|
| 208 | 195 | if (ret) |
|---|
| 209 | 196 | goto exit; |
|---|
| 210 | 197 | |
|---|
| 211 | | - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
|---|
| 198 | + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); |
|---|
| 212 | 199 | if (ret < 0) |
|---|
| 213 | 200 | goto exit; |
|---|
| 214 | | - |
|---|
| 215 | | - if (msg->result != EC_RES_SUCCESS) { |
|---|
| 216 | | - ret = -EINVAL; |
|---|
| 217 | | - goto exit; |
|---|
| 218 | | - } |
|---|
| 219 | 201 | |
|---|
| 220 | 202 | ret = count; |
|---|
| 221 | 203 | exit: |
|---|
| .. | .. |
|---|
| 273 | 255 | goto exit; |
|---|
| 274 | 256 | } |
|---|
| 275 | 257 | |
|---|
| 276 | | - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
|---|
| 258 | + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); |
|---|
| 277 | 259 | if (ret < 0) |
|---|
| 278 | | - goto exit; |
|---|
| 279 | | - |
|---|
| 280 | | - if (msg->result != EC_RES_SUCCESS) |
|---|
| 281 | 260 | goto exit; |
|---|
| 282 | 261 | |
|---|
| 283 | 262 | i = 0; |
|---|
| .. | .. |
|---|
| 320 | 299 | if (ret) |
|---|
| 321 | 300 | goto exit; |
|---|
| 322 | 301 | |
|---|
| 323 | | - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
|---|
| 324 | | - if (ret < 0) |
|---|
| 325 | | - goto exit; |
|---|
| 326 | | - |
|---|
| 327 | | - if (msg->result != EC_RES_SUCCESS) { |
|---|
| 328 | | - ret = scnprintf(buf, PAGE_SIZE, |
|---|
| 329 | | - "ERROR: EC returned %d\n", msg->result); |
|---|
| 302 | + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); |
|---|
| 303 | + if (ret < 0) { |
|---|
| 304 | + ret = scnprintf(buf, PAGE_SIZE, "XFER / EC ERROR %d / %d\n", |
|---|
| 305 | + ret, msg->result); |
|---|
| 330 | 306 | goto exit; |
|---|
| 331 | 307 | } |
|---|
| 332 | 308 | |
|---|
| .. | .. |
|---|
| 359 | 335 | if (ret) |
|---|
| 360 | 336 | goto error; |
|---|
| 361 | 337 | |
|---|
| 362 | | - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
|---|
| 338 | + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); |
|---|
| 363 | 339 | if (ret < 0) |
|---|
| 364 | 340 | goto error; |
|---|
| 365 | | - if (msg->result != EC_RES_SUCCESS) { |
|---|
| 366 | | - ret = -EINVAL; |
|---|
| 367 | | - goto error; |
|---|
| 368 | | - } |
|---|
| 341 | + |
|---|
| 369 | 342 | ret = 0; |
|---|
| 370 | 343 | error: |
|---|
| 371 | 344 | kfree(msg); |
|---|
| .. | .. |
|---|
| 373 | 346 | return ret; |
|---|
| 374 | 347 | } |
|---|
| 375 | 348 | |
|---|
| 376 | | -int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable) |
|---|
| 349 | +static int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable) |
|---|
| 377 | 350 | { |
|---|
| 378 | 351 | struct ec_params_lightbar *param; |
|---|
| 379 | 352 | struct cros_ec_command *msg; |
|---|
| 380 | 353 | int ret; |
|---|
| 381 | | - |
|---|
| 382 | | - if (ec != ec_with_lightbar) |
|---|
| 383 | | - return 0; |
|---|
| 384 | 354 | |
|---|
| 385 | 355 | msg = alloc_lightbar_cmd_msg(ec); |
|---|
| 386 | 356 | if (!msg) |
|---|
| .. | .. |
|---|
| 395 | 365 | if (ret) |
|---|
| 396 | 366 | goto error; |
|---|
| 397 | 367 | |
|---|
| 398 | | - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
|---|
| 368 | + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); |
|---|
| 399 | 369 | if (ret < 0) |
|---|
| 400 | 370 | goto error; |
|---|
| 401 | | - if (msg->result != EC_RES_SUCCESS) { |
|---|
| 402 | | - ret = -EINVAL; |
|---|
| 403 | | - goto error; |
|---|
| 404 | | - } |
|---|
| 371 | + |
|---|
| 405 | 372 | ret = 0; |
|---|
| 406 | 373 | error: |
|---|
| 407 | 374 | kfree(msg); |
|---|
| 408 | 375 | |
|---|
| 409 | 376 | return ret; |
|---|
| 410 | 377 | } |
|---|
| 411 | | -EXPORT_SYMBOL(lb_manual_suspend_ctrl); |
|---|
| 412 | | - |
|---|
| 413 | | -int lb_suspend(struct cros_ec_dev *ec) |
|---|
| 414 | | -{ |
|---|
| 415 | | - if (userspace_control || ec != ec_with_lightbar) |
|---|
| 416 | | - return 0; |
|---|
| 417 | | - |
|---|
| 418 | | - return lb_send_empty_cmd(ec, LIGHTBAR_CMD_SUSPEND); |
|---|
| 419 | | -} |
|---|
| 420 | | -EXPORT_SYMBOL(lb_suspend); |
|---|
| 421 | | - |
|---|
| 422 | | -int lb_resume(struct cros_ec_dev *ec) |
|---|
| 423 | | -{ |
|---|
| 424 | | - if (userspace_control || ec != ec_with_lightbar) |
|---|
| 425 | | - return 0; |
|---|
| 426 | | - |
|---|
| 427 | | - return lb_send_empty_cmd(ec, LIGHTBAR_CMD_RESUME); |
|---|
| 428 | | -} |
|---|
| 429 | | -EXPORT_SYMBOL(lb_resume); |
|---|
| 430 | 378 | |
|---|
| 431 | 379 | static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, |
|---|
| 432 | 380 | const char *buf, size_t count) |
|---|
| .. | .. |
|---|
| 462 | 410 | if (ret) |
|---|
| 463 | 411 | goto exit; |
|---|
| 464 | 412 | |
|---|
| 465 | | - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
|---|
| 413 | + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); |
|---|
| 466 | 414 | if (ret < 0) |
|---|
| 467 | 415 | goto exit; |
|---|
| 468 | | - |
|---|
| 469 | | - if (msg->result != EC_RES_SUCCESS) { |
|---|
| 470 | | - ret = -EINVAL; |
|---|
| 471 | | - goto exit; |
|---|
| 472 | | - } |
|---|
| 473 | 416 | |
|---|
| 474 | 417 | ret = count; |
|---|
| 475 | 418 | exit: |
|---|
| .. | .. |
|---|
| 524 | 467 | */ |
|---|
| 525 | 468 | msg->outsize = count + extra_bytes; |
|---|
| 526 | 469 | |
|---|
| 527 | | - ret = cros_ec_cmd_xfer(ec->ec_dev, msg); |
|---|
| 470 | + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); |
|---|
| 528 | 471 | if (ret < 0) |
|---|
| 529 | 472 | goto exit; |
|---|
| 530 | | - if (msg->result != EC_RES_SUCCESS) { |
|---|
| 531 | | - ret = -EINVAL; |
|---|
| 532 | | - goto exit; |
|---|
| 533 | | - } |
|---|
| 534 | 473 | |
|---|
| 535 | 474 | ret = count; |
|---|
| 536 | 475 | exit: |
|---|
| .. | .. |
|---|
| 584 | 523 | NULL, |
|---|
| 585 | 524 | }; |
|---|
| 586 | 525 | |
|---|
| 587 | | -bool ec_has_lightbar(struct cros_ec_dev *ec) |
|---|
| 526 | +static struct attribute_group cros_ec_lightbar_attr_group = { |
|---|
| 527 | + .name = "lightbar", |
|---|
| 528 | + .attrs = __lb_cmds_attrs, |
|---|
| 529 | +}; |
|---|
| 530 | + |
|---|
| 531 | +static int cros_ec_lightbar_probe(struct platform_device *pd) |
|---|
| 588 | 532 | { |
|---|
| 589 | | - return !!get_lightbar_version(ec, NULL, NULL); |
|---|
| 533 | + struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); |
|---|
| 534 | + struct cros_ec_platform *pdata = dev_get_platdata(ec_dev->dev); |
|---|
| 535 | + struct device *dev = &pd->dev; |
|---|
| 536 | + int ret; |
|---|
| 537 | + |
|---|
| 538 | + /* |
|---|
| 539 | + * Only instantiate the lightbar if the EC name is 'cros_ec'. Other EC |
|---|
| 540 | + * devices like 'cros_pd' doesn't have a lightbar. |
|---|
| 541 | + */ |
|---|
| 542 | + if (strcmp(pdata->ec_name, CROS_EC_DEV_NAME) != 0) |
|---|
| 543 | + return -ENODEV; |
|---|
| 544 | + |
|---|
| 545 | + /* |
|---|
| 546 | + * Ask then for the lightbar version, if it's 0 then the 'cros_ec' |
|---|
| 547 | + * doesn't have a lightbar. |
|---|
| 548 | + */ |
|---|
| 549 | + if (!get_lightbar_version(ec_dev, NULL, NULL)) |
|---|
| 550 | + return -ENODEV; |
|---|
| 551 | + |
|---|
| 552 | + /* Take control of the lightbar from the EC. */ |
|---|
| 553 | + lb_manual_suspend_ctrl(ec_dev, 1); |
|---|
| 554 | + |
|---|
| 555 | + ret = sysfs_create_group(&ec_dev->class_dev.kobj, |
|---|
| 556 | + &cros_ec_lightbar_attr_group); |
|---|
| 557 | + if (ret < 0) |
|---|
| 558 | + dev_err(dev, "failed to create %s attributes. err=%d\n", |
|---|
| 559 | + cros_ec_lightbar_attr_group.name, ret); |
|---|
| 560 | + |
|---|
| 561 | + return ret; |
|---|
| 590 | 562 | } |
|---|
| 591 | 563 | |
|---|
| 592 | | -static umode_t cros_ec_lightbar_attrs_are_visible(struct kobject *kobj, |
|---|
| 593 | | - struct attribute *a, int n) |
|---|
| 564 | +static int cros_ec_lightbar_remove(struct platform_device *pd) |
|---|
| 594 | 565 | { |
|---|
| 595 | | - struct device *dev = container_of(kobj, struct device, kobj); |
|---|
| 596 | | - struct cros_ec_dev *ec = to_cros_ec_dev(dev); |
|---|
| 597 | | - struct platform_device *pdev = to_platform_device(ec->dev); |
|---|
| 598 | | - struct cros_ec_platform *pdata = pdev->dev.platform_data; |
|---|
| 599 | | - int is_cros_ec; |
|---|
| 566 | + struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); |
|---|
| 600 | 567 | |
|---|
| 601 | | - is_cros_ec = strcmp(pdata->ec_name, CROS_EC_DEV_NAME); |
|---|
| 568 | + sysfs_remove_group(&ec_dev->class_dev.kobj, |
|---|
| 569 | + &cros_ec_lightbar_attr_group); |
|---|
| 602 | 570 | |
|---|
| 603 | | - if (is_cros_ec != 0) |
|---|
| 604 | | - return 0; |
|---|
| 571 | + /* Let the EC take over the lightbar again. */ |
|---|
| 572 | + lb_manual_suspend_ctrl(ec_dev, 0); |
|---|
| 605 | 573 | |
|---|
| 606 | | - /* Only instantiate this stuff if the EC has a lightbar */ |
|---|
| 607 | | - if (ec_has_lightbar(ec)) { |
|---|
| 608 | | - ec_with_lightbar = ec; |
|---|
| 609 | | - return a->mode; |
|---|
| 610 | | - } |
|---|
| 611 | 574 | return 0; |
|---|
| 612 | 575 | } |
|---|
| 613 | 576 | |
|---|
| 614 | | -struct attribute_group cros_ec_lightbar_attr_group = { |
|---|
| 615 | | - .name = "lightbar", |
|---|
| 616 | | - .attrs = __lb_cmds_attrs, |
|---|
| 617 | | - .is_visible = cros_ec_lightbar_attrs_are_visible, |
|---|
| 577 | +static int __maybe_unused cros_ec_lightbar_resume(struct device *dev) |
|---|
| 578 | +{ |
|---|
| 579 | + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); |
|---|
| 580 | + |
|---|
| 581 | + if (userspace_control) |
|---|
| 582 | + return 0; |
|---|
| 583 | + |
|---|
| 584 | + return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_RESUME); |
|---|
| 585 | +} |
|---|
| 586 | + |
|---|
| 587 | +static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev) |
|---|
| 588 | +{ |
|---|
| 589 | + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); |
|---|
| 590 | + |
|---|
| 591 | + if (userspace_control) |
|---|
| 592 | + return 0; |
|---|
| 593 | + |
|---|
| 594 | + return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_SUSPEND); |
|---|
| 595 | +} |
|---|
| 596 | + |
|---|
| 597 | +static SIMPLE_DEV_PM_OPS(cros_ec_lightbar_pm_ops, |
|---|
| 598 | + cros_ec_lightbar_suspend, cros_ec_lightbar_resume); |
|---|
| 599 | + |
|---|
| 600 | +static struct platform_driver cros_ec_lightbar_driver = { |
|---|
| 601 | + .driver = { |
|---|
| 602 | + .name = DRV_NAME, |
|---|
| 603 | + .pm = &cros_ec_lightbar_pm_ops, |
|---|
| 604 | + }, |
|---|
| 605 | + .probe = cros_ec_lightbar_probe, |
|---|
| 606 | + .remove = cros_ec_lightbar_remove, |
|---|
| 618 | 607 | }; |
|---|
| 619 | | -EXPORT_SYMBOL(cros_ec_lightbar_attr_group); |
|---|
| 608 | + |
|---|
| 609 | +module_platform_driver(cros_ec_lightbar_driver); |
|---|
| 610 | + |
|---|
| 611 | +MODULE_LICENSE("GPL"); |
|---|
| 612 | +MODULE_DESCRIPTION("Expose the Chromebook Pixel's lightbar to userspace"); |
|---|
| 613 | +MODULE_ALIAS("platform:" DRV_NAME); |
|---|