extcon-usbc-cros-ec.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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.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. bool dp; /* DisplayPort enabled */
  32. bool mux; /* SuperSpeed (usb3) enabled */
  33. unsigned int power_type;
  34. };
  35. static const unsigned int usb_type_c_cable[] = {
  36. EXTCON_DISP_DP,
  37. EXTCON_NONE,
  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. ret = cros_ec_pd_command(info, EC_CMD_USB_PD_CONTROL, 1,
  133. &pd_control, sizeof(pd_control),
  134. &resp, sizeof(resp));
  135. if (ret < 0)
  136. return ret;
  137. if (!(resp.enabled & PD_CTRL_RESP_ENABLED_CONNECTED))
  138. return -ENOTCONN;
  139. *polarity = resp.polarity;
  140. return resp.role;
  141. }
  142. /**
  143. * cros_ec_pd_get_num_ports() - Get number of EC charge ports.
  144. * @info: pointer to struct cros_ec_extcon_info
  145. *
  146. * Return: number of ports on success, <0 on failure.
  147. */
  148. static int cros_ec_pd_get_num_ports(struct cros_ec_extcon_info *info)
  149. {
  150. struct ec_response_usb_pd_ports resp;
  151. int ret;
  152. ret = cros_ec_pd_command(info, EC_CMD_USB_PD_PORTS,
  153. 0, NULL, 0, &resp, sizeof(resp));
  154. if (ret < 0)
  155. return ret;
  156. return resp.num_ports;
  157. }
  158. static int extcon_cros_ec_detect_cable(struct cros_ec_extcon_info *info,
  159. bool force)
  160. {
  161. struct device *dev = info->dev;
  162. int role, power_type;
  163. bool polarity = false;
  164. bool dp = false;
  165. bool mux = false;
  166. bool hpd = false;
  167. power_type = cros_ec_usb_get_power_type(info);
  168. if (power_type < 0) {
  169. dev_err(dev, "failed getting power type err = %d\n",
  170. power_type);
  171. return power_type;
  172. }
  173. role = cros_ec_usb_get_role(info, &polarity);
  174. if (role < 0) {
  175. if (role != -ENOTCONN) {
  176. dev_err(dev, "failed getting role err = %d\n", role);
  177. return role;
  178. }
  179. } else {
  180. int pd_mux_state;
  181. pd_mux_state = cros_ec_usb_get_pd_mux_state(info);
  182. if (pd_mux_state < 0)
  183. pd_mux_state = USB_PD_MUX_USB_ENABLED;
  184. dp = pd_mux_state & USB_PD_MUX_DP_ENABLED;
  185. mux = pd_mux_state & USB_PD_MUX_USB_ENABLED;
  186. hpd = pd_mux_state & USB_PD_MUX_HPD_IRQ;
  187. }
  188. if (force || info->dp != dp || info->mux != mux ||
  189. info->power_type != power_type) {
  190. info->dp = dp;
  191. info->mux = mux;
  192. info->power_type = power_type;
  193. extcon_set_state(info->edev, EXTCON_DISP_DP, dp);
  194. extcon_set_property(info->edev, EXTCON_DISP_DP,
  195. EXTCON_PROP_USB_TYPEC_POLARITY,
  196. (union extcon_property_value)(int)polarity);
  197. extcon_set_property(info->edev, EXTCON_DISP_DP,
  198. EXTCON_PROP_USB_SS,
  199. (union extcon_property_value)(int)mux);
  200. extcon_set_property(info->edev, EXTCON_DISP_DP,
  201. EXTCON_PROP_DISP_HPD,
  202. (union extcon_property_value)(int)hpd);
  203. extcon_sync(info->edev, EXTCON_DISP_DP);
  204. } else if (hpd) {
  205. extcon_set_property(info->edev, EXTCON_DISP_DP,
  206. EXTCON_PROP_DISP_HPD,
  207. (union extcon_property_value)(int)hpd);
  208. extcon_sync(info->edev, EXTCON_DISP_DP);
  209. }
  210. return 0;
  211. }
  212. static int extcon_cros_ec_event(struct notifier_block *nb,
  213. unsigned long queued_during_suspend,
  214. void *_notify)
  215. {
  216. struct cros_ec_extcon_info *info;
  217. struct cros_ec_device *ec;
  218. u32 host_event;
  219. info = container_of(nb, struct cros_ec_extcon_info, notifier);
  220. ec = info->ec;
  221. host_event = cros_ec_get_host_event(ec);
  222. if (host_event & (EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU) |
  223. EC_HOST_EVENT_MASK(EC_HOST_EVENT_USB_MUX))) {
  224. extcon_cros_ec_detect_cable(info, false);
  225. return NOTIFY_OK;
  226. }
  227. return NOTIFY_DONE;
  228. }
  229. static int extcon_cros_ec_probe(struct platform_device *pdev)
  230. {
  231. struct cros_ec_extcon_info *info;
  232. struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
  233. struct device *dev = &pdev->dev;
  234. struct device_node *np = dev->of_node;
  235. int numports, ret;
  236. info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
  237. if (!info)
  238. return -ENOMEM;
  239. info->dev = dev;
  240. info->ec = ec;
  241. if (np) {
  242. u32 port;
  243. ret = of_property_read_u32(np, "google,usb-port-id", &port);
  244. if (ret < 0) {
  245. dev_err(dev, "Missing google,usb-port-id property\n");
  246. return ret;
  247. }
  248. info->port_id = port;
  249. } else {
  250. info->port_id = pdev->id;
  251. }
  252. numports = cros_ec_pd_get_num_ports(info);
  253. if (numports < 0) {
  254. dev_err(dev, "failed getting number of ports! ret = %d\n",
  255. numports);
  256. return numports;
  257. }
  258. if (info->port_id >= numports) {
  259. dev_err(dev, "This system only supports %d ports\n", numports);
  260. return -ENODEV;
  261. }
  262. info->edev = devm_extcon_dev_allocate(dev, usb_type_c_cable);
  263. if (IS_ERR(info->edev)) {
  264. dev_err(dev, "failed to allocate extcon device\n");
  265. return -ENOMEM;
  266. }
  267. ret = devm_extcon_dev_register(dev, info->edev);
  268. if (ret < 0) {
  269. dev_err(dev, "failed to register extcon device\n");
  270. return ret;
  271. }
  272. extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
  273. EXTCON_PROP_USB_TYPEC_POLARITY);
  274. extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
  275. EXTCON_PROP_USB_SS);
  276. extcon_set_property_capability(info->edev, EXTCON_DISP_DP,
  277. EXTCON_PROP_DISP_HPD);
  278. platform_set_drvdata(pdev, info);
  279. /* Get PD events from the EC */
  280. info->notifier.notifier_call = extcon_cros_ec_event;
  281. ret = blocking_notifier_chain_register(&info->ec->event_notifier,
  282. &info->notifier);
  283. if (ret < 0) {
  284. dev_err(dev, "failed to register notifier\n");
  285. return ret;
  286. }
  287. /* Perform initial detection */
  288. ret = extcon_cros_ec_detect_cable(info, true);
  289. if (ret < 0) {
  290. dev_err(dev, "failed to detect initial cable state\n");
  291. goto unregister_notifier;
  292. }
  293. return 0;
  294. unregister_notifier:
  295. blocking_notifier_chain_unregister(&info->ec->event_notifier,
  296. &info->notifier);
  297. return ret;
  298. }
  299. static int extcon_cros_ec_remove(struct platform_device *pdev)
  300. {
  301. struct cros_ec_extcon_info *info = platform_get_drvdata(pdev);
  302. blocking_notifier_chain_unregister(&info->ec->event_notifier,
  303. &info->notifier);
  304. return 0;
  305. }
  306. #ifdef CONFIG_PM_SLEEP
  307. static int extcon_cros_ec_suspend(struct device *dev)
  308. {
  309. return 0;
  310. }
  311. static int extcon_cros_ec_resume(struct device *dev)
  312. {
  313. int ret;
  314. struct cros_ec_extcon_info *info = dev_get_drvdata(dev);
  315. ret = extcon_cros_ec_detect_cable(info, true);
  316. if (ret < 0)
  317. dev_err(dev, "failed to detect cable state on resume\n");
  318. return 0;
  319. }
  320. static const struct dev_pm_ops extcon_cros_ec_dev_pm_ops = {
  321. SET_SYSTEM_SLEEP_PM_OPS(extcon_cros_ec_suspend, extcon_cros_ec_resume)
  322. };
  323. #define DEV_PM_OPS (&extcon_cros_ec_dev_pm_ops)
  324. #else
  325. #define DEV_PM_OPS NULL
  326. #endif /* CONFIG_PM_SLEEP */
  327. #ifdef CONFIG_OF
  328. static const struct of_device_id extcon_cros_ec_of_match[] = {
  329. { .compatible = "google,extcon-usbc-cros-ec" },
  330. { /* sentinel */ }
  331. };
  332. MODULE_DEVICE_TABLE(of, extcon_cros_ec_of_match);
  333. #endif /* CONFIG_OF */
  334. static struct platform_driver extcon_cros_ec_driver = {
  335. .driver = {
  336. .name = "extcon-usbc-cros-ec",
  337. .of_match_table = of_match_ptr(extcon_cros_ec_of_match),
  338. .pm = DEV_PM_OPS,
  339. },
  340. .remove = extcon_cros_ec_remove,
  341. .probe = extcon_cros_ec_probe,
  342. };
  343. module_platform_driver(extcon_cros_ec_driver);
  344. MODULE_DESCRIPTION("ChromeOS Embedded Controller extcon driver");
  345. MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
  346. MODULE_LICENSE("GPL");