xref: /openbmc/linux/drivers/usb/typec/tcpm/tcpci.c (revision 7126a2ae)
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
605ae8a2ca8SHeikki Krogerus 	/* Handle vendor init */
606ae8a2ca8SHeikki Krogerus 	if (tcpci->data->init) {
607ae8a2ca8SHeikki Krogerus 		ret = tcpci->data->init(tcpci, tcpci->data);
608ae8a2ca8SHeikki Krogerus 		if (ret < 0)
609ae8a2ca8SHeikki Krogerus 			return ret;
610ae8a2ca8SHeikki Krogerus 	}
611ae8a2ca8SHeikki Krogerus 
612ae8a2ca8SHeikki Krogerus 	/* Clear all events */
613ae8a2ca8SHeikki Krogerus 	ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff);
614ae8a2ca8SHeikki Krogerus 	if (ret < 0)
615ae8a2ca8SHeikki Krogerus 		return ret;
616ae8a2ca8SHeikki Krogerus 
617ae8a2ca8SHeikki Krogerus 	if (tcpci->controls_vbus)
618ae8a2ca8SHeikki Krogerus 		reg = TCPC_POWER_STATUS_VBUS_PRES;
619ae8a2ca8SHeikki Krogerus 	else
620ae8a2ca8SHeikki Krogerus 		reg = 0;
621ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_POWER_STATUS_MASK, reg);
622ae8a2ca8SHeikki Krogerus 	if (ret < 0)
623ae8a2ca8SHeikki Krogerus 		return ret;
624ae8a2ca8SHeikki Krogerus 
625ae8a2ca8SHeikki Krogerus 	/* Enable Vbus detection */
626ae8a2ca8SHeikki Krogerus 	ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
627ae8a2ca8SHeikki Krogerus 			   TCPC_CMD_ENABLE_VBUS_DETECT);
628ae8a2ca8SHeikki Krogerus 	if (ret < 0)
629ae8a2ca8SHeikki Krogerus 		return ret;
630ae8a2ca8SHeikki Krogerus 
631ae8a2ca8SHeikki Krogerus 	reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
632ae8a2ca8SHeikki Krogerus 		TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
633ae8a2ca8SHeikki Krogerus 		TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
634ae8a2ca8SHeikki Krogerus 	if (tcpci->controls_vbus)
635ae8a2ca8SHeikki Krogerus 		reg |= TCPC_ALERT_POWER_STATUS;
636766c485bSBadhri Jagan Sridharan 	/* Enable VSAFE0V status interrupt when detecting VSAFE0V is supported */
637766c485bSBadhri Jagan Sridharan 	if (tcpci->data->vbus_vsafe0v) {
638766c485bSBadhri Jagan Sridharan 		reg |= TCPC_ALERT_EXTENDED_STATUS;
639766c485bSBadhri Jagan Sridharan 		ret = regmap_write(tcpci->regmap, TCPC_EXTENDED_STATUS_MASK,
640766c485bSBadhri Jagan Sridharan 				   TCPC_EXTENDED_STATUS_VSAFE0V);
641766c485bSBadhri Jagan Sridharan 		if (ret < 0)
642766c485bSBadhri Jagan Sridharan 			return ret;
643766c485bSBadhri Jagan Sridharan 	}
644ccb0beb4SXu Yang 
645ccb0beb4SXu Yang 	tcpci->alert_mask = reg;
646ccb0beb4SXu Yang 
647ae8a2ca8SHeikki Krogerus 	return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg);
648ae8a2ca8SHeikki Krogerus }
649ae8a2ca8SHeikki Krogerus 
650ae8a2ca8SHeikki Krogerus irqreturn_t tcpci_irq(struct tcpci *tcpci)
651ae8a2ca8SHeikki Krogerus {
652ae8a2ca8SHeikki Krogerus 	u16 status;
653766c485bSBadhri Jagan Sridharan 	int ret;
654766c485bSBadhri Jagan Sridharan 	unsigned int raw;
655ae8a2ca8SHeikki Krogerus 
656ae8a2ca8SHeikki Krogerus 	tcpci_read16(tcpci, TCPC_ALERT, &status);
657ae8a2ca8SHeikki Krogerus 
658ae8a2ca8SHeikki Krogerus 	/*
659ae8a2ca8SHeikki Krogerus 	 * Clear alert status for everything except RX_STATUS, which shouldn't
660ae8a2ca8SHeikki Krogerus 	 * be cleared until we have successfully retrieved message.
661ae8a2ca8SHeikki Krogerus 	 */
662ae8a2ca8SHeikki Krogerus 	if (status & ~TCPC_ALERT_RX_STATUS)
663ae8a2ca8SHeikki Krogerus 		tcpci_write16(tcpci, TCPC_ALERT,
664ae8a2ca8SHeikki Krogerus 			      status & ~TCPC_ALERT_RX_STATUS);
665ae8a2ca8SHeikki Krogerus 
666ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_CC_STATUS)
667ae8a2ca8SHeikki Krogerus 		tcpm_cc_change(tcpci->port);
668ae8a2ca8SHeikki Krogerus 
669ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_POWER_STATUS) {
670766c485bSBadhri Jagan Sridharan 		regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &raw);
671ae8a2ca8SHeikki Krogerus 		/*
672ae8a2ca8SHeikki Krogerus 		 * If power status mask has been reset, then the TCPC
673ae8a2ca8SHeikki Krogerus 		 * has reset.
674ae8a2ca8SHeikki Krogerus 		 */
675766c485bSBadhri Jagan Sridharan 		if (raw == 0xff)
676ae8a2ca8SHeikki Krogerus 			tcpm_tcpc_reset(tcpci->port);
677ae8a2ca8SHeikki Krogerus 		else
678ae8a2ca8SHeikki Krogerus 			tcpm_vbus_change(tcpci->port);
679ae8a2ca8SHeikki Krogerus 	}
680ae8a2ca8SHeikki Krogerus 
681ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_RX_STATUS) {
682ae8a2ca8SHeikki Krogerus 		struct pd_message msg;
683c215e48eSDouglas Gilbert 		unsigned int cnt, payload_cnt;
684ae8a2ca8SHeikki Krogerus 		u16 header;
685ae8a2ca8SHeikki Krogerus 
686ae8a2ca8SHeikki Krogerus 		regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt);
687c215e48eSDouglas Gilbert 		/*
688c215e48eSDouglas Gilbert 		 * 'cnt' corresponds to READABLE_BYTE_COUNT in section 4.4.14
689c215e48eSDouglas Gilbert 		 * of the TCPCI spec [Rev 2.0 Ver 1.0 October 2017] and is
690c215e48eSDouglas Gilbert 		 * defined in table 4-36 as one greater than the number of
691c215e48eSDouglas Gilbert 		 * bytes received. And that number includes the header. So:
692c215e48eSDouglas Gilbert 		 */
693c215e48eSDouglas Gilbert 		if (cnt > 3)
694c215e48eSDouglas Gilbert 			payload_cnt = cnt - (1 + sizeof(msg.header));
695c215e48eSDouglas Gilbert 		else
696c215e48eSDouglas Gilbert 			payload_cnt = 0;
697ae8a2ca8SHeikki Krogerus 
698ae8a2ca8SHeikki Krogerus 		tcpci_read16(tcpci, TCPC_RX_HDR, &header);
699ae8a2ca8SHeikki Krogerus 		msg.header = cpu_to_le16(header);
700ae8a2ca8SHeikki Krogerus 
701c215e48eSDouglas Gilbert 		if (WARN_ON(payload_cnt > sizeof(msg.payload)))
702c215e48eSDouglas Gilbert 			payload_cnt = sizeof(msg.payload);
703ae8a2ca8SHeikki Krogerus 
704c215e48eSDouglas Gilbert 		if (payload_cnt > 0)
705ae8a2ca8SHeikki Krogerus 			regmap_raw_read(tcpci->regmap, TCPC_RX_DATA,
706c215e48eSDouglas Gilbert 					&msg.payload, payload_cnt);
707ae8a2ca8SHeikki Krogerus 
708ae8a2ca8SHeikki Krogerus 		/* Read complete, clear RX status alert bit */
709ae8a2ca8SHeikki Krogerus 		tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
710ae8a2ca8SHeikki Krogerus 
711ae8a2ca8SHeikki Krogerus 		tcpm_pd_receive(tcpci->port, &msg);
712ae8a2ca8SHeikki Krogerus 	}
713ae8a2ca8SHeikki Krogerus 
71405300871SXu Yang 	if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) {
715766c485bSBadhri Jagan Sridharan 		ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, &raw);
716766c485bSBadhri Jagan Sridharan 		if (!ret && (raw & TCPC_EXTENDED_STATUS_VSAFE0V))
717766c485bSBadhri Jagan Sridharan 			tcpm_vbus_change(tcpci->port);
718766c485bSBadhri Jagan Sridharan 	}
719766c485bSBadhri Jagan Sridharan 
720ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_RX_HARD_RST)
721ae8a2ca8SHeikki Krogerus 		tcpm_pd_hard_reset(tcpci->port);
722ae8a2ca8SHeikki Krogerus 
723ae8a2ca8SHeikki Krogerus 	if (status & TCPC_ALERT_TX_SUCCESS)
724ae8a2ca8SHeikki Krogerus 		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_SUCCESS);
725ae8a2ca8SHeikki Krogerus 	else if (status & TCPC_ALERT_TX_DISCARDED)
726ae8a2ca8SHeikki Krogerus 		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_DISCARDED);
727ae8a2ca8SHeikki Krogerus 	else if (status & TCPC_ALERT_TX_FAILED)
728ae8a2ca8SHeikki Krogerus 		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED);
729ae8a2ca8SHeikki Krogerus 
730ccb0beb4SXu Yang 	return IRQ_RETVAL(status & tcpci->alert_mask);
731ae8a2ca8SHeikki Krogerus }
732ae8a2ca8SHeikki Krogerus EXPORT_SYMBOL_GPL(tcpci_irq);
733ae8a2ca8SHeikki Krogerus 
734ae8a2ca8SHeikki Krogerus static irqreturn_t _tcpci_irq(int irq, void *dev_id)
735ae8a2ca8SHeikki Krogerus {
736ae8a2ca8SHeikki Krogerus 	struct tcpci_chip *chip = dev_id;
737ae8a2ca8SHeikki Krogerus 
738ae8a2ca8SHeikki Krogerus 	return tcpci_irq(chip->tcpci);
739ae8a2ca8SHeikki Krogerus }
740ae8a2ca8SHeikki Krogerus 
741ae8a2ca8SHeikki Krogerus static const struct regmap_config tcpci_regmap_config = {
742ae8a2ca8SHeikki Krogerus 	.reg_bits = 8,
743ae8a2ca8SHeikki Krogerus 	.val_bits = 8,
744ae8a2ca8SHeikki Krogerus 
745ae8a2ca8SHeikki Krogerus 	.max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */
746ae8a2ca8SHeikki Krogerus };
747ae8a2ca8SHeikki Krogerus 
748ae8a2ca8SHeikki Krogerus static int tcpci_parse_config(struct tcpci *tcpci)
749ae8a2ca8SHeikki Krogerus {
750ae8a2ca8SHeikki Krogerus 	tcpci->controls_vbus = true; /* XXX */
751ae8a2ca8SHeikki Krogerus 
752ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.fwnode = device_get_named_child_node(tcpci->dev,
753ae8a2ca8SHeikki Krogerus 							 "connector");
754ae8a2ca8SHeikki Krogerus 	if (!tcpci->tcpc.fwnode) {
755ae8a2ca8SHeikki Krogerus 		dev_err(tcpci->dev, "Can't find connector node.\n");
756ae8a2ca8SHeikki Krogerus 		return -EINVAL;
757ae8a2ca8SHeikki Krogerus 	}
758ae8a2ca8SHeikki Krogerus 
759ae8a2ca8SHeikki Krogerus 	return 0;
760ae8a2ca8SHeikki Krogerus }
761ae8a2ca8SHeikki Krogerus 
762ae8a2ca8SHeikki Krogerus struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
763ae8a2ca8SHeikki Krogerus {
764ae8a2ca8SHeikki Krogerus 	struct tcpci *tcpci;
765ae8a2ca8SHeikki Krogerus 	int err;
766ae8a2ca8SHeikki Krogerus 
767ae8a2ca8SHeikki Krogerus 	tcpci = devm_kzalloc(dev, sizeof(*tcpci), GFP_KERNEL);
768ae8a2ca8SHeikki Krogerus 	if (!tcpci)
769ae8a2ca8SHeikki Krogerus 		return ERR_PTR(-ENOMEM);
770ae8a2ca8SHeikki Krogerus 
771ae8a2ca8SHeikki Krogerus 	tcpci->dev = dev;
772ae8a2ca8SHeikki Krogerus 	tcpci->data = data;
773ae8a2ca8SHeikki Krogerus 	tcpci->regmap = data->regmap;
774ae8a2ca8SHeikki Krogerus 
775ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.init = tcpci_init;
776ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.get_vbus = tcpci_get_vbus;
777ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_vbus = tcpci_set_vbus;
778ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_cc = tcpci_set_cc;
7797257fbc7SBadhri Jagan Sridharan 	tcpci->tcpc.apply_rc = tcpci_apply_rc;
780ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.get_cc = tcpci_get_cc;
781ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_polarity = tcpci_set_polarity;
782ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_vconn = tcpci_set_vconn;
7837893f9e1SHans de Goede 	tcpci->tcpc.start_toggling = tcpci_start_toggling;
784ae8a2ca8SHeikki Krogerus 
785ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx;
786ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.set_roles = tcpci_set_roles;
787ae8a2ca8SHeikki Krogerus 	tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
788c081ac42SBadhri Jagan Sridharan 	tcpci->tcpc.set_bist_data = tcpci_set_bist_data;
78911121c24SBadhri Jagan Sridharan 	tcpci->tcpc.enable_frs = tcpci_enable_frs;
790a57d253fSBadhri Jagan Sridharan 	tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus;
791372a3d0bSBadhri Jagan Sridharan 	tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable;
792ae8a2ca8SHeikki Krogerus 
793abc028a2SBadhri Jagan Sridharan 	if (tcpci->data->check_contaminant)
794abc028a2SBadhri Jagan Sridharan 		tcpci->tcpc.check_contaminant = tcpci_check_contaminant;
795abc028a2SBadhri Jagan Sridharan 
796e1a97bf8SBadhri Jagan Sridharan 	if (tcpci->data->auto_discharge_disconnect) {
797e1a97bf8SBadhri Jagan Sridharan 		tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge;
798e1a97bf8SBadhri Jagan Sridharan 		tcpci->tcpc.set_auto_vbus_discharge_threshold =
799e1a97bf8SBadhri Jagan Sridharan 			tcpci_set_auto_vbus_discharge_threshold;
8003b6c3d04SBadhri Jagan Sridharan 		regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_BLEED_DISCHARGE,
8013b6c3d04SBadhri Jagan Sridharan 				   TCPC_POWER_CTRL_BLEED_DISCHARGE);
802e1a97bf8SBadhri Jagan Sridharan 	}
803e1a97bf8SBadhri Jagan Sridharan 
804766c485bSBadhri Jagan Sridharan 	if (tcpci->data->vbus_vsafe0v)
805766c485bSBadhri Jagan Sridharan 		tcpci->tcpc.is_vbus_vsafe0v = tcpci_is_vbus_vsafe0v;
806766c485bSBadhri Jagan Sridharan 
807ae8a2ca8SHeikki Krogerus 	err = tcpci_parse_config(tcpci);
808ae8a2ca8SHeikki Krogerus 	if (err < 0)
809ae8a2ca8SHeikki Krogerus 		return ERR_PTR(err);
810ae8a2ca8SHeikki Krogerus 
811ae8a2ca8SHeikki Krogerus 	tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc);
8120384e87eSYang Yingliang 	if (IS_ERR(tcpci->port)) {
8130384e87eSYang Yingliang 		fwnode_handle_put(tcpci->tcpc.fwnode);
814ae8a2ca8SHeikki Krogerus 		return ERR_CAST(tcpci->port);
8150384e87eSYang Yingliang 	}
816ae8a2ca8SHeikki Krogerus 
817ae8a2ca8SHeikki Krogerus 	return tcpci;
818ae8a2ca8SHeikki Krogerus }
819ae8a2ca8SHeikki Krogerus EXPORT_SYMBOL_GPL(tcpci_register_port);
820ae8a2ca8SHeikki Krogerus 
821ae8a2ca8SHeikki Krogerus void tcpci_unregister_port(struct tcpci *tcpci)
822ae8a2ca8SHeikki Krogerus {
823ae8a2ca8SHeikki Krogerus 	tcpm_unregister_port(tcpci->port);
8240384e87eSYang Yingliang 	fwnode_handle_put(tcpci->tcpc.fwnode);
825ae8a2ca8SHeikki Krogerus }
826ae8a2ca8SHeikki Krogerus EXPORT_SYMBOL_GPL(tcpci_unregister_port);
827ae8a2ca8SHeikki Krogerus 
828bdd0400dSUwe Kleine-König static int tcpci_probe(struct i2c_client *client)
829ae8a2ca8SHeikki Krogerus {
830ae8a2ca8SHeikki Krogerus 	struct tcpci_chip *chip;
831ae8a2ca8SHeikki Krogerus 	int err;
832ae8a2ca8SHeikki Krogerus 	u16 val = 0;
833ae8a2ca8SHeikki Krogerus 
834ae8a2ca8SHeikki Krogerus 	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
835ae8a2ca8SHeikki Krogerus 	if (!chip)
836ae8a2ca8SHeikki Krogerus 		return -ENOMEM;
837ae8a2ca8SHeikki Krogerus 
838ae8a2ca8SHeikki Krogerus 	chip->data.regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config);
839ae8a2ca8SHeikki Krogerus 	if (IS_ERR(chip->data.regmap))
840ae8a2ca8SHeikki Krogerus 		return PTR_ERR(chip->data.regmap);
841ae8a2ca8SHeikki Krogerus 
842ae8a2ca8SHeikki Krogerus 	i2c_set_clientdata(client, chip);
843ae8a2ca8SHeikki Krogerus 
844ae8a2ca8SHeikki Krogerus 	/* Disable chip interrupts before requesting irq */
845ae8a2ca8SHeikki Krogerus 	err = regmap_raw_write(chip->data.regmap, TCPC_ALERT_MASK, &val,
846ae8a2ca8SHeikki Krogerus 			       sizeof(u16));
847ae8a2ca8SHeikki Krogerus 	if (err < 0)
848ae8a2ca8SHeikki Krogerus 		return err;
849ae8a2ca8SHeikki Krogerus 
850ae8a2ca8SHeikki Krogerus 	chip->tcpci = tcpci_register_port(&client->dev, &chip->data);
851ae8a2ca8SHeikki Krogerus 	if (IS_ERR(chip->tcpci))
852ae8a2ca8SHeikki Krogerus 		return PTR_ERR(chip->tcpci);
853ae8a2ca8SHeikki Krogerus 
854ae8a2ca8SHeikki Krogerus 	err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
855ae8a2ca8SHeikki Krogerus 					_tcpci_irq,
856ccb0beb4SXu Yang 					IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
857ae8a2ca8SHeikki Krogerus 					dev_name(&client->dev), chip);
858ae8a2ca8SHeikki Krogerus 	if (err < 0) {
859ae8a2ca8SHeikki Krogerus 		tcpci_unregister_port(chip->tcpci);
860ae8a2ca8SHeikki Krogerus 		return err;
861ae8a2ca8SHeikki Krogerus 	}
862ae8a2ca8SHeikki Krogerus 
863ae8a2ca8SHeikki Krogerus 	return 0;
864ae8a2ca8SHeikki Krogerus }
865ae8a2ca8SHeikki Krogerus 
866ed5c2f5fSUwe Kleine-König static void tcpci_remove(struct i2c_client *client)
867ae8a2ca8SHeikki Krogerus {
868ae8a2ca8SHeikki Krogerus 	struct tcpci_chip *chip = i2c_get_clientdata(client);
8693ba76256SJun Li 	int err;
8703ba76256SJun Li 
8713ba76256SJun Li 	/* Disable chip interrupts before unregistering port */
8723ba76256SJun Li 	err = tcpci_write16(chip->tcpci, TCPC_ALERT_MASK, 0);
8733ba76256SJun Li 	if (err < 0)
874bbc126aeSUwe Kleine-König 		dev_warn(&client->dev, "Failed to disable irqs (%pe)\n", ERR_PTR(err));
875ae8a2ca8SHeikki Krogerus 
876ae8a2ca8SHeikki Krogerus 	tcpci_unregister_port(chip->tcpci);
877ae8a2ca8SHeikki Krogerus }
878ae8a2ca8SHeikki Krogerus 
879ae8a2ca8SHeikki Krogerus static const struct i2c_device_id tcpci_id[] = {
880ae8a2ca8SHeikki Krogerus 	{ "tcpci", 0 },
881ae8a2ca8SHeikki Krogerus 	{ }
882ae8a2ca8SHeikki Krogerus };
883ae8a2ca8SHeikki Krogerus MODULE_DEVICE_TABLE(i2c, tcpci_id);
884ae8a2ca8SHeikki Krogerus 
885ae8a2ca8SHeikki Krogerus #ifdef CONFIG_OF
886ae8a2ca8SHeikki Krogerus static const struct of_device_id tcpci_of_match[] = {
887ae8a2ca8SHeikki Krogerus 	{ .compatible = "nxp,ptn5110", },
888ae8a2ca8SHeikki Krogerus 	{},
889ae8a2ca8SHeikki Krogerus };
890ae8a2ca8SHeikki Krogerus MODULE_DEVICE_TABLE(of, tcpci_of_match);
891ae8a2ca8SHeikki Krogerus #endif
892ae8a2ca8SHeikki Krogerus 
893ae8a2ca8SHeikki Krogerus static struct i2c_driver tcpci_i2c_driver = {
894ae8a2ca8SHeikki Krogerus 	.driver = {
895ae8a2ca8SHeikki Krogerus 		.name = "tcpci",
896ae8a2ca8SHeikki Krogerus 		.of_match_table = of_match_ptr(tcpci_of_match),
897ae8a2ca8SHeikki Krogerus 	},
898*7126a2aeSUwe Kleine-König 	.probe = tcpci_probe,
899ae8a2ca8SHeikki Krogerus 	.remove = tcpci_remove,
900ae8a2ca8SHeikki Krogerus 	.id_table = tcpci_id,
901ae8a2ca8SHeikki Krogerus };
902ae8a2ca8SHeikki Krogerus module_i2c_driver(tcpci_i2c_driver);
903ae8a2ca8SHeikki Krogerus 
904ae8a2ca8SHeikki Krogerus MODULE_DESCRIPTION("USB Type-C Port Controller Interface driver");
905ae8a2ca8SHeikki Krogerus MODULE_LICENSE("GPL");
906