]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
platform/chrome: lightbar: Add support for large sequence
authorGwendal Grignou <gwendal@chromium.org>
Fri, 30 Jan 2026 08:13:51 +0000 (00:13 -0800)
committerTzung-Bi Shih <tzungbi@kernel.org>
Fri, 30 Jan 2026 08:30:01 +0000 (08:30 +0000)
Current sequences are limited to 192 bytes. Increase support to whatever
the EC support. If the sequence is too long, the EC will return an
OVERFLOW error.

Test: Check sending a large sequence is received by the EC.

Signed-off-by: Gwendal Grignou <gwendal@google.com>
Link: https://lore.kernel.org/r/20260130081351.487517-2-gwendal@google.com
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
drivers/platform/chrome/cros_ec_lightbar.c
include/linux/platform_data/cros_ec_commands.h

index 8ee76145039496ed77c8202392c27eb42ddcdd56..2d1aa6edda1a7d78ec981986b00e9da5c266b8ae 100644 (file)
@@ -37,6 +37,11 @@ static bool userspace_control;
  */
 static bool has_manual_suspend;
 
+/*
+ * Lightbar version
+ */
+static int lb_version;
+
 static ssize_t interval_msec_show(struct device *dev,
                                  struct device_attribute *attr, char *buf)
 {
@@ -93,11 +98,8 @@ out:
 
 static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec)
 {
+       int len = max(ec->ec_dev->max_response, ec->ec_dev->max_request);
        struct cros_ec_command *msg;
-       int len;
-
-       len = max(sizeof(struct ec_params_lightbar),
-                 sizeof(struct ec_response_lightbar));
 
        msg = kmalloc(sizeof(*msg) + len, GFP_KERNEL);
        if (!msg)
@@ -105,6 +107,11 @@ static struct cros_ec_command *alloc_lightbar_cmd_msg(struct cros_ec_dev *ec)
 
        msg->version = 0;
        msg->command = EC_CMD_LIGHTBAR_CMD + ec->cmd_offset;
+       /*
+        * Default sizes for regular commands.
+        * Can be set smaller to optimize transfer,
+        * larger when sending large light sequences.
+        */
        msg->outsize = sizeof(struct ec_params_lightbar);
        msg->insize = sizeof(struct ec_response_lightbar);
 
@@ -471,10 +478,11 @@ exit:
 static ssize_t program_store(struct device *dev, struct device_attribute *attr,
                             const char *buf, size_t count)
 {
-       int extra_bytes, max_size, ret;
+       size_t extra_bytes, max_size;
        struct ec_params_lightbar *param;
        struct cros_ec_command *msg;
        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
+       int ret;
 
        /*
         * We might need to reject the program for size reasons. The EC
@@ -482,14 +490,22 @@ static ssize_t program_store(struct device *dev, struct device_attribute *attr,
         * and send a program that is too big for the protocol. In order
         * to ensure the latter, we also need to ensure we have extra bytes
         * to represent the rest of the packet.
+        * With V3, larger program can be sent, limited only by the EC.
+        * Only the protocol limit the payload size.
         */
-       extra_bytes = sizeof(*param) - sizeof(param->set_program.data);
-       max_size = min(EC_LB_PROG_LEN, ec->ec_dev->max_request - extra_bytes);
-       if (count > max_size) {
-               dev_err(dev, "Program is %u bytes, too long to send (max: %u)",
-                       (unsigned int)count, max_size);
-
-               return -EINVAL;
+       if (lb_version < 3) {
+               extra_bytes = sizeof(*param) - sizeof(param->set_program.data);
+               max_size = min(EC_LB_PROG_LEN, ec->ec_dev->max_request - extra_bytes);
+               if (count > max_size) {
+                       dev_err(dev, "Program is %zu bytes, too long to send (max: %zu)",
+                               count, max_size);
+
+                       return -EINVAL;
+               }
+       } else {
+               extra_bytes = offsetof(typeof(*param), set_program_ex) +
+                       sizeof(param->set_program_ex);
+               max_size = ec->ec_dev->max_request - extra_bytes;
        }
 
        msg = alloc_lightbar_cmd_msg(ec);
@@ -499,26 +515,44 @@ static ssize_t program_store(struct device *dev, struct device_attribute *attr,
        ret = lb_throttle();
        if (ret)
                goto exit;
+       param = (struct ec_params_lightbar *)msg->data;
 
-       dev_info(dev, "Copying %zu byte program to EC", count);
+       if (lb_version < 3) {
+               dev_info(dev, "Copying %zu byte program to EC", count);
 
-       param = (struct ec_params_lightbar *)msg->data;
-       param->cmd = LIGHTBAR_CMD_SET_PROGRAM;
+               param->cmd = LIGHTBAR_CMD_SET_PROGRAM;
 
-       param->set_program.size = count;
-       memcpy(param->set_program.data, buf, count);
+               param->set_program.size = count;
+               memcpy(param->set_program.data, buf, count);
 
-       /*
-        * We need to set the message size manually or else it will use
-        * EC_LB_PROG_LEN. This might be too long, and the program
-        * is unlikely to use all of the space.
-        */
-       msg->outsize = count + extra_bytes;
+               /*
+                * We need to set the message size manually or else it will use
+                * EC_LB_PROG_LEN. This might be too long, and the program
+                * is unlikely to use all of the space.
+                */
+               msg->outsize = count + extra_bytes;
 
-       ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
-       if (ret < 0)
-               goto exit;
+               ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
+               if (ret < 0)
+                       goto exit;
+       } else {
+               size_t offset = 0;
+               size_t payload = 0;
+
+               param->cmd = LIGHTBAR_CMD_SET_PROGRAM_EX;
+               while (offset < count) {
+                       payload = min(max_size, count - offset);
+                       param->set_program_ex.offset = offset;
+                       param->set_program_ex.size = payload;
+                       memcpy(param->set_program_ex.data, &buf[offset], payload);
+                       msg->outsize = payload + extra_bytes;
 
+                       ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
+                       if (ret < 0)
+                               goto exit;
+                       offset += payload;
+               }
+       }
        ret = count;
 exit:
        kfree(msg);
@@ -596,7 +630,7 @@ static int cros_ec_lightbar_probe(struct platform_device *pd)
         * 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))
+       if (!get_lightbar_version(ec_dev, &lb_version, NULL))
                return -ENODEV;
 
        /* Take control of the lightbar from the EC. */
index 9cbf024f56c3e6ae954387587acc07335b76ee73..14424314303424fd1ec736c841bf2f8046981e58 100644 (file)
@@ -2020,6 +2020,17 @@ struct lightbar_program {
        uint8_t data[EC_LB_PROG_LEN];
 } __ec_todo_unpacked;
 
+/*
+ * Lightbar program for large sequences. Sequences are sent in pieces, with
+ * increasing offset. The sequences are still limited by the amount reserved in
+ * EC RAM.
+ */
+struct lightbar_program_ex {
+       uint16_t offset;
+       uint8_t size;
+       uint8_t data[0];
+} __ec_todo_unpacked;
+
 struct ec_params_lightbar {
        uint8_t cmd;                  /* Command (see enum lightbar_command) */
        union {
@@ -2066,6 +2077,7 @@ struct ec_params_lightbar {
                struct lightbar_params_v2_colors set_v2par_colors;
 
                struct lightbar_program set_program;
+               struct lightbar_program_ex set_program_ex;
        };
 } __ec_todo_packed;
 
@@ -2154,6 +2166,7 @@ enum lightbar_command {
        LIGHTBAR_CMD_GET_PARAMS_V2_COLORS = 32,
        LIGHTBAR_CMD_SET_PARAMS_V2_COLORS = 33,
        LIGHTBAR_CMD_GET_PARAMS_V3 = 34,
+       LIGHTBAR_CMD_SET_PROGRAM_EX = 35,
        LIGHTBAR_NUM_CMDS
 };