18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for the OLPC XO-1.75 Embedded Controller. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * The EC protocol is documented at: 68c2ecf20Sopenharmony_ci * http://wiki.laptop.org/go/XO_1.75_HOST_to_EC_Protocol 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Copyright (C) 2010 One Laptop per Child Foundation. 98c2ecf20Sopenharmony_ci * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk> 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/completion.h> 138c2ecf20Sopenharmony_ci#include <linux/ctype.h> 148c2ecf20Sopenharmony_ci#include <linux/delay.h> 158c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 168c2ecf20Sopenharmony_ci#include <linux/input.h> 178c2ecf20Sopenharmony_ci#include <linux/kfifo.h> 188c2ecf20Sopenharmony_ci#include <linux/module.h> 198c2ecf20Sopenharmony_ci#include <linux/olpc-ec.h> 208c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 218c2ecf20Sopenharmony_ci#include <linux/power_supply.h> 228c2ecf20Sopenharmony_ci#include <linux/reboot.h> 238c2ecf20Sopenharmony_ci#include <linux/slab.h> 248c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 258c2ecf20Sopenharmony_ci#include <linux/spi/spi.h> 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistruct ec_cmd_t { 288c2ecf20Sopenharmony_ci u8 cmd; 298c2ecf20Sopenharmony_ci u8 bytes_returned; 308c2ecf20Sopenharmony_ci}; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cienum ec_chan_t { 338c2ecf20Sopenharmony_ci CHAN_NONE = 0, 348c2ecf20Sopenharmony_ci CHAN_SWITCH, 358c2ecf20Sopenharmony_ci CHAN_CMD_RESP, 368c2ecf20Sopenharmony_ci CHAN_KEYBOARD, 378c2ecf20Sopenharmony_ci CHAN_TOUCHPAD, 388c2ecf20Sopenharmony_ci CHAN_EVENT, 398c2ecf20Sopenharmony_ci CHAN_DEBUG, 408c2ecf20Sopenharmony_ci CHAN_CMD_ERROR, 418c2ecf20Sopenharmony_ci}; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci/* 448c2ecf20Sopenharmony_ci * EC events 458c2ecf20Sopenharmony_ci */ 468c2ecf20Sopenharmony_ci#define EVENT_AC_CHANGE 1 /* AC plugged/unplugged */ 478c2ecf20Sopenharmony_ci#define EVENT_BATTERY_STATUS 2 /* Battery low/full/error/gone */ 488c2ecf20Sopenharmony_ci#define EVENT_BATTERY_CRITICAL 3 /* Battery critical voltage */ 498c2ecf20Sopenharmony_ci#define EVENT_BATTERY_SOC_CHANGE 4 /* 1% SOC Change */ 508c2ecf20Sopenharmony_ci#define EVENT_BATTERY_ERROR 5 /* Abnormal error, query for cause */ 518c2ecf20Sopenharmony_ci#define EVENT_POWER_PRESSED 6 /* Power button was pressed */ 528c2ecf20Sopenharmony_ci#define EVENT_POWER_PRESS_WAKE 7 /* Woken up with a power button */ 538c2ecf20Sopenharmony_ci#define EVENT_TIMED_HOST_WAKE 8 /* Host wake timer */ 548c2ecf20Sopenharmony_ci#define EVENT_OLS_HIGH_LIMIT 9 /* OLS crossed dark threshold */ 558c2ecf20Sopenharmony_ci#define EVENT_OLS_LOW_LIMIT 10 /* OLS crossed light threshold */ 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci/* 588c2ecf20Sopenharmony_ci * EC commands 598c2ecf20Sopenharmony_ci * (from http://dev.laptop.org/git/users/rsmith/ec-1.75/tree/ec_cmd.h) 608c2ecf20Sopenharmony_ci */ 618c2ecf20Sopenharmony_ci#define CMD_GET_API_VERSION 0x08 /* out: u8 */ 628c2ecf20Sopenharmony_ci#define CMD_READ_VOLTAGE 0x10 /* out: u16, *9.76/32, mV */ 638c2ecf20Sopenharmony_ci#define CMD_READ_CURRENT 0x11 /* out: s16, *15.625/120, mA */ 648c2ecf20Sopenharmony_ci#define CMD_READ_ACR 0x12 /* out: s16, *6250/15, uAh */ 658c2ecf20Sopenharmony_ci#define CMD_READ_BATT_TEMPERATURE 0x13 /* out: u16, *100/256, deg C */ 668c2ecf20Sopenharmony_ci#define CMD_READ_AMBIENT_TEMPERATURE 0x14 /* unimplemented, no hardware */ 678c2ecf20Sopenharmony_ci#define CMD_READ_BATTERY_STATUS 0x15 /* out: u8, bitmask */ 688c2ecf20Sopenharmony_ci#define CMD_READ_SOC 0x16 /* out: u8, percentage */ 698c2ecf20Sopenharmony_ci#define CMD_READ_GAUGE_ID 0x17 /* out: u8 * 8 */ 708c2ecf20Sopenharmony_ci#define CMD_READ_GAUGE_DATA 0x18 /* in: u8 addr, out: u8 data */ 718c2ecf20Sopenharmony_ci#define CMD_READ_BOARD_ID 0x19 /* out: u16 (platform id) */ 728c2ecf20Sopenharmony_ci#define CMD_READ_BATT_ERR_CODE 0x1f /* out: u8, error bitmask */ 738c2ecf20Sopenharmony_ci#define CMD_SET_DCON_POWER 0x26 /* in: u8 */ 748c2ecf20Sopenharmony_ci#define CMD_RESET_EC 0x28 /* none */ 758c2ecf20Sopenharmony_ci#define CMD_READ_BATTERY_TYPE 0x2c /* out: u8 */ 768c2ecf20Sopenharmony_ci#define CMD_SET_AUTOWAK 0x33 /* out: u8 */ 778c2ecf20Sopenharmony_ci#define CMD_SET_EC_WAKEUP_TIMER 0x36 /* in: u32, out: ? */ 788c2ecf20Sopenharmony_ci#define CMD_READ_EXT_SCI_MASK 0x37 /* ? */ 798c2ecf20Sopenharmony_ci#define CMD_WRITE_EXT_SCI_MASK 0x38 /* ? */ 808c2ecf20Sopenharmony_ci#define CMD_CLEAR_EC_WAKEUP_TIMER 0x39 /* none */ 818c2ecf20Sopenharmony_ci#define CMD_ENABLE_RUNIN_DISCHARGE 0x3B /* none */ 828c2ecf20Sopenharmony_ci#define CMD_DISABLE_RUNIN_DISCHARGE 0x3C /* none */ 838c2ecf20Sopenharmony_ci#define CMD_READ_MPPT_ACTIVE 0x3d /* out: u8 */ 848c2ecf20Sopenharmony_ci#define CMD_READ_MPPT_LIMIT 0x3e /* out: u8 */ 858c2ecf20Sopenharmony_ci#define CMD_SET_MPPT_LIMIT 0x3f /* in: u8 */ 868c2ecf20Sopenharmony_ci#define CMD_DISABLE_MPPT 0x40 /* none */ 878c2ecf20Sopenharmony_ci#define CMD_ENABLE_MPPT 0x41 /* none */ 888c2ecf20Sopenharmony_ci#define CMD_READ_VIN 0x42 /* out: u16 */ 898c2ecf20Sopenharmony_ci#define CMD_EXT_SCI_QUERY 0x43 /* ? */ 908c2ecf20Sopenharmony_ci#define RSP_KEYBOARD_DATA 0x48 /* ? */ 918c2ecf20Sopenharmony_ci#define RSP_TOUCHPAD_DATA 0x49 /* ? */ 928c2ecf20Sopenharmony_ci#define CMD_GET_FW_VERSION 0x4a /* out: u8 * 16 */ 938c2ecf20Sopenharmony_ci#define CMD_POWER_CYCLE 0x4b /* none */ 948c2ecf20Sopenharmony_ci#define CMD_POWER_OFF 0x4c /* none */ 958c2ecf20Sopenharmony_ci#define CMD_RESET_EC_SOFT 0x4d /* none */ 968c2ecf20Sopenharmony_ci#define CMD_READ_GAUGE_U16 0x4e /* ? */ 978c2ecf20Sopenharmony_ci#define CMD_ENABLE_MOUSE 0x4f /* ? */ 988c2ecf20Sopenharmony_ci#define CMD_ECHO 0x52 /* in: u8 * 5, out: u8 * 5 */ 998c2ecf20Sopenharmony_ci#define CMD_GET_FW_DATE 0x53 /* out: u8 * 16 */ 1008c2ecf20Sopenharmony_ci#define CMD_GET_FW_USER 0x54 /* out: u8 * 16 */ 1018c2ecf20Sopenharmony_ci#define CMD_TURN_OFF_POWER 0x55 /* none (same as 0x4c) */ 1028c2ecf20Sopenharmony_ci#define CMD_READ_OLS 0x56 /* out: u16 */ 1038c2ecf20Sopenharmony_ci#define CMD_OLS_SMT_LEDON 0x57 /* none */ 1048c2ecf20Sopenharmony_ci#define CMD_OLS_SMT_LEDOFF 0x58 /* none */ 1058c2ecf20Sopenharmony_ci#define CMD_START_OLS_ASSY 0x59 /* none */ 1068c2ecf20Sopenharmony_ci#define CMD_STOP_OLS_ASSY 0x5a /* none */ 1078c2ecf20Sopenharmony_ci#define CMD_OLS_SMTTEST_STOP 0x5b /* none */ 1088c2ecf20Sopenharmony_ci#define CMD_READ_VIN_SCALED 0x5c /* out: u16 */ 1098c2ecf20Sopenharmony_ci#define CMD_READ_BAT_MIN_W 0x5d /* out: u16 */ 1108c2ecf20Sopenharmony_ci#define CMD_READ_BAR_MAX_W 0x5e /* out: u16 */ 1118c2ecf20Sopenharmony_ci#define CMD_RESET_BAT_MINMAX_W 0x5f /* none */ 1128c2ecf20Sopenharmony_ci#define CMD_READ_LOCATION 0x60 /* in: u16 addr, out: u8 data */ 1138c2ecf20Sopenharmony_ci#define CMD_WRITE_LOCATION 0x61 /* in: u16 addr, u8 data */ 1148c2ecf20Sopenharmony_ci#define CMD_KEYBOARD_CMD 0x62 /* in: u8, out: ? */ 1158c2ecf20Sopenharmony_ci#define CMD_TOUCHPAD_CMD 0x63 /* in: u8, out: ? */ 1168c2ecf20Sopenharmony_ci#define CMD_GET_FW_HASH 0x64 /* out: u8 * 16 */ 1178c2ecf20Sopenharmony_ci#define CMD_SUSPEND_HINT 0x65 /* in: u8 */ 1188c2ecf20Sopenharmony_ci#define CMD_ENABLE_WAKE_TIMER 0x66 /* in: u8 */ 1198c2ecf20Sopenharmony_ci#define CMD_SET_WAKE_TIMER 0x67 /* in: 32 */ 1208c2ecf20Sopenharmony_ci#define CMD_ENABLE_WAKE_AUTORESET 0x68 /* in: u8 */ 1218c2ecf20Sopenharmony_ci#define CMD_OLS_SET_LIMITS 0x69 /* in: u16, u16 */ 1228c2ecf20Sopenharmony_ci#define CMD_OLS_GET_LIMITS 0x6a /* out: u16, u16 */ 1238c2ecf20Sopenharmony_ci#define CMD_OLS_SET_CEILING 0x6b /* in: u16 */ 1248c2ecf20Sopenharmony_ci#define CMD_OLS_GET_CEILING 0x6c /* out: u16 */ 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci/* 1278c2ecf20Sopenharmony_ci * Accepted EC commands, and how many bytes they return. There are plenty 1288c2ecf20Sopenharmony_ci * of EC commands that are no longer implemented, or are implemented only on 1298c2ecf20Sopenharmony_ci * certain older boards. 1308c2ecf20Sopenharmony_ci */ 1318c2ecf20Sopenharmony_cistatic const struct ec_cmd_t olpc_xo175_ec_cmds[] = { 1328c2ecf20Sopenharmony_ci { CMD_GET_API_VERSION, 1 }, 1338c2ecf20Sopenharmony_ci { CMD_READ_VOLTAGE, 2 }, 1348c2ecf20Sopenharmony_ci { CMD_READ_CURRENT, 2 }, 1358c2ecf20Sopenharmony_ci { CMD_READ_ACR, 2 }, 1368c2ecf20Sopenharmony_ci { CMD_READ_BATT_TEMPERATURE, 2 }, 1378c2ecf20Sopenharmony_ci { CMD_READ_BATTERY_STATUS, 1 }, 1388c2ecf20Sopenharmony_ci { CMD_READ_SOC, 1 }, 1398c2ecf20Sopenharmony_ci { CMD_READ_GAUGE_ID, 8 }, 1408c2ecf20Sopenharmony_ci { CMD_READ_GAUGE_DATA, 1 }, 1418c2ecf20Sopenharmony_ci { CMD_READ_BOARD_ID, 2 }, 1428c2ecf20Sopenharmony_ci { CMD_READ_BATT_ERR_CODE, 1 }, 1438c2ecf20Sopenharmony_ci { CMD_SET_DCON_POWER, 0 }, 1448c2ecf20Sopenharmony_ci { CMD_RESET_EC, 0 }, 1458c2ecf20Sopenharmony_ci { CMD_READ_BATTERY_TYPE, 1 }, 1468c2ecf20Sopenharmony_ci { CMD_ENABLE_RUNIN_DISCHARGE, 0 }, 1478c2ecf20Sopenharmony_ci { CMD_DISABLE_RUNIN_DISCHARGE, 0 }, 1488c2ecf20Sopenharmony_ci { CMD_READ_MPPT_ACTIVE, 1 }, 1498c2ecf20Sopenharmony_ci { CMD_READ_MPPT_LIMIT, 1 }, 1508c2ecf20Sopenharmony_ci { CMD_SET_MPPT_LIMIT, 0 }, 1518c2ecf20Sopenharmony_ci { CMD_DISABLE_MPPT, 0 }, 1528c2ecf20Sopenharmony_ci { CMD_ENABLE_MPPT, 0 }, 1538c2ecf20Sopenharmony_ci { CMD_READ_VIN, 2 }, 1548c2ecf20Sopenharmony_ci { CMD_GET_FW_VERSION, 16 }, 1558c2ecf20Sopenharmony_ci { CMD_POWER_CYCLE, 0 }, 1568c2ecf20Sopenharmony_ci { CMD_POWER_OFF, 0 }, 1578c2ecf20Sopenharmony_ci { CMD_RESET_EC_SOFT, 0 }, 1588c2ecf20Sopenharmony_ci { CMD_ECHO, 5 }, 1598c2ecf20Sopenharmony_ci { CMD_GET_FW_DATE, 16 }, 1608c2ecf20Sopenharmony_ci { CMD_GET_FW_USER, 16 }, 1618c2ecf20Sopenharmony_ci { CMD_TURN_OFF_POWER, 0 }, 1628c2ecf20Sopenharmony_ci { CMD_READ_OLS, 2 }, 1638c2ecf20Sopenharmony_ci { CMD_OLS_SMT_LEDON, 0 }, 1648c2ecf20Sopenharmony_ci { CMD_OLS_SMT_LEDOFF, 0 }, 1658c2ecf20Sopenharmony_ci { CMD_START_OLS_ASSY, 0 }, 1668c2ecf20Sopenharmony_ci { CMD_STOP_OLS_ASSY, 0 }, 1678c2ecf20Sopenharmony_ci { CMD_OLS_SMTTEST_STOP, 0 }, 1688c2ecf20Sopenharmony_ci { CMD_READ_VIN_SCALED, 2 }, 1698c2ecf20Sopenharmony_ci { CMD_READ_BAT_MIN_W, 2 }, 1708c2ecf20Sopenharmony_ci { CMD_READ_BAR_MAX_W, 2 }, 1718c2ecf20Sopenharmony_ci { CMD_RESET_BAT_MINMAX_W, 0 }, 1728c2ecf20Sopenharmony_ci { CMD_READ_LOCATION, 1 }, 1738c2ecf20Sopenharmony_ci { CMD_WRITE_LOCATION, 0 }, 1748c2ecf20Sopenharmony_ci { CMD_GET_FW_HASH, 16 }, 1758c2ecf20Sopenharmony_ci { CMD_SUSPEND_HINT, 0 }, 1768c2ecf20Sopenharmony_ci { CMD_ENABLE_WAKE_TIMER, 0 }, 1778c2ecf20Sopenharmony_ci { CMD_SET_WAKE_TIMER, 0 }, 1788c2ecf20Sopenharmony_ci { CMD_ENABLE_WAKE_AUTORESET, 0 }, 1798c2ecf20Sopenharmony_ci { CMD_OLS_SET_LIMITS, 0 }, 1808c2ecf20Sopenharmony_ci { CMD_OLS_GET_LIMITS, 4 }, 1818c2ecf20Sopenharmony_ci { CMD_OLS_SET_CEILING, 0 }, 1828c2ecf20Sopenharmony_ci { CMD_OLS_GET_CEILING, 2 }, 1838c2ecf20Sopenharmony_ci { CMD_READ_EXT_SCI_MASK, 2 }, 1848c2ecf20Sopenharmony_ci { CMD_WRITE_EXT_SCI_MASK, 0 }, 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci { } 1878c2ecf20Sopenharmony_ci}; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci#define EC_MAX_CMD_DATA_LEN 5 1908c2ecf20Sopenharmony_ci#define EC_MAX_RESP_LEN 16 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci#define LOG_BUF_SIZE 128 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci#define PM_WAKEUP_TIME 1000 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci#define EC_ALL_EVENTS GENMASK(15, 0) 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cienum ec_state_t { 1998c2ecf20Sopenharmony_ci CMD_STATE_IDLE = 0, 2008c2ecf20Sopenharmony_ci CMD_STATE_WAITING_FOR_SWITCH, 2018c2ecf20Sopenharmony_ci CMD_STATE_CMD_IN_TX_FIFO, 2028c2ecf20Sopenharmony_ci CMD_STATE_CMD_SENT, 2038c2ecf20Sopenharmony_ci CMD_STATE_RESP_RECEIVED, 2048c2ecf20Sopenharmony_ci CMD_STATE_ERROR_RECEIVED, 2058c2ecf20Sopenharmony_ci}; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistruct olpc_xo175_ec_cmd { 2088c2ecf20Sopenharmony_ci u8 command; 2098c2ecf20Sopenharmony_ci u8 nr_args; 2108c2ecf20Sopenharmony_ci u8 data_len; 2118c2ecf20Sopenharmony_ci u8 args[EC_MAX_CMD_DATA_LEN]; 2128c2ecf20Sopenharmony_ci}; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_cistruct olpc_xo175_ec_resp { 2158c2ecf20Sopenharmony_ci u8 channel; 2168c2ecf20Sopenharmony_ci u8 byte; 2178c2ecf20Sopenharmony_ci}; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistruct olpc_xo175_ec { 2208c2ecf20Sopenharmony_ci bool suspended; 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci /* SPI related stuff. */ 2238c2ecf20Sopenharmony_ci struct spi_device *spi; 2248c2ecf20Sopenharmony_ci struct spi_transfer xfer; 2258c2ecf20Sopenharmony_ci struct spi_message msg; 2268c2ecf20Sopenharmony_ci union { 2278c2ecf20Sopenharmony_ci struct olpc_xo175_ec_cmd cmd; 2288c2ecf20Sopenharmony_ci struct olpc_xo175_ec_resp resp; 2298c2ecf20Sopenharmony_ci } tx_buf, rx_buf; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci /* GPIO for the CMD signals. */ 2328c2ecf20Sopenharmony_ci struct gpio_desc *gpio_cmd; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci /* Command handling related state. */ 2358c2ecf20Sopenharmony_ci spinlock_t cmd_state_lock; 2368c2ecf20Sopenharmony_ci int cmd_state; 2378c2ecf20Sopenharmony_ci bool cmd_running; 2388c2ecf20Sopenharmony_ci struct completion cmd_done; 2398c2ecf20Sopenharmony_ci struct olpc_xo175_ec_cmd cmd; 2408c2ecf20Sopenharmony_ci u8 resp_data[EC_MAX_RESP_LEN]; 2418c2ecf20Sopenharmony_ci int expected_resp_len; 2428c2ecf20Sopenharmony_ci int resp_len; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci /* Power button. */ 2458c2ecf20Sopenharmony_ci struct input_dev *pwrbtn; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci /* Debug handling. */ 2488c2ecf20Sopenharmony_ci char logbuf[LOG_BUF_SIZE]; 2498c2ecf20Sopenharmony_ci int logbuf_len; 2508c2ecf20Sopenharmony_ci}; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic struct platform_device *olpc_ec; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int olpc_xo175_ec_resp_len(u8 cmd) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci const struct ec_cmd_t *p; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci for (p = olpc_xo175_ec_cmds; p->cmd; p++) { 2598c2ecf20Sopenharmony_ci if (p->cmd == cmd) 2608c2ecf20Sopenharmony_ci return p->bytes_returned; 2618c2ecf20Sopenharmony_ci } 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci return -EINVAL; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic void olpc_xo175_ec_flush_logbuf(struct olpc_xo175_ec *priv) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci dev_dbg(&priv->spi->dev, "got debug string [%*pE]\n", 2698c2ecf20Sopenharmony_ci priv->logbuf_len, priv->logbuf); 2708c2ecf20Sopenharmony_ci priv->logbuf_len = 0; 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cistatic void olpc_xo175_ec_complete(void *arg); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cistatic void olpc_xo175_ec_send_command(struct olpc_xo175_ec *priv, void *cmd, 2768c2ecf20Sopenharmony_ci size_t cmdlen) 2778c2ecf20Sopenharmony_ci{ 2788c2ecf20Sopenharmony_ci int ret; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci memcpy(&priv->tx_buf, cmd, cmdlen); 2818c2ecf20Sopenharmony_ci priv->xfer.len = cmdlen; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci priv->msg.complete = olpc_xo175_ec_complete; 2868c2ecf20Sopenharmony_ci priv->msg.context = priv; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci ret = spi_async(priv->spi, &priv->msg); 2898c2ecf20Sopenharmony_ci if (ret) 2908c2ecf20Sopenharmony_ci dev_err(&priv->spi->dev, "spi_async() failed %d\n", ret); 2918c2ecf20Sopenharmony_ci} 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cistatic void olpc_xo175_ec_read_packet(struct olpc_xo175_ec *priv) 2948c2ecf20Sopenharmony_ci{ 2958c2ecf20Sopenharmony_ci u8 nonce[] = {0xA5, 0x5A}; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci olpc_xo175_ec_send_command(priv, nonce, sizeof(nonce)); 2988c2ecf20Sopenharmony_ci} 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_cistatic void olpc_xo175_ec_complete(void *arg) 3018c2ecf20Sopenharmony_ci{ 3028c2ecf20Sopenharmony_ci struct olpc_xo175_ec *priv = arg; 3038c2ecf20Sopenharmony_ci struct device *dev = &priv->spi->dev; 3048c2ecf20Sopenharmony_ci struct power_supply *psy; 3058c2ecf20Sopenharmony_ci unsigned long flags; 3068c2ecf20Sopenharmony_ci u8 channel; 3078c2ecf20Sopenharmony_ci u8 byte; 3088c2ecf20Sopenharmony_ci int ret; 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci ret = priv->msg.status; 3118c2ecf20Sopenharmony_ci if (ret) { 3128c2ecf20Sopenharmony_ci dev_err(dev, "SPI transfer failed: %d\n", ret); 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->cmd_state_lock, flags); 3158c2ecf20Sopenharmony_ci if (priv->cmd_running) { 3168c2ecf20Sopenharmony_ci priv->resp_len = 0; 3178c2ecf20Sopenharmony_ci priv->cmd_state = CMD_STATE_ERROR_RECEIVED; 3188c2ecf20Sopenharmony_ci complete(&priv->cmd_done); 3198c2ecf20Sopenharmony_ci } 3208c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->cmd_state_lock, flags); 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci if (ret != -EINTR) 3238c2ecf20Sopenharmony_ci olpc_xo175_ec_read_packet(priv); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci return; 3268c2ecf20Sopenharmony_ci } 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci channel = priv->rx_buf.resp.channel; 3298c2ecf20Sopenharmony_ci byte = priv->rx_buf.resp.byte; 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci switch (channel) { 3328c2ecf20Sopenharmony_ci case CHAN_NONE: 3338c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->cmd_state_lock, flags); 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci if (!priv->cmd_running) { 3368c2ecf20Sopenharmony_ci /* We can safely ignore these */ 3378c2ecf20Sopenharmony_ci dev_err(dev, "spurious FIFO read packet\n"); 3388c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->cmd_state_lock, flags); 3398c2ecf20Sopenharmony_ci return; 3408c2ecf20Sopenharmony_ci } 3418c2ecf20Sopenharmony_ci 3428c2ecf20Sopenharmony_ci priv->cmd_state = CMD_STATE_CMD_SENT; 3438c2ecf20Sopenharmony_ci if (!priv->expected_resp_len) 3448c2ecf20Sopenharmony_ci complete(&priv->cmd_done); 3458c2ecf20Sopenharmony_ci olpc_xo175_ec_read_packet(priv); 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->cmd_state_lock, flags); 3488c2ecf20Sopenharmony_ci return; 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci case CHAN_SWITCH: 3518c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->cmd_state_lock, flags); 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci if (!priv->cmd_running) { 3548c2ecf20Sopenharmony_ci /* Just go with the flow */ 3558c2ecf20Sopenharmony_ci dev_err(dev, "spurious SWITCH packet\n"); 3568c2ecf20Sopenharmony_ci memset(&priv->cmd, 0, sizeof(priv->cmd)); 3578c2ecf20Sopenharmony_ci priv->cmd.command = CMD_ECHO; 3588c2ecf20Sopenharmony_ci } 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci priv->cmd_state = CMD_STATE_CMD_IN_TX_FIFO; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci /* Throw command into TxFIFO */ 3638c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(priv->gpio_cmd, 0); 3648c2ecf20Sopenharmony_ci olpc_xo175_ec_send_command(priv, &priv->cmd, sizeof(priv->cmd)); 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->cmd_state_lock, flags); 3678c2ecf20Sopenharmony_ci return; 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_ci case CHAN_CMD_RESP: 3708c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->cmd_state_lock, flags); 3718c2ecf20Sopenharmony_ci 3728c2ecf20Sopenharmony_ci if (!priv->cmd_running) { 3738c2ecf20Sopenharmony_ci dev_err(dev, "spurious response packet\n"); 3748c2ecf20Sopenharmony_ci } else if (priv->resp_len >= priv->expected_resp_len) { 3758c2ecf20Sopenharmony_ci dev_err(dev, "too many response packets\n"); 3768c2ecf20Sopenharmony_ci } else { 3778c2ecf20Sopenharmony_ci priv->resp_data[priv->resp_len++] = byte; 3788c2ecf20Sopenharmony_ci if (priv->resp_len == priv->expected_resp_len) { 3798c2ecf20Sopenharmony_ci priv->cmd_state = CMD_STATE_RESP_RECEIVED; 3808c2ecf20Sopenharmony_ci complete(&priv->cmd_done); 3818c2ecf20Sopenharmony_ci } 3828c2ecf20Sopenharmony_ci } 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->cmd_state_lock, flags); 3858c2ecf20Sopenharmony_ci break; 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci case CHAN_CMD_ERROR: 3888c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->cmd_state_lock, flags); 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci if (!priv->cmd_running) { 3918c2ecf20Sopenharmony_ci dev_err(dev, "spurious cmd error packet\n"); 3928c2ecf20Sopenharmony_ci } else { 3938c2ecf20Sopenharmony_ci priv->resp_data[0] = byte; 3948c2ecf20Sopenharmony_ci priv->resp_len = 1; 3958c2ecf20Sopenharmony_ci priv->cmd_state = CMD_STATE_ERROR_RECEIVED; 3968c2ecf20Sopenharmony_ci complete(&priv->cmd_done); 3978c2ecf20Sopenharmony_ci } 3988c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->cmd_state_lock, flags); 3998c2ecf20Sopenharmony_ci break; 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci case CHAN_KEYBOARD: 4028c2ecf20Sopenharmony_ci dev_warn(dev, "keyboard is not supported\n"); 4038c2ecf20Sopenharmony_ci break; 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci case CHAN_TOUCHPAD: 4068c2ecf20Sopenharmony_ci dev_warn(dev, "touchpad is not supported\n"); 4078c2ecf20Sopenharmony_ci break; 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci case CHAN_EVENT: 4108c2ecf20Sopenharmony_ci dev_dbg(dev, "got event %.2x\n", byte); 4118c2ecf20Sopenharmony_ci switch (byte) { 4128c2ecf20Sopenharmony_ci case EVENT_AC_CHANGE: 4138c2ecf20Sopenharmony_ci psy = power_supply_get_by_name("olpc_ac"); 4148c2ecf20Sopenharmony_ci if (psy) { 4158c2ecf20Sopenharmony_ci power_supply_changed(psy); 4168c2ecf20Sopenharmony_ci power_supply_put(psy); 4178c2ecf20Sopenharmony_ci } 4188c2ecf20Sopenharmony_ci break; 4198c2ecf20Sopenharmony_ci case EVENT_BATTERY_STATUS: 4208c2ecf20Sopenharmony_ci case EVENT_BATTERY_CRITICAL: 4218c2ecf20Sopenharmony_ci case EVENT_BATTERY_SOC_CHANGE: 4228c2ecf20Sopenharmony_ci case EVENT_BATTERY_ERROR: 4238c2ecf20Sopenharmony_ci psy = power_supply_get_by_name("olpc_battery"); 4248c2ecf20Sopenharmony_ci if (psy) { 4258c2ecf20Sopenharmony_ci power_supply_changed(psy); 4268c2ecf20Sopenharmony_ci power_supply_put(psy); 4278c2ecf20Sopenharmony_ci } 4288c2ecf20Sopenharmony_ci break; 4298c2ecf20Sopenharmony_ci case EVENT_POWER_PRESSED: 4308c2ecf20Sopenharmony_ci input_report_key(priv->pwrbtn, KEY_POWER, 1); 4318c2ecf20Sopenharmony_ci input_sync(priv->pwrbtn); 4328c2ecf20Sopenharmony_ci input_report_key(priv->pwrbtn, KEY_POWER, 0); 4338c2ecf20Sopenharmony_ci input_sync(priv->pwrbtn); 4348c2ecf20Sopenharmony_ci fallthrough; 4358c2ecf20Sopenharmony_ci case EVENT_POWER_PRESS_WAKE: 4368c2ecf20Sopenharmony_ci case EVENT_TIMED_HOST_WAKE: 4378c2ecf20Sopenharmony_ci pm_wakeup_event(priv->pwrbtn->dev.parent, 4388c2ecf20Sopenharmony_ci PM_WAKEUP_TIME); 4398c2ecf20Sopenharmony_ci break; 4408c2ecf20Sopenharmony_ci default: 4418c2ecf20Sopenharmony_ci dev_dbg(dev, "ignored unknown event %.2x\n", byte); 4428c2ecf20Sopenharmony_ci break; 4438c2ecf20Sopenharmony_ci } 4448c2ecf20Sopenharmony_ci break; 4458c2ecf20Sopenharmony_ci 4468c2ecf20Sopenharmony_ci case CHAN_DEBUG: 4478c2ecf20Sopenharmony_ci if (byte == '\n') { 4488c2ecf20Sopenharmony_ci olpc_xo175_ec_flush_logbuf(priv); 4498c2ecf20Sopenharmony_ci } else if (isprint(byte)) { 4508c2ecf20Sopenharmony_ci priv->logbuf[priv->logbuf_len++] = byte; 4518c2ecf20Sopenharmony_ci if (priv->logbuf_len == LOG_BUF_SIZE) 4528c2ecf20Sopenharmony_ci olpc_xo175_ec_flush_logbuf(priv); 4538c2ecf20Sopenharmony_ci } 4548c2ecf20Sopenharmony_ci break; 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci default: 4578c2ecf20Sopenharmony_ci dev_warn(dev, "unknown channel: %d, %.2x\n", channel, byte); 4588c2ecf20Sopenharmony_ci break; 4598c2ecf20Sopenharmony_ci } 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci /* Most non-command packets get the TxFIFO refilled and an ACK. */ 4628c2ecf20Sopenharmony_ci olpc_xo175_ec_read_packet(priv); 4638c2ecf20Sopenharmony_ci} 4648c2ecf20Sopenharmony_ci 4658c2ecf20Sopenharmony_ci/* 4668c2ecf20Sopenharmony_ci * This function is protected with a mutex. We can safely assume that 4678c2ecf20Sopenharmony_ci * there will be only one instance of this function running at a time. 4688c2ecf20Sopenharmony_ci * One of the ways in which we enforce this is by waiting until we get 4698c2ecf20Sopenharmony_ci * all response bytes back from the EC, rather than just the number that 4708c2ecf20Sopenharmony_ci * the caller requests (otherwise, we might start a new command while an 4718c2ecf20Sopenharmony_ci * old command's response bytes are still incoming). 4728c2ecf20Sopenharmony_ci */ 4738c2ecf20Sopenharmony_cistatic int olpc_xo175_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *resp, 4748c2ecf20Sopenharmony_ci size_t resp_len, void *ec_cb_arg) 4758c2ecf20Sopenharmony_ci{ 4768c2ecf20Sopenharmony_ci struct olpc_xo175_ec *priv = ec_cb_arg; 4778c2ecf20Sopenharmony_ci struct device *dev = &priv->spi->dev; 4788c2ecf20Sopenharmony_ci unsigned long flags; 4798c2ecf20Sopenharmony_ci size_t nr_bytes; 4808c2ecf20Sopenharmony_ci int ret = 0; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci dev_dbg(dev, "CMD %x, %zd bytes expected\n", cmd, resp_len); 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci if (inlen > 5) { 4858c2ecf20Sopenharmony_ci dev_err(dev, "command len %zd too big!\n", resp_len); 4868c2ecf20Sopenharmony_ci return -EOVERFLOW; 4878c2ecf20Sopenharmony_ci } 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_ci /* Suspending in the middle of an EC command hoses things badly! */ 4908c2ecf20Sopenharmony_ci if (WARN_ON(priv->suspended)) 4918c2ecf20Sopenharmony_ci return -EBUSY; 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_ci /* Ensure a valid command and return bytes */ 4948c2ecf20Sopenharmony_ci ret = olpc_xo175_ec_resp_len(cmd); 4958c2ecf20Sopenharmony_ci if (ret < 0) { 4968c2ecf20Sopenharmony_ci dev_err_ratelimited(dev, "unknown command 0x%x\n", cmd); 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci /* 4998c2ecf20Sopenharmony_ci * Assume the best in our callers, and allow unknown commands 5008c2ecf20Sopenharmony_ci * through. I'm not the charitable type, but it was beaten 5018c2ecf20Sopenharmony_ci * into me. Just maintain a minimum standard of sanity. 5028c2ecf20Sopenharmony_ci */ 5038c2ecf20Sopenharmony_ci if (resp_len > sizeof(priv->resp_data)) { 5048c2ecf20Sopenharmony_ci dev_err(dev, "response too big: %zd!\n", resp_len); 5058c2ecf20Sopenharmony_ci return -EOVERFLOW; 5068c2ecf20Sopenharmony_ci } 5078c2ecf20Sopenharmony_ci nr_bytes = resp_len; 5088c2ecf20Sopenharmony_ci } else { 5098c2ecf20Sopenharmony_ci nr_bytes = (size_t)ret; 5108c2ecf20Sopenharmony_ci ret = 0; 5118c2ecf20Sopenharmony_ci } 5128c2ecf20Sopenharmony_ci resp_len = min(resp_len, nr_bytes); 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->cmd_state_lock, flags); 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_ci /* Initialize the state machine */ 5178c2ecf20Sopenharmony_ci init_completion(&priv->cmd_done); 5188c2ecf20Sopenharmony_ci priv->cmd_running = true; 5198c2ecf20Sopenharmony_ci priv->cmd_state = CMD_STATE_WAITING_FOR_SWITCH; 5208c2ecf20Sopenharmony_ci memset(&priv->cmd, 0, sizeof(priv->cmd)); 5218c2ecf20Sopenharmony_ci priv->cmd.command = cmd; 5228c2ecf20Sopenharmony_ci priv->cmd.nr_args = inlen; 5238c2ecf20Sopenharmony_ci priv->cmd.data_len = 0; 5248c2ecf20Sopenharmony_ci memcpy(priv->cmd.args, inbuf, inlen); 5258c2ecf20Sopenharmony_ci priv->expected_resp_len = nr_bytes; 5268c2ecf20Sopenharmony_ci priv->resp_len = 0; 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_ci /* Tickle the cmd gpio to get things started */ 5298c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(priv->gpio_cmd, 1); 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->cmd_state_lock, flags); 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci /* The irq handler should do the rest */ 5348c2ecf20Sopenharmony_ci if (!wait_for_completion_timeout(&priv->cmd_done, 5358c2ecf20Sopenharmony_ci msecs_to_jiffies(4000))) { 5368c2ecf20Sopenharmony_ci dev_err(dev, "EC cmd error: timeout in STATE %d\n", 5378c2ecf20Sopenharmony_ci priv->cmd_state); 5388c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(priv->gpio_cmd, 0); 5398c2ecf20Sopenharmony_ci spi_slave_abort(priv->spi); 5408c2ecf20Sopenharmony_ci olpc_xo175_ec_read_packet(priv); 5418c2ecf20Sopenharmony_ci return -ETIMEDOUT; 5428c2ecf20Sopenharmony_ci } 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci spin_lock_irqsave(&priv->cmd_state_lock, flags); 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_ci /* Deal with the results. */ 5478c2ecf20Sopenharmony_ci if (priv->cmd_state == CMD_STATE_ERROR_RECEIVED) { 5488c2ecf20Sopenharmony_ci /* EC-provided error is in the single response byte */ 5498c2ecf20Sopenharmony_ci dev_err(dev, "command 0x%x returned error 0x%x\n", 5508c2ecf20Sopenharmony_ci cmd, priv->resp_data[0]); 5518c2ecf20Sopenharmony_ci ret = -EREMOTEIO; 5528c2ecf20Sopenharmony_ci } else if (priv->resp_len != nr_bytes) { 5538c2ecf20Sopenharmony_ci dev_err(dev, "command 0x%x returned %d bytes, expected %zd bytes\n", 5548c2ecf20Sopenharmony_ci cmd, priv->resp_len, nr_bytes); 5558c2ecf20Sopenharmony_ci ret = -EREMOTEIO; 5568c2ecf20Sopenharmony_ci } else { 5578c2ecf20Sopenharmony_ci /* 5588c2ecf20Sopenharmony_ci * We may have 8 bytes in priv->resp, but we only care about 5598c2ecf20Sopenharmony_ci * what we've been asked for. If the caller asked for only 2 5608c2ecf20Sopenharmony_ci * bytes, give them that. We've guaranteed that 5618c2ecf20Sopenharmony_ci * resp_len <= priv->resp_len and priv->resp_len == nr_bytes. 5628c2ecf20Sopenharmony_ci */ 5638c2ecf20Sopenharmony_ci memcpy(resp, priv->resp_data, resp_len); 5648c2ecf20Sopenharmony_ci } 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci /* This should already be low, but just in case. */ 5678c2ecf20Sopenharmony_ci gpiod_set_value_cansleep(priv->gpio_cmd, 0); 5688c2ecf20Sopenharmony_ci priv->cmd_running = false; 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&priv->cmd_state_lock, flags); 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci return ret; 5738c2ecf20Sopenharmony_ci} 5748c2ecf20Sopenharmony_ci 5758c2ecf20Sopenharmony_cistatic int olpc_xo175_ec_set_event_mask(unsigned int mask) 5768c2ecf20Sopenharmony_ci{ 5778c2ecf20Sopenharmony_ci u8 args[2]; 5788c2ecf20Sopenharmony_ci 5798c2ecf20Sopenharmony_ci args[0] = mask >> 0; 5808c2ecf20Sopenharmony_ci args[1] = mask >> 8; 5818c2ecf20Sopenharmony_ci return olpc_ec_cmd(CMD_WRITE_EXT_SCI_MASK, args, 2, NULL, 0); 5828c2ecf20Sopenharmony_ci} 5838c2ecf20Sopenharmony_ci 5848c2ecf20Sopenharmony_cistatic void olpc_xo175_ec_power_off(void) 5858c2ecf20Sopenharmony_ci{ 5868c2ecf20Sopenharmony_ci while (1) { 5878c2ecf20Sopenharmony_ci olpc_ec_cmd(CMD_POWER_OFF, NULL, 0, NULL, 0); 5888c2ecf20Sopenharmony_ci mdelay(1000); 5898c2ecf20Sopenharmony_ci } 5908c2ecf20Sopenharmony_ci} 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_cistatic int __maybe_unused olpc_xo175_ec_suspend(struct device *dev) 5938c2ecf20Sopenharmony_ci{ 5948c2ecf20Sopenharmony_ci struct olpc_xo175_ec *priv = dev_get_drvdata(dev); 5958c2ecf20Sopenharmony_ci static struct { 5968c2ecf20Sopenharmony_ci u8 suspend; 5978c2ecf20Sopenharmony_ci u32 suspend_count; 5988c2ecf20Sopenharmony_ci } __packed hintargs; 5998c2ecf20Sopenharmony_ci static unsigned int suspend_count; 6008c2ecf20Sopenharmony_ci 6018c2ecf20Sopenharmony_ci /* 6028c2ecf20Sopenharmony_ci * SOC_SLEEP is not wired to the EC on B3 and earlier boards. 6038c2ecf20Sopenharmony_ci * This command lets the EC know instead. The suspend count doesn't seem 6048c2ecf20Sopenharmony_ci * to be used anywhere but in the EC debug output. 6058c2ecf20Sopenharmony_ci */ 6068c2ecf20Sopenharmony_ci hintargs.suspend = 1; 6078c2ecf20Sopenharmony_ci hintargs.suspend_count = suspend_count++; 6088c2ecf20Sopenharmony_ci olpc_ec_cmd(CMD_SUSPEND_HINT, (void *)&hintargs, sizeof(hintargs), 6098c2ecf20Sopenharmony_ci NULL, 0); 6108c2ecf20Sopenharmony_ci 6118c2ecf20Sopenharmony_ci /* 6128c2ecf20Sopenharmony_ci * After we've sent the suspend hint, don't allow further EC commands 6138c2ecf20Sopenharmony_ci * to be run until we've resumed. Userspace tasks should be frozen, 6148c2ecf20Sopenharmony_ci * but kernel threads and interrupts could still schedule EC commands. 6158c2ecf20Sopenharmony_ci */ 6168c2ecf20Sopenharmony_ci priv->suspended = true; 6178c2ecf20Sopenharmony_ci 6188c2ecf20Sopenharmony_ci return 0; 6198c2ecf20Sopenharmony_ci} 6208c2ecf20Sopenharmony_ci 6218c2ecf20Sopenharmony_cistatic int __maybe_unused olpc_xo175_ec_resume_noirq(struct device *dev) 6228c2ecf20Sopenharmony_ci{ 6238c2ecf20Sopenharmony_ci struct olpc_xo175_ec *priv = dev_get_drvdata(dev); 6248c2ecf20Sopenharmony_ci 6258c2ecf20Sopenharmony_ci priv->suspended = false; 6268c2ecf20Sopenharmony_ci 6278c2ecf20Sopenharmony_ci return 0; 6288c2ecf20Sopenharmony_ci} 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_cistatic int __maybe_unused olpc_xo175_ec_resume(struct device *dev) 6318c2ecf20Sopenharmony_ci{ 6328c2ecf20Sopenharmony_ci u8 x = 0; 6338c2ecf20Sopenharmony_ci 6348c2ecf20Sopenharmony_ci /* 6358c2ecf20Sopenharmony_ci * The resume hint is only needed if no other commands are 6368c2ecf20Sopenharmony_ci * being sent during resume. all it does is tell the EC 6378c2ecf20Sopenharmony_ci * the SoC is definitely awake. 6388c2ecf20Sopenharmony_ci */ 6398c2ecf20Sopenharmony_ci olpc_ec_cmd(CMD_SUSPEND_HINT, &x, 1, NULL, 0); 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_ci /* Enable all EC events while we're awake */ 6428c2ecf20Sopenharmony_ci olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); 6438c2ecf20Sopenharmony_ci 6448c2ecf20Sopenharmony_ci return 0; 6458c2ecf20Sopenharmony_ci} 6468c2ecf20Sopenharmony_ci 6478c2ecf20Sopenharmony_cistatic struct olpc_ec_driver olpc_xo175_ec_driver = { 6488c2ecf20Sopenharmony_ci .ec_cmd = olpc_xo175_ec_cmd, 6498c2ecf20Sopenharmony_ci}; 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_cistatic int olpc_xo175_ec_remove(struct spi_device *spi) 6528c2ecf20Sopenharmony_ci{ 6538c2ecf20Sopenharmony_ci if (pm_power_off == olpc_xo175_ec_power_off) 6548c2ecf20Sopenharmony_ci pm_power_off = NULL; 6558c2ecf20Sopenharmony_ci 6568c2ecf20Sopenharmony_ci spi_slave_abort(spi); 6578c2ecf20Sopenharmony_ci 6588c2ecf20Sopenharmony_ci platform_device_unregister(olpc_ec); 6598c2ecf20Sopenharmony_ci olpc_ec = NULL; 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci return 0; 6628c2ecf20Sopenharmony_ci} 6638c2ecf20Sopenharmony_ci 6648c2ecf20Sopenharmony_cistatic int olpc_xo175_ec_probe(struct spi_device *spi) 6658c2ecf20Sopenharmony_ci{ 6668c2ecf20Sopenharmony_ci struct olpc_xo175_ec *priv; 6678c2ecf20Sopenharmony_ci int ret; 6688c2ecf20Sopenharmony_ci 6698c2ecf20Sopenharmony_ci if (olpc_ec) { 6708c2ecf20Sopenharmony_ci dev_err(&spi->dev, "OLPC EC already registered.\n"); 6718c2ecf20Sopenharmony_ci return -EBUSY; 6728c2ecf20Sopenharmony_ci } 6738c2ecf20Sopenharmony_ci 6748c2ecf20Sopenharmony_ci priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); 6758c2ecf20Sopenharmony_ci if (!priv) 6768c2ecf20Sopenharmony_ci return -ENOMEM; 6778c2ecf20Sopenharmony_ci 6788c2ecf20Sopenharmony_ci priv->gpio_cmd = devm_gpiod_get(&spi->dev, "cmd", GPIOD_OUT_LOW); 6798c2ecf20Sopenharmony_ci if (IS_ERR(priv->gpio_cmd)) { 6808c2ecf20Sopenharmony_ci dev_err(&spi->dev, "failed to get cmd gpio: %ld\n", 6818c2ecf20Sopenharmony_ci PTR_ERR(priv->gpio_cmd)); 6828c2ecf20Sopenharmony_ci return PTR_ERR(priv->gpio_cmd); 6838c2ecf20Sopenharmony_ci } 6848c2ecf20Sopenharmony_ci 6858c2ecf20Sopenharmony_ci priv->spi = spi; 6868c2ecf20Sopenharmony_ci 6878c2ecf20Sopenharmony_ci spin_lock_init(&priv->cmd_state_lock); 6888c2ecf20Sopenharmony_ci priv->cmd_state = CMD_STATE_IDLE; 6898c2ecf20Sopenharmony_ci init_completion(&priv->cmd_done); 6908c2ecf20Sopenharmony_ci 6918c2ecf20Sopenharmony_ci priv->logbuf_len = 0; 6928c2ecf20Sopenharmony_ci 6938c2ecf20Sopenharmony_ci /* Set up power button input device */ 6948c2ecf20Sopenharmony_ci priv->pwrbtn = devm_input_allocate_device(&spi->dev); 6958c2ecf20Sopenharmony_ci if (!priv->pwrbtn) 6968c2ecf20Sopenharmony_ci return -ENOMEM; 6978c2ecf20Sopenharmony_ci priv->pwrbtn->name = "Power Button"; 6988c2ecf20Sopenharmony_ci priv->pwrbtn->dev.parent = &spi->dev; 6998c2ecf20Sopenharmony_ci input_set_capability(priv->pwrbtn, EV_KEY, KEY_POWER); 7008c2ecf20Sopenharmony_ci ret = input_register_device(priv->pwrbtn); 7018c2ecf20Sopenharmony_ci if (ret) { 7028c2ecf20Sopenharmony_ci dev_err(&spi->dev, "error registering input device: %d\n", ret); 7038c2ecf20Sopenharmony_ci return ret; 7048c2ecf20Sopenharmony_ci } 7058c2ecf20Sopenharmony_ci 7068c2ecf20Sopenharmony_ci spi_set_drvdata(spi, priv); 7078c2ecf20Sopenharmony_ci 7088c2ecf20Sopenharmony_ci priv->xfer.rx_buf = &priv->rx_buf; 7098c2ecf20Sopenharmony_ci priv->xfer.tx_buf = &priv->tx_buf; 7108c2ecf20Sopenharmony_ci 7118c2ecf20Sopenharmony_ci olpc_xo175_ec_read_packet(priv); 7128c2ecf20Sopenharmony_ci 7138c2ecf20Sopenharmony_ci olpc_ec_driver_register(&olpc_xo175_ec_driver, priv); 7148c2ecf20Sopenharmony_ci olpc_ec = platform_device_register_resndata(&spi->dev, "olpc-ec", -1, 7158c2ecf20Sopenharmony_ci NULL, 0, NULL, 0); 7168c2ecf20Sopenharmony_ci 7178c2ecf20Sopenharmony_ci /* Enable all EC events while we're awake */ 7188c2ecf20Sopenharmony_ci olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); 7198c2ecf20Sopenharmony_ci 7208c2ecf20Sopenharmony_ci if (pm_power_off == NULL) 7218c2ecf20Sopenharmony_ci pm_power_off = olpc_xo175_ec_power_off; 7228c2ecf20Sopenharmony_ci 7238c2ecf20Sopenharmony_ci dev_info(&spi->dev, "OLPC XO-1.75 Embedded Controller driver\n"); 7248c2ecf20Sopenharmony_ci 7258c2ecf20Sopenharmony_ci return 0; 7268c2ecf20Sopenharmony_ci} 7278c2ecf20Sopenharmony_ci 7288c2ecf20Sopenharmony_cistatic const struct dev_pm_ops olpc_xo175_ec_pm_ops = { 7298c2ecf20Sopenharmony_ci SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, olpc_xo175_ec_resume_noirq) 7308c2ecf20Sopenharmony_ci SET_RUNTIME_PM_OPS(olpc_xo175_ec_suspend, olpc_xo175_ec_resume, NULL) 7318c2ecf20Sopenharmony_ci}; 7328c2ecf20Sopenharmony_ci 7338c2ecf20Sopenharmony_cistatic const struct of_device_id olpc_xo175_ec_of_match[] = { 7348c2ecf20Sopenharmony_ci { .compatible = "olpc,xo1.75-ec" }, 7358c2ecf20Sopenharmony_ci { } 7368c2ecf20Sopenharmony_ci}; 7378c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, olpc_xo175_ec_of_match); 7388c2ecf20Sopenharmony_ci 7398c2ecf20Sopenharmony_cistatic const struct spi_device_id olpc_xo175_ec_id_table[] = { 7408c2ecf20Sopenharmony_ci { "xo1.75-ec", 0 }, 7418c2ecf20Sopenharmony_ci {} 7428c2ecf20Sopenharmony_ci}; 7438c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(spi, olpc_xo175_ec_id_table); 7448c2ecf20Sopenharmony_ci 7458c2ecf20Sopenharmony_cistatic struct spi_driver olpc_xo175_ec_spi_driver = { 7468c2ecf20Sopenharmony_ci .driver = { 7478c2ecf20Sopenharmony_ci .name = "olpc-xo175-ec", 7488c2ecf20Sopenharmony_ci .of_match_table = olpc_xo175_ec_of_match, 7498c2ecf20Sopenharmony_ci .pm = &olpc_xo175_ec_pm_ops, 7508c2ecf20Sopenharmony_ci }, 7518c2ecf20Sopenharmony_ci .probe = olpc_xo175_ec_probe, 7528c2ecf20Sopenharmony_ci .remove = olpc_xo175_ec_remove, 7538c2ecf20Sopenharmony_ci}; 7548c2ecf20Sopenharmony_cimodule_spi_driver(olpc_xo175_ec_spi_driver); 7558c2ecf20Sopenharmony_ci 7568c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("OLPC XO-1.75 Embedded Controller driver"); 7578c2ecf20Sopenharmony_ciMODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>"); /* Functionality */ 7588c2ecf20Sopenharmony_ciMODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); /* Bugs */ 7598c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 760