xref: /linux/drivers/usb/typec/ucsi/ucsi_yoga_c630.c (revision fab183d632628381b466a41479489541ac0e29a0)
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