// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2015-2017 Google, Inc
*
* USB Type-C Port Controller Interface.
*/
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/usb/pd.h>
#include <linux/usb/tcpci.h>
#include <linux/usb/tcpm.h>
#include <linux/usb/typec.h>
#include <linux/regulator/consumer.h>
#define PD_RETRY_COUNT_DEFAULT 3
#define PD_RETRY_COUNT_3_0_OR_HIGHER 2
#define AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV 3500
#define VSINKPD_MIN_IR_DROP_MV 750
#define VSRC_NEW_MIN_PERCENT 95
#define VSRC_VALID_MIN_MV 500
#define VPPS_NEW_MIN_PERCENT 95
#define VPPS_VALID_MIN_MV 100
#define VSINKDISCONNECT_PD_MIN_PERCENT 90
#define VPPS_SHUTDOWN_MIN_PERCENT 85
struct tcpci {
struct device *dev;
struct tcpm_port *port;
struct regmap *regmap;
unsigned int alert_mask;
bool controls_vbus;
struct tcpc_dev tcpc;
struct tcpci_data *data;
struct gpio_desc *orientation_gpio;
};
struct tcpci_chip {
struct tcpci *tcpci;
struct tcpci_data data;
};
struct tcpm_port *tcpci_get_tcpm_port(struct tcpci *tcpci)
{
return tcpci->port;
}
EXPORT_SYMBOL_GPL(tcpci_get_tcpm_port);
static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc)
{
return container_of(tcpc, struct tcpci, tcpc);
}
static int tcpci_read16(struct tcpci *tcpci, unsigned int reg, u16 *val)
{
return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16));
}
static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16 val)
{
return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16));
}
static int tcpci_check_std_output_cap(struct regmap *regmap, u8 mask)
{
unsigned int reg;
int ret;
ret = regmap_read(regmap, TCPC_STD_OUTPUT_CAP, ®);
if (ret < 0)
return ret;
return (reg & mask) == mask;
}
static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc)
{
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
bool vconn_pres;
enum typec_cc_polarity polarity = TYPEC_POLARITY_CC1;
unsigned int reg;
int ret;
ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®);
if (ret < 0)
return ret;
vconn_pres = !!(reg & TCPC_POWER_STATUS_VCONN_PRES);
if (vconn_pres) {
ret = regmap_read(tcpci->regmap, TCPC_TCPC_CTRL, ®);
if (ret < 0)
return ret;
if (reg & TCPC_TCPC_CTRL_ORIENTATION)
polarity = TYPEC_POLARITY_CC2;
}
switch (cc) {
case TYPEC_CC_RA:
reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RA)
| FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RA));
break;
case TYPEC_CC_RD:
reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RD)
| FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RD));
break;
case TYPEC_CC_RP_DEF:
reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP)
| FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP)
| FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
TCPC_ROLE_CTRL_RP_VAL_DEF));
break;
case TYPEC_CC_RP_1_5:
reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP)
| FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP)
| FIELD_PREP(TCPC_ROLE_CTRL_RP_VAL,
TCPC_ROLE_CTRL_RP_VAL_1_5));
break;
case TYPEC_CC_RP_3_0:
reg = (FIELD_PREP(TCPC_ROLE_CTRL_CC1, TCPC_ROLE_CTRL_CC_RP)
| FIELD_PREP(TCPC_ROLE_CTRL_CC2, TCPC_ROLE_CTRL_CC_RP)
| FIELD_PR