Merge "usb: typec: ucsi: Optimise ucsi_unregister()"

This commit is contained in:
qctecmdr 2019-11-27 20:15:18 -08:00 • committed by Gerrit - the friendly Code Review server
commit 5ec3e080e9
7 changed files with 637 additions and 754 deletions

View file

@ -48,7 +48,8 @@ struct ucsi_dp {
static int ucsi_displayport_enter(struct typec_altmode *alt)
{
struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
struct ucsi_control ctrl;
struct ucsi *ucsi = dp->con->ucsi;
u64 command;
u8 cur = 0;
int ret;
@ -59,25 +60,21 @@ static int ucsi_displayport_enter(struct typec_altmode *alt)
dev_warn(&p->dev,
"firmware doesn't support alternate mode overriding\n");
mutex_unlock(&dp->con->lock);
return -EOPNOTSUPP;
ret = -EOPNOTSUPP;
goto err_unlock;
}
UCSI_CMD_GET_CURRENT_CAM(ctrl, dp->con->num);
ret = ucsi_send_command(dp->con->ucsi, &ctrl, &cur, sizeof(cur));
command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(dp->con->num);
ret = ucsi_send_command(ucsi, command, &cur, sizeof(cur));
if (ret < 0) {
if (dp->con->ucsi->ppm->data->version > 0x0100) {
mutex_unlock(&dp->con->lock);
return ret;
}
if (ucsi->version > 0x0100)
goto err_unlock;
cur = 0xff;
}
if (cur != 0xff) {
mutex_unlock(&dp->con->lock);
if (dp->con->port_altmode[cur] == alt)
return 0;
return -EBUSY;
ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY;
goto err_unlock;
}
/*
@ -94,16 +91,17 @@ static int ucsi_displayport_enter(struct typec_altmode *alt)
dp->vdo_size = 1;
schedule_work(&dp->work);
ret = 0;
err_unlock:
mutex_unlock(&dp->con->lock);
return 0;
return ret;
}
static int ucsi_displayport_exit(struct typec_altmode *alt)
{
struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
struct ucsi_control ctrl;
u64 command;
int ret = 0;
mutex_lock(&dp->con->lock);
@ -117,8 +115,8 @@ static int ucsi_displayport_exit(struct typec_altmode *alt)
goto out_unlock;
}
ctrl.raw_cmd = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
ret = ucsi_send_command(dp->con->ucsi, &ctrl, NULL, 0);
command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
ret = ucsi_send_command(dp->con->ucsi, command, NULL, 0);
if (ret < 0)
goto out_unlock;
@ -172,14 +170,14 @@ static int ucsi_displayport_status_update(struct ucsi_dp *dp)
static int ucsi_displayport_configure(struct ucsi_dp *dp)
{
u32 pins = DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
struct ucsi_control ctrl;
u64 command;
if (!dp->override)
return 0;
ctrl.raw_cmd = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
return ucsi_send_command(dp->con->ucsi, &ctrl, NULL, 0);
return ucsi_send_command(dp->con->ucsi, command, NULL, 0);
}
static int ucsi_displayport_vdm(struct typec_altmode *alt,

View file

@ -33,17 +33,6 @@ const char *ucsi_cmd_str(u64 raw_cmd)
return ucsi_cmd_strs[(cmd >= ARRAY_SIZE(ucsi_cmd_strs)) ? 0 : cmd];
}
static const char * const ucsi_ack_strs[] = {
[0] = "",
[UCSI_ACK_EVENT] = "event",
[UCSI_ACK_CMD] = "command",
};
const char *ucsi_ack_str(u8 ack)
{
return ucsi_ack_strs[(ack >= ARRAY_SIZE(ucsi_ack_strs)) ? 0 : ack];
}
const char *ucsi_cci_str(u32 cci)
{
if (cci & GENMASK(7, 0)) {

View file

@ -10,54 +10,18 @@
#include <linux/usb/typec_altmode.h>
const char *ucsi_cmd_str(u64 raw_cmd);
const char *ucsi_ack_str(u8 ack);
const char *ucsi_cci_str(u32 cci);
const char *ucsi_recipient_str(u8 recipient);
DECLARE_EVENT_CLASS(ucsi_log_ack,
TP_PROTO(u8 ack),
TP_ARGS(ack),
TP_STRUCT__entry(
__field(u8, ack)
),
TP_fast_assign(
__entry->ack = ack;
),
TP_printk("ACK %s", ucsi_ack_str(__entry->ack))
);
DEFINE_EVENT(ucsi_log_ack, ucsi_ack,
TP_PROTO(u8 ack),
TP_ARGS(ack)
);
DECLARE_EVENT_CLASS(ucsi_log_control,
TP_PROTO(struct ucsi_control *ctrl),
TP_ARGS(ctrl),
TP_STRUCT__entry(
__field(u64, ctrl)
),
TP_fast_assign(
__entry->ctrl = ctrl->raw_cmd;
),
TP_printk("control=%08llx (%s)", __entry->ctrl,
ucsi_cmd_str(__entry->ctrl))
);
DEFINE_EVENT(ucsi_log_control, ucsi_command,
TP_PROTO(struct ucsi_control *ctrl),
TP_ARGS(ctrl)
);
DECLARE_EVENT_CLASS(ucsi_log_command,
TP_PROTO(struct ucsi_control *ctrl, int ret),
TP_ARGS(ctrl, ret),
TP_PROTO(u64 command, int ret),
TP_ARGS(command, ret),
TP_STRUCT__entry(
__field(u64, ctrl)
__field(int, ret)
),
TP_fast_assign(
__entry->ctrl = ctrl->raw_cmd;
__entry->ctrl = command;
__entry->ret = ret;
),
TP_printk("%s -> %s (err=%d)", ucsi_cmd_str(__entry->ctrl),
@ -66,30 +30,13 @@ DECLARE_EVENT_CLASS(ucsi_log_command,
);
DEFINE_EVENT(ucsi_log_command, ucsi_run_command,
TP_PROTO(struct ucsi_control *ctrl, int ret),
TP_ARGS(ctrl, ret)
TP_PROTO(u64 command, int ret),
TP_ARGS(command, ret)
);
DEFINE_EVENT(ucsi_log_command, ucsi_reset_ppm,
TP_PROTO(struct ucsi_control *ctrl, int ret),
TP_ARGS(ctrl, ret)
);
DECLARE_EVENT_CLASS(ucsi_log_cci,
TP_PROTO(u32 cci),
TP_ARGS(cci),
TP_STRUCT__entry(
__field(u32, cci)
),
TP_fast_assign(
__entry->cci = cci;
),
TP_printk("CCI=%08x %s", __entry->cci, ucsi_cci_str(__entry->cci))
);
DEFINE_EVENT(ucsi_log_cci, ucsi_notify,
TP_PROTO(u32 cci),
TP_ARGS(cci)
TP_PROTO(u64 command, int ret),
TP_ARGS(command, ret)
);
DECLARE_EVENT_CLASS(ucsi_log_connector_status,
@ -109,13 +56,13 @@ DECLARE_EVENT_CLASS(ucsi_log_connector_status,
TP_fast_assign(
__entry->port = port - 1;
__entry->change = status->change;
__entry->opmode = status->pwr_op_mode;
__entry->connected = status->connected;
__entry->pwr_dir = status->pwr_dir;
__entry->partner_flags = status->partner_flags;
__entry->partner_type = status->partner_type;
__entry->opmode = UCSI_CONSTAT_PWR_OPMODE(status->flags);
__entry->connected = !!(status->flags & UCSI_CONSTAT_CONNECTED);
__entry->pwr_dir = !!(status->flags & UCSI_CONSTAT_PWR_DIR);
__entry->partner_flags = UCSI_CONSTAT_PARTNER_FLAGS(status->flags);
__entry->partner_type = UCSI_CONSTAT_PARTNER_TYPE(status->flags);
__entry->request_data_obj = status->request_data_obj;
__entry->bc_status = status->bc_status;
__entry->bc_status = UCSI_CONSTAT_BC_STATUS(status->pwr_status);
),
TP_printk("port%d status: change=%04x, opmode=%x, connected=%d, "
"sourcing=%d, partner_flags=%x, partner_type=%x, "

View file

@ -36,169 +36,148 @@
*/
#define UCSI_SWAP_TIMEOUT_MS 5000
static inline int ucsi_sync(struct ucsi *ucsi)
static int ucsi_acknowledge_command(struct ucsi *ucsi)
{
if (ucsi->ppm && ucsi->ppm->sync)
return ucsi->ppm->sync(ucsi->ppm);
return 0;
u64 ctrl;
ctrl = UCSI_ACK_CC_CI;
ctrl |= UCSI_ACK_COMMAND_COMPLETE;
return ucsi->ops->sync_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl));
}
static int ucsi_command(struct ucsi *ucsi, struct ucsi_control *ctrl)
static int ucsi_acknowledge_connector_change(struct ucsi *ucsi)
{
int ret;
u64 ctrl;
trace_ucsi_command(ctrl);
ctrl = UCSI_ACK_CC_CI;
ctrl |= UCSI_ACK_CONNECTOR_CHANGE;
set_bit(COMMAND_PENDING, &ucsi->flags);
ret = ucsi->ppm->cmd(ucsi->ppm, ctrl);
if (ret)
goto err_clear_flag;
if (!wait_for_completion_timeout(&ucsi->complete,
msecs_to_jiffies(UCSI_TIMEOUT_MS))) {
dev_warn(ucsi->dev, "PPM NOT RESPONDING\n");
ret = -ETIMEDOUT;
}
err_clear_flag:
clear_bit(COMMAND_PENDING, &ucsi->flags);
return ret;
return ucsi->ops->async_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl));
}
static int ucsi_ack(struct ucsi *ucsi, u8 ack)
static int ucsi_exec_command(struct ucsi *ucsi, u64 command);
static int ucsi_read_error(struct ucsi *ucsi)
{
struct ucsi_control ctrl;
int ret;
trace_ucsi_ack(ack);
set_bit(ACK_PENDING, &ucsi->flags);
UCSI_CMD_ACK(ctrl, ack);
ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl);
if (ret)
goto out_clear_bit;
/* Waiting for ACK with ACK CMD, but not with EVENT for now */
if (ack == UCSI_ACK_EVENT)
goto out_clear_bit;
if (!wait_for_completion_timeout(&ucsi->complete,
msecs_to_jiffies(UCSI_TIMEOUT_MS)))
ret = -ETIMEDOUT;
out_clear_bit:
clear_bit(ACK_PENDING, &ucsi->flags);
if (ret)
dev_err(ucsi->dev, "%s: failed\n", __func__);
return ret;
}
static int ucsi_run_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
void *data, size_t size)
{
struct ucsi_control _ctrl;
u8 data_length;
u16 error;
int ret;
ret = ucsi_command(ucsi, ctrl);
/* Acknowlege the command that failed */
ret = ucsi_acknowledge_command(ucsi);
if (ret)
goto err;
return ret;
switch (ucsi->status) {
case UCSI_IDLE:
ret = ucsi_sync(ucsi);
if (ret)
dev_warn(ucsi->dev, "%s: sync failed\n", __func__);
ret = ucsi_exec_command(ucsi, UCSI_GET_ERROR_STATUS);
if (ret < 0)
return ret;
if (data)
memcpy(data, ucsi->ppm->data->message_in, size);
ret = ucsi->ops->read(ucsi, UCSI_MESSAGE_IN, &error, sizeof(error));
if (ret)
return ret;
data_length = ucsi->ppm->data->cci.data_length;
ret = ucsi_ack(ucsi, UCSI_ACK_CMD);
if (!ret)
ret = data_length;
switch (error) {
case UCSI_ERROR_INCOMPATIBLE_PARTNER:
return -EOPNOTSUPP;
case UCSI_ERROR_CC_COMMUNICATION_ERR:
return -ECOMM;
case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL:
return -EPROTO;
case UCSI_ERROR_DEAD_BATTERY:
dev_warn(ucsi->dev, "Dead battery condition!\n");
return -EPERM;
case UCSI_ERROR_INVALID_CON_NUM:
case UCSI_ERROR_UNREGONIZED_CMD:
case UCSI_ERROR_INVALID_CMD_ARGUMENT:
dev_err(ucsi->dev, "possible UCSI driver bug %u\n", error);
return -EINVAL;
case UCSI_ERROR_OVERCURRENT:
dev_warn(ucsi->dev, "Overcurrent condition\n");
break;
case UCSI_BUSY:
/* The caller decides whether to cancel or not */
ret = -EBUSY;
case UCSI_ERROR_PARTNER_REJECTED_SWAP:
dev_warn(ucsi->dev, "Partner rejected swap\n");
break;
case UCSI_ERROR:
ret = ucsi_ack(ucsi, UCSI_ACK_CMD);
if (ret)
break;
_ctrl.raw_cmd = 0;
_ctrl.cmd.cmd = UCSI_GET_ERROR_STATUS;
ret = ucsi_command(ucsi, &_ctrl);
if (ret) {
dev_err(ucsi->dev, "reading error failed!\n");
break;
}
memcpy(&error, ucsi->ppm->data->message_in, sizeof(error));
/* Something has really gone wrong */
if (WARN_ON(ucsi->status == UCSI_ERROR)) {
ret = -ENODEV;
break;
}
ret = ucsi_ack(ucsi, UCSI_ACK_CMD);
if (ret)
break;
switch (error) {
case UCSI_ERROR_INCOMPATIBLE_PARTNER:
ret = -EOPNOTSUPP;
break;
case UCSI_ERROR_CC_COMMUNICATION_ERR:
ret = -ECOMM;
break;
case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL:
ret = -EPROTO;
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:
dev_warn(ucsi->dev,
"%s: possible UCSI driver bug - error 0x%x\n",
__func__, error);
ret = -EINVAL;
break;
default:
dev_warn(ucsi->dev,
"%s: error without status\n", __func__);
ret = -EIO;
break;
}
case UCSI_ERROR_HARD_RESET:
dev_warn(ucsi->dev, "Hard reset occurred\n");
break;
case UCSI_ERROR_PPM_POLICY_CONFLICT:
dev_warn(ucsi->dev, "PPM Policy conflict\n");
break;
case UCSI_ERROR_SWAP_REJECTED:
dev_warn(ucsi->dev, "Swap rejected\n");
break;
case UCSI_ERROR_UNDEFINED:
default:
dev_err(ucsi->dev, "unknown error %u\n", error);
break;
}
err:
trace_ucsi_run_command(ctrl, ret);
return ret;
return -EIO;
}
int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
static int ucsi_exec_command(struct ucsi *ucsi, u64 cmd)
{
u32 cci;
int ret;
ret = ucsi->ops->sync_write(ucsi, UCSI_CONTROL, &cmd, sizeof(cmd));
if (ret)
return ret;
ret = ucsi->ops->read(ucsi, UCSI_CCI, &cci, sizeof(cci));
if (ret)
return ret;
if (cci & UCSI_CCI_BUSY)
return -EBUSY;
if (!(cci & UCSI_CCI_COMMAND_COMPLETE))
return -EIO;
if (cci & UCSI_CCI_NOT_SUPPORTED)
return -EOPNOTSUPP;
if (cci & UCSI_CCI_ERROR) {
if (cmd == UCSI_GET_ERROR_STATUS)
return -EIO;
return ucsi_read_error(ucsi);
}
return UCSI_CCI_LENGTH(cci);
}
static int ucsi_run_command(struct ucsi *ucsi, u64 command,
void *data, size_t size)
{
u8 length;
int ret;
ret = ucsi_exec_command(ucsi, command);
if (ret < 0)
return ret;
length = ret;
if (data) {
ret = ucsi->ops->read(ucsi, UCSI_MESSAGE_IN, data, size);
if (ret)
return ret;
}
ret = ucsi_acknowledge_command(ucsi);
if (ret)
return ret;
return length;
}
int ucsi_send_command(struct ucsi *ucsi, u64 command,
void *retval, size_t size)
{
int ret;
mutex_lock(&ucsi->ppm_lock);
ret = ucsi_run_command(ucsi, ctrl, retval, size);
ret = ucsi_run_command(ucsi, command, retval, size);
mutex_unlock(&ucsi->ppm_lock);
return ret;
@ -207,11 +186,12 @@ EXPORT_SYMBOL_GPL(ucsi_send_command);
int ucsi_resume(struct ucsi *ucsi)
{
struct ucsi_control ctrl;
u64 command;
/* Restore UCSI notification enable mask after system resume */
UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_ALL);
return ucsi_send_command(ucsi, &ctrl, NULL, 0);
command = UCSI_SET_NOTIFICATION_ENABLE | UCSI_ENABLE_NTFY_ALL;
return ucsi_send_command(ucsi, command, NULL, 0);
}
EXPORT_SYMBOL_GPL(ucsi_resume);
/* -------------------------------------------------------------------------- */
@ -219,15 +199,15 @@ EXPORT_SYMBOL_GPL(ucsi_resume);
void ucsi_altmode_update_active(struct ucsi_connector *con)
{
const struct typec_altmode *altmode = NULL;
struct ucsi_control ctrl;
u64 command;
int ret;
u8 cur;
int i;
UCSI_CMD_GET_CURRENT_CAM(ctrl, con->num);
ret = ucsi_run_command(con->ucsi, &ctrl, &cur, sizeof(cur));
command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(con->num);
ret = ucsi_run_command(con->ucsi, command, &cur, sizeof(cur));
if (ret < 0) {
if (con->ucsi->ppm->data->version > 0x0100) {
if (con->ucsi->version > 0x0100) {
dev_err(con->ucsi->dev,
"GET_CURRENT_CAM command failed\n");
return;
@ -343,7 +323,7 @@ static int ucsi_register_altmodes(struct ucsi_connector *con, u8 recipient)
int max_altmodes = UCSI_MAX_ALTMODES;
struct typec_altmode_desc desc;
struct ucsi_altmode alt[2];
struct ucsi_control ctrl;
u64 command;
int num = 1;
int ret;
int len;
@ -361,8 +341,11 @@ static int ucsi_register_altmodes(struct ucsi_connector *con, u8 recipient)
for (i = 0; i < max_altmodes;) {
memset(alt, 0, sizeof(alt));
UCSI_CMD_GET_ALTERNATE_MODES(ctrl, recipient, con->num, i, 1);
len = ucsi_run_command(con->ucsi, &ctrl, alt, sizeof(alt));
command = UCSI_GET_ALTERNATE_MODES;
command |= UCSI_GET_ALTMODE_RECIPIENT(recipient);
command |= UCSI_GET_ALTMODE_CONNECTOR_NUMBER(con->num);
command |= UCSI_GET_ALTMODE_OFFSET(i);
len = ucsi_run_command(con->ucsi, command, alt, sizeof(alt));
if (len <= 0)
return len;
@ -424,7 +407,7 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
{
switch (con->status.pwr_op_mode) {
switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
case UCSI_CONSTAT_PWR_OPMODE_PD:
typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD);
break;
@ -442,6 +425,7 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
static int ucsi_register_partner(struct ucsi_connector *con)
{
u8 pwr_opmode = UCSI_CONSTAT_PWR_OPMODE(con->status.flags);
struct typec_partner_desc desc;
struct typec_partner *partner;
@ -450,7 +434,7 @@ static int ucsi_register_partner(struct ucsi_connector *con)
memset(&desc, 0, sizeof(desc));
switch (con->status.partner_type) {
switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) {
case UCSI_CONSTAT_PARTNER_TYPE_DEBUG:
desc.accessory = TYPEC_ACCESSORY_DEBUG;
break;
@ -461,7 +445,7 @@ static int ucsi_register_partner(struct ucsi_connector *con)
break;
}
desc.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD;
desc.usb_pd = pwr_opmode == UCSI_CONSTAT_PWR_OPMODE_PD;
partner = typec_register_partner(con->port, &desc);
if (IS_ERR(partner)) {
@ -493,7 +477,7 @@ static void ucsi_partner_change(struct ucsi_connector *con)
if (!con->partner)
return;
switch (con->status.partner_type) {
switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) {
case UCSI_CONSTAT_PARTNER_TYPE_UFP:
typec_set_data_role(con->port, TYPEC_HOST);
break;
@ -518,29 +502,33 @@ static void ucsi_partner_change(struct ucsi_connector *con)
ucsi_altmode_update_active(con);
}
static void ucsi_connector_change(struct work_struct *work)
static void ucsi_handle_connector_change(struct work_struct *work)
{
struct ucsi_connector *con = container_of(work, struct ucsi_connector,
work);
struct ucsi *ucsi = con->ucsi;
struct ucsi_control ctrl;
enum typec_role role;
u64 command;
int ret;
mutex_lock(&con->lock);
UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);
ret = ucsi_send_command(ucsi, &ctrl, &con->status, sizeof(con->status));
command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num);
ret = ucsi_send_command(ucsi, command, &con->status,
sizeof(con->status));
if (ret < 0) {
dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n",
__func__, ret);
goto out_unlock;
}
role = !!(con->status.flags & UCSI_CONSTAT_PWR_DIR);
if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE)
ucsi_pwr_opmode_change(con);
if (con->status.change & UCSI_CONSTAT_POWER_DIR_CHANGE) {
typec_set_pwr_role(con->port, con->status.pwr_dir);
typec_set_pwr_role(con->port, role);
/* Complete pending power role swap */
if (!completion_done(&con->complete))
@ -548,9 +536,9 @@ static void ucsi_connector_change(struct work_struct *work)
}
if (con->status.change & UCSI_CONSTAT_CONNECT_CHANGE) {
typec_set_pwr_role(con->port, con->status.pwr_dir);
typec_set_pwr_role(con->port, role);
switch (con->status.partner_type) {
switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) {
case UCSI_CONSTAT_PARTNER_TYPE_UFP:
typec_set_data_role(con->port, TYPEC_HOST);
break;
@ -561,7 +549,7 @@ static void ucsi_connector_change(struct work_struct *work)
break;
}
if (con->status.connected)
if (con->status.flags & UCSI_CONSTAT_CONNECTED)
ucsi_register_partner(con);
else
ucsi_unregister_partner(con);
@ -573,14 +561,15 @@ static void ucsi_connector_change(struct work_struct *work)
* Running GET_CAM_SUPPORTED command just to make sure the PPM
* does not get stuck in case it assumes we do so.
*/
UCSI_CMD_GET_CAM_SUPPORTED(ctrl, con->num);
ucsi_run_command(con->ucsi, &ctrl, NULL, 0);
command = UCSI_GET_CAM_SUPPORTED;
command |= UCSI_CONNECTOR_NUMBER(con->num);
ucsi_run_command(con->ucsi, command, NULL, 0);
}
if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE)
ucsi_partner_change(con);
ret = ucsi_ack(ucsi, UCSI_ACK_EVENT);
ret = ucsi_acknowledge_connector_change(ucsi);
if (ret)
dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret);
@ -592,117 +581,83 @@ out_unlock:
}
/**
* ucsi_notify - PPM notification handler
* @ucsi: Source UCSI Interface for the notifications
*
* Handle notifications from PPM of @ucsi.
* ucsi_connector_change - Process Connector Change Event
* @ucsi: UCSI Interface
* @num: Connector number
*/
void ucsi_notify(struct ucsi *ucsi)
void ucsi_connector_change(struct ucsi *ucsi, u8 num)
{
struct ucsi_cci *cci;
struct ucsi_connector *con = &ucsi->connector[num - 1];
/* There is no requirement to sync here, but no harm either. */
ucsi_sync(ucsi);
cci = &ucsi->ppm->data->cci;
if (cci->error)
ucsi->status = UCSI_ERROR;
else if (cci->busy)
ucsi->status = UCSI_BUSY;
else
ucsi->status = UCSI_IDLE;
if (cci->cmd_complete && test_bit(COMMAND_PENDING, &ucsi->flags)) {
complete(&ucsi->complete);
} else if (cci->ack_complete && test_bit(ACK_PENDING, &ucsi->flags)) {
complete(&ucsi->complete);
} else if (cci->connector_change) {
struct ucsi_connector *con;
con = &ucsi->connector[cci->connector_change - 1];
if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags))
schedule_work(&con->work);
}
trace_ucsi_notify(ucsi->ppm->data->raw_cci);
if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags))
schedule_work(&con->work);
}
EXPORT_SYMBOL_GPL(ucsi_notify);
EXPORT_SYMBOL_GPL(ucsi_connector_change);
/* -------------------------------------------------------------------------- */
static int ucsi_reset_connector(struct ucsi_connector *con, bool hard)
{
struct ucsi_control ctrl;
u64 command;
UCSI_CMD_CONNECTOR_RESET(ctrl, con, hard);
command = UCSI_CONNECTOR_RESET | UCSI_CONNECTOR_NUMBER(con->num);
command |= hard ? UCSI_CONNECTOR_RESET_HARD : 0;
return ucsi_send_command(con->ucsi, &ctrl, NULL, 0);
return ucsi_send_command(con->ucsi, command, NULL, 0);
}
static int ucsi_reset_ppm(struct ucsi *ucsi)
{
struct ucsi_control ctrl;
u64 command = UCSI_PPM_RESET;
unsigned long tmo;
u32 cci;
int ret;
ctrl.raw_cmd = 0;
ctrl.cmd.cmd = UCSI_PPM_RESET;
trace_ucsi_command(&ctrl);
ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl);
if (ret)
goto err;
ret = ucsi->ops->async_write(ucsi, UCSI_CONTROL, &command,
sizeof(command));
if (ret < 0)
return ret;
tmo = jiffies + msecs_to_jiffies(UCSI_TIMEOUT_MS);
do {
/* Here sync is critical. */
ret = ucsi_sync(ucsi);
if (ret)
goto err;
if (time_is_before_jiffies(tmo))
return -ETIMEDOUT;
if (ucsi->ppm->data->cci.reset_complete)
break;
ret = ucsi->ops->read(ucsi, UCSI_CCI, &cci, sizeof(cci));
if (ret)
return ret;
/* If the PPM is still doing something else, reset it again. */
if (ucsi->ppm->data->raw_cci) {
dev_warn_ratelimited(ucsi->dev,
"Failed to reset PPM! Trying again..\n");
trace_ucsi_command(&ctrl);
ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl);
if (ret)
goto err;
if (cci & ~UCSI_CCI_RESET_COMPLETE) {
ret = ucsi->ops->async_write(ucsi, UCSI_CONTROL,
&command,
sizeof(command));
if (ret < 0)
return ret;
}
/* Letting the PPM settle down. */
msleep(20);
} while (!(cci & UCSI_CCI_RESET_COMPLETE));
ret = -ETIMEDOUT;
} while (time_is_after_jiffies(tmo));
err:
trace_ucsi_reset_ppm(&ctrl, ret);
return ret;
return 0;
}
static int ucsi_role_cmd(struct ucsi_connector *con, struct ucsi_control *ctrl)
static int ucsi_role_cmd(struct ucsi_connector *con, u64 command)
{
int ret;
ret = ucsi_send_command(con->ucsi, ctrl, NULL, 0);
ret = ucsi_send_command(con->ucsi, command, NULL, 0);
if (ret == -ETIMEDOUT) {
struct ucsi_control c;
u64 c;
/* PPM most likely stopped responding. Resetting everything. */
mutex_lock(&con->ucsi->ppm_lock);
ucsi_reset_ppm(con->ucsi);
mutex_unlock(&con->ucsi->ppm_lock);
UCSI_CMD_SET_NTFY_ENABLE(c, UCSI_ENABLE_NTFY_ALL);
ucsi_send_command(con->ucsi, &c, NULL, 0);
c = UCSI_SET_NOTIFICATION_ENABLE | UCSI_ENABLE_NTFY_ALL;
ucsi_send_command(con->ucsi, c, NULL, 0);
ucsi_reset_connector(con, true);
}
@ -713,7 +668,8 @@ static int ucsi_role_cmd(struct ucsi_connector *con, struct ucsi_control *ctrl)
static int ucsi_dr_swap(struct typec_port *port, enum typec_data_role role)
{
struct ucsi_connector *con = typec_get_drvdata(port);
struct ucsi_control ctrl;
u8 partner_type;
u64 command;
int ret = 0;
mutex_lock(&con->lock);
@ -723,14 +679,17 @@ static int ucsi_dr_swap(struct typec_port *port, enum typec_data_role role)
goto out_unlock;
}
if ((con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_DFP &&
partner_type = UCSI_CONSTAT_PARTNER_TYPE(con->status.flags);
if ((partner_type == UCSI_CONSTAT_PARTNER_TYPE_DFP &&
role == TYPEC_DEVICE) ||
(con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_UFP &&
(partner_type == UCSI_CONSTAT_PARTNER_TYPE_UFP &&
role == TYPEC_HOST))
goto out_unlock;
UCSI_CMD_SET_UOR(ctrl, con, role);
ret = ucsi_role_cmd(con, &ctrl);
command = UCSI_SET_UOR | UCSI_CONNECTOR_NUMBER(con->num);
command |= UCSI_SET_UOR_ROLE(role);
command |= UCSI_SET_UOR_ACCEPT_ROLE_SWAPS;
ret = ucsi_role_cmd(con, command);
if (ret < 0)
goto out_unlock;
@ -747,7 +706,8 @@ out_unlock:
static int ucsi_pr_swap(struct typec_port *port, enum typec_role role)
{
struct ucsi_connector *con = typec_get_drvdata(port);
struct ucsi_control ctrl;
enum typec_role cur_role;
u64 command;
int ret = 0;
mutex_lock(&con->lock);
@ -757,11 +717,15 @@ static int ucsi_pr_swap(struct typec_port *port, enum typec_role role)
goto out_unlock;
}
if (con->status.pwr_dir == role)
cur_role = !!(con->status.flags & UCSI_CONSTAT_PWR_DIR);
if (cur_role == role)
goto out_unlock;
UCSI_CMD_SET_PDR(ctrl, con, role);
ret = ucsi_role_cmd(con, &ctrl);
command = UCSI_SET_PDR | UCSI_CONNECTOR_NUMBER(con->num);
command |= UCSI_SET_PDR_ROLE(role);
command |= UCSI_SET_PDR_ACCEPT_ROLE_SWAPS;
ret = ucsi_role_cmd(con, command);
if (ret < 0)
goto out_unlock;
@ -772,7 +736,8 @@ static int ucsi_pr_swap(struct typec_port *port, enum typec_role role)
}
/* Something has gone wrong while swapping the role */
if (con->status.pwr_op_mode != UCSI_CONSTAT_PWR_OPMODE_PD) {
if (UCSI_CONSTAT_PWR_OPMODE(con->status.flags) !=
UCSI_CONSTAT_PWR_OPMODE_PD) {
ucsi_reset_connector(con, true);
ret = -EPROTO;
}
@ -804,18 +769,19 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
struct ucsi_connector *con = &ucsi->connector[index];
struct typec_capability *cap = &con->typec_cap;
enum typec_accessory *accessory = cap->accessory;
struct ucsi_control ctrl;
u64 command;
int ret;
INIT_WORK(&con->work, ucsi_connector_change);
INIT_WORK(&con->work, ucsi_handle_connector_change);
init_completion(&con->complete);
mutex_init(&con->lock);
con->num = index + 1;
con->ucsi = ucsi;
/* Get connector capability */
UCSI_CMD_GET_CONNECTOR_CAPABILITY(ctrl, con->num);
ret = ucsi_run_command(ucsi, &ctrl, &con->cap, sizeof(con->cap));
command = UCSI_GET_CONNECTOR_CAPABILITY;
command |= UCSI_CONNECTOR_NUMBER(con->num);
ret = ucsi_run_command(ucsi, command, &con->cap, sizeof(con->cap));
if (ret < 0)
return ret;
@ -826,11 +792,12 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_UFP)
cap->data = TYPEC_PORT_UFP;
if (con->cap.provider && con->cap.consumer)
if ((con->cap.flags & UCSI_CONCAP_FLAG_PROVIDER) &&
(con->cap.flags & UCSI_CONCAP_FLAG_CONSUMER))
cap->type = TYPEC_PORT_DRP;
else if (con->cap.provider)
else if (con->cap.flags & UCSI_CONCAP_FLAG_PROVIDER)
cap->type = TYPEC_PORT_SRC;
else if (con->cap.consumer)
else if (con->cap.flags & UCSI_CONCAP_FLAG_CONSUMER)
cap->type = TYPEC_PORT_SNK;
cap->revision = ucsi->cap.typec_version;
@ -858,17 +825,15 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
con->num);
/* Get the status */
UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);
ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status));
command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num);
ret = ucsi_run_command(ucsi, command, &con->status,
sizeof(con->status));
if (ret < 0) {
dev_err(ucsi->dev, "con%d: failed to get status\n", con->num);
return 0;
}
ucsi_pwr_opmode_change(con);
typec_set_pwr_role(con->port, con->status.pwr_dir);
switch (con->status.partner_type) {
switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) {
case UCSI_CONSTAT_PARTNER_TYPE_UFP:
typec_set_data_role(con->port, TYPEC_HOST);
break;
@ -880,8 +845,12 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
}
/* Check if there is already something connected */
if (con->status.connected)
if (con->status.flags & UCSI_CONSTAT_CONNECTED) {
typec_set_pwr_role(con->port,
!!(con->status.flags & UCSI_CONSTAT_PWR_DIR));
ucsi_pwr_opmode_change(con);
ucsi_register_partner(con);
}
if (con->partner) {
ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP);
@ -898,11 +867,16 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
return 0;
}
static void ucsi_init(struct work_struct *work)
/**
* ucsi_init - Initialize UCSI interface
* @ucsi: UCSI to be initialized
*
* Registers all ports @ucsi has and enables all notification events.
*/
int ucsi_init(struct ucsi *ucsi)
{
struct ucsi *ucsi = container_of(work, struct ucsi, work);
struct ucsi_connector *con;
struct ucsi_control ctrl;
u64 command;
int ret;
int i;
@ -916,15 +890,15 @@ static void ucsi_init(struct work_struct *work)
}
/* Enable basic notifications */
UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE |
UCSI_ENABLE_NTFY_ERROR);
ret = ucsi_run_command(ucsi, &ctrl, NULL, 0);
command = UCSI_SET_NOTIFICATION_ENABLE;
command |= UCSI_ENABLE_NTFY_CMD_COMPLETE | UCSI_ENABLE_NTFY_ERROR;
ret = ucsi_run_command(ucsi, command, NULL, 0);
if (ret < 0)
goto err_reset;
/* Get PPM capabilities */
UCSI_CMD_GET_CAPABILITY(ctrl);
ret = ucsi_run_command(ucsi, &ctrl, &ucsi->cap, sizeof(ucsi->cap));
command = UCSI_GET_CAPABILITY;
ret = ucsi_run_command(ucsi, command, &ucsi->cap, sizeof(ucsi->cap));
if (ret < 0)
goto err_reset;
@ -949,14 +923,14 @@ static void ucsi_init(struct work_struct *work)
}
/* Enable all notifications */
UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_ALL);
ret = ucsi_run_command(ucsi, &ctrl, NULL, 0);
command = UCSI_SET_NOTIFICATION_ENABLE | UCSI_ENABLE_NTFY_ALL;
ret = ucsi_run_command(ucsi, command, NULL, 0);
if (ret < 0)
goto err_unregister;
mutex_unlock(&ucsi->ppm_lock);
return;
return 0;
err_unregister:
for (con = ucsi->connector; con->port; con++) {
@ -970,59 +944,115 @@ err_reset:
ucsi_reset_ppm(ucsi);
err:
mutex_unlock(&ucsi->ppm_lock);
dev_err(ucsi->dev, "PPM init failed (%d)\n", ret);
return ret;
}
EXPORT_SYMBOL_GPL(ucsi_init);
static void ucsi_init_work(struct work_struct *work)
{
struct ucsi *ucsi = container_of(work, struct ucsi, work);
int ret;
ret = ucsi_init(ucsi);
if (ret)
dev_err(ucsi->dev, "PPM init failed (%d)\n", ret);
}
/**
* ucsi_register_ppm - Register UCSI PPM Interface
* @dev: Device interface to the PPM
* @ppm: The PPM interface
*
* Allocates UCSI instance, associates it with @ppm and returns it to the
* caller, and schedules initialization of the interface.
* ucsi_get_drvdata - Return private driver data pointer
* @ucsi: UCSI interface
*/
struct ucsi *ucsi_register_ppm(struct device *dev, struct ucsi_ppm *ppm)
void *ucsi_get_drvdata(struct ucsi *ucsi)
{
return ucsi->driver_data;
}
EXPORT_SYMBOL_GPL(ucsi_get_drvdata);
/**
* ucsi_get_drvdata - Assign private driver data pointer
* @ucsi: UCSI interface
* @data: Private data pointer
*/
void ucsi_set_drvdata(struct ucsi *ucsi, void *data)
{
ucsi->driver_data = data;
}
EXPORT_SYMBOL_GPL(ucsi_set_drvdata);
/**
* ucsi_create - Allocate UCSI instance
* @dev: Device interface to the PPM (Platform Policy Manager)
* @ops: I/O routines
*/
struct ucsi *ucsi_create(struct device *dev, const struct ucsi_operations *ops)
{
struct ucsi *ucsi;
if (!ops || !ops->read || !ops->sync_write || !ops->async_write)
return ERR_PTR(-EINVAL);
ucsi = kzalloc(sizeof(*ucsi), GFP_KERNEL);
if (!ucsi)
return ERR_PTR(-ENOMEM);
INIT_WORK(&ucsi->work, ucsi_init);
init_completion(&ucsi->complete);
INIT_WORK(&ucsi->work, ucsi_init_work);
mutex_init(&ucsi->ppm_lock);
ucsi->dev = dev;
ucsi->ppm = ppm;
/*
* Communication with the PPM takes a lot of time. It is not reasonable
* to initialize the driver here. Using a work for now.
*/
queue_work(system_long_wq, &ucsi->work);
ucsi->ops = ops;
return ucsi;
}
EXPORT_SYMBOL_GPL(ucsi_register_ppm);
EXPORT_SYMBOL_GPL(ucsi_create);
/**
* ucsi_unregister_ppm - Unregister UCSI PPM Interface
* @ucsi: struct ucsi associated with the PPM
*
* Unregister UCSI PPM that was created with ucsi_register().
* ucsi_destroy - Free UCSI instance
* @ucsi: UCSI instance to be freed
*/
void ucsi_unregister_ppm(struct ucsi *ucsi)
void ucsi_destroy(struct ucsi *ucsi)
{
struct ucsi_control ctrl;
kfree(ucsi);
}
EXPORT_SYMBOL_GPL(ucsi_destroy);
/**
* ucsi_register - Register UCSI interface
* @ucsi: UCSI instance
*/
int ucsi_register(struct ucsi *ucsi)
{
int ret;
ret = ucsi->ops->read(ucsi, UCSI_VERSION, &ucsi->version,
sizeof(ucsi->version));
if (ret)
return ret;
if (!ucsi->version)
return -ENODEV;
queue_work(system_long_wq, &ucsi->work);
return 0;
}
EXPORT_SYMBOL_GPL(ucsi_register);
/**
* ucsi_unregister - Unregister UCSI interface
* @ucsi: UCSI interface to be unregistered
*
* Unregister UCSI interface that was created with ucsi_register().
*/
void ucsi_unregister(struct ucsi *ucsi)
{
u64 cmd = UCSI_SET_NOTIFICATION_ENABLE;
int i;
/* Make sure that we are not in the middle of driver initialization */
cancel_work_sync(&ucsi->work);
/* Disable everything except command complete notification */
UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE)
ucsi_send_command(ucsi, &ctrl, NULL, 0);
/* Disable notifications */
ucsi->ops->async_write(ucsi, UCSI_CONTROL, &cmd, sizeof(cmd));
for (i = 0; i < ucsi->cap.num_connectors; i++) {
cancel_work_sync(&ucsi->connector[i].work);
@ -1032,12 +1062,9 @@ void ucsi_unregister_ppm(struct ucsi *ucsi)
typec_unregister_port(ucsi->connector[i].port);
}
ucsi_reset_ppm(ucsi);
kfree(ucsi->connector);
kfree(ucsi);
}
EXPORT_SYMBOL_GPL(ucsi_unregister_ppm);
EXPORT_SYMBOL_GPL(ucsi_unregister);
MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
MODULE_LICENSE("GPL v2");

