xref: /linux/drivers/usb/typec/ucsi/thunderbolt.c (revision 17f8d2009367c3da82882f70ccbdca9f8c7b5f20)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * UCSI Thunderbolt Alternate Mode Support
4  *
5  * Copyright 2026 Google LLC
6  */
7 
8 #include <linux/usb/typec_tbt.h>
9 #include <linux/usb/pd_vdo.h>
10 #include <linux/err.h>
11 #include <linux/dev_printk.h>
12 #include <linux/device/devres.h>
13 #include <linux/gfp_types.h>
14 #include <linux/types.h>
15 #include <linux/usb/typec_altmode.h>
16 #include <linux/workqueue.h>
17 
18 #include "ucsi.h"
19 
20 /**
21  * struct ucsi_tbt - Thunderbolt Alternate Mode private data structure
22  * @con: Pointer to UCSI connector structure
23  * @alt: Pointer to typec altmode structure
24  * @work: Work structure
25  * @cam: An offset into the list of alternate modes supported by the PPM
26  * @header: VDO header
27  */
28 struct ucsi_tbt {
29 	struct ucsi_connector *con;
30 	struct typec_altmode *alt;
31 	struct work_struct work;
32 	int cam;
33 	u32 header;
34 };
35 
ucsi_thunderbolt_work(struct work_struct * work)36 static void ucsi_thunderbolt_work(struct work_struct *work)
37 {
38 	struct ucsi_tbt *tbt = container_of(work, struct ucsi_tbt, work);
39 
40 	if (typec_altmode_vdm(tbt->alt, tbt->header, NULL, 0))
41 		dev_err(&tbt->alt->dev, "VDM 0x%x failed\n", tbt->header);
42 
43 	tbt->header = 0;
44 }
45 
ucsi_thunderbolt_set_altmode(struct ucsi_tbt * tbt,bool enter,u32 vdo)46 static int ucsi_thunderbolt_set_altmode(struct ucsi_tbt *tbt,
47 					bool enter, u32 vdo)
48 {
49 	int svdm_version;
50 	int cmd;
51 	int ret;
52 	u64 command = UCSI_SET_NEW_CAM |
53 		      UCSI_CONNECTOR_NUMBER(tbt->con->num) |
54 		      UCSI_SET_NEW_CAM_SET_AM(tbt->cam) |
55 		      ((u64)vdo << 32);
56 
57 	if (enter)
58 		command |= (1 << 23);
59 
60 	ret = ucsi_send_command(tbt->con->ucsi, command, NULL, 0);
61 	if (ret < 0)
62 		return ret;
63 
64 	svdm_version = typec_altmode_get_svdm_version(tbt->alt);
65 	if (svdm_version < 0)
66 		return svdm_version;
67 
68 	if (enter)
69 		cmd = CMD_ENTER_MODE;
70 	else
71 		cmd = CMD_EXIT_MODE;
72 	tbt->header = VDO(USB_TYPEC_TBT_SID, 1, svdm_version, cmd);
73 	tbt->header |= VDO_OPOS(TYPEC_TBT_MODE);
74 	tbt->header |= VDO_CMDT(CMDT_RSP_ACK);
75 
76 	schedule_work(&tbt->work);
77 
78 	return 0;
79 }
80 
ucsi_thunderbolt_enter(struct typec_altmode * alt,u32 * vdo)81 static int ucsi_thunderbolt_enter(struct typec_altmode *alt, u32 *vdo)
82 {
83 	struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt);
84 	struct ucsi_connector *con = tbt->con;
85 	u64 command;
86 	u8 cur = 0;
87 	int ret;
88 
89 	if (!ucsi_con_mutex_lock(con))
90 		return -ENOTCONN;
91 
92 	command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(con->num);
93 	ret = ucsi_send_command(con->ucsi, command, &cur, sizeof(cur));
94 	if (ret < 0) {
95 		if (con->ucsi->version > 0x0100)
96 			goto err_unlock;
97 		cur = 0xff;
98 	}
99 
100 	if (cur != 0xff) {
101 		if (cur >= UCSI_MAX_ALTMODES || con->port_altmode[cur] != alt)
102 			ret = -EBUSY;
103 		else
104 			ret = 0;
105 		goto err_unlock;
106 	}
107 
108 	ret = ucsi_thunderbolt_set_altmode(tbt, true, *vdo);
109 	ucsi_altmode_update_active(tbt->con);
110 
111 err_unlock:
112 	ucsi_con_mutex_unlock(con);
113 
114 	return ret;
115 }
116 
ucsi_thunderbolt_exit(struct typec_altmode * alt)117 static int ucsi_thunderbolt_exit(struct typec_altmode *alt)
118 {
119 	struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt);
120 	int ret;
121 
122 	if (!ucsi_con_mutex_lock(tbt->con))
123 		return -ENOTCONN;
124 
125 	ret = ucsi_thunderbolt_set_altmode(tbt, false, 0);
126 
127 	ucsi_con_mutex_unlock(tbt->con);
128 
129 	return ret;
130 }
131 
ucsi_thunderbolt_vdm(struct typec_altmode * alt,u32 header,const u32 * data,int count)132 static int ucsi_thunderbolt_vdm(struct typec_altmode *alt,
133 				u32 header, const u32 *data, int count)
134 {
135 	struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt);
136 	int cmd_type = PD_VDO_CMDT(header);
137 	int cmd = PD_VDO_CMD(header);
138 	int svdm_version;
139 
140 	if (!ucsi_con_mutex_lock(tbt->con))
141 		return -ENOTCONN;
142 
143 	svdm_version = typec_altmode_get_svdm_version(alt);
144 	if (svdm_version < 0) {
145 		ucsi_con_mutex_unlock(tbt->con);
146 		return svdm_version;
147 	}
148 
149 	switch (cmd_type) {
150 	case CMDT_INIT:
151 		if (PD_VDO_SVDM_VER(header) < svdm_version) {
152 			svdm_version = PD_VDO_SVDM_VER(header);
153 			typec_partner_set_svdm_version(tbt->con->partner, svdm_version);
154 		}
155 		tbt->header = VDO(USB_TYPEC_TBT_SID, 1, svdm_version, cmd);
156 		tbt->header |= VDO_OPOS(TYPEC_TBT_MODE);
157 		tbt->header |= VDO_CMDT(CMDT_RSP_ACK);
158 
159 		schedule_work(&tbt->work);
160 		break;
161 	default:
162 		break;
163 	}
164 
165 	ucsi_con_mutex_unlock(tbt->con);
166 
167 	return 0;
168 }
169 
170 static const struct typec_altmode_ops ucsi_thunderbolt_ops = {
171 	.enter = ucsi_thunderbolt_enter,
172 	.exit = ucsi_thunderbolt_exit,
173 	.vdm = ucsi_thunderbolt_vdm,
174 };
175 
ucsi_register_thunderbolt(struct ucsi_connector * con,bool override,int offset,struct typec_altmode_desc * desc)176 struct typec_altmode *ucsi_register_thunderbolt(struct ucsi_connector *con,
177 						bool override, int offset,
178 						struct typec_altmode_desc *desc)
179 {
180 	struct typec_altmode *alt;
181 	struct ucsi_tbt *tbt;
182 
183 	alt = typec_port_register_altmode(con->port, desc);
184 	if (IS_ERR(alt) || !override)
185 		return alt;
186 
187 	tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL);
188 	if (!tbt) {
189 		typec_unregister_altmode(alt);
190 		return ERR_PTR(-ENOMEM);
191 	}
192 
193 	tbt->cam = offset;
194 	tbt->con = con;
195 	tbt->alt = alt;
196 	INIT_WORK(&tbt->work, ucsi_thunderbolt_work);
197 	typec_altmode_set_drvdata(alt, tbt);
198 	typec_altmode_set_ops(alt, &ucsi_thunderbolt_ops);
199 
200 	return alt;
201 }
202 
ucsi_thunderbolt_remove_partner(struct typec_altmode * alt)203 void ucsi_thunderbolt_remove_partner(struct typec_altmode *alt)
204 {
205 	struct ucsi_tbt *tbt;
206 
207 	if (alt) {
208 		tbt = typec_altmode_get_drvdata(alt);
209 		if (tbt)
210 			cancel_work_sync(&tbt->work);
211 	}
212 }
213