Merge tag 'samsung-soc-5.10' of https://git.kernel.org/pub/scm/linux/kernel/git/krzk...
[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         u64 command;
53         u8 cur = 0;
54         int ret;
55
56         mutex_lock(&dp->con->lock);
57
58         if (!dp->override && dp->initialized) {
59                 const struct typec_altmode *p = typec_altmode_get_partner(alt);
60
61                 dev_warn(&p->dev,
62                          "firmware doesn't support alternate mode overriding\n");
63                 ret = -EOPNOTSUPP;
64                 goto err_unlock;
65         }
66
67         command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(dp->con->num);
68         ret = ucsi_send_command(ucsi, command, &cur, sizeof(cur));
69         if (ret < 0) {
70                 if (ucsi->version > 0x0100)
71                         goto err_unlock;
72                 cur = 0xff;
73         }
74
75         if (cur != 0xff) {
76                 ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY;
77                 goto err_unlock;
78         }
79
80         /*
81          * We can't send the New CAM command yet to the PPM as it needs the
82          * configuration value as well. Pretending that we have now entered the
83          * mode, and letting the alt mode driver continue.
84          */
85
86         dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_ENTER_MODE);
87         dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
88         dp->header |= VDO_CMDT(CMDT_RSP_ACK);
89
90         dp->vdo_data = NULL;
91         dp->vdo_size = 1;
92
93         schedule_work(&dp->work);
94         ret = 0;
95 err_unlock:
96         mutex_unlock(&dp->con->lock);
97
98         return ret;
99 }
100
101 static int ucsi_displayport_exit(struct typec_altmode *alt)
102 {
103         struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
104         u64 command;
105         int ret = 0;
106
107         mutex_lock(&dp->con->lock);
108
109         if (!dp->override) {
110                 const struct typec_altmode *p = typec_altmode_get_partner(alt);
111
112                 dev_warn(&p->dev,
113                          "firmware doesn't support alternate mode overriding\n");
114                 ret = -EOPNOTSUPP;
115                 goto out_unlock;
116         }
117
118         command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
119         ret = ucsi_send_command(dp->con->ucsi, command, NULL, 0);
120         if (ret < 0)
121                 goto out_unlock;
122
123         dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_EXIT_MODE);
124         dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
125         dp->header |= VDO_CMDT(CMDT_RSP_ACK);
126
127         dp->vdo_data = NULL;
128         dp->vdo_size = 1;
129
130         schedule_work(&dp->work);
131
132 out_unlock:
133         mutex_unlock(&dp->con->lock);
134
135         return ret;
136 }
137
138 /*
139  * We do not actually have access to the Status Update VDO, so we have to guess
140  * things.
141  */
142 static int ucsi_displayport_status_update(struct ucsi_dp *dp)
143 {
144         u32 cap = dp->alt->vdo;
145
146         dp->data.status = DP_STATUS_ENABLED;
147
148         /*
149          * If pin assignement D is supported, claiming always
150          * that Multi-function is preferred.
151          */
152         if (DP_CAP_CAPABILITY(cap) & DP_CAP_UFP_D) {
153                 dp->data.status |= DP_STATUS_CON_UFP_D;
154
155                 if (DP_CAP_UFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
156                         dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
157         } else {
158                 dp->data.status |= DP_STATUS_CON_DFP_D;
159
160                 if (DP_CAP_DFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
161                         dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
162         }
163
164         dp->vdo_data = &dp->data.status;
165         dp->vdo_size = 2;
166
167         return 0;
168 }
169
170 static int ucsi_displayport_configure(struct ucsi_dp *dp)
171 {
172         u32 pins = DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
173         u64 command;
174
175         if (!dp->override)
176                 return 0;
177
178         command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
179
180         return ucsi_send_command(dp->con->ucsi, command, NULL, 0);
181 }
182
183 static int ucsi_displayport_vdm(struct typec_altmode *alt,
184                                 u32 header, const u32 *data, int count)
185 {
186         struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
187         int cmd_type = PD_VDO_CMDT(header);
188         int cmd = PD_VDO_CMD(header);
189
190         mutex_lock(&dp->con->lock);
191
192         if (!dp->override && dp->initialized) {
193                 const struct typec_altmode *p = typec_altmode_get_partner(alt);
194
195                 dev_warn(&p->dev,
196                          "firmware doesn't support alternate mode overriding\n");
197                 mutex_unlock(&dp->con->lock);
198                 return -EOPNOTSUPP;
199         }
200
201         switch (cmd_type) {
202         case CMDT_INIT:
203                 dp->header = VDO(USB_TYPEC_DP_SID, 1, cmd);
204                 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
205
206                 switch (cmd) {
207                 case DP_CMD_STATUS_UPDATE:
208                         if (ucsi_displayport_status_update(dp))
209                                 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
210                         else
211                                 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
212                         break;
213                 case DP_CMD_CONFIGURE:
214                         dp->data.conf = *data;
215                         if (ucsi_displayport_configure(dp)) {
216                                 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
217                         } else {
218                                 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
219                                 if (dp->initialized)
220                                         ucsi_altmode_update_active(dp->con);
221                                 else
222                                         dp->initialized = true;
223                         }
224                         break;
225                 default:
226                         dp->header |= VDO_CMDT(CMDT_RSP_ACK);
227                         break;
228                 }
229
230                 schedule_work(&dp->work);
231                 break;
232         default:
233                 break;
234         }
235
236         mutex_unlock(&dp->con->lock);
237
238         return 0;
239 }
240
241 static const struct typec_altmode_ops ucsi_displayport_ops = {
242         .enter = ucsi_displayport_enter,
243         .exit = ucsi_displayport_exit,
244         .vdm = ucsi_displayport_vdm,
245 };
246
247 static void ucsi_displayport_work(struct work_struct *work)
248 {
249         struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work);
250         int ret;
251
252         mutex_lock(&dp->con->lock);
253
254         ret = typec_altmode_vdm(dp->alt, dp->header,
255                                 dp->vdo_data, dp->vdo_size);
256         if (ret)
257                 dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
258
259         dp->vdo_data = NULL;
260         dp->vdo_size = 0;
261         dp->header = 0;
262
263         mutex_unlock(&dp->con->lock);
264 }
265
266 void ucsi_displayport_remove_partner(struct typec_altmode *alt)
267 {
268         struct ucsi_dp *dp;
269
270         if (!alt)
271                 return;
272
273         dp = typec_altmode_get_drvdata(alt);
274         if (!dp)
275                 return;
276
277         dp->data.conf = 0;
278         dp->data.status = 0;
279         dp->initialized = false;
280 }
281
282 struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
283                                                 bool override, int offset,
284                                                 struct typec_altmode_desc *desc)
285 {
286         u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
287                              BIT(DP_PIN_ASSIGN_E);
288         struct typec_altmode *alt;
289         struct ucsi_dp *dp;
290
291         /* We can't rely on the firmware with the capabilities. */
292         desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
293
294         /* Claiming that we support all pin assignments */
295         desc->vdo |= all_assignments << 8;
296         desc->vdo |= all_assignments << 16;
297
298         alt = typec_port_register_altmode(con->port, desc);
299         if (IS_ERR(alt))
300                 return alt;
301
302         dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
303         if (!dp) {
304                 typec_unregister_altmode(alt);
305                 return ERR_PTR(-ENOMEM);
306         }
307
308         INIT_WORK(&dp->work, ucsi_displayport_work);
309         dp->override = override;
310         dp->offset = offset;
311         dp->con = con;
312         dp->alt = alt;
313
314         alt->ops = &ucsi_displayport_ops;
315         typec_altmode_set_drvdata(alt, dp);
316
317         return alt;
318 }