Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
mariux64
/
linux
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
2
Pull requests
0
Actions
Projects
0
Wiki
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Wiki
Security
Insights
Files
2cbf475
Documentation
LICENSES
arch
block
certs
crypto
drivers
accel
accessibility
acpi
amba
android
ata
atm
auxdisplay
base
bcma
block
bluetooth
bus
cdrom
cdx
char
clk
clocksource
comedi
connector
counter
cpufreq
cpuidle
crypto
cxl
dax
dca
devfreq
dio
dma-buf
dma
edac
eisa
extcon
firewire
firmware
fpga
fsi
gnss
gpio
gpu
greybus
hid
hsi
hte
hv
hwmon
hwspinlock
hwtracing
i2c
i3c
idle
iio
infiniband
input
interconnect
iommu
ipack
irqchip
isdn
leds
macintosh
mailbox
mcb
md
media
memory
memstick
message
mfd
misc
mmc
most
mtd
mux
net
nfc
ntb
nubus
nvdimm
nvme
nvmem
of
opp
parisc
parport
pci
pcmcia
peci
perf
phy
pinctrl
platform
chrome
wilco_ec
Kconfig
Makefile
chromeos_acpi.c
chromeos_laptop.c
chromeos_privacy_screen.c
chromeos_pstore.c
chromeos_tbmc.c
cros_ec.c
cros_ec.h
cros_ec_chardev.c
cros_ec_debugfs.c
cros_ec_i2c.c
cros_ec_ishtp.c
cros_ec_lightbar.c
cros_ec_lpc.c
cros_ec_lpc_mec.c
cros_ec_lpc_mec.h
cros_ec_proto.c
cros_ec_proto_test.c
cros_ec_rpmsg.c
cros_ec_sensorhub.c
cros_ec_sensorhub_ring.c
cros_ec_sensorhub_trace.h
cros_ec_spi.c
cros_ec_sysfs.c
cros_ec_trace.c
cros_ec_trace.h
cros_ec_typec.c
cros_ec_typec.h
cros_ec_uart.c
cros_ec_vbc.c
cros_hps_i2c.c
cros_kbd_led_backlight.c
cros_kunit_util.c
cros_kunit_util.h
cros_typec_switch.c
cros_typec_vdm.c
cros_typec_vdm.h
cros_usbpd_logger.c
cros_usbpd_notify.c
goldfish
loongarch
mellanox
mips
olpc
surface
x86
Kconfig
Makefile
pnp
power
powercap
pps
ps3
ptp
pwm
rapidio
ras
regulator
remoteproc
reset
rpmsg
rtc
s390
sbus
scsi
sh
siox
slimbus
soc
soundwire
spi
spmi
ssb
staging
target
tc
tee
thermal
thunderbolt
tty
ufs
uio
usb
vdpa
vfio
vhost
video
virt
virtio
vlynq
w1
watchdog
xen
zorro
Kconfig
Makefile
fs
include
init
io_uring
ipc
kernel
lib
mm
net
rust
samples
scripts
security
sound
tools
usr
virt
.clang-format
.cocciconfig
.get_maintainer.ignore
.gitattributes
.gitignore
.mailmap
.rustfmt.toml
COPYING
CREDITS
Kbuild
Kconfig
MAINTAINERS
Makefile
README
Breadcrumbs
linux
/
drivers
/
platform
/
chrome
/
cros_ec_lpc.c
Copy path
Blame
Blame
Latest commit
History
History
641 lines (548 loc) · 16.2 KB
Breadcrumbs
linux
/
drivers
/
platform
/
chrome
/
cros_ec_lpc.c
Top
File metadata and controls
Code
Blame
641 lines (548 loc) · 16.2 KB
Raw
// 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. #include <linux/acpi.h> #include <linux/dmi.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/interrupt.h> #include <linux/kobject.h> #include <linux/module.h> #include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_device.h> #include <linux/printk.h> #include <linux/reboot.h> #include <linux/suspend.h> #include "cros_ec.h" #include "cros_ec_lpc_mec.h" #define DRV_NAME "cros_ec_lpcs" #define ACPI_DRV_NAME "GOOG0004" /* True if ACPI device is present */ static bool cros_ec_lpc_acpi_device_found; /** * struct lpc_driver_ops - LPC driver operations * @read: Copy length bytes from EC address offset into buffer dest. Returns * the 8-bit checksum of all bytes read. * @write: Copy length bytes from buffer msg into EC address offset. Returns * the 8-bit checksum of all bytes written. */ struct lpc_driver_ops { u8 (*read)(unsigned int offset, unsigned int length, u8 *dest); u8 (*write)(unsigned int offset, unsigned int length, const u8 *msg); }; static struct lpc_driver_ops cros_ec_lpc_ops = { }; /* * A generic instance of the read function of struct lpc_driver_ops, used for * the LPC EC. */ static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) { int 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. */ static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, const u8 *msg) { int 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. */ static u8 cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length, u8 *dest) { int in_range = cros_ec_lpc_mec_in_range(offset, length); if (in_range < 0) return 0; return in_range ? cros_ec_lpc_io_bytes_mec(MEC_IO_READ, offset - EC_HOST_CMD_REGION0, length, dest) : cros_ec_lpc_read_bytes(offset, length, dest); } /* * An instance of the write function of struct lpc_driver_ops, used for the * MEC variant of LPC EC. */ static u8 cros_ec_lpc_mec_write_bytes(unsigned int offset, unsigned int length, const u8 *msg) { int in_range = cros_ec_lpc_mec_in_range(offset, length); if (in_range < 0) return 0; return in_range ? cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, offset - EC_HOST_CMD_REGION0, length, (u8 *)msg) : cros_ec_lpc_write_bytes(offset, length, msg); } static int ec_response_timed_out(void) { unsigned long one_second = jiffies + HZ; u8 data; usleep_range(200, 300); do { if (!(cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_CMD, 1, &data) & EC_LPC_STATUS_BUSY_MASK)) return 0; usleep_range(100, 200); } while (time_before(jiffies, one_second)); return 1; } static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, struct cros_ec_command *msg) { struct ec_host_response response; u8 sum; int ret = 0; u8 *dout; ret = cros_ec_prepare_tx(ec, msg); if (ret < 0) goto done; /* Write buffer */ cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout); /* Here we go */ sum = EC_COMMAND_PROTOCOL_3; cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum); if (ec_response_timed_out()) { dev_warn(ec->dev, "EC response timed out\n"); ret = -EIO; goto done; } /* Check result */ msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum); ret = cros_ec_check_result(ec, msg); if (ret) goto done; /* Read back response */ dout = (u8 *)&response; sum = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response), dout); 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 */ sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET + sizeof(response), response.data_len, msg->data); 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; } static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, struct cros_ec_command *msg) { struct ec_lpc_host_args args; u8 sum; int ret = 0; if (msg->outsize > EC_PROTO2_MAX_PARAM_SIZE || msg->insize > EC_PROTO2_MAX_PARAM_SIZE) { dev_err(ec->dev, "invalid buffer sizes (out %d, in %d)\n", msg->outsize, msg->insize); return -EINVAL; } /* 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; /* Initialize checksum */ sum = msg->command + args.flags + args.command_version + args.data_size; /* Copy data and update checksum */ sum += cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize, msg->data); /* Finalize checksum and write args */ args.checksum = sum; cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args); /* Here we go */ sum = msg->command; cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum); if (ec_response_timed_out()) { dev_warn(ec->dev, "EC response timed out\n"); ret = -EIO; goto done; } /* Check result */ msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum); ret = cros_ec_check_result(ec, msg); if (ret) goto done; /* Read back args */ cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args); if (args.data_size > msg->insize) { dev_err(ec->dev, "packet too long (%d bytes, expected %d)", args.data_size, msg->insize); ret = -ENOSPC; goto done; } /* Start calculating response checksum */ sum = msg->command + args.flags + args.command_version + args.data_size; /* Read response and update checksum */ sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size, msg->data); /* Verify checksum */ if (args.checksum != sum) { dev_err(ec->dev, "bad packet checksum, expected %02x, got %02x\n", args.checksum, sum); ret = -EBADMSG; goto done; } /* 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. */ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, unsigned int bytes, void *dest) { int i = offset; char *s = dest; int cnt = 0; if (offset >= EC_MEMMAP_SIZE - bytes) return -EINVAL; /* fixed length */ if (bytes) { cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + offset, bytes, s); return bytes; } /* string */ for (; i < EC_MEMMAP_SIZE; i++, s++) { cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + i, 1, s); cnt++; if (!*s) break; } return cnt; } static void cros_ec_lpc_acpi_notify(acpi_handle device, u32 value, void *data) { static const char *env[] = { "ERROR=PANIC", NULL }; struct cros_ec_device *ec_dev = data; bool ec_has_more_events; int ret; ec_dev->last_event_time = cros_ec_get_time_ns(); 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. Force shutdown after 1 second. */ hw_protection_shutdown("CrOS EC Panic", 1000); /* Do not query for other events after a panic is reported */ return; } if (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(); } static int cros_ec_lpc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct acpi_device *adev; acpi_status status; struct cros_ec_device *ec_dev; u8 buf[2] = {}; int irq, ret; /* * 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; } cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0, EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE); /* * 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. */ cros_ec_lpc_ops.read = cros_ec_lpc_mec_read_bytes; cros_ec_lpc_ops.write = cros_ec_lpc_mec_write_bytes; cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf); if (buf[0] != 'E' || buf[1] != 'C') { if (!devm_request_region(dev, EC_LPC_ADDR_MEMMAP, 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 */ cros_ec_lpc_ops.read = cros_ec_lpc_read_bytes; cros_ec_lpc_ops.write = cros_ec_lpc_write_bytes; cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf); 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; platform_set_drvdata(pdev, ec_dev); ec_dev->dev = dev; ec_dev->phys_name = dev_name(dev); ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; ec_dev->pkt_xfer = cros_ec_pkt_xfer_lpc; ec_dev->cmd_readmem = cros_ec_lpc_readmem; ec_dev->din_size = sizeof(struct ec_host_response) + sizeof(struct ec_response_get_protocol_info); ec_dev->dout_size = sizeof(struct ec_host_request); /* * 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; else if (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. */ adev = ACPI_COMPANION(dev); 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); } return 0; } static int cros_ec_lpc_remove(struct platform_device *pdev) { struct cros_ec_device *ec_dev = platform_get_drvdata(pdev); struct acpi_device *adev; adev = ACPI_COMPANION(&pdev->dev); if (adev) acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY, cros_ec_lpc_acpi_notify); cros_ec_unregister(ec_dev); return 0; } static const struct acpi_device_id cros_ec_lpc_acpi_device_ids[] = { { ACPI_DRV_NAME, 0 }, { } }; MODULE_DEVICE_TABLE(acpi, cros_ec_lpc_acpi_device_ids); static const struct dmi_system_id cros_ec_lpc_dmi_table[] __initconst = { { /* * Today all Chromebooks/boxes ship with Google_* as version and * coreboot as bios vendor. No other systems with this * combination are known to date. */ .matches = { DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), DMI_MATCH(DMI_BIOS_VERSION, "Google_"), }, }, { /* * If the box is running custom coreboot firmware then the * DMI BIOS version string will not be matched by "Google_", * but the system vendor string will still be matched by * "GOOGLE". */ .matches = { DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), }, }, { /* x86-link, the Chromebook Pixel. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), DMI_MATCH(DMI_PRODUCT_NAME, "Link"), }, }, { /* x86-samus, the Chromebook Pixel 2. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), DMI_MATCH(DMI_PRODUCT_NAME, "Samus"), }, }, { /* x86-peppy, the Acer C720 Chromebook. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"), }, }, { /* x86-glimmer, the Lenovo Thinkpad Yoga 11e. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), DMI_MATCH(DMI_PRODUCT_NAME, "Glimmer"), }, }, /* A small number of non-Chromebook/box machines also use the ChromeOS EC */ { /* the Framework Laptop */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Framework"), DMI_MATCH(DMI_PRODUCT_NAME, "Laptop"), }, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(dmi, cros_ec_lpc_dmi_table); #ifdef CONFIG_PM_SLEEP static int cros_ec_lpc_suspend(struct device *dev) { struct cros_ec_device *ec_dev = dev_get_drvdata(dev); return cros_ec_suspend(ec_dev); } static int cros_ec_lpc_resume(struct device *dev) { struct cros_ec_device *ec_dev = dev_get_drvdata(dev); return cros_ec_resume(ec_dev); } #endif static const struct dev_pm_ops cros_ec_lpc_pm_ops = { SET_LATE_SYSTEM_SLEEP_PM_OPS(cros_ec_lpc_suspend, cros_ec_lpc_resume) }; static struct platform_driver cros_ec_lpc_driver = { .driver = { .name = DRV_NAME, .acpi_match_table = cros_ec_lpc_acpi_device_ids, .pm = &cros_ec_lpc_pm_ops, /* * ACPI child devices may probe before us, and they racily * check our drvdata pointer. Force synchronous probe until * those races are resolved. */ .probe_type = PROBE_FORCE_SYNCHRONOUS, }, .probe = cros_ec_lpc_probe, .remove = cros_ec_lpc_remove, }; static struct platform_device cros_ec_lpc_device = { .name = DRV_NAME }; static acpi_status cros_ec_lpc_parse_device(acpi_handle handle, u32 level, void *context, void **retval) { *(bool *)context = true; return AE_CTRL_TERMINATE; } static int __init cros_ec_lpc_init(void) { int ret; acpi_status status; status = acpi_get_devices(ACPI_DRV_NAME, cros_ec_lpc_parse_device, &cros_ec_lpc_acpi_device_found, NULL); if (ACPI_FAILURE(status)) pr_warn(DRV_NAME ": Looking for %s failed\n", ACPI_DRV_NAME); if (!cros_ec_lpc_acpi_device_found && !dmi_check_system(cros_ec_lpc_dmi_table)) { pr_err(DRV_NAME ": unsupported system.\n"); return -ENODEV; } /* 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) { /* 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; } static void __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); } module_init(cros_ec_lpc_init); module_exit(cros_ec_lpc_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("ChromeOS EC LPC driver");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
You can’t perform that action at this time.