Merge tag 'mtd/for-5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux
[linux-2.6-microblaze.git] / drivers / extcon / extcon-usbc-cros-ec.c
1 // SPDX-License-Identifier: GPL-2.0
2 // ChromeOS Embedded Controller extcon
3 //
4 // Copyright (C) 2017 Google, Inc.
5 // Author: Benson Leung <bleung@chromium.org>
6
7 #include <linux/extcon-provider.h>
8 #include <linux/kernel.h>
9 #include <linux/module.h>
10 #include <linux/notifier.h>
11 #include <linux/of.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>
17
18 struct cros_ec_extcon_info {
19         struct device *dev;
20         struct extcon_dev *edev;
21
22         int port_id;
23
24         struct cros_ec_device *ec;
25
26         struct notifier_block notifier;
27
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;
33 };
34
35 static const unsigned int usb_type_c_cable[] = {
36         EXTCON_USB,
37         EXTCON_USB_HOST,
38         EXTCON_DISP_DP,
39         EXTCON_NONE,
40 };
41
42 enum usb_data_roles {
43         DR_NONE,
44         DR_HOST,
45         DR_DEVICE,
46 };
47
48 /**
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
57  *
58  * Return: 0 on success, <0 on failure.
59  */
60 static int cros_ec_pd_command(struct cros_ec_extcon_info *info,
61                               unsigned int command,
62                               unsigned int version,
63                               void *outdata,
64                               unsigned int outsize,
65                               void *indata,
66                               unsigned int insize)
67 {
68         struct cros_ec_command *msg;
69         int ret;
70
71         msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL);
72         if (!msg)
73                 return -ENOMEM;
74
75         msg->version = version;
76         msg->command = command;
77         msg->outsize = outsize;
78         msg->insize = insize;
79
80         if (outsize)
81                 memcpy(msg->data, outdata, outsize);
82
83         ret = cros_ec_cmd_xfer_status(info->ec, msg);
84         if (ret >= 0 && insize)
85                 memcpy(indata, msg->data, insize);
86
87         kfree(msg);
88         return ret;
89 }
90
91 /**
92  * cros_ec_usb_get_power_type() - Get power type info about PD device attached
93  * to given port.
94  * @info: pointer to struct cros_ec_extcon_info
95  *
96  * Return: power type on success, <0 on failure.
97  */
98 static int cros_ec_usb_get_power_type(struct cros_ec_extcon_info *info)
99 {
100         struct ec_params_usb_pd_power_info req;
101         struct ec_response_usb_pd_power_info resp;
102         int ret;
103
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));
107         if (ret < 0)
108                 return ret;
109
110         return resp.type;
111 }
112
113 /**
114  * cros_ec_usb_get_pd_mux_state() - Get PD mux state for given port.
115  * @info: pointer to struct cros_ec_extcon_info
116  *
117  * Return: PD mux state on success, <0 on failure.
118  */
119 static int cros_ec_usb_get_pd_mux_state(struct cros_ec_extcon_info *info)
120 {
121         struct ec_params_usb_pd_mux_info req;
122         struct ec_response_usb_pd_mux_info resp;
123         int ret;
124
125         req.port = info->port_id;
126         ret = cros_ec_pd_command(info, EC_CMD_USB_PD_MUX_INFO, 0,
127                                  &req, sizeof(req),
128                                  &resp, sizeof(resp));
129         if (ret < 0)
130                 return ret;
131
132         return resp.flags;
133 }
134
135 /**
136  * cros_ec_usb_get_role() - Get role info about possible PD device attached to a
137  * given port.
138  * @info: pointer to struct cros_ec_extcon_info
139  * @polarity: pointer to cable polarity (return value)
140  *
141  * Return: role info on success, -ENOTCONN if no cable is connected, <0 on
142  * failure.
143  */
144 static int cros_ec_usb_get_role(struct cros_ec_extcon_info *info,
145                                 bool *polarity)
146 {
147         struct ec_params_usb_pd_control pd_control;
148         struct ec_response_usb_pd_control_v1 resp;
149         int ret;
150
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));
158         if (ret < 0)
159                 return ret;
160
161         if (!(resp.enabled & PD_CTRL_RESP_ENABLED_CONNECTED))
162                 return -ENOTCONN;
163
164         *polarity = resp.polarity;
165
166         return resp.role;
167 }
168
169 /**
170  * cros_ec_pd_get_num_ports() - Get number of EC charge ports.
171  * @info: pointer to struct cros_ec_extcon_info
172  *
173  * Return: number of ports on success, <0 on failure.
174  */
175 static int cros_ec_pd_get_num_ports(struct cros_ec_extcon_info *info)
176 {
177         struct ec_response_usb_pd_ports resp;
178         int ret;
179
180         ret = cros_ec_pd_command(info, EC_CMD_USB_PD_PORTS,
181                                  0, NULL, 0, &resp, sizeof(resp));
182         if (ret < 0)
183                 return ret;
184
185         return resp.num_ports;
186 }
187
188 static const char *cros_ec_usb_role_string(unsigned int role)
189 {
190         return role == DR_NONE ? "DISCONNECTED" :
191                 (role == DR_HOST ? "DFP" : "UFP");
192 }
193
194 static const char *cros_ec_usb_power_type_string(unsigned int type)
195 {
196         switch (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";
203         case USB_CHG_TYPE_C:
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";
217         default:
218                 return "USB_CHG_TYPE_UNKNOWN";
219         }
220 }
221
222 static bool cros_ec_usb_power_type_is_wall_wart(unsigned int type,
223                                                 unsigned int role)
224 {
225         switch (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.
230          */
231         case USB_CHG_TYPE_PROPRIETARY:
232         case USB_CHG_TYPE_BC12_DCP:
233                 return true;
234         case USB_CHG_TYPE_PD:
235         case USB_CHG_TYPE_C:
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:
242         default:
243                 return false;
244         }
245 }
246
247 static int extcon_cros_ec_detect_cable(struct cros_ec_extcon_info *info,
248                                        bool force)
249 {
250         struct device *dev = info->dev;
251         int role, power_type;
252         unsigned int dr = DR_NONE;
253         bool pr = false;
254         bool polarity = false;
255         bool dp = false;
256         bool mux = false;
257         bool hpd = false;
258
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",
262                         power_type);
263                 return power_type;
264         }
265
266         role = cros_ec_usb_get_role(info, &polarity);
267         if (role < 0) {
268                 if (role != -ENOTCONN) {
269                         dev_err(dev, "failed getting role err = %d\n", role);
270                         return role;
271                 }
272                 dev_dbg(dev, "disconnected\n");
273         } else {
274                 int pd_mux_state;
275
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;
281
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;
285
286                 dev_dbg(dev,
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);
289         }
290
291         /*
292          * When there is no USB host (e.g. USB PD charger),
293          * we are not really a UFP for the AP.
294          */
295         if (dr == DR_DEVICE &&
296             cros_ec_usb_power_type_is_wall_wart(power_type, role))
297                 dr = DR_NONE;
298
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;
302
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));
306                 info->dr = dr;
307                 info->pr = pr;
308                 info->dp = dp;
309                 info->mux = mux;
310                 info->power_type = power_type;
311
312                 if (dr == DR_DEVICE)
313                         device_connected = true;
314                 else if (dr == DR_HOST)
315                         host_connected = true;
316
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,
336                                     EXTCON_PROP_USB_SS,
337                                     (union extcon_property_value)(int)mux);
338                 extcon_set_property(info->edev, EXTCON_USB_HOST,
339                                     EXTCON_PROP_USB_SS,
340                                     (union extcon_property_value)(int)mux);
341                 extcon_set_property(info->edev, EXTCON_DISP_DP,
342                                     EXTCON_PROP_USB_SS,
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);
347
348                 extcon_sync(info->edev, EXTCON_USB);
349                 extcon_sync(info->edev, EXTCON_USB_HOST);
350                 extcon_sync(info->edev, EXTCON_DISP_DP);
351
352         } else if (hpd) {
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);
357         }
358
359         return 0;
360 }
361
362 static int extcon_cros_ec_event(struct notifier_block *nb,
363                                 unsigned long queued_during_suspend,
364                                 void *_notify)
365 {
366         struct cros_ec_extcon_info *info;
367         struct cros_ec_device *ec;
368         u32 host_event;
369
370         info = container_of(nb, struct cros_ec_extcon_info, notifier);
371         ec = info->ec;
372
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);
377                 return NOTIFY_OK;
378         }
379
380         return NOTIFY_DONE;
381 }
382
383 static int extcon_cros_ec_probe(struct platform_device *pdev)
384 {
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;
389         int numports, ret;
390
391         info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
392         if (!info)
393                 return -ENOMEM;
394
395         info->dev = dev;
396         info->ec = ec;
397
398         if (np) {
399                 u32 port;
400
401                 ret = of_property_read_u32(np, "google,usb-port-id", &port);
402                 if (ret < 0) {
403                         dev_err(dev, "Missing google,usb-port-id property\n");
404                         return ret;
405                 }
406                 info->port_id = port;
407         } else {
408                 info->port_id = pdev->id;
409         }
410
411         numports = cros_ec_pd_get_num_ports(info);
412         if (numports < 0) {
413                 dev_err(dev, "failed getting number of ports! ret = %d\n",
414                         numports);
415                 return numports;
416         }
417
418         if (info->port_id >= numports) {
419                 dev_err(dev, "This system only supports %d ports\n", numports);
420                 return -ENODEV;
421         }
422
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");
426                 return -ENOMEM;
427         }
428
429         ret = devm_extcon_dev_register(dev, info->edev);
430         if (ret < 0) {
431                 dev_err(dev, "failed to register extcon device\n");
432                 return ret;
433         }
434
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,
446                                        EXTCON_PROP_USB_SS);
447         extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
448                                        EXTCON_PROP_USB_SS);
449         extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
450                                        EXTCON_PROP_USB_SS);
451         extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
452                                        EXTCON_PROP_DISP_HPD);
453
454         info->dr = DR_NONE;
455         info->pr = false;
456
457         platform_set_drvdata(pdev, info);
458
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,
462                                                &info->notifier);
463         if (ret < 0) {
464                 dev_err(dev, "failed to register notifier\n");
465                 return ret;
466         }
467
468         /* Perform initial detection */
469         ret = extcon_cros_ec_detect_cable(info, true);
470         if (ret < 0) {
471                 dev_err(dev, "failed to detect initial cable state\n");
472                 goto unregister_notifier;
473         }
474
475         return 0;
476
477 unregister_notifier:
478         blocking_notifier_chain_unregister(&info->ec->event_notifier,
479                                            &info->notifier);
480         return ret;
481 }
482
483 static int extcon_cros_ec_remove(struct platform_device *pdev)
484 {
485         struct cros_ec_extcon_info *info = platform_get_drvdata(pdev);
486
487         blocking_notifier_chain_unregister(&info->ec->event_notifier,
488                                            &info->notifier);
489
490         return 0;
491 }
492
493 #ifdef CONFIG_PM_SLEEP
494 static int extcon_cros_ec_suspend(struct device *dev)
495 {
496         return 0;
497 }
498
499 static int extcon_cros_ec_resume(struct device *dev)
500 {
501         int ret;
502         struct cros_ec_extcon_info *info = dev_get_drvdata(dev);
503
504         ret = extcon_cros_ec_detect_cable(info, true);
505         if (ret < 0)
506                 dev_err(dev, "failed to detect cable state on resume\n");
507
508         return 0;
509 }
510
511 static const struct dev_pm_ops extcon_cros_ec_dev_pm_ops = {
512         SET_SYSTEM_SLEEP_PM_OPS(extcon_cros_ec_suspend, extcon_cros_ec_resume)
513 };
514
515 #define DEV_PM_OPS      (&extcon_cros_ec_dev_pm_ops)
516 #else
517 #define DEV_PM_OPS      NULL
518 #endif /* CONFIG_PM_SLEEP */
519
520 #ifdef CONFIG_OF
521 static const struct of_device_id extcon_cros_ec_of_match[] = {
522         { .compatible = "google,extcon-usbc-cros-ec" },
523         { /* sentinel */ }
524 };
525 MODULE_DEVICE_TABLE(of, extcon_cros_ec_of_match);
526 #endif /* CONFIG_OF */
527
528 static struct platform_driver extcon_cros_ec_driver = {
529         .driver = {
530                 .name  = "extcon-usbc-cros-ec",
531                 .of_match_table = of_match_ptr(extcon_cros_ec_of_match),
532                 .pm = DEV_PM_OPS,
533         },
534         .remove  = extcon_cros_ec_remove,
535         .probe   = extcon_cros_ec_probe,
536 };
537
538 module_platform_driver(extcon_cros_ec_driver);
539
540 MODULE_DESCRIPTION("ChromeOS Embedded Controller extcon driver");
541 MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
542 MODULE_LICENSE("GPL v2");