linux/drivers/platform/chrome/cros_ec_lightbar.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2// Expose the Chromebook Pixel lightbar to userspace
   3//
   4// Copyright (C) 2014 Google, Inc.
   5
   6#include <linux/ctype.h>
   7#include <linux/delay.h>
   8#include <linux/device.h>
   9#include <linux/fs.h>
  10#include <linux/kobject.h>
  11#include <linux/mfd/cros_ec.h>
  12#include <linux/mfd/cros_ec_commands.h>
  13#include <linux/module.h>
  14#include <linux/platform_device.h>
  15#include <linux/sched.h>
  16#include <linux/types.h>
  17#include <linux/uaccess.h>
  18#include <linux/slab.h>
  19
  20#define DRV_NAME "cros-ec-lightbar"
  21
  22/* Rate-limit the lightbar interface to prevent DoS. */
  23static unsigned long lb_interval_jiffies = 50 * HZ / 1000;
  24
  25/*
  26 * Whether or not we have given userspace control of the lightbar.
  27 * If this is true, we won't do anything during suspend/resume.
  28 */
  29static bool userspace_control;
  30
  31static ssize_t interval_msec_show(struct device *dev,
  32                                  struct device_attribute *attr, char *buf)
  33{
  34        unsigned long msec = lb_interval_jiffies * 1000 / HZ;
  35
  36        return scnprintf(buf, PAGE_SIZE, "%lu\n", msec);
  37}
  38
  39static ssize_t interval_msec_store(struct device *dev,
  40                                   struct device_attribute *attr,
  41                                   const char *buf, size_t count)
  42{
  43        unsigned long msec;
  44
  45        if (kstrtoul(buf, 0, &msec))
  46                return -EINVAL;
  47
  48        lb_interval_jiffies = msec * HZ / 1000;
  49
  50        return count;
  51}
  52
  53static DEFINE_MUTEX(lb_mutex);
  54/* Return 0 if able to throttle correctly, error otherwise */
  55static int lb_throttle(void)
  56{
  57        static unsigned long last_access;
  58        unsigned long now, next_timeslot;
  59        long delay;
  60        int ret = 0;
  61
  62        mutex_lock(&lb_mutex);
  63
  64        now = jiffies;
  65        next_timeslot = last_access + lb_interval_jiffies;
  66
  67        if (time_before(now, next_timeslot)) {
  68                delay = (long)(next_timeslot) - (long)now;
  69                set_current_state(TASK_INTERRUPTIBLE);
  70                if (schedule_timeout(delay) > 0) {
  71                        /* interrupted - just abort */
  72                        ret = -EINTR;
  73                        goto out;
  74                }
  75                now = jiffies;
  76        }
  77
  78        last_access = now;
  79out:
  80        mutex_unlock(&lb_mutex);
  81
  82        return ret;
  83}
  84
  85static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec)
  86{
  87        struct cros_ec_command *msg;
  88        int len;
  89
  90        len = max(sizeof(struct ec_params_lightbar),
  91                  sizeof(struct ec_response_lightbar));
  92
  93        msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL);
  94        if (!msg)
  95                return NULL;
  96
  97        msg->version = 0;
  98        msg->command = EC_CMD_LIGHTBAR_CMD + ec->cmd_offset;
  99        msg->outsize = sizeof(struct ec_params_lightbar);
 100        msg->insize = sizeof(struct ec_response_lightbar);
 101
 102        return msg;
 103}
 104
 105static int get_lightbar_version(struct cros_ec_dev *ec,
 106                                uint32_t *ver_ptr, uint32_t *flg_ptr)
 107{
 108        struct ec_params_lightbar *param;
 109        struct ec_response_lightbar *resp;
 110        struct cros_ec_command *msg;
 111        int ret;
 112
 113        msg = alloc_lightbar_cmd_msg(ec);
 114        if (!msg)
 115                return 0;
 116
 117        param = (struct ec_params_lightbar *)msg->data;
 118        param->cmd = LIGHTBAR_CMD_VERSION;
 119        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 120        if (ret < 0) {
 121                ret = 0;
 122                goto exit;
 123        }
 124
 125        switch (msg->result) {
 126        case EC_RES_INVALID_PARAM:
 127                /* Pixel had no version command. */
 128                if (ver_ptr)
 129                        *ver_ptr = 0;
 130                if (flg_ptr)
 131                        *flg_ptr = 0;
 132                ret = 1;
 133                goto exit;
 134
 135        case EC_RES_SUCCESS:
 136                resp = (struct ec_response_lightbar *)msg->data;
 137
 138                /* Future devices w/lightbars should implement this command */
 139                if (ver_ptr)
 140                        *ver_ptr = resp->version.num;
 141                if (flg_ptr)
 142                        *flg_ptr = resp->version.flags;
 143                ret = 1;
 144                goto exit;
 145        }
 146
 147        /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */
 148        ret = 0;
 149exit:
 150        kfree(msg);
 151        return ret;
 152}
 153
 154static ssize_t version_show(struct device *dev,
 155                            struct device_attribute *attr, char *buf)
 156{
 157        uint32_t version = 0, flags = 0;
 158        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 159        int ret;
 160
 161        ret = lb_throttle();
 162        if (ret)
 163                return ret;
 164
 165        /* This should always succeed, because we check during init. */
 166        if (!get_lightbar_version(ec, &version, &flags))
 167                return -EIO;
 168
 169        return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags);
 170}
 171
 172static ssize_t brightness_store(struct device *dev,
 173                                struct device_attribute *attr,
 174                                const char *buf, size_t count)
 175{
 176        struct ec_params_lightbar *param;
 177        struct cros_ec_command *msg;
 178        int ret;
 179        unsigned int val;
 180        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 181
 182        if (kstrtouint(buf, 0, &val))
 183                return -EINVAL;
 184
 185        msg = alloc_lightbar_cmd_msg(ec);
 186        if (!msg)
 187                return -ENOMEM;
 188
 189        param = (struct ec_params_lightbar *)msg->data;
 190        param->cmd = LIGHTBAR_CMD_SET_BRIGHTNESS;
 191        param->set_brightness.num = val;
 192        ret = lb_throttle();
 193        if (ret)
 194                goto exit;
 195
 196        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 197        if (ret < 0)
 198                goto exit;
 199
 200        if (msg->result != EC_RES_SUCCESS) {
 201                ret = -EINVAL;
 202                goto exit;
 203        }
 204
 205        ret = count;
 206exit:
 207        kfree(msg);
 208        return ret;
 209}
 210
 211
 212/*
 213 * We expect numbers, and we'll keep reading until we find them, skipping over
 214 * any whitespace (sysfs guarantees that the input is null-terminated). Every
 215 * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first
 216 * parsing error, if we don't parse any numbers, or if we have numbers left
 217 * over.
 218 */
 219static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr,
 220                             const char *buf, size_t count)
 221{
 222        struct ec_params_lightbar *param;
 223        struct cros_ec_command *msg;
 224        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 225        unsigned int val[4];
 226        int ret, i = 0, j = 0, ok = 0;
 227
 228        msg = alloc_lightbar_cmd_msg(ec);
 229        if (!msg)
 230                return -ENOMEM;
 231
 232        do {
 233                /* Skip any whitespace */
 234                while (*buf && isspace(*buf))
 235                        buf++;
 236
 237                if (!*buf)
 238                        break;
 239
 240                ret = sscanf(buf, "%i", &val[i++]);
 241                if (ret == 0)
 242                        goto exit;
 243
 244                if (i == 4) {
 245                        param = (struct ec_params_lightbar *)msg->data;
 246                        param->cmd = LIGHTBAR_CMD_SET_RGB;
 247                        param->set_rgb.led = val[0];
 248                        param->set_rgb.red = val[1];
 249                        param->set_rgb.green = val[2];
 250                        param->set_rgb.blue = val[3];
 251                        /*
 252                         * Throttle only the first of every four transactions,
 253                         * so that the user can update all four LEDs at once.
 254                         */
 255                        if ((j++ % 4) == 0) {
 256                                ret = lb_throttle();
 257                                if (ret)
 258                                        goto exit;
 259                        }
 260
 261                        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 262                        if (ret < 0)
 263                                goto exit;
 264
 265                        if (msg->result != EC_RES_SUCCESS)
 266                                goto exit;
 267
 268                        i = 0;
 269                        ok = 1;
 270                }
 271
 272                /* Skip over the number we just read */
 273                while (*buf && !isspace(*buf))
 274                        buf++;
 275
 276        } while (*buf);
 277
 278exit:
 279        kfree(msg);
 280        return (ok && i == 0) ? count : -EINVAL;
 281}
 282
 283static char const *seqname[] = {
 284        "ERROR", "S5", "S3", "S0", "S5S3", "S3S0",
 285        "S0S3", "S3S5", "STOP", "RUN", "KONAMI",
 286        "TAP", "PROGRAM",
 287};
 288
 289static ssize_t sequence_show(struct device *dev,
 290                             struct device_attribute *attr, char *buf)
 291{
 292        struct ec_params_lightbar *param;
 293        struct ec_response_lightbar *resp;
 294        struct cros_ec_command *msg;
 295        int ret;
 296        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 297
 298        msg = alloc_lightbar_cmd_msg(ec);
 299        if (!msg)
 300                return -ENOMEM;
 301
 302        param = (struct ec_params_lightbar *)msg->data;
 303        param->cmd = LIGHTBAR_CMD_GET_SEQ;
 304        ret = lb_throttle();
 305        if (ret)
 306                goto exit;
 307
 308        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 309        if (ret < 0)
 310                goto exit;
 311
 312        if (msg->result != EC_RES_SUCCESS) {
 313                ret = scnprintf(buf, PAGE_SIZE,
 314                                "ERROR: EC returned %d\n", msg->result);
 315                goto exit;
 316        }
 317
 318        resp = (struct ec_response_lightbar *)msg->data;
 319        if (resp->get_seq.num >= ARRAY_SIZE(seqname))
 320                ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num);
 321        else
 322                ret = scnprintf(buf, PAGE_SIZE, "%s\n",
 323                                seqname[resp->get_seq.num]);
 324
 325exit:
 326        kfree(msg);
 327        return ret;
 328}
 329
 330static int lb_send_empty_cmd(struct cros_ec_dev *ec, uint8_t cmd)
 331{
 332        struct ec_params_lightbar *param;
 333        struct cros_ec_command *msg;
 334        int ret;
 335
 336        msg = alloc_lightbar_cmd_msg(ec);
 337        if (!msg)
 338                return -ENOMEM;
 339
 340        param = (struct ec_params_lightbar *)msg->data;
 341        param->cmd = cmd;
 342
 343        ret = lb_throttle();
 344        if (ret)
 345                goto error;
 346
 347        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 348        if (ret < 0)
 349                goto error;
 350        if (msg->result != EC_RES_SUCCESS) {
 351                ret = -EINVAL;
 352                goto error;
 353        }
 354        ret = 0;
 355error:
 356        kfree(msg);
 357
 358        return ret;
 359}
 360
 361static int lb_manual_suspend_ctrl(struct cros_ec_dev *ec, uint8_t enable)
 362{
 363        struct ec_params_lightbar *param;
 364        struct cros_ec_command *msg;
 365        int ret;
 366
 367        msg = alloc_lightbar_cmd_msg(ec);
 368        if (!msg)
 369                return -ENOMEM;
 370
 371        param = (struct ec_params_lightbar *)msg->data;
 372
 373        param->cmd = LIGHTBAR_CMD_MANUAL_SUSPEND_CTRL;
 374        param->manual_suspend_ctrl.enable = enable;
 375
 376        ret = lb_throttle();
 377        if (ret)
 378                goto error;
 379
 380        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 381        if (ret < 0)
 382                goto error;
 383        if (msg->result != EC_RES_SUCCESS) {
 384                ret = -EINVAL;
 385                goto error;
 386        }
 387        ret = 0;
 388error:
 389        kfree(msg);
 390
 391        return ret;
 392}
 393
 394static ssize_t sequence_store(struct device *dev, struct device_attribute *attr,
 395                              const char *buf, size_t count)
 396{
 397        struct ec_params_lightbar *param;
 398        struct cros_ec_command *msg;
 399        unsigned int num;
 400        int ret, len;
 401        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 402
 403        for (len = 0; len < count; len++)
 404                if (!isalnum(buf[len]))
 405                        break;
 406
 407        for (num = 0; num < ARRAY_SIZE(seqname); num++)
 408                if (!strncasecmp(seqname[num], buf, len))
 409                        break;
 410
 411        if (num >= ARRAY_SIZE(seqname)) {
 412                ret = kstrtouint(buf, 0, &num);
 413                if (ret)
 414                        return ret;
 415        }
 416
 417        msg = alloc_lightbar_cmd_msg(ec);
 418        if (!msg)
 419                return -ENOMEM;
 420
 421        param = (struct ec_params_lightbar *)msg->data;
 422        param->cmd = LIGHTBAR_CMD_SEQ;
 423        param->seq.num = num;
 424        ret = lb_throttle();
 425        if (ret)
 426                goto exit;
 427
 428        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 429        if (ret < 0)
 430                goto exit;
 431
 432        if (msg->result != EC_RES_SUCCESS) {
 433                ret = -EINVAL;
 434                goto exit;
 435        }
 436
 437        ret = count;
 438exit:
 439        kfree(msg);
 440        return ret;
 441}
 442
 443static ssize_t program_store(struct device *dev, struct device_attribute *attr,
 444                             const char *buf, size_t count)
 445{
 446        int extra_bytes, max_size, ret;
 447        struct ec_params_lightbar *param;
 448        struct cros_ec_command *msg;
 449        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 450
 451        /*
 452         * We might need to reject the program for size reasons. The EC
 453         * enforces a maximum program size, but we also don't want to try
 454         * and send a program that is too big for the protocol. In order
 455         * to ensure the latter, we also need to ensure we have extra bytes
 456         * to represent the rest of the packet.
 457         */
 458        extra_bytes = sizeof(*param) - sizeof(param->set_program.data);
 459        max_size = min(EC_LB_PROG_LEN, ec->ec_dev->max_request - extra_bytes);
 460        if (count > max_size) {
 461                dev_err(dev, "Program is %u bytes, too long to send (max: %u)",
 462                        (unsigned int)count, max_size);
 463
 464                return -EINVAL;
 465        }
 466
 467        msg = alloc_lightbar_cmd_msg(ec);
 468        if (!msg)
 469                return -ENOMEM;
 470
 471        ret = lb_throttle();
 472        if (ret)
 473                goto exit;
 474
 475        dev_info(dev, "Copying %zu byte program to EC", count);
 476
 477        param = (struct ec_params_lightbar *)msg->data;
 478        param->cmd = LIGHTBAR_CMD_SET_PROGRAM;
 479
 480        param->set_program.size = count;
 481        memcpy(param->set_program.data, buf, count);
 482
 483        /*
 484         * We need to set the message size manually or else it will use
 485         * EC_LB_PROG_LEN. This might be too long, and the program
 486         * is unlikely to use all of the space.
 487         */
 488        msg->outsize = count + extra_bytes;
 489
 490        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 491        if (ret < 0)
 492                goto exit;
 493        if (msg->result != EC_RES_SUCCESS) {
 494                ret = -EINVAL;
 495                goto exit;
 496        }
 497
 498        ret = count;
 499exit:
 500        kfree(msg);
 501
 502        return ret;
 503}
 504
 505static ssize_t userspace_control_show(struct device *dev,
 506                                      struct device_attribute *attr,
 507                                      char *buf)
 508{
 509        return scnprintf(buf, PAGE_SIZE, "%d\n", userspace_control);
 510}
 511
 512static ssize_t userspace_control_store(struct device *dev,
 513                                       struct device_attribute *attr,
 514                                       const char *buf,
 515                                       size_t count)
 516{
 517        bool enable;
 518        int ret;
 519
 520        ret = strtobool(buf, &enable);
 521        if (ret < 0)
 522                return ret;
 523
 524        userspace_control = enable;
 525
 526        return count;
 527}
 528
 529/* Module initialization */
 530
 531static DEVICE_ATTR_RW(interval_msec);
 532static DEVICE_ATTR_RO(version);
 533static DEVICE_ATTR_WO(brightness);
 534static DEVICE_ATTR_WO(led_rgb);
 535static DEVICE_ATTR_RW(sequence);
 536static DEVICE_ATTR_WO(program);
 537static DEVICE_ATTR_RW(userspace_control);
 538
 539static struct attribute *__lb_cmds_attrs[] = {
 540        &dev_attr_interval_msec.attr,
 541        &dev_attr_version.attr,
 542        &dev_attr_brightness.attr,
 543        &dev_attr_led_rgb.attr,
 544        &dev_attr_sequence.attr,
 545        &dev_attr_program.attr,
 546        &dev_attr_userspace_control.attr,
 547        NULL,
 548};
 549
 550struct attribute_group cros_ec_lightbar_attr_group = {
 551        .name = "lightbar",
 552        .attrs = __lb_cmds_attrs,
 553};
 554
 555static int cros_ec_lightbar_probe(struct platform_device *pd)
 556{
 557        struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
 558        struct cros_ec_platform *pdata = dev_get_platdata(ec_dev->dev);
 559        struct device *dev = &pd->dev;
 560        int ret;
 561
 562        /*
 563         * Only instantiate the lightbar if the EC name is 'cros_ec'. Other EC
 564         * devices like 'cros_pd' doesn't have a lightbar.
 565         */
 566        if (strcmp(pdata->ec_name, CROS_EC_DEV_NAME) != 0)
 567                return -ENODEV;
 568
 569        /*
 570         * Ask then for the lightbar version, if it's 0 then the 'cros_ec'
 571         * doesn't have a lightbar.
 572         */
 573        if (!get_lightbar_version(ec_dev, NULL, NULL))
 574                return -ENODEV;
 575
 576        /* Take control of the lightbar from the EC. */
 577        lb_manual_suspend_ctrl(ec_dev, 1);
 578
 579        ret = sysfs_create_group(&ec_dev->class_dev.kobj,
 580                                 &cros_ec_lightbar_attr_group);
 581        if (ret < 0)
 582                dev_err(dev, "failed to create %s attributes. err=%d\n",
 583                        cros_ec_lightbar_attr_group.name, ret);
 584
 585        return ret;
 586}
 587
 588static int cros_ec_lightbar_remove(struct platform_device *pd)
 589{
 590        struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
 591
 592        sysfs_remove_group(&ec_dev->class_dev.kobj,
 593                           &cros_ec_lightbar_attr_group);
 594
 595        /* Let the EC take over the lightbar again. */
 596        lb_manual_suspend_ctrl(ec_dev, 0);
 597
 598        return 0;
 599}
 600
 601static int __maybe_unused cros_ec_lightbar_resume(struct device *dev)
 602{
 603        struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
 604
 605        if (userspace_control)
 606                return 0;
 607
 608        return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_RESUME);
 609}
 610
 611static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev)
 612{
 613        struct cros_ec_dev *ec_dev = dev_get_drvdata(dev);
 614
 615        if (userspace_control)
 616                return 0;
 617
 618        return lb_send_empty_cmd(ec_dev, LIGHTBAR_CMD_SUSPEND);
 619}
 620
 621static SIMPLE_DEV_PM_OPS(cros_ec_lightbar_pm_ops,
 622                         cros_ec_lightbar_suspend, cros_ec_lightbar_resume);
 623
 624static struct platform_driver cros_ec_lightbar_driver = {
 625        .driver = {
 626                .name = DRV_NAME,
 627                .pm = &cros_ec_lightbar_pm_ops,
 628        },
 629        .probe = cros_ec_lightbar_probe,
 630        .remove = cros_ec_lightbar_remove,
 631};
 632
 633module_platform_driver(cros_ec_lightbar_driver);
 634
 635MODULE_LICENSE("GPL");
 636MODULE_DESCRIPTION("Expose the Chromebook Pixel's lightbar to userspace");
 637MODULE_ALIAS("platform:" DRV_NAME);
 638