xref: /openbmc/linux/drivers/platform/chrome/wilco_ec/telemetry.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
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