Commit 6f1d912b authored by Vic Yang's avatar Vic Yang Committed by Lee Jones

mfd: cros_ec: Add MKBP event support

Newer revisions of the ChromeOS EC add more events besides the keyboard
ones. So handle interrupts in the MFD driver and let consumers register
for notifications for the events they might care.

To keep backward compatibility, if the EC doesn't support MKBP event, we
fall back to the old MKBP key matrix host command.

Cc: Randall Spangler <rspangler@chromium.org>
Cc: Vincent Palatin <vpalatin@chromium.org>
Cc: Benson Leung <bleung@chromium.org>
Signed-off-by: default avatarVic Yang <victoryang@google.com>
Signed-off-by: default avatarTomeu Vizoso <tomeu.vizoso@collabora.com>
Tested-by: default avatarEnric Balletbo i Serra <enric.balletbo@collabora.com>
Acked-by: default avatarOlof Johansson <olof@lixom.net>
Signed-off-by: default avatarLee Jones <lee.jones@linaro.org>
parent 694d0d0b
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/mfd/core.h> #include <linux/mfd/core.h>
#include <linux/mfd/cros_ec.h> #include <linux/mfd/cros_ec.h>
#include <asm/unaligned.h>
#define CROS_EC_DEV_EC_INDEX 0 #define CROS_EC_DEV_EC_INDEX 0
#define CROS_EC_DEV_PD_INDEX 1 #define CROS_EC_DEV_PD_INDEX 1
...@@ -49,11 +50,28 @@ static const struct mfd_cell ec_pd_cell = { ...@@ -49,11 +50,28 @@ static const struct mfd_cell ec_pd_cell = {
.pdata_size = sizeof(pd_p), .pdata_size = sizeof(pd_p),
}; };
static irqreturn_t ec_irq_thread(int irq, void *data)
{
struct cros_ec_device *ec_dev = data;
int ret;
if (device_may_wakeup(ec_dev->dev))
pm_wakeup_event(ec_dev->dev, 0);
ret = cros_ec_get_next_event(ec_dev);
if (ret > 0)
blocking_notifier_call_chain(&ec_dev->event_notifier,
0, ec_dev);
return IRQ_HANDLED;
}
int cros_ec_register(struct cros_ec_device *ec_dev) int cros_ec_register(struct cros_ec_device *ec_dev)
{ {
struct device *dev = ec_dev->dev; struct device *dev = ec_dev->dev;
int err = 0; int err = 0;
BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier);
ec_dev->max_request = sizeof(struct ec_params_hello); ec_dev->max_request = sizeof(struct ec_params_hello);
ec_dev->max_response = sizeof(struct ec_response_get_protocol_info); ec_dev->max_response = sizeof(struct ec_response_get_protocol_info);
ec_dev->max_passthru = 0; ec_dev->max_passthru = 0;
...@@ -70,13 +88,24 @@ int cros_ec_register(struct cros_ec_device *ec_dev) ...@@ -70,13 +88,24 @@ int cros_ec_register(struct cros_ec_device *ec_dev)
cros_ec_query_all(ec_dev); cros_ec_query_all(ec_dev);
if (ec_dev->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, "Failed to request IRQ %d: %d",
ec_dev->irq, err);
return err;
}
}
err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO, &ec_cell, 1, err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO, &ec_cell, 1,
NULL, ec_dev->irq, NULL); NULL, ec_dev->irq, NULL);
if (err) { if (err) {
dev_err(dev, dev_err(dev,
"Failed to register Embedded Controller subdevice %d\n", "Failed to register Embedded Controller subdevice %d\n",
err); err);
return err; goto fail_mfd;
} }
if (ec_dev->max_passthru) { if (ec_dev->max_passthru) {
...@@ -94,7 +123,7 @@ int cros_ec_register(struct cros_ec_device *ec_dev) ...@@ -94,7 +123,7 @@ int cros_ec_register(struct cros_ec_device *ec_dev)
dev_err(dev, dev_err(dev,
"Failed to register Power Delivery subdevice %d\n", "Failed to register Power Delivery subdevice %d\n",
err); err);
return err; goto fail_mfd;
} }
} }
...@@ -103,13 +132,18 @@ int cros_ec_register(struct cros_ec_device *ec_dev) ...@@ -103,13 +132,18 @@ int cros_ec_register(struct cros_ec_device *ec_dev)
if (err) { if (err) {
mfd_remove_devices(dev); mfd_remove_devices(dev);
dev_err(dev, "Failed to register sub-devices\n"); dev_err(dev, "Failed to register sub-devices\n");
return err; goto fail_mfd;
} }
} }
dev_info(dev, "Chrome EC device registered\n"); dev_info(dev, "Chrome EC device registered\n");
return 0; return 0;
fail_mfd:
if (ec_dev->irq)
free_irq(ec_dev->irq, ec_dev);
return err;
} }
EXPORT_SYMBOL(cros_ec_register); EXPORT_SYMBOL(cros_ec_register);
...@@ -136,13 +170,31 @@ int cros_ec_suspend(struct cros_ec_device *ec_dev) ...@@ -136,13 +170,31 @@ int cros_ec_suspend(struct cros_ec_device *ec_dev)
} }
EXPORT_SYMBOL(cros_ec_suspend); EXPORT_SYMBOL(cros_ec_suspend);
static void cros_ec_drain_events(struct cros_ec_device *ec_dev)
{
while (cros_ec_get_next_event(ec_dev) > 0)
blocking_notifier_call_chain(&ec_dev->event_notifier,
1, ec_dev);
}
int cros_ec_resume(struct cros_ec_device *ec_dev) int cros_ec_resume(struct cros_ec_device *ec_dev)
{ {
enable_irq(ec_dev->irq); enable_irq(ec_dev->irq);
/*
* In some cases, we need to distinguish between events that occur
* during suspend if the EC is not a wake source. For example,
* keypresses during suspend should be discarded if it does not wake
* the system.
*
* If the EC is not a wake source, drain the event queue and mark them
* as "queued during suspend".
*/
if (ec_dev->wake_enabled) { if (ec_dev->wake_enabled) {
disable_irq_wake(ec_dev->irq); disable_irq_wake(ec_dev->irq);
ec_dev->wake_enabled = 0; ec_dev->wake_enabled = 0;
} else {
cros_ec_drain_events(ec_dev);
} }
return 0; return 0;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <linux/device.h> #include <linux/device.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <asm/unaligned.h>
#define EC_COMMAND_RETRIES 50 #define EC_COMMAND_RETRIES 50
...@@ -234,11 +235,44 @@ static int cros_ec_host_command_proto_query_v2(struct cros_ec_device *ec_dev) ...@@ -234,11 +235,44 @@ static int cros_ec_host_command_proto_query_v2(struct cros_ec_device *ec_dev)
return ret; return ret;
} }
static int cros_ec_get_host_command_version_mask(struct cros_ec_device *ec_dev,
u16 cmd, u32 *mask)
{
struct ec_params_get_cmd_versions *pver;
struct ec_response_get_cmd_versions *rver;
struct cros_ec_command *msg;
int ret;
msg = kmalloc(sizeof(*msg) + max(sizeof(*rver), sizeof(*pver)),
GFP_KERNEL);
if (!msg)
return -ENOMEM;
msg->version = 0;
msg->command = EC_CMD_GET_CMD_VERSIONS;
msg->insize = sizeof(*rver);
msg->outsize = sizeof(*pver);
pver = (struct ec_params_get_cmd_versions *)msg->data;
pver->cmd = cmd;
ret = cros_ec_cmd_xfer(ec_dev, msg);
if (ret > 0) {
rver = (struct ec_response_get_cmd_versions *)msg->data;
*mask = rver->version_mask;
}
kfree(msg);
return ret;
}
int cros_ec_query_all(struct cros_ec_device *ec_dev) int cros_ec_query_all(struct cros_ec_device *ec_dev)
{ {
struct device *dev = ec_dev->dev; struct device *dev = ec_dev->dev;
struct cros_ec_command *proto_msg; struct cros_ec_command *proto_msg;
struct ec_response_get_protocol_info *proto_info; struct ec_response_get_protocol_info *proto_info;
u32 ver_mask = 0;
int ret; int ret;
proto_msg = kzalloc(sizeof(*proto_msg) + sizeof(*proto_info), proto_msg = kzalloc(sizeof(*proto_msg) + sizeof(*proto_info),
...@@ -328,6 +362,15 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev) ...@@ -328,6 +362,15 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev)
goto exit; goto exit;
} }
/* Probe if MKBP event is supported */
ret = cros_ec_get_host_command_version_mask(ec_dev,
EC_CMD_GET_NEXT_EVENT,
&ver_mask);
if (ret < 0 || ver_mask == 0)
ec_dev->mkbp_event_supported = 0;
else
ec_dev->mkbp_event_supported = 1;
exit: exit:
kfree(proto_msg); kfree(proto_msg);
return ret; return ret;
...@@ -397,3 +440,52 @@ int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev, ...@@ -397,3 +440,52 @@ int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev,
return ret; return ret;
} }
EXPORT_SYMBOL(cros_ec_cmd_xfer_status); EXPORT_SYMBOL(cros_ec_cmd_xfer_status);
static int get_next_event(struct cros_ec_device *ec_dev)
{
u8 buffer[sizeof(struct cros_ec_command) + sizeof(ec_dev->event_data)];
struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;
int ret;
msg->version = 0;
msg->command = EC_CMD_GET_NEXT_EVENT;
msg->insize = sizeof(ec_dev->event_data);
msg->outsize = 0;
ret = cros_ec_cmd_xfer(ec_dev, msg);
if (ret > 0) {
ec_dev->event_size = ret - 1;
memcpy(&ec_dev->event_data, msg->data,
sizeof(ec_dev->event_data));
}
return ret;
}
static int get_keyboard_state_event(struct cros_ec_device *ec_dev)
{
u8 buffer[sizeof(struct cros_ec_command) +
sizeof(ec_dev->event_data.data)];
struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;
msg->version = 0;
msg->command = EC_CMD_MKBP_STATE;
msg->insize = sizeof(ec_dev->event_data.data);
msg->outsize = 0;
ec_dev->event_size = cros_ec_cmd_xfer(ec_dev, msg);
ec_dev->event_data.event_type = EC_MKBP_EVENT_KEY_MATRIX;
memcpy(&ec_dev->event_data.data, msg->data,
sizeof(ec_dev->event_data.data));
return ec_dev->event_size;
}
int cros_ec_get_next_event(struct cros_ec_device *ec_dev)
{
if (ec_dev->mkbp_event_supported)
return get_next_event(ec_dev);
else
return get_keyboard_state_event(ec_dev);
}
EXPORT_SYMBOL(cros_ec_get_next_event);
...@@ -109,6 +109,10 @@ struct cros_ec_command { ...@@ -109,6 +109,10 @@ struct cros_ec_command {
* should check msg.result for the EC's result code. * should check msg.result for the EC's result code.
* @pkt_xfer: send packet to EC and get response * @pkt_xfer: send packet to EC and get response
* @lock: one transaction at a time * @lock: one transaction at a time
* @mkbp_event_supported: true if this EC supports the MKBP event protocol.
* @event_notifier: interrupt event notifier for transport devices.
* @event_data: raw payload transferred with the MKBP event.
* @event_size: size in bytes of the event data.
*/ */
struct cros_ec_device { struct cros_ec_device {
...@@ -137,6 +141,11 @@ struct cros_ec_device { ...@@ -137,6 +141,11 @@ struct cros_ec_device {
int (*pkt_xfer)(struct cros_ec_device *ec, int (*pkt_xfer)(struct cros_ec_device *ec,
struct cros_ec_command *msg); struct cros_ec_command *msg);
struct mutex lock; struct mutex lock;
bool mkbp_event_supported;
struct blocking_notifier_head event_notifier;
struct ec_response_get_next_event event_data;
int event_size;
}; };
/* struct cros_ec_platform - ChromeOS EC platform information /* struct cros_ec_platform - ChromeOS EC platform information
...@@ -269,6 +278,15 @@ int cros_ec_register(struct cros_ec_device *ec_dev); ...@@ -269,6 +278,15 @@ int cros_ec_register(struct cros_ec_device *ec_dev);
*/ */
int cros_ec_query_all(struct cros_ec_device *ec_dev); int cros_ec_query_all(struct cros_ec_device *ec_dev);
/**
* cros_ec_get_next_event - Fetch next event from the ChromeOS EC
*
* @ec_dev: Device to fetch event from
*
* Returns: 0 on success, Linux error number on failure
*/
int cros_ec_get_next_event(struct cros_ec_device *ec_dev);
/* sysfs stuff */ /* sysfs stuff */
extern struct attribute_group cros_ec_attr_group; extern struct attribute_group cros_ec_attr_group;
extern struct attribute_group cros_ec_lightbar_attr_group; extern struct attribute_group cros_ec_lightbar_attr_group;
......
...@@ -1793,6 +1793,40 @@ struct ec_result_keyscan_seq_ctrl { ...@@ -1793,6 +1793,40 @@ struct ec_result_keyscan_seq_ctrl {
}; };
} __packed; } __packed;
/*
* Command for retrieving the next pending MKBP event from the EC device
*
* The device replies with UNAVAILABLE if there aren't any pending events.
*/
#define EC_CMD_GET_NEXT_EVENT 0x67
enum ec_mkbp_event {
/* Keyboard matrix changed. The event data is the new matrix state. */
EC_MKBP_EVENT_KEY_MATRIX = 0,
/* New host event. The event data is 4 bytes of host event flags. */
EC_MKBP_EVENT_HOST_EVENT = 1,
/* New Sensor FIFO data. The event data is fifo_info structure. */
EC_MKBP_EVENT_SENSOR_FIFO = 2,
/* Number of MKBP events */
EC_MKBP_EVENT_COUNT,
};
union ec_response_get_next_data {
uint8_t key_matrix[13];
/* Unaligned */
uint32_t host_event;
} __packed;
struct ec_response_get_next_event {
uint8_t event_type;
/* Followed by event data if any */
union ec_response_get_next_data data;
} __packed;
/*****************************************************************************/ /*****************************************************************************/
/* Temperature sensor commands */ /* Temperature sensor commands */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment