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