diff mbox series

[RFC] usb: typec: ucsi: add support for separate DP altmode devices

Message ID 20190628054547.26764-1-ajayg@nvidia.com (mailing list archive)
State Superseded
Headers show
Series [RFC] usb: typec: ucsi: add support for separate DP altmode devices | expand

Commit Message

Ajay Gupta June 28, 2019, 5:45 a.m. UTC
CCGx controller used on NVIDIA GPU card has two separate display
altmode for two DP pin assignments. UCSI specification doesn't
prohibits using separate display altmode.

Current UCSI Type-C framework expects only one display altmode for
all DP pin assignment. This patch squashes two separate display
altmode into single altmode to support controllers with separate
display altmode. We first read all the alternate modes of connector
and then run through it to know if there are separate display
altmodes. If so, it prepares a new port altmode set after squashing
two or more separate altmodes into one.

Signed-off-by: Ajay Gupta <ajayg@nvidia.com>
---
 drivers/usb/typec/ucsi/ucsi.c | 191 +++++++++++++++++++++++++++++++++-
 drivers/usb/typec/ucsi/ucsi.h |  12 +++
 2 files changed, 199 insertions(+), 4 deletions(-)

Comments

Heikki Krogerus June 28, 2019, 10:46 a.m. UTC | #1
On Thu, Jun 27, 2019 at 10:45:47PM -0700, Ajay Gupta wrote:
> CCGx controller used on NVIDIA GPU card has two separate display
> altmode for two DP pin assignments. UCSI specification doesn't
> prohibits using separate display altmode.
> 
> Current UCSI Type-C framework expects only one display altmode for
> all DP pin assignment. This patch squashes two separate display
> altmode into single altmode to support controllers with separate
> display altmode. We first read all the alternate modes of connector
> and then run through it to know if there are separate display
> altmodes. If so, it prepares a new port altmode set after squashing
> two or more separate altmodes into one.

I don't have any better ideas how to solve this at them moment.
Therefore, I don't have a problem with going forward with this. But
please note, that we are already at -rc6, so nothing is going to
happen for a while. I'll away for the next four weeks.

