forked from torvalds/linux
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
usb: typec: maxim_contaminant: Implement check_contaminant callback
Maxim TCPC has additional ADCs and low current(1ua) current source to measure the impedance of CC and SBU pins. When tcpm invokes the check_contaminant callback, Maxim TCPC measures the impedance of the CC & SBU pins and when the impedance measured is less than 1MOhm, it is assumed that USB-C port is contaminated. CC comparators are also checked to differentiate between presence of sink and contaminant. Once USB-C is deemed to be contaminated, MAXIM TCPC has additional hardware to disable normal DRP toggling cycle and enable 1ua on CC pins once every 2.4secs/4.8secs. Maxim TCPC interrupts AP once the impedance on the CC pin is above the 1MOhm threshold. The Maxim tcpc driver then signals TCPM_PORT_CLEAN to restart toggling. Renaming tcpci_maxim.c to tcpci_maxim_core.c and moving reg read/write helper functions to the tcpci_maxim.h header file. Signed-off-by: Badhri Jagan Sridharan <badhri@google.com>
- Loading branch information
1 parent
652f2aa
commit e04cbf4
Showing
4 changed files
with
476 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,338 @@ | ||
// SPDX-License-Identifier: GPL-2.0+ | ||
/* | ||
* Copyright 2022 Google, Inc | ||
* | ||
* USB-C module to reduce wakeups due to contaminants. | ||
*/ | ||
|
||
#include <linux/device.h> | ||
#include <linux/irqreturn.h> | ||
#include <linux/module.h> | ||
#include <linux/regmap.h> | ||
#include <linux/usb/tcpci.h> | ||
#include <linux/usb/tcpm.h> | ||
#include <linux/usb/typec.h> | ||
|
||
#include "tcpci_maxim.h" | ||
|
||
enum fladc_select { | ||
CC1_SCALE1 = 1, | ||
CC1_SCALE2, | ||
CC2_SCALE1, | ||
CC2_SCALE2, | ||
SBU1, | ||
SBU2, | ||
}; | ||
|
||
#define FLADC_1uA_LSB_MV 25 | ||
/* High range CC */ | ||
#define FLADC_CC_HIGH_RANGE_LSB_MV 208 | ||
/* Low range CC */ | ||
#define FLADC_CC_LOW_RANGE_LSB_MV 126 | ||
|
||
/* 1uA current source */ | ||
#define FLADC_CC_SCALE1 1 | ||
/* 5 uA current source */ | ||
#define FLADC_CC_SCALE2 5 | ||
|
||
#define FLADC_1uA_CC_OFFSET_MV 300 | ||
#define FLADC_CC_HIGH_RANGE_OFFSET_MV 624 | ||
#define FLADC_CC_LOW_RANGE_OFFSET_MV 378 | ||
|
||
#define CONTAMINANT_THRESHOLD_SBU_K 1000 | ||
#define CONTAMINANT_THRESHOLD_CC_K 1000 | ||
|
||
#define READ1_SLEEP_MS 10 | ||
#define READ2_SLEEP_MS 5 | ||
|
||
#define STATUS_CHECK(reg, mask, val) (((reg) & (mask)) == (val)) | ||
|
||
#define IS_CC_OPEN(cc_status) \ | ||
(STATUS_CHECK((cc_status), TCPC_CC_STATUS_CC1_MASK << TCPC_CC_STATUS_CC1_SHIFT, \ | ||
TCPC_CC_STATE_SRC_OPEN) && STATUS_CHECK((cc_status), \ | ||
TCPC_CC_STATUS_CC2_MASK << \ | ||
TCPC_CC_STATUS_CC2_SHIFT, \ | ||
TCPC_CC_STATE_SRC_OPEN)) | ||
|
||
static int adc_to_mv(struct max_tcpci_chip *chip, enum fladc_select channel, bool ua_src, u8 fladc) | ||
{ | ||
/* SBU channels only have 1 scale with 1uA. */ | ||
if ((ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2 || channel == SBU1 || | ||
channel == SBU2))) | ||
/* Mean of range */ | ||
return FLADC_1uA_CC_OFFSET_MV + (fladc * FLADC_1uA_LSB_MV); | ||
else if (!ua_src && (channel == CC1_SCALE1 || channel == CC2_SCALE1)) | ||
return FLADC_CC_HIGH_RANGE_OFFSET_MV + (fladc * FLADC_CC_HIGH_RANGE_LSB_MV); | ||
else if (!ua_src && (channel == CC1_SCALE2 || channel == CC2_SCALE2)) | ||
return FLADC_CC_LOW_RANGE_OFFSET_MV + (fladc * FLADC_CC_LOW_RANGE_LSB_MV); | ||
|
||
dev_err(chip->dev, "ADC ERROR: SCALE UNKNOWN"); | ||
|
||
return -EINVAL; | ||
} | ||
|
||
static int read_adc_mv(struct max_tcpci_chip *chip, enum fladc_select channel, int sleep_msec, | ||
bool raw, bool ua_src) | ||
{ | ||
struct regmap *regmap = chip->data.regmap; | ||
u8 fladc; | ||
int ret; | ||
|
||
/* Channel & scale select */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK, | ||
channel << ADC_CHANNEL_OFFSET); | ||
if (ret < 0) | ||
return ret; | ||
|
||
/* Enable ADC */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, ADCEN); | ||
if (ret < 0) | ||
return ret; | ||
|
||
usleep_range(sleep_msec * 1000, (sleep_msec + 1) * 1000); | ||
ret = max_tcpci_read8(chip, TCPC_VENDOR_FLADC_STATUS, &fladc); | ||
if (ret < 0) | ||
return ret; | ||
|
||
/* Disable ADC */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCEN, 0); | ||
if (ret < 0) | ||
return ret; | ||
|
||
ret = regmap_update_bits(regmap, TCPC_VENDOR_ADC_CTRL1, ADCINSEL_MASK, 0); | ||
if (ret < 0) | ||
return ret; | ||
|
||
if (!raw) | ||
return adc_to_mv(chip, channel, ua_src, fladc); | ||
else | ||
return fladc; | ||
} | ||
|
||
static int read_resistance_kohm(struct max_tcpci_chip *chip, enum fladc_select channel, | ||
int sleep_msec, bool raw) | ||
{ | ||
struct regmap *regmap = chip->data.regmap; | ||
int mv; | ||
int ret; | ||
|
||
if (channel == CC1_SCALE1 || channel == CC2_SCALE1 || channel == CC1_SCALE2 || | ||
channel == CC2_SCALE2) { | ||
/* Enable 1uA current source */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL_MASK, | ||
ULTRA_LOW_POWER_MODE); | ||
if (ret < 0) | ||
return ret; | ||
|
||
/* Enable 1uA current source */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, UA_1_SRC); | ||
if (ret < 0) | ||
return ret; | ||
|
||
/* OVP disable */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, CCOVPDIS); | ||
if (ret < 0) | ||
return ret; | ||
|
||
mv = read_adc_mv(chip, channel, sleep_msec, raw, true); | ||
/* OVP enable */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCOVPDIS, 0); | ||
if (ret < 0) | ||
return ret; | ||
/* returns KOhm as 1uA source is used. */ | ||
return mv; | ||
} | ||
|
||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, SBUOVPDIS); | ||
if (ret < 0) | ||
return ret; | ||
|
||
/* SBU switches auto configure when channel is selected. */ | ||
/* Enable 1ua current source */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, SBURPCTRL); | ||
if (ret < 0) | ||
return ret; | ||
|
||
mv = read_adc_mv(chip, channel, sleep_msec, raw, true); | ||
/* Disable current source */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBURPCTRL, 0); | ||
if (ret < 0) | ||
return ret; | ||
|
||
/* OVP disable */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, SBUOVPDIS, 0); | ||
if (ret < 0) | ||
return ret; | ||
|
||
return mv; | ||
} | ||
|
||
static void read_comparators(struct max_tcpci_chip *chip, u8 *vendor_cc_status2_cc1, | ||
u8 *vendor_cc_status2_cc2) | ||
{ | ||
struct regmap *regmap = chip->data.regmap; | ||
int ret; | ||
|
||
/* Enable 80uA source */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, UA_80_SRC); | ||
if (ret < 0) | ||
return; | ||
|
||
/* Enable comparators */ | ||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, CCCOMPEN); | ||
if (ret < 0) | ||
return; | ||
|
||
/* Sleep to allow comparators settle */ | ||
usleep_range(5000, 6000); | ||
ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC1); | ||
if (ret < 0) | ||
return; | ||
|
||
usleep_range(5000, 6000); | ||
ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc1); | ||
if (ret < 0) | ||
return; | ||
|
||
ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_ORIENTATION, PLUG_ORNT_CC2); | ||
if (ret < 0) | ||
return; | ||
|
||
usleep_range(5000, 6000); | ||
ret = max_tcpci_read8(chip, VENDOR_CC_STATUS2, vendor_cc_status2_cc2); | ||
if (ret < 0) | ||
return; | ||
|
||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCOMPEN, 0); | ||
if (ret < 0) | ||
return; | ||
regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCRPCTRL_MASK, 0); | ||
} | ||
|
||
static int detect_contaminant(struct max_tcpci_chip *chip) | ||
{ | ||
int cc1_k, cc2_k, sbu1_k, sbu2_k; | ||
u8 vendor_cc_status2_cc1 = 0xff, vendor_cc_status2_cc2 = 0xff; | ||
u8 role_ctrl = 0, role_ctrl_backup = 0; | ||
int inferred_state = NOT_DETECTED; | ||
|
||
max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl); | ||
role_ctrl_backup = role_ctrl; | ||
role_ctrl = 0x0F; | ||
max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl); | ||
|
||
cc1_k = read_resistance_kohm(chip, CC1_SCALE2, READ1_SLEEP_MS, false); | ||
cc2_k = read_resistance_kohm(chip, CC2_SCALE2, READ2_SLEEP_MS, false); | ||
|
||
sbu1_k = read_resistance_kohm(chip, SBU1, READ1_SLEEP_MS, false); | ||
sbu2_k = read_resistance_kohm(chip, SBU2, READ2_SLEEP_MS, false); | ||
read_comparators(chip, &vendor_cc_status2_cc1, &vendor_cc_status2_cc2); | ||
|
||
if ((!(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1) || | ||
!(CC2_VUFP_RD0P5 & vendor_cc_status2_cc2)) && | ||
!(CC1_VUFP_RD0P5 & vendor_cc_status2_cc1 && CC2_VUFP_RD0P5 & vendor_cc_status2_cc2)) | ||
inferred_state = SINK; | ||
else if ((cc1_k < CONTAMINANT_THRESHOLD_CC_K || cc2_k < CONTAMINANT_THRESHOLD_CC_K) && | ||
(sbu1_k < CONTAMINANT_THRESHOLD_SBU_K || sbu2_k < CONTAMINANT_THRESHOLD_SBU_K)) | ||
inferred_state = DETECTED; | ||
|
||
if (inferred_state == NOT_DETECTED) | ||
max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); | ||
else | ||
max_tcpci_write8(chip, TCPC_ROLE_CTRL, (TCPC_ROLE_CTRL_DRP | 0xA)); | ||
|
||
return inferred_state; | ||
} | ||
|
||
static int enable_dry_detection(struct max_tcpci_chip *chip) | ||
{ | ||
struct regmap *regmap = chip->data.regmap; | ||
u8 temp; | ||
int ret; | ||
|
||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL3, CCWTRDEB_MASK | CCWTRSEL_MASK | ||
| WTRCYCLE_MASK, CCWTRDEB_1MS << CCWTRDEB_SHIFT | | ||
CCWTRSEL_1V << CCWTRSEL_SHIFT | WTRCYCLE_4_8_S << | ||
WTRCYCLE_SHIFT); | ||
if (ret < 0) | ||
return ret; | ||
|
||
ret = regmap_update_bits(regmap, TCPC_ROLE_CTRL, TCPC_ROLE_CTRL_DRP, TCPC_ROLE_CTRL_DRP); | ||
if (ret < 0) | ||
return ret; | ||
|
||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL1, CCCONNDRY, CCCONNDRY); | ||
if (ret < 0) | ||
return ret; | ||
ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL1, &temp); | ||
if (ret < 0) | ||
return ret; | ||
|
||
ret = regmap_update_bits(regmap, TCPC_VENDOR_CC_CTRL2, CCLPMODESEL_MASK, | ||
ULTRA_LOW_POWER_MODE); | ||
if (ret < 0) | ||
return ret; | ||
ret = max_tcpci_read8(chip, TCPC_VENDOR_CC_CTRL2, &temp); | ||
if (ret < 0) | ||
return ret; | ||
|
||
/* Enable Look4Connection before sending the command */ | ||
ret = regmap_update_bits(regmap, TCPC_TCPC_CTRL, TCPC_TCPC_CTRL_EN_LK4CONN_ALRT, | ||
TCPC_TCPC_CTRL_EN_LK4CONN_ALRT); | ||
if (ret < 0) | ||
return ret; | ||
|
||
ret = max_tcpci_write8(chip, TCPC_COMMAND, TCPC_CMD_LOOK4CONNECTION); | ||
if (ret < 0) | ||
return ret; | ||
return 0; | ||
} | ||
|
||
bool is_contaminant(struct max_tcpci_chip *chip, bool disconnect_while_debounce) | ||
{ | ||
u8 cc_status, pwr_cntl; | ||
enum contamiant_state state; | ||
|
||
max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status); | ||
max_tcpci_read8(chip, TCPC_POWER_CTRL, &pwr_cntl); | ||
|
||
if (chip->contaminant_state == NOT_DETECTED || chip->contaminant_state == SINK) { | ||
if (!disconnect_while_debounce) | ||
msleep(100); | ||
|
||
max_tcpci_read8(chip, TCPC_CC_STATUS, &cc_status); | ||
if (IS_CC_OPEN(cc_status)) { | ||
u8 role_ctrl, role_ctrl_backup; | ||
|
||
max_tcpci_read8(chip, TCPC_ROLE_CTRL, &role_ctrl); | ||
role_ctrl_backup = role_ctrl; | ||
role_ctrl |= 0x0F; | ||
role_ctrl &= ~(TCPC_ROLE_CTRL_DRP); | ||
max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl); | ||
|
||
chip->contaminant_state = detect_contaminant(chip); | ||
|
||
max_tcpci_write8(chip, TCPC_ROLE_CTRL, role_ctrl_backup); | ||
if (state == DETECTED) { | ||
enable_dry_detection(chip); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} else if (chip->contaminant_state == DETECTED) { | ||
if (STATUS_CHECK(cc_status, TCPC_CC_STATUS_TOGGLING, 0)) { | ||
chip->contaminant_state = detect_contaminant(chip); | ||
if (state == DETECTED) { | ||
enable_dry_detection(chip); | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
EXPORT_SYMBOL_GPL(is_contaminant); | ||
|
||
MODULE_DESCRIPTION("MAXIM TCPC CONTAMINANT Module"); | ||
MODULE_AUTHOR("Badhri Jagan Sridharan <badhri@google.com>"); | ||
MODULE_LICENSE("GPL"); |
Oops, something went wrong.