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