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(map: regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK, |
84 | val: channel << ADC_CHANNEL_OFFSET); |
85 | if (ret < 0) |
86 | return ret; |
87 | |
88 | /* Enable ADC */ |
89 | ret = regmap_update_bits(map: regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, ADCEN); |
90 | if (ret < 0) |
91 | return ret; |
92 | |
93 | usleep_range(min: sleep_msec * 1000, max: (sleep_msec + 1) * 1000); |
94 | ret = max_tcpci_read8(chip, TCPC_VENDOR_FLADC_STATUS, val: &fladc); |
95 | if (ret < 0) |
96 | return ret; |
97 | |
98 | /* Disable ADC */ |
99 | ret = regmap_update_bits(map: regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, val: 0); |
100 | if (ret < 0) |
101 | return ret; |
102 | |
103 | ret = regmap_update_bits(map: regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK, val: 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(map: 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(map: 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(map: 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, ua_src: true); |
139 | if (mv < 0) |
140 | return ret; |
141 | |
142 | /* OVP enable */ |
143 | ret = regmap_update_bits(map: regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, val: 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(map: 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(map: 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, ua_src: true); |
161 | if (mv < 0) |
162 | return ret; |
163 | /* Disable current source */ |
164 | ret = regmap_update_bits(map: regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, val: 0); |
165 | if (ret < 0) |
166 | return ret; |
167 | |
168 | /* OVP disable */ |
169 | ret = regmap_update_bits(map: regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, val: 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(map: 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(map: regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, CCCOMPEN); |
189 | if (ret < 0) |
190 | return ret; |
191 | |
192 | /* Sleep to allow comparators settle */ |
193 | usleep_range(min: 5000, max: 6000); |
194 | ret = regmap_update_bits(map: regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC1); |
195 | if (ret < 0) |
196 | return ret; |
197 | |
198 | usleep_range(min: 5000, max: 6000); |
199 | ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, val: vendor_cc_status2_cc1); |
200 | if (ret < 0) |
201 | return ret; |
202 | |
203 | ret = regmap_update_bits(map: regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC2); |
204 | if (ret < 0) |
205 | return ret; |
206 | |
207 | usleep_range(min: 5000, max: 6000); |
208 | ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, val: vendor_cc_status2_cc2); |
209 | if (ret < 0) |
210 | return ret; |
211 | |
212 | ret = regmap_update_bits(map: regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, val: 0); |
213 | if (ret < 0) |
214 | return ret; |
215 | |
216 | ret = regmap_update_bits(map: regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, val: 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, val: &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, val: role_ctrl); |
237 | if (ret < 0) |
238 | return NOT_DETECTED; |
239 | |
240 | cc1_k = max_contaminant_read_resistance_kohm(chip, channel: CC1_SCALE2, READ1_SLEEP_MS, raw: false); |
241 | if (cc1_k < 0) |
242 | goto exit; |
243 | |
244 | cc2_k = max_contaminant_read_resistance_kohm(chip, channel: CC2_SCALE2, READ2_SLEEP_MS, raw: false); |
245 | if (cc2_k < 0) |
246 | goto exit; |
247 | |
248 | sbu1_k = max_contaminant_read_resistance_kohm(chip, channel: SBU1, READ1_SLEEP_MS, raw: false); |
249 | if (sbu1_k < 0) |
250 | goto exit; |
251 | |
252 | sbu2_k = max_contaminant_read_resistance_kohm(chip, channel: SBU2, READ2_SLEEP_MS, raw: false); |
253 | if (sbu2_k < 0) |
254 | goto exit; |
255 | |
256 | ret = max_contaminant_read_comparators(chip, vendor_cc_status2_cc1: &vendor_cc_status2_cc1, |
257 | vendor_cc_status2_cc2: &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, val: role_ctrl_backup); |
272 | else |
273 | max_tcpci_write8(chip, TCPC_ROLE_CTRL, val: (TCPC_ROLE_CTRL_DRP | 0xA)); |
274 | |
275 | return inferred_state; |
276 | exit: |
277 | max_tcpci_write8(chip, TCPC_ROLE_CTRL, val: 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(map: 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(map: 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(map: regmap, TCPC_VENDOR_CC_CTRL1, CCCONNDRY, CCCONNDRY); |
299 | if (ret < 0) |
300 | return ret; |
301 | ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL1, val: &temp); |
302 | if (ret < 0) |
303 | return ret; |
304 | |
305 | ret = regmap_update_bits(map: 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, val: &temp); |
310 | if (ret < 0) |
311 | return ret; |
312 | |
313 | /* Enable Look4Connection before sending the command */ |
314 | ret = regmap_update_bits(map: 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, val: &cc_status); |
331 | if (ret < 0) |
332 | return false; |
333 | |
334 | ret = max_tcpci_read8(chip, TCPC_POWER_CTRL, val: &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(msecs: 100); |
341 | |
342 | ret = max_tcpci_read8(chip, TCPC_CC_STATUS, val: &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, val: &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, val: 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, val: 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 | |