| // SPDX-License-Identifier: GPL-2.0+ | 
 | /* | 
 |  * Driver for the OLPC XO-1.75 Embedded Controller. | 
 |  * | 
 |  * The EC protocol is documented at: | 
 |  * http://wiki.laptop.org/go/XO_1.75_HOST_to_EC_Protocol | 
 |  * | 
 |  * Copyright (C) 2010 One Laptop per Child Foundation. | 
 |  * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk> | 
 |  */ | 
 |  | 
 | #include <linux/completion.h> | 
 | #include <linux/ctype.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/gpio/consumer.h> | 
 | #include <linux/input.h> | 
 | #include <linux/kfifo.h> | 
 | #include <linux/module.h> | 
 | #include <linux/olpc-ec.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/power_supply.h> | 
 | #include <linux/reboot.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/spinlock.h> | 
 | #include <linux/spi/spi.h> | 
 |  | 
 | struct ec_cmd_t { | 
 | 	u8 cmd; | 
 | 	u8 bytes_returned; | 
 | }; | 
 |  | 
 | enum ec_chan_t { | 
 | 	CHAN_NONE = 0, | 
 | 	CHAN_SWITCH, | 
 | 	CHAN_CMD_RESP, | 
 | 	CHAN_KEYBOARD, | 
 | 	CHAN_TOUCHPAD, | 
 | 	CHAN_EVENT, | 
 | 	CHAN_DEBUG, | 
 | 	CHAN_CMD_ERROR, | 
 | }; | 
 |  | 
 | /* | 
 |  * EC events | 
 |  */ | 
 | #define EVENT_AC_CHANGE			1  /* AC plugged/unplugged */ | 
 | #define EVENT_BATTERY_STATUS		2  /* Battery low/full/error/gone */ | 
 | #define EVENT_BATTERY_CRITICAL		3  /* Battery critical voltage */ | 
 | #define EVENT_BATTERY_SOC_CHANGE	4  /* 1% SOC Change */ | 
 | #define EVENT_BATTERY_ERROR		5  /* Abnormal error, query for cause */ | 
 | #define EVENT_POWER_PRESSED		6  /* Power button was pressed */ | 
 | #define EVENT_POWER_PRESS_WAKE		7  /* Woken up with a power button */ | 
 | #define EVENT_TIMED_HOST_WAKE		8  /* Host wake timer */ | 
 | #define EVENT_OLS_HIGH_LIMIT		9  /* OLS crossed dark threshold */ | 
 | #define EVENT_OLS_LOW_LIMIT		10 /* OLS crossed light threshold */ | 
 |  | 
 | /* | 
 |  * EC commands | 
 |  * (from http://dev.laptop.org/git/users/rsmith/ec-1.75/tree/ec_cmd.h) | 
 |  */ | 
 | #define CMD_GET_API_VERSION		0x08 /* out: u8 */ | 
 | #define CMD_READ_VOLTAGE		0x10 /* out: u16, *9.76/32, mV */ | 
 | #define CMD_READ_CURRENT		0x11 /* out: s16, *15.625/120, mA */ | 
 | #define CMD_READ_ACR			0x12 /* out: s16, *6250/15, uAh */ | 
 | #define CMD_READ_BATT_TEMPERATURE	0x13 /* out: u16, *100/256, deg C */ | 
 | #define CMD_READ_AMBIENT_TEMPERATURE	0x14 /* unimplemented, no hardware */ | 
 | #define CMD_READ_BATTERY_STATUS		0x15 /* out: u8, bitmask */ | 
 | #define CMD_READ_SOC			0x16 /* out: u8, percentage */ | 
 | #define CMD_READ_GAUGE_ID		0x17 /* out: u8 * 8 */ | 
 | #define CMD_READ_GAUGE_DATA		0x18 /* in: u8 addr, out: u8 data */ | 
 | #define CMD_READ_BOARD_ID		0x19 /* out: u16 (platform id) */ | 
 | #define CMD_READ_BATT_ERR_CODE		0x1f /* out: u8, error bitmask */ | 
 | #define CMD_SET_DCON_POWER		0x26 /* in: u8 */ | 
 | #define CMD_RESET_EC			0x28 /* none */ | 
 | #define CMD_READ_BATTERY_TYPE		0x2c /* out: u8 */ | 
 | #define CMD_SET_AUTOWAK			0x33 /* out: u8 */ | 
 | #define CMD_SET_EC_WAKEUP_TIMER		0x36 /* in: u32, out: ? */ | 
 | #define CMD_READ_EXT_SCI_MASK		0x37 /* ? */ | 
 | #define CMD_WRITE_EXT_SCI_MASK		0x38 /* ? */ | 
 | #define CMD_CLEAR_EC_WAKEUP_TIMER	0x39 /* none */ | 
 | #define CMD_ENABLE_RUNIN_DISCHARGE	0x3B /* none */ | 
 | #define CMD_DISABLE_RUNIN_DISCHARGE	0x3C /* none */ | 
 | #define CMD_READ_MPPT_ACTIVE		0x3d /* out: u8 */ | 
 | #define CMD_READ_MPPT_LIMIT		0x3e /* out: u8 */ | 
 | #define CMD_SET_MPPT_LIMIT		0x3f /* in: u8 */ | 
 | #define CMD_DISABLE_MPPT		0x40 /* none */ | 
 | #define CMD_ENABLE_MPPT			0x41 /* none */ | 
 | #define CMD_READ_VIN			0x42 /* out: u16 */ | 
 | #define CMD_EXT_SCI_QUERY		0x43 /* ? */ | 
 | #define RSP_KEYBOARD_DATA		0x48 /* ? */ | 
 | #define RSP_TOUCHPAD_DATA		0x49 /* ? */ | 
 | #define CMD_GET_FW_VERSION		0x4a /* out: u8 * 16 */ | 
 | #define CMD_POWER_CYCLE			0x4b /* none */ | 
 | #define CMD_POWER_OFF			0x4c /* none */ | 
 | #define CMD_RESET_EC_SOFT		0x4d /* none */ | 
 | #define CMD_READ_GAUGE_U16		0x4e /* ? */ | 
 | #define CMD_ENABLE_MOUSE		0x4f /* ? */ | 
 | #define CMD_ECHO			0x52 /* in: u8 * 5, out: u8 * 5 */ | 
 | #define CMD_GET_FW_DATE			0x53 /* out: u8 * 16 */ | 
 | #define CMD_GET_FW_USER			0x54 /* out: u8 * 16 */ | 
 | #define CMD_TURN_OFF_POWER		0x55 /* none (same as 0x4c) */ | 
 | #define CMD_READ_OLS			0x56 /* out: u16 */ | 
 | #define CMD_OLS_SMT_LEDON		0x57 /* none */ | 
 | #define CMD_OLS_SMT_LEDOFF		0x58 /* none */ | 
 | #define CMD_START_OLS_ASSY		0x59 /* none */ | 
 | #define CMD_STOP_OLS_ASSY		0x5a /* none */ | 
 | #define CMD_OLS_SMTTEST_STOP		0x5b /* none */ | 
 | #define CMD_READ_VIN_SCALED		0x5c /* out: u16 */ | 
 | #define CMD_READ_BAT_MIN_W		0x5d /* out: u16 */ | 
 | #define CMD_READ_BAR_MAX_W		0x5e /* out: u16 */ | 
 | #define CMD_RESET_BAT_MINMAX_W		0x5f /* none */ | 
 | #define CMD_READ_LOCATION		0x60 /* in: u16 addr, out: u8 data */ | 
 | #define CMD_WRITE_LOCATION		0x61 /* in: u16 addr, u8 data */ | 
 | #define CMD_KEYBOARD_CMD		0x62 /* in: u8, out: ? */ | 
 | #define CMD_TOUCHPAD_CMD		0x63 /* in: u8, out: ? */ | 
 | #define CMD_GET_FW_HASH			0x64 /* out: u8 * 16 */ | 
 | #define CMD_SUSPEND_HINT		0x65 /* in: u8 */ | 
 | #define CMD_ENABLE_WAKE_TIMER		0x66 /* in: u8 */ | 
 | #define CMD_SET_WAKE_TIMER		0x67 /* in: 32 */ | 
 | #define CMD_ENABLE_WAKE_AUTORESET	0x68 /* in: u8 */ | 
 | #define CMD_OLS_SET_LIMITS		0x69 /* in: u16, u16 */ | 
 | #define CMD_OLS_GET_LIMITS		0x6a /* out: u16, u16 */ | 
 | #define CMD_OLS_SET_CEILING		0x6b /* in: u16 */ | 
 | #define CMD_OLS_GET_CEILING		0x6c /* out: u16 */ | 
 |  | 
 | /* | 
 |  * Accepted EC commands, and how many bytes they return. There are plenty | 
 |  * of EC commands that are no longer implemented, or are implemented only on | 
 |  * certain older boards. | 
 |  */ | 
 | static const struct ec_cmd_t olpc_xo175_ec_cmds[] = { | 
 | 	{ CMD_GET_API_VERSION, 1 }, | 
 | 	{ CMD_READ_VOLTAGE, 2 }, | 
 | 	{ CMD_READ_CURRENT, 2 }, | 
 | 	{ CMD_READ_ACR, 2 }, | 
 | 	{ CMD_READ_BATT_TEMPERATURE, 2 }, | 
 | 	{ CMD_READ_BATTERY_STATUS, 1 }, | 
 | 	{ CMD_READ_SOC, 1 }, | 
 | 	{ CMD_READ_GAUGE_ID, 8 }, | 
 | 	{ CMD_READ_GAUGE_DATA, 1 }, | 
 | 	{ CMD_READ_BOARD_ID, 2 }, | 
 | 	{ CMD_READ_BATT_ERR_CODE, 1 }, | 
 | 	{ CMD_SET_DCON_POWER, 0 }, | 
 | 	{ CMD_RESET_EC, 0 }, | 
 | 	{ CMD_READ_BATTERY_TYPE, 1 }, | 
 | 	{ CMD_ENABLE_RUNIN_DISCHARGE, 0 }, | 
 | 	{ CMD_DISABLE_RUNIN_DISCHARGE, 0 }, | 
 | 	{ CMD_READ_MPPT_ACTIVE, 1 }, | 
 | 	{ CMD_READ_MPPT_LIMIT, 1 }, | 
 | 	{ CMD_SET_MPPT_LIMIT, 0 }, | 
 | 	{ CMD_DISABLE_MPPT, 0 }, | 
 | 	{ CMD_ENABLE_MPPT, 0 }, | 
 | 	{ CMD_READ_VIN, 2 }, | 
 | 	{ CMD_GET_FW_VERSION, 16 }, | 
 | 	{ CMD_POWER_CYCLE, 0 }, | 
 | 	{ CMD_POWER_OFF, 0 }, | 
 | 	{ CMD_RESET_EC_SOFT, 0 }, | 
 | 	{ CMD_ECHO, 5 }, | 
 | 	{ CMD_GET_FW_DATE, 16 }, | 
 | 	{ CMD_GET_FW_USER, 16 }, | 
 | 	{ CMD_TURN_OFF_POWER, 0 }, | 
 | 	{ CMD_READ_OLS, 2 }, | 
 | 	{ CMD_OLS_SMT_LEDON, 0 }, | 
 | 	{ CMD_OLS_SMT_LEDOFF, 0 }, | 
 | 	{ CMD_START_OLS_ASSY, 0 }, | 
 | 	{ CMD_STOP_OLS_ASSY, 0 }, | 
 | 	{ CMD_OLS_SMTTEST_STOP, 0 }, | 
 | 	{ CMD_READ_VIN_SCALED, 2 }, | 
 | 	{ CMD_READ_BAT_MIN_W, 2 }, | 
 | 	{ CMD_READ_BAR_MAX_W, 2 }, | 
 | 	{ CMD_RESET_BAT_MINMAX_W, 0 }, | 
 | 	{ CMD_READ_LOCATION, 1 }, | 
 | 	{ CMD_WRITE_LOCATION, 0 }, | 
 | 	{ CMD_GET_FW_HASH, 16 }, | 
 | 	{ CMD_SUSPEND_HINT, 0 }, | 
 | 	{ CMD_ENABLE_WAKE_TIMER, 0 }, | 
 | 	{ CMD_SET_WAKE_TIMER, 0 }, | 
 | 	{ CMD_ENABLE_WAKE_AUTORESET, 0 }, | 
 | 	{ CMD_OLS_SET_LIMITS, 0 }, | 
 | 	{ CMD_OLS_GET_LIMITS, 4 }, | 
 | 	{ CMD_OLS_SET_CEILING, 0 }, | 
 | 	{ CMD_OLS_GET_CEILING, 2 }, | 
 | 	{ CMD_READ_EXT_SCI_MASK, 2 }, | 
 | 	{ CMD_WRITE_EXT_SCI_MASK, 0 }, | 
 |  | 
 | 	{ } | 
 | }; | 
 |  | 
 | #define EC_MAX_CMD_DATA_LEN	5 | 
 | #define EC_MAX_RESP_LEN		16 | 
 |  | 
 | #define LOG_BUF_SIZE		128 | 
 |  | 
 | #define PM_WAKEUP_TIME		1000 | 
 |  | 
 | #define EC_ALL_EVENTS		GENMASK(15, 0) | 
 |  | 
 | enum ec_state_t { | 
 | 	CMD_STATE_IDLE = 0, | 
 | 	CMD_STATE_WAITING_FOR_SWITCH, | 
 | 	CMD_STATE_CMD_IN_TX_FIFO, | 
 | 	CMD_STATE_CMD_SENT, | 
 | 	CMD_STATE_RESP_RECEIVED, | 
 | 	CMD_STATE_ERROR_RECEIVED, | 
 | }; | 
 |  | 
 | struct olpc_xo175_ec_cmd { | 
 | 	u8 command; | 
 | 	u8 nr_args; | 
 | 	u8 data_len; | 
 | 	u8 args[EC_MAX_CMD_DATA_LEN]; | 
 | }; | 
 |  | 
 | struct olpc_xo175_ec_resp { | 
 | 	u8 channel; | 
 | 	u8 byte; | 
 | }; | 
 |  | 
 | struct olpc_xo175_ec { | 
 | 	bool suspended; | 
 |  | 
 | 	/* SPI related stuff. */ | 
 | 	struct spi_device *spi; | 
 | 	struct spi_transfer xfer; | 
 | 	struct spi_message msg; | 
 | 	union { | 
 | 		struct olpc_xo175_ec_cmd cmd; | 
 | 		struct olpc_xo175_ec_resp resp; | 
 | 	} tx_buf, rx_buf; | 
 |  | 
 | 	/* GPIO for the CMD signals. */ | 
 | 	struct gpio_desc *gpio_cmd; | 
 |  | 
 | 	/* Command handling related state. */ | 
 | 	spinlock_t cmd_state_lock; | 
 | 	int cmd_state; | 
 | 	bool cmd_running; | 
 | 	struct completion cmd_done; | 
 | 	struct olpc_xo175_ec_cmd cmd; | 
 | 	u8 resp_data[EC_MAX_RESP_LEN]; | 
 | 	int expected_resp_len; | 
 | 	int resp_len; | 
 |  | 
 | 	/* Power button. */ | 
 | 	struct input_dev *pwrbtn; | 
 |  | 
 | 	/* Debug handling. */ | 
 | 	char logbuf[LOG_BUF_SIZE]; | 
 | 	int logbuf_len; | 
 | }; | 
 |  | 
 | static struct platform_device *olpc_ec; | 
 |  | 
 | static int olpc_xo175_ec_resp_len(u8 cmd) | 
 | { | 
 | 	const struct ec_cmd_t *p; | 
 |  | 
 | 	for (p = olpc_xo175_ec_cmds; p->cmd; p++) { | 
 | 		if (p->cmd == cmd) | 
 | 			return p->bytes_returned; | 
 | 	} | 
 |  | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static void olpc_xo175_ec_flush_logbuf(struct olpc_xo175_ec *priv) | 
 | { | 
 | 	dev_dbg(&priv->spi->dev, "got debug string [%*pE]\n", | 
 | 				priv->logbuf_len, priv->logbuf); | 
 | 	priv->logbuf_len = 0; | 
 | } | 
 |  | 
 | static void olpc_xo175_ec_complete(void *arg); | 
 |  | 
 | static void olpc_xo175_ec_send_command(struct olpc_xo175_ec *priv, void *cmd, | 
 | 								size_t cmdlen) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	memcpy(&priv->tx_buf, cmd, cmdlen); | 
 | 	priv->xfer.len = cmdlen; | 
 |  | 
 | 	spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); | 
 |  | 
 | 	priv->msg.complete = olpc_xo175_ec_complete; | 
 | 	priv->msg.context = priv; | 
 |  | 
 | 	ret = spi_async(priv->spi, &priv->msg); | 
 | 	if (ret) | 
 | 		dev_err(&priv->spi->dev, "spi_async() failed %d\n", ret); | 
 | } | 
 |  | 
 | static void olpc_xo175_ec_read_packet(struct olpc_xo175_ec *priv) | 
 | { | 
 | 	u8 nonce[] = {0xA5, 0x5A}; | 
 |  | 
 | 	olpc_xo175_ec_send_command(priv, nonce, sizeof(nonce)); | 
 | } | 
 |  | 
 | static void olpc_xo175_ec_complete(void *arg) | 
 | { | 
 | 	struct olpc_xo175_ec *priv = arg; | 
 | 	struct device *dev = &priv->spi->dev; | 
 | 	struct power_supply *psy; | 
 | 	unsigned long flags; | 
 | 	u8 channel; | 
 | 	u8 byte; | 
 | 	int ret; | 
 |  | 
 | 	ret = priv->msg.status; | 
 | 	if (ret) { | 
 | 		dev_err(dev, "SPI transfer failed: %d\n", ret); | 
 |  | 
 | 		spin_lock_irqsave(&priv->cmd_state_lock, flags); | 
 | 		if (priv->cmd_running) { | 
 | 			priv->resp_len = 0; | 
 | 			priv->cmd_state = CMD_STATE_ERROR_RECEIVED; | 
 | 			complete(&priv->cmd_done); | 
 | 		} | 
 | 		spin_unlock_irqrestore(&priv->cmd_state_lock, flags); | 
 |  | 
 | 		if (ret != -EINTR) | 
 | 			olpc_xo175_ec_read_packet(priv); | 
 |  | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	channel = priv->rx_buf.resp.channel; | 
 | 	byte = priv->rx_buf.resp.byte; | 
 |  | 
 | 	switch (channel) { | 
 | 	case CHAN_NONE: | 
 | 		spin_lock_irqsave(&priv->cmd_state_lock, flags); | 
 |  | 
 | 		if (!priv->cmd_running) { | 
 | 			/* We can safely ignore these */ | 
 | 			dev_err(dev, "spurious FIFO read packet\n"); | 
 | 			spin_unlock_irqrestore(&priv->cmd_state_lock, flags); | 
 | 			return; | 
 | 		} | 
 |  | 
 | 		priv->cmd_state = CMD_STATE_CMD_SENT; | 
 | 		if (!priv->expected_resp_len) | 
 | 			complete(&priv->cmd_done); | 
 | 		olpc_xo175_ec_read_packet(priv); | 
 |  | 
 | 		spin_unlock_irqrestore(&priv->cmd_state_lock, flags); | 
 | 		return; | 
 |  | 
 | 	case CHAN_SWITCH: | 
 | 		spin_lock_irqsave(&priv->cmd_state_lock, flags); | 
 |  | 
 | 		if (!priv->cmd_running) { | 
 | 			/* Just go with the flow */ | 
 | 			dev_err(dev, "spurious SWITCH packet\n"); | 
 | 			memset(&priv->cmd, 0, sizeof(priv->cmd)); | 
 | 			priv->cmd.command = CMD_ECHO; | 
 | 		} | 
 |  | 
 | 		priv->cmd_state = CMD_STATE_CMD_IN_TX_FIFO; | 
 |  | 
 | 		/* Throw command into TxFIFO */ | 
 | 		gpiod_set_value_cansleep(priv->gpio_cmd, 0); | 
 | 		olpc_xo175_ec_send_command(priv, &priv->cmd, sizeof(priv->cmd)); | 
 |  | 
 | 		spin_unlock_irqrestore(&priv->cmd_state_lock, flags); | 
 | 		return; | 
 |  | 
 | 	case CHAN_CMD_RESP: | 
 | 		spin_lock_irqsave(&priv->cmd_state_lock, flags); | 
 |  | 
 | 		if (!priv->cmd_running) { | 
 | 			dev_err(dev, "spurious response packet\n"); | 
 | 		} else if (priv->resp_len >= priv->expected_resp_len) { | 
 | 			dev_err(dev, "too many response packets\n"); | 
 | 		} else { | 
 | 			priv->resp_data[priv->resp_len++] = byte; | 
 | 			if (priv->resp_len == priv->expected_resp_len) { | 
 | 				priv->cmd_state = CMD_STATE_RESP_RECEIVED; | 
 | 				complete(&priv->cmd_done); | 
 | 			} | 
 | 		} | 
 |  | 
 | 		spin_unlock_irqrestore(&priv->cmd_state_lock, flags); | 
 | 		break; | 
 |  | 
 | 	case CHAN_CMD_ERROR: | 
 | 		spin_lock_irqsave(&priv->cmd_state_lock, flags); | 
 |  | 
 | 		if (!priv->cmd_running) { | 
 | 			dev_err(dev, "spurious cmd error packet\n"); | 
 | 		} else { | 
 | 			priv->resp_data[0] = byte; | 
 | 			priv->resp_len = 1; | 
 | 			priv->cmd_state = CMD_STATE_ERROR_RECEIVED; | 
 | 			complete(&priv->cmd_done); | 
 | 		} | 
 | 		spin_unlock_irqrestore(&priv->cmd_state_lock, flags); | 
 | 		break; | 
 |  | 
 | 	case CHAN_KEYBOARD: | 
 | 		dev_warn(dev, "keyboard is not supported\n"); | 
 | 		break; | 
 |  | 
 | 	case CHAN_TOUCHPAD: | 
 | 		dev_warn(dev, "touchpad is not supported\n"); | 
 | 		break; | 
 |  | 
 | 	case CHAN_EVENT: | 
 | 		dev_dbg(dev, "got event %.2x\n", byte); | 
 | 		switch (byte) { | 
 | 		case EVENT_AC_CHANGE: | 
 | 			psy = power_supply_get_by_name("olpc_ac"); | 
 | 			if (psy) { | 
 | 				power_supply_changed(psy); | 
 | 				power_supply_put(psy); | 
 | 			} | 
 | 			break; | 
 | 		case EVENT_BATTERY_STATUS: | 
 | 		case EVENT_BATTERY_CRITICAL: | 
 | 		case EVENT_BATTERY_SOC_CHANGE: | 
 | 		case EVENT_BATTERY_ERROR: | 
 | 			psy = power_supply_get_by_name("olpc_battery"); | 
 | 			if (psy) { | 
 | 				power_supply_changed(psy); | 
 | 				power_supply_put(psy); | 
 | 			} | 
 | 			break; | 
 | 		case EVENT_POWER_PRESSED: | 
 | 			input_report_key(priv->pwrbtn, KEY_POWER, 1); | 
 | 			input_sync(priv->pwrbtn); | 
 | 			input_report_key(priv->pwrbtn, KEY_POWER, 0); | 
 | 			input_sync(priv->pwrbtn); | 
 | 			fallthrough; | 
 | 		case EVENT_POWER_PRESS_WAKE: | 
 | 		case EVENT_TIMED_HOST_WAKE: | 
 | 			pm_wakeup_event(priv->pwrbtn->dev.parent, | 
 | 						PM_WAKEUP_TIME); | 
 | 			break; | 
 | 		default: | 
 | 			dev_dbg(dev, "ignored unknown event %.2x\n", byte); | 
 | 			break; | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	case CHAN_DEBUG: | 
 | 		if (byte == '\n') { | 
 | 			olpc_xo175_ec_flush_logbuf(priv); | 
 | 		} else if (isprint(byte)) { | 
 | 			priv->logbuf[priv->logbuf_len++] = byte; | 
 | 			if (priv->logbuf_len == LOG_BUF_SIZE) | 
 | 				olpc_xo175_ec_flush_logbuf(priv); | 
 | 		} | 
 | 		break; | 
 |  | 
 | 	default: | 
 | 		dev_warn(dev, "unknown channel: %d, %.2x\n", channel, byte); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	/* Most non-command packets get the TxFIFO refilled and an ACK. */ | 
 | 	olpc_xo175_ec_read_packet(priv); | 
 | } | 
 |  | 
 | /* | 
 |  * This function is protected with a mutex. We can safely assume that | 
 |  * there will be only one instance of this function running at a time. | 
 |  * One of the ways in which we enforce this is by waiting until we get | 
 |  * all response bytes back from the EC, rather than just the number that | 
 |  * the caller requests (otherwise, we might start a new command while an | 
 |  * old command's response bytes are still incoming). | 
 |  */ | 
 | static int olpc_xo175_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *resp, | 
 | 					size_t resp_len, void *ec_cb_arg) | 
 | { | 
 | 	struct olpc_xo175_ec *priv = ec_cb_arg; | 
 | 	struct device *dev = &priv->spi->dev; | 
 | 	unsigned long flags; | 
 | 	size_t nr_bytes; | 
 | 	int ret = 0; | 
 |  | 
 | 	dev_dbg(dev, "CMD %x, %zd bytes expected\n", cmd, resp_len); | 
 |  | 
 | 	if (inlen > 5) { | 
 | 		dev_err(dev, "command len %zd too big!\n", resp_len); | 
 | 		return -EOVERFLOW; | 
 | 	} | 
 |  | 
 | 	/* Suspending in the middle of an EC command hoses things badly! */ | 
 | 	if (WARN_ON(priv->suspended)) | 
 | 		return -EBUSY; | 
 |  | 
 | 	/* Ensure a valid command and return bytes */ | 
 | 	ret = olpc_xo175_ec_resp_len(cmd); | 
 | 	if (ret < 0) { | 
 | 		dev_err_ratelimited(dev, "unknown command 0x%x\n", cmd); | 
 |  | 
 | 		/* | 
 | 		 * Assume the best in our callers, and allow unknown commands | 
 | 		 * through. I'm not the charitable type, but it was beaten | 
 | 		 * into me. Just maintain a minimum standard of sanity. | 
 | 		 */ | 
 | 		if (resp_len > sizeof(priv->resp_data)) { | 
 | 			dev_err(dev, "response too big: %zd!\n", resp_len); | 
 | 			return -EOVERFLOW; | 
 | 		} | 
 | 		nr_bytes = resp_len; | 
 | 	} else { | 
 | 		nr_bytes = (size_t)ret; | 
 | 		ret = 0; | 
 | 	} | 
 | 	resp_len = min(resp_len, nr_bytes); | 
 |  | 
 | 	spin_lock_irqsave(&priv->cmd_state_lock, flags); | 
 |  | 
 | 	/* Initialize the state machine */ | 
 | 	init_completion(&priv->cmd_done); | 
 | 	priv->cmd_running = true; | 
 | 	priv->cmd_state = CMD_STATE_WAITING_FOR_SWITCH; | 
 | 	memset(&priv->cmd, 0, sizeof(priv->cmd)); | 
 | 	priv->cmd.command = cmd; | 
 | 	priv->cmd.nr_args = inlen; | 
 | 	priv->cmd.data_len = 0; | 
 | 	memcpy(priv->cmd.args, inbuf, inlen); | 
 | 	priv->expected_resp_len = nr_bytes; | 
 | 	priv->resp_len = 0; | 
 |  | 
 | 	/* Tickle the cmd gpio to get things started */ | 
 | 	gpiod_set_value_cansleep(priv->gpio_cmd, 1); | 
 |  | 
 | 	spin_unlock_irqrestore(&priv->cmd_state_lock, flags); | 
 |  | 
 | 	/* The irq handler should do the rest */ | 
 | 	if (!wait_for_completion_timeout(&priv->cmd_done, | 
 | 			msecs_to_jiffies(4000))) { | 
 | 		dev_err(dev, "EC cmd error: timeout in STATE %d\n", | 
 | 				priv->cmd_state); | 
 | 		gpiod_set_value_cansleep(priv->gpio_cmd, 0); | 
 | 		spi_target_abort(priv->spi); | 
 | 		olpc_xo175_ec_read_packet(priv); | 
 | 		return -ETIMEDOUT; | 
 | 	} | 
 |  | 
 | 	spin_lock_irqsave(&priv->cmd_state_lock, flags); | 
 |  | 
 | 	/* Deal with the results. */ | 
 | 	if (priv->cmd_state == CMD_STATE_ERROR_RECEIVED) { | 
 | 		/* EC-provided error is in the single response byte */ | 
 | 		dev_err(dev, "command 0x%x returned error 0x%x\n", | 
 | 						cmd, priv->resp_data[0]); | 
 | 		ret = -EREMOTEIO; | 
 | 	} else if (priv->resp_len != nr_bytes) { | 
 | 		dev_err(dev, "command 0x%x returned %d bytes, expected %zd bytes\n", | 
 | 						cmd, priv->resp_len, nr_bytes); | 
 | 		ret = -EREMOTEIO; | 
 | 	} else { | 
 | 		/* | 
 | 		 * We may have 8 bytes in priv->resp, but we only care about | 
 | 		 * what we've been asked for. If the caller asked for only 2 | 
 | 		 * bytes, give them that. We've guaranteed that | 
 | 		 * resp_len <= priv->resp_len and priv->resp_len == nr_bytes. | 
 | 		 */ | 
 | 		memcpy(resp, priv->resp_data, resp_len); | 
 | 	} | 
 |  | 
 | 	/* This should already be low, but just in case. */ | 
 | 	gpiod_set_value_cansleep(priv->gpio_cmd, 0); | 
 | 	priv->cmd_running = false; | 
 |  | 
 | 	spin_unlock_irqrestore(&priv->cmd_state_lock, flags); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int olpc_xo175_ec_set_event_mask(unsigned int mask) | 
 | { | 
 | 	u8 args[2]; | 
 |  | 
 | 	args[0] = mask >> 0; | 
 | 	args[1] = mask >> 8; | 
 | 	return olpc_ec_cmd(CMD_WRITE_EXT_SCI_MASK, args, 2, NULL, 0); | 
 | } | 
 |  | 
 | static void olpc_xo175_ec_power_off(void) | 
 | { | 
 | 	while (1) { | 
 | 		olpc_ec_cmd(CMD_POWER_OFF, NULL, 0, NULL, 0); | 
 | 		mdelay(1000); | 
 | 	} | 
 | } | 
 |  | 
 | static int __maybe_unused olpc_xo175_ec_suspend(struct device *dev) | 
 | { | 
 | 	struct olpc_xo175_ec *priv = dev_get_drvdata(dev); | 
 | 	static struct { | 
 | 		u8 suspend; | 
 | 		u32 suspend_count; | 
 | 	} __packed hintargs; | 
 | 	static unsigned int suspend_count; | 
 |  | 
 | 	/* | 
 | 	 * SOC_SLEEP is not wired to the EC on B3 and earlier boards. | 
 | 	 * This command lets the EC know instead. The suspend count doesn't seem | 
 | 	 * to be used anywhere but in the EC debug output. | 
 | 	 */ | 
 | 	hintargs.suspend = 1; | 
 | 	hintargs.suspend_count = suspend_count++; | 
 | 	olpc_ec_cmd(CMD_SUSPEND_HINT, (void *)&hintargs, sizeof(hintargs), | 
 | 								NULL, 0); | 
 |  | 
 | 	/* | 
 | 	 * After we've sent the suspend hint, don't allow further EC commands | 
 | 	 * to be run until we've resumed. Userspace tasks should be frozen, | 
 | 	 * but kernel threads and interrupts could still schedule EC commands. | 
 | 	 */ | 
 | 	priv->suspended = true; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int __maybe_unused olpc_xo175_ec_resume_noirq(struct device *dev) | 
 | { | 
 | 	struct olpc_xo175_ec *priv = dev_get_drvdata(dev); | 
 |  | 
 | 	priv->suspended = false; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int __maybe_unused olpc_xo175_ec_resume(struct device *dev) | 
 | { | 
 | 	u8 x = 0; | 
 |  | 
 | 	/* | 
 | 	 * The resume hint is only needed if no other commands are | 
 | 	 * being sent during resume. all it does is tell the EC | 
 | 	 * the SoC is definitely awake. | 
 | 	 */ | 
 | 	olpc_ec_cmd(CMD_SUSPEND_HINT, &x, 1, NULL, 0); | 
 |  | 
 | 	/* Enable all EC events while we're awake */ | 
 | 	olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct olpc_ec_driver olpc_xo175_ec_driver = { | 
 | 	.ec_cmd = olpc_xo175_ec_cmd, | 
 | }; | 
 |  | 
 | static void olpc_xo175_ec_remove(struct spi_device *spi) | 
 | { | 
 | 	if (pm_power_off == olpc_xo175_ec_power_off) | 
 | 		pm_power_off = NULL; | 
 |  | 
 | 	spi_target_abort(spi); | 
 |  | 
 | 	platform_device_unregister(olpc_ec); | 
 | 	olpc_ec = NULL; | 
 | } | 
 |  | 
 | static int olpc_xo175_ec_probe(struct spi_device *spi) | 
 | { | 
 | 	struct olpc_xo175_ec *priv; | 
 | 	int ret; | 
 |  | 
 | 	if (olpc_ec) { | 
 | 		dev_err(&spi->dev, "OLPC EC already registered.\n"); | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); | 
 | 	if (!priv) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	priv->gpio_cmd = devm_gpiod_get(&spi->dev, "cmd", GPIOD_OUT_LOW); | 
 | 	if (IS_ERR(priv->gpio_cmd)) { | 
 | 		dev_err(&spi->dev, "failed to get cmd gpio: %ld\n", | 
 | 					PTR_ERR(priv->gpio_cmd)); | 
 | 		return PTR_ERR(priv->gpio_cmd); | 
 | 	} | 
 |  | 
 | 	priv->spi = spi; | 
 |  | 
 | 	spin_lock_init(&priv->cmd_state_lock); | 
 | 	priv->cmd_state = CMD_STATE_IDLE; | 
 | 	init_completion(&priv->cmd_done); | 
 |  | 
 | 	priv->logbuf_len = 0; | 
 |  | 
 | 	/* Set up power button input device */ | 
 | 	priv->pwrbtn = devm_input_allocate_device(&spi->dev); | 
 | 	if (!priv->pwrbtn) | 
 | 		return -ENOMEM; | 
 | 	priv->pwrbtn->name = "Power Button"; | 
 | 	priv->pwrbtn->dev.parent = &spi->dev; | 
 | 	input_set_capability(priv->pwrbtn, EV_KEY, KEY_POWER); | 
 | 	ret = input_register_device(priv->pwrbtn); | 
 | 	if (ret) { | 
 | 		dev_err(&spi->dev, "error registering input device: %d\n", ret); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	spi_set_drvdata(spi, priv); | 
 |  | 
 | 	priv->xfer.rx_buf = &priv->rx_buf; | 
 | 	priv->xfer.tx_buf = &priv->tx_buf; | 
 |  | 
 | 	olpc_xo175_ec_read_packet(priv); | 
 |  | 
 | 	olpc_ec_driver_register(&olpc_xo175_ec_driver, priv); | 
 | 	olpc_ec = platform_device_register_resndata(&spi->dev, "olpc-ec", -1, | 
 | 							NULL, 0, NULL, 0); | 
 |  | 
 | 	/* Enable all EC events while we're awake */ | 
 | 	olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); | 
 |  | 
 | 	if (pm_power_off == NULL) | 
 | 		pm_power_off = olpc_xo175_ec_power_off; | 
 |  | 
 | 	dev_info(&spi->dev, "OLPC XO-1.75 Embedded Controller driver\n"); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct dev_pm_ops olpc_xo175_ec_pm_ops = { | 
 | 	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, olpc_xo175_ec_resume_noirq) | 
 | 	SET_RUNTIME_PM_OPS(olpc_xo175_ec_suspend, olpc_xo175_ec_resume, NULL) | 
 | }; | 
 |  | 
 | static const struct of_device_id olpc_xo175_ec_of_match[] = { | 
 | 	{ .compatible = "olpc,xo1.75-ec" }, | 
 | 	{ } | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, olpc_xo175_ec_of_match); | 
 |  | 
 | static const struct spi_device_id olpc_xo175_ec_id_table[] = { | 
 | 	{ "xo1.75-ec", 0 }, | 
 | 	{} | 
 | }; | 
 | MODULE_DEVICE_TABLE(spi, olpc_xo175_ec_id_table); | 
 |  | 
 | static struct spi_driver olpc_xo175_ec_spi_driver = { | 
 | 	.driver = { | 
 | 		.name	= "olpc-xo175-ec", | 
 | 		.of_match_table = olpc_xo175_ec_of_match, | 
 | 		.pm = &olpc_xo175_ec_pm_ops, | 
 | 	}, | 
 | 	.id_table	= olpc_xo175_ec_id_table, | 
 | 	.probe		= olpc_xo175_ec_probe, | 
 | 	.remove		= olpc_xo175_ec_remove, | 
 | }; | 
 | module_spi_driver(olpc_xo175_ec_spi_driver); | 
 |  | 
 | MODULE_DESCRIPTION("OLPC XO-1.75 Embedded Controller driver"); | 
 | MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>"); /* Functionality */ | 
 | MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); /* Bugs */ | 
 | MODULE_LICENSE("GPL"); |