| // SPDX-License-Identifier: GPL-2.0 |
| // ChromeOS EC communication protocol helper functions |
| // |
| // Copyright (C) 2015 Google, Inc |
| |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/limits.h> |
| #include <linux/module.h> |
| #include <linux/platform_data/cros_ec_commands.h> |
| #include <linux/platform_data/cros_ec_proto.h> |
| #include <linux/slab.h> |
| #include <linux/unaligned.h> |
| |
| #include "cros_ec_trace.h" |
| |
| #define EC_COMMAND_RETRIES 50 |
| |
| static const int cros_ec_error_map[] = { |
| [EC_RES_INVALID_COMMAND] = -EOPNOTSUPP, |
| [EC_RES_ERROR] = -EIO, |
| [EC_RES_INVALID_PARAM] = -EINVAL, |
| [EC_RES_ACCESS_DENIED] = -EACCES, |
| [EC_RES_INVALID_RESPONSE] = -EPROTO, |
| [EC_RES_INVALID_VERSION] = -ENOPROTOOPT, |
| [EC_RES_INVALID_CHECKSUM] = -EBADMSG, |
| [EC_RES_IN_PROGRESS] = -EINPROGRESS, |
| [EC_RES_UNAVAILABLE] = -ENODATA, |
| [EC_RES_TIMEOUT] = -ETIMEDOUT, |
| [EC_RES_OVERFLOW] = -EOVERFLOW, |
| [EC_RES_INVALID_HEADER] = -EBADR, |
| [EC_RES_REQUEST_TRUNCATED] = -EBADR, |
| [EC_RES_RESPONSE_TOO_BIG] = -EFBIG, |
| [EC_RES_BUS_ERROR] = -EFAULT, |
| [EC_RES_BUSY] = -EBUSY, |
| [EC_RES_INVALID_HEADER_VERSION] = -EBADMSG, |
| [EC_RES_INVALID_HEADER_CRC] = -EBADMSG, |
| [EC_RES_INVALID_DATA_CRC] = -EBADMSG, |
| [EC_RES_DUP_UNAVAILABLE] = -ENODATA, |
| }; |
| |
| static int cros_ec_map_error(uint32_t result) |
| { |
| int ret = 0; |
| |
| if (result != EC_RES_SUCCESS) { |
| if (result < ARRAY_SIZE(cros_ec_error_map) && cros_ec_error_map[result]) |
| ret = cros_ec_error_map[result]; |
| else |
| ret = -EPROTO; |
| } |
| |
| return ret; |
| } |
| |
| static int prepare_tx(struct cros_ec_device *ec_dev, |
| struct cros_ec_command *msg) |
| { |
| struct ec_host_request *request; |
| u8 *out; |
| int i; |
| u8 csum = 0; |
| |
| if (msg->outsize + sizeof(*request) > ec_dev->dout_size) |
| return -EINVAL; |
| |
| out = ec_dev->dout; |
| request = (struct ec_host_request *)out; |
| request->struct_version = EC_HOST_REQUEST_VERSION; |
| request->checksum = 0; |
| request->command = msg->command; |
| request->command_version = msg->version; |
| request->reserved = 0; |
| request->data_len = msg->outsize; |
| |
| for (i = 0; i < sizeof(*request); i++) |
| csum += out[i]; |
| |
| /* Copy data and update checksum */ |
| memcpy(out + sizeof(*request), msg->data, msg->outsize); |
| for (i = 0; i < msg->outsize; i++) |
| csum += msg->data[i]; |
| |
| request->checksum = -csum; |
| |
| return sizeof(*request) + msg->outsize; |
| } |
| |
| static int prepare_tx_legacy(struct cros_ec_device *ec_dev, |
| struct cros_ec_command *msg) |
| { |
| u8 *out; |
| u8 csum; |
| int i; |
| |
| if (msg->outsize > EC_PROTO2_MAX_PARAM_SIZE) |
| return -EINVAL; |
| |
| 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->data[i]; |
| out[EC_MSG_TX_HEADER_BYTES + msg->outsize] = csum; |
| |
| return EC_MSG_TX_PROTO_BYTES + msg->outsize; |
| } |
| |
| static int cros_ec_xfer_command(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) |
| { |
| int ret; |
| int (*xfer_fxn)(struct cros_ec_device *ec, struct cros_ec_command *msg); |
| |
| if (ec_dev->proto_version > 2) |
| xfer_fxn = ec_dev->pkt_xfer; |
| else |
| xfer_fxn = ec_dev->cmd_xfer; |
| |
| if (!xfer_fxn) { |
| /* |
| * This error can happen if a communication error happened and |
| * the EC is trying to use protocol v2, on an underlying |
| * communication mechanism that does not support v2. |
| */ |
| dev_err_once(ec_dev->dev, "missing EC transfer API, cannot send command\n"); |
| return -EIO; |
| } |
| |
| trace_cros_ec_request_start(msg); |
| ret = (*xfer_fxn)(ec_dev, msg); |
| trace_cros_ec_request_done(msg, ret); |
| |
| return ret; |
| } |
| |
| static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev, uint32_t *result) |
| { |
| struct { |
| struct cros_ec_command msg; |
| struct ec_response_get_comms_status status; |
| } __packed buf; |
| struct cros_ec_command *msg = &buf.msg; |
| struct ec_response_get_comms_status *status = &buf.status; |
| int ret = 0, i; |
| |
| msg->version = 0; |
| msg->command = EC_CMD_GET_COMMS_STATUS; |
| msg->insize = sizeof(*status); |
| msg->outsize = 0; |
| |
| /* 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 = cros_ec_xfer_command(ec_dev, msg); |
| if (ret == -EAGAIN) |
| continue; |
| if (ret < 0) |
| return ret; |
| |
| *result = msg->result; |
| if (msg->result != EC_RES_SUCCESS) |
| return ret; |
| |
| if (ret == 0) { |
| ret = -EPROTO; |
| break; |
| } |
| |
| if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) |
| return ret; |
| } |
| |
| if (i >= EC_COMMAND_RETRIES) |
| ret = -EAGAIN; |
| |
| return ret; |
| } |
| |
| static int cros_ec_send_command(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) |
| { |
| int ret = cros_ec_xfer_command(ec_dev, msg); |
| |
| if (msg->result == EC_RES_IN_PROGRESS) |
| ret = cros_ec_wait_until_complete(ec_dev, &msg->result); |
| |
| return ret; |
| } |
| |
| /** |
| * cros_ec_prepare_tx() - Prepare an outgoing message in the output buffer. |
| * @ec_dev: Device to register. |
| * @msg: Message to write. |
| * |
| * This is used by all ChromeOS EC drivers to prepare the outgoing message |
| * according to different protocol versions. |
| * |
| * Return: number of prepared bytes on success or negative error code. |
| */ |
| int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, |
| struct cros_ec_command *msg) |
| { |
| if (ec_dev->proto_version > 2) |
| return prepare_tx(ec_dev, msg); |
| |
| return prepare_tx_legacy(ec_dev, msg); |
| } |
| EXPORT_SYMBOL(cros_ec_prepare_tx); |
| |
| /** |
| * cros_ec_check_result() - Check ec_msg->result. |
| * @ec_dev: EC device. |
| * @msg: Message to check. |
| * |
| * This is used by ChromeOS EC drivers to check the ec_msg->result for |
| * EC_RES_IN_PROGRESS and to warn about them. |
| * |
| * The function should not check for furthermore error codes. Otherwise, |
| * it would break the ABI. |
| * |
| * Return: -EAGAIN if ec_msg->result == EC_RES_IN_PROGRESS. Otherwise, 0. |
| */ |
| 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); |
| |
| /** |
| * cros_ec_get_host_event_wake_mask |
| * |
| * Get the mask of host events that cause wake from suspend. |
| * |
| * @ec_dev: EC device to call |
| * @mask: result when function returns 0. |
| * |
| * LOCKING: |
| * the caller has ec_dev->lock mutex, or the caller knows there is |
| * no other command in progress. |
| */ |
| static int cros_ec_get_host_event_wake_mask(struct cros_ec_device *ec_dev, uint32_t *mask) |
| { |
| struct cros_ec_command *msg; |
| struct ec_response_host_event_mask *r; |
| int ret, mapped; |
| |
| msg = kzalloc(sizeof(*msg) + sizeof(*r), GFP_KERNEL); |
| if (!msg) |
| return -ENOMEM; |
| |
| msg->command = EC_CMD_HOST_EVENT_GET_WAKE_MASK; |
| msg->insize = sizeof(*r); |
| |
| ret = cros_ec_send_command(ec_dev, msg); |
| if (ret < 0) |
| goto exit; |
| |
| mapped = cros_ec_map_error(msg->result); |
| if (mapped) { |
| ret = mapped; |
| goto exit; |
| } |
| |
| if (ret == 0) { |
| ret = -EPROTO; |
| goto exit; |
| } |
| |
| r = (struct ec_response_host_event_mask *)msg->data; |
| *mask = r->mask; |
| ret = 0; |
| exit: |
| kfree(msg); |
| return ret; |
| } |
| |
| static int cros_ec_get_proto_info(struct cros_ec_device *ec_dev, int devidx) |
| { |
| struct cros_ec_command *msg; |
| struct ec_response_get_protocol_info *info; |
| int ret, mapped; |
| |
| ec_dev->proto_version = 3; |
| if (devidx > 0) |
| ec_dev->max_passthru = 0; |
| |
| msg = kzalloc(sizeof(*msg) + sizeof(*info), GFP_KERNEL); |
| if (!msg) |
| return -ENOMEM; |
| |
| msg->command = EC_CMD_PASSTHRU_OFFSET(devidx) | EC_CMD_GET_PROTOCOL_INFO; |
| msg->insize = sizeof(*info); |
| |
| ret = cros_ec_send_command(ec_dev, msg); |
| /* |
| * Send command once again when timeout occurred. |
| * Fingerprint MCU (FPMCU) is restarted during system boot which |
| * introduces small window in which FPMCU won't respond for any |
| * messages sent by kernel. There is no need to wait before next |
| * attempt because we waited at least EC_MSG_DEADLINE_MS. |
| */ |
| if (ret == -ETIMEDOUT) |
| ret = cros_ec_send_command(ec_dev, msg); |
| |
| if (ret < 0) { |
| dev_dbg(ec_dev->dev, |
| "failed to check for EC[%d] protocol version: %d\n", |
| devidx, ret); |
| goto exit; |
| } |
| |
| mapped = cros_ec_map_error(msg->result); |
| if (mapped) { |
| ret = mapped; |
| goto exit; |
| } |
| |
| if (ret == 0) { |
| ret = -EPROTO; |
| goto exit; |
| } |
| |
| info = (struct ec_response_get_protocol_info *)msg->data; |
| |
| switch (devidx) { |
| case CROS_EC_DEV_EC_INDEX: |
| ec_dev->max_request = info->max_request_packet_size - |
| sizeof(struct ec_host_request); |
| ec_dev->max_response = info->max_response_packet_size - |
| sizeof(struct ec_host_response); |
| ec_dev->proto_version = min(EC_HOST_REQUEST_VERSION, |
| fls(info->protocol_versions) - 1); |
| ec_dev->din_size = info->max_response_packet_size + EC_MAX_RESPONSE_OVERHEAD; |
| ec_dev->dout_size = info->max_request_packet_size + EC_MAX_REQUEST_OVERHEAD; |
| |
| dev_dbg(ec_dev->dev, "using proto v%u\n", ec_dev->proto_version); |
| break; |
| case CROS_EC_DEV_PD_INDEX: |
| ec_dev->max_passthru = info->max_request_packet_size - |
| sizeof(struct ec_host_request); |
| |
| dev_dbg(ec_dev->dev, "found PD chip\n"); |
| break; |
| default: |
| dev_dbg(ec_dev->dev, "unknown passthru index: %d\n", devidx); |
| break; |
| } |
| |
| ret = 0; |
| exit: |
| kfree(msg); |
| return ret; |
| } |
| |
| static int cros_ec_get_proto_info_legacy(struct cros_ec_device *ec_dev) |
| { |
| struct cros_ec_command *msg; |
| struct ec_params_hello *params; |
| struct ec_response_hello *response; |
| int ret, mapped; |
| |
| ec_dev->proto_version = 2; |
| |
| msg = kzalloc(sizeof(*msg) + max(sizeof(*params), sizeof(*response)), GFP_KERNEL); |
| if (!msg) |
| return -ENOMEM; |
| |
| msg->command = EC_CMD_HELLO; |
| msg->insize = sizeof(*response); |
| msg->outsize = sizeof(*params); |
| |
| params = (struct ec_params_hello *)msg->data; |
| params->in_data = 0xa0b0c0d0; |
| |
| ret = cros_ec_send_command(ec_dev, msg); |
| if (ret < 0) { |
| dev_dbg(ec_dev->dev, "EC failed to respond to v2 hello: %d\n", ret); |
| goto exit; |
| } |
| |
| mapped = cros_ec_map_error(msg->result); |
| if (mapped) { |
| ret = mapped; |
| dev_err(ec_dev->dev, "EC responded to v2 hello with error: %d\n", msg->result); |
| goto exit; |
| } |
| |
| if (ret == 0) { |
| ret = -EPROTO; |
| goto exit; |
| } |
| |
| response = (struct ec_response_hello *)msg->data; |
| if (response->out_data != 0xa1b2c3d4) { |
| dev_err(ec_dev->dev, |
| "EC responded to v2 hello with bad result: %u\n", |
| response->out_data); |
| ret = -EBADMSG; |
| goto exit; |
| } |
| |
| ec_dev->max_request = EC_PROTO2_MAX_PARAM_SIZE; |
| ec_dev->max_response = EC_PROTO2_MAX_PARAM_SIZE; |
| ec_dev->max_passthru = 0; |
| ec_dev->pkt_xfer = NULL; |
| ec_dev->din_size = EC_PROTO2_MSG_BYTES; |
| ec_dev->dout_size = EC_PROTO2_MSG_BYTES; |
| |
| dev_dbg(ec_dev->dev, "falling back to proto v2\n"); |
| ret = 0; |
| exit: |
| kfree(msg); |
| return ret; |
| } |
| |
| /** |
| * cros_ec_get_host_command_version_mask |
| * |
| * Get the version mask of a given command. |
| * |
| * @ec_dev: EC device to call |
| * @cmd: command to get the version of. |
| * @mask: result when function returns 0. |
| * |
| * @return 0 on success, error code otherwise |
| * |
| * LOCKING: |
| * the caller has ec_dev->lock mutex or the caller knows there is |
| * no other command in progress. |
| */ |
| static int cros_ec_get_host_command_version_mask(struct cros_ec_device *ec_dev, u16 cmd, u32 *mask) |
| { |
| struct ec_params_get_cmd_versions *pver; |
| struct ec_response_get_cmd_versions *rver; |
| struct cros_ec_command *msg; |
| int ret, mapped; |
| |
| msg = kmalloc(sizeof(*msg) + max(sizeof(*rver), sizeof(*pver)), |
| GFP_KERNEL); |
| if (!msg) |
| return -ENOMEM; |
| |
| msg->version = 0; |
| msg->command = EC_CMD_GET_CMD_VERSIONS; |
| msg->insize = sizeof(*rver); |
| msg->outsize = sizeof(*pver); |
| |
| pver = (struct ec_params_get_cmd_versions *)msg->data; |
| pver->cmd = cmd; |
| |
| ret = cros_ec_send_command(ec_dev, msg); |
| if (ret < 0) |
| goto exit; |
| |
| mapped = cros_ec_map_error(msg->result); |
| if (mapped) { |
| ret = mapped; |
| goto exit; |
| } |
| |
| if (ret == 0) { |
| ret = -EPROTO; |
| goto exit; |
| } |
| |
| rver = (struct ec_response_get_cmd_versions *)msg->data; |
| *mask = rver->version_mask; |
| ret = 0; |
| exit: |
| kfree(msg); |
| return ret; |
| } |
| |
| /** |
| * cros_ec_query_all() - Query the protocol version supported by the |
| * ChromeOS EC. |
| * @ec_dev: Device to register. |
| * |
| * Return: 0 on success or negative error code. |
| */ |
| int cros_ec_query_all(struct cros_ec_device *ec_dev) |
| { |
| struct device *dev = ec_dev->dev; |
| u32 ver_mask; |
| int ret; |
| |
| /* First try sending with proto v3. */ |
| if (!cros_ec_get_proto_info(ec_dev, CROS_EC_DEV_EC_INDEX)) { |
| /* Check for PD. */ |
| cros_ec_get_proto_info(ec_dev, CROS_EC_DEV_PD_INDEX); |
| } else { |
| /* Try querying with a v2 hello message. */ |
| ret = cros_ec_get_proto_info_legacy(ec_dev); |
| if (ret) { |
| /* |
| * It's possible for a test to occur too early when |
| * the EC isn't listening. If this happens, we'll |
| * test later when the first command is run. |
| */ |
| ec_dev->proto_version = EC_PROTO_VERSION_UNKNOWN; |
| dev_dbg(ec_dev->dev, "EC query failed: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| devm_kfree(dev, ec_dev->din); |
| devm_kfree(dev, ec_dev->dout); |
| |
| ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); |
| if (!ec_dev->din) { |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL); |
| if (!ec_dev->dout) { |
| devm_kfree(dev, ec_dev->din); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| |
| /* Probe if MKBP event is supported */ |
| ret = cros_ec_get_host_command_version_mask(ec_dev, EC_CMD_GET_NEXT_EVENT, &ver_mask); |
| if (ret < 0 || ver_mask == 0) { |
| ec_dev->mkbp_event_supported = 0; |
| } else { |
| ec_dev->mkbp_event_supported = fls(ver_mask); |
| |
| dev_dbg(ec_dev->dev, "MKBP support version %u\n", ec_dev->mkbp_event_supported - 1); |
| } |
| |
| /* Probe if host sleep v1 is supported for S0ix failure detection. */ |
| ret = cros_ec_get_host_command_version_mask(ec_dev, EC_CMD_HOST_SLEEP_EVENT, &ver_mask); |
| ec_dev->host_sleep_v1 = (ret == 0 && (ver_mask & EC_VER_MASK(1))); |
| |
| /* Get host event wake mask. */ |
| ret = cros_ec_get_host_event_wake_mask(ec_dev, &ec_dev->host_event_wake_mask); |
| if (ret < 0) { |
| /* |
| * If the EC doesn't support EC_CMD_HOST_EVENT_GET_WAKE_MASK, |
| * use a reasonable default. Note that we ignore various |
| * battery, AC status, and power-state events, because (a) |
| * those can be quite common (e.g., when sitting at full |
| * charge, on AC) and (b) these are not actionable wake events; |
| * if anything, we'd like to continue suspending (to save |
| * power), not wake up. |
| */ |
| ec_dev->host_event_wake_mask = U32_MAX & |
| ~(EC_HOST_EVENT_MASK(EC_HOST_EVENT_LID_CLOSED) | |
| EC_HOST_EVENT_MASK(EC_HOST_EVENT_AC_DISCONNECTED) | |
| EC_HOST_EVENT_MASK(EC_HOST_EVENT_BATTERY_LOW) | |
| EC_HOST_EVENT_MASK(EC_HOST_EVENT_BATTERY_CRITICAL) | |
| EC_HOST_EVENT_MASK(EC_HOST_EVENT_BATTERY) | |
| EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU) | |
| EC_HOST_EVENT_MASK(EC_HOST_EVENT_BATTERY_STATUS)); |
| /* |
| * Old ECs may not support this command. Complain about all |
| * other errors. |
| */ |
| if (ret != -EOPNOTSUPP) |
| dev_err(ec_dev->dev, |
| "failed to retrieve wake mask: %d\n", ret); |
| } |
| |
| ret = 0; |
| |
| exit: |
| return ret; |
| } |
| EXPORT_SYMBOL(cros_ec_query_all); |
| |
| /** |
| * cros_ec_cmd_xfer() - Send a command to the ChromeOS EC. |
| * @ec_dev: EC device. |
| * @msg: Message to write. |
| * |
| * Call this to send a command to the ChromeOS EC. This should be used instead |
| * of calling the EC's cmd_xfer() callback directly. This function does not |
| * convert EC command execution error codes to Linux error codes. Most |
| * in-kernel users will want to use cros_ec_cmd_xfer_status() instead since |
| * that function implements the conversion. |
| * |
| * Return: |
| * >0 - EC command was executed successfully. The return value is the number |
| * of bytes returned by the EC (excluding the header). |
| * =0 - EC communication was successful. EC command execution results are |
| * reported in msg->result. The result will be EC_RES_SUCCESS if the |
| * command was executed successfully or report an EC command execution |
| * error. |
| * <0 - EC communication error. Return value is the Linux error code. |
| */ |
| int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) |
| { |
| int ret; |
| |
| mutex_lock(&ec_dev->lock); |
| if (ec_dev->proto_version == EC_PROTO_VERSION_UNKNOWN) { |
| ret = cros_ec_query_all(ec_dev); |
| if (ret) { |
| dev_err(ec_dev->dev, |
| "EC version unknown and query failed; aborting command\n"); |
| mutex_unlock(&ec_dev->lock); |
| return ret; |
| } |
| } |
| |
| if (msg->insize > ec_dev->max_response) { |
| dev_dbg(ec_dev->dev, "clamping message receive buffer\n"); |
| msg->insize = ec_dev->max_response; |
| } |
| |
| if (msg->command < EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX)) { |
| if (msg->outsize > ec_dev->max_request) { |
| dev_err(ec_dev->dev, |
| "request of size %u is too big (max: %u)\n", |
| msg->outsize, |
| ec_dev->max_request); |
| mutex_unlock(&ec_dev->lock); |
| return -EMSGSIZE; |
| } |
| } else { |
| if (msg->outsize > ec_dev->max_passthru) { |
| dev_err(ec_dev->dev, |
| "passthru rq of size %u is too big (max: %u)\n", |
| msg->outsize, |
| ec_dev->max_passthru); |
| mutex_unlock(&ec_dev->lock); |
| return -EMSGSIZE; |
| } |
| } |
| |
| ret = cros_ec_send_command(ec_dev, msg); |
| mutex_unlock(&ec_dev->lock); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(cros_ec_cmd_xfer); |
| |
| /** |
| * cros_ec_cmd_xfer_status() - Send a command to the ChromeOS EC. |
| * @ec_dev: EC device. |
| * @msg: Message to write. |
| * |
| * Call this to send a command to the ChromeOS EC. This should be used instead of calling the EC's |
| * cmd_xfer() callback directly. It returns success status only if both the command was transmitted |
| * successfully and the EC replied with success status. |
| * |
| * Return: |
| * >=0 - The number of bytes transferred. |
| * <0 - Linux error code |
| */ |
| int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev, |
| struct cros_ec_command *msg) |
| { |
| int ret, mapped; |
| |
| ret = cros_ec_cmd_xfer(ec_dev, msg); |
| if (ret < 0) |
| return ret; |
| |
| mapped = cros_ec_map_error(msg->result); |
| if (mapped) { |
| dev_dbg(ec_dev->dev, "Command result (err: %d [%d])\n", |
| msg->result, mapped); |
| ret = mapped; |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(cros_ec_cmd_xfer_status); |
| |
| static int get_next_event_xfer(struct cros_ec_device *ec_dev, |
| struct cros_ec_command *msg, |
| struct ec_response_get_next_event_v3 *event, |
| int version, uint32_t size) |
| { |
| int ret; |
| |
| msg->version = version; |
| msg->command = EC_CMD_GET_NEXT_EVENT; |
| msg->insize = size; |
| msg->outsize = 0; |
| |
| ret = cros_ec_cmd_xfer_status(ec_dev, msg); |
| if (ret > 0) { |
| ec_dev->event_size = ret - 1; |
| ec_dev->event_data = *event; |
| } |
| |
| return ret; |
| } |
| |
| static int get_next_event(struct cros_ec_device *ec_dev) |
| { |
| struct { |
| struct cros_ec_command msg; |
| struct ec_response_get_next_event_v3 event; |
| } __packed buf; |
| struct cros_ec_command *msg = &buf.msg; |
| struct ec_response_get_next_event_v3 *event = &buf.event; |
| int cmd_version = ec_dev->mkbp_event_supported - 1; |
| u32 size; |
| |
| memset(msg, 0, sizeof(*msg)); |
| if (ec_dev->suspended) { |
| dev_dbg(ec_dev->dev, "Device suspended.\n"); |
| return -EHOSTDOWN; |
| } |
| |
| if (cmd_version == 0) { |
| size = sizeof(struct ec_response_get_next_event); |
| } else if (cmd_version < 3) { |
| size = sizeof(struct ec_response_get_next_event_v1); |
| } else { |
| /* |
| * The max version we support is v3. So, we speak v3 even if the |
| * EC says it supports v4+. |
| */ |
| cmd_version = 3; |
| size = sizeof(struct ec_response_get_next_event_v3); |
| } |
| |
| return get_next_event_xfer(ec_dev, msg, event, cmd_version, size); |
| } |
| |
| static int get_keyboard_state_event(struct cros_ec_device *ec_dev) |
| { |
| u8 buffer[sizeof(struct cros_ec_command) + |
| sizeof(ec_dev->event_data.data)]; |
| struct cros_ec_command *msg = (struct cros_ec_command *)&buffer; |
| |
| msg->version = 0; |
| msg->command = EC_CMD_MKBP_STATE; |
| msg->insize = sizeof(ec_dev->event_data.data); |
| msg->outsize = 0; |
| |
| ec_dev->event_size = cros_ec_cmd_xfer_status(ec_dev, msg); |
| ec_dev->event_data.event_type = EC_MKBP_EVENT_KEY_MATRIX; |
| memcpy(&ec_dev->event_data.data, msg->data, |
| sizeof(ec_dev->event_data.data)); |
| |
| return ec_dev->event_size; |
| } |
| |
| /** |
| * cros_ec_get_next_event() - Fetch next event from the ChromeOS EC. |
| * @ec_dev: Device to fetch event from. |
| * @wake_event: Pointer to a bool set to true upon return if the event might be |
| * treated as a wake event. Ignored if null. |
| * @has_more_events: Pointer to bool set to true if more than one event is |
| * pending. |
| * Some EC will set this flag to indicate cros_ec_get_next_event() |
| * can be called multiple times in a row. |
| * It is an optimization to prevent issuing a EC command for |
| * nothing or wait for another interrupt from the EC to process |
| * the next message. |
| * Ignored if null. |
| * |
| * Return: negative error code on errors; 0 for no data; or else number of |
| * bytes received (i.e., an event was retrieved successfully). Event types are |
| * written out to @ec_dev->event_data.event_type on success. |
| */ |
| int cros_ec_get_next_event(struct cros_ec_device *ec_dev, |
| bool *wake_event, |
| bool *has_more_events) |
| { |
| u8 event_type; |
| u32 host_event; |
| int ret; |
| u32 ver_mask; |
| |
| /* |
| * Default value for wake_event. |
| * Wake up on keyboard event, wake up for spurious interrupt or link |
| * error to the EC. |
| */ |
| if (wake_event) |
| *wake_event = true; |
| |
| /* |
| * Default value for has_more_events. |
| * EC will raise another interrupt if AP does not process all events |
| * anyway. |
| */ |
| if (has_more_events) |
| *has_more_events = false; |
| |
| if (!ec_dev->mkbp_event_supported) |
| return get_keyboard_state_event(ec_dev); |
| |
| ret = get_next_event(ec_dev); |
| /* |
| * -ENOPROTOOPT is returned when EC returns EC_RES_INVALID_VERSION. |
| * This can occur when EC based device (e.g. Fingerprint MCU) jumps to |
| * the RO image which doesn't support newer version of the command. In |
| * this case we will attempt to update maximum supported version of the |
| * EC_CMD_GET_NEXT_EVENT. |
| */ |
| if (ret == -ENOPROTOOPT) { |
| dev_dbg(ec_dev->dev, |
| "GET_NEXT_EVENT returned invalid version error.\n"); |
| mutex_lock(&ec_dev->lock); |
| ret = cros_ec_get_host_command_version_mask(ec_dev, |
| EC_CMD_GET_NEXT_EVENT, |
| &ver_mask); |
| mutex_unlock(&ec_dev->lock); |
| if (ret < 0 || ver_mask == 0) |
| /* |
| * Do not change the MKBP supported version if we can't |
| * obtain supported version correctly. Please note that |
| * calling EC_CMD_GET_NEXT_EVENT returned |
| * EC_RES_INVALID_VERSION which means that the command |
| * is present. |
| */ |
| return -ENOPROTOOPT; |
| |
| ec_dev->mkbp_event_supported = fls(ver_mask); |
| dev_dbg(ec_dev->dev, "MKBP support version changed to %u\n", |
| ec_dev->mkbp_event_supported - 1); |
| |
| /* Try to get next event with new MKBP support version set. */ |
| ret = get_next_event(ec_dev); |
| } |
| |
| if (ret <= 0) |
| return ret; |
| |
| if (has_more_events) |
| *has_more_events = ec_dev->event_data.event_type & |
| EC_MKBP_HAS_MORE_EVENTS; |
| ec_dev->event_data.event_type &= EC_MKBP_EVENT_TYPE_MASK; |
| |
| if (wake_event) { |
| event_type = ec_dev->event_data.event_type; |
| host_event = cros_ec_get_host_event(ec_dev); |
| |
| /* |
| * Sensor events need to be parsed by the sensor sub-device. |
| * Defer them, and don't report the wakeup here. |
| */ |
| if (event_type == EC_MKBP_EVENT_SENSOR_FIFO) { |
| *wake_event = false; |
| } else if (host_event) { |
| /* rtc_update_irq() already handles wakeup events. */ |
| if (host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_RTC)) |
| *wake_event = false; |
| /* Masked host-events should not count as wake events. */ |
| if (!(host_event & ec_dev->host_event_wake_mask)) |
| *wake_event = false; |
| } |
| } |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(cros_ec_get_next_event); |
| |
| /** |
| * cros_ec_get_host_event() - Return a mask of event set by the ChromeOS EC. |
| * @ec_dev: Device to fetch event from. |
| * |
| * When MKBP is supported, when the EC raises an interrupt, we collect the |
| * events raised and call the functions in the ec notifier. This function |
| * is a helper to know which events are raised. |
| * |
| * Return: 0 on error or non-zero bitmask of one or more EC_HOST_EVENT_*. |
| */ |
| u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev) |
| { |
| u32 host_event; |
| |
| if (!ec_dev->mkbp_event_supported) |
| return 0; |
| |
| if (ec_dev->event_data.event_type != EC_MKBP_EVENT_HOST_EVENT) |
| return 0; |
| |
| if (ec_dev->event_size != sizeof(host_event)) { |
| dev_warn(ec_dev->dev, "Invalid host event size\n"); |
| return 0; |
| } |
| |
| host_event = get_unaligned_le32(&ec_dev->event_data.data.host_event); |
| |
| return host_event; |
| } |
| EXPORT_SYMBOL(cros_ec_get_host_event); |
| |
| /** |
| * cros_ec_check_features() - Test for the presence of EC features |
| * |
| * @ec: EC device, does not have to be connected directly to the AP, |
| * can be daisy chained through another device. |
| * @feature: One of ec_feature_code bit. |
| * |
| * Call this function to test whether the ChromeOS EC supports a feature. |
| * |
| * Return: true if supported, false if not (or if an error was encountered). |
| */ |
| bool cros_ec_check_features(struct cros_ec_dev *ec, int feature) |
| { |
| struct ec_response_get_features *features = &ec->features; |
| int ret; |
| |
| if (features->flags[0] == -1U && features->flags[1] == -1U) { |
| /* features bitmap not read yet */ |
| ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_GET_FEATURES + ec->cmd_offset, |
| NULL, 0, features, sizeof(*features)); |
| if (ret < 0) { |
| dev_warn(ec->dev, "cannot get EC features: %d\n", ret); |
| memset(features, 0, sizeof(*features)); |
| } |
| |
| dev_dbg(ec->dev, "EC features %08x %08x\n", |
| features->flags[0], features->flags[1]); |
| } |
| |
| return !!(features->flags[feature / 32] & EC_FEATURE_MASK_0(feature)); |
| } |
| EXPORT_SYMBOL_GPL(cros_ec_check_features); |
| |
| /** |
| * cros_ec_get_sensor_count() - Return the number of MEMS sensors supported. |
| * |
| * @ec: EC device, does not have to be connected directly to the AP, |
| * can be daisy chained through another device. |
| * Return: < 0 in case of error. |
| */ |
| int cros_ec_get_sensor_count(struct cros_ec_dev *ec) |
| { |
| /* |
| * Issue a command to get the number of sensor reported. |
| * If not supported, check for legacy mode. |
| */ |
| int ret, sensor_count; |
| struct ec_params_motion_sense *params; |
| struct ec_response_motion_sense *resp; |
| struct cros_ec_command *msg; |
| struct cros_ec_device *ec_dev = ec->ec_dev; |
| u8 status; |
| |
| msg = kzalloc(sizeof(*msg) + max(sizeof(*params), sizeof(*resp)), |
| GFP_KERNEL); |
| if (!msg) |
| return -ENOMEM; |
| |
| msg->version = 1; |
| msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; |
| msg->outsize = sizeof(*params); |
| msg->insize = sizeof(*resp); |
| |
| params = (struct ec_params_motion_sense *)msg->data; |
| params->cmd = MOTIONSENSE_CMD_DUMP; |
| |
| ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); |
| if (ret < 0) { |
| sensor_count = ret; |
| } else { |
| resp = (struct ec_response_motion_sense *)msg->data; |
| sensor_count = resp->dump.sensor_count; |
| } |
| kfree(msg); |
| |
| /* |
| * Check legacy mode: Let's find out if sensors are accessible |
| * via LPC interface. |
| */ |
| if (sensor_count < 0 && ec->cmd_offset == 0 && ec_dev->cmd_readmem) { |
| ret = ec_dev->cmd_readmem(ec_dev, EC_MEMMAP_ACC_STATUS, |
| 1, &status); |
| if (ret >= 0 && |
| (status & EC_MEMMAP_ACC_STATUS_PRESENCE_BIT)) { |
| /* |
| * We have 2 sensors, one in the lid, one in the base. |
| */ |
| sensor_count = 2; |
| } else { |
| /* |
| * EC uses LPC interface and no sensors are presented. |
| */ |
| sensor_count = 0; |
| } |
| } |
| return sensor_count; |
| } |
| EXPORT_SYMBOL_GPL(cros_ec_get_sensor_count); |
| |
| /** |
| * cros_ec_cmd - Send a command to the EC. |
| * |
| * @ec_dev: EC device |
| * @version: EC command version |
| * @command: EC command |
| * @outdata: EC command output data |
| * @outsize: Size of outdata |
| * @indata: EC command input data |
| * @insize: Size of indata |
| * |
| * Return: >= 0 on success, negative error number on failure. |
| */ |
| int cros_ec_cmd(struct cros_ec_device *ec_dev, |
| unsigned int version, |
| int command, |
| const void *outdata, |
| size_t outsize, |
| void *indata, |
| size_t insize) |
| { |
| struct cros_ec_command *msg; |
| int ret; |
| |
| msg = kzalloc(sizeof(*msg) + max(insize, outsize), GFP_KERNEL); |
| if (!msg) |
| return -ENOMEM; |
| |
| msg->version = version; |
| msg->command = command; |
| msg->outsize = outsize; |
| msg->insize = insize; |
| |
| if (outsize) |
| memcpy(msg->data, outdata, outsize); |
| |
| ret = cros_ec_cmd_xfer_status(ec_dev, msg); |
| if (ret < 0) |
| goto error; |
| |
| if (insize) |
| memcpy(indata, msg->data, insize); |
| error: |
| kfree(msg); |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(cros_ec_cmd); |
| |
| /** |
| * cros_ec_cmd_readmem - Read from EC memory. |
| * |
| * @ec_dev: EC device |
| * @offset: Is within EC_LPC_ADDR_MEMMAP region. |
| * @size: Number of bytes to read. |
| * @dest: EC command output data |
| * |
| * Return: >= 0 on success, negative error number on failure. |
| */ |
| int cros_ec_cmd_readmem(struct cros_ec_device *ec_dev, u8 offset, u8 size, void *dest) |
| { |
| struct ec_params_read_memmap params = {}; |
| |
| if (!size) |
| return -EINVAL; |
| |
| if (ec_dev->cmd_readmem) |
| return ec_dev->cmd_readmem(ec_dev, offset, size, dest); |
| |
| params.offset = offset; |
| params.size = size; |
| return cros_ec_cmd(ec_dev, 0, EC_CMD_READ_MEMMAP, |
| ¶ms, sizeof(params), dest, size); |
| } |
| EXPORT_SYMBOL_GPL(cros_ec_cmd_readmem); |
| |
| /** |
| * cros_ec_get_cmd_versions - Get supported version mask. |
| * |
| * @ec_dev: EC device |
| * @cmd: Command to test |
| * |
| * Return: version mask on success, negative error number on failure. |
| */ |
| int cros_ec_get_cmd_versions(struct cros_ec_device *ec_dev, u16 cmd) |
| { |
| struct ec_params_get_cmd_versions req_v0; |
| struct ec_params_get_cmd_versions_v1 req_v1; |
| struct ec_response_get_cmd_versions resp; |
| int ret; |
| |
| if (cmd <= U8_MAX) { |
| req_v0.cmd = cmd; |
| ret = cros_ec_cmd(ec_dev, 0, EC_CMD_GET_CMD_VERSIONS, |
| &req_v0, sizeof(req_v0), &resp, sizeof(resp)); |
| } else { |
| req_v1.cmd = cmd; |
| ret = cros_ec_cmd(ec_dev, 1, EC_CMD_GET_CMD_VERSIONS, |
| &req_v1, sizeof(req_v1), &resp, sizeof(resp)); |
| } |
| |
| if (ret == -EINVAL) |
| return 0; /* Command not implemented */ |
| else if (ret < 0) |
| return ret; |
| else |
| return resp.version_mask; |
| } |
| EXPORT_SYMBOL_GPL(cros_ec_get_cmd_versions); |