Commit 8243edf4 authored by Heikki Krogerus's avatar Heikki Krogerus Committed by Greg Kroah-Hartman

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: default avatarHeikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: default avatarGuenter Roeck <linux@roeck-us.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent c1b0bc2d
......@@ -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.
......@@ -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
/*
* 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");
#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;
/* -------------------------------------------------------------------------- */
......@@ -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
......@@ -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
/*
* 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");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment