extcon-usbc-cros-ec.c 14 KB

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