18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Telemetry communication for Wilco EC
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2019 Google LLC
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * The Wilco Embedded Controller is able to send telemetry data
88c2ecf20Sopenharmony_ci * which is useful for enterprise applications. A daemon running on
98c2ecf20Sopenharmony_ci * the OS sends a command to the EC via a write() to a char device,
108c2ecf20Sopenharmony_ci * and can read the response with a read(). The write() request is
118c2ecf20Sopenharmony_ci * verified by the driver to ensure that it is performing only one
128c2ecf20Sopenharmony_ci * of the allowlisted commands, and that no extraneous data is
138c2ecf20Sopenharmony_ci * being transmitted to the EC. The response is passed directly
148c2ecf20Sopenharmony_ci * back to the reader with no modification.
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci * The character device will appear as /dev/wilco_telemN, where N
178c2ecf20Sopenharmony_ci * is some small non-negative integer, starting with 0. Only one
188c2ecf20Sopenharmony_ci * process may have the file descriptor open at a time. The calling
198c2ecf20Sopenharmony_ci * userspace program needs to keep the device file descriptor open
208c2ecf20Sopenharmony_ci * between the calls to write() and read() in order to preserve the
218c2ecf20Sopenharmony_ci * response. Up to 32 bytes will be available for reading.
228c2ecf20Sopenharmony_ci *
238c2ecf20Sopenharmony_ci * For testing purposes, try requesting the EC's firmware build
248c2ecf20Sopenharmony_ci * date, by sending the WILCO_EC_TELEM_GET_VERSION command with
258c2ecf20Sopenharmony_ci * argument index=3. i.e. write [0x38, 0x00, 0x03]
268c2ecf20Sopenharmony_ci * to the device node. An ASCII string of the build date is
278c2ecf20Sopenharmony_ci * returned.
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#include <linux/cdev.h>
318c2ecf20Sopenharmony_ci#include <linux/device.h>
328c2ecf20Sopenharmony_ci#include <linux/fs.h>
338c2ecf20Sopenharmony_ci#include <linux/module.h>
348c2ecf20Sopenharmony_ci#include <linux/platform_data/wilco-ec.h>
358c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
368c2ecf20Sopenharmony_ci#include <linux/slab.h>
378c2ecf20Sopenharmony_ci#include <linux/types.h>
388c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#define TELEM_DEV_NAME		"wilco_telem"
418c2ecf20Sopenharmony_ci#define TELEM_CLASS_NAME	TELEM_DEV_NAME
428c2ecf20Sopenharmony_ci#define DRV_NAME		TELEM_DEV_NAME
438c2ecf20Sopenharmony_ci#define TELEM_DEV_NAME_FMT	(TELEM_DEV_NAME "%d")
448c2ecf20Sopenharmony_cistatic struct class telem_class = {
458c2ecf20Sopenharmony_ci	.owner	= THIS_MODULE,
468c2ecf20Sopenharmony_ci	.name	= TELEM_CLASS_NAME,
478c2ecf20Sopenharmony_ci};
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci/* Keep track of all the device numbers used. */
508c2ecf20Sopenharmony_ci#define TELEM_MAX_DEV 128
518c2ecf20Sopenharmony_cistatic int telem_major;
528c2ecf20Sopenharmony_cistatic DEFINE_IDA(telem_ida);
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/* EC telemetry command codes */
558c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_LOG			0x99
568c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_VERSION		0x38
578c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_FAN_INFO		0x2E
588c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_DIAG_INFO		0xFA
598c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_TEMP_INFO		0x95
608c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_TEMP_READ		0x2C
618c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_BATT_EXT_INFO	0x07
628c2ecf20Sopenharmony_ci#define WILCO_EC_TELEM_GET_BATT_PPID_INFO	0x8A
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci#define TELEM_ARGS_SIZE_MAX	30
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci/*
678c2ecf20Sopenharmony_ci * The following telem_args_get_* structs are embedded within the |args| field
688c2ecf20Sopenharmony_ci * of wilco_ec_telem_request.
698c2ecf20Sopenharmony_ci */
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistruct telem_args_get_log {
728c2ecf20Sopenharmony_ci	u8 log_type;
738c2ecf20Sopenharmony_ci	u8 log_index;
748c2ecf20Sopenharmony_ci} __packed;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci/*
778c2ecf20Sopenharmony_ci * Get a piece of info about the EC firmware version:
788c2ecf20Sopenharmony_ci * 0 = label
798c2ecf20Sopenharmony_ci * 1 = svn_rev
808c2ecf20Sopenharmony_ci * 2 = model_no
818c2ecf20Sopenharmony_ci * 3 = build_date
828c2ecf20Sopenharmony_ci * 4 = frio_version
838c2ecf20Sopenharmony_ci */
848c2ecf20Sopenharmony_cistruct telem_args_get_version {
858c2ecf20Sopenharmony_ci	u8 index;
868c2ecf20Sopenharmony_ci} __packed;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_cistruct telem_args_get_fan_info {
898c2ecf20Sopenharmony_ci	u8 command;
908c2ecf20Sopenharmony_ci	u8 fan_number;
918c2ecf20Sopenharmony_ci	u8 arg;
928c2ecf20Sopenharmony_ci} __packed;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistruct telem_args_get_diag_info {
958c2ecf20Sopenharmony_ci	u8 type;
968c2ecf20Sopenharmony_ci	u8 sub_type;
978c2ecf20Sopenharmony_ci} __packed;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_cistruct telem_args_get_temp_info {
1008c2ecf20Sopenharmony_ci	u8 command;
1018c2ecf20Sopenharmony_ci	u8 index;
1028c2ecf20Sopenharmony_ci	u8 field;
1038c2ecf20Sopenharmony_ci	u8 zone;
1048c2ecf20Sopenharmony_ci} __packed;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistruct telem_args_get_temp_read {
1078c2ecf20Sopenharmony_ci	u8 sensor_index;
1088c2ecf20Sopenharmony_ci} __packed;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistruct telem_args_get_batt_ext_info {
1118c2ecf20Sopenharmony_ci	u8 var_args[5];
1128c2ecf20Sopenharmony_ci} __packed;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cistruct telem_args_get_batt_ppid_info {
1158c2ecf20Sopenharmony_ci	u8 always1; /* Should always be 1 */
1168c2ecf20Sopenharmony_ci} __packed;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci/**
1198c2ecf20Sopenharmony_ci * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC.
1208c2ecf20Sopenharmony_ci * @command: One of WILCO_EC_TELEM_GET_* command codes.
1218c2ecf20Sopenharmony_ci * @reserved: Must be 0.
1228c2ecf20Sopenharmony_ci * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0.
1238c2ecf20Sopenharmony_ci */
1248c2ecf20Sopenharmony_cistruct wilco_ec_telem_request {
1258c2ecf20Sopenharmony_ci	u8 command;
1268c2ecf20Sopenharmony_ci	u8 reserved;
1278c2ecf20Sopenharmony_ci	union {
1288c2ecf20Sopenharmony_ci		u8 buf[TELEM_ARGS_SIZE_MAX];
1298c2ecf20Sopenharmony_ci		struct telem_args_get_log		get_log;
1308c2ecf20Sopenharmony_ci		struct telem_args_get_version		get_version;
1318c2ecf20Sopenharmony_ci		struct telem_args_get_fan_info		get_fan_info;
1328c2ecf20Sopenharmony_ci		struct telem_args_get_diag_info		get_diag_info;
1338c2ecf20Sopenharmony_ci		struct telem_args_get_temp_info		get_temp_info;
1348c2ecf20Sopenharmony_ci		struct telem_args_get_temp_read		get_temp_read;
1358c2ecf20Sopenharmony_ci		struct telem_args_get_batt_ext_info	get_batt_ext_info;
1368c2ecf20Sopenharmony_ci		struct telem_args_get_batt_ppid_info	get_batt_ppid_info;
1378c2ecf20Sopenharmony_ci	} args;
1388c2ecf20Sopenharmony_ci} __packed;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci/**
1418c2ecf20Sopenharmony_ci * check_telem_request() - Ensure that a request from userspace is valid.
1428c2ecf20Sopenharmony_ci * @rq: Request buffer copied from userspace.
1438c2ecf20Sopenharmony_ci * @size: Number of bytes copied from userspace.
1448c2ecf20Sopenharmony_ci *
1458c2ecf20Sopenharmony_ci * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero,
1468c2ecf20Sopenharmony_ci *         -EMSGSIZE if the request is too long.
1478c2ecf20Sopenharmony_ci *
1488c2ecf20Sopenharmony_ci * We do not want to allow userspace to send arbitrary telemetry commands to
1498c2ecf20Sopenharmony_ci * the EC. Therefore we check to ensure that
1508c2ecf20Sopenharmony_ci * 1. The request follows the format of struct wilco_ec_telem_request.
1518c2ecf20Sopenharmony_ci * 2. The supplied command code is one of the allowlisted commands.
1528c2ecf20Sopenharmony_ci * 3. The request only contains the necessary data for the header and arguments.
1538c2ecf20Sopenharmony_ci */
1548c2ecf20Sopenharmony_cistatic int check_telem_request(struct wilco_ec_telem_request *rq,
1558c2ecf20Sopenharmony_ci			       size_t size)
1568c2ecf20Sopenharmony_ci{
1578c2ecf20Sopenharmony_ci	size_t max_size = offsetof(struct wilco_ec_telem_request, args);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	if (rq->reserved)
1608c2ecf20Sopenharmony_ci		return -EINVAL;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	switch (rq->command) {
1638c2ecf20Sopenharmony_ci	case WILCO_EC_TELEM_GET_LOG:
1648c2ecf20Sopenharmony_ci		max_size += sizeof(rq->args.get_log);
1658c2ecf20Sopenharmony_ci		break;
1668c2ecf20Sopenharmony_ci	case WILCO_EC_TELEM_GET_VERSION:
1678c2ecf20Sopenharmony_ci		max_size += sizeof(rq->args.get_version);
1688c2ecf20Sopenharmony_ci		break;
1698c2ecf20Sopenharmony_ci	case WILCO_EC_TELEM_GET_FAN_INFO:
1708c2ecf20Sopenharmony_ci		max_size += sizeof(rq->args.get_fan_info);
1718c2ecf20Sopenharmony_ci		break;
1728c2ecf20Sopenharmony_ci	case WILCO_EC_TELEM_GET_DIAG_INFO:
1738c2ecf20Sopenharmony_ci		max_size += sizeof(rq->args.get_diag_info);
1748c2ecf20Sopenharmony_ci		break;
1758c2ecf20Sopenharmony_ci	case WILCO_EC_TELEM_GET_TEMP_INFO:
1768c2ecf20Sopenharmony_ci		max_size += sizeof(rq->args.get_temp_info);
1778c2ecf20Sopenharmony_ci		break;
1788c2ecf20Sopenharmony_ci	case WILCO_EC_TELEM_GET_TEMP_READ:
1798c2ecf20Sopenharmony_ci		max_size += sizeof(rq->args.get_temp_read);
1808c2ecf20Sopenharmony_ci		break;
1818c2ecf20Sopenharmony_ci	case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
1828c2ecf20Sopenharmony_ci		max_size += sizeof(rq->args.get_batt_ext_info);
1838c2ecf20Sopenharmony_ci		break;
1848c2ecf20Sopenharmony_ci	case WILCO_EC_TELEM_GET_BATT_PPID_INFO:
1858c2ecf20Sopenharmony_ci		if (rq->args.get_batt_ppid_info.always1 != 1)
1868c2ecf20Sopenharmony_ci			return -EINVAL;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci		max_size += sizeof(rq->args.get_batt_ppid_info);
1898c2ecf20Sopenharmony_ci		break;
1908c2ecf20Sopenharmony_ci	default:
1918c2ecf20Sopenharmony_ci		return -EINVAL;
1928c2ecf20Sopenharmony_ci	}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	return (size <= max_size) ? 0 : -EMSGSIZE;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci/**
1988c2ecf20Sopenharmony_ci * struct telem_device_data - Data for a Wilco EC device that queries telemetry.
1998c2ecf20Sopenharmony_ci * @cdev: Char dev that userspace reads and polls from.
2008c2ecf20Sopenharmony_ci * @dev: Device associated with the %cdev.
2018c2ecf20Sopenharmony_ci * @ec: Wilco EC that we will be communicating with using the mailbox interface.
2028c2ecf20Sopenharmony_ci * @available: Boolean of if the device can be opened.
2038c2ecf20Sopenharmony_ci */
2048c2ecf20Sopenharmony_cistruct telem_device_data {
2058c2ecf20Sopenharmony_ci	struct device dev;
2068c2ecf20Sopenharmony_ci	struct cdev cdev;
2078c2ecf20Sopenharmony_ci	struct wilco_ec_device *ec;
2088c2ecf20Sopenharmony_ci	atomic_t available;
2098c2ecf20Sopenharmony_ci};
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci#define TELEM_RESPONSE_SIZE	EC_MAILBOX_DATA_SIZE
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci/**
2148c2ecf20Sopenharmony_ci * struct telem_session_data - Data that exists between open() and release().
2158c2ecf20Sopenharmony_ci * @dev_data: Pointer to get back to the device data and EC.
2168c2ecf20Sopenharmony_ci * @request: Command and arguments sent to EC.
2178c2ecf20Sopenharmony_ci * @response: Response buffer of data from EC.
2188c2ecf20Sopenharmony_ci * @has_msg: Is there data available to read from a previous write?
2198c2ecf20Sopenharmony_ci */
2208c2ecf20Sopenharmony_cistruct telem_session_data {
2218c2ecf20Sopenharmony_ci	struct telem_device_data *dev_data;
2228c2ecf20Sopenharmony_ci	struct wilco_ec_telem_request request;
2238c2ecf20Sopenharmony_ci	u8 response[TELEM_RESPONSE_SIZE];
2248c2ecf20Sopenharmony_ci	bool has_msg;
2258c2ecf20Sopenharmony_ci};
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci/**
2288c2ecf20Sopenharmony_ci * telem_open() - Callback for when the device node is opened.
2298c2ecf20Sopenharmony_ci * @inode: inode for this char device node.
2308c2ecf20Sopenharmony_ci * @filp: file for this char device node.
2318c2ecf20Sopenharmony_ci *
2328c2ecf20Sopenharmony_ci * We need to ensure that after writing a command to the device,
2338c2ecf20Sopenharmony_ci * the same userspace process reads the corresponding result.
2348c2ecf20Sopenharmony_ci * Therefore, we increment a refcount on opening the device, so that
2358c2ecf20Sopenharmony_ci * only one process can communicate with the EC at a time.
2368c2ecf20Sopenharmony_ci *
2378c2ecf20Sopenharmony_ci * Return: 0 on success, or negative error code on failure.
2388c2ecf20Sopenharmony_ci */
2398c2ecf20Sopenharmony_cistatic int telem_open(struct inode *inode, struct file *filp)
2408c2ecf20Sopenharmony_ci{
2418c2ecf20Sopenharmony_ci	struct telem_device_data *dev_data;
2428c2ecf20Sopenharmony_ci	struct telem_session_data *sess_data;
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	/* Ensure device isn't already open */
2458c2ecf20Sopenharmony_ci	dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev);
2468c2ecf20Sopenharmony_ci	if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0)
2478c2ecf20Sopenharmony_ci		return -EBUSY;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	get_device(&dev_data->dev);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL);
2528c2ecf20Sopenharmony_ci	if (!sess_data) {
2538c2ecf20Sopenharmony_ci		atomic_set(&dev_data->available, 1);
2548c2ecf20Sopenharmony_ci		return -ENOMEM;
2558c2ecf20Sopenharmony_ci	}
2568c2ecf20Sopenharmony_ci	sess_data->dev_data = dev_data;
2578c2ecf20Sopenharmony_ci	sess_data->has_msg = false;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	nonseekable_open(inode, filp);
2608c2ecf20Sopenharmony_ci	filp->private_data = sess_data;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	return 0;
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_cistatic ssize_t telem_write(struct file *filp, const char __user *buf,
2668c2ecf20Sopenharmony_ci			   size_t count, loff_t *pos)
2678c2ecf20Sopenharmony_ci{
2688c2ecf20Sopenharmony_ci	struct telem_session_data *sess_data = filp->private_data;
2698c2ecf20Sopenharmony_ci	struct wilco_ec_message msg = {};
2708c2ecf20Sopenharmony_ci	int ret;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	if (count > sizeof(sess_data->request))
2738c2ecf20Sopenharmony_ci		return -EMSGSIZE;
2748c2ecf20Sopenharmony_ci	memset(&sess_data->request, 0, sizeof(sess_data->request));
2758c2ecf20Sopenharmony_ci	if (copy_from_user(&sess_data->request, buf, count))
2768c2ecf20Sopenharmony_ci		return -EFAULT;
2778c2ecf20Sopenharmony_ci	ret = check_telem_request(&sess_data->request, count);
2788c2ecf20Sopenharmony_ci	if (ret < 0)
2798c2ecf20Sopenharmony_ci		return ret;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	memset(sess_data->response, 0, sizeof(sess_data->response));
2828c2ecf20Sopenharmony_ci	msg.type = WILCO_EC_MSG_TELEMETRY;
2838c2ecf20Sopenharmony_ci	msg.request_data = &sess_data->request;
2848c2ecf20Sopenharmony_ci	msg.request_size = sizeof(sess_data->request);
2858c2ecf20Sopenharmony_ci	msg.response_data = sess_data->response;
2868c2ecf20Sopenharmony_ci	msg.response_size = sizeof(sess_data->response);
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg);
2898c2ecf20Sopenharmony_ci	if (ret < 0)
2908c2ecf20Sopenharmony_ci		return ret;
2918c2ecf20Sopenharmony_ci	if (ret != sizeof(sess_data->response))
2928c2ecf20Sopenharmony_ci		return -EMSGSIZE;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	sess_data->has_msg = true;
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	return count;
2978c2ecf20Sopenharmony_ci}
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_cistatic ssize_t telem_read(struct file *filp, char __user *buf, size_t count,
3008c2ecf20Sopenharmony_ci			  loff_t *pos)
3018c2ecf20Sopenharmony_ci{
3028c2ecf20Sopenharmony_ci	struct telem_session_data *sess_data = filp->private_data;
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	if (!sess_data->has_msg)
3058c2ecf20Sopenharmony_ci		return -ENODATA;
3068c2ecf20Sopenharmony_ci	if (count > sizeof(sess_data->response))
3078c2ecf20Sopenharmony_ci		return -EINVAL;
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	if (copy_to_user(buf, sess_data->response, count))
3108c2ecf20Sopenharmony_ci		return -EFAULT;
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	sess_data->has_msg = false;
3138c2ecf20Sopenharmony_ci
3148c2ecf20Sopenharmony_ci	return count;
3158c2ecf20Sopenharmony_ci}
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_cistatic int telem_release(struct inode *inode, struct file *filp)
3188c2ecf20Sopenharmony_ci{
3198c2ecf20Sopenharmony_ci	struct telem_session_data *sess_data = filp->private_data;
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	atomic_set(&sess_data->dev_data->available, 1);
3228c2ecf20Sopenharmony_ci	put_device(&sess_data->dev_data->dev);
3238c2ecf20Sopenharmony_ci	kfree(sess_data);
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	return 0;
3268c2ecf20Sopenharmony_ci}
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_cistatic const struct file_operations telem_fops = {
3298c2ecf20Sopenharmony_ci	.open = telem_open,
3308c2ecf20Sopenharmony_ci	.write = telem_write,
3318c2ecf20Sopenharmony_ci	.read = telem_read,
3328c2ecf20Sopenharmony_ci	.release = telem_release,
3338c2ecf20Sopenharmony_ci	.llseek = no_llseek,
3348c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
3358c2ecf20Sopenharmony_ci};
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci/**
3388c2ecf20Sopenharmony_ci * telem_device_free() - Callback to free the telem_device_data structure.
3398c2ecf20Sopenharmony_ci * @d: The device embedded in our device data, which we have been ref counting.
3408c2ecf20Sopenharmony_ci *
3418c2ecf20Sopenharmony_ci * Once all open file descriptors are closed and the device has been removed,
3428c2ecf20Sopenharmony_ci * the refcount of the device will fall to 0 and this will be called.
3438c2ecf20Sopenharmony_ci */
3448c2ecf20Sopenharmony_cistatic void telem_device_free(struct device *d)
3458c2ecf20Sopenharmony_ci{
3468c2ecf20Sopenharmony_ci	struct telem_device_data *dev_data;
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ci	dev_data = container_of(d, struct telem_device_data, dev);
3498c2ecf20Sopenharmony_ci	kfree(dev_data);
3508c2ecf20Sopenharmony_ci}
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci/**
3538c2ecf20Sopenharmony_ci * telem_device_probe() - Callback when creating a new device.
3548c2ecf20Sopenharmony_ci * @pdev: platform device that we will be receiving telems from.
3558c2ecf20Sopenharmony_ci *
3568c2ecf20Sopenharmony_ci * This finds a free minor number for the device, allocates and initializes
3578c2ecf20Sopenharmony_ci * some device data, and creates a new device and char dev node.
3588c2ecf20Sopenharmony_ci *
3598c2ecf20Sopenharmony_ci * Return: 0 on success, negative error code on failure.
3608c2ecf20Sopenharmony_ci */
3618c2ecf20Sopenharmony_cistatic int telem_device_probe(struct platform_device *pdev)
3628c2ecf20Sopenharmony_ci{
3638c2ecf20Sopenharmony_ci	struct telem_device_data *dev_data;
3648c2ecf20Sopenharmony_ci	int error, minor;
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	/* Get the next available device number */
3678c2ecf20Sopenharmony_ci	minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL);
3688c2ecf20Sopenharmony_ci	if (minor < 0) {
3698c2ecf20Sopenharmony_ci		error = minor;
3708c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to find minor number: %d\n", error);
3718c2ecf20Sopenharmony_ci		return error;
3728c2ecf20Sopenharmony_ci	}
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_ci	dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
3758c2ecf20Sopenharmony_ci	if (!dev_data) {
3768c2ecf20Sopenharmony_ci		ida_simple_remove(&telem_ida, minor);
3778c2ecf20Sopenharmony_ci		return -ENOMEM;
3788c2ecf20Sopenharmony_ci	}
3798c2ecf20Sopenharmony_ci
3808c2ecf20Sopenharmony_ci	/* Initialize the device data */
3818c2ecf20Sopenharmony_ci	dev_data->ec = dev_get_platdata(&pdev->dev);
3828c2ecf20Sopenharmony_ci	atomic_set(&dev_data->available, 1);
3838c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, dev_data);
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	/* Initialize the device */
3868c2ecf20Sopenharmony_ci	dev_data->dev.devt = MKDEV(telem_major, minor);
3878c2ecf20Sopenharmony_ci	dev_data->dev.class = &telem_class;
3888c2ecf20Sopenharmony_ci	dev_data->dev.release = telem_device_free;
3898c2ecf20Sopenharmony_ci	dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor);
3908c2ecf20Sopenharmony_ci	device_initialize(&dev_data->dev);
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci	/* Initialize the character device and add it to userspace */;
3938c2ecf20Sopenharmony_ci	cdev_init(&dev_data->cdev, &telem_fops);
3948c2ecf20Sopenharmony_ci	error = cdev_device_add(&dev_data->cdev, &dev_data->dev);
3958c2ecf20Sopenharmony_ci	if (error) {
3968c2ecf20Sopenharmony_ci		put_device(&dev_data->dev);
3978c2ecf20Sopenharmony_ci		ida_simple_remove(&telem_ida, minor);
3988c2ecf20Sopenharmony_ci		return error;
3998c2ecf20Sopenharmony_ci	}
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	return 0;
4028c2ecf20Sopenharmony_ci}
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_cistatic int telem_device_remove(struct platform_device *pdev)
4058c2ecf20Sopenharmony_ci{
4068c2ecf20Sopenharmony_ci	struct telem_device_data *dev_data = platform_get_drvdata(pdev);
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci	cdev_device_del(&dev_data->cdev, &dev_data->dev);
4098c2ecf20Sopenharmony_ci	ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt));
4108c2ecf20Sopenharmony_ci	put_device(&dev_data->dev);
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	return 0;
4138c2ecf20Sopenharmony_ci}
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_cistatic struct platform_driver telem_driver = {
4168c2ecf20Sopenharmony_ci	.probe = telem_device_probe,
4178c2ecf20Sopenharmony_ci	.remove = telem_device_remove,
4188c2ecf20Sopenharmony_ci	.driver = {
4198c2ecf20Sopenharmony_ci		.name = DRV_NAME,
4208c2ecf20Sopenharmony_ci	},
4218c2ecf20Sopenharmony_ci};
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_cistatic int __init telem_module_init(void)
4248c2ecf20Sopenharmony_ci{
4258c2ecf20Sopenharmony_ci	dev_t dev_num = 0;
4268c2ecf20Sopenharmony_ci	int ret;
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci	ret = class_register(&telem_class);
4298c2ecf20Sopenharmony_ci	if (ret) {
4308c2ecf20Sopenharmony_ci		pr_err(DRV_NAME ": Failed registering class: %d\n", ret);
4318c2ecf20Sopenharmony_ci		return ret;
4328c2ecf20Sopenharmony_ci	}
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_ci	/* Request the kernel for device numbers, starting with minor=0 */
4358c2ecf20Sopenharmony_ci	ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME);
4368c2ecf20Sopenharmony_ci	if (ret) {
4378c2ecf20Sopenharmony_ci		pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret);
4388c2ecf20Sopenharmony_ci		goto destroy_class;
4398c2ecf20Sopenharmony_ci	}
4408c2ecf20Sopenharmony_ci	telem_major = MAJOR(dev_num);
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	ret = platform_driver_register(&telem_driver);
4438c2ecf20Sopenharmony_ci	if (ret < 0) {
4448c2ecf20Sopenharmony_ci		pr_err(DRV_NAME ": Failed registering driver: %d\n", ret);
4458c2ecf20Sopenharmony_ci		goto unregister_region;
4468c2ecf20Sopenharmony_ci	}
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	return 0;
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ciunregister_region:
4518c2ecf20Sopenharmony_ci	unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
4528c2ecf20Sopenharmony_cidestroy_class:
4538c2ecf20Sopenharmony_ci	class_unregister(&telem_class);
4548c2ecf20Sopenharmony_ci	ida_destroy(&telem_ida);
4558c2ecf20Sopenharmony_ci	return ret;
4568c2ecf20Sopenharmony_ci}
4578c2ecf20Sopenharmony_ci
4588c2ecf20Sopenharmony_cistatic void __exit telem_module_exit(void)
4598c2ecf20Sopenharmony_ci{
4608c2ecf20Sopenharmony_ci	platform_driver_unregister(&telem_driver);
4618c2ecf20Sopenharmony_ci	unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV);
4628c2ecf20Sopenharmony_ci	class_unregister(&telem_class);
4638c2ecf20Sopenharmony_ci	ida_destroy(&telem_ida);
4648c2ecf20Sopenharmony_ci}
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_cimodule_init(telem_module_init);
4678c2ecf20Sopenharmony_cimodule_exit(telem_module_exit);
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ciMODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
4708c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Wilco EC telemetry driver");
4718c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
4728c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME);
473