brintos

brintos / linux-shallow public Read only

0
0
Text · 7.7 KiB · 39d3b50 Raw
310 lines · c
1// SPDX-License-Identifier: GPL-2.02//3// Copyright 2018 Google LLC.4 5#include <linux/completion.h>6#include <linux/delay.h>7#include <linux/kernel.h>8#include <linux/module.h>9#include <linux/of.h>10#include <linux/platform_data/cros_ec_commands.h>11#include <linux/platform_data/cros_ec_proto.h>12#include <linux/platform_device.h>13#include <linux/rpmsg.h>14#include <linux/slab.h>15 16#include "cros_ec.h"17 18#define EC_MSG_TIMEOUT_MS	20019#define HOST_COMMAND_MARK	120#define HOST_EVENT_MARK		221 22/**23 * struct cros_ec_rpmsg_response - rpmsg message format from from EC.24 *25 * @type:	The type of message, should be either HOST_COMMAND_MARK or26 *		HOST_EVENT_MARK, representing that the message is a response to27 *		host command, or a host event.28 * @data:	ec_host_response for host command.29 */30struct cros_ec_rpmsg_response {31	u8 type;32	u8 data[] __aligned(4);33};34 35/**36 * struct cros_ec_rpmsg - information about a EC over rpmsg.37 *38 * @rpdev:	rpmsg device we are connected to39 * @xfer_ack:	completion for host command transfer.40 * @host_event_work:	Work struct for pending host event.41 * @ept: The rpmsg endpoint of this channel.42 * @has_pending_host_event: Boolean used to check if there is a pending event.43 * @probe_done: Flag to indicate that probe is done.44 */45struct cros_ec_rpmsg {46	struct rpmsg_device *rpdev;47	struct completion xfer_ack;48	struct work_struct host_event_work;49	struct rpmsg_endpoint *ept;50	bool has_pending_host_event;51	bool probe_done;52};53 54/**55 * cros_ec_cmd_xfer_rpmsg - Transfer a message over rpmsg and receive the reply56 *57 * @ec_dev: ChromeOS EC device58 * @ec_msg: Message to transfer59 *60 * This is only used for old EC proto version, and is not supported for this61 * driver.62 *63 * Return: -EINVAL64 */65static int cros_ec_cmd_xfer_rpmsg(struct cros_ec_device *ec_dev,66				  struct cros_ec_command *ec_msg)67{68	return -EINVAL;69}70 71/**72 * cros_ec_pkt_xfer_rpmsg - Transfer a packet over rpmsg and receive the reply73 *74 * @ec_dev: ChromeOS EC device75 * @ec_msg: Message to transfer76 *77 * Return: number of bytes of the reply on success or negative error code.78 */79static int cros_ec_pkt_xfer_rpmsg(struct cros_ec_device *ec_dev,80				  struct cros_ec_command *ec_msg)81{82	struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv;83	struct ec_host_response *response;84	unsigned long timeout;85	int len;86	int ret;87	u8 sum;88	int i;89 90	ec_msg->result = 0;91	len = cros_ec_prepare_tx(ec_dev, ec_msg);92	if (len < 0)93		return len;94	dev_dbg(ec_dev->dev, "prepared, len=%d\n", len);95 96	reinit_completion(&ec_rpmsg->xfer_ack);97	ret = rpmsg_send(ec_rpmsg->ept, ec_dev->dout, len);98	if (ret) {99		dev_err(ec_dev->dev, "rpmsg send failed\n");100		return ret;101	}102 103	timeout = msecs_to_jiffies(EC_MSG_TIMEOUT_MS);104	ret = wait_for_completion_timeout(&ec_rpmsg->xfer_ack, timeout);105	if (!ret) {106		dev_err(ec_dev->dev, "rpmsg send timeout\n");107		return -EIO;108	}109 110	/* check response error code */111	response = (struct ec_host_response *)ec_dev->din;112	ec_msg->result = response->result;113 114	ret = cros_ec_check_result(ec_dev, ec_msg);115	if (ret)116		goto exit;117 118	if (response->data_len > ec_msg->insize) {119		dev_err(ec_dev->dev, "packet too long (%d bytes, expected %d)",120			response->data_len, ec_msg->insize);121		ret = -EMSGSIZE;122		goto exit;123	}124 125	/* copy response packet payload and compute checksum */126	memcpy(ec_msg->data, ec_dev->din + sizeof(*response),127	       response->data_len);128 129	sum = 0;130	for (i = 0; i < sizeof(*response) + response->data_len; i++)131		sum += ec_dev->din[i];132 133	if (sum) {134		dev_err(ec_dev->dev, "bad packet checksum, calculated %x\n",135			sum);136		ret = -EBADMSG;137		goto exit;138	}139 140	ret = response->data_len;141exit:142	if (ec_msg->command == EC_CMD_REBOOT_EC)143		msleep(EC_REBOOT_DELAY_MS);144 145	return ret;146}147 148static void149cros_ec_rpmsg_host_event_function(struct work_struct *host_event_work)150{151	struct cros_ec_rpmsg *ec_rpmsg = container_of(host_event_work,152						      struct cros_ec_rpmsg,153						      host_event_work);154 155	cros_ec_irq_thread(0, dev_get_drvdata(&ec_rpmsg->rpdev->dev));156}157 158static int cros_ec_rpmsg_callback(struct rpmsg_device *rpdev, void *data,159				  int len, void *priv, u32 src)160{161	struct cros_ec_device *ec_dev = dev_get_drvdata(&rpdev->dev);162	struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv;163	struct cros_ec_rpmsg_response *resp;164 165	if (!len) {166		dev_warn(ec_dev->dev, "rpmsg received empty response");167		return -EINVAL;168	}169 170	resp = data;171	len -= offsetof(struct cros_ec_rpmsg_response, data);172	if (resp->type == HOST_COMMAND_MARK) {173		if (len > ec_dev->din_size) {174			dev_warn(ec_dev->dev,175				 "received length %d > din_size %d, truncating",176				 len, ec_dev->din_size);177			len = ec_dev->din_size;178		}179 180		memcpy(ec_dev->din, resp->data, len);181		complete(&ec_rpmsg->xfer_ack);182	} else if (resp->type == HOST_EVENT_MARK) {183		/*184		 * If the host event is sent before cros_ec_register is185		 * finished, queue the host event.186		 */187		if (ec_rpmsg->probe_done)188			schedule_work(&ec_rpmsg->host_event_work);189		else190			ec_rpmsg->has_pending_host_event = true;191	} else {192		dev_warn(ec_dev->dev, "rpmsg received invalid type = %d",193			 resp->type);194		return -EINVAL;195	}196 197	return 0;198}199 200static struct rpmsg_endpoint *201cros_ec_rpmsg_create_ept(struct rpmsg_device *rpdev)202{203	struct rpmsg_channel_info chinfo = {};204 205	strscpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);206	chinfo.src = rpdev->src;207	chinfo.dst = RPMSG_ADDR_ANY;208 209	return rpmsg_create_ept(rpdev, cros_ec_rpmsg_callback, NULL, chinfo);210}211 212static int cros_ec_rpmsg_probe(struct rpmsg_device *rpdev)213{214	struct device *dev = &rpdev->dev;215	struct cros_ec_rpmsg *ec_rpmsg;216	struct cros_ec_device *ec_dev;217	int ret;218 219	ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);220	if (!ec_dev)221		return -ENOMEM;222 223	ec_rpmsg = devm_kzalloc(dev, sizeof(*ec_rpmsg), GFP_KERNEL);224	if (!ec_rpmsg)225		return -ENOMEM;226 227	ec_dev->dev = dev;228	ec_dev->priv = ec_rpmsg;229	ec_dev->cmd_xfer = cros_ec_cmd_xfer_rpmsg;230	ec_dev->pkt_xfer = cros_ec_pkt_xfer_rpmsg;231	ec_dev->phys_name = dev_name(&rpdev->dev);232	ec_dev->din_size = sizeof(struct ec_host_response) +233			   sizeof(struct ec_response_get_protocol_info);234	ec_dev->dout_size = sizeof(struct ec_host_request);235	dev_set_drvdata(dev, ec_dev);236 237	ec_rpmsg->rpdev = rpdev;238	init_completion(&ec_rpmsg->xfer_ack);239	INIT_WORK(&ec_rpmsg->host_event_work,240		  cros_ec_rpmsg_host_event_function);241 242	ec_rpmsg->ept = cros_ec_rpmsg_create_ept(rpdev);243	if (!ec_rpmsg->ept)244		return -ENOMEM;245 246	ret = cros_ec_register(ec_dev);247	if (ret < 0) {248		rpmsg_destroy_ept(ec_rpmsg->ept);249		cancel_work_sync(&ec_rpmsg->host_event_work);250		return ret;251	}252 253	ec_rpmsg->probe_done = true;254 255	if (ec_rpmsg->has_pending_host_event)256		schedule_work(&ec_rpmsg->host_event_work);257 258	return 0;259}260 261static void cros_ec_rpmsg_remove(struct rpmsg_device *rpdev)262{263	struct cros_ec_device *ec_dev = dev_get_drvdata(&rpdev->dev);264	struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv;265 266	cros_ec_unregister(ec_dev);267	rpmsg_destroy_ept(ec_rpmsg->ept);268	cancel_work_sync(&ec_rpmsg->host_event_work);269}270 271#ifdef CONFIG_PM_SLEEP272static int cros_ec_rpmsg_suspend(struct device *dev)273{274	struct cros_ec_device *ec_dev = dev_get_drvdata(dev);275 276	return cros_ec_suspend(ec_dev);277}278 279static int cros_ec_rpmsg_resume(struct device *dev)280{281	struct cros_ec_device *ec_dev = dev_get_drvdata(dev);282 283	return cros_ec_resume(ec_dev);284}285#endif286 287static SIMPLE_DEV_PM_OPS(cros_ec_rpmsg_pm_ops, cros_ec_rpmsg_suspend,288			 cros_ec_rpmsg_resume);289 290static const struct of_device_id cros_ec_rpmsg_of_match[] = {291	{ .compatible = "google,cros-ec-rpmsg", },292	{ }293};294MODULE_DEVICE_TABLE(of, cros_ec_rpmsg_of_match);295 296static struct rpmsg_driver cros_ec_driver_rpmsg = {297	.drv = {298		.name   = "cros-ec-rpmsg",299		.of_match_table = cros_ec_rpmsg_of_match,300		.pm	= &cros_ec_rpmsg_pm_ops,301	},302	.probe		= cros_ec_rpmsg_probe,303	.remove		= cros_ec_rpmsg_remove,304};305 306module_rpmsg_driver(cros_ec_driver_rpmsg);307 308MODULE_LICENSE("GPL v2");309MODULE_DESCRIPTION("ChromeOS EC multi function device (rpmsg)");310