Commit 4ab6174e8cdb007cf500e484bdf454b8d14d524a
Committed by
Samuel Ortiz
1 parent
deaf39efbc
Exists in
smarc-l5.0.0_1.0.0-ga
and in
5 other branches
mfd: Add ChromeOS EC implementation
This is the base EC implementation, which provides a high level interface to the EC for use by the rest of the kernel. The actual communcations is dealt with by a separate protocol driver which registers itself with this interface. Interrupts are passed on through a notifier. A simple message structure is used to pass messages to the protocol driver. Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Che-Liang Chiou <clchiou@chromium.org> Signed-off-by: Jonathan Kliegman <kliegs@chromium.org> Signed-off-by: Luigi Semenzato <semenzato@chromium.org> Signed-off-by: Olof Johansson <olofj@chromium.org> Signed-off-by: Vincent Palatin <vpalatin@chromium.org> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Showing 5 changed files with 424 additions and 0 deletions Side-by-side Diff
Documentation/devicetree/bindings/mfd/cros-ec.txt
1 | +ChromeOS Embedded Controller | |
2 | + | |
3 | +Google's ChromeOS EC is a Cortex-M device which talks to the AP and | |
4 | +implements various function such as keyboard and battery charging. | |
5 | + | |
6 | +The EC can be connect through various means (I2C, SPI, LPC) and the | |
7 | +compatible string used depends on the inteface. Each connection method has | |
8 | +its own driver which connects to the top level interface-agnostic EC driver. | |
9 | +Other Linux driver (such as cros-ec-keyb for the matrix keyboard) connect to | |
10 | +the top-level driver. | |
11 | + | |
12 | +Required properties (I2C): | |
13 | +- compatible: "google,cros-ec-i2c" | |
14 | +- reg: I2C slave address | |
15 | + | |
16 | +Required properties (SPI): | |
17 | +- compatible: "google,cros-ec-spi" | |
18 | +- reg: SPI chip select | |
19 | + | |
20 | +Required properties (LPC): | |
21 | +- compatible: "google,cros-ec-lpc" | |
22 | +- reg: List of (IO address, size) pairs defining the interface uses | |
23 | + | |
24 | + | |
25 | +Example for I2C: | |
26 | + | |
27 | +i2c@12CA0000 { | |
28 | + cros-ec@1e { | |
29 | + reg = <0x1e>; | |
30 | + compatible = "google,cros-ec-i2c"; | |
31 | + interrupts = <14 0>; | |
32 | + interrupt-parent = <&wakeup_eint>; | |
33 | + wakeup-source; | |
34 | + }; | |
35 | + | |
36 | + | |
37 | +Example for SPI: | |
38 | + | |
39 | +spi@131b0000 { | |
40 | + ec@0 { | |
41 | + compatible = "google,cros-ec-spi"; | |
42 | + reg = <0x0>; | |
43 | + interrupts = <14 0>; | |
44 | + interrupt-parent = <&wakeup_eint>; | |
45 | + wakeup-source; | |
46 | + spi-max-frequency = <5000000>; | |
47 | + controller-data { | |
48 | + cs-gpio = <&gpf0 3 4 3 0>; | |
49 | + samsung,spi-cs; | |
50 | + samsung,spi-feedback-delay = <2>; | |
51 | + }; | |
52 | + }; | |
53 | +}; | |
54 | + | |
55 | + | |
56 | +Example for LPC is not supplied as it is not yet implemented. |
drivers/mfd/Kconfig
... | ... | @@ -21,6 +21,14 @@ |
21 | 21 | select individual components like voltage regulators, RTC and |
22 | 22 | battery-charger under the corresponding menus. |
23 | 23 | |
24 | +config MFD_CROS_EC | |
25 | + tristate "Support ChromeOS Embedded Controller" | |
26 | + help | |
27 | + If you say Y here you get support for the ChromeOS Embedded | |
28 | + Controller (EC) providing keyboard, battery and power services. | |
29 | + You also ned to enable the driver for the bus you are using. The | |
30 | + protocol for talking to the EC is defined by the bus driver. | |
31 | + | |
24 | 32 | config MFD_88PM800 |
25 | 33 | tristate "Support Marvell 88PM800" |
26 | 34 | depends on I2C=y && GENERIC_HARDIRQS |
drivers/mfd/Makefile
... | ... | @@ -8,6 +8,7 @@ |
8 | 8 | obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o |
9 | 9 | obj-$(CONFIG_MFD_SM501) += sm501.o |
10 | 10 | obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o |
11 | +obj-$(CONFIG_MFD_CROS_EC) += cros_ec.o | |
11 | 12 | |
12 | 13 | rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o |
13 | 14 | obj-$(CONFIG_MFD_RTSX_PCI) += rtsx_pci.o |
drivers/mfd/cros_ec.c
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/mfd/core.h> | |
23 | +#include <linux/mfd/cros_ec.h> | |
24 | +#include <linux/mfd/cros_ec_commands.h> | |
25 | + | |
26 | +int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, | |
27 | + struct cros_ec_msg *msg) | |
28 | +{ | |
29 | + uint8_t *out; | |
30 | + int csum, i; | |
31 | + | |
32 | + BUG_ON(msg->out_len > EC_HOST_PARAM_SIZE); | |
33 | + out = ec_dev->dout; | |
34 | + out[0] = EC_CMD_VERSION0 + msg->version; | |
35 | + out[1] = msg->cmd; | |
36 | + out[2] = msg->out_len; | |
37 | + csum = out[0] + out[1] + out[2]; | |
38 | + for (i = 0; i < msg->out_len; i++) | |
39 | + csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->out_buf[i]; | |
40 | + out[EC_MSG_TX_HEADER_BYTES + msg->out_len] = (uint8_t)(csum & 0xff); | |
41 | + | |
42 | + return EC_MSG_TX_PROTO_BYTES + msg->out_len; | |
43 | +} | |
44 | + | |
45 | +static int cros_ec_command_sendrecv(struct cros_ec_device *ec_dev, | |
46 | + uint16_t cmd, void *out_buf, int out_len, | |
47 | + void *in_buf, int in_len) | |
48 | +{ | |
49 | + struct cros_ec_msg msg; | |
50 | + | |
51 | + msg.version = cmd >> 8; | |
52 | + msg.cmd = cmd & 0xff; | |
53 | + msg.out_buf = out_buf; | |
54 | + msg.out_len = out_len; | |
55 | + msg.in_buf = in_buf; | |
56 | + msg.in_len = in_len; | |
57 | + | |
58 | + return ec_dev->command_xfer(ec_dev, &msg); | |
59 | +} | |
60 | + | |
61 | +static int cros_ec_command_recv(struct cros_ec_device *ec_dev, | |
62 | + uint16_t cmd, void *buf, int buf_len) | |
63 | +{ | |
64 | + return cros_ec_command_sendrecv(ec_dev, cmd, NULL, 0, buf, buf_len); | |
65 | +} | |
66 | + | |
67 | +static int cros_ec_command_send(struct cros_ec_device *ec_dev, | |
68 | + uint16_t cmd, void *buf, int buf_len) | |
69 | +{ | |
70 | + return cros_ec_command_sendrecv(ec_dev, cmd, buf, buf_len, NULL, 0); | |
71 | +} | |
72 | + | |
73 | +static irqreturn_t ec_irq_thread(int irq, void *data) | |
74 | +{ | |
75 | + struct cros_ec_device *ec_dev = data; | |
76 | + | |
77 | + if (device_may_wakeup(ec_dev->dev)) | |
78 | + pm_wakeup_event(ec_dev->dev, 0); | |
79 | + | |
80 | + blocking_notifier_call_chain(&ec_dev->event_notifier, 1, ec_dev); | |
81 | + | |
82 | + return IRQ_HANDLED; | |
83 | +} | |
84 | + | |
85 | +static struct mfd_cell cros_devs[] = { | |
86 | + { | |
87 | + .name = "cros-ec-keyb", | |
88 | + .id = 1, | |
89 | + .of_compatible = "google,cros-ec-keyb", | |
90 | + }, | |
91 | +}; | |
92 | + | |
93 | +int cros_ec_register(struct cros_ec_device *ec_dev) | |
94 | +{ | |
95 | + struct device *dev = ec_dev->dev; | |
96 | + int err = 0; | |
97 | + | |
98 | + BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier); | |
99 | + | |
100 | + ec_dev->command_send = cros_ec_command_send; | |
101 | + ec_dev->command_recv = cros_ec_command_recv; | |
102 | + ec_dev->command_sendrecv = cros_ec_command_sendrecv; | |
103 | + | |
104 | + if (ec_dev->din_size) { | |
105 | + ec_dev->din = kmalloc(ec_dev->din_size, GFP_KERNEL); | |
106 | + if (!ec_dev->din) { | |
107 | + err = -ENOMEM; | |
108 | + goto fail_din; | |
109 | + } | |
110 | + } | |
111 | + if (ec_dev->dout_size) { | |
112 | + ec_dev->dout = kmalloc(ec_dev->dout_size, GFP_KERNEL); | |
113 | + if (!ec_dev->dout) { | |
114 | + err = -ENOMEM; | |
115 | + goto fail_dout; | |
116 | + } | |
117 | + } | |
118 | + | |
119 | + if (!ec_dev->irq) { | |
120 | + dev_dbg(dev, "no valid IRQ: %d\n", ec_dev->irq); | |
121 | + goto fail_irq; | |
122 | + } | |
123 | + | |
124 | + err = request_threaded_irq(ec_dev->irq, NULL, ec_irq_thread, | |
125 | + IRQF_TRIGGER_LOW | IRQF_ONESHOT, | |
126 | + "chromeos-ec", ec_dev); | |
127 | + if (err) { | |
128 | + dev_err(dev, "request irq %d: error %d\n", ec_dev->irq, err); | |
129 | + goto fail_irq; | |
130 | + } | |
131 | + | |
132 | + err = mfd_add_devices(dev, 0, cros_devs, | |
133 | + ARRAY_SIZE(cros_devs), | |
134 | + NULL, ec_dev->irq, NULL); | |
135 | + if (err) { | |
136 | + dev_err(dev, "failed to add mfd devices\n"); | |
137 | + goto fail_mfd; | |
138 | + } | |
139 | + | |
140 | + dev_info(dev, "Chrome EC (%s)\n", ec_dev->name); | |
141 | + | |
142 | + return 0; | |
143 | + | |
144 | +fail_mfd: | |
145 | + free_irq(ec_dev->irq, ec_dev); | |
146 | +fail_irq: | |
147 | + kfree(ec_dev->dout); | |
148 | +fail_dout: | |
149 | + kfree(ec_dev->din); | |
150 | +fail_din: | |
151 | + return err; | |
152 | +} | |
153 | + | |
154 | +int cros_ec_remove(struct cros_ec_device *ec_dev) | |
155 | +{ | |
156 | + mfd_remove_devices(ec_dev->dev); | |
157 | + free_irq(ec_dev->irq, ec_dev); | |
158 | + kfree(ec_dev->dout); | |
159 | + kfree(ec_dev->din); | |
160 | + | |
161 | + return 0; | |
162 | +} | |
163 | + | |
164 | +#ifdef CONFIG_PM_SLEEP | |
165 | +int cros_ec_suspend(struct cros_ec_device *ec_dev) | |
166 | +{ | |
167 | + struct device *dev = ec_dev->dev; | |
168 | + | |
169 | + if (device_may_wakeup(dev)) | |
170 | + ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq); | |
171 | + | |
172 | + disable_irq(ec_dev->irq); | |
173 | + ec_dev->was_wake_device = ec_dev->wake_enabled; | |
174 | + | |
175 | + return 0; | |
176 | +} | |
177 | + | |
178 | +int cros_ec_resume(struct cros_ec_device *ec_dev) | |
179 | +{ | |
180 | + enable_irq(ec_dev->irq); | |
181 | + | |
182 | + if (ec_dev->wake_enabled) { | |
183 | + disable_irq_wake(ec_dev->irq); | |
184 | + ec_dev->wake_enabled = 0; | |
185 | + } | |
186 | + | |
187 | + return 0; | |
188 | +} | |
189 | +#endif |
include/linux/mfd/cros_ec.h
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 | + | |
16 | +#ifndef __LINUX_MFD_CROS_EC_H | |
17 | +#define __LINUX_MFD_CROS_EC_H | |
18 | + | |
19 | +#include <linux/mfd/cros_ec_commands.h> | |
20 | + | |
21 | +/* | |
22 | + * Command interface between EC and AP, for LPC, I2C and SPI interfaces. | |
23 | + */ | |
24 | +enum { | |
25 | + EC_MSG_TX_HEADER_BYTES = 3, | |
26 | + EC_MSG_TX_TRAILER_BYTES = 1, | |
27 | + EC_MSG_TX_PROTO_BYTES = EC_MSG_TX_HEADER_BYTES + | |
28 | + EC_MSG_TX_TRAILER_BYTES, | |
29 | + EC_MSG_RX_PROTO_BYTES = 3, | |
30 | + | |
31 | + /* Max length of messages */ | |
32 | + EC_MSG_BYTES = EC_HOST_PARAM_SIZE + EC_MSG_TX_PROTO_BYTES, | |
33 | + | |
34 | +}; | |
35 | + | |
36 | +/** | |
37 | + * struct cros_ec_msg - A message sent to the EC, and its reply | |
38 | + * | |
39 | + * @version: Command version number (often 0) | |
40 | + * @cmd: Command to send (EC_CMD_...) | |
41 | + * @out_buf: Outgoing payload (to EC) | |
42 | + * @outlen: Outgoing length | |
43 | + * @in_buf: Incoming payload (from EC) | |
44 | + * @in_len: Incoming length | |
45 | + */ | |
46 | +struct cros_ec_msg { | |
47 | + u8 version; | |
48 | + u8 cmd; | |
49 | + uint8_t *out_buf; | |
50 | + int out_len; | |
51 | + uint8_t *in_buf; | |
52 | + int in_len; | |
53 | +}; | |
54 | + | |
55 | +/** | |
56 | + * struct cros_ec_device - Information about a ChromeOS EC device | |
57 | + * | |
58 | + * @name: Name of this EC interface | |
59 | + * @priv: Private data | |
60 | + * @irq: Interrupt to use | |
61 | + * @din: input buffer (from EC) | |
62 | + * @dout: output buffer (to EC) | |
63 | + * \note | |
64 | + * These two buffers will always be dword-aligned and include enough | |
65 | + * space for up to 7 word-alignment bytes also, so we can ensure that | |
66 | + * the body of the message is always dword-aligned (64-bit). | |
67 | + * | |
68 | + * We use this alignment to keep ARM and x86 happy. Probably word | |
69 | + * alignment would be OK, there might be a small performance advantage | |
70 | + * to using dword. | |
71 | + * @din_size: size of din buffer | |
72 | + * @dout_size: size of dout buffer | |
73 | + * @command_send: send a command | |
74 | + * @command_recv: receive a command | |
75 | + * @ec_name: name of EC device (e.g. 'chromeos-ec') | |
76 | + * @phys_name: name of physical comms layer (e.g. 'i2c-4') | |
77 | + * @parent: pointer to parent device (e.g. i2c or spi device) | |
78 | + * @dev: Device pointer | |
79 | + * dev_lock: Lock to prevent concurrent access | |
80 | + * @wake_enabled: true if this device can wake the system from sleep | |
81 | + * @was_wake_device: true if this device was set to wake the system from | |
82 | + * sleep at the last suspend | |
83 | + * @event_notifier: interrupt event notifier for transport devices | |
84 | + */ | |
85 | +struct cros_ec_device { | |
86 | + const char *name; | |
87 | + void *priv; | |
88 | + int irq; | |
89 | + uint8_t *din; | |
90 | + uint8_t *dout; | |
91 | + int din_size; | |
92 | + int dout_size; | |
93 | + int (*command_send)(struct cros_ec_device *ec, | |
94 | + uint16_t cmd, void *out_buf, int out_len); | |
95 | + int (*command_recv)(struct cros_ec_device *ec, | |
96 | + uint16_t cmd, void *in_buf, int in_len); | |
97 | + int (*command_sendrecv)(struct cros_ec_device *ec, | |
98 | + uint16_t cmd, void *out_buf, int out_len, | |
99 | + void *in_buf, int in_len); | |
100 | + int (*command_xfer)(struct cros_ec_device *ec, | |
101 | + struct cros_ec_msg *msg); | |
102 | + | |
103 | + const char *ec_name; | |
104 | + const char *phys_name; | |
105 | + struct device *parent; | |
106 | + | |
107 | + /* These are --private-- fields - do not assign */ | |
108 | + struct device *dev; | |
109 | + struct mutex dev_lock; | |
110 | + bool wake_enabled; | |
111 | + bool was_wake_device; | |
112 | + struct blocking_notifier_head event_notifier; | |
113 | +}; | |
114 | + | |
115 | +/** | |
116 | + * cros_ec_suspend - Handle a suspend operation for the ChromeOS EC device | |
117 | + * | |
118 | + * This can be called by drivers to handle a suspend event. | |
119 | + * | |
120 | + * ec_dev: Device to suspend | |
121 | + * @return 0 if ok, -ve on error | |
122 | + */ | |
123 | +int cros_ec_suspend(struct cros_ec_device *ec_dev); | |
124 | + | |
125 | +/** | |
126 | + * cros_ec_resume - Handle a resume operation for the ChromeOS EC device | |
127 | + * | |
128 | + * This can be called by drivers to handle a resume event. | |
129 | + * | |
130 | + * @ec_dev: Device to resume | |
131 | + * @return 0 if ok, -ve on error | |
132 | + */ | |
133 | +int cros_ec_resume(struct cros_ec_device *ec_dev); | |
134 | + | |
135 | +/** | |
136 | + * cros_ec_prepare_tx - Prepare an outgoing message in the output buffer | |
137 | + * | |
138 | + * This is intended to be used by all ChromeOS EC drivers, but at present | |
139 | + * only SPI uses it. Once LPC uses the same protocol it can start using it. | |
140 | + * I2C could use it now, with a refactor of the existing code. | |
141 | + * | |
142 | + * @ec_dev: Device to register | |
143 | + * @msg: Message to write | |
144 | + */ | |
145 | +int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, | |
146 | + struct cros_ec_msg *msg); | |
147 | + | |
148 | +/** | |
149 | + * cros_ec_remove - Remove a ChromeOS EC | |
150 | + * | |
151 | + * Call this to deregister a ChromeOS EC. After this you should call | |
152 | + * cros_ec_free(). | |
153 | + * | |
154 | + * @ec_dev: Device to register | |
155 | + * @return 0 if ok, -ve on error | |
156 | + */ | |
157 | +int cros_ec_remove(struct cros_ec_device *ec_dev); | |
158 | + | |
159 | +/** | |
160 | + * cros_ec_register - Register a new ChromeOS EC, using the provided info | |
161 | + * | |
162 | + * Before calling this, allocate a pointer to a new device and then fill | |
163 | + * in all the fields up to the --private-- marker. | |
164 | + * | |
165 | + * @ec_dev: Device to register | |
166 | + * @return 0 if ok, -ve on error | |
167 | + */ | |
168 | +int cros_ec_register(struct cros_ec_device *ec_dev); | |
169 | + | |
170 | +#endif /* __LINUX_MFD_CROS_EC_H */ |