diff options
author | Simon Glass <sjg@chromium.org> | 2013-02-25 17:08:38 -0500 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2013-04-05 05:20:13 -0400 |
commit | 89969009485fa9e62814afaa438c12c45d7d2def (patch) | |
tree | 91fda02a31a094727ade6f8bb35aebdd392b9e61 | |
parent | 4ab6174e8cdb007cf500e484bdf454b8d14d524a (diff) |
mfd: Add ChromeOS EC I2C driver
This uses an I2C bus to talk to the ChromeOS EC. The protocol
is defined by the EC and is fairly simple, with a length byte,
checksum, command byte and version byte (to permit easy creation
of new commands).
Signed-off-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Che-Liang Chiou <clchiou@chromium.org>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
-rw-r--r-- | drivers/mfd/Kconfig | 10 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/cros_ec_i2c.c | 201 |
3 files changed, 212 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a8bafb560196..4e54b5b01295 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig | |||
@@ -29,6 +29,16 @@ config MFD_CROS_EC | |||
29 | You also ned to enable the driver for the bus you are using. The | 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. | 30 | protocol for talking to the EC is defined by the bus driver. |
31 | 31 | ||
32 | config MFD_CROS_EC_I2C | ||
33 | tristate "ChromeOS Embedded Controller (I2C)" | ||
34 | depends on MFD_CROS_EC && I2C | ||
35 | |||
36 | help | ||
37 | If you say Y here, you get support for talking to the ChromeOS | ||
38 | EC through an I2C bus. This uses a simple byte-level protocol with | ||
39 | a checksum. Failing accesses will be retried three times to | ||
40 | improve reliability. | ||
41 | |||
32 | config MFD_88PM800 | 42 | config MFD_88PM800 |
33 | tristate "Support Marvell 88PM800" | 43 | tristate "Support Marvell 88PM800" |
34 | depends on I2C=y && GENERIC_HARDIRQS | 44 | depends on I2C=y && GENERIC_HARDIRQS |
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 967767d3d759..895257bec849 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile | |||
@@ -9,6 +9,7 @@ obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o | |||
9 | obj-$(CONFIG_MFD_SM501) += sm501.o | 9 | obj-$(CONFIG_MFD_SM501) += sm501.o |
10 | obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o | 10 | obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o |
11 | obj-$(CONFIG_MFD_CROS_EC) += cros_ec.o | 11 | obj-$(CONFIG_MFD_CROS_EC) += cros_ec.o |
12 | obj-$(CONFIG_MFD_CROS_EC_I2C) += cros_ec_i2c.o | ||
12 | 13 | ||
13 | rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o | 14 | rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o |
14 | obj-$(CONFIG_MFD_RTSX_PCI) += rtsx_pci.o | 15 | obj-$(CONFIG_MFD_RTSX_PCI) += rtsx_pci.o |
diff --git a/drivers/mfd/cros_ec_i2c.c b/drivers/mfd/cros_ec_i2c.c new file mode 100644 index 000000000000..123044608b63 --- /dev/null +++ b/drivers/mfd/cros_ec_i2c.c | |||
@@ -0,0 +1,201 @@ | |||
1 | /* | ||
2 | * ChromeOS EC multi-function device (I2C) | ||
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 | #include <linux/kernel.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/i2c.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | #include <linux/mfd/cros_ec.h> | ||
21 | #include <linux/mfd/cros_ec_commands.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/slab.h> | ||
24 | |||
25 | static inline struct cros_ec_device *to_ec_dev(struct device *dev) | ||
26 | { | ||
27 | struct i2c_client *client = to_i2c_client(dev); | ||
28 | |||
29 | return i2c_get_clientdata(client); | ||
30 | } | ||
31 | |||
32 | static int cros_ec_command_xfer(struct cros_ec_device *ec_dev, | ||
33 | struct cros_ec_msg *msg) | ||
34 | { | ||
35 | struct i2c_client *client = ec_dev->priv; | ||
36 | int ret = -ENOMEM; | ||
37 | int i; | ||
38 | int packet_len; | ||
39 | u8 *out_buf = NULL; | ||
40 | u8 *in_buf = NULL; | ||
41 | u8 sum; | ||
42 | struct i2c_msg i2c_msg[2]; | ||
43 | |||
44 | i2c_msg[0].addr = client->addr; | ||
45 | i2c_msg[0].flags = 0; | ||
46 | i2c_msg[1].addr = client->addr; | ||
47 | i2c_msg[1].flags = I2C_M_RD; | ||
48 | |||
49 | /* | ||
50 | * allocate larger packet (one byte for checksum, one byte for | ||
51 | * length, and one for result code) | ||
52 | */ | ||
53 | packet_len = msg->in_len + 3; | ||
54 | in_buf = kzalloc(packet_len, GFP_KERNEL); | ||
55 | if (!in_buf) | ||
56 | goto done; | ||
57 | i2c_msg[1].len = packet_len; | ||
58 | i2c_msg[1].buf = (char *)in_buf; | ||
59 | |||
60 | /* | ||
61 | * allocate larger packet (one byte for checksum, one for | ||
62 | * command code, one for length, and one for command version) | ||
63 | */ | ||
64 | packet_len = msg->out_len + 4; | ||
65 | out_buf = kzalloc(packet_len, GFP_KERNEL); | ||
66 | if (!out_buf) | ||
67 | goto done; | ||
68 | i2c_msg[0].len = packet_len; | ||
69 | i2c_msg[0].buf = (char *)out_buf; | ||
70 | |||
71 | out_buf[0] = EC_CMD_VERSION0 + msg->version; | ||
72 | out_buf[1] = msg->cmd; | ||
73 | out_buf[2] = msg->out_len; | ||
74 | |||
75 | /* copy message payload and compute checksum */ | ||
76 | sum = out_buf[0] + out_buf[1] + out_buf[2]; | ||
77 | for (i = 0; i < msg->out_len; i++) { | ||
78 | out_buf[3 + i] = msg->out_buf[i]; | ||
79 | sum += out_buf[3 + i]; | ||
80 | } | ||
81 | out_buf[3 + msg->out_len] = sum; | ||
82 | |||
83 | /* send command to EC and read answer */ | ||
84 | ret = i2c_transfer(client->adapter, i2c_msg, 2); | ||
85 | if (ret < 0) { | ||
86 | dev_err(ec_dev->dev, "i2c transfer failed: %d\n", ret); | ||
87 | goto done; | ||
88 | } else if (ret != 2) { | ||
89 | dev_err(ec_dev->dev, "failed to get response: %d\n", ret); | ||
90 | ret = -EIO; | ||
91 | goto done; | ||
92 | } | ||
93 | |||
94 | /* check response error code */ | ||
95 | if (i2c_msg[1].buf[0]) { | ||
96 | dev_warn(ec_dev->dev, "command 0x%02x returned an error %d\n", | ||
97 | msg->cmd, i2c_msg[1].buf[0]); | ||
98 | ret = -EINVAL; | ||
99 | goto done; | ||
100 | } | ||
101 | |||
102 | /* copy response packet payload and compute checksum */ | ||
103 | sum = in_buf[0] + in_buf[1]; | ||
104 | for (i = 0; i < msg->in_len; i++) { | ||
105 | msg->in_buf[i] = in_buf[2 + i]; | ||
106 | sum += in_buf[2 + i]; | ||
107 | } | ||
108 | dev_dbg(ec_dev->dev, "packet: %*ph, sum = %02x\n", | ||
109 | i2c_msg[1].len, in_buf, sum); | ||
110 | if (sum != in_buf[2 + msg->in_len]) { | ||
111 | dev_err(ec_dev->dev, "bad packet checksum\n"); | ||
112 | ret = -EBADMSG; | ||
113 | goto done; | ||
114 | } | ||
115 | |||
116 | ret = 0; | ||
117 | done: | ||
118 | kfree(in_buf); | ||
119 | kfree(out_buf); | ||
120 | return ret; | ||
121 | } | ||
122 | |||
123 | static int cros_ec_probe_i2c(struct i2c_client *client, | ||
124 | const struct i2c_device_id *dev_id) | ||
125 | { | ||
126 | struct device *dev = &client->dev; | ||
127 | struct cros_ec_device *ec_dev = NULL; | ||
128 | int err; | ||
129 | |||
130 | ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); | ||
131 | if (!ec_dev) | ||
132 | return -ENOMEM; | ||
133 | |||
134 | i2c_set_clientdata(client, ec_dev); | ||
135 | ec_dev->name = "I2C"; | ||
136 | ec_dev->dev = dev; | ||
137 | ec_dev->priv = client; | ||
138 | ec_dev->irq = client->irq; | ||
139 | ec_dev->command_xfer = cros_ec_command_xfer; | ||
140 | ec_dev->ec_name = client->name; | ||
141 | ec_dev->phys_name = client->adapter->name; | ||
142 | ec_dev->parent = &client->dev; | ||
143 | |||
144 | err = cros_ec_register(ec_dev); | ||
145 | if (err) { | ||
146 | dev_err(dev, "cannot register EC\n"); | ||
147 | return err; | ||
148 | } | ||
149 | |||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int cros_ec_remove_i2c(struct i2c_client *client) | ||
154 | { | ||
155 | struct cros_ec_device *ec_dev = i2c_get_clientdata(client); | ||
156 | |||
157 | cros_ec_remove(ec_dev); | ||
158 | |||
159 | return 0; | ||
160 | } | ||
161 | |||
162 | #ifdef CONFIG_PM_SLEEP | ||
163 | static int cros_ec_i2c_suspend(struct device *dev) | ||
164 | { | ||
165 | struct cros_ec_device *ec_dev = to_ec_dev(dev); | ||
166 | |||
167 | return cros_ec_suspend(ec_dev); | ||
168 | } | ||
169 | |||
170 | static int cros_ec_i2c_resume(struct device *dev) | ||
171 | { | ||
172 | struct cros_ec_device *ec_dev = to_ec_dev(dev); | ||
173 | |||
174 | return cros_ec_resume(ec_dev); | ||
175 | } | ||
176 | #endif | ||
177 | |||
178 | static SIMPLE_DEV_PM_OPS(cros_ec_i2c_pm_ops, cros_ec_i2c_suspend, | ||
179 | cros_ec_i2c_resume); | ||
180 | |||
181 | static const struct i2c_device_id cros_ec_i2c_id[] = { | ||
182 | { "cros-ec-i2c", 0 }, | ||
183 | { } | ||
184 | }; | ||
185 | MODULE_DEVICE_TABLE(i2c, cros_ec_i2c_id); | ||
186 | |||
187 | static struct i2c_driver cros_ec_driver = { | ||
188 | .driver = { | ||
189 | .name = "cros-ec-i2c", | ||
190 | .owner = THIS_MODULE, | ||
191 | .pm = &cros_ec_i2c_pm_ops, | ||
192 | }, | ||
193 | .probe = cros_ec_probe_i2c, | ||
194 | .remove = cros_ec_remove_i2c, | ||
195 | .id_table = cros_ec_i2c_id, | ||
196 | }; | ||
197 | |||
198 | module_i2c_driver(cros_ec_driver); | ||
199 | |||
200 | MODULE_LICENSE("GPL"); | ||
201 | MODULE_DESCRIPTION("ChromeOS EC multi function device"); | ||