11210d1e6SNick Crews // SPDX-License-Identifier: GPL-2.0
21210d1e6SNick Crews /*
31210d1e6SNick Crews * Telemetry communication for Wilco EC
41210d1e6SNick Crews *
51210d1e6SNick Crews * Copyright 2019 Google LLC
61210d1e6SNick Crews *
71210d1e6SNick Crews * The Wilco Embedded Controller is able to send telemetry data
81210d1e6SNick Crews * which is useful for enterprise applications. A daemon running on
91210d1e6SNick Crews * the OS sends a command to the EC via a write() to a char device,
101210d1e6SNick Crews * and can read the response with a read(). The write() request is
111210d1e6SNick Crews * verified by the driver to ensure that it is performing only one
123b81d8bdSNick Crews * of the allowlisted commands, and that no extraneous data is
131210d1e6SNick Crews * being transmitted to the EC. The response is passed directly
141210d1e6SNick Crews * back to the reader with no modification.
151210d1e6SNick Crews *
161210d1e6SNick Crews * The character device will appear as /dev/wilco_telemN, where N
171210d1e6SNick Crews * is some small non-negative integer, starting with 0. Only one
181210d1e6SNick Crews * process may have the file descriptor open at a time. The calling
191210d1e6SNick Crews * userspace program needs to keep the device file descriptor open
201210d1e6SNick Crews * between the calls to write() and read() in order to preserve the
211210d1e6SNick Crews * response. Up to 32 bytes will be available for reading.
221210d1e6SNick Crews *
231210d1e6SNick Crews * For testing purposes, try requesting the EC's firmware build
241210d1e6SNick Crews * date, by sending the WILCO_EC_TELEM_GET_VERSION command with
251210d1e6SNick Crews * argument index=3. i.e. write [0x38, 0x00, 0x03]
261210d1e6SNick Crews * to the device node. An ASCII string of the build date is
271210d1e6SNick Crews * returned.
281210d1e6SNick Crews */
291210d1e6SNick Crews
301210d1e6SNick Crews #include <linux/cdev.h>
311210d1e6SNick Crews #include <linux/device.h>
321210d1e6SNick Crews #include <linux/fs.h>
331210d1e6SNick Crews #include <linux/module.h>
341210d1e6SNick Crews #include <linux/platform_data/wilco-ec.h>
351210d1e6SNick Crews #include <linux/platform_device.h>
361210d1e6SNick Crews #include <linux/slab.h>
371210d1e6SNick Crews #include <linux/types.h>
381210d1e6SNick Crews #include <linux/uaccess.h>
391210d1e6SNick Crews
401210d1e6SNick Crews #define TELEM_DEV_NAME "wilco_telem"
411210d1e6SNick Crews #define TELEM_CLASS_NAME TELEM_DEV_NAME
421210d1e6SNick Crews #define DRV_NAME TELEM_DEV_NAME
431210d1e6SNick Crews #define TELEM_DEV_NAME_FMT (TELEM_DEV_NAME "%d")
441210d1e6SNick Crews static struct class telem_class = {
451210d1e6SNick Crews .name = TELEM_CLASS_NAME,
461210d1e6SNick Crews };
471210d1e6SNick Crews
481210d1e6SNick Crews /* Keep track of all the device numbers used. */
491210d1e6SNick Crews #define TELEM_MAX_DEV 128
501210d1e6SNick Crews static int telem_major;
511210d1e6SNick Crews static DEFINE_IDA(telem_ida);
521210d1e6SNick Crews
531210d1e6SNick Crews /* EC telemetry command codes */
541210d1e6SNick Crews #define WILCO_EC_TELEM_GET_LOG 0x99
551210d1e6SNick Crews #define WILCO_EC_TELEM_GET_VERSION 0x38
561210d1e6SNick Crews #define WILCO_EC_TELEM_GET_FAN_INFO 0x2E
571210d1e6SNick Crews #define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA
581210d1e6SNick Crews #define WILCO_EC_TELEM_GET_TEMP_INFO 0x95
591210d1e6SNick Crews #define WILCO_EC_TELEM_GET_TEMP_READ 0x2C
601210d1e6SNick Crews #define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x07
613b81d8bdSNick Crews #define WILCO_EC_TELEM_GET_BATT_PPID_INFO 0x8A
621210d1e6SNick Crews
631210d1e6SNick Crews #define TELEM_ARGS_SIZE_MAX 30
641210d1e6SNick Crews
651210d1e6SNick Crews /*
661210d1e6SNick Crews * The following telem_args_get_* structs are embedded within the |args| field
671210d1e6SNick Crews * of wilco_ec_telem_request.
681210d1e6SNick Crews */
691210d1e6SNick Crews
701210d1e6SNick Crews struct telem_args_get_log {
711210d1e6SNick Crews u8 log_type;
721210d1e6SNick Crews u8 log_index;
731210d1e6SNick Crews } __packed;
741210d1e6SNick Crews
751210d1e6SNick Crews /*
761210d1e6SNick Crews * Get a piece of info about the EC firmware version:
771210d1e6SNick Crews * 0 = label
781210d1e6SNick Crews * 1 = svn_rev
791210d1e6SNick Crews * 2 = model_no
801210d1e6SNick Crews * 3 = build_date
811210d1e6SNick Crews * 4 = frio_version
821210d1e6SNick Crews */
831210d1e6SNick Crews struct telem_args_get_version {
841210d1e6SNick Crews u8 index;
851210d1e6SNick Crews } __packed;
861210d1e6SNick Crews
871210d1e6SNick Crews struct telem_args_get_fan_info {
881210d1e6SNick Crews u8 command;
891210d1e6SNick Crews u8 fan_number;
901210d1e6SNick Crews u8 arg;
911210d1e6SNick Crews } __packed;
921210d1e6SNick Crews
931210d1e6SNick Crews struct telem_args_get_diag_info {
941210d1e6SNick Crews u8 type;
951210d1e6SNick Crews u8 sub_type;
961210d1e6SNick Crews } __packed;
971210d1e6SNick Crews
981210d1e6SNick Crews struct telem_args_get_temp_info {
991210d1e6SNick Crews u8 command;
1001210d1e6SNick Crews u8 index;
1011210d1e6SNick Crews u8 field;
1021210d1e6SNick Crews u8 zone;
1031210d1e6SNick Crews } __packed;
1041210d1e6SNick Crews
1051210d1e6SNick Crews struct telem_args_get_temp_read {
1061210d1e6SNick Crews u8 sensor_index;
1071210d1e6SNick Crews } __packed;
1081210d1e6SNick Crews
1091210d1e6SNick Crews struct telem_args_get_batt_ext_info {
1101210d1e6SNick Crews u8 var_args[5];
1111210d1e6SNick Crews } __packed;
1121210d1e6SNick Crews
1133b81d8bdSNick Crews struct telem_args_get_batt_ppid_info {
1143b81d8bdSNick Crews u8 always1; /* Should always be 1 */
1153b81d8bdSNick Crews } __packed;
1163b81d8bdSNick Crews
1173b81d8bdSNick Crews /**
1183b81d8bdSNick Crews * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
1193b81d8bdSNick Crews * @command: One of WILCO_EC_TELEM_GET_* command codes.
1203b81d8bdSNick Crews * @reserved: Must be 0.
1213b81d8bdSNick Crews * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
1223b81d8bdSNick Crews */
1233b81d8bdSNick Crews struct wilco_ec_telem_request {
1243b81d8bdSNick Crews u8 command;
1253b81d8bdSNick Crews u8 reserved;
1263b81d8bdSNick Crews union {
1273b81d8bdSNick Crews u8 buf[TELEM_ARGS_SIZE_MAX];
1283b81d8bdSNick Crews struct telem_args_get_log get_log;
1293b81d8bdSNick Crews struct telem_args_get_version get_version;
1303b81d8bdSNick Crews struct telem_args_get_fan_info get_fan_info;
1313b81d8bdSNick Crews struct telem_args_get_diag_info get_diag_info;
1323b81d8bdSNick Crews struct telem_args_get_temp_info get_temp_info;
1333b81d8bdSNick Crews struct telem_args_get_temp_read get_temp_read;
1343b81d8bdSNick Crews struct telem_args_get_batt_ext_info get_batt_ext_info;
1353b81d8bdSNick Crews struct telem_args_get_batt_ppid_info get_batt_ppid_info;
1363b81d8bdSNick Crews } args;
1373b81d8bdSNick Crews } __packed;
1383b81d8bdSNick Crews
1391210d1e6SNick Crews /**
1401210d1e6SNick Crews * check_telem_request() - Ensure that a request from userspace is valid.
1411210d1e6SNick Crews * @rq: Request buffer copied from userspace.
1421210d1e6SNick Crews * @size: Number of bytes copied from userspace.
1431210d1e6SNick Crews *
1441210d1e6SNick Crews * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,
1451210d1e6SNick Crews * -EMSGSIZE if the request is too long.
1461210d1e6SNick Crews *
1471210d1e6SNick Crews * We do not want to allow userspace to send arbitrary telemetry commands to
1481210d1e6SNick Crews * the EC. Therefore we check to ensure that
1491210d1e6SNick Crews * 1. The request follows the format of struct wilco_ec_telem_request.
1503b81d8bdSNick Crews * 2. The supplied command code is one of the allowlisted commands.
1511210d1e6SNick Crews * 3. The request only contains the necessary data for the header and arguments.
1521210d1e6SNick Crews */
check_telem_request(struct wilco_ec_telem_request * rq,size_t size)1531210d1e6SNick Crews static int check_telem_request(struct wilco_ec_telem_request *rq,
1541210d1e6SNick Crews size_t size)
1551210d1e6SNick Crews {
1561210d1e6SNick Crews size_t max_size = offsetof(struct wilco_ec_telem_request, args);
1571210d1e6SNick Crews
1581210d1e6SNick Crews if (rq->reserved)
1591210d1e6SNick Crews return -EINVAL;
1601210d1e6SNick Crews
1611210d1e6SNick Crews switch (rq->command) {
1621210d1e6SNick Crews case WILCO_EC_TELEM_GET_LOG:
1633b81d8bdSNick Crews max_size += sizeof(rq->args.get_log);
1641210d1e6SNick Crews break;
1651210d1e6SNick Crews case WILCO_EC_TELEM_GET_VERSION:
1663b81d8bdSNick Crews max_size += sizeof(rq->args.get_version);
1671210d1e6SNick Crews break;
1681210d1e6SNick Crews case WILCO_EC_TELEM_GET_FAN_INFO:
1693b81d8bdSNick Crews max_size += sizeof(rq->args.get_fan_info);
1701210d1e6SNick Crews break;
1711210d1e6SNick Crews case WILCO_EC_TELEM_GET_DIAG_INFO:
1723b81d8bdSNick Crews max_size += sizeof(rq->args.get_diag_info);
1731210d1e6SNick Crews break;
1741210d1e6SNick Crews case WILCO_EC_TELEM_GET_TEMP_INFO:
1753b81d8bdSNick Crews max_size += sizeof(rq->args.get_temp_info);
1761210d1e6SNick Crews break;
1771210d1e6SNick Crews case WILCO_EC_TELEM_GET_TEMP_READ:
1783b81d8bdSNick Crews max_size += sizeof(rq->args.get_temp_read);
1791210d1e6SNick Crews break;
1801210d1e6SNick Crews case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
1813b81d8bdSNick Crews max_size += sizeof(rq->args.get_batt_ext_info);
1823b81d8bdSNick Crews break;
1833b81d8bdSNick Crews case WILCO_EC_TELEM_GET_BATT_PPID_INFO:
1843b81d8bdSNick Crews if (rq->args.get_batt_ppid_info.always1 != 1)
1853b81d8bdSNick Crews return -EINVAL;
1863b81d8bdSNick Crews
1873b81d8bdSNick Crews max_size += sizeof(rq->args.get_batt_ppid_info);
1881210d1e6SNick Crews break;
1891210d1e6SNick Crews default:
1901210d1e6SNick Crews return -EINVAL;
1911210d1e6SNick Crews }
1921210d1e6SNick Crews
1931210d1e6SNick Crews return (size <= max_size) ? 0 : -EMSGSIZE;
1941210d1e6SNick Crews }
1951210d1e6SNick Crews
1961210d1e6SNick Crews /**
1971210d1e6SNick Crews * struct telem_device_data - Data for a Wilco EC device that queries telemetry.
1981210d1e6SNick Crews * @cdev: Char dev that userspace reads and polls from.
1991210d1e6SNick Crews * @dev: Device associated with the %cdev.
2001210d1e6SNick Crews * @ec: Wilco EC that we will be communicating with using the mailbox interface.
2011210d1e6SNick Crews * @available: Boolean of if the device can be opened.
2021210d1e6SNick Crews */
2031210d1e6SNick Crews struct telem_device_data {
2041210d1e6SNick Crews struct device dev;
2051210d1e6SNick Crews struct cdev cdev;
2061210d1e6SNick Crews struct wilco_ec_device *ec;
2071210d1e6SNick Crews atomic_t available;
2081210d1e6SNick Crews };
2091210d1e6SNick Crews
2101210d1e6SNick Crews #define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE
2111210d1e6SNick Crews
2121210d1e6SNick Crews /**
2131210d1e6SNick Crews * struct telem_session_data - Data that exists between open() and release().
2141210d1e6SNick Crews * @dev_data: Pointer to get back to the device data and EC.
2151210d1e6SNick Crews * @request: Command and arguments sent to EC.
2161210d1e6SNick Crews * @response: Response buffer of data from EC.
2171210d1e6SNick Crews * @has_msg: Is there data available to read from a previous write?
2181210d1e6SNick Crews */
2191210d1e6SNick Crews struct telem_session_data {
2201210d1e6SNick Crews struct telem_device_data *dev_data;
2211210d1e6SNick Crews struct wilco_ec_telem_request request;
2221210d1e6SNick Crews u8 response[TELEM_RESPONSE_SIZE];
2231210d1e6SNick Crews bool has_msg;
2241210d1e6SNick Crews };
2251210d1e6SNick Crews
2261210d1e6SNick Crews /**
2271210d1e6SNick Crews * telem_open() - Callback for when the device node is opened.
2281210d1e6SNick Crews * @inode: inode for this char device node.
2291210d1e6SNick Crews * @filp: file for this char device node.
2301210d1e6SNick Crews *
2311210d1e6SNick Crews * We need to ensure that after writing a command to the device,
2321210d1e6SNick Crews * the same userspace process reads the corresponding result.
2331210d1e6SNick Crews * Therefore, we increment a refcount on opening the device, so that
2341210d1e6SNick Crews * only one process can communicate with the EC at a time.
2351210d1e6SNick Crews *
2361210d1e6SNick Crews * Return: 0 on success, or negative error code on failure.
2371210d1e6SNick Crews */
telem_open(struct inode * inode,struct file * filp)2381210d1e6SNick Crews static int telem_open(struct inode *inode, struct file *filp)
2391210d1e6SNick Crews {
2401210d1e6SNick Crews struct telem_device_data *dev_data;
2411210d1e6SNick Crews struct telem_session_data *sess_data;
2421210d1e6SNick Crews
2431210d1e6SNick Crews /* Ensure device isn't already open */
2441210d1e6SNick Crews dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
2451210d1e6SNick Crews if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
2461210d1e6SNick Crews return -EBUSY;
2471210d1e6SNick Crews
2481210d1e6SNick Crews get_device(&dev_data->dev);
2491210d1e6SNick Crews
2501210d1e6SNick Crews sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);
2511210d1e6SNick Crews if (!sess_data) {
2521210d1e6SNick Crews atomic_set(&dev_data->available, 1);
2531210d1e6SNick Crews return -ENOMEM;
2541210d1e6SNick Crews }
2551210d1e6SNick Crews sess_data->dev_data = dev_data;
2561210d1e6SNick Crews sess_data->has_msg = false;
2571210d1e6SNick Crews
258*dbc334fbSYang Li stream_open(inode, filp);
2591210d1e6SNick Crews filp->private_data = sess_data;
2601210d1e6SNick Crews
2611210d1e6SNick Crews return 0;
2621210d1e6SNick Crews }
2631210d1e6SNick Crews
telem_write(struct file * filp,const char __user * buf,size_t count,loff_t * pos)2641210d1e6SNick Crews static ssize_t telem_write(struct file *filp, const char __user *buf,
2651210d1e6SNick Crews size_t count, loff_t *pos)
2661210d1e6SNick Crews {
2671210d1e6SNick Crews struct telem_session_data *sess_data = filp->private_data;
2681210d1e6SNick Crews struct wilco_ec_message msg = {};
2691210d1e6SNick Crews int ret;
2701210d1e6SNick Crews
2711210d1e6SNick Crews if (count > sizeof(sess_data->request))
2721210d1e6SNick Crews return -EMSGSIZE;
2733b81d8bdSNick Crews memset(&sess_data->request, 0, sizeof(sess_data->request));
2741210d1e6SNick Crews if (copy_from_user(&sess_data->request, buf, count))
2751210d1e6SNick Crews return -EFAULT;
2761210d1e6SNick Crews ret = check_telem_request(&sess_data->request, count);
2771210d1e6SNick Crews if (ret < 0)
2781210d1e6SNick Crews return ret;
2791210d1e6SNick Crews
2801210d1e6SNick Crews memset(sess_data->response, 0, sizeof(sess_data->response));
2811210d1e6SNick Crews msg.type = WILCO_EC_MSG_TELEMETRY;
2821210d1e6SNick Crews msg.request_data = &sess_data->request;
2831210d1e6SNick Crews msg.request_size = sizeof(sess_data->request);
2841210d1e6SNick Crews msg.response_data = sess_data->response;
2851210d1e6SNick Crews msg.response_size = sizeof(sess_data->response);
2861210d1e6SNick Crews
2871210d1e6SNick Crews ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
2881210d1e6SNick Crews if (ret < 0)
2891210d1e6SNick Crews return ret;
2901210d1e6SNick Crews if (ret != sizeof(sess_data->response))
2911210d1e6SNick Crews return -EMSGSIZE;
2921210d1e6SNick Crews
2931210d1e6SNick Crews sess_data->has_msg = true;
2941210d1e6SNick Crews
2951210d1e6SNick Crews return count;
2961210d1e6SNick Crews }
2971210d1e6SNick Crews
telem_read(struct file * filp,char __user * buf,size_t count,loff_t * pos)2981210d1e6SNick Crews static ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
2991210d1e6SNick Crews loff_t *pos)
3001210d1e6SNick Crews {
3011210d1e6SNick Crews struct telem_session_data *sess_data = filp->private_data;
3021210d1e6SNick Crews
3031210d1e6SNick Crews if (!sess_data->has_msg)
3041210d1e6SNick Crews return -ENODATA;
3051210d1e6SNick Crews if (count > sizeof(sess_data->response))
3061210d1e6SNick Crews return -EINVAL;
3071210d1e6SNick Crews
3081210d1e6SNick Crews if (copy_to_user(buf, sess_data->response, count))
3091210d1e6SNick Crews return -EFAULT;
3101210d1e6SNick Crews
3111210d1e6SNick Crews sess_data->has_msg = false;
3121210d1e6SNick Crews
3131210d1e6SNick Crews return count;
3141210d1e6SNick Crews }
3151210d1e6SNick Crews
telem_release(struct inode * inode,struct file * filp)3161210d1e6SNick Crews static int telem_release(struct inode *inode, struct file *filp)
3171210d1e6SNick Crews {
3181210d1e6SNick Crews struct telem_session_data *sess_data = filp->private_data;
3191210d1e6SNick Crews
3201210d1e6SNick Crews atomic_set(&sess_data->dev_data->available, 1);
3211210d1e6SNick Crews put_device(&sess_data->dev_data->dev);
3221210d1e6SNick Crews kfree(sess_data);
3231210d1e6SNick Crews
3241210d1e6SNick Crews return 0;
3251210d1e6SNick Crews }
3261210d1e6SNick Crews
3271210d1e6SNick Crews static const struct file_operations telem_fops = {
3281210d1e6SNick Crews .open = telem_open,
3291210d1e6SNick Crews .write = telem_write,
3301210d1e6SNick Crews .read = telem_read,
3311210d1e6SNick Crews .release = telem_release,
3321210d1e6SNick Crews .llseek = no_llseek,
3331210d1e6SNick Crews .owner = THIS_MODULE,
3341210d1e6SNick Crews };
3351210d1e6SNick Crews
3361210d1e6SNick Crews /**
3371210d1e6SNick Crews * telem_device_free() - Callback to free the telem_device_data structure.
3381210d1e6SNick Crews * @d: The device embedded in our device data, which we have been ref counting.
3391210d1e6SNick Crews *
3401210d1e6SNick Crews * Once all open file descriptors are closed and the device has been removed,
3411210d1e6SNick Crews * the refcount of the device will fall to 0 and this will be called.
3421210d1e6SNick Crews */
telem_device_free(struct device * d)3431210d1e6SNick Crews static void telem_device_free(struct device *d)
3441210d1e6SNick Crews {
3451210d1e6SNick Crews struct telem_device_data *dev_data;
3461210d1e6SNick Crews
3471210d1e6SNick Crews dev_data = container_of(d, struct telem_device_data, dev);
3481210d1e6SNick Crews kfree(dev_data);
3491210d1e6SNick Crews }
3501210d1e6SNick Crews
3511210d1e6SNick Crews /**
3521210d1e6SNick Crews * telem_device_probe() - Callback when creating a new device.
3531210d1e6SNick Crews * @pdev: platform device that we will be receiving telems from.
3541210d1e6SNick Crews *
3551210d1e6SNick Crews * This finds a free minor number for the device, allocates and initializes
3561210d1e6SNick Crews * some device data, and creates a new device and char dev node.
3571210d1e6SNick Crews *
3581210d1e6SNick Crews * Return: 0 on success, negative error code on failure.
3591210d1e6SNick Crews */
telem_device_probe(struct platform_device * pdev)3601210d1e6SNick Crews static int telem_device_probe(struct platform_device *pdev)
3611210d1e6SNick Crews {
3621210d1e6SNick Crews struct telem_device_data *dev_data;
3631210d1e6SNick Crews int error, minor;
3641210d1e6SNick Crews
3651210d1e6SNick Crews /* Get the next available device number */
3661210d1e6SNick Crews minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
3671210d1e6SNick Crews if (minor < 0) {
3681210d1e6SNick Crews error = minor;
369a532149cSStephen Boyd dev_err(&pdev->dev, "Failed to find minor number: %d\n", error);
3701210d1e6SNick Crews return error;
3711210d1e6SNick Crews }
3721210d1e6SNick Crews
3731210d1e6SNick Crews dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
3741210d1e6SNick Crews if (!dev_data) {
3751210d1e6SNick Crews ida_simple_remove(&telem_ida, minor);
3761210d1e6SNick Crews return -ENOMEM;
3771210d1e6SNick Crews }
3781210d1e6SNick Crews
3791210d1e6SNick Crews /* Initialize the device data */
3801210d1e6SNick Crews dev_data->ec = dev_get_platdata(&pdev->dev);
3811210d1e6SNick Crews atomic_set(&dev_data->available, 1);
3821210d1e6SNick Crews platform_set_drvdata(pdev, dev_data);
3831210d1e6SNick Crews
3841210d1e6SNick Crews /* Initialize the device */
3851210d1e6SNick Crews dev_data->dev.devt = MKDEV(telem_major, minor);
3861210d1e6SNick Crews dev_data->dev.class = &telem_class;
3871210d1e6SNick Crews dev_data->dev.release = telem_device_free;
3881210d1e6SNick Crews dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
3891210d1e6SNick Crews device_initialize(&dev_data->dev);
3901210d1e6SNick Crews
3911210d1e6SNick Crews /* Initialize the character device and add it to userspace */;
3921210d1e6SNick Crews cdev_init(&dev_data->cdev, &telem_fops);
3931210d1e6SNick Crews error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
3941210d1e6SNick Crews if (error) {
3951210d1e6SNick Crews put_device(&dev_data->dev);
3961210d1e6SNick Crews ida_simple_remove(&telem_ida, minor);
3971210d1e6SNick Crews return error;
3981210d1e6SNick Crews }
3991210d1e6SNick Crews
4001210d1e6SNick Crews return 0;
4011210d1e6SNick Crews }
4021210d1e6SNick Crews
telem_device_remove(struct platform_device * pdev)4031210d1e6SNick Crews static int telem_device_remove(struct platform_device *pdev)
4041210d1e6SNick Crews {
4051210d1e6SNick Crews struct telem_device_data *dev_data = platform_get_drvdata(pdev);
4061210d1e6SNick Crews
4071210d1e6SNick Crews cdev_device_del(&dev_data->cdev, &dev_data->dev);
4081210d1e6SNick Crews ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt));
409856a0a6eSWen Yang put_device(&dev_data->dev);
4101210d1e6SNick Crews
4111210d1e6SNick Crews return 0;
4121210d1e6SNick Crews }
4131210d1e6SNick Crews
4141210d1e6SNick Crews static struct platform_driver telem_driver = {
4151210d1e6SNick Crews .probe = telem_device_probe,
4161210d1e6SNick Crews .remove = telem_device_remove,
4171210d1e6SNick Crews .driver = {
4181210d1e6SNick Crews .name = DRV_NAME,
4191210d1e6SNick Crews },
4201210d1e6SNick Crews };
4211210d1e6SNick Crews
telem_module_init(void)4221210d1e6SNick Crews static int __init telem_module_init(void)
4231210d1e6SNick Crews {
4241210d1e6SNick Crews dev_t dev_num = 0;
4251210d1e6SNick Crews int ret;
4261210d1e6SNick Crews
4271210d1e6SNick Crews ret = class_register(&telem_class);
4281210d1e6SNick Crews if (ret) {
429a532149cSStephen Boyd pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
4301210d1e6SNick Crews return ret;
4311210d1e6SNick Crews }
4321210d1e6SNick Crews
4331210d1e6SNick Crews /* Request the kernel for device numbers, starting with minor=0 */
4341210d1e6SNick Crews ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
4351210d1e6SNick Crews if (ret) {
436a532149cSStephen Boyd pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
4371210d1e6SNick Crews goto destroy_class;
4381210d1e6SNick Crews }
4391210d1e6SNick Crews telem_major = MAJOR(dev_num);
4401210d1e6SNick Crews
4411210d1e6SNick Crews ret = platform_driver_register(&telem_driver);
4421210d1e6SNick Crews if (ret < 0) {
4431210d1e6SNick Crews pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
4441210d1e6SNick Crews goto unregister_region;
4451210d1e6SNick Crews }
4461210d1e6SNick Crews
4471210d1e6SNick Crews return 0;
4481210d1e6SNick Crews
4491210d1e6SNick Crews unregister_region:
4501210d1e6SNick Crews unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
4511210d1e6SNick Crews destroy_class:
4521210d1e6SNick Crews class_unregister(&telem_class);
4531210d1e6SNick Crews ida_destroy(&telem_ida);
4541210d1e6SNick Crews return ret;
4551210d1e6SNick Crews }
4561210d1e6SNick Crews
telem_module_exit(void)4571210d1e6SNick Crews static void __exit telem_module_exit(void)
4581210d1e6SNick Crews {
4591210d1e6SNick Crews platform_driver_unregister(&telem_driver);
4601210d1e6SNick Crews unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
4611210d1e6SNick Crews class_unregister(&telem_class);
4621210d1e6SNick Crews ida_destroy(&telem_ida);
4631210d1e6SNick Crews }
4641210d1e6SNick Crews
4651210d1e6SNick Crews module_init(telem_module_init);
4661210d1e6SNick Crews module_exit(telem_module_exit);
4671210d1e6SNick Crews
4681210d1e6SNick Crews MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
4691210d1e6SNick Crews MODULE_DESCRIPTION("Wilco EC telemetry driver");
4701210d1e6SNick Crews MODULE_LICENSE("GPL");
4711210d1e6SNick Crews MODULE_ALIAS("platform:" DRV_NAME);
472