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/mfd/cros_ec.h>
10 #include <linux/module.h>
11 #include <linux/notifier.h>
13 #include <linux/platform_device.h>
14 #include <linux/slab.h>
15 #include <linux/sched.h>
17 struct cros_ec_extcon_info
{
19 struct extcon_dev
*edev
;
23 struct cros_ec_device
*ec
;
25 struct notifier_block notifier
;
27 unsigned int dr
; /* data role */
28 bool pr
; /* power role (true if VBUS enabled) */
29 bool dp
; /* DisplayPort enabled */
30 bool mux
; /* SuperSpeed (usb3) enabled */
31 unsigned int power_type
;
34 static const unsigned int usb_type_c_cable
[] = {
48 * cros_ec_pd_command() - Send a command to the EC.
49 * @info: pointer to struct cros_ec_extcon_info
50 * @command: EC command
51 * @version: EC command version
52 * @outdata: EC command output data
53 * @outsize: Size of outdata
54 * @indata: EC command input data
55 * @insize: Size of indata
57 * Return: 0 on success, <0 on failure.
59 static int cros_ec_pd_command(struct cros_ec_extcon_info
*info
,
67 struct cros_ec_command
*msg
;
70 msg
= kzalloc(sizeof(*msg
) + max(outsize
, insize
), GFP_KERNEL
);
74 msg
->version
= version
;
75 msg
->command
= command
;
76 msg
->outsize
= outsize
;
80 memcpy(msg
->data
, outdata
, outsize
);
82 ret
= cros_ec_cmd_xfer_status(info
->ec
, msg
);
83 if (ret
>= 0 && insize
)
84 memcpy(indata
, msg
->data
, insize
);
91 * cros_ec_usb_get_power_type() - Get power type info about PD device attached
93 * @info: pointer to struct cros_ec_extcon_info
95 * Return: power type on success, <0 on failure.
97 static int cros_ec_usb_get_power_type(struct cros_ec_extcon_info
*info
)
99 struct ec_params_usb_pd_power_info req
;
100 struct ec_response_usb_pd_power_info resp
;
103 req
.port
= info
->port_id
;
104 ret
= cros_ec_pd_command(info
, EC_CMD_USB_PD_POWER_INFO
, 0,
105 &req
, sizeof(req
), &resp
, sizeof(resp
));
113 * cros_ec_usb_get_pd_mux_state() - Get PD mux state for given port.
114 * @info: pointer to struct cros_ec_extcon_info
116 * Return: PD mux state on success, <0 on failure.
118 static int cros_ec_usb_get_pd_mux_state(struct cros_ec_extcon_info
*info
)
120 struct ec_params_usb_pd_mux_info req
;
121 struct ec_response_usb_pd_mux_info resp
;
124 req
.port
= info
->port_id
;
125 ret
= cros_ec_pd_command(info
, EC_CMD_USB_PD_MUX_INFO
, 0,
127 &resp
, sizeof(resp
));
135 * cros_ec_usb_get_role() - Get role info about possible PD device attached to a
137 * @info: pointer to struct cros_ec_extcon_info
138 * @polarity: pointer to cable polarity (return value)
140 * Return: role info on success, -ENOTCONN if no cable is connected, <0 on
143 static int cros_ec_usb_get_role(struct cros_ec_extcon_info
*info
,
146 struct ec_params_usb_pd_control pd_control
;
147 struct ec_response_usb_pd_control_v1 resp
;
150 pd_control
.port
= info
->port_id
;
151 pd_control
.role
= USB_PD_CTRL_ROLE_NO_CHANGE
;
152 pd_control
.mux
= USB_PD_CTRL_MUX_NO_CHANGE
;
153 pd_control
.swap
= USB_PD_CTRL_SWAP_NONE
;
154 ret
= cros_ec_pd_command(info
, EC_CMD_USB_PD_CONTROL
, 1,
155 &pd_control
, sizeof(pd_control
),
156 &resp
, sizeof(resp
));
160 if (!(resp
.enabled
& PD_CTRL_RESP_ENABLED_CONNECTED
))
163 *polarity
= resp
.polarity
;
169 * cros_ec_pd_get_num_ports() - Get number of EC charge ports.
170 * @info: pointer to struct cros_ec_extcon_info
172 * Return: number of ports on success, <0 on failure.
174 static int cros_ec_pd_get_num_ports(struct cros_ec_extcon_info
*info
)
176 struct ec_response_usb_pd_ports resp
;
179 ret
= cros_ec_pd_command(info
, EC_CMD_USB_PD_PORTS
,
180 0, NULL
, 0, &resp
, sizeof(resp
));
184 return resp
.num_ports
;
187 static const char *cros_ec_usb_role_string(unsigned int role
)
189 return role
== DR_NONE
? "DISCONNECTED" :
190 (role
== DR_HOST
? "DFP" : "UFP");
193 static const char *cros_ec_usb_power_type_string(unsigned int type
)
196 case USB_CHG_TYPE_NONE
:
197 return "USB_CHG_TYPE_NONE";
198 case USB_CHG_TYPE_PD
:
199 return "USB_CHG_TYPE_PD";
200 case USB_CHG_TYPE_PROPRIETARY
:
201 return "USB_CHG_TYPE_PROPRIETARY";
203 return "USB_CHG_TYPE_C";
204 case USB_CHG_TYPE_BC12_DCP
:
205 return "USB_CHG_TYPE_BC12_DCP";
206 case USB_CHG_TYPE_BC12_CDP
:
207 return "USB_CHG_TYPE_BC12_CDP";
208 case USB_CHG_TYPE_BC12_SDP
:
209 return "USB_CHG_TYPE_BC12_SDP";
210 case USB_CHG_TYPE_OTHER
:
211 return "USB_CHG_TYPE_OTHER";
212 case USB_CHG_TYPE_VBUS
:
213 return "USB_CHG_TYPE_VBUS";
214 case USB_CHG_TYPE_UNKNOWN
:
215 return "USB_CHG_TYPE_UNKNOWN";
217 return "USB_CHG_TYPE_UNKNOWN";
221 static bool cros_ec_usb_power_type_is_wall_wart(unsigned int type
,
225 /* FIXME : Guppy, Donnettes, and other chargers will be miscategorized
226 * because they identify with USB_CHG_TYPE_C, but we can't return true
227 * here from that code because that breaks Suzy-Q and other kinds of
228 * USB Type-C cables and peripherals.
230 case USB_CHG_TYPE_PROPRIETARY
:
231 case USB_CHG_TYPE_BC12_DCP
:
233 case USB_CHG_TYPE_PD
:
235 case USB_CHG_TYPE_BC12_CDP
:
236 case USB_CHG_TYPE_BC12_SDP
:
237 case USB_CHG_TYPE_OTHER
:
238 case USB_CHG_TYPE_VBUS
:
239 case USB_CHG_TYPE_UNKNOWN
:
240 case USB_CHG_TYPE_NONE
:
246 static int extcon_cros_ec_detect_cable(struct cros_ec_extcon_info
*info
,
249 struct device
*dev
= info
->dev
;
250 int role
, power_type
;
251 unsigned int dr
= DR_NONE
;
253 bool polarity
= false;
258 power_type
= cros_ec_usb_get_power_type(info
);
259 if (power_type
< 0) {
260 dev_err(dev
, "failed getting power type err = %d\n",
265 role
= cros_ec_usb_get_role(info
, &polarity
);
267 if (role
!= -ENOTCONN
) {
268 dev_err(dev
, "failed getting role err = %d\n", role
);
271 dev_dbg(dev
, "disconnected\n");
275 dr
= (role
& PD_CTRL_RESP_ROLE_DATA
) ? DR_HOST
: DR_DEVICE
;
276 pr
= (role
& PD_CTRL_RESP_ROLE_POWER
);
277 pd_mux_state
= cros_ec_usb_get_pd_mux_state(info
);
278 if (pd_mux_state
< 0)
279 pd_mux_state
= USB_PD_MUX_USB_ENABLED
;
281 dp
= pd_mux_state
& USB_PD_MUX_DP_ENABLED
;
282 mux
= pd_mux_state
& USB_PD_MUX_USB_ENABLED
;
283 hpd
= pd_mux_state
& USB_PD_MUX_HPD_IRQ
;
286 "connected role 0x%x pwr type %d dr %d pr %d pol %d mux %d dp %d hpd %d\n",
287 role
, power_type
, dr
, pr
, polarity
, mux
, dp
, hpd
);
291 * When there is no USB host (e.g. USB PD charger),
292 * we are not really a UFP for the AP.
294 if (dr
== DR_DEVICE
&&
295 cros_ec_usb_power_type_is_wall_wart(power_type
, role
))
298 if (force
|| info
->dr
!= dr
|| info
->pr
!= pr
|| info
->dp
!= dp
||
299 info
->mux
!= mux
|| info
->power_type
!= power_type
) {
300 bool host_connected
= false, device_connected
= false;
302 dev_dbg(dev
, "Type/Role switch! type = %s role = %s\n",
303 cros_ec_usb_power_type_string(power_type
),
304 cros_ec_usb_role_string(dr
));
309 info
->power_type
= power_type
;
312 device_connected
= true;
313 else if (dr
== DR_HOST
)
314 host_connected
= true;
316 extcon_set_state(info
->edev
, EXTCON_USB
, device_connected
);
317 extcon_set_state(info
->edev
, EXTCON_USB_HOST
, host_connected
);
318 extcon_set_state(info
->edev
, EXTCON_DISP_DP
, dp
);
319 extcon_set_property(info
->edev
, EXTCON_USB
,
320 EXTCON_PROP_USB_VBUS
,
321 (union extcon_property_value
)(int)pr
);
322 extcon_set_property(info
->edev
, EXTCON_USB_HOST
,
323 EXTCON_PROP_USB_VBUS
,
324 (union extcon_property_value
)(int)pr
);
325 extcon_set_property(info
->edev
, EXTCON_USB
,
326 EXTCON_PROP_USB_TYPEC_POLARITY
,
327 (union extcon_property_value
)(int)polarity
);
328 extcon_set_property(info
->edev
, EXTCON_USB_HOST
,
329 EXTCON_PROP_USB_TYPEC_POLARITY
,
330 (union extcon_property_value
)(int)polarity
);
331 extcon_set_property(info
->edev
, EXTCON_DISP_DP
,
332 EXTCON_PROP_USB_TYPEC_POLARITY
,
333 (union extcon_property_value
)(int)polarity
);
334 extcon_set_property(info
->edev
, EXTCON_USB
,
336 (union extcon_property_value
)(int)mux
);
337 extcon_set_property(info
->edev
, EXTCON_USB_HOST
,
339 (union extcon_property_value
)(int)mux
);
340 extcon_set_property(info
->edev
, EXTCON_DISP_DP
,
342 (union extcon_property_value
)(int)mux
);
343 extcon_set_property(info
->edev
, EXTCON_DISP_DP
,
344 EXTCON_PROP_DISP_HPD
,
345 (union extcon_property_value
)(int)hpd
);
347 extcon_sync(info
->edev
, EXTCON_USB
);
348 extcon_sync(info
->edev
, EXTCON_USB_HOST
);
349 extcon_sync(info
->edev
, EXTCON_DISP_DP
);
352 extcon_set_property(info
->edev
, EXTCON_DISP_DP
,
353 EXTCON_PROP_DISP_HPD
,
354 (union extcon_property_value
)(int)hpd
);
355 extcon_sync(info
->edev
, EXTCON_DISP_DP
);
361 static int extcon_cros_ec_event(struct notifier_block
*nb
,
362 unsigned long queued_during_suspend
,
365 struct cros_ec_extcon_info
*info
;
366 struct cros_ec_device
*ec
;
369 info
= container_of(nb
, struct cros_ec_extcon_info
, notifier
);
372 host_event
= cros_ec_get_host_event(ec
);
373 if (host_event
& (EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU
) |
374 EC_HOST_EVENT_MASK(EC_HOST_EVENT_USB_MUX
))) {
375 extcon_cros_ec_detect_cable(info
, false);
382 static int extcon_cros_ec_probe(struct platform_device
*pdev
)
384 struct cros_ec_extcon_info
*info
;
385 struct cros_ec_device
*ec
= dev_get_drvdata(pdev
->dev
.parent
);
386 struct device
*dev
= &pdev
->dev
;
387 struct device_node
*np
= dev
->of_node
;
390 info
= devm_kzalloc(dev
, sizeof(*info
), GFP_KERNEL
);
400 ret
= of_property_read_u32(np
, "google,usb-port-id", &port
);
402 dev_err(dev
, "Missing google,usb-port-id property\n");
405 info
->port_id
= port
;
407 info
->port_id
= pdev
->id
;
410 numports
= cros_ec_pd_get_num_ports(info
);
412 dev_err(dev
, "failed getting number of ports! ret = %d\n",
417 if (info
->port_id
>= numports
) {
418 dev_err(dev
, "This system only supports %d ports\n", numports
);
422 info
->edev
= devm_extcon_dev_allocate(dev
, usb_type_c_cable
);
423 if (IS_ERR(info
->edev
)) {
424 dev_err(dev
, "failed to allocate extcon device\n");
428 ret
= devm_extcon_dev_register(dev
, info
->edev
);
430 dev_err(dev
, "failed to register extcon device\n");
434 extcon_set_property_capability(info
->edev
, EXTCON_USB
,
435 EXTCON_PROP_USB_VBUS
);
436 extcon_set_property_capability(info
->edev
, EXTCON_USB_HOST
,
437 EXTCON_PROP_USB_VBUS
);
438 extcon_set_property_capability(info
->edev
, EXTCON_USB
,
439 EXTCON_PROP_USB_TYPEC_POLARITY
);
440 extcon_set_property_capability(info
->edev
, EXTCON_USB_HOST
,
441 EXTCON_PROP_USB_TYPEC_POLARITY
);
442 extcon_set_property_capability(info
->edev
, EXTCON_DISP_DP
,
443 EXTCON_PROP_USB_TYPEC_POLARITY
);
444 extcon_set_property_capability(info
->edev
, EXTCON_USB
,
446 extcon_set_property_capability(info
->edev
, EXTCON_USB_HOST
,
448 extcon_set_property_capability(info
->edev
, EXTCON_DISP_DP
,
450 extcon_set_property_capability(info
->edev
, EXTCON_DISP_DP
,
451 EXTCON_PROP_DISP_HPD
);
456 platform_set_drvdata(pdev
, info
);
458 /* Get PD events from the EC */
459 info
->notifier
.notifier_call
= extcon_cros_ec_event
;
460 ret
= blocking_notifier_chain_register(&info
->ec
->event_notifier
,
463 dev_err(dev
, "failed to register notifier\n");
467 /* Perform initial detection */
468 ret
= extcon_cros_ec_detect_cable(info
, true);
470 dev_err(dev
, "failed to detect initial cable state\n");
471 goto unregister_notifier
;
477 blocking_notifier_chain_unregister(&info
->ec
->event_notifier
,
482 static int extcon_cros_ec_remove(struct platform_device
*pdev
)
484 struct cros_ec_extcon_info
*info
= platform_get_drvdata(pdev
);
486 blocking_notifier_chain_unregister(&info
->ec
->event_notifier
,
492 #ifdef CONFIG_PM_SLEEP
493 static int extcon_cros_ec_suspend(struct device
*dev
)
498 static int extcon_cros_ec_resume(struct device
*dev
)
501 struct cros_ec_extcon_info
*info
= dev_get_drvdata(dev
);
503 ret
= extcon_cros_ec_detect_cable(info
, true);
505 dev_err(dev
, "failed to detect cable state on resume\n");
510 static const struct dev_pm_ops extcon_cros_ec_dev_pm_ops
= {
511 SET_SYSTEM_SLEEP_PM_OPS(extcon_cros_ec_suspend
, extcon_cros_ec_resume
)
514 #define DEV_PM_OPS (&extcon_cros_ec_dev_pm_ops)
516 #define DEV_PM_OPS NULL
517 #endif /* CONFIG_PM_SLEEP */
520 static const struct of_device_id extcon_cros_ec_of_match
[] = {
521 { .compatible
= "google,extcon-usbc-cros-ec" },
524 MODULE_DEVICE_TABLE(of
, extcon_cros_ec_of_match
);
525 #endif /* CONFIG_OF */
527 static struct platform_driver extcon_cros_ec_driver
= {
529 .name
= "extcon-usbc-cros-ec",
530 .of_match_table
= of_match_ptr(extcon_cros_ec_of_match
),
533 .remove
= extcon_cros_ec_remove
,
534 .probe
= extcon_cros_ec_probe
,
537 module_platform_driver(extcon_cros_ec_driver
);
539 MODULE_DESCRIPTION("ChromeOS Embedded Controller extcon driver");
540 MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
541 MODULE_LICENSE("GPL v2");