1/*
2 * ChromeOS EC multi-function device
3 *
4 * Copyright (C) 2012 Google, Inc
5 *
6 * This software is licensed under the terms of the GNU General Public
7 * License version 2, as published by the Free Software Foundation, and
8 * may be copied, distributed, and modified under those terms.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * The ChromeOS EC multi function device is used to mux all the requests
16 * to the EC device for its multiple features: keyboard controller,
17 * battery charging and regulator control, firmware update.
18 */
19
20#include <linux/interrupt.h>
21#include <linux/slab.h>
22#include <linux/module.h>
23#include <linux/mfd/core.h>
24#include <linux/mfd/cros_ec.h>
25#include <linux/mfd/cros_ec_commands.h>
26#include <linux/delay.h>
27
28#define EC_COMMAND_RETRIES	50
29
30int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
31		       struct cros_ec_command *msg)
32{
33	uint8_t *out;
34	int csum, i;
35
36	BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE);
37	out = ec_dev->dout;
38	out[0] = EC_CMD_VERSION0 + msg->version;
39	out[1] = msg->command;
40	out[2] = msg->outsize;
41	csum = out[0] + out[1] + out[2];
42	for (i = 0; i < msg->outsize; i++)
43		csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->outdata[i];
44	out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff);
45
46	return EC_MSG_TX_PROTO_BYTES + msg->outsize;
47}
48EXPORT_SYMBOL(cros_ec_prepare_tx);
49
50int cros_ec_check_result(struct cros_ec_device *ec_dev,
51			 struct cros_ec_command *msg)
52{
53	switch (msg->result) {
54	case EC_RES_SUCCESS:
55		return 0;
56	case EC_RES_IN_PROGRESS:
57		dev_dbg(ec_dev->dev, "command 0x%02x in progress\n",
58			msg->command);
59		return -EAGAIN;
60	default:
61		dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n",
62			msg->command, msg->result);
63		return 0;
64	}
65}
66EXPORT_SYMBOL(cros_ec_check_result);
67
68int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
69		     struct cros_ec_command *msg)
70{
71	int ret;
72
73	mutex_lock(&ec_dev->lock);
74	ret = ec_dev->cmd_xfer(ec_dev, msg);
75	if (msg->result == EC_RES_IN_PROGRESS) {
76		int i;
77		struct cros_ec_command status_msg = { };
78		struct ec_response_get_comms_status *status;
79
80		status_msg.command = EC_CMD_GET_COMMS_STATUS;
81		status_msg.insize = sizeof(*status);
82
83		/*
84		 * Query the EC's status until it's no longer busy or
85		 * we encounter an error.
86		 */
87		for (i = 0; i < EC_COMMAND_RETRIES; i++) {
88			usleep_range(10000, 11000);
89
90			ret = ec_dev->cmd_xfer(ec_dev, &status_msg);
91			if (ret < 0)
92				break;
93
94			msg->result = status_msg.result;
95			if (status_msg.result != EC_RES_SUCCESS)
96				break;
97
98			status = (struct ec_response_get_comms_status *)
99				 status_msg.indata;
100			if (!(status->flags & EC_COMMS_STATUS_PROCESSING))
101				break;
102		}
103	}
104	mutex_unlock(&ec_dev->lock);
105
106	return ret;
107}
108EXPORT_SYMBOL(cros_ec_cmd_xfer);
109
110static const struct mfd_cell cros_devs[] = {
111	{
112		.name = "cros-ec-keyb",
113		.id = 1,
114		.of_compatible = "google,cros-ec-keyb",
115	},
116	{
117		.name = "cros-ec-i2c-tunnel",
118		.id = 2,
119		.of_compatible = "google,cros-ec-i2c-tunnel",
120	},
121	{
122		.name = "cros-ec-ctl",
123		.id = 3,
124	},
125};
126
127int cros_ec_register(struct cros_ec_device *ec_dev)
128{
129	struct device *dev = ec_dev->dev;
130	int err = 0;
131
132	if (ec_dev->din_size) {
133		ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL);
134		if (!ec_dev->din)
135			return -ENOMEM;
136	}
137	if (ec_dev->dout_size) {
138		ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL);
139		if (!ec_dev->dout)
140			return -ENOMEM;
141	}
142
143	mutex_init(&ec_dev->lock);
144
145	err = mfd_add_devices(dev, 0, cros_devs,
146			      ARRAY_SIZE(cros_devs),
147			      NULL, ec_dev->irq, NULL);
148	if (err) {
149		dev_err(dev, "failed to add mfd devices\n");
150		return err;
151	}
152
153	dev_info(dev, "Chrome EC device registered\n");
154
155	return 0;
156}
157EXPORT_SYMBOL(cros_ec_register);
158
159int cros_ec_remove(struct cros_ec_device *ec_dev)
160{
161	mfd_remove_devices(ec_dev->dev);
162
163	return 0;
164}
165EXPORT_SYMBOL(cros_ec_remove);
166
167#ifdef CONFIG_PM_SLEEP
168int cros_ec_suspend(struct cros_ec_device *ec_dev)
169{
170	struct device *dev = ec_dev->dev;
171
172	if (device_may_wakeup(dev))
173		ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq);
174
175	disable_irq(ec_dev->irq);
176	ec_dev->was_wake_device = ec_dev->wake_enabled;
177
178	return 0;
179}
180EXPORT_SYMBOL(cros_ec_suspend);
181
182int cros_ec_resume(struct cros_ec_device *ec_dev)
183{
184	enable_irq(ec_dev->irq);
185
186	if (ec_dev->wake_enabled) {
187		disable_irq_wake(ec_dev->irq);
188		ec_dev->wake_enabled = 0;
189	}
190
191	return 0;
192}
193EXPORT_SYMBOL(cros_ec_resume);
194
195#endif
196
197MODULE_LICENSE("GPL");
198MODULE_DESCRIPTION("ChromeOS EC core driver");
199