Merge tag 'usb-5.14' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
[linux-2.6-microblaze.git] / drivers / usb / typec / ucsi / displayport.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * UCSI DisplayPort Alternate Mode Support
4  *
5  * Copyright (C) 2018, Intel Corporation
6  * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
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
18 struct 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  * Note. Alternate mode control is optional feature in UCSI. It means that even
35  * if the system supports alternate modes, the OS may not be aware of them.
36  *
37  * In most cases however, the OS will be able to see the supported alternate
38  * modes, but it may still not be able to configure them, not even enter or exit
39  * them. That is because UCSI defines alt mode details and alt mode "overriding"
40  * as separate options.
41  *
42  * In case alt mode details are supported, but overriding is not, the driver
43  * will still display the supported pin assignments and configuration, but any
44  * changes the user attempts to do will lead into failure with return value of
45  * -EOPNOTSUPP.
46  */
47
48 static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo)
49 {
50         struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
51         struct ucsi *ucsi = dp->con->ucsi;
52         int svdm_version;
53         u64 command;
54         u8 cur = 0;
55         int ret;
56
57         mutex_lock(&dp->con->lock);
58
59         if (!dp->override && dp->initialized) {
60                 const struct typec_altmode *p = typec_altmode_get_partner(alt);
61
62                 dev_warn(&p->dev,
63                          "firmware doesn't support alternate mode overriding\n");
64                 ret = -EOPNOTSUPP;
65                 goto err_unlock;
66         }
67
68         command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(dp->con->num);
69         ret = ucsi_send_command(ucsi, command, &cur, sizeof(cur));
70         if (ret < 0) {
71                 if (ucsi->version > 0x0100)
72                         goto err_unlock;
73                 cur = 0xff;
74         }
75
76         if (cur != 0xff) {
77                 ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY;
78                 goto err_unlock;
79         }
80
81         /*
82          * We can't send the New CAM command yet to the PPM as it needs the
83          * configuration value as well. Pretending that we have now entered the
84          * mode, and letting the alt mode driver continue.
85          */
86
87         svdm_version = typec_altmode_get_svdm_version(alt);
88         if (svdm_version < 0) {
89                 ret = svdm_version;
90                 goto err_unlock;
91         }
92
93         dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_ENTER_MODE);
94         dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
95         dp->header |= VDO_CMDT(CMDT_RSP_ACK);
96
97         dp->vdo_data = NULL;
98         dp->vdo_size = 1;
99
100         schedule_work(&dp->work);
101         ret = 0;
102 err_unlock:
103         mutex_unlock(&dp->con->lock);
104
105         return ret;
106 }
107
108 static int ucsi_displayport_exit(struct typec_altmode *alt)
109 {
110         struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
111         int svdm_version;
112         u64 command;
113         int ret = 0;
114
115         mutex_lock(&dp->con->lock);
116
117         if (!dp->override) {
118                 const struct typec_altmode *p = typec_altmode_get_partner(alt);
119
120                 dev_warn(&p->dev,
121                          "firmware doesn't support alternate mode overriding\n");
122                 ret = -EOPNOTSUPP;
123                 goto out_unlock;
124         }
125
126         command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
127         ret = ucsi_send_command(dp->con->ucsi, command, NULL, 0);
128         if (ret < 0)
129                 goto out_unlock;
130
131         svdm_version = typec_altmode_get_svdm_version(alt);
132         if (svdm_version < 0) {
133                 ret = svdm_version;
134                 goto out_unlock;
135         }
136
137         dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_EXIT_MODE);
138         dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
139         dp->header |= VDO_CMDT(CMDT_RSP_ACK);
140
141         dp->vdo_data = NULL;
142         dp->vdo_size = 1;
143
144         schedule_work(&dp->work);
145
146 out_unlock:
147         mutex_unlock(&dp->con->lock);
148
149         return ret;
150 }
151
152 /*
153  * We do not actually have access to the Status Update VDO, so we have to guess
154  * things.
155  */
156 static int ucsi_displayport_status_update(struct ucsi_dp *dp)
157 {
158         u32 cap = dp->alt->vdo;
159
160         dp->data.status = DP_STATUS_ENABLED;
161
162         /*
163          * If pin assignement D is supported, claiming always
164          * that Multi-function is preferred.
165          */
166         if (DP_CAP_CAPABILITY(cap) & DP_CAP_UFP_D) {
167                 dp->data.status |= DP_STATUS_CON_UFP_D;
168
169                 if (DP_CAP_UFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
170                         dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
171         } else {
172                 dp->data.status |= DP_STATUS_CON_DFP_D;
173
174                 if (DP_CAP_DFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
175                         dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
176         }
177
178         dp->vdo_data = &dp->data.status;
179         dp->vdo_size = 2;
180
181         return 0;
182 }
183
184 static int ucsi_displayport_configure(struct ucsi_dp *dp)
185 {
186         u32 pins = DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
187         u64 command;
188
189         if (!dp->override)
190                 return 0;
191
192         command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
193
194         return ucsi_send_command(dp->con->ucsi, command, NULL, 0);
195 }
196
197 static int ucsi_displayport_vdm(struct typec_altmode *alt,
198                                 u32 header, const u32 *data, int count)
199 {
200         struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
201         int cmd_type = PD_VDO_CMDT(header);
202         int cmd = PD_VDO_CMD(header);
203         int svdm_version;
204
205         mutex_lock(&dp->con->lock);
206
207         if (!dp->override && dp->initialized) {
208                 const struct typec_altmode *p = typec_altmode_get_partner(alt);
209
210                 dev_warn(&p->dev,
211                          "firmware doesn't support alternate mode overriding\n");
212                 mutex_unlock(&dp->con->lock);
213                 return -EOPNOTSUPP;
214         }
215
216         svdm_version = typec_altmode_get_svdm_version(alt);
217         if (svdm_version < 0) {
218                 mutex_unlock(&dp->con->lock);
219                 return svdm_version;
220         }
221
222         switch (cmd_type) {
223         case CMDT_INIT:
224                 if (PD_VDO_SVDM_VER(header) < svdm_version) {
225                         typec_partner_set_svdm_version(dp->con->partner, PD_VDO_SVDM_VER(header));
226                         svdm_version = PD_VDO_SVDM_VER(header);
227                 }
228
229                 dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, cmd);
230                 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
231
232                 switch (cmd) {
233                 case DP_CMD_STATUS_UPDATE:
234                         if (ucsi_displayport_status_update(dp))
235                                 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
236                         else
237                                 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
238                         break;
239                 case DP_CMD_CONFIGURE:
240                         dp->data.conf = *data;
241                         if (ucsi_displayport_configure(dp)) {
242                                 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
243                         } else {
244                                 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
245                                 if (dp->initialized)
246                                         ucsi_altmode_update_active(dp->con);
247                                 else
248                                         dp->initialized = true;
249                         }
250                         break;
251                 default:
252                         dp->header |= VDO_CMDT(CMDT_RSP_ACK);
253                         break;
254                 }
255
256                 schedule_work(&dp->work);
257                 break;
258         default:
259                 break;
260         }
261
262         mutex_unlock(&dp->con->lock);
263
264         return 0;
265 }
266
267 static const struct typec_altmode_ops ucsi_displayport_ops = {
268         .enter = ucsi_displayport_enter,
269         .exit = ucsi_displayport_exit,
270         .vdm = ucsi_displayport_vdm,
271 };
272
273 static void ucsi_displayport_work(struct work_struct *work)
274 {
275         struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work);
276         int ret;
277
278         mutex_lock(&dp->con->lock);
279
280         ret = typec_altmode_vdm(dp->alt, dp->header,
281                                 dp->vdo_data, dp->vdo_size);
282         if (ret)
283                 dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
284
285         dp->vdo_data = NULL;
286         dp->vdo_size = 0;
287         dp->header = 0;
288
289         mutex_unlock(&dp->con->lock);
290 }
291
292 void ucsi_displayport_remove_partner(struct typec_altmode *alt)
293 {
294         struct ucsi_dp *dp;
295
296         if (!alt)
297                 return;
298
299         dp = typec_altmode_get_drvdata(alt);
300         if (!dp)
301                 return;
302
303         dp->data.conf = 0;
304         dp->data.status = 0;
305         dp->initialized = false;
306 }
307
308 struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
309                                                 bool override, int offset,
310                                                 struct typec_altmode_desc *desc)
311 {
312         u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
313                              BIT(DP_PIN_ASSIGN_E);
314         struct typec_altmode *alt;
315         struct ucsi_dp *dp;
316
317         /* We can't rely on the firmware with the capabilities. */
318         desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
319
320         /* Claiming that we support all pin assignments */
321         desc->vdo |= all_assignments << 8;
322         desc->vdo |= all_assignments << 16;
323
324         alt = typec_port_register_altmode(con->port, desc);
325         if (IS_ERR(alt))
326                 return alt;
327
328         dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
329         if (!dp) {
330                 typec_unregister_altmode(alt);
331                 return ERR_PTR(-ENOMEM);
332         }
333
334         INIT_WORK(&dp->work, ucsi_displayport_work);
335         dp->override = override;
336         dp->offset = offset;
337         dp->con = con;
338         dp->alt = alt;
339
340         alt->ops = &ucsi_displayport_ops;
341         typec_altmode_set_drvdata(alt, dp);
342
343         return alt;
344 }