View file

@ -10,177 +10,55 @@
/* -------------------------------------------------------------------------- */
/* Command Status and Connector Change Indication (CCI) data structure */
struct ucsi_cci {
u8:1; /* reserved */
u8 connector_change:7;
u8 data_length;
u16:9; /* reserved */
u16 not_supported:1;
u16 cancel_complete:1;
u16 reset_complete:1;
u16 busy:1;
u16 ack_complete:1;
u16 error:1;
u16 cmd_complete:1;
} __packed;
struct ucsi;
/* Default fields in CONTROL data structure */
struct ucsi_command {
u8 cmd;
u8 length;
u64 data:48;
} __packed;
/* UCSI offsets (Bytes) */
#define UCSI_VERSION 0
#define UCSI_CCI 4
#define UCSI_CONTROL 8
#define UCSI_MESSAGE_IN 16
#define UCSI_MESSAGE_OUT 32
/* ACK Command structure */
struct ucsi_ack_cmd {
u8 cmd;
u8 length;
u8 cci_ack:1;
u8 cmd_ack:1;
u8:6; /* reserved */
} __packed;
/* Command Status and Connector Change Indication (CCI) bits */
#define UCSI_CCI_CONNECTOR(_c_) (((_c_) & GENMASK(7, 0)) >> 1)
#define UCSI_CCI_LENGTH(_c_) (((_c_) & GENMASK(15, 8)) >> 8)
#define UCSI_CCI_NOT_SUPPORTED BIT(25)
#define UCSI_CCI_CANCEL_COMPLETE BIT(26)
#define UCSI_CCI_RESET_COMPLETE BIT(27)
#define UCSI_CCI_BUSY BIT(28)
#define UCSI_CCI_ACK_COMPLETE BIT(29)
#define UCSI_CCI_ERROR BIT(30)
#define UCSI_CCI_COMMAND_COMPLETE BIT(31)
/* Connector Reset Command structure */
struct ucsi_con_rst {
u8 cmd;
u8 length;
u8 con_num:7;
u8 hard_reset:1;
} __packed;
/* Set USB Operation Mode Command structure */
struct ucsi_uor_cmd {
u8 cmd;
u8 length;
u16 con_num:7;
u16 role:3;
#define UCSI_UOR_ROLE_DFP BIT(0)
#define UCSI_UOR_ROLE_UFP BIT(1)
#define UCSI_UOR_ROLE_DRP BIT(2)
u16:6; /* reserved */
} __packed;
/* Get Alternate Modes Command structure */
struct ucsi_altmode_cmd {
u8 cmd;
u8 length;
u8 recipient;
#define UCSI_RECIPIENT_CON 0
#define UCSI_RECIPIENT_SOP 1
#define UCSI_RECIPIENT_SOP_P 2
#define UCSI_RECIPIENT_SOP_PP 3
u8 con_num;
u8 offset;
u8 num_altmodes;
} __packed;
struct ucsi_control {
union {
u64 raw_cmd;
struct ucsi_command cmd;
struct ucsi_uor_cmd uor;
struct ucsi_ack_cmd ack;
struct ucsi_con_rst con_rst;
struct ucsi_altmode_cmd alt;
};
/**
* struct ucsi_operations - UCSI I/O operations
* @read: Read operation
* @sync_write: Blocking write operation
* @async_write: Non-blocking write operation
*
* Read and write routines for UCSI interface. @sync_write must wait for the
* Command Completion Event from the PPM before returning, and @async_write must
* return immediately after sending the data to the PPM.
*/
struct ucsi_operations {
int (*read)(struct ucsi *ucsi, unsigned int offset,
void *val, size_t val_len);
int (*sync_write)(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len);
int (*async_write)(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len);
};
#define __UCSI_CMD(_ctrl_, _cmd_) \
{ \
(_ctrl_).raw_cmd = 0; \
(_ctrl_).cmd.cmd = _cmd_; \
}
struct ucsi *ucsi_create(struct device *dev, const struct ucsi_operations *ops);
void ucsi_destroy(struct ucsi *ucsi);
int ucsi_register(struct ucsi *ucsi);
void ucsi_unregister(struct ucsi *ucsi);
void *ucsi_get_drvdata(struct ucsi *ucsi);
void ucsi_set_drvdata(struct ucsi *ucsi, void *data);
/* Helper for preparing ucsi_control for CONNECTOR_RESET command. */
#define UCSI_CMD_CONNECTOR_RESET(_ctrl_, _con_, _hard_) \
{ \
__UCSI_CMD(_ctrl_, UCSI_CONNECTOR_RESET) \
(_ctrl_).con_rst.con_num = (_con_)->num; \
(_ctrl_).con_rst.hard_reset = _hard_; \
}
void ucsi_connector_change(struct ucsi *ucsi, u8 num);
/* Helper for preparing ucsi_control for ACK_CC_CI command. */
#define UCSI_CMD_ACK(_ctrl_, _ack_) \
{ \
__UCSI_CMD(_ctrl_, UCSI_ACK_CC_CI) \
(_ctrl_).ack.cci_ack = ((_ack_) == UCSI_ACK_EVENT); \
(_ctrl_).ack.cmd_ack = ((_ack_) == UCSI_ACK_CMD); \
}
/* Helper for preparing ucsi_control for SET_NOTIFY_ENABLE command. */
#define UCSI_CMD_SET_NTFY_ENABLE(_ctrl_, _ntfys_) \
{ \
__UCSI_CMD(_ctrl_, UCSI_SET_NOTIFICATION_ENABLE) \
(_ctrl_).cmd.data = _ntfys_; \
}
/* Helper for preparing ucsi_control for GET_CAPABILITY command. */
#define UCSI_CMD_GET_CAPABILITY(_ctrl_) \
{ \
__UCSI_CMD(_ctrl_, UCSI_GET_CAPABILITY) \
}
/* Helper for preparing ucsi_control for GET_CONNECTOR_CAPABILITY command. */
#define UCSI_CMD_GET_CONNECTOR_CAPABILITY(_ctrl_, _con_) \
{ \
__UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_CAPABILITY) \
(_ctrl_).cmd.data = _con_; \
}
/* Helper for preparing ucsi_control for GET_ALTERNATE_MODES command. */
#define UCSI_CMD_GET_ALTERNATE_MODES(_ctrl_, _r_, _con_num_, _o_, _num_)\
{ \
__UCSI_CMD((_ctrl_), UCSI_GET_ALTERNATE_MODES) \
_ctrl_.alt.recipient = (_r_); \
_ctrl_.alt.con_num = (_con_num_); \
_ctrl_.alt.offset = (_o_); \
_ctrl_.alt.num_altmodes = (_num_) - 1; \
}
/* Helper for preparing ucsi_control for GET_CAM_SUPPORTED command. */
#define UCSI_CMD_GET_CAM_SUPPORTED(_ctrl_, _con_) \
{ \
__UCSI_CMD((_ctrl_), UCSI_GET_CAM_SUPPORTED) \
_ctrl_.cmd.data = (_con_); \
}
/* Helper for preparing ucsi_control for GET_CAM_SUPPORTED command. */
#define UCSI_CMD_GET_CURRENT_CAM(_ctrl_, _con_) \
{ \
__UCSI_CMD((_ctrl_), UCSI_GET_CURRENT_CAM) \
_ctrl_.cmd.data = (_con_); \
}
/* Helper for preparing ucsi_control for GET_CONNECTOR_STATUS command. */
#define UCSI_CMD_GET_CONNECTOR_STATUS(_ctrl_, _con_) \
{ \
__UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_STATUS) \
(_ctrl_).cmd.data = _con_; \
}
#define __UCSI_ROLE(_ctrl_, _cmd_, _con_num_) \
{ \
__UCSI_CMD(_ctrl_, _cmd_) \
(_ctrl_).uor.con_num = _con_num_; \
(_ctrl_).uor.role = UCSI_UOR_ROLE_DRP; \
}
/* Helper for preparing ucsi_control for SET_UOR command. */
#define UCSI_CMD_SET_UOR(_ctrl_, _con_, _role_) \
{ \
__UCSI_ROLE(_ctrl_, UCSI_SET_UOR, (_con_)->num) \
(_ctrl_).uor.role |= (_role_) == TYPEC_HOST ? UCSI_UOR_ROLE_DFP : \
UCSI_UOR_ROLE_UFP; \
}
/* Helper for preparing ucsi_control for SET_PDR command. */
#define UCSI_CMD_SET_PDR(_ctrl_, _con_, _role_) \
{ \
__UCSI_ROLE(_ctrl_, UCSI_SET_PDR, (_con_)->num) \
(_ctrl_).uor.role |= (_role_) == TYPEC_SOURCE ? UCSI_UOR_ROLE_DFP : \
UCSI_UOR_ROLE_UFP; \
}
/* -------------------------------------------------------------------------- */
/* Commands */
#define UCSI_PPM_RESET 0x01
@ -203,24 +81,49 @@ struct ucsi_control {
#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
#define UCSI_CONNECTOR_NUMBER(_num_) ((u64)(_num_) << 16)
/* 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
/* CONNECTOR_RESET command bits */
#define UCSI_CONNECTOR_RESET_HARD BIT(23) /* Deprecated in v1.1 */
/* ACK_CC_CI bits */
#define UCSI_ACK_CONNECTOR_CHANGE BIT(16)
#define UCSI_ACK_COMMAND_COMPLETE BIT(17)
/* SET_NOTIFICATION_ENABLE command bits */
#define UCSI_ENABLE_NTFY_CMD_COMPLETE BIT(16)
#define UCSI_ENABLE_NTFY_EXT_PWR_SRC_CHANGE BIT(17)
#define UCSI_ENABLE_NTFY_PWR_OPMODE_CHANGE BIT(18)
#define UCSI_ENABLE_NTFY_CAP_CHANGE BIT(19)
#define UCSI_ENABLE_NTFY_PWR_LEVEL_CHANGE BIT(20)
#define UCSI_ENABLE_NTFY_PD_RESET_COMPLETE BIT(21)
#define UCSI_ENABLE_NTFY_CAM_CHANGE BIT(22)
#define UCSI_ENABLE_NTFY_BAT_STATUS_CHANGE BIT(23)
#define UCSI_ENABLE_NTFY_PARTNER_CHANGE BIT(24)
#define UCSI_ENABLE_NTFY_PWR_DIR_CHANGE BIT(25)
#define UCSI_ENABLE_NTFY_CONNECTOR_CHANGE BIT(26)
#define UCSI_ENABLE_NTFY_ERROR BIT(27)
#define UCSI_ENABLE_NTFY_ALL 0xdbe70000
/* SET_UOR command bits */
#define UCSI_SET_UOR_ROLE(_r_) (((_r_) == TYPEC_HOST ? 1 : 2) << 23)
#define UCSI_SET_UOR_ACCEPT_ROLE_SWAPS BIT(25)
/* SET_PDF command bits */
#define UCSI_SET_PDR_ROLE(_r_) (((_r_) == TYPEC_SOURCE ? 1 : 2) << 23)
#define UCSI_SET_PDR_ACCEPT_ROLE_SWAPS BIT(25)
/* GET_ALTERNATE_MODES command bits */
#define UCSI_GET_ALTMODE_RECIPIENT(_r_) ((u64)(_r_) << 16)
#define UCSI_RECIPIENT_CON 0
#define UCSI_RECIPIENT_SOP 1
#define UCSI_RECIPIENT_SOP_P 2
#define UCSI_RECIPIENT_SOP_PP 3
#define UCSI_GET_ALTMODE_CONNECTOR_NUMBER(_r_) ((u64)(_r_) << 24)
#define UCSI_GET_ALTMODE_OFFSET(_r_) ((u64)(_r_) << 32)
#define UCSI_GET_ALTMODE_NUM_ALTMODES(_r_) ((u64)(_r_) << 40)
/* -------------------------------------------------------------------------- */
/* Error information returned by PPM in response to GET_ERROR_STATUS command. */
#define UCSI_ERROR_UNREGONIZED_CMD BIT(0)
@ -230,6 +133,12 @@ struct ucsi_control {
#define UCSI_ERROR_CC_COMMUNICATION_ERR BIT(4)
#define UCSI_ERROR_DEAD_BATTERY BIT(5)
#define UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL BIT(6)
#define UCSI_ERROR_OVERCURRENT BIT(7)
#define UCSI_ERROR_UNDEFINED BIT(8)
#define UCSI_ERROR_PARTNER_REJECTED_SWAP BIT(9)
#define UCSI_ERROR_HARD_RESET BIT(10)
#define UCSI_ERROR_PPM_POLICY_CONFLICT BIT(11)
#define UCSI_ERROR_SWAP_REJECTED BIT(12)
/* Data structure filled by PPM in response to GET_CAPABILITY command. */
struct ucsi_capability {
@ -241,8 +150,8 @@ struct ucsi_capability {
#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)
u32 num_connectors:8;
u32 features:24;
u8 num_connectors;
u8 features;
#define UCSI_CAP_SET_UOM BIT(0)
#define UCSI_CAP_SET_PDM BIT(1)
#define UCSI_CAP_ALT_MODE_DETAILS BIT(2)
@ -251,8 +160,9 @@ struct ucsi_capability {
#define UCSI_CAP_CABLE_DETAILS BIT(5)
#define UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS BIT(6)
#define UCSI_CAP_PD_RESET BIT(7)
u16 reserved_1;
u8 num_alt_modes;
u8 reserved;
u8 reserved_2;
u16 bc_version;
u16 pd_version;
u16 typec_version;
@ -269,9 +179,9 @@ struct ucsi_connector_capability {
#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;
u8:6; /* reserved */
u8 flags;
#define UCSI_CONCAP_FLAG_PROVIDER BIT(0)
#define UCSI_CONCAP_FLAG_CONSUMER BIT(1)
} __packed;
struct ucsi_altmode {
@ -283,18 +193,17 @@ struct ucsi_altmode {
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:2; /* reserved */
u8 latency:4;
u8:4; /* reserved */
u8 flags;
#define UCSI_CABLE_PROP_FLAG_VBUS_IN_CABLE BIT(0)
#define UCSI_CABLE_PROP_FLAG_ACTIVE_CABLE BIT(1)
#define UCSI_CABLE_PROP_FLAG_DIRECTIONALITY BIT(2)
#define UCSI_CABLE_PROP_FLAG_PLUG_TYPE(_f_) ((_f_) & GENMASK(3, 0))
#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
#define UCSI_CABLE_PROP_MODE_SUPPORT BIT(5)
u8 latency;
} __packed;
/* Data structure filled by PPM in response to GET_CONNECTOR_STATUS command. */
@ -311,83 +220,47 @@ struct ucsi_connector_status {
#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_5 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 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
u16 flags;
#define UCSI_CONSTAT_PWR_OPMODE(_f_) ((_f_) & GENMASK(2, 0))
#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_5 4
#define UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0 5
#define UCSI_CONSTAT_CONNECTED BIT(3)
#define UCSI_CONSTAT_PWR_DIR BIT(4)
#define UCSI_CONSTAT_PARTNER_FLAGS(_f_) (((_f_) & GENMASK(12, 5)) >> 5)
#define UCSI_CONSTAT_PARTNER_FLAG_USB 1
#define UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE 2
#define UCSI_CONSTAT_PARTNER_TYPE(_f_) (((_f_) & GENMASK(15, 13)) >> 13)
#define UCSI_CONSTAT_PARTNER_TYPE_DFP 1
#define UCSI_CONSTAT_PARTNER_TYPE_UFP 2
#define UCSI_CONSTAT_PARTNER_TYPE_CABLE 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:2; /* reserved */
u8 pwr_status;
#define UCSI_CONSTAT_BC_STATUS(_p_) ((_p_) & GENMASK(2, 0))
#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
#define UCSI_CONSTAT_PROVIDER_CAP_LIMIT(_p_) (((_p_) & GENMASK(6, 3)) >> 3)
#define UCSI_CONSTAT_CAP_PWR_LOWERED 0
#define UCSI_CONSTAT_CAP_PWR_BUDGET_LIMIT 1
} __packed;
/* -------------------------------------------------------------------------- */
struct ucsi;
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;
/*
* struct ucsi_ppm - Interface to UCSI Platform Policy Manager
* @data: memory location to the UCSI data structures
* @cmd: UCSI command execution routine
* @sync: Refresh UCSI mailbox (the data structures)
*/
struct ucsi_ppm {
struct ucsi_data *data;
int (*cmd)(struct ucsi_ppm *, struct ucsi_control *);
int (*sync)(struct ucsi_ppm *);
};
struct ucsi *ucsi_register_ppm(struct device *dev, struct ucsi_ppm *ppm);
void ucsi_unregister_ppm(struct ucsi *ucsi);
void ucsi_notify(struct ucsi *ucsi);
/* -------------------------------------------------------------------------- */
enum ucsi_status {
UCSI_IDLE = 0,
UCSI_BUSY,
UCSI_ERROR,
};
struct ucsi {
u16 version;
struct device *dev;
struct ucsi_ppm *ppm;
struct driver_data *driver_data;
const struct ucsi_operations *ops;
enum ucsi_status status;
struct completion complete;
struct ucsi_capability cap;
struct ucsi_connector *connector;
@ -426,7 +299,7 @@ struct ucsi_connector {
struct ucsi_connector_capability cap;
};
int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
int ucsi_send_command(struct ucsi *ucsi, u64 command,
void *retval, size_t size);
void ucsi_altmode_update_active(struct ucsi_connector *con);

View file

@ -19,7 +19,9 @@
struct ucsi_acpi {
struct device *dev;
struct ucsi *ucsi;
struct ucsi_ppm ppm;
void __iomem *base;
struct completion complete;
unsigned long flags;
guid_t guid;
};
@ -39,27 +41,73 @@ static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func)
return 0;
}
static int ucsi_acpi_cmd(struct ucsi_ppm *ppm, struct ucsi_control *ctrl)
static int ucsi_acpi_read(struct ucsi *ucsi, unsigned int offset,
void *val, size_t val_len)
{
struct ucsi_acpi *ua = container_of(ppm, struct ucsi_acpi, ppm);
struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
int ret;
ppm->data->ctrl.raw_cmd = ctrl->raw_cmd;
ret = ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ);
if (ret)
return ret;
memcpy(val, (const void __force *)(ua->base + offset), val_len);
return 0;
}
static int ucsi_acpi_async_write(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len)
{
struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
memcpy((void __force *)(ua->base + offset), val, val_len);
return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_WRITE);
}
static int ucsi_acpi_sync(struct ucsi_ppm *ppm)
static int ucsi_acpi_sync_write(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len)
{
struct ucsi_acpi *ua = container_of(ppm, struct ucsi_acpi, ppm);
struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
int ret;
return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ);
set_bit(COMMAND_PENDING, &ua->flags);
ret = ucsi_acpi_async_write(ucsi, offset, val, val_len);
if (ret)
goto out_clear_bit;
if (!wait_for_completion_timeout(&ua->complete, msecs_to_jiffies(5000)))
ret = -ETIMEDOUT;
out_clear_bit:
clear_bit(COMMAND_PENDING, &ua->flags);
return ret;
}
static const struct ucsi_operations ucsi_acpi_ops = {
.read = ucsi_acpi_read,
.sync_write = ucsi_acpi_sync_write,
.async_write = ucsi_acpi_async_write
};
static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data)
{
struct ucsi_acpi *ua = data;
u32 cci;
int ret;
ucsi_notify(ua->ucsi);
ret = ucsi_acpi_read(ua->ucsi, UCSI_CCI, &cci, sizeof(cci));
if (ret)
return;
if (test_bit(COMMAND_PENDING, &ua->flags) &&
cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE))
complete(&ua->complete);
else if (UCSI_CCI_CONNECTOR(cci))
ucsi_connector_change(ua->ucsi, UCSI_CCI_CONNECTOR(cci));
}
static int ucsi_acpi_probe(struct platform_device *pdev)
@ -90,35 +138,39 @@ static int ucsi_acpi_probe(struct platform_device *pdev)
* 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)
ua->base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!ua->base)
return -ENOMEM;
if (!ua->ppm.data->version)
return -ENODEV;
ret = guid_parse(UCSI_DSM_UUID, &ua->guid);
if (ret)
return ret;
ua->ppm.cmd = ucsi_acpi_cmd;
ua->ppm.sync = ucsi_acpi_sync;
init_completion(&ua->complete);
ua->dev = &pdev->dev;
ua->ucsi = ucsi_create(&pdev->dev, &ucsi_acpi_ops);
if (IS_ERR(ua->ucsi))
return PTR_ERR(ua->ucsi);
ucsi_set_drvdata(ua->ucsi, ua);
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");
ucsi_destroy(ua->ucsi);
return -ENODEV;
}
ua->ucsi = ucsi_register_ppm(&pdev->dev, &ua->ppm);
if (IS_ERR(ua->ucsi)) {
ret = ucsi_register(ua->ucsi);
if (ret) {
acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev),
ACPI_DEVICE_NOTIFY,
ucsi_acpi_notify);
return PTR_ERR(ua->ucsi);
ucsi_destroy(ua->ucsi);
return ret;
}
platform_set_drvdata(pdev, ua);
@ -130,7 +182,8 @@ static int ucsi_acpi_remove(struct platform_device *pdev)
{
struct ucsi_acpi *ua = platform_get_drvdata(pdev);
ucsi_unregister_ppm(ua->ucsi);
ucsi_unregister(ua->ucsi);
ucsi_destroy(ua->ucsi);
acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY,
ucsi_acpi_notify);

