14ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass/*
24ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * ChromeOS EC multi-function device
34ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass *
44ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * Copyright (C) 2012 Google, Inc
54ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass *
64ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * This software is licensed under the terms of the GNU General Public
74ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * License version 2, as published by the Free Software Foundation, and
84ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * may be copied, distributed, and modified under those terms.
94ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass *
104ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * This program is distributed in the hope that it will be useful,
114ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * but WITHOUT ANY WARRANTY; without even the implied warranty of
124ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
134ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * GNU General Public License for more details.
144ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass *
154ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * The ChromeOS EC multi function device is used to mux all the requests
164ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * to the EC device for its multiple features: keyboard controller,
174ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass * battery charging and regulator control, firmware update.
184ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass */
194ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
204ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass#include <linux/interrupt.h>
214ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass#include <linux/slab.h>
225ebeaff5c5be9f21680b91aac77ee155d935f888Samuel Ortiz#include <linux/module.h>
234ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass#include <linux/mfd/core.h>
244ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass#include <linux/mfd/cros_ec.h>
254ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass#include <linux/mfd/cros_ec_commands.h>
26d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker#include <linux/delay.h>
27d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker
28d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker#define EC_COMMAND_RETRIES	50
294ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
304ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glassint cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
315d4773e27e8ab37491767a6ef99ffd7100fe6341Bill Richardson		       struct cros_ec_command *msg)
324ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass{
334ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	uint8_t *out;
344ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	int csum, i;
354ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
365d4773e27e8ab37491767a6ef99ffd7100fe6341Bill Richardson	BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE);
374ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	out = ec_dev->dout;
384ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	out[0] = EC_CMD_VERSION0 + msg->version;
395d4773e27e8ab37491767a6ef99ffd7100fe6341Bill Richardson	out[1] = msg->command;
405d4773e27e8ab37491767a6ef99ffd7100fe6341Bill Richardson	out[2] = msg->outsize;
414ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	csum = out[0] + out[1] + out[2];
425d4773e27e8ab37491767a6ef99ffd7100fe6341Bill Richardson	for (i = 0; i < msg->outsize; i++)
435d4773e27e8ab37491767a6ef99ffd7100fe6341Bill Richardson		csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->outdata[i];
445d4773e27e8ab37491767a6ef99ffd7100fe6341Bill Richardson	out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff);
454ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
465d4773e27e8ab37491767a6ef99ffd7100fe6341Bill Richardson	return EC_MSG_TX_PROTO_BYTES + msg->outsize;
474ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass}
485ebeaff5c5be9f21680b91aac77ee155d935f888Samuel OrtizEXPORT_SYMBOL(cros_ec_prepare_tx);
494ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
506db07b6336589ff480528173e41f8f6af3f0097fBill Richardsonint cros_ec_check_result(struct cros_ec_device *ec_dev,
516db07b6336589ff480528173e41f8f6af3f0097fBill Richardson			 struct cros_ec_command *msg)
526db07b6336589ff480528173e41f8f6af3f0097fBill Richardson{
536db07b6336589ff480528173e41f8f6af3f0097fBill Richardson	switch (msg->result) {
546db07b6336589ff480528173e41f8f6af3f0097fBill Richardson	case EC_RES_SUCCESS:
556db07b6336589ff480528173e41f8f6af3f0097fBill Richardson		return 0;
566db07b6336589ff480528173e41f8f6af3f0097fBill Richardson	case EC_RES_IN_PROGRESS:
576db07b6336589ff480528173e41f8f6af3f0097fBill Richardson		dev_dbg(ec_dev->dev, "command 0x%02x in progress\n",
586db07b6336589ff480528173e41f8f6af3f0097fBill Richardson			msg->command);
596db07b6336589ff480528173e41f8f6af3f0097fBill Richardson		return -EAGAIN;
606db07b6336589ff480528173e41f8f6af3f0097fBill Richardson	default:
616db07b6336589ff480528173e41f8f6af3f0097fBill Richardson		dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n",
626db07b6336589ff480528173e41f8f6af3f0097fBill Richardson			msg->command, msg->result);
636db07b6336589ff480528173e41f8f6af3f0097fBill Richardson		return 0;
646db07b6336589ff480528173e41f8f6af3f0097fBill Richardson	}
656db07b6336589ff480528173e41f8f6af3f0097fBill Richardson}
666db07b6336589ff480528173e41f8f6af3f0097fBill RichardsonEXPORT_SYMBOL(cros_ec_check_result);
676db07b6336589ff480528173e41f8f6af3f0097fBill Richardson
68a6551a76fff15056fde2342d0f7de41ee605264eAndrew Brestickerint cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
69a6551a76fff15056fde2342d0f7de41ee605264eAndrew Bresticker		     struct cros_ec_command *msg)
70a6551a76fff15056fde2342d0f7de41ee605264eAndrew Bresticker{
7163427530fa7a78b42a19f47fb0c12b303c0666caAndrew Bresticker	int ret;
7263427530fa7a78b42a19f47fb0c12b303c0666caAndrew Bresticker
7363427530fa7a78b42a19f47fb0c12b303c0666caAndrew Bresticker	mutex_lock(&ec_dev->lock);
7463427530fa7a78b42a19f47fb0c12b303c0666caAndrew Bresticker	ret = ec_dev->cmd_xfer(ec_dev, msg);
75d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker	if (msg->result == EC_RES_IN_PROGRESS) {
76d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		int i;
77d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		struct cros_ec_command status_msg;
78d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		struct ec_response_get_comms_status status;
79d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker
80d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		status_msg.version = 0;
81d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		status_msg.command = EC_CMD_GET_COMMS_STATUS;
82d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		status_msg.outdata = NULL;
83d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		status_msg.outsize = 0;
84d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		status_msg.indata = (uint8_t *)&status;
85d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		status_msg.insize = sizeof(status);
86d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker
87d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		/*
88d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		 * Query the EC's status until it's no longer busy or
89d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		 * we encounter an error.
90d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		 */
91d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		for (i = 0; i < EC_COMMAND_RETRIES; i++) {
92d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker			usleep_range(10000, 11000);
93d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker
94d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker			ret = ec_dev->cmd_xfer(ec_dev, &status_msg);
95d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker			if (ret < 0)
96d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker				break;
97d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker
98d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker			msg->result = status_msg.result;
99d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker			if (status_msg.result != EC_RES_SUCCESS)
100d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker				break;
101d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker			if (!(status.flags & EC_COMMS_STATUS_PROCESSING))
102d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker				break;
103d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker		}
104d86c21fd31114e3ef9fae64be335c76aa22859dcAndrew Bresticker	}
10563427530fa7a78b42a19f47fb0c12b303c0666caAndrew Bresticker	mutex_unlock(&ec_dev->lock);
10663427530fa7a78b42a19f47fb0c12b303c0666caAndrew Bresticker
10763427530fa7a78b42a19f47fb0c12b303c0666caAndrew Bresticker	return ret;
108a6551a76fff15056fde2342d0f7de41ee605264eAndrew Bresticker}
109a6551a76fff15056fde2342d0f7de41ee605264eAndrew BrestickerEXPORT_SYMBOL(cros_ec_cmd_xfer);
110a6551a76fff15056fde2342d0f7de41ee605264eAndrew Bresticker
1115ac98553afe41ffb5513fa8aac6df699a70231a3Geert Uytterhoevenstatic const struct mfd_cell cros_devs[] = {
1124ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	{
1134ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass		.name = "cros-ec-keyb",
1144ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass		.id = 1,
1154ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass		.of_compatible = "google,cros-ec-keyb",
1164ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	},
1179d230c9e4f4e67cb1c1cb9e0f6142da16b0f2796Doug Anderson	{
1189d230c9e4f4e67cb1c1cb9e0f6142da16b0f2796Doug Anderson		.name = "cros-ec-i2c-tunnel",
1199d230c9e4f4e67cb1c1cb9e0f6142da16b0f2796Doug Anderson		.id = 2,
1209d230c9e4f4e67cb1c1cb9e0f6142da16b0f2796Doug Anderson		.of_compatible = "google,cros-ec-i2c-tunnel",
1219d230c9e4f4e67cb1c1cb9e0f6142da16b0f2796Doug Anderson	},
1224ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass};
1234ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1244ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glassint cros_ec_register(struct cros_ec_device *ec_dev)
1254ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass{
1264ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	struct device *dev = ec_dev->dev;
1274ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	int err = 0;
1284ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1294ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	if (ec_dev->din_size) {
13022f9ee756bfd9ce9b066e2f90cf9f002aa7d1d44Lee Jones		ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL);
13122f9ee756bfd9ce9b066e2f90cf9f002aa7d1d44Lee Jones		if (!ec_dev->din)
13222f9ee756bfd9ce9b066e2f90cf9f002aa7d1d44Lee Jones			return -ENOMEM;
1334ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	}
1344ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	if (ec_dev->dout_size) {
13522f9ee756bfd9ce9b066e2f90cf9f002aa7d1d44Lee Jones		ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL);
13622f9ee756bfd9ce9b066e2f90cf9f002aa7d1d44Lee Jones		if (!ec_dev->dout)
13722f9ee756bfd9ce9b066e2f90cf9f002aa7d1d44Lee Jones			return -ENOMEM;
1384ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	}
1394ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
14063427530fa7a78b42a19f47fb0c12b303c0666caAndrew Bresticker	mutex_init(&ec_dev->lock);
14163427530fa7a78b42a19f47fb0c12b303c0666caAndrew Bresticker
1424ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	err = mfd_add_devices(dev, 0, cros_devs,
1434ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass			      ARRAY_SIZE(cros_devs),
1444ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass			      NULL, ec_dev->irq, NULL);
1454ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	if (err) {
1464ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass		dev_err(dev, "failed to add mfd devices\n");
147d1fd345e2087f0362c92bd3b0a1cea7fe636ac3aAndrew Bresticker		return err;
1484ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	}
1494ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
150533cec8f34778de10412dfabac991cf458ebf3c9Bill Richardson	dev_info(dev, "Chrome EC device registered\n");
1514ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1524ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	return 0;
1534ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass}
1545ebeaff5c5be9f21680b91aac77ee155d935f888Samuel OrtizEXPORT_SYMBOL(cros_ec_register);
1554ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1564ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glassint cros_ec_remove(struct cros_ec_device *ec_dev)
1574ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass{
1584ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	mfd_remove_devices(ec_dev->dev);
1594ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1604ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	return 0;
1614ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass}
1625ebeaff5c5be9f21680b91aac77ee155d935f888Samuel OrtizEXPORT_SYMBOL(cros_ec_remove);
1634ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1644ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass#ifdef CONFIG_PM_SLEEP
1654ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glassint cros_ec_suspend(struct cros_ec_device *ec_dev)
1664ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass{
1674ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	struct device *dev = ec_dev->dev;
1684ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1694ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	if (device_may_wakeup(dev))
1704ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass		ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq);
1714ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1724ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	disable_irq(ec_dev->irq);
1734ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	ec_dev->was_wake_device = ec_dev->wake_enabled;
1744ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1754ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	return 0;
1764ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass}
1775ebeaff5c5be9f21680b91aac77ee155d935f888Samuel OrtizEXPORT_SYMBOL(cros_ec_suspend);
1784ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1794ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glassint cros_ec_resume(struct cros_ec_device *ec_dev)
1804ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass{
1814ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	enable_irq(ec_dev->irq);
1824ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1834ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	if (ec_dev->wake_enabled) {
1844ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass		disable_irq_wake(ec_dev->irq);
1854ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass		ec_dev->wake_enabled = 0;
1864ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	}
1874ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass
1884ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass	return 0;
1894ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass}
1905ebeaff5c5be9f21680b91aac77ee155d935f888Samuel OrtizEXPORT_SYMBOL(cros_ec_resume);
1915ebeaff5c5be9f21680b91aac77ee155d935f888Samuel Ortiz
1924ab6174e8cdb007cf500e484bdf454b8d14d524aSimon Glass#endif
193a865a589144da6dbf26750b7193a9748da159305Bill Richardson
194a865a589144da6dbf26750b7193a9748da159305Bill RichardsonMODULE_LICENSE("GPL");
195a865a589144da6dbf26750b7193a9748da159305Bill RichardsonMODULE_DESCRIPTION("ChromeOS EC core driver");
196