root/drivers/platform/olpc/olpc-xo175-ec.c
// 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", inlen);
                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");