File Index
Files and directories
Sign in
FILE

ucsi_huawei_gaokun.c

File preview with syntax highlighting.

linux-6.19.14/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go
  4 *
  5 * Copyright (C) 2024-2025 Pengyu Luo <mitltlatltl@gmail.com>
  6 */
  7
  8#include <drm/bridge/aux-bridge.h>
  9#include <linux/auxiliary_bus.h>
 10#include <linux/bitops.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/platform_data/huawei-gaokun-ec.h>
 17#include <linux/string.h>
 18#include <linux/usb/pd_vdo.h>
 19#include <linux/usb/typec_altmode.h>
 20#include <linux/usb/typec_dp.h>
 21#include <linux/workqueue_types.h>
 22
 23#include "ucsi.h"
 24
 25#define EC_EVENT_UCSI	0x21
 26#define EC_EVENT_USB	0x22
 27
 28#define GAOKUN_CCX_MASK		GENMASK(1, 0)
 29#define GAOKUN_MUX_MASK		GENMASK(3, 2)
 30
 31#define GAOKUN_DPAM_MASK	GENMASK(3, 0)
 32#define GAOKUN_HPD_STATE_MASK	BIT(4)
 33#define GAOKUN_HPD_IRQ_MASK	BIT(5)
 34
 35#define GET_IDX(updt) (ffs(updt) - 1)
 36
 37#define CCX_TO_ORI(ccx) (++(ccx) % 3) /* convert ccx to enum typec_orientation */
 38
 39/* Configuration Channel Extension */
 40enum gaokun_ucsi_ccx {
 41	USBC_CCX_NORMAL,
 42	USBC_CCX_REVERSE,
 43	USBC_CCX_NONE,
 44};
 45
 46enum gaokun_ucsi_mux {
 47	USBC_MUX_NONE,
 48	USBC_MUX_USB_2L,
 49	USBC_MUX_DP_4L,
 50	USBC_MUX_USB_DP,
 51};
 52
 53/* based on pmic_glink_altmode_pin_assignment */
 54enum gaokun_ucsi_dpam_pan {	/* DP Alt Mode Pin Assignments */
 55	USBC_DPAM_PAN_NONE,
 56	USBC_DPAM_PAN_A,	/* Not supported after USB Type-C Standard v1.0b */
 57	USBC_DPAM_PAN_B,	/* Not supported after USB Type-C Standard v1.0b */
 58	USBC_DPAM_PAN_C,	/* USBC_DPAM_PAN_C_REVERSE - 6 */
 59	USBC_DPAM_PAN_D,
 60	USBC_DPAM_PAN_E,
 61	USBC_DPAM_PAN_F,	/* Not supported after USB Type-C Standard v1.0b */
 62	USBC_DPAM_PAN_A_REVERSE,/* Not supported after USB Type-C Standard v1.0b */
 63	USBC_DPAM_PAN_B_REVERSE,/* Not supported after USB Type-C Standard v1.0b */
 64	USBC_DPAM_PAN_C_REVERSE,
 65	USBC_DPAM_PAN_D_REVERSE,
 66	USBC_DPAM_PAN_E_REVERSE,
 67	USBC_DPAM_PAN_F_REVERSE,/* Not supported after USB Type-C Standard v1.0b */
 68};
 69
 70struct gaokun_ucsi_reg {
 71	u8 num_ports;
 72	u8 port_updt;
 73	u8 port_data[4];
 74	u8 checksum;
 75	u8 reserved;
 76} __packed;
 77
 78struct gaokun_ucsi_port {
 79	struct completion usb_ack;
 80	spinlock_t lock; /* serializing port resource access */
 81
 82	struct gaokun_ucsi *ucsi;
 83	struct auxiliary_device *bridge;
 84
 85	int idx;
 86	enum gaokun_ucsi_ccx ccx;
 87	enum gaokun_ucsi_mux mux;
 88	u8 mode;
 89	u16 svid;
 90	u8 hpd_state;
 91	u8 hpd_irq;
 92};
 93
 94struct gaokun_ucsi {
 95	struct gaokun_ec *ec;
 96	struct ucsi *ucsi;
 97	struct gaokun_ucsi_port *ports;
 98	struct device *dev;
 99	struct delayed_work work;
100	struct notifier_block nb;
101	u16 version;
102	u8 num_ports;
103};
104
105/* -------------------------------------------------------------------------- */
106/* For UCSI */
107
108static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version)
109{
110	struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
111
112	*version = uec->version;
113
114	return 0;
115}
116
117static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
118{
119	struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
120	u8 buf[GAOKUN_UCSI_READ_SIZE];
121	int ret;
122
123	ret = gaokun_ec_ucsi_read(uec->ec, buf);
124	if (ret)
125		return ret;
126
127	memcpy(cci, buf, sizeof(*cci));
128
129	return 0;
130}
131
132static int gaokun_ucsi_read_message_in(struct ucsi *ucsi,
133				       void *val, size_t val_len)
134{
135	struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
136	u8 buf[GAOKUN_UCSI_READ_SIZE];
137	int ret;
138
139	ret = gaokun_ec_ucsi_read(uec->ec, buf);
140	if (ret)
141		return ret;
142
143	memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE,
144	       min(val_len, GAOKUN_UCSI_MSGI_SIZE));
145
146	return 0;
147}
148
149static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command)
150{
151	struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi);
152	u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {};
153
154	memcpy(buf, &command, sizeof(command));
155
156	return gaokun_ec_ucsi_write(uec->ec, buf);
157}
158
159static void gaokun_ucsi_update_connector(struct ucsi_connector *con)
160{
161	struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
162
163	if (con->num > uec->num_ports)
164		return;
165
166	con->typec_cap.orientation_aware = true;
167}
168
169static void gaokun_set_orientation(struct ucsi_connector *con,
170				   struct gaokun_ucsi_port *port)
171{
172	enum gaokun_ucsi_ccx ccx;
173	unsigned long flags;
174
175	spin_lock_irqsave(&port->lock, flags);
176	ccx = port->ccx;
177	spin_unlock_irqrestore(&port->lock, flags);
178
179	typec_set_orientation(con->port, CCX_TO_ORI(ccx));
180}
181
182static void gaokun_ucsi_connector_status(struct ucsi_connector *con)
183{
184	struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi);
185	int idx;
186
187	idx = con->num - 1;
188	if (con->num > uec->num_ports) {
189		dev_warn(uec->dev, "set orientation out of range: con%d\n", idx);
190		return;
191	}
192
193	gaokun_set_orientation(con, &uec->ports[idx]);
194}
195
196const struct ucsi_operations gaokun_ucsi_ops = {
197	.read_version = gaokun_ucsi_read_version,
198	.read_cci = gaokun_ucsi_read_cci,
199	.poll_cci = gaokun_ucsi_read_cci,
200	.read_message_in = gaokun_ucsi_read_message_in,
201	.sync_control = ucsi_sync_control_common,
202	.async_control = gaokun_ucsi_async_control,
203	.update_connector = gaokun_ucsi_update_connector,
204	.connector_status = gaokun_ucsi_connector_status,
205};
206
207/* -------------------------------------------------------------------------- */
208/* For Altmode */
209
210static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port,
211				    const u8 *port_data)
212{
213	struct gaokun_ucsi *uec = port->ucsi;
214	int offset = port->idx * 2; /* every port has 2 Bytes data */
215	unsigned long flags;
216	u8 dcc, ddi;
217
218	dcc = port_data[offset];
219	ddi = port_data[offset + 1];
220
221	spin_lock_irqsave(&port->lock, flags);
222
223	port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc);
224	port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc);
225	port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi);
226	port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi);
227	port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi);
228
229	/* Mode and SVID are unused; keeping them to make things clearer */
230	switch (port->mode) {
231	case USBC_DPAM_PAN_C:
232	case USBC_DPAM_PAN_C_REVERSE:
233		port->mode = DP_PIN_ASSIGN_C; /* correct it for usb later */
234		break;
235	case USBC_DPAM_PAN_D:
236	case USBC_DPAM_PAN_D_REVERSE:
237		port->mode = DP_PIN_ASSIGN_D;
238		break;
239	case USBC_DPAM_PAN_E:
240	case USBC_DPAM_PAN_E_REVERSE:
241		port->mode = DP_PIN_ASSIGN_E;
242		break;
243	case USBC_DPAM_PAN_NONE:
244		port->mode = TYPEC_STATE_SAFE;
245		break;
246	default:
247		dev_warn(uec->dev, "unknown mode %d\n", port->mode);
248		break;
249	}
250
251	switch (port->mux) {
252	case USBC_MUX_NONE:
253		port->svid = 0;
254		break;
255	case USBC_MUX_USB_2L:
256		port->svid = USB_SID_PD;
257		port->mode = TYPEC_STATE_USB; /* same as PAN_C, correct it */
258		break;
259	case USBC_MUX_DP_4L:
260	case USBC_MUX_USB_DP:
261		port->svid = USB_SID_DISPLAYPORT;
262		break;
263	default:
264		dev_warn(uec->dev, "unknown mux state %d\n", port->mux);
265		break;
266	}
267
268	spin_unlock_irqrestore(&port->lock, flags);
269}
270
271static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec)
272{
273	struct gaokun_ucsi_reg ureg;
274	int ret, idx;
275
276	ret = gaokun_ec_ucsi_get_reg(uec->ec, &ureg);
277	if (ret)
278		return GAOKUN_UCSI_NO_PORT_UPDATE;
279
280	uec->num_ports = ureg.num_ports;
281	idx = GET_IDX(ureg.port_updt);
282
283	if (idx < 0 || idx >= ureg.num_ports)
284		return GAOKUN_UCSI_NO_PORT_UPDATE;
285
286	gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data);
287	return idx;
288}
289
290static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port)
291{
292	struct gaokun_ucsi *uec = port->ucsi;
293	int idx = port->idx;
294
295	if (idx >= uec->ucsi->cap.num_connectors) {
296		dev_warn(uec->dev, "altmode port out of range: %d\n", idx);
297		return;
298	}
299
300	/* UCSI callback .connector_status() have set orientation */
301	if (port->bridge)
302		drm_aux_hpd_bridge_notify(&port->bridge->dev,
303					  port->hpd_state ?
304					  connector_status_connected :
305					  connector_status_disconnected);
306
307	gaokun_ec_ucsi_pan_ack(uec->ec, port->idx);
308}
309
310static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec)
311{
312	int idx;
313
314	if (!uec->ucsi->connector) { /* slow to register */
315		dev_err_ratelimited(uec->dev, "ucsi connector is not initialized yet\n");
316		return;
317	}
318
319	idx = gaokun_ucsi_refresh(uec);
320	if (idx == GAOKUN_UCSI_NO_PORT_UPDATE)
321		gaokun_ec_ucsi_pan_ack(uec->ec, idx); /* ack directly if no update */
322	else
323		gaokun_ucsi_handle_altmode(&uec->ports[idx]);
324}
325
326/*
327 * When inserting, 2 UCSI events(connector change) are followed by USB events.
328 * If we received one USB event, that means USB events are not blocked, so we
329 * can complelte for all ports, and we should signal all events.
330 */
331static void gaokun_ucsi_complete_usb_ack(struct gaokun_ucsi *uec)
332{
333	struct gaokun_ucsi_port *port;
334	int idx = 0;
335
336	while (idx < uec->num_ports) {
337		port = &uec->ports[idx++];
338		if (!completion_done(&port->usb_ack))
339			complete_all(&port->usb_ack);
340	}
341}
342
343/*
344 * USB event is necessary for enabling altmode, the event should follow
345 * UCSI event, if not after timeout(this notify may be disabled somehow),
346 * then force to enable altmode.
347 */
348static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx)
349{
350	struct gaokun_ucsi_port *port;
351
352	port = &uec->ports[idx];
353	if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) {
354		dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT");
355		gaokun_ucsi_altmode_notify_ind(uec);
356	}
357}
358
359static int gaokun_ucsi_notify(struct notifier_block *nb,
360			      unsigned long action, void *data)
361{
362	u32 cci;
363	int ret;
364	struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb);
365
366	switch (action) {
367	case EC_EVENT_USB:
368		gaokun_ucsi_complete_usb_ack(uec);
369		gaokun_ucsi_altmode_notify_ind(uec);
370		return NOTIFY_OK;
371
372	case EC_EVENT_UCSI:
373		ret = gaokun_ucsi_read_cci(uec->ucsi, &cci);
374		if (ret)
375			return NOTIFY_DONE;
376
377		ucsi_notify_common(uec->ucsi, cci);
378		if (UCSI_CCI_CONNECTOR(cci))
379			gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1);
380
381		return NOTIFY_OK;
382
383	default:
384		return NOTIFY_DONE;
385	}
386}
387
388static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec)
389{
390	struct gaokun_ucsi_port *ucsi_port;
391	struct gaokun_ucsi_reg ureg = {};
392	struct device *dev = uec->dev;
393	struct fwnode_handle *fwnode;
394	int i, ret, num_ports;
395	u32 port;
396
397	gaokun_ec_ucsi_get_reg(uec->ec, &ureg);
398	num_ports = ureg.num_ports;
399	uec->ports = devm_kcalloc(dev, num_ports, sizeof(*uec->ports),
400				  GFP_KERNEL);
401	if (!uec->ports)
402		return -ENOMEM;
403
404	for (i = 0; i < num_ports; ++i) {
405		ucsi_port = &uec->ports[i];
406		ucsi_port->ccx = USBC_CCX_NONE;
407		ucsi_port->idx = i;
408		ucsi_port->ucsi = uec;
409		init_completion(&ucsi_port->usb_ack);
410		spin_lock_init(&ucsi_port->lock);
411	}
412
413	device_for_each_child_node(dev, fwnode) {
414		ret = fwnode_property_read_u32(fwnode, "reg", &port);
415		if (ret < 0) {
416			dev_err(dev, "missing reg property of %pOFn\n", fwnode);
417			fwnode_handle_put(fwnode);
418			return ret;
419		}
420
421		if (port >= num_ports) {
422			dev_warn(dev, "invalid connector number %d, ignoring\n", port);
423			continue;
424		}
425
426		ucsi_port = &uec->ports[port];
427		ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode));
428		if (IS_ERR(ucsi_port->bridge)) {
429			fwnode_handle_put(fwnode);
430			return PTR_ERR(ucsi_port->bridge);
431		}
432	}
433
434	for (i = 0; i < num_ports; i++) {
435		if (!uec->ports[i].bridge)
436			continue;
437
438		ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge);
439		if (ret)
440			return ret;
441	}
442
443	return 0;
444}
445
446static void gaokun_ucsi_register_worker(struct work_struct *work)
447{
448	struct gaokun_ucsi *uec;
449	struct ucsi *ucsi;
450	int ret;
451
452	uec = container_of(work, struct gaokun_ucsi, work.work);
453	ucsi = uec->ucsi;
454
455	ret = gaokun_ec_register_notify(uec->ec, &uec->nb);
456	if (ret) {
457		dev_err_probe(ucsi->dev, ret, "notifier register failed\n");
458		return;
459	}
460
461	ret = ucsi_register(ucsi);
462	if (ret)
463		dev_err_probe(ucsi->dev, ret, "ucsi register failed\n");
464}
465
466static int gaokun_ucsi_probe(struct auxiliary_device *adev,
467			     const struct auxiliary_device_id *id)
468{
469	struct gaokun_ec *ec = adev->dev.platform_data;
470	struct device *dev = &adev->dev;
471	struct gaokun_ucsi *uec;
472	int ret;
473
474	uec = devm_kzalloc(dev, sizeof(*uec), GFP_KERNEL);
475	if (!uec)
476		return -ENOMEM;
477
478	uec->ec = ec;
479	uec->dev = dev;
480	uec->version = UCSI_VERSION_1_0;
481	uec->nb.notifier_call = gaokun_ucsi_notify;
482
483	INIT_DELAYED_WORK(&uec->work, gaokun_ucsi_register_worker);
484
485	ret = gaokun_ucsi_ports_init(uec);
486	if (ret)
487		return ret;
488
489	uec->ucsi = ucsi_create(dev, &gaokun_ucsi_ops);
490	if (IS_ERR(uec->ucsi))
491		return PTR_ERR(uec->ucsi);
492
493	ucsi_set_drvdata(uec->ucsi, uec);
494	auxiliary_set_drvdata(adev, uec);
495
496	/* EC can't handle UCSI properly in the early stage */
497	schedule_delayed_work(&uec->work, 3 * HZ);
498
499	return 0;
500}
501
502static void gaokun_ucsi_remove(struct auxiliary_device *adev)
503{
504	struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev);
505
506	disable_delayed_work_sync(&uec->work);
507	gaokun_ec_unregister_notify(uec->ec, &uec->nb);
508	ucsi_unregister(uec->ucsi);
509	ucsi_destroy(uec->ucsi);
510}
511
512static const struct auxiliary_device_id gaokun_ucsi_id_table[] = {
513	{ .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_UCSI, },
514	{}
515};
516MODULE_DEVICE_TABLE(auxiliary, gaokun_ucsi_id_table);
517
518static struct auxiliary_driver gaokun_ucsi_driver = {
519	.name = GAOKUN_DEV_UCSI,
520	.id_table = gaokun_ucsi_id_table,
521	.probe = gaokun_ucsi_probe,
522	.remove = gaokun_ucsi_remove,
523};
524
525module_auxiliary_driver(gaokun_ucsi_driver);
526
527MODULE_DESCRIPTION("HUAWEI Matebook E Go UCSI driver");
528MODULE_LICENSE("GPL");
~/ Read-only file preview.