1 // SPDX-License-Identifier: GPL-2.0
2 // ChromeOS Embedded Controller extcon
4 // Copyright (C) 2017 Google, Inc.
5 // Author: Benson Leung <bleung@chromium.org>
7 #include <linux/extcon-provider.h>
8 #include <linux/kernel.h>
9 #include <linux/module.h>
10 #include <linux/notifier.h>
12 #include <linux/platform_data/cros_ec_commands.h>
13 #include <linux/platform_data/cros_ec_proto.h>
14 #include <linux/platform_device.h>
15 #include <linux/slab.h>
16 #include <linux/sched.h>
18 struct cros_ec_extcon_info
{
20 struct extcon_dev
*edev
;
24 struct cros_ec_device
*ec
;
26 struct notifier_block notifier
;
28 unsigned int dr
; /* data role */
29 bool pr
; /* power role (true if VBUS enabled) */
30 bool dp
; /* DisplayPort enabled */
31 bool mux
; /* SuperSpeed (usb3) enabled */
32 unsigned int power_type
;
35 static const unsigned int usb_type_c_cable
[] = {
49 * cros_ec_pd_command() - Send a command to the EC.
50 * @info: pointer to struct cros_ec_extcon_info
51 * @command: EC command
52 * @version: EC command version
53 * @outdata: EC command output data
54 * @outsize: Size of outdata
55 * @indata: EC command input data
56 * @insize: Size of indata
58 * Return: 0 on success, <0 on failure.
60 static int cros_ec_pd_command(struct cros_ec_extcon_info
*info
,
68 struct cros_ec_command
*msg
;
71 msg
= kzalloc(struct_size(msg
, data
, max(outsize
, insize
)), GFP_KERNEL
);
75 msg
->version
= version
;
76 msg
->command
= command
;
77 msg
->outsize
= outsize
;
81 memcpy(msg
->data
, outdata
, outsize
);
83 ret
= cros_ec_cmd_xfer_status(info
->ec
, msg
);
84 if (ret
>= 0 && insize
)
85 memcpy(indata
, msg
->data
, insize
);
92 * cros_ec_usb_get_power_type() - Get power type info about PD device attached
94 * @info: pointer to struct cros_ec_extcon_info
96 * Return: power type on success, <0 on failure.
98 static int cros_ec_usb_get_power_type(struct cros_ec_extcon_info
*info
)
100 struct ec_params_usb_pd_power_info req
;
101 struct ec_response_usb_pd_power_info resp
;
104 req
.port
= info
->port_id
;
105 ret
= cros_ec_pd_command(info
, EC_CMD_USB_PD_POWER_INFO
, 0,
106 &req
, sizeof(req
), &resp
, sizeof(resp
));
114 * cros_ec_usb_get_pd_mux_state() - Get PD mux state for given port.
115 * @info: pointer to struct cros_ec_extcon_info
117 * Return: PD mux state on success, <0 on failure.
119 static int cros_ec_usb_get_pd_mux_state(struct cros_ec_extcon_info
*info
)
121 struct ec_params_usb_pd_mux_info req
;
122 struct ec_response_usb_pd_mux_info resp
;
125 req
.port
= info
->port_id
;
126 ret
= cros_ec_pd_command(info
, EC_CMD_USB_PD_MUX_INFO
, 0,
128 &resp
, sizeof(resp
));
136 * cros_ec_usb_get_role() - Get role info about possible PD device attached to a
138 * @info: pointer to struct cros_ec_extcon_info
139 * @polarity: pointer to cable polarity (return value)
141 * Return: role info on success, -ENOTCONN if no cable is connected, <0 on
144 static int cros_ec_usb_get_role(struct cros_ec_extcon_info
*info
,
147 struct ec_params_usb_pd_control pd_control
;
148 struct ec_response_usb_pd_control_v1 resp
;
151 pd_control
.port
= info
->port_id
;
152 pd_control
.role
= USB_PD_CTRL_ROLE_NO_CHANGE
;
153 pd_control
.mux
= USB_PD_CTRL_MUX_NO_CHANGE
;
154 pd_control
.swap
= USB_PD_CTRL_SWAP_NONE
;
155 ret
= cros_ec_pd_command(info
, EC_CMD_USB_PD_CONTROL
, 1,
156 &pd_control
, sizeof(pd_control
),
157 &resp
, sizeof(resp
));
161 if (!(resp
.enabled
& PD_CTRL_RESP_ENABLED_CONNECTED
))
164 *polarity
= resp
.polarity
;
170 * cros_ec_pd_get_num_ports() - Get number of EC charge ports.
171 * @info: pointer to struct cros_ec_extcon_info
173 * Return: number of ports on success, <0 on failure.
175 static int cros_ec_pd_get_num_ports(struct cros_ec_extcon_info
*info
)
177 struct ec_response_usb_pd_ports resp
;
180 ret
= cros_ec_pd_command(info
, EC_CMD_USB_PD_PORTS
,
181 0, NULL
, 0, &resp
, sizeof(resp
));
185 return resp
.num_ports
;
188 static const char *cros_ec_usb_role_string(unsigned int role
)
190 return role
== DR_NONE
? "DISCONNECTED" :
191 (role
== DR_HOST
? "DFP" : "UFP");
194 static const char *cros_ec_usb_power_type_string(unsigned int type
)
197 case USB_CHG_TYPE_NONE
:
198 return "USB_CHG_TYPE_NONE";
199 case USB_CHG_TYPE_PD
:
200 return "USB_CHG_TYPE_PD";
201 case USB_CHG_TYPE_PROPRIETARY
:
202 return "USB_CHG_TYPE_PROPRIETARY";
204 return "USB_CHG_TYPE_C";
205 case USB_CHG_TYPE_BC12_DCP
:
206 return "USB_CHG_TYPE_BC12_DCP";
207 case USB_CHG_TYPE_BC12_CDP
:
208 return "USB_CHG_TYPE_BC12_CDP";
209 case USB_CHG_TYPE_BC12_SDP
:
210 return "USB_CHG_TYPE_BC12_SDP";
211 case USB_CHG_TYPE_OTHER
:
212 return "USB_CHG_TYPE_OTHER";
213 case USB_CHG_TYPE_VBUS
:
214 return "USB_CHG_TYPE_VBUS";
215 case USB_CHG_TYPE_UNKNOWN
:
216 return "USB_CHG_TYPE_UNKNOWN";
218 return "USB_CHG_TYPE_UNKNOWN";
222 static bool cros_ec_usb_power_type_is_wall_wart(unsigned int type
,
226 /* FIXME : Guppy, Donnettes, and other chargers will be miscategorized
227 * because they identify with USB_CHG_TYPE_C, but we can't return true
228 * here from that code because that breaks Suzy-Q and other kinds of
229 * USB Type-C cables and peripherals.
231 case USB_CHG_TYPE_PROPRIETARY
:
232 case USB_CHG_TYPE_BC12_DCP
:
234 case USB_CHG_TYPE_PD
:
236 case USB_CHG_TYPE_BC12_CDP
:
237 case USB_CHG_TYPE_BC12_SDP
:
238 case USB_CHG_TYPE_OTHER
:
239 case USB_CHG_TYPE_VBUS
:
240 case USB_CHG_TYPE_UNKNOWN
:
241 case USB_CHG_TYPE_NONE
:
247 static int extcon_cros_ec_detect_cable(struct cros_ec_extcon_info
*info
,
250 struct device
*dev
= info
->dev
;
251 int role
, power_type
;
252 unsigned int dr
= DR_NONE
;
254 bool polarity
= false;
259 power_type
= cros_ec_usb_get_power_type(info
);
260 if (power_type
< 0) {
261 dev_err(dev
, "failed getting power type err = %d\n",
266 role
= cros_ec_usb_get_role(info
, &polarity
);
268 if (role
!= -ENOTCONN
) {
269 dev_err(dev
, "failed getting role err = %d\n", role
);
272 dev_dbg(dev
, "disconnected\n");
276 dr
= (role
& PD_CTRL_RESP_ROLE_DATA
) ? DR_HOST
: DR_DEVICE
;
277 pr
= (role
& PD_CTRL_RESP_ROLE_POWER
);
278 pd_mux_state
= cros_ec_usb_get_pd_mux_state(info
);
279 if (pd_mux_state
< 0)
280 pd_mux_state
= USB_PD_MUX_USB_ENABLED
;
282 dp
= pd_mux_state
& USB_PD_MUX_DP_ENABLED
;
283 mux
= pd_mux_state
& USB_PD_MUX_USB_ENABLED
;
284 hpd
= pd_mux_state
& USB_PD_MUX_HPD_IRQ
;
287 "connected role 0x%x pwr type %d dr %d pr %d pol %d mux %d dp %d hpd %d\n",
288 role
, power_type
, dr
, pr
, polarity
, mux
, dp
, hpd
);
292 * When there is no USB host (e.g. USB PD charger),
293 * we are not really a UFP for the AP.
295 if (dr
== DR_DEVICE
&&
296 cros_ec_usb_power_type_is_wall_wart(power_type
, role
))
299 if (force
|| info
->dr
!= dr
|| info
->pr
!= pr
|| info
->dp
!= dp
||
300 info
->mux
!= mux
|| info
->power_type
!= power_type
) {
301 bool host_connected
= false, device_connected
= false;
303 dev_dbg(dev
, "Type/Role switch! type = %s role = %s\n",
304 cros_ec_usb_power_type_string(power_type
),
305 cros_ec_usb_role_string(dr
));
310 info
->power_type
= power_type
;
313 device_connected
= true;
314 else if (dr
== DR_HOST
)
315 host_connected
= true;
317 extcon_set_state(info
->edev
, EXTCON_USB
, device_connected
);
318 extcon_set_state(info
->edev
, EXTCON_USB_HOST
, host_connected
);
319 extcon_set_state(info
->edev
, EXTCON_DISP_DP
, dp
);
320 extcon_set_property(info
->edev
, EXTCON_USB
,
321 EXTCON_PROP_USB_VBUS
,
322 (union extcon_property_value
)(int)pr
);
323 extcon_set_property(info
->edev
, EXTCON_USB_HOST
,
324 EXTCON_PROP_USB_VBUS
,
325 (union extcon_property_value
)(int)pr
);
326 extcon_set_property(info
->edev
, EXTCON_USB
,
327 EXTCON_PROP_USB_TYPEC_POLARITY
,
328 (union extcon_property_value
)(int)polarity
);
329 extcon_set_property(info
->edev
, EXTCON_USB_HOST
,
330 EXTCON_PROP_USB_TYPEC_POLARITY
,
331 (union extcon_property_value
)(int)polarity
);
332 extcon_set_property(info
->edev
, EXTCON_DISP_DP
,
333 EXTCON_PROP_USB_TYPEC_POLARITY
,
334 (union extcon_property_value
)(int)polarity
);
335 extcon_set_property(info
->edev
, EXTCON_USB
,
337 (union extcon_property_value
)(int)mux
);
338 extcon_set_property(info
->edev
, EXTCON_USB_HOST
,
340 (union extcon_property_value
)(int)mux
);
341 extcon_set_property(info
->edev
, EXTCON_DISP_DP
,
343 (union extcon_property_value
)(int)mux
);
344 extcon_set_property(info
->edev
, EXTCON_DISP_DP
,
345 EXTCON_PROP_DISP_HPD
,
346 (union extcon_property_value
)(int)hpd
);
348 extcon_sync(info
->edev
, EXTCON_USB
);
349 extcon_sync(info
->edev
, EXTCON_USB_HOST
);
350 extcon_sync(info
->edev
, EXTCON_DISP_DP
);
353 extcon_set_property(info
->edev
, EXTCON_DISP_DP
,
354 EXTCON_PROP_DISP_HPD
,
355 (union extcon_property_value
)(int)hpd
);
356 extcon_sync(info
->edev
, EXTCON_DISP_DP
);
362 static int extcon_cros_ec_event(struct notifier_block
*nb
,
363 unsigned long queued_during_suspend
,
366 struct cros_ec_extcon_info
*info
;
367 struct cros_ec_device
*ec
;
370 info
= container_of(nb
, struct cros_ec_extcon_info
, notifier
);
373 host_event
= cros_ec_get_host_event(ec
);
374 if (host_event
& (EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU
) |
375 EC_HOST_EVENT_MASK(EC_HOST_EVENT_USB_MUX
))) {
376 extcon_cros_ec_detect_cable(info
, false);
383 static int extcon_cros_ec_probe(struct platform_device
*pdev
)
385 struct cros_ec_extcon_info
*info
;
386 struct cros_ec_device
*ec
= dev_get_drvdata(pdev
->dev
.parent
);
387 struct device
*dev
= &pdev
->dev
;
388 struct device_node
*np
= dev
->of_node
;
391 info
= devm_kzalloc(dev
, sizeof(*info
), GFP_KERNEL
);
401 ret
= of_property_read_u32(np
, "google,usb-port-id", &port
);
403 dev_err(dev
, "Missing google,usb-port-id property\n");
406 info
->port_id
= port
;
408 info
->port_id
= pdev
->id
;
411 numports
= cros_ec_pd_get_num_ports(info
);
413 dev_err(dev
, "failed getting number of ports! ret = %d\n",
418 if (info
->port_id
>= numports
) {
419 dev_err(dev
, "This system only supports %d ports\n", numports
);
423 info
->edev
= devm_extcon_dev_allocate(dev
, usb_type_c_cable
);
424 if (IS_ERR(info
->edev
)) {
425 dev_err(dev
, "failed to allocate extcon device\n");
429 ret
= devm_extcon_dev_register(dev
, info
->edev
);
431 dev_err(dev
, "failed to register extcon device\n");
435 extcon_set_property_capability(info
->edev
, EXTCON_USB
,
436 EXTCON_PROP_USB_VBUS
);
437 extcon_set_property_capability(info
->edev
, EXTCON_USB_HOST
,
438 EXTCON_PROP_USB_VBUS
);
439 extcon_set_property_capability(info
->edev
, EXTCON_USB
,
440 EXTCON_PROP_USB_TYPEC_POLARITY
);
441 extcon_set_property_capability(info
->edev
, EXTCON_USB_HOST
,
442 EXTCON_PROP_USB_TYPEC_POLARITY
);
443 extcon_set_property_capability(info
->edev
, EXTCON_DISP_DP
,
444 EXTCON_PROP_USB_TYPEC_POLARITY
);
445 extcon_set_property_capability(info
->edev
, EXTCON_USB
,
447 extcon_set_property_capability(info
->edev
, EXTCON_USB_HOST
,
449 extcon_set_property_capability(info
->edev
, EXTCON_DISP_DP
,
451 extcon_set_property_capability(info
->edev
, EXTCON_DISP_DP
,
452 EXTCON_PROP_DISP_HPD
);
457 platform_set_drvdata(pdev
, info
);
459 /* Get PD events from the EC */
460 info
->notifier
.notifier_call
= extcon_cros_ec_event
;
461 ret
= blocking_notifier_chain_register(&info
->ec
->event_notifier
,
464 dev_err(dev
, "failed to register notifier\n");
468 /* Perform initial detection */
469 ret
= extcon_cros_ec_detect_cable(info
, true);
471 dev_err(dev
, "failed to detect initial cable state\n");
472 goto unregister_notifier
;
478 blocking_notifier_chain_unregister(&info
->ec
->event_notifier
,
483 static void extcon_cros_ec_remove(struct platform_device
*pdev
)
485 struct cros_ec_extcon_info
*info
= platform_get_drvdata(pdev
);
487 blocking_notifier_chain_unregister(&info
->ec
->event_notifier
,
491 #ifdef CONFIG_PM_SLEEP
492 static int extcon_cros_ec_suspend(struct device
*dev
)
497 static int extcon_cros_ec_resume(struct device
*dev
)
500 struct cros_ec_extcon_info
*info
= dev_get_drvdata(dev
);
502 ret
= extcon_cros_ec_detect_cable(info
, true);
504 dev_err(dev
, "failed to detect cable state on resume\n");
509 static const struct dev_pm_ops extcon_cros_ec_dev_pm_ops
= {
510 SET_SYSTEM_SLEEP_PM_OPS(extcon_cros_ec_suspend
, extcon_cros_ec_resume
)
513 #define DEV_PM_OPS (&extcon_cros_ec_dev_pm_ops)
515 #define DEV_PM_OPS NULL
516 #endif /* CONFIG_PM_SLEEP */
519 static const struct of_device_id extcon_cros_ec_of_match
[] = {
520 { .compatible
= "google,extcon-usbc-cros-ec" },
523 MODULE_DEVICE_TABLE(of
, extcon_cros_ec_of_match
);
524 #endif /* CONFIG_OF */
526 static struct platform_driver extcon_cros_ec_driver
= {
528 .name
= "extcon-usbc-cros-ec",
529 .of_match_table
= of_match_ptr(extcon_cros_ec_of_match
),
532 .remove
= extcon_cros_ec_remove
,
533 .probe
= extcon_cros_ec_probe
,
536 module_platform_driver(extcon_cros_ec_driver
);
538 MODULE_DESCRIPTION("ChromeOS Embedded Controller extcon driver");
539 MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
540 MODULE_LICENSE("GPL v2");