linux/drivers/platform/chrome/cros_ec_sysfs.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2// Expose the ChromeOS EC through sysfs
   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/printk.h>
  16#include <linux/slab.h>
  17#include <linux/stat.h>
  18#include <linux/types.h>
  19#include <linux/uaccess.h>
  20
  21#define DRV_NAME "cros-ec-sysfs"
  22
  23/* Accessor functions */
  24
  25static ssize_t reboot_show(struct device *dev,
  26                           struct device_attribute *attr, char *buf)
  27{
  28        int count = 0;
  29
  30        count += scnprintf(buf + count, PAGE_SIZE - count,
  31                           "ro|rw|cancel|cold|disable-jump|hibernate");
  32        count += scnprintf(buf + count, PAGE_SIZE - count,
  33                           " [at-shutdown]\n");
  34        return count;
  35}
  36
  37static ssize_t reboot_store(struct device *dev,
  38                            struct device_attribute *attr,
  39                            const char *buf, size_t count)
  40{
  41        static const struct {
  42                const char * const str;
  43                uint8_t cmd;
  44                uint8_t flags;
  45        } words[] = {
  46                {"cancel",       EC_REBOOT_CANCEL, 0},
  47                {"ro",           EC_REBOOT_JUMP_RO, 0},
  48                {"rw",           EC_REBOOT_JUMP_RW, 0},
  49                {"cold",         EC_REBOOT_COLD, 0},
  50                {"disable-jump", EC_REBOOT_DISABLE_JUMP, 0},
  51                {"hibernate",    EC_REBOOT_HIBERNATE, 0},
  52                {"at-shutdown",  -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN},
  53        };
  54        struct cros_ec_command *msg;
  55        struct ec_params_reboot_ec *param;
  56        int got_cmd = 0, offset = 0;
  57        int i;
  58        int ret;
  59        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
  60
  61        msg = kmalloc(sizeof(*msg) + sizeof(*param), GFP_KERNEL);
  62        if (!msg)
  63                return -ENOMEM;
  64
  65        param = (struct ec_params_reboot_ec *)msg->data;
  66
  67        param->flags = 0;
  68        while (1) {
  69                /* Find word to start scanning */
  70                while (buf[offset] && isspace(buf[offset]))
  71                        offset++;
  72                if (!buf[offset])
  73                        break;
  74
  75                for (i = 0; i < ARRAY_SIZE(words); i++) {
  76                        if (!strncasecmp(words[i].str, buf+offset,
  77                                         strlen(words[i].str))) {
  78                                if (words[i].flags) {
  79                                        param->flags |= words[i].flags;
  80                                } else {
  81                                        param->cmd = words[i].cmd;
  82                                        got_cmd = 1;
  83                                }
  84                                break;
  85                        }
  86                }
  87
  88                /* On to the next word, if any */
  89                while (buf[offset] && !isspace(buf[offset]))
  90                        offset++;
  91        }
  92
  93        if (!got_cmd) {
  94                count = -EINVAL;
  95                goto exit;
  96        }
  97
  98        msg->version = 0;
  99        msg->command = EC_CMD_REBOOT_EC + ec->cmd_offset;
 100        msg->outsize = sizeof(*param);
 101        msg->insize = 0;
 102        ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
 103        if (ret < 0)
 104                count = ret;
 105exit:
 106        kfree(msg);
 107        return count;
 108}
 109
 110static ssize_t version_show(struct device *dev,
 111                            struct device_attribute *attr, char *buf)
 112{
 113        static const char * const image_names[] = {"unknown", "RO", "RW"};
 114        struct ec_response_get_version *r_ver;
 115        struct ec_response_get_chip_info *r_chip;
 116        struct ec_response_board_version *r_board;
 117        struct cros_ec_command *msg;
 118        int ret;
 119        int count = 0;
 120        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 121
 122        msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL);
 123        if (!msg)
 124                return -ENOMEM;
 125
 126        /* Get versions. RW may change. */
 127        msg->version = 0;
 128        msg->command = EC_CMD_GET_VERSION + ec->cmd_offset;
 129        msg->insize = sizeof(*r_ver);
 130        msg->outsize = 0;
 131        ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
 132        if (ret < 0) {
 133                count = ret;
 134                goto exit;
 135        }
 136        r_ver = (struct ec_response_get_version *)msg->data;
 137        /* Strings should be null-terminated, but let's be sure. */
 138        r_ver->version_string_ro[sizeof(r_ver->version_string_ro) - 1] = '\0';
 139        r_ver->version_string_rw[sizeof(r_ver->version_string_rw) - 1] = '\0';
 140        count += scnprintf(buf + count, PAGE_SIZE - count,
 141                           "RO version:    %s\n", r_ver->version_string_ro);
 142        count += scnprintf(buf + count, PAGE_SIZE - count,
 143                           "RW version:    %s\n", r_ver->version_string_rw);
 144        count += scnprintf(buf + count, PAGE_SIZE - count,
 145                           "Firmware copy: %s\n",
 146                           (r_ver->current_image < ARRAY_SIZE(image_names) ?
 147                            image_names[r_ver->current_image] : "?"));
 148
 149        /* Get build info. */
 150        msg->command = EC_CMD_GET_BUILD_INFO + ec->cmd_offset;
 151        msg->insize = EC_HOST_PARAM_SIZE;
 152        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 153        if (ret < 0)
 154                count += scnprintf(buf + count, PAGE_SIZE - count,
 155                                   "Build info:    XFER ERROR %d\n", ret);
 156        else if (msg->result != EC_RES_SUCCESS)
 157                count += scnprintf(buf + count, PAGE_SIZE - count,
 158                                   "Build info:    EC error %d\n", msg->result);
 159        else {
 160                msg->data[EC_HOST_PARAM_SIZE - 1] = '\0';
 161                count += scnprintf(buf + count, PAGE_SIZE - count,
 162                                   "Build info:    %s\n", msg->data);
 163        }
 164
 165        /* Get chip info. */
 166        msg->command = EC_CMD_GET_CHIP_INFO + ec->cmd_offset;
 167        msg->insize = sizeof(*r_chip);
 168        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 169        if (ret < 0)
 170                count += scnprintf(buf + count, PAGE_SIZE - count,
 171                                   "Chip info:     XFER ERROR %d\n", ret);
 172        else if (msg->result != EC_RES_SUCCESS)
 173                count += scnprintf(buf + count, PAGE_SIZE - count,
 174                                   "Chip info:     EC error %d\n", msg->result);
 175        else {
 176                r_chip = (struct ec_response_get_chip_info *)msg->data;
 177
 178                r_chip->vendor[sizeof(r_chip->vendor) - 1] = '\0';
 179                r_chip->name[sizeof(r_chip->name) - 1] = '\0';
 180                r_chip->revision[sizeof(r_chip->revision) - 1] = '\0';
 181                count += scnprintf(buf + count, PAGE_SIZE - count,
 182                                   "Chip vendor:   %s\n", r_chip->vendor);
 183                count += scnprintf(buf + count, PAGE_SIZE - count,
 184                                   "Chip name:     %s\n", r_chip->name);
 185                count += scnprintf(buf + count, PAGE_SIZE - count,
 186                                   "Chip revision: %s\n", r_chip->revision);
 187        }
 188
 189        /* Get board version */
 190        msg->command = EC_CMD_GET_BOARD_VERSION + ec->cmd_offset;
 191        msg->insize = sizeof(*r_board);
 192        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 193        if (ret < 0)
 194                count += scnprintf(buf + count, PAGE_SIZE - count,
 195                                   "Board version: XFER ERROR %d\n", ret);
 196        else if (msg->result != EC_RES_SUCCESS)
 197                count += scnprintf(buf + count, PAGE_SIZE - count,
 198                                   "Board version: EC error %d\n", msg->result);
 199        else {
 200                r_board = (struct ec_response_board_version *)msg->data;
 201
 202                count += scnprintf(buf + count, PAGE_SIZE - count,
 203                                   "Board version: %d\n",
 204                                   r_board->board_version);
 205        }
 206
 207exit:
 208        kfree(msg);
 209        return count;
 210}
 211
 212static ssize_t flashinfo_show(struct device *dev,
 213                              struct device_attribute *attr, char *buf)
 214{
 215        struct ec_response_flash_info *resp;
 216        struct cros_ec_command *msg;
 217        int ret;
 218        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 219
 220        msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
 221        if (!msg)
 222                return -ENOMEM;
 223
 224        /* The flash info shouldn't ever change, but ask each time anyway. */
 225        msg->version = 0;
 226        msg->command = EC_CMD_FLASH_INFO + ec->cmd_offset;
 227        msg->insize = sizeof(*resp);
 228        msg->outsize = 0;
 229        ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
 230        if (ret < 0)
 231                goto exit;
 232
 233        resp = (struct ec_response_flash_info *)msg->data;
 234
 235        ret = scnprintf(buf, PAGE_SIZE,
 236                        "FlashSize %d\nWriteSize %d\n"
 237                        "EraseSize %d\nProtectSize %d\n",
 238                        resp->flash_size, resp->write_block_size,
 239                        resp->erase_block_size, resp->protect_block_size);
 240exit:
 241        kfree(msg);
 242        return ret;
 243}
 244
 245/* Keyboard wake angle control */
 246static ssize_t kb_wake_angle_show(struct device *dev,
 247                                  struct device_attribute *attr, char *buf)
 248{
 249        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 250        struct ec_response_motion_sense *resp;
 251        struct ec_params_motion_sense *param;
 252        struct cros_ec_command *msg;
 253        int ret;
 254
 255        msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL);
 256        if (!msg)
 257                return -ENOMEM;
 258
 259        param = (struct ec_params_motion_sense *)msg->data;
 260        msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
 261        msg->version = 2;
 262        param->cmd = MOTIONSENSE_CMD_KB_WAKE_ANGLE;
 263        param->kb_wake_angle.data = EC_MOTION_SENSE_NO_VALUE;
 264        msg->outsize = sizeof(*param);
 265        msg->insize = sizeof(*resp);
 266
 267        ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
 268        if (ret < 0)
 269                goto exit;
 270
 271        resp = (struct ec_response_motion_sense *)msg->data;
 272        ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->kb_wake_angle.ret);
 273exit:
 274        kfree(msg);
 275        return ret;
 276}
 277
 278static ssize_t kb_wake_angle_store(struct device *dev,
 279                                   struct device_attribute *attr,
 280                                   const char *buf, size_t count)
 281{
 282        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 283        struct ec_params_motion_sense *param;
 284        struct cros_ec_command *msg;
 285        u16 angle;
 286        int ret;
 287
 288        ret = kstrtou16(buf, 0, &angle);
 289        if (ret)
 290                return ret;
 291
 292        msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL);
 293        if (!msg)
 294                return -ENOMEM;
 295
 296        param = (struct ec_params_motion_sense *)msg->data;
 297        msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
 298        msg->version = 2;
 299        param->cmd = MOTIONSENSE_CMD_KB_WAKE_ANGLE;
 300        param->kb_wake_angle.data = angle;
 301        msg->outsize = sizeof(*param);
 302        msg->insize = sizeof(struct ec_response_motion_sense);
 303
 304        ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
 305        kfree(msg);
 306        if (ret < 0)
 307                return ret;
 308        return count;
 309}
 310
 311/* Module initialization */
 312
 313static DEVICE_ATTR_RW(reboot);
 314static DEVICE_ATTR_RO(version);
 315static DEVICE_ATTR_RO(flashinfo);
 316static DEVICE_ATTR_RW(kb_wake_angle);
 317
 318static struct attribute *__ec_attrs[] = {
 319        &dev_attr_kb_wake_angle.attr,
 320        &dev_attr_reboot.attr,
 321        &dev_attr_version.attr,
 322        &dev_attr_flashinfo.attr,
 323        NULL,
 324};
 325
 326static umode_t cros_ec_ctrl_visible(struct kobject *kobj,
 327                                    struct attribute *a, int n)
 328{
 329        struct device *dev = container_of(kobj, struct device, kobj);
 330        struct cros_ec_dev *ec = to_cros_ec_dev(dev);
 331
 332        if (a == &dev_attr_kb_wake_angle.attr && !ec->has_kb_wake_angle)
 333                return 0;
 334
 335        return a->mode;
 336}
 337
 338struct attribute_group cros_ec_attr_group = {
 339        .attrs = __ec_attrs,
 340        .is_visible = cros_ec_ctrl_visible,
 341};
 342
 343static int cros_ec_sysfs_probe(struct platform_device *pd)
 344{
 345        struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
 346        struct device *dev = &pd->dev;
 347        int ret;
 348
 349        ret = sysfs_create_group(&ec_dev->class_dev.kobj, &cros_ec_attr_group);
 350        if (ret < 0)
 351                dev_err(dev, "failed to create attributes. err=%d\n", ret);
 352
 353        return ret;
 354}
 355
 356static int cros_ec_sysfs_remove(struct platform_device *pd)
 357{
 358        struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
 359
 360        sysfs_remove_group(&ec_dev->class_dev.kobj, &cros_ec_attr_group);
 361
 362        return 0;
 363}
 364
 365static struct platform_driver cros_ec_sysfs_driver = {
 366        .driver = {
 367                .name = DRV_NAME,
 368        },
 369        .probe = cros_ec_sysfs_probe,
 370        .remove = cros_ec_sysfs_remove,
 371};
 372
 373module_platform_driver(cros_ec_sysfs_driver);
 374
 375MODULE_LICENSE("GPL");
 376MODULE_DESCRIPTION("Expose the ChromeOS EC through sysfs");
 377MODULE_ALIAS("platform:" DRV_NAME);
 378