1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2022-2024, Linaro Ltd
4 * Authors:
5 * Bjorn Andersson
6 * Dmitry Baryshkov
7 */
8 #include <linux/auxiliary_bus.h>
9 #include <linux/bitops.h>
10 #include <linux/bitfield.h>
11 #include <linux/completion.h>
12 #include <linux/container_of.h>
13 #include <linux/module.h>
14 #include <linux/notifier.h>
15 #include <linux/of.h>
16 #include <linux/property.h>
17 #include <linux/string.h>
18 #include <linux/platform_data/lenovo-yoga-c630.h>
19 #include <linux/usb/typec_dp.h>
20
21 #include <drm/bridge/aux-bridge.h>
22
23 #include "ucsi.h"
24
25 #define LENOVO_EC_USB_MUX 0x08
26
27 #define USB_MUX_MUXC GENMASK(1, 0)
28 #define USB_MUX_CCST GENMASK(3, 2)
29 #define USB_MUX_DPPN GENMASK(7, 4)
30 #define USB_MUX_HPDS BIT(8)
31 #define USB_MUX_HSFL GENMASK(11, 9)
32
33 struct yoga_c630_ucsi {
34 struct yoga_c630_ec *ec;
35 struct ucsi *ucsi;
36 struct auxiliary_device *bridge;
37 struct notifier_block nb;
38 u16 version;
39 };
40
yoga_c630_ucsi_read_version(struct ucsi * ucsi,u16 * version)41 static int yoga_c630_ucsi_read_version(struct ucsi *ucsi, u16 *version)
42 {
43 struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
44
45 *version = uec->version;
46
47 return 0;
48 }
49
yoga_c630_ucsi_read_cci(struct ucsi * ucsi,u32 * cci)50 static int yoga_c630_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
51 {
52 struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
53 u8 buf[YOGA_C630_UCSI_READ_SIZE];
54 int ret;
55
56 ret = yoga_c630_ec_ucsi_read(uec->ec, buf);
57 if (ret)
58 return ret;
59
60 memcpy(cci, buf, sizeof(*cci));
61
62 return 0;
63 }
64
yoga_c630_ucsi_read_message_in(struct ucsi * ucsi,void * val,size_t val_len)65 static int yoga_c630_ucsi_read_message_in(struct ucsi *ucsi,
66 void *val, size_t val_len)
67 {
68 struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
69 u8 buf[YOGA_C630_UCSI_READ_SIZE];
70 int ret;
71
72 ret = yoga_c630_ec_ucsi_read(uec->ec, buf);
73 if (ret)
74 return ret;
75
76 memcpy(val, buf + YOGA_C630_UCSI_CCI_SIZE,
77 min(val_len, YOGA_C630_UCSI_DATA_SIZE));
78
79 return 0;
80 }
81
yoga_c630_ucsi_async_control(struct ucsi * ucsi,u64 command)82 static int yoga_c630_ucsi_async_control(struct ucsi *ucsi, u64 command)
83 {
84 struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
85
86 return yoga_c630_ec_ucsi_write(uec->ec, (u8*)&command);
87 }
88
yoga_c630_ucsi_sync_control(struct ucsi * ucsi,u64 command,u32 * cci,void * data,size_t size,void * msg_out,size_t msg_out_size)89 static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi,
90 u64 command,
91 u32 *cci,
92 void *data, size_t size,
93 void *msg_out, size_t msg_out_size)
94 {
95 int ret;
96
97 /*
98 * EC doesn't return connector's DP mode even though it is supported.
99 * Fake it.
100 */
101 if (UCSI_COMMAND(command) == UCSI_GET_ALTERNATE_MODES &&
102 UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(command) == 1 &&
103 UCSI_ALTMODE_RECIPIENT(command) == UCSI_RECIPIENT_CON &&
104 UCSI_ALTMODE_OFFSET(command) == 0) {
105 static const struct ucsi_altmode alt = {
106 .svid = USB_TYPEC_DP_SID,
107 .mid = USB_TYPEC_DP_MODE,
108 };
109
110 dev_dbg(ucsi->dev, "faking DP altmode for con1\n");
111 memset(data, 0, size);
112 memcpy(data, &alt, min(sizeof(alt), size));
113 *cci = UCSI_CCI_COMMAND_COMPLETE | UCSI_SET_CCI_LENGTH(sizeof(alt));
114 return 0;
115 }
116
117 /*
118 * EC can return AltModes present on CON1 (port0, right) for CON2
119 * (port1, left) too. Ignore all requests going to CON2 (it doesn't
120 * support DP anyway).
121 */
122 if (UCSI_COMMAND(command) == UCSI_GET_ALTERNATE_MODES &&
123 UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(command) == 2) {
124 dev_dbg(ucsi->dev, "ignoring altmodes for con2\n");
125 memset(data, 0, size);
126 *cci = UCSI_CCI_COMMAND_COMPLETE;
127 return 0;
128 }
129
130 ret = ucsi_sync_control_common(ucsi, command, cci,
131 data, size, msg_out, msg_out_size);
132 if (ret < 0)
133 return ret;
134
135 /* UCSI_GET_CURRENT_CAM is off-by-one on all ports */
136 if (UCSI_COMMAND(command) == UCSI_GET_CURRENT_CAM && data)
137 ((u8 *)data)[0]--;
138
139 return ret;
140 }
141
yoga_c630_ucsi_update_connector(struct ucsi_connector * con)142 static void yoga_c630_ucsi_update_connector(struct ucsi_connector *con)
143 {
144 if (con->num == 1)
145 con->typec_cap.orientation_aware = true;
146 }
147
148 static const struct ucsi_operations yoga_c630_ucsi_ops = {
149 .read_version = yoga_c630_ucsi_read_version,
150 .read_cci = yoga_c630_ucsi_read_cci,
151 .poll_cci = yoga_c630_ucsi_read_cci,
152 .read_message_in = yoga_c630_ucsi_read_message_in,
153 .sync_control = yoga_c630_ucsi_sync_control,
154 .async_control = yoga_c630_ucsi_async_control,
155 .update_connector = yoga_c630_ucsi_update_connector,
156 };
157
yoga_c630_ucsi_read_port0_status(struct yoga_c630_ucsi * uec)158 static void yoga_c630_ucsi_read_port0_status(struct yoga_c630_ucsi *uec)
159 {
160 int val;
161 unsigned int muxc, ccst, dppn, hpds, hsfl;
162
163 val = yoga_c630_ec_read16(uec->ec, LENOVO_EC_USB_MUX);
164
165 muxc = FIELD_GET(USB_MUX_MUXC, val);
166 ccst = FIELD_GET(USB_MUX_CCST, val);
167 dppn = FIELD_GET(USB_MUX_DPPN, val);
168 hpds = FIELD_GET(USB_MUX_HPDS, val);
169 hsfl = FIELD_GET(USB_MUX_HSFL, val);
170
171 dev_dbg(uec->ucsi->dev, " mux %04x (muxc %d ccst %d dppn %d hpds %d hsfl %d)\n",
172 val,
173 muxc, ccst, dppn, hpds, hsfl);
174
175 if (uec->ucsi->connector && uec->ucsi->connector[0].port)
176 typec_set_orientation(uec->ucsi->connector[0].port,
177 ccst == 1 ?
178 TYPEC_ORIENTATION_REVERSE :
179 TYPEC_ORIENTATION_NORMAL);
180
181 if (uec->bridge)
182 drm_aux_hpd_bridge_notify(&uec->bridge->dev,
183 dppn != 0 ?
184 connector_status_connected :
185 connector_status_disconnected);
186
187 }
188
yoga_c630_ucsi_notify(struct notifier_block * nb,unsigned long action,void * data)189 static int yoga_c630_ucsi_notify(struct notifier_block *nb,
190 unsigned long action, void *data)
191 {
192 struct yoga_c630_ucsi *uec = container_of(nb, struct yoga_c630_ucsi, nb);
193 u32 cci;
194 int ret;
195
196 switch (action) {
197 case LENOVO_EC_EVENT_USB:
198 case LENOVO_EC_EVENT_HPD:
199 yoga_c630_ucsi_read_port0_status(uec);
200 ucsi_connector_change(uec->ucsi, 1);
201 return NOTIFY_OK;
202
203 case LENOVO_EC_EVENT_UCSI:
204 ret = uec->ucsi->ops->read_cci(uec->ucsi, &cci);
205 if (ret)
206 return NOTIFY_DONE;
207
208 ucsi_notify_common(uec->ucsi, cci);
209
210 return NOTIFY_OK;
211
212 default:
213 return NOTIFY_DONE;
214 }
215 }
216
yoga_c630_ucsi_probe(struct auxiliary_device * adev,const struct auxiliary_device_id * id)217 static int yoga_c630_ucsi_probe(struct auxiliary_device *adev,
218 const struct auxiliary_device_id *id)
219 {
220 struct yoga_c630_ec *ec = adev->dev.platform_data;
221 struct yoga_c630_ucsi *uec;
222 int ret;
223
224 uec = devm_kzalloc(&adev->dev, sizeof(*uec), GFP_KERNEL);
225 if (!uec)
226 return -ENOMEM;
227
228 uec->ec = ec;
229 uec->nb.notifier_call = yoga_c630_ucsi_notify;
230
231 device_for_each_child_node_scoped(&adev->dev, fwnode) {
232 u32 port;
233
234 ret = fwnode_property_read_u32(fwnode, "reg", &port);
235 if (ret < 0) {
236 dev_err(&adev->dev, "missing reg property of %pfwP\n", fwnode);
237 return ret;
238 }
239
240 /* DP is only on port0 */
241 if (port != 0)
242 continue;
243
244 uec->bridge = devm_drm_dp_hpd_bridge_alloc(&adev->dev, to_of_node(fwnode));
245 if (IS_ERR(uec->bridge))
246 return PTR_ERR(uec->bridge);
247 }
248
249 uec->ucsi = ucsi_create(&adev->dev, &yoga_c630_ucsi_ops);
250 if (IS_ERR(uec->ucsi))
251 return PTR_ERR(uec->ucsi);
252
253 ucsi_set_drvdata(uec->ucsi, uec);
254
255 uec->version = yoga_c630_ec_ucsi_get_version(uec->ec);
256
257 auxiliary_set_drvdata(adev, uec);
258
259 ret = yoga_c630_ec_register_notify(ec, &uec->nb);
260 if (ret)
261 goto err_destroy;
262
263 ret = ucsi_register(uec->ucsi);
264 if (ret)
265 goto err_unregister;
266
267 if (uec->bridge) {
268 ret = devm_drm_dp_hpd_bridge_add(&adev->dev, uec->bridge);
269 if (ret)
270 goto err_ucsi_unregister;
271 }
272
273 return 0;
274
275 err_ucsi_unregister:
276 ucsi_unregister(uec->ucsi);
277
278 err_unregister:
279 yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
280
281 err_destroy:
282 ucsi_destroy(uec->ucsi);
283
284 return ret;
285 }
286
yoga_c630_ucsi_remove(struct auxiliary_device * adev)287 static void yoga_c630_ucsi_remove(struct auxiliary_device *adev)
288 {
289 struct yoga_c630_ucsi *uec = auxiliary_get_drvdata(adev);
290
291 ucsi_unregister(uec->ucsi);
292 yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
293 ucsi_destroy(uec->ucsi);
294 }
295
296 static const struct auxiliary_device_id yoga_c630_ucsi_id_table[] = {
297 { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_UCSI, },
298 {}
299 };
300 MODULE_DEVICE_TABLE(auxiliary, yoga_c630_ucsi_id_table);
301
302 static struct auxiliary_driver yoga_c630_ucsi_driver = {
303 .name = YOGA_C630_DEV_UCSI,
304 .id_table = yoga_c630_ucsi_id_table,
305 .probe = yoga_c630_ucsi_probe,
306 .remove = yoga_c630_ucsi_remove,
307 };
308
309 module_auxiliary_driver(yoga_c630_ucsi_driver);
310
311 MODULE_DESCRIPTION("Lenovo Yoga C630 UCSI");
312 MODULE_LICENSE("GPL");
313