diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/i2c/busses/i2c-cros-ec-tunnel.c | 51 | ||||
-rw-r--r-- | drivers/input/keyboard/cros_ec_keyb.c | 13 | ||||
-rw-r--r-- | drivers/mfd/cros_ec.c | 19 | ||||
-rw-r--r-- | drivers/platform/chrome/Kconfig | 26 | ||||
-rw-r--r-- | drivers/platform/chrome/Makefile | 3 | ||||
-rw-r--r-- | drivers/platform/chrome/chromeos_laptop.c | 35 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_dev.c | 274 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_dev.h | 53 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_lightbar.c | 367 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_lpc.c | 319 | ||||
-rw-r--r-- | drivers/platform/chrome/cros_ec_sysfs.c | 271 |
11 files changed, 1365 insertions, 66 deletions
diff --git a/drivers/i2c/busses/i2c-cros-ec-tunnel.c b/drivers/i2c/busses/i2c-cros-ec-tunnel.c index 875c22ae5400..fa8dedd8c3a2 100644 --- a/drivers/i2c/busses/i2c-cros-ec-tunnel.c +++ b/drivers/i2c/busses/i2c-cros-ec-tunnel.c | |||
@@ -182,72 +182,41 @@ static int ec_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg i2c_msgs[], | |||
182 | const u16 bus_num = bus->remote_bus; | 182 | const u16 bus_num = bus->remote_bus; |
183 | int request_len; | 183 | int request_len; |
184 | int response_len; | 184 | int response_len; |
185 | u8 *request = NULL; | ||
186 | u8 *response = NULL; | ||
187 | int result; | 185 | int result; |
188 | struct cros_ec_command msg; | 186 | struct cros_ec_command msg = { }; |
189 | 187 | ||
190 | request_len = ec_i2c_count_message(i2c_msgs, num); | 188 | request_len = ec_i2c_count_message(i2c_msgs, num); |
191 | if (request_len < 0) { | 189 | if (request_len < 0) { |
192 | dev_warn(dev, "Error constructing message %d\n", request_len); | 190 | dev_warn(dev, "Error constructing message %d\n", request_len); |
193 | result = request_len; | 191 | return request_len; |
194 | goto exit; | ||
195 | } | 192 | } |
193 | |||
196 | response_len = ec_i2c_count_response(i2c_msgs, num); | 194 | response_len = ec_i2c_count_response(i2c_msgs, num); |
197 | if (response_len < 0) { | 195 | if (response_len < 0) { |
198 | /* Unexpected; no errors should come when NULL response */ | 196 | /* Unexpected; no errors should come when NULL response */ |
199 | dev_warn(dev, "Error preparing response %d\n", response_len); | 197 | dev_warn(dev, "Error preparing response %d\n", response_len); |
200 | result = response_len; | 198 | return response_len; |
201 | goto exit; | ||
202 | } | ||
203 | |||
204 | if (request_len <= ARRAY_SIZE(bus->request_buf)) { | ||
205 | request = bus->request_buf; | ||
206 | } else { | ||
207 | request = kzalloc(request_len, GFP_KERNEL); | ||
208 | if (request == NULL) { | ||
209 | result = -ENOMEM; | ||
210 | goto exit; | ||
211 | } | ||
212 | } | ||
213 | if (response_len <= ARRAY_SIZE(bus->response_buf)) { | ||
214 | response = bus->response_buf; | ||
215 | } else { | ||
216 | response = kzalloc(response_len, GFP_KERNEL); | ||
217 | if (response == NULL) { | ||
218 | result = -ENOMEM; | ||
219 | goto exit; | ||
220 | } | ||
221 | } | 199 | } |
222 | 200 | ||
223 | result = ec_i2c_construct_message(request, i2c_msgs, num, bus_num); | 201 | result = ec_i2c_construct_message(msg.outdata, i2c_msgs, num, bus_num); |
224 | if (result) | 202 | if (result) |
225 | goto exit; | 203 | return result; |
226 | 204 | ||
227 | msg.version = 0; | 205 | msg.version = 0; |
228 | msg.command = EC_CMD_I2C_PASSTHRU; | 206 | msg.command = EC_CMD_I2C_PASSTHRU; |
229 | msg.outdata = request; | ||
230 | msg.outsize = request_len; | 207 | msg.outsize = request_len; |
231 | msg.indata = response; | ||
232 | msg.insize = response_len; | 208 | msg.insize = response_len; |
233 | 209 | ||
234 | result = cros_ec_cmd_xfer(bus->ec, &msg); | 210 | result = cros_ec_cmd_xfer(bus->ec, &msg); |
235 | if (result < 0) | 211 | if (result < 0) |
236 | goto exit; | 212 | return result; |
237 | 213 | ||
238 | result = ec_i2c_parse_response(response, i2c_msgs, &num); | 214 | result = ec_i2c_parse_response(msg.indata, i2c_msgs, &num); |
239 | if (result < 0) | 215 | if (result < 0) |
240 | goto exit; | 216 | return result; |
241 | 217 | ||
242 | /* Indicate success by saying how many messages were sent */ | 218 | /* Indicate success by saying how many messages were sent */ |
243 | result = num; | 219 | return num; |
244 | exit: | ||
245 | if (request != bus->request_buf) | ||
246 | kfree(request); | ||
247 | if (response != bus->response_buf) | ||
248 | kfree(response); | ||
249 | |||
250 | return result; | ||
251 | } | 220 | } |
252 | 221 | ||
253 | static u32 ec_i2c_functionality(struct i2c_adapter *adap) | 222 | static u32 ec_i2c_functionality(struct i2c_adapter *adap) |
diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c index 64b9b59ad4cb..b50c5b8b8a4d 100644 --- a/drivers/input/keyboard/cros_ec_keyb.c +++ b/drivers/input/keyboard/cros_ec_keyb.c | |||
@@ -148,16 +148,19 @@ static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev, | |||
148 | 148 | ||
149 | static int cros_ec_keyb_get_state(struct cros_ec_keyb *ckdev, uint8_t *kb_state) | 149 | static int cros_ec_keyb_get_state(struct cros_ec_keyb *ckdev, uint8_t *kb_state) |
150 | { | 150 | { |
151 | int ret; | ||
151 | struct cros_ec_command msg = { | 152 | struct cros_ec_command msg = { |
152 | .version = 0, | ||
153 | .command = EC_CMD_MKBP_STATE, | 153 | .command = EC_CMD_MKBP_STATE, |
154 | .outdata = NULL, | ||
155 | .outsize = 0, | ||
156 | .indata = kb_state, | ||
157 | .insize = ckdev->cols, | 154 | .insize = ckdev->cols, |
158 | }; | 155 | }; |
159 | 156 | ||
160 | return cros_ec_cmd_xfer(ckdev->ec, &msg); | 157 | ret = cros_ec_cmd_xfer(ckdev->ec, &msg); |
158 | if (ret < 0) | ||
159 | return ret; | ||
160 | |||
161 | memcpy(kb_state, msg.indata, ckdev->cols); | ||
162 | |||
163 | return 0; | ||
161 | } | 164 | } |
162 | 165 | ||
163 | static irqreturn_t cros_ec_keyb_irq(int irq, void *data) | 166 | static irqreturn_t cros_ec_keyb_irq(int irq, void *data) |
diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index fc0c81ef04ff..c4aecc6f8373 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c | |||
@@ -74,15 +74,11 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, | |||
74 | ret = ec_dev->cmd_xfer(ec_dev, msg); | 74 | ret = ec_dev->cmd_xfer(ec_dev, msg); |
75 | if (msg->result == EC_RES_IN_PROGRESS) { | 75 | if (msg->result == EC_RES_IN_PROGRESS) { |
76 | int i; | 76 | int i; |
77 | struct cros_ec_command status_msg; | 77 | struct cros_ec_command status_msg = { }; |
78 | struct ec_response_get_comms_status status; | 78 | struct ec_response_get_comms_status *status; |
79 | 79 | ||
80 | status_msg.version = 0; | ||
81 | status_msg.command = EC_CMD_GET_COMMS_STATUS; | 80 | status_msg.command = EC_CMD_GET_COMMS_STATUS; |
82 | status_msg.outdata = NULL; | 81 | status_msg.insize = sizeof(*status); |
83 | status_msg.outsize = 0; | ||
84 | status_msg.indata = (uint8_t *)&status; | ||
85 | status_msg.insize = sizeof(status); | ||
86 | 82 | ||
87 | /* | 83 | /* |
88 | * Query the EC's status until it's no longer busy or | 84 | * Query the EC's status until it's no longer busy or |
@@ -98,7 +94,10 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, | |||
98 | msg->result = status_msg.result; | 94 | msg->result = status_msg.result; |
99 | if (status_msg.result != EC_RES_SUCCESS) | 95 | if (status_msg.result != EC_RES_SUCCESS) |
100 | break; | 96 | break; |
101 | if (!(status.flags & EC_COMMS_STATUS_PROCESSING)) | 97 | |
98 | status = (struct ec_response_get_comms_status *) | ||
99 | status_msg.indata; | ||
100 | if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) | ||
102 | break; | 101 | break; |
103 | } | 102 | } |
104 | } | 103 | } |
@@ -119,6 +118,10 @@ static const struct mfd_cell cros_devs[] = { | |||
119 | .id = 2, | 118 | .id = 2, |
120 | .of_compatible = "google,cros-ec-i2c-tunnel", | 119 | .of_compatible = "google,cros-ec-i2c-tunnel", |
121 | }, | 120 | }, |
121 | { | ||
122 | .name = "cros-ec-ctl", | ||
123 | .id = 3, | ||
124 | }, | ||
122 | }; | 125 | }; |
123 | 126 | ||
124 | int cros_ec_register(struct cros_ec_device *ec_dev) | 127 | int cros_ec_register(struct cros_ec_device *ec_dev) |
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 440ed776efd4..2a6531a5fde8 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig | |||
@@ -4,7 +4,7 @@ | |||
4 | 4 | ||
5 | menuconfig CHROME_PLATFORMS | 5 | menuconfig CHROME_PLATFORMS |
6 | bool "Platform support for Chrome hardware" | 6 | bool "Platform support for Chrome hardware" |
7 | depends on X86 | 7 | depends on X86 || ARM |
8 | ---help--- | 8 | ---help--- |
9 | Say Y here to get to see options for platform support for | 9 | Say Y here to get to see options for platform support for |
10 | various Chromebooks and Chromeboxes. This option alone does | 10 | various Chromebooks and Chromeboxes. This option alone does |
@@ -16,8 +16,7 @@ if CHROME_PLATFORMS | |||
16 | 16 | ||
17 | config CHROMEOS_LAPTOP | 17 | config CHROMEOS_LAPTOP |
18 | tristate "Chrome OS Laptop" | 18 | tristate "Chrome OS Laptop" |
19 | depends on I2C | 19 | depends on I2C && DMI && X86 |
20 | depends on DMI | ||
21 | ---help--- | 20 | ---help--- |
22 | This driver instantiates i2c and smbus devices such as | 21 | This driver instantiates i2c and smbus devices such as |
23 | light sensors and touchpads. | 22 | light sensors and touchpads. |
@@ -27,6 +26,7 @@ config CHROMEOS_LAPTOP | |||
27 | 26 | ||
28 | config CHROMEOS_PSTORE | 27 | config CHROMEOS_PSTORE |
29 | tristate "Chrome OS pstore support" | 28 | tristate "Chrome OS pstore support" |
29 | depends on X86 | ||
30 | ---help--- | 30 | ---help--- |
31 | This module instantiates the persistent storage on x86 ChromeOS | 31 | This module instantiates the persistent storage on x86 ChromeOS |
32 | devices. It can be used to store away console logs and crash | 32 | devices. It can be used to store away console logs and crash |
@@ -38,5 +38,25 @@ config CHROMEOS_PSTORE | |||
38 | If you have a supported Chromebook, choose Y or M here. | 38 | If you have a supported Chromebook, choose Y or M here. |
39 | The module will be called chromeos_pstore. | 39 | The module will be called chromeos_pstore. |
40 | 40 | ||
41 | config CROS_EC_CHARDEV | ||
42 | tristate "Chrome OS Embedded Controller userspace device interface" | ||
43 | depends on MFD_CROS_EC | ||
44 | ---help--- | ||
45 | This driver adds support to talk with the ChromeOS EC from userspace. | ||
46 | |||
47 | If you have a supported Chromebook, choose Y or M here. | ||
48 | The module will be called cros_ec_dev. | ||
49 | |||
50 | config CROS_EC_LPC | ||
51 | tristate "ChromeOS Embedded Controller (LPC)" | ||
52 | depends on MFD_CROS_EC && (X86 || COMPILE_TEST) | ||
53 | help | ||
54 | If you say Y here, you get support for talking to the ChromeOS EC | ||
55 | over an LPC bus. This uses a simple byte-level protocol with a | ||
56 | checksum. This is used for userspace access only. The kernel | ||
57 | typically has its own communication methods. | ||
58 | |||
59 | To compile this driver as a module, choose M here: the | ||
60 | module will be called cros_ec_lpc. | ||
41 | 61 | ||
42 | endif # CHROMEOS_PLATFORMS | 62 | endif # CHROMEOS_PLATFORMS |
diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 2b860ca7450f..bd8d8601e875 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile | |||
@@ -1,3 +1,6 @@ | |||
1 | 1 | ||
2 | obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o | 2 | obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o |
3 | obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o | 3 | obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o |
4 | cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o cros_ec_lightbar.o | ||
5 | obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o | ||
6 | obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o | ||
diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index b84fdd6b629b..a04019ab9feb 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c | |||
@@ -133,12 +133,13 @@ static struct i2c_client *__add_probed_i2c_device( | |||
133 | const char *name, | 133 | const char *name, |
134 | int bus, | 134 | int bus, |
135 | struct i2c_board_info *info, | 135 | struct i2c_board_info *info, |
136 | const unsigned short *addrs) | 136 | const unsigned short *alt_addr_list) |
137 | { | 137 | { |
138 | const struct dmi_device *dmi_dev; | 138 | const struct dmi_device *dmi_dev; |
139 | const struct dmi_dev_onboard *dev_data; | 139 | const struct dmi_dev_onboard *dev_data; |
140 | struct i2c_adapter *adapter; | 140 | struct i2c_adapter *adapter; |
141 | struct i2c_client *client; | 141 | struct i2c_client *client = NULL; |
142 | const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END }; | ||
142 | 143 | ||
143 | if (bus < 0) | 144 | if (bus < 0) |
144 | return NULL; | 145 | return NULL; |
@@ -169,8 +170,28 @@ static struct i2c_client *__add_probed_i2c_device( | |||
169 | return NULL; | 170 | return NULL; |
170 | } | 171 | } |
171 | 172 | ||
172 | /* add the i2c device */ | 173 | /* |
173 | client = i2c_new_probed_device(adapter, info, addrs, NULL); | 174 | * Add the i2c device. If we can't detect it at the primary |
175 | * address we scan secondary addresses. In any case the client | ||
176 | * structure gets assigned primary address. | ||
177 | */ | ||
178 | client = i2c_new_probed_device(adapter, info, addr_list, NULL); | ||
179 | if (!client && alt_addr_list) { | ||
180 | struct i2c_board_info dummy_info = { | ||
181 | I2C_BOARD_INFO("dummy", info->addr), | ||
182 | }; | ||
183 | struct i2c_client *dummy; | ||
184 | |||
185 | dummy = i2c_new_probed_device(adapter, &dummy_info, | ||
186 | alt_addr_list, NULL); | ||
187 | if (dummy) { | ||
188 | pr_debug("%s %d-%02x is probed at %02x\n", | ||
189 | __func__, bus, info->addr, dummy->addr); | ||
190 | i2c_unregister_device(dummy); | ||
191 | client = i2c_new_device(adapter, info); | ||
192 | } | ||
193 | } | ||
194 | |||
174 | if (!client) | 195 | if (!client) |
175 | pr_notice("%s failed to register device %d-%02x\n", | 196 | pr_notice("%s failed to register device %d-%02x\n", |
176 | __func__, bus, info->addr); | 197 | __func__, bus, info->addr); |
@@ -254,12 +275,10 @@ static struct i2c_client *add_i2c_device(const char *name, | |||
254 | enum i2c_adapter_type type, | 275 | enum i2c_adapter_type type, |
255 | struct i2c_board_info *info) | 276 | struct i2c_board_info *info) |
256 | { | 277 | { |
257 | const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END }; | ||
258 | |||
259 | return __add_probed_i2c_device(name, | 278 | return __add_probed_i2c_device(name, |
260 | find_i2c_adapter_num(type), | 279 | find_i2c_adapter_num(type), |
261 | info, | 280 | info, |
262 | addr_list); | 281 | NULL); |
263 | } | 282 | } |
264 | 283 | ||
265 | static int setup_cyapa_tp(enum i2c_adapter_type type) | 284 | static int setup_cyapa_tp(enum i2c_adapter_type type) |
@@ -275,7 +294,6 @@ static int setup_cyapa_tp(enum i2c_adapter_type type) | |||
275 | static int setup_atmel_224s_tp(enum i2c_adapter_type type) | 294 | static int setup_atmel_224s_tp(enum i2c_adapter_type type) |
276 | { | 295 | { |
277 | const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR, | 296 | const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR, |
278 | ATMEL_TP_I2C_ADDR, | ||
279 | I2C_CLIENT_END }; | 297 | I2C_CLIENT_END }; |
280 | if (tp) | 298 | if (tp) |
281 | return 0; | 299 | return 0; |
@@ -289,7 +307,6 @@ static int setup_atmel_224s_tp(enum i2c_adapter_type type) | |||
289 | static int setup_atmel_1664s_ts(enum i2c_adapter_type type) | 307 | static int setup_atmel_1664s_ts(enum i2c_adapter_type type) |
290 | { | 308 | { |
291 | const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR, | 309 | const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR, |
292 | ATMEL_TS_I2C_ADDR, | ||
293 | I2C_CLIENT_END }; | 310 | I2C_CLIENT_END }; |
294 | if (ts) | 311 | if (ts) |
295 | return 0; | 312 | return 0; |
diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c new file mode 100644 index 000000000000..6090d0b2826f --- /dev/null +++ b/drivers/platform/chrome/cros_ec_dev.c | |||
@@ -0,0 +1,274 @@ | |||
1 | /* | ||
2 | * cros_ec_dev - expose the Chrome OS Embedded Controller to user-space | ||
3 | * | ||
4 | * Copyright (C) 2014 Google, Inc. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #include <linux/fs.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/uaccess.h> | ||
24 | |||
25 | #include "cros_ec_dev.h" | ||
26 | |||
27 | /* Device variables */ | ||
28 | #define CROS_MAX_DEV 128 | ||
29 | static struct class *cros_class; | ||
30 | static int ec_major; | ||
31 | |||
32 | /* Basic communication */ | ||
33 | static int ec_get_version(struct cros_ec_device *ec, char *str, int maxlen) | ||
34 | { | ||
35 | struct ec_response_get_version *resp; | ||
36 | static const char * const current_image_name[] = { | ||
37 | "unknown", "read-only", "read-write", "invalid", | ||
38 | }; | ||
39 | struct cros_ec_command msg = { | ||
40 | .version = 0, | ||
41 | .command = EC_CMD_GET_VERSION, | ||
42 | .outdata = { 0 }, | ||
43 | .outsize = 0, | ||
44 | .indata = { 0 }, | ||
45 | .insize = sizeof(*resp), | ||
46 | }; | ||
47 | int ret; | ||
48 | |||
49 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
50 | if (ret < 0) | ||
51 | return ret; | ||
52 | |||
53 | if (msg.result != EC_RES_SUCCESS) { | ||
54 | snprintf(str, maxlen, | ||
55 | "%s\nUnknown EC version: EC returned %d\n", | ||
56 | CROS_EC_DEV_VERSION, msg.result); | ||
57 | return 0; | ||
58 | } | ||
59 | |||
60 | resp = (struct ec_response_get_version *)msg.indata; | ||
61 | if (resp->current_image >= ARRAY_SIZE(current_image_name)) | ||
62 | resp->current_image = 3; /* invalid */ | ||
63 | |||
64 | snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION, | ||
65 | resp->version_string_ro, resp->version_string_rw, | ||
66 | current_image_name[resp->current_image]); | ||
67 | |||
68 | return 0; | ||
69 | } | ||
70 | |||
71 | /* Device file ops */ | ||
72 | static int ec_device_open(struct inode *inode, struct file *filp) | ||
73 | { | ||
74 | filp->private_data = container_of(inode->i_cdev, | ||
75 | struct cros_ec_device, cdev); | ||
76 | return 0; | ||
77 | } | ||
78 | |||
79 | static int ec_device_release(struct inode *inode, struct file *filp) | ||
80 | { | ||
81 | return 0; | ||
82 | } | ||
83 | |||
84 | static ssize_t ec_device_read(struct file *filp, char __user *buffer, | ||
85 | size_t length, loff_t *offset) | ||
86 | { | ||
87 | struct cros_ec_device *ec = filp->private_data; | ||
88 | char msg[sizeof(struct ec_response_get_version) + | ||
89 | sizeof(CROS_EC_DEV_VERSION)]; | ||
90 | size_t count; | ||
91 | int ret; | ||
92 | |||
93 | if (*offset != 0) | ||
94 | return 0; | ||
95 | |||
96 | ret = ec_get_version(ec, msg, sizeof(msg)); | ||
97 | if (ret) | ||
98 | return ret; | ||
99 | |||
100 | count = min(length, strlen(msg)); | ||
101 | |||
102 | if (copy_to_user(buffer, msg, count)) | ||
103 | return -EFAULT; | ||
104 | |||
105 | *offset = count; | ||
106 | return count; | ||
107 | } | ||
108 | |||
109 | /* Ioctls */ | ||
110 | static long ec_device_ioctl_xcmd(struct cros_ec_device *ec, void __user *arg) | ||
111 | { | ||
112 | long ret; | ||
113 | struct cros_ec_command s_cmd = { }; | ||
114 | |||
115 | if (copy_from_user(&s_cmd, arg, sizeof(s_cmd))) | ||
116 | return -EFAULT; | ||
117 | |||
118 | ret = cros_ec_cmd_xfer(ec, &s_cmd); | ||
119 | /* Only copy data to userland if data was received. */ | ||
120 | if (ret < 0) | ||
121 | return ret; | ||
122 | |||
123 | if (copy_to_user(arg, &s_cmd, sizeof(s_cmd))) | ||
124 | return -EFAULT; | ||
125 | |||
126 | return 0; | ||
127 | } | ||
128 | |||
129 | static long ec_device_ioctl_readmem(struct cros_ec_device *ec, void __user *arg) | ||
130 | { | ||
131 | struct cros_ec_readmem s_mem = { }; | ||
132 | long num; | ||
133 | |||
134 | /* Not every platform supports direct reads */ | ||
135 | if (!ec->cmd_readmem) | ||
136 | return -ENOTTY; | ||
137 | |||
138 | if (copy_from_user(&s_mem, arg, sizeof(s_mem))) | ||
139 | return -EFAULT; | ||
140 | |||
141 | num = ec->cmd_readmem(ec, s_mem.offset, s_mem.bytes, s_mem.buffer); | ||
142 | if (num <= 0) | ||
143 | return num; | ||
144 | |||
145 | if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) | ||
146 | return -EFAULT; | ||
147 | |||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | static long ec_device_ioctl(struct file *filp, unsigned int cmd, | ||
152 | unsigned long arg) | ||
153 | { | ||
154 | struct cros_ec_device *ec = filp->private_data; | ||
155 | |||
156 | if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) | ||
157 | return -ENOTTY; | ||
158 | |||
159 | switch (cmd) { | ||
160 | case CROS_EC_DEV_IOCXCMD: | ||
161 | return ec_device_ioctl_xcmd(ec, (void __user *)arg); | ||
162 | case CROS_EC_DEV_IOCRDMEM: | ||
163 | return ec_device_ioctl_readmem(ec, (void __user *)arg); | ||
164 | } | ||
165 | |||
166 | return -ENOTTY; | ||
167 | } | ||
168 | |||
169 | /* Module initialization */ | ||
170 | static const struct file_operations fops = { | ||
171 | .open = ec_device_open, | ||
172 | .release = ec_device_release, | ||
173 | .read = ec_device_read, | ||
174 | .unlocked_ioctl = ec_device_ioctl, | ||
175 | }; | ||
176 | |||
177 | static int ec_device_probe(struct platform_device *pdev) | ||
178 | { | ||
179 | struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); | ||
180 | int retval = -ENOTTY; | ||
181 | dev_t devno = MKDEV(ec_major, 0); | ||
182 | |||
183 | /* Instantiate it (and remember the EC) */ | ||
184 | cdev_init(&ec->cdev, &fops); | ||
185 | |||
186 | retval = cdev_add(&ec->cdev, devno, 1); | ||
187 | if (retval) { | ||
188 | dev_err(&pdev->dev, ": failed to add character device\n"); | ||
189 | return retval; | ||
190 | } | ||
191 | |||
192 | ec->vdev = device_create(cros_class, NULL, devno, ec, | ||
193 | CROS_EC_DEV_NAME); | ||
194 | if (IS_ERR(ec->vdev)) { | ||
195 | retval = PTR_ERR(ec->vdev); | ||
196 | dev_err(&pdev->dev, ": failed to create device\n"); | ||
197 | cdev_del(&ec->cdev); | ||
198 | return retval; | ||
199 | } | ||
200 | |||
201 | /* Initialize extra interfaces */ | ||
202 | ec_dev_sysfs_init(ec); | ||
203 | ec_dev_lightbar_init(ec); | ||
204 | |||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | static int ec_device_remove(struct platform_device *pdev) | ||
209 | { | ||
210 | struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); | ||
211 | |||
212 | ec_dev_lightbar_remove(ec); | ||
213 | ec_dev_sysfs_remove(ec); | ||
214 | device_destroy(cros_class, MKDEV(ec_major, 0)); | ||
215 | cdev_del(&ec->cdev); | ||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | static struct platform_driver cros_ec_dev_driver = { | ||
220 | .driver = { | ||
221 | .name = "cros-ec-ctl", | ||
222 | }, | ||
223 | .probe = ec_device_probe, | ||
224 | .remove = ec_device_remove, | ||
225 | }; | ||
226 | |||
227 | static int __init cros_ec_dev_init(void) | ||
228 | { | ||
229 | int ret; | ||
230 | dev_t dev = 0; | ||
231 | |||
232 | cros_class = class_create(THIS_MODULE, "chromeos"); | ||
233 | if (IS_ERR(cros_class)) { | ||
234 | pr_err(CROS_EC_DEV_NAME ": failed to register device class\n"); | ||
235 | return PTR_ERR(cros_class); | ||
236 | } | ||
237 | |||
238 | /* Get a range of minor numbers (starting with 0) to work with */ | ||
239 | ret = alloc_chrdev_region(&dev, 0, CROS_MAX_DEV, CROS_EC_DEV_NAME); | ||
240 | if (ret < 0) { | ||
241 | pr_err(CROS_EC_DEV_NAME ": alloc_chrdev_region() failed\n"); | ||
242 | goto failed_chrdevreg; | ||
243 | } | ||
244 | ec_major = MAJOR(dev); | ||
245 | |||
246 | /* Register the driver */ | ||
247 | ret = platform_driver_register(&cros_ec_dev_driver); | ||
248 | if (ret < 0) { | ||
249 | pr_warn(CROS_EC_DEV_NAME ": can't register driver: %d\n", ret); | ||
250 | goto failed_devreg; | ||
251 | } | ||
252 | return 0; | ||
253 | |||
254 | failed_devreg: | ||
255 | unregister_chrdev_region(MKDEV(ec_major, 0), CROS_MAX_DEV); | ||
256 | failed_chrdevreg: | ||
257 | class_destroy(cros_class); | ||
258 | return ret; | ||
259 | } | ||
260 | |||
261 | static void __exit cros_ec_dev_exit(void) | ||
262 | { | ||
263 | platform_driver_unregister(&cros_ec_dev_driver); | ||
264 | unregister_chrdev(ec_major, CROS_EC_DEV_NAME); | ||
265 | class_destroy(cros_class); | ||
266 | } | ||
267 | |||
268 | module_init(cros_ec_dev_init); | ||
269 | module_exit(cros_ec_dev_exit); | ||
270 | |||
271 | MODULE_AUTHOR("Bill Richardson <wfrichar@chromium.org>"); | ||
272 | MODULE_DESCRIPTION("Userspace interface to the Chrome OS Embedded Controller"); | ||
273 | MODULE_VERSION("1.0"); | ||
274 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/platform/chrome/cros_ec_dev.h b/drivers/platform/chrome/cros_ec_dev.h new file mode 100644 index 000000000000..45d67f7e518c --- /dev/null +++ b/drivers/platform/chrome/cros_ec_dev.h | |||
@@ -0,0 +1,53 @@ | |||
1 | /* | ||
2 | * cros_ec_dev - expose the Chrome OS Embedded Controller to userspace | ||
3 | * | ||
4 | * Copyright (C) 2014 Google, Inc. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #ifndef _CROS_EC_DEV_H_ | ||
21 | #define _CROS_EC_DEV_H_ | ||
22 | |||
23 | #include <linux/ioctl.h> | ||
24 | #include <linux/types.h> | ||
25 | #include <linux/mfd/cros_ec.h> | ||
26 | |||
27 | #define CROS_EC_DEV_NAME "cros_ec" | ||
28 | #define CROS_EC_DEV_VERSION "1.0.0" | ||
29 | |||
30 | /* | ||
31 | * @offset: within EC_LPC_ADDR_MEMMAP region | ||
32 | * @bytes: number of bytes to read. zero means "read a string" (including '\0') | ||
33 | * (at most only EC_MEMMAP_SIZE bytes can be read) | ||
34 | * @buffer: where to store the result | ||
35 | * ioctl returns the number of bytes read, negative on error | ||
36 | */ | ||
37 | struct cros_ec_readmem { | ||
38 | uint32_t offset; | ||
39 | uint32_t bytes; | ||
40 | uint8_t buffer[EC_MEMMAP_SIZE]; | ||
41 | }; | ||
42 | |||
43 | #define CROS_EC_DEV_IOC 0xEC | ||
44 | #define CROS_EC_DEV_IOCXCMD _IOWR(CROS_EC_DEV_IOC, 0, struct cros_ec_command) | ||
45 | #define CROS_EC_DEV_IOCRDMEM _IOWR(CROS_EC_DEV_IOC, 1, struct cros_ec_readmem) | ||
46 | |||
47 | void ec_dev_sysfs_init(struct cros_ec_device *); | ||
48 | void ec_dev_sysfs_remove(struct cros_ec_device *); | ||
49 | |||
50 | void ec_dev_lightbar_init(struct cros_ec_device *); | ||
51 | void ec_dev_lightbar_remove(struct cros_ec_device *); | ||
52 | |||
53 | #endif /* _CROS_EC_DEV_H_ */ | ||
diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c new file mode 100644 index 000000000000..b4ff47a9069a --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lightbar.c | |||
@@ -0,0 +1,367 @@ | |||
1 | /* | ||
2 | * cros_ec_lightbar - expose the Chromebook Pixel lightbar to userspace | ||
3 | * | ||
4 | * Copyright (C) 2014 Google, Inc. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #define pr_fmt(fmt) "cros_ec_lightbar: " fmt | ||
21 | |||
22 | #include <linux/ctype.h> | ||
23 | #include <linux/delay.h> | ||
24 | #include <linux/device.h> | ||
25 | #include <linux/fs.h> | ||
26 | #include <linux/kobject.h> | ||
27 | #include <linux/mfd/cros_ec.h> | ||
28 | #include <linux/mfd/cros_ec_commands.h> | ||
29 | #include <linux/module.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/sched.h> | ||
32 | #include <linux/types.h> | ||
33 | #include <linux/uaccess.h> | ||
34 | |||
35 | #include "cros_ec_dev.h" | ||
36 | |||
37 | /* Rate-limit the lightbar interface to prevent DoS. */ | ||
38 | static unsigned long lb_interval_jiffies = 50 * HZ / 1000; | ||
39 | |||
40 | static ssize_t interval_msec_show(struct device *dev, | ||
41 | struct device_attribute *attr, char *buf) | ||
42 | { | ||
43 | unsigned long msec = lb_interval_jiffies * 1000 / HZ; | ||
44 | |||
45 | return scnprintf(buf, PAGE_SIZE, "%lu\n", msec); | ||
46 | } | ||
47 | |||
48 | static ssize_t interval_msec_store(struct device *dev, | ||
49 | struct device_attribute *attr, | ||
50 | const char *buf, size_t count) | ||
51 | { | ||
52 | unsigned long msec; | ||
53 | |||
54 | if (kstrtoul(buf, 0, &msec)) | ||
55 | return -EINVAL; | ||
56 | |||
57 | lb_interval_jiffies = msec * HZ / 1000; | ||
58 | |||
59 | return count; | ||
60 | } | ||
61 | |||
62 | static DEFINE_MUTEX(lb_mutex); | ||
63 | /* Return 0 if able to throttle correctly, error otherwise */ | ||
64 | static int lb_throttle(void) | ||
65 | { | ||
66 | static unsigned long last_access; | ||
67 | unsigned long now, next_timeslot; | ||
68 | long delay; | ||
69 | int ret = 0; | ||
70 | |||
71 | mutex_lock(&lb_mutex); | ||
72 | |||
73 | now = jiffies; | ||
74 | next_timeslot = last_access + lb_interval_jiffies; | ||
75 | |||
76 | if (time_before(now, next_timeslot)) { | ||
77 | delay = (long)(next_timeslot) - (long)now; | ||
78 | set_current_state(TASK_INTERRUPTIBLE); | ||
79 | if (schedule_timeout(delay) > 0) { | ||
80 | /* interrupted - just abort */ | ||
81 | ret = -EINTR; | ||
82 | goto out; | ||
83 | } | ||
84 | now = jiffies; | ||
85 | } | ||
86 | |||
87 | last_access = now; | ||
88 | out: | ||
89 | mutex_unlock(&lb_mutex); | ||
90 | |||
91 | return ret; | ||
92 | } | ||
93 | |||
94 | #define INIT_MSG(P, R) { \ | ||
95 | .command = EC_CMD_LIGHTBAR_CMD, \ | ||
96 | .outsize = sizeof(*P), \ | ||
97 | .insize = sizeof(*R), \ | ||
98 | } | ||
99 | |||
100 | static int get_lightbar_version(struct cros_ec_device *ec, | ||
101 | uint32_t *ver_ptr, uint32_t *flg_ptr) | ||
102 | { | ||
103 | struct ec_params_lightbar *param; | ||
104 | struct ec_response_lightbar *resp; | ||
105 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
106 | int ret; | ||
107 | |||
108 | param = (struct ec_params_lightbar *)msg.outdata; | ||
109 | param->cmd = LIGHTBAR_CMD_VERSION; | ||
110 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
111 | if (ret < 0) | ||
112 | return 0; | ||
113 | |||
114 | switch (msg.result) { | ||
115 | case EC_RES_INVALID_PARAM: | ||
116 | /* Pixel had no version command. */ | ||
117 | if (ver_ptr) | ||
118 | *ver_ptr = 0; | ||
119 | if (flg_ptr) | ||
120 | *flg_ptr = 0; | ||
121 | return 1; | ||
122 | |||
123 | case EC_RES_SUCCESS: | ||
124 | resp = (struct ec_response_lightbar *)msg.indata; | ||
125 | |||
126 | /* Future devices w/lightbars should implement this command */ | ||
127 | if (ver_ptr) | ||
128 | *ver_ptr = resp->version.num; | ||
129 | if (flg_ptr) | ||
130 | *flg_ptr = resp->version.flags; | ||
131 | return 1; | ||
132 | } | ||
133 | |||
134 | /* Anything else (ie, EC_RES_INVALID_COMMAND) - no lightbar */ | ||
135 | return 0; | ||
136 | } | ||
137 | |||
138 | static ssize_t version_show(struct device *dev, | ||
139 | struct device_attribute *attr, char *buf) | ||
140 | { | ||
141 | uint32_t version, flags; | ||
142 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
143 | int ret; | ||
144 | |||
145 | ret = lb_throttle(); | ||
146 | if (ret) | ||
147 | return ret; | ||
148 | |||
149 | /* This should always succeed, because we check during init. */ | ||
150 | if (!get_lightbar_version(ec, &version, &flags)) | ||
151 | return -EIO; | ||
152 | |||
153 | return scnprintf(buf, PAGE_SIZE, "%d %d\n", version, flags); | ||
154 | } | ||
155 | |||
156 | static ssize_t brightness_store(struct device *dev, | ||
157 | struct device_attribute *attr, | ||
158 | const char *buf, size_t count) | ||
159 | { | ||
160 | struct ec_params_lightbar *param; | ||
161 | struct ec_response_lightbar *resp; | ||
162 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
163 | int ret; | ||
164 | unsigned int val; | ||
165 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
166 | |||
167 | if (kstrtouint(buf, 0, &val)) | ||
168 | return -EINVAL; | ||
169 | |||
170 | param = (struct ec_params_lightbar *)msg.outdata; | ||
171 | param->cmd = LIGHTBAR_CMD_BRIGHTNESS; | ||
172 | param->brightness.num = val; | ||
173 | ret = lb_throttle(); | ||
174 | if (ret) | ||
175 | return ret; | ||
176 | |||
177 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
178 | if (ret < 0) | ||
179 | return ret; | ||
180 | |||
181 | if (msg.result != EC_RES_SUCCESS) | ||
182 | return -EINVAL; | ||
183 | |||
184 | return count; | ||
185 | } | ||
186 | |||
187 | |||
188 | /* | ||
189 | * We expect numbers, and we'll keep reading until we find them, skipping over | ||
190 | * any whitespace (sysfs guarantees that the input is null-terminated). Every | ||
191 | * four numbers are sent to the lightbar as <LED,R,G,B>. We fail at the first | ||
192 | * parsing error, if we don't parse any numbers, or if we have numbers left | ||
193 | * over. | ||
194 | */ | ||
195 | static ssize_t led_rgb_store(struct device *dev, struct device_attribute *attr, | ||
196 | const char *buf, size_t count) | ||
197 | { | ||
198 | struct ec_params_lightbar *param; | ||
199 | struct ec_response_lightbar *resp; | ||
200 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
201 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
202 | unsigned int val[4]; | ||
203 | int ret, i = 0, j = 0, ok = 0; | ||
204 | |||
205 | do { | ||
206 | /* Skip any whitespace */ | ||
207 | while (*buf && isspace(*buf)) | ||
208 | buf++; | ||
209 | |||
210 | if (!*buf) | ||
211 | break; | ||
212 | |||
213 | ret = sscanf(buf, "%i", &val[i++]); | ||
214 | if (ret == 0) | ||
215 | return -EINVAL; | ||
216 | |||
217 | if (i == 4) { | ||
218 | param = (struct ec_params_lightbar *)msg.outdata; | ||
219 | param->cmd = LIGHTBAR_CMD_RGB; | ||
220 | param->rgb.led = val[0]; | ||
221 | param->rgb.red = val[1]; | ||
222 | param->rgb.green = val[2]; | ||
223 | param->rgb.blue = val[3]; | ||
224 | /* | ||
225 | * Throttle only the first of every four transactions, | ||
226 | * so that the user can update all four LEDs at once. | ||
227 | */ | ||
228 | if ((j++ % 4) == 0) { | ||
229 | ret = lb_throttle(); | ||
230 | if (ret) | ||
231 | return ret; | ||
232 | } | ||
233 | |||
234 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
235 | if (ret < 0) | ||
236 | return ret; | ||
237 | |||
238 | if (msg.result != EC_RES_SUCCESS) | ||
239 | return -EINVAL; | ||
240 | |||
241 | i = 0; | ||
242 | ok = 1; | ||
243 | } | ||
244 | |||
245 | /* Skip over the number we just read */ | ||
246 | while (*buf && !isspace(*buf)) | ||
247 | buf++; | ||
248 | |||
249 | } while (*buf); | ||
250 | |||
251 | return (ok && i == 0) ? count : -EINVAL; | ||
252 | } | ||
253 | |||
254 | static char const *seqname[] = { | ||
255 | "ERROR", "S5", "S3", "S0", "S5S3", "S3S0", | ||
256 | "S0S3", "S3S5", "STOP", "RUN", "PULSE", "TEST", "KONAMI", | ||
257 | }; | ||
258 | |||
259 | static ssize_t sequence_show(struct device *dev, | ||
260 | struct device_attribute *attr, char *buf) | ||
261 | { | ||
262 | struct ec_params_lightbar *param; | ||
263 | struct ec_response_lightbar *resp; | ||
264 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
265 | int ret; | ||
266 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
267 | |||
268 | param = (struct ec_params_lightbar *)msg.outdata; | ||
269 | param->cmd = LIGHTBAR_CMD_GET_SEQ; | ||
270 | ret = lb_throttle(); | ||
271 | if (ret) | ||
272 | return ret; | ||
273 | |||
274 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
275 | if (ret < 0) | ||
276 | return ret; | ||
277 | |||
278 | if (msg.result != EC_RES_SUCCESS) | ||
279 | return scnprintf(buf, PAGE_SIZE, | ||
280 | "ERROR: EC returned %d\n", msg.result); | ||
281 | |||
282 | resp = (struct ec_response_lightbar *)msg.indata; | ||
283 | if (resp->get_seq.num >= ARRAY_SIZE(seqname)) | ||
284 | return scnprintf(buf, PAGE_SIZE, "%d\n", resp->get_seq.num); | ||
285 | else | ||
286 | return scnprintf(buf, PAGE_SIZE, "%s\n", | ||
287 | seqname[resp->get_seq.num]); | ||
288 | } | ||
289 | |||
290 | static ssize_t sequence_store(struct device *dev, struct device_attribute *attr, | ||
291 | const char *buf, size_t count) | ||
292 | { | ||
293 | struct ec_params_lightbar *param; | ||
294 | struct ec_response_lightbar *resp; | ||
295 | struct cros_ec_command msg = INIT_MSG(param, resp); | ||
296 | unsigned int num; | ||
297 | int ret, len; | ||
298 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
299 | |||
300 | for (len = 0; len < count; len++) | ||
301 | if (!isalnum(buf[len])) | ||
302 | break; | ||
303 | |||
304 | for (num = 0; num < ARRAY_SIZE(seqname); num++) | ||
305 | if (!strncasecmp(seqname[num], buf, len)) | ||
306 | break; | ||
307 | |||
308 | if (num >= ARRAY_SIZE(seqname)) { | ||
309 | ret = kstrtouint(buf, 0, &num); | ||
310 | if (ret) | ||
311 | return ret; | ||
312 | } | ||
313 | |||
314 | param = (struct ec_params_lightbar *)msg.outdata; | ||
315 | param->cmd = LIGHTBAR_CMD_SEQ; | ||
316 | param->seq.num = num; | ||
317 | ret = lb_throttle(); | ||
318 | if (ret) | ||
319 | return ret; | ||
320 | |||
321 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
322 | if (ret < 0) | ||
323 | return ret; | ||
324 | |||
325 | if (msg.result != EC_RES_SUCCESS) | ||
326 | return -EINVAL; | ||
327 | |||
328 | return count; | ||
329 | } | ||
330 | |||
331 | /* Module initialization */ | ||
332 | |||
333 | static DEVICE_ATTR_RW(interval_msec); | ||
334 | static DEVICE_ATTR_RO(version); | ||
335 | static DEVICE_ATTR_WO(brightness); | ||
336 | static DEVICE_ATTR_WO(led_rgb); | ||
337 | static DEVICE_ATTR_RW(sequence); | ||
338 | static struct attribute *__lb_cmds_attrs[] = { | ||
339 | &dev_attr_interval_msec.attr, | ||
340 | &dev_attr_version.attr, | ||
341 | &dev_attr_brightness.attr, | ||
342 | &dev_attr_led_rgb.attr, | ||
343 | &dev_attr_sequence.attr, | ||
344 | NULL, | ||
345 | }; | ||
346 | static struct attribute_group lb_cmds_attr_group = { | ||
347 | .name = "lightbar", | ||
348 | .attrs = __lb_cmds_attrs, | ||
349 | }; | ||
350 | |||
351 | void ec_dev_lightbar_init(struct cros_ec_device *ec) | ||
352 | { | ||
353 | int ret = 0; | ||
354 | |||
355 | /* Only instantiate this stuff if the EC has a lightbar */ | ||
356 | if (!get_lightbar_version(ec, NULL, NULL)) | ||
357 | return; | ||
358 | |||
359 | ret = sysfs_create_group(&ec->vdev->kobj, &lb_cmds_attr_group); | ||
360 | if (ret) | ||
361 | pr_warn("sysfs_create_group() failed: %d\n", ret); | ||
362 | } | ||
363 | |||
364 | void ec_dev_lightbar_remove(struct cros_ec_device *ec) | ||
365 | { | ||
366 | sysfs_remove_group(&ec->vdev->kobj, &lb_cmds_attr_group); | ||
367 | } | ||
diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c new file mode 100644 index 000000000000..8f9ac4d7bbd0 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_lpc.c | |||
@@ -0,0 +1,319 @@ | |||
1 | /* | ||
2 | * cros_ec_lpc - LPC access to the Chrome OS Embedded Controller | ||
3 | * | ||
4 | * Copyright (C) 2012-2015 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 | * This driver uses the Chrome OS EC byte-level message-based protocol for | ||
16 | * communicating the keyboard state (which keys are pressed) from a keyboard EC | ||
17 | * to the AP over some bus (such as i2c, lpc, spi). The EC does debouncing, | ||
18 | * but everything else (including deghosting) is done here. The main | ||
19 | * motivation for this is to keep the EC firmware as simple as possible, since | ||
20 | * it cannot be easily upgraded and EC flash/IRAM space is relatively | ||
21 | * expensive. | ||
22 | */ | ||
23 | |||
24 | #include <linux/dmi.h> | ||
25 | #include <linux/delay.h> | ||
26 | #include <linux/io.h> | ||
27 | #include <linux/mfd/cros_ec.h> | ||
28 | #include <linux/mfd/cros_ec_commands.h> | ||
29 | #include <linux/module.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/printk.h> | ||
32 | |||
33 | #define DRV_NAME "cros_ec_lpc" | ||
34 | |||
35 | static int ec_response_timed_out(void) | ||
36 | { | ||
37 | unsigned long one_second = jiffies + HZ; | ||
38 | |||
39 | usleep_range(200, 300); | ||
40 | do { | ||
41 | if (!(inb(EC_LPC_ADDR_HOST_CMD) & EC_LPC_STATUS_BUSY_MASK)) | ||
42 | return 0; | ||
43 | usleep_range(100, 200); | ||
44 | } while (time_before(jiffies, one_second)); | ||
45 | |||
46 | return 1; | ||
47 | } | ||
48 | |||
49 | static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, | ||
50 | struct cros_ec_command *msg) | ||
51 | { | ||
52 | struct ec_lpc_host_args args; | ||
53 | int csum; | ||
54 | int i; | ||
55 | int ret = 0; | ||
56 | |||
57 | if (msg->outsize > EC_PROTO2_MAX_PARAM_SIZE || | ||
58 | msg->insize > EC_PROTO2_MAX_PARAM_SIZE) { | ||
59 | dev_err(ec->dev, | ||
60 | "invalid buffer sizes (out %d, in %d)\n", | ||
61 | msg->outsize, msg->insize); | ||
62 | return -EINVAL; | ||
63 | } | ||
64 | |||
65 | /* Now actually send the command to the EC and get the result */ | ||
66 | args.flags = EC_HOST_ARGS_FLAG_FROM_HOST; | ||
67 | args.command_version = msg->version; | ||
68 | args.data_size = msg->outsize; | ||
69 | |||
70 | /* Initialize checksum */ | ||
71 | csum = msg->command + args.flags + | ||
72 | args.command_version + args.data_size; | ||
73 | |||
74 | /* Copy data and update checksum */ | ||
75 | for (i = 0; i < msg->outsize; i++) { | ||
76 | outb(msg->outdata[i], EC_LPC_ADDR_HOST_PARAM + i); | ||
77 | csum += msg->outdata[i]; | ||
78 | } | ||
79 | |||
80 | /* Finalize checksum and write args */ | ||
81 | args.checksum = csum & 0xFF; | ||
82 | outb(args.flags, EC_LPC_ADDR_HOST_ARGS); | ||
83 | outb(args.command_version, EC_LPC_ADDR_HOST_ARGS + 1); | ||
84 | outb(args.data_size, EC_LPC_ADDR_HOST_ARGS + 2); | ||
85 | outb(args.checksum, EC_LPC_ADDR_HOST_ARGS + 3); | ||
86 | |||
87 | /* Here we go */ | ||
88 | outb(msg->command, EC_LPC_ADDR_HOST_CMD); | ||
89 | |||
90 | if (ec_response_timed_out()) { | ||
91 | dev_warn(ec->dev, "EC responsed timed out\n"); | ||
92 | ret = -EIO; | ||
93 | goto done; | ||
94 | } | ||
95 | |||
96 | /* Check result */ | ||
97 | msg->result = inb(EC_LPC_ADDR_HOST_DATA); | ||
98 | |||
99 | switch (msg->result) { | ||
100 | case EC_RES_SUCCESS: | ||
101 | break; | ||
102 | case EC_RES_IN_PROGRESS: | ||
103 | ret = -EAGAIN; | ||
104 | dev_dbg(ec->dev, "command 0x%02x in progress\n", | ||
105 | msg->command); | ||
106 | goto done; | ||
107 | default: | ||
108 | dev_dbg(ec->dev, "command 0x%02x returned %d\n", | ||
109 | msg->command, msg->result); | ||
110 | } | ||
111 | |||
112 | /* Read back args */ | ||
113 | args.flags = inb(EC_LPC_ADDR_HOST_ARGS); | ||
114 | args.command_version = inb(EC_LPC_ADDR_HOST_ARGS + 1); | ||
115 | args.data_size = inb(EC_LPC_ADDR_HOST_ARGS + 2); | ||
116 | args.checksum = inb(EC_LPC_ADDR_HOST_ARGS + 3); | ||
117 | |||
118 | if (args.data_size > msg->insize) { | ||
119 | dev_err(ec->dev, | ||
120 | "packet too long (%d bytes, expected %d)", | ||
121 | args.data_size, msg->insize); | ||
122 | ret = -ENOSPC; | ||
123 | goto done; | ||
124 | } | ||
125 | |||
126 | /* Start calculating response checksum */ | ||
127 | csum = msg->command + args.flags + | ||
128 | args.command_version + args.data_size; | ||
129 | |||
130 | /* Read response and update checksum */ | ||
131 | for (i = 0; i < args.data_size; i++) { | ||
132 | msg->indata[i] = inb(EC_LPC_ADDR_HOST_PARAM + i); | ||
133 | csum += msg->indata[i]; | ||
134 | } | ||
135 | |||
136 | /* Verify checksum */ | ||
137 | if (args.checksum != (csum & 0xFF)) { | ||
138 | dev_err(ec->dev, | ||
139 | "bad packet checksum, expected %02x, got %02x\n", | ||
140 | args.checksum, csum & 0xFF); | ||
141 | ret = -EBADMSG; | ||
142 | goto done; | ||
143 | } | ||
144 | |||
145 | /* Return actual amount of data received */ | ||
146 | ret = args.data_size; | ||
147 | done: | ||
148 | return ret; | ||
149 | } | ||
150 | |||
151 | /* Returns num bytes read, or negative on error. Doesn't need locking. */ | ||
152 | static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, | ||
153 | unsigned int bytes, void *dest) | ||
154 | { | ||
155 | int i = offset; | ||
156 | char *s = dest; | ||
157 | int cnt = 0; | ||
158 | |||
159 | if (offset >= EC_MEMMAP_SIZE - bytes) | ||
160 | return -EINVAL; | ||
161 | |||
162 | /* fixed length */ | ||
163 | if (bytes) { | ||
164 | for (; cnt < bytes; i++, s++, cnt++) | ||
165 | *s = inb(EC_LPC_ADDR_MEMMAP + i); | ||
166 | return cnt; | ||
167 | } | ||
168 | |||
169 | /* string */ | ||
170 | for (; i < EC_MEMMAP_SIZE; i++, s++) { | ||
171 | *s = inb(EC_LPC_ADDR_MEMMAP + i); | ||
172 | cnt++; | ||
173 | if (!*s) | ||
174 | break; | ||
175 | } | ||
176 | |||
177 | return cnt; | ||
178 | } | ||
179 | |||
180 | static int cros_ec_lpc_probe(struct platform_device *pdev) | ||
181 | { | ||
182 | struct device *dev = &pdev->dev; | ||
183 | struct cros_ec_device *ec_dev; | ||
184 | int ret; | ||
185 | |||
186 | if (!devm_request_region(dev, EC_LPC_ADDR_MEMMAP, EC_MEMMAP_SIZE, | ||
187 | dev_name(dev))) { | ||
188 | dev_err(dev, "couldn't reserve memmap region\n"); | ||
189 | return -EBUSY; | ||
190 | } | ||
191 | |||
192 | if ((inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID) != 'E') || | ||
193 | (inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1) != 'C')) { | ||
194 | dev_err(dev, "EC ID not detected\n"); | ||
195 | return -ENODEV; | ||
196 | } | ||
197 | |||
198 | if (!devm_request_region(dev, EC_HOST_CMD_REGION0, | ||
199 | EC_HOST_CMD_REGION_SIZE, dev_name(dev))) { | ||
200 | dev_err(dev, "couldn't reserve region0\n"); | ||
201 | return -EBUSY; | ||
202 | } | ||
203 | if (!devm_request_region(dev, EC_HOST_CMD_REGION1, | ||
204 | EC_HOST_CMD_REGION_SIZE, dev_name(dev))) { | ||
205 | dev_err(dev, "couldn't reserve region1\n"); | ||
206 | return -EBUSY; | ||
207 | } | ||
208 | |||
209 | ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); | ||
210 | if (!ec_dev) | ||
211 | return -ENOMEM; | ||
212 | |||
213 | platform_set_drvdata(pdev, ec_dev); | ||
214 | ec_dev->dev = dev; | ||
215 | ec_dev->ec_name = pdev->name; | ||
216 | ec_dev->phys_name = dev_name(dev); | ||
217 | ec_dev->parent = dev; | ||
218 | ec_dev->cmd_xfer = cros_ec_cmd_xfer_lpc; | ||
219 | ec_dev->cmd_readmem = cros_ec_lpc_readmem; | ||
220 | |||
221 | ret = cros_ec_register(ec_dev); | ||
222 | if (ret) { | ||
223 | dev_err(dev, "couldn't register ec_dev (%d)\n", ret); | ||
224 | return ret; | ||
225 | } | ||
226 | |||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | static int cros_ec_lpc_remove(struct platform_device *pdev) | ||
231 | { | ||
232 | struct cros_ec_device *ec_dev; | ||
233 | |||
234 | ec_dev = platform_get_drvdata(pdev); | ||
235 | cros_ec_remove(ec_dev); | ||
236 | |||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | static struct dmi_system_id cros_ec_lpc_dmi_table[] __initdata = { | ||
241 | { | ||
242 | /* | ||
243 | * Today all Chromebooks/boxes ship with Google_* as version and | ||
244 | * coreboot as bios vendor. No other systems with this | ||
245 | * combination are known to date. | ||
246 | */ | ||
247 | .matches = { | ||
248 | DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), | ||
249 | DMI_MATCH(DMI_BIOS_VERSION, "Google_"), | ||
250 | }, | ||
251 | }, | ||
252 | { | ||
253 | /* x86-link, the Chromebook Pixel. */ | ||
254 | .matches = { | ||
255 | DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), | ||
256 | DMI_MATCH(DMI_PRODUCT_NAME, "Link"), | ||
257 | }, | ||
258 | }, | ||
259 | { | ||
260 | /* x86-peppy, the Acer C720 Chromebook. */ | ||
261 | .matches = { | ||
262 | DMI_MATCH(DMI_SYS_VENDOR, "Acer"), | ||
263 | DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"), | ||
264 | }, | ||
265 | }, | ||
266 | { /* sentinel */ } | ||
267 | }; | ||
268 | MODULE_DEVICE_TABLE(dmi, cros_ec_lpc_dmi_table); | ||
269 | |||
270 | static struct platform_driver cros_ec_lpc_driver = { | ||
271 | .driver = { | ||
272 | .name = DRV_NAME, | ||
273 | }, | ||
274 | .probe = cros_ec_lpc_probe, | ||
275 | .remove = cros_ec_lpc_remove, | ||
276 | }; | ||
277 | |||
278 | static struct platform_device cros_ec_lpc_device = { | ||
279 | .name = DRV_NAME | ||
280 | }; | ||
281 | |||
282 | static int __init cros_ec_lpc_init(void) | ||
283 | { | ||
284 | int ret; | ||
285 | |||
286 | if (!dmi_check_system(cros_ec_lpc_dmi_table)) { | ||
287 | pr_err(DRV_NAME ": unsupported system.\n"); | ||
288 | return -ENODEV; | ||
289 | } | ||
290 | |||
291 | /* Register the driver */ | ||
292 | ret = platform_driver_register(&cros_ec_lpc_driver); | ||
293 | if (ret) { | ||
294 | pr_err(DRV_NAME ": can't register driver: %d\n", ret); | ||
295 | return ret; | ||
296 | } | ||
297 | |||
298 | /* Register the device, and it'll get hooked up automatically */ | ||
299 | ret = platform_device_register(&cros_ec_lpc_device); | ||
300 | if (ret) { | ||
301 | pr_err(DRV_NAME ": can't register device: %d\n", ret); | ||
302 | platform_driver_unregister(&cros_ec_lpc_driver); | ||
303 | return ret; | ||
304 | } | ||
305 | |||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static void __exit cros_ec_lpc_exit(void) | ||
310 | { | ||
311 | platform_device_unregister(&cros_ec_lpc_device); | ||
312 | platform_driver_unregister(&cros_ec_lpc_driver); | ||
313 | } | ||
314 | |||
315 | module_init(cros_ec_lpc_init); | ||
316 | module_exit(cros_ec_lpc_exit); | ||
317 | |||
318 | MODULE_LICENSE("GPL"); | ||
319 | MODULE_DESCRIPTION("ChromeOS EC LPC driver"); | ||
diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c new file mode 100644 index 000000000000..fb62ab6cc659 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_sysfs.c | |||
@@ -0,0 +1,271 @@ | |||
1 | /* | ||
2 | * cros_ec_sysfs - expose the Chrome OS EC through sysfs | ||
3 | * | ||
4 | * Copyright (C) 2014 Google, Inc. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #define pr_fmt(fmt) "cros_ec_sysfs: " fmt | ||
21 | |||
22 | #include <linux/ctype.h> | ||
23 | #include <linux/delay.h> | ||
24 | #include <linux/device.h> | ||
25 | #include <linux/fs.h> | ||
26 | #include <linux/kobject.h> | ||
27 | #include <linux/mfd/cros_ec.h> | ||
28 | #include <linux/mfd/cros_ec_commands.h> | ||
29 | #include <linux/module.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/printk.h> | ||
32 | #include <linux/stat.h> | ||
33 | #include <linux/types.h> | ||
34 | #include <linux/uaccess.h> | ||
35 | |||
36 | #include "cros_ec_dev.h" | ||
37 | |||
38 | /* Accessor functions */ | ||
39 | |||
40 | static ssize_t show_ec_reboot(struct device *dev, | ||
41 | struct device_attribute *attr, char *buf) | ||
42 | { | ||
43 | int count = 0; | ||
44 | |||
45 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
46 | "ro|rw|cancel|cold|disable-jump|hibernate"); | ||
47 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
48 | " [at-shutdown]\n"); | ||
49 | return count; | ||
50 | } | ||
51 | |||
52 | static ssize_t store_ec_reboot(struct device *dev, | ||
53 | struct device_attribute *attr, | ||
54 | const char *buf, size_t count) | ||
55 | { | ||
56 | static const struct { | ||
57 | const char * const str; | ||
58 | uint8_t cmd; | ||
59 | uint8_t flags; | ||
60 | } words[] = { | ||
61 | {"cancel", EC_REBOOT_CANCEL, 0}, | ||
62 | {"ro", EC_REBOOT_JUMP_RO, 0}, | ||
63 | {"rw", EC_REBOOT_JUMP_RW, 0}, | ||
64 | {"cold", EC_REBOOT_COLD, 0}, | ||
65 | {"disable-jump", EC_REBOOT_DISABLE_JUMP, 0}, | ||
66 | {"hibernate", EC_REBOOT_HIBERNATE, 0}, | ||
67 | {"at-shutdown", -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN}, | ||
68 | }; | ||
69 | struct cros_ec_command msg = { 0 }; | ||
70 | struct ec_params_reboot_ec *param = | ||
71 | (struct ec_params_reboot_ec *)msg.outdata; | ||
72 | int got_cmd = 0, offset = 0; | ||
73 | int i; | ||
74 | int ret; | ||
75 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
76 | |||
77 | param->flags = 0; | ||
78 | while (1) { | ||
79 | /* Find word to start scanning */ | ||
80 | while (buf[offset] && isspace(buf[offset])) | ||
81 | offset++; | ||
82 | if (!buf[offset]) | ||
83 | break; | ||
84 | |||
85 | for (i = 0; i < ARRAY_SIZE(words); i++) { | ||
86 | if (!strncasecmp(words[i].str, buf+offset, | ||
87 | strlen(words[i].str))) { | ||
88 | if (words[i].flags) { | ||
89 | param->flags |= words[i].flags; | ||
90 | } else { | ||
91 | param->cmd = words[i].cmd; | ||
92 | got_cmd = 1; | ||
93 | } | ||
94 | break; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | /* On to the next word, if any */ | ||
99 | while (buf[offset] && !isspace(buf[offset])) | ||
100 | offset++; | ||
101 | } | ||
102 | |||
103 | if (!got_cmd) | ||
104 | return -EINVAL; | ||
105 | |||
106 | msg.command = EC_CMD_REBOOT_EC; | ||
107 | msg.outsize = sizeof(param); | ||
108 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
109 | if (ret < 0) | ||
110 | return ret; | ||
111 | if (msg.result != EC_RES_SUCCESS) { | ||
112 | dev_dbg(ec->dev, "EC result %d\n", msg.result); | ||
113 | return -EINVAL; | ||
114 | } | ||
115 | |||
116 | return count; | ||
117 | } | ||
118 | |||
119 | static ssize_t show_ec_version(struct device *dev, | ||
120 | struct device_attribute *attr, char *buf) | ||
121 | { | ||
122 | static const char * const image_names[] = {"unknown", "RO", "RW"}; | ||
123 | struct ec_response_get_version *r_ver; | ||
124 | struct ec_response_get_chip_info *r_chip; | ||
125 | struct ec_response_board_version *r_board; | ||
126 | struct cros_ec_command msg = { 0 }; | ||
127 | int ret; | ||
128 | int count = 0; | ||
129 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
130 | |||
131 | /* Get versions. RW may change. */ | ||
132 | msg.command = EC_CMD_GET_VERSION; | ||
133 | msg.insize = sizeof(*r_ver); | ||
134 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
135 | if (ret < 0) | ||
136 | return ret; | ||
137 | if (msg.result != EC_RES_SUCCESS) | ||
138 | return scnprintf(buf, PAGE_SIZE, | ||
139 | "ERROR: EC returned %d\n", msg.result); | ||
140 | |||
141 | r_ver = (struct ec_response_get_version *)msg.indata; | ||
142 | /* Strings should be null-terminated, but let's be sure. */ | ||
143 | r_ver->version_string_ro[sizeof(r_ver->version_string_ro) - 1] = '\0'; | ||
144 | r_ver->version_string_rw[sizeof(r_ver->version_string_rw) - 1] = '\0'; | ||
145 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
146 | "RO version: %s\n", r_ver->version_string_ro); | ||
147 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
148 | "RW version: %s\n", r_ver->version_string_rw); | ||
149 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
150 | "Firmware copy: %s\n", | ||
151 | (r_ver->current_image < ARRAY_SIZE(image_names) ? | ||
152 | image_names[r_ver->current_image] : "?")); | ||
153 | |||
154 | /* Get build info. */ | ||
155 | msg.command = EC_CMD_GET_BUILD_INFO; | ||
156 | msg.insize = sizeof(msg.indata); | ||
157 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
158 | if (ret < 0) | ||
159 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
160 | "Build info: XFER ERROR %d\n", ret); | ||
161 | else if (msg.result != EC_RES_SUCCESS) | ||
162 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
163 | "Build info: EC error %d\n", msg.result); | ||
164 | else { | ||
165 | msg.indata[sizeof(msg.indata) - 1] = '\0'; | ||
166 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
167 | "Build info: %s\n", msg.indata); | ||
168 | } | ||
169 | |||
170 | /* Get chip info. */ | ||
171 | msg.command = EC_CMD_GET_CHIP_INFO; | ||
172 | msg.insize = sizeof(*r_chip); | ||
173 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
174 | if (ret < 0) | ||
175 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
176 | "Chip info: XFER ERROR %d\n", ret); | ||
177 | else if (msg.result != EC_RES_SUCCESS) | ||
178 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
179 | "Chip info: EC error %d\n", msg.result); | ||
180 | else { | ||
181 | r_chip = (struct ec_response_get_chip_info *)msg.indata; | ||
182 | |||
183 | r_chip->vendor[sizeof(r_chip->vendor) - 1] = '\0'; | ||
184 | r_chip->name[sizeof(r_chip->name) - 1] = '\0'; | ||
185 | r_chip->revision[sizeof(r_chip->revision) - 1] = '\0'; | ||
186 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
187 | "Chip vendor: %s\n", r_chip->vendor); | ||
188 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
189 | "Chip name: %s\n", r_chip->name); | ||
190 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
191 | "Chip revision: %s\n", r_chip->revision); | ||
192 | } | ||
193 | |||
194 | /* Get board version */ | ||
195 | msg.command = EC_CMD_GET_BOARD_VERSION; | ||
196 | msg.insize = sizeof(*r_board); | ||
197 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
198 | if (ret < 0) | ||
199 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
200 | "Board version: XFER ERROR %d\n", ret); | ||
201 | else if (msg.result != EC_RES_SUCCESS) | ||
202 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
203 | "Board version: EC error %d\n", msg.result); | ||
204 | else { | ||
205 | r_board = (struct ec_response_board_version *)msg.indata; | ||
206 | |||
207 | count += scnprintf(buf + count, PAGE_SIZE - count, | ||
208 | "Board version: %d\n", | ||
209 | r_board->board_version); | ||
210 | } | ||
211 | |||
212 | return count; | ||
213 | } | ||
214 | |||
215 | static ssize_t show_ec_flashinfo(struct device *dev, | ||
216 | struct device_attribute *attr, char *buf) | ||
217 | { | ||
218 | struct ec_response_flash_info *resp; | ||
219 | struct cros_ec_command msg = { 0 }; | ||
220 | int ret; | ||
221 | struct cros_ec_device *ec = dev_get_drvdata(dev); | ||
222 | |||
223 | /* The flash info shouldn't ever change, but ask each time anyway. */ | ||
224 | msg.command = EC_CMD_FLASH_INFO; | ||
225 | msg.insize = sizeof(*resp); | ||
226 | ret = cros_ec_cmd_xfer(ec, &msg); | ||
227 | if (ret < 0) | ||
228 | return ret; | ||
229 | if (msg.result != EC_RES_SUCCESS) | ||
230 | return scnprintf(buf, PAGE_SIZE, | ||
231 | "ERROR: EC returned %d\n", msg.result); | ||
232 | |||
233 | resp = (struct ec_response_flash_info *)msg.indata; | ||
234 | |||
235 | return scnprintf(buf, PAGE_SIZE, | ||
236 | "FlashSize %d\nWriteSize %d\n" | ||
237 | "EraseSize %d\nProtectSize %d\n", | ||
238 | resp->flash_size, resp->write_block_size, | ||
239 | resp->erase_block_size, resp->protect_block_size); | ||
240 | } | ||
241 | |||
242 | /* Module initialization */ | ||
243 | |||
244 | static DEVICE_ATTR(reboot, S_IWUSR | S_IRUGO, show_ec_reboot, store_ec_reboot); | ||
245 | static DEVICE_ATTR(version, S_IRUGO, show_ec_version, NULL); | ||
246 | static DEVICE_ATTR(flashinfo, S_IRUGO, show_ec_flashinfo, NULL); | ||
247 | |||
248 | static struct attribute *__ec_attrs[] = { | ||
249 | &dev_attr_reboot.attr, | ||
250 | &dev_attr_version.attr, | ||
251 | &dev_attr_flashinfo.attr, | ||
252 | NULL, | ||
253 | }; | ||
254 | |||
255 | static struct attribute_group ec_attr_group = { | ||
256 | .attrs = __ec_attrs, | ||
257 | }; | ||
258 | |||
259 | void ec_dev_sysfs_init(struct cros_ec_device *ec) | ||
260 | { | ||
261 | int error; | ||
262 | |||
263 | error = sysfs_create_group(&ec->vdev->kobj, &ec_attr_group); | ||
264 | if (error) | ||
265 | pr_warn("failed to create group: %d\n", error); | ||
266 | } | ||
267 | |||
268 | void ec_dev_sysfs_remove(struct cros_ec_device *ec) | ||
269 | { | ||
270 | sysfs_remove_group(&ec->vdev->kobj, &ec_attr_group); | ||
271 | } | ||