mfd: Add ChromeOS EC implementation
This is the base EC implementation, which provides a high level interface to the EC for use by the rest of the kernel. The actual communcations is dealt with by a separate protocol driver which registers itself with this interface. Interrupts are passed on through a notifier. A simple message structure is used to pass messages to the protocol driver. Signed-off-by: Simon Glass <sjg@chromium.org> Signed-off-by: Che-Liang Chiou <clchiou@chromium.org> Signed-off-by: Jonathan Kliegman <kliegs@chromium.org> Signed-off-by: Luigi Semenzato <semenzato@chromium.org> Signed-off-by: Olof Johansson <olofj@chromium.org> Signed-off-by: Vincent Palatin <vpalatin@chromium.org> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
parent
deaf39efbc
commit
4ab6174e8c
56
Documentation/devicetree/bindings/mfd/cros-ec.txt
Normal file
56
Documentation/devicetree/bindings/mfd/cros-ec.txt
Normal file
@ -0,0 +1,56 @@
|
||||
ChromeOS Embedded Controller
|
||||
|
||||
Google's ChromeOS EC is a Cortex-M device which talks to the AP and
|
||||
implements various function such as keyboard and battery charging.
|
||||
|
||||
The EC can be connect through various means (I2C, SPI, LPC) and the
|
||||
compatible string used depends on the inteface. Each connection method has
|
||||
its own driver which connects to the top level interface-agnostic EC driver.
|
||||
Other Linux driver (such as cros-ec-keyb for the matrix keyboard) connect to
|
||||
the top-level driver.
|
||||
|
||||
Required properties (I2C):
|
||||
- compatible: "google,cros-ec-i2c"
|
||||
- reg: I2C slave address
|
||||
|
||||
Required properties (SPI):
|
||||
- compatible: "google,cros-ec-spi"
|
||||
- reg: SPI chip select
|
||||
|
||||
Required properties (LPC):
|
||||
- compatible: "google,cros-ec-lpc"
|
||||
- reg: List of (IO address, size) pairs defining the interface uses
|
||||
|
||||
|
||||
Example for I2C:
|
||||
|
||||
i2c@12CA0000 {
|
||||
cros-ec@1e {
|
||||
reg = <0x1e>;
|
||||
compatible = "google,cros-ec-i2c";
|
||||
interrupts = <14 0>;
|
||||
interrupt-parent = <&wakeup_eint>;
|
||||
wakeup-source;
|
||||
};
|
||||
|
||||
|
||||
Example for SPI:
|
||||
|
||||
spi@131b0000 {
|
||||
ec@0 {
|
||||
compatible = "google,cros-ec-spi";
|
||||
reg = <0x0>;
|
||||
interrupts = <14 0>;
|
||||
interrupt-parent = <&wakeup_eint>;
|
||||
wakeup-source;
|
||||
spi-max-frequency = <5000000>;
|
||||
controller-data {
|
||||
cs-gpio = <&gpf0 3 4 3 0>;
|
||||
samsung,spi-cs;
|
||||
samsung,spi-feedback-delay = <2>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Example for LPC is not supplied as it is not yet implemented.
|
@ -21,6 +21,14 @@ config MFD_88PM860X
|
||||
select individual components like voltage regulators, RTC and
|
||||
battery-charger under the corresponding menus.
|
||||
|
||||
config MFD_CROS_EC
|
||||
tristate "Support ChromeOS Embedded Controller"
|
||||
help
|
||||
If you say Y here you get support for the ChromeOS Embedded
|
||||
Controller (EC) providing keyboard, battery and power services.
|
||||
You also ned to enable the driver for the bus you are using. The
|
||||
protocol for talking to the EC is defined by the bus driver.
|
||||
|
||||
config MFD_88PM800
|
||||
tristate "Support Marvell 88PM800"
|
||||
depends on I2C=y && GENERIC_HARDIRQS
|
||||
|
@ -8,6 +8,7 @@ obj-$(CONFIG_MFD_88PM800) += 88pm800.o 88pm80x.o
|
||||
obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o
|
||||
obj-$(CONFIG_MFD_SM501) += sm501.o
|
||||
obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o
|
||||
obj-$(CONFIG_MFD_CROS_EC) += cros_ec.o
|
||||
|
||||
rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o rts5227.o
|
||||
obj-$(CONFIG_MFD_RTSX_PCI) += rtsx_pci.o
|
||||
|
189
drivers/mfd/cros_ec.c
Normal file
189
drivers/mfd/cros_ec.c
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* ChromeOS EC multi-function device
|
||||
*
|
||||
* Copyright (C) 2012 Google, Inc
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* The ChromeOS EC multi function device is used to mux all the requests
|
||||
* to the EC device for its multiple features: keyboard controller,
|
||||
* battery charging and regulator control, firmware update.
|
||||
*/
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/cros_ec.h>
|
||||
#include <linux/mfd/cros_ec_commands.h>
|
||||
|
||||
int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
|
||||
struct cros_ec_msg *msg)
|
||||
{
|
||||
uint8_t *out;
|
||||
int csum, i;
|
||||
|
||||
BUG_ON(msg->out_len > EC_HOST_PARAM_SIZE);
|
||||
out = ec_dev->dout;
|
||||
out[0] = EC_CMD_VERSION0 + msg->version;
|
||||
out[1] = msg->cmd;
|
||||
out[2] = msg->out_len;
|
||||
csum = out[0] + out[1] + out[2];
|
||||
for (i = 0; i < msg->out_len; i++)
|
||||
csum += out[EC_MSG_TX_HEADER_BYTES + i] = msg->out_buf[i];
|
||||
out[EC_MSG_TX_HEADER_BYTES + msg->out_len] = (uint8_t)(csum & 0xff);
|
||||
|
||||
return EC_MSG_TX_PROTO_BYTES + msg->out_len;
|
||||
}
|
||||
|
||||
static int cros_ec_command_sendrecv(struct cros_ec_device *ec_dev,
|
||||
uint16_t cmd, void *out_buf, int out_len,
|
||||
void *in_buf, int in_len)
|
||||
{
|
||||
struct cros_ec_msg msg;
|
||||
|
||||
msg.version = cmd >> 8;
|
||||
msg.cmd = cmd & 0xff;
|
||||
msg.out_buf = out_buf;
|
||||
msg.out_len = out_len;
|
||||
msg.in_buf = in_buf;
|
||||
msg.in_len = in_len;
|
||||
|
||||
return ec_dev->command_xfer(ec_dev, &msg);
|
||||
}
|
||||
|
||||
static int cros_ec_command_recv(struct cros_ec_device *ec_dev,
|
||||
uint16_t cmd, void *buf, int buf_len)
|
||||
{
|
||||
return cros_ec_command_sendrecv(ec_dev, cmd, NULL, 0, buf, buf_len);
|
||||
}
|
||||
|
||||
static int cros_ec_command_send(struct cros_ec_device *ec_dev,
|
||||
uint16_t cmd, void *buf, int buf_len)
|
||||
{
|
||||
return cros_ec_command_sendrecv(ec_dev, cmd, buf, buf_len, NULL, 0);
|
||||
}
|
||||
|
||||
static irqreturn_t ec_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct cros_ec_device *ec_dev = data;
|
||||
|
||||
if (device_may_wakeup(ec_dev->dev))
|
||||
pm_wakeup_event(ec_dev->dev, 0);
|
||||
|
||||
blocking_notifier_call_chain(&ec_dev->event_notifier, 1, ec_dev);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct mfd_cell cros_devs[] = {
|
||||
{
|
||||
.name = "cros-ec-keyb",
|
||||
.id = 1,
|
||||
.of_compatible = "google,cros-ec-keyb",
|
||||
},
|
||||
};
|
||||
|
||||
int cros_ec_register(struct cros_ec_device *ec_dev)
|
||||
{
|
||||
struct device *dev = ec_dev->dev;
|
||||
int err = 0;
|
||||
|
||||
BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier);
|
||||
|
||||
ec_dev->command_send = cros_ec_command_send;
|
||||
ec_dev->command_recv = cros_ec_command_recv;
|
||||
ec_dev->command_sendrecv = cros_ec_command_sendrecv;
|
||||
|
||||
if (ec_dev->din_size) {
|
||||
ec_dev->din = kmalloc(ec_dev->din_size, GFP_KERNEL);
|
||||
if (!ec_dev->din) {
|
||||
err = -ENOMEM;
|
||||
goto fail_din;
|
||||
}
|
||||
}
|
||||
if (ec_dev->dout_size) {
|
||||
ec_dev->dout = kmalloc(ec_dev->dout_size, GFP_KERNEL);
|
||||
if (!ec_dev->dout) {
|
||||
err = -ENOMEM;
|
||||
goto fail_dout;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ec_dev->irq) {
|
||||
dev_dbg(dev, "no valid IRQ: %d\n", ec_dev->irq);
|
||||
goto fail_irq;
|
||||
}
|
||||
|
||||
err = request_threaded_irq(ec_dev->irq, NULL, ec_irq_thread,
|
||||
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||||
"chromeos-ec", ec_dev);
|
||||
if (err) {
|
||||
dev_err(dev, "request irq %d: error %d\n", ec_dev->irq, err);
|
||||
goto fail_irq;
|
||||
}
|
||||
|
||||
err = mfd_add_devices(dev, 0, cros_devs,
|
||||
ARRAY_SIZE(cros_devs),
|
||||
NULL, ec_dev->irq, NULL);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to add mfd devices\n");
|
||||
goto fail_mfd;
|
||||
}
|
||||
|
||||
dev_info(dev, "Chrome EC (%s)\n", ec_dev->name);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_mfd:
|
||||
free_irq(ec_dev->irq, ec_dev);
|
||||
fail_irq:
|
||||
kfree(ec_dev->dout);
|
||||
fail_dout:
|
||||
kfree(ec_dev->din);
|
||||
fail_din:
|
||||
return err;
|
||||
}
|
||||
|
||||
int cros_ec_remove(struct cros_ec_device *ec_dev)
|
||||
{
|
||||
mfd_remove_devices(ec_dev->dev);
|
||||
free_irq(ec_dev->irq, ec_dev);
|
||||
kfree(ec_dev->dout);
|
||||
kfree(ec_dev->din);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
int cros_ec_suspend(struct cros_ec_device *ec_dev)
|
||||
{
|
||||
struct device *dev = ec_dev->dev;
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq);
|
||||
|
||||
disable_irq(ec_dev->irq);
|
||||
ec_dev->was_wake_device = ec_dev->wake_enabled;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cros_ec_resume(struct cros_ec_device *ec_dev)
|
||||
{
|
||||
enable_irq(ec_dev->irq);
|
||||
|
||||
if (ec_dev->wake_enabled) {
|
||||
disable_irq_wake(ec_dev->irq);
|
||||
ec_dev->wake_enabled = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
170
include/linux/mfd/cros_ec.h
Normal file
170
include/linux/mfd/cros_ec.h
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* ChromeOS EC multi-function device
|
||||
*
|
||||
* Copyright (C) 2012 Google, Inc
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MFD_CROS_EC_H
|
||||
#define __LINUX_MFD_CROS_EC_H
|
||||
|
||||
#include <linux/mfd/cros_ec_commands.h>
|
||||
|
||||
/*
|
||||
* Command interface between EC and AP, for LPC, I2C and SPI interfaces.
|
||||
*/
|
||||
enum {
|
||||
EC_MSG_TX_HEADER_BYTES = 3,
|
||||
EC_MSG_TX_TRAILER_BYTES = 1,
|
||||
EC_MSG_TX_PROTO_BYTES = EC_MSG_TX_HEADER_BYTES +
|
||||
EC_MSG_TX_TRAILER_BYTES,
|
||||
EC_MSG_RX_PROTO_BYTES = 3,
|
||||
|
||||
/* Max length of messages */
|
||||
EC_MSG_BYTES = EC_HOST_PARAM_SIZE + EC_MSG_TX_PROTO_BYTES,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* struct cros_ec_msg - A message sent to the EC, and its reply
|
||||
*
|
||||
* @version: Command version number (often 0)
|
||||
* @cmd: Command to send (EC_CMD_...)
|
||||
* @out_buf: Outgoing payload (to EC)
|
||||
* @outlen: Outgoing length
|
||||
* @in_buf: Incoming payload (from EC)
|
||||
* @in_len: Incoming length
|
||||
*/
|
||||
struct cros_ec_msg {
|
||||
u8 version;
|
||||
u8 cmd;
|
||||
uint8_t *out_buf;
|
||||
int out_len;
|
||||
uint8_t *in_buf;
|
||||
int in_len;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct cros_ec_device - Information about a ChromeOS EC device
|
||||
*
|
||||
* @name: Name of this EC interface
|
||||
* @priv: Private data
|
||||
* @irq: Interrupt to use
|
||||
* @din: input buffer (from EC)
|
||||
* @dout: output buffer (to EC)
|
||||
* \note
|
||||
* These two buffers will always be dword-aligned and include enough
|
||||
* space for up to 7 word-alignment bytes also, so we can ensure that
|
||||
* the body of the message is always dword-aligned (64-bit).
|
||||
*
|
||||
* We use this alignment to keep ARM and x86 happy. Probably word
|
||||
* alignment would be OK, there might be a small performance advantage
|
||||
* to using dword.
|
||||
* @din_size: size of din buffer
|
||||
* @dout_size: size of dout buffer
|
||||
* @command_send: send a command
|
||||
* @command_recv: receive a command
|
||||
* @ec_name: name of EC device (e.g. 'chromeos-ec')
|
||||
* @phys_name: name of physical comms layer (e.g. 'i2c-4')
|
||||
* @parent: pointer to parent device (e.g. i2c or spi device)
|
||||
* @dev: Device pointer
|
||||
* dev_lock: Lock to prevent concurrent access
|
||||
* @wake_enabled: true if this device can wake the system from sleep
|
||||
* @was_wake_device: true if this device was set to wake the system from
|
||||
* sleep at the last suspend
|
||||
* @event_notifier: interrupt event notifier for transport devices
|
||||
*/
|
||||
struct cros_ec_device {
|
||||
const char *name;
|
||||
void *priv;
|
||||
int irq;
|
||||
uint8_t *din;
|
||||
uint8_t *dout;
|
||||
int din_size;
|
||||
int dout_size;
|
||||
int (*command_send)(struct cros_ec_device *ec,
|
||||
uint16_t cmd, void *out_buf, int out_len);
|
||||
int (*command_recv)(struct cros_ec_device *ec,
|
||||
uint16_t cmd, void *in_buf, int in_len);
|
||||
int (*command_sendrecv)(struct cros_ec_device *ec,
|
||||
uint16_t cmd, void *out_buf, int out_len,
|
||||
void *in_buf, int in_len);
|
||||
int (*command_xfer)(struct cros_ec_device *ec,
|
||||
struct cros_ec_msg *msg);
|
||||
|
||||
const char *ec_name;
|
||||
const char *phys_name;
|
||||
struct device *parent;
|
||||
|
||||
/* These are --private-- fields - do not assign */
|
||||
struct device *dev;
|
||||
struct mutex dev_lock;
|
||||
bool wake_enabled;
|
||||
bool was_wake_device;
|
||||
struct blocking_notifier_head event_notifier;
|
||||
};
|
||||
|
||||
/**
|
||||
* cros_ec_suspend - Handle a suspend operation for the ChromeOS EC device
|
||||
*
|
||||
* This can be called by drivers to handle a suspend event.
|
||||
*
|
||||
* ec_dev: Device to suspend
|
||||
* @return 0 if ok, -ve on error
|
||||
*/
|
||||
int cros_ec_suspend(struct cros_ec_device *ec_dev);
|
||||
|
||||
/**
|
||||
* cros_ec_resume - Handle a resume operation for the ChromeOS EC device
|
||||
*
|
||||
* This can be called by drivers to handle a resume event.
|
||||
*
|
||||
* @ec_dev: Device to resume
|
||||
* @return 0 if ok, -ve on error
|
||||
*/
|
||||
int cros_ec_resume(struct cros_ec_device *ec_dev);
|
||||
|
||||
/**
|
||||
* cros_ec_prepare_tx - Prepare an outgoing message in the output buffer
|
||||
*
|
||||
* This is intended to be used by all ChromeOS EC drivers, but at present
|
||||
* only SPI uses it. Once LPC uses the same protocol it can start using it.
|
||||
* I2C could use it now, with a refactor of the existing code.
|
||||
*
|
||||
* @ec_dev: Device to register
|
||||
* @msg: Message to write
|
||||
*/
|
||||
int cros_ec_prepare_tx(struct cros_ec_device *ec_dev,
|
||||
struct cros_ec_msg *msg);
|
||||
|
||||
/**
|
||||
* cros_ec_remove - Remove a ChromeOS EC
|
||||
*
|
||||
* Call this to deregister a ChromeOS EC. After this you should call
|
||||
* cros_ec_free().
|
||||
*
|
||||
* @ec_dev: Device to register
|
||||
* @return 0 if ok, -ve on error
|
||||
*/
|
||||
int cros_ec_remove(struct cros_ec_device *ec_dev);
|
||||
|
||||
/**
|
||||
* cros_ec_register - Register a new ChromeOS EC, using the provided info
|
||||
*
|
||||
* Before calling this, allocate a pointer to a new device and then fill
|
||||
* in all the fields up to the --private-- marker.
|
||||
*
|
||||
* @ec_dev: Device to register
|
||||
* @return 0 if ok, -ve on error
|
||||
*/
|
||||
int cros_ec_register(struct cros_ec_device *ec_dev);
|
||||
|
||||
#endif /* __LINUX_MFD_CROS_EC_H */
|
Loading…
Reference in New Issue
Block a user