xref: /openbmc/linux/drivers/usb/typec/tcpm/tcpci.c (revision 05300871)
1ae8a2ca8SHeikki Krogerus // SPDX-License-Identifier: GPL-2.0+
2ae8a2ca8SHeikki Krogerus /*
3ae8a2ca8SHeikki Krogerus  * Copyright 2015-2017 Google, Inc
4ae8a2ca8SHeikki Krogerus  *
5ae8a2ca8SHeikki Krogerus  * USB Type-C Port Controller Interface.
6ae8a2ca8SHeikki Krogerus  */
7ae8a2ca8SHeikki Krogerus 
8ae8a2ca8SHeikki Krogerus #include <linux/delay.h>
9ae8a2ca8SHeikki Krogerus #include <linux/kernel.h>
10ae8a2ca8SHeikki Krogerus #include <linux/module.h>
11ae8a2ca8SHeikki Krogerus #include <linux/i2c.h>
12ae8a2ca8SHeikki Krogerus #include <linux/interrupt.h>
13ae8a2ca8SHeikki Krogerus #include <linux/property.h>
14ae8a2ca8SHeikki Krogerus #include <linux/regmap.h>
15ae8a2ca8SHeikki Krogerus #include <linux/usb/pd.h>
16ae8a2ca8SHeikki Krogerus #include <linux/usb/tcpm.h>
17ae8a2ca8SHeikki Krogerus #include <linux/usb/typec.h>
18ae8a2ca8SHeikki Krogerus 
19ae8a2ca8SHeikki Krogerus #include "tcpci.h"
20ae8a2ca8SHeikki Krogerus 
21e4a93780SBadhri Jagan Sridharan #define	PD_RETRY_COUNT_DEFAULT			3
22e4a93780SBadhri Jagan Sridharan #define	PD_RETRY_COUNT_3_0_OR_HIGHER		2
23e1a97bf8SBadhri Jagan Sridharan #define	AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV	3500
244288debeSBadhri Jagan Sridharan #define	VSINKPD_MIN_IR_DROP_MV			750
254288debeSBadhri Jagan Sridharan #define	VSRC_NEW_MIN_PERCENT			95
264288debeSBadhri Jagan Sridharan #define	VSRC_VALID_MIN_MV			500
274288debeSBadhri Jagan Sridharan #define	VPPS_NEW_MIN_PERCENT			95
284288debeSBadhri Jagan Sridharan #define	VPPS_VALID_MIN_MV			100
294288debeSBadhri Jagan Sridharan #define	VSINKDISCONNECT_PD_MIN_PERCENT		90
30ae8a2ca8SHeikki Krogerus 
31aecb1e45SBadhri Jagan Sridharan #define tcpc_presenting_rd(reg, cc) \
3219c234a1SBadhri Jagan Sridharan 	(!(TCPC_ROLE_CTRL_DRP & (reg)) && \
33aecb1e45SBadhri Jagan Sridharan 	 (((reg) & (TCPC_ROLE_CTRL_## cc ##_MASK << TCPC_ROLE_CTRL_## cc ##_SHIFT)) == \
34aecb1e45SBadhri Jagan Sridharan 	  (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_## cc ##_SHIFT)))
3519c234a1SBadhri Jagan Sridharan 
36ae8a2ca8SHeikki Krogerus struct tcpci {
37ae8a2ca8SHeikki Krogerus 	struct device *dev;
38ae8a2ca8SHeikki Krogerus 
39ae8a2ca8SHeikki Krogerus 	struct tcpm_port *port;
40ae8a2ca8SHeikki Krogerus 
41ae8a2ca8SHeikki Krogerus 	struct regmap *regmap;
42ae8a2ca8SHeikki Krogerus 
43ae8a2ca8SHeikki Krogerus 	bool controls_vbus;
44ae8a2ca8SHeikki Krogerus 
45ae8a2ca8SHeikki Krogerus 	struct tcpc_dev tcpc;
46ae8a2ca8SHeikki Krogerus 	struct tcpci_data *data;
47ae8a2ca8SHeikki Krogerus };
48ae8a2ca8SHeikki Krogerus 
49ae8a2ca8SHeikki Krogerus struct tcpci_chip {
50ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci;
51ae8a2ca8SHeikki Krogerus 	struct tcpci_data data;
52ae8a2ca8SHeikki Krogerus };
53ae8a2ca8SHeikki Krogerus 
5458ea326bSBadhri Jagan Sridharan struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci)
5558ea326bSBadhri Jagan Sridharan {
5658ea326bSBadhri Jagan Sridharan 	return tcpci->port;
5758ea326bSBadhri Jagan Sridharan }
5858ea326bSBadhri Jagan Sridharan EXPORT_SYMBOL_GPL(tcpci_get_tcpm_port);
5958ea326bSBadhri Jagan Sridharan 
60ae8a2ca8SHeikki Krogerus static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc)
61ae8a2ca8SHeikki Krogerus {
62ae8a2ca8SHeikki Krogerus 	return container_of(tcpc, struct tcpci, tcpc);
63ae8a2ca8SHeikki Krogerus }
64ae8a2ca8SHeikki Krogerus 
65ae8a2ca8SHeikki Krogerus static int tcpci_read16(struct tcpci *tcpci, unsigned int reg, u16 *val)
66ae8a2ca8SHeikki Krogerus {
67ae8a2ca8SHeikki Krogerus 	return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16));
68ae8a2ca8SHeikki Krogerus }
69ae8a2ca8SHeikki Krogerus 
70ae8a2ca8SHeikki Krogerus static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16 val)
71ae8a2ca8SHeikki Krogerus {
72ae8a2ca8SHeikki Krogerus 	return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16));
73ae8a2ca8SHeikki Krogerus }
74ae8a2ca8SHeikki Krogerus 
75ae8a2ca8SHeikki Krogerus static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc)
76ae8a2ca8SHeikki Krogerus {
77ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
78ae8a2ca8SHeikki Krogerus 	unsigned int reg;
79ae8a2ca8SHeikki Krogerus 	int ret;
80ae8a2ca8SHeikki Krogerus 
81ae8a2ca8SHeikki Krogerus 	switch (cc) {
82ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RA:
83ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT) |
84ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC2_SHIFT);
85ae8a2ca8SHeikki Krogerus 		break;
86ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RD:
87ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) |
88ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT);
89ae8a2ca8SHeikki Krogerus 		break;
90ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_DEF:
91ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
92ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
93ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_RP_VAL_DEF <<
94ae8a2ca8SHeikki Krogerus 			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
95ae8a2ca8SHeikki Krogerus 		break;
96ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_1_5:
97ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
98ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
99ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_RP_VAL_1_5 <<
100ae8a2ca8SHeikki Krogerus 			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
101ae8a2ca8SHeikki Krogerus 		break;
102ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_3_0:
103ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
104ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
105ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_RP_VAL_3_0 <<
106ae8a2ca8SHeikki Krogerus 			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
107ae8a2ca8SHeikki Krogerus 		break;
108ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_OPEN:
109ae8a2ca8SHeikki Krogerus 	default:
110ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT) |
111ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT);
112ae8a2ca8SHeikki Krogerus 		break;
113ae8a2ca8SHeikki Krogerus 	}
114ae8a2ca8SHeikki Krogerus 
115ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
116ae8a2ca8SHeikki Krogerus 	if (ret < 0)
117ae8a2ca8SHeikki Krogerus 		return ret;
118ae8a2ca8SHeikki Krogerus 
119ae8a2ca8SHeikki Krogerus 	return 0;
120ae8a2ca8SHeikki Krogerus }
121ae8a2ca8SHeikki Krogerus 
122a0765597SWei Yongjun static int tcpci_apply_rc(struct tcpc_dev *tcpc, enum typec_cc_status cc,
123a0765597SWei Yongjun 			  enum typec_cc_polarity polarity)
1247257fbc7SBadhri Jagan Sridharan {
1257257fbc7SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
1267257fbc7SBadhri Jagan Sridharan 	unsigned int reg;
1277257fbc7SBadhri Jagan Sridharan 	int ret;
1287257fbc7SBadhri Jagan Sridharan 
1297257fbc7SBadhri Jagan Sridharan 	ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, &reg);
1307257fbc7SBadhri Jagan Sridharan 	if (ret < 0)
1317257fbc7SBadhri Jagan Sridharan 		return ret;
1327257fbc7SBadhri Jagan Sridharan 
1337257fbc7SBadhri Jagan Sridharan 	/*
1347257fbc7SBadhri Jagan Sridharan 	 * APPLY_RC state is when ROLE_CONTROL.CC1 != ROLE_CONTROL.CC2 and vbus autodischarge on
1357257fbc7SBadhri Jagan Sridharan 	 * disconnect is disabled. Bail out when ROLE_CONTROL.CC1 != ROLE_CONTROL.CC2.
1367257fbc7SBadhri Jagan Sridharan 	 */
1377257fbc7SBadhri Jagan Sridharan 	if (((reg & (TCPC_ROLE_CTRL_CC2_MASK << TCPC_ROLE_CTRL_CC2_SHIFT)) >>
1387257fbc7SBadhri Jagan Sridharan 	     TCPC_ROLE_CTRL_CC2_SHIFT) !=
1397257fbc7SBadhri Jagan Sridharan 	    ((reg & (TCPC_ROLE_CTRL_CC1_MASK << TCPC_ROLE_CTRL_CC1_SHIFT)) >>
1407257fbc7SBadhri Jagan Sridharan 	     TCPC_ROLE_CTRL_CC1_SHIFT))
1417257fbc7SBadhri Jagan Sridharan 		return 0;
1427257fbc7SBadhri Jagan Sridharan 
1437257fbc7SBadhri Jagan Sridharan 	return regmap_update_bits(tcpci->regmap, TCPC_ROLE_CTRL, polarity == TYPEC_POLARITY_CC1 ?
1447257fbc7SBadhri Jagan Sridharan 				  TCPC_ROLE_CTRL_CC2_MASK << TCPC_ROLE_CTRL_CC2_SHIFT :
1457257fbc7SBadhri Jagan Sridharan 				  TCPC_ROLE_CTRL_CC1_MASK << TCPC_ROLE_CTRL_CC1_SHIFT,
1467257fbc7SBadhri Jagan Sridharan 				  TCPC_ROLE_CTRL_CC_OPEN);
1477257fbc7SBadhri Jagan Sridharan }
1487257fbc7SBadhri Jagan Sridharan 
1497893f9e1SHans de Goede static int tcpci_start_toggling(struct tcpc_dev *tcpc,
1507893f9e1SHans de Goede 				enum typec_port_type port_type,
151ae8a2ca8SHeikki Krogerus 				enum typec_cc_status cc)
152ae8a2ca8SHeikki Krogerus {
153ae8a2ca8SHeikki Krogerus 	int ret;
154ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
155ae8a2ca8SHeikki Krogerus 	unsigned int reg = TCPC_ROLE_CTRL_DRP;
156ae8a2ca8SHeikki Krogerus 
1577893f9e1SHans de Goede 	if (port_type != TYPEC_PORT_DRP)
1587893f9e1SHans de Goede 		return -EOPNOTSUPP;
1597893f9e1SHans de Goede 
160ae8a2ca8SHeikki Krogerus 	/* Handle vendor drp toggling */
161ae8a2ca8SHeikki Krogerus 	if (tcpci->data->start_drp_toggling) {
162ae8a2ca8SHeikki Krogerus 		ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc);
163ae8a2ca8SHeikki Krogerus 		if (ret < 0)
164ae8a2ca8SHeikki Krogerus 			return ret;
165ae8a2ca8SHeikki Krogerus 	}
166ae8a2ca8SHeikki Krogerus 
167ae8a2ca8SHeikki Krogerus 	switch (cc) {
168ae8a2ca8SHeikki Krogerus 	default:
169ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_DEF:
170ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_RP_VAL_DEF <<
171ae8a2ca8SHeikki Krogerus 			TCPC_ROLE_CTRL_RP_VAL_SHIFT);
172ae8a2ca8SHeikki Krogerus 		break;
173ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_1_5:
174ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_RP_VAL_1_5 <<
175ae8a2ca8SHeikki Krogerus 			TCPC_ROLE_CTRL_RP_VAL_SHIFT);
176ae8a2ca8SHeikki Krogerus 		break;
177ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_3_0:
178ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_RP_VAL_3_0 <<
179ae8a2ca8SHeikki Krogerus 			TCPC_ROLE_CTRL_RP_VAL_SHIFT);
180ae8a2ca8SHeikki Krogerus 		break;
181ae8a2ca8SHeikki Krogerus 	}
182ae8a2ca8SHeikki Krogerus 
183ae8a2ca8SHeikki Krogerus 	if (cc == TYPEC_CC_RD)
184ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) |
185ae8a2ca8SHeikki Krogerus 			   (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT);
186ae8a2ca8SHeikki Krogerus 	else
187ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
188ae8a2ca8SHeikki Krogerus 			   (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT);
189ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
190ae8a2ca8SHeikki Krogerus 	if (ret < 0)
191ae8a2ca8SHeikki Krogerus 		return ret;
192ae8a2ca8SHeikki Krogerus 	return regmap_write(tcpci->regmap, TCPC_COMMAND,
193ae8a2ca8SHeikki Krogerus 			    TCPC_CMD_LOOK4CONNECTION);
194ae8a2ca8SHeikki Krogerus }
195ae8a2ca8SHeikki Krogerus 
196ae8a2ca8SHeikki Krogerus static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool sink)
197ae8a2ca8SHeikki Krogerus {
198ae8a2ca8SHeikki Krogerus 	switch (cc) {
199ae8a2ca8SHeikki Krogerus 	case 0x1:
200ae8a2ca8SHeikki Krogerus 		return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA;
201ae8a2ca8SHeikki Krogerus 	case 0x2:
202ae8a2ca8SHeikki Krogerus 		return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD;
203ae8a2ca8SHeikki Krogerus 	case 0x3:
204ae8a2ca8SHeikki Krogerus 		if (sink)
205ae8a2ca8SHeikki Krogerus 			return TYPEC_CC_RP_3_0;
206df561f66SGustavo A. R. Silva 		fallthrough;
207ae8a2ca8SHeikki Krogerus 	case 0x0:
208ae8a2ca8SHeikki Krogerus 	default:
209ae8a2ca8SHeikki Krogerus 		return TYPEC_CC_OPEN;
210ae8a2ca8SHeikki Krogerus 	}
211ae8a2ca8SHeikki Krogerus }
212ae8a2ca8SHeikki Krogerus 
213ae8a2ca8SHeikki Krogerus static int tcpci_get_cc(struct tcpc_dev *tcpc,
214ae8a2ca8SHeikki Krogerus 			enum typec_cc_status *cc1, enum typec_cc_status *cc2)
215ae8a2ca8SHeikki Krogerus {
216ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
21719c234a1SBadhri Jagan Sridharan 	unsigned int reg, role_control;
218ae8a2ca8SHeikki Krogerus 	int ret;
219ae8a2ca8SHeikki Krogerus 
22019c234a1SBadhri Jagan Sridharan 	ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, &role_control);
22119c234a1SBadhri Jagan Sridharan 	if (ret < 0)
22219c234a1SBadhri Jagan Sridharan 		return ret;
22319c234a1SBadhri Jagan Sridharan 
224ae8a2ca8SHeikki Krogerus 	ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, &reg);
225ae8a2ca8SHeikki Krogerus 	if (ret < 0)
226ae8a2ca8SHeikki Krogerus 		return ret;
227ae8a2ca8SHeikki Krogerus 
228ae8a2ca8SHeikki Krogerus 	*cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) &
229ae8a2ca8SHeikki Krogerus 				 TCPC_CC_STATUS_CC1_MASK,
23019c234a1SBadhri Jagan Sridharan 				 reg & TCPC_CC_STATUS_TERM ||
231aecb1e45SBadhri Jagan Sridharan 				 tcpc_presenting_rd(role_control, CC1));
232ae8a2ca8SHeikki Krogerus 	*cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) &
233ae8a2ca8SHeikki Krogerus 				 TCPC_CC_STATUS_CC2_MASK,
23419c234a1SBadhri Jagan Sridharan 				 reg & TCPC_CC_STATUS_TERM ||
235aecb1e45SBadhri Jagan Sridharan 				 tcpc_presenting_rd(role_control, CC2));
236ae8a2ca8SHeikki Krogerus 
237ae8a2ca8SHeikki Krogerus 	return 0;
238ae8a2ca8SHeikki Krogerus }
239ae8a2ca8SHeikki Krogerus 
240ae8a2ca8SHeikki Krogerus static int tcpci_set_polarity(struct tcpc_dev *tcpc,
241ae8a2ca8SHeikki Krogerus 			      enum typec_cc_polarity polarity)
242ae8a2ca8SHeikki Krogerus {
243ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
244ae8a2ca8SHeikki Krogerus 	unsigned int reg;
245ae8a2ca8SHeikki Krogerus 	int ret;
24657ce6466SBadhri Jagan Sridharan 	enum typec_cc_status cc1, cc2;
247ae8a2ca8SHeikki Krogerus 
24857ce6466SBadhri Jagan Sridharan 	/* Obtain Rp setting from role control */
249ae8a2ca8SHeikki Krogerus 	ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, &reg);
250ae8a2ca8SHeikki Krogerus 	if (ret < 0)
251ae8a2ca8SHeikki Krogerus 		return ret;
252ae8a2ca8SHeikki Krogerus 
25357ce6466SBadhri Jagan Sridharan 	ret = tcpci_get_cc(tcpc, &cc1, &cc2);
25457ce6466SBadhri Jagan Sridharan 	if (ret < 0)
25557ce6466SBadhri Jagan Sridharan 		return ret;
25657ce6466SBadhri Jagan Sridharan 
25757ce6466SBadhri Jagan Sridharan 	/*
25857ce6466SBadhri Jagan Sridharan 	 * When port has drp toggling enabled, ROLE_CONTROL would only have the initial
25957ce6466SBadhri Jagan Sridharan 	 * terminations for the toggling and does not indicate the final cc
26057ce6466SBadhri Jagan Sridharan 	 * terminations when ConnectionResult is 0 i.e. drp toggling stops and
26157ce6466SBadhri Jagan Sridharan 	 * the connection is resolbed. Infer port role from TCPC_CC_STATUS based on the
26257ce6466SBadhri Jagan Sridharan 	 * terminations seen. The port role is then used to set the cc terminations.
26357ce6466SBadhri Jagan Sridharan 	 */
26457ce6466SBadhri Jagan Sridharan 	if (reg & TCPC_ROLE_CTRL_DRP) {
26557ce6466SBadhri Jagan Sridharan 		/* Disable DRP for the OPEN setting to take effect */
26657ce6466SBadhri Jagan Sridharan 		reg = reg & ~TCPC_ROLE_CTRL_DRP;
26757ce6466SBadhri Jagan Sridharan 
26857ce6466SBadhri Jagan Sridharan 		if (polarity == TYPEC_POLARITY_CC2) {
26957ce6466SBadhri Jagan Sridharan 			reg &= ~(TCPC_ROLE_CTRL_CC2_MASK << TCPC_ROLE_CTRL_CC2_SHIFT);
27057ce6466SBadhri Jagan Sridharan 			/* Local port is source */
27157ce6466SBadhri Jagan Sridharan 			if (cc2 == TYPEC_CC_RD)
27257ce6466SBadhri Jagan Sridharan 				/* Role control would have the Rp setting when DRP was enabled */
27357ce6466SBadhri Jagan Sridharan 				reg |= TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT;
27457ce6466SBadhri Jagan Sridharan 			else
27557ce6466SBadhri Jagan Sridharan 				reg |= TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT;
27657ce6466SBadhri Jagan Sridharan 		} else {
27757ce6466SBadhri Jagan Sridharan 			reg &= ~(TCPC_ROLE_CTRL_CC1_MASK << TCPC_ROLE_CTRL_CC1_SHIFT);
27857ce6466SBadhri Jagan Sridharan 			/* Local port is source */
27957ce6466SBadhri Jagan Sridharan 			if (cc1 == TYPEC_CC_RD)
28057ce6466SBadhri Jagan Sridharan 				/* Role control would have the Rp setting when DRP was enabled */
28157ce6466SBadhri Jagan Sridharan 				reg |= TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT;
28257ce6466SBadhri Jagan Sridharan 			else
28357ce6466SBadhri Jagan Sridharan 				reg |= TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT;
28457ce6466SBadhri Jagan Sridharan 		}
28557ce6466SBadhri Jagan Sridharan 	}
28657ce6466SBadhri Jagan Sridharan 
287ae8a2ca8SHeikki Krogerus 	if (polarity == TYPEC_POLARITY_CC2)
288ae8a2ca8SHeikki Krogerus 		reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT;
289ae8a2ca8SHeikki Krogerus 	else
290ae8a2ca8SHeikki Krogerus 		reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT;
291ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
292ae8a2ca8SHeikki Krogerus 	if (ret < 0)
293ae8a2ca8SHeikki Krogerus 		return ret;
294ae8a2ca8SHeikki Krogerus 
295ae8a2ca8SHeikki Krogerus 	return regmap_write(tcpci->regmap, TCPC_TCPC_CTRL,
296ae8a2ca8SHeikki Krogerus 			   (polarity == TYPEC_POLARITY_CC2) ?
297ae8a2ca8SHeikki Krogerus 			   TCPC_TCPC_CTRL_ORIENTATION : 0);
298ae8a2ca8SHeikki Krogerus }
299ae8a2ca8SHeikki Krogerus 
300372a3d0bSBadhri Jagan Sridharan static void tcpci_set_partner_usb_comm_capable(struct tcpc_dev *tcpc, bool capable)
301372a3d0bSBadhri Jagan Sridharan {
302372a3d0bSBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
303372a3d0bSBadhri Jagan Sridharan 
304372a3d0bSBadhri Jagan Sridharan 	if (tcpci->data->set_partner_usb_comm_capable)
305372a3d0bSBadhri Jagan Sridharan 		tcpci->data->set_partner_usb_comm_capable(tcpci, tcpci->data, capable);
306372a3d0bSBadhri Jagan Sridharan }
307372a3d0bSBadhri Jagan Sridharan 
308ae8a2ca8SHeikki Krogerus static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable)
309ae8a2ca8SHeikki Krogerus {
310ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
311ae8a2ca8SHeikki Krogerus 	int ret;
312ae8a2ca8SHeikki Krogerus 
313ae8a2ca8SHeikki Krogerus 	/* Handle vendor set vconn */
314ae8a2ca8SHeikki Krogerus 	if (tcpci->data->set_vconn) {
315ae8a2ca8SHeikki Krogerus 		ret = tcpci->data->set_vconn(tcpci, tcpci->data, enable);
316ae8a2ca8SHeikki Krogerus 		if (ret < 0)
317ae8a2ca8SHeikki Krogerus 			return ret;
318ae8a2ca8SHeikki Krogerus 	}
319ae8a2ca8SHeikki Krogerus 
320ae8a2ca8SHeikki Krogerus 	return regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL,
321ae8a2ca8SHeikki Krogerus 				TCPC_POWER_CTRL_VCONN_ENABLE,
322ae8a2ca8SHeikki Krogerus 				enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0);
323ae8a2ca8SHeikki Krogerus }
324ae8a2ca8SHeikki Krogerus 
325e1a97bf8SBadhri Jagan Sridharan static int tcpci_enable_auto_vbus_discharge(struct tcpc_dev *dev, bool enable)
326e1a97bf8SBadhri Jagan Sridharan {
327e1a97bf8SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(dev);
328e1a97bf8SBadhri Jagan Sridharan 	int ret;
329e1a97bf8SBadhri Jagan Sridharan 
330e1a97bf8SBadhri Jagan Sridharan 	ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_AUTO_DISCHARGE,
331e1a97bf8SBadhri Jagan Sridharan 				 enable ? TCPC_POWER_CTRL_AUTO_DISCHARGE : 0);
332e1a97bf8SBadhri Jagan Sridharan 	return ret;
333e1a97bf8SBadhri Jagan Sridharan }
334e1a97bf8SBadhri Jagan Sridharan 
335e1a97bf8SBadhri Jagan Sridharan static int tcpci_set_auto_vbus_discharge_threshold(struct tcpc_dev *dev, enum typec_pwr_opmode mode,
336e1a97bf8SBadhri Jagan Sridharan 						   bool pps_active, u32 requested_vbus_voltage_mv)
337e1a97bf8SBadhri Jagan Sridharan {
338e1a97bf8SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(dev);
339e1a97bf8SBadhri Jagan Sridharan 	unsigned int pwr_ctrl, threshold = 0;
340e1a97bf8SBadhri Jagan Sridharan 	int ret;
341e1a97bf8SBadhri Jagan Sridharan 
342e1a97bf8SBadhri Jagan Sridharan 	/*
343e1a97bf8SBadhri Jagan Sridharan 	 * Indicates that vbus is going to go away due PR_SWAP, hard reset etc.
344e1a97bf8SBadhri Jagan Sridharan 	 * Do not discharge vbus here.
345e1a97bf8SBadhri Jagan Sridharan 	 */
346e1a97bf8SBadhri Jagan Sridharan 	if (requested_vbus_voltage_mv == 0)
347e1a97bf8SBadhri Jagan Sridharan 		goto write_thresh;
348e1a97bf8SBadhri Jagan Sridharan 
349e1a97bf8SBadhri Jagan Sridharan 	ret = regmap_read(tcpci->regmap, TCPC_POWER_CTRL, &pwr_ctrl);
350e1a97bf8SBadhri Jagan Sridharan 	if (ret < 0)
351e1a97bf8SBadhri Jagan Sridharan 		return ret;
352e1a97bf8SBadhri Jagan Sridharan 
353e1a97bf8SBadhri Jagan Sridharan 	if (pwr_ctrl & TCPC_FAST_ROLE_SWAP_EN) {
354e1a97bf8SBadhri Jagan Sridharan 		/* To prevent disconnect when the source is fast role swap is capable. */
355e1a97bf8SBadhri Jagan Sridharan 		threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV;
356e1a97bf8SBadhri Jagan Sridharan 	} else if (mode == TYPEC_PWR_MODE_PD) {
357e1a97bf8SBadhri Jagan Sridharan 		if (pps_active)
3584288debeSBadhri Jagan Sridharan 			threshold = ((VPPS_NEW_MIN_PERCENT * requested_vbus_voltage_mv / 100) -
3594288debeSBadhri Jagan Sridharan 				     VSINKPD_MIN_IR_DROP_MV - VPPS_VALID_MIN_MV) *
3604288debeSBadhri Jagan Sridharan 				     VSINKDISCONNECT_PD_MIN_PERCENT / 100;
361e1a97bf8SBadhri Jagan Sridharan 		else
3624288debeSBadhri Jagan Sridharan 			threshold = ((VSRC_NEW_MIN_PERCENT * requested_vbus_voltage_mv / 100) -
3634288debeSBadhri Jagan Sridharan 				     VSINKPD_MIN_IR_DROP_MV - VSRC_VALID_MIN_MV) *
3644288debeSBadhri Jagan Sridharan 				     VSINKDISCONNECT_PD_MIN_PERCENT / 100;
365e1a97bf8SBadhri Jagan Sridharan 	} else {
366e1a97bf8SBadhri Jagan Sridharan 		/* 3.5V for non-pd sink */
367e1a97bf8SBadhri Jagan Sridharan 		threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV;
368e1a97bf8SBadhri Jagan Sridharan 	}
369e1a97bf8SBadhri Jagan Sridharan 
370e1a97bf8SBadhri Jagan Sridharan 	threshold = threshold / TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV;
371e1a97bf8SBadhri Jagan Sridharan 
372e1a97bf8SBadhri Jagan Sridharan 	if (threshold > TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX)
373e1a97bf8SBadhri Jagan Sridharan 		return -EINVAL;
374e1a97bf8SBadhri Jagan Sridharan 
375e1a97bf8SBadhri Jagan Sridharan write_thresh:
376e1a97bf8SBadhri Jagan Sridharan 	return tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, threshold);
377e1a97bf8SBadhri Jagan Sridharan }
378e1a97bf8SBadhri Jagan Sridharan 
37911121c24SBadhri Jagan Sridharan static int tcpci_enable_frs(struct tcpc_dev *dev, bool enable)
38011121c24SBadhri Jagan Sridharan {
38111121c24SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(dev);
38211121c24SBadhri Jagan Sridharan 	int ret;
38311121c24SBadhri Jagan Sridharan 
38411121c24SBadhri Jagan Sridharan 	/* To prevent disconnect during FRS, set disconnect threshold to 3.5V */
38511121c24SBadhri Jagan Sridharan 	ret = tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, enable ? 0 : 0x8c);
38611121c24SBadhri Jagan Sridharan 	if (ret < 0)
38711121c24SBadhri Jagan Sridharan 		return ret;
38811121c24SBadhri Jagan Sridharan 
38911121c24SBadhri Jagan Sridharan 	ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_FAST_ROLE_SWAP_EN, enable ?
39011121c24SBadhri Jagan Sridharan 				 TCPC_FAST_ROLE_SWAP_EN : 0);
39111121c24SBadhri Jagan Sridharan 
39211121c24SBadhri Jagan Sridharan 	return ret;
39311121c24SBadhri Jagan Sridharan }
39411121c24SBadhri Jagan Sridharan 
395a57d253fSBadhri Jagan Sridharan static void tcpci_frs_sourcing_vbus(struct tcpc_dev *dev)
396a57d253fSBadhri Jagan Sridharan {
397a57d253fSBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(dev);
398a57d253fSBadhri Jagan Sridharan 
399a57d253fSBadhri Jagan Sridharan 	if (tcpci->data->frs_sourcing_vbus)
400a57d253fSBadhri Jagan Sridharan 		tcpci->data->frs_sourcing_vbus(tcpci, tcpci->data);
401a57d253fSBadhri Jagan Sridharan }
402a57d253fSBadhri Jagan Sridharan 
403c081ac42SBadhri Jagan Sridharan static int tcpci_set_bist_data(struct tcpc_dev *tcpc, bool enable)
404c081ac42SBadhri Jagan Sridharan {
405c081ac42SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
406c081ac42SBadhri Jagan Sridharan 
407c081ac42SBadhri Jagan Sridharan 	return regmap_update_bits(tcpci->regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_BIST_TM,
408c081ac42SBadhri Jagan Sridharan 				 enable ? TCPC_TCPC_CTRL_BIST_TM : 0);
409c081ac42SBadhri Jagan Sridharan }
410c081ac42SBadhri Jagan Sridharan 
411ae8a2ca8SHeikki Krogerus static int tcpci_set_roles(struct tcpc_dev *tcpc, bool attached,
412ae8a2ca8SHeikki Krogerus 			   enum typec_role role, enum typec_data_role data)
413ae8a2ca8SHeikki Krogerus {
414ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
415ae8a2ca8SHeikki Krogerus 	unsigned int reg;
416ae8a2ca8SHeikki Krogerus 	int ret;
417ae8a2ca8SHeikki Krogerus 
418ae8a2ca8SHeikki Krogerus 	reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT;
419ae8a2ca8SHeikki Krogerus 	if (role == TYPEC_SOURCE)
420ae8a2ca8SHeikki Krogerus 		reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
421ae8a2ca8SHeikki Krogerus 	if (data == TYPEC_HOST)
422ae8a2ca8SHeikki Krogerus 		reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
423ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg);
424ae8a2ca8SHeikki Krogerus 	if (ret < 0)
425ae8a2ca8SHeikki Krogerus 		return ret;
426ae8a2ca8SHeikki Krogerus 
427ae8a2ca8SHeikki Krogerus 	return 0;
428ae8a2ca8SHeikki Krogerus }
429ae8a2ca8SHeikki Krogerus 
430ae8a2ca8SHeikki Krogerus static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable)
431ae8a2ca8SHeikki Krogerus {
432ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
433ae8a2ca8SHeikki Krogerus 	unsigned int reg = 0;
434ae8a2ca8SHeikki Krogerus 	int ret;
435ae8a2ca8SHeikki Krogerus 
436ae8a2ca8SHeikki Krogerus 	if (enable)
437ae8a2ca8SHeikki Krogerus 		reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
438ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
439ae8a2ca8SHeikki Krogerus 	if (ret < 0)
440ae8a2ca8SHeikki Krogerus 		return ret;
441ae8a2ca8SHeikki Krogerus 
442ae8a2ca8SHeikki Krogerus 	return 0;
443ae8a2ca8SHeikki Krogerus }
444ae8a2ca8SHeikki Krogerus 
445ae8a2ca8SHeikki Krogerus static int tcpci_get_vbus(struct tcpc_dev *tcpc)
446ae8a2ca8SHeikki Krogerus {
447ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
448ae8a2ca8SHeikki Krogerus 	unsigned int reg;
449ae8a2ca8SHeikki Krogerus 	int ret;
450ae8a2ca8SHeikki Krogerus 
451ae8a2ca8SHeikki Krogerus 	ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
452ae8a2ca8SHeikki Krogerus 	if (ret < 0)
453ae8a2ca8SHeikki Krogerus 		return ret;
454ae8a2ca8SHeikki Krogerus 
455ae8a2ca8SHeikki Krogerus 	return !!(reg & TCPC_POWER_STATUS_VBUS_PRES);
456ae8a2ca8SHeikki Krogerus }
457ae8a2ca8SHeikki Krogerus 
458766c485bSBadhri Jagan Sridharan static bool tcpci_is_vbus_vsafe0v(struct tcpc_dev *tcpc)
459766c485bSBadhri Jagan Sridharan {
460766c485bSBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
461766c485bSBadhri Jagan Sridharan 	unsigned int reg;
462766c485bSBadhri Jagan Sridharan 	int ret;
463766c485bSBadhri Jagan Sridharan 
464766c485bSBadhri Jagan Sridharan 	ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, &reg);
465766c485bSBadhri Jagan Sridharan 	if (ret < 0)
466766c485bSBadhri Jagan Sridharan 		return false;
467766c485bSBadhri Jagan Sridharan 
468766c485bSBadhri Jagan Sridharan 	return !!(reg & TCPC_EXTENDED_STATUS_VSAFE0V);
469766c485bSBadhri Jagan Sridharan }
470766c485bSBadhri Jagan Sridharan 
471ae8a2ca8SHeikki Krogerus static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
472ae8a2ca8SHeikki Krogerus {
473ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
474ae8a2ca8SHeikki Krogerus 	int ret;
475ae8a2ca8SHeikki Krogerus 
476b9358a06SBadhri Jagan Sridharan 	if (tcpci->data->set_vbus) {
477b9358a06SBadhri Jagan Sridharan 		ret = tcpci->data->set_vbus(tcpci, tcpci->data, source, sink);
478b9358a06SBadhri Jagan Sridharan 		/* Bypass when ret > 0 */
479b9358a06SBadhri Jagan Sridharan 		if (ret != 0)
480b9358a06SBadhri Jagan Sridharan 			return ret < 0 ? ret : 0;
481b9358a06SBadhri Jagan Sridharan 	}
482b9358a06SBadhri Jagan Sridharan 
483ae8a2ca8SHeikki Krogerus 	/* Disable both source and sink first before enabling anything */
484ae8a2ca8SHeikki Krogerus 
485ae8a2ca8SHeikki Krogerus 	if (!source) {
486ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
487ae8a2ca8SHeikki Krogerus 				   TCPC_CMD_DISABLE_SRC_VBUS);
488ae8a2ca8SHeikki Krogerus 		if (ret < 0)
489ae8a2ca8SHeikki Krogerus 			return ret;
490ae8a2ca8SHeikki Krogerus 	}
491ae8a2ca8SHeikki Krogerus 
492ae8a2ca8SHeikki Krogerus 	if (!sink) {
493ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
494ae8a2ca8SHeikki Krogerus 				   TCPC_CMD_DISABLE_SINK_VBUS);
495ae8a2ca8SHeikki Krogerus 		if (ret < 0)
496ae8a2ca8SHeikki Krogerus 			return ret;
497ae8a2ca8SHeikki Krogerus 	}
498ae8a2ca8SHeikki Krogerus 
499ae8a2ca8SHeikki Krogerus 	if (source) {
500ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
501ae8a2ca8SHeikki Krogerus 				   TCPC_CMD_SRC_VBUS_DEFAULT);
502ae8a2ca8SHeikki Krogerus 		if (ret < 0)
503ae8a2ca8SHeikki Krogerus 			return ret;
504ae8a2ca8SHeikki Krogerus 	}
505ae8a2ca8SHeikki Krogerus 
506ae8a2ca8SHeikki Krogerus 	if (sink) {
507ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
508ae8a2ca8SHeikki Krogerus 				   TCPC_CMD_SINK_VBUS);
509ae8a2ca8SHeikki Krogerus 		if (ret < 0)
510ae8a2ca8SHeikki Krogerus 			return ret;
511ae8a2ca8SHeikki Krogerus 	}
512ae8a2ca8SHeikki Krogerus 
513ae8a2ca8SHeikki Krogerus 	return 0;
514ae8a2ca8SHeikki Krogerus }
515ae8a2ca8SHeikki Krogerus 
516e4a93780SBadhri Jagan Sridharan static int tcpci_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type,
517e4a93780SBadhri Jagan Sridharan 			     const struct pd_message *msg, unsigned int negotiated_rev)
518ae8a2ca8SHeikki Krogerus {
519ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
520ae8a2ca8SHeikki Krogerus 	u16 header = msg ? le16_to_cpu(msg->header) : 0;
521ae8a2ca8SHeikki Krogerus 	unsigned int reg, cnt;
522ae8a2ca8SHeikki Krogerus 	int ret;
523ae8a2ca8SHeikki Krogerus 
524ae8a2ca8SHeikki Krogerus 	cnt = msg ? pd_header_cnt(header) * 4 : 0;
52519b65476SBadhri Jagan Sridharan 	/**
52619b65476SBadhri Jagan Sridharan 	 * TCPCI spec forbids direct access of TCPC_TX_DATA.
52719b65476SBadhri Jagan Sridharan 	 * But, since some of the chipsets offer this capability,
52819b65476SBadhri Jagan Sridharan 	 * it's fair to support both.
52919b65476SBadhri Jagan Sridharan 	 */
53019b65476SBadhri Jagan Sridharan 	if (tcpci->data->TX_BUF_BYTE_x_hidden) {
53119b65476SBadhri Jagan Sridharan 		u8 buf[TCPC_TRANSMIT_BUFFER_MAX_LEN] = {0,};
53219b65476SBadhri Jagan Sridharan 		u8 pos = 0;
53319b65476SBadhri Jagan Sridharan 
53419b65476SBadhri Jagan Sridharan 		/* Payload + header + TCPC_TX_BYTE_CNT */
53519b65476SBadhri Jagan Sridharan 		buf[pos++] = cnt + 2;
53619b65476SBadhri Jagan Sridharan 
53719b65476SBadhri Jagan Sridharan 		if (msg)
53819b65476SBadhri Jagan Sridharan 			memcpy(&buf[pos], &msg->header, sizeof(msg->header));
53919b65476SBadhri Jagan Sridharan 
54019b65476SBadhri Jagan Sridharan 		pos += sizeof(header);
54119b65476SBadhri Jagan Sridharan 
54219b65476SBadhri Jagan Sridharan 		if (cnt > 0)
54319b65476SBadhri Jagan Sridharan 			memcpy(&buf[pos], msg->payload, cnt);
54419b65476SBadhri Jagan Sridharan 
54519b65476SBadhri Jagan Sridharan 		pos += cnt;
54619b65476SBadhri Jagan Sridharan 		ret = regmap_raw_write(tcpci->regmap, TCPC_TX_BYTE_CNT, buf, pos);
54719b65476SBadhri Jagan Sridharan 		if (ret < 0)
54819b65476SBadhri Jagan Sridharan 			return ret;
54919b65476SBadhri Jagan Sridharan 	} else {
550ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2);
551ae8a2ca8SHeikki Krogerus 		if (ret < 0)
552ae8a2ca8SHeikki Krogerus 			return ret;
553ae8a2ca8SHeikki Krogerus 
554ae8a2ca8SHeikki Krogerus 		ret = tcpci_write16(tcpci, TCPC_TX_HDR, header);
555ae8a2ca8SHeikki Krogerus 		if (ret < 0)
556ae8a2ca8SHeikki Krogerus 			return ret;
557ae8a2ca8SHeikki Krogerus 
558ae8a2ca8SHeikki Krogerus 		if (cnt > 0) {
55919b65476SBadhri Jagan Sridharan 			ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, &msg->payload, cnt);
560ae8a2ca8SHeikki Krogerus 			if (ret < 0)
561ae8a2ca8SHeikki Krogerus 				return ret;
562ae8a2ca8SHeikki Krogerus 		}
56319b65476SBadhri Jagan Sridharan 	}
564ae8a2ca8SHeikki Krogerus 
565e4a93780SBadhri Jagan Sridharan 	/* nRetryCount is 3 in PD2.0 spec where 2 in PD3.0 spec */
566e4a93780SBadhri Jagan Sridharan 	reg = ((negotiated_rev > PD_REV20 ? PD_RETRY_COUNT_3_0_OR_HIGHER : PD_RETRY_COUNT_DEFAULT)
567e4a93780SBadhri Jagan Sridharan 	       << TCPC_TRANSMIT_RETRY_SHIFT) | (type << TCPC_TRANSMIT_TYPE_SHIFT);
568ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg);
569ae8a2ca8SHeikki Krogerus 	if (ret < 0)
570ae8a2ca8SHeikki Krogerus 		return ret;
571ae8a2ca8SHeikki Krogerus 
572ae8a2ca8SHeikki Krogerus 	return 0;
573ae8a2ca8SHeikki Krogerus }
574ae8a2ca8SHeikki Krogerus 
575ae8a2ca8SHeikki Krogerus static int tcpci_init(struct tcpc_dev *tcpc)
576ae8a2ca8SHeikki Krogerus {
577ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
578ae8a2ca8SHeikki Krogerus 	unsigned long timeout = jiffies + msecs_to_jiffies(2000); /* XXX */
579ae8a2ca8SHeikki Krogerus 	unsigned int reg;
580ae8a2ca8SHeikki Krogerus 	int ret;
581ae8a2ca8SHeikki Krogerus 
582ae8a2ca8SHeikki Krogerus 	while (time_before_eq(jiffies, timeout)) {
583ae8a2ca8SHeikki Krogerus 		ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
584ae8a2ca8SHeikki Krogerus 		if (ret < 0)
585ae8a2ca8SHeikki Krogerus 			return ret;
586ae8a2ca8SHeikki Krogerus 		if (!(reg & TCPC_POWER_STATUS_UNINIT))
587ae8a2ca8SHeikki Krogerus 			break;
588ae8a2ca8SHeikki Krogerus 		usleep_range(10000, 20000);
589ae8a2ca8SHeikki Krogerus 	}
590ae8a2ca8SHeikki Krogerus 	if (time_after(jiffies, timeout))
591ae8a2ca8SHeikki Krogerus 		return -ETIMEDOUT;
592ae8a2ca8SHeikki Krogerus 
593ae8a2ca8SHeikki Krogerus 	/* Handle vendor init */
594ae8a2ca8SHeikki Krogerus 	if (tcpci->data->init) {
595ae8a2ca8SHeikki Krogerus 		ret = tcpci->data->init(tcpci, tcpci->data);
596ae8a2ca8SHeikki Krogerus 		if (ret < 0)
597ae8a2ca8SHeikki Krogerus 			return ret;
598ae8a2ca8SHeikki Krogerus 	}
599ae8a2ca8SHeikki Krogerus 
600ae8a2ca8SHeikki Krogerus 	/* Clear all events */
601ae8a2ca8SHeikki Krogerus 	ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff);
602ae8a2ca8SHeikki Krogerus 	if (ret < 0)
603ae8a2ca8SHeikki Krogerus 		return ret;
604ae8a2ca8SHeikki Krogerus 
605ae8a2ca8SHeikki Krogerus 	if (tcpci->controls_vbus)
606ae8a2ca8SHeikki Krogerus 		reg = TCPC_POWER_STATUS_VBUS_PRES;
607ae8a2ca8SHeikki Krogerus 	else
608ae8a2ca8SHeikki Krogerus 		reg = 0;
609ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_POWER_STATUS_MASK, reg);
610ae8a2ca8SHeikki Krogerus 	if (ret < 0)
611ae8a2ca8SHeikki Krogerus 		return ret;
612ae8a2ca8SHeikki Krogerus 
613ae8a2ca8SHeikki Krogerus 	/* Enable Vbus detection */
614ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
615ae8a2ca8SHeikki Krogerus 			   TCPC_CMD_ENABLE_VBUS_DETECT);
616ae8a2ca8SHeikki Krogerus 	if (ret < 0)
617ae8a2ca8SHeikki Krogerus 		return ret;
618ae8a2ca8SHeikki Krogerus 
619ae8a2ca8SHeikki Krogerus 	reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
620ae8a2ca8SHeikki Krogerus 		TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
621ae8a2ca8SHeikki Krogerus 		TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
622ae8a2ca8SHeikki Krogerus 	if (tcpci->controls_vbus)
623ae8a2ca8SHeikki Krogerus 		reg |= TCPC_ALERT_POWER_STATUS;
624766c485bSBadhri Jagan Sridharan 	/* Enable VSAFE0V status interrupt when detecting VSAFE0V is supported */
625766c485bSBadhri Jagan Sridharan 	if (tcpci->data->vbus_vsafe0v) {
626766c485bSBadhri Jagan Sridharan 		reg |= TCPC_ALERT_EXTENDED_STATUS;
627766c485bSBadhri Jagan Sridharan 		ret = regmap_write(tcpci->regmap, TCPC_EXTENDED_STATUS_MASK,
628766c485bSBadhri Jagan Sridharan 				   TCPC_EXTENDED_STATUS_VSAFE0V);
629766c485bSBadhri Jagan Sridharan 		if (ret < 0)
630766c485bSBadhri Jagan Sridharan 			return ret;
631766c485bSBadhri Jagan Sridharan 	}
632ae8a2ca8SHeikki Krogerus 	return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg);
633ae8a2ca8SHeikki Krogerus }
634ae8a2ca8SHeikki Krogerus 
635ae8a2ca8SHeikki Krogerus irqreturn_t tcpci_irq(struct tcpci *tcpci)
636ae8a2ca8SHeikki Krogerus {
637ae8a2ca8SHeikki Krogerus 	u16 status;
638766c485bSBadhri Jagan Sridharan 	int ret;
639766c485bSBadhri Jagan Sridharan 	unsigned int raw;
640ae8a2ca8SHeikki Krogerus 
641ae8a2ca8SHeikki Krogerus 	tcpci_read16(tcpci, TCPC_ALERT, &status);
642ae8a2ca8SHeikki Krogerus 
643ae8a2ca8SHeikki Krogerus 	/*
644ae8a2ca8SHeikki Krogerus 	 * Clear alert status for everything except RX_STATUS, which shouldn't
645ae8a2ca8SHeikki Krogerus 	 * be cleared until we have successfully retrieved message.
646ae8a2ca8SHeikki Krogerus 	 */
647ae8a2ca8SHeikki Krogerus 	if (status & ~TCPC_ALERT_RX_STATUS)
648ae8a2ca8SHeikki Krogerus 		tcpci_write16(tcpci, TCPC_ALERT,
649ae8a2ca8SHeikki Krogerus 			      status & ~TCPC_ALERT_RX_STATUS);
650ae8a2ca8SHeikki Krogerus 
651ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_CC_STATUS)
652ae8a2ca8SHeikki Krogerus 		tcpm_cc_change(tcpci->port);
653ae8a2ca8SHeikki Krogerus 
654ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_POWER_STATUS) {
655766c485bSBadhri Jagan Sridharan 		regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &raw);
656ae8a2ca8SHeikki Krogerus 		/*
657ae8a2ca8SHeikki Krogerus 		 * If power status mask has been reset, then the TCPC
658ae8a2ca8SHeikki Krogerus 		 * has reset.
659ae8a2ca8SHeikki Krogerus 		 */
660766c485bSBadhri Jagan Sridharan 		if (raw == 0xff)
661ae8a2ca8SHeikki Krogerus 			tcpm_tcpc_reset(tcpci->port);
662ae8a2ca8SHeikki Krogerus 		else
663ae8a2ca8SHeikki Krogerus 			tcpm_vbus_change(tcpci->port);
664ae8a2ca8SHeikki Krogerus 	}
665ae8a2ca8SHeikki Krogerus 
666ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_RX_STATUS) {
667ae8a2ca8SHeikki Krogerus 		struct pd_message msg;
668c215e48eSDouglas Gilbert 		unsigned int cnt, payload_cnt;
669ae8a2ca8SHeikki Krogerus 		u16 header;
670ae8a2ca8SHeikki Krogerus 
671ae8a2ca8SHeikki Krogerus 		regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt);
672c215e48eSDouglas Gilbert 		/*
673c215e48eSDouglas Gilbert 		 * 'cnt' corresponds to READABLE_BYTE_COUNT in section 4.4.14
674c215e48eSDouglas Gilbert 		 * of the TCPCI spec [Rev 2.0 Ver 1.0 October 2017] and is
675c215e48eSDouglas Gilbert 		 * defined in table 4-36 as one greater than the number of
676c215e48eSDouglas Gilbert 		 * bytes received. And that number includes the header. So:
677c215e48eSDouglas Gilbert 		 */
678c215e48eSDouglas Gilbert 		if (cnt > 3)
679c215e48eSDouglas Gilbert 			payload_cnt = cnt - (1 + sizeof(msg.header));
680c215e48eSDouglas Gilbert 		else
681c215e48eSDouglas Gilbert 			payload_cnt = 0;
682ae8a2ca8SHeikki Krogerus 
683ae8a2ca8SHeikki Krogerus 		tcpci_read16(tcpci, TCPC_RX_HDR, &header);
684ae8a2ca8SHeikki Krogerus 		msg.header = cpu_to_le16(header);
685ae8a2ca8SHeikki Krogerus 
686c215e48eSDouglas Gilbert 		if (WARN_ON(payload_cnt > sizeof(msg.payload)))
687c215e48eSDouglas Gilbert 			payload_cnt = sizeof(msg.payload);
688ae8a2ca8SHeikki Krogerus 
689c215e48eSDouglas Gilbert 		if (payload_cnt > 0)
690ae8a2ca8SHeikki Krogerus 			regmap_raw_read(tcpci->regmap, TCPC_RX_DATA,
691c215e48eSDouglas Gilbert 					&msg.payload, payload_cnt);
692ae8a2ca8SHeikki Krogerus 
693ae8a2ca8SHeikki Krogerus 		/* Read complete, clear RX status alert bit */
694ae8a2ca8SHeikki Krogerus 		tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
695ae8a2ca8SHeikki Krogerus 
696ae8a2ca8SHeikki Krogerus 		tcpm_pd_receive(tcpci->port, &msg);
697ae8a2ca8SHeikki Krogerus 	}
698ae8a2ca8SHeikki Krogerus 
699*05300871SXu Yang 	if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) {
700766c485bSBadhri Jagan Sridharan 		ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, &raw);
701766c485bSBadhri Jagan Sridharan 		if (!ret && (raw & TCPC_EXTENDED_STATUS_VSAFE0V))
702766c485bSBadhri Jagan Sridharan 			tcpm_vbus_change(tcpci->port);
703766c485bSBadhri Jagan Sridharan 	}
704766c485bSBadhri Jagan Sridharan 
705ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_RX_HARD_RST)
706ae8a2ca8SHeikki Krogerus 		tcpm_pd_hard_reset(tcpci->port);
707ae8a2ca8SHeikki Krogerus 
708ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_TX_SUCCESS)
709ae8a2ca8SHeikki Krogerus 		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_SUCCESS);
710ae8a2ca8SHeikki Krogerus 	else if (status & TCPC_ALERT_TX_DISCARDED)
711ae8a2ca8SHeikki Krogerus 		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_DISCARDED);
712ae8a2ca8SHeikki Krogerus 	else if (status & TCPC_ALERT_TX_FAILED)
713ae8a2ca8SHeikki Krogerus 		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED);
714ae8a2ca8SHeikki Krogerus 
715ae8a2ca8SHeikki Krogerus 	return IRQ_HANDLED;
716ae8a2ca8SHeikki Krogerus }
717ae8a2ca8SHeikki Krogerus EXPORT_SYMBOL_GPL(tcpci_irq);
718ae8a2ca8SHeikki Krogerus 
719ae8a2ca8SHeikki Krogerus static irqreturn_t _tcpci_irq(int irq, void *dev_id)
720ae8a2ca8SHeikki Krogerus {
721ae8a2ca8SHeikki Krogerus 	struct tcpci_chip *chip = dev_id;
722ae8a2ca8SHeikki Krogerus 
723ae8a2ca8SHeikki Krogerus 	return tcpci_irq(chip->tcpci);
724ae8a2ca8SHeikki Krogerus }
725ae8a2ca8SHeikki Krogerus 
726ae8a2ca8SHeikki Krogerus static const struct regmap_config tcpci_regmap_config = {
727ae8a2ca8SHeikki Krogerus 	.reg_bits = 8,
728ae8a2ca8SHeikki Krogerus 	.val_bits = 8,
729ae8a2ca8SHeikki Krogerus 
730ae8a2ca8SHeikki Krogerus 	.max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */
731ae8a2ca8SHeikki Krogerus };
732ae8a2ca8SHeikki Krogerus 
733ae8a2ca8SHeikki Krogerus static int tcpci_parse_config(struct tcpci *tcpci)
734ae8a2ca8SHeikki Krogerus {
735ae8a2ca8SHeikki Krogerus 	tcpci->controls_vbus = true; /* XXX */
736ae8a2ca8SHeikki Krogerus 
737ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.fwnode = device_get_named_child_node(tcpci->dev,
738ae8a2ca8SHeikki Krogerus 							 "connector");
739ae8a2ca8SHeikki Krogerus 	if (!tcpci->tcpc.fwnode) {
740ae8a2ca8SHeikki Krogerus 		dev_err(tcpci->dev, "Can't find connector node.\n");
741ae8a2ca8SHeikki Krogerus 		return -EINVAL;
742ae8a2ca8SHeikki Krogerus 	}
743ae8a2ca8SHeikki Krogerus 
744ae8a2ca8SHeikki Krogerus 	return 0;
745ae8a2ca8SHeikki Krogerus }
746ae8a2ca8SHeikki Krogerus 
747ae8a2ca8SHeikki Krogerus struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
748ae8a2ca8SHeikki Krogerus {
749ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci;
750ae8a2ca8SHeikki Krogerus 	int err;
751ae8a2ca8SHeikki Krogerus 
752ae8a2ca8SHeikki Krogerus 	tcpci = devm_kzalloc(dev, sizeof(*tcpci), GFP_KERNEL);
753ae8a2ca8SHeikki Krogerus 	if (!tcpci)
754ae8a2ca8SHeikki Krogerus 		return ERR_PTR(-ENOMEM);
755ae8a2ca8SHeikki Krogerus 
756ae8a2ca8SHeikki Krogerus 	tcpci->dev = dev;
757ae8a2ca8SHeikki Krogerus 	tcpci->data = data;
758ae8a2ca8SHeikki Krogerus 	tcpci->regmap = data->regmap;
759ae8a2ca8SHeikki Krogerus 
760ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.init = tcpci_init;
761ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.get_vbus = tcpci_get_vbus;
762ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_vbus = tcpci_set_vbus;
763ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_cc = tcpci_set_cc;
7647257fbc7SBadhri Jagan Sridharan 	tcpci->tcpc.apply_rc = tcpci_apply_rc;
765ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.get_cc = tcpci_get_cc;
766ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_polarity = tcpci_set_polarity;
767ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_vconn = tcpci_set_vconn;
7687893f9e1SHans de Goede 	tcpci->tcpc.start_toggling = tcpci_start_toggling;
769ae8a2ca8SHeikki Krogerus 
770ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx;
771ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_roles = tcpci_set_roles;
772ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
773c081ac42SBadhri Jagan Sridharan 	tcpci->tcpc.set_bist_data = tcpci_set_bist_data;
77411121c24SBadhri Jagan Sridharan 	tcpci->tcpc.enable_frs = tcpci_enable_frs;
775a57d253fSBadhri Jagan Sridharan 	tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus;
776372a3d0bSBadhri Jagan Sridharan 	tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable;
777ae8a2ca8SHeikki Krogerus 
778e1a97bf8SBadhri Jagan Sridharan 	if (tcpci->data->auto_discharge_disconnect) {
779e1a97bf8SBadhri Jagan Sridharan 		tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge;
780e1a97bf8SBadhri Jagan Sridharan 		tcpci->tcpc.set_auto_vbus_discharge_threshold =
781e1a97bf8SBadhri Jagan Sridharan 			tcpci_set_auto_vbus_discharge_threshold;
7823b6c3d04SBadhri Jagan Sridharan 		regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_BLEED_DISCHARGE,
7833b6c3d04SBadhri Jagan Sridharan 				   TCPC_POWER_CTRL_BLEED_DISCHARGE);
784e1a97bf8SBadhri Jagan Sridharan 	}
785e1a97bf8SBadhri Jagan Sridharan 
786766c485bSBadhri Jagan Sridharan 	if (tcpci->data->vbus_vsafe0v)
787766c485bSBadhri Jagan Sridharan 		tcpci->tcpc.is_vbus_vsafe0v = tcpci_is_vbus_vsafe0v;
788766c485bSBadhri Jagan Sridharan 
789ae8a2ca8SHeikki Krogerus 	err = tcpci_parse_config(tcpci);
790ae8a2ca8SHeikki Krogerus 	if (err < 0)
791ae8a2ca8SHeikki Krogerus 		return ERR_PTR(err);
792ae8a2ca8SHeikki Krogerus 
793ae8a2ca8SHeikki Krogerus 	tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc);
794ae8a2ca8SHeikki Krogerus 	if (IS_ERR(tcpci->port))
795ae8a2ca8SHeikki Krogerus 		return ERR_CAST(tcpci->port);
796ae8a2ca8SHeikki Krogerus 
797ae8a2ca8SHeikki Krogerus 	return tcpci;
798ae8a2ca8SHeikki Krogerus }
799ae8a2ca8SHeikki Krogerus EXPORT_SYMBOL_GPL(tcpci_register_port);
800ae8a2ca8SHeikki Krogerus 
801ae8a2ca8SHeikki Krogerus void tcpci_unregister_port(struct tcpci *tcpci)
802ae8a2ca8SHeikki Krogerus {
803ae8a2ca8SHeikki Krogerus 	tcpm_unregister_port(tcpci->port);
804ae8a2ca8SHeikki Krogerus }
805ae8a2ca8SHeikki Krogerus EXPORT_SYMBOL_GPL(tcpci_unregister_port);
806ae8a2ca8SHeikki Krogerus 
807ae8a2ca8SHeikki Krogerus static int tcpci_probe(struct i2c_client *client,
808ae8a2ca8SHeikki Krogerus 		       const struct i2c_device_id *i2c_id)
809ae8a2ca8SHeikki Krogerus {
810ae8a2ca8SHeikki Krogerus 	struct tcpci_chip *chip;
811ae8a2ca8SHeikki Krogerus 	int err;
812ae8a2ca8SHeikki Krogerus 	u16 val = 0;
813ae8a2ca8SHeikki Krogerus 
814ae8a2ca8SHeikki Krogerus 	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
815ae8a2ca8SHeikki Krogerus 	if (!chip)
816ae8a2ca8SHeikki Krogerus 		return -ENOMEM;
817ae8a2ca8SHeikki Krogerus 
818ae8a2ca8SHeikki Krogerus 	chip->data.regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config);
819ae8a2ca8SHeikki Krogerus 	if (IS_ERR(chip->data.regmap))
820ae8a2ca8SHeikki Krogerus 		return PTR_ERR(chip->data.regmap);
821ae8a2ca8SHeikki Krogerus 
822ae8a2ca8SHeikki Krogerus 	i2c_set_clientdata(client, chip);
823ae8a2ca8SHeikki Krogerus 
824ae8a2ca8SHeikki Krogerus 	/* Disable chip interrupts before requesting irq */
825ae8a2ca8SHeikki Krogerus 	err = regmap_raw_write(chip->data.regmap, TCPC_ALERT_MASK, &val,
826ae8a2ca8SHeikki Krogerus 			       sizeof(u16));
827ae8a2ca8SHeikki Krogerus 	if (err < 0)
828ae8a2ca8SHeikki Krogerus 		return err;
829ae8a2ca8SHeikki Krogerus 
830ae8a2ca8SHeikki Krogerus 	chip->tcpci = tcpci_register_port(&client->dev, &chip->data);
831ae8a2ca8SHeikki Krogerus 	if (IS_ERR(chip->tcpci))
832ae8a2ca8SHeikki Krogerus 		return PTR_ERR(chip->tcpci);
833ae8a2ca8SHeikki Krogerus 
834ae8a2ca8SHeikki Krogerus 	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
835ae8a2ca8SHeikki Krogerus 					_tcpci_irq,
836ae8a2ca8SHeikki Krogerus 					IRQF_ONESHOT | IRQF_TRIGGER_LOW,
837ae8a2ca8SHeikki Krogerus 					dev_name(&client->dev), chip);
838ae8a2ca8SHeikki Krogerus 	if (err < 0) {
839ae8a2ca8SHeikki Krogerus 		tcpci_unregister_port(chip->tcpci);
840ae8a2ca8SHeikki Krogerus 		return err;
841ae8a2ca8SHeikki Krogerus 	}
842ae8a2ca8SHeikki Krogerus 
843ae8a2ca8SHeikki Krogerus 	return 0;
844ae8a2ca8SHeikki Krogerus }
845ae8a2ca8SHeikki Krogerus 
846ae8a2ca8SHeikki Krogerus static int tcpci_remove(struct i2c_client *client)
847ae8a2ca8SHeikki Krogerus {
848ae8a2ca8SHeikki Krogerus 	struct tcpci_chip *chip = i2c_get_clientdata(client);
8493ba76256SJun Li 	int err;
8503ba76256SJun Li 
8513ba76256SJun Li 	/* Disable chip interrupts before unregistering port */
8523ba76256SJun Li 	err = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, 0);
8533ba76256SJun Li 	if (err < 0)
8543ba76256SJun Li 		return err;
855ae8a2ca8SHeikki Krogerus 
856ae8a2ca8SHeikki Krogerus 	tcpci_unregister_port(chip->tcpci);
857ae8a2ca8SHeikki Krogerus 
858ae8a2ca8SHeikki Krogerus 	return 0;
859ae8a2ca8SHeikki Krogerus }
860ae8a2ca8SHeikki Krogerus 
861ae8a2ca8SHeikki Krogerus static const struct i2c_device_id tcpci_id[] = {
862ae8a2ca8SHeikki Krogerus 	{ "tcpci", 0 },
863ae8a2ca8SHeikki Krogerus 	{ }
864ae8a2ca8SHeikki Krogerus };
865ae8a2ca8SHeikki Krogerus MODULE_DEVICE_TABLE(i2c, tcpci_id);
866ae8a2ca8SHeikki Krogerus 
867ae8a2ca8SHeikki Krogerus #ifdef CONFIG_OF
868ae8a2ca8SHeikki Krogerus static const struct of_device_id tcpci_of_match[] = {
869ae8a2ca8SHeikki Krogerus 	{ .compatible = "nxp,ptn5110", },
870ae8a2ca8SHeikki Krogerus 	{},
871ae8a2ca8SHeikki Krogerus };
872ae8a2ca8SHeikki Krogerus MODULE_DEVICE_TABLE(of, tcpci_of_match);
873ae8a2ca8SHeikki Krogerus #endif
874ae8a2ca8SHeikki Krogerus 
875ae8a2ca8SHeikki Krogerus static struct i2c_driver tcpci_i2c_driver = {
876ae8a2ca8SHeikki Krogerus 	.driver = {
877ae8a2ca8SHeikki Krogerus 		.name = "tcpci",
878ae8a2ca8SHeikki Krogerus 		.of_match_table = of_match_ptr(tcpci_of_match),
879ae8a2ca8SHeikki Krogerus 	},
880ae8a2ca8SHeikki Krogerus 	.probe = tcpci_probe,
881ae8a2ca8SHeikki Krogerus 	.remove = tcpci_remove,
882ae8a2ca8SHeikki Krogerus 	.id_table = tcpci_id,
883ae8a2ca8SHeikki Krogerus };
884ae8a2ca8SHeikki Krogerus module_i2c_driver(tcpci_i2c_driver);
885ae8a2ca8SHeikki Krogerus 
886ae8a2ca8SHeikki Krogerus MODULE_DESCRIPTION("USB Type-C Port Controller Interface driver");
887ae8a2ca8SHeikki Krogerus MODULE_LICENSE("GPL");
888