View file

@ -176,8 +176,8 @@ struct ccg_resp {
struct ucsi_ccg {
struct device *dev;
struct ucsi *ucsi;
struct ucsi_ppm ppm;
struct i2c_client *client;
struct ccg_dev_info info;
/* version info for boot, primary and secondary */
struct version_info version[FW2 + 1];
@ -196,6 +196,8 @@ struct ucsi_ccg {
/* fw build with vendor information */
u16 fw_build;
struct work_struct pm_work;
struct completion complete;
};
static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len)
@ -243,7 +245,7 @@ static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len)
return 0;
}
static int ccg_write(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len)
static int ccg_write(struct ucsi_ccg *uc, u16 rab, const u8 *data, u32 len)
{
struct i2c_client *client = uc->client;
unsigned char *buf;
@ -317,88 +319,85 @@ static int ucsi_ccg_init(struct ucsi_ccg *uc)
return -ETIMEDOUT;
}
static int ucsi_ccg_send_data(struct ucsi_ccg *uc)
static int ucsi_ccg_read(struct ucsi *ucsi, unsigned int offset,
void *val, size_t val_len)
{
u8 *ppm = (u8 *)uc->ppm.data;
int status;
u16 rab;
u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(offset);
rab = CCGX_RAB_UCSI_DATA_BLOCK(offsetof(struct ucsi_data, message_out));
status = ccg_write(uc, rab, ppm +
offsetof(struct ucsi_data, message_out),
sizeof(uc->ppm.data->message_out));
if (status < 0)
return status;
rab = CCGX_RAB_UCSI_DATA_BLOCK(offsetof(struct ucsi_data, ctrl));
return ccg_write(uc, rab, ppm + offsetof(struct ucsi_data, ctrl),
sizeof(uc->ppm.data->ctrl));
return ccg_read(ucsi_get_drvdata(ucsi), reg, val, val_len);
}
static int ucsi_ccg_recv_data(struct ucsi_ccg *uc)
static int ucsi_ccg_async_write(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len)
{
u8 *ppm = (u8 *)uc->ppm.data;
int status;
u16 rab;
u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(offset);
rab = CCGX_RAB_UCSI_DATA_BLOCK(offsetof(struct ucsi_data, cci));
status = ccg_read(uc, rab, ppm + offsetof(struct ucsi_data, cci),
sizeof(uc->ppm.data->cci));
if (status < 0)
return status;
rab = CCGX_RAB_UCSI_DATA_BLOCK(offsetof(struct ucsi_data, message_in));
return ccg_read(uc, rab, ppm + offsetof(struct ucsi_data, message_in),
sizeof(uc->ppm.data->message_in));
return ccg_write(ucsi_get_drvdata(ucsi), reg, val, val_len);
}
static int ucsi_ccg_ack_interrupt(struct ucsi_ccg *uc)
static int ucsi_ccg_sync_write(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len)
{
int status;
unsigned char data;
struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi);
int ret;
status = ccg_read(uc, CCGX_RAB_INTR_REG, &data, sizeof(data));
if (status < 0)
return status;
mutex_lock(&uc->lock);
pm_runtime_get_sync(uc->dev);
set_bit(DEV_CMD_PENDING, &uc->flags);
return ccg_write(uc, CCGX_RAB_INTR_REG, &data, sizeof(data));
ret = ucsi_ccg_async_write(ucsi, offset, val, val_len);
if (ret)
goto err_clear_bit;
if (!wait_for_completion_timeout(&uc->complete, msecs_to_jiffies(5000)))
ret = -ETIMEDOUT;
err_clear_bit:
clear_bit(DEV_CMD_PENDING, &uc->flags);
pm_runtime_put_sync(uc->dev);
mutex_unlock(&uc->lock);
return ret;
}
static int ucsi_ccg_sync(struct ucsi_ppm *ppm)
{
struct ucsi_ccg *uc = container_of(ppm, struct ucsi_ccg, ppm);
int status;
status = ucsi_ccg_recv_data(uc);
if (status < 0)
return status;
/* ack interrupt to allow next command to run */
return ucsi_ccg_ack_interrupt(uc);
}
static int ucsi_ccg_cmd(struct ucsi_ppm *ppm, struct ucsi_control *ctrl)
{
struct ucsi_ccg *uc = container_of(ppm, struct ucsi_ccg, ppm);
ppm->data->ctrl.raw_cmd = ctrl->raw_cmd;
return ucsi_ccg_send_data(uc);
}
static const struct ucsi_operations ucsi_ccg_ops = {
.read = ucsi_ccg_read,
.sync_write = ucsi_ccg_sync_write,
.async_write = ucsi_ccg_async_write
};
static irqreturn_t ccg_irq_handler(int irq, void *data)
{
u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CCI);
struct ucsi_ccg *uc = data;
u8 intr_reg;
u32 cci;
int ret;
ucsi_notify(uc->ucsi);
ret = ccg_read(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg));
if (ret)
return ret;
ret = ccg_read(uc, reg, (void *)&cci, sizeof(cci));
if (ret)
goto err_clear_irq;
if (UCSI_CCI_CONNECTOR(cci))
ucsi_connector_change(uc->ucsi, UCSI_CCI_CONNECTOR(cci));
if (test_bit(DEV_CMD_PENDING, &uc->flags) &&
cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE))
complete(&uc->complete);
err_clear_irq:
ccg_write(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg));
return IRQ_HANDLED;
}
static void ccg_pm_workaround_work(struct work_struct *pm_work)
{
struct ucsi_ccg *uc = container_of(pm_work, struct ucsi_ccg, pm_work);
ucsi_notify(uc->ucsi);
ccg_irq_handler(0, container_of(pm_work, struct ucsi_ccg, pm_work));
}
static int get_fw_info(struct ucsi_ccg *uc)
@ -1027,10 +1026,10 @@ static int ccg_restart(struct ucsi_ccg *uc)
return status;
}
uc->ucsi = ucsi_register_ppm(dev, &uc->ppm);
if (IS_ERR(uc->ucsi)) {
dev_err(uc->dev, "ucsi_register_ppm failed\n");
return PTR_ERR(uc->ucsi);
status = ucsi_register(uc->ucsi);
if (status) {
dev_err(uc->dev, "failed to register the interface\n");
return status;
}
return 0;
@ -1047,7 +1046,7 @@ static void ccg_update_firmware(struct work_struct *work)
return;
if (flash_mode != FLASH_NOT_NEEDED) {
ucsi_unregister_ppm(uc->ucsi);
ucsi_unregister(uc->ucsi);
free_irq(uc->irq, uc);
ccg_fw_update(uc, flash_mode);
@ -1091,21 +1090,15 @@ static int ucsi_ccg_probe(struct i2c_client *client,
struct device *dev = &client->dev;
struct ucsi_ccg *uc;
int status;
u16 rab;
uc = devm_kzalloc(dev, sizeof(*uc), GFP_KERNEL);
if (!uc)
return -ENOMEM;
uc->ppm.data = devm_kzalloc(dev, sizeof(struct ucsi_data), GFP_KERNEL);
if (!uc->ppm.data)
return -ENOMEM;
uc->ppm.cmd = ucsi_ccg_cmd;
uc->ppm.sync = ucsi_ccg_sync;
uc->dev = dev;
uc->client = client;
mutex_init(&uc->lock);
init_completion(&uc->complete);
INIT_WORK(&uc->work, ccg_update_firmware);
INIT_WORK(&uc->pm_work, ccg_pm_workaround_work);
@ -1133,30 +1126,25 @@ static int ucsi_ccg_probe(struct i2c_client *client,
if (uc->info.mode & CCG_DEVINFO_PDPORTS_MASK)
uc->port_num++;
uc->ucsi = ucsi_create(dev, &ucsi_ccg_ops);
if (IS_ERR(uc->ucsi))
return PTR_ERR(uc->ucsi);
ucsi_set_drvdata(uc->ucsi, uc);
status = request_threaded_irq(client->irq, NULL, ccg_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
dev_name(dev), uc);
if (status < 0) {
dev_err(uc->dev, "request_threaded_irq failed - %d\n", status);
return status;
goto out_ucsi_destroy;
}
uc->irq = client->irq;
uc->ucsi = ucsi_register_ppm(dev, &uc->ppm);
if (IS_ERR(uc->ucsi)) {
dev_err(uc->dev, "ucsi_register_ppm failed\n");
return PTR_ERR(uc->ucsi);
}
rab = CCGX_RAB_UCSI_DATA_BLOCK(offsetof(struct ucsi_data, version));
status = ccg_read(uc, rab, (u8 *)(uc->ppm.data) +
offsetof(struct ucsi_data, version),
sizeof(uc->ppm.data->version));
if (status < 0) {
ucsi_unregister_ppm(uc->ucsi);
return status;
}
status = ucsi_register(uc->ucsi);
if (status)
goto out_free_irq;
i2c_set_clientdata(client, uc);
@ -1167,6 +1155,13 @@ static int ucsi_ccg_probe(struct i2c_client *client,
pm_runtime_idle(uc->dev);
return 0;
out_free_irq:
free_irq(uc->irq, uc);
out_ucsi_destroy:
ucsi_destroy(uc->ucsi);
return status;
}
static int ucsi_ccg_remove(struct i2c_client *client)
@ -1175,8 +1170,9 @@ static int ucsi_ccg_remove(struct i2c_client *client)
cancel_work_sync(&uc->pm_work);
cancel_work_sync(&uc->work);
ucsi_unregister_ppm(uc->ucsi);
pm_runtime_disable(uc->dev);
ucsi_unregister(uc->ucsi);
ucsi_destroy(uc->ucsi);
free_irq(uc->irq, uc);
return 0;