15e011558SThierry Escande /* 25e011558SThierry Escande * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space 35e011558SThierry Escande * 45e011558SThierry Escande * Copyright (C) 2014 Google, Inc. 55e011558SThierry Escande * 65e011558SThierry Escande * This program is free software; you can redistribute it and/or modify 75e011558SThierry Escande * it under the terms of the GNU General Public License as published by 85e011558SThierry Escande * the Free Software Foundation; either version 2 of the License, or 95e011558SThierry Escande * (at your option) any later version. 105e011558SThierry Escande * 115e011558SThierry Escande * This program is distributed in the hope that it will be useful, 125e011558SThierry Escande * but WITHOUT ANY WARRANTY; without even the implied warranty of 135e011558SThierry Escande * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 145e011558SThierry Escande * GNU General Public License for more details. 155e011558SThierry Escande * 165e011558SThierry Escande * You should have received a copy of the GNU General Public License 175e011558SThierry Escande * along with this program. If not, see <http://www.gnu.org/licenses/>. 185e011558SThierry Escande */ 195e011558SThierry Escande 205e011558SThierry Escande #include <linux/fs.h> 215e011558SThierry Escande #include <linux/mfd/core.h> 225e011558SThierry Escande #include <linux/module.h> 23ac316725SRandy Dunlap #include <linux/mod_devicetable.h> 240545625bSEnric Balletbo i Serra #include <linux/of_platform.h> 255e011558SThierry Escande #include <linux/platform_device.h> 265e011558SThierry Escande #include <linux/pm.h> 275e011558SThierry Escande #include <linux/slab.h> 285e011558SThierry Escande #include <linux/uaccess.h> 295e011558SThierry Escande 305e011558SThierry Escande #include "cros_ec_dev.h" 315e011558SThierry Escande 325e011558SThierry Escande #define DRV_NAME "cros-ec-dev" 335e011558SThierry Escande 345e011558SThierry Escande /* Device variables */ 355e011558SThierry Escande #define CROS_MAX_DEV 128 365e011558SThierry Escande static int ec_major; 375e011558SThierry Escande 385e011558SThierry Escande static struct class cros_class = { 395e011558SThierry Escande .owner = THIS_MODULE, 405e011558SThierry Escande .name = "chromeos", 415e011558SThierry Escande }; 425e011558SThierry Escande 435e011558SThierry Escande /* Basic communication */ 445e011558SThierry Escande static int ec_get_version(struct cros_ec_dev *ec, char *str, int maxlen) 455e011558SThierry Escande { 465e011558SThierry Escande struct ec_response_get_version *resp; 475e011558SThierry Escande static const char * const current_image_name[] = { 485e011558SThierry Escande "unknown", "read-only", "read-write", "invalid", 495e011558SThierry Escande }; 505e011558SThierry Escande struct cros_ec_command *msg; 515e011558SThierry Escande int ret; 525e011558SThierry Escande 535e011558SThierry Escande msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); 545e011558SThierry Escande if (!msg) 555e011558SThierry Escande return -ENOMEM; 565e011558SThierry Escande 575e011558SThierry Escande msg->version = 0; 585e011558SThierry Escande msg->command = EC_CMD_GET_VERSION + ec->cmd_offset; 595e011558SThierry Escande msg->insize = sizeof(*resp); 605e011558SThierry Escande msg->outsize = 0; 615e011558SThierry Escande 625e011558SThierry Escande ret = cros_ec_cmd_xfer(ec->ec_dev, msg); 635e011558SThierry Escande if (ret < 0) 645e011558SThierry Escande goto exit; 655e011558SThierry Escande 665e011558SThierry Escande if (msg->result != EC_RES_SUCCESS) { 675e011558SThierry Escande snprintf(str, maxlen, 685e011558SThierry Escande "%s\nUnknown EC version: EC returned %d\n", 695e011558SThierry Escande CROS_EC_DEV_VERSION, msg->result); 705e011558SThierry Escande ret = -EINVAL; 715e011558SThierry Escande goto exit; 725e011558SThierry Escande } 735e011558SThierry Escande 745e011558SThierry Escande resp = (struct ec_response_get_version *)msg->data; 755e011558SThierry Escande if (resp->current_image >= ARRAY_SIZE(current_image_name)) 765e011558SThierry Escande resp->current_image = 3; /* invalid */ 775e011558SThierry Escande 785e011558SThierry Escande snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION, 795e011558SThierry Escande resp->version_string_ro, resp->version_string_rw, 805e011558SThierry Escande current_image_name[resp->current_image]); 815e011558SThierry Escande 825e011558SThierry Escande ret = 0; 835e011558SThierry Escande exit: 845e011558SThierry Escande kfree(msg); 855e011558SThierry Escande return ret; 865e011558SThierry Escande } 875e011558SThierry Escande 885e011558SThierry Escande static int cros_ec_check_features(struct cros_ec_dev *ec, int feature) 895e011558SThierry Escande { 905e011558SThierry Escande struct cros_ec_command *msg; 915e011558SThierry Escande int ret; 925e011558SThierry Escande 935e011558SThierry Escande if (ec->features[0] == -1U && ec->features[1] == -1U) { 945e011558SThierry Escande /* features bitmap not read yet */ 955e011558SThierry Escande 965e011558SThierry Escande msg = kmalloc(sizeof(*msg) + sizeof(ec->features), GFP_KERNEL); 975e011558SThierry Escande if (!msg) 985e011558SThierry Escande return -ENOMEM; 995e011558SThierry Escande 1005e011558SThierry Escande msg->version = 0; 1015e011558SThierry Escande msg->command = EC_CMD_GET_FEATURES + ec->cmd_offset; 1025e011558SThierry Escande msg->insize = sizeof(ec->features); 1035e011558SThierry Escande msg->outsize = 0; 1045e011558SThierry Escande 1055e011558SThierry Escande ret = cros_ec_cmd_xfer(ec->ec_dev, msg); 1065e011558SThierry Escande if (ret < 0 || msg->result != EC_RES_SUCCESS) { 1075e011558SThierry Escande dev_warn(ec->dev, "cannot get EC features: %d/%d\n", 1085e011558SThierry Escande ret, msg->result); 1095e011558SThierry Escande memset(ec->features, 0, sizeof(ec->features)); 110df7c3bf2SStephen Boyd } else { 1115e011558SThierry Escande memcpy(ec->features, msg->data, sizeof(ec->features)); 112df7c3bf2SStephen Boyd } 1135e011558SThierry Escande 1145e011558SThierry Escande dev_dbg(ec->dev, "EC features %08x %08x\n", 1155e011558SThierry Escande ec->features[0], ec->features[1]); 1165e011558SThierry Escande 1175e011558SThierry Escande kfree(msg); 1185e011558SThierry Escande } 1195e011558SThierry Escande 1205e011558SThierry Escande return ec->features[feature / 32] & EC_FEATURE_MASK_0(feature); 1215e011558SThierry Escande } 1225e011558SThierry Escande 1235e011558SThierry Escande /* Device file ops */ 1245e011558SThierry Escande static int ec_device_open(struct inode *inode, struct file *filp) 1255e011558SThierry Escande { 1265e011558SThierry Escande struct cros_ec_dev *ec = container_of(inode->i_cdev, 1275e011558SThierry Escande struct cros_ec_dev, cdev); 1285e011558SThierry Escande filp->private_data = ec; 1295e011558SThierry Escande nonseekable_open(inode, filp); 1305e011558SThierry Escande return 0; 1315e011558SThierry Escande } 1325e011558SThierry Escande 1335e011558SThierry Escande static int ec_device_release(struct inode *inode, struct file *filp) 1345e011558SThierry Escande { 1355e011558SThierry Escande return 0; 1365e011558SThierry Escande } 1375e011558SThierry Escande 1385e011558SThierry Escande static ssize_t ec_device_read(struct file *filp, char __user *buffer, 1395e011558SThierry Escande size_t length, loff_t *offset) 1405e011558SThierry Escande { 1415e011558SThierry Escande struct cros_ec_dev *ec = filp->private_data; 1425e011558SThierry Escande char msg[sizeof(struct ec_response_get_version) + 1435e011558SThierry Escande sizeof(CROS_EC_DEV_VERSION)]; 1445e011558SThierry Escande size_t count; 1455e011558SThierry Escande int ret; 1465e011558SThierry Escande 1475e011558SThierry Escande if (*offset != 0) 1485e011558SThierry Escande return 0; 1495e011558SThierry Escande 1505e011558SThierry Escande ret = ec_get_version(ec, msg, sizeof(msg)); 1515e011558SThierry Escande if (ret) 1525e011558SThierry Escande return ret; 1535e011558SThierry Escande 1545e011558SThierry Escande count = min(length, strlen(msg)); 1555e011558SThierry Escande 1565e011558SThierry Escande if (copy_to_user(buffer, msg, count)) 1575e011558SThierry Escande return -EFAULT; 1585e011558SThierry Escande 1595e011558SThierry Escande *offset = count; 1605e011558SThierry Escande return count; 1615e011558SThierry Escande } 1625e011558SThierry Escande 1635e011558SThierry Escande /* Ioctls */ 1645e011558SThierry Escande static long ec_device_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg) 1655e011558SThierry Escande { 1665e011558SThierry Escande long ret; 1675e011558SThierry Escande struct cros_ec_command u_cmd; 1685e011558SThierry Escande struct cros_ec_command *s_cmd; 1695e011558SThierry Escande 1705e011558SThierry Escande if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) 1715e011558SThierry Escande return -EFAULT; 1725e011558SThierry Escande 1735e011558SThierry Escande if ((u_cmd.outsize > EC_MAX_MSG_BYTES) || 1745e011558SThierry Escande (u_cmd.insize > EC_MAX_MSG_BYTES)) 1755e011558SThierry Escande return -EINVAL; 1765e011558SThierry Escande 1775e011558SThierry Escande s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize), 1785e011558SThierry Escande GFP_KERNEL); 1795e011558SThierry Escande if (!s_cmd) 1805e011558SThierry Escande return -ENOMEM; 1815e011558SThierry Escande 1825e011558SThierry Escande if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) { 1835e011558SThierry Escande ret = -EFAULT; 1845e011558SThierry Escande goto exit; 1855e011558SThierry Escande } 1865e011558SThierry Escande 1875e011558SThierry Escande if (u_cmd.outsize != s_cmd->outsize || 1885e011558SThierry Escande u_cmd.insize != s_cmd->insize) { 1895e011558SThierry Escande ret = -EINVAL; 1905e011558SThierry Escande goto exit; 1915e011558SThierry Escande } 1925e011558SThierry Escande 1935e011558SThierry Escande s_cmd->command += ec->cmd_offset; 1945e011558SThierry Escande ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd); 1955e011558SThierry Escande /* Only copy data to userland if data was received. */ 1965e011558SThierry Escande if (ret < 0) 1975e011558SThierry Escande goto exit; 1985e011558SThierry Escande 1995e011558SThierry Escande if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize)) 2005e011558SThierry Escande ret = -EFAULT; 2015e011558SThierry Escande exit: 2025e011558SThierry Escande kfree(s_cmd); 2035e011558SThierry Escande return ret; 2045e011558SThierry Escande } 2055e011558SThierry Escande 2065e011558SThierry Escande static long ec_device_ioctl_readmem(struct cros_ec_dev *ec, void __user *arg) 2075e011558SThierry Escande { 2085e011558SThierry Escande struct cros_ec_device *ec_dev = ec->ec_dev; 2095e011558SThierry Escande struct cros_ec_readmem s_mem = { }; 2105e011558SThierry Escande long num; 2115e011558SThierry Escande 2125e011558SThierry Escande /* Not every platform supports direct reads */ 2135e011558SThierry Escande if (!ec_dev->cmd_readmem) 2145e011558SThierry Escande return -ENOTTY; 2155e011558SThierry Escande 2165e011558SThierry Escande if (copy_from_user(&s_mem, arg, sizeof(s_mem))) 2175e011558SThierry Escande return -EFAULT; 2185e011558SThierry Escande 2195e011558SThierry Escande num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, 2205e011558SThierry Escande s_mem.buffer); 2215e011558SThierry Escande if (num <= 0) 2225e011558SThierry Escande return num; 2235e011558SThierry Escande 2245e011558SThierry Escande if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) 2255e011558SThierry Escande return -EFAULT; 2265e011558SThierry Escande 227c1778e58SGuenter Roeck return num; 2285e011558SThierry Escande } 2295e011558SThierry Escande 2305e011558SThierry Escande static long ec_device_ioctl(struct file *filp, unsigned int cmd, 2315e011558SThierry Escande unsigned long arg) 2325e011558SThierry Escande { 2335e011558SThierry Escande struct cros_ec_dev *ec = filp->private_data; 2345e011558SThierry Escande 2355e011558SThierry Escande if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) 2365e011558SThierry Escande return -ENOTTY; 2375e011558SThierry Escande 2385e011558SThierry Escande switch (cmd) { 2395e011558SThierry Escande case CROS_EC_DEV_IOCXCMD: 2405e011558SThierry Escande return ec_device_ioctl_xcmd(ec, (void __user *)arg); 2415e011558SThierry Escande case CROS_EC_DEV_IOCRDMEM: 2425e011558SThierry Escande return ec_device_ioctl_readmem(ec, (void __user *)arg); 2435e011558SThierry Escande } 2445e011558SThierry Escande 2455e011558SThierry Escande return -ENOTTY; 2465e011558SThierry Escande } 2475e011558SThierry Escande 2485e011558SThierry Escande /* Module initialization */ 2495e011558SThierry Escande static const struct file_operations fops = { 2505e011558SThierry Escande .open = ec_device_open, 2515e011558SThierry Escande .release = ec_device_release, 2525e011558SThierry Escande .read = ec_device_read, 2535e011558SThierry Escande .unlocked_ioctl = ec_device_ioctl, 2545e011558SThierry Escande #ifdef CONFIG_COMPAT 2555e011558SThierry Escande .compat_ioctl = ec_device_ioctl, 2565e011558SThierry Escande #endif 2575e011558SThierry Escande }; 2585e011558SThierry Escande 25948a2ca0eSEnric Balletbo i Serra static void cros_ec_class_release(struct device *dev) 26048a2ca0eSEnric Balletbo i Serra { 26148a2ca0eSEnric Balletbo i Serra kfree(to_cros_ec_dev(dev)); 26248a2ca0eSEnric Balletbo i Serra } 26348a2ca0eSEnric Balletbo i Serra 2645e011558SThierry Escande static void cros_ec_sensors_register(struct cros_ec_dev *ec) 2655e011558SThierry Escande { 2665e011558SThierry Escande /* 2675e011558SThierry Escande * Issue a command to get the number of sensor reported. 2685e011558SThierry Escande * Build an array of sensors driver and register them all. 2695e011558SThierry Escande */ 2705e011558SThierry Escande int ret, i, id, sensor_num; 2715e011558SThierry Escande struct mfd_cell *sensor_cells; 2725e011558SThierry Escande struct cros_ec_sensor_platform *sensor_platforms; 2735e011558SThierry Escande int sensor_type[MOTIONSENSE_TYPE_MAX]; 2745e011558SThierry Escande struct ec_params_motion_sense *params; 2755e011558SThierry Escande struct ec_response_motion_sense *resp; 2765e011558SThierry Escande struct cros_ec_command *msg; 2775e011558SThierry Escande 2785e011558SThierry Escande msg = kzalloc(sizeof(struct cros_ec_command) + 2795e011558SThierry Escande max(sizeof(*params), sizeof(*resp)), GFP_KERNEL); 2805e011558SThierry Escande if (msg == NULL) 2815e011558SThierry Escande return; 2825e011558SThierry Escande 2835e011558SThierry Escande msg->version = 2; 2845e011558SThierry Escande msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; 2855e011558SThierry Escande msg->outsize = sizeof(*params); 2865e011558SThierry Escande msg->insize = sizeof(*resp); 2875e011558SThierry Escande 2885e011558SThierry Escande params = (struct ec_params_motion_sense *)msg->data; 2895e011558SThierry Escande params->cmd = MOTIONSENSE_CMD_DUMP; 2905e011558SThierry Escande 2915e011558SThierry Escande ret = cros_ec_cmd_xfer(ec->ec_dev, msg); 2925e011558SThierry Escande if (ret < 0 || msg->result != EC_RES_SUCCESS) { 2935e011558SThierry Escande dev_warn(ec->dev, "cannot get EC sensor information: %d/%d\n", 2945e011558SThierry Escande ret, msg->result); 2955e011558SThierry Escande goto error; 2965e011558SThierry Escande } 2975e011558SThierry Escande 2985e011558SThierry Escande resp = (struct ec_response_motion_sense *)msg->data; 2995e011558SThierry Escande sensor_num = resp->dump.sensor_count; 3001bb407f1SGwendal Grignou /* 3011bb407f1SGwendal Grignou * Allocate 2 extra sensors if lid angle sensor and/or FIFO are needed. 3021bb407f1SGwendal Grignou */ 3031bb407f1SGwendal Grignou sensor_cells = kcalloc(sensor_num + 2, sizeof(struct mfd_cell), 3045e011558SThierry Escande GFP_KERNEL); 3055e011558SThierry Escande if (sensor_cells == NULL) 3065e011558SThierry Escande goto error; 3075e011558SThierry Escande 3081bb407f1SGwendal Grignou sensor_platforms = kcalloc(sensor_num, 3096396bb22SKees Cook sizeof(struct cros_ec_sensor_platform), 3106396bb22SKees Cook GFP_KERNEL); 3115e011558SThierry Escande if (sensor_platforms == NULL) 3125e011558SThierry Escande goto error_platforms; 3135e011558SThierry Escande 3145e011558SThierry Escande memset(sensor_type, 0, sizeof(sensor_type)); 3155e011558SThierry Escande id = 0; 3165e011558SThierry Escande for (i = 0; i < sensor_num; i++) { 3175e011558SThierry Escande params->cmd = MOTIONSENSE_CMD_INFO; 3185e011558SThierry Escande params->info.sensor_num = i; 3195e011558SThierry Escande ret = cros_ec_cmd_xfer(ec->ec_dev, msg); 3205e011558SThierry Escande if (ret < 0 || msg->result != EC_RES_SUCCESS) { 3215e011558SThierry Escande dev_warn(ec->dev, "no info for EC sensor %d : %d/%d\n", 3225e011558SThierry Escande i, ret, msg->result); 3235e011558SThierry Escande continue; 3245e011558SThierry Escande } 3255e011558SThierry Escande switch (resp->info.type) { 3265e011558SThierry Escande case MOTIONSENSE_TYPE_ACCEL: 3275e011558SThierry Escande sensor_cells[id].name = "cros-ec-accel"; 3285e011558SThierry Escande break; 3295e011558SThierry Escande case MOTIONSENSE_TYPE_BARO: 3305e011558SThierry Escande sensor_cells[id].name = "cros-ec-baro"; 3315e011558SThierry Escande break; 3325e011558SThierry Escande case MOTIONSENSE_TYPE_GYRO: 3335e011558SThierry Escande sensor_cells[id].name = "cros-ec-gyro"; 3345e011558SThierry Escande break; 3355e011558SThierry Escande case MOTIONSENSE_TYPE_MAG: 3365e011558SThierry Escande sensor_cells[id].name = "cros-ec-mag"; 3375e011558SThierry Escande break; 3385e011558SThierry Escande case MOTIONSENSE_TYPE_PROX: 3395e011558SThierry Escande sensor_cells[id].name = "cros-ec-prox"; 3405e011558SThierry Escande break; 3415e011558SThierry Escande case MOTIONSENSE_TYPE_LIGHT: 3425e011558SThierry Escande sensor_cells[id].name = "cros-ec-light"; 3435e011558SThierry Escande break; 3445e011558SThierry Escande case MOTIONSENSE_TYPE_ACTIVITY: 3455e011558SThierry Escande sensor_cells[id].name = "cros-ec-activity"; 3465e011558SThierry Escande break; 3475e011558SThierry Escande default: 3485e011558SThierry Escande dev_warn(ec->dev, "unknown type %d\n", resp->info.type); 3495e011558SThierry Escande continue; 3505e011558SThierry Escande } 3515e011558SThierry Escande sensor_platforms[id].sensor_num = i; 3525e011558SThierry Escande sensor_cells[id].id = sensor_type[resp->info.type]; 3535e011558SThierry Escande sensor_cells[id].platform_data = &sensor_platforms[id]; 3545e011558SThierry Escande sensor_cells[id].pdata_size = 3555e011558SThierry Escande sizeof(struct cros_ec_sensor_platform); 3565e011558SThierry Escande 3575e011558SThierry Escande sensor_type[resp->info.type]++; 3585e011558SThierry Escande id++; 3595e011558SThierry Escande } 3605e011558SThierry Escande 361c1d1e91aSGwendal Grignou if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) 362c1d1e91aSGwendal Grignou ec->has_kb_wake_angle = true; 363c1d1e91aSGwendal Grignou 3645e011558SThierry Escande if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) { 3655e011558SThierry Escande sensor_cells[id].name = "cros-ec-ring"; 3665e011558SThierry Escande id++; 3675e011558SThierry Escande } 3681bb407f1SGwendal Grignou if (cros_ec_check_features(ec, 3691bb407f1SGwendal Grignou EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) { 3701bb407f1SGwendal Grignou sensor_cells[id].name = "cros-ec-lid-angle"; 3711bb407f1SGwendal Grignou id++; 3721bb407f1SGwendal Grignou } 3735e011558SThierry Escande 3745e011558SThierry Escande ret = mfd_add_devices(ec->dev, 0, sensor_cells, id, 3755e011558SThierry Escande NULL, 0, NULL); 3765e011558SThierry Escande if (ret) 3775e011558SThierry Escande dev_err(ec->dev, "failed to add EC sensors\n"); 3785e011558SThierry Escande 3795e011558SThierry Escande kfree(sensor_platforms); 3805e011558SThierry Escande error_platforms: 3815e011558SThierry Escande kfree(sensor_cells); 3825e011558SThierry Escande error: 3835e011558SThierry Escande kfree(msg); 3845e011558SThierry Escande } 3855e011558SThierry Escande 38603a5755cSNeil Armstrong static const struct mfd_cell cros_ec_cec_cells[] = { 38703a5755cSNeil Armstrong { .name = "cros-ec-cec" } 38803a5755cSNeil Armstrong }; 38903a5755cSNeil Armstrong 39095a4d07fSEnric Balletbo i Serra static const struct mfd_cell cros_ec_rtc_cells[] = { 39195a4d07fSEnric Balletbo i Serra { .name = "cros-ec-rtc" } 39295a4d07fSEnric Balletbo i Serra }; 39395a4d07fSEnric Balletbo i Serra 3943144dce7SEnric Balletbo i Serra static const struct mfd_cell cros_usbpd_charger_cells[] = { 39549a65e3cSEnric Balletbo i Serra { .name = "cros-usbpd-charger" }, 39649a65e3cSEnric Balletbo i Serra { .name = "cros-usbpd-logger" }, 3973144dce7SEnric Balletbo i Serra }; 3983144dce7SEnric Balletbo i Serra 399ecf8a6cdSEnric Balletbo i Serra static const struct mfd_cell cros_ec_platform_cells[] = { 4006fce0a2cSEnric Balletbo i Serra { .name = "cros-ec-debugfs" }, 401ecf8a6cdSEnric Balletbo i Serra { .name = "cros-ec-lightbar" }, 4026fd7f2bbSEnric Balletbo i Serra { .name = "cros-ec-sysfs" }, 4030545625bSEnric Balletbo i Serra }; 4040545625bSEnric Balletbo i Serra 4050545625bSEnric Balletbo i Serra static const struct mfd_cell cros_ec_vbc_cells[] = { 4060545625bSEnric Balletbo i Serra { .name = "cros-ec-vbc" } 407ecf8a6cdSEnric Balletbo i Serra }; 408ecf8a6cdSEnric Balletbo i Serra 4095e011558SThierry Escande static int ec_device_probe(struct platform_device *pdev) 4105e011558SThierry Escande { 4115e011558SThierry Escande int retval = -ENOMEM; 4120545625bSEnric Balletbo i Serra struct device_node *node; 4135e011558SThierry Escande struct device *dev = &pdev->dev; 4145e011558SThierry Escande struct cros_ec_platform *ec_platform = dev_get_platdata(dev); 41548a2ca0eSEnric Balletbo i Serra struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL); 4165e011558SThierry Escande 4175e011558SThierry Escande if (!ec) 4185e011558SThierry Escande return retval; 4195e011558SThierry Escande 4205e011558SThierry Escande dev_set_drvdata(dev, ec); 4215e011558SThierry Escande ec->ec_dev = dev_get_drvdata(dev->parent); 4225e011558SThierry Escande ec->dev = dev; 4235e011558SThierry Escande ec->cmd_offset = ec_platform->cmd_offset; 4245e011558SThierry Escande ec->features[0] = -1U; /* Not cached yet */ 4255e011558SThierry Escande ec->features[1] = -1U; /* Not cached yet */ 4265e011558SThierry Escande device_initialize(&ec->class_dev); 4275e011558SThierry Escande cdev_init(&ec->cdev, &fops); 4285e011558SThierry Escande 42990486af5SEnric Balletbo i Serra /* Check whether this is actually a Fingerprint MCU rather than an EC */ 43090486af5SEnric Balletbo i Serra if (cros_ec_check_features(ec, EC_FEATURE_FINGERPRINT)) { 43190486af5SEnric Balletbo i Serra dev_info(dev, "CrOS Fingerprint MCU detected.\n"); 43290486af5SEnric Balletbo i Serra /* 43390486af5SEnric Balletbo i Serra * Help userspace differentiating ECs from FP MCU, 43490486af5SEnric Balletbo i Serra * regardless of the probing order. 43590486af5SEnric Balletbo i Serra */ 43690486af5SEnric Balletbo i Serra ec_platform->ec_name = CROS_EC_DEV_FP_NAME; 43790486af5SEnric Balletbo i Serra } 43890486af5SEnric Balletbo i Serra 4395e011558SThierry Escande /* 440d4cee950SRushikesh S Kadam * Check whether this is actually an Integrated Sensor Hub (ISH) 441d4cee950SRushikesh S Kadam * rather than an EC. 442d4cee950SRushikesh S Kadam */ 443d4cee950SRushikesh S Kadam if (cros_ec_check_features(ec, EC_FEATURE_ISH)) { 444d4cee950SRushikesh S Kadam dev_info(dev, "CrOS ISH MCU detected.\n"); 445d4cee950SRushikesh S Kadam /* 446d4cee950SRushikesh S Kadam * Help userspace differentiating ECs from ISH MCU, 447d4cee950SRushikesh S Kadam * regardless of the probing order. 448d4cee950SRushikesh S Kadam */ 449d4cee950SRushikesh S Kadam ec_platform->ec_name = CROS_EC_DEV_ISH_NAME; 450d4cee950SRushikesh S Kadam } 451d4cee950SRushikesh S Kadam 4524f8f2bb7SEnric Balletbo i Serra /* Check whether this is actually a Touchpad MCU rather than an EC */ 4534f8f2bb7SEnric Balletbo i Serra if (cros_ec_check_features(ec, EC_FEATURE_TOUCHPAD)) { 4544f8f2bb7SEnric Balletbo i Serra dev_info(dev, "CrOS Touchpad MCU detected.\n"); 4554f8f2bb7SEnric Balletbo i Serra /* 4564f8f2bb7SEnric Balletbo i Serra * Help userspace differentiating ECs from TP MCU, 4574f8f2bb7SEnric Balletbo i Serra * regardless of the probing order. 4584f8f2bb7SEnric Balletbo i Serra */ 4594f8f2bb7SEnric Balletbo i Serra ec_platform->ec_name = CROS_EC_DEV_TP_NAME; 4604f8f2bb7SEnric Balletbo i Serra } 4614f8f2bb7SEnric Balletbo i Serra 462d4cee950SRushikesh S Kadam /* 4635e011558SThierry Escande * Add the class device 4645e011558SThierry Escande * Link to the character device for creating the /dev entry 4655e011558SThierry Escande * in devtmpfs. 4665e011558SThierry Escande */ 4675e011558SThierry Escande ec->class_dev.devt = MKDEV(ec_major, pdev->id); 4685e011558SThierry Escande ec->class_dev.class = &cros_class; 4695e011558SThierry Escande ec->class_dev.parent = dev; 47048a2ca0eSEnric Balletbo i Serra ec->class_dev.release = cros_ec_class_release; 4715e011558SThierry Escande 4725e011558SThierry Escande retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name); 4735e011558SThierry Escande if (retval) { 4745e011558SThierry Escande dev_err(dev, "dev_set_name failed => %d\n", retval); 4755e011558SThierry Escande goto failed; 4765e011558SThierry Escande } 4775e011558SThierry Escande 478c1d1e91aSGwendal Grignou /* check whether this EC is a sensor hub. */ 479c1d1e91aSGwendal Grignou if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE)) 480c1d1e91aSGwendal Grignou cros_ec_sensors_register(ec); 481c1d1e91aSGwendal Grignou 48203a5755cSNeil Armstrong /* Check whether this EC instance has CEC host command support */ 48303a5755cSNeil Armstrong if (cros_ec_check_features(ec, EC_FEATURE_CEC)) { 48403a5755cSNeil Armstrong retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO, 48503a5755cSNeil Armstrong cros_ec_cec_cells, 48603a5755cSNeil Armstrong ARRAY_SIZE(cros_ec_cec_cells), 48703a5755cSNeil Armstrong NULL, 0, NULL); 48803a5755cSNeil Armstrong if (retval) 48903a5755cSNeil Armstrong dev_err(ec->dev, 49003a5755cSNeil Armstrong "failed to add cros-ec-cec device: %d\n", 49103a5755cSNeil Armstrong retval); 49203a5755cSNeil Armstrong } 49303a5755cSNeil Armstrong 49495a4d07fSEnric Balletbo i Serra /* Check whether this EC instance has RTC host command support */ 49595a4d07fSEnric Balletbo i Serra if (cros_ec_check_features(ec, EC_FEATURE_RTC)) { 49695a4d07fSEnric Balletbo i Serra retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO, 49795a4d07fSEnric Balletbo i Serra cros_ec_rtc_cells, 49895a4d07fSEnric Balletbo i Serra ARRAY_SIZE(cros_ec_rtc_cells), 49995a4d07fSEnric Balletbo i Serra NULL, 0, NULL); 50095a4d07fSEnric Balletbo i Serra if (retval) 50195a4d07fSEnric Balletbo i Serra dev_err(ec->dev, 50295a4d07fSEnric Balletbo i Serra "failed to add cros-ec-rtc device: %d\n", 50395a4d07fSEnric Balletbo i Serra retval); 50495a4d07fSEnric Balletbo i Serra } 50595a4d07fSEnric Balletbo i Serra 5063144dce7SEnric Balletbo i Serra /* Check whether this EC instance has the PD charge manager */ 5073144dce7SEnric Balletbo i Serra if (cros_ec_check_features(ec, EC_FEATURE_USB_PD)) { 5083144dce7SEnric Balletbo i Serra retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO, 5093144dce7SEnric Balletbo i Serra cros_usbpd_charger_cells, 5103144dce7SEnric Balletbo i Serra ARRAY_SIZE(cros_usbpd_charger_cells), 5113144dce7SEnric Balletbo i Serra NULL, 0, NULL); 5123144dce7SEnric Balletbo i Serra if (retval) 5133144dce7SEnric Balletbo i Serra dev_err(ec->dev, 5143144dce7SEnric Balletbo i Serra "failed to add cros-usbpd-charger device: %d\n", 5153144dce7SEnric Balletbo i Serra retval); 5163144dce7SEnric Balletbo i Serra } 5173144dce7SEnric Balletbo i Serra 518c1d1e91aSGwendal Grignou /* We can now add the sysfs class, we know which parameter to show */ 5195e011558SThierry Escande retval = cdev_device_add(&ec->cdev, &ec->class_dev); 5205e011558SThierry Escande if (retval) { 5215e011558SThierry Escande dev_err(dev, "cdev_device_add failed => %d\n", retval); 5225e011558SThierry Escande goto failed; 5235e011558SThierry Escande } 5245e011558SThierry Escande 525ecf8a6cdSEnric Balletbo i Serra retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO, 526ecf8a6cdSEnric Balletbo i Serra cros_ec_platform_cells, 527ecf8a6cdSEnric Balletbo i Serra ARRAY_SIZE(cros_ec_platform_cells), 528ecf8a6cdSEnric Balletbo i Serra NULL, 0, NULL); 529ecf8a6cdSEnric Balletbo i Serra if (retval) 530ecf8a6cdSEnric Balletbo i Serra dev_warn(ec->dev, 531ecf8a6cdSEnric Balletbo i Serra "failed to add cros-ec platform devices: %d\n", 532ecf8a6cdSEnric Balletbo i Serra retval); 533ecf8a6cdSEnric Balletbo i Serra 5340545625bSEnric Balletbo i Serra /* Check whether this EC instance has a VBC NVRAM */ 5350545625bSEnric Balletbo i Serra node = ec->ec_dev->dev->of_node; 5360545625bSEnric Balletbo i Serra if (of_property_read_bool(node, "google,has-vbc-nvram")) { 5370545625bSEnric Balletbo i Serra retval = mfd_add_devices(ec->dev, PLATFORM_DEVID_AUTO, 5380545625bSEnric Balletbo i Serra cros_ec_vbc_cells, 5390545625bSEnric Balletbo i Serra ARRAY_SIZE(cros_ec_vbc_cells), 5400545625bSEnric Balletbo i Serra NULL, 0, NULL); 5410545625bSEnric Balletbo i Serra if (retval) 5420545625bSEnric Balletbo i Serra dev_warn(ec->dev, "failed to add VBC devices: %d\n", 5430545625bSEnric Balletbo i Serra retval); 5440545625bSEnric Balletbo i Serra } 5450545625bSEnric Balletbo i Serra 5465e011558SThierry Escande return 0; 5475e011558SThierry Escande 5485e011558SThierry Escande failed: 5495e011558SThierry Escande put_device(&ec->class_dev); 5505e011558SThierry Escande return retval; 5515e011558SThierry Escande } 5525e011558SThierry Escande 5535e011558SThierry Escande static int ec_device_remove(struct platform_device *pdev) 5545e011558SThierry Escande { 5555e011558SThierry Escande struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev); 5565e011558SThierry Escande 55718e294ddSEnric Balletbo i Serra mfd_remove_devices(ec->dev); 5585e011558SThierry Escande cdev_del(&ec->cdev); 5595e011558SThierry Escande device_unregister(&ec->class_dev); 5605e011558SThierry Escande return 0; 5615e011558SThierry Escande } 5625e011558SThierry Escande 5635e011558SThierry Escande static const struct platform_device_id cros_ec_id[] = { 5645e011558SThierry Escande { DRV_NAME, 0 }, 565abeed71bSWei-Ning Huang { /* sentinel */ } 5665e011558SThierry Escande }; 5675e011558SThierry Escande MODULE_DEVICE_TABLE(platform, cros_ec_id); 5685e011558SThierry Escande 5695e011558SThierry Escande static struct platform_driver cros_ec_dev_driver = { 5705e011558SThierry Escande .driver = { 5715e011558SThierry Escande .name = DRV_NAME, 5725e011558SThierry Escande }, 5736eb35784SNathan Chancellor .id_table = cros_ec_id, 5745e011558SThierry Escande .probe = ec_device_probe, 5755e011558SThierry Escande .remove = ec_device_remove, 5765e011558SThierry Escande }; 5775e011558SThierry Escande 5785e011558SThierry Escande static int __init cros_ec_dev_init(void) 5795e011558SThierry Escande { 5805e011558SThierry Escande int ret; 5815e011558SThierry Escande dev_t dev = 0; 5825e011558SThierry Escande 5835e011558SThierry Escande ret = class_register(&cros_class); 5845e011558SThierry Escande if (ret) { 5855e011558SThierry Escande pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); 5865e011558SThierry Escande return ret; 5875e011558SThierry Escande } 5885e011558SThierry Escande 5895e011558SThierry Escande /* Get a range of minor numbers (starting with 0) to work with */ 5905e011558SThierry Escande ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME); 5915e011558SThierry Escande if (ret < 0) { 5925e011558SThierry Escande pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n"); 5935e011558SThierry Escande goto failed_chrdevreg; 5945e011558SThierry Escande } 5955e011558SThierry Escande ec_major = MAJOR(dev); 5965e011558SThierry Escande 5975e011558SThierry Escande /* Register the driver */ 5985e011558SThierry Escande ret = platform_driver_register(&cros_ec_dev_driver); 5995e011558SThierry Escande if (ret < 0) { 6005e011558SThierry Escande pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); 6015e011558SThierry Escande goto failed_devreg; 6025e011558SThierry Escande } 6035e011558SThierry Escande return 0; 6045e011558SThierry Escande 6055e011558SThierry Escande failed_devreg: 6065e011558SThierry Escande unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); 6075e011558SThierry Escande failed_chrdevreg: 6085e011558SThierry Escande class_unregister(&cros_class); 6095e011558SThierry Escande return ret; 6105e011558SThierry Escande } 6115e011558SThierry Escande 6125e011558SThierry Escande static void __exit cros_ec_dev_exit(void) 6135e011558SThierry Escande { 6145e011558SThierry Escande platform_driver_unregister(&cros_ec_dev_driver); 6155e011558SThierry Escande unregister_chrdev(ec_major, CROS_EC_DEV_NAME); 6165e011558SThierry Escande class_unregister(&cros_class); 6175e011558SThierry Escande } 6185e011558SThierry Escande 6195e011558SThierry Escande module_init(cros_ec_dev_init); 6205e011558SThierry Escande module_exit(cros_ec_dev_exit); 6215e011558SThierry Escande 6225e011558SThierry Escande MODULE_ALIAS("platform:" DRV_NAME); 6235e011558SThierry Escande MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); 6245e011558SThierry Escande MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); 6255e011558SThierry Escande MODULE_VERSION("1.0"); 6265e011558SThierry Escande MODULE_LICENSE("GPL"); 627