123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- /*
- * ChromeOS EC multi-function device
- *
- * Copyright (C) 2012 Google, Inc
- *
- * This software is licensed under the terms of the GNU General Public
- * License version 2, as published by the Free Software Foundation, and
- * may be copied, distributed, and modified under those terms.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * The ChromeOS EC multi function device is used to mux all the requests
- * to the EC device for its multiple features: keyboard controller,
- * battery charging and regulator control, firmware update.
- */
- #include <linux/interrupt.h>
- #include <linux/slab.h>
- #include <linux/module.h>
- #include <linux/mfd/core.h>
- #include <linux/mfd/cros_ec.h>
- #include <linux/mfd/cros_ec_commands.h>
- #include <linux/delay.h>
- #define EC_COMMAND_RETRIES 50
- int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
- struct cros_ec_command *msg)
- {
- uint8_t *out;
- int csum, i;
- BUG_ON(msg->outsize > EC_PROTO2_MAX_PARAM_SIZE);
- out = ec_dev->dout;
- out[0] = EC_CMD_VERSION0 + msg->version;
- out[1] = msg->command;
- out[2] = msg->outsize;
- csum = out[0] + out[1] + out[2];
- for (i = 0; i < msg->outsize; i++)
- csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->outdata[i];
- out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = (uint8_t)(csum & 0xff);
- return EC_MSG_TX_PROTO_BYTES + msg->outsize;
- }
- EXPORT_SYMBOL(cros_ec_prepare_tx);
- int cros_ec_check_result(struct cros_ec_device *ec_dev,
- struct cros_ec_command *msg)
- {
- switch (msg->result) {
- case EC_RES_SUCCESS:
- return 0;
- case EC_RES_IN_PROGRESS:
- dev_dbg(ec_dev->dev, "command 0x%02x in progress\n",
- msg->command);
- return -EAGAIN;
- default:
- dev_dbg(ec_dev->dev, "command 0x%02x returned %d\n",
- msg->command, msg->result);
- return 0;
- }
- }
- EXPORT_SYMBOL(cros_ec_check_result);
- int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev,
- struct cros_ec_command *msg)
- {
- int ret;
- mutex_lock(&ec_dev->lock);
- ret = ec_dev->cmd_xfer(ec_dev, msg);
- if (msg->result == EC_RES_IN_PROGRESS) {
- int i;
- struct cros_ec_command status_msg;
- struct ec_response_get_comms_status status;
- status_msg.version = 0;
- status_msg.command = EC_CMD_GET_COMMS_STATUS;
- status_msg.outdata = NULL;
- status_msg.outsize = 0;
- status_msg.indata = (uint8_t *)&status;
- status_msg.insize = sizeof(status);
- /*
- * Query the EC's status until it's no longer busy or
- * we encounter an error.
- */
- for (i = 0; i < EC_COMMAND_RETRIES; i++) {
- usleep_range(10000, 11000);
- ret = ec_dev->cmd_xfer(ec_dev, &status_msg);
- if (ret < 0)
- break;
- msg->result = status_msg.result;
- if (status_msg.result != EC_RES_SUCCESS)
- break;
- if (!(status.flags & EC_COMMS_STATUS_PROCESSING))
- break;
- }
- }
- mutex_unlock(&ec_dev->lock);
- return ret;
- }
- EXPORT_SYMBOL(cros_ec_cmd_xfer);
- static const struct mfd_cell cros_devs[] = {
- {
- .name = "cros-ec-keyb",
- .id = 1,
- .of_compatible = "google,cros-ec-keyb",
- },
- {
- .name = "cros-ec-i2c-tunnel",
- .id = 2,
- .of_compatible = "google,cros-ec-i2c-tunnel",
- },
- };
- int cros_ec_register(struct cros_ec_device *ec_dev)
- {
- struct device *dev = ec_dev->dev;
- int err = 0;
- if (ec_dev->din_size) {
- ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL);
- if (!ec_dev->din)
- return -ENOMEM;
- }
- if (ec_dev->dout_size) {
- ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL);
- if (!ec_dev->dout)
- return -ENOMEM;
- }
- mutex_init(&ec_dev->lock);
- err = mfd_add_devices(dev, 0, cros_devs,
- ARRAY_SIZE(cros_devs),
- NULL, ec_dev->irq, NULL);
- if (err) {
- dev_err(dev, "failed to add mfd devices\n");
- return err;
- }
- dev_info(dev, "Chrome EC device registered\n");
- return 0;
- }
- EXPORT_SYMBOL(cros_ec_register);
- int cros_ec_remove(struct cros_ec_device *ec_dev)
- {
- mfd_remove_devices(ec_dev->dev);
- return 0;
- }
- EXPORT_SYMBOL(cros_ec_remove);
- #ifdef CONFIG_PM_SLEEP
- int cros_ec_suspend(struct cros_ec_device *ec_dev)
- {
- struct device *dev = ec_dev->dev;
- if (device_may_wakeup(dev))
- ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq);
- disable_irq(ec_dev->irq);
- ec_dev->was_wake_device = ec_dev->wake_enabled;
- return 0;
- }
- EXPORT_SYMBOL(cros_ec_suspend);
- int cros_ec_resume(struct cros_ec_device *ec_dev)
- {
- enable_irq(ec_dev->irq);
- if (ec_dev->wake_enabled) {
- disable_irq_wake(ec_dev->irq);
- ec_dev->wake_enabled = 0;
- }
- return 0;
- }
- EXPORT_SYMBOL(cros_ec_resume);
- #endif
- MODULE_LICENSE("GPL");
- MODULE_DESCRIPTION("ChromeOS EC core driver");
|