> Signed-off-by: Ajay Gupta <ajayg@nvidia.com>
> ---
>  drivers/usb/typec/ucsi/ucsi.c | 191 +++++++++++++++++++++++++++++++++-
>  drivers/usb/typec/ucsi/ucsi.h |  12 +++
>  2 files changed, 199 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
> index ba288b964dc8..491b5c925a90 100644
> --- a/drivers/usb/typec/ucsi/ucsi.c
> +++ b/drivers/usb/typec/ucsi/ucsi.c
> @@ -39,8 +39,27 @@
>   */
>  #define UCSI_SWAP_TIMEOUT_MS	5000
>  
> +static void ucsi_update_get_current_cam_cmd(struct ucsi_connector *con,
> +					    struct ucsi_data *data)
> +{
> +	u8 cam, new_cam;
> +
> +	if (data->cci.data_length == 0x1) {
> +		cam = data->message_in[0];
> +		new_cam = con->port_alt[cam].linked_idx;
> +		data->message_in[0] = new_cam;
> +		con->new_port_alt[new_cam].active_idx = cam;
> +	}
> +}
> +
>  static inline int ucsi_sync(struct ucsi *ucsi)
>  {
> +	struct ucsi_connector *con = ucsi->connector;
> +	struct ucsi_data *data = ucsi->ppm->data;
> +
> +	if (data->ctrl.alt.cmd == UCSI_GET_CURRENT_CAM && con->has_multiple_dp)
> +		ucsi_update_get_current_cam_cmd(con, data);
> +
>  	if (ucsi->ppm && ucsi->ppm->sync)
>  		return ucsi->ppm->sync(ucsi->ppm);
>  	return 0;
> @@ -101,14 +120,57 @@ static int ucsi_ack(struct ucsi *ucsi, u8 ack)
>  	return ret;
>  }
>  
> +static void ucsi_update_set_new_cam_cmd(struct ucsi_connector *con,
> +					struct ucsi_control *ctrl)
> +{
> +	struct new_ucsi_altmode *new_port, *port;
> +	struct typec_altmode *partner = NULL;
> +	u64 cmd;
> +	u8 new_cam, cam;
> +	bool enter_new_mode;
> +	int i, j;
> +
> +	cmd = ctrl->raw_cmd;
> +	new_cam = (cmd >> 24) & 0xff;
> +	new_port = &con->new_port_alt[new_cam];
> +	cam = new_port->linked_idx;
> +	enter_new_mode = (cmd >> 23) & 1;
> +
> +	if (cam == UCSI_MULTI_LINKED_INDEX) {
> +		if (enter_new_mode) {
> +			port = con->port_alt;
> +			for (i = 0; con->partner_altmode[i]; i++) {
> +				partner = con->partner_altmode[i];
> +				if (partner->svid == new_port->svid)
> +					break;
> +			}
> +			for (j = 0; port[j].svid; j++)
> +				if (partner && port[j].svid == partner->svid &&
> +				    port[j].mid & partner->vdo)
> +					break;
> +			cam = j;
> +			new_port->active_idx = cam;
> +		} else {
> +			cam = new_port->active_idx;
> +		}
> +	}
> +	cmd &= ~(0xff << 24);
> +	cmd |= (cam << 24);
> +	ctrl->raw_cmd = cmd;
> +}
> +
>  static int ucsi_run_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
>  			    void *data, size_t size)
>  {
>  	struct ucsi_control _ctrl;
> +	struct ucsi_connector *con = ucsi->connector;
>  	u8 data_length;
>  	u16 error;
>  	int ret;
>  
> +	if (ctrl->alt.cmd == UCSI_SET_NEW_CAM && con->has_multiple_dp)
> +		ucsi_update_set_new_cam_cmd(con, ctrl);
> +
>  	ret = ucsi_command(ucsi, ctrl);
>  	if (ret)
>  		goto err;
> @@ -364,10 +426,24 @@ 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));
> -		if (len <= 0)
> -			return len;
> +
> +		if (recipient == UCSI_RECIPIENT_CON) {
> +			if (con->has_multiple_dp) {
> +				alt[0].svid = con->new_port_alt[i].svid;
> +				alt[0].mid = con->new_port_alt[i].mid;
> +			} else {
> +				alt[0].svid = con->port_alt[i].svid;
> +				alt[0].mid = con->port_alt[i].mid;
> +			}
> +			len = sizeof(alt[0]);
> +		} else {
> +			UCSI_CMD_GET_ALTERNATE_MODES(ctrl, recipient,
> +						     con->num, i, 1);
> +			len = ucsi_run_command(con->ucsi, &ctrl, alt,
> +					       sizeof(alt));
> +			if (len <= 0)
> +				return len;
> +		}
>  
>  		/*
>  		 * This code is requesting one alt mode at a time, but some PPMs
> @@ -521,6 +597,103 @@ static void ucsi_partner_change(struct ucsi_connector *con)
>  		ucsi_altmode_update_active(con);
>  }
>  
> +static void ucsi_update_con_altmodes(struct ucsi_connector *con)
> +{
> +	int max_altmodes = con->ucsi->cap.num_alt_modes;
> +	struct new_ucsi_altmode *alt, *new_alt;
> +	int i, j, k = 0;
> +	bool found = false;
> +
> +	alt = con->port_alt;
> +	new_alt = con->new_port_alt;
> +	memset(con->new_port_alt, 0, sizeof(con->new_port_alt));
> +
> +	for (i = 0; i < max_altmodes; i++) {
> +		if (!alt[i].svid)
> +			break;
> +
> +		/* already checked and considered */
> +		if (alt[i].checked)
> +			continue;
> +
> +		if (!DP_CONF_GET_PIN_ASSIGN(alt[i].mid)) {
> +			/* Found Non DP altmode */
> +			new_alt[k].svid = alt[i].svid;
> +			new_alt[k].mid |= alt[i].mid;
> +			new_alt[k].linked_idx = i;
> +			alt[i].linked_idx = k;
> +			k++;
> +			continue;
> +		}
> +
> +		for (j = i + 1; j < max_altmodes; j++) {
> +			if (alt[i].svid != alt[j].svid ||
> +			    !DP_CONF_GET_PIN_ASSIGN(alt[j].mid)) {
> +				continue;
> +			} else {
> +				/* Found duplicate DP mode */
> +				new_alt[k].svid = alt[i].svid;
> +				new_alt[k].mid |= alt[i].mid | alt[j].mid;
> +				new_alt[k].linked_idx = UCSI_MULTI_LINKED_INDEX;
> +				alt[i].linked_idx = k;
> +				alt[j].linked_idx = k;
> +				alt[j].checked = true;
> +				found = true;
> +			}
> +		}
> +		if (found) {
> +			con->has_multiple_dp = true;
> +		} else {
> +			/* Didn't find any duplicate DP altmode */
> +			new_alt[k].svid = alt[i].svid;
> +			new_alt[k].mid |= alt[i].mid;
> +			new_alt[k].linked_idx = i;
> +			alt[i].linked_idx = k;
> +		}
> +		k++;
> +	}
> +}
> +
> +static int ucsi_get_altmodes(struct ucsi_connector *con)
> +{
> +	int max_altmodes = con->ucsi->cap.num_alt_modes;
> +	struct ucsi_altmode alt[2];
> +	struct ucsi_control ctrl;
> +	int num = 1;
> +	int len;
> +	int j;
> +	int i;
> +	int k = 0;
> +
> +	memset(con->port_alt, 0, sizeof(con->port_alt));
> +
> +	for (i = 0; i < max_altmodes;) {
> +		memset(alt, 0, sizeof(alt));
> +		UCSI_CMD_GET_ALTERNATE_MODES(ctrl, UCSI_RECIPIENT_CON,
> +					     con->num, i, 1);
> +		len = ucsi_run_command(con->ucsi, &ctrl, alt, sizeof(alt));
> +		if (len <= 0)
> +			return len;
> +
> +		/*
> +		 * This code is requesting one alt mode at a time, but some PPMs
> +		 * may still return two.
> +		 */
> +		num = len / sizeof(alt[0]);
> +		i += num;
> +
> +		for (j = 0; j < num; j++) {
> +			if (!alt[j].svid)
> +				return 0;
> +
> +			con->port_alt[k].mid = alt[j].mid;
> +			con->port_alt[k].svid = alt[j].svid;
> +			k++;
> +		}
> +	}
> +	return 0;
> +}
> +
>  static void ucsi_connector_change(struct work_struct *work)
>  {
>  	struct ucsi_connector *con = container_of(work, struct ucsi_connector,
> @@ -851,6 +1024,16 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
>  	if (IS_ERR(con->port))
>  		return PTR_ERR(con->port);
>  
> +	/* Get Alternate modes */
> +	ret = ucsi_get_altmodes(con);
> +	if (ret) {
> +		dev_err(ucsi->dev,
> +			"%s: con%d failed (%d)to get port alternate modes\n",
> +			__func__, con->num, ret);
> +		return 0;
> +	}
> +	ucsi_update_con_altmodes(con);
> +
>  	/* Alternate modes */
>  	ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_CON);
>  	if (ret)
> diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
> index de87d0b8319d..7bbdf83c8d4a 100644
> --- a/drivers/usb/typec/ucsi/ucsi.h
> +++ b/drivers/usb/typec/ucsi/ucsi.h
> @@ -274,6 +274,15 @@ struct ucsi_connector_capability {
>  	u8:6; /* reserved */
>  } __packed;
>  
> +struct new_ucsi_altmode {
> +	u16 svid;
> +	u32 mid;
> +	u8 linked_idx;
> +	u8 active_idx;
> +#define UCSI_MULTI_LINKED_INDEX	(0xff)
> +	bool checked;
> +} __packed;
> +
>  struct ucsi_altmode {
>  	u16 svid;
>  	u32 mid;
> @@ -408,6 +417,7 @@ struct ucsi {
>  
>  struct ucsi_connector {
>  	int num;
> +	bool has_multiple_dp;
>  
>  	struct ucsi *ucsi;
>  	struct mutex lock; /* port lock */
> @@ -424,6 +434,8 @@ struct ucsi_connector {
>  
>  	struct ucsi_connector_status status;
>  	struct ucsi_connector_capability cap;
> +	struct new_ucsi_altmode port_alt[UCSI_MAX_ALTMODES];
> +	struct new_ucsi_altmode new_port_alt[UCSI_MAX_ALTMODES];
>  };
>  
>  int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
> -- 
> 2.17.1

thanks,
Ajay Gupta June 28, 2019, 11:20 a.m. UTC | #2
Hi Heikki,

> On Jun 28, 2019, at 3:47 AM, Heikki Krogerus <heikki.krogerus@linux.intel.com> wrote:
> 
>> On Thu, Jun 27, 2019 at 10:45:47PM -0700, Ajay Gupta wrote:
>> CCGx controller used on NVIDIA GPU card has two separate display
>> altmode for two DP pin assignments. UCSI specification doesn't
>> prohibits using separate display altmode.
>> 
>> Current UCSI Type-C framework expects only one display altmode for
>> all DP pin assignment. This patch squashes two separate display
>> altmode into single altmode to support controllers with separate
>> display altmode. We first read all the alternate modes of connector
>> and then run through it to know if there are separate display
>> altmodes. If so, it prepares a new port altmode set after squashing
>> two or more separate altmodes into one.
> 
> I don't have any better ideas how to solve this at them moment.
> Therefore, I don't have a problem with going forward with this.

Thanks, please review the change and provide any comment. I will anyways repost this as a patch.

Thanks 
> nvpublic 
> But
> please note, that we are already at -rc6, so nothing is going to
> happen for a while. I'll away for the next four weeks.
> 
>> Signed-off-by: Ajay Gupta <ajayg@nvidia.com>
>> ---
>> drivers/usb/typec/ucsi/ucsi.c | 191 +++++++++++++++++++++++++++++++++-
>> drivers/usb/typec/ucsi/ucsi.h |  12 +++
>> 2 files changed, 199 insertions(+), 4 deletions(-)
>> 
>> diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
>> index ba288b964dc8..491b5c925a90 100644
>> --- a/drivers/usb/typec/ucsi/ucsi.c
>> +++ b/drivers/usb/typec/ucsi/ucsi.c
>> @@ -39,8 +39,27 @@
>>  */
>> #define UCSI_SWAP_TIMEOUT_MS    5000
>> 
>> +static void ucsi_update_get_current_cam_cmd(struct ucsi_connector *con,
>> +                        struct ucsi_data *data)
>> +{
>> +    u8 cam, new_cam;
>> +
>> +    if (data->cci.data_length == 0x1) {
>> +        cam = data->message_in[0];
>> +        new_cam = con->port_alt[cam].linked_idx;
>> +        data->message_in[0] = new_cam;
>> +        con->new_port_alt[new_cam].active_idx = cam;
>> +    }
>> +}
>> +
>> static inline int ucsi_sync(struct ucsi *ucsi)
>> {
>> +    struct ucsi_connector *con = ucsi->connector;
>> +    struct ucsi_data *data = ucsi->ppm->data;
>> +
>> +    if (data->ctrl.alt.cmd == UCSI_GET_CURRENT_CAM && con->has_multiple_dp)
>> +        ucsi_update_get_current_cam_cmd(con, data);
>> +
>>    if (ucsi->ppm && ucsi->ppm->sync)
>>        return ucsi->ppm->sync(ucsi->ppm);
>>    return 0;
>> @@ -101,14 +120,57 @@ static int ucsi_ack(struct ucsi *ucsi, u8 ack)
>>    return ret;
>> }
>> 
>> +static void ucsi_update_set_new_cam_cmd(struct ucsi_connector *con,
>> +                    struct ucsi_control *ctrl)
>> +{
>> +    struct new_ucsi_altmode *new_port, *port;
>> +    struct typec_altmode *partner = NULL;
>> +    u64 cmd;
>> +    u8 new_cam, cam;
>> +    bool enter_new_mode;
>> +    int i, j;
>> +
>> +    cmd = ctrl->raw_cmd;
>> +    new_cam = (cmd >> 24) & 0xff;
>> +    new_port = &con->new_port_alt[new_cam];
>> +    cam = new_port->linked_idx;
>> +    enter_new_mode = (cmd >> 23) & 1;
>> +
>> +    if (cam == UCSI_MULTI_LINKED_INDEX) {
>> +        if (enter_new_mode) {
>> +            port = con->port_alt;
>> +            for (i = 0; con->partner_altmode[i]; i++) {
>> +                partner = con->partner_altmode[i];
>> +                if (partner->svid == new_port->svid)
>> +                    break;
>> +            }
>> +            for (j = 0; port[j].svid; j++)
>> +                if (partner && port[j].svid == partner->svid &&
>> +                    port[j].mid & partner->vdo)
>> +                    break;
>> +            cam = j;
>> +            new_port->active_idx = cam;
>> +        } else {
>> +            cam = new_port->active_idx;
>> +        }
>> +    }
>> +    cmd &= ~(0xff << 24);
>> +    cmd |= (cam << 24);
>> +    ctrl->raw_cmd = cmd;
>> +}
>> +
>> static int ucsi_run_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
>>                void *data, size_t size)
>> {
>>    struct ucsi_control _ctrl;
>> +    struct ucsi_connector *con = ucsi->connector;
>>    u8 data_length;
>>    u16 error;
>>    int ret;
>> 
>> +    if (ctrl->alt.cmd == UCSI_SET_NEW_CAM && con->has_multiple_dp)
>> +        ucsi_update_set_new_cam_cmd(con, ctrl);
>> +
>>    ret = ucsi_command(ucsi, ctrl);
>>    if (ret)
>>        goto err;
>> @@ -364,10 +426,24 @@ 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));
>> -        if (len <= 0)
>> -            return len;
>> +
>> +        if (recipient == UCSI_RECIPIENT_CON) {
>> +            if (con->has_multiple_dp) {
>> +                alt[0].svid = con->new_port_alt[i].svid;
>> +                alt[0].mid = con->new_port_alt[i].mid;
>> +            } else {
>> +                alt[0].svid = con->port_alt[i].svid;
>> +                alt[0].mid = con->port_alt[i].mid;
>> +            }
>> +            len = sizeof(alt[0]);
>> +        } else {
>> +            UCSI_CMD_GET_ALTERNATE_MODES(ctrl, recipient,
>> +                             con->num, i, 1);
>> +            len = ucsi_run_command(con->ucsi, &ctrl, alt,
>> +                           sizeof(alt));
>> +            if (len <= 0)
>> +                return len;
>> +        }
>> 
>>        /*
>>         * This code is requesting one alt mode at a time, but some PPMs
>> @@ -521,6 +597,103 @@ static void ucsi_partner_change(struct ucsi_connector *con)
>>        ucsi_altmode_update_active(con);
>> }
>> 
>> +static void ucsi_update_con_altmodes(struct ucsi_connector *con)
>> +{
>> +    int max_altmodes = con->ucsi->cap.num_alt_modes;
>> +    struct new_ucsi_altmode *alt, *new_alt;
>> +    int i, j, k = 0;
>> +    bool found = false;
>> +
>> +    alt = con->port_alt;
>> +    new_alt = con->new_port_alt;
>> +    memset(con->new_port_alt, 0, sizeof(con->new_port_alt));
>> +
>> +    for (i = 0; i < max_altmodes; i++) {
>> +        if (!alt[i].svid)
>> +            break;
>> +
>> +        /* already checked and considered */
>> +        if (alt[i].checked)
>> +            continue;
>> +
>> +        if (!DP_CONF_GET_PIN_ASSIGN(alt[i].mid)) {
>> +            /* Found Non DP altmode */
>> +            new_alt[k].svid = alt[i].svid;
>> +            new_alt[k].mid |= alt[i].mid;
>> +            new_alt[k].linked_idx = i;
>> +            alt[i].linked_idx = k;
>> +            k++;
>> +            continue;
>> +        }
>> +
>> +        for (j = i + 1; j < max_altmodes; j++) {
>> +            if (alt[i].svid != alt[j].svid ||
>> +                !DP_CONF_GET_PIN_ASSIGN(alt[j].mid)) {
>> +                continue;
>> +            } else {
>> +                /* Found duplicate DP mode */
>> +                new_alt[k].svid = alt[i].svid;
>> +                new_alt[k].mid |= alt[i].mid | alt[j].mid;
>> +                new_alt[k].linked_idx = UCSI_MULTI_LINKED_INDEX;
>> +                alt[i].linked_idx = k;
>> +                alt[j].linked_idx = k;
>> +                alt[j].checked = true;
>> +                found = true;
>> +            }
>> +        }
>> +        if (found) {
>> +            con->has_multiple_dp = true;
>> +        } else {
>> +            /* Didn't find any duplicate DP altmode */
>> +            new_alt[k].svid = alt[i].svid;
>> +            new_alt[k].mid |= alt[i].mid;
>> +            new_alt[k].linked_idx = i;
>> +            alt[i].linked_idx = k;
>> +        }
>> +        k++;
>> +    }
>> +}
>> +
>> +static int ucsi_get_altmodes(struct ucsi_connector *con)
>> +{
>> +    int max_altmodes = con->ucsi->cap.num_alt_modes;
>> +    struct ucsi_altmode alt[2];
>> +    struct ucsi_control ctrl;
>> +    int num = 1;
>> +    int len;
>> +    int j;
>> +    int i;
>> +    int k = 0;
>> +
>> +    memset(con->port_alt, 0, sizeof(con->port_alt));
>> +
>> +    for (i = 0; i < max_altmodes;) {
>> +        memset(alt, 0, sizeof(alt));
>> +        UCSI_CMD_GET_ALTERNATE_MODES(ctrl, UCSI_RECIPIENT_CON,
>> +                         con->num, i, 1);
>> +        len = ucsi_run_command(con->ucsi, &ctrl, alt, sizeof(alt));
>> +        if (len <= 0)
>> +            return len;
>> +
>> +        /*
>> +         * This code is requesting one alt mode at a time, but some PPMs
>> +         * may still return two.
>> +         */
>> +        num = len / sizeof(alt[0]);
>> +        i += num;
>> +
>> +        for (j = 0; j < num; j++) {
>> +            if (!alt[j].svid)
>> +                return 0;
>> +
>> +            con->port_alt[k].mid = alt[j].mid;
>> +            con->port_alt[k].svid = alt[j].svid;
>> +            k++;
>> +        }
>> +    }
>> +    return 0;
>> +}
>> +
>> static void ucsi_connector_change(struct work_struct *work)
>> {
>>    struct ucsi_connector *con = container_of(work, struct ucsi_connector,
>> @@ -851,6 +1024,16 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
>>    if (IS_ERR(con->port))
>>        return PTR_ERR(con->port);
>> 
>> +    /* Get Alternate modes */
>> +    ret = ucsi_get_altmodes(con);
>> +    if (ret) {
>> +        dev_err(ucsi->dev,
>> +            "%s: con%d failed (%d)to get port alternate modes\n",
>> +            __func__, con->num, ret);
>> +        return 0;
>> +    }
>> +    ucsi_update_con_altmodes(con);
>> +
>>    /* Alternate modes */
>>    ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_CON);
>>    if (ret)
>> diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
>> index de87d0b8319d..7bbdf83c8d4a 100644
>> --- a/drivers/usb/typec/ucsi/ucsi.h
>> +++ b/drivers/usb/typec/ucsi/ucsi.h
>> @@ -274,6 +274,15 @@ struct ucsi_connector_capability {
>>    u8:6; /* reserved */
>> } __packed;
>> 
>> +struct new_ucsi_altmode {
>> +    u16 svid;
>> +    u32 mid;
>> +    u8 linked_idx;
>> +    u8 active_idx;
>> +#define UCSI_MULTI_LINKED_INDEX    (0xff)
>> +    bool checked;
>> +} __packed;
>> +
>> struct ucsi_altmode {
>>    u16 svid;
>>    u32 mid;
>> @@ -408,6 +417,7 @@ struct ucsi {
>> 
>> struct ucsi_connector {
>>    int num;
>> +    bool has_multiple_dp;
>> 
>>    struct ucsi *ucsi;
>>    struct mutex lock; /* port lock */
>> @@ -424,6 +434,8 @@ struct ucsi_connector {
>> 
>>    struct ucsi_connector_status status;
>>    struct ucsi_connector_capability cap;
>> +    struct new_ucsi_altmode port_alt[UCSI_MAX_ALTMODES];
>> +    struct new_ucsi_altmode new_port_alt[UCSI_MAX_ALTMODES];
>> };
>> 
>> int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
>> -- 
>> 2.17.1
> 
> thanks,
> 
> -- 
> heikki
diff mbox series

Patch

diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index ba288b964dc8..491b5c925a90 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -39,8 +39,27 @@ 
  */
 #define UCSI_SWAP_TIMEOUT_MS	5000
 
+static void ucsi_update_get_current_cam_cmd(struct ucsi_connector *con,
+					    struct ucsi_data *data)
+{
+	u8 cam, new_cam;
+
+	if (data->cci.data_length == 0x1) {
+		cam = data->message_in[0];
+		new_cam = con->port_alt[cam].linked_idx;
+		data->message_in[0] = new_cam;
+		con->new_port_alt[new_cam].active_idx = cam;
+	}
+}
+
 static inline int ucsi_sync(struct ucsi *ucsi)
 {
+	struct ucsi_connector *con = ucsi->connector;
+	struct ucsi_data *data = ucsi->ppm->data;
+
+	if (data->ctrl.alt.cmd == UCSI_GET_CURRENT_CAM && con->has_multiple_dp)
+		ucsi_update_get_current_cam_cmd(con, data);
+
 	if (ucsi->ppm && ucsi->ppm->sync)
 		return ucsi->ppm->sync(ucsi->ppm);
 	return 0;
@@ -101,14 +120,57 @@  static int ucsi_ack(struct ucsi *ucsi, u8 ack)
 	return ret;
 }
 
+static void ucsi_update_set_new_cam_cmd(struct ucsi_connector *con,
+					struct ucsi_control *ctrl)
+{
+	struct new_ucsi_altmode *new_port, *port;
+	struct typec_altmode *partner = NULL;
+	u64 cmd;
+	u8 new_cam, cam;
+	bool enter_new_mode;
+	int i, j;
+
+	cmd = ctrl->raw_cmd;
+	new_cam = (cmd >> 24) & 0xff;
+	new_port = &con->new_port_alt[new_cam];
+	cam = new_port->linked_idx;
+	enter_new_mode = (cmd >> 23) & 1;
+
+	if (cam == UCSI_MULTI_LINKED_INDEX) {
+		if (enter_new_mode) {
+			port = con->port_alt;
+			for (i = 0; con->partner_altmode[i]; i++) {
+				partner = con->partner_altmode[i];
+				if (partner->svid == new_port->svid)
+					break;
+			}
+			for (j = 0; port[j].svid; j++)
+				if (partner && port[j].svid == partner->svid &&
+				    port[j].mid & partner->vdo)
+					break;
+			cam = j;
+			new_port->active_idx = cam;
+		} else {
+			cam = new_port->active_idx;
+		}
+	}
+	cmd &= ~(0xff << 24);
+	cmd |= (cam << 24);
+	ctrl->raw_cmd = cmd;
+}
+
 static int ucsi_run_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
 			    void *data, size_t size)
 {
 	struct ucsi_control _ctrl;
+	struct ucsi_connector *con = ucsi->connector;
 	u8 data_length;
 	u16 error;
 	int ret;
 
+	if (ctrl->alt.cmd == UCSI_SET_NEW_CAM && con->has_multiple_dp)
+		ucsi_update_set_new_cam_cmd(con, ctrl);
+
 	ret = ucsi_command(ucsi, ctrl);
 	if (ret)
 		goto err;
@@ -364,10 +426,24 @@  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));
-		if (len <= 0)
-			return len;
+
+		if (recipient == UCSI_RECIPIENT_CON) {
+			if (con->has_multiple_dp) {
+				alt[0].svid = con->new_port_alt[i].svid;
+				alt[0].mid = con->new_port_alt[i].mid;
+			} else {
+				alt[0].svid = con->port_alt[i].svid;
+				alt[0].mid = con->port_alt[i].mid;
+			}
+			len = sizeof(alt[0]);
+		} else {
+			UCSI_CMD_GET_ALTERNATE_MODES(ctrl, recipient,
+						     con->num, i, 1);
+			len = ucsi_run_command(con->ucsi, &ctrl, alt,
+					       sizeof(alt));
+			if (len <= 0)
+				return len;
+		}
 
 		/*
 		 * This code is requesting one alt mode at a time, but some PPMs
@@ -521,6 +597,103 @@  static void ucsi_partner_change(struct ucsi_connector *con)
 		ucsi_altmode_update_active(con);
 }
 
+static void ucsi_update_con_altmodes(struct ucsi_connector *con)
+{
+	int max_altmodes = con->ucsi->cap.num_alt_modes;
+	struct new_ucsi_altmode *alt, *new_alt;
+	int i, j, k = 0;
+	bool found = false;
+
+	alt = con->port_alt;
+	new_alt = con->new_port_alt;
+	memset(con->new_port_alt, 0, sizeof(con->new_port_alt));
+
+	for (i = 0; i < max_altmodes; i++) {
+		if (!alt[i].svid)
+			break;
+
+		/* already checked and considered */
+		if (alt[i].checked)
+			continue;
+
+		if (!DP_CONF_GET_PIN_ASSIGN(alt[i].mid)) {
+			/* Found Non DP altmode */
+			new_alt[k].svid = alt[i].svid;
+			new_alt[k].mid |= alt[i].mid;
+			new_alt[k].linked_idx = i;
+			alt[i].linked_idx = k;
+			k++;
+			continue;
+		}
+
+		for (j = i + 1; j < max_altmodes; j++) {
+			if (alt[i].svid != alt[j].svid ||
+			    !DP_CONF_GET_PIN_ASSIGN(alt[j].mid)) {
+				continue;
+			} else {
+				/* Found duplicate DP mode */
+				new_alt[k].svid = alt[i].svid;
+				new_alt[k].mid |= alt[i].mid | alt[j].mid;
+				new_alt[k].linked_idx = UCSI_MULTI_LINKED_INDEX;
+				alt[i].linked_idx = k;
+				alt[j].linked_idx = k;
+				alt[j].checked = true;
+				found = true;
+			}
+		}
+		if (found) {
+			con->has_multiple_dp = true;
+		} else {
+			/* Didn't find any duplicate DP altmode */
+			new_alt[k].svid = alt[i].svid;
+			new_alt[k].mid |= alt[i].mid;
+			new_alt[k].linked_idx = i;
+			alt[i].linked_idx = k;
+		}
+		k++;
+	}
+}
+
+static int ucsi_get_altmodes(struct ucsi_connector *con)
+{
+	int max_altmodes = con->ucsi->cap.num_alt_modes;
+	struct ucsi_altmode alt[2];
+	struct ucsi_control ctrl;
+	int num = 1;
+	int len;
+	int j;
+	int i;
+	int k = 0;
+
+	memset(con->port_alt, 0, sizeof(con->port_alt));
+
+	for (i = 0; i < max_altmodes;) {
+		memset(alt, 0, sizeof(alt));
+		UCSI_CMD_GET_ALTERNATE_MODES(ctrl, UCSI_RECIPIENT_CON,
+					     con->num, i, 1);
+		len = ucsi_run_command(con->ucsi, &ctrl, alt, sizeof(alt));
+		if (len <= 0)
+			return len;
+
+		/*
+		 * This code is requesting one alt mode at a time, but some PPMs
+		 * may still return two.
+		 */
+		num = len / sizeof(alt[0]);
+		i += num;
+
+		for (j = 0; j < num; j++) {
+			if (!alt[j].svid)
+				return 0;
+
+			con->port_alt[k].mid = alt[j].mid;
+			con->port_alt[k].svid = alt[j].svid;
+			k++;
+		}
+	}
+	return 0;
+}
+
 static void ucsi_connector_change(struct work_struct *work)
 {
 	struct ucsi_connector *con = container_of(work, struct ucsi_connector,
@@ -851,6 +1024,16 @@  static int ucsi_register_port(struct ucsi *ucsi, int index)
 	if (IS_ERR(con->port))
 		return PTR_ERR(con->port);
 
+	/* Get Alternate modes */
+	ret = ucsi_get_altmodes(con);
+	if (ret) {
+		dev_err(ucsi->dev,
+			"%s: con%d failed (%d)to get port alternate modes\n",
+			__func__, con->num, ret);
+		return 0;
+	}
+	ucsi_update_con_altmodes(con);
+
 	/* Alternate modes */
 	ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_CON);
 	if (ret)
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index de87d0b8319d..7bbdf83c8d4a 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -274,6 +274,15 @@  struct ucsi_connector_capability {
 	u8:6; /* reserved */
 } __packed;
 
+struct new_ucsi_altmode {
+	u16 svid;
+	u32 mid;
+	u8 linked_idx;
+	u8 active_idx;
+#define UCSI_MULTI_LINKED_INDEX	(0xff)
+	bool checked;
+} __packed;
+
 struct ucsi_altmode {
 	u16 svid;
 	u32 mid;
@@ -408,6 +417,7 @@  struct ucsi {
 
 struct ucsi_connector {
 	int num;
+	bool has_multiple_dp;
 
 	struct ucsi *ucsi;
 	struct mutex lock; /* port lock */
@@ -424,6 +434,8 @@  struct ucsi_connector {
 
 	struct ucsi_connector_status status;
 	struct ucsi_connector_capability cap;
+	struct new_ucsi_altmode port_alt[UCSI_MAX_ALTMODES];
+	struct new_ucsi_altmode new_port_alt[UCSI_MAX_ALTMODES];
 };
 
 int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl,