diff --git a/Documentation/ABI/testing/sysfs-platform-wilco-ec b/Documentation/ABI/testing/sysfs-platform-wilco-ec index 8827a734f933..5f60b184a5a5 100644 --- a/Documentation/ABI/testing/sysfs-platform-wilco-ec +++ b/Documentation/ABI/testing/sysfs-platform-wilco-ec @@ -31,6 +31,23 @@ Description: Output will a version string be similar to the example below: 08B6 +What: /sys/bus/platform/devices/GOOG000C\:00/usb_charge +Date: October 2019 +KernelVersion: 5.5 +Description: + Control the USB PowerShare Policy. USB PowerShare is a policy + which affects charging via the special USB PowerShare port + (marked with a small lightning bolt or battery icon) when in + low power states: + - In S0, the port will always provide power. + - In S0ix, if usb_charge is enabled, then power will be + supplied to the port when on AC or if battery is > 50%. + Else no power is supplied. + - In S5, if usb_charge is enabled, then power will be supplied + to the port when on AC. Else no power is supplied. + + Input should be either "0" or "1". + What: /sys/bus/platform/devices/GOOG000C\:00/version Date: May 2019 KernelVersion: 5.3 diff --git a/drivers/iio/accel/cros_ec_accel_legacy.c b/drivers/iio/accel/cros_ec_accel_legacy.c index fcc3f999e482..65f85faf6f31 100644 --- a/drivers/iio/accel/cros_ec_accel_legacy.c +++ b/drivers/iio/accel/cros_ec_accel_legacy.c @@ -163,16 +163,10 @@ static const struct iio_chan_spec cros_ec_accel_legacy_channels[] = { static int cros_ec_accel_legacy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct cros_ec_dev *ec = dev_get_drvdata(dev->parent); struct iio_dev *indio_dev; struct cros_ec_sensors_core_state *state; int ret; - if (!ec || !ec->ec_dev) { - dev_warn(&pdev->dev, "No EC device found.\n"); - return -EINVAL; - } - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state)); if (!indio_dev) return -ENOMEM; diff --git a/drivers/iio/common/cros_ec_sensors/Kconfig b/drivers/iio/common/cros_ec_sensors/Kconfig index cdbb29cfb907..fefad9572790 100644 --- a/drivers/iio/common/cros_ec_sensors/Kconfig +++ b/drivers/iio/common/cros_ec_sensors/Kconfig @@ -4,7 +4,7 @@ # config IIO_CROS_EC_SENSORS_CORE tristate "ChromeOS EC Sensors Core" - depends on SYSFS && CROS_EC + depends on SYSFS && CROS_EC_SENSORHUB select IIO_BUFFER select IIO_TRIGGERED_BUFFER help diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c index a6987726eeb8..7dce04473467 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors.c @@ -222,17 +222,11 @@ static const struct iio_info ec_sensors_info = { static int cros_ec_sensors_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); struct iio_dev *indio_dev; struct cros_ec_sensors_state *state; struct iio_chan_spec *channel; int ret, i; - if (!ec_dev || !ec_dev->ec_dev) { - dev_warn(&pdev->dev, "No CROS EC device found.\n"); - return -EINVAL; - } - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state)); if (!indio_dev) return -ENOMEM; diff --git a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c index d2609e6feda4..81a7f692de2f 100644 --- a/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c +++ b/drivers/iio/common/cros_ec_sensors/cros_ec_sensors_core.c @@ -18,6 +18,7 @@ #include #include #include +#include #include static char *cros_ec_loc[] = { @@ -88,7 +89,8 @@ int cros_ec_sensors_core_init(struct platform_device *pdev, { struct device *dev = &pdev->dev; struct cros_ec_sensors_core_state *state = iio_priv(indio_dev); - struct cros_ec_dev *ec = dev_get_drvdata(pdev->dev.parent); + struct cros_ec_sensorhub *sensor_hub = dev_get_drvdata(dev->parent); + struct cros_ec_dev *ec = sensor_hub->ec; struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev); u32 ver_mask; int ret, i; diff --git a/drivers/iio/light/cros_ec_light_prox.c b/drivers/iio/light/cros_ec_light_prox.c index c5263b563fc1..d85a391e50c5 100644 --- a/drivers/iio/light/cros_ec_light_prox.c +++ b/drivers/iio/light/cros_ec_light_prox.c @@ -169,17 +169,11 @@ static const struct iio_info cros_ec_light_prox_info = { static int cros_ec_light_prox_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); struct iio_dev *indio_dev; struct cros_ec_light_prox_state *state; struct iio_chan_spec *channel; int ret; - if (!ec_dev || !ec_dev->ec_dev) { - dev_warn(dev, "No CROS EC device found.\n"); - return -EINVAL; - } - indio_dev = devm_iio_device_alloc(dev, sizeof(*state)); if (!indio_dev) return -ENOMEM; diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c index 8d4d9786cc74..2b71c5a51f90 100644 --- a/drivers/input/keyboard/cros_ec_keyb.c +++ b/drivers/input/keyboard/cros_ec_keyb.c @@ -226,8 +226,6 @@ static int cros_ec_keyb_work(struct notifier_block *nb, { struct cros_ec_keyb *ckdev = container_of(nb, struct cros_ec_keyb, notifier); - uint8_t mkbp_event_type = ckdev->ec->event_data.event_type & - EC_MKBP_EVENT_TYPE_MASK; u32 val; unsigned int ev_type; @@ -239,7 +237,7 @@ static int cros_ec_keyb_work(struct notifier_block *nb, if (queued_during_suspend && !device_may_wakeup(ckdev->dev)) return NOTIFY_OK; - switch (mkbp_event_type) { + switch (ckdev->ec->event_data.event_type) { case EC_MKBP_EVENT_KEY_MATRIX: pm_wakeup_event(ckdev->dev, 0); @@ -266,7 +264,7 @@ static int cros_ec_keyb_work(struct notifier_block *nb, case EC_MKBP_EVENT_SWITCH: pm_wakeup_event(ckdev->dev, 0); - if (mkbp_event_type == EC_MKBP_EVENT_BUTTON) { + 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; diff --git a/drivers/mfd/cros_ec_dev.c b/drivers/mfd/cros_ec_dev.c index 6e6dfd6c1871..c4b977a5dd96 100644 --- a/drivers/mfd/cros_ec_dev.c +++ b/drivers/mfd/cros_ec_dev.c @@ -78,6 +78,10 @@ static const struct mfd_cell cros_ec_rtc_cells[] = { { .name = "cros-ec-rtc", }, }; +static const struct mfd_cell cros_ec_sensorhub_cells[] = { + { .name = "cros-ec-sensorhub", }, +}; + static const struct mfd_cell cros_usbpd_charger_cells[] = { { .name = "cros-usbpd-charger", }, { .name = "cros-usbpd-logger", }, @@ -112,229 +116,11 @@ static const struct mfd_cell cros_ec_vbc_cells[] = { { .name = "cros-ec-vbc", } }; -static int cros_ec_check_features(struct cros_ec_dev *ec, int feature) -{ - struct cros_ec_command *msg; - int ret; - - if (ec->features[0] == -1U && ec->features[1] == -1U) { - /* features bitmap not read yet */ - msg = kzalloc(sizeof(*msg) + sizeof(ec->features), GFP_KERNEL); - if (!msg) - return -ENOMEM; - - msg->command = EC_CMD_GET_FEATURES + ec->cmd_offset; - msg->insize = sizeof(ec->features); - - ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); - if (ret < 0) { - dev_warn(ec->dev, "cannot get EC features: %d/%d\n", - ret, msg->result); - memset(ec->features, 0, sizeof(ec->features)); - } else { - memcpy(ec->features, msg->data, sizeof(ec->features)); - } - - dev_dbg(ec->dev, "EC features %08x %08x\n", - ec->features[0], ec->features[1]); - - kfree(msg); - } - - return ec->features[feature / 32] & EC_FEATURE_MASK_0(feature); -} - static void cros_ec_class_release(struct device *dev) { kfree(to_cros_ec_dev(dev)); } -static void cros_ec_sensors_register(struct cros_ec_dev *ec) -{ - /* - * Issue a command to get the number of sensor reported. - * Build an array of sensors driver and register them all. - */ - int ret, i, id, sensor_num; - struct mfd_cell *sensor_cells; - struct cros_ec_sensor_platform *sensor_platforms; - int sensor_type[MOTIONSENSE_TYPE_MAX]; - struct ec_params_motion_sense *params; - struct ec_response_motion_sense *resp; - struct cros_ec_command *msg; - - msg = kzalloc(sizeof(struct cros_ec_command) + - max(sizeof(*params), sizeof(*resp)), GFP_KERNEL); - if (msg == NULL) - return; - - msg->version = 2; - msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; - msg->outsize = sizeof(*params); - msg->insize = sizeof(*resp); - - params = (struct ec_params_motion_sense *)msg->data; - params->cmd = MOTIONSENSE_CMD_DUMP; - - ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); - if (ret < 0) { - dev_warn(ec->dev, "cannot get EC sensor information: %d/%d\n", - ret, msg->result); - goto error; - } - - resp = (struct ec_response_motion_sense *)msg->data; - sensor_num = resp->dump.sensor_count; - /* - * Allocate 2 extra sensors if lid angle sensor and/or FIFO are needed. - */ - sensor_cells = kcalloc(sensor_num + 2, sizeof(struct mfd_cell), - GFP_KERNEL); - if (sensor_cells == NULL) - goto error; - - sensor_platforms = kcalloc(sensor_num, - sizeof(struct cros_ec_sensor_platform), - GFP_KERNEL); - if (sensor_platforms == NULL) - goto error_platforms; - - memset(sensor_type, 0, sizeof(sensor_type)); - id = 0; - for (i = 0; i < sensor_num; i++) { - params->cmd = MOTIONSENSE_CMD_INFO; - params->info.sensor_num = i; - ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); - if (ret < 0) { - dev_warn(ec->dev, "no info for EC sensor %d : %d/%d\n", - i, ret, msg->result); - continue; - } - switch (resp->info.type) { - case MOTIONSENSE_TYPE_ACCEL: - sensor_cells[id].name = "cros-ec-accel"; - break; - case MOTIONSENSE_TYPE_BARO: - sensor_cells[id].name = "cros-ec-baro"; - break; - case MOTIONSENSE_TYPE_GYRO: - sensor_cells[id].name = "cros-ec-gyro"; - break; - case MOTIONSENSE_TYPE_MAG: - sensor_cells[id].name = "cros-ec-mag"; - break; - case MOTIONSENSE_TYPE_PROX: - sensor_cells[id].name = "cros-ec-prox"; - break; - case MOTIONSENSE_TYPE_LIGHT: - sensor_cells[id].name = "cros-ec-light"; - break; - case MOTIONSENSE_TYPE_ACTIVITY: - sensor_cells[id].name = "cros-ec-activity"; - break; - default: - dev_warn(ec->dev, "unknown type %d\n", resp->info.type); - continue; - } - sensor_platforms[id].sensor_num = i; - sensor_cells[id].id = sensor_type[resp->info.type]; - sensor_cells[id].platform_data = &sensor_platforms[id]; - sensor_cells[id].pdata_size = - sizeof(struct cros_ec_sensor_platform); - - sensor_type[resp->info.type]++; - id++; - } - - if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) - ec->has_kb_wake_angle = true; - - if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE_FIFO)) { - sensor_cells[id].name = "cros-ec-ring"; - id++; - } - if (cros_ec_check_features(ec, - EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) { - sensor_cells[id].name = "cros-ec-lid-angle"; - id++; - } - - ret = mfd_add_devices(ec->dev, 0, sensor_cells, id, - NULL, 0, NULL); - if (ret) - dev_err(ec->dev, "failed to add EC sensors\n"); - - kfree(sensor_platforms); -error_platforms: - kfree(sensor_cells); -error: - kfree(msg); -} - -static struct cros_ec_sensor_platform sensor_platforms[] = { - { .sensor_num = 0 }, - { .sensor_num = 1 } -}; - -static const struct mfd_cell cros_ec_accel_legacy_cells[] = { - { - .name = "cros-ec-accel-legacy", - .platform_data = &sensor_platforms[0], - .pdata_size = sizeof(struct cros_ec_sensor_platform), - }, - { - .name = "cros-ec-accel-legacy", - .platform_data = &sensor_platforms[1], - .pdata_size = sizeof(struct cros_ec_sensor_platform), - } -}; - -static void cros_ec_accel_legacy_register(struct cros_ec_dev *ec) -{ - struct cros_ec_device *ec_dev = ec->ec_dev; - u8 status; - int ret; - - /* - * ECs that need legacy support are the main EC, directly connected to - * the AP. - */ - if (ec->cmd_offset != 0) - return; - - /* - * Check if EC supports direct memory reads and if EC has - * accelerometers. - */ - if (ec_dev->cmd_readmem) { - ret = ec_dev->cmd_readmem(ec_dev, EC_MEMMAP_ACC_STATUS, 1, - &status); - if (ret < 0) { - dev_warn(ec->dev, "EC direct read error.\n"); - return; - } - - /* Check if EC has accelerometers. */ - if (!(status & EC_MEMMAP_ACC_STATUS_PRESENCE_BIT)) { - dev_info(ec->dev, "EC does not have accelerometers.\n"); - return; - } - } - - /* - * The device may still support accelerometers: - * it would be an older ARM based device that do not suppor the - * EC_CMD_GET_FEATURES command. - * - * Register 2 accelerometers, we will fail in the IIO driver if there - * are no sensors. - */ - ret = mfd_add_hotplug_devices(ec->dev, cros_ec_accel_legacy_cells, - ARRAY_SIZE(cros_ec_accel_legacy_cells)); - if (ret) - dev_err(ec_dev->dev, "failed to add EC sensors\n"); -} - static int ec_device_probe(struct platform_device *pdev) { int retval = -ENOMEM; @@ -390,11 +176,14 @@ static int ec_device_probe(struct platform_device *pdev) goto failed; /* check whether this EC is a sensor hub. */ - if (cros_ec_check_features(ec, EC_FEATURE_MOTION_SENSE)) - cros_ec_sensors_register(ec); - else - /* Workaroud for older EC firmware */ - cros_ec_accel_legacy_register(ec); + if (cros_ec_get_sensor_count(ec) > 0) { + retval = mfd_add_hotplug_devices(ec->dev, + cros_ec_sensorhub_cells, + ARRAY_SIZE(cros_ec_sensorhub_cells)); + if (retval) + dev_err(ec->dev, "failed to add %s subdevice: %d\n", + cros_ec_sensorhub_cells->name, retval); + } /* * The following subdevices can be detected by sending the diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index ee5f08ea57b6..5f57282a28da 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -132,9 +132,9 @@ config CROS_EC_LPC module will be called cros_ec_lpcs. config CROS_EC_PROTO - bool - help - ChromeOS EC communication protocol helpers. + bool + help + ChromeOS EC communication protocol helpers. config CROS_KBD_LED_BACKLIGHT tristate "Backlight LED support for Chrome OS keyboards" @@ -190,6 +190,19 @@ config CROS_EC_DEBUGFS To compile this driver as a module, choose M here: the module will be called cros_ec_debugfs. +config CROS_EC_SENSORHUB + tristate "ChromeOS EC MEMS Sensor Hub" + depends on MFD_CROS_EC_DEV + default MFD_CROS_EC_DEV + help + Allow loading IIO sensors. This driver is loaded by MFD and will in + turn query the EC and register the sensors. + It also spreads the sensor data coming from the EC to the IIO sensor + object. + + To compile this driver as a module, choose M here: the + module will be called cros_ec_sensorhub. + config CROS_EC_SYSFS tristate "ChromeOS EC control and information through sysfs" depends on MFD_CROS_EC_DEV && SYSFS diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 477ec3d1d1c9..aacd5920d8a1 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_chardev.o obj-$(CONFIG_CROS_EC_LIGHTBAR) += cros_ec_lightbar.o obj-$(CONFIG_CROS_EC_VBC) += cros_ec_vbc.o obj-$(CONFIG_CROS_EC_DEBUGFS) += cros_ec_debugfs.o +obj-$(CONFIG_CROS_EC_SENSORHUB) += cros_ec_sensorhub.o obj-$(CONFIG_CROS_EC_SYSFS) += cros_ec_sysfs.o obj-$(CONFIG_CROS_USBPD_LOGGER) += cros_usbpd_logger.o diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c index fd77e6fa74c2..6d6ce86a1408 100644 --- a/drivers/platform/chrome/cros_ec.c +++ b/drivers/platform/chrome/cros_ec.c @@ -31,13 +31,32 @@ static struct cros_ec_platform pd_p = { .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX), }; -static irqreturn_t ec_irq_thread(int irq, void *data) +static irqreturn_t ec_irq_handler(int irq, void *data) { struct cros_ec_device *ec_dev = data; - bool wake_event = true; + + ec_dev->last_event_time = cros_ec_get_time_ns(); + + return IRQ_WAKE_THREAD; +} + +/** + * cros_ec_handle_event() - process and forward pending events on EC + * @ec_dev: Device with events to process. + * + * Call this function in a loop when the kernel is notified that the EC has + * pending events. + * + * Return: true if more events are still pending and this function should be + * called again. + */ +bool cros_ec_handle_event(struct cros_ec_device *ec_dev) +{ + bool wake_event; + bool ec_has_more_events; int ret; - ret = cros_ec_get_next_event(ec_dev, &wake_event); + ret = cros_ec_get_next_event(ec_dev, &wake_event, &ec_has_more_events); /* * Signal only if wake host events or any interrupt if @@ -50,6 +69,20 @@ static irqreturn_t ec_irq_thread(int irq, void *data) if (ret > 0) blocking_notifier_call_chain(&ec_dev->event_notifier, 0, ec_dev); + + return ec_has_more_events; +} +EXPORT_SYMBOL(cros_ec_handle_event); + +static irqreturn_t ec_irq_thread(int irq, void *data) +{ + struct cros_ec_device *ec_dev = data; + bool ec_has_more_events; + + do { + ec_has_more_events = cros_ec_handle_event(ec_dev); + } while (ec_has_more_events); + return IRQ_HANDLED; } @@ -104,6 +137,15 @@ static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event) return ret; } +/** + * cros_ec_register() - Register a new ChromeOS EC, using the provided info. + * @ec_dev: Device to register. + * + * Before calling this, allocate a pointer to a new device and then fill + * in all the fields up to the --private-- marker. + * + * Return: 0 on success or negative error code. + */ int cros_ec_register(struct cros_ec_device *ec_dev) { struct device *dev = ec_dev->dev; @@ -131,10 +173,12 @@ int cros_ec_register(struct cros_ec_device *ec_dev) return err; } - if (ec_dev->irq) { - err = devm_request_threaded_irq(dev, ec_dev->irq, NULL, - ec_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, - "chromeos-ec", ec_dev); + if (ec_dev->irq > 0) { + err = devm_request_threaded_irq(dev, ec_dev->irq, + ec_irq_handler, + 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); @@ -198,6 +242,14 @@ int cros_ec_register(struct cros_ec_device *ec_dev) } EXPORT_SYMBOL(cros_ec_register); +/** + * cros_ec_unregister() - Remove a ChromeOS EC. + * @ec_dev: Device to unregister. + * + * Call this to deregister a ChromeOS EC, then clean up any private data. + * + * Return: 0 on success or negative error code. + */ int cros_ec_unregister(struct cros_ec_device *ec_dev) { if (ec_dev->pd) @@ -209,6 +261,14 @@ int cros_ec_unregister(struct cros_ec_device *ec_dev) EXPORT_SYMBOL(cros_ec_unregister); #ifdef CONFIG_PM_SLEEP +/** + * cros_ec_suspend() - Handle a suspend operation for the ChromeOS EC device. + * @ec_dev: Device to suspend. + * + * This can be called by drivers to handle a suspend event. + * + * Return: 0 on success or negative error code. + */ int cros_ec_suspend(struct cros_ec_device *ec_dev) { struct device *dev = ec_dev->dev; @@ -238,11 +298,19 @@ EXPORT_SYMBOL(cros_ec_suspend); static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev) { while (ec_dev->mkbp_event_supported && - cros_ec_get_next_event(ec_dev, NULL) > 0) + cros_ec_get_next_event(ec_dev, NULL, NULL) > 0) blocking_notifier_call_chain(&ec_dev->event_notifier, 1, ec_dev); } +/** + * cros_ec_resume() - Handle a resume operation for the ChromeOS EC device. + * @ec_dev: Device to resume. + * + * This can be called by drivers to handle a resume event. + * + * Return: 0 on success or negative error code. + */ int cros_ec_resume(struct cros_ec_device *ec_dev) { int ret; diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c index 25ca2c894b4d..e5996821d08b 100644 --- a/drivers/platform/chrome/cros_ec_ishtp.c +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -136,11 +136,11 @@ static void ish_evt_handler(struct work_struct *work) struct ishtp_cl_data *client_data = container_of(work, struct ishtp_cl_data, work_ec_evt); struct cros_ec_device *ec_dev = client_data->ec_dev; + bool ec_has_more_events; - if (cros_ec_get_next_event(ec_dev, NULL) > 0) { - blocking_notifier_call_chain(&ec_dev->event_notifier, - 0, ec_dev); - } + do { + ec_has_more_events = cros_ec_handle_event(ec_dev); + } while (ec_has_more_events); } /** @@ -200,13 +200,14 @@ static int ish_send(struct ishtp_cl_data *client_data, * process_recv() - Received and parse incoming packet * @cros_ish_cl: Client instance to get stats * @rb_in_proc: Host interface message buffer + * @timestamp: Timestamp of when parent callback started * * Parse the incoming packet. If it is a response packet then it will * update per instance flags and wake up the caller waiting to for the * response. If it is an event packet then it will schedule event work. */ static void process_recv(struct ishtp_cl *cros_ish_cl, - struct ishtp_cl_rb *rb_in_proc) + struct ishtp_cl_rb *rb_in_proc, ktime_t timestamp) { size_t data_len = rb_in_proc->buf_idx; struct ishtp_cl_data *client_data = @@ -295,6 +296,11 @@ error_wake_up: break; case CROS_MKBP_EVENT: + /* + * Set timestamp from beginning of function since we actually + * got an incoming MKBP event + */ + client_data->ec_dev->last_event_time = timestamp; /* The event system doesn't send any data in buffer */ schedule_work(&client_data->work_ec_evt); @@ -322,10 +328,17 @@ static void ish_event_cb(struct ishtp_cl_device *cl_device) { struct ishtp_cl_rb *rb_in_proc; struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + ktime_t timestamp; + + /* + * Take timestamp as close to hardware interrupt as possible for sensor + * timestamps. + */ + timestamp = cros_ec_get_time_ns(); while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) { /* Decide what to do with received data */ - process_recv(cros_ish_cl, rb_in_proc); + process_recv(cros_ish_cl, rb_in_proc, timestamp); } } diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 7d10d909435f..dccf479c6625 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -312,11 +312,20 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, static void cros_ec_lpc_acpi_notify(acpi_handle device, u32 value, void *data) { struct cros_ec_device *ec_dev = data; + bool ec_has_more_events; + int ret; - if (ec_dev->mkbp_event_supported && - cros_ec_get_next_event(ec_dev, NULL) > 0) - blocking_notifier_call_chain(&ec_dev->event_notifier, 0, - ec_dev); + ec_dev->last_event_time = cros_ec_get_time_ns(); + + if (ec_dev->mkbp_event_supported) + do { + ret = cros_ec_get_next_event(ec_dev, NULL, + &ec_has_more_events); + if (ret > 0) + blocking_notifier_call_chain( + &ec_dev->event_notifier, 0, + ec_dev); + } while (ec_has_more_events); if (value == ACPI_NOTIFY_DEVICE_WAKE) pm_system_wakeup(); diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index f659f96bda12..da1b1c450433 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -117,6 +117,17 @@ static int send_command(struct cros_ec_device *ec_dev, return ret; } +/** + * cros_ec_prepare_tx() - Prepare an outgoing message in the output buffer. + * @ec_dev: Device to register. + * @msg: Message to write. + * + * 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. + * + * Return: 0 on success or negative error code. + */ int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) { @@ -141,6 +152,16 @@ int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, } EXPORT_SYMBOL(cros_ec_prepare_tx); +/** + * cros_ec_check_result() - Check ec_msg->result. + * @ec_dev: EC device. + * @msg: Message to check. + * + * This is used by ChromeOS EC drivers to check the ec_msg->result for + * errors and to warn about them. + * + * Return: 0 on success or negative error code. + */ int cros_ec_check_result(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) { @@ -326,6 +347,13 @@ static int cros_ec_get_host_command_version_mask(struct cros_ec_device *ec_dev, return ret; } +/** + * cros_ec_query_all() - Query the protocol version supported by the + * ChromeOS EC. + * @ec_dev: Device to register. + * + * Return: 0 on success or negative error code. + */ int cros_ec_query_all(struct cros_ec_device *ec_dev) { struct device *dev = ec_dev->dev; @@ -428,7 +456,10 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev) if (ret < 0 || ver_mask == 0) ec_dev->mkbp_event_supported = 0; else - ec_dev->mkbp_event_supported = 1; + ec_dev->mkbp_event_supported = fls(ver_mask); + + dev_dbg(ec_dev->dev, "MKBP support version %u\n", + ec_dev->mkbp_event_supported - 1); /* Probe if host sleep v1 is supported for S0ix failure detection. */ ret = cros_ec_get_host_command_version_mask(ec_dev, @@ -453,6 +484,16 @@ exit: } EXPORT_SYMBOL(cros_ec_query_all); +/** + * cros_ec_cmd_xfer() - Send a command to the ChromeOS EC. + * @ec_dev: EC device. + * @msg: Message to write. + * + * Call this to send a command to the ChromeOS EC. This should be used + * instead of calling the EC's cmd_xfer() callback directly. + * + * Return: 0 on success or negative error code. + */ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) { @@ -500,6 +541,18 @@ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, } EXPORT_SYMBOL(cros_ec_cmd_xfer); +/** + * cros_ec_cmd_xfer_status() - Send a command to the ChromeOS EC. + * @ec_dev: EC device. + * @msg: Message to write. + * + * This function is identical to cros_ec_cmd_xfer, except it returns success + * status only if both the command was transmitted successfully and the EC + * replied with success status. It's not necessary to check msg->result when + * using this function. + * + * Return: The number of bytes transferred on success or negative error code. + */ int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) { @@ -519,6 +572,7 @@ EXPORT_SYMBOL(cros_ec_cmd_xfer_status); static int get_next_event_xfer(struct cros_ec_device *ec_dev, struct cros_ec_command *msg, + struct ec_response_get_next_event_v1 *event, int version, uint32_t size) { int ret; @@ -531,7 +585,7 @@ static int get_next_event_xfer(struct cros_ec_device *ec_dev, ret = cros_ec_cmd_xfer(ec_dev, msg); if (ret > 0) { ec_dev->event_size = ret - 1; - memcpy(&ec_dev->event_data, msg->data, ret); + ec_dev->event_data = *event; } return ret; @@ -539,30 +593,26 @@ static int get_next_event_xfer(struct cros_ec_device *ec_dev, 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; - static int cmd_version = 1; - int ret; + struct { + struct cros_ec_command msg; + struct ec_response_get_next_event_v1 event; + } __packed buf; + struct cros_ec_command *msg = &buf.msg; + struct ec_response_get_next_event_v1 *event = &buf.event; + const int cmd_version = ec_dev->mkbp_event_supported - 1; + memset(msg, 0, sizeof(*msg)); if (ec_dev->suspended) { dev_dbg(ec_dev->dev, "Device suspended.\n"); return -EHOSTDOWN; } - if (cmd_version == 1) { - ret = get_next_event_xfer(ec_dev, msg, cmd_version, - sizeof(struct ec_response_get_next_event_v1)); - if (ret < 0 || msg->result != EC_RES_INVALID_VERSION) - return ret; - - /* Fallback to version 0 for future send attempts */ - cmd_version = 0; - } - - ret = get_next_event_xfer(ec_dev, msg, cmd_version, + if (cmd_version == 0) + return get_next_event_xfer(ec_dev, msg, event, 0, sizeof(struct ec_response_get_next_event)); - return ret; + return get_next_event_xfer(ec_dev, msg, event, cmd_version, + sizeof(struct ec_response_get_next_event_v1)); } static int get_keyboard_state_event(struct cros_ec_device *ec_dev) @@ -584,27 +634,60 @@ static int get_keyboard_state_event(struct cros_ec_device *ec_dev) return ec_dev->event_size; } -int cros_ec_get_next_event(struct cros_ec_device *ec_dev, bool *wake_event) +/** + * cros_ec_get_next_event() - Fetch next event from the ChromeOS EC. + * @ec_dev: Device to fetch event from. + * @wake_event: Pointer to a bool set to true upon return if the event might be + * treated as a wake event. Ignored if null. + * @has_more_events: Pointer to bool set to true if more than one event is + * pending. + * Some EC will set this flag to indicate cros_ec_get_next_event() + * can be called multiple times in a row. + * It is an optimization to prevent issuing a EC command for + * nothing or wait for another interrupt from the EC to process + * the next message. + * Ignored if null. + * + * Return: negative error code on errors; 0 for no data; or else number of + * bytes received (i.e., an event was retrieved successfully). Event types are + * written out to @ec_dev->event_data.event_type on success. + */ +int cros_ec_get_next_event(struct cros_ec_device *ec_dev, + bool *wake_event, + bool *has_more_events) { u8 event_type; u32 host_event; int ret; - if (!ec_dev->mkbp_event_supported) { - ret = get_keyboard_state_event(ec_dev); - if (ret <= 0) - return ret; + /* + * Default value for wake_event. + * Wake up on keyboard event, wake up for spurious interrupt or link + * error to the EC. + */ + if (wake_event) + *wake_event = true; - if (wake_event) - *wake_event = true; + /* + * Default value for has_more_events. + * EC will raise another interrupt if AP does not process all events + * anyway. + */ + if (has_more_events) + *has_more_events = false; - return ret; - } + if (!ec_dev->mkbp_event_supported) + return get_keyboard_state_event(ec_dev); ret = get_next_event(ec_dev); if (ret <= 0) return ret; + if (has_more_events) + *has_more_events = ec_dev->event_data.event_type & + EC_MKBP_HAS_MORE_EVENTS; + ec_dev->event_data.event_type &= EC_MKBP_EVENT_TYPE_MASK; + if (wake_event) { event_type = ec_dev->event_data.event_type; host_event = cros_ec_get_host_event(ec_dev); @@ -619,15 +702,22 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev, bool *wake_event) else if (host_event && !(host_event & ec_dev->host_event_wake_mask)) *wake_event = false; - /* Consider all other events as wake events. */ - else - *wake_event = true; } return ret; } EXPORT_SYMBOL(cros_ec_get_next_event); +/** + * cros_ec_get_host_event() - Return a mask of event set by the ChromeOS EC. + * @ec_dev: Device to fetch event from. + * + * When MKBP is supported, when the EC raises an interrupt, we collect the + * events raised and call the functions in the ec notifier. This function + * is a helper to know which events are raised. + * + * Return: 0 on error or non-zero bitmask of one or more EC_HOST_EVENT_*. + */ u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev) { u32 host_event; @@ -647,3 +737,120 @@ u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev) return host_event; } EXPORT_SYMBOL(cros_ec_get_host_event); + +/** + * cros_ec_check_features() - Test for the presence of EC features + * + * @ec: EC device, does not have to be connected directly to the AP, + * can be daisy chained through another device. + * @feature: One of ec_feature_code bit. + * + * Call this function to test whether the ChromeOS EC supports a feature. + * + * Return: 1 if supported, 0 if not + */ +int cros_ec_check_features(struct cros_ec_dev *ec, int feature) +{ + struct cros_ec_command *msg; + int ret; + + if (ec->features[0] == -1U && ec->features[1] == -1U) { + /* features bitmap not read yet */ + msg = kzalloc(sizeof(*msg) + sizeof(ec->features), GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->command = EC_CMD_GET_FEATURES + ec->cmd_offset; + msg->insize = sizeof(ec->features); + + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); + if (ret < 0) { + dev_warn(ec->dev, "cannot get EC features: %d/%d\n", + ret, msg->result); + memset(ec->features, 0, sizeof(ec->features)); + } else { + memcpy(ec->features, msg->data, sizeof(ec->features)); + } + + dev_dbg(ec->dev, "EC features %08x %08x\n", + ec->features[0], ec->features[1]); + + kfree(msg); + } + + return ec->features[feature / 32] & EC_FEATURE_MASK_0(feature); +} +EXPORT_SYMBOL_GPL(cros_ec_check_features); + +/** + * cros_ec_get_sensor_count() - Return the number of MEMS sensors supported. + * + * @ec: EC device, does not have to be connected directly to the AP, + * can be daisy chained through another device. + * Return: < 0 in case of error. + */ +int cros_ec_get_sensor_count(struct cros_ec_dev *ec) +{ + /* + * Issue a command to get the number of sensor reported. + * If not supported, check for legacy mode. + */ + int ret, sensor_count; + struct ec_params_motion_sense *params; + struct ec_response_motion_sense *resp; + struct cros_ec_command *msg; + struct cros_ec_device *ec_dev = ec->ec_dev; + u8 status; + + msg = kzalloc(sizeof(*msg) + max(sizeof(*params), sizeof(*resp)), + GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->version = 1; + msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; + msg->outsize = sizeof(*params); + msg->insize = sizeof(*resp); + + params = (struct ec_params_motion_sense *)msg->data; + params->cmd = MOTIONSENSE_CMD_DUMP; + + ret = cros_ec_cmd_xfer(ec->ec_dev, msg); + if (ret < 0) { + sensor_count = ret; + } else if (msg->result != EC_RES_SUCCESS) { + sensor_count = -EPROTO; + } else { + resp = (struct ec_response_motion_sense *)msg->data; + sensor_count = resp->dump.sensor_count; + } + kfree(msg); + + /* + * Check legacy mode: Let's find out if sensors are accessible + * via LPC interface. + */ + if (sensor_count == -EPROTO && + ec->cmd_offset == 0 && + ec_dev->cmd_readmem) { + ret = ec_dev->cmd_readmem(ec_dev, EC_MEMMAP_ACC_STATUS, + 1, &status); + if (ret >= 0 && + (status & EC_MEMMAP_ACC_STATUS_PRESENCE_BIT)) { + /* + * We have 2 sensors, one in the lid, one in the base. + */ + sensor_count = 2; + } else { + /* + * EC uses LPC interface and no sensors are presented. + */ + sensor_count = 0; + } + } else if (sensor_count == -EPROTO) { + /* EC responded, but does not understand DUMP command. */ + sensor_count = 0; + } + return sensor_count; +} +EXPORT_SYMBOL_GPL(cros_ec_get_sensor_count); diff --git a/drivers/platform/chrome/cros_ec_rpmsg.c b/drivers/platform/chrome/cros_ec_rpmsg.c index 0c3738c3244d..bd068afe43b5 100644 --- a/drivers/platform/chrome/cros_ec_rpmsg.c +++ b/drivers/platform/chrome/cros_ec_rpmsg.c @@ -143,22 +143,11 @@ cros_ec_rpmsg_host_event_function(struct work_struct *host_event_work) struct cros_ec_rpmsg, host_event_work); struct cros_ec_device *ec_dev = dev_get_drvdata(&ec_rpmsg->rpdev->dev); - bool wake_event = true; - int ret; + bool ec_has_more_events; - ret = cros_ec_get_next_event(ec_dev, &wake_event); - - /* - * Signal only if wake host events or any interrupt if - * cros_ec_get_next_event() returned an error (default value for - * wake_event is true) - */ - if (wake_event && device_may_wakeup(ec_dev->dev)) - pm_wakeup_event(ec_dev->dev, 0); - - if (ret > 0) - blocking_notifier_call_chain(&ec_dev->event_notifier, - 0, ec_dev); + do { + ec_has_more_events = cros_ec_handle_event(ec_dev); + } while (ec_has_more_events); } static int cros_ec_rpmsg_callback(struct rpmsg_device *rpdev, void *data, diff --git a/drivers/platform/chrome/cros_ec_sensorhub.c b/drivers/platform/chrome/cros_ec_sensorhub.c new file mode 100644 index 000000000000..04d8879689e9 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_sensorhub.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sensor HUB driver that discovers sensors behind a ChromeOS Embedded + * Controller. + * + * Copyright 2019 Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "cros-ec-sensorhub" + +static void cros_ec_sensorhub_free_sensor(void *arg) +{ + struct platform_device *pdev = arg; + + platform_device_unregister(pdev); +} + +static int cros_ec_sensorhub_allocate_sensor(struct device *parent, + char *sensor_name, + int sensor_num) +{ + struct cros_ec_sensor_platform sensor_platforms = { + .sensor_num = sensor_num, + }; + struct platform_device *pdev; + + pdev = platform_device_register_data(parent, sensor_name, + PLATFORM_DEVID_AUTO, + &sensor_platforms, + sizeof(sensor_platforms)); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); + + return devm_add_action_or_reset(parent, + cros_ec_sensorhub_free_sensor, + pdev); +} + +static int cros_ec_sensorhub_register(struct device *dev, + struct cros_ec_sensorhub *sensorhub) +{ + int sensor_type[MOTIONSENSE_TYPE_MAX] = { 0 }; + struct cros_ec_dev *ec = sensorhub->ec; + struct ec_params_motion_sense *params; + struct ec_response_motion_sense *resp; + struct cros_ec_command *msg; + int ret, i, sensor_num; + char *name; + + sensor_num = cros_ec_get_sensor_count(ec); + if (sensor_num < 0) { + dev_err(dev, + "Unable to retrieve sensor information (err:%d)\n", + sensor_num); + return sensor_num; + } + + if (sensor_num == 0) { + dev_err(dev, "Zero sensors reported.\n"); + return -EINVAL; + } + + /* Prepare a message to send INFO command to each sensor. */ + msg = kzalloc(sizeof(*msg) + max(sizeof(*params), sizeof(*resp)), + GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->version = 1; + msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; + msg->outsize = sizeof(*params); + msg->insize = sizeof(*resp); + params = (struct ec_params_motion_sense *)msg->data; + resp = (struct ec_response_motion_sense *)msg->data; + + for (i = 0; i < sensor_num; i++) { + params->cmd = MOTIONSENSE_CMD_INFO; + params->info.sensor_num = i; + + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); + if (ret < 0) { + dev_warn(dev, "no info for EC sensor %d : %d/%d\n", + i, ret, msg->result); + continue; + } + + switch (resp->info.type) { + case MOTIONSENSE_TYPE_ACCEL: + name = "cros-ec-accel"; + break; + case MOTIONSENSE_TYPE_BARO: + name = "cros-ec-baro"; + break; + case MOTIONSENSE_TYPE_GYRO: + name = "cros-ec-gyro"; + break; + case MOTIONSENSE_TYPE_MAG: + name = "cros-ec-mag"; + break; + case MOTIONSENSE_TYPE_PROX: + name = "cros-ec-prox"; + break; + case MOTIONSENSE_TYPE_LIGHT: + name = "cros-ec-light"; + break; + case MOTIONSENSE_TYPE_ACTIVITY: + name = "cros-ec-activity"; + break; + default: + dev_warn(dev, "unknown type %d\n", resp->info.type); + continue; + } + + ret = cros_ec_sensorhub_allocate_sensor(dev, name, i); + if (ret) + goto error; + + sensor_type[resp->info.type]++; + } + + if (sensor_type[MOTIONSENSE_TYPE_ACCEL] >= 2) + ec->has_kb_wake_angle = true; + + if (cros_ec_check_features(ec, + EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS)) { + ret = cros_ec_sensorhub_allocate_sensor(dev, + "cros-ec-lid-angle", + 0); + if (ret) + goto error; + } + + kfree(msg); + return 0; + +error: + kfree(msg); + return ret; +} + +static int cros_ec_sensorhub_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_sensorhub *data; + int ret; + int i; + + data = devm_kzalloc(dev, sizeof(struct cros_ec_sensorhub), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->ec = dev_get_drvdata(dev->parent); + dev_set_drvdata(dev, data); + + /* Check whether this EC is a sensor hub. */ + if (cros_ec_check_features(data->ec, EC_FEATURE_MOTION_SENSE)) { + ret = cros_ec_sensorhub_register(dev, data); + if (ret) + return ret; + } else { + /* + * If the device has sensors but does not claim to + * be a sensor hub, we are in legacy mode. + */ + for (i = 0; i < 2; i++) { + ret = cros_ec_sensorhub_allocate_sensor(dev, + "cros-ec-accel-legacy", i); + if (ret) + return ret; + } + } + + return 0; +} + +static struct platform_driver cros_ec_sensorhub_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_ec_sensorhub_probe, +}; + +module_platform_driver(cros_ec_sensorhub_driver); + +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_AUTHOR("Gwendal Grignou "); +MODULE_DESCRIPTION("ChromeOS EC MEMS Sensor Hub Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/chrome/cros_usbpd_logger.c b/drivers/platform/chrome/cros_usbpd_logger.c index 2430e8b82810..374cdd1e868a 100644 --- a/drivers/platform/chrome/cros_usbpd_logger.c +++ b/drivers/platform/chrome/cros_usbpd_logger.c @@ -224,6 +224,7 @@ static int cros_usbpd_logger_remove(struct platform_device *pd) struct logger_data *logger = platform_get_drvdata(pd); cancel_delayed_work_sync(&logger->log_work); + destroy_workqueue(logger->log_workqueue); return 0; } diff --git a/drivers/platform/chrome/wilco_ec/Kconfig b/drivers/platform/chrome/wilco_ec/Kconfig index 89007b0bc743..365f30e116ee 100644 --- a/drivers/platform/chrome/wilco_ec/Kconfig +++ b/drivers/platform/chrome/wilco_ec/Kconfig @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only config WILCO_EC tristate "ChromeOS Wilco Embedded Controller" - depends on ACPI && X86 && CROS_EC_LPC + depends on ACPI && X86 && CROS_EC_LPC && LEDS_CLASS help If you say Y here, you get support for talking to the ChromeOS Wilco EC over an eSPI bus. This uses a simple byte-level protocol diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile index bc817164596e..ecb3145cab18 100644 --- a/drivers/platform/chrome/wilco_ec/Makefile +++ b/drivers/platform/chrome/wilco_ec/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 -wilco_ec-objs := core.o mailbox.o properties.o sysfs.o +wilco_ec-objs := core.o keyboard_leds.o mailbox.o \ + properties.o sysfs.o obj-$(CONFIG_WILCO_EC) += wilco_ec.o wilco_ec_debugfs-objs := debugfs.o obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c index 3724bf4b77c6..5210c357feef 100644 --- a/drivers/platform/chrome/wilco_ec/core.c +++ b/drivers/platform/chrome/wilco_ec/core.c @@ -5,10 +5,6 @@ * Copyright 2018 Google LLC * * This is the entry point for the drivers that control the Wilco EC. - * This driver is responsible for several tasks: - * - Initialize the register interface that is used by wilco_ec_mailbox() - * - Create a platform device which is picked up by the debugfs driver - * - Create a platform device which is picked up by the RTC driver */ #include @@ -87,12 +83,31 @@ static int wilco_ec_probe(struct platform_device *pdev) goto unregister_debugfs; } + /* Set up the keyboard backlight LEDs. */ + ret = wilco_keyboard_leds_init(ec); + if (ret < 0) { + dev_err(dev, + "Failed to initialize keyboard LEDs: %d\n", + ret); + goto unregister_rtc; + } + ret = wilco_ec_add_sysfs(ec); if (ret < 0) { dev_err(dev, "Failed to create sysfs entries: %d", ret); goto unregister_rtc; } + /* Register child device to be found by charger config driver. */ + ec->charger_pdev = platform_device_register_data(dev, "wilco-charger", + PLATFORM_DEVID_AUTO, + NULL, 0); + if (IS_ERR(ec->charger_pdev)) { + dev_err(dev, "Failed to create charger platform device\n"); + ret = PTR_ERR(ec->charger_pdev); + goto remove_sysfs; + } + /* Register child device that will be found by the telemetry driver. */ ec->telem_pdev = platform_device_register_data(dev, "wilco_telem", PLATFORM_DEVID_AUTO, @@ -100,11 +115,13 @@ static int wilco_ec_probe(struct platform_device *pdev) if (IS_ERR(ec->telem_pdev)) { dev_err(dev, "Failed to create telemetry platform device\n"); ret = PTR_ERR(ec->telem_pdev); - goto remove_sysfs; + goto unregister_charge_config; } return 0; +unregister_charge_config: + platform_device_unregister(ec->charger_pdev); remove_sysfs: wilco_ec_remove_sysfs(ec); unregister_rtc: @@ -120,6 +137,7 @@ static int wilco_ec_remove(struct platform_device *pdev) { struct wilco_ec_device *ec = platform_get_drvdata(pdev); + platform_device_unregister(ec->charger_pdev); wilco_ec_remove_sysfs(ec); platform_device_unregister(ec->telem_pdev); platform_device_unregister(ec->rtc_pdev); diff --git a/drivers/platform/chrome/wilco_ec/debugfs.c b/drivers/platform/chrome/wilco_ec/debugfs.c index 8d65a1e2f1a3..df5a5f6c3ec6 100644 --- a/drivers/platform/chrome/wilco_ec/debugfs.c +++ b/drivers/platform/chrome/wilco_ec/debugfs.c @@ -160,29 +160,29 @@ static const struct file_operations fops_raw = { #define CMD_KB_CHROME 0x88 #define SUB_CMD_H1_GPIO 0x0A +#define SUB_CMD_TEST_EVENT 0x0B -struct h1_gpio_status_request { +struct ec_request { u8 cmd; /* Always CMD_KB_CHROME */ u8 reserved; - u8 sub_cmd; /* Always SUB_CMD_H1_GPIO */ + u8 sub_cmd; } __packed; -struct hi_gpio_status_response { +struct ec_response { u8 status; /* 0 if allowed */ - u8 val; /* BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL */ + u8 val; } __packed; -static int h1_gpio_get(void *arg, u64 *val) +static int send_ec_cmd(struct wilco_ec_device *ec, u8 sub_cmd, u8 *out_val) { - struct wilco_ec_device *ec = arg; - struct h1_gpio_status_request rq; - struct hi_gpio_status_response rs; + struct ec_request rq; + struct ec_response rs; struct wilco_ec_message msg; int ret; memset(&rq, 0, sizeof(rq)); rq.cmd = CMD_KB_CHROME; - rq.sub_cmd = SUB_CMD_H1_GPIO; + rq.sub_cmd = sub_cmd; memset(&msg, 0, sizeof(msg)); msg.type = WILCO_EC_MSG_LEGACY; @@ -196,13 +196,38 @@ static int h1_gpio_get(void *arg, u64 *val) if (rs.status) return -EIO; - *val = rs.val; + *out_val = rs.val; return 0; } +/** + * h1_gpio_get() - Gets h1 gpio status. + * @arg: The wilco EC device. + * @val: BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL + */ +static int h1_gpio_get(void *arg, u64 *val) +{ + return send_ec_cmd(arg, SUB_CMD_H1_GPIO, (u8 *)val); +} + DEFINE_DEBUGFS_ATTRIBUTE(fops_h1_gpio, h1_gpio_get, NULL, "0x%02llx\n"); +/** + * test_event_set() - Sends command to EC to cause an EC test event. + * @arg: The wilco EC device. + * @val: unused. + */ +static int test_event_set(void *arg, u64 val) +{ + u8 ret; + + return send_ec_cmd(arg, SUB_CMD_TEST_EVENT, &ret); +} + +/* Format is unused since it is only required for get method which is NULL */ +DEFINE_DEBUGFS_ATTRIBUTE(fops_test_event, NULL, test_event_set, "%llu\n"); + /** * wilco_ec_debugfs_probe() - Create the debugfs node * @pdev: The platform device, probably created in core.c @@ -226,6 +251,8 @@ static int wilco_ec_debugfs_probe(struct platform_device *pdev) debugfs_create_file("raw", 0644, debug_info->dir, NULL, &fops_raw); debugfs_create_file("h1_gpio", 0444, debug_info->dir, ec, &fops_h1_gpio); + debugfs_create_file("test_event", 0200, debug_info->dir, ec, + &fops_test_event); return 0; } diff --git a/drivers/platform/chrome/wilco_ec/keyboard_leds.c b/drivers/platform/chrome/wilco_ec/keyboard_leds.c new file mode 100644 index 000000000000..bb0edf51dfda --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/keyboard_leds.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Keyboard backlight LED driver for the Wilco Embedded Controller + * + * Copyright 2019 Google LLC + * + * Since the EC will never change the backlight level of its own accord, + * we don't need to implement a brightness_get() method. + */ + +#include +#include +#include +#include +#include + +#define WILCO_EC_COMMAND_KBBL 0x75 +#define WILCO_KBBL_MODE_FLAG_PWM BIT(1) /* Set brightness by percent. */ +#define WILCO_KBBL_DEFAULT_BRIGHTNESS 0 + +struct wilco_keyboard_leds { + struct wilco_ec_device *ec; + struct led_classdev keyboard; +}; + +enum wilco_kbbl_subcommand { + WILCO_KBBL_SUBCMD_GET_FEATURES = 0x00, + WILCO_KBBL_SUBCMD_GET_STATE = 0x01, + WILCO_KBBL_SUBCMD_SET_STATE = 0x02, +}; + +/** + * struct wilco_keyboard_leds_msg - Message to/from EC for keyboard LED control. + * @command: Always WILCO_EC_COMMAND_KBBL. + * @status: Set by EC to 0 on success, 0xFF on failure. + * @subcmd: One of enum wilco_kbbl_subcommand. + * @reserved3: Should be 0. + * @mode: Bit flags for used mode, we want to use WILCO_KBBL_MODE_FLAG_PWM. + * @reserved5to8: Should be 0. + * @percent: Brightness in 0-100. Only meaningful in PWM mode. + * @reserved10to15: Should be 0. + */ +struct wilco_keyboard_leds_msg { + u8 command; + u8 status; + u8 subcmd; + u8 reserved3; + u8 mode; + u8 reserved5to8[4]; + u8 percent; + u8 reserved10to15[6]; +} __packed; + +/* Send a request, get a response, and check that the response is good. */ +static int send_kbbl_msg(struct wilco_ec_device *ec, + struct wilco_keyboard_leds_msg *request, + struct wilco_keyboard_leds_msg *response) +{ + struct wilco_ec_message msg; + int ret; + + memset(&msg, 0, sizeof(msg)); + msg.type = WILCO_EC_MSG_LEGACY; + msg.request_data = request; + msg.request_size = sizeof(*request); + msg.response_data = response; + msg.response_size = sizeof(*response); + + ret = wilco_ec_mailbox(ec, &msg); + if (ret < 0) { + dev_err(ec->dev, + "Failed sending keyboard LEDs command: %d", ret); + return ret; + } + + if (response->status) { + dev_err(ec->dev, + "EC reported failure sending keyboard LEDs command: %d", + response->status); + return -EIO; + } + + return 0; +} + +static int set_kbbl(struct wilco_ec_device *ec, enum led_brightness brightness) +{ + struct wilco_keyboard_leds_msg request; + struct wilco_keyboard_leds_msg response; + + memset(&request, 0, sizeof(request)); + request.command = WILCO_EC_COMMAND_KBBL; + request.subcmd = WILCO_KBBL_SUBCMD_SET_STATE; + request.mode = WILCO_KBBL_MODE_FLAG_PWM; + request.percent = brightness; + + return send_kbbl_msg(ec, &request, &response); +} + +static int kbbl_exist(struct wilco_ec_device *ec, bool *exists) +{ + struct wilco_keyboard_leds_msg request; + struct wilco_keyboard_leds_msg response; + int ret; + + memset(&request, 0, sizeof(request)); + request.command = WILCO_EC_COMMAND_KBBL; + request.subcmd = WILCO_KBBL_SUBCMD_GET_FEATURES; + + ret = send_kbbl_msg(ec, &request, &response); + if (ret < 0) + return ret; + + *exists = response.status != 0xFF; + + return 0; +} + +/** + * kbbl_init() - Initialize the state of the keyboard backlight. + * @ec: EC device to talk to. + * + * Gets the current brightness, ensuring that the BIOS already initialized the + * backlight to PWM mode. If not in PWM mode, then the current brightness is + * meaningless, so set the brightness to WILCO_KBBL_DEFAULT_BRIGHTNESS. + * + * Return: Final brightness of the keyboard, or negative error code on failure. + */ +static int kbbl_init(struct wilco_ec_device *ec) +{ + struct wilco_keyboard_leds_msg request; + struct wilco_keyboard_leds_msg response; + int ret; + + memset(&request, 0, sizeof(request)); + request.command = WILCO_EC_COMMAND_KBBL; + request.subcmd = WILCO_KBBL_SUBCMD_GET_STATE; + + ret = send_kbbl_msg(ec, &request, &response); + if (ret < 0) + return ret; + + if (response.mode & WILCO_KBBL_MODE_FLAG_PWM) + return response.percent; + + ret = set_kbbl(ec, WILCO_KBBL_DEFAULT_BRIGHTNESS); + if (ret < 0) + return ret; + + return WILCO_KBBL_DEFAULT_BRIGHTNESS; +} + +static int wilco_keyboard_leds_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct wilco_keyboard_leds *wkl = + container_of(cdev, struct wilco_keyboard_leds, keyboard); + return set_kbbl(wkl->ec, brightness); +} + +int wilco_keyboard_leds_init(struct wilco_ec_device *ec) +{ + struct wilco_keyboard_leds *wkl; + bool leds_exist; + int ret; + + ret = kbbl_exist(ec, &leds_exist); + if (ret < 0) { + dev_err(ec->dev, + "Failed checking keyboard LEDs support: %d", ret); + return ret; + } + if (!leds_exist) + return 0; + + wkl = devm_kzalloc(ec->dev, sizeof(*wkl), GFP_KERNEL); + if (!wkl) + return -ENOMEM; + + wkl->ec = ec; + wkl->keyboard.name = "platform::kbd_backlight"; + wkl->keyboard.max_brightness = 100; + wkl->keyboard.flags = LED_CORE_SUSPENDRESUME; + wkl->keyboard.brightness_set_blocking = wilco_keyboard_leds_set; + ret = kbbl_init(ec); + if (ret < 0) + return ret; + wkl->keyboard.brightness = ret; + + return devm_led_classdev_register(ec->dev, &wkl->keyboard); +} diff --git a/drivers/platform/chrome/wilco_ec/sysfs.c b/drivers/platform/chrome/wilco_ec/sysfs.c index 3b86a21005d3..f0d174b6bb21 100644 --- a/drivers/platform/chrome/wilco_ec/sysfs.c +++ b/drivers/platform/chrome/wilco_ec/sysfs.c @@ -23,6 +23,26 @@ struct boot_on_ac_request { u8 reserved7; } __packed; +#define CMD_USB_CHARGE 0x39 + +enum usb_charge_op { + USB_CHARGE_GET = 0, + USB_CHARGE_SET = 1, +}; + +struct usb_charge_request { + u8 cmd; /* Always CMD_USB_CHARGE */ + u8 reserved; + u8 op; /* One of enum usb_charge_op */ + u8 val; /* When setting, either 0 or 1 */ +} __packed; + +struct usb_charge_response { + u8 reserved; + u8 status; /* Set by EC to 0 on success, other value on failure */ + u8 val; /* When getting, set by EC to either 0 or 1 */ +} __packed; + #define CMD_EC_INFO 0x38 enum get_ec_info_op { CMD_GET_EC_LABEL = 0, @@ -131,12 +151,83 @@ static ssize_t model_number_show(struct device *dev, static DEVICE_ATTR_RO(model_number); +static int send_usb_charge(struct wilco_ec_device *ec, + struct usb_charge_request *rq, + struct usb_charge_response *rs) +{ + struct wilco_ec_message msg; + int ret; + + memset(&msg, 0, sizeof(msg)); + msg.type = WILCO_EC_MSG_LEGACY; + msg.request_data = rq; + msg.request_size = sizeof(*rq); + msg.response_data = rs; + msg.response_size = sizeof(*rs); + ret = wilco_ec_mailbox(ec, &msg); + if (ret < 0) + return ret; + if (rs->status) + return -EIO; + + return 0; +} + +static ssize_t usb_charge_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wilco_ec_device *ec = dev_get_drvdata(dev); + struct usb_charge_request rq; + struct usb_charge_response rs; + int ret; + + memset(&rq, 0, sizeof(rq)); + rq.cmd = CMD_USB_CHARGE; + rq.op = USB_CHARGE_GET; + + ret = send_usb_charge(ec, &rq, &rs); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", rs.val); +} + +static ssize_t usb_charge_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wilco_ec_device *ec = dev_get_drvdata(dev); + struct usb_charge_request rq; + struct usb_charge_response rs; + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret < 0) + return ret; + if (val > 1) + return -EINVAL; + + memset(&rq, 0, sizeof(rq)); + rq.cmd = CMD_USB_CHARGE; + rq.op = USB_CHARGE_SET; + rq.val = val; + + ret = send_usb_charge(ec, &rq, &rs); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(usb_charge); static struct attribute *wilco_dev_attrs[] = { &dev_attr_boot_on_ac.attr, &dev_attr_build_date.attr, &dev_attr_build_revision.attr, &dev_attr_model_number.attr, + &dev_attr_usb_charge.attr, &dev_attr_version.attr, NULL, }; diff --git a/drivers/platform/chrome/wilco_ec/telemetry.c b/drivers/platform/chrome/wilco_ec/telemetry.c index b9d03c33d8dc..1176d543191a 100644 --- a/drivers/platform/chrome/wilco_ec/telemetry.c +++ b/drivers/platform/chrome/wilco_ec/telemetry.c @@ -406,8 +406,8 @@ static int telem_device_remove(struct platform_device *pdev) struct telem_device_data *dev_data = platform_get_drvdata(pdev); cdev_device_del(&dev_data->cdev, &dev_data->dev); - put_device(&dev_data->dev); ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt)); + put_device(&dev_data->dev); return 0; } diff --git a/include/linux/platform_data/cros_ec_proto.h b/include/linux/platform_data/cros_ec_proto.h index eab7036cda09..30098a551523 100644 --- a/include/linux/platform_data/cros_ec_proto.h +++ b/include/linux/platform_data/cros_ec_proto.h @@ -12,6 +12,7 @@ #include #include +#include #include #define CROS_EC_DEV_NAME "cros_ec" @@ -115,12 +116,16 @@ struct cros_ec_command { * code. * @pkt_xfer: Send packet to EC and get response. * @lock: One transaction at a time. - * @mkbp_event_supported: True if this EC supports the MKBP event protocol. + * @mkbp_event_supported: 0 if MKBP not supported. Otherwise its value is + * the maximum supported version of the MKBP host event + * command + 1. * @host_sleep_v1: True if this EC supports the sleep v1 command. * @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. * @host_event_wake_mask: Mask of host events that cause wake from suspend. + * @last_event_time: exact time from the hard irq when we got notified of + * a new event. * @ec: The platform_device used by the mfd driver to interface with the * main EC. * @pd: The platform_device used by the mfd driver to interface with the @@ -153,7 +158,7 @@ struct cros_ec_device { int (*pkt_xfer)(struct cros_ec_device *ec, struct cros_ec_command *msg); struct mutex lock; - bool mkbp_event_supported; + u8 mkbp_event_supported; bool host_sleep_v1; struct blocking_notifier_head event_notifier; @@ -161,20 +166,13 @@ struct cros_ec_device { int event_size; u32 host_event_wake_mask; u32 last_resume_result; + ktime_t last_event_time; /* The platform devices used by the mfd driver */ struct platform_device *ec; struct platform_device *pd; }; -/** - * struct cros_ec_sensor_platform - ChromeOS EC sensor platform information. - * @sensor_num: Id of the sensor, as reported by the EC. - */ -struct cros_ec_sensor_platform { - u8 sensor_num; -}; - /** * struct cros_ec_platform - ChromeOS EC platform information. * @ec_name: Name of EC device (e.g. 'cros-ec', 'cros-pd', ...) @@ -187,133 +185,51 @@ struct cros_ec_platform { u16 cmd_offset; }; -/** - * cros_ec_suspend() - Handle a suspend operation for the ChromeOS EC device. - * @ec_dev: Device to suspend. - * - * This can be called by drivers to handle a suspend event. - * - * Return: 0 on success or negative error code. - */ int cros_ec_suspend(struct cros_ec_device *ec_dev); -/** - * cros_ec_resume() - Handle a resume operation for the ChromeOS EC device. - * @ec_dev: Device to resume. - * - * This can be called by drivers to handle a resume event. - * - * Return: 0 on success or negative error code. - */ int cros_ec_resume(struct cros_ec_device *ec_dev); -/** - * cros_ec_prepare_tx() - Prepare an outgoing message in the output buffer. - * @ec_dev: Device to register. - * @msg: Message to write. - * - * 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. - * - * Return: 0 on success or negative error code. - */ int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, struct cros_ec_command *msg); -/** - * cros_ec_check_result() - Check ec_msg->result. - * @ec_dev: EC device. - * @msg: Message to check. - * - * This is used by ChromeOS EC drivers to check the ec_msg->result for - * errors and to warn about them. - * - * Return: 0 on success or negative error code. - */ int cros_ec_check_result(struct cros_ec_device *ec_dev, struct cros_ec_command *msg); -/** - * cros_ec_cmd_xfer() - Send a command to the ChromeOS EC. - * @ec_dev: EC device. - * @msg: Message to write. - * - * Call this to send a command to the ChromeOS EC. This should be used - * instead of calling the EC's cmd_xfer() callback directly. - * - * Return: 0 on success or negative error code. - */ int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, struct cros_ec_command *msg); -/** - * cros_ec_cmd_xfer_status() - Send a command to the ChromeOS EC. - * @ec_dev: EC device. - * @msg: Message to write. - * - * This function is identical to cros_ec_cmd_xfer, except it returns success - * status only if both the command was transmitted successfully and the EC - * replied with success status. It's not necessary to check msg->result when - * using this function. - * - * Return: The number of bytes transferred on success or negative error code. - */ int cros_ec_cmd_xfer_status(struct cros_ec_device *ec_dev, struct cros_ec_command *msg); -/** - * cros_ec_register() - Register a new ChromeOS EC, using the provided info. - * @ec_dev: Device to register. - * - * Before calling this, allocate a pointer to a new device and then fill - * in all the fields up to the --private-- marker. - * - * Return: 0 on success or negative error code. - */ int cros_ec_register(struct cros_ec_device *ec_dev); -/** - * cros_ec_unregister() - Remove a ChromeOS EC. - * @ec_dev: Device to unregister. - * - * Call this to deregister a ChromeOS EC, then clean up any private data. - * - * Return: 0 on success or negative error code. - */ int cros_ec_unregister(struct cros_ec_device *ec_dev); -/** - * cros_ec_query_all() - Query the protocol version supported by the - * ChromeOS EC. - * @ec_dev: Device to register. - * - * Return: 0 on success or negative error code. - */ 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. - * @wake_event: Pointer to a bool set to true upon return if the event might be - * treated as a wake event. Ignored if null. - * - * Return: negative error code on errors; 0 for no data; or else number of - * bytes received (i.e., an event was retrieved successfully). Event types are - * written out to @ec_dev->event_data.event_type on success. - */ -int cros_ec_get_next_event(struct cros_ec_device *ec_dev, bool *wake_event); +int cros_ec_get_next_event(struct cros_ec_device *ec_dev, + bool *wake_event, + bool *has_more_events); -/** - * cros_ec_get_host_event() - Return a mask of event set by the ChromeOS EC. - * @ec_dev: Device to fetch event from. - * - * When MKBP is supported, when the EC raises an interrupt, we collect the - * events raised and call the functions in the ec notifier. This function - * is a helper to know which events are raised. - * - * Return: 0 on error or non-zero bitmask of one or more EC_HOST_EVENT_*. - */ u32 cros_ec_get_host_event(struct cros_ec_device *ec_dev); +int cros_ec_check_features(struct cros_ec_dev *ec, int feature); + +int cros_ec_get_sensor_count(struct cros_ec_dev *ec); + +bool cros_ec_handle_event(struct cros_ec_device *ec_dev); + +/** + * cros_ec_get_time_ns() - Return time in ns. + * + * This is the function used to record the time for last_event_time in struct + * cros_ec_device during the hard irq. + * + * Return: ktime_t format since boot. + */ +static inline ktime_t cros_ec_get_time_ns(void) +{ + return ktime_get_boottime_ns(); +} + #endif /* __LINUX_CROS_EC_PROTO_H */ diff --git a/include/linux/platform_data/cros_ec_sensorhub.h b/include/linux/platform_data/cros_ec_sensorhub.h new file mode 100644 index 000000000000..bef7ffc7fce1 --- /dev/null +++ b/include/linux/platform_data/cros_ec_sensorhub.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Chrome OS EC MEMS Sensor Hub driver. + * + * Copyright 2019 Google LLC + */ + +#ifndef __LINUX_PLATFORM_DATA_CROS_EC_SENSORHUB_H +#define __LINUX_PLATFORM_DATA_CROS_EC_SENSORHUB_H + +#include + +/** + * struct cros_ec_sensor_platform - ChromeOS EC sensor platform information. + * @sensor_num: Id of the sensor, as reported by the EC. + */ +struct cros_ec_sensor_platform { + u8 sensor_num; +}; + +/** + * struct cros_ec_sensorhub - Sensor Hub device data. + * + * @ec: Embedded Controller where the hub is located. + */ +struct cros_ec_sensorhub { + struct cros_ec_dev *ec; +}; + +#endif /* __LINUX_PLATFORM_DATA_CROS_EC_SENSORHUB_H */ diff --git a/include/linux/platform_data/wilco-ec.h b/include/linux/platform_data/wilco-ec.h index ad03b586a095..afede15a95bf 100644 --- a/include/linux/platform_data/wilco-ec.h +++ b/include/linux/platform_data/wilco-ec.h @@ -29,6 +29,7 @@ * @data_size: Size of the data buffer used for EC communication. * @debugfs_pdev: The child platform_device used by the debugfs sub-driver. * @rtc_pdev: The child platform_device used by the RTC sub-driver. + * @charger_pdev: Child platform_device used by the charger config sub-driver. * @telem_pdev: The child platform_device used by the telemetry sub-driver. */ struct wilco_ec_device { @@ -41,6 +42,7 @@ struct wilco_ec_device { size_t data_size; struct platform_device *debugfs_pdev; struct platform_device *rtc_pdev; + struct platform_device *charger_pdev; struct platform_device *telem_pdev; }; @@ -120,6 +122,19 @@ struct wilco_ec_message { */ int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg); +/** + * wilco_keyboard_leds_init() - Set up the keyboard backlight LEDs. + * @ec: EC device to query. + * + * After this call, the keyboard backlight will be exposed through a an LED + * device at /sys/class/leds. + * + * This may sleep because it uses wilco_ec_mailbox(). + * + * Return: 0 on success, negative error code on failure. + */ +int wilco_keyboard_leds_init(struct wilco_ec_device *ec); + /* * A Property is typically a data item that is stored to NVRAM * by the EC. Each of these data items has an index associated