linux/drivers/mfd/cros_ec_dev.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space
   4 *
   5 * Copyright (C) 2014 Google, Inc.
   6 */
   7
   8#include <linux/fs.h>
   9#include <linux/mfd/core.h>
  10#include <linux/module.h>
  11#include <linux/mod_devicetable.h>
  12#include <linux/of_platform.h>
  13#include <linux/platform_device.h>
  14#include <linux/pm.h>
  15#include <linux/slab.h>
  16#include <linux/uaccess.h>
  17
  18#include "cros_ec_dev.h"
  19
  20#define DRV_NAME "cros-ec-dev"
  21
  22/* Device variables */
  23#define CROS_MAX_DEV 128
  24static int ec_major;
  25
  26static struct class cros_class = {
  27        .owner          = THIS_MODULE,
  28        .name           = "chromeos",
  29};
  30
  31/* Basic communication */
  32static int ec_get_version(struct cros_ec_dev *ec, char *str, int maxlen)
  33{
  34        struct ec_response_get_version *resp;
  35        static const char * const current_image_name[] = {
  36                "unknown", "read-only", "read-write", "invalid",
  37        };
  38        struct cros_ec_command *msg;
  39        int ret;
  40
  41        msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
  42        if (!msg)
  43                return -ENOMEM;
  44
  45        msg->version = 0;
  46        msg->command = EC_CMD_GET_VERSION + ec->cmd_offset;
  47        msg->insize = sizeof(*resp);
  48        msg->outsize = 0;
  49
  50        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
  51        if (ret < 0)
  52                goto exit;
  53
  54        if (msg->result != EC_RES_SUCCESS) {
  55                snprintf(str, maxlen,
  56                         "%s\nUnknown EC version: EC returned %d\n",
  57                         CROS_EC_DEV_VERSION, msg->result);
  58                ret = -EINVAL;
  59                goto exit;
  60        }
  61
  62        resp = (struct ec_response_get_version *)msg->data;
  63        if (resp->current_image >= ARRAY_SIZE(current_image_name))
  64                resp->current_image = 3; /* invalid */
  65
  66        snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION,
  67                 resp->version_string_ro, resp->version_string_rw,
  68                 current_image_name[resp->current_image]);
  69
  70        ret = 0;
  71exit:
  72        kfree(msg);
  73        return ret;
  74}
  75
  76static int cros_ec_check_features(struct cros_ec_dev *ec, int feature)
  77{
  78        struct cros_ec_command *msg;
  79        int ret;
  80
  81        if (ec->features[0] == -1U && ec->features[1] == -1U) {
  82                /* features bitmap not read yet */
  83
  84                msg = kmalloc(sizeof(*msg) + sizeof(ec->features), GFP_KERNEL);
  85                if (!msg)
  86                        return -ENOMEM;
  87
  88                msg->version = 0;
  89                msg->command = EC_CMD_GET_FEATURES + ec->cmd_offset;
  90                msg->insize = sizeof(ec->features);
  91                msg->outsize = 0;
  92
  93                ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
  94                if (ret < 0 || msg->result != EC_RES_SUCCESS) {
  95                        dev_warn(ec->dev, "cannot get EC features: %d/%d\n",
  96                                 ret, msg->result);
  97                        memset(ec->features, 0, sizeof(ec->features));
  98                } else {
  99                        memcpy(ec->features, msg->data, sizeof(ec->features));
 100                }
 101
 102                dev_dbg(ec->dev, "EC features %08x %08x\n",
 103                        ec->features[0], ec->features[1]);
 104
 105                kfree(msg);
 106        }
 107
 108        return ec->features[feature / 32] & EC_FEATURE_MASK_0(feature);
 109}
 110
 111/* Device file ops */
 112static int ec_device_open(struct inode *inode, struct file *filp)
 113{
 114        struct cros_ec_dev *ec = container_of(inode->i_cdev,
 115                                              struct cros_ec_dev, cdev);
 116        filp->private_data = ec;
 117        nonseekable_open(inode, filp);
 118        return 0;
 119}
 120
 121static int ec_device_release(struct inode *inode, struct file *filp)
 122{
 123        return 0;
 124}
 125
 126static ssize_t ec_device_read(struct file *filp, char __user *buffer,
 127                              size_t length, loff_t *offset)
 128{
 129        struct cros_ec_dev *ec = filp->private_data;
 130        char msg[sizeof(struct ec_response_get_version) +
 131                 sizeof(CROS_EC_DEV_VERSION)];
 132        size_t count;
 133        int ret;
 134
 135        if (*offset != 0)
 136                return 0;
 137
 138        ret = ec_get_version(ec, msg, sizeof(msg));
 139        if (ret)
 140                return ret;
 141
 142        count = min(length, strlen(msg));
 143
 144        if (copy_to_user(buffer, msg, count))
 145                return -EFAULT;
 146
 147        *offset = count;
 148        return count;
 149}
 150
 151/* Ioctls */
 152static long ec_device_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg)
 153{
 154        long ret;
 155        struct cros_ec_command u_cmd;
 156        struct cros_ec_command *s_cmd;
 157
 158        if (copy_from_user(&u_cmd, arg, sizeof(u_cmd)))
 159                return -EFAULT;
 160
 161        if ((u_cmd.outsize > EC_MAX_MSG_BYTES) ||
 162            (u_cmd.insize > EC_MAX_MSG_BYTES))
 163                return -EINVAL;
 164
 165        s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize),
 166                        GFP_KERNEL);
 167        if (!s_cmd)
 168                return -ENOMEM;
 169
 170        if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) {
 171                ret = -EFAULT;
 172                goto exit;
 173        }
 174
 175        if (u_cmd.outsize != s_cmd->outsize ||
 176            u_cmd.insize != s_cmd->insize) {
 177                ret = -EINVAL;
 178                goto exit;
 179        }
 180
 181        s_cmd->command += ec->cmd_offset;
 182        ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd);
 183        /* Only copy data to userland if data was received. */
 184        if (ret < 0)
 185                goto exit;
 186
 187        if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize))
 188                ret = -EFAULT;
 189exit:
 190        kfree(s_cmd);
 191        return ret;
 192}
 193
 194static long ec_device_ioctl_readmem(struct cros_ec_dev *ec, void __user *arg)
 195{
 196        struct cros_ec_device *ec_dev = ec->ec_dev;
 197        struct cros_ec_readmem s_mem = { };
 198        long num;
 199
 200        /* Not every platform supports direct reads */
 201        if (!ec_dev->cmd_readmem)
 202                return -ENOTTY;
 203
 204        if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
 205                return -EFAULT;
 206
 207        num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes,
 208                                  s_mem.buffer);
 209        if (num <= 0)
 210                return num;
 211
 212        if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem)))
 213                return -EFAULT;
 214
 215        return num;
 216}
 217
 218static long ec_device_ioctl(struct file *filp, unsigned int cmd,
 219                            unsigned long arg)
 220{
 221        struct cros_ec_dev *ec = filp->private_data;
 222
 223        if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC)
 224                return -ENOTTY;
 225
 226        switch (cmd) {
 227        case CROS_EC_DEV_IOCXCMD:
 228                return ec_device_ioctl_xcmd(ec, (void __user *)arg);
 229        case CROS_EC_DEV_IOCRDMEM:
 230                return ec_device_ioctl_readmem(ec, (void __user *)arg);
 231        }
 232
 233        return -ENOTTY;
 234}
 235
 236/* Module initialization */
 237static const struct file_operations fops = {
 238        .open = ec_device_open,
 239        .release = ec_device_release,
 240        .read = ec_device_read,
 241        .unlocked_ioctl = ec_device_ioctl,
 242#ifdef CONFIG_COMPAT
 243        .compat_ioctl = ec_device_ioctl,
 244#endif
 245};
 246
 247static void cros_ec_class_release(struct device *dev)
 248{
 249        kfree(to_cros_ec_dev(dev));
 250}
 251
 252static void cros_ec_sensors_register(struct cros_ec_dev *ec)
 253{
 254        /*
 255         * Issue a command to get the number of sensor reported.
 256         * Build an array of sensors driver and register them all.
 257         */
 258        int ret, i, id, sensor_num;
 259        struct mfd_cell *sensor_cells;
 260        struct cros_ec_sensor_platform *sensor_platforms;
 261        int sensor_type[MOTIONSENSE_TYPE_MAX];
 262        struct ec_params_motion_sense *params;
 263        struct ec_response_motion_sense *resp;
 264        struct cros_ec_command *msg;
 265
 266        msg = kzalloc(sizeof(struct cros_ec_command) +
 267                      max(sizeof(*params), sizeof(*resp)), GFP_KERNEL);
 268        if (msg == NULL)
 269                return;
 270
 271        msg->version = 2;
 272        msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
 273        msg->outsize = sizeof(*params);
 274        msg->insize = sizeof(*resp);
 275
 276        params = (struct ec_params_motion_sense *)msg->data;
 277        params->cmd = MOTIONSENSE_CMD_DUMP;
 278
 279        ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 280        if (ret < 0 || msg->result != EC_RES_SUCCESS) {
 281                dev_warn(ec->dev, "cannot get EC sensor information: %d/%d\n",
 282                         ret, msg->result);
 283                goto error;
 284        }
 285
 286        resp = (struct ec_response_motion_sense *)msg->data;
 287        sensor_num = resp->dump.sensor_count;
 288        /*
 289         * Allocate 2 extra sensors if lid angle sensor and/or FIFO are needed.
 290         */
 291        sensor_cells = kcalloc(sensor_num + 2, sizeof(struct mfd_cell),
 292                               GFP_KERNEL);
 293        if (sensor_cells == NULL)
 294                goto error;
 295
 296        sensor_platforms = kcalloc(sensor_num,
 297                                   sizeof(struct cros_ec_sensor_platform),
 298                                   GFP_KERNEL);
 299        if (sensor_platforms == NULL)
 300                goto error_platforms;
 301
 302        memset(sensor_type, 0, sizeof(sensor_type));
 303        id = 0;
 304        for (i = 0; i < sensor_num; i++) {
 305                params->cmd = MOTIONSENSE_CMD_INFO;
 306                params->info.sensor_num = i;
 307                ret = cros_ec_cmd_xfer(ec->ec_dev, msg);
 308                if (ret < 0 || msg->result != EC_RES_SUCCESS) {
 309                        dev_warn(ec->dev, "no info for EC sensor %d : %d/%d\n",
 310                                 i, ret, msg->result);
 311                        continue;
 312                }
 313                switch (resp->info.type) {
 314                case MOTIONSENSE_TYPE_ACCEL:
 315                        sensor_cells[id].name = "cros-ec-accel";
 316                        break;
 317                case MOTIONSENSE_TYPE_BARO:
 318                        sensor_cells[id].name = "cros-ec-baro";
 319                        break;
 320                case MOTIONSENSE_TYPE_GYRO:
 321                        sensor_cells[id].name = "cros-ec-gyro";
 322                        break;
 323                case MOTIONSENSE_TYPE_MAG:
 324                        sensor_cells[id].name = "cros-ec-mag";
 325                        break;
 326                case MOTIONSENSE_TYPE_PROX:
 327                        sensor_cells[id].name = "cros-ec-prox";
 328                        break;
 329                case MOTIONSENSE_TYPE_LIGHT:
 330                        sensor_cells[id].name = "cros-ec-light";
 331                        break;
 332                case MOTIONSENSE_TYPE_ACTIVITY:
 333                        sensor_cells[id].name = "cros-ec-activity";
 334                        break;
 335                default:
 336                        dev_warn(ec->dev, "unknown type %d\n", resp->info.type);
 337                        continue;
 338                }
 339                sensor_platforms[id].sensor_num = i;
 340                sensor_cells[id].id = sensor_type[resp->info.type];
 341                sensor_cells[id].platform_data = &sensor_platforms[id];
 342                sensor_cells[id].pdata_size =
 343                        sizeof(struct cros_ec_sensor_platform);
 344
 345                sensor_type[resp->info.type]++;
 346                id++;
 347        }
 348
 349        if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2)
 350                ec->has_kb_wake_angle = true;
 351
 352        if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) {
 353                sensor_cells[id].name = "cros-ec-ring";
 354                id++;
 355        }
 356        if (cros_ec_check_features(ec,
 357                                EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) {
 358                sensor_cells[id].name = "cros-ec-lid-angle";
 359                id++;
 360        }
 361
 362        ret = mfd_add_devices(ec->dev, 0, sensor_cells, id,
 363                              NULL, 0, NULL);
 364        if (ret)
 365                dev_err(ec->dev, "failed to add EC sensors\n");
 366
 367        kfree(sensor_platforms);
 368error_platforms:
 369        kfree(sensor_cells);
 370error:
 371        kfree(msg);
 372}
 373
 374static struct cros_ec_sensor_platform sensor_platforms[] = {
 375        { .sensor_num = 0 },
 376        { .sensor_num = 1 }
 377};
 378
 379static const struct mfd_cell cros_ec_accel_legacy_cells[] = {
 380        {
 381                .name = "cros-ec-accel-legacy",
 382                .platform_data = &sensor_platforms[0],
 383                .pdata_size = sizeof(struct cros_ec_sensor_platform),
 384        },
 385        {
 386                .name = "cros-ec-accel-legacy",
 387                .platform_data = &sensor_platforms[1],
 388                .pdata_size = sizeof(struct cros_ec_sensor_platform),
 389        }
 390};
 391
 392static void cros_ec_accel_legacy_register(struct cros_ec_dev *ec)
 393{
 394        struct cros_ec_device *ec_dev = ec->ec_dev;
 395        u8 status;
 396        int ret;
 397
 398        /*
 399         * ECs that need legacy support are the main EC, directly connected to
 400         * the AP.
 401         */
 402        if (ec->cmd_offset != 0)
 403                return;
 404
 405        /*
 406         * Check if EC supports direct memory reads and if EC has
 407         * accelerometers.
 408         */
 409        if (ec_dev->cmd_readmem) {
 410                ret = ec_dev->cmd_readmem(ec_dev, EC_MEMMAP_ACC_STATUS, 1,
 411                                          &status);
 412                if (ret < 0) {
 413                        dev_warn(ec->dev, "EC direct read error.\n");
 414                        return;
 415                }
 416
 417                /* Check if EC has accelerometers. */
 418                if (!(status & EC_MEMMAP_ACC_STATUS_PRESENCE_BIT)) {
 419                        dev_info(ec->dev, "EC does not have accelerometers.\n");
 420                        return;
 421                }
 422        }
 423
 424        /*
 425         * The device may still support accelerometers:
 426         * it would be an older ARM based device that do not suppor the
 427         * EC_CMD_GET_FEATURES command.
 428         *
 429         * Register 2 accelerometers, we will fail in the IIO driver if there
 430         * are no sensors.
 431         */
 432        ret = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO,
 433                              cros_ec_accel_legacy_cells,
 434                              ARRAY_SIZE(cros_ec_accel_legacy_cells),
 435                              NULL, 0, NULL);
 436        if (ret)
 437                dev_err(ec_dev->dev, "failed to add EC sensors\n");
 438}
 439
 440static const struct mfd_cell cros_ec_cec_cells[] = {
 441        { .name = "cros-ec-cec" }
 442};
 443
 444static const struct mfd_cell cros_ec_rtc_cells[] = {
 445        { .name = "cros-ec-rtc" }
 446};
 447
 448static const struct mfd_cell cros_usbpd_charger_cells[] = {
 449        { .name = "cros-usbpd-charger" },
 450        { .name = "cros-usbpd-logger" },
 451};
 452
 453static const struct mfd_cell cros_ec_platform_cells[] = {
 454        { .name = "cros-ec-debugfs" },
 455        { .name = "cros-ec-lightbar" },
 456        { .name = "cros-ec-sysfs" },
 457};
 458
 459static const struct mfd_cell cros_ec_vbc_cells[] = {
 460        { .name = "cros-ec-vbc" }
 461};
 462
 463static int ec_device_probe(struct platform_device *pdev)
 464{
 465        int retval = -ENOMEM;
 466        struct device_node *node;
 467        struct device *dev = &pdev->dev;
 468        struct cros_ec_platform *ec_platform = dev_get_platdata(dev);
 469        struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL);
 470
 471        if (!ec)
 472                return retval;
 473
 474        dev_set_drvdata(dev, ec);
 475        ec->ec_dev = dev_get_drvdata(dev->parent);
 476        ec->dev = dev;
 477        ec->cmd_offset = ec_platform->cmd_offset;
 478        ec->features[0] = -1U; /* Not cached yet */
 479        ec->features[1] = -1U; /* Not cached yet */
 480        device_initialize(&ec->class_dev);
 481        cdev_init(&ec->cdev, &fops);
 482
 483        /* Check whether this is actually a Fingerprint MCU rather than an EC */
 484        if (cros_ec_check_features(ec, EC_FEATURE_FINGERPRINT)) {
 485                dev_info(dev, "CrOS Fingerprint MCU detected.\n");
 486                /*
 487                 * Help userspace differentiating ECs from FP MCU,
 488                 * regardless of the probing order.
 489                 */
 490                ec_platform->ec_name = CROS_EC_DEV_FP_NAME;
 491        }
 492
 493        /*
 494         * Check whether this is actually an Integrated Sensor Hub (ISH)
 495         * rather than an EC.
 496         */
 497        if (cros_ec_check_features(ec, EC_FEATURE_ISH)) {
 498                dev_info(dev, "CrOS ISH MCU detected.\n");
 499                /*
 500                 * Help userspace differentiating ECs from ISH MCU,
 501                 * regardless of the probing order.
 502                 */
 503                ec_platform->ec_name = CROS_EC_DEV_ISH_NAME;
 504        }
 505
 506        /* Check whether this is actually a Touchpad MCU rather than an EC */
 507        if (cros_ec_check_features(ec, EC_FEATURE_TOUCHPAD)) {
 508                dev_info(dev, "CrOS Touchpad MCU detected.\n");
 509                /*
 510                 * Help userspace differentiating ECs from TP MCU,
 511                 * regardless of the probing order.
 512                 */
 513                ec_platform->ec_name = CROS_EC_DEV_TP_NAME;
 514        }
 515
 516        /* Check whether this is actually a SCP rather than an EC. */
 517        if (cros_ec_check_features(ec, EC_FEATURE_SCP)) {
 518                dev_info(dev, "CrOS SCP MCU detected.\n");
 519                /*
 520                 * Help userspace differentiating ECs from SCP,
 521                 * regardless of the probing order.
 522                 */
 523                ec_platform->ec_name = CROS_EC_DEV_SCP_NAME;
 524        }
 525
 526        /*
 527         * Add the class device
 528         * Link to the character device for creating the /dev entry
 529         * in devtmpfs.
 530         */
 531        ec->class_dev.devt = MKDEV(ec_major, pdev->id);
 532        ec->class_dev.class = &cros_class;
 533        ec->class_dev.parent = dev;
 534        ec->class_dev.release = cros_ec_class_release;
 535
 536        retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name);
 537        if (retval) {
 538                dev_err(dev, "dev_set_name failed => %d\n", retval);
 539                goto failed;
 540        }
 541
 542        /* check whether this EC is a sensor hub. */
 543        if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE))
 544                cros_ec_sensors_register(ec);
 545        else
 546                /* Workaroud for older EC firmware */
 547                cros_ec_accel_legacy_register(ec);
 548
 549        /* Check whether this EC instance has CEC host command support */
 550        if (cros_ec_check_features(ec, EC_FEATURE_CEC)) {
 551                retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO,
 552                                         cros_ec_cec_cells,
 553                                         ARRAY_SIZE(cros_ec_cec_cells),
 554                                         NULL, 0, NULL);
 555                if (retval)
 556                        dev_err(ec->dev,
 557                                "failed to add cros-ec-cec device: %d\n",
 558                                retval);
 559        }
 560
 561        /* Check whether this EC instance has RTC host command support */
 562        if (cros_ec_check_features(ec, EC_FEATURE_RTC)) {
 563                retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO,
 564                                         cros_ec_rtc_cells,
 565                                         ARRAY_SIZE(cros_ec_rtc_cells),
 566                                         NULL, 0, NULL);
 567                if (retval)
 568                        dev_err(ec->dev,
 569                                "failed to add cros-ec-rtc device: %d\n",
 570                                retval);
 571        }
 572
 573        /* Check whether this EC instance has the PD charge manager */
 574        if (cros_ec_check_features(ec, EC_FEATURE_USB_PD)) {
 575                retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO,
 576                                         cros_usbpd_charger_cells,
 577                                         ARRAY_SIZE(cros_usbpd_charger_cells),
 578                                         NULL, 0, NULL);
 579                if (retval)
 580                        dev_err(ec->dev,
 581                                "failed to add cros-usbpd-charger device: %d\n",
 582                                retval);
 583        }
 584
 585        /* We can now add the sysfs class, we know which parameter to show */
 586        retval = cdev_device_add(&ec->cdev, &ec->class_dev);
 587        if (retval) {
 588                dev_err(dev, "cdev_device_add failed => %d\n", retval);
 589                goto failed;
 590        }
 591
 592        retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO,
 593                                 cros_ec_platform_cells,
 594                                 ARRAY_SIZE(cros_ec_platform_cells),
 595                                 NULL, 0, NULL);
 596        if (retval)
 597                dev_warn(ec->dev,
 598                         "failed to add cros-ec platform devices: %d\n",
 599                         retval);
 600
 601        /* Check whether this EC instance has a VBC NVRAM */
 602        node = ec->ec_dev->dev->of_node;
 603        if (of_property_read_bool(node, "google,has-vbc-nvram")) {
 604                retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO,
 605                                         cros_ec_vbc_cells,
 606                                         ARRAY_SIZE(cros_ec_vbc_cells),
 607                                         NULL, 0, NULL);
 608                if (retval)
 609                        dev_warn(ec->dev, "failed to add VBC devices: %d\n",
 610                                 retval);
 611        }
 612
 613        return 0;
 614
 615failed:
 616        put_device(&ec->class_dev);
 617        return retval;
 618}
 619
 620static int ec_device_remove(struct platform_device *pdev)
 621{
 622        struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev);
 623
 624        mfd_remove_devices(ec->dev);
 625        cdev_del(&ec->cdev);
 626        device_unregister(&ec->class_dev);
 627        return 0;
 628}
 629
 630static const struct platform_device_id cros_ec_id[] = {
 631        { DRV_NAME, 0 },
 632        { /* sentinel */ }
 633};
 634MODULE_DEVICE_TABLE(platform, cros_ec_id);
 635
 636static struct platform_driver cros_ec_dev_driver = {
 637        .driver = {
 638                .name = DRV_NAME,
 639        },
 640        .id_table = cros_ec_id,
 641        .probe = ec_device_probe,
 642        .remove = ec_device_remove,
 643};
 644
 645static int __init cros_ec_dev_init(void)
 646{
 647        int ret;
 648        dev_t dev = 0;
 649
 650        ret  = class_register(&cros_class);
 651        if (ret) {
 652                pr_err(CROS_EC_DEV_NAME ": failed to register device class\n");
 653                return ret;
 654        }
 655
 656        /* Get a range of minor numbers (starting with 0) to work with */
 657        ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME);
 658        if (ret < 0) {
 659                pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n");
 660                goto failed_chrdevreg;
 661        }
 662        ec_major = MAJOR(dev);
 663
 664        /* Register the driver */
 665        ret = platform_driver_register(&cros_ec_dev_driver);
 666        if (ret < 0) {
 667                pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret);
 668                goto failed_devreg;
 669        }
 670        return 0;
 671
 672failed_devreg:
 673        unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV);
 674failed_chrdevreg:
 675        class_unregister(&cros_class);
 676        return ret;
 677}
 678
 679static void __exit cros_ec_dev_exit(void)
 680{
 681        platform_driver_unregister(&cros_ec_dev_driver);
 682        unregister_chrdev(ec_major, CROS_EC_DEV_NAME);
 683        class_unregister(&cros_class);
 684}
 685
 686module_init(cros_ec_dev_init);
 687module_exit(cros_ec_dev_exit);
 688
 689MODULE_ALIAS("platform:" DRV_NAME);
 690MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>");
 691MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller");
 692MODULE_VERSION("1.0");
 693MODULE_LICENSE("GPL");
 694