extcon-usbc-cros-ec.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. /**
  2. * drivers/extcon/extcon-usbc-cros-ec - ChromeOS Embedded Controller extcon
  3. *
  4. * Copyright (C) 2017 Google, Inc
  5. * Author: Benson Leung <bleung@chromium.org>
  6. *
  7. * This software is licensed under the terms of the GNU General Public
  8. * License version 2, as published by the Free Software Foundation, and
  9. * may be copied, distributed, and modified under those terms.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. */
  16. #include <linux/extcon-provider.h>
  17. #include <linux/kernel.h>
  18. #include <linux/mfd/cros_ec.h>
  19. #include <linux/module.h>
  20. #include <linux/notifier.h>
  21. #include <linux/of.h>
  22. #include <linux/platform_device.h>
  23. #include <linux/slab.h>
  24. #include <linux/sched.h>
  25. struct cros_ec_extcon_info {
  26. struct device *dev;
  27. struct extcon_dev *edev;
  28. int port_id;
  29. struct cros_ec_device *ec;
  30. struct notifier_block notifier;
  31. unsigned int dr; /* data role */
  32. bool pr; /* power role (true if VBUS enabled) */
  33. bool dp; /* DisplayPort enabled */
  34. bool mux; /* SuperSpeed (usb3) enabled */
  35. unsigned int power_type;
  36. };
  37. static const unsigned int usb_type_c_cable[] = {
  38. EXTCON_USB,
  39. EXTCON_USB_HOST,
  40. EXTCON_DISP_DP,
  41. EXTCON_NONE,
  42. };
  43. enum usb_data_roles {
  44. DR_NONE,
  45. DR_HOST,
  46. DR_DEVICE,
  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. msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL);
  71. if (!msg)
  72. return -ENOMEM;
  73. msg->version = version;
  74. msg->command = command;
  75. msg->outsize = outsize;
  76. msg->insize = insize;
  77. if (outsize)
  78. memcpy(msg->data, outdata, outsize);
  79. ret = cros_ec_cmd_xfer_status(info->ec, msg);
  80. if (ret >= 0 && insize)
  81. memcpy(indata, msg->data, insize);
  82. kfree(msg);
  83. return ret;
  84. }
  85. /**
  86. * cros_ec_usb_get_power_type() - Get power type info about PD device attached
  87. * to given port.
  88. * @info: pointer to struct cros_ec_extcon_info
  89. *
  90. * Return: power type on success, <0 on failure.
  91. */
  92. static int cros_ec_usb_get_power_type(struct cros_ec_extcon_info *info)
  93. {
  94. struct ec_params_usb_pd_power_info req;
  95. struct ec_response_usb_pd_power_info resp;
  96. int ret;
  97. req.port = info->port_id;
  98. ret = cros_ec_pd_command(info, EC_CMD_USB_PD_POWER_INFO, 0,
  99. &req, sizeof(req), &resp, sizeof(resp));
  100. if (ret < 0)
  101. return ret;
  102. return resp.type;
  103. }
  104. /**
  105. * cros_ec_usb_get_pd_mux_state() - Get PD mux state for given port.
  106. * @info: pointer to struct cros_ec_extcon_info
  107. *
  108. * Return: PD mux state on success, <0 on failure.
  109. */
  110. static int cros_ec_usb_get_pd_mux_state(struct cros_ec_extcon_info *info)
  111. {
  112. struct ec_params_usb_pd_mux_info req;
  113. struct ec_response_usb_pd_mux_info resp;
  114. int ret;
  115. req.port = info->port_id;
  116. ret = cros_ec_pd_command(info, EC_CMD_USB_PD_MUX_INFO, 0,
  117. &req, sizeof(req),
  118. &resp, sizeof(resp));
  119. if (ret < 0)
  120. return ret;
  121. return resp.flags;
  122. }
  123. /**
  124. * cros_ec_usb_get_role() - Get role info about possible PD device attached to a
  125. * given port.
  126. * @info: pointer to struct cros_ec_extcon_info
  127. * @polarity: pointer to cable polarity (return value)
  128. *
  129. * Return: role info on success, -ENOTCONN if no cable is connected, <0 on
  130. * failure.
  131. */
  132. static int cros_ec_usb_get_role(struct cros_ec_extcon_info *info,
  133. bool *polarity)
  134. {
  135. struct ec_params_usb_pd_control pd_control;
  136. struct ec_response_usb_pd_control_v1 resp;
  137. int ret;
  138. pd_control.port = info->port_id;
  139. pd_control.role = USB_PD_CTRL_ROLE_NO_CHANGE;
  140. pd_control.mux = USB_PD_CTRL_MUX_NO_CHANGE;
  141. pd_control.swap = USB_PD_CTRL_SWAP_NONE;
  142. ret = cros_ec_pd_command(info, EC_CMD_USB_PD_CONTROL, 1,
  143. &pd_control, sizeof(pd_control),
  144. &resp, sizeof(resp));
  145. if (ret < 0)
  146. return ret;
  147. if (!(resp.enabled & PD_CTRL_RESP_ENABLED_CONNECTED))
  148. return -ENOTCONN;
  149. *polarity = resp.polarity;
  150. return resp.role;
  151. }
  152. /**
  153. * cros_ec_pd_get_num_ports() - Get number of EC charge ports.
  154. * @info: pointer to struct cros_ec_extcon_info
  155. *
  156. * Return: number of ports on success, <0 on failure.
  157. */
  158. static int cros_ec_pd_get_num_ports(struct cros_ec_extcon_info *info)
  159. {
  160. struct ec_response_usb_pd_ports resp;
  161. int ret;
  162. ret = cros_ec_pd_command(info, EC_CMD_USB_PD_PORTS,
  163. 0, NULL, 0, &resp, sizeof(resp));
  164. if (ret < 0)
  165. return ret;
  166. return resp.num_ports;
  167. }
  168. static const char *cros_ec_usb_role_string(unsigned int role)
  169. {
  170. return role == DR_NONE ? "DISCONNECTED" :
  171. (role == DR_HOST ? "DFP" : "UFP");
  172. }
  173. static const char *cros_ec_usb_power_type_string(unsigned int type)
  174. {
  175. switch (type) {
  176. case USB_CHG_TYPE_NONE:
  177. return "USB_CHG_TYPE_NONE";
  178. case USB_CHG_TYPE_PD:
  179. return "USB_CHG_TYPE_PD";
  180. case USB_CHG_TYPE_PROPRIETARY:
  181. return "USB_CHG_TYPE_PROPRIETARY";
  182. case USB_CHG_TYPE_C:
  183. return "USB_CHG_TYPE_C";
  184. case USB_CHG_TYPE_BC12_DCP:
  185. return "USB_CHG_TYPE_BC12_DCP";
  186. case USB_CHG_TYPE_BC12_CDP:
  187. return "USB_CHG_TYPE_BC12_CDP";
  188. case USB_CHG_TYPE_BC12_SDP:
  189. return "USB_CHG_TYPE_BC12_SDP";
  190. case USB_CHG_TYPE_OTHER:
  191. return "USB_CHG_TYPE_OTHER";
  192. case USB_CHG_TYPE_VBUS:
  193. return "USB_CHG_TYPE_VBUS";
  194. case USB_CHG_TYPE_UNKNOWN:
  195. return "USB_CHG_TYPE_UNKNOWN";
  196. default:
  197. return "USB_CHG_TYPE_UNKNOWN";
  198. }
  199. }
  200. static bool cros_ec_usb_power_type_is_wall_wart(unsigned int type,
  201. unsigned int role)
  202. {
  203. switch (type) {
  204. /* FIXME : Guppy, Donnettes, and other chargers will be miscategorized
  205. * because they identify with USB_CHG_TYPE_C, but we can't return true
  206. * here from that code because that breaks Suzy-Q and other kinds of
  207. * USB Type-C cables and peripherals.
  208. */
  209. case USB_CHG_TYPE_PROPRIETARY:
  210. case USB_CHG_TYPE_BC12_DCP:
  211. return true;
  212. case USB_CHG_TYPE_PD:
  213. case USB_CHG_TYPE_C:
  214. case USB_CHG_TYPE_BC12_CDP:
  215. case USB_CHG_TYPE_BC12_SDP:
  216. case USB_CHG_TYPE_OTHER:
  217. case USB_CHG_TYPE_VBUS:
  218. case USB_CHG_TYPE_UNKNOWN:
  219. case USB_CHG_TYPE_NONE:
  220. default:
  221. return false;
  222. }
  223. }
  224. static int extcon_cros_ec_detect_cable(struct cros_ec_extcon_info *info,
  225. bool force)
  226. {
  227. struct device *dev = info->dev;
  228. int role, power_type;
  229. unsigned int dr = DR_NONE;
  230. bool pr = false;
  231. bool polarity = false;
  232. bool dp = false;
  233. bool mux = false;
  234. bool hpd = false;
  235. power_type = cros_ec_usb_get_power_type(info);
  236. if (power_type < 0) {
  237. dev_err(dev, "failed getting power type err = %d\n",
  238. power_type);
  239. return power_type;
  240. }
  241. role = cros_ec_usb_get_role(info, &polarity);
  242. if (role < 0) {
  243. if (role != -ENOTCONN) {
  244. dev_err(dev, "failed getting role err = %d\n", role);
  245. return role;
  246. }
  247. dev_dbg(dev, "disconnected\n");
  248. } else {
  249. int pd_mux_state;
  250. dr = (role & PD_CTRL_RESP_ROLE_DATA) ? DR_HOST : DR_DEVICE;
  251. pr = (role & PD_CTRL_RESP_ROLE_POWER);
  252. pd_mux_state = cros_ec_usb_get_pd_mux_state(info);
  253. if (pd_mux_state < 0)
  254. pd_mux_state = USB_PD_MUX_USB_ENABLED;
  255. dp = pd_mux_state & USB_PD_MUX_DP_ENABLED;
  256. mux = pd_mux_state & USB_PD_MUX_USB_ENABLED;
  257. hpd = pd_mux_state & USB_PD_MUX_HPD_IRQ;
  258. dev_dbg(dev,
  259. "connected role 0x%x pwr type %d dr %d pr %d pol %d mux %d dp %d hpd %d\n",
  260. role, power_type, dr, pr, polarity, mux, dp, hpd);
  261. }
  262. /*
  263. * When there is no USB host (e.g. USB PD charger),
  264. * we are not really a UFP for the AP.
  265. */
  266. if (dr == DR_DEVICE &&
  267. cros_ec_usb_power_type_is_wall_wart(power_type, role))
  268. dr = DR_NONE;
  269. if (force || info->dr != dr || info->pr != pr || info->dp != dp ||
  270. info->mux != mux || info->power_type != power_type) {
  271. bool host_connected = false, device_connected = false;
  272. dev_dbg(dev, "Type/Role switch! type = %s role = %s\n",
  273. cros_ec_usb_power_type_string(power_type),
  274. cros_ec_usb_role_string(dr));
  275. info->dr = dr;
  276. info->pr = pr;
  277. info->dp = dp;
  278. info->mux = mux;
  279. info->power_type = power_type;
  280. if (dr == DR_DEVICE)
  281. device_connected = true;
  282. else if (dr == DR_HOST)
  283. host_connected = true;
  284. extcon_set_state(info->edev, EXTCON_USB, device_connected);
  285. extcon_set_state(info->edev, EXTCON_USB_HOST, host_connected);
  286. extcon_set_state(info->edev, EXTCON_DISP_DP, dp);
  287. extcon_set_property(info->edev, EXTCON_USB,
  288. EXTCON_PROP_USB_VBUS,
  289. (union extcon_property_value)(int)pr);
  290. extcon_set_property(info->edev, EXTCON_USB_HOST,
  291. EXTCON_PROP_USB_VBUS,
  292. (union extcon_property_value)(int)pr);
  293. extcon_set_property(info->edev, EXTCON_USB,
  294. EXTCON_PROP_USB_TYPEC_POLARITY,
  295. (union extcon_property_value)(int)polarity);
  296. extcon_set_property(info->edev, EXTCON_USB_HOST,
  297. EXTCON_PROP_USB_TYPEC_POLARITY,
  298. (union extcon_property_value)(int)polarity);
  299. extcon_set_property(info->edev, EXTCON_DISP_DP,
  300. EXTCON_PROP_USB_TYPEC_POLARITY,
  301. (union extcon_property_value)(int)polarity);
  302. extcon_set_property(info->edev, EXTCON_USB,
  303. EXTCON_PROP_USB_SS,
  304. (union extcon_property_value)(int)mux);
  305. extcon_set_property(info->edev, EXTCON_USB_HOST,
  306. EXTCON_PROP_USB_SS,
  307. (union extcon_property_value)(int)mux);
  308. extcon_set_property(info->edev, EXTCON_DISP_DP,
  309. EXTCON_PROP_USB_SS,
  310. (union extcon_property_value)(int)mux);
  311. extcon_set_property(info->edev, EXTCON_DISP_DP,
  312. EXTCON_PROP_DISP_HPD,
  313. (union extcon_property_value)(int)hpd);
  314. extcon_sync(info->edev, EXTCON_USB);
  315. extcon_sync(info->edev, EXTCON_USB_HOST);
  316. extcon_sync(info->edev, EXTCON_DISP_DP);
  317. } else if (hpd) {
  318. extcon_set_property(info->edev, EXTCON_DISP_DP,
  319. EXTCON_PROP_DISP_HPD,
  320. (union extcon_property_value)(int)hpd);
  321. extcon_sync(info->edev, EXTCON_DISP_DP);
  322. }
  323. return 0;
  324. }
  325. static int extcon_cros_ec_event(struct notifier_block *nb,
  326. unsigned long queued_during_suspend,
  327. void *_notify)
  328. {
  329. struct cros_ec_extcon_info *info;
  330. struct cros_ec_device *ec;
  331. u32 host_event;
  332. info = container_of(nb, struct cros_ec_extcon_info, notifier);
  333. ec = info->ec;
  334. host_event = cros_ec_get_host_event(ec);
  335. if (host_event & (EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU) |
  336. EC_HOST_EVENT_MASK(EC_HOST_EVENT_USB_MUX))) {
  337. extcon_cros_ec_detect_cable(info, false);
  338. return NOTIFY_OK;
  339. }
  340. return NOTIFY_DONE;
  341. }
  342. static int extcon_cros_ec_probe(struct platform_device *pdev)
  343. {
  344. struct cros_ec_extcon_info *info;
  345. struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
  346. struct device *dev = &pdev->dev;
  347. struct device_node *np = dev->of_node;
  348. int numports, ret;
  349. info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
  350. if (!info)
  351. return -ENOMEM;
  352. info->dev = dev;
  353. info->ec = ec;
  354. if (np) {
  355. u32 port;
  356. ret = of_property_read_u32(np, "google,usb-port-id", &port);
  357. if (ret < 0) {
  358. dev_err(dev, "Missing google,usb-port-id property\n");
  359. return ret;
  360. }
  361. info->port_id = port;
  362. } else {
  363. info->port_id = pdev->id;
  364. }
  365. numports = cros_ec_pd_get_num_ports(info);
  366. if (numports < 0) {
  367. dev_err(dev, "failed getting number of ports! ret = %d\n",
  368. numports);
  369. return numports;
  370. }
  371. if (info->port_id >= numports) {
  372. dev_err(dev, "This system only supports %d ports\n", numports);
  373. return -ENODEV;
  374. }
  375. info->edev = devm_extcon_dev_allocate(dev, usb_type_c_cable);
  376. if (IS_ERR(info->edev)) {
  377. dev_err(dev, "failed to allocate extcon device\n");
  378. return -ENOMEM;
  379. }
  380. ret = devm_extcon_dev_register(dev, info->edev);
  381. if (ret < 0) {
  382. dev_err(dev, "failed to register extcon device\n");
  383. return ret;
  384. }
  385. extcon_set_property_capability(info->edev, EXTCON_USB,
  386. EXTCON_PROP_USB_VBUS);
  387. extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
  388. EXTCON_PROP_USB_VBUS);
  389. extcon_set_property_capability(info->edev, EXTCON_USB,
  390. EXTCON_PROP_USB_TYPEC_POLARITY);
  391. extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
  392. EXTCON_PROP_USB_TYPEC_POLARITY);
  393. extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
  394. EXTCON_PROP_USB_TYPEC_POLARITY);
  395. extcon_set_property_capability(info->edev, EXTCON_USB,
  396. EXTCON_PROP_USB_SS);
  397. extcon_set_property_capability(info->edev, EXTCON_USB_HOST,
  398. EXTCON_PROP_USB_SS);
  399. extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
  400. EXTCON_PROP_USB_SS);
  401. extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
  402. EXTCON_PROP_DISP_HPD);
  403. info->dr = DR_NONE;
  404. info->pr = false;
  405. platform_set_drvdata(pdev, info);
  406. /* Get PD events from the EC */
  407. info->notifier.notifier_call = extcon_cros_ec_event;
  408. ret = blocking_notifier_chain_register(&info->ec->event_notifier,
  409. &info->notifier);
  410. if (ret < 0) {
  411. dev_err(dev, "failed to register notifier\n");
  412. return ret;
  413. }
  414. /* Perform initial detection */
  415. ret = extcon_cros_ec_detect_cable(info, true);
  416. if (ret < 0) {
  417. dev_err(dev, "failed to detect initial cable state\n");
  418. goto unregister_notifier;
  419. }
  420. return 0;
  421. unregister_notifier:
  422. blocking_notifier_chain_unregister(&info->ec->event_notifier,
  423. &info->notifier);
  424. return ret;
  425. }
  426. static int extcon_cros_ec_remove(struct platform_device *pdev)
  427. {
  428. struct cros_ec_extcon_info *info = platform_get_drvdata(pdev);
  429. blocking_notifier_chain_unregister(&info->ec->event_notifier,
  430. &info->notifier);
  431. return 0;
  432. }
  433. #ifdef CONFIG_PM_SLEEP
  434. static int extcon_cros_ec_suspend(struct device *dev)
  435. {
  436. return 0;
  437. }
  438. static int extcon_cros_ec_resume(struct device *dev)
  439. {
  440. int ret;
  441. struct cros_ec_extcon_info *info = dev_get_drvdata(dev);
  442. ret = extcon_cros_ec_detect_cable(info, true);
  443. if (ret < 0)
  444. dev_err(dev, "failed to detect cable state on resume\n");
  445. return 0;
  446. }
  447. static const struct dev_pm_ops extcon_cros_ec_dev_pm_ops = {
  448. SET_SYSTEM_SLEEP_PM_OPS(extcon_cros_ec_suspend, extcon_cros_ec_resume)
  449. };
  450. #define DEV_PM_OPS (&extcon_cros_ec_dev_pm_ops)
  451. #else
  452. #define DEV_PM_OPS NULL
  453. #endif /* CONFIG_PM_SLEEP */
  454. #ifdef CONFIG_OF
  455. static const struct of_device_id extcon_cros_ec_of_match[] = {
  456. { .compatible = "google,extcon-usbc-cros-ec" },
  457. { /* sentinel */ }
  458. };
  459. MODULE_DEVICE_TABLE(of, extcon_cros_ec_of_match);
  460. #endif /* CONFIG_OF */
  461. static struct platform_driver extcon_cros_ec_driver = {
  462. .driver = {
  463. .name = "extcon-usbc-cros-ec",
  464. .of_match_table = of_match_ptr(extcon_cros_ec_of_match),
  465. .pm = DEV_PM_OPS,
  466. },
  467. .remove = extcon_cros_ec_remove,
  468. .probe = extcon_cros_ec_probe,
  469. };
  470. module_platform_driver(extcon_cros_ec_driver);
  471. MODULE_DESCRIPTION("ChromeOS Embedded Controller extcon driver");
  472. MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
  473. MODULE_LICENSE("GPL");