162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Telemetry communication for Wilco EC
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2019 Google LLC
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * The Wilco Embedded Controller is able to send telemetry data
862306a36Sopenharmony_ci * which is useful for enterprise applications. A daemon running on
962306a36Sopenharmony_ci * the OS sends a command to the EC via a write() to a char device,
1062306a36Sopenharmony_ci * and can read the response with a read(). The write() request is
1162306a36Sopenharmony_ci * verified by the driver to ensure that it is performing only one
1262306a36Sopenharmony_ci * of the allowlisted commands, and that no extraneous data is
1362306a36Sopenharmony_ci * being transmitted to the EC. The response is passed directly
1462306a36Sopenharmony_ci * back to the reader with no modification.
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * The character device will appear as /dev/wilco_telemN, where N
1762306a36Sopenharmony_ci * is some small non-negative integer, starting with 0. Only one
1862306a36Sopenharmony_ci * process may have the file descriptor open at a time. The calling
1962306a36Sopenharmony_ci * userspace program needs to keep the device file descriptor open
2062306a36Sopenharmony_ci * between the calls to write() and read() in order to preserve the
2162306a36Sopenharmony_ci * response. Up to 32 bytes will be available for reading.
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci * For testing purposes, try requesting the EC's firmware build
2462306a36Sopenharmony_ci * date, by sending the WILCO_EC_TELEM_GET_VERSION command with
2562306a36Sopenharmony_ci * argument index=3. i.e. write [0x38, 0x00, 0x03]
2662306a36Sopenharmony_ci * to the device node. An ASCII string of the build date is
2762306a36Sopenharmony_ci * returned.
2862306a36Sopenharmony_ci */
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#include <linux/cdev.h>
3162306a36Sopenharmony_ci#include <linux/device.h>
3262306a36Sopenharmony_ci#include <linux/fs.h>
3362306a36Sopenharmony_ci#include <linux/module.h>
3462306a36Sopenharmony_ci#include <linux/platform_data/wilco-ec.h>
3562306a36Sopenharmony_ci#include <linux/platform_device.h>
3662306a36Sopenharmony_ci#include <linux/slab.h>
3762306a36Sopenharmony_ci#include <linux/types.h>
3862306a36Sopenharmony_ci#include <linux/uaccess.h>
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define TELEM_DEV_NAME		"wilco_telem"
4162306a36Sopenharmony_ci#define TELEM_CLASS_NAME	TELEM_DEV_NAME
4262306a36Sopenharmony_ci#define DRV_NAME		TELEM_DEV_NAME
4362306a36Sopenharmony_ci#define TELEM_DEV_NAME_FMT	(TELEM_DEV_NAME "%d")
4462306a36Sopenharmony_cistatic struct class telem_class = {
4562306a36Sopenharmony_ci	.name	= TELEM_CLASS_NAME,
4662306a36Sopenharmony_ci};
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/* Keep track of all the device numbers used. */
4962306a36Sopenharmony_ci#define TELEM_MAX_DEV 128
5062306a36Sopenharmony_cistatic int telem_major;
5162306a36Sopenharmony_cistatic DEFINE_IDA(telem_ida);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci/* EC telemetry command codes */
5462306a36Sopenharmony_ci#define WILCO_EC_TELEM_GET_LOG			0x99
5562306a36Sopenharmony_ci#define WILCO_EC_TELEM_GET_VERSION		0x38
5662306a36Sopenharmony_ci#define WILCO_EC_TELEM_GET_FAN_INFO		0x2E
5762306a36Sopenharmony_ci#define WILCO_EC_TELEM_GET_DIAG_INFO		0xFA
5862306a36Sopenharmony_ci#define WILCO_EC_TELEM_GET_TEMP_INFO		0x95
5962306a36Sopenharmony_ci#define WILCO_EC_TELEM_GET_TEMP_READ		0x2C
6062306a36Sopenharmony_ci#define WILCO_EC_TELEM_GET_BATT_EXT_INFO	0x07
6162306a36Sopenharmony_ci#define WILCO_EC_TELEM_GET_BATT_PPID_INFO	0x8A
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci#define TELEM_ARGS_SIZE_MAX	30
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci/*
6662306a36Sopenharmony_ci * The following telem_args_get_* structs are embedded within the |args| field
6762306a36Sopenharmony_ci * of wilco_ec_telem_request.
6862306a36Sopenharmony_ci */
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistruct telem_args_get_log {
7162306a36Sopenharmony_ci	u8 log_type;
7262306a36Sopenharmony_ci	u8 log_index;
7362306a36Sopenharmony_ci} __packed;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci/*
7662306a36Sopenharmony_ci * Get a piece of info about the EC firmware version:
7762306a36Sopenharmony_ci * 0 = label
7862306a36Sopenharmony_ci * 1 = svn_rev
7962306a36Sopenharmony_ci * 2 = model_no
8062306a36Sopenharmony_ci * 3 = build_date
8162306a36Sopenharmony_ci * 4 = frio_version
8262306a36Sopenharmony_ci */
8362306a36Sopenharmony_cistruct telem_args_get_version {
8462306a36Sopenharmony_ci	u8 index;
8562306a36Sopenharmony_ci} __packed;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistruct telem_args_get_fan_info {
8862306a36Sopenharmony_ci	u8 command;
8962306a36Sopenharmony_ci	u8 fan_number;
9062306a36Sopenharmony_ci	u8 arg;
9162306a36Sopenharmony_ci} __packed;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistruct telem_args_get_diag_info {
9462306a36Sopenharmony_ci	u8 type;
9562306a36Sopenharmony_ci	u8 sub_type;
9662306a36Sopenharmony_ci} __packed;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistruct telem_args_get_temp_info {
9962306a36Sopenharmony_ci	u8 command;
10062306a36Sopenharmony_ci	u8 index;
10162306a36Sopenharmony_ci	u8 field;
10262306a36Sopenharmony_ci	u8 zone;
10362306a36Sopenharmony_ci} __packed;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistruct telem_args_get_temp_read {
10662306a36Sopenharmony_ci	u8 sensor_index;
10762306a36Sopenharmony_ci} __packed;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistruct telem_args_get_batt_ext_info {
11062306a36Sopenharmony_ci	u8 var_args[5];
11162306a36Sopenharmony_ci} __packed;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistruct telem_args_get_batt_ppid_info {
11462306a36Sopenharmony_ci	u8 always1; /* Should always be 1 */
11562306a36Sopenharmony_ci} __packed;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci/**
11862306a36Sopenharmony_ci * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
11962306a36Sopenharmony_ci * @command: One of WILCO_EC_TELEM_GET_* command codes.
12062306a36Sopenharmony_ci * @reserved: Must be 0.
12162306a36Sopenharmony_ci * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
12262306a36Sopenharmony_ci */
12362306a36Sopenharmony_cistruct wilco_ec_telem_request {
12462306a36Sopenharmony_ci	u8 command;
12562306a36Sopenharmony_ci	u8 reserved;
12662306a36Sopenharmony_ci	union {
12762306a36Sopenharmony_ci		u8 buf[TELEM_ARGS_SIZE_MAX];
12862306a36Sopenharmony_ci		struct telem_args_get_log		get_log;
12962306a36Sopenharmony_ci		struct telem_args_get_version		get_version;
13062306a36Sopenharmony_ci		struct telem_args_get_fan_info		get_fan_info;
13162306a36Sopenharmony_ci		struct telem_args_get_diag_info		get_diag_info;
13262306a36Sopenharmony_ci		struct telem_args_get_temp_info		get_temp_info;
13362306a36Sopenharmony_ci		struct telem_args_get_temp_read		get_temp_read;
13462306a36Sopenharmony_ci		struct telem_args_get_batt_ext_info	get_batt_ext_info;
13562306a36Sopenharmony_ci		struct telem_args_get_batt_ppid_info	get_batt_ppid_info;
13662306a36Sopenharmony_ci	} args;
13762306a36Sopenharmony_ci} __packed;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci/**
14062306a36Sopenharmony_ci * check_telem_request() - Ensure that a request from userspace is valid.
14162306a36Sopenharmony_ci * @rq: Request buffer copied from userspace.
14262306a36Sopenharmony_ci * @size: Number of bytes copied from userspace.
14362306a36Sopenharmony_ci *
14462306a36Sopenharmony_ci * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,
14562306a36Sopenharmony_ci *         -EMSGSIZE if the request is too long.
14662306a36Sopenharmony_ci *
14762306a36Sopenharmony_ci * We do not want to allow userspace to send arbitrary telemetry commands to
14862306a36Sopenharmony_ci * the EC. Therefore we check to ensure that
14962306a36Sopenharmony_ci * 1. The request follows the format of struct wilco_ec_telem_request.
15062306a36Sopenharmony_ci * 2. The supplied command code is one of the allowlisted commands.
15162306a36Sopenharmony_ci * 3. The request only contains the necessary data for the header and arguments.
15262306a36Sopenharmony_ci */
15362306a36Sopenharmony_cistatic int check_telem_request(struct wilco_ec_telem_request *rq,
15462306a36Sopenharmony_ci			       size_t size)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	size_t max_size = offsetof(struct wilco_ec_telem_request, args);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (rq->reserved)
15962306a36Sopenharmony_ci		return -EINVAL;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	switch (rq->command) {
16262306a36Sopenharmony_ci	case WILCO_EC_TELEM_GET_LOG:
16362306a36Sopenharmony_ci		max_size += sizeof(rq->args.get_log);
16462306a36Sopenharmony_ci		break;
16562306a36Sopenharmony_ci	case WILCO_EC_TELEM_GET_VERSION:
16662306a36Sopenharmony_ci		max_size += sizeof(rq->args.get_version);
16762306a36Sopenharmony_ci		break;
16862306a36Sopenharmony_ci	case WILCO_EC_TELEM_GET_FAN_INFO:
16962306a36Sopenharmony_ci		max_size += sizeof(rq->args.get_fan_info);
17062306a36Sopenharmony_ci		break;
17162306a36Sopenharmony_ci	case WILCO_EC_TELEM_GET_DIAG_INFO:
17262306a36Sopenharmony_ci		max_size += sizeof(rq->args.get_diag_info);
17362306a36Sopenharmony_ci		break;
17462306a36Sopenharmony_ci	case WILCO_EC_TELEM_GET_TEMP_INFO:
17562306a36Sopenharmony_ci		max_size += sizeof(rq->args.get_temp_info);
17662306a36Sopenharmony_ci		break;
17762306a36Sopenharmony_ci	case WILCO_EC_TELEM_GET_TEMP_READ:
17862306a36Sopenharmony_ci		max_size += sizeof(rq->args.get_temp_read);
17962306a36Sopenharmony_ci		break;
18062306a36Sopenharmony_ci	case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
18162306a36Sopenharmony_ci		max_size += sizeof(rq->args.get_batt_ext_info);
18262306a36Sopenharmony_ci		break;
18362306a36Sopenharmony_ci	case WILCO_EC_TELEM_GET_BATT_PPID_INFO:
18462306a36Sopenharmony_ci		if (rq->args.get_batt_ppid_info.always1 != 1)
18562306a36Sopenharmony_ci			return -EINVAL;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci		max_size += sizeof(rq->args.get_batt_ppid_info);
18862306a36Sopenharmony_ci		break;
18962306a36Sopenharmony_ci	default:
19062306a36Sopenharmony_ci		return -EINVAL;
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	return (size <= max_size) ? 0 : -EMSGSIZE;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci/**
19762306a36Sopenharmony_ci * struct telem_device_data - Data for a Wilco EC device that queries telemetry.
19862306a36Sopenharmony_ci * @cdev: Char dev that userspace reads and polls from.
19962306a36Sopenharmony_ci * @dev: Device associated with the %cdev.
20062306a36Sopenharmony_ci * @ec: Wilco EC that we will be communicating with using the mailbox interface.
20162306a36Sopenharmony_ci * @available: Boolean of if the device can be opened.
20262306a36Sopenharmony_ci */
20362306a36Sopenharmony_cistruct telem_device_data {
20462306a36Sopenharmony_ci	struct device dev;
20562306a36Sopenharmony_ci	struct cdev cdev;
20662306a36Sopenharmony_ci	struct wilco_ec_device *ec;
20762306a36Sopenharmony_ci	atomic_t available;
20862306a36Sopenharmony_ci};
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci#define TELEM_RESPONSE_SIZE	EC_MAILBOX_DATA_SIZE
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci/**
21362306a36Sopenharmony_ci * struct telem_session_data - Data that exists between open() and release().
21462306a36Sopenharmony_ci * @dev_data: Pointer to get back to the device data and EC.
21562306a36Sopenharmony_ci * @request: Command and arguments sent to EC.
21662306a36Sopenharmony_ci * @response: Response buffer of data from EC.
21762306a36Sopenharmony_ci * @has_msg: Is there data available to read from a previous write?
21862306a36Sopenharmony_ci */
21962306a36Sopenharmony_cistruct telem_session_data {
22062306a36Sopenharmony_ci	struct telem_device_data *dev_data;
22162306a36Sopenharmony_ci	struct wilco_ec_telem_request request;
22262306a36Sopenharmony_ci	u8 response[TELEM_RESPONSE_SIZE];
22362306a36Sopenharmony_ci	bool has_msg;
22462306a36Sopenharmony_ci};
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci/**
22762306a36Sopenharmony_ci * telem_open() - Callback for when the device node is opened.
22862306a36Sopenharmony_ci * @inode: inode for this char device node.
22962306a36Sopenharmony_ci * @filp: file for this char device node.
23062306a36Sopenharmony_ci *
23162306a36Sopenharmony_ci * We need to ensure that after writing a command to the device,
23262306a36Sopenharmony_ci * the same userspace process reads the corresponding result.
23362306a36Sopenharmony_ci * Therefore, we increment a refcount on opening the device, so that
23462306a36Sopenharmony_ci * only one process can communicate with the EC at a time.
23562306a36Sopenharmony_ci *
23662306a36Sopenharmony_ci * Return: 0 on success, or negative error code on failure.
23762306a36Sopenharmony_ci */
23862306a36Sopenharmony_cistatic int telem_open(struct inode *inode, struct file *filp)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct telem_device_data *dev_data;
24162306a36Sopenharmony_ci	struct telem_session_data *sess_data;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	/* Ensure device isn't already open */
24462306a36Sopenharmony_ci	dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
24562306a36Sopenharmony_ci	if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
24662306a36Sopenharmony_ci		return -EBUSY;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	get_device(&dev_data->dev);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);
25162306a36Sopenharmony_ci	if (!sess_data) {
25262306a36Sopenharmony_ci		atomic_set(&dev_data->available, 1);
25362306a36Sopenharmony_ci		return -ENOMEM;
25462306a36Sopenharmony_ci	}
25562306a36Sopenharmony_ci	sess_data->dev_data = dev_data;
25662306a36Sopenharmony_ci	sess_data->has_msg = false;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	stream_open(inode, filp);
25962306a36Sopenharmony_ci	filp->private_data = sess_data;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	return 0;
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic ssize_t telem_write(struct file *filp, const char __user *buf,
26562306a36Sopenharmony_ci			   size_t count, loff_t *pos)
26662306a36Sopenharmony_ci{
26762306a36Sopenharmony_ci	struct telem_session_data *sess_data = filp->private_data;
26862306a36Sopenharmony_ci	struct wilco_ec_message msg = {};
26962306a36Sopenharmony_ci	int ret;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	if (count > sizeof(sess_data->request))
27262306a36Sopenharmony_ci		return -EMSGSIZE;
27362306a36Sopenharmony_ci	memset(&sess_data->request, 0, sizeof(sess_data->request));
27462306a36Sopenharmony_ci	if (copy_from_user(&sess_data->request, buf, count))
27562306a36Sopenharmony_ci		return -EFAULT;
27662306a36Sopenharmony_ci	ret = check_telem_request(&sess_data->request, count);
27762306a36Sopenharmony_ci	if (ret < 0)
27862306a36Sopenharmony_ci		return ret;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	memset(sess_data->response, 0, sizeof(sess_data->response));
28162306a36Sopenharmony_ci	msg.type = WILCO_EC_MSG_TELEMETRY;
28262306a36Sopenharmony_ci	msg.request_data = &sess_data->request;
28362306a36Sopenharmony_ci	msg.request_size = sizeof(sess_data->request);
28462306a36Sopenharmony_ci	msg.response_data = sess_data->response;
28562306a36Sopenharmony_ci	msg.response_size = sizeof(sess_data->response);
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
28862306a36Sopenharmony_ci	if (ret < 0)
28962306a36Sopenharmony_ci		return ret;
29062306a36Sopenharmony_ci	if (ret != sizeof(sess_data->response))
29162306a36Sopenharmony_ci		return -EMSGSIZE;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	sess_data->has_msg = true;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	return count;
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
29962306a36Sopenharmony_ci			  loff_t *pos)
30062306a36Sopenharmony_ci{
30162306a36Sopenharmony_ci	struct telem_session_data *sess_data = filp->private_data;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	if (!sess_data->has_msg)
30462306a36Sopenharmony_ci		return -ENODATA;
30562306a36Sopenharmony_ci	if (count > sizeof(sess_data->response))
30662306a36Sopenharmony_ci		return -EINVAL;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	if (copy_to_user(buf, sess_data->response, count))
30962306a36Sopenharmony_ci		return -EFAULT;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	sess_data->has_msg = false;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	return count;
31462306a36Sopenharmony_ci}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_cistatic int telem_release(struct inode *inode, struct file *filp)
31762306a36Sopenharmony_ci{
31862306a36Sopenharmony_ci	struct telem_session_data *sess_data = filp->private_data;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	atomic_set(&sess_data->dev_data->available, 1);
32162306a36Sopenharmony_ci	put_device(&sess_data->dev_data->dev);
32262306a36Sopenharmony_ci	kfree(sess_data);
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	return 0;
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cistatic const struct file_operations telem_fops = {
32862306a36Sopenharmony_ci	.open = telem_open,
32962306a36Sopenharmony_ci	.write = telem_write,
33062306a36Sopenharmony_ci	.read = telem_read,
33162306a36Sopenharmony_ci	.release = telem_release,
33262306a36Sopenharmony_ci	.llseek = no_llseek,
33362306a36Sopenharmony_ci	.owner = THIS_MODULE,
33462306a36Sopenharmony_ci};
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci/**
33762306a36Sopenharmony_ci * telem_device_free() - Callback to free the telem_device_data structure.
33862306a36Sopenharmony_ci * @d: The device embedded in our device data, which we have been ref counting.
33962306a36Sopenharmony_ci *
34062306a36Sopenharmony_ci * Once all open file descriptors are closed and the device has been removed,
34162306a36Sopenharmony_ci * the refcount of the device will fall to 0 and this will be called.
34262306a36Sopenharmony_ci */
34362306a36Sopenharmony_cistatic void telem_device_free(struct device *d)
34462306a36Sopenharmony_ci{
34562306a36Sopenharmony_ci	struct telem_device_data *dev_data;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	dev_data = container_of(d, struct telem_device_data, dev);
34862306a36Sopenharmony_ci	kfree(dev_data);
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci/**
35262306a36Sopenharmony_ci * telem_device_probe() - Callback when creating a new device.
35362306a36Sopenharmony_ci * @pdev: platform device that we will be receiving telems from.
35462306a36Sopenharmony_ci *
35562306a36Sopenharmony_ci * This finds a free minor number for the device, allocates and initializes
35662306a36Sopenharmony_ci * some device data, and creates a new device and char dev node.
35762306a36Sopenharmony_ci *
35862306a36Sopenharmony_ci * Return: 0 on success, negative error code on failure.
35962306a36Sopenharmony_ci */
36062306a36Sopenharmony_cistatic int telem_device_probe(struct platform_device *pdev)
36162306a36Sopenharmony_ci{
36262306a36Sopenharmony_ci	struct telem_device_data *dev_data;
36362306a36Sopenharmony_ci	int error, minor;
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	/* Get the next available device number */
36662306a36Sopenharmony_ci	minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
36762306a36Sopenharmony_ci	if (minor < 0) {
36862306a36Sopenharmony_ci		error = minor;
36962306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to find minor number: %d\n", error);
37062306a36Sopenharmony_ci		return error;
37162306a36Sopenharmony_ci	}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
37462306a36Sopenharmony_ci	if (!dev_data) {
37562306a36Sopenharmony_ci		ida_simple_remove(&telem_ida, minor);
37662306a36Sopenharmony_ci		return -ENOMEM;
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	/* Initialize the device data */
38062306a36Sopenharmony_ci	dev_data->ec = dev_get_platdata(&pdev->dev);
38162306a36Sopenharmony_ci	atomic_set(&dev_data->available, 1);
38262306a36Sopenharmony_ci	platform_set_drvdata(pdev, dev_data);
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	/* Initialize the device */
38562306a36Sopenharmony_ci	dev_data->dev.devt = MKDEV(telem_major, minor);
38662306a36Sopenharmony_ci	dev_data->dev.class = &telem_class;
38762306a36Sopenharmony_ci	dev_data->dev.release = telem_device_free;
38862306a36Sopenharmony_ci	dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
38962306a36Sopenharmony_ci	device_initialize(&dev_data->dev);
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	/* Initialize the character device and add it to userspace */;
39262306a36Sopenharmony_ci	cdev_init(&dev_data->cdev, &telem_fops);
39362306a36Sopenharmony_ci	error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
39462306a36Sopenharmony_ci	if (error) {
39562306a36Sopenharmony_ci		put_device(&dev_data->dev);
39662306a36Sopenharmony_ci		ida_simple_remove(&telem_ida, minor);
39762306a36Sopenharmony_ci		return error;
39862306a36Sopenharmony_ci	}
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	return 0;
40162306a36Sopenharmony_ci}
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_cistatic int telem_device_remove(struct platform_device *pdev)
40462306a36Sopenharmony_ci{
40562306a36Sopenharmony_ci	struct telem_device_data *dev_data = platform_get_drvdata(pdev);
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	cdev_device_del(&dev_data->cdev, &dev_data->dev);
40862306a36Sopenharmony_ci	ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt));
40962306a36Sopenharmony_ci	put_device(&dev_data->dev);
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	return 0;
41262306a36Sopenharmony_ci}
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_cistatic struct platform_driver telem_driver = {
41562306a36Sopenharmony_ci	.probe = telem_device_probe,
41662306a36Sopenharmony_ci	.remove = telem_device_remove,
41762306a36Sopenharmony_ci	.driver = {
41862306a36Sopenharmony_ci		.name = DRV_NAME,
41962306a36Sopenharmony_ci	},
42062306a36Sopenharmony_ci};
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_cistatic int __init telem_module_init(void)
42362306a36Sopenharmony_ci{
42462306a36Sopenharmony_ci	dev_t dev_num = 0;
42562306a36Sopenharmony_ci	int ret;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	ret = class_register(&telem_class);
42862306a36Sopenharmony_ci	if (ret) {
42962306a36Sopenharmony_ci		pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
43062306a36Sopenharmony_ci		return ret;
43162306a36Sopenharmony_ci	}
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	/* Request the kernel for device numbers, starting with minor=0 */
43462306a36Sopenharmony_ci	ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
43562306a36Sopenharmony_ci	if (ret) {
43662306a36Sopenharmony_ci		pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
43762306a36Sopenharmony_ci		goto destroy_class;
43862306a36Sopenharmony_ci	}
43962306a36Sopenharmony_ci	telem_major = MAJOR(dev_num);
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	ret = platform_driver_register(&telem_driver);
44262306a36Sopenharmony_ci	if (ret < 0) {
44362306a36Sopenharmony_ci		pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
44462306a36Sopenharmony_ci		goto unregister_region;
44562306a36Sopenharmony_ci	}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	return 0;
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ciunregister_region:
45062306a36Sopenharmony_ci	unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
45162306a36Sopenharmony_cidestroy_class:
45262306a36Sopenharmony_ci	class_unregister(&telem_class);
45362306a36Sopenharmony_ci	ida_destroy(&telem_ida);
45462306a36Sopenharmony_ci	return ret;
45562306a36Sopenharmony_ci}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_cistatic void __exit telem_module_exit(void)
45862306a36Sopenharmony_ci{
45962306a36Sopenharmony_ci	platform_driver_unregister(&telem_driver);
46062306a36Sopenharmony_ci	unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
46162306a36Sopenharmony_ci	class_unregister(&telem_class);
46262306a36Sopenharmony_ci	ida_destroy(&telem_ida);
46362306a36Sopenharmony_ci}
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_cimodule_init(telem_module_init);
46662306a36Sopenharmony_cimodule_exit(telem_module_exit);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ciMODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
46962306a36Sopenharmony_ciMODULE_DESCRIPTION("Wilco EC telemetry driver");
47062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
47162306a36Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME);
472