xref: /openbmc/linux/drivers/usb/typec/tcpm/tcpci.c (revision 55b724b8)
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>
167963d4d7SXin Ji #include <linux/usb/tcpci.h>
17ae8a2ca8SHeikki Krogerus #include <linux/usb/tcpm.h>
18ae8a2ca8SHeikki Krogerus #include <linux/usb/typec.h>
19ae8a2ca8SHeikki Krogerus 
20e4a93780SBadhri Jagan Sridharan #define	PD_RETRY_COUNT_DEFAULT			3
21e4a93780SBadhri Jagan Sridharan #define	PD_RETRY_COUNT_3_0_OR_HIGHER		2
22e1a97bf8SBadhri Jagan Sridharan #define	AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV	3500
234288debeSBadhri Jagan Sridharan #define	VSINKPD_MIN_IR_DROP_MV			750
244288debeSBadhri Jagan Sridharan #define	VSRC_NEW_MIN_PERCENT			95
254288debeSBadhri Jagan Sridharan #define	VSRC_VALID_MIN_MV			500
264288debeSBadhri Jagan Sridharan #define	VPPS_NEW_MIN_PERCENT			95
274288debeSBadhri Jagan Sridharan #define	VPPS_VALID_MIN_MV			100
284288debeSBadhri Jagan Sridharan #define	VSINKDISCONNECT_PD_MIN_PERCENT		90
29ae8a2ca8SHeikki Krogerus 
30ae8a2ca8SHeikki Krogerus struct tcpci {
31ae8a2ca8SHeikki Krogerus 	struct device *dev;
32ae8a2ca8SHeikki Krogerus 
33ae8a2ca8SHeikki Krogerus 	struct tcpm_port *port;
34ae8a2ca8SHeikki Krogerus 
35ae8a2ca8SHeikki Krogerus 	struct regmap *regmap;
36ccb0beb4SXu Yang 	unsigned int alert_mask;
37ae8a2ca8SHeikki Krogerus 
38ae8a2ca8SHeikki Krogerus 	bool controls_vbus;
39ae8a2ca8SHeikki Krogerus 
40ae8a2ca8SHeikki Krogerus 	struct tcpc_dev tcpc;
41ae8a2ca8SHeikki Krogerus 	struct tcpci_data *data;
42ae8a2ca8SHeikki Krogerus };
43ae8a2ca8SHeikki Krogerus 
44ae8a2ca8SHeikki Krogerus struct tcpci_chip {
45ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci;
46ae8a2ca8SHeikki Krogerus 	struct tcpci_data data;
47ae8a2ca8SHeikki Krogerus };
48ae8a2ca8SHeikki Krogerus 
tcpci_get_tcpm_port(struct tcpci * tcpci)4958ea326bSBadhri Jagan Sridharan struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci)
5058ea326bSBadhri Jagan Sridharan {
5158ea326bSBadhri Jagan Sridharan 	return tcpci->port;
5258ea326bSBadhri Jagan Sridharan }
5358ea326bSBadhri Jagan Sridharan EXPORT_SYMBOL_GPL(tcpci_get_tcpm_port);
5458ea326bSBadhri Jagan Sridharan 
tcpc_to_tcpci(struct tcpc_dev * tcpc)55ae8a2ca8SHeikki Krogerus static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc)
56ae8a2ca8SHeikki Krogerus {
57ae8a2ca8SHeikki Krogerus 	return container_of(tcpc, struct tcpci, tcpc);
58ae8a2ca8SHeikki Krogerus }
59ae8a2ca8SHeikki Krogerus 
tcpci_read16(struct tcpci * tcpci,unsigned int reg,u16 * val)60ae8a2ca8SHeikki Krogerus static int tcpci_read16(struct tcpci *tcpci, unsigned int reg, u16 *val)
61ae8a2ca8SHeikki Krogerus {
62ae8a2ca8SHeikki Krogerus 	return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16));
63ae8a2ca8SHeikki Krogerus }
64ae8a2ca8SHeikki Krogerus 
tcpci_write16(struct tcpci * tcpci,unsigned int reg,u16 val)65ae8a2ca8SHeikki Krogerus static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16 val)
66ae8a2ca8SHeikki Krogerus {
67ae8a2ca8SHeikki Krogerus 	return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16));
68ae8a2ca8SHeikki Krogerus }
69ae8a2ca8SHeikki Krogerus 
tcpci_set_cc(struct tcpc_dev * tcpc,enum typec_cc_status cc)70ae8a2ca8SHeikki Krogerus static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc)
71ae8a2ca8SHeikki Krogerus {
72ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
735638b0dfSXu Yang 	bool vconn_pres;
745638b0dfSXu Yang 	enum typec_cc_polarity polarity = TYPEC_POLARITY_CC1;
75ae8a2ca8SHeikki Krogerus 	unsigned int reg;
76ae8a2ca8SHeikki Krogerus 	int ret;
77ae8a2ca8SHeikki Krogerus 
785638b0dfSXu Yang 	ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
795638b0dfSXu Yang 	if (ret < 0)
805638b0dfSXu Yang 		return ret;
815638b0dfSXu Yang 
825638b0dfSXu Yang 	vconn_pres = !!(reg & TCPC_POWER_STATUS_VCONN_PRES);
835638b0dfSXu Yang 	if (vconn_pres) {
845638b0dfSXu Yang 		ret = regmap_read(tcpci->regmap, TCPC_TCPC_CTRL, &reg);
855638b0dfSXu Yang 		if (ret < 0)
865638b0dfSXu Yang 			return ret;
875638b0dfSXu Yang 
885638b0dfSXu Yang 		if (reg & TCPC_TCPC_CTRL_ORIENTATION)
895638b0dfSXu Yang 			polarity = TYPEC_POLARITY_CC2;
905638b0dfSXu Yang 	}
915638b0dfSXu Yang 
92ae8a2ca8SHeikki Krogerus 	switch (cc) {
93ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RA:
94ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT) |
95ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC2_SHIFT);
96ae8a2ca8SHeikki Krogerus 		break;
97ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RD:
98ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) |
99ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT);
100ae8a2ca8SHeikki Krogerus 		break;
101ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_DEF:
102ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
103ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
104ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_RP_VAL_DEF <<
105ae8a2ca8SHeikki Krogerus 			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
106ae8a2ca8SHeikki Krogerus 		break;
107ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_1_5:
108ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
109ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
110ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_RP_VAL_1_5 <<
111ae8a2ca8SHeikki Krogerus 			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
112ae8a2ca8SHeikki Krogerus 		break;
113ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_3_0:
114ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
115ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
116ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_RP_VAL_3_0 <<
117ae8a2ca8SHeikki Krogerus 			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
118ae8a2ca8SHeikki Krogerus 		break;
119ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_OPEN:
120ae8a2ca8SHeikki Krogerus 	default:
121ae8a2ca8SHeikki Krogerus 		reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT) |
122ae8a2ca8SHeikki Krogerus 			(TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT);
123ae8a2ca8SHeikki Krogerus 		break;
124ae8a2ca8SHeikki Krogerus 	}
125ae8a2ca8SHeikki Krogerus 
1265638b0dfSXu Yang 	if (vconn_pres) {
1275638b0dfSXu Yang 		if (polarity == TYPEC_POLARITY_CC2) {
1285638b0dfSXu Yang 			reg &= ~(TCPC_ROLE_CTRL_CC1_MASK << TCPC_ROLE_CTRL_CC1_SHIFT);
1295638b0dfSXu Yang 			reg |= (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT);
1305638b0dfSXu Yang 		} else {
1315638b0dfSXu Yang 			reg &= ~(TCPC_ROLE_CTRL_CC2_MASK << TCPC_ROLE_CTRL_CC2_SHIFT);
1325638b0dfSXu Yang 			reg |= (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT);
1335638b0dfSXu Yang 		}
1345638b0dfSXu Yang 	}
1355638b0dfSXu Yang 
136ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
137ae8a2ca8SHeikki Krogerus 	if (ret < 0)
138ae8a2ca8SHeikki Krogerus 		return ret;
139ae8a2ca8SHeikki Krogerus 
140ae8a2ca8SHeikki Krogerus 	return 0;
141ae8a2ca8SHeikki Krogerus }
142ae8a2ca8SHeikki Krogerus 
tcpci_apply_rc(struct tcpc_dev * tcpc,enum typec_cc_status cc,enum typec_cc_polarity polarity)143a0765597SWei Yongjun static int tcpci_apply_rc(struct tcpc_dev *tcpc, enum typec_cc_status cc,
144a0765597SWei Yongjun 			  enum typec_cc_polarity polarity)
1457257fbc7SBadhri Jagan Sridharan {
1467257fbc7SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
1477257fbc7SBadhri Jagan Sridharan 	unsigned int reg;
1487257fbc7SBadhri Jagan Sridharan 	int ret;
1497257fbc7SBadhri Jagan Sridharan 
1507257fbc7SBadhri Jagan Sridharan 	ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, &reg);
1517257fbc7SBadhri Jagan Sridharan 	if (ret < 0)
1527257fbc7SBadhri Jagan Sridharan 		return ret;
1537257fbc7SBadhri Jagan Sridharan 
1547257fbc7SBadhri Jagan Sridharan 	/*
1557257fbc7SBadhri Jagan Sridharan 	 * APPLY_RC state is when ROLE_CONTROL.CC1 != ROLE_CONTROL.CC2 and vbus autodischarge on
1567257fbc7SBadhri Jagan Sridharan 	 * disconnect is disabled. Bail out when ROLE_CONTROL.CC1 != ROLE_CONTROL.CC2.
1577257fbc7SBadhri Jagan Sridharan 	 */
1587257fbc7SBadhri Jagan Sridharan 	if (((reg & (TCPC_ROLE_CTRL_CC2_MASK << TCPC_ROLE_CTRL_CC2_SHIFT)) >>
1597257fbc7SBadhri Jagan Sridharan 	     TCPC_ROLE_CTRL_CC2_SHIFT) !=
1607257fbc7SBadhri Jagan Sridharan 	    ((reg & (TCPC_ROLE_CTRL_CC1_MASK << TCPC_ROLE_CTRL_CC1_SHIFT)) >>
1617257fbc7SBadhri Jagan Sridharan 	     TCPC_ROLE_CTRL_CC1_SHIFT))
1627257fbc7SBadhri Jagan Sridharan 		return 0;
1637257fbc7SBadhri Jagan Sridharan 
1647257fbc7SBadhri Jagan Sridharan 	return regmap_update_bits(tcpci->regmap, TCPC_ROLE_CTRL, polarity == TYPEC_POLARITY_CC1 ?
1657257fbc7SBadhri Jagan Sridharan 				  TCPC_ROLE_CTRL_CC2_MASK << TCPC_ROLE_CTRL_CC2_SHIFT :
1667257fbc7SBadhri Jagan Sridharan 				  TCPC_ROLE_CTRL_CC1_MASK << TCPC_ROLE_CTRL_CC1_SHIFT,
1677257fbc7SBadhri Jagan Sridharan 				  TCPC_ROLE_CTRL_CC_OPEN);
1687257fbc7SBadhri Jagan Sridharan }
1697257fbc7SBadhri Jagan Sridharan 
tcpci_start_toggling(struct tcpc_dev * tcpc,enum typec_port_type port_type,enum typec_cc_status cc)1707893f9e1SHans de Goede static int tcpci_start_toggling(struct tcpc_dev *tcpc,
1717893f9e1SHans de Goede 				enum typec_port_type port_type,
172ae8a2ca8SHeikki Krogerus 				enum typec_cc_status cc)
173ae8a2ca8SHeikki Krogerus {
174ae8a2ca8SHeikki Krogerus 	int ret;
175ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
176ae8a2ca8SHeikki Krogerus 	unsigned int reg = TCPC_ROLE_CTRL_DRP;
177ae8a2ca8SHeikki Krogerus 
1787893f9e1SHans de Goede 	if (port_type != TYPEC_PORT_DRP)
1797893f9e1SHans de Goede 		return -EOPNOTSUPP;
1807893f9e1SHans de Goede 
181ae8a2ca8SHeikki Krogerus 	/* Handle vendor drp toggling */
182ae8a2ca8SHeikki Krogerus 	if (tcpci->data->start_drp_toggling) {
183ae8a2ca8SHeikki Krogerus 		ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc);
184ae8a2ca8SHeikki Krogerus 		if (ret < 0)
185ae8a2ca8SHeikki Krogerus 			return ret;
186ae8a2ca8SHeikki Krogerus 	}
187ae8a2ca8SHeikki Krogerus 
188ae8a2ca8SHeikki Krogerus 	switch (cc) {
189ae8a2ca8SHeikki Krogerus 	default:
190ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_DEF:
191ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_RP_VAL_DEF <<
192ae8a2ca8SHeikki Krogerus 			TCPC_ROLE_CTRL_RP_VAL_SHIFT);
193ae8a2ca8SHeikki Krogerus 		break;
194ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_1_5:
195ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_RP_VAL_1_5 <<
196ae8a2ca8SHeikki Krogerus 			TCPC_ROLE_CTRL_RP_VAL_SHIFT);
197ae8a2ca8SHeikki Krogerus 		break;
198ae8a2ca8SHeikki Krogerus 	case TYPEC_CC_RP_3_0:
199ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_RP_VAL_3_0 <<
200ae8a2ca8SHeikki Krogerus 			TCPC_ROLE_CTRL_RP_VAL_SHIFT);
201ae8a2ca8SHeikki Krogerus 		break;
202ae8a2ca8SHeikki Krogerus 	}
203ae8a2ca8SHeikki Krogerus 
204ae8a2ca8SHeikki Krogerus 	if (cc == TYPEC_CC_RD)
205ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) |
206ae8a2ca8SHeikki Krogerus 			   (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT);
207ae8a2ca8SHeikki Krogerus 	else
208ae8a2ca8SHeikki Krogerus 		reg |= (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
209ae8a2ca8SHeikki Krogerus 			   (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT);
210ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
211ae8a2ca8SHeikki Krogerus 	if (ret < 0)
212ae8a2ca8SHeikki Krogerus 		return ret;
213ae8a2ca8SHeikki Krogerus 	return regmap_write(tcpci->regmap, TCPC_COMMAND,
214ae8a2ca8SHeikki Krogerus 			    TCPC_CMD_LOOK4CONNECTION);
215ae8a2ca8SHeikki Krogerus }
216ae8a2ca8SHeikki Krogerus 
tcpci_get_cc(struct tcpc_dev * tcpc,enum typec_cc_status * cc1,enum typec_cc_status * cc2)217ae8a2ca8SHeikki Krogerus static int tcpci_get_cc(struct tcpc_dev *tcpc,
218ae8a2ca8SHeikki Krogerus 			enum typec_cc_status *cc1, enum typec_cc_status *cc2)
219ae8a2ca8SHeikki Krogerus {
220ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
22119c234a1SBadhri Jagan Sridharan 	unsigned int reg, role_control;
222ae8a2ca8SHeikki Krogerus 	int ret;
223ae8a2ca8SHeikki Krogerus 
22419c234a1SBadhri Jagan Sridharan 	ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, &role_control);
22519c234a1SBadhri Jagan Sridharan 	if (ret < 0)
22619c234a1SBadhri Jagan Sridharan 		return ret;
22719c234a1SBadhri Jagan Sridharan 
228ae8a2ca8SHeikki Krogerus 	ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, &reg);
229ae8a2ca8SHeikki Krogerus 	if (ret < 0)
230ae8a2ca8SHeikki Krogerus 		return ret;
231ae8a2ca8SHeikki Krogerus 
232ae8a2ca8SHeikki Krogerus 	*cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) &
233ae8a2ca8SHeikki Krogerus 				 TCPC_CC_STATUS_CC1_MASK,
23419c234a1SBadhri Jagan Sridharan 				 reg & TCPC_CC_STATUS_TERM ||
235aecb1e45SBadhri Jagan Sridharan 				 tcpc_presenting_rd(role_control, CC1));
236ae8a2ca8SHeikki Krogerus 	*cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) &
237ae8a2ca8SHeikki Krogerus 				 TCPC_CC_STATUS_CC2_MASK,
23819c234a1SBadhri Jagan Sridharan 				 reg & TCPC_CC_STATUS_TERM ||
239aecb1e45SBadhri Jagan Sridharan 				 tcpc_presenting_rd(role_control, CC2));
240ae8a2ca8SHeikki Krogerus 
241ae8a2ca8SHeikki Krogerus 	return 0;
242ae8a2ca8SHeikki Krogerus }
243ae8a2ca8SHeikki Krogerus 
tcpci_set_polarity(struct tcpc_dev * tcpc,enum typec_cc_polarity polarity)244ae8a2ca8SHeikki Krogerus static int tcpci_set_polarity(struct tcpc_dev *tcpc,
245ae8a2ca8SHeikki Krogerus 			      enum typec_cc_polarity polarity)
246ae8a2ca8SHeikki Krogerus {
247ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
248ae8a2ca8SHeikki Krogerus 	unsigned int reg;
249ae8a2ca8SHeikki Krogerus 	int ret;
25057ce6466SBadhri Jagan Sridharan 	enum typec_cc_status cc1, cc2;
251ae8a2ca8SHeikki Krogerus 
25257ce6466SBadhri Jagan Sridharan 	/* Obtain Rp setting from role control */
253ae8a2ca8SHeikki Krogerus 	ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, &reg);
254ae8a2ca8SHeikki Krogerus 	if (ret < 0)
255ae8a2ca8SHeikki Krogerus 		return ret;
256ae8a2ca8SHeikki Krogerus 
25757ce6466SBadhri Jagan Sridharan 	ret = tcpci_get_cc(tcpc, &cc1, &cc2);
25857ce6466SBadhri Jagan Sridharan 	if (ret < 0)
25957ce6466SBadhri Jagan Sridharan 		return ret;
26057ce6466SBadhri Jagan Sridharan 
26157ce6466SBadhri Jagan Sridharan 	/*
26257ce6466SBadhri Jagan Sridharan 	 * When port has drp toggling enabled, ROLE_CONTROL would only have the initial
26357ce6466SBadhri Jagan Sridharan 	 * terminations for the toggling and does not indicate the final cc
26457ce6466SBadhri Jagan Sridharan 	 * terminations when ConnectionResult is 0 i.e. drp toggling stops and
265b53908f9SXu Yang 	 * the connection is resolved. Infer port role from TCPC_CC_STATUS based on the
26657ce6466SBadhri Jagan Sridharan 	 * terminations seen. The port role is then used to set the cc terminations.
26757ce6466SBadhri Jagan Sridharan 	 */
26857ce6466SBadhri Jagan Sridharan 	if (reg & TCPC_ROLE_CTRL_DRP) {
26957ce6466SBadhri Jagan Sridharan 		/* Disable DRP for the OPEN setting to take effect */
27057ce6466SBadhri Jagan Sridharan 		reg = reg & ~TCPC_ROLE_CTRL_DRP;
27157ce6466SBadhri Jagan Sridharan 
27257ce6466SBadhri Jagan Sridharan 		if (polarity == TYPEC_POLARITY_CC2) {
27357ce6466SBadhri Jagan Sridharan 			reg &= ~(TCPC_ROLE_CTRL_CC2_MASK << TCPC_ROLE_CTRL_CC2_SHIFT);
27457ce6466SBadhri Jagan Sridharan 			/* Local port is source */
27557ce6466SBadhri Jagan Sridharan 			if (cc2 == TYPEC_CC_RD)
27657ce6466SBadhri Jagan Sridharan 				/* Role control would have the Rp setting when DRP was enabled */
27757ce6466SBadhri Jagan Sridharan 				reg |= TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT;
27857ce6466SBadhri Jagan Sridharan 			else
27957ce6466SBadhri Jagan Sridharan 				reg |= TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT;
28057ce6466SBadhri Jagan Sridharan 		} else {
28157ce6466SBadhri Jagan Sridharan 			reg &= ~(TCPC_ROLE_CTRL_CC1_MASK << TCPC_ROLE_CTRL_CC1_SHIFT);
28257ce6466SBadhri Jagan Sridharan 			/* Local port is source */
28357ce6466SBadhri Jagan Sridharan 			if (cc1 == TYPEC_CC_RD)
28457ce6466SBadhri Jagan Sridharan 				/* Role control would have the Rp setting when DRP was enabled */
28557ce6466SBadhri Jagan Sridharan 				reg |= TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT;
28657ce6466SBadhri Jagan Sridharan 			else
28757ce6466SBadhri Jagan Sridharan 				reg |= TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT;
28857ce6466SBadhri Jagan Sridharan 		}
28957ce6466SBadhri Jagan Sridharan 	}
29057ce6466SBadhri Jagan Sridharan 
291ae8a2ca8SHeikki Krogerus 	if (polarity == TYPEC_POLARITY_CC2)
292ae8a2ca8SHeikki Krogerus 		reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT;
293ae8a2ca8SHeikki Krogerus 	else
294ae8a2ca8SHeikki Krogerus 		reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT;
295ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
296ae8a2ca8SHeikki Krogerus 	if (ret < 0)
297ae8a2ca8SHeikki Krogerus 		return ret;
298ae8a2ca8SHeikki Krogerus 
299ae8a2ca8SHeikki Krogerus 	return regmap_write(tcpci->regmap, TCPC_TCPC_CTRL,
300ae8a2ca8SHeikki Krogerus 			   (polarity == TYPEC_POLARITY_CC2) ?
301ae8a2ca8SHeikki Krogerus 			   TCPC_TCPC_CTRL_ORIENTATION : 0);
302ae8a2ca8SHeikki Krogerus }
303ae8a2ca8SHeikki Krogerus 
tcpci_set_partner_usb_comm_capable(struct tcpc_dev * tcpc,bool capable)304372a3d0bSBadhri Jagan Sridharan static void tcpci_set_partner_usb_comm_capable(struct tcpc_dev *tcpc, bool capable)
305372a3d0bSBadhri Jagan Sridharan {
306372a3d0bSBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
307372a3d0bSBadhri Jagan Sridharan 
308372a3d0bSBadhri Jagan Sridharan 	if (tcpci->data->set_partner_usb_comm_capable)
309372a3d0bSBadhri Jagan Sridharan 		tcpci->data->set_partner_usb_comm_capable(tcpci, tcpci->data, capable);
310372a3d0bSBadhri Jagan Sridharan }
311372a3d0bSBadhri Jagan Sridharan 
tcpci_set_vconn(struct tcpc_dev * tcpc,bool enable)312ae8a2ca8SHeikki Krogerus static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable)
313ae8a2ca8SHeikki Krogerus {
314ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
315ae8a2ca8SHeikki Krogerus 	int ret;
316ae8a2ca8SHeikki Krogerus 
317ae8a2ca8SHeikki Krogerus 	/* Handle vendor set vconn */
318ae8a2ca8SHeikki Krogerus 	if (tcpci->data->set_vconn) {
319ae8a2ca8SHeikki Krogerus 		ret = tcpci->data->set_vconn(tcpci, tcpci->data, enable);
320ae8a2ca8SHeikki Krogerus 		if (ret < 0)
321ae8a2ca8SHeikki Krogerus 			return ret;
322ae8a2ca8SHeikki Krogerus 	}
323ae8a2ca8SHeikki Krogerus 
324ae8a2ca8SHeikki Krogerus 	return regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL,
325ae8a2ca8SHeikki Krogerus 				TCPC_POWER_CTRL_VCONN_ENABLE,
326ae8a2ca8SHeikki Krogerus 				enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0);
327ae8a2ca8SHeikki Krogerus }
328ae8a2ca8SHeikki Krogerus 
tcpci_enable_auto_vbus_discharge(struct tcpc_dev * dev,bool enable)329e1a97bf8SBadhri Jagan Sridharan static int tcpci_enable_auto_vbus_discharge(struct tcpc_dev *dev, bool enable)
330e1a97bf8SBadhri Jagan Sridharan {
331e1a97bf8SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(dev);
332e1a97bf8SBadhri Jagan Sridharan 	int ret;
333e1a97bf8SBadhri Jagan Sridharan 
334e1a97bf8SBadhri Jagan Sridharan 	ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_AUTO_DISCHARGE,
335e1a97bf8SBadhri Jagan Sridharan 				 enable ? TCPC_POWER_CTRL_AUTO_DISCHARGE : 0);
336e1a97bf8SBadhri Jagan Sridharan 	return ret;
337e1a97bf8SBadhri Jagan Sridharan }
338e1a97bf8SBadhri Jagan Sridharan 
tcpci_set_auto_vbus_discharge_threshold(struct tcpc_dev * dev,enum typec_pwr_opmode mode,bool pps_active,u32 requested_vbus_voltage_mv)339e1a97bf8SBadhri Jagan Sridharan static int tcpci_set_auto_vbus_discharge_threshold(struct tcpc_dev *dev, enum typec_pwr_opmode mode,
340e1a97bf8SBadhri Jagan Sridharan 						   bool pps_active, u32 requested_vbus_voltage_mv)
341e1a97bf8SBadhri Jagan Sridharan {
342e1a97bf8SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(dev);
343e1a97bf8SBadhri Jagan Sridharan 	unsigned int pwr_ctrl, threshold = 0;
344e1a97bf8SBadhri Jagan Sridharan 	int ret;
345e1a97bf8SBadhri Jagan Sridharan 
346e1a97bf8SBadhri Jagan Sridharan 	/*
347e1a97bf8SBadhri Jagan Sridharan 	 * Indicates that vbus is going to go away due PR_SWAP, hard reset etc.
348e1a97bf8SBadhri Jagan Sridharan 	 * Do not discharge vbus here.
349e1a97bf8SBadhri Jagan Sridharan 	 */
350e1a97bf8SBadhri Jagan Sridharan 	if (requested_vbus_voltage_mv == 0)
351e1a97bf8SBadhri Jagan Sridharan 		goto write_thresh;
352e1a97bf8SBadhri Jagan Sridharan 
353e1a97bf8SBadhri Jagan Sridharan 	ret = regmap_read(tcpci->regmap, TCPC_POWER_CTRL, &pwr_ctrl);
354e1a97bf8SBadhri Jagan Sridharan 	if (ret < 0)
355e1a97bf8SBadhri Jagan Sridharan 		return ret;
356e1a97bf8SBadhri Jagan Sridharan 
357e1a97bf8SBadhri Jagan Sridharan 	if (pwr_ctrl & TCPC_FAST_ROLE_SWAP_EN) {
358e1a97bf8SBadhri Jagan Sridharan 		/* To prevent disconnect when the source is fast role swap is capable. */
359e1a97bf8SBadhri Jagan Sridharan 		threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV;
360e1a97bf8SBadhri Jagan Sridharan 	} else if (mode == TYPEC_PWR_MODE_PD) {
361e1a97bf8SBadhri Jagan Sridharan 		if (pps_active)
3624288debeSBadhri Jagan Sridharan 			threshold = ((VPPS_NEW_MIN_PERCENT * requested_vbus_voltage_mv / 100) -
3634288debeSBadhri Jagan Sridharan 				     VSINKPD_MIN_IR_DROP_MV - VPPS_VALID_MIN_MV) *
3644288debeSBadhri Jagan Sridharan 				     VSINKDISCONNECT_PD_MIN_PERCENT / 100;
365e1a97bf8SBadhri Jagan Sridharan 		else
3664288debeSBadhri Jagan Sridharan 			threshold = ((VSRC_NEW_MIN_PERCENT * requested_vbus_voltage_mv / 100) -
3674288debeSBadhri Jagan Sridharan 				     VSINKPD_MIN_IR_DROP_MV - VSRC_VALID_MIN_MV) *
3684288debeSBadhri Jagan Sridharan 				     VSINKDISCONNECT_PD_MIN_PERCENT / 100;
369e1a97bf8SBadhri Jagan Sridharan 	} else {
370e1a97bf8SBadhri Jagan Sridharan 		/* 3.5V for non-pd sink */
371e1a97bf8SBadhri Jagan Sridharan 		threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV;
372e1a97bf8SBadhri Jagan Sridharan 	}
373e1a97bf8SBadhri Jagan Sridharan 
374e1a97bf8SBadhri Jagan Sridharan 	threshold = threshold / TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV;
375e1a97bf8SBadhri Jagan Sridharan 
376e1a97bf8SBadhri Jagan Sridharan 	if (threshold > TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX)
377e1a97bf8SBadhri Jagan Sridharan 		return -EINVAL;
378e1a97bf8SBadhri Jagan Sridharan 
379e1a97bf8SBadhri Jagan Sridharan write_thresh:
380e1a97bf8SBadhri Jagan Sridharan 	return tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, threshold);
381e1a97bf8SBadhri Jagan Sridharan }
382e1a97bf8SBadhri Jagan Sridharan 
tcpci_enable_frs(struct tcpc_dev * dev,bool enable)38311121c24SBadhri Jagan Sridharan static int tcpci_enable_frs(struct tcpc_dev *dev, bool enable)
38411121c24SBadhri Jagan Sridharan {
38511121c24SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(dev);
38611121c24SBadhri Jagan Sridharan 	int ret;
38711121c24SBadhri Jagan Sridharan 
38811121c24SBadhri Jagan Sridharan 	/* To prevent disconnect during FRS, set disconnect threshold to 3.5V */
38911121c24SBadhri Jagan Sridharan 	ret = tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, enable ? 0 : 0x8c);
39011121c24SBadhri Jagan Sridharan 	if (ret < 0)
39111121c24SBadhri Jagan Sridharan 		return ret;
39211121c24SBadhri Jagan Sridharan 
39311121c24SBadhri Jagan Sridharan 	ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_FAST_ROLE_SWAP_EN, enable ?
39411121c24SBadhri Jagan Sridharan 				 TCPC_FAST_ROLE_SWAP_EN : 0);
39511121c24SBadhri Jagan Sridharan 
39611121c24SBadhri Jagan Sridharan 	return ret;
39711121c24SBadhri Jagan Sridharan }
39811121c24SBadhri Jagan Sridharan 
tcpci_frs_sourcing_vbus(struct tcpc_dev * dev)399a57d253fSBadhri Jagan Sridharan static void tcpci_frs_sourcing_vbus(struct tcpc_dev *dev)
400a57d253fSBadhri Jagan Sridharan {
401a57d253fSBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(dev);
402a57d253fSBadhri Jagan Sridharan 
403a57d253fSBadhri Jagan Sridharan 	if (tcpci->data->frs_sourcing_vbus)
404a57d253fSBadhri Jagan Sridharan 		tcpci->data->frs_sourcing_vbus(tcpci, tcpci->data);
405a57d253fSBadhri Jagan Sridharan }
406a57d253fSBadhri Jagan Sridharan 
tcpci_check_contaminant(struct tcpc_dev * dev)407abc028a2SBadhri Jagan Sridharan static void tcpci_check_contaminant(struct tcpc_dev *dev)
408abc028a2SBadhri Jagan Sridharan {
409abc028a2SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(dev);
410abc028a2SBadhri Jagan Sridharan 
411abc028a2SBadhri Jagan Sridharan 	if (tcpci->data->check_contaminant)
412abc028a2SBadhri Jagan Sridharan 		tcpci->data->check_contaminant(tcpci, tcpci->data);
413abc028a2SBadhri Jagan Sridharan }
414abc028a2SBadhri Jagan Sridharan 
tcpci_set_bist_data(struct tcpc_dev * tcpc,bool enable)415c081ac42SBadhri Jagan Sridharan static int tcpci_set_bist_data(struct tcpc_dev *tcpc, bool enable)
416c081ac42SBadhri Jagan Sridharan {
417c081ac42SBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
418c081ac42SBadhri Jagan Sridharan 
419c081ac42SBadhri Jagan Sridharan 	return regmap_update_bits(tcpci->regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_BIST_TM,
420c081ac42SBadhri Jagan Sridharan 				 enable ? TCPC_TCPC_CTRL_BIST_TM : 0);
421c081ac42SBadhri Jagan Sridharan }
422c081ac42SBadhri Jagan Sridharan 
tcpci_set_roles(struct tcpc_dev * tcpc,bool attached,enum typec_role role,enum typec_data_role data)423ae8a2ca8SHeikki Krogerus static int tcpci_set_roles(struct tcpc_dev *tcpc, bool attached,
424ae8a2ca8SHeikki Krogerus 			   enum typec_role role, enum typec_data_role data)
425ae8a2ca8SHeikki Krogerus {
426ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
427ae8a2ca8SHeikki Krogerus 	unsigned int reg;
428ae8a2ca8SHeikki Krogerus 	int ret;
429ae8a2ca8SHeikki Krogerus 
430ae8a2ca8SHeikki Krogerus 	reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT;
431ae8a2ca8SHeikki Krogerus 	if (role == TYPEC_SOURCE)
432ae8a2ca8SHeikki Krogerus 		reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
433ae8a2ca8SHeikki Krogerus 	if (data == TYPEC_HOST)
434ae8a2ca8SHeikki Krogerus 		reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
435ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg);
436ae8a2ca8SHeikki Krogerus 	if (ret < 0)
437ae8a2ca8SHeikki Krogerus 		return ret;
438ae8a2ca8SHeikki Krogerus 
439ae8a2ca8SHeikki Krogerus 	return 0;
440ae8a2ca8SHeikki Krogerus }
441ae8a2ca8SHeikki Krogerus 
tcpci_set_pd_rx(struct tcpc_dev * tcpc,bool enable)442ae8a2ca8SHeikki Krogerus static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable)
443ae8a2ca8SHeikki Krogerus {
444ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
445ae8a2ca8SHeikki Krogerus 	unsigned int reg = 0;
446ae8a2ca8SHeikki Krogerus 	int ret;
447ae8a2ca8SHeikki Krogerus 
448ae8a2ca8SHeikki Krogerus 	if (enable)
449ae8a2ca8SHeikki Krogerus 		reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
450ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
451ae8a2ca8SHeikki Krogerus 	if (ret < 0)
452ae8a2ca8SHeikki Krogerus 		return ret;
453ae8a2ca8SHeikki Krogerus 
454ae8a2ca8SHeikki Krogerus 	return 0;
455ae8a2ca8SHeikki Krogerus }
456ae8a2ca8SHeikki Krogerus 
tcpci_get_vbus(struct tcpc_dev * tcpc)457ae8a2ca8SHeikki Krogerus static int tcpci_get_vbus(struct tcpc_dev *tcpc)
458ae8a2ca8SHeikki Krogerus {
459ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
460ae8a2ca8SHeikki Krogerus 	unsigned int reg;
461ae8a2ca8SHeikki Krogerus 	int ret;
462ae8a2ca8SHeikki Krogerus 
463ae8a2ca8SHeikki Krogerus 	ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
464ae8a2ca8SHeikki Krogerus 	if (ret < 0)
465ae8a2ca8SHeikki Krogerus 		return ret;
466ae8a2ca8SHeikki Krogerus 
467ae8a2ca8SHeikki Krogerus 	return !!(reg & TCPC_POWER_STATUS_VBUS_PRES);
468ae8a2ca8SHeikki Krogerus }
469ae8a2ca8SHeikki Krogerus 
tcpci_is_vbus_vsafe0v(struct tcpc_dev * tcpc)470766c485bSBadhri Jagan Sridharan static bool tcpci_is_vbus_vsafe0v(struct tcpc_dev *tcpc)
471766c485bSBadhri Jagan Sridharan {
472766c485bSBadhri Jagan Sridharan 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
473766c485bSBadhri Jagan Sridharan 	unsigned int reg;
474766c485bSBadhri Jagan Sridharan 	int ret;
475766c485bSBadhri Jagan Sridharan 
476766c485bSBadhri Jagan Sridharan 	ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, &reg);
477766c485bSBadhri Jagan Sridharan 	if (ret < 0)
478766c485bSBadhri Jagan Sridharan 		return false;
479766c485bSBadhri Jagan Sridharan 
480766c485bSBadhri Jagan Sridharan 	return !!(reg & TCPC_EXTENDED_STATUS_VSAFE0V);
481766c485bSBadhri Jagan Sridharan }
482766c485bSBadhri Jagan Sridharan 
tcpci_set_vbus(struct tcpc_dev * tcpc,bool source,bool sink)483ae8a2ca8SHeikki Krogerus static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
484ae8a2ca8SHeikki Krogerus {
485ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
486ae8a2ca8SHeikki Krogerus 	int ret;
487ae8a2ca8SHeikki Krogerus 
488b9358a06SBadhri Jagan Sridharan 	if (tcpci->data->set_vbus) {
489b9358a06SBadhri Jagan Sridharan 		ret = tcpci->data->set_vbus(tcpci, tcpci->data, source, sink);
490b9358a06SBadhri Jagan Sridharan 		/* Bypass when ret > 0 */
491b9358a06SBadhri Jagan Sridharan 		if (ret != 0)
492b9358a06SBadhri Jagan Sridharan 			return ret < 0 ? ret : 0;
493b9358a06SBadhri Jagan Sridharan 	}
494b9358a06SBadhri Jagan Sridharan 
495ae8a2ca8SHeikki Krogerus 	/* Disable both source and sink first before enabling anything */
496ae8a2ca8SHeikki Krogerus 
497ae8a2ca8SHeikki Krogerus 	if (!source) {
498ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
499ae8a2ca8SHeikki Krogerus 				   TCPC_CMD_DISABLE_SRC_VBUS);
500ae8a2ca8SHeikki Krogerus 		if (ret < 0)
501ae8a2ca8SHeikki Krogerus 			return ret;
502ae8a2ca8SHeikki Krogerus 	}
503ae8a2ca8SHeikki Krogerus 
504ae8a2ca8SHeikki Krogerus 	if (!sink) {
505ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
506ae8a2ca8SHeikki Krogerus 				   TCPC_CMD_DISABLE_SINK_VBUS);
507ae8a2ca8SHeikki Krogerus 		if (ret < 0)
508ae8a2ca8SHeikki Krogerus 			return ret;
509ae8a2ca8SHeikki Krogerus 	}
510ae8a2ca8SHeikki Krogerus 
511ae8a2ca8SHeikki Krogerus 	if (source) {
512ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
513ae8a2ca8SHeikki Krogerus 				   TCPC_CMD_SRC_VBUS_DEFAULT);
514ae8a2ca8SHeikki Krogerus 		if (ret < 0)
515ae8a2ca8SHeikki Krogerus 			return ret;
516ae8a2ca8SHeikki Krogerus 	}
517ae8a2ca8SHeikki Krogerus 
518ae8a2ca8SHeikki Krogerus 	if (sink) {
519ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
520ae8a2ca8SHeikki Krogerus 				   TCPC_CMD_SINK_VBUS);
521ae8a2ca8SHeikki Krogerus 		if (ret < 0)
522ae8a2ca8SHeikki Krogerus 			return ret;
523ae8a2ca8SHeikki Krogerus 	}
524ae8a2ca8SHeikki Krogerus 
525ae8a2ca8SHeikki Krogerus 	return 0;
526ae8a2ca8SHeikki Krogerus }
527ae8a2ca8SHeikki Krogerus 
tcpci_pd_transmit(struct tcpc_dev * tcpc,enum tcpm_transmit_type type,const struct pd_message * msg,unsigned int negotiated_rev)528e4a93780SBadhri Jagan Sridharan static int tcpci_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type,
529e4a93780SBadhri Jagan Sridharan 			     const struct pd_message *msg, unsigned int negotiated_rev)
530ae8a2ca8SHeikki Krogerus {
531ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
532ae8a2ca8SHeikki Krogerus 	u16 header = msg ? le16_to_cpu(msg->header) : 0;
533ae8a2ca8SHeikki Krogerus 	unsigned int reg, cnt;
534ae8a2ca8SHeikki Krogerus 	int ret;
535ae8a2ca8SHeikki Krogerus 
536ae8a2ca8SHeikki Krogerus 	cnt = msg ? pd_header_cnt(header) * 4 : 0;
53719b65476SBadhri Jagan Sridharan 	/**
53819b65476SBadhri Jagan Sridharan 	 * TCPCI spec forbids direct access of TCPC_TX_DATA.
53919b65476SBadhri Jagan Sridharan 	 * But, since some of the chipsets offer this capability,
54019b65476SBadhri Jagan Sridharan 	 * it's fair to support both.
54119b65476SBadhri Jagan Sridharan 	 */
54219b65476SBadhri Jagan Sridharan 	if (tcpci->data->TX_BUF_BYTE_x_hidden) {
54319b65476SBadhri Jagan Sridharan 		u8 buf[TCPC_TRANSMIT_BUFFER_MAX_LEN] = {0,};
54419b65476SBadhri Jagan Sridharan 		u8 pos = 0;
54519b65476SBadhri Jagan Sridharan 
54619b65476SBadhri Jagan Sridharan 		/* Payload + header + TCPC_TX_BYTE_CNT */
54719b65476SBadhri Jagan Sridharan 		buf[pos++] = cnt + 2;
54819b65476SBadhri Jagan Sridharan 
54919b65476SBadhri Jagan Sridharan 		if (msg)
55019b65476SBadhri Jagan Sridharan 			memcpy(&buf[pos], &msg->header, sizeof(msg->header));
55119b65476SBadhri Jagan Sridharan 
55219b65476SBadhri Jagan Sridharan 		pos += sizeof(header);
55319b65476SBadhri Jagan Sridharan 
55419b65476SBadhri Jagan Sridharan 		if (cnt > 0)
55519b65476SBadhri Jagan Sridharan 			memcpy(&buf[pos], msg->payload, cnt);
55619b65476SBadhri Jagan Sridharan 
55719b65476SBadhri Jagan Sridharan 		pos += cnt;
55819b65476SBadhri Jagan Sridharan 		ret = regmap_raw_write(tcpci->regmap, TCPC_TX_BYTE_CNT, buf, pos);
55919b65476SBadhri Jagan Sridharan 		if (ret < 0)
56019b65476SBadhri Jagan Sridharan 			return ret;
56119b65476SBadhri Jagan Sridharan 	} else {
562ae8a2ca8SHeikki Krogerus 		ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2);
563ae8a2ca8SHeikki Krogerus 		if (ret < 0)
564ae8a2ca8SHeikki Krogerus 			return ret;
565ae8a2ca8SHeikki Krogerus 
566ae8a2ca8SHeikki Krogerus 		ret = tcpci_write16(tcpci, TCPC_TX_HDR, header);
567ae8a2ca8SHeikki Krogerus 		if (ret < 0)
568ae8a2ca8SHeikki Krogerus 			return ret;
569ae8a2ca8SHeikki Krogerus 
570ae8a2ca8SHeikki Krogerus 		if (cnt > 0) {
57119b65476SBadhri Jagan Sridharan 			ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, &msg->payload, cnt);
572ae8a2ca8SHeikki Krogerus 			if (ret < 0)
573ae8a2ca8SHeikki Krogerus 				return ret;
574ae8a2ca8SHeikki Krogerus 		}
57519b65476SBadhri Jagan Sridharan 	}
576ae8a2ca8SHeikki Krogerus 
577e4a93780SBadhri Jagan Sridharan 	/* nRetryCount is 3 in PD2.0 spec where 2 in PD3.0 spec */
578e4a93780SBadhri Jagan Sridharan 	reg = ((negotiated_rev > PD_REV20 ? PD_RETRY_COUNT_3_0_OR_HIGHER : PD_RETRY_COUNT_DEFAULT)
579e4a93780SBadhri Jagan Sridharan 	       << TCPC_TRANSMIT_RETRY_SHIFT) | (type << TCPC_TRANSMIT_TYPE_SHIFT);
580ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg);
581ae8a2ca8SHeikki Krogerus 	if (ret < 0)
582ae8a2ca8SHeikki Krogerus 		return ret;
583ae8a2ca8SHeikki Krogerus 
584ae8a2ca8SHeikki Krogerus 	return 0;
585ae8a2ca8SHeikki Krogerus }
586ae8a2ca8SHeikki Krogerus 
tcpci_init(struct tcpc_dev * tcpc)587ae8a2ca8SHeikki Krogerus static int tcpci_init(struct tcpc_dev *tcpc)
588ae8a2ca8SHeikki Krogerus {
589ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
590ae8a2ca8SHeikki Krogerus 	unsigned long timeout = jiffies + msecs_to_jiffies(2000); /* XXX */
591ae8a2ca8SHeikki Krogerus 	unsigned int reg;
592ae8a2ca8SHeikki Krogerus 	int ret;
593ae8a2ca8SHeikki Krogerus 
594ae8a2ca8SHeikki Krogerus 	while (time_before_eq(jiffies, timeout)) {
595ae8a2ca8SHeikki Krogerus 		ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
596ae8a2ca8SHeikki Krogerus 		if (ret < 0)
597ae8a2ca8SHeikki Krogerus 			return ret;
598ae8a2ca8SHeikki Krogerus 		if (!(reg & TCPC_POWER_STATUS_UNINIT))
599ae8a2ca8SHeikki Krogerus 			break;
600ae8a2ca8SHeikki Krogerus 		usleep_range(10000, 20000);
601ae8a2ca8SHeikki Krogerus 	}
602ae8a2ca8SHeikki Krogerus 	if (time_after(jiffies, timeout))
603ae8a2ca8SHeikki Krogerus 		return -ETIMEDOUT;
604ae8a2ca8SHeikki Krogerus 
60523e60c8dSMarco Felsch 	ret = tcpci_write16(tcpci, TCPC_FAULT_STATUS, TCPC_FAULT_STATUS_ALL_REG_RST_TO_DEFAULT);
60623e60c8dSMarco Felsch 	if (ret < 0)
60723e60c8dSMarco Felsch 		return ret;
60823e60c8dSMarco Felsch 
609ae8a2ca8SHeikki Krogerus 	/* Handle vendor init */
610ae8a2ca8SHeikki Krogerus 	if (tcpci->data->init) {
611ae8a2ca8SHeikki Krogerus 		ret = tcpci->data->init(tcpci, tcpci->data);
612ae8a2ca8SHeikki Krogerus 		if (ret < 0)
613ae8a2ca8SHeikki Krogerus 			return ret;
614ae8a2ca8SHeikki Krogerus 	}
615ae8a2ca8SHeikki Krogerus 
616ae8a2ca8SHeikki Krogerus 	/* Clear all events */
617ae8a2ca8SHeikki Krogerus 	ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff);
618ae8a2ca8SHeikki Krogerus 	if (ret < 0)
619ae8a2ca8SHeikki Krogerus 		return ret;
620ae8a2ca8SHeikki Krogerus 
621ae8a2ca8SHeikki Krogerus 	if (tcpci->controls_vbus)
622ae8a2ca8SHeikki Krogerus 		reg = TCPC_POWER_STATUS_VBUS_PRES;
623ae8a2ca8SHeikki Krogerus 	else
624ae8a2ca8SHeikki Krogerus 		reg = 0;
625ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_POWER_STATUS_MASK, reg);
626ae8a2ca8SHeikki Krogerus 	if (ret < 0)
627ae8a2ca8SHeikki Krogerus 		return ret;
628ae8a2ca8SHeikki Krogerus 
629ae8a2ca8SHeikki Krogerus 	/* Enable Vbus detection */
630ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
631ae8a2ca8SHeikki Krogerus 			   TCPC_CMD_ENABLE_VBUS_DETECT);
632ae8a2ca8SHeikki Krogerus 	if (ret < 0)
633ae8a2ca8SHeikki Krogerus 		return ret;
634ae8a2ca8SHeikki Krogerus 
635ae8a2ca8SHeikki Krogerus 	reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
636ae8a2ca8SHeikki Krogerus 		TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
637ae8a2ca8SHeikki Krogerus 		TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
638ae8a2ca8SHeikki Krogerus 	if (tcpci->controls_vbus)
639ae8a2ca8SHeikki Krogerus 		reg |= TCPC_ALERT_POWER_STATUS;
640766c485bSBadhri Jagan Sridharan 	/* Enable VSAFE0V status interrupt when detecting VSAFE0V is supported */
641766c485bSBadhri Jagan Sridharan 	if (tcpci->data->vbus_vsafe0v) {
642766c485bSBadhri Jagan Sridharan 		reg |= TCPC_ALERT_EXTENDED_STATUS;
643766c485bSBadhri Jagan Sridharan 		ret = regmap_write(tcpci->regmap, TCPC_EXTENDED_STATUS_MASK,
644766c485bSBadhri Jagan Sridharan 				   TCPC_EXTENDED_STATUS_VSAFE0V);
645766c485bSBadhri Jagan Sridharan 		if (ret < 0)
646766c485bSBadhri Jagan Sridharan 			return ret;
647766c485bSBadhri Jagan Sridharan 	}
648ccb0beb4SXu Yang 
649ccb0beb4SXu Yang 	tcpci->alert_mask = reg;
650ccb0beb4SXu Yang 
651ae8a2ca8SHeikki Krogerus 	return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg);
652ae8a2ca8SHeikki Krogerus }
653ae8a2ca8SHeikki Krogerus 
tcpci_irq(struct tcpci * tcpci)654ae8a2ca8SHeikki Krogerus irqreturn_t tcpci_irq(struct tcpci *tcpci)
655ae8a2ca8SHeikki Krogerus {
656ae8a2ca8SHeikki Krogerus 	u16 status;
657766c485bSBadhri Jagan Sridharan 	int ret;
658766c485bSBadhri Jagan Sridharan 	unsigned int raw;
659ae8a2ca8SHeikki Krogerus 
660ae8a2ca8SHeikki Krogerus 	tcpci_read16(tcpci, TCPC_ALERT, &status);
661ae8a2ca8SHeikki Krogerus 
662ae8a2ca8SHeikki Krogerus 	/*
663ae8a2ca8SHeikki Krogerus 	 * Clear alert status for everything except RX_STATUS, which shouldn't
664ae8a2ca8SHeikki Krogerus 	 * be cleared until we have successfully retrieved message.
665ae8a2ca8SHeikki Krogerus 	 */
666ae8a2ca8SHeikki Krogerus 	if (status & ~TCPC_ALERT_RX_STATUS)
667ae8a2ca8SHeikki Krogerus 		tcpci_write16(tcpci, TCPC_ALERT,
668ae8a2ca8SHeikki Krogerus 			      status & ~TCPC_ALERT_RX_STATUS);
669ae8a2ca8SHeikki Krogerus 
670ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_CC_STATUS)
671ae8a2ca8SHeikki Krogerus 		tcpm_cc_change(tcpci->port);
672ae8a2ca8SHeikki Krogerus 
673ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_POWER_STATUS) {
674766c485bSBadhri Jagan Sridharan 		regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &raw);
675ae8a2ca8SHeikki Krogerus 		/*
676ae8a2ca8SHeikki Krogerus 		 * If power status mask has been reset, then the TCPC
677ae8a2ca8SHeikki Krogerus 		 * has reset.
678ae8a2ca8SHeikki Krogerus 		 */
679766c485bSBadhri Jagan Sridharan 		if (raw == 0xff)
680ae8a2ca8SHeikki Krogerus 			tcpm_tcpc_reset(tcpci->port);
681ae8a2ca8SHeikki Krogerus 		else
682ae8a2ca8SHeikki Krogerus 			tcpm_vbus_change(tcpci->port);
683ae8a2ca8SHeikki Krogerus 	}
684ae8a2ca8SHeikki Krogerus 
685ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_RX_STATUS) {
686ae8a2ca8SHeikki Krogerus 		struct pd_message msg;
687c215e48eSDouglas Gilbert 		unsigned int cnt, payload_cnt;
688ae8a2ca8SHeikki Krogerus 		u16 header;
689ae8a2ca8SHeikki Krogerus 
690ae8a2ca8SHeikki Krogerus 		regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt);
691c215e48eSDouglas Gilbert 		/*
692c215e48eSDouglas Gilbert 		 * 'cnt' corresponds to READABLE_BYTE_COUNT in section 4.4.14
693c215e48eSDouglas Gilbert 		 * of the TCPCI spec [Rev 2.0 Ver 1.0 October 2017] and is
694c215e48eSDouglas Gilbert 		 * defined in table 4-36 as one greater than the number of
695c215e48eSDouglas Gilbert 		 * bytes received. And that number includes the header. So:
696c215e48eSDouglas Gilbert 		 */
697c215e48eSDouglas Gilbert 		if (cnt > 3)
698c215e48eSDouglas Gilbert 			payload_cnt = cnt - (1 + sizeof(msg.header));
699c215e48eSDouglas Gilbert 		else
700c215e48eSDouglas Gilbert 			payload_cnt = 0;
701ae8a2ca8SHeikki Krogerus 
702ae8a2ca8SHeikki Krogerus 		tcpci_read16(tcpci, TCPC_RX_HDR, &header);
703ae8a2ca8SHeikki Krogerus 		msg.header = cpu_to_le16(header);
704ae8a2ca8SHeikki Krogerus 
705c215e48eSDouglas Gilbert 		if (WARN_ON(payload_cnt > sizeof(msg.payload)))
706c215e48eSDouglas Gilbert 			payload_cnt = sizeof(msg.payload);
707ae8a2ca8SHeikki Krogerus 
708c215e48eSDouglas Gilbert 		if (payload_cnt > 0)
709ae8a2ca8SHeikki Krogerus 			regmap_raw_read(tcpci->regmap, TCPC_RX_DATA,
710c215e48eSDouglas Gilbert 					&msg.payload, payload_cnt);
711ae8a2ca8SHeikki Krogerus 
712ae8a2ca8SHeikki Krogerus 		/* Read complete, clear RX status alert bit */
713ae8a2ca8SHeikki Krogerus 		tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
714ae8a2ca8SHeikki Krogerus 
715ae8a2ca8SHeikki Krogerus 		tcpm_pd_receive(tcpci->port, &msg);
716ae8a2ca8SHeikki Krogerus 	}
717ae8a2ca8SHeikki Krogerus 
71805300871SXu Yang 	if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) {
719766c485bSBadhri Jagan Sridharan 		ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, &raw);
720766c485bSBadhri Jagan Sridharan 		if (!ret && (raw & TCPC_EXTENDED_STATUS_VSAFE0V))
721766c485bSBadhri Jagan Sridharan 			tcpm_vbus_change(tcpci->port);
722766c485bSBadhri Jagan Sridharan 	}
723766c485bSBadhri Jagan Sridharan 
724ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_RX_HARD_RST)
725ae8a2ca8SHeikki Krogerus 		tcpm_pd_hard_reset(tcpci->port);
726ae8a2ca8SHeikki Krogerus 
727ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_TX_SUCCESS)
728ae8a2ca8SHeikki Krogerus 		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_SUCCESS);
729ae8a2ca8SHeikki Krogerus 	else if (status & TCPC_ALERT_TX_DISCARDED)
730ae8a2ca8SHeikki Krogerus 		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_DISCARDED);
731ae8a2ca8SHeikki Krogerus 	else if (status & TCPC_ALERT_TX_FAILED)
732ae8a2ca8SHeikki Krogerus 		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED);
733ae8a2ca8SHeikki Krogerus 
734ccb0beb4SXu Yang 	return IRQ_RETVAL(status & tcpci->alert_mask);
735ae8a2ca8SHeikki Krogerus }
736ae8a2ca8SHeikki Krogerus EXPORT_SYMBOL_GPL(tcpci_irq);
737ae8a2ca8SHeikki Krogerus 
_tcpci_irq(int irq,void * dev_id)738ae8a2ca8SHeikki Krogerus static irqreturn_t _tcpci_irq(int irq, void *dev_id)
739ae8a2ca8SHeikki Krogerus {
740ae8a2ca8SHeikki Krogerus 	struct tcpci_chip *chip = dev_id;
741ae8a2ca8SHeikki Krogerus 
742ae8a2ca8SHeikki Krogerus 	return tcpci_irq(chip->tcpci);
743ae8a2ca8SHeikki Krogerus }
744ae8a2ca8SHeikki Krogerus 
745ae8a2ca8SHeikki Krogerus static const struct regmap_config tcpci_regmap_config = {
746ae8a2ca8SHeikki Krogerus 	.reg_bits = 8,
747ae8a2ca8SHeikki Krogerus 	.val_bits = 8,
748ae8a2ca8SHeikki Krogerus 
749ae8a2ca8SHeikki Krogerus 	.max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */
750ae8a2ca8SHeikki Krogerus };
751ae8a2ca8SHeikki Krogerus 
tcpci_parse_config(struct tcpci * tcpci)752ae8a2ca8SHeikki Krogerus static int tcpci_parse_config(struct tcpci *tcpci)
753ae8a2ca8SHeikki Krogerus {
754ae8a2ca8SHeikki Krogerus 	tcpci->controls_vbus = true; /* XXX */
755ae8a2ca8SHeikki Krogerus 
756ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.fwnode = device_get_named_child_node(tcpci->dev,
757ae8a2ca8SHeikki Krogerus 							 "connector");
758ae8a2ca8SHeikki Krogerus 	if (!tcpci->tcpc.fwnode) {
759ae8a2ca8SHeikki Krogerus 		dev_err(tcpci->dev, "Can't find connector node.\n");
760ae8a2ca8SHeikki Krogerus 		return -EINVAL;
761ae8a2ca8SHeikki Krogerus 	}
762ae8a2ca8SHeikki Krogerus 
763ae8a2ca8SHeikki Krogerus 	return 0;
764ae8a2ca8SHeikki Krogerus }
765ae8a2ca8SHeikki Krogerus 
tcpci_register_port(struct device * dev,struct tcpci_data * data)766ae8a2ca8SHeikki Krogerus struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
767ae8a2ca8SHeikki Krogerus {
768ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci;
769ae8a2ca8SHeikki Krogerus 	int err;
770ae8a2ca8SHeikki Krogerus 
771ae8a2ca8SHeikki Krogerus 	tcpci = devm_kzalloc(dev, sizeof(*tcpci), GFP_KERNEL);
772ae8a2ca8SHeikki Krogerus 	if (!tcpci)
773ae8a2ca8SHeikki Krogerus 		return ERR_PTR(-ENOMEM);
774ae8a2ca8SHeikki Krogerus 
775ae8a2ca8SHeikki Krogerus 	tcpci->dev = dev;
776ae8a2ca8SHeikki Krogerus 	tcpci->data = data;
777ae8a2ca8SHeikki Krogerus 	tcpci->regmap = data->regmap;
778ae8a2ca8SHeikki Krogerus 
779ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.init = tcpci_init;
780ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.get_vbus = tcpci_get_vbus;
781ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_vbus = tcpci_set_vbus;
782ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_cc = tcpci_set_cc;
7837257fbc7SBadhri Jagan Sridharan 	tcpci->tcpc.apply_rc = tcpci_apply_rc;
784ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.get_cc = tcpci_get_cc;
785ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_polarity = tcpci_set_polarity;
786ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_vconn = tcpci_set_vconn;
7877893f9e1SHans de Goede 	tcpci->tcpc.start_toggling = tcpci_start_toggling;
788ae8a2ca8SHeikki Krogerus 
789ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx;
790ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_roles = tcpci_set_roles;
791ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
792c081ac42SBadhri Jagan Sridharan 	tcpci->tcpc.set_bist_data = tcpci_set_bist_data;
79311121c24SBadhri Jagan Sridharan 	tcpci->tcpc.enable_frs = tcpci_enable_frs;
794a57d253fSBadhri Jagan Sridharan 	tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus;
795372a3d0bSBadhri Jagan Sridharan 	tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable;
796ae8a2ca8SHeikki Krogerus 
797abc028a2SBadhri Jagan Sridharan 	if (tcpci->data->check_contaminant)
798abc028a2SBadhri Jagan Sridharan 		tcpci->tcpc.check_contaminant = tcpci_check_contaminant;
799abc028a2SBadhri Jagan Sridharan 
800e1a97bf8SBadhri Jagan Sridharan 	if (tcpci->data->auto_discharge_disconnect) {
801e1a97bf8SBadhri Jagan Sridharan 		tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge;
802e1a97bf8SBadhri Jagan Sridharan 		tcpci->tcpc.set_auto_vbus_discharge_threshold =
803e1a97bf8SBadhri Jagan Sridharan 			tcpci_set_auto_vbus_discharge_threshold;
8043b6c3d04SBadhri Jagan Sridharan 		regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_BLEED_DISCHARGE,
8053b6c3d04SBadhri Jagan Sridharan 				   TCPC_POWER_CTRL_BLEED_DISCHARGE);
806e1a97bf8SBadhri Jagan Sridharan 	}
807e1a97bf8SBadhri Jagan Sridharan 
808766c485bSBadhri Jagan Sridharan 	if (tcpci->data->vbus_vsafe0v)
809766c485bSBadhri Jagan Sridharan 		tcpci->tcpc.is_vbus_vsafe0v = tcpci_is_vbus_vsafe0v;
810766c485bSBadhri Jagan Sridharan 
811ae8a2ca8SHeikki Krogerus 	err = tcpci_parse_config(tcpci);
812ae8a2ca8SHeikki Krogerus 	if (err < 0)
813ae8a2ca8SHeikki Krogerus 		return ERR_PTR(err);
814ae8a2ca8SHeikki Krogerus 
815ae8a2ca8SHeikki Krogerus 	tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc);
8160384e87eSYang Yingliang 	if (IS_ERR(tcpci->port)) {
8170384e87eSYang Yingliang 		fwnode_handle_put(tcpci->tcpc.fwnode);
818ae8a2ca8SHeikki Krogerus 		return ERR_CAST(tcpci->port);
8190384e87eSYang Yingliang 	}
820ae8a2ca8SHeikki Krogerus 
821ae8a2ca8SHeikki Krogerus 	return tcpci;
822ae8a2ca8SHeikki Krogerus }
823ae8a2ca8SHeikki Krogerus EXPORT_SYMBOL_GPL(tcpci_register_port);
824ae8a2ca8SHeikki Krogerus 
tcpci_unregister_port(struct tcpci * tcpci)825ae8a2ca8SHeikki Krogerus void tcpci_unregister_port(struct tcpci *tcpci)
826ae8a2ca8SHeikki Krogerus {
827ae8a2ca8SHeikki Krogerus 	tcpm_unregister_port(tcpci->port);
8280384e87eSYang Yingliang 	fwnode_handle_put(tcpci->tcpc.fwnode);
829ae8a2ca8SHeikki Krogerus }
830ae8a2ca8SHeikki Krogerus EXPORT_SYMBOL_GPL(tcpci_unregister_port);
831ae8a2ca8SHeikki Krogerus 
tcpci_probe(struct i2c_client * client)832bdd0400dSUwe Kleine-König static int tcpci_probe(struct i2c_client *client)
833ae8a2ca8SHeikki Krogerus {
834ae8a2ca8SHeikki Krogerus 	struct tcpci_chip *chip;
835ae8a2ca8SHeikki Krogerus 	int err;
836ae8a2ca8SHeikki Krogerus 	u16 val = 0;
837ae8a2ca8SHeikki Krogerus 
838ae8a2ca8SHeikki Krogerus 	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
839ae8a2ca8SHeikki Krogerus 	if (!chip)
840ae8a2ca8SHeikki Krogerus 		return -ENOMEM;
841ae8a2ca8SHeikki Krogerus 
842ae8a2ca8SHeikki Krogerus 	chip->data.regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config);
843ae8a2ca8SHeikki Krogerus 	if (IS_ERR(chip->data.regmap))
844ae8a2ca8SHeikki Krogerus 		return PTR_ERR(chip->data.regmap);
845ae8a2ca8SHeikki Krogerus 
846ae8a2ca8SHeikki Krogerus 	i2c_set_clientdata(client, chip);
847ae8a2ca8SHeikki Krogerus 
848ae8a2ca8SHeikki Krogerus 	/* Disable chip interrupts before requesting irq */
849ae8a2ca8SHeikki Krogerus 	err = regmap_raw_write(chip->data.regmap, TCPC_ALERT_MASK, &val,
850ae8a2ca8SHeikki Krogerus 			       sizeof(u16));
851ae8a2ca8SHeikki Krogerus 	if (err < 0)
852ae8a2ca8SHeikki Krogerus 		return err;
853ae8a2ca8SHeikki Krogerus 
854ae8a2ca8SHeikki Krogerus 	chip->tcpci = tcpci_register_port(&client->dev, &chip->data);
855ae8a2ca8SHeikki Krogerus 	if (IS_ERR(chip->tcpci))
856ae8a2ca8SHeikki Krogerus 		return PTR_ERR(chip->tcpci);
857ae8a2ca8SHeikki Krogerus 
858ae8a2ca8SHeikki Krogerus 	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
859ae8a2ca8SHeikki Krogerus 					_tcpci_irq,
860ccb0beb4SXu Yang 					IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
861ae8a2ca8SHeikki Krogerus 					dev_name(&client->dev), chip);
862ae8a2ca8SHeikki Krogerus 	if (err < 0) {
863ae8a2ca8SHeikki Krogerus 		tcpci_unregister_port(chip->tcpci);
864ae8a2ca8SHeikki Krogerus 		return err;
865ae8a2ca8SHeikki Krogerus 	}
866ae8a2ca8SHeikki Krogerus 
867ae8a2ca8SHeikki Krogerus 	return 0;
868ae8a2ca8SHeikki Krogerus }
869ae8a2ca8SHeikki Krogerus 
tcpci_remove(struct i2c_client * client)870ed5c2f5fSUwe Kleine-König static void tcpci_remove(struct i2c_client *client)
871ae8a2ca8SHeikki Krogerus {
872ae8a2ca8SHeikki Krogerus 	struct tcpci_chip *chip = i2c_get_clientdata(client);
8733ba76256SJun Li 	int err;
8743ba76256SJun Li 
8753ba76256SJun Li 	/* Disable chip interrupts before unregistering port */
8763ba76256SJun Li 	err = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, 0);
8773ba76256SJun Li 	if (err < 0)
878bbc126aeSUwe Kleine-König 		dev_warn(&client->dev, "Failed to disable irqs (%pe)\n", ERR_PTR(err));
879ae8a2ca8SHeikki Krogerus 
880ae8a2ca8SHeikki Krogerus 	tcpci_unregister_port(chip->tcpci);
881ae8a2ca8SHeikki Krogerus }
882ae8a2ca8SHeikki Krogerus 
883ae8a2ca8SHeikki Krogerus static const struct i2c_device_id tcpci_id[] = {
884ae8a2ca8SHeikki Krogerus 	{ "tcpci", 0 },
885ae8a2ca8SHeikki Krogerus 	{ }
886ae8a2ca8SHeikki Krogerus };
887ae8a2ca8SHeikki Krogerus MODULE_DEVICE_TABLE(i2c, tcpci_id);
888ae8a2ca8SHeikki Krogerus 
889ae8a2ca8SHeikki Krogerus #ifdef CONFIG_OF
890ae8a2ca8SHeikki Krogerus static const struct of_device_id tcpci_of_match[] = {
891ae8a2ca8SHeikki Krogerus 	{ .compatible = "nxp,ptn5110", },
89255b724b8SMarco Felsch 	{ .compatible = "tcpci", },
893ae8a2ca8SHeikki Krogerus 	{},
894ae8a2ca8SHeikki Krogerus };
895ae8a2ca8SHeikki Krogerus MODULE_DEVICE_TABLE(of, tcpci_of_match);
896ae8a2ca8SHeikki Krogerus #endif
897ae8a2ca8SHeikki Krogerus 
898ae8a2ca8SHeikki Krogerus static struct i2c_driver tcpci_i2c_driver = {
899ae8a2ca8SHeikki Krogerus 	.driver = {
900ae8a2ca8SHeikki Krogerus 		.name = "tcpci",
901ae8a2ca8SHeikki Krogerus 		.of_match_table = of_match_ptr(tcpci_of_match),
902ae8a2ca8SHeikki Krogerus 	},
9037126a2aeSUwe Kleine-König 	.probe = tcpci_probe,
904ae8a2ca8SHeikki Krogerus 	.remove = tcpci_remove,
905ae8a2ca8SHeikki Krogerus 	.id_table = tcpci_id,
906ae8a2ca8SHeikki Krogerus };
907ae8a2ca8SHeikki Krogerus module_i2c_driver(tcpci_i2c_driver);
908ae8a2ca8SHeikki Krogerus 
909ae8a2ca8SHeikki Krogerus MODULE_DESCRIPTION("USB Type-C Port Controller Interface driver");
910ae8a2ca8SHeikki Krogerus MODULE_LICENSE("GPL");
911