blob: b03d7030b92cddb9b2f860f26da7e3f32ac3a953 [file] [log] [blame]
From 355ba9e15dbc29f7e215d9ed77233faad56de5b7 Mon Sep 17 00:00:00 2001
From: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Date: Mon, 19 Jun 2017 13:03:00 +0200
Subject: [PATCH] CHROMIUM: mfd /platform: cros_ec: cros_ec_pd_update: Add
driver for updating of PD device firmware
Add a driver for auto-updating of PD devices. The driver currently performs
the following task: Upon receiving a host event, probe every PD port on the
EC, and send commands to update the PD device firmware if mismatched
firmware is found.
BUG=chromium:785506,chromium:965213,chromium:972644
Squash and syntax fixes of
- d1528e626e05 ("CHROMIUM: mfd: cros_ec_pd_update: refresh the power supply state")
- b9a7e5b751a9 ("CHROMIUM: mfd: cros_ec_pd_update: Add driver for updating of PD device firmware")
- 85b2654 ("CHROMIUM: mfd / platform: cros_ec: Move pd update sysfs to its own driver")
Conflicts:
include/linux/mfd/cros_ec_commands.h
TEST=Using the whole suite, check on eve,cyan,glimmer AIDA64 works.
Check on pixel 2 usb-pd-charger exits.
TEST=Check on samus-kernelnext the driver is present:
/sys/devices/pci0000:00/0000:00:1f.0/PNP0C09:00/GOOG0004:00/cros-ec-dev.1.auto/chromeos/cros_ec/pd_update
/sys/devices/pci0000:00/0000:00:1f.0/PNP0C09:00/GOOG0004:00/cros-ec-dev.6.auto/chromeos/cros_pd/pd_update
Only the last one has information:
cat /sys/.../G0004:00/cros-ec-dev.6.auto/chromeos/cros_pd/pd_update/firmware_images
0: 1.1 cros-pd/zinger_v1.7.539-91a0fa2.bin
...
4: 4.1 cros-pd/hoho_v1.7.684-69498dd.bin
Check real pd driver is loaded:
ls -l /sys/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1d/PNP0C09:00/GOOG0003:00
total 0
--w-------. 1 root root 4096 Jun 7 14:54 disable
...
[rebase419(groeck): Conflict resolution]
[rebase54(gwendal): merged with CL:1652004 to account for file location changes.]
[rebase510(gwendal): Conflict resolution in Kconfig - remove mfd/cros_ec.h]
Change-Id: Ia4905f3586237b2379520860566884820b56daec
Signed-off-by: Shawn Nematbakhsh <shawnn@chromium.org>
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Signed-off-by: Gwendal Grignou <gwendal@chromium.org>
Signed-off-by: Guenter Roeck <groeck@chromium.org>
Reviewed-by: Benson Leung <bleung@google.com>
fix cros_ec_pd_update.c
Signed-off-by: Gwendal Grignou <gwendal@chromium.org>
Change-Id: Id472b073fcfabb275c59d3935cf7144972616bfb
---
...sfs-class-chromeos-driver-cros-ec-pd-sysfs | 10 +
drivers/mfd/cros_ec_dev.c | 1 +
drivers/platform/chrome/Kconfig | 11 +
drivers/platform/chrome/Makefile | 1 +
drivers/platform/chrome/cros_ec_pd_update.c | 1070 +++++++++++++++++
.../linux/platform_data/cros_ec_pd_update.h | 87 ++
include/linux/platform_data/cros_ec_proto.h | 3 +
7 files changed, 1183 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-class-chromeos-driver-cros-ec-pd-sysfs
create mode 100644 drivers/platform/chrome/cros_ec_pd_update.c
create mode 100644 include/linux/platform_data/cros_ec_pd_update.h
diff --git a/Documentation/ABI/testing/sysfs-class-chromeos-driver-cros-ec-pd-sysfs b/Documentation/ABI/testing/sysfs-class-chromeos-driver-cros-ec-pd-sysfs
new file mode 100644
index 000000000000..41a490d83f4a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-chromeos-driver-cros-ec-pd-sysfs
@@ -0,0 +1,10 @@
+What: /sys/class/chromeos/<ec-device-name>/pd_update/firmware_images
+Date: August 2015
+KernelVersion: 4.2
+Description:
+ List the names of the available firmware images for USB PD
+ that use cros_ec OS.
+ Each line contains a slot, PD major/minor and firmware name:
+ 0: 1.1 cros-pd/zinger_v1.7.539-91a0fa2.bin
+
+ The images are in the kernel firmware images directory.
diff --git a/drivers/mfd/cros_ec_dev.c b/drivers/mfd/cros_ec_dev.c
index 8c08d1c55726..6d0cc3307a32 100644
--- a/drivers/mfd/cros_ec_dev.c
+++ b/drivers/mfd/cros_ec_dev.c
@@ -113,6 +113,7 @@ static const struct cros_feature_to_cells cros_subdevices[] = {
static const struct mfd_cell cros_ec_platform_cells[] = {
{ .name = "cros-ec-chardev", },
{ .name = "cros-ec-debugfs", },
+ { .name = "cros-ec-pd-sysfs" },
{ .name = "cros-ec-sysfs", },
{ .name = "cros-ec-pchg", },
};
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig
index 9879e6877140..7e364616cf89 100644
--- a/drivers/platform/chrome/Kconfig
+++ b/drivers/platform/chrome/Kconfig
@@ -212,6 +212,17 @@ config CROS_EC_SYSFS
To compile this driver as a module, choose M here: the
module will be called cros_ec_sysfs.
+config CROS_EC_PD_UPDATE
+ tristate "ChromeOS Embedded Controller PD device update driver"
+ depends on MFD_CROS_EC_DEV
+
+ help
+ If you say Y here, you get support for updating ChromeOS
+ PD device firmware.
+
+ To compile this driver as a module, choose M here: the module will be
+ called cros_ec_pd_update.
+
config CROS_EC_TYPEC
tristate "ChromeOS EC Type-C Connector Control"
depends on MFD_CROS_EC_DEV && TYPEC
diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
index 25f33ab6fff4..5b83b2775317 100644
--- a/drivers/platform/chrome/Makefile
+++ b/drivers/platform/chrome/Makefile
@@ -16,6 +16,7 @@ cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o
obj-$(CONFIG_CROS_EC_TYPEC) += cros_ec_typec.o
obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o
obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o
+obj-$(CONFIG_CROS_EC_PD_UPDATE) += cros_ec_pd_update.o
obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o
obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_chardev.o
obj-$(CONFIG_CROS_EC_LIGHTBAR) += cros_ec_lightbar.o
diff --git a/drivers/platform/chrome/cros_ec_pd_update.c b/drivers/platform/chrome/cros_ec_pd_update.c
new file mode 100644
index 000000000000..f44fcf2133af
--- /dev/null
+++ b/drivers/platform/chrome/cros_ec_pd_update.c
@@ -0,0 +1,1070 @@
+/*
+ * cros_ec_pd_update - Chrome OS EC Power Delivery Device FW Update Driver
+ *
+ * Copyright (C) 2014 Google, Inc
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This driver communicates with a Chrome OS PD device and performs tasks
+ * related to auto-updating its firmware.
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_pd_update.h>
+#include <linux/platform_data/cros_ec_proto.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+/* Store our PD device pointer so we can send update-related commands. */
+static struct cros_ec_dev *pd_ec;
+
+/* Allow disabling of the update for testing purposes */
+static int disable;
+
+/*
+ * $DEVICE_known_update_hashes - A list of old known RW hashes from which we
+ * wish to upgrade. When firmware_images is updated, the old hash should
+ * probably be added here. The latest hash currently in firmware_images should
+ * NOT appear here.
+ */
+static uint8_t zinger_known_update_hashes[][PD_RW_HASH_SIZE] = {
+ /* zinger_v1.7.509-e5bffd3.bin */
+ { 0x02, 0xad, 0x4c, 0x95, 0x25,
+ 0x89, 0xe5, 0xe7, 0x1e, 0xc6,
+ 0xaf, 0x9c, 0x0e, 0xaa, 0xbb,
+ 0x6c, 0xa7, 0x52, 0x8c, 0x3a },
+ /* zinger_v1.7.262-9a5b8f4.bin */
+ { 0x05, 0x94, 0xb8, 0x97, 0x8a,
+ 0x9a, 0xa0, 0x0a, 0x71, 0x07,
+ 0x37, 0xba, 0x8f, 0x4c, 0x01,
+ 0xe6, 0x45, 0x6d, 0xb0, 0x01 },
+};
+
+static uint8_t dingdong_known_update_hashes[][PD_RW_HASH_SIZE] = {
+ /* dingdong_v1.7.575-96b74f1.bin devid: 3.2 */
+ { 0x64, 0xdb, 0x4e, 0x86, 0xd6,
+ 0x7d, 0x7a, 0xce, 0x41, 0xfd,
+ 0x09, 0x3b, 0xd4, 0x8b, 0x3f,
+ 0x1f, 0xba, 0x73, 0xcb, 0x73 },
+ /* dingdong_v1.7.489-8533e9d.bin devid: 3.2 */
+ { 0x53, 0x20, 0x21, 0x34, 0xc2,
+ 0xee, 0x2f, 0x07, 0xbb, 0x24,
+ 0x94, 0xab, 0xbe, 0x1f, 0xee,
+ 0xf2, 0xb3, 0x7e, 0xff, 0x23 },
+ /* dingdong_v1.7.317-b0bb7c9.bin devid: 3.1 */
+ { 0x0f, 0x1e, 0x93, 0x9f, 0xbc,
+ 0x23, 0x0a, 0x3f, 0x4f, 0x35,
+ 0xf8, 0xfe, 0xd8, 0xa9, 0x71,
+ 0x8f, 0xef, 0x15, 0xc8, 0xea },
+};
+
+static uint8_t hoho_known_update_hashes[][PD_RW_HASH_SIZE] = {
+ /* hoho_v1.7.575-96b74f1.bin devid: 4.2 */
+ { 0x4b, 0x3d, 0x8b, 0xba, 0x8a,
+ 0x62, 0xae, 0x4f, 0x64, 0xd2,
+ 0x0f, 0x96, 0xf9, 0x4e, 0xc7,
+ 0xf6, 0x6a, 0x19, 0x84, 0x1c },
+ /* hoho_v1.7.489-8533e9d.bin devid: 4.2 */
+ { 0xac, 0x00, 0xc1, 0x4c, 0x3a,
+ 0x77, 0xa6, 0x1f, 0xf9, 0xd5,
+ 0x59, 0x3a, 0x56, 0x06, 0x5c,
+ 0x86, 0x09, 0xe0, 0x03, 0xb3 },
+ /* hoho_v1.7.317-b0bb7c9.bin devid:4.1 */
+ { 0x98, 0x19, 0xa6, 0x6b, 0x61,
+ 0x1f, 0x28, 0xba, 0xde, 0x80,
+ 0xa3, 0x88, 0x95, 0x67, 0x57,
+ 0xa2, 0x98, 0xe4, 0xf1, 0x62 },
+};
+
+/*
+ * firmware_images - Keep this updated with the latest RW FW + hash for each
+ * PD device. Entries should be primary sorted by id_major and secondary
+ * sorted by id_minor.
+ */
+static const struct cros_ec_pd_firmware_image firmware_images[] = {
+ /* PD_DEVICE_TYPE_ZINGER */
+ {
+ .id_major = PD_DEVICE_TYPE_ZINGER,
+ .id_minor = 1,
+ .usb_vid = USB_VID_GOOGLE,
+ .usb_pid = USB_PID_ZINGER,
+ .filename = "cros-pd/zinger_v1.7.539-91a0fa2.bin",
+ .rw_image_size = (16 * 1024),
+ .hash = { 0x3b, 0x2e, 0xe3, 0xf6, 0x1e,
+ 0x6a, 0x1d, 0x49, 0xd3, 0x1c,
+ 0xf5, 0x77, 0x5e, 0xa7, 0x19,
+ 0xdb, 0xde, 0xcd, 0xaa, 0xc2 },
+ .update_hashes = &zinger_known_update_hashes,
+ .update_hash_count = ARRAY_SIZE(zinger_known_update_hashes),
+ },
+ {
+ .id_major = PD_DEVICE_TYPE_DINGDONG,
+ .id_minor = 2,
+ .usb_vid = USB_VID_GOOGLE,
+ .usb_pid = USB_PID_DINGDONG,
+ .filename = "cros-pd/dingdong_v1.7.684-69498dd.bin",
+ .rw_image_size = (64 * 1024),
+ .hash = { 0xe6, 0x97, 0x90, 0xd9, 0xe5,
+ 0x01, 0x15, 0x22, 0xee, 0x1c,
+ 0x7e, 0x4d, 0x6c, 0x54, 0x78,
+ 0xd4, 0x7a, 0xa7, 0xda, 0x1d },
+ .update_hashes = &dingdong_known_update_hashes,
+ .update_hash_count = ARRAY_SIZE(dingdong_known_update_hashes),
+ },
+ {
+ .id_major = PD_DEVICE_TYPE_DINGDONG,
+ .id_minor = 1,
+ .usb_vid = USB_VID_GOOGLE,
+ .usb_pid = USB_PID_DINGDONG,
+ .filename = "cros-pd/dingdong_v1.7.684-69498dd.bin",
+ .rw_image_size = (64 * 1024),
+ .hash = { 0xe6, 0x97, 0x90, 0xd9, 0xe5,
+ 0x01, 0x15, 0x22, 0xee, 0x1c,
+ 0x7e, 0x4d, 0x6c, 0x54, 0x78,
+ 0xd4, 0x7a, 0xa7, 0xda, 0x1d },
+ .update_hashes = &dingdong_known_update_hashes,
+ .update_hash_count = ARRAY_SIZE(dingdong_known_update_hashes),
+ },
+ {
+ .id_major = PD_DEVICE_TYPE_HOHO,
+ .id_minor = 2,
+ .usb_vid = USB_VID_GOOGLE,
+ .usb_pid = USB_PID_HOHO,
+ .filename = "cros-pd/hoho_v1.7.684-69498dd.bin",
+ .rw_image_size = (64 * 1024),
+ .hash = { 0x43, 0x1b, 0x4e, 0x20, 0xe8,
+ 0x38, 0xdd, 0x29, 0x42, 0xbd,
+ 0x6d, 0xfc, 0x13, 0xf2, 0xb2,
+ 0x46, 0xa6, 0xf4, 0x98, 0x08 },
+ .update_hashes = &hoho_known_update_hashes,
+ .update_hash_count = ARRAY_SIZE(hoho_known_update_hashes),
+ },
+ {
+ .id_major = PD_DEVICE_TYPE_HOHO,
+ .id_minor = 1,
+ .usb_vid = USB_VID_GOOGLE,
+ .usb_pid = USB_PID_HOHO,
+ .filename = "cros-pd/hoho_v1.7.684-69498dd.bin",
+ .rw_image_size = (64 * 1024),
+ .hash = { 0x43, 0x1b, 0x4e, 0x20, 0xe8,
+ 0x38, 0xdd, 0x29, 0x42, 0xbd,
+ 0x6d, 0xfc, 0x13, 0xf2, 0xb2,
+ 0x46, 0xa6, 0xf4, 0x98, 0x08 },
+ .update_hashes = &hoho_known_update_hashes,
+ .update_hash_count = ARRAY_SIZE(hoho_known_update_hashes),
+ },
+};
+
+static const int firmware_image_count = ARRAY_SIZE(firmware_images);
+
+/**
+ * cros_ec_pd_command - Send a command to the EC. Returns 0 on success,
+ * <0 on failure.
+ *
+ * @dev: PD device
+ * @pd_dev: EC PD device
+ * @command: EC command
+ * @outdata: EC command output data
+ * @outsize: Size of outdata
+ * @indata: EC command input data
+ * @insize: Size of indata
+ */
+static int cros_ec_pd_command(struct device *dev,
+ struct cros_ec_dev *pd_dev,
+ int command,
+ uint8_t *outdata,
+ int outsize,
+ uint8_t *indata,
+ int insize)
+{
+ int ret;
+ struct cros_ec_command *msg;
+
+ msg = kzalloc(sizeof(*msg) + max(insize, outsize), GFP_KERNEL);
+ if (!msg)
+ return -EC_RES_ERROR;
+
+ msg->command = command | pd_dev->cmd_offset;
+ msg->outsize = outsize;
+ msg->insize = insize;
+
+ if (outsize)
+ memcpy(msg->data, outdata, outsize);
+
+ ret = cros_ec_cmd_xfer_status(pd_dev->ec_dev, msg);
+ if (ret < 0)
+ goto error;
+
+ if (insize)
+ memcpy(indata, msg->data, insize);
+ ret = EC_RES_SUCCESS;
+error:
+ kfree(msg);
+ return ret;
+}
+
+/**
+ * cros_ec_pd_enter_gfu - Enter GFU alternate mode.
+ * Returns 0 if ec command successful <0 on failure.
+ *
+ * Note, doesn't guarantee entry.
+ *
+ * @dev: PD device
+ * @pd_dev: EC PD device
+ * @port: Port # on device
+ */
+static int cros_ec_pd_enter_gfu(struct device *dev, struct cros_ec_dev *pd_dev,
+ int port)
+{
+ int rv;
+ struct ec_params_usb_pd_set_mode_request set_mode_request;
+
+ set_mode_request.port = port;
+ set_mode_request.svid = USB_VID_GOOGLE;
+ /* TODO(tbroch) Will GFU always be '1'? */
+ set_mode_request.opos = 1;
+ set_mode_request.cmd = PD_ENTER_MODE;
+ rv = cros_ec_pd_command(dev, pd_dev, EC_CMD_USB_PD_SET_AMODE,
+ (uint8_t *)&set_mode_request,
+ sizeof(set_mode_request),
+ NULL, 0);
+ if (!rv)
+ /* Allow time to enter GFU mode */
+ msleep(500);
+
+ return rv;
+}
+
+/**
+ * cros_ec_pd_get_status - Get info about a possible PD device attached to a
+ * given port. Returns 0 on success, <0 on failure.
+ *
+ * @dev: PD device
+ * @pd_dev: EC PD device
+ * @port: Port # on device
+ * @hash_entry: Stores received PD device RW FW info, on success
+ * @discovery_entry: Stores received PD device USB info, if device present
+ */
+static int cros_ec_pd_get_status(struct device *dev,
+ struct cros_ec_dev *pd_dev,
+ int port,
+ struct ec_params_usb_pd_rw_hash_entry
+ *hash_entry,
+ struct ec_params_usb_pd_discovery_entry
+ *discovery_entry)
+{
+ struct ec_params_usb_pd_info_request info_request;
+ int ret;
+
+ info_request.port = port;
+ ret = cros_ec_pd_command(dev, pd_dev, EC_CMD_USB_PD_DEV_INFO,
+ (uint8_t *)&info_request, sizeof(info_request),
+ (uint8_t *)hash_entry, sizeof(*hash_entry));
+ /* Skip getting USB discovery data if no device present on port */
+ if (ret < 0 || hash_entry->dev_id == PD_DEVICE_TYPE_NONE)
+ return ret;
+
+ return cros_ec_pd_command(dev, pd_dev, EC_CMD_USB_PD_DISCOVERY,
+ (uint8_t *)&info_request,
+ sizeof(info_request),
+ (uint8_t *)discovery_entry,
+ sizeof(*discovery_entry));
+}
+
+/**
+ * cros_ec_pd_send_hash_entry - Inform the EC of a PD devices for which we
+ * have firmware available. EC typically will not store more than four hashes.
+ * Returns 0 on success, <0 on failure.
+ *
+ * @dev: PD device
+ * @pd_dev: EC PD device
+ * @fw: FW update image to inform the EC of
+ */
+static int cros_ec_pd_send_hash_entry(struct device *dev,
+ struct cros_ec_dev *pd_dev,
+ const struct cros_ec_pd_firmware_image
+ *fw)
+{
+ struct ec_params_usb_pd_rw_hash_entry hash_entry;
+
+ hash_entry.dev_id = MAJOR_MINOR_TO_DEV_ID(fw->id_major, fw->id_minor);
+ memcpy(hash_entry.dev_rw_hash, fw->hash, PD_RW_HASH_SIZE);
+
+ return cros_ec_pd_command(dev, pd_dev, EC_CMD_USB_PD_RW_HASH_ENTRY,
+ (uint8_t *)&hash_entry, sizeof(hash_entry),
+ NULL, 0);
+}
+
+/**
+ * cros_ec_pd_send_fw_update_cmd - Send update-related EC command.
+ * Returns 0 on success, <0 on failure.
+ *
+ * @dev: PD device
+ * @pd_dev: EC PD device
+ * @pd_cmd: fw_update command
+ */
+static int cros_ec_pd_send_fw_update_cmd(struct device *dev,
+ struct cros_ec_dev *pd_dev,
+ struct ec_params_usb_pd_fw_update
+ *pd_cmd)
+{
+ return cros_ec_pd_command(dev, pd_dev, EC_CMD_USB_PD_FW_UPDATE,
+ (uint8_t *)pd_cmd,
+ pd_cmd->size + sizeof(*pd_cmd),
+ NULL, 0);
+}
+
+/**
+ * cros_ec_pd_get_num_ports - Get number of EC charge ports.
+ * Returns 0 on success, <0 on failure.
+ *
+ * @dev: PD device
+ * @pd_dev: EC PD device
+ * @num_ports: Holds number of ports, on command success
+ */
+static int cros_ec_pd_get_num_ports(struct device *dev,
+ struct cros_ec_dev *pd_dev,
+ int *num_ports)
+{
+ struct ec_response_usb_pd_ports resp;
+ int ret;
+
+ ret = cros_ec_pd_command(dev, pd_dev, EC_CMD_USB_PD_PORTS,
+ NULL, 0,
+ (uint8_t *)&resp, sizeof(resp));
+ if (ret == EC_RES_SUCCESS)
+ *num_ports = resp.num_ports;
+ return ret;
+}
+
+
+/**
+ * cros_ec_pd_fw_update - Send EC_CMD_USB_PD_FW_UPDATE command to perform
+ * update-related operation.
+ * Returns 0 on success, <0 on failure.
+ *
+ * @dev: PD device
+ * @pd_dev: EC PD device
+ * @fw: RW FW update file
+ * @port: Port# to which update device is attached
+ */
+static int cros_ec_pd_fw_update(struct cros_ec_pd_update_data *drv_data,
+ struct cros_ec_dev *pd_dev,
+ const struct firmware *fw,
+ uint8_t port)
+{
+ uint8_t cmd_buf[sizeof(struct ec_params_usb_pd_fw_update) +
+ PD_FLASH_WRITE_STEP];
+ struct ec_params_usb_pd_fw_update *pd_cmd =
+ (struct ec_params_usb_pd_fw_update *)cmd_buf;
+ uint8_t *pd_cmd_data = cmd_buf + sizeof(*pd_cmd);
+ struct device *dev = drv_data->dev;
+ int i, ret;
+
+ if (drv_data->is_suspending)
+ return -EBUSY;
+
+ /* Common port */
+ pd_cmd->port = port;
+
+ /* Erase signature */
+ pd_cmd->cmd = USB_PD_FW_ERASE_SIG;
+ pd_cmd->size = 0;
+ ret = cros_ec_pd_send_fw_update_cmd(dev, pd_dev, pd_cmd);
+ if (ret < 0) {
+ dev_err(dev,
+ "Unable to clear Port%d PD signature (err:%d)\n",
+ port, ret);
+ return ret;
+ }
+
+ /* Reboot PD */
+ pd_cmd->cmd = USB_PD_FW_REBOOT;
+ pd_cmd->size = 0;
+ ret = cros_ec_pd_send_fw_update_cmd(dev, pd_dev, pd_cmd);
+ if (ret < 0) {
+ dev_err(dev, "Unable to reboot Port%d PD (err:%d)\n",
+ port, ret);
+ return ret;
+ }
+
+ /*
+ * Wait for the charger to reboot.
+ * TODO(shawnn): Instead of waiting for a fixed period of time, wait
+ * to receive an interrupt that signals the charger is back online.
+ */
+ msleep(4000);
+
+ if (drv_data->is_suspending)
+ return -EBUSY;
+
+ /*
+ * Force re-entry into GFU mode for USBPD devices that don't enter
+ * it by default.
+ */
+ ret = cros_ec_pd_enter_gfu(dev, pd_dev, port);
+ if (ret < 0)
+ dev_warn(dev, "Unable to enter GFU (err:%d)\n", ret);
+
+ /* Erase RW flash */
+ pd_cmd->cmd = USB_PD_FW_FLASH_ERASE;
+ pd_cmd->size = 0;
+ ret = cros_ec_pd_send_fw_update_cmd(dev, pd_dev, pd_cmd);
+ if (ret < 0) {
+ dev_err(dev, "Unable to erase Port%d PD RW flash (err:%d)\n",
+ port, ret);
+ return ret;
+ }
+
+ /* Wait 3 seconds for the PD peripheral to finalize RW erase */
+ msleep(3000);
+
+ /* Write RW flash */
+ pd_cmd->cmd = USB_PD_FW_FLASH_WRITE;
+ for (i = 0; i < fw->size; i += PD_FLASH_WRITE_STEP) {
+ if (drv_data->is_suspending)
+ return -EBUSY;
+ pd_cmd->size = min(fw->size - i, (size_t)PD_FLASH_WRITE_STEP);
+ memcpy(pd_cmd_data, fw->data + i, pd_cmd->size);
+ ret = cros_ec_pd_send_fw_update_cmd(dev, pd_dev, pd_cmd);
+ if (ret < 0) {
+ dev_err(dev,
+ "Unable to write Port%d PD RW flash (err:%d)\n",
+ port, ret);
+ return ret;
+ }
+ }
+
+ /* Wait 100ms to guarantee that writes finish */
+ msleep(100);
+
+ /* Reboot PD into new RW */
+ pd_cmd->cmd = USB_PD_FW_REBOOT;
+ pd_cmd->size = 0;
+ ret = cros_ec_pd_send_fw_update_cmd(dev, pd_dev, pd_cmd);
+ if (ret < 0) {
+ dev_err(dev,
+ "Unable to reboot Port%d PD post-flash (err:%d)\n",
+ port, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * cros_ec_find_update_firmware - Search firmware image table for an image
+ * matching the passed attributes, then decide whether an update should
+ * be performed.
+ * Returns PD_DO_UPDATE if an update should be performed, and writes the
+ * firmware_image pointer to update_image.
+ * Returns reason for not updating otherwise.
+ *
+ * @dev: PD device
+ * @hash_entry: Pre-filled hash entry struct for matching
+ * @discovery_entry: Pre-filled discovery entry struct for matching
+ * @update_image: Stores update firmware image on success
+ */
+static enum cros_ec_pd_find_update_firmware_result cros_ec_find_update_firmware(
+ struct device *dev,
+ struct ec_params_usb_pd_rw_hash_entry *hash_entry,
+ struct ec_params_usb_pd_discovery_entry *discovery_entry,
+ const struct cros_ec_pd_firmware_image **update_image)
+{
+ const struct cros_ec_pd_firmware_image *img;
+ int i;
+
+ if (hash_entry->dev_id == PD_DEVICE_TYPE_NONE)
+ return PD_UNKNOWN_DEVICE;
+
+ /*
+ * Search for a matching firmware update image.
+ * TODO(shawnn): Replace sequential table search with modified binary
+ * search on major / minor.
+ */
+ for (i = 0; i < firmware_image_count; ++i) {
+ img = &firmware_images[i];
+ if (MAJOR_MINOR_TO_DEV_ID(img->id_major, img->id_minor)
+ == hash_entry->dev_id &&
+ img->usb_vid == discovery_entry->vid &&
+ img->usb_pid == discovery_entry->pid)
+ break;
+ }
+ *update_image = img;
+
+ if (i == firmware_image_count)
+ return PD_UNKNOWN_DEVICE;
+
+ if (!memcmp(hash_entry->dev_rw_hash, img->hash, PD_RW_HASH_SIZE)) {
+ if (hash_entry->current_image != EC_IMAGE_RW)
+ /*
+ * As signature isn't factored into the hash if we've
+ * previously updated RW but subsequently invalidate
+ * signature we can get into this situation. Need to
+ * reflash.
+ */
+ return PD_DO_UPDATE;
+ /* Device is already updated */
+ return PD_ALREADY_HAVE_LATEST;
+ }
+
+ /* Always update if PD device is stuck in RO. */
+ if (hash_entry->current_image != EC_IMAGE_RW) {
+ dev_info(dev, "Updating FW since PD dev is in RO\n");
+ return PD_DO_UPDATE;
+ }
+
+ dev_info(dev, "Considering upgrade from existing RW: %x %x %x %x\n",
+ hash_entry->dev_rw_hash[0],
+ hash_entry->dev_rw_hash[1],
+ hash_entry->dev_rw_hash[2],
+ hash_entry->dev_rw_hash[3]);
+
+ /* Verify RW is a known update image so we don't roll-back. */
+ for (i = 0; i < img->update_hash_count; ++i)
+ if (memcmp(hash_entry->dev_rw_hash,
+ (*img->update_hashes)[i],
+ PD_RW_HASH_SIZE) == 0) {
+ dev_info(dev, "Updating FW since RW is known\n");
+ return PD_DO_UPDATE;
+ }
+
+ dev_info(dev, "Skipping FW update since RW is unknown\n");
+ return PD_UNKNOWN_RW;
+}
+
+/**
+ * cros_ec_pd_get_host_event_status - Get host event status and return. If
+ * failure return 0.
+ *
+ * @dev: PD device
+ * @pd_dev: EC PD device
+ */
+static uint32_t cros_ec_pd_get_host_event_status(struct device *dev,
+ struct cros_ec_dev *pd_dev)
+{
+ int ret;
+ struct ec_response_host_event_status host_event_status;
+
+ /* Check for host events on EC. */
+ ret = cros_ec_pd_command(dev, pd_dev, EC_CMD_PD_HOST_EVENT_STATUS,
+ NULL, 0,
+ (uint8_t *)&host_event_status,
+ sizeof(host_event_status));
+ if (ret) {
+ dev_err(dev, "Can't get host event status (err: %d)\n", ret);
+ return 0;
+ }
+ dev_dbg(dev, "Got host event status %x\n", host_event_status.status);
+ return host_event_status.status;
+}
+
+/**
+ * cros_ec_pd_update_check - Probe the status of attached PD devices and kick
+ * off an RW firmware update if needed. This is run as a deferred task on
+ * module load, resume, and when an ACPI event is received (typically on
+ * PD device insertion).
+ *
+ * @work: Delayed work pointer
+ */
+static void cros_ec_pd_update_check(struct work_struct *work)
+{
+ const struct cros_ec_pd_firmware_image *img;
+ const struct firmware *fw;
+ struct ec_params_usb_pd_rw_hash_entry hash_entry;
+ struct ec_params_usb_pd_discovery_entry discovery_entry;
+ struct cros_ec_pd_update_data *drv_data =
+ container_of(to_delayed_work(work),
+ struct cros_ec_pd_update_data, work);
+ struct device *dev = drv_data->dev;
+ struct power_supply *charger;
+ enum cros_ec_pd_find_update_firmware_result result;
+ int ret, port;
+ uint32_t pd_status;
+
+ if (disable) {
+ dev_info(dev, "Update is disabled\n");
+ return;
+ }
+
+ dev_dbg(dev, "Checking for updates\n");
+
+ /* Force GFU entry for devices not in GFU by default. */
+ for (port = 0; port < drv_data->num_ports; ++port) {
+ dev_dbg(dev, "Considering GFU entry on C%d\n", port);
+ ret = cros_ec_pd_get_status(dev, pd_ec, port, &hash_entry,
+ &discovery_entry);
+ if (ret || (hash_entry.dev_id == PD_DEVICE_TYPE_NONE)) {
+ dev_dbg(dev, "Forcing GFU entry on C%d\n", port);
+ cros_ec_pd_enter_gfu(dev, pd_ec, port);
+ }
+ }
+
+ pd_status = cros_ec_pd_get_host_event_status(dev, pd_ec);
+
+ /*
+ * Override status received from EC if update is forced, such as
+ * after power-on or after resume.
+ */
+ if (drv_data->force_update) {
+ pd_status = PD_EVENT_POWER_CHANGE | PD_EVENT_UPDATE_DEVICE;
+ drv_data->force_update = 0;
+ }
+
+ /*
+ * If there is an EC based charger, send a notification to it to
+ * trigger a refresh of the power supply state.
+ */
+ charger = pd_ec->ec_dev->charger;
+ if ((pd_status & PD_EVENT_POWER_CHANGE) && charger)
+ charger->desc->external_power_changed(charger);
+
+ if (!(pd_status & PD_EVENT_UPDATE_DEVICE))
+ return;
+
+ /* Received notification, send command to check on PD status. */
+ for (port = 0; port < drv_data->num_ports; ++port) {
+ /* Don't try to update if we're going to suspend. */
+ if (drv_data->is_suspending)
+ return;
+
+ ret = cros_ec_pd_get_status(dev, pd_ec, port, &hash_entry,
+ &discovery_entry);
+ if (ret < 0) {
+ dev_err(dev,
+ "Can't get Port%d device status (err:%d)\n",
+ port, ret);
+ return;
+ }
+
+ result = cros_ec_find_update_firmware(dev,
+ &hash_entry,
+ &discovery_entry,
+ &img);
+ dev_dbg(dev, "Find Port%d FW result: %d\n", port, result);
+
+ switch (result) {
+ case PD_DO_UPDATE:
+ if (request_firmware(&fw, img->filename, dev)) {
+ dev_err(dev,
+ "Error, Port%d can't load file %s\n",
+ port, img->filename);
+ break;
+ }
+
+ if (fw->size != img->rw_image_size) {
+ dev_err(dev,
+ "Port%d FW file %s size %zd != %zd\n",
+ port, img->filename, fw->size,
+ img->rw_image_size);
+ goto done;
+ }
+
+ /* Update firmware */
+ dev_info(dev, "Updating Port%d RW to %s\n", port,
+ img->filename);
+ ret = cros_ec_pd_fw_update(drv_data, pd_ec, fw, port);
+ dev_info(dev,
+ "Port%d FW update completed with status %d\n",
+ port, ret);
+done:
+ release_firmware(fw);
+ break;
+ case PD_ALREADY_HAVE_LATEST:
+ /*
+ * Device already has latest firmare. Send hash entry
+ * to EC so we don't get subsequent FW update requests.
+ */
+ dev_info(dev, "Port%d FW is already up-to-date %s\n",
+ port, img->filename);
+ cros_ec_pd_send_hash_entry(dev, pd_ec, img);
+ break;
+ case PD_UNKNOWN_DEVICE:
+ case PD_UNKNOWN_RW:
+ /* Unknown PD device or RW -- don't update FW */
+ break;
+ }
+ }
+}
+
+/**
+ * cros_ec_pd_notify - Called upon receiving a PD MCU event (typically
+ * due to PD device insertion). Queue a delayed task to check if a PD
+ * device FW update is necessary.
+ */
+static void cros_ec_pd_notify(struct device *dev, u32 event)
+{
+ struct cros_ec_pd_update_data *drv_data =
+ (struct cros_ec_pd_update_data *)
+ dev_get_drvdata(dev);
+
+ if (drv_data)
+ queue_delayed_work(drv_data->workqueue, &drv_data->work,
+ PD_UPDATE_CHECK_DELAY);
+ else
+ dev_warn(dev, "PD notification skipped due to missing drv_data\n");
+}
+
+static ssize_t disable_firmware_update(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ unsigned int val;
+ struct cros_ec_pd_update_data *drv_data;
+
+ ret = sscanf(buf, "%i", &val);
+ if (ret != 1)
+ return -EINVAL;
+
+ disable = !!val;
+ dev_info(dev, "FW update is %sabled\n", disable ? "dis" : "en");
+
+ drv_data = (struct cros_ec_pd_update_data *)dev_get_drvdata(dev);
+
+ /* If re-enabled then force update */
+ if (!disable && drv_data) {
+ drv_data->force_update = 1;
+ queue_delayed_work(drv_data->workqueue, &drv_data->work,
+ PD_UPDATE_CHECK_DELAY);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(disable, 0200, NULL, disable_firmware_update);
+
+static struct attribute *pd_attrs[] = {
+ &dev_attr_disable.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(pd);
+
+static int cros_ec_pd_add(struct device *dev)
+{
+ struct cros_ec_pd_update_data *drv_data;
+ int ret, i;
+
+ /* If pd_ec is not initialized, try again later */
+ if (!pd_ec)
+ return -EPROBE_DEFER;
+
+ drv_data =
+ devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL);
+ if (!drv_data)
+ return -ENOMEM;
+
+ drv_data->dev = dev;
+ INIT_DELAYED_WORK(&drv_data->work, cros_ec_pd_update_check);
+ drv_data->workqueue =
+ create_singlethread_workqueue("cros_ec_pd_update");
+ if (cros_ec_pd_get_num_ports(drv_data->dev,
+ pd_ec,
+ &drv_data->num_ports) < 0) {
+ dev_err(drv_data->dev, "Can't get num_ports\n");
+ return -EINVAL;
+ }
+ drv_data->force_update = 1;
+ drv_data->is_suspending = 0;
+ dev_set_drvdata(dev, drv_data);
+ ret = sysfs_create_groups(&dev->kobj, pd_groups);
+ if (ret) {
+ dev_err(dev, "failed to create sysfs attributes: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Send list of update FW hashes to PD MCU.
+ * TODO(crosbug.com/p/35510): This won't scale past four update
+ * devices. Find a better solution once we get there.
+ */
+ for (i = 0; i < firmware_image_count; ++i)
+ cros_ec_pd_send_hash_entry(drv_data->dev,
+ pd_ec,
+ &firmware_images[i]);
+
+ queue_delayed_work(drv_data->workqueue, &drv_data->work,
+ PD_UPDATE_CHECK_DELAY);
+ return 0;
+}
+
+static int cros_ec_pd_resume(struct device *dev)
+{
+ struct cros_ec_pd_update_data *drv_data =
+ (struct cros_ec_pd_update_data *)dev_get_drvdata(dev);
+
+ if (drv_data) {
+ drv_data->force_update = 1;
+ drv_data->is_suspending = 0;
+ queue_delayed_work(drv_data->workqueue, &drv_data->work,
+ PD_UPDATE_CHECK_DELAY);
+ }
+ return 0;
+}
+
+static int cros_ec_pd_remove(struct device *dev)
+{
+ struct cros_ec_pd_update_data *drv_data =
+ (struct cros_ec_pd_update_data *)
+ dev_get_drvdata(dev);
+
+ if (drv_data) {
+ drv_data->is_suspending = 1;
+ cancel_delayed_work_sync(&drv_data->work);
+ }
+ return 0;
+}
+
+static int cros_ec_pd_suspend(struct device *dev)
+{
+ struct cros_ec_pd_update_data *drv_data =
+ (struct cros_ec_pd_update_data *)dev_get_drvdata(dev);
+
+ if (drv_data) {
+ drv_data->is_suspending = 1;
+ cancel_delayed_work_sync(&drv_data->work);
+ disable = 0;
+ }
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(cros_ec_pd_pm,
+ cros_ec_pd_suspend, cros_ec_pd_resume);
+
+#ifdef CONFIG_ACPI
+static void acpi_cros_ec_pd_notify(struct acpi_device *acpi_device, u32 event)
+{
+ cros_ec_pd_notify(&acpi_device->dev, event);
+}
+
+static int acpi_cros_ec_pd_add(struct acpi_device *acpi_device)
+{
+ return cros_ec_pd_add(&acpi_device->dev);
+}
+
+static int acpi_cros_ec_pd_remove(struct acpi_device *acpi_device)
+{
+ return cros_ec_pd_remove(&acpi_device->dev);
+}
+
+static const struct acpi_device_id pd_device_ids[] = {
+ { "GOOG0003", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(acpi, pd_device_ids);
+
+static struct acpi_driver acpi_cros_ec_pd_driver = {
+ .name = "cros_ec_pd_update",
+ .class = "cros_ec_pd_update",
+ .ids = pd_device_ids,
+ .ops = {
+ .add = acpi_cros_ec_pd_add,
+ .remove = acpi_cros_ec_pd_remove,
+ .notify = acpi_cros_ec_pd_notify,
+ },
+ .drv.pm = &cros_ec_pd_pm,
+};
+
+module_acpi_driver(acpi_cros_ec_pd_driver);
+#else /* CONFIG_ACPI */
+static int _ec_pd_notify(struct notifier_block *nb,
+ unsigned long queued_during_suspend, void *_notify)
+{
+ struct cros_ec_pd_update_data *drv_data;
+ struct device *dev;
+ struct cros_ec_device *ec;
+ u32 host_event;
+
+ drv_data = container_of(nb, struct cros_ec_pd_update_data, notifier);
+ dev = drv_data->dev;
+ ec = dev_get_drvdata(dev->parent);
+
+ host_event = cros_ec_get_host_event(ec);
+ if (host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU)) {
+ cros_ec_pd_notify(dev, host_event);
+ return NOTIFY_OK;
+ } else {
+ return NOTIFY_DONE;
+ }
+}
+
+static int plat_cros_ec_pd_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
+ struct cros_ec_pd_update_data *drv_data =
+ (struct cros_ec_pd_update_data *)dev_get_drvdata(dev);
+ int ret;
+
+ ret = cros_ec_pd_add(dev);
+ if (ret < 0)
+ return ret;
+
+ drv_data = (struct cros_ec_pd_update_data *)dev_get_drvdata(dev);
+ /* Get PD events from the EC */
+ drv_data->notifier.notifier_call = _ec_pd_notify;
+ ret = blocking_notifier_chain_register(&ec->event_notifier,
+ &drv_data->notifier);
+ if (ret < 0)
+ dev_warn(dev, "failed to register notifier\n");
+
+ return 0;
+}
+
+static int plat_cros_ec_pd_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
+ struct cros_ec_pd_update_data *drv_data =
+ (struct cros_ec_pd_update_data *)dev_get_drvdata(dev);
+
+ blocking_notifier_chain_unregister(&ec->event_notifier,
+ &drv_data->notifier);
+
+ return cros_ec_pd_remove(dev);
+}
+
+static const struct of_device_id cros_ec_pd_of_match[] = {
+ { .compatible = "google,cros-ec-pd-update" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cros_ec_pd_of_match);
+
+static struct platform_driver cros_ec_pd_driver = {
+ .driver = {
+ .name = "cros-ec-pd-update",
+ .of_match_table = of_match_ptr(cros_ec_pd_of_match),
+ .pm = &cros_ec_pd_pm,
+ },
+ .remove = plat_cros_ec_pd_remove,
+ .probe = plat_cros_ec_pd_probe,
+};
+
+module_platform_driver(cros_ec_pd_driver);
+
+#endif /* CONFIG_ACPI */
+
+/*
+ * Driver loaded on top of the EC object.
+ *
+ * It exposes a sysfs interface, but most importantly, set global pd_ec to
+ * let the real driver knows which pd_ec device to talk to.
+ */
+#define DRV_NAME "cros-ec-pd-sysfs"
+
+static umode_t cros_ec_pd_attrs_are_visible(struct kobject *kobj,
+ struct attribute *a, int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct cros_ec_dev *ec = container_of(dev, struct cros_ec_dev,
+ class_dev);
+ struct ec_params_usb_pd_rw_hash_entry hash_entry;
+ struct ec_params_usb_pd_discovery_entry discovery_entry;
+
+ /* Check if a PD MCU is present */
+ if (cros_ec_pd_get_status(dev,
+ ec,
+ 0,
+ &hash_entry,
+ &discovery_entry) == EC_RES_SUCCESS) {
+ /*
+ * Save our ec pointer so we can conduct transactions.
+ * TODO(shawnn): Find a better way to access the ec pointer.
+ */
+ if (!pd_ec)
+ pd_ec = ec;
+ return a->mode;
+ }
+
+ return 0;
+}
+
+static ssize_t firmware_images_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int size = 0;
+ int i;
+
+ for (i = 0; i < firmware_image_count; ++i) {
+ if (firmware_images[i].filename == NULL)
+ size += scnprintf(buf + size, PAGE_SIZE,
+ "%d: %d.%d NONE\n", i,
+ firmware_images[i].id_major,
+ firmware_images[i].id_minor);
+ else
+ size += scnprintf(buf + size, PAGE_SIZE,
+ "%d: %d.%d %s\n", i,
+ firmware_images[i].id_major,
+ firmware_images[i].id_minor,
+ firmware_images[i].filename);
+ }
+
+ return size;
+}
+
+static DEVICE_ATTR_RO(firmware_images);
+
+static struct attribute *__pd_attrs[] = {
+ &dev_attr_firmware_images.attr,
+ NULL,
+};
+
+static struct attribute_group cros_ec_pd_attr_group = {
+ .name = "pd_update",
+ .attrs = __pd_attrs,
+ .is_visible = cros_ec_pd_attrs_are_visible,
+};
+
+
+static int cros_ec_pd_sysfs_probe(struct platform_device *pd)
+{
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
+ struct device *dev = &pd->dev;
+ int ret;
+
+ ret = sysfs_create_group(&ec_dev->class_dev.kobj,
+ &cros_ec_pd_attr_group);
+ if (ret < 0)
+ dev_err(dev, "failed to create attributes. err=%d\n", ret);
+
+ return ret;
+}
+
+static int cros_ec_pd_sysfs_remove(struct platform_device *pd)
+{
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
+
+ sysfs_remove_group(&ec_dev->class_dev.kobj, &cros_ec_pd_attr_group);
+
+ return 0;
+}
+
+static struct platform_driver cros_ec_pd_sysfs_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .probe = cros_ec_pd_sysfs_probe,
+ .remove = cros_ec_pd_sysfs_remove,
+};
+
+module_platform_driver(cros_ec_pd_sysfs_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ChromeOS power device FW update driver");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/include/linux/platform_data/cros_ec_pd_update.h b/include/linux/platform_data/cros_ec_pd_update.h
new file mode 100644
index 000000000000..b4b153a9b1ac
--- /dev/null
+++ b/include/linux/platform_data/cros_ec_pd_update.h
@@ -0,0 +1,87 @@
+/*
+ * cros_ec_pd - Chrome OS EC Power Delivery Device Driver
+ *
+ * Copyright (C) 2014 Google, Inc
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __CROS_EC_PD_UPDATE_H
+#define __CROS_EC_PD_UPDATE_H
+
+#include <linux/types.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+
+enum cros_ec_pd_device_type {
+ PD_DEVICE_TYPE_NONE = 0,
+ PD_DEVICE_TYPE_ZINGER = 1,
+ PD_DEVICE_TYPE_DINGDONG = 3,
+ PD_DEVICE_TYPE_HOHO = 4,
+ PD_DEVICE_TYPE_COUNT,
+};
+
+#define USB_VID_GOOGLE 0x18d1
+
+#define USB_PID_DINGDONG 0x5011
+#define USB_PID_HOHO 0x5010
+#define USB_PID_ZINGER 0x5012
+
+struct cros_ec_pd_firmware_image {
+ unsigned int id_major;
+ unsigned int id_minor;
+ uint16_t usb_vid;
+ uint16_t usb_pid;
+ char *filename;
+ ssize_t rw_image_size;
+ uint8_t hash[PD_RW_HASH_SIZE];
+ uint8_t (*update_hashes)[][PD_RW_HASH_SIZE];
+ int update_hash_count;
+};
+
+struct cros_ec_pd_update_data {
+ struct device *dev;
+
+ struct delayed_work work;
+ struct workqueue_struct *workqueue;
+ struct notifier_block notifier;
+
+ int num_ports;
+ int force_update;
+ int is_suspending;
+};
+
+#define PD_ID_MAJOR_SHIFT 0
+#define PD_ID_MAJOR_MASK 0x03ff
+#define PD_ID_MINOR_SHIFT 10
+#define PD_ID_MINOR_MASK 0xfc00
+
+#define MAJOR_MINOR_TO_DEV_ID(major, minor) \
+ ((((major) << PD_ID_MAJOR_SHIFT) & PD_ID_MAJOR_MASK) | \
+ (((minor) << PD_ID_MINOR_SHIFT) & PD_ID_MINOR_MASK))
+
+enum cros_ec_pd_find_update_firmware_result {
+ PD_DO_UPDATE,
+ PD_ALREADY_HAVE_LATEST,
+ PD_UNKNOWN_DEVICE,
+ PD_UNKNOWN_RW,
+};
+
+/* Send 96 bytes per write command when flashing PD device */
+#define PD_FLASH_WRITE_STEP 96
+
+/*
+ * Wait 2s to start an update check after scheduling. This helps to remove
+ * needless extra update checks (ex. if a PD device is reset several times
+ * immediately after insertion) and fixes load issues on resume.
+ */
+#define PD_UPDATE_CHECK_DELAY msecs_to_jiffies(2000)
+
+#endif /* __CROS_EC_PD_UPDATE_H */
diff --git a/include/linux/platform_data/cros_ec_proto.h b/include/linux/platform_data/cros_ec_proto.h
index 02599687770c..999c964a6ec4 100644
--- a/include/linux/platform_data/cros_ec_proto.h
+++ b/include/linux/platform_data/cros_ec_proto.h
@@ -11,6 +11,7 @@
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
+#include <linux/power_supply.h>
#include <linux/platform_data/cros_ec_commands.h>
@@ -115,6 +116,7 @@ struct cros_ec_command {
* code.
* @pkt_xfer: Send packet to EC and get response.
* @lock: One transaction at a time.
+ * @charger: Charger connected to the EC, if any.
* @mkbp_event_supported: 0 if MKBP not supported. Otherwise its value is
* the maximum supported version of the MKBP host event
* command + 1.
@@ -159,6 +161,7 @@ struct cros_ec_device {
struct cros_ec_command *msg);
int (*pkt_xfer)(struct cros_ec_device *ec,
struct cros_ec_command *msg);
+ struct power_supply *charger;
struct mutex lock;
u8 mkbp_event_supported;
bool host_sleep_v1;
--
2.17.1