1
2
3
4
5
6
7
8
9#include <linux/usb/typec_dp.h>
10#include <linux/usb/pd_vdo.h>
11
12#include "ucsi.h"
13
14#define UCSI_CMD_SET_NEW_CAM(_con_num_, _enter_, _cam_, _am_) \
15 (UCSI_SET_NEW_CAM | ((_con_num_) << 16) | ((_enter_) << 23) | \
16 ((_cam_) << 24) | ((u64)(_am_) << 32))
17
18struct ucsi_dp {
19 struct typec_displayport_data data;
20 struct ucsi_connector *con;
21 struct typec_altmode *alt;
22 struct work_struct work;
23 int offset;
24
25 bool override;
26 bool initialized;
27
28 u32 header;
29 u32 *vdo_data;
30 u8 vdo_size;
31};
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48static int ucsi_displayport_enter(struct typec_altmode *alt)
49{
50 struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
51 struct ucsi_control ctrl;
52 u8 cur = 0;
53 int ret;
54
55 mutex_lock(&dp->con->lock);
56
57 if (!dp->override && dp->initialized) {
58 const struct typec_altmode *p = typec_altmode_get_partner(alt);
59
60 dev_warn(&p->dev,
61 "firmware doesn't support alternate mode overriding\n");
62 mutex_unlock(&dp->con->lock);
63 return -EOPNOTSUPP;
64 }
65
66 UCSI_CMD_GET_CURRENT_CAM(ctrl, dp->con->num);
67 ret = ucsi_send_command(dp->con->ucsi, &ctrl, &cur, sizeof(cur));
68 if (ret < 0) {
69 if (dp->con->ucsi->ppm->data->version > 0x0100) {
70 mutex_unlock(&dp->con->lock);
71 return ret;
72 }
73 cur = 0xff;
74 }
75
76 if (cur != 0xff) {
77 mutex_unlock(&dp->con->lock);
78 if (dp->con->port_altmode[cur] == alt)
79 return 0;
80 return -EBUSY;
81 }
82
83
84
85
86
87
88
89 dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_ENTER_MODE);
90 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
91 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
92
93 dp->vdo_data = NULL;
94 dp->vdo_size = 1;
95
96 schedule_work(&dp->work);
97
98 mutex_unlock(&dp->con->lock);
99
100 return 0;
101}
102
103static int ucsi_displayport_exit(struct typec_altmode *alt)
104{
105 struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
106 struct ucsi_control ctrl;
107 int ret = 0;
108
109 mutex_lock(&dp->con->lock);
110
111 if (!dp->override) {
112 const struct typec_altmode *p = typec_altmode_get_partner(alt);
113
114 dev_warn(&p->dev,
115 "firmware doesn't support alternate mode overriding\n");
116 ret = -EOPNOTSUPP;
117 goto out_unlock;
118 }
119
120 ctrl.raw_cmd = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
121 ret = ucsi_send_command(dp->con->ucsi, &ctrl, NULL, 0);
122 if (ret < 0)
123 goto out_unlock;
124
125 dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_EXIT_MODE);
126 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
127 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
128
129 dp->vdo_data = NULL;
130 dp->vdo_size = 1;
131
132 schedule_work(&dp->work);
133
134out_unlock:
135 mutex_unlock(&dp->con->lock);
136
137 return ret;
138}
139
140
141
142
143
144static int ucsi_displayport_status_update(struct ucsi_dp *dp)
145{
146 u32 cap = dp->alt->vdo;
147
148 dp->data.status = DP_STATUS_ENABLED;
149
150
151
152
153
154 if (DP_CAP_CAPABILITY(cap) & DP_CAP_UFP_D) {
155 dp->data.status |= DP_STATUS_CON_UFP_D;
156
157 if (DP_CAP_UFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
158 dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
159 } else {
160 dp->data.status |= DP_STATUS_CON_DFP_D;
161
162 if (DP_CAP_DFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
163 dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
164 }
165
166 dp->vdo_data = &dp->data.status;
167 dp->vdo_size = 2;
168
169 return 0;
170}
171
172static int ucsi_displayport_configure(struct ucsi_dp *dp)
173{
174 u32 pins = DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
175 struct ucsi_control ctrl;
176
177 if (!dp->override)
178 return 0;
179
180 ctrl.raw_cmd = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
181
182 return ucsi_send_command(dp->con->ucsi, &ctrl, NULL, 0);
183}
184
185static int ucsi_displayport_vdm(struct typec_altmode *alt,
186 u32 header, const u32 *data, int count)
187{
188 struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
189 int cmd_type = PD_VDO_CMDT(header);
190 int cmd = PD_VDO_CMD(header);
191
192 mutex_lock(&dp->con->lock);
193
194 if (!dp->override && dp->initialized) {
195 const struct typec_altmode *p = typec_altmode_get_partner(alt);
196
197 dev_warn(&p->dev,
198 "firmware doesn't support alternate mode overriding\n");
199 mutex_unlock(&dp->con->lock);
200 return -EOPNOTSUPP;
201 }
202
203 switch (cmd_type) {
204 case CMDT_INIT:
205 dp->header = VDO(USB_TYPEC_DP_SID, 1, cmd);
206 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
207
208 switch (cmd) {
209 case DP_CMD_STATUS_UPDATE:
210 if (ucsi_displayport_status_update(dp))
211 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
212 else
213 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
214 break;
215 case DP_CMD_CONFIGURE:
216 dp->data.conf = *data;
217 if (ucsi_displayport_configure(dp)) {
218 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
219 } else {
220 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
221 if (dp->initialized)
222 ucsi_altmode_update_active(dp->con);
223 else
224 dp->initialized = true;
225 }
226 break;
227 default:
228 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
229 break;
230 }
231
232 schedule_work(&dp->work);
233 break;
234 default:
235 break;
236 }
237
238 mutex_unlock(&dp->con->lock);
239
240 return 0;
241}
242
243static const struct typec_altmode_ops ucsi_displayport_ops = {
244 .enter = ucsi_displayport_enter,
245 .exit = ucsi_displayport_exit,
246 .vdm = ucsi_displayport_vdm,
247};
248
249static void ucsi_displayport_work(struct work_struct *work)
250{
251 struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work);
252 int ret;
253
254 mutex_lock(&dp->con->lock);
255
256 ret = typec_altmode_vdm(dp->alt, dp->header,
257 dp->vdo_data, dp->vdo_size);
258 if (ret)
259 dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
260
261 dp->vdo_data = NULL;
262 dp->vdo_size = 0;
263 dp->header = 0;
264
265 mutex_unlock(&dp->con->lock);
266}
267
268void ucsi_displayport_remove_partner(struct typec_altmode *alt)
269{
270 struct ucsi_dp *dp;
271
272 if (!alt)
273 return;
274
275 dp = typec_altmode_get_drvdata(alt);
276 dp->data.conf = 0;
277 dp->data.status = 0;
278 dp->initialized = false;
279}
280
281struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
282 bool override, int offset,
283 struct typec_altmode_desc *desc)
284{
285 u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
286 BIT(DP_PIN_ASSIGN_E);
287 struct typec_altmode *alt;
288 struct ucsi_dp *dp;
289
290
291 desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
292
293
294 desc->vdo |= all_assignments << 8;
295 desc->vdo |= all_assignments << 16;
296
297 alt = typec_port_register_altmode(con->port, desc);
298 if (IS_ERR(alt))
299 return alt;
300
301 dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
302 if (!dp) {
303 typec_unregister_altmode(alt);
304 return ERR_PTR(-ENOMEM);
305 }
306
307 INIT_WORK(&dp->work, ucsi_displayport_work);
308 dp->override = override;
309 dp->offset = offset;
310 dp->con = con;
311 dp->alt = alt;
312
313 alt->ops = &ucsi_displayport_ops;
314 typec_altmode_set_drvdata(alt, dp);
315
316 return alt;
317}
318