linux/drivers/mfd/cros_ec.c
<<
>>
Prefs
   1/*
   2 * ChromeOS EC multi-function device
   3 *
   4 * Copyright (C) 2012 Google, Inc
   5 *
   6 * This software is licensed under the terms of the GNU General Public
   7 * License version 2, as published by the Free Software Foundation, and
   8 * may be copied, distributed, and modified under those terms.
   9 *
  10 * This program is distributed in the hope that it will be useful,
  11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13 * GNU General Public License for more details.
  14 *
  15 * The ChromeOS EC multi function device is used to mux all the requests
  16 * to the EC device for its multiple features: keyboard controller,
  17 * battery charging and regulator control, firmware update.
  18 */
  19
  20#include <linux/interrupt.h>
  21#include <linux/slab.h>
  22#include <linux/module.h>
  23#include <linux/mfd/core.h>
  24#include <linux/mfd/cros_ec.h>
  25#include <linux/mfd/cros_ec_commands.h>
  26
  27int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
  28                       struct cros_ec_msg *msg)
  29{
  30        uint8_t *out;
  31        int csum, i;
  32
  33        BUG_ON(msg->out_len > EC_HOST_PARAM_SIZE);
  34        out = ec_dev->dout;
  35        out[0] = EC_CMD_VERSION0 + msg->version;
  36        out[1] = msg->cmd;
  37        out[2] = msg->out_len;
  38        csum = out[0] + out[1] + out[2];
  39        for (i = 0; i < msg->out_len; i++)
  40                csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->out_buf[i];
  41        out[EC_MSG_TX_HEADER_BYTES + msg->out_len] = (uint8_t)(csum & 0xff);
  42
  43        return EC_MSG_TX_PROTO_BYTES + msg->out_len;
  44}
  45EXPORT_SYMBOL(cros_ec_prepare_tx);
  46
  47static int cros_ec_command_sendrecv(struct cros_ec_device *ec_dev,
  48                uint16_t cmd, void *out_buf, int out_len,
  49                void *in_buf, int in_len)
  50{
  51        struct cros_ec_msg msg;
  52
  53        msg.version = cmd >> 8;
  54        msg.cmd = cmd & 0xff;
  55        msg.out_buf = out_buf;
  56        msg.out_len = out_len;
  57        msg.in_buf = in_buf;
  58        msg.in_len = in_len;
  59
  60        return ec_dev->command_xfer(ec_dev, &msg);
  61}
  62
  63static int cros_ec_command_recv(struct cros_ec_device *ec_dev,
  64                uint16_t cmd, void *buf, int buf_len)
  65{
  66        return cros_ec_command_sendrecv(ec_dev, cmd, NULL, 0, buf, buf_len);
  67}
  68
  69static int cros_ec_command_send(struct cros_ec_device *ec_dev,
  70                uint16_t cmd, void *buf, int buf_len)
  71{
  72        return cros_ec_command_sendrecv(ec_dev, cmd, buf, buf_len, NULL, 0);
  73}
  74
  75static irqreturn_t ec_irq_thread(int irq, void *data)
  76{
  77        struct cros_ec_device *ec_dev = data;
  78
  79        if (device_may_wakeup(ec_dev->dev))
  80                pm_wakeup_event(ec_dev->dev, 0);
  81
  82        blocking_notifier_call_chain(&ec_dev->event_notifier, 1, ec_dev);
  83
  84        return IRQ_HANDLED;
  85}
  86
  87static struct mfd_cell cros_devs[] = {
  88        {
  89                .name = "cros-ec-keyb",
  90                .id = 1,
  91                .of_compatible = "google,cros-ec-keyb",
  92        },
  93};
  94
  95int cros_ec_register(struct cros_ec_device *ec_dev)
  96{
  97        struct device *dev = ec_dev->dev;
  98        int err = 0;
  99
 100        BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier);
 101
 102        ec_dev->command_send = cros_ec_command_send;
 103        ec_dev->command_recv = cros_ec_command_recv;
 104        ec_dev->command_sendrecv = cros_ec_command_sendrecv;
 105
 106        if (ec_dev->din_size) {
 107                ec_dev->din = kmalloc(ec_dev->din_size, GFP_KERNEL);
 108                if (!ec_dev->din) {
 109                        err = -ENOMEM;
 110                        goto fail_din;
 111                }
 112        }
 113        if (ec_dev->dout_size) {
 114                ec_dev->dout = kmalloc(ec_dev->dout_size, GFP_KERNEL);
 115                if (!ec_dev->dout) {
 116                        err = -ENOMEM;
 117                        goto fail_dout;
 118                }
 119        }
 120
 121        if (!ec_dev->irq) {
 122                dev_dbg(dev, "no valid IRQ: %d\n", ec_dev->irq);
 123                goto fail_irq;
 124        }
 125
 126        err = request_threaded_irq(ec_dev->irq, NULL, ec_irq_thread,
 127                                   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
 128                                   "chromeos-ec", ec_dev);
 129        if (err) {
 130                dev_err(dev, "request irq %d: error %d\n", ec_dev->irq, err);
 131                goto fail_irq;
 132        }
 133
 134        err = mfd_add_devices(dev, 0, cros_devs,
 135                              ARRAY_SIZE(cros_devs),
 136                              NULL, ec_dev->irq, NULL);
 137        if (err) {
 138                dev_err(dev, "failed to add mfd devices\n");
 139                goto fail_mfd;
 140        }
 141
 142        dev_info(dev, "Chrome EC (%s)\n", ec_dev->name);
 143
 144        return 0;
 145
 146fail_mfd:
 147        free_irq(ec_dev->irq, ec_dev);
 148fail_irq:
 149        kfree(ec_dev->dout);
 150fail_dout:
 151        kfree(ec_dev->din);
 152fail_din:
 153        return err;
 154}
 155EXPORT_SYMBOL(cros_ec_register);
 156
 157int cros_ec_remove(struct cros_ec_device *ec_dev)
 158{
 159        mfd_remove_devices(ec_dev->dev);
 160        free_irq(ec_dev->irq, ec_dev);
 161        kfree(ec_dev->dout);
 162        kfree(ec_dev->din);
 163
 164        return 0;
 165}
 166EXPORT_SYMBOL(cros_ec_remove);
 167
 168#ifdef CONFIG_PM_SLEEP
 169int cros_ec_suspend(struct cros_ec_device *ec_dev)
 170{
 171        struct device *dev = ec_dev->dev;
 172
 173        if (device_may_wakeup(dev))
 174                ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq);
 175
 176        disable_irq(ec_dev->irq);
 177        ec_dev->was_wake_device = ec_dev->wake_enabled;
 178
 179        return 0;
 180}
 181EXPORT_SYMBOL(cros_ec_suspend);
 182
 183int cros_ec_resume(struct cros_ec_device *ec_dev)
 184{
 185        enable_irq(ec_dev->irq);
 186
 187        if (ec_dev->wake_enabled) {
 188                disable_irq_wake(ec_dev->irq);
 189                ec_dev->wake_enabled = 0;
 190        }
 191
 192        return 0;
 193}
 194EXPORT_SYMBOL(cros_ec_resume);
 195
 196#endif
 197