xref: /linux/drivers/usb/typec/ucsi/ucsi_yoga_c630.c (revision c875a6c3246713a018d8b7b143deb3ebaeecaf1c)
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)89 static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi,
90 				       u64 command,
91 				       u32 *cci,
92 				       void *data, size_t size)
93 {
94 	int ret;
95 
96 	/*
97 	 * EC doesn't return connector's DP mode even though it is supported.
98 	 * Fake it.
99 	 */
100 	if (UCSI_COMMAND(command) == UCSI_GET_ALTERNATE_MODES &&
101 	    UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(command) == 1 &&
102 	    UCSI_ALTMODE_RECIPIENT(command) == UCSI_RECIPIENT_CON &&
103 	    UCSI_ALTMODE_OFFSET(command) == 0) {
104 		static const struct ucsi_altmode alt = {
105 			.svid = USB_TYPEC_DP_SID,
106 			.mid = USB_TYPEC_DP_MODE,
107 		};
108 
109 		dev_dbg(ucsi->dev, "faking DP altmode for con1\n");
110 		memset(data, 0, size);
111 		memcpy(data, &alt, min(sizeof(alt), size));
112 		*cci = UCSI_CCI_COMMAND_COMPLETE | UCSI_SET_CCI_LENGTH(sizeof(alt));
113 		return 0;
114 	}
115 
116 	/*
117 	 * EC can return AltModes present on CON1 (port0, right) for CON2
118 	 * (port1, left) too. Ignore all requests going to CON2 (it doesn't
119 	 * support DP anyway).
120 	 */
121 	if (UCSI_COMMAND(command) == UCSI_GET_ALTERNATE_MODES &&
122 	    UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(command) == 2) {
123 		dev_dbg(ucsi->dev, "ignoring altmodes for con2\n");
124 		memset(data, 0, size);
125 		*cci = UCSI_CCI_COMMAND_COMPLETE;
126 		return 0;
127 	}
128 
129 	ret = ucsi_sync_control_common(ucsi, command, cci, data, size);
130 	if (ret < 0)
131 		return ret;
132 
133 	/* UCSI_GET_CURRENT_CAM is off-by-one on all ports */
134 	if (UCSI_COMMAND(command) == UCSI_GET_CURRENT_CAM && data)
135 		((u8 *)data)[0]--;
136 
137 	return ret;
138 }
139 
yoga_c630_ucsi_update_altmodes(struct ucsi * ucsi,u8 recipient,struct ucsi_altmode * orig,struct ucsi_altmode * updated)140 static bool yoga_c630_ucsi_update_altmodes(struct ucsi *ucsi,
141 					   u8 recipient,
142 					   struct ucsi_altmode *orig,
143 					   struct ucsi_altmode *updated)
144 {
145 	int i;
146 
147 	if (orig[0].svid == 0 || recipient != UCSI_RECIPIENT_SOP)
148 		return false;
149 
150 	/* EC is nice and repeats altmodes again and again. Ignore copies. */
151 	for (i = 1; i < UCSI_MAX_ALTMODES; i++) {
152 		if (orig[i].svid == orig[0].svid) {
153 			dev_dbg(ucsi->dev, "Found duplicate altmodes, starting from %d\n", i);
154 			memset(&orig[i], 0, (UCSI_MAX_ALTMODES - i) * sizeof(*orig));
155 			break;
156 		}
157 	}
158 
159 	return false;
160 }
161 
yoga_c630_ucsi_update_connector(struct ucsi_connector * con)162 static void yoga_c630_ucsi_update_connector(struct ucsi_connector *con)
163 {
164 	if (con->num == 1)
165 		con->typec_cap.orientation_aware = true;
166 }
167 
168 static const struct ucsi_operations yoga_c630_ucsi_ops = {
169 	.read_version = yoga_c630_ucsi_read_version,
170 	.read_cci = yoga_c630_ucsi_read_cci,
171 	.poll_cci = yoga_c630_ucsi_read_cci,
172 	.read_message_in = yoga_c630_ucsi_read_message_in,
173 	.sync_control = yoga_c630_ucsi_sync_control,
174 	.async_control = yoga_c630_ucsi_async_control,
175 	.update_altmodes = yoga_c630_ucsi_update_altmodes,
176 	.update_connector = yoga_c630_ucsi_update_connector,
177 };
178 
yoga_c630_ucsi_read_port0_status(struct yoga_c630_ucsi * uec)179 static void yoga_c630_ucsi_read_port0_status(struct yoga_c630_ucsi *uec)
180 {
181 	int val;
182 	unsigned int muxc, ccst, dppn, hpds, hsfl;
183 
184 	val = yoga_c630_ec_read16(uec->ec, LENOVO_EC_USB_MUX);
185 
186 	muxc = FIELD_GET(USB_MUX_MUXC, val);
187 	ccst = FIELD_GET(USB_MUX_CCST, val);
188 	dppn = FIELD_GET(USB_MUX_DPPN, val);
189 	hpds = FIELD_GET(USB_MUX_HPDS, val);
190 	hsfl = FIELD_GET(USB_MUX_HSFL, val);
191 
192 	dev_dbg(uec->ucsi->dev, " mux %04x (muxc %d ccst %d dppn %d hpds %d hsfl %d)\n",
193 		val,
194 		muxc, ccst, dppn, hpds, hsfl);
195 
196 	if (uec->ucsi->connector && uec->ucsi->connector[0].port)
197 		typec_set_orientation(uec->ucsi->connector[0].port,
198 				      ccst == 1 ?
199 				      TYPEC_ORIENTATION_REVERSE :
200 				      TYPEC_ORIENTATION_NORMAL);
201 
202 	if (uec->bridge)
203 		drm_aux_hpd_bridge_notify(&uec->bridge->dev,
204 					  dppn != 0 ?
205 					  connector_status_connected :
206 					  connector_status_disconnected);
207 
208 }
209 
yoga_c630_ucsi_notify(struct notifier_block * nb,unsigned long action,void * data)210 static int yoga_c630_ucsi_notify(struct notifier_block *nb,
211 				 unsigned long action, void *data)
212 {
213 	struct yoga_c630_ucsi *uec = container_of(nb, struct yoga_c630_ucsi, nb);
214 	u32 cci;
215 	int ret;
216 
217 	switch (action) {
218 	case LENOVO_EC_EVENT_USB:
219 	case LENOVO_EC_EVENT_HPD:
220 		yoga_c630_ucsi_read_port0_status(uec);
221 		ucsi_connector_change(uec->ucsi, 1);
222 		return NOTIFY_OK;
223 
224 	case LENOVO_EC_EVENT_UCSI:
225 		ret = uec->ucsi->ops->read_cci(uec->ucsi, &cci);
226 		if (ret)
227 			return NOTIFY_DONE;
228 
229 		ucsi_notify_common(uec->ucsi, cci);
230 
231 		return NOTIFY_OK;
232 
233 	default:
234 		return NOTIFY_DONE;
235 	}
236 }
237 
yoga_c630_ucsi_probe(struct auxiliary_device * adev,const struct auxiliary_device_id * id)238 static int yoga_c630_ucsi_probe(struct auxiliary_device *adev,
239 				const struct auxiliary_device_id *id)
240 {
241 	struct yoga_c630_ec *ec = adev->dev.platform_data;
242 	struct yoga_c630_ucsi *uec;
243 	int ret;
244 
245 	uec = devm_kzalloc(&adev->dev, sizeof(*uec), GFP_KERNEL);
246 	if (!uec)
247 		return -ENOMEM;
248 
249 	uec->ec = ec;
250 	uec->nb.notifier_call = yoga_c630_ucsi_notify;
251 
252 	device_for_each_child_node_scoped(&adev->dev, fwnode) {
253 		u32 port;
254 
255 		ret = fwnode_property_read_u32(fwnode, "reg", &port);
256 		if (ret < 0) {
257 			dev_err(&adev->dev, "missing reg property of %pfwP\n", fwnode);
258 			return ret;
259 		}
260 
261 		/* DP is only on port0 */
262 		if (port != 0)
263 			continue;
264 
265 		uec->bridge = devm_drm_dp_hpd_bridge_alloc(&adev->dev, to_of_node(fwnode));
266 		if (IS_ERR(uec->bridge))
267 			return PTR_ERR(uec->bridge);
268 	}
269 
270 	uec->ucsi = ucsi_create(&adev->dev, &yoga_c630_ucsi_ops);
271 	if (IS_ERR(uec->ucsi))
272 		return PTR_ERR(uec->ucsi);
273 
274 	ucsi_set_drvdata(uec->ucsi, uec);
275 
276 	uec->version = yoga_c630_ec_ucsi_get_version(uec->ec);
277 
278 	auxiliary_set_drvdata(adev, uec);
279 
280 	ret = yoga_c630_ec_register_notify(ec, &uec->nb);
281 	if (ret)
282 		goto err_destroy;
283 
284 	ret = ucsi_register(uec->ucsi);
285 	if (ret)
286 		goto err_unregister;
287 
288 	if (uec->bridge) {
289 		ret = devm_drm_dp_hpd_bridge_add(&adev->dev, uec->bridge);
290 		if (ret)
291 			goto err_ucsi_unregister;
292 	}
293 
294 	return 0;
295 
296 err_ucsi_unregister:
297 	ucsi_unregister(uec->ucsi);
298 
299 err_unregister:
300 	yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
301 
302 err_destroy:
303 	ucsi_destroy(uec->ucsi);
304 
305 	return ret;
306 }
307 
yoga_c630_ucsi_remove(struct auxiliary_device * adev)308 static void yoga_c630_ucsi_remove(struct auxiliary_device *adev)
309 {
310 	struct yoga_c630_ucsi *uec = auxiliary_get_drvdata(adev);
311 
312 	ucsi_unregister(uec->ucsi);
313 	yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
314 	ucsi_destroy(uec->ucsi);
315 }
316 
317 static const struct auxiliary_device_id yoga_c630_ucsi_id_table[] = {
318 	{ .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_UCSI, },
319 	{}
320 };
321 MODULE_DEVICE_TABLE(auxiliary, yoga_c630_ucsi_id_table);
322 
323 static struct auxiliary_driver yoga_c630_ucsi_driver = {
324 	.name = YOGA_C630_DEV_UCSI,
325 	.id_table = yoga_c630_ucsi_id_table,
326 	.probe = yoga_c630_ucsi_probe,
327 	.remove = yoga_c630_ucsi_remove,
328 };
329 
330 module_auxiliary_driver(yoga_c630_ucsi_driver);
331 
332 MODULE_DESCRIPTION("Lenovo Yoga C630 UCSI");
333 MODULE_LICENSE("GPL");
334