diff options
author | Heikki Krogerus <heikki.krogerus@linux.intel.com> | 2017-06-16 11:21:25 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-06-27 17:55:45 +0200 |
commit | 8243edf44152c08c3efa1d551fc48605d674ad18 (patch) | |
tree | 3dd7b9ed384b537706b94ffda00cd87ed2826fa4 /drivers/usb | |
parent | c1b0bc2dabfa884dea49c02adaf3cd6b52b33d2f (diff) |
usb: typec: ucsi: Add ACPI driver
Driver for ACPI UCSI interface method. This driver replaces
the previous UCSI driver drivers/usb/misc/ucsi.c.
Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/misc/Kconfig | 26 | ||||
-rw-r--r-- | drivers/usb/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/misc/ucsi.c | 478 | ||||
-rw-r--r-- | drivers/usb/misc/ucsi.h | 215 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/Kconfig | 16 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi_acpi.c | 158 |
7 files changed, 176 insertions, 720 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 1d1d70d62a19..0f9f25db9163 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -275,29 +275,3 @@ config USB_CHAOSKEY To compile this driver as a module, choose M here: the module will be called chaoskey. - -config UCSI - tristate "USB Type-C Connector System Software Interface driver" - depends on ACPI - help - UCSI driver is meant to be used as a convenience tool for desktop and - server systems that are not equipped to handle USB in device mode. It - will always select USB host role for the USB Type-C ports on systems - that provide UCSI interface. - - USB Type-C Connector System Software Interface (UCSI) is a - specification for an interface that allows the Operating System to - control the USB Type-C ports on a system. Things the need controlling - include the USB Data Role (host or device), and when USB Power - Delivery is supported, the Power Role (source or sink). With USB - Type-C connectors, when two dual role capable devices are attached - together, the data role is selected randomly. Therefore it is - important to give the OS a way to select the role. Otherwise the user - would have to unplug and replug in order in order to attempt to swap - the data and power roles. - - The UCSI specification can be downloaded from: - http://www.intel.com/content/www/us/en/io/universal-serial-bus/usb-type-c-ucsi-spec.html - - To compile the driver as a module, choose M here: the module will be - called ucsi. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index f6ac6c99a6e6..7fdb45fc976f 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -27,7 +27,6 @@ obj-$(CONFIG_USB_HUB_USB251XB) += usb251xb.o obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o obj-$(CONFIG_USB_HSIC_USB4604) += usb4604.o obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o -obj-$(CONFIG_UCSI) += ucsi.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o diff --git a/drivers/usb/misc/ucsi.c b/drivers/usb/misc/ucsi.c deleted file mode 100644 index 07397bddefa3..000000000000 --- a/drivers/usb/misc/ucsi.c +++ /dev/null @@ -1,478 +0,0 @@ -/* - * USB Type-C Connector System Software Interface driver - * - * Copyright (C) 2016, Intel Corporation - * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/platform_device.h> -#include <linux/module.h> -#include <linux/delay.h> -#include <linux/acpi.h> - -#include "ucsi.h" - -/* Double the time defined by MIN_TIME_TO_RESPOND_WITH_BUSY */ -#define UCSI_TIMEOUT_MS 20 - -enum ucsi_status { - UCSI_IDLE = 0, - UCSI_BUSY, - UCSI_ERROR, -}; - -struct ucsi_connector { - int num; - struct ucsi *ucsi; - struct work_struct work; - struct ucsi_connector_capability cap; -}; - -struct ucsi { - struct device *dev; - struct ucsi_data __iomem *data; - - enum ucsi_status status; - struct completion complete; - struct ucsi_capability cap; - struct ucsi_connector *connector; - - /* device lock */ - spinlock_t dev_lock; - - /* PPM Communication lock */ - struct mutex ppm_lock; - - /* PPM communication flags */ - unsigned long flags; -#define EVENT_PENDING 0 -#define COMMAND_PENDING 1 -}; - -static int ucsi_acpi_cmd(struct ucsi *ucsi, struct ucsi_control *ctrl) -{ - uuid_le uuid = UUID_LE(0x6f8398c2, 0x7ca4, 0x11e4, - 0xad, 0x36, 0x63, 0x10, 0x42, 0xb5, 0x00, 0x8f); - union acpi_object *obj; - - ucsi->data->ctrl.raw_cmd = ctrl->raw_cmd; - - obj = acpi_evaluate_dsm(ACPI_HANDLE(ucsi->dev), uuid.b, 1, 1, NULL); - if (!obj) { - dev_err(ucsi->dev, "%s: failed to evaluate _DSM\n", __func__); - return -EIO; - } - - ACPI_FREE(obj); - return 0; -} - -static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) -{ - struct ucsi *ucsi = data; - struct ucsi_cci *cci; - - spin_lock(&ucsi->dev_lock); - - ucsi->status = UCSI_IDLE; - cci = &ucsi->data->cci; - - /* - * REVISIT: This is not documented behavior, but all known PPMs ACK - * asynchronous events by sending notification with cleared CCI. - */ - if (!ucsi->data->raw_cci) { - if (test_bit(EVENT_PENDING, &ucsi->flags)) - complete(&ucsi->complete); - else - dev_WARN(ucsi->dev, "spurious notification\n"); - goto out_unlock; - } - - if (test_bit(COMMAND_PENDING, &ucsi->flags)) { - if (cci->busy) { - ucsi->status = UCSI_BUSY; - complete(&ucsi->complete); - - goto out_unlock; - } else if (cci->ack_complete || cci->cmd_complete) { - /* Error Indication is only valid with commands */ - if (cci->error && cci->cmd_complete) - ucsi->status = UCSI_ERROR; - - ucsi->data->ctrl.raw_cmd = 0; - complete(&ucsi->complete); - } - } - - if (cci->connector_change) { - struct ucsi_connector *con; - - /* - * This is workaround for buggy PPMs that create asynchronous - * event notifications before OPM has enabled them. - */ - if (!ucsi->connector) - goto out_unlock; - - con = ucsi->connector + (cci->connector_change - 1); - - /* - * PPM will not clear the connector specific bit in Connector - * Change Indication field of CCI until the driver has ACK it, - * and the driver can not ACK it before it has been processed. - * The PPM will not generate new events before the first has - * been acknowledged, even if they are for an other connector. - * So only one event at a time. - */ - if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) - schedule_work(&con->work); - } -out_unlock: - spin_unlock(&ucsi->dev_lock); -} - -static int ucsi_ack(struct ucsi *ucsi, u8 cmd) -{ - struct ucsi_control ctrl; - int ret; - - ctrl.cmd.cmd = UCSI_ACK_CC_CI; - ctrl.cmd.length = 0; - ctrl.cmd.data = cmd; - ret = ucsi_acpi_cmd(ucsi, &ctrl); - if (ret) - return ret; - - /* Waiting for ACK also with ACK CMD for now */ - ret = wait_for_completion_timeout(&ucsi->complete, - msecs_to_jiffies(UCSI_TIMEOUT_MS)); - if (!ret) - return -ETIMEDOUT; - return 0; -} - -static int ucsi_run_cmd(struct ucsi *ucsi, struct ucsi_control *ctrl, - void *data, size_t size) -{ - u16 err_value = 0; - int ret; - - set_bit(COMMAND_PENDING, &ucsi->flags); - - ret = ucsi_acpi_cmd(ucsi, ctrl); - if (ret) - goto err_clear_flag; - - ret = wait_for_completion_timeout(&ucsi->complete, - msecs_to_jiffies(UCSI_TIMEOUT_MS)); - if (!ret) { - ret = -ETIMEDOUT; - goto err_clear_flag; - } - - switch (ucsi->status) { - case UCSI_IDLE: - if (data) - memcpy(data, ucsi->data->message_in, size); - - ret = ucsi_ack(ucsi, UCSI_ACK_CMD); - break; - case UCSI_BUSY: - /* The caller decides whether to cancel or not */ - ret = -EBUSY; - goto err_clear_flag; - case UCSI_ERROR: - ret = ucsi_ack(ucsi, UCSI_ACK_CMD); - if (ret) - goto err_clear_flag; - - ctrl->cmd.cmd = UCSI_GET_ERROR_STATUS; - ctrl->cmd.length = 0; - ctrl->cmd.data = 0; - ret = ucsi_acpi_cmd(ucsi, ctrl); - if (ret) - goto err_clear_flag; - - ret = wait_for_completion_timeout(&ucsi->complete, - msecs_to_jiffies(UCSI_TIMEOUT_MS)); - if (!ret) { - ret = -ETIMEDOUT; - goto err_clear_flag; - } - - memcpy(&err_value, ucsi->data->message_in, sizeof(err_value)); - - /* Something has really gone wrong */ - if (WARN_ON(ucsi->status == UCSI_ERROR)) { - ret = -ENODEV; - goto err_clear_flag; - } - - ret = ucsi_ack(ucsi, UCSI_ACK_CMD); - if (ret) - goto err_clear_flag; - - switch (err_value) { - case UCSI_ERROR_INCOMPATIBLE_PARTNER: - ret = -EOPNOTSUPP; - break; - case UCSI_ERROR_CC_COMMUNICATION_ERR: - ret = -ECOMM; - break; - case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL: - ret = -EIO; - break; - case UCSI_ERROR_DEAD_BATTERY: - dev_warn(ucsi->dev, "Dead battery condition!\n"); - ret = -EPERM; - break; - /* The following mean a bug in this driver */ - case UCSI_ERROR_INVALID_CON_NUM: - case UCSI_ERROR_UNREGONIZED_CMD: - case UCSI_ERROR_INVALID_CMD_ARGUMENT: - default: - dev_warn(ucsi->dev, - "%s: possible UCSI driver bug - error %hu\n", - __func__, err_value); - ret = -EINVAL; - break; - } - break; - } - ctrl->raw_cmd = 0; -err_clear_flag: - clear_bit(COMMAND_PENDING, &ucsi->flags); - return ret; -} - -static void ucsi_connector_change(struct work_struct *work) -{ - struct ucsi_connector *con = container_of(work, struct ucsi_connector, - work); - struct ucsi_connector_status constat; - struct ucsi *ucsi = con->ucsi; - struct ucsi_control ctrl; - int ret; - - mutex_lock(&ucsi->ppm_lock); - - ctrl.cmd.cmd = UCSI_GET_CONNECTOR_STATUS; - ctrl.cmd.length = 0; - ctrl.cmd.data = con->num; - ret = ucsi_run_cmd(con->ucsi, &ctrl, &constat, sizeof(constat)); - if (ret) { - dev_err(ucsi->dev, "%s: failed to read connector status (%d)\n", - __func__, ret); - goto out_ack_event; - } - - /* Ignoring disconnections and Alternate Modes */ - if (!constat.connected || !(constat.change & - (UCSI_CONSTAT_PARTNER_CHANGE | UCSI_CONSTAT_CONNECT_CHANGE)) || - constat.partner_flags & UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE) - goto out_ack_event; - - /* If the partner got USB Host role, attempting swap */ - if (constat.partner_type & UCSI_CONSTAT_PARTNER_TYPE_DFP) { - ctrl.uor.cmd = UCSI_SET_UOR; - ctrl.uor.con_num = con->num; - ctrl.uor.role = UCSI_UOR_ROLE_DFP; - - ret = ucsi_run_cmd(con->ucsi, &ctrl, NULL, 0); - if (ret) - dev_err(ucsi->dev, "%s: failed to swap role (%d)\n", - __func__, ret); - } -out_ack_event: - ucsi_ack(ucsi, UCSI_ACK_EVENT); - clear_bit(EVENT_PENDING, &ucsi->flags); - mutex_unlock(&ucsi->ppm_lock); -} - -static int ucsi_reset_ppm(struct ucsi *ucsi) -{ - int timeout = UCSI_TIMEOUT_MS; - struct ucsi_control ctrl; - int ret; - - memset(&ctrl, 0, sizeof(ctrl)); - ctrl.cmd.cmd = UCSI_PPM_RESET; - ret = ucsi_acpi_cmd(ucsi, &ctrl); - if (ret) - return ret; - - /* There is no quarantee the PPM will ever set the RESET_COMPLETE bit */ - while (!ucsi->data->cci.reset_complete && timeout--) - usleep_range(1000, 2000); - return 0; -} - -static int ucsi_init(struct ucsi *ucsi) -{ - struct ucsi_connector *con; - struct ucsi_control ctrl; - int ret; - int i; - - init_completion(&ucsi->complete); - spin_lock_init(&ucsi->dev_lock); - mutex_init(&ucsi->ppm_lock); - - /* Reset the PPM */ - ret = ucsi_reset_ppm(ucsi); - if (ret) - return ret; - - /* - * REVISIT: Executing second reset to WA an issue seen on some of the - * Broxton based platforms, where the first reset puts the PPM into a - * state where it's unable to recognise some of the commands. - */ - ret = ucsi_reset_ppm(ucsi); - if (ret) - return ret; - - mutex_lock(&ucsi->ppm_lock); - - /* Enable basic notifications */ - ctrl.cmd.cmd = UCSI_SET_NOTIFICATION_ENABLE; - ctrl.cmd.length = 0; - ctrl.cmd.data = UCSI_ENABLE_NTFY_CMD_COMPLETE | UCSI_ENABLE_NTFY_ERROR; - ret = ucsi_run_cmd(ucsi, &ctrl, NULL, 0); - if (ret) - goto err_reset; - - /* Get PPM capabilities */ - ctrl.cmd.cmd = UCSI_GET_CAPABILITY; - ret = ucsi_run_cmd(ucsi, &ctrl, &ucsi->cap, sizeof(ucsi->cap)); - if (ret) - goto err_reset; - - if (!ucsi->cap.num_connectors) { - ret = -ENODEV; - goto err_reset; - } - - ucsi->connector = devm_kcalloc(ucsi->dev, ucsi->cap.num_connectors, - sizeof(*ucsi->connector), GFP_KERNEL); - if (!ucsi->connector) { - ret = -ENOMEM; - goto err_reset; - } - - for (i = 1, con = ucsi->connector; i < ucsi->cap.num_connectors + 1; - i++, con++) { - /* Get connector capability */ - ctrl.cmd.cmd = UCSI_GET_CONNECTOR_CAPABILITY; - ctrl.cmd.data = i; - ret = ucsi_run_cmd(ucsi, &ctrl, &con->cap, sizeof(con->cap)); - if (ret) - goto err_reset; - - con->num = i; - con->ucsi = ucsi; - INIT_WORK(&con->work, ucsi_connector_change); - } - - /* Enable all notifications */ - ctrl.cmd.cmd = UCSI_SET_NOTIFICATION_ENABLE; - ctrl.cmd.data = UCSI_ENABLE_NTFY_ALL; - ret = ucsi_run_cmd(ucsi, &ctrl, NULL, 0); - if (ret < 0) - goto err_reset; - - mutex_unlock(&ucsi->ppm_lock); - return 0; -err_reset: - ucsi_reset_ppm(ucsi); - mutex_unlock(&ucsi->ppm_lock); - return ret; -} - -static int ucsi_acpi_probe(struct platform_device *pdev) -{ - struct resource *res; - acpi_status status; - struct ucsi *ucsi; - int ret; - - ucsi = devm_kzalloc(&pdev->dev, sizeof(*ucsi), GFP_KERNEL); - if (!ucsi) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "missing memory resource\n"); - return -ENODEV; - } - - /* - * NOTE: ACPI has claimed the memory region as it's also an Operation - * Region. It's not possible to request it in the driver. - */ - ucsi->data = devm_ioremap(&pdev->dev, res->start, resource_size(res)); - if (!ucsi->data) - return -ENOMEM; - - ucsi->dev = &pdev->dev; - - status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), - ACPI_ALL_NOTIFY, - ucsi_acpi_notify, ucsi); - if (ACPI_FAILURE(status)) - return -ENODEV; - - ret = ucsi_init(ucsi); - if (ret) { - acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), - ACPI_ALL_NOTIFY, - ucsi_acpi_notify); - return ret; - } - - platform_set_drvdata(pdev, ucsi); - return 0; -} - -static int ucsi_acpi_remove(struct platform_device *pdev) -{ - struct ucsi *ucsi = platform_get_drvdata(pdev); - - acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), - ACPI_ALL_NOTIFY, ucsi_acpi_notify); - - /* Make sure there are no events in the middle of being processed */ - if (wait_on_bit_timeout(&ucsi->flags, EVENT_PENDING, - TASK_UNINTERRUPTIBLE, - msecs_to_jiffies(UCSI_TIMEOUT_MS))) - dev_WARN(ucsi->dev, "%s: Events still pending\n", __func__); - - ucsi_reset_ppm(ucsi); - return 0; -} - -static const struct acpi_device_id ucsi_acpi_match[] = { - { "PNP0CA0", 0 }, - { }, -}; -MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); - -static struct platform_driver ucsi_acpi_platform_driver = { - .driver = { - .name = "ucsi_acpi", - .acpi_match_table = ACPI_PTR(ucsi_acpi_match), - }, - .probe = ucsi_acpi_probe, - .remove = ucsi_acpi_remove, -}; - -module_platform_driver(ucsi_acpi_platform_driver); - -MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("USB Type-C System Software Interface (UCSI) driver"); diff --git a/drivers/usb/misc/ucsi.h b/drivers/usb/misc/ucsi.h deleted file mode 100644 index 6dd11d1fe225..000000000000 --- a/drivers/usb/misc/ucsi.h +++ /dev/null @@ -1,215 +0,0 @@ - -#include <linux/types.h> - -/* -------------------------------------------------------------------------- */ - -/* Command Status and Connector Change Indication (CCI) data structure */ -struct ucsi_cci { - unsigned int RESERVED1:1; - unsigned int connector_change:7; - u8 data_length; - unsigned int RESERVED9:9; - unsigned int not_supported:1; - unsigned int cancel_complete:1; - unsigned int reset_complete:1; - unsigned int busy:1; - unsigned int ack_complete:1; - unsigned int error:1; - unsigned int cmd_complete:1; -} __packed; - -/* Default fields in CONTROL data structure */ -struct ucsi_command { - u8 cmd; - u8 length; - u64 data:48; -} __packed; - -/* Set USB Operation Mode Command structure */ -struct ucsi_uor_cmd { - u8 cmd; - u8 length; - u64 con_num:7; - u64 role:3; -#define UCSI_UOR_ROLE_DFP BIT(0) -#define UCSI_UOR_ROLE_UFP BIT(1) -#define UCSI_UOR_ROLE_DRP BIT(2) - u64 data:38; -} __packed; - -struct ucsi_control { - union { - u64 raw_cmd; - struct ucsi_command cmd; - struct ucsi_uor_cmd uor; - }; -}; - -struct ucsi_data { - u16 version; - u16 RESERVED; - union { - u32 raw_cci; - struct ucsi_cci cci; - }; - struct ucsi_control ctrl; - u32 message_in[4]; - u32 message_out[4]; -} __packed; - -/* Commands */ -#define UCSI_PPM_RESET 0x01 -#define UCSI_CANCEL 0x02 -#define UCSI_CONNECTOR_RESET 0x03 -#define UCSI_ACK_CC_CI 0x04 -#define UCSI_SET_NOTIFICATION_ENABLE 0x05 -#define UCSI_GET_CAPABILITY 0x06 -#define UCSI_GET_CONNECTOR_CAPABILITY 0x07 -#define UCSI_SET_UOM 0x08 -#define UCSI_SET_UOR 0x09 -#define UCSI_SET_PDM 0x0A -#define UCSI_SET_PDR 0x0B -#define UCSI_GET_ALTERNATE_MODES 0x0C -#define UCSI_GET_CAM_SUPPORTED 0x0D -#define UCSI_GET_CURRENT_CAM 0x0E -#define UCSI_SET_NEW_CAM 0x0F -#define UCSI_GET_PDOS 0x10 -#define UCSI_GET_CABLE_PROPERTY 0x11 -#define UCSI_GET_CONNECTOR_STATUS 0x12 -#define UCSI_GET_ERROR_STATUS 0x13 - -/* ACK_CC_CI commands */ -#define UCSI_ACK_EVENT 1 -#define UCSI_ACK_CMD 2 - -/* Bits for SET_NOTIFICATION_ENABLE command */ -#define UCSI_ENABLE_NTFY_CMD_COMPLETE BIT(0) -#define UCSI_ENABLE_NTFY_EXT_PWR_SRC_CHANGE BIT(1) -#define UCSI_ENABLE_NTFY_PWR_OPMODE_CHANGE BIT(2) -#define UCSI_ENABLE_NTFY_CAP_CHANGE BIT(5) -#define UCSI_ENABLE_NTFY_PWR_LEVEL_CHANGE BIT(6) -#define UCSI_ENABLE_NTFY_PD_RESET_COMPLETE BIT(7) -#define UCSI_ENABLE_NTFY_CAM_CHANGE BIT(8) -#define UCSI_ENABLE_NTFY_BAT_STATUS_CHANGE BIT(9) -#define UCSI_ENABLE_NTFY_PARTNER_CHANGE BIT(11) -#define UCSI_ENABLE_NTFY_PWR_DIR_CHANGE BIT(12) -#define UCSI_ENABLE_NTFY_CONNECTOR_CHANGE BIT(14) -#define UCSI_ENABLE_NTFY_ERROR BIT(15) -#define UCSI_ENABLE_NTFY_ALL 0xdbe7 - -/* Error information returned by PPM in response to GET_ERROR_STATUS command. */ -#define UCSI_ERROR_UNREGONIZED_CMD BIT(0) -#define UCSI_ERROR_INVALID_CON_NUM BIT(1) -#define UCSI_ERROR_INVALID_CMD_ARGUMENT BIT(2) -#define UCSI_ERROR_INCOMPATIBLE_PARTNER BIT(3) -#define UCSI_ERROR_CC_COMMUNICATION_ERR BIT(4) -#define UCSI_ERROR_DEAD_BATTERY BIT(5) -#define UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL BIT(6) - -/* Data structure filled by PPM in response to GET_CAPABILITY command. */ -struct ucsi_capability { - u32 attributes; -#define UCSI_CAP_ATTR_DISABLE_STATE BIT(0) -#define UCSI_CAP_ATTR_BATTERY_CHARGING BIT(1) -#define UCSI_CAP_ATTR_USB_PD BIT(2) -#define UCSI_CAP_ATTR_TYPEC_CURRENT BIT(6) -#define UCSI_CAP_ATTR_POWER_AC_SUPPLY BIT(8) -#define UCSI_CAP_ATTR_POWER_OTHER BIT(10) -#define UCSI_CAP_ATTR_POWER_VBUS BIT(14) - u8 num_connectors; - u32 features:24; -#define UCSI_CAP_SET_UOM BIT(0) -#define UCSI_CAP_SET_PDM BIT(1) -#define UCSI_CAP_ALT_MODE_DETAILS BIT(2) -#define UCSI_CAP_ALT_MODE_OVERRIDE BIT(3) -#define UCSI_CAP_PDO_DETAILS BIT(4) -#define UCSI_CAP_CABLE_DETAILS BIT(5) -#define UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS BIT(6) -#define UCSI_CAP_PD_RESET BIT(7) - u8 num_alt_modes; - u8 RESERVED; - u16 bc_version; - u16 pd_version; - u16 typec_version; -} __packed; - -/* Data structure filled by PPM in response to GET_CONNECTOR_CAPABILITY cmd. */ -struct ucsi_connector_capability { - u8 op_mode; -#define UCSI_CONCAP_OPMODE_DFP BIT(0) -#define UCSI_CONCAP_OPMODE_UFP BIT(1) -#define UCSI_CONCAP_OPMODE_DRP BIT(2) -#define UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY BIT(3) -#define UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY BIT(4) -#define UCSI_CONCAP_OPMODE_USB2 BIT(5) -#define UCSI_CONCAP_OPMODE_USB3 BIT(6) -#define UCSI_CONCAP_OPMODE_ALT_MODE BIT(7) - u8 provider:1; - u8 consumer:1; -} __packed; - -/* Data structure filled by PPM in response to GET_CABLE_PROPERTY command. */ -struct ucsi_cable_property { - u16 speed_supported; - u8 current_capability; - u8 vbus_in_cable:1; - u8 active_cable:1; - u8 directionality:1; - u8 plug_type:2; -#define UCSI_CABLE_PROPERTY_PLUG_TYPE_A 0 -#define UCSI_CABLE_PROPERTY_PLUG_TYPE_B 1 -#define UCSI_CABLE_PROPERTY_PLUG_TYPE_C 2 -#define UCSI_CABLE_PROPERTY_PLUG_OTHER 3 - u8 mode_support:1; - u8 RESERVED_2:2; - u8 latency:4; - u8 RESERVED_4:4; -} __packed; - -/* Data structure filled by PPM in response to GET_CONNECTOR_STATUS command. */ -struct ucsi_connector_status { - u16 change; -#define UCSI_CONSTAT_EXT_SUPPLY_CHANGE BIT(1) -#define UCSI_CONSTAT_POWER_OPMODE_CHANGE BIT(2) -#define UCSI_CONSTAT_PDOS_CHANGE BIT(5) -#define UCSI_CONSTAT_POWER_LEVEL_CHANGE BIT(6) -#define UCSI_CONSTAT_PD_RESET_COMPLETE BIT(7) -#define UCSI_CONSTAT_CAM_CHANGE BIT(8) -#define UCSI_CONSTAT_BC_CHANGE BIT(9) -#define UCSI_CONSTAT_PARTNER_CHANGE BIT(11) -#define UCSI_CONSTAT_POWER_DIR_CHANGE BIT(12) -#define UCSI_CONSTAT_CONNECT_CHANGE BIT(14) -#define UCSI_CONSTAT_ERROR BIT(15) - u16 pwr_op_mode:3; -#define UCSI_CONSTAT_PWR_OPMODE_NONE 0 -#define UCSI_CONSTAT_PWR_OPMODE_DEFAULT 1 -#define UCSI_CONSTAT_PWR_OPMODE_BC 2 -#define UCSI_CONSTAT_PWR_OPMODE_PD 3 -#define UCSI_CONSTAT_PWR_OPMODE_TYPEC1_3 4 -#define UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0 5 - u16 connected:1; - u16 pwr_dir:1; - u16 partner_flags:8; -#define UCSI_CONSTAT_PARTNER_FLAG_USB BIT(0) -#define UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE BIT(1) - u16 partner_type:3; -#define UCSI_CONSTAT_PARTNER_TYPE_DFP 1 -#define UCSI_CONSTAT_PARTNER_TYPE_UFP 2 -#define UCSI_CONSTAT_PARTNER_TYPE_CABLE_NO_UFP 3 /* Powered Cable */ -#define UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP 4 /* Powered Cable */ -#define UCSI_CONSTAT_PARTNER_TYPE_DEBUG 5 -#define UCSI_CONSTAT_PARTNER_TYPE_AUDIO 6 - u32 request_data_obj; - u8 bc_status:2; -#define UCSI_CONSTAT_BC_NOT_CHARGING 0 -#define UCSI_CONSTAT_BC_NOMINAL_CHARGING 1 -#define UCSI_CONSTAT_BC_SLOW_CHARGING 2 -#define UCSI_CONSTAT_BC_TRICKLE_CHARGING 3 - u8 provider_cap_limit_reason:4; -#define UCSI_CONSTAT_CAP_PWR_LOWERED 0 -#define UCSI_CONSTAT_CAP_PWR_BUDGET_LIMIT 1 - u8 RESERVED:2; -} __packed; - -/* -------------------------------------------------------------------------- */ - diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index da4c5c3d8870..d0c31cee4720 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -21,3 +21,19 @@ config TYPEC_UCSI To compile the driver as a module, choose M here: the module will be called typec_ucsi. + +if TYPEC_UCSI + +config UCSI_ACPI + tristate "UCSI ACPI Interface Driver" + depends on ACPI + help + This driver enables UCSI support on platforms that expose UCSI + interface as ACPI device. On new Intel Atom based platforms starting + from Broxton SoCs and Core platforms stating from Skylake, UCSI is an + ACPI enumerated device. + + To compile the driver as a module, choose M here: the module will be + called ucsi_acpi + +endif diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile index 87dd6ee6c9f3..8372fc22f9b3 100644 --- a/drivers/usb/typec/ucsi/Makefile +++ b/drivers/usb/typec/ucsi/Makefile @@ -5,3 +5,5 @@ obj-$(CONFIG_TYPEC_UCSI) += typec_ucsi.o typec_ucsi-y := ucsi.o typec_ucsi-$(CONFIG_FTRACE) += trace.o + +obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c new file mode 100644 index 000000000000..3fb2e48e1c91 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -0,0 +1,158 @@ +/* + * UCSI ACPI driver + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/acpi.h> + +#include "ucsi.h" + +#define UCSI_DSM_UUID "6f8398c2-7ca4-11e4-ad36-631042b5008f" +#define UCSI_DSM_FUNC_WRITE 1 +#define UCSI_DSM_FUNC_READ 2 + +struct ucsi_acpi { + struct device *dev; + struct ucsi *ucsi; + struct ucsi_ppm ppm; + uuid_le uuid; +}; + +static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func) +{ + union acpi_object *obj; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(ua->dev), ua->uuid.b, 1, func, + NULL); + if (!obj) { + dev_err(ua->dev, "%s: failed to evaluate _DSM %d\n", + __func__, func); + return -EIO; + } + + ACPI_FREE(obj); + return 0; +} + +static int ucsi_acpi_cmd(struct ucsi_ppm *ppm, struct ucsi_control *ctrl) +{ + struct ucsi_acpi *ua = container_of(ppm, struct ucsi_acpi, ppm); + + ppm->data->ctrl.raw_cmd = ctrl->raw_cmd; + + return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_WRITE); +} + +static int ucsi_acpi_sync(struct ucsi_ppm *ppm) +{ + struct ucsi_acpi *ua = container_of(ppm, struct ucsi_acpi, ppm); + + return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); +} + +static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) +{ + struct ucsi_acpi *ua = data; + + ucsi_notify(ua->ucsi); +} + +static int ucsi_acpi_probe(struct platform_device *pdev) +{ + struct ucsi_acpi *ua; + struct resource *res; + acpi_status status; + int ret; + + ua = devm_kzalloc(&pdev->dev, sizeof(*ua), GFP_KERNEL); + if (!ua) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + /* + * NOTE: The memory region for the data structures is used also in an + * operation region, which means ACPI has already reserved it. Therefore + * it can not be requested here, and we can not use + * devm_ioremap_resource(). + */ + ua->ppm.data = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!ua->ppm.data) + return -ENOMEM; + + if (!ua->ppm.data->version) + return -ENODEV; + + ret = uuid_le_to_bin(UCSI_DSM_UUID, &ua->uuid); + if (ret) + return ret; + + ua->ppm.cmd = ucsi_acpi_cmd; + ua->ppm.sync = ucsi_acpi_sync; + ua->dev = &pdev->dev; + + status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), + ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify, ua); + if (ACPI_FAILURE(status)) { + dev_err(&pdev->dev, "failed to install notify handler\n"); + return -ENODEV; + } + + ua->ucsi = ucsi_register_ppm(&pdev->dev, &ua->ppm); + if (IS_ERR(ua->ucsi)) { + acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), + ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify); + return PTR_ERR(ua->ucsi); + } + + platform_set_drvdata(pdev, ua); + + return 0; +} + +static int ucsi_acpi_remove(struct platform_device *pdev) +{ + struct ucsi_acpi *ua = platform_get_drvdata(pdev); + + ucsi_unregister_ppm(ua->ucsi); + + acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify); + + return 0; +} + +static const struct acpi_device_id ucsi_acpi_match[] = { + { "PNP0CA0", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); + +static struct platform_driver ucsi_acpi_platform_driver = { + .driver = { + .name = "ucsi_acpi", + .acpi_match_table = ACPI_PTR(ucsi_acpi_match), + }, + .probe = ucsi_acpi_probe, + .remove = ucsi_acpi_remove, +}; + +module_platform_driver(ucsi_acpi_platform_driver); + +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("UCSI ACPI driver"); |