On Fri, Jan 20, 2017 at 11:14:15AM +0100, Enric Balletbo i Serra wrote: > From: Douglas Anderson <dianders@xxxxxxxxxxxx> > > On some newer boards using mkbp we're hooking up non-matrix buttons and > switches to the EC but NOT to the main application processor. > > Let's add kernel support to handle this. Rather than creating a whole > new input driver, we'll continue to use cros_ec_keyb and just report the > new keys. > > Signed-off-by: Douglas Anderson <dianders@xxxxxxxxxxxx> > Signed-off-by: Enric Balletbo i Serra <enric.balletbo@xxxxxxxxxxxxx> Acked-by: Dmitry Torokhov <dmitry.torokhov@xxxxxxxxx> > --- > drivers/input/keyboard/cros_ec_keyb.c | 447 ++++++++++++++++++++++++++++++---- > 1 file changed, 402 insertions(+), 45 deletions(-) > > diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c > index 25943e9..ad74ebc 100644 > --- a/drivers/input/keyboard/cros_ec_keyb.c > +++ b/drivers/input/keyboard/cros_ec_keyb.c > @@ -34,6 +34,8 @@ > #include <linux/mfd/cros_ec.h> > #include <linux/mfd/cros_ec_commands.h> > > +#include <asm/unaligned.h> > + > /* > * @rows: Number of rows in the keypad > * @cols: Number of columns in the keypad > @@ -43,8 +45,9 @@ > * @valid_keys: bitmap of existing keys for each matrix column > * @old_kb_state: bitmap of keys pressed last scan > * @dev: Device pointer > - * @idev: Input device > * @ec: Top level ChromeOS device to use to talk to EC > + * @idev: The input device for the matrix keys. > + * @bs_idev: The input device for non-matrix buttons and switches (or NULL). > * @notifier: interrupt event notifier for transport devices > */ > struct cros_ec_keyb { > @@ -57,12 +60,59 @@ struct cros_ec_keyb { > uint8_t *old_kb_state; > > struct device *dev; > - struct input_dev *idev; > struct cros_ec_device *ec; > + > + struct input_dev *idev; > + struct input_dev *bs_idev; > struct notifier_block notifier; > }; > > > +/** > + * cros_ec_bs_map - Struct mapping Linux keycodes to EC button/switch bitmap > + * #defines > + * > + * @ev_type: The type of the input event to generate (e.g., EV_KEY). > + * @code: A linux keycode > + * @bit: A #define like EC_MKBP_POWER_BUTTON or EC_MKBP_LID_OPEN > + * @inverted: If the #define and EV_SW have opposite meanings, this is true. > + * Only applicable to switches. > + */ > +struct cros_ec_bs_map { > + unsigned int ev_type; > + unsigned int code; > + u8 bit; > + bool inverted; > +}; > + > +/* cros_ec_keyb_bs - Map EC button/switch #defines into kernel ones */ > +static const struct cros_ec_bs_map cros_ec_keyb_bs[] = { > + /* Buttons */ > + { > + .ev_type = EV_KEY, > + .code = KEY_POWER, > + .bit = EC_MKBP_POWER_BUTTON, > + }, > + { > + .ev_type = EV_KEY, > + .code = KEY_VOLUMEUP, > + .bit = EC_MKBP_VOL_UP, > + }, > + { > + .ev_type = EV_KEY, > + .code = KEY_VOLUMEDOWN, > + .bit = EC_MKBP_VOL_DOWN, > + }, > + > + /* Switches */ > + { > + .ev_type = EV_SW, > + .code = SW_LID, > + .bit = EC_MKBP_LID_OPEN, > + .inverted = true, > + }, > +}; > + > /* > * Returns true when there is at least one combination of pressed keys that > * results in ghosting. > @@ -149,20 +199,33 @@ static void cros_ec_keyb_process(struct cros_ec_keyb *ckdev, > input_sync(ckdev->idev); > } > > -static int cros_ec_keyb_open(struct input_dev *dev) > +/** > + * cros_ec_keyb_report_bs - Report non-matrixed buttons or switches > + * > + * This takes a bitmap of buttons or switches from the EC and reports events, > + * syncing at the end. > + * > + * @ckdev: The keyboard device. > + * @ev_type: The input event type (e.g., EV_KEY). > + * @mask: A bitmap of buttons from the EC. > + */ > +static void cros_ec_keyb_report_bs(struct cros_ec_keyb *ckdev, > + unsigned int ev_type, u32 mask) > + > { > - struct cros_ec_keyb *ckdev = input_get_drvdata(dev); > + struct input_dev *idev = ckdev->bs_idev; > + int i; > > - return blocking_notifier_chain_register(&ckdev->ec->event_notifier, > - &ckdev->notifier); > -} > + for (i = 0; i < ARRAY_SIZE(cros_ec_keyb_bs); i++) { > + const struct cros_ec_bs_map *map = &cros_ec_keyb_bs[i]; > > -static void cros_ec_keyb_close(struct input_dev *dev) > -{ > - struct cros_ec_keyb *ckdev = input_get_drvdata(dev); > + if (map->ev_type != ev_type) > + continue; > > - blocking_notifier_chain_unregister(&ckdev->ec->event_notifier, > - &ckdev->notifier); > + input_event(idev, ev_type, map->code, > + !!(mask & BIT(map->bit)) ^ map->inverted); > + } > + input_sync(idev); > } > > static int cros_ec_keyb_work(struct notifier_block *nb, > @@ -170,22 +233,54 @@ static int cros_ec_keyb_work(struct notifier_block *nb, > { > struct cros_ec_keyb *ckdev = container_of(nb, struct cros_ec_keyb, > notifier); > + u32 val; > + unsigned int ev_type; > + > + switch (ckdev->ec->event_data.event_type) { > + case EC_MKBP_EVENT_KEY_MATRIX: > + /* > + * If EC is not the wake source, discard key state changes > + * during suspend. > + */ > + if (queued_during_suspend) > + return NOTIFY_OK; > + > + if (ckdev->ec->event_size != ckdev->cols) { > + dev_err(ckdev->dev, > + "Discarded incomplete key matrix event.\n"); > + return NOTIFY_OK; > + } > + cros_ec_keyb_process(ckdev, > + ckdev->ec->event_data.data.key_matrix, > + ckdev->ec->event_size); > + break; > + > + case EC_MKBP_EVENT_BUTTON: > + case EC_MKBP_EVENT_SWITCH: > + /* > + * If EC is not the wake source, discard key state > + * changes during suspend. Switches will be re-checked in > + * cros_ec_keyb_resume() to be sure nothing is lost. > + */ > + if (queued_during_suspend) > + return NOTIFY_OK; > + > + if (ckdev->ec->event_data.event_type == EC_MKBP_EVENT_BUTTON) { > + val = get_unaligned_le32( > + &ckdev->ec->event_data.data.buttons); > + ev_type = EV_KEY; > + } else { > + val = get_unaligned_le32( > + &ckdev->ec->event_data.data.switches); > + ev_type = EV_SW; > + } > + cros_ec_keyb_report_bs(ckdev, ev_type, val); > + break; > > - if (ckdev->ec->event_data.event_type != EC_MKBP_EVENT_KEY_MATRIX) > + default: > return NOTIFY_DONE; > - /* > - * If EC is not the wake source, discard key state changes during > - * suspend. > - */ > - if (queued_during_suspend) > - return NOTIFY_OK; > - if (ckdev->ec->event_size != ckdev->cols) { > - dev_err(ckdev->dev, > - "Discarded incomplete key matrix event.\n"); > - return NOTIFY_OK; > } > - cros_ec_keyb_process(ckdev, ckdev->ec->event_data.data.key_matrix, > - ckdev->ec->event_size); > + > return NOTIFY_OK; > } > > @@ -213,22 +308,228 @@ static void cros_ec_keyb_compute_valid_keys(struct cros_ec_keyb *ckdev) > } > } > > -static int cros_ec_keyb_probe(struct platform_device *pdev) > +/** > + * cros_ec_keyb_info - Wrap the EC command EC_CMD_MKBP_INFO > + * > + * This wraps the EC_CMD_MKBP_INFO, abstracting out all of the marshalling and > + * unmarshalling and different version nonsense into something simple. > + * > + * @ec_dev: The EC device > + * @info_type: Either EC_MKBP_INFO_SUPPORTED or EC_MKBP_INFO_CURRENT. > + * @event_type: Either EC_MKBP_EVENT_BUTTON or EC_MKBP_EVENT_SWITCH. Actually > + * in some cases this could be EC_MKBP_EVENT_KEY_MATRIX or > + * EC_MKBP_EVENT_HOST_EVENT too but we don't use in this driver. > + * @result: Where we'll store the result; a union > + * @result_size: The size of the result. Expected to be the size of one of > + * the elements in the union. > + * > + * Returns 0 if no error or -error upon error. > + */ > +static int cros_ec_keyb_info(struct cros_ec_device *ec_dev, > + enum ec_mkbp_info_type info_type, > + enum ec_mkbp_event event_type, > + union ec_response_get_next_data *result, > + size_t result_size) > { > - struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); > - struct device *dev = &pdev->dev; > - struct cros_ec_keyb *ckdev; > + struct ec_params_mkbp_info *params; > + struct cros_ec_command *msg; > + int ret; > + > + msg = kzalloc(sizeof(*msg) + max_t(size_t, result_size, > + sizeof(*params)), GFP_KERNEL); > + if (!msg) > + return -ENOMEM; > + > + msg->command = EC_CMD_MKBP_INFO; > + msg->version = 1; > + msg->outsize = sizeof(*params); > + msg->insize = result_size; > + params = (struct ec_params_mkbp_info *)msg->data; > + params->info_type = info_type; > + params->event_type = event_type; > + > + ret = cros_ec_cmd_xfer(ec_dev, msg); > + if (ret < 0) { > + dev_warn(ec_dev->dev, "Transfer error %d/%d: %d\n", > + (int)info_type, (int)event_type, ret); > + } else if (msg->result == EC_RES_INVALID_VERSION) { > + /* With older ECs we just return 0 for everything */ > + memset(result, 0, result_size); > + ret = 0; > + } else if (msg->result != EC_RES_SUCCESS) { > + dev_warn(ec_dev->dev, "Error getting info %d/%d: %d\n", > + (int)info_type, (int)event_type, msg->result); > + ret = -EPROTO; > + } else if (ret != result_size) { > + dev_warn(ec_dev->dev, "Wrong size %d/%d: %d != %zu\n", > + (int)info_type, (int)event_type, > + ret, result_size); > + ret = -EPROTO; > + } else { > + memcpy(result, msg->data, result_size); > + ret = 0; > + } > + > + kfree(msg); > + > + return ret; > +} > + > +/** > + * cros_ec_keyb_query_switches - Query the state of switches and report > + * > + * This will ask the EC about the current state of switches and report to the > + * kernel. Note that we don't query for buttons because they are more > + * transitory and we'll get an update on the next release / press. > + * > + * @ckdev: The keyboard device > + * > + * Returns 0 if no error or -error upon error. > + */ > +static int cros_ec_keyb_query_switches(struct cros_ec_keyb *ckdev) > +{ > + struct cros_ec_device *ec_dev = ckdev->ec; > + union ec_response_get_next_data event_data = {}; > + int ret; > + > + ret = cros_ec_keyb_info(ec_dev, EC_MKBP_INFO_CURRENT, > + EC_MKBP_EVENT_SWITCH, &event_data, > + sizeof(event_data.switches)); > + if (ret) > + return ret; > + > + cros_ec_keyb_report_bs(ckdev, EV_SW, > + get_unaligned_le32(&event_data.switches)); > + > + return 0; > +} > + > +/** > + * cros_ec_keyb_resume - Resume the keyboard > + * > + * We use the resume notification as a chance to query the EC for switches. > + * > + * @dev: The keyboard device > + * > + * Returns 0 if no error or -error upon error. > + */ > +static __maybe_unused int cros_ec_keyb_resume(struct device *dev) > +{ > + struct cros_ec_keyb *ckdev = dev_get_drvdata(dev); > + > + if (ckdev->bs_idev) > + return cros_ec_keyb_query_switches(ckdev); > + > + return 0; > +} > + > +/** > + * cros_ec_keyb_register_bs - Register non-matrix buttons/switches > + * > + * Handles all the bits of the keyboard driver related to non-matrix buttons > + * and switches, including asking the EC about which are present and telling > + * the kernel to expect them. > + * > + * If this device has no support for buttons and switches we'll return no error > + * but the ckdev->bs_idev will remain NULL when this function exits. > + * > + * @ckdev: The keyboard device > + * > + * Returns 0 if no error or -error upon error. > + */ > +static int cros_ec_keyb_register_bs(struct cros_ec_keyb *ckdev) > +{ > + struct cros_ec_device *ec_dev = ckdev->ec; > + struct device *dev = ckdev->dev; > struct input_dev *idev; > - struct device_node *np; > - int err; > + union ec_response_get_next_data event_data = {}; > + const char *phys; > + u32 buttons; > + u32 switches; > + int ret; > + int i; > + > + ret = cros_ec_keyb_info(ec_dev, EC_MKBP_INFO_SUPPORTED, > + EC_MKBP_EVENT_BUTTON, &event_data, > + sizeof(event_data.buttons)); > + if (ret) > + return ret; > + buttons = get_unaligned_le32(&event_data.buttons); > + > + ret = cros_ec_keyb_info(ec_dev, EC_MKBP_INFO_SUPPORTED, > + EC_MKBP_EVENT_SWITCH, &event_data, > + sizeof(event_data.switches)); > + if (ret) > + return ret; > + switches = get_unaligned_le32(&event_data.switches); > + > + if (!buttons && !switches) > + return 0; > > - np = pdev->dev.of_node; > - if (!np) > - return -ENODEV; > + /* > + * We call the non-matrix buttons/switches 'input1', if present. > + * Allocate phys before input dev, to ensure correct tear-down > + * ordering. > + */ > + phys = devm_kasprintf(dev, GFP_KERNEL, "%s/input1", ec_dev->phys_name); > + if (!phys) > + return -ENOMEM; > > - ckdev = devm_kzalloc(dev, sizeof(*ckdev), GFP_KERNEL); > - if (!ckdev) > + idev = devm_input_allocate_device(dev); > + if (!idev) > return -ENOMEM; > + > + idev->name = "cros_ec_buttons"; > + idev->phys = phys; > + __set_bit(EV_REP, idev->evbit); > + > + idev->id.bustype = BUS_VIRTUAL; > + idev->id.version = 1; > + idev->id.product = 0; > + idev->dev.parent = dev; > + > + input_set_drvdata(idev, ckdev); > + ckdev->bs_idev = idev; > + > + for (i = 0; i < ARRAY_SIZE(cros_ec_keyb_bs); i++) { > + const struct cros_ec_bs_map *map = &cros_ec_keyb_bs[i]; > + > + if (buttons & BIT(map->bit)) > + input_set_capability(idev, map->ev_type, map->code); > + } > + > + ret = cros_ec_keyb_query_switches(ckdev); > + if (ret) { > + dev_err(dev, "cannot query switches\n"); > + return ret; > + } > + > + ret = input_register_device(ckdev->bs_idev); > + if (ret) { > + dev_err(dev, "cannot register input device\n"); > + return ret; > + } > + > + return 0; > +} > + > +/** > + * cros_ec_keyb_register_bs - Register matrix keys > + * > + * Handles all the bits of the keyboard driver related to matrix keys. > + * > + * @ckdev: The keyboard device > + * > + * Returns 0 if no error or -error upon error. > + */ > +static int cros_ec_keyb_register_matrix(struct cros_ec_keyb *ckdev) > +{ > + struct cros_ec_device *ec_dev = ckdev->ec; > + struct device *dev = ckdev->dev; > + struct input_dev *idev; > + const char *phys; > + int err; > + > err = matrix_keypad_parse_of_params(dev, &ckdev->rows, &ckdev->cols); > if (err) > return err; > @@ -241,27 +542,28 @@ static int cros_ec_keyb_probe(struct platform_device *pdev) > if (!ckdev->old_kb_state) > return -ENOMEM; > > + /* > + * We call the keyboard matrix 'input0'. Allocate phys before input > + * dev, to ensure correct tear-down ordering. > + */ > + phys = devm_kasprintf(dev, GFP_KERNEL, "%s/input0", ec_dev->phys_name); > + if (!phys) > + return -ENOMEM; > + > idev = devm_input_allocate_device(dev); > if (!idev) > return -ENOMEM; > > - ckdev->ec = ec; > - ckdev->notifier.notifier_call = cros_ec_keyb_work; > - ckdev->dev = dev; > - dev_set_drvdata(dev, ckdev); > - > idev->name = CROS_EC_DEV_NAME; > - idev->phys = ec->phys_name; > + idev->phys = phys; > __set_bit(EV_REP, idev->evbit); > > idev->id.bustype = BUS_VIRTUAL; > idev->id.version = 1; > idev->id.product = 0; > idev->dev.parent = dev; > - idev->open = cros_ec_keyb_open; > - idev->close = cros_ec_keyb_close; > > - ckdev->ghost_filter = of_property_read_bool(np, > + ckdev->ghost_filter = of_property_read_bool(dev->of_node, > "google,needs-ghost-filter"); > > err = matrix_keypad_build_keymap(NULL, NULL, ckdev->rows, ckdev->cols, > @@ -287,6 +589,57 @@ static int cros_ec_keyb_probe(struct platform_device *pdev) > return 0; > } > > +static int cros_ec_keyb_probe(struct platform_device *pdev) > +{ > + struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent); > + struct device *dev = &pdev->dev; > + struct cros_ec_keyb *ckdev; > + int err; > + > + if (!dev->of_node) > + return -ENODEV; > + > + ckdev = devm_kzalloc(dev, sizeof(*ckdev), GFP_KERNEL); > + if (!ckdev) > + return -ENOMEM; > + > + ckdev->ec = ec; > + ckdev->dev = dev; > + dev_set_drvdata(dev, ckdev); > + > + err = cros_ec_keyb_register_matrix(ckdev); > + if (err) { > + dev_err(dev, "cannot register matrix inputs: %d\n", err); > + return err; > + } > + > + err = cros_ec_keyb_register_bs(ckdev); > + if (err) { > + dev_err(dev, "cannot register non-matrix inputs: %d\n", err); > + return err; > + } > + > + ckdev->notifier.notifier_call = cros_ec_keyb_work; > + err = blocking_notifier_chain_register(&ckdev->ec->event_notifier, > + &ckdev->notifier); > + if (err) { > + dev_err(dev, "cannot register notifier: %d\n", err); > + return err; > + } > + > + return 0; > +} > + > +static int cros_ec_keyb_remove(struct platform_device *pdev) > +{ > + struct cros_ec_keyb *ckdev = dev_get_drvdata(&pdev->dev); > + > + blocking_notifier_chain_unregister(&ckdev->ec->event_notifier, > + &ckdev->notifier); > + > + return 0; > +} > + > #ifdef CONFIG_OF > static const struct of_device_id cros_ec_keyb_of_match[] = { > { .compatible = "google,cros-ec-keyb" }, > @@ -295,11 +648,15 @@ static const struct of_device_id cros_ec_keyb_of_match[] = { > MODULE_DEVICE_TABLE(of, cros_ec_keyb_of_match); > #endif > > +static const SIMPLE_DEV_PM_OPS(cros_ec_keyb_pm_ops, NULL, cros_ec_keyb_resume); > + > static struct platform_driver cros_ec_keyb_driver = { > .probe = cros_ec_keyb_probe, > + .remove = cros_ec_keyb_remove, > .driver = { > .name = "cros-ec-keyb", > .of_match_table = of_match_ptr(cros_ec_keyb_of_match), > + .pm = &cros_ec_keyb_pm_ops, > }, > }; > > -- > 2.9.3 > -- Dmitry -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html