Lubomir Rintel | 0c3d931 | 2019-05-13 09:56:37 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * Driver for the OLPC XO-1.75 Embedded Controller. |
| 4 | * |
| 5 | * The EC protocol is documented at: |
| 6 | * http://wiki.laptop.org/go/XO_1.75_HOST_to_EC_Protocol |
| 7 | * |
| 8 | * Copyright (C) 2010 One Laptop per Child Foundation. |
| 9 | * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk> |
| 10 | */ |
| 11 | |
| 12 | #include <linux/completion.h> |
| 13 | #include <linux/ctype.h> |
| 14 | #include <linux/delay.h> |
| 15 | #include <linux/gpio/consumer.h> |
| 16 | #include <linux/input.h> |
| 17 | #include <linux/kfifo.h> |
| 18 | #include <linux/module.h> |
| 19 | #include <linux/olpc-ec.h> |
| 20 | #include <linux/platform_device.h> |
| 21 | #include <linux/power_supply.h> |
| 22 | #include <linux/reboot.h> |
| 23 | #include <linux/slab.h> |
| 24 | #include <linux/spinlock.h> |
| 25 | #include <linux/spi/spi.h> |
| 26 | |
| 27 | struct ec_cmd_t { |
| 28 | u8 cmd; |
| 29 | u8 bytes_returned; |
| 30 | }; |
| 31 | |
| 32 | enum ec_chan_t { |
| 33 | CHAN_NONE = 0, |
| 34 | CHAN_SWITCH, |
| 35 | CHAN_CMD_RESP, |
| 36 | CHAN_KEYBOARD, |
| 37 | CHAN_TOUCHPAD, |
| 38 | CHAN_EVENT, |
| 39 | CHAN_DEBUG, |
| 40 | CHAN_CMD_ERROR, |
| 41 | }; |
| 42 | |
| 43 | /* |
| 44 | * EC events |
| 45 | */ |
| 46 | #define EVENT_AC_CHANGE 1 /* AC plugged/unplugged */ |
| 47 | #define EVENT_BATTERY_STATUS 2 /* Battery low/full/error/gone */ |
| 48 | #define EVENT_BATTERY_CRITICAL 3 /* Battery critical voltage */ |
| 49 | #define EVENT_BATTERY_SOC_CHANGE 4 /* 1% SOC Change */ |
| 50 | #define EVENT_BATTERY_ERROR 5 /* Abnormal error, query for cause */ |
| 51 | #define EVENT_POWER_PRESSED 6 /* Power button was pressed */ |
| 52 | #define EVENT_POWER_PRESS_WAKE 7 /* Woken up with a power button */ |
| 53 | #define EVENT_TIMED_HOST_WAKE 8 /* Host wake timer */ |
| 54 | #define EVENT_OLS_HIGH_LIMIT 9 /* OLS crossed dark threshold */ |
| 55 | #define EVENT_OLS_LOW_LIMIT 10 /* OLS crossed light threshold */ |
| 56 | |
| 57 | /* |
| 58 | * EC commands |
| 59 | * (from http://dev.laptop.org/git/users/rsmith/ec-1.75/tree/ec_cmd.h) |
| 60 | */ |
| 61 | #define CMD_GET_API_VERSION 0x08 /* out: u8 */ |
| 62 | #define CMD_READ_VOLTAGE 0x10 /* out: u16, *9.76/32, mV */ |
| 63 | #define CMD_READ_CURRENT 0x11 /* out: s16, *15.625/120, mA */ |
| 64 | #define CMD_READ_ACR 0x12 /* out: s16, *6250/15, uAh */ |
| 65 | #define CMD_READ_BATT_TEMPERATURE 0x13 /* out: u16, *100/256, deg C */ |
| 66 | #define CMD_READ_AMBIENT_TEMPERATURE 0x14 /* unimplemented, no hardware */ |
| 67 | #define CMD_READ_BATTERY_STATUS 0x15 /* out: u8, bitmask */ |
| 68 | #define CMD_READ_SOC 0x16 /* out: u8, percentage */ |
| 69 | #define CMD_READ_GAUGE_ID 0x17 /* out: u8 * 8 */ |
| 70 | #define CMD_READ_GAUGE_DATA 0x18 /* in: u8 addr, out: u8 data */ |
| 71 | #define CMD_READ_BOARD_ID 0x19 /* out: u16 (platform id) */ |
| 72 | #define CMD_READ_BATT_ERR_CODE 0x1f /* out: u8, error bitmask */ |
| 73 | #define CMD_SET_DCON_POWER 0x26 /* in: u8 */ |
| 74 | #define CMD_RESET_EC 0x28 /* none */ |
| 75 | #define CMD_READ_BATTERY_TYPE 0x2c /* out: u8 */ |
| 76 | #define CMD_SET_AUTOWAK 0x33 /* out: u8 */ |
| 77 | #define CMD_SET_EC_WAKEUP_TIMER 0x36 /* in: u32, out: ? */ |
| 78 | #define CMD_READ_EXT_SCI_MASK 0x37 /* ? */ |
| 79 | #define CMD_WRITE_EXT_SCI_MASK 0x38 /* ? */ |
| 80 | #define CMD_CLEAR_EC_WAKEUP_TIMER 0x39 /* none */ |
| 81 | #define CMD_ENABLE_RUNIN_DISCHARGE 0x3B /* none */ |
| 82 | #define CMD_DISABLE_RUNIN_DISCHARGE 0x3C /* none */ |
| 83 | #define CMD_READ_MPPT_ACTIVE 0x3d /* out: u8 */ |
| 84 | #define CMD_READ_MPPT_LIMIT 0x3e /* out: u8 */ |
| 85 | #define CMD_SET_MPPT_LIMIT 0x3f /* in: u8 */ |
| 86 | #define CMD_DISABLE_MPPT 0x40 /* none */ |
| 87 | #define CMD_ENABLE_MPPT 0x41 /* none */ |
| 88 | #define CMD_READ_VIN 0x42 /* out: u16 */ |
| 89 | #define CMD_EXT_SCI_QUERY 0x43 /* ? */ |
| 90 | #define RSP_KEYBOARD_DATA 0x48 /* ? */ |
| 91 | #define RSP_TOUCHPAD_DATA 0x49 /* ? */ |
| 92 | #define CMD_GET_FW_VERSION 0x4a /* out: u8 * 16 */ |
| 93 | #define CMD_POWER_CYCLE 0x4b /* none */ |
| 94 | #define CMD_POWER_OFF 0x4c /* none */ |
| 95 | #define CMD_RESET_EC_SOFT 0x4d /* none */ |
| 96 | #define CMD_READ_GAUGE_U16 0x4e /* ? */ |
| 97 | #define CMD_ENABLE_MOUSE 0x4f /* ? */ |
| 98 | #define CMD_ECHO 0x52 /* in: u8 * 5, out: u8 * 5 */ |
| 99 | #define CMD_GET_FW_DATE 0x53 /* out: u8 * 16 */ |
| 100 | #define CMD_GET_FW_USER 0x54 /* out: u8 * 16 */ |
| 101 | #define CMD_TURN_OFF_POWER 0x55 /* none (same as 0x4c) */ |
| 102 | #define CMD_READ_OLS 0x56 /* out: u16 */ |
| 103 | #define CMD_OLS_SMT_LEDON 0x57 /* none */ |
| 104 | #define CMD_OLS_SMT_LEDOFF 0x58 /* none */ |
| 105 | #define CMD_START_OLS_ASSY 0x59 /* none */ |
| 106 | #define CMD_STOP_OLS_ASSY 0x5a /* none */ |
| 107 | #define CMD_OLS_SMTTEST_STOP 0x5b /* none */ |
| 108 | #define CMD_READ_VIN_SCALED 0x5c /* out: u16 */ |
| 109 | #define CMD_READ_BAT_MIN_W 0x5d /* out: u16 */ |
| 110 | #define CMD_READ_BAR_MAX_W 0x5e /* out: u16 */ |
| 111 | #define CMD_RESET_BAT_MINMAX_W 0x5f /* none */ |
| 112 | #define CMD_READ_LOCATION 0x60 /* in: u16 addr, out: u8 data */ |
| 113 | #define CMD_WRITE_LOCATION 0x61 /* in: u16 addr, u8 data */ |
| 114 | #define CMD_KEYBOARD_CMD 0x62 /* in: u8, out: ? */ |
| 115 | #define CMD_TOUCHPAD_CMD 0x63 /* in: u8, out: ? */ |
| 116 | #define CMD_GET_FW_HASH 0x64 /* out: u8 * 16 */ |
| 117 | #define CMD_SUSPEND_HINT 0x65 /* in: u8 */ |
| 118 | #define CMD_ENABLE_WAKE_TIMER 0x66 /* in: u8 */ |
| 119 | #define CMD_SET_WAKE_TIMER 0x67 /* in: 32 */ |
| 120 | #define CMD_ENABLE_WAKE_AUTORESET 0x68 /* in: u8 */ |
| 121 | #define CMD_OLS_SET_LIMITS 0x69 /* in: u16, u16 */ |
| 122 | #define CMD_OLS_GET_LIMITS 0x6a /* out: u16, u16 */ |
| 123 | #define CMD_OLS_SET_CEILING 0x6b /* in: u16 */ |
| 124 | #define CMD_OLS_GET_CEILING 0x6c /* out: u16 */ |
| 125 | |
| 126 | /* |
| 127 | * Accepted EC commands, and how many bytes they return. There are plenty |
| 128 | * of EC commands that are no longer implemented, or are implemented only on |
| 129 | * certain older boards. |
| 130 | */ |
| 131 | static const struct ec_cmd_t olpc_xo175_ec_cmds[] = { |
| 132 | { CMD_GET_API_VERSION, 1 }, |
| 133 | { CMD_READ_VOLTAGE, 2 }, |
| 134 | { CMD_READ_CURRENT, 2 }, |
| 135 | { CMD_READ_ACR, 2 }, |
| 136 | { CMD_READ_BATT_TEMPERATURE, 2 }, |
| 137 | { CMD_READ_BATTERY_STATUS, 1 }, |
| 138 | { CMD_READ_SOC, 1 }, |
| 139 | { CMD_READ_GAUGE_ID, 8 }, |
| 140 | { CMD_READ_GAUGE_DATA, 1 }, |
| 141 | { CMD_READ_BOARD_ID, 2 }, |
| 142 | { CMD_READ_BATT_ERR_CODE, 1 }, |
| 143 | { CMD_SET_DCON_POWER, 0 }, |
| 144 | { CMD_RESET_EC, 0 }, |
| 145 | { CMD_READ_BATTERY_TYPE, 1 }, |
| 146 | { CMD_ENABLE_RUNIN_DISCHARGE, 0 }, |
| 147 | { CMD_DISABLE_RUNIN_DISCHARGE, 0 }, |
| 148 | { CMD_READ_MPPT_ACTIVE, 1 }, |
| 149 | { CMD_READ_MPPT_LIMIT, 1 }, |
| 150 | { CMD_SET_MPPT_LIMIT, 0 }, |
| 151 | { CMD_DISABLE_MPPT, 0 }, |
| 152 | { CMD_ENABLE_MPPT, 0 }, |
| 153 | { CMD_READ_VIN, 2 }, |
| 154 | { CMD_GET_FW_VERSION, 16 }, |
| 155 | { CMD_POWER_CYCLE, 0 }, |
| 156 | { CMD_POWER_OFF, 0 }, |
| 157 | { CMD_RESET_EC_SOFT, 0 }, |
| 158 | { CMD_ECHO, 5 }, |
| 159 | { CMD_GET_FW_DATE, 16 }, |
| 160 | { CMD_GET_FW_USER, 16 }, |
| 161 | { CMD_TURN_OFF_POWER, 0 }, |
| 162 | { CMD_READ_OLS, 2 }, |
| 163 | { CMD_OLS_SMT_LEDON, 0 }, |
| 164 | { CMD_OLS_SMT_LEDOFF, 0 }, |
| 165 | { CMD_START_OLS_ASSY, 0 }, |
| 166 | { CMD_STOP_OLS_ASSY, 0 }, |
| 167 | { CMD_OLS_SMTTEST_STOP, 0 }, |
| 168 | { CMD_READ_VIN_SCALED, 2 }, |
| 169 | { CMD_READ_BAT_MIN_W, 2 }, |
| 170 | { CMD_READ_BAR_MAX_W, 2 }, |
| 171 | { CMD_RESET_BAT_MINMAX_W, 0 }, |
| 172 | { CMD_READ_LOCATION, 1 }, |
| 173 | { CMD_WRITE_LOCATION, 0 }, |
| 174 | { CMD_GET_FW_HASH, 16 }, |
| 175 | { CMD_SUSPEND_HINT, 0 }, |
| 176 | { CMD_ENABLE_WAKE_TIMER, 0 }, |
| 177 | { CMD_SET_WAKE_TIMER, 0 }, |
| 178 | { CMD_ENABLE_WAKE_AUTORESET, 0 }, |
| 179 | { CMD_OLS_SET_LIMITS, 0 }, |
| 180 | { CMD_OLS_GET_LIMITS, 4 }, |
| 181 | { CMD_OLS_SET_CEILING, 0 }, |
| 182 | { CMD_OLS_GET_CEILING, 2 }, |
| 183 | { CMD_READ_EXT_SCI_MASK, 2 }, |
| 184 | { CMD_WRITE_EXT_SCI_MASK, 0 }, |
| 185 | |
| 186 | { } |
| 187 | }; |
| 188 | |
| 189 | #define EC_MAX_CMD_DATA_LEN 5 |
| 190 | #define EC_MAX_RESP_LEN 16 |
| 191 | |
| 192 | #define LOG_BUF_SIZE 128 |
| 193 | |
| 194 | #define PM_WAKEUP_TIME 1000 |
| 195 | |
| 196 | #define EC_ALL_EVENTS GENMASK(15, 0) |
| 197 | |
| 198 | enum ec_state_t { |
| 199 | CMD_STATE_IDLE = 0, |
| 200 | CMD_STATE_WAITING_FOR_SWITCH, |
| 201 | CMD_STATE_CMD_IN_TX_FIFO, |
| 202 | CMD_STATE_CMD_SENT, |
| 203 | CMD_STATE_RESP_RECEIVED, |
| 204 | CMD_STATE_ERROR_RECEIVED, |
| 205 | }; |
| 206 | |
| 207 | struct olpc_xo175_ec_cmd { |
| 208 | u8 command; |
| 209 | u8 nr_args; |
| 210 | u8 data_len; |
| 211 | u8 args[EC_MAX_CMD_DATA_LEN]; |
| 212 | }; |
| 213 | |
| 214 | struct olpc_xo175_ec_resp { |
| 215 | u8 channel; |
| 216 | u8 byte; |
| 217 | }; |
| 218 | |
| 219 | struct olpc_xo175_ec { |
| 220 | bool suspended; |
| 221 | |
| 222 | /* SPI related stuff. */ |
| 223 | struct spi_device *spi; |
| 224 | struct spi_transfer xfer; |
| 225 | struct spi_message msg; |
| 226 | union { |
| 227 | struct olpc_xo175_ec_cmd cmd; |
| 228 | struct olpc_xo175_ec_resp resp; |
| 229 | } tx_buf, rx_buf; |
| 230 | |
| 231 | /* GPIO for the CMD signals. */ |
| 232 | struct gpio_desc *gpio_cmd; |
| 233 | |
| 234 | /* Command handling related state. */ |
| 235 | spinlock_t cmd_state_lock; |
| 236 | int cmd_state; |
| 237 | bool cmd_running; |
| 238 | struct completion cmd_done; |
| 239 | struct olpc_xo175_ec_cmd cmd; |
| 240 | u8 resp_data[EC_MAX_RESP_LEN]; |
| 241 | int expected_resp_len; |
| 242 | int resp_len; |
| 243 | |
| 244 | /* Power button. */ |
| 245 | struct input_dev *pwrbtn; |
| 246 | |
| 247 | /* Debug handling. */ |
| 248 | char logbuf[LOG_BUF_SIZE]; |
| 249 | int logbuf_len; |
| 250 | }; |
| 251 | |
| 252 | static struct platform_device *olpc_ec; |
| 253 | |
| 254 | static int olpc_xo175_ec_resp_len(u8 cmd) |
| 255 | { |
| 256 | const struct ec_cmd_t *p; |
| 257 | |
| 258 | for (p = olpc_xo175_ec_cmds; p->cmd; p++) { |
| 259 | if (p->cmd == cmd) |
| 260 | return p->bytes_returned; |
| 261 | } |
| 262 | |
| 263 | return -EINVAL; |
| 264 | } |
| 265 | |
| 266 | static void olpc_xo175_ec_flush_logbuf(struct olpc_xo175_ec *priv) |
| 267 | { |
| 268 | dev_dbg(&priv->spi->dev, "got debug string [%*pE]\n", |
| 269 | priv->logbuf_len, priv->logbuf); |
| 270 | priv->logbuf_len = 0; |
| 271 | } |
| 272 | |
| 273 | static void olpc_xo175_ec_complete(void *arg); |
| 274 | |
| 275 | static void olpc_xo175_ec_send_command(struct olpc_xo175_ec *priv, void *cmd, |
| 276 | size_t cmdlen) |
| 277 | { |
| 278 | int ret; |
| 279 | |
| 280 | memcpy(&priv->tx_buf, cmd, cmdlen); |
| 281 | priv->xfer.len = cmdlen; |
| 282 | |
| 283 | spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); |
| 284 | |
| 285 | priv->msg.complete = olpc_xo175_ec_complete; |
| 286 | priv->msg.context = priv; |
| 287 | |
| 288 | ret = spi_async(priv->spi, &priv->msg); |
| 289 | if (ret) |
| 290 | dev_err(&priv->spi->dev, "spi_async() failed %d\n", ret); |
| 291 | } |
| 292 | |
| 293 | static void olpc_xo175_ec_read_packet(struct olpc_xo175_ec *priv) |
| 294 | { |
| 295 | u8 nonce[] = {0xA5, 0x5A}; |
| 296 | |
| 297 | olpc_xo175_ec_send_command(priv, nonce, sizeof(nonce)); |
| 298 | } |
| 299 | |
| 300 | static void olpc_xo175_ec_complete(void *arg) |
| 301 | { |
| 302 | struct olpc_xo175_ec *priv = arg; |
| 303 | struct device *dev = &priv->spi->dev; |
| 304 | struct power_supply *psy; |
| 305 | unsigned long flags; |
| 306 | u8 channel; |
| 307 | u8 byte; |
| 308 | int ret; |
| 309 | |
| 310 | ret = priv->msg.status; |
| 311 | if (ret) { |
| 312 | dev_err(dev, "SPI transfer failed: %d\n", ret); |
| 313 | |
| 314 | spin_lock_irqsave(&priv->cmd_state_lock, flags); |
| 315 | if (priv->cmd_running) { |
| 316 | priv->resp_len = 0; |
| 317 | priv->cmd_state = CMD_STATE_ERROR_RECEIVED; |
| 318 | complete(&priv->cmd_done); |
| 319 | } |
| 320 | spin_unlock_irqrestore(&priv->cmd_state_lock, flags); |
| 321 | |
| 322 | if (ret != -EINTR) |
| 323 | olpc_xo175_ec_read_packet(priv); |
| 324 | |
| 325 | return; |
| 326 | } |
| 327 | |
| 328 | channel = priv->rx_buf.resp.channel; |
| 329 | byte = priv->rx_buf.resp.byte; |
| 330 | |
| 331 | switch (channel) { |
| 332 | case CHAN_NONE: |
| 333 | spin_lock_irqsave(&priv->cmd_state_lock, flags); |
| 334 | |
| 335 | if (!priv->cmd_running) { |
| 336 | /* We can safely ignore these */ |
| 337 | dev_err(dev, "spurious FIFO read packet\n"); |
| 338 | spin_unlock_irqrestore(&priv->cmd_state_lock, flags); |
| 339 | return; |
| 340 | } |
| 341 | |
| 342 | priv->cmd_state = CMD_STATE_CMD_SENT; |
| 343 | if (!priv->expected_resp_len) |
| 344 | complete(&priv->cmd_done); |
| 345 | olpc_xo175_ec_read_packet(priv); |
| 346 | |
| 347 | spin_unlock_irqrestore(&priv->cmd_state_lock, flags); |
| 348 | return; |
| 349 | |
| 350 | case CHAN_SWITCH: |
| 351 | spin_lock_irqsave(&priv->cmd_state_lock, flags); |
| 352 | |
| 353 | if (!priv->cmd_running) { |
| 354 | /* Just go with the flow */ |
| 355 | dev_err(dev, "spurious SWITCH packet\n"); |
| 356 | memset(&priv->cmd, 0, sizeof(priv->cmd)); |
| 357 | priv->cmd.command = CMD_ECHO; |
| 358 | } |
| 359 | |
| 360 | priv->cmd_state = CMD_STATE_CMD_IN_TX_FIFO; |
| 361 | |
| 362 | /* Throw command into TxFIFO */ |
| 363 | gpiod_set_value_cansleep(priv->gpio_cmd, 0); |
| 364 | olpc_xo175_ec_send_command(priv, &priv->cmd, sizeof(priv->cmd)); |
| 365 | |
| 366 | spin_unlock_irqrestore(&priv->cmd_state_lock, flags); |
| 367 | return; |
| 368 | |
| 369 | case CHAN_CMD_RESP: |
| 370 | spin_lock_irqsave(&priv->cmd_state_lock, flags); |
| 371 | |
| 372 | if (!priv->cmd_running) { |
| 373 | dev_err(dev, "spurious response packet\n"); |
| 374 | } else if (priv->resp_len >= priv->expected_resp_len) { |
| 375 | dev_err(dev, "too many response packets\n"); |
| 376 | } else { |
| 377 | priv->resp_data[priv->resp_len++] = byte; |
| 378 | if (priv->resp_len == priv->expected_resp_len) { |
| 379 | priv->cmd_state = CMD_STATE_RESP_RECEIVED; |
| 380 | complete(&priv->cmd_done); |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | spin_unlock_irqrestore(&priv->cmd_state_lock, flags); |
| 385 | break; |
| 386 | |
| 387 | case CHAN_CMD_ERROR: |
| 388 | spin_lock_irqsave(&priv->cmd_state_lock, flags); |
| 389 | |
| 390 | if (!priv->cmd_running) { |
| 391 | dev_err(dev, "spurious cmd error packet\n"); |
| 392 | } else { |
| 393 | priv->resp_data[0] = byte; |
| 394 | priv->resp_len = 1; |
| 395 | priv->cmd_state = CMD_STATE_ERROR_RECEIVED; |
| 396 | complete(&priv->cmd_done); |
| 397 | } |
| 398 | spin_unlock_irqrestore(&priv->cmd_state_lock, flags); |
| 399 | break; |
| 400 | |
| 401 | case CHAN_KEYBOARD: |
| 402 | dev_warn(dev, "keyboard is not supported\n"); |
| 403 | break; |
| 404 | |
| 405 | case CHAN_TOUCHPAD: |
| 406 | dev_warn(dev, "touchpad is not supported\n"); |
| 407 | break; |
| 408 | |
| 409 | case CHAN_EVENT: |
| 410 | dev_dbg(dev, "got event %.2x\n", byte); |
| 411 | switch (byte) { |
| 412 | case EVENT_AC_CHANGE: |
Lubomir Rintel | 29e9eff | 2019-12-21 08:17:51 +0100 | [diff] [blame] | 413 | psy = power_supply_get_by_name("olpc_ac"); |
Lubomir Rintel | 0c3d931 | 2019-05-13 09:56:37 +0200 | [diff] [blame] | 414 | if (psy) { |
| 415 | power_supply_changed(psy); |
| 416 | power_supply_put(psy); |
| 417 | } |
| 418 | break; |
| 419 | case EVENT_BATTERY_STATUS: |
| 420 | case EVENT_BATTERY_CRITICAL: |
| 421 | case EVENT_BATTERY_SOC_CHANGE: |
| 422 | case EVENT_BATTERY_ERROR: |
Lubomir Rintel | 29e9eff | 2019-12-21 08:17:51 +0100 | [diff] [blame] | 423 | psy = power_supply_get_by_name("olpc_battery"); |
Lubomir Rintel | 0c3d931 | 2019-05-13 09:56:37 +0200 | [diff] [blame] | 424 | if (psy) { |
| 425 | power_supply_changed(psy); |
| 426 | power_supply_put(psy); |
| 427 | } |
| 428 | break; |
| 429 | case EVENT_POWER_PRESSED: |
| 430 | input_report_key(priv->pwrbtn, KEY_POWER, 1); |
| 431 | input_sync(priv->pwrbtn); |
| 432 | input_report_key(priv->pwrbtn, KEY_POWER, 0); |
| 433 | input_sync(priv->pwrbtn); |
Gustavo A. R. Silva | df561f66 | 2020-08-23 17:36:59 -0500 | [diff] [blame] | 434 | fallthrough; |
Lubomir Rintel | 0c3d931 | 2019-05-13 09:56:37 +0200 | [diff] [blame] | 435 | case EVENT_POWER_PRESS_WAKE: |
| 436 | case EVENT_TIMED_HOST_WAKE: |
| 437 | pm_wakeup_event(priv->pwrbtn->dev.parent, |
| 438 | PM_WAKEUP_TIME); |
| 439 | break; |
| 440 | default: |
| 441 | dev_dbg(dev, "ignored unknown event %.2x\n", byte); |
| 442 | break; |
| 443 | } |
| 444 | break; |
| 445 | |
| 446 | case CHAN_DEBUG: |
| 447 | if (byte == '\n') { |
| 448 | olpc_xo175_ec_flush_logbuf(priv); |
| 449 | } else if (isprint(byte)) { |
| 450 | priv->logbuf[priv->logbuf_len++] = byte; |
| 451 | if (priv->logbuf_len == LOG_BUF_SIZE) |
| 452 | olpc_xo175_ec_flush_logbuf(priv); |
| 453 | } |
| 454 | break; |
| 455 | |
| 456 | default: |
| 457 | dev_warn(dev, "unknown channel: %d, %.2x\n", channel, byte); |
| 458 | break; |
| 459 | } |
| 460 | |
| 461 | /* Most non-command packets get the TxFIFO refilled and an ACK. */ |
| 462 | olpc_xo175_ec_read_packet(priv); |
| 463 | } |
| 464 | |
| 465 | /* |
| 466 | * This function is protected with a mutex. We can safely assume that |
| 467 | * there will be only one instance of this function running at a time. |
| 468 | * One of the ways in which we enforce this is by waiting until we get |
| 469 | * all response bytes back from the EC, rather than just the number that |
| 470 | * the caller requests (otherwise, we might start a new command while an |
| 471 | * old command's response bytes are still incoming). |
| 472 | */ |
| 473 | static int olpc_xo175_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *resp, |
| 474 | size_t resp_len, void *ec_cb_arg) |
| 475 | { |
| 476 | struct olpc_xo175_ec *priv = ec_cb_arg; |
| 477 | struct device *dev = &priv->spi->dev; |
| 478 | unsigned long flags; |
| 479 | size_t nr_bytes; |
| 480 | int ret = 0; |
| 481 | |
| 482 | dev_dbg(dev, "CMD %x, %zd bytes expected\n", cmd, resp_len); |
| 483 | |
| 484 | if (inlen > 5) { |
| 485 | dev_err(dev, "command len %zd too big!\n", resp_len); |
| 486 | return -EOVERFLOW; |
| 487 | } |
| 488 | |
| 489 | /* Suspending in the middle of an EC command hoses things badly! */ |
| 490 | if (WARN_ON(priv->suspended)) |
| 491 | return -EBUSY; |
| 492 | |
| 493 | /* Ensure a valid command and return bytes */ |
| 494 | ret = olpc_xo175_ec_resp_len(cmd); |
| 495 | if (ret < 0) { |
| 496 | dev_err_ratelimited(dev, "unknown command 0x%x\n", cmd); |
| 497 | |
| 498 | /* |
| 499 | * Assume the best in our callers, and allow unknown commands |
| 500 | * through. I'm not the charitable type, but it was beaten |
| 501 | * into me. Just maintain a minimum standard of sanity. |
| 502 | */ |
| 503 | if (resp_len > sizeof(priv->resp_data)) { |
| 504 | dev_err(dev, "response too big: %zd!\n", resp_len); |
| 505 | return -EOVERFLOW; |
| 506 | } |
| 507 | nr_bytes = resp_len; |
| 508 | } else { |
| 509 | nr_bytes = (size_t)ret; |
Lubomir Rintel | fd43f16 | 2019-05-29 10:34:03 +0200 | [diff] [blame] | 510 | ret = 0; |
Lubomir Rintel | 0c3d931 | 2019-05-13 09:56:37 +0200 | [diff] [blame] | 511 | } |
| 512 | resp_len = min(resp_len, nr_bytes); |
| 513 | |
| 514 | spin_lock_irqsave(&priv->cmd_state_lock, flags); |
| 515 | |
| 516 | /* Initialize the state machine */ |
| 517 | init_completion(&priv->cmd_done); |
| 518 | priv->cmd_running = true; |
| 519 | priv->cmd_state = CMD_STATE_WAITING_FOR_SWITCH; |
| 520 | memset(&priv->cmd, 0, sizeof(priv->cmd)); |
| 521 | priv->cmd.command = cmd; |
| 522 | priv->cmd.nr_args = inlen; |
| 523 | priv->cmd.data_len = 0; |
| 524 | memcpy(priv->cmd.args, inbuf, inlen); |
| 525 | priv->expected_resp_len = nr_bytes; |
| 526 | priv->resp_len = 0; |
| 527 | |
| 528 | /* Tickle the cmd gpio to get things started */ |
| 529 | gpiod_set_value_cansleep(priv->gpio_cmd, 1); |
| 530 | |
| 531 | spin_unlock_irqrestore(&priv->cmd_state_lock, flags); |
| 532 | |
| 533 | /* The irq handler should do the rest */ |
| 534 | if (!wait_for_completion_timeout(&priv->cmd_done, |
| 535 | msecs_to_jiffies(4000))) { |
| 536 | dev_err(dev, "EC cmd error: timeout in STATE %d\n", |
| 537 | priv->cmd_state); |
| 538 | gpiod_set_value_cansleep(priv->gpio_cmd, 0); |
| 539 | spi_slave_abort(priv->spi); |
| 540 | olpc_xo175_ec_read_packet(priv); |
| 541 | return -ETIMEDOUT; |
| 542 | } |
| 543 | |
| 544 | spin_lock_irqsave(&priv->cmd_state_lock, flags); |
| 545 | |
| 546 | /* Deal with the results. */ |
| 547 | if (priv->cmd_state == CMD_STATE_ERROR_RECEIVED) { |
| 548 | /* EC-provided error is in the single response byte */ |
| 549 | dev_err(dev, "command 0x%x returned error 0x%x\n", |
| 550 | cmd, priv->resp_data[0]); |
| 551 | ret = -EREMOTEIO; |
| 552 | } else if (priv->resp_len != nr_bytes) { |
| 553 | dev_err(dev, "command 0x%x returned %d bytes, expected %zd bytes\n", |
| 554 | cmd, priv->resp_len, nr_bytes); |
| 555 | ret = -EREMOTEIO; |
| 556 | } else { |
| 557 | /* |
| 558 | * We may have 8 bytes in priv->resp, but we only care about |
| 559 | * what we've been asked for. If the caller asked for only 2 |
| 560 | * bytes, give them that. We've guaranteed that |
| 561 | * resp_len <= priv->resp_len and priv->resp_len == nr_bytes. |
| 562 | */ |
| 563 | memcpy(resp, priv->resp_data, resp_len); |
| 564 | } |
| 565 | |
| 566 | /* This should already be low, but just in case. */ |
| 567 | gpiod_set_value_cansleep(priv->gpio_cmd, 0); |
| 568 | priv->cmd_running = false; |
| 569 | |
| 570 | spin_unlock_irqrestore(&priv->cmd_state_lock, flags); |
| 571 | |
| 572 | return ret; |
| 573 | } |
| 574 | |
| 575 | static int olpc_xo175_ec_set_event_mask(unsigned int mask) |
| 576 | { |
| 577 | u8 args[2]; |
| 578 | |
| 579 | args[0] = mask >> 0; |
| 580 | args[1] = mask >> 8; |
| 581 | return olpc_ec_cmd(CMD_WRITE_EXT_SCI_MASK, args, 2, NULL, 0); |
| 582 | } |
| 583 | |
| 584 | static void olpc_xo175_ec_power_off(void) |
| 585 | { |
| 586 | while (1) { |
| 587 | olpc_ec_cmd(CMD_POWER_OFF, NULL, 0, NULL, 0); |
| 588 | mdelay(1000); |
| 589 | } |
| 590 | } |
| 591 | |
| 592 | static int __maybe_unused olpc_xo175_ec_suspend(struct device *dev) |
| 593 | { |
| 594 | struct olpc_xo175_ec *priv = dev_get_drvdata(dev); |
| 595 | static struct { |
| 596 | u8 suspend; |
| 597 | u32 suspend_count; |
| 598 | } __packed hintargs; |
| 599 | static unsigned int suspend_count; |
| 600 | |
| 601 | /* |
| 602 | * SOC_SLEEP is not wired to the EC on B3 and earlier boards. |
| 603 | * This command lets the EC know instead. The suspend count doesn't seem |
| 604 | * to be used anywhere but in the EC debug output. |
| 605 | */ |
| 606 | hintargs.suspend = 1; |
| 607 | hintargs.suspend_count = suspend_count++; |
| 608 | olpc_ec_cmd(CMD_SUSPEND_HINT, (void *)&hintargs, sizeof(hintargs), |
| 609 | NULL, 0); |
| 610 | |
| 611 | /* |
| 612 | * After we've sent the suspend hint, don't allow further EC commands |
| 613 | * to be run until we've resumed. Userspace tasks should be frozen, |
| 614 | * but kernel threads and interrupts could still schedule EC commands. |
| 615 | */ |
| 616 | priv->suspended = true; |
| 617 | |
| 618 | return 0; |
| 619 | } |
| 620 | |
| 621 | static int __maybe_unused olpc_xo175_ec_resume_noirq(struct device *dev) |
| 622 | { |
| 623 | struct olpc_xo175_ec *priv = dev_get_drvdata(dev); |
| 624 | |
| 625 | priv->suspended = false; |
| 626 | |
| 627 | return 0; |
| 628 | } |
| 629 | |
| 630 | static int __maybe_unused olpc_xo175_ec_resume(struct device *dev) |
| 631 | { |
| 632 | u8 x = 0; |
| 633 | |
| 634 | /* |
| 635 | * The resume hint is only needed if no other commands are |
| 636 | * being sent during resume. all it does is tell the EC |
| 637 | * the SoC is definitely awake. |
| 638 | */ |
| 639 | olpc_ec_cmd(CMD_SUSPEND_HINT, &x, 1, NULL, 0); |
| 640 | |
| 641 | /* Enable all EC events while we're awake */ |
| 642 | olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); |
| 643 | |
| 644 | return 0; |
| 645 | } |
| 646 | |
| 647 | static struct olpc_ec_driver olpc_xo175_ec_driver = { |
| 648 | .ec_cmd = olpc_xo175_ec_cmd, |
| 649 | }; |
| 650 | |
| 651 | static int olpc_xo175_ec_remove(struct spi_device *spi) |
| 652 | { |
| 653 | if (pm_power_off == olpc_xo175_ec_power_off) |
| 654 | pm_power_off = NULL; |
| 655 | |
| 656 | spi_slave_abort(spi); |
| 657 | |
| 658 | platform_device_unregister(olpc_ec); |
| 659 | olpc_ec = NULL; |
| 660 | |
| 661 | return 0; |
| 662 | } |
| 663 | |
| 664 | static int olpc_xo175_ec_probe(struct spi_device *spi) |
| 665 | { |
| 666 | struct olpc_xo175_ec *priv; |
| 667 | int ret; |
| 668 | |
| 669 | if (olpc_ec) { |
| 670 | dev_err(&spi->dev, "OLPC EC already registered.\n"); |
| 671 | return -EBUSY; |
| 672 | } |
| 673 | |
| 674 | priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); |
| 675 | if (!priv) |
| 676 | return -ENOMEM; |
| 677 | |
| 678 | priv->gpio_cmd = devm_gpiod_get(&spi->dev, "cmd", GPIOD_OUT_LOW); |
| 679 | if (IS_ERR(priv->gpio_cmd)) { |
| 680 | dev_err(&spi->dev, "failed to get cmd gpio: %ld\n", |
| 681 | PTR_ERR(priv->gpio_cmd)); |
| 682 | return PTR_ERR(priv->gpio_cmd); |
| 683 | } |
| 684 | |
| 685 | priv->spi = spi; |
| 686 | |
| 687 | spin_lock_init(&priv->cmd_state_lock); |
| 688 | priv->cmd_state = CMD_STATE_IDLE; |
| 689 | init_completion(&priv->cmd_done); |
| 690 | |
| 691 | priv->logbuf_len = 0; |
| 692 | |
| 693 | /* Set up power button input device */ |
| 694 | priv->pwrbtn = devm_input_allocate_device(&spi->dev); |
| 695 | if (!priv->pwrbtn) |
| 696 | return -ENOMEM; |
| 697 | priv->pwrbtn->name = "Power Button"; |
| 698 | priv->pwrbtn->dev.parent = &spi->dev; |
| 699 | input_set_capability(priv->pwrbtn, EV_KEY, KEY_POWER); |
| 700 | ret = input_register_device(priv->pwrbtn); |
| 701 | if (ret) { |
| 702 | dev_err(&spi->dev, "error registering input device: %d\n", ret); |
| 703 | return ret; |
| 704 | } |
| 705 | |
| 706 | spi_set_drvdata(spi, priv); |
| 707 | |
| 708 | priv->xfer.rx_buf = &priv->rx_buf; |
| 709 | priv->xfer.tx_buf = &priv->tx_buf; |
| 710 | |
| 711 | olpc_xo175_ec_read_packet(priv); |
| 712 | |
| 713 | olpc_ec_driver_register(&olpc_xo175_ec_driver, priv); |
| 714 | olpc_ec = platform_device_register_resndata(&spi->dev, "olpc-ec", -1, |
| 715 | NULL, 0, NULL, 0); |
| 716 | |
| 717 | /* Enable all EC events while we're awake */ |
| 718 | olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); |
| 719 | |
| 720 | if (pm_power_off == NULL) |
| 721 | pm_power_off = olpc_xo175_ec_power_off; |
| 722 | |
| 723 | dev_info(&spi->dev, "OLPC XO-1.75 Embedded Controller driver\n"); |
| 724 | |
| 725 | return 0; |
| 726 | } |
| 727 | |
| 728 | static const struct dev_pm_ops olpc_xo175_ec_pm_ops = { |
| 729 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, olpc_xo175_ec_resume_noirq) |
| 730 | SET_RUNTIME_PM_OPS(olpc_xo175_ec_suspend, olpc_xo175_ec_resume, NULL) |
| 731 | }; |
| 732 | |
| 733 | static const struct of_device_id olpc_xo175_ec_of_match[] = { |
| 734 | { .compatible = "olpc,xo1.75-ec" }, |
| 735 | { } |
| 736 | }; |
| 737 | MODULE_DEVICE_TABLE(of, olpc_xo175_ec_of_match); |
| 738 | |
Lubomir Rintel | 6acf5d7 | 2019-07-23 11:20:22 +0200 | [diff] [blame] | 739 | static const struct spi_device_id olpc_xo175_ec_id_table[] = { |
| 740 | { "xo1.75-ec", 0 }, |
| 741 | {} |
| 742 | }; |
| 743 | MODULE_DEVICE_TABLE(spi, olpc_xo175_ec_id_table); |
| 744 | |
Lubomir Rintel | 0c3d931 | 2019-05-13 09:56:37 +0200 | [diff] [blame] | 745 | static struct spi_driver olpc_xo175_ec_spi_driver = { |
| 746 | .driver = { |
| 747 | .name = "olpc-xo175-ec", |
| 748 | .of_match_table = olpc_xo175_ec_of_match, |
| 749 | .pm = &olpc_xo175_ec_pm_ops, |
| 750 | }, |
| 751 | .probe = olpc_xo175_ec_probe, |
| 752 | .remove = olpc_xo175_ec_remove, |
| 753 | }; |
| 754 | module_spi_driver(olpc_xo175_ec_spi_driver); |
| 755 | |
| 756 | MODULE_DESCRIPTION("OLPC XO-1.75 Embedded Controller driver"); |
| 757 | MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>"); /* Functionality */ |
| 758 | MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); /* Bugs */ |
| 759 | MODULE_LICENSE("GPL"); |