xref: /linux/drivers/usb/typec/tcpm/maxim_contaminant.c (revision 42874e4eb35bdfc54f8514685e50434098ba4f6c)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright 2022 Google, Inc
4  *
5  * USB-C module to reduce wakeups due to contaminants.
6  */
7 
8 #include <linux/device.h>
9 #include <linux/irqreturn.h>
10 #include <linux/module.h>
11 #include <linux/regmap.h>
12 #include <linux/usb/tcpci.h>
13 #include <linux/usb/tcpm.h>
14 #include <linux/usb/typec.h>
15 
16 #include "tcpci_maxim.h"
17 
18 enum fladc_select {
19 	CC1_SCALE1 = 1,
20 	CC1_SCALE2,
21 	CC2_SCALE1,
22 	CC2_SCALE2,
23 	SBU1,
24 	SBU2,
25 };
26 
27 #define FLADC_1uA_LSB_MV		25
28 /* High range CC */
29 #define FLADC_CC_HIGH_RANGE_LSB_MV	208
30 /* Low range CC */
31 #define FLADC_CC_LOW_RANGE_LSB_MV      126
32 
33 /* 1uA current source */
34 #define FLADC_CC_SCALE1			1
35 /* 5 uA current source */
36 #define FLADC_CC_SCALE2			5
37 
38 #define FLADC_1uA_CC_OFFSET_MV		300
39 #define FLADC_CC_HIGH_RANGE_OFFSET_MV	624
40 #define FLADC_CC_LOW_RANGE_OFFSET_MV	378
41 
42 #define CONTAMINANT_THRESHOLD_SBU_K	1000
43 #define	CONTAMINANT_THRESHOLD_CC_K	1000
44 
45 #define READ1_SLEEP_MS			10
46 #define READ2_SLEEP_MS			5
47 
48 #define STATUS_CHECK(reg, mask, val)	(((reg) & (mask)) == (val))
49 
50 #define IS_CC_OPEN(cc_status) \
51 	(STATUS_CHECK((cc_status), TCPC_CC_STATUS_CC1_MASK << TCPC_CC_STATUS_CC1_SHIFT,  \
52 		      TCPC_CC_STATE_SRC_OPEN) && STATUS_CHECK((cc_status),               \
53 							      TCPC_CC_STATUS_CC2_MASK << \
54 							      TCPC_CC_STATUS_CC2_SHIFT,  \
55 							      TCPC_CC_STATE_SRC_OPEN))
56 
57 static int max_contaminant_adc_to_mv(struct max_tcpci_chip *chip, enum fladc_select channel,
58 				     bool ua_src, u8 fladc)
59 {
60 	/* SBU channels only have 1 scale with 1uA. */
61 	if ((ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2 || channel == SBU1 ||
62 			channel == SBU2)))
63 		/* Mean of range */
64 		return FLADC_1uA_CC_OFFSET_MV + (fladc * FLADC_1uA_LSB_MV);
65 	else if (!ua_src && (channel == CC1_SCALE1 || channel == CC2_SCALE1))
66 		return FLADC_CC_HIGH_RANGE_OFFSET_MV + (fladc * FLADC_CC_HIGH_RANGE_LSB_MV);
67 	else if (!ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2))
68 		return FLADC_CC_LOW_RANGE_OFFSET_MV + (fladc * FLADC_CC_LOW_RANGE_LSB_MV);
69 
70 	dev_err_once(chip->dev, "ADC ERROR: SCALE UNKNOWN");
71 
72 	return -EINVAL;
73 }
74 
75 static int max_contaminant_read_adc_mv(struct max_tcpci_chip *chip, enum fladc_select channel,
76 				       int sleep_msec, bool raw, bool ua_src)
77 {
78 	struct regmap *regmap = chip->data.regmap;
79 	u8 fladc;
80 	int ret;
81 
82 	/* Channel & scale select */
83 	ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK,
84 				 channel << ADC_CHANNEL_OFFSET);
85 	if (ret < 0)
86 		return ret;
87 
88 	/* Enable ADC */
89 	ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, ADCEN);
90 	if (ret < 0)
91 		return ret;
92 
93 	usleep_range(sleep_msec * 1000, (sleep_msec + 1) * 1000);
94 	ret = max_tcpci_read8(chip, TCPC_VENDOR_FLADC_STATUS, &fladc);
95 	if (ret < 0)
96 		return ret;
97 
98 	/* Disable ADC */
99 	ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, 0);
100 	if (ret < 0)
101 		return ret;
102 
103 	ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK, 0);
104 	if (ret < 0)
105 		return ret;
106 
107 	if (!raw)
108 		return max_contaminant_adc_to_mv(chip, channel, ua_src, fladc);
109 	else
110 		return fladc;
111 }
112 
113 static int max_contaminant_read_resistance_kohm(struct max_tcpci_chip *chip,
114 						enum fladc_select channel, int sleep_msec, bool raw)
115 {
116 	struct regmap *regmap = chip->data.regmap;
117 	int mv;
118 	int ret;
119 
120 	if (channel == CC1_SCALE1 || channel == CC2_SCALE1 || channel == CC1_SCALE2 ||
121 	    channel == CC2_SCALE2) {
122 		/* Enable 1uA current source */
123 		ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL_MASK,
124 					 ULTRA_LOW_POWER_MODE);
125 		if (ret < 0)
126 			return ret;
127 
128 		/* Enable 1uA current source */
129 		ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, UA_1_SRC);
130 		if (ret < 0)
131 			return ret;
132 
133 		/* OVP disable */
134 		ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, CCOVPDIS);
135 		if (ret < 0)
136 			return ret;
137 
138 		mv = max_contaminant_read_adc_mv(chip, channel, sleep_msec, raw, true);
139 		if (mv < 0)
140 			return ret;
141 
142 		/* OVP enable */
143 		ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, 0);
144 		if (ret < 0)
145 			return ret;
146 		/* returns KOhm as 1uA source is used. */
147 		return mv;
148 	}
149 
150 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, SBUOVPDIS);
151 	if (ret < 0)
152 		return ret;
153 
154 	/* SBU switches auto configure when channel is selected. */
155 	/* Enable 1ua current source */
156 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, SBURPCTRL);
157 	if (ret < 0)
158 		return ret;
159 
160 	mv = max_contaminant_read_adc_mv(chip, channel, sleep_msec, raw, true);
161 	if (mv < 0)
162 		return ret;
163 	/* Disable current source */
164 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, 0);
165 	if (ret < 0)
166 		return ret;
167 
168 	/* OVP disable */
169 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, 0);
170 	if (ret < 0)
171 		return ret;
172 
173 	return mv;
174 }
175 
176 static int max_contaminant_read_comparators(struct max_tcpci_chip *chip, u8 *vendor_cc_status2_cc1,
177 					    u8 *vendor_cc_status2_cc2)
178 {
179 	struct regmap *regmap = chip->data.regmap;
180 	int ret;
181 
182 	/* Enable 80uA source */
183 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, UA_80_SRC);
184 	if (ret < 0)
185 		return ret;
186 
187 	/* Enable comparators */
188 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, CCCOMPEN);
189 	if (ret < 0)
190 		return ret;
191 
192 	/* Sleep to allow comparators settle */
193 	usleep_range(5000, 6000);
194 	ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC1);
195 	if (ret < 0)
196 		return ret;
197 
198 	usleep_range(5000, 6000);
199 	ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc1);
200 	if (ret < 0)
201 		return ret;
202 
203 	ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC2);
204 	if (ret < 0)
205 		return ret;
206 
207 	usleep_range(5000, 6000);
208 	ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc2);
209 	if (ret < 0)
210 		return ret;
211 
212 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, 0);
213 	if (ret < 0)
214 		return ret;
215 
216 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, 0);
217 	if (ret < 0)
218 		return ret;
219 
220 	return 0;
221 }
222 
223 static int max_contaminant_detect_contaminant(struct max_tcpci_chip *chip)
224 {
225 	int cc1_k, cc2_k, sbu1_k, sbu2_k, ret;
226 	u8 vendor_cc_status2_cc1 = 0xff, vendor_cc_status2_cc2 = 0xff;
227 	u8 role_ctrl = 0, role_ctrl_backup = 0;
228 	int inferred_state = NOT_DETECTED;
229 
230 	ret = max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl);
231 	if (ret < 0)
232 		return NOT_DETECTED;
233 
234 	role_ctrl_backup = role_ctrl;
235 	role_ctrl = 0x0F;
236 	ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl);
237 	if (ret < 0)
238 		return NOT_DETECTED;
239 
240 	cc1_k = max_contaminant_read_resistance_kohm(chip, CC1_SCALE2, READ1_SLEEP_MS, false);
241 	if (cc1_k < 0)
242 		goto exit;
243 
244 	cc2_k = max_contaminant_read_resistance_kohm(chip, CC2_SCALE2, READ2_SLEEP_MS, false);
245 	if (cc2_k < 0)
246 		goto exit;
247 
248 	sbu1_k = max_contaminant_read_resistance_kohm(chip, SBU1, READ1_SLEEP_MS, false);
249 	if (sbu1_k < 0)
250 		goto exit;
251 
252 	sbu2_k = max_contaminant_read_resistance_kohm(chip, SBU2, READ2_SLEEP_MS, false);
253 	if (sbu2_k < 0)
254 		goto exit;
255 
256 	ret = max_contaminant_read_comparators(chip, &vendor_cc_status2_cc1,
257 					       &vendor_cc_status2_cc2);
258 
259 	if (ret < 0)
260 		goto exit;
261 
262 	if ((!(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1) ||
263 	     !(CC2_VUFP_RD0P5 & vendor_cc_status2_cc2)) &&
264 	    !(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1 && CC2_VUFP_RD0P5 & vendor_cc_status2_cc2))
265 		inferred_state = SINK;
266 	else if ((cc1_k < CONTAMINANT_THRESHOLD_CC_K || cc2_k < CONTAMINANT_THRESHOLD_CC_K) &&
267 		 (sbu1_k < CONTAMINANT_THRESHOLD_SBU_K || sbu2_k < CONTAMINANT_THRESHOLD_SBU_K))
268 		inferred_state = DETECTED;
269 
270 	if (inferred_state == NOT_DETECTED)
271 		max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup);
272 	else
273 		max_tcpci_write8(chip, TCPC_ROLE_CTRL, (TCPC_ROLE_CTRL_DRP | 0xA));
274 
275 	return inferred_state;
276 exit:
277 	max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup);
278 	return NOT_DETECTED;
279 }
280 
281 static int max_contaminant_enable_dry_detection(struct max_tcpci_chip *chip)
282 {
283 	struct regmap *regmap = chip->data.regmap;
284 	u8 temp;
285 	int ret;
286 
287 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL3, CCWTRDEB_MASK | CCWTRSEL_MASK
288 				    | WTRCYCLE_MASK, CCWTRDEB_1MS << CCWTRDEB_SHIFT |
289 				    CCWTRSEL_1V << CCWTRSEL_SHIFT | WTRCYCLE_4_8_S <<
290 				    WTRCYCLE_SHIFT);
291 	if (ret < 0)
292 		return ret;
293 
294 	ret = regmap_update_bits(regmap, TCPC_ROLE_CTRL, TCPC_ROLE_CTRL_DRP, TCPC_ROLE_CTRL_DRP);
295 	if (ret < 0)
296 		return ret;
297 
298 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCONNDRY, CCCONNDRY);
299 	if (ret < 0)
300 		return ret;
301 	ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL1, &temp);
302 	if (ret < 0)
303 		return ret;
304 
305 	ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL_MASK,
306 				 ULTRA_LOW_POWER_MODE);
307 	if (ret < 0)
308 		return ret;
309 	ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL2, &temp);
310 	if (ret < 0)
311 		return ret;
312 
313 	/* Enable Look4Connection before sending the command */
314 	ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_EN_LK4CONN_ALRT,
315 				 TCPC_TCPC_CTRL_EN_LK4CONN_ALRT);
316 	if (ret < 0)
317 		return ret;
318 
319 	ret = max_tcpci_write8(chip, TCPC_COMMAND, TCPC_CMD_LOOK4CONNECTION);
320 	if (ret < 0)
321 		return ret;
322 	return 0;
323 }
324 
325 bool max_contaminant_is_contaminant(struct max_tcpci_chip *chip, bool disconnect_while_debounce)
326 {
327 	u8 cc_status, pwr_cntl;
328 	int ret;
329 
330 	ret = max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status);
331 	if (ret < 0)
332 		return false;
333 
334 	ret = max_tcpci_read8(chip, TCPC_POWER_CTRL, &pwr_cntl);
335 	if (ret < 0)
336 		return false;
337 
338 	if (chip->contaminant_state == NOT_DETECTED || chip->contaminant_state == SINK) {
339 		if (!disconnect_while_debounce)
340 			msleep(100);
341 
342 		ret = max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status);
343 		if (ret < 0)
344 			return false;
345 
346 		if (IS_CC_OPEN(cc_status)) {
347 			u8 role_ctrl, role_ctrl_backup;
348 
349 			ret = max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl);
350 			if (ret < 0)
351 				return false;
352 
353 			role_ctrl_backup = role_ctrl;
354 			role_ctrl |= 0x0F;
355 			role_ctrl &= ~(TCPC_ROLE_CTRL_DRP);
356 			ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl);
357 			if (ret < 0)
358 				return false;
359 
360 			chip->contaminant_state = max_contaminant_detect_contaminant(chip);
361 
362 			ret = max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup);
363 			if (ret < 0)
364 				return false;
365 
366 			if (chip->contaminant_state == DETECTED) {
367 				max_contaminant_enable_dry_detection(chip);
368 				return true;
369 			}
370 		}
371 		return false;
372 	} else if (chip->contaminant_state == DETECTED) {
373 		if (STATUS_CHECK(cc_status, TCPC_CC_STATUS_TOGGLING, 0)) {
374 			chip->contaminant_state = max_contaminant_detect_contaminant(chip);
375 			if (chip->contaminant_state == DETECTED) {
376 				max_contaminant_enable_dry_detection(chip);
377 				return true;
378 			}
379 		}
380 	}
381 
382 	return false;
383 }
384 
385 MODULE_DESCRIPTION("MAXIM TCPC CONTAMINANT Module");
386 MODULE_AUTHOR("Badhri Jagan Sridharan <badhri@google.com>");
387 MODULE_LICENSE("GPL");
388