// SPDX-License-Identifier: GPL-2.0 // LPC interface for ChromeOS Embedded Controller // // Copyright (C) 2012-2015 Google, Inc // // This driver uses the ChromeOS EC byte-level message-based protocol for // communicating the keyboard state (which keys are pressed) from a keyboard EC // to the AP over some bus (such as i2c, lpc, spi). The EC does debouncing, // but everything else (including deghosting) is done here. The main // motivation for this is to keep the EC firmware as simple as possible, since // it cannot be easily upgraded and EC flash/IRAM space is relatively // expensive.
/* True if ACPI device is present */ staticbool cros_ec_lpc_acpi_device_found;
/* * Indicates that lpc_driver_data.quirk_mmio_memory_base should * be used as the base port for EC mapped memory.
*/ #define CROS_EC_LPC_QUIRK_REMAP_MEMORY BIT(0) /* * Indicates that lpc_driver_data.quirk_acpi_id should be used to find * the ACPI device.
*/ #define CROS_EC_LPC_QUIRK_ACPI_ID BIT(1) /* * Indicates that lpc_driver_data.quirk_aml_mutex_name should be used * to find an AML mutex to protect access to Microchip EC.
*/ #define CROS_EC_LPC_QUIRK_AML_MUTEX BIT(2)
/** * struct lpc_driver_data - driver data attached to a DMI device ID to indicate * hardware quirks. * @quirks: a bitfield composed of quirks from CROS_EC_LPC_QUIRK_* * @quirk_mmio_memory_base: The first I/O port addressing EC mapped memory (used * when quirk ...REMAP_MEMORY is set.) * @quirk_acpi_id: An ACPI HID to be used to find the ACPI device. * @quirk_aml_mutex_name: The name of an AML mutex to be used to protect access * to Microchip EC.
*/ struct lpc_driver_data {
u32 quirks;
u16 quirk_mmio_memory_base; constchar *quirk_acpi_id; constchar *quirk_aml_mutex_name;
};
/** * struct cros_ec_lpc - LPC device-specific data * @mmio_memory_base: The first I/O port addressing EC mapped memory. * @base: For EC supporting memory mapping, base address of the mapped region. * @mem32: Information about the memory mapped register region, if present. * @read: Copy length bytes from EC address offset into buffer dest. * Returns a negative error code on error, or the 8-bit checksum * of all bytes read. * @write: Copy length bytes from buffer msg into EC address offset. * Returns a negative error code on error, or the 8-bit checksum * of all bytes written.
*/ struct cros_ec_lpc {
u16 mmio_memory_base; void __iomem *base; struct acpi_resource_fixed_memory32 mem32; int (*read)(struct cros_ec_lpc *ec_lpc, unsignedint offset, unsignedint length, u8 *dest); int (*write)(struct cros_ec_lpc *ec_lpc, unsignedint offset, unsignedint length, const u8 *msg);
};
/* * A generic instance of the read function of struct lpc_driver_ops, used for * the LPC EC.
*/ staticint cros_ec_lpc_read_bytes(struct cros_ec_lpc *_, unsignedint offset, unsignedint length,
u8 *dest)
{
u8 sum = 0; int i;
for (i = 0; i < length; ++i) {
dest[i] = inb(offset + i);
sum += dest[i];
}
/* Return checksum of all bytes read */ return sum;
}
/* * A generic instance of the write function of struct lpc_driver_ops, used for * the LPC EC.
*/ staticint cros_ec_lpc_write_bytes(struct cros_ec_lpc *_, unsignedint offset, unsignedint length, const u8 *msg)
{
u8 sum = 0; int i;
for (i = 0; i < length; ++i) {
outb(msg[i], offset + i);
sum += msg[i];
}
/* Return checksum of all bytes written */ return sum;
}
/* * An instance of the read function of struct lpc_driver_ops, used for the * MEC variant of LPC EC.
*/ staticint cros_ec_lpc_mec_read_bytes(struct cros_ec_lpc *ec_lpc, unsignedint offset, unsignedint length, u8 *dest)
{ int in_range = cros_ec_lpc_mec_in_range(offset, length);
/* * An instance of the write function of struct lpc_driver_ops, used for the * MEC variant of LPC EC.
*/ staticint cros_ec_lpc_mec_write_bytes(struct cros_ec_lpc *ec_lpc, unsignedint offset, unsignedint length, const u8 *msg)
{ int in_range = cros_ec_lpc_mec_in_range(offset, length);
ret = cros_ec_prepare_tx(ec, msg); if (ret < 0) goto done;
/* Write buffer */
ret = ec_lpc->write(ec_lpc, EC_LPC_ADDR_HOST_PACKET, ret, ec->dout); if (ret < 0) goto done;
/* Here we go */
sum = EC_COMMAND_PROTOCOL_3;
ret = ec_lpc->write(ec_lpc, EC_LPC_ADDR_HOST_CMD, 1, &sum); if (ret < 0) goto done;
ret = ec_response_timed_out(ec_lpc); if (ret < 0) goto done; if (ret) {
dev_warn(ec->dev, "EC response timed out\n");
ret = -EIO; goto done;
}
/* Check result */
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_DATA, 1, &sum); if (ret < 0) goto done;
msg->result = ret;
ret = cros_ec_check_result(ec, msg); if (ret) goto done;
/* Read back response */
dout = (u8 *)&response;
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_PACKET, sizeof(response),
dout); if (ret < 0) goto done;
sum = ret;
msg->result = response.result;
if (response.data_len > msg->insize) {
dev_err(ec->dev, "packet too long (%d bytes, expected %d)",
response.data_len, msg->insize);
ret = -EMSGSIZE; goto done;
}
/* Read response and process checksum */
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_HOST_PACKET + sizeof(response), response.data_len,
msg->data); if (ret < 0) goto done;
sum += ret;
if (sum) {
dev_err(ec->dev, "bad packet checksum %02x\n",
response.checksum);
ret = -EBADMSG; goto done;
}
/* Return actual amount of data received */
ret = response.data_len;
done: return ret;
}
/* Now actually send the command to the EC and get the result */
args.flags = EC_HOST_ARGS_FLAG_FROM_HOST;
args.command_version = msg->version;
args.data_size = msg->outsize;
/* Return actual amount of data received */
ret = args.data_size;
done: return ret;
}
/* Returns num bytes read, or negative on error. Doesn't need locking. */ staticint cros_ec_lpc_readmem(struct cros_ec_device *ec, unsignedint offset, unsignedint bytes, void *dest)
{ struct cros_ec_lpc *ec_lpc = ec->priv; int i = offset; char *s = dest; int cnt = 0; int ret;
if (offset >= EC_MEMMAP_SIZE - bytes) return -EINVAL;
/* fixed length */ if (bytes) {
ret = ec_lpc->read(ec_lpc, ec_lpc->mmio_memory_base + offset, bytes, s); if (ret < 0) return ret; return bytes;
}
/* string */ for (; i < EC_MEMMAP_SIZE; i++, s++) {
ret = ec_lpc->read(ec_lpc, ec_lpc->mmio_memory_base + i, 1, s); if (ret < 0) return ret;
cnt++; if (!*s) break;
}
if (value == ACPI_NOTIFY_CROS_EC_PANIC) {
dev_emerg(ec_dev->dev, "CrOS EC Panic Reported. Shutdown is imminent!");
blocking_notifier_call_chain(&ec_dev->panic_notifier, 0, ec_dev);
kobject_uevent_env(&ec_dev->dev->kobj, KOBJ_CHANGE, (char **)env); /* Begin orderly shutdown. EC will force reset after a short period. */
__hw_protection_trigger("CrOS EC Panic", -1, HWPROT_ACT_SHUTDOWN); /* Do not query for other events after a panic is reported */ return;
}
if (value == ACPI_NOTIFY_CROS_EC_MKBP && ec_dev->mkbp_event_supported) do {
ret = cros_ec_get_next_event(ec_dev, NULL,
&ec_has_more_events); if (ret > 0)
blocking_notifier_call_chain(
&ec_dev->event_notifier, 0,
ec_dev);
} while (ec_has_more_events);
if (value == ACPI_NOTIFY_DEVICE_WAKE)
pm_system_wakeup();
}
ec_lpc = devm_kzalloc(dev, sizeof(*ec_lpc), GFP_KERNEL); if (!ec_lpc) return -ENOMEM;
ec_lpc->mmio_memory_base = EC_LPC_ADDR_MEMMAP;
driver_data = platform_get_drvdata(pdev); if (!driver_data)
driver_data = acpi_device_get_match_data(dev);
if (driver_data) {
quirks = driver_data->quirks;
if (quirks)
dev_info(dev, "loaded with quirks %8.08x\n", quirks);
if (quirks & CROS_EC_LPC_QUIRK_REMAP_MEMORY)
ec_lpc->mmio_memory_base = driver_data->quirk_mmio_memory_base;
if (quirks & CROS_EC_LPC_QUIRK_ACPI_ID) {
adev = cros_ec_lpc_get_device(driver_data->quirk_acpi_id); if (!adev) {
dev_err(dev, "failed to get ACPI device '%s'",
driver_data->quirk_acpi_id); return -ENODEV;
}
ACPI_COMPANION_SET(dev, adev);
}
if (quirks & CROS_EC_LPC_QUIRK_AML_MUTEX) { constchar *name = driver_data->quirk_aml_mutex_name;
ret = cros_ec_lpc_mec_acpi_mutex(ACPI_COMPANION(dev), name); if (ret) {
dev_err(dev, "failed to get AML mutex '%s'", name); return ret;
}
dev_info(dev, "got AML mutex '%s'", name);
}
}
adev = ACPI_COMPANION(dev); if (adev) { /* * Retrieve the resource information in the CRS register, if available.
*/
status = acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
cros_ec_lpc_resources, ec_lpc); if (ACPI_SUCCESS(status) && ec_lpc->mem32.address_length) {
ec_lpc->base = devm_ioremap(dev,
ec_lpc->mem32.address,
ec_lpc->mem32.address_length); if (!ec_lpc->base) return -EINVAL;
ec_lpc->read = cros_ec_lpc_direct_read;
ec_lpc->write = cros_ec_lpc_direct_write;
}
} if (!ec_lpc->read) { /* * The Framework Laptop (and possibly other non-ChromeOS devices) * only exposes the eight I/O ports that are required for the Microchip EC. * Requesting a larger reservation will fail.
*/ if (!devm_request_region(dev, EC_HOST_CMD_REGION0,
EC_HOST_CMD_MEC_REGION_SIZE, dev_name(dev))) {
dev_err(dev, "couldn't reserve MEC region\n"); return -EBUSY;
}
/* * Read the mapped ID twice, the first one is assuming the * EC is a Microchip Embedded Controller (MEC) variant, if the * protocol fails, fallback to the non MEC variant and try to * read again the ID.
*/
ec_lpc->read = cros_ec_lpc_mec_read_bytes;
ec_lpc->write = cros_ec_lpc_mec_write_bytes;
}
ret = ec_lpc->read(ec_lpc, EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf); if (ret < 0) return ret; if (buf[0] != 'E' || buf[1] != 'C') { if (!devm_request_region(dev, ec_lpc->mmio_memory_base, EC_MEMMAP_SIZE,
dev_name(dev))) {
dev_err(dev, "couldn't reserve memmap region\n"); return -EBUSY;
}
/* Re-assign read/write operations for the non MEC variant */
ec_lpc->read = cros_ec_lpc_read_bytes;
ec_lpc->write = cros_ec_lpc_write_bytes;
ret = ec_lpc->read(ec_lpc, ec_lpc->mmio_memory_base + EC_MEMMAP_ID, 2,
buf); if (ret < 0) return ret; if (buf[0] != 'E' || buf[1] != 'C') {
dev_err(dev, "EC ID not detected\n"); return -ENODEV;
}
/* Reserve the remaining I/O ports required by the non-MEC protocol. */ if (!devm_request_region(dev, EC_HOST_CMD_REGION0 + EC_HOST_CMD_MEC_REGION_SIZE,
EC_HOST_CMD_REGION_SIZE - EC_HOST_CMD_MEC_REGION_SIZE,
dev_name(dev))) {
dev_err(dev, "couldn't reserve remainder of region0\n"); return -EBUSY;
} if (!devm_request_region(dev, EC_HOST_CMD_REGION1,
EC_HOST_CMD_REGION_SIZE, dev_name(dev))) {
dev_err(dev, "couldn't reserve region1\n"); return -EBUSY;
}
}
ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); if (!ec_dev) return -ENOMEM;
/* * Some boards do not have an IRQ allotted for cros_ec_lpc, * which makes ENXIO an expected (and safe) scenario.
*/
irq = platform_get_irq_optional(pdev, 0); if (irq > 0)
ec_dev->irq = irq; elseif (irq != -ENXIO) {
dev_err(dev, "couldn't retrieve IRQ number (%d)\n", irq); return irq;
}
ret = cros_ec_register(ec_dev); if (ret) {
dev_err(dev, "couldn't register ec_dev (%d)\n", ret); return ret;
}
/* * Connect a notify handler to process MKBP messages if we have a * companion ACPI device.
*/ if (adev) {
status = acpi_install_notify_handler(adev->handle,
ACPI_ALL_NOTIFY,
cros_ec_lpc_acpi_notify,
ec_dev); if (ACPI_FAILURE(status))
dev_warn(dev, "Failed to register notifier %08x\n",
status);
}
/* Register the driver */
ret = platform_driver_register(&cros_ec_lpc_driver); if (ret) {
pr_err(DRV_NAME ": can't register driver: %d\n", ret); return ret;
}
if (!cros_ec_lpc_acpi_device_found) { /* Pass the DMI match's driver data down to the platform device */
platform_set_drvdata(&cros_ec_lpc_device, dmi_match->driver_data);
/* Register the device, and it'll get hooked up automatically */
ret = platform_device_register(&cros_ec_lpc_device); if (ret) {
pr_err(DRV_NAME ": can't register device: %d\n", ret);
platform_driver_unregister(&cros_ec_lpc_driver);
}
}
return ret;
}
staticvoid __exit cros_ec_lpc_exit(void)
{ if (!cros_ec_lpc_acpi_device_found)
platform_device_unregister(&cros_ec_lpc_device);
platform_driver_unregister(&cros_ec_lpc_driver);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.