diff options
| -rw-r--r-- | Documentation/ioctl/ioctl-number.txt | 1 | ||||
| -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 | ||||
| -rw-r--r-- | include/linux/mfd/cros_ec.h | 23 |
13 files changed, 1384 insertions, 71 deletions
diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 8136e1fd30fd..51f4221657bf 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt | |||
| @@ -321,6 +321,7 @@ Code Seq#(hex) Include File Comments | |||
| 321 | 0xDB 00-0F drivers/char/mwave/mwavepub.h | 321 | 0xDB 00-0F drivers/char/mwave/mwavepub.h |
| 322 | 0xDD 00-3F ZFCP device driver see drivers/s390/scsi/ | 322 | 0xDD 00-3F ZFCP device driver see drivers/s390/scsi/ |
| 323 | <mailto:aherrman@de.ibm.com> | 323 | <mailto:aherrman@de.ibm.com> |
| 324 | 0xEC 00-01 drivers/platform/chrome/cros_ec_dev.h ChromeOS EC driver | ||
| 324 | 0xF3 00-3F drivers/usb/misc/sisusbvga/sisusb.h sisfb (in development) | 325 | 0xF3 00-3F drivers/usb/misc/sisusbvga/sisusb.h sisfb (in development) |
| 325 | <mailto:thomas@winischhofer.net> | 326 | <mailto:thomas@winischhofer.net> |
| 326 | 0xF4 00-1F video/mbxfb.h mbxfb | 327 | 0xF4 00-1F video/mbxfb.h mbxfb |
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 | } | ||
diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index 0e166b92f5b4..324a34683971 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h | |||
| @@ -16,6 +16,7 @@ | |||
| 16 | #ifndef __LINUX_MFD_CROS_EC_H | 16 | #ifndef __LINUX_MFD_CROS_EC_H |
| 17 | #define __LINUX_MFD_CROS_EC_H | 17 | #define __LINUX_MFD_CROS_EC_H |
| 18 | 18 | ||
| 19 | #include <linux/cdev.h> | ||
| 19 | #include <linux/notifier.h> | 20 | #include <linux/notifier.h> |
| 20 | #include <linux/mfd/cros_ec_commands.h> | 21 | #include <linux/mfd/cros_ec_commands.h> |
| 21 | #include <linux/mutex.h> | 22 | #include <linux/mutex.h> |
| @@ -38,20 +39,20 @@ enum { | |||
| 38 | /* | 39 | /* |
| 39 | * @version: Command version number (often 0) | 40 | * @version: Command version number (often 0) |
| 40 | * @command: Command to send (EC_CMD_...) | 41 | * @command: Command to send (EC_CMD_...) |
| 41 | * @outdata: Outgoing data to EC | ||
| 42 | * @outsize: Outgoing length in bytes | 42 | * @outsize: Outgoing length in bytes |
| 43 | * @indata: Where to put the incoming data from EC | ||
| 44 | * @insize: Max number of bytes to accept from EC | 43 | * @insize: Max number of bytes to accept from EC |
| 45 | * @result: EC's response to the command (separate from communication failure) | 44 | * @result: EC's response to the command (separate from communication failure) |
| 45 | * @outdata: Outgoing data to EC | ||
| 46 | * @indata: Where to put the incoming data from EC | ||
| 46 | */ | 47 | */ |
| 47 | struct cros_ec_command { | 48 | struct cros_ec_command { |
| 48 | uint32_t version; | 49 | uint32_t version; |
| 49 | uint32_t command; | 50 | uint32_t command; |
| 50 | uint8_t *outdata; | ||
| 51 | uint32_t outsize; | 51 | uint32_t outsize; |
| 52 | uint8_t *indata; | ||
| 53 | uint32_t insize; | 52 | uint32_t insize; |
| 54 | uint32_t result; | 53 | uint32_t result; |
| 54 | uint8_t outdata[EC_PROTO2_MAX_PARAM_SIZE]; | ||
| 55 | uint8_t indata[EC_PROTO2_MAX_PARAM_SIZE]; | ||
| 55 | }; | 56 | }; |
| 56 | 57 | ||
| 57 | /** | 58 | /** |
| @@ -59,9 +60,17 @@ struct cros_ec_command { | |||
| 59 | * | 60 | * |
| 60 | * @ec_name: name of EC device (e.g. 'chromeos-ec') | 61 | * @ec_name: name of EC device (e.g. 'chromeos-ec') |
| 61 | * @phys_name: name of physical comms layer (e.g. 'i2c-4') | 62 | * @phys_name: name of physical comms layer (e.g. 'i2c-4') |
| 62 | * @dev: Device pointer | 63 | * @dev: Device pointer for physical comms device |
| 64 | * @vdev: Device pointer for virtual comms device | ||
| 65 | * @cdev: Character device structure for virtual comms device | ||
| 63 | * @was_wake_device: true if this device was set to wake the system from | 66 | * @was_wake_device: true if this device was set to wake the system from |
| 64 | * sleep at the last suspend | 67 | * sleep at the last suspend |
| 68 | * @cmd_readmem: direct read of the EC memory-mapped region, if supported | ||
| 69 | * @offset is within EC_LPC_ADDR_MEMMAP region. | ||
| 70 | * @bytes: number of bytes to read. zero means "read a string" (including | ||
| 71 | * the trailing '\0'). At most only EC_MEMMAP_SIZE bytes can be read. | ||
| 72 | * Caller must ensure that the buffer is large enough for the result when | ||
| 73 | * reading a string. | ||
| 65 | * | 74 | * |
| 66 | * @priv: Private data | 75 | * @priv: Private data |
| 67 | * @irq: Interrupt to use | 76 | * @irq: Interrupt to use |
| @@ -90,8 +99,12 @@ struct cros_ec_device { | |||
| 90 | const char *ec_name; | 99 | const char *ec_name; |
| 91 | const char *phys_name; | 100 | const char *phys_name; |
| 92 | struct device *dev; | 101 | struct device *dev; |
| 102 | struct device *vdev; | ||
| 103 | struct cdev cdev; | ||
| 93 | bool was_wake_device; | 104 | bool was_wake_device; |
| 94 | struct class *cros_class; | 105 | struct class *cros_class; |
| 106 | int (*cmd_readmem)(struct cros_ec_device *ec, unsigned int offset, | ||
| 107 | unsigned int bytes, void *dest); | ||
| 95 | 108 | ||
| 96 | /* These are used to implement the platform-specific interface */ | 109 | /* These are used to implement the platform-specific interface */ |
| 97 | void *priv; | 110 | void *priv; |
