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