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