diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 1abf76be2..7ad8b090c 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -92,6 +92,8 @@ source "drivers/staging/fbtft/Kconfig" source "drivers/staging/fsl-dpaa2/Kconfig" +source "drivers/staging/fusb30x/Kconfig" + source "drivers/staging/most/Kconfig" source "drivers/staging/ks7010/Kconfig" diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index ab0cbe881..2e308d901 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_UNISYSSPAR) += unisys/ obj-$(CONFIG_COMMON_CLK_XLNX_CLKWZRD) += clocking-wizard/ obj-$(CONFIG_FB_TFT) += fbtft/ obj-$(CONFIG_FSL_DPAA2) += fsl-dpaa2/ +obj-$(CONFIG_FUSB_30X) += fusb30x/ obj-$(CONFIG_MOST) += most/ obj-$(CONFIG_KS7010) += ks7010/ obj-$(CONFIG_GREYBUS) += greybus/ diff --git a/drivers/staging/fusb30x/Kconfig b/drivers/staging/fusb30x/Kconfig new file mode 100644 index 000000000..5bb75270f --- /dev/null +++ b/drivers/staging/fusb30x/Kconfig @@ -0,0 +1,10 @@ +config FUSB_30X + tristate "Fairchild FUSB30X Type-C chip driver" + depends on I2C + help + This is a driver for the Fairchild FUSB302 Type-C chip. It supports + USB Type-C PD functionality controlled using I2C. + + This driver supports extcon reporting not yet implemented in the + mainline FUSB302 driver. + diff --git a/drivers/staging/fusb30x/Makefile b/drivers/staging/fusb30x/Makefile new file mode 100644 index 000000000..1c8e35df3 --- /dev/null +++ b/drivers/staging/fusb30x/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_FUSB_30X) += fusb30x.o diff --git a/drivers/staging/fusb30x/fusb30x.c b/drivers/staging/fusb30x/fusb30x.c new file mode 100644 index 000000000..56d22648c --- /dev/null +++ b/drivers/staging/fusb30x/fusb30x.c @@ -0,0 +1,3434 @@ +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + * Author: Zain Wang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * Some ideas are from chrome ec and fairchild GPL fusb302 driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fusb30x.h" + +#define FUSB302_MAX_REG (FUSB_REG_FIFO + 50) +#define FUSB_MS_TO_NS(x) ((s64)x * 1000 * 1000) + +#define TYPEC_CC_VOLT_OPEN 0 +#define TYPEC_CC_VOLT_RA 1 +#define TYPEC_CC_VOLT_RD 2 +#define TYPEC_CC_VOLT_RP 3 + +#define EVENT_CC BIT(0) +#define EVENT_RX BIT(1) +#define EVENT_TX BIT(2) +#define EVENT_REC_RESET BIT(3) +#define EVENT_WORK_CONTINUE BIT(5) +#define EVENT_TIMER_MUX BIT(6) +#define EVENT_TIMER_STATE BIT(7) +#define EVENT_DELAY_CC BIT(8) +#define FLAG_EVENT (EVENT_RX | EVENT_TIMER_MUX | \ + EVENT_TIMER_STATE) + +#define PACKET_IS_CONTROL_MSG(header, type) \ + (PD_HEADER_CNT(header) == 0 && \ + PD_HEADER_TYPE(header) == type) + +#define PACKET_IS_DATA_MSG(header, type) \ + (PD_HEADER_CNT(header) != 0 && \ + PD_HEADER_TYPE(header) == type) + +/* + * DisplayPort modes capabilities + * ------------------------------- + * <31:24> : Reserved (always 0). + * <23:16> : UFP_D pin assignment supported + * <15:8> : DFP_D pin assignment supported + * <7> : USB 2.0 signaling (0b=yes, 1b=no) + * <6> : Plug | Receptacle (0b == plug, 1b == receptacle) + * <5:2> : xxx1: Supports DPv1.3, xx1x Supports USB Gen 2 signaling + * Other bits are reserved. + * <1:0> : signal direction ( 00b=rsv, 01b=sink, 10b=src 11b=both ) + */ +#define PD_DP_PIN_CAPS(x) ((((x) >> 6) & 0x1) ? (((x) >> 16) & 0x3f) \ + : (((x) >> 8) & 0x3f)) +#define PD_DP_SIGNAL_GEN2(x) (((x) >> 3) & 0x1) + +#define MODE_DP_PIN_A BIT(0) +#define MODE_DP_PIN_B BIT(1) +#define MODE_DP_PIN_C BIT(2) +#define MODE_DP_PIN_D BIT(3) +#define MODE_DP_PIN_E BIT(4) +#define MODE_DP_PIN_F BIT(5) + +/* Pin configs B/D/F support multi-function */ +#define MODE_DP_PIN_MF_MASK (MODE_DP_PIN_B | MODE_DP_PIN_D | MODE_DP_PIN_F) +/* Pin configs A/B support BR2 signaling levels */ +#define MODE_DP_PIN_BR2_MASK (MODE_DP_PIN_A | MODE_DP_PIN_B) +/* Pin configs C/D/E/F support DP signaling levels */ +#define MODE_DP_PIN_DP_MASK (MODE_DP_PIN_C | MODE_DP_PIN_D | \ + MODE_DP_PIN_E | MODE_DP_PIN_F) + +/* + * DisplayPort Status VDO + * ---------------------- + * <31:9> : Reserved (always 0). + * <8> : IRQ_HPD : 1 == irq arrived since last message otherwise 0. + * <7> : HPD state : 0 = HPD_LOW, 1 == HPD_HIGH + * <6> : Exit DP Alt mode: 0 == maintain, 1 == exit + * <5> : USB config : 0 == maintain current, 1 == switch to USB from DP + * <4> : Multi-function preference : 0 == no pref, 1 == MF preferred. + * <3> : enabled : is DPout on/off. + * <2> : power low : 0 == normal or LPM disabled, 1 == DP disabled for LPM + * <1:0> : connect status : 00b == no (DFP|UFP)_D is connected or disabled. + * 01b == DFP_D connected, 10b == UFP_D connected, 11b == both. + */ +#define PD_VDO_DPSTS_HPD_IRQ(x) (((x) >> 8) & 0x1) +#define PD_VDO_DPSTS_HPD_LVL(x) (((x) >> 7) & 0x1) +#define PD_VDO_DPSTS_MF_PREF(x) (((x) >> 4) & 0x1) + +static u8 fusb30x_port_used; +static struct fusb30x_chip *fusb30x_port_info[256]; + +static bool is_write_reg(struct device *dev, unsigned int reg) +{ + if (reg >= FUSB_REG_FIFO) + return true; + else + return ((reg < (FUSB_REG_CONTROL4 + 1)) && (reg > 0x01)) ? + true : false; +} + +static bool is_volatile_reg(struct device *dev, unsigned int reg) +{ + if (reg > FUSB_REG_CONTROL4) + return true; + + switch (reg) { + case FUSB_REG_CONTROL0: + case FUSB_REG_CONTROL1: + case FUSB_REG_CONTROL3: + case FUSB_REG_RESET: + return true; + } + return false; +} + +struct regmap_config fusb302_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = is_write_reg, + .volatile_reg = is_volatile_reg, + .max_register = FUSB302_MAX_REG, + .cache_type = REGCACHE_RBTREE, +}; + +static void dump_notify_info(struct fusb30x_chip *chip) +{ + dev_dbg(chip->dev, "port %d\n", chip->port_num); + dev_dbg(chip->dev, "orientation %d\n", chip->notify.orientation); + dev_dbg(chip->dev, "power_role %d\n", chip->notify.power_role); + dev_dbg(chip->dev, "data_role %d\n", chip->notify.data_role); + dev_dbg(chip->dev, "cc %d\n", chip->notify.is_cc_connected); + dev_dbg(chip->dev, "pd %d\n", chip->notify.is_pd_connected); + dev_dbg(chip->dev, "enter_mode %d\n", chip->notify.is_enter_mode); + dev_dbg(chip->dev, "pin support %d\n", + chip->notify.pin_assignment_support); + dev_dbg(chip->dev, "pin def %d\n", chip->notify.pin_assignment_def); + dev_dbg(chip->dev, "attention %d\n", chip->notify.attention); +} + +static const unsigned int fusb302_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_CDP, + EXTCON_CHG_USB_DCP, + EXTCON_CHG_USB_SLOW, + EXTCON_CHG_USB_FAST, + EXTCON_DISP_DP, + EXTCON_NONE, +}; + +static void fusb_set_pos_power(struct fusb30x_chip *chip, int max_vol, + int max_cur) +{ + int i; + int pos_find; + int tmp; + + pos_find = 0; + for (i = PD_HEADER_CNT(chip->rec_head) - 1; i >= 0; i--) { + switch (CAP_POWER_TYPE(chip->rec_load[i])) { + case 0: + /* Fixed Supply */ + if ((CAP_FPDO_VOLTAGE(chip->rec_load[i]) * 50) <= + max_vol && + (CAP_FPDO_CURRENT(chip->rec_load[i]) * 10) <= + max_cur) { + chip->pos_power = i + 1; + tmp = CAP_FPDO_VOLTAGE(chip->rec_load[i]); + chip->pd_output_vol = tmp * 50; + tmp = CAP_FPDO_CURRENT(chip->rec_load[i]); + chip->pd_output_cur = tmp * 10; + pos_find = 1; + } + break; + case 1: + /* Battery */ + if ((CAP_VPDO_VOLTAGE(chip->rec_load[i]) * 50) <= + max_vol && + (CAP_VPDO_CURRENT(chip->rec_load[i]) * 10) <= + max_cur) { + chip->pos_power = i + 1; + tmp = CAP_VPDO_VOLTAGE(chip->rec_load[i]); + chip->pd_output_vol = tmp * 50; + tmp = CAP_VPDO_CURRENT(chip->rec_load[i]); + chip->pd_output_cur = tmp * 10; + pos_find = 1; + } + break; + default: + /* not meet battery caps */ + break; + } + if (pos_find) + break; + } +} + +static int fusb302_set_pos_power_by_charge_ic(struct fusb30x_chip *chip) +{ + struct power_supply *psy = NULL; + union power_supply_propval val; + enum power_supply_property psp; + int max_vol, max_cur; + + max_vol = 0; + max_cur = 0; + psy = power_supply_get_by_phandle(chip->dev->of_node, "charge-dev"); + if (!psy || IS_ERR(psy)) + return -1; + + psp = POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX; + if (power_supply_get_property(psy, psp, &val) == 0) + max_vol = val.intval / 1000; + + psp = POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; + if (power_supply_get_property(psy, psp, &val) == 0) + max_cur = val.intval / 1000; + + if (max_vol > 0 && max_cur > 0) + fusb_set_pos_power(chip, max_vol, max_cur); + + return 0; +} + +void fusb_irq_disable(struct fusb30x_chip *chip) +{ + unsigned long irqflags = 0; + + spin_lock_irqsave(&chip->irq_lock, irqflags); + if (chip->enable_irq) { + disable_irq_nosync(chip->gpio_int_irq); + chip->enable_irq = 0; + } else { + dev_warn(chip->dev, "irq have already disabled\n"); + } + spin_unlock_irqrestore(&chip->irq_lock, irqflags); +} + +void fusb_irq_enable(struct fusb30x_chip *chip) +{ + unsigned long irqflags = 0; + + spin_lock_irqsave(&chip->irq_lock, irqflags); + if (!chip->enable_irq) { + enable_irq(chip->gpio_int_irq); + chip->enable_irq = 1; + } + spin_unlock_irqrestore(&chip->irq_lock, irqflags); +} + +static void platform_fusb_notify(struct fusb30x_chip *chip) +{ + bool plugged = false, flip = false, dfp = false, ufp = false, + dp = false, usb_ss = false, hpd = false; + union extcon_property_value property; + + if (chip->notify.is_cc_connected) + chip->notify.orientation = + (chip->cc_polarity == TYPEC_POLARITY_CC1) ? + CC1 : CC2; + + /* avoid notify repeated */ + if (memcmp(&chip->notify, &chip->notify_cmp, + sizeof(struct notify_info))) { + dump_notify_info(chip); + chip->notify.attention = false; + memcpy(&chip->notify_cmp, &chip->notify, + sizeof(struct notify_info)); + + plugged = chip->notify.is_cc_connected || + chip->notify.is_pd_connected; + if (chip->notify.orientation != NONE) + flip = (chip->notify.orientation == CC1) ? false : true; + dp = chip->notify.is_enter_mode; + + if (dp) { + dfp = true; + usb_ss = (chip->notify.pin_assignment_def & + MODE_DP_PIN_MF_MASK) ? true : false; + hpd = GET_DP_STATUS_HPD(chip->notify.dp_status); + } else if (chip->notify.data_role) { + dfp = true; + usb_ss = true; + } else if (plugged) { + ufp = true; + usb_ss = true; + } + + property.intval = flip; + extcon_set_property(chip->extcon, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY, property); + extcon_set_property(chip->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY, property); + extcon_set_property(chip->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_TYPEC_POLARITY, property); + + property.intval = usb_ss; + extcon_set_property(chip->extcon, EXTCON_USB, + EXTCON_PROP_USB_SS, property); + extcon_set_property(chip->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_SS, property); + extcon_set_property(chip->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_SS, property); + extcon_set_state(chip->extcon, EXTCON_USB, ufp); + extcon_set_state(chip->extcon, EXTCON_USB_HOST, dfp); + extcon_set_state(chip->extcon, EXTCON_DISP_DP, dp && hpd); + extcon_sync(chip->extcon, EXTCON_USB); + extcon_sync(chip->extcon, EXTCON_USB_HOST); + extcon_sync(chip->extcon, EXTCON_DISP_DP); + if (chip->notify.power_role == POWER_ROLE_SINK && + chip->notify.is_pd_connected && + chip->pd_output_vol > 0 && chip->pd_output_cur > 0) { + extcon_set_state(chip->extcon, EXTCON_CHG_USB_FAST, true); + property.intval = + (chip->pd_output_cur << 15 | + chip->pd_output_vol); + extcon_set_property(chip->extcon, EXTCON_CHG_USB_FAST, + EXTCON_PROP_USB_TYPEC_POLARITY, + property); + extcon_sync(chip->extcon, EXTCON_CHG_USB_FAST); + } + } +} + +static bool platform_get_device_irq_state(struct fusb30x_chip *chip) +{ + return !gpiod_get_value(chip->gpio_int); +} + +static void fusb_timer_start(struct hrtimer *timer, int ms) +{ + ktime_t ktime; + + ktime = ktime_set(0, FUSB_MS_TO_NS(ms)); + hrtimer_start(timer, ktime, HRTIMER_MODE_REL); +} + +static void platform_set_vbus_lvl_enable(struct fusb30x_chip *chip, int vbus_5v, + int vbus_other) +{ + bool gpio_vbus_value = false; + + gpio_vbus_value = gpiod_get_value(chip->gpio_vbus_5v); + if (chip->gpio_vbus_5v) { + gpiod_set_raw_value(chip->gpio_vbus_5v, vbus_5v); + } + + if (chip->gpio_vbus_other) + gpiod_set_raw_value(chip->gpio_vbus_5v, vbus_other); + + if (chip->gpio_discharge && !vbus_5v && gpio_vbus_value) { + gpiod_set_value(chip->gpio_discharge, 1); + msleep(20); + gpiod_set_value(chip->gpio_discharge, 0); + } +} + +static void set_state(struct fusb30x_chip *chip, enum connection_state state) +{ + dev_dbg(chip->dev, "port %d, state %d\n", chip->port_num, state); + if (!state) + dev_info(chip->dev, "PD disabled\n"); + chip->conn_state = state; + chip->sub_state = 0; + chip->val_tmp = 0; + chip->work_continue |= EVENT_WORK_CONTINUE; +} + +static int tcpm_get_message(struct fusb30x_chip *chip) +{ + u8 buf[32]; + int len; + + do { + regmap_raw_read(chip->regmap, FUSB_REG_FIFO, buf, 3); + chip->rec_head = (buf[1] & 0xff) | ((buf[2] << 8) & 0xff00); + + len = PD_HEADER_CNT(chip->rec_head) << 2; + regmap_raw_read(chip->regmap, FUSB_REG_FIFO, buf, len + 4); + /* ignore good_crc message */ + } while (PACKET_IS_CONTROL_MSG(chip->rec_head, CMT_GOODCRC)); + + memcpy(chip->rec_load, buf, len); + + return 0; +} + +static void fusb302_flush_rx_fifo(struct fusb30x_chip *chip) +{ + regmap_write(chip->regmap, FUSB_REG_CONTROL1, CONTROL1_RX_FLUSH); +} + +static int tcpm_get_cc(struct fusb30x_chip *chip, int *CC1, int *CC2) +{ + u32 val; + int *CC_MEASURE; + u32 store; + + *CC1 = TYPEC_CC_VOLT_OPEN; + *CC2 = TYPEC_CC_VOLT_OPEN; + + if (chip->cc_state & CC_STATE_TOGSS_CC1) + CC_MEASURE = CC1; + else + CC_MEASURE = CC2; + + if (chip->cc_state & CC_STATE_TOGSS_IS_UFP) { + regmap_read(chip->regmap, FUSB_REG_SWITCHES0, &store); + /* measure cc1 first */ + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES0, + SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2 | + SWITCHES0_PU_EN1 | SWITCHES0_PU_EN2 | + SWITCHES0_PDWN1 | SWITCHES0_PDWN2, + SWITCHES0_PDWN1 | SWITCHES0_PDWN2 | + SWITCHES0_MEAS_CC1); + usleep_range(250, 300); + + regmap_read(chip->regmap, FUSB_REG_STATUS0, &val); + val &= STATUS0_BC_LVL; + *CC1 = val ? TYPEC_CC_VOLT_RP : TYPEC_CC_VOLT_OPEN; + + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES0, + SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2 | + SWITCHES0_PU_EN1 | SWITCHES0_PU_EN2 | + SWITCHES0_PDWN1 | SWITCHES0_PDWN2, + SWITCHES0_PDWN1 | SWITCHES0_PDWN2 | + SWITCHES0_MEAS_CC2); + usleep_range(250, 300); + + regmap_read(chip->regmap, FUSB_REG_STATUS0, &val); + val &= STATUS0_BC_LVL; + *CC2 = val ? TYPEC_CC_VOLT_RP : TYPEC_CC_VOLT_OPEN; + + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES0, + SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2, + store); + } else { + regmap_read(chip->regmap, FUSB_REG_SWITCHES0, &store); + val = store; + val &= ~(SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2 | + SWITCHES0_PU_EN1 | SWITCHES0_PU_EN2); + if (chip->cc_state & CC_STATE_TOGSS_CC1) { + val |= SWITCHES0_MEAS_CC1 | SWITCHES0_PU_EN1; + } else { + val |= SWITCHES0_MEAS_CC2 | SWITCHES0_PU_EN2; + } + regmap_write(chip->regmap, FUSB_REG_SWITCHES0, val); + + regmap_write(chip->regmap, FUSB_REG_MEASURE, chip->cc_meas_high); + usleep_range(250, 300); + + regmap_read(chip->regmap, FUSB_REG_STATUS0, &val); + if (val & STATUS0_COMP) { + int retry = 3; + int comp_times = 0; + + while (retry--) { + regmap_write(chip->regmap, FUSB_REG_MEASURE, chip->cc_meas_high); + usleep_range(250, 300); + regmap_read(chip->regmap, FUSB_REG_STATUS0, &val); + if (val & STATUS0_COMP) { + comp_times++; + if (comp_times == 3) { + *CC_MEASURE = TYPEC_CC_VOLT_OPEN; + regmap_write(chip->regmap, FUSB_REG_SWITCHES0, store); + } + } + } + } else { + regmap_write(chip->regmap, FUSB_REG_MEASURE, chip->cc_meas_low); + regmap_read(chip->regmap, FUSB_REG_MEASURE, &val); + usleep_range(250, 300); + + regmap_read(chip->regmap, FUSB_REG_STATUS0, &val); + + if (val & STATUS0_COMP) + *CC_MEASURE = TYPEC_CC_VOLT_RD; + else + *CC_MEASURE = TYPEC_CC_VOLT_RA; + } + regmap_write(chip->regmap, FUSB_REG_SWITCHES0, store); + regmap_write(chip->regmap, FUSB_REG_MEASURE, + chip->cc_meas_high); + } + + return 0; +} + +static void tcpm_set_cc_pull_mode(struct fusb30x_chip *chip, enum CC_MODE mode) +{ + u8 val; + + switch (mode) { + case CC_PULL_UP: + if (chip->cc_polarity == TYPEC_POLARITY_CC1) + val = SWITCHES0_PU_EN1; + else + val = SWITCHES0_PU_EN2; + break; + case CC_PULL_DOWN: + val = SWITCHES0_PDWN1 | SWITCHES0_PDWN2; + break; + default: + val = 0; + break; + } + + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES0, + SWITCHES0_PU_EN1 | SWITCHES0_PU_EN2 | + SWITCHES0_PDWN1 | SWITCHES0_PDWN2, + val); + + if (chip->cc_meas_high && mode == CC_PULL_UP) + regmap_write(chip->regmap, FUSB_REG_MEASURE, + chip->cc_meas_high); +} + +static int tcpm_set_cc(struct fusb30x_chip *chip, enum role_mode mode) +{ + switch (mode) { + case ROLE_MODE_DFP: + tcpm_set_cc_pull_mode(chip, CC_PULL_UP); + regmap_update_bits(chip->regmap, FUSB_REG_CONTROL2, + CONTROL2_MODE | CONTROL2_TOG_RD_ONLY, + CONTROL2_MODE_DFP | CONTROL2_TOG_RD_ONLY); + break; + case ROLE_MODE_UFP: + tcpm_set_cc_pull_mode(chip, CC_PULL_UP); + regmap_update_bits(chip->regmap, FUSB_REG_CONTROL2, + CONTROL2_MODE | CONTROL2_TOG_RD_ONLY, + CONTROL2_MODE_UFP); + break; + case ROLE_MODE_DRP: + tcpm_set_cc_pull_mode(chip, CC_PULL_NONE); + regmap_update_bits(chip->regmap, FUSB_REG_CONTROL2, + CONTROL2_MODE | CONTROL2_TOG_RD_ONLY, + CONTROL2_MODE_DRP | CONTROL2_TOG_RD_ONLY); + break; + default: + dev_err(chip->dev, "%s: Unsupport cc mode %d\n", + __func__, mode); + return -EINVAL; + break; + } + + regmap_update_bits(chip->regmap, FUSB_REG_CONTROL2, CONTROL2_TOGGLE, + CONTROL2_TOGGLE); + + return 0; +} + +static int tcpm_set_rx_enable(struct fusb30x_chip *chip, int enable) +{ + u8 val = 0; + + if (enable) { + if (chip->cc_polarity == TYPEC_POLARITY_CC1) + val |= SWITCHES0_MEAS_CC1; + else + val |= SWITCHES0_MEAS_CC2; + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES0, + SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2, + val); + fusb302_flush_rx_fifo(chip); + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES1, + SWITCHES1_AUTO_CRC, SWITCHES1_AUTO_CRC); + } else { + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES0, + SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2, + 0); + regmap_update_bits(chip->regmap, + FUSB_REG_SWITCHES1, SWITCHES1_AUTO_CRC, 0); + } + + return 0; +} + +static int tcpm_set_msg_header(struct fusb30x_chip *chip) +{ + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES1, + SWITCHES1_POWERROLE | SWITCHES1_DATAROLE, + (chip->notify.power_role << 7) | + (chip->notify.data_role << 4)); + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES1, + SWITCHES1_SPECREV, 2 << 5); + return 0; +} + +static int tcpm_set_polarity(struct fusb30x_chip *chip, + enum typec_cc_polarity polarity) +{ + u8 val = 0; + + if (chip->vconn_enabled) { + if (polarity) + val |= SWITCHES0_VCONN_CC1; + else + val |= SWITCHES0_VCONN_CC2; + } + + if (chip->cc_state & CC_STATE_TOGSS_IS_UFP) { + if (polarity == TYPEC_POLARITY_CC1) + val |= SWITCHES0_MEAS_CC1; + else + val |= SWITCHES0_MEAS_CC2; + } else { + if (polarity == TYPEC_POLARITY_CC1) + val |= SWITCHES0_MEAS_CC1 | SWITCHES0_PU_EN1; + else + val |= SWITCHES0_MEAS_CC2 | SWITCHES0_PU_EN2; + } + + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES0, + SWITCHES0_VCONN_CC1 | SWITCHES0_VCONN_CC2 | + SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2 | + SWITCHES0_PU_EN1 | SWITCHES0_PU_EN2, + val); + + val = 0; + if (polarity == TYPEC_POLARITY_CC1) + val |= SWITCHES1_TXCC1; + else + val |= SWITCHES1_TXCC2; + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES1, + SWITCHES1_TXCC1 | SWITCHES1_TXCC2, + val); + + chip->cc_polarity = polarity; + + return 0; +} + +static int tcpm_set_vconn(struct fusb30x_chip *chip, int enable) +{ + u8 val = 0; + + if (enable) { + if (chip->cc_polarity == TYPEC_POLARITY_CC1) + val = SWITCHES0_VCONN_CC2; + else + val = SWITCHES0_VCONN_CC1; + } + regmap_update_bits(chip->regmap, FUSB_REG_SWITCHES0, + SWITCHES0_VCONN_CC1 | SWITCHES0_VCONN_CC2, + val); + chip->vconn_enabled = (bool)enable; + return 0; +} + +static void fusb302_pd_reset(struct fusb30x_chip *chip) +{ + regmap_write(chip->regmap, FUSB_REG_RESET, RESET_PD_RESET); + regmap_reinit_cache(chip->regmap, &fusb302_regmap_config); +} + +static void tcpm_select_rp_value(struct fusb30x_chip *chip, u32 rp) +{ + u32 control0_reg; + + regmap_read(chip->regmap, FUSB_REG_CONTROL0, &control0_reg); + + control0_reg &= ~CONTROL0_HOST_CUR; + /* + * according to the host current, the compare value is different + * Fusb302 datasheet Table 3 + */ + switch (rp) { + /* + * host pull up current is 80ua , high voltage is 1.596v, + * low is 0.21v + */ + case TYPEC_RP_USB: + chip->cc_meas_high = 0x26; + chip->cc_meas_low = 0x5; + control0_reg |= CONTROL0_HOST_CUR_USB; + break; + /* + * host pull up current is 330ua , high voltage is 2.604v, + * low is 0.798v + */ + case TYPEC_RP_3A0: + chip->cc_meas_high = 0x3e; + chip->cc_meas_low = 0x13; + control0_reg |= CONTROL0_HOST_CUR_3A0; + break; + /* + * host pull up current is 180ua , high voltage is 1.596v, + * low is 0.42v + */ + case TYPEC_RP_1A5: + default: + chip->cc_meas_high = 0x26; + chip->cc_meas_low = 0xa; + control0_reg |= CONTROL0_HOST_CUR_1A5; + break; + } + + regmap_write(chip->regmap, FUSB_REG_CONTROL0, control0_reg); +} + +static int tcpm_check_vbus(struct fusb30x_chip *chip) +{ + u32 val; + + /* Read status register */ + regmap_read(chip->regmap, FUSB_REG_STATUS0, &val); + + return (val & STATUS0_VBUSOK) ? 1 : 0; +} + +static void tcpm_init(struct fusb30x_chip *chip) +{ + u8 val; + u32 tmp; + + regmap_read(chip->regmap, FUSB_REG_DEVICEID, &tmp); + chip->chip_id = (u8)tmp; + platform_set_vbus_lvl_enable(chip, 0, 0); + chip->notify.is_cc_connected = false; + chip->cc_state = 0; + + /* restore default settings */ + regmap_update_bits(chip->regmap, FUSB_REG_RESET, RESET_SW_RESET, + RESET_SW_RESET); + fusb302_pd_reset(chip); + /* set auto_retry and number of retries */ + regmap_update_bits(chip->regmap, FUSB_REG_CONTROL3, + CONTROL3_AUTO_RETRY | CONTROL3_N_RETRIES, + CONTROL3_AUTO_RETRY | CONTROL3_N_RETRIES), + + /* set interrupts */ + val = 0xff; + val &= ~(MASK_M_COLLISION | MASK_M_ALERT | MASK_M_VBUSOK); + regmap_write(chip->regmap, FUSB_REG_MASK, val); + + val = 0xff; + val &= ~(MASKA_M_RETRYFAIL | MASKA_M_HARDSENT | MASKA_M_TXSENT | + MASKA_M_HARDRST | MASKA_M_TOGDONE); + regmap_write(chip->regmap, FUSB_REG_MASKA, val); + + val = ~MASKB_M_GCRCSEND; + regmap_write(chip->regmap, FUSB_REG_MASKB, val); + + tcpm_select_rp_value(chip, TYPEC_RP_1A5); + /* Interrupts Enable */ + regmap_update_bits(chip->regmap, FUSB_REG_CONTROL0, CONTROL0_INT_MASK, + ~CONTROL0_INT_MASK); + + tcpm_set_vconn(chip, 0); + + regmap_write(chip->regmap, FUSB_REG_POWER, 0xf); +} + +static void pd_execute_hard_reset(struct fusb30x_chip *chip) +{ + chip->msg_id = 0; + chip->vdm_state = VDM_STATE_DISCOVERY_ID; + if (chip->notify.power_role) + set_state(chip, policy_src_transition_default); + else + set_state(chip, policy_snk_transition_default); +} + +static void tcpc_alert(struct fusb30x_chip *chip, u32 *evt) +{ + int interrupt, interrupta, interruptb; + u32 val; + static int retry; + + regmap_read(chip->regmap, FUSB_REG_INTERRUPT, &interrupt); + regmap_read(chip->regmap, FUSB_REG_INTERRUPTA, &interrupta); + regmap_read(chip->regmap, FUSB_REG_INTERRUPTB, &interruptb); + + if ((interrupt & INTERRUPT_COMP_CHNG) && + (!(chip->cc_state & CC_STATE_TOGSS_IS_UFP))) { + regmap_read(chip->regmap, FUSB_REG_STATUS0, &val); + if (val & STATUS0_COMP) + *evt |= EVENT_CC; + } + + if (interrupt & INTERRUPT_VBUSOK) { + if (chip->notify.is_cc_connected) + *evt |= EVENT_CC; + } + + if (interrupta & INTERRUPTA_TOGDONE) { + *evt |= EVENT_CC; + regmap_read(chip->regmap, FUSB_REG_STATUS1A, &val); + chip->cc_state = ((u8)val >> 3) & 0x07; + + regmap_update_bits(chip->regmap, FUSB_REG_CONTROL2, + CONTROL2_TOGGLE, + 0); + } + + if (interrupta & INTERRUPTA_TXSENT) { + *evt |= EVENT_TX; + chip->tx_state = tx_success; + } + + if (interruptb & INTERRUPTB_GCRCSENT) + *evt |= EVENT_RX; + + if (interrupta & INTERRUPTA_HARDRST) { + fusb302_pd_reset(chip); + pd_execute_hard_reset(chip); + *evt |= EVENT_REC_RESET; + } + + if (interrupta & INTERRUPTA_RETRYFAIL) { + *evt |= EVENT_TX; + chip->tx_state = tx_failed; + } + + if (interrupta & INTERRUPTA_HARDSENT) { + /* + * The fusb PD should be reset once to sync adapter PD + * signal after fusb sent hard reset cmd.This is not PD + * device if reset failed. + */ + if (!retry) { + retry = 1; + fusb302_pd_reset(chip); + pd_execute_hard_reset(chip); + } else { + retry = 0; + chip->tx_state = tx_success; + chip->timer_state = T_DISABLED; + *evt |= EVENT_TX; + } + } +} + +static void mux_alert(struct fusb30x_chip *chip, u32 *evt) +{ + if (!chip->timer_mux) { + *evt |= EVENT_TIMER_MUX; + chip->timer_mux = T_DISABLED; + } + + if (!chip->timer_state) { + *evt |= EVENT_TIMER_STATE; + chip->timer_state = T_DISABLED; + } + + if (chip->work_continue) { + *evt |= chip->work_continue; + chip->work_continue = 0; + } +} + +static void set_state_unattached(struct fusb30x_chip *chip) +{ + dev_info(chip->dev, "connection has disconnected\n"); + tcpm_init(chip); + tcpm_set_rx_enable(chip, 0); + set_state(chip, unattached); + tcpm_set_cc(chip, chip->role); + + /* claer notify_info */ + memset(&chip->notify, 0, sizeof(struct notify_info)); + platform_fusb_notify(chip); + + if (chip->gpio_discharge) + gpiod_set_value(chip->gpio_discharge, 1); + msleep(100); + if (chip->gpio_discharge) + gpiod_set_value(chip->gpio_discharge, 0); + + regmap_update_bits(chip->regmap, FUSB_REG_MASK, + MASK_M_COMP_CHNG, MASK_M_COMP_CHNG); + chip->try_role_complete = false; +} + +static void set_mesg(struct fusb30x_chip *chip, int cmd, int is_DMT) +{ + int i; + struct PD_CAP_INFO *pd_cap_info = &chip->pd_cap_info; + + chip->send_head = ((chip->msg_id & 0x7) << 9) | + ((chip->notify.power_role & 0x1) << 8) | + (1 << 6) | + ((chip->notify.data_role & 0x1) << 5); + + if (is_DMT) { + switch (cmd) { + case DMT_SOURCECAPABILITIES: + chip->send_head |= ((chip->n_caps_used & 0x3) << 12) | (cmd & 0xf); + + for (i = 0; i < chip->n_caps_used; i++) { + chip->send_load[i] = (pd_cap_info->supply_type << 30) | + (pd_cap_info->dual_role_power << 29) | + (pd_cap_info->usb_suspend_support << 28) | + (pd_cap_info->externally_powered << 27) | + (pd_cap_info->usb_communications_cap << 26) | + (pd_cap_info->data_role_swap << 25) | + (pd_cap_info->peak_current << 20) | + (chip->source_power_supply[i] << 10) | + (chip->source_max_current[i]); + } + break; + case DMT_REQUEST: + chip->send_head |= ((1 << 12) | (cmd & 0xf)); + /* send request with FVRDO */ + chip->send_load[0] = (chip->pos_power << 28) | + (0 << 27) | + (1 << 26) | + (0 << 25) | + (0 << 24); + + switch (CAP_POWER_TYPE(chip->rec_load[chip->pos_power - 1])) { + case 0: + /* Fixed Supply */ + chip->send_load[0] |= ((CAP_FPDO_VOLTAGE(chip->rec_load[chip->pos_power - 1]) << 10) & 0x3ff); + chip->send_load[0] |= (CAP_FPDO_CURRENT(chip->rec_load[chip->pos_power - 1]) & 0x3ff); + break; + case 1: + /* Battery */ + chip->send_load[0] |= ((CAP_VPDO_VOLTAGE(chip->rec_load[chip->pos_power - 1]) << 10) & 0x3ff); + chip->send_load[0] |= (CAP_VPDO_CURRENT(chip->rec_load[chip->pos_power - 1]) & 0x3ff); + break; + default: + /* not meet battery caps */ + break; + } + break; + case DMT_SINKCAPABILITIES: + break; + case DMT_VENDERDEFINED: + break; + default: + break; + } + } else { + chip->send_head |= (cmd & 0xf); + } +} + +/* + * This algorithm defaults to choosing higher pin config over lower ones in + * order to prefer multi-function if desired. + * + * NAME | SIGNALING | OUTPUT TYPE | MULTI-FUNCTION | PIN CONFIG + * ------------------------------------------------------------- + * A | USB G2 | ? | no | 00_0001 + * B | USB G2 | ? | yes | 00_0010 + * C | DP | CONVERTED | no | 00_0100 + * D | PD | CONVERTED | yes | 00_1000 + * E | DP | DP | no | 01_0000 + * F | PD | DP | yes | 10_0000 + * + * if UFP has NOT asserted multi-function preferred code masks away B/D/F + * leaving only A/C/E. For single-output dongles that should leave only one + * possible pin config depending on whether its a converter DP->(VGA|HDMI) or DP + * output. If UFP is a USB-C receptacle it may assert C/D/E/F. The DFP USB-C + * receptacle must always choose C/D in those cases. + */ +static int pd_dfp_dp_get_pin_assignment(struct fusb30x_chip *chip, + uint32_t caps, uint32_t status) +{ + uint32_t pin_caps; + + /* revisit with DFP that can be a sink */ + pin_caps = PD_DP_PIN_CAPS(caps); + + /* if don't want multi-function then ignore those pin configs */ + if (!PD_VDO_DPSTS_MF_PREF(status)) + pin_caps &= ~MODE_DP_PIN_MF_MASK; + + /* revisit if DFP drives USB Gen 2 signals */ + if (PD_DP_SIGNAL_GEN2(caps)) + pin_caps &= ~MODE_DP_PIN_DP_MASK; + else + pin_caps &= ~MODE_DP_PIN_BR2_MASK; + + /* if C/D present they have precedence over E/F for USB-C->USB-C */ + if (pin_caps & (MODE_DP_PIN_C | MODE_DP_PIN_D)) + pin_caps &= ~(MODE_DP_PIN_E | MODE_DP_PIN_F); + + /* returns undefined for zero */ + if (!pin_caps) + return 0; + + /* choosing higher pin config over lower ones */ + return 1 << (31 - __builtin_clz(pin_caps)); +} + +static void set_vdm_mesg(struct fusb30x_chip *chip, int cmd, int type, int mode) +{ + chip->send_head = (chip->msg_id & 0x7) << 9; + chip->send_head |= (chip->notify.power_role & 0x1) << 8; + + chip->send_head = ((chip->msg_id & 0x7) << 9) | + ((chip->notify.power_role & 0x1) << 8) | + (1 << 6) | + ((chip->notify.data_role & 0x1) << 5) | + (DMT_VENDERDEFINED & 0xf); + + chip->send_load[0] = (1 << 15) | + (0 << 13) | + (type << 6) | + (cmd); + + switch (cmd) { + case VDM_DISCOVERY_ID: + case VDM_DISCOVERY_SVIDS: + case VDM_ATTENTION: + chip->send_load[0] |= (0xff00 << 16); + chip->send_head |= (1 << 12); + break; + case VDM_DISCOVERY_MODES: + chip->send_load[0] |= + (chip->vdm_svid[chip->val_tmp >> 1] << 16); + chip->send_head |= (1 << 12); + break; + case VDM_ENTER_MODE: + chip->send_head |= (1 << 12); + chip->send_load[0] |= (mode << 8) | (0xff01 << 16); + break; + case VDM_EXIT_MODE: + chip->send_head |= (1 << 12); + chip->send_load[0] |= (0x0f << 8) | (0xff01 << 16); + break; + case VDM_DP_STATUS_UPDATE: + chip->send_head |= (2 << 12); + chip->send_load[0] |= (1 << 8) | (0xff01 << 16); + chip->send_load[1] = 5; + break; + case VDM_DP_CONFIG: + chip->send_head |= (2 << 12); + chip->send_load[0] |= (1 << 8) | (0xff01 << 16); + + chip->notify.pin_assignment_def = + pd_dfp_dp_get_pin_assignment(chip, chip->notify.dp_caps, + chip->notify.dp_status); + + chip->send_load[1] = (chip->notify.pin_assignment_def << 8) | + (1 << 2) | 2; + dev_dbg(chip->dev, "DisplayPort Configurations: 0x%08x\n", + chip->send_load[1]); + break; + default: + break; + } +} + +static enum tx_state policy_send_hardrst(struct fusb30x_chip *chip, u32 evt) +{ + switch (chip->tx_state) { + case 0: + regmap_update_bits(chip->regmap, FUSB_REG_CONTROL3, + CONTROL3_SEND_HARDRESET, + CONTROL3_SEND_HARDRESET); + chip->tx_state = tx_busy; + chip->timer_state = T_BMC_TIMEOUT; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + break; + default: + if (evt & EVENT_TIMER_STATE) + chip->tx_state = tx_success; + break; + } + return chip->tx_state; +} + +static enum tx_state policy_send_data(struct fusb30x_chip *chip) +{ + u8 senddata[40]; + int pos = 0; + u8 len; + + switch (chip->tx_state) { + case 0: + senddata[pos++] = FUSB_TKN_SYNC1; + senddata[pos++] = FUSB_TKN_SYNC1; + senddata[pos++] = FUSB_TKN_SYNC1; + senddata[pos++] = FUSB_TKN_SYNC2; + + len = PD_HEADER_CNT(chip->send_head) << 2; + senddata[pos++] = FUSB_TKN_PACKSYM | ((len + 2) & 0x1f); + + senddata[pos++] = chip->send_head & 0xff; + senddata[pos++] = (chip->send_head >> 8) & 0xff; + + memcpy(&senddata[pos], chip->send_load, len); + pos += len; + + senddata[pos++] = FUSB_TKN_JAMCRC; + senddata[pos++] = FUSB_TKN_EOP; + senddata[pos++] = FUSB_TKN_TXOFF; + senddata[pos++] = FUSB_TKN_TXON; + + regmap_raw_write(chip->regmap, FUSB_REG_FIFO, senddata, pos); + chip->tx_state = tx_busy; + break; + + default: + /* wait Tx result */ + break; + } + + return chip->tx_state; +} + +static void process_vdm_msg(struct fusb30x_chip *chip) +{ + u32 vdm_header = chip->rec_load[0]; + int i; + u32 tmp; + + /* can't procee unstructed vdm msg */ + if (!GET_VDMHEAD_STRUCT_TYPE(vdm_header)) { + dev_warn(chip->dev, "unknown unstructed vdm message\n"); + return; + } + + switch (GET_VDMHEAD_CMD_TYPE(vdm_header)) { + case VDM_TYPE_INIT: + switch (GET_VDMHEAD_CMD(vdm_header)) { + case VDM_ATTENTION: + chip->notify.dp_status = GET_DP_STATUS(chip->rec_load[1]); + dev_info(chip->dev, "attention, dp_status %x\n", + chip->rec_load[1]); + chip->notify.attention = true; + platform_fusb_notify(chip); + break; + default: + dev_warn(chip->dev, "rec unknown init vdm msg\n"); + break; + } + break; + case VDM_TYPE_ACK: + switch (GET_VDMHEAD_CMD(vdm_header)) { + case VDM_DISCOVERY_ID: + chip->vdm_id = chip->rec_load[1]; + break; + case VDM_DISCOVERY_SVIDS: + for (i = 0; i < 6; i++) { + tmp = (chip->rec_load[i + 1] >> 16) & + 0x0000ffff; + if (tmp) { + chip->vdm_svid[i * 2] = tmp; + chip->vdm_svid_num++; + } else { + break; + } + + tmp = (chip->rec_load[i + 1] & 0x0000ffff); + if (tmp) { + chip->vdm_svid[i * 2 + 1] = tmp; + chip->vdm_svid_num++; + } else { + break; + } + } + break; + case VDM_DISCOVERY_MODES: + /* indicate there are some vdo modes */ + if (PD_HEADER_CNT(chip->rec_head) > 1) { + /* + * store mode config, + * enter first mode default + */ + tmp = chip->rec_load[1]; + + if ((!((tmp >> 8) & 0x3f)) && + (!((tmp >> 16) & 0x3f))) { + chip->val_tmp |= 1; + break; + } + chip->notify.dp_caps = chip->rec_load[1]; + chip->notify.pin_assignment_def = 0; + chip->notify.pin_assignment_support = + PD_DP_PIN_CAPS(tmp); + chip->val_tmp |= 1; + dev_dbg(chip->dev, + "DisplayPort Capabilities: 0x%08x\n", + chip->rec_load[1]); + } + break; + case VDM_ENTER_MODE: + chip->val_tmp = 1; + break; + case VDM_DP_STATUS_UPDATE: + chip->notify.dp_status = GET_DP_STATUS(chip->rec_load[1]); + dev_dbg(chip->dev, "DisplayPort Status: 0x%08x\n", + chip->rec_load[1]); + chip->val_tmp = 1; + break; + case VDM_DP_CONFIG: + chip->val_tmp = 1; + dev_info(chip->dev, + "DP config successful, pin_assignment 0x%x\n", + chip->notify.pin_assignment_def); + chip->notify.is_enter_mode = true; + break; + default: + break; + } + break; + case VDM_TYPE_NACK: + dev_warn(chip->dev, "REC NACK for 0x%x\n", + GET_VDMHEAD_CMD(vdm_header)); + /* disable vdm */ + chip->vdm_state = VDM_STATE_ERR; + break; + } +} + +static int vdm_send_discoveryid(struct fusb30x_chip *chip, u32 evt) +{ + int tmp; + + switch (chip->vdm_send_state) { + case 0: + set_vdm_mesg(chip, VDM_DISCOVERY_ID, VDM_TYPE_INIT, 0); + chip->vdm_id = 0; + chip->tx_state = 0; + chip->vdm_send_state++; + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->vdm_send_state++; + chip->timer_state = T_SENDER_RESPONSE; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + dev_warn(chip->dev, "VDM_DISCOVERY_ID send failed\n"); + /* disable auto_vdm_machine */ + chip->vdm_state = VDM_STATE_ERR; + return -EPIPE; + } + + if (chip->vdm_send_state != 2) + break; + default: + if (chip->vdm_id) { + chip->vdm_send_state = 0; + return 0; + } else if (evt & EVENT_TIMER_STATE) { + dev_warn(chip->dev, "VDM_DISCOVERY_ID time out\n"); + chip->vdm_state = VDM_STATE_ERR; + chip->work_continue |= EVENT_WORK_CONTINUE; + return -ETIMEDOUT; + } + break; + } + return -EINPROGRESS; +} + +static int vdm_send_discoverysvid(struct fusb30x_chip *chip, u32 evt) +{ + int tmp; + + switch (chip->vdm_send_state) { + case 0: + set_vdm_mesg(chip, VDM_DISCOVERY_SVIDS, VDM_TYPE_INIT, 0); + memset(chip->vdm_svid, 0, sizeof(chip->vdm_svid)); + chip->vdm_svid_num = 0; + chip->tx_state = 0; + chip->vdm_send_state++; + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->vdm_send_state++; + chip->timer_state = T_SENDER_RESPONSE; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + dev_warn(chip->dev, "VDM_DISCOVERY_SVIDS send failed\n"); + /* disable auto_vdm_machine */ + chip->vdm_state = VDM_STATE_ERR; + return -EPIPE; + } + + if (chip->vdm_send_state != 2) + break; + default: + if (chip->vdm_svid_num) { + chip->vdm_send_state = 0; + return 0; + } else if (evt & EVENT_TIMER_STATE) { + dev_warn(chip->dev, "VDM_DISCOVERY_SVIDS time out\n"); + chip->vdm_state = VDM_STATE_ERR; + chip->work_continue |= EVENT_WORK_CONTINUE; + return -ETIMEDOUT; + } + break; + } + return -EINPROGRESS; +} + +static int vdm_send_discoverymodes(struct fusb30x_chip *chip, u32 evt) +{ + int tmp; + + if ((chip->val_tmp >> 1) != chip->vdm_svid_num) { + switch (chip->vdm_send_state) { + case 0: + set_vdm_mesg(chip, VDM_DISCOVERY_MODES, + VDM_TYPE_INIT, 0); + chip->tx_state = 0; + chip->vdm_send_state++; + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->vdm_send_state++; + chip->timer_state = T_SENDER_RESPONSE; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + dev_warn(chip->dev, + "VDM_DISCOVERY_MODES send failed\n"); + chip->vdm_state = VDM_STATE_ERR; + return -EPIPE; + } + + if (chip->vdm_send_state != 2) + break; + default: + if (chip->val_tmp & 1) { + chip->val_tmp &= 0xfe; + chip->val_tmp += 2; + chip->vdm_send_state = 0; + chip->work_continue |= EVENT_WORK_CONTINUE; + } else if (evt & EVENT_TIMER_STATE) { + dev_warn(chip->dev, + "VDM_DISCOVERY_MODES time out\n"); + chip->vdm_state = VDM_STATE_ERR; + chip->work_continue |= EVENT_WORK_CONTINUE; + return -ETIMEDOUT; + } + break; + } + } else { + chip->val_tmp = 0; + return 0; + } + + return -EINPROGRESS; +} + +static int vdm_send_entermode(struct fusb30x_chip *chip, u32 evt) +{ + int tmp; + + switch (chip->vdm_send_state) { + case 0: + set_vdm_mesg(chip, VDM_ENTER_MODE, VDM_TYPE_INIT, 1); + chip->tx_state = 0; + chip->vdm_send_state++; + chip->notify.is_enter_mode = false; + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->vdm_send_state++; + chip->timer_state = T_SENDER_RESPONSE; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + dev_warn(chip->dev, "VDM_ENTER_MODE send failed\n"); + /* disable auto_vdm_machine */ + chip->vdm_state = VDM_STATE_ERR; + return -EPIPE; + } + + if (chip->vdm_send_state != 2) + break; + default: + if (chip->val_tmp) { + chip->val_tmp = 0; + chip->vdm_send_state = 0; + return 0; + } else if (evt & EVENT_TIMER_STATE) { + dev_warn(chip->dev, "VDM_ENTER_MODE time out\n"); + chip->vdm_state = VDM_STATE_ERR; + chip->work_continue |= EVENT_WORK_CONTINUE; + return -ETIMEDOUT; + } + break; + } + return -EINPROGRESS; +} + +static int vdm_send_getdpstatus(struct fusb30x_chip *chip, u32 evt) +{ + int tmp; + + switch (chip->vdm_send_state) { + case 0: + set_vdm_mesg(chip, VDM_DP_STATUS_UPDATE, VDM_TYPE_INIT, 1); + chip->tx_state = 0; + chip->vdm_send_state++; + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->vdm_send_state++; + chip->timer_state = T_SENDER_RESPONSE; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + dev_warn(chip->dev, + "VDM_DP_STATUS_UPDATE send failed\n"); + /* disable auto_vdm_machine */ + chip->vdm_state = VDM_STATE_ERR; + return -EPIPE; + } + + if (chip->vdm_send_state != 2) + break; + default: + if (chip->val_tmp) { + chip->val_tmp = 0; + chip->vdm_send_state = 0; + return 0; + } else if (evt & EVENT_TIMER_STATE) { + dev_warn(chip->dev, "VDM_DP_STATUS_UPDATE time out\n"); + chip->vdm_state = VDM_STATE_ERR; + chip->work_continue |= EVENT_WORK_CONTINUE; + return -ETIMEDOUT; + } + break; + } + return -EINPROGRESS; +} + +static int vdm_send_dpconfig(struct fusb30x_chip *chip, u32 evt) +{ + int tmp; + + switch (chip->vdm_send_state) { + case 0: + set_vdm_mesg(chip, VDM_DP_CONFIG, VDM_TYPE_INIT, 0); + chip->tx_state = 0; + chip->vdm_send_state++; + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->vdm_send_state++; + chip->timer_state = T_SENDER_RESPONSE; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + dev_warn(chip->dev, "vdm_send_dpconfig send failed\n"); + /* disable auto_vdm_machine */ + chip->vdm_state = VDM_STATE_ERR; + return -EPIPE; + } + + if (chip->vdm_send_state != 2) + break; + default: + if (chip->val_tmp) { + chip->val_tmp = 0; + chip->vdm_send_state = 0; + return 0; + } else if (evt & EVENT_TIMER_STATE) { + dev_warn(chip->dev, "vdm_send_dpconfig time out\n"); + chip->vdm_state = VDM_STATE_ERR; + chip->work_continue |= EVENT_WORK_CONTINUE; + return -ETIMEDOUT; + } + break; + } + return -EINPROGRESS; +} + +/* without break if success */ +#define AUTO_VDM_HANDLE(func, chip, evt, conditions) \ +do { \ + conditions = func(chip, evt); \ + if (!conditions) { \ + chip->vdm_state++; \ + chip->work_continue |= EVENT_WORK_CONTINUE; \ + } else { \ + if (conditions != -EINPROGRESS) \ + chip->vdm_state = VDM_STATE_ERR; \ + } \ +} while (0) + +static void auto_vdm_machine(struct fusb30x_chip *chip, u32 evt) +{ + int conditions; + + switch (chip->vdm_state) { + case VDM_STATE_DISCOVERY_ID: + AUTO_VDM_HANDLE(vdm_send_discoveryid, chip, evt, conditions); + break; + case VDM_STATE_DISCOVERY_SVID: + AUTO_VDM_HANDLE(vdm_send_discoverysvid, chip, evt, conditions); + break; + case VDM_STATE_DISCOVERY_MODES: + AUTO_VDM_HANDLE(vdm_send_discoverymodes, chip, evt, conditions); + break; + case VDM_STATE_ENTER_MODE: + AUTO_VDM_HANDLE(vdm_send_entermode, chip, evt, conditions); + break; + case VDM_STATE_UPDATE_STATUS: + AUTO_VDM_HANDLE(vdm_send_getdpstatus, chip, evt, conditions); + break; + case VDM_STATE_DP_CONFIG: + AUTO_VDM_HANDLE(vdm_send_dpconfig, chip, evt, conditions); + break; + case VDM_STATE_NOTIFY: + platform_fusb_notify(chip); + chip->vdm_state = VDM_STATE_READY; + break; + default: + break; + } +} + +static void fusb_state_disabled(struct fusb30x_chip *chip, u32 evt) +{ + /* Do nothing */ +} + +static void fusb_state_unattached(struct fusb30x_chip *chip, u32 evt) +{ + chip->notify.is_cc_connected = false; + chip->is_pd_support = false; + + if ((evt & EVENT_CC) && chip->cc_state) { + if (chip->cc_state & CC_STATE_TOGSS_IS_UFP) + set_state(chip, attach_wait_sink); + else + set_state(chip, attach_wait_source); + + chip->vbus_begin = tcpm_check_vbus(chip); + + tcpm_set_polarity(chip, (chip->cc_state & CC_STATE_TOGSS_CC1) ? + TYPEC_POLARITY_CC1 : + TYPEC_POLARITY_CC2); + tcpm_get_cc(chip, &chip->cc1, &chip->cc2); + chip->debounce_cnt = 0; + chip->timer_mux = 2; + fusb_timer_start(&chip->timer_mux_machine, chip->timer_mux); + } +} + +static void fusb_state_try_attach_set(struct fusb30x_chip *chip, + enum role_mode mode) +{ + if (mode == ROLE_MODE_NONE || mode == ROLE_MODE_DRP || + mode == ROLE_MODE_ASS) + return; + + tcpm_init(chip); + tcpm_set_cc(chip, (mode == ROLE_MODE_DFP) ? + ROLE_MODE_DFP : ROLE_MODE_UFP); + chip->timer_mux = T_PD_TRY_DRP; + fusb_timer_start(&chip->timer_mux_machine, chip->timer_mux); + set_state(chip, (mode == ROLE_MODE_DFP) ? + attach_try_src : attach_try_snk); +} + +static void fusb_state_attach_wait_sink(struct fusb30x_chip *chip, u32 evt) +{ + int cc1, cc2; + + if (evt & EVENT_TIMER_MUX) { + if (tcpm_check_vbus(chip)) { + chip->timer_mux = T_DISABLED; + if (chip->role == ROLE_MODE_DRP && + chip->try_role == ROLE_MODE_DFP && + !chip->try_role_complete) { + fusb_state_try_attach_set(chip, ROLE_MODE_DFP); + return; + } else if (chip->try_role_complete) { + chip->timer_mux = T_PD_SOURCE_ON; + fusb_timer_start(&chip->timer_mux_machine, + chip->timer_mux); + set_state(chip, attached_sink); + return; + } + } + + tcpm_get_cc(chip, &cc1, &cc2); + + if ((chip->cc1 == cc1) && (chip->cc2 == cc2)) { + chip->debounce_cnt++; + } else { + chip->cc1 = cc1; + chip->cc2 = cc2; + chip->debounce_cnt = 0; + } + + if (chip->debounce_cnt > N_DEBOUNCE_CNT) { + chip->timer_mux = T_DISABLED; + if ((chip->cc1 == TYPEC_CC_VOLT_RP && + chip->cc2 == TYPEC_CC_VOLT_OPEN) || + (chip->cc2 == TYPEC_CC_VOLT_RP && + chip->cc1 == TYPEC_CC_VOLT_OPEN)) { + chip->timer_mux = T_PD_SOURCE_ON; + fusb_timer_start(&chip->timer_mux_machine, + chip->timer_mux); + set_state(chip, attached_sink); + } else { + set_state_unattached(chip); + } + return; + } + + chip->timer_mux = 2; + fusb_timer_start(&chip->timer_mux_machine, + chip->timer_mux); + } +} + +static void fusb_state_attach_wait_source(struct fusb30x_chip *chip, u32 evt) +{ + int cc1, cc2; + + if (evt & EVENT_TIMER_MUX) { + tcpm_get_cc(chip, &cc1, &cc2); + + if ((chip->cc1 == cc1) && (chip->cc2 == cc2)) { + chip->debounce_cnt++; + } else { + chip->cc1 = cc1; + chip->cc2 = cc2; + chip->debounce_cnt = 0; + } + + if (chip->debounce_cnt > N_DEBOUNCE_CNT) { + if (((!chip->cc1) || (!chip->cc2)) && + ((chip->cc1 == TYPEC_CC_VOLT_RD) || + (chip->cc2 == TYPEC_CC_VOLT_RD))) { + if (chip->role == ROLE_MODE_DRP && + chip->try_role == ROLE_MODE_UFP && + !chip->try_role_complete) + fusb_state_try_attach_set(chip, + ROLE_MODE_UFP); + else + set_state(chip, attached_source); + } else { + set_state_unattached(chip); + } + return; + } + + chip->timer_mux = 2; + fusb_timer_start(&chip->timer_mux_machine, + chip->timer_mux); + } +} + +static void fusb_state_attached_source(struct fusb30x_chip *chip, u32 evt) +{ + platform_set_vbus_lvl_enable(chip, 1, 0); + tcpm_set_polarity(chip, (chip->cc_state & CC_STATE_TOGSS_CC1) ? + TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2); + tcpm_set_vconn(chip, 1); + + chip->notify.is_cc_connected = true; + + chip->notify.power_role = POWER_ROLE_SOURCE; + chip->notify.data_role = DATA_ROLE_DFP; + chip->hardrst_count = 0; + set_state(chip, policy_src_startup); + regmap_update_bits(chip->regmap, FUSB_REG_MASK, MASK_M_COMP_CHNG, 0); + dev_info(chip->dev, "CC connected in %s as DFP\n", + chip->cc_polarity ? "CC1" : "CC2"); +} + +static void fusb_state_attached_sink(struct fusb30x_chip *chip, u32 evt) +{ + if (tcpm_check_vbus(chip)) { + chip->timer_mux = T_DISABLED; + chip->timer_state = T_DISABLED; + if (!chip->try_role_complete && + chip->try_role == ROLE_MODE_DFP && + chip->role == ROLE_MODE_DRP) { + fusb_state_try_attach_set(chip, ROLE_MODE_DFP); + return; + } + + chip->try_role_complete = true; + chip->notify.is_cc_connected = true; + chip->notify.power_role = POWER_ROLE_SINK; + chip->notify.data_role = DATA_ROLE_UFP; + chip->hardrst_count = 0; + set_state(chip, policy_snk_startup); + dev_info(chip->dev, "CC connected in %s as UFP\n", + chip->cc_polarity ? "CC1" : "CC2"); + return; + } else if (evt & EVENT_TIMER_MUX) { + set_state_unattached(chip); + return; + } + + chip->timer_state = 2; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); +} + +static void fusb_state_try_attach(struct fusb30x_chip *chip, u32 evt, + enum role_mode mode) +{ + if ((evt & EVENT_CC) && chip->cc_state) { + chip->try_role_complete = true; + if (chip->cc_state & CC_STATE_TOGSS_IS_UFP) + set_state(chip, (mode == ROLE_MODE_UFP) ? + attach_wait_sink : error_recovery); + else + set_state(chip, (mode == ROLE_MODE_DFP) ? + attach_wait_source : error_recovery); + + tcpm_set_polarity(chip, (chip->cc_state & CC_STATE_TOGSS_CC1) ? + TYPEC_POLARITY_CC1 : + TYPEC_POLARITY_CC2); + tcpm_get_cc(chip, &chip->cc1, &chip->cc2); + chip->debounce_cnt = 0; + chip->timer_mux = 2; + fusb_timer_start(&chip->timer_mux_machine, chip->timer_mux); + } else if (evt & EVENT_TIMER_MUX) { + if (!chip->try_role_complete) { + chip->try_role_complete = true; + fusb_state_try_attach_set(chip, + (mode == ROLE_MODE_DFP) ? + ROLE_MODE_UFP : + ROLE_MODE_DFP); + } else { + set_state(chip, error_recovery); + } + } +} + +static void fusb_soft_reset_parameter(struct fusb30x_chip *chip) +{ + chip->caps_counter = 0; + chip->msg_id = 0; + chip->vdm_state = VDM_STATE_DISCOVERY_ID; + chip->vdm_substate = 0; + chip->vdm_send_state = 0; + chip->val_tmp = 0; + chip->pos_power = 0; +} + +static void fusb_state_src_startup(struct fusb30x_chip *chip, u32 evt) +{ + chip->notify.is_pd_connected = false; + fusb_soft_reset_parameter(chip); + + memset(chip->partner_cap, 0, sizeof(chip->partner_cap)); + + tcpm_set_msg_header(chip); + tcpm_set_polarity(chip, chip->cc_polarity); + tcpm_set_rx_enable(chip, 1); + + set_state(chip, policy_src_send_caps); + platform_fusb_notify(chip); +} + +static void fusb_state_src_discovery(struct fusb30x_chip *chip, u32 evt) +{ + switch (chip->sub_state) { + case 0: + chip->caps_counter++; + + if (chip->caps_counter < N_CAPS_COUNT) { + chip->timer_state = T_TYPEC_SEND_SOURCECAP; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->sub_state = 1; + } else { + set_state(chip, disabled); + } + break; + default: + if (evt & EVENT_TIMER_STATE) { + set_state(chip, policy_src_send_caps); + } else if (evt & EVENT_TIMER_MUX) { + if (!chip->is_pd_support) + set_state(chip, disabled); + else if (chip->hardrst_count > N_HARDRESET_COUNT) + set_state(chip, error_recovery); + else + set_state(chip, policy_src_send_hardrst); + } + break; + } +} + +static void fusb_state_src_send_caps(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, DMT_SOURCECAPABILITIES, DATAMESSAGE); + chip->sub_state = 1; + chip->tx_state = tx_idle; + /* without break */ + case 1: + tmp = policy_send_data(chip); + + if (tmp == tx_success) { + chip->hardrst_count = 0; + chip->caps_counter = 0; + chip->timer_state = T_SENDER_RESPONSE; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->timer_mux = T_DISABLED; + chip->sub_state++; + chip->is_pd_support = true; + } else if (tmp == tx_failed) { + set_state(chip, policy_src_discovery); + break; + } + + if (!(evt & FLAG_EVENT)) + break; + default: + if (evt & EVENT_RX) { + if (PACKET_IS_DATA_MSG(chip->rec_head, DMT_REQUEST)) { + set_state(chip, policy_src_negotiate_cap); + } else { + set_state(chip, policy_src_send_softrst); + } + } else if (evt & EVENT_TIMER_STATE) { + if (chip->hardrst_count <= N_HARDRESET_COUNT) + set_state(chip, policy_src_send_hardrst); + else + set_state(chip, disabled); + } else if (evt & EVENT_TIMER_MUX) { + if (!chip->is_pd_support) + set_state(chip, disabled); + else if (chip->hardrst_count > N_HARDRESET_COUNT) + set_state(chip, error_recovery); + else + set_state(chip, policy_src_send_hardrst); + } + break; + } +} + +static void fusb_state_src_negotiate_cap(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + /* base on evb1 */ + tmp = (chip->rec_load[0] >> 28) & 0x07; + if (tmp > chip->n_caps_used) + set_state(chip, policy_src_cap_response); + else + set_state(chip, policy_src_transition_supply); +} + +static void fusb_state_src_transition_supply(struct fusb30x_chip *chip, + u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, CMT_ACCEPT, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + /* without break */ + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->timer_state = T_SRC_TRANSITION; + chip->sub_state++; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + set_state(chip, policy_src_send_softrst); + } + break; + case 2: + if (evt & EVENT_TIMER_STATE) { + chip->notify.is_pd_connected = true; + platform_set_vbus_lvl_enable(chip, 1, 0); + set_mesg(chip, CMT_PS_RDY, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + chip->work_continue |= EVENT_WORK_CONTINUE; + } + break; + default: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + dev_info(chip->dev, + "PD connected as DFP, supporting 5V\n"); + set_state(chip, policy_src_ready); + } else if (tmp == tx_failed) { + set_state(chip, policy_src_send_softrst); + } + break; + } +} + +static void fusb_state_src_cap_response(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, CMT_REJECT, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + /* without break */ + default: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + if (chip->notify.is_pd_connected) { + dev_info(chip->dev, + "PD connected as DFP, supporting 5V\n"); + set_state(chip, policy_src_ready); + } else { + set_state(chip, policy_src_send_hardrst); + } + } else if (tmp == tx_failed) { + set_state(chip, policy_src_send_softrst); + } + break; + } +} + +static void fusb_state_src_transition_default(struct fusb30x_chip *chip, + u32 evt) +{ + switch (chip->sub_state) { + case 0: + chip->notify.is_pd_connected = false; + platform_set_vbus_lvl_enable(chip, 0, 0); + if (chip->notify.data_role) + regmap_update_bits(chip->regmap, + FUSB_REG_SWITCHES1, + SWITCHES1_DATAROLE, + SWITCHES1_DATAROLE); + else + regmap_update_bits(chip->regmap, + FUSB_REG_SWITCHES1, + SWITCHES1_DATAROLE, + 0); + + chip->timer_state = T_SRC_RECOVER; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->sub_state++; + break; + default: + if (evt & EVENT_TIMER_STATE) { + platform_set_vbus_lvl_enable(chip, 1, 0); + chip->timer_mux = T_NO_RESPONSE; + fusb_timer_start(&chip->timer_mux_machine, + chip->timer_mux); + set_state(chip, policy_src_startup); + dev_dbg(chip->dev, "reset over-> src startup\n"); + } + break; + } +} + +static void fusb_state_vcs_ufp_evaluate_swap(struct fusb30x_chip *chip, u32 evt) +{ + if (chip->vconn_supported) + set_state(chip, policy_vcs_ufp_accept); + else + set_state(chip, policy_vcs_ufp_reject); +} + +static void fusb_state_swap_msg_process(struct fusb30x_chip *chip, u32 evt) +{ + if (evt & EVENT_RX) { + if (PACKET_IS_CONTROL_MSG(chip->rec_head, CMT_PR_SWAP)) { + set_state(chip, policy_src_prs_evaluate); + } else if (PACKET_IS_CONTROL_MSG(chip->rec_head, + CMT_VCONN_SWAP)) { + if (chip->notify.data_role) + set_state(chip, chip->conn_state); + else + set_state(chip, policy_vcs_ufp_evaluate_swap); + } else if (PACKET_IS_CONTROL_MSG(chip->rec_head, + CMT_DR_SWAP)) { + if (chip->notify.data_role) + set_state(chip, policy_drs_dfp_evaluate); + else + set_state(chip, policy_drs_ufp_evaluate); + } + } +} + +#define VDM_IS_ACTIVE(chip) \ + (chip->notify.data_role && chip->vdm_state < VDM_STATE_READY) + +static void fusb_state_src_ready(struct fusb30x_chip *chip, u32 evt) +{ + if (evt & EVENT_RX) { + if (PACKET_IS_DATA_MSG(chip->rec_head, DMT_VENDERDEFINED)) { + process_vdm_msg(chip); + chip->work_continue |= EVENT_WORK_CONTINUE; + chip->timer_state = T_DISABLED; + } else if (!VDM_IS_ACTIVE(chip)) { + fusb_state_swap_msg_process(chip, evt); + } + } + + if (!chip->partner_cap[0]) + set_state(chip, policy_src_get_sink_caps); + else if (VDM_IS_ACTIVE(chip)) + auto_vdm_machine(chip, evt); +} + +static void fusb_state_prs_evaluate(struct fusb30x_chip *chip, u32 evt) +{ + if (chip->role == ROLE_MODE_DRP) + set_state(chip, policy_src_prs_accept); + else + set_state(chip, policy_src_prs_reject); +} + +static void fusb_state_send_simple_msg(struct fusb30x_chip *chip, u32 evt, + int cmd, int is_DMT, + enum connection_state state_success, + enum connection_state state_failed) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, cmd, is_DMT); + chip->tx_state = tx_idle; + chip->sub_state++; + /* fallthrough */ + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) + set_state(chip, state_success); + else if (tmp == tx_failed) + set_state(chip, state_failed); + } +} + +static void fusb_state_prs_reject(struct fusb30x_chip *chip, u32 evt) +{ + fusb_state_send_simple_msg(chip, evt, CMT_REJECT, CONTROLMESSAGE, + (chip->notify.power_role) ? + policy_src_ready : policy_snk_ready, + (chip->notify.power_role) ? + policy_src_send_softrst : + policy_snk_send_softrst); +} + +static void fusb_state_prs_accept(struct fusb30x_chip *chip, u32 evt) +{ + fusb_state_send_simple_msg(chip, evt, CMT_ACCEPT, CONTROLMESSAGE, + (chip->notify.power_role) ? + policy_src_prs_transition_to_off : + policy_snk_prs_transition_to_off, + (chip->notify.power_role) ? + policy_src_send_softrst : + policy_snk_send_softrst); +} + +static void fusb_state_vcs_ufp_accept(struct fusb30x_chip *chip, u32 evt) +{ + fusb_state_send_simple_msg(chip, evt, CMT_ACCEPT, CONTROLMESSAGE, + (chip->vconn_enabled) ? + policy_vcs_ufp_wait_for_dfp_vconn : + policy_vcs_ufp_turn_on_vconn, + (chip->notify.power_role) ? + policy_src_send_softrst : + policy_snk_send_softrst); +} + +static void fusb_state_vcs_set_vconn(struct fusb30x_chip *chip, + u32 evt, bool on) +{ + if (on) { + tcpm_set_vconn(chip, 1); + set_state(chip, chip->notify.data_role ? + policy_vcs_dfp_send_ps_rdy : + policy_vcs_ufp_send_ps_rdy); + } else { + tcpm_set_vconn(chip, 0); + if (chip->notify.power_role) + set_state(chip, policy_src_ready); + else + set_state(chip, policy_snk_ready); + } +} + +static void fusb_state_vcs_send_ps_rdy(struct fusb30x_chip *chip, u32 evt) +{ + fusb_state_send_simple_msg(chip, evt, CMT_PS_RDY, CONTROLMESSAGE, + (chip->notify.power_role) ? + policy_src_ready : policy_snk_ready, + (chip->notify.power_role) ? + policy_src_send_softrst : + policy_snk_send_softrst); +} + +static void fusb_state_vcs_wait_for_vconn(struct fusb30x_chip *chip, + u32 evt) +{ + switch (chip->sub_state) { + case 0: + chip->timer_state = T_PD_VCONN_SRC_ON; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->sub_state++; + /* fallthrough */ + case 1: + if (evt & EVENT_RX) { + if (PACKET_IS_CONTROL_MSG(chip->rec_head, CMT_PS_RDY)) + set_state(chip, chip->notify.data_role ? + policy_vcs_dfp_turn_off_vconn : + policy_vcs_ufp_turn_off_vconn); + } else if (evt & EVENT_TIMER_STATE) { + if (chip->notify.power_role) + set_state(chip, policy_src_send_hardrst); + else + set_state(chip, policy_snk_send_hardrst); + } + } +} + +static void fusb_state_src_prs_transition_to_off(struct fusb30x_chip *chip, + u32 evt) +{ + switch (chip->sub_state) { + case 0: + chip->timer_state = T_SRC_TRANSITION; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->sub_state++; + break; + case 1: + if (evt & EVENT_TIMER_STATE) { + platform_set_vbus_lvl_enable(chip, 0, 0); + chip->notify.power_role = POWER_ROLE_SINK; + tcpm_set_msg_header(chip); + if (chip->role == ROLE_MODE_DRP) + set_state(chip, policy_src_prs_assert_rd); + else + set_state(chip, policy_src_prs_source_off); + } + } +} + +static void fusb_state_src_prs_assert_rd(struct fusb30x_chip *chip, u32 evt) +{ + tcpm_set_cc_pull_mode(chip, CC_PULL_DOWN); + set_state(chip, policy_src_prs_source_off); +} + +static void fusb_state_src_prs_source_off(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, CMT_PS_RDY, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + /* fallthrough */ + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->timer_state = T_PD_SOURCE_ON; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->sub_state++; + } else if (tmp == tx_failed) { + chip->notify.power_role = POWER_ROLE_SOURCE; + tcpm_set_msg_header(chip); + set_state(chip, policy_src_send_hardrst); + } + if (chip->sub_state != 3) + break; + case 2: + if (evt & EVENT_RX) { + if (PACKET_IS_CONTROL_MSG(chip->rec_head, + CMT_PS_RDY)) { + chip->timer_state = T_DISABLED; + /* snk startup */ + chip->notify.is_pd_connected = false; + chip->cc_state |= CC_STATE_TOGSS_IS_UFP; + tcpm_set_polarity(chip, chip->cc_polarity); + tcpm_set_rx_enable(chip, 1); + set_state(chip, policy_snk_discovery); + } else { + dev_dbg(chip->dev, + "rec careless msg: head %x\n", + chip->rec_head); + } + } else if (evt & EVENT_TIMER_STATE) { + chip->notify.power_role = POWER_ROLE_SOURCE; + tcpm_set_msg_header(chip); + set_state(chip, policy_src_send_hardrst); + } + } +} + +static void fusb_state_drs_evaluate(struct fusb30x_chip *chip, u32 evt) +{ + if (chip->pd_cap_info.data_role_swap) + /* + * TODO: + * NOW REJECT swap when the port is DFP + * since we should work together with USB part + */ + set_state(chip, chip->notify.data_role ? + policy_drs_dfp_reject : policy_drs_ufp_accept); + else + set_state(chip, chip->notify.data_role ? + policy_drs_dfp_reject : policy_drs_ufp_reject); +} + +static void fusb_state_drs_send_accept(struct fusb30x_chip *chip, u32 evt) +{ + fusb_state_send_simple_msg(chip, evt, CMT_ACCEPT, CONTROLMESSAGE, + chip->notify.power_role ? + policy_drs_dfp_change : + policy_drs_ufp_change, + error_recovery); +} + +static void fusb_state_drs_role_change(struct fusb30x_chip *chip, u32 evt) +{ + chip->notify.data_role = chip->notify.data_role ? + DATA_ROLE_UFP : DATA_ROLE_DFP; + tcpm_set_msg_header(chip); + set_state(chip, chip->notify.power_role ? policy_src_ready : + policy_snk_ready); +} + +static void fusb_state_src_get_sink_cap(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, CMT_GETSINKCAP, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + /* without break */ + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->timer_state = T_SENDER_RESPONSE; + chip->sub_state++; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + set_state(chip, policy_src_send_softrst); + } + + if (!(evt & FLAG_EVENT)) + break; + default: + if (evt & EVENT_RX) { + if (PACKET_IS_DATA_MSG(chip->rec_head, + DMT_SINKCAPABILITIES)) { + for (tmp = 0; + tmp < PD_HEADER_CNT(chip->rec_head); + tmp++) { + chip->partner_cap[tmp] = + chip->rec_load[tmp]; + } + set_state(chip, policy_src_ready); + } else { + chip->partner_cap[0] = 0xffffffff; + set_state(chip, policy_src_ready); + } + } else if (evt & EVENT_TIMER_STATE) { + dev_warn(chip->dev, "Get sink cap time out\n"); + chip->partner_cap[0] = 0xffffffff; + set_state(chip, policy_src_ready); + } + } +} + +static void fusb_state_src_send_hardreset(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + chip->tx_state = tx_idle; + chip->sub_state++; + /* without break */ + default: + tmp = policy_send_hardrst(chip, evt); + if (tmp == tx_success) { + chip->hardrst_count++; + set_state(chip, policy_src_transition_default); + } else if (tmp == tx_failed) { + /* can't reach here */ + set_state(chip, error_recovery); + } + break; + } +} + +static void fusb_state_src_softreset(struct fusb30x_chip *chip) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, CMT_ACCEPT, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + /* without break */ + default: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + fusb_soft_reset_parameter(chip); + set_state(chip, policy_src_send_caps); + } else if (tmp == tx_failed) { + set_state(chip, policy_src_send_hardrst); + } + break; + } +} + +static void fusb_state_src_send_softreset(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, CMT_SOFTRESET, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + /* without break */ + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->timer_state = T_SENDER_RESPONSE; + chip->sub_state++; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + set_state(chip, policy_src_send_hardrst); + } + + if (!(evt & FLAG_EVENT)) + break; + default: + if (evt & EVENT_RX) { + if (PACKET_IS_CONTROL_MSG(chip->rec_head, CMT_ACCEPT)) { + fusb_soft_reset_parameter(chip); + set_state(chip, policy_src_send_caps); + } + } else if (evt & EVENT_TIMER_STATE) { + set_state(chip, policy_src_send_hardrst); + } + break; + } +} + +static void fusb_state_snk_startup(struct fusb30x_chip *chip, u32 evt) +{ + chip->notify.is_pd_connected = false; + fusb_soft_reset_parameter(chip); + + memset(chip->partner_cap, 0, sizeof(chip->partner_cap)); + + tcpm_set_msg_header(chip); + tcpm_set_polarity(chip, chip->cc_polarity); + tcpm_set_rx_enable(chip, 1); + set_state(chip, policy_snk_discovery); + platform_fusb_notify(chip); +} + +static void fusb_state_snk_discovery(struct fusb30x_chip *chip, u32 evt) +{ + set_state(chip, policy_snk_wait_caps); + chip->timer_state = T_TYPEC_SINK_WAIT_CAP; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); +} + +static void fusb_state_snk_wait_caps(struct fusb30x_chip *chip, u32 evt) +{ + if (evt & EVENT_RX) { + if (PACKET_IS_DATA_MSG(chip->rec_head, + DMT_SOURCECAPABILITIES)) { + chip->is_pd_support = true; + chip->timer_mux = T_DISABLED; + set_state(chip, policy_snk_evaluate_caps); + } + } else if (evt & EVENT_TIMER_STATE) { + if (chip->hardrst_count <= N_HARDRESET_COUNT) { + if (chip->vbus_begin) { + chip->vbus_begin = false; + set_state(chip, policy_snk_send_softrst); + } else { + set_state(chip, policy_snk_send_hardrst); + } + } else { + if (chip->is_pd_support) + set_state(chip, error_recovery); + else + set_state(chip, disabled); + } + } else if ((evt & EVENT_TIMER_MUX) && + (chip->hardrst_count > N_HARDRESET_COUNT)) { + if (chip->is_pd_support) + set_state(chip, error_recovery); + else + set_state(chip, disabled); + } +} + +static void fusb_state_snk_evaluate_caps(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + chip->hardrst_count = 0; + chip->pos_power = 0; + + for (tmp = 0; tmp < PD_HEADER_CNT(chip->rec_head); tmp++) { + switch (CAP_POWER_TYPE(chip->rec_load[tmp])) { + case 0: + /* Fixed Supply */ + if (CAP_FPDO_VOLTAGE(chip->rec_load[tmp]) <= 100) + chip->pos_power = tmp + 1; + break; + case 1: + /* Battery */ + if (CAP_VPDO_VOLTAGE(chip->rec_load[tmp]) <= 100) + chip->pos_power = tmp + 1; + break; + default: + /* not meet battery caps */ + break; + } + } + fusb302_set_pos_power_by_charge_ic(chip); + + if ((!chip->pos_power) || (chip->pos_power > 7)) { + chip->pos_power = 0; + set_state(chip, policy_snk_wait_caps); + } else { + set_state(chip, policy_snk_select_cap); + } +} + +static void fusb_state_snk_select_cap(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, DMT_REQUEST, DATAMESSAGE); + chip->sub_state = 1; + chip->tx_state = tx_idle; + /* without break */ + case 1: + tmp = policy_send_data(chip); + + if (tmp == tx_success) { + chip->timer_state = T_SENDER_RESPONSE; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->sub_state++; + } else if (tmp == tx_failed) { + set_state(chip, policy_snk_discovery); + break; + } + + if (!(evt & FLAG_EVENT)) + break; + default: + if (evt & EVENT_RX) { + if (!PD_HEADER_CNT(chip->rec_head)) { + switch (PD_HEADER_TYPE(chip->rec_head)) { + case CMT_ACCEPT: + set_state(chip, + policy_snk_transition_sink); + chip->timer_state = T_PS_TRANSITION; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + break; + case CMT_WAIT: + case CMT_REJECT: + if (chip->notify.is_pd_connected) { + dev_info(chip->dev, + "PD connected as UFP, fetching 5V\n"); + set_state(chip, + policy_snk_ready); + } else { + set_state(chip, + policy_snk_wait_caps); + /* + * make sure don't send + * hard reset to prevent + * infinite loop + */ + chip->hardrst_count = + N_HARDRESET_COUNT + 1; + } + break; + default: + break; + } + } + } else if (evt & EVENT_TIMER_STATE) { + set_state(chip, policy_snk_send_hardrst); + } + break; + } +} + +static void fusb_state_snk_transition_sink(struct fusb30x_chip *chip, u32 evt) +{ + if (evt & EVENT_RX) { + if (PACKET_IS_CONTROL_MSG(chip->rec_head, CMT_PS_RDY)) { + chip->notify.is_pd_connected = true; + dev_info(chip->dev, + "PD connected as UFP, fetching 5V\n"); + set_state(chip, policy_snk_ready); + } else if (PACKET_IS_DATA_MSG(chip->rec_head, + DMT_SOURCECAPABILITIES)) { + set_state(chip, policy_snk_evaluate_caps); + } + } else if (evt & EVENT_TIMER_STATE) { + set_state(chip, policy_snk_send_hardrst); + } +} + +static void fusb_state_snk_transition_default(struct fusb30x_chip *chip, + u32 evt) +{ + switch (chip->sub_state) { + case 0: + chip->notify.is_pd_connected = false; + chip->timer_mux = T_NO_RESPONSE; + fusb_timer_start(&chip->timer_mux_machine, + chip->timer_mux); + chip->timer_state = T_PS_HARD_RESET_MAX + T_SAFE_0V; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + if (chip->notify.data_role) + tcpm_set_msg_header(chip); + + chip->sub_state++; + /* fallthrough */ + case 1: + if (!tcpm_check_vbus(chip)) { + chip->sub_state++; + chip->timer_state = T_SRC_RECOVER_MAX + T_SRC_TURN_ON; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (evt & EVENT_TIMER_STATE) { + set_state(chip, policy_snk_startup); + } + break; + default: + if (tcpm_check_vbus(chip)) { + chip->timer_state = T_DISABLED; + set_state(chip, policy_snk_startup); + } else if (evt & EVENT_TIMER_STATE) { + set_state(chip, policy_snk_startup); + } + break; + } +} + +static void fusb_state_snk_ready(struct fusb30x_chip *chip, u32 evt) +{ + if (evt & EVENT_RX) { + if (PACKET_IS_DATA_MSG(chip->rec_head, DMT_VENDERDEFINED)) { + process_vdm_msg(chip); + chip->work_continue |= EVENT_WORK_CONTINUE; + chip->timer_state = T_DISABLED; + } else if (!VDM_IS_ACTIVE(chip)) { + fusb_state_swap_msg_process(chip, evt); + } + } + + if (VDM_IS_ACTIVE(chip)) + auto_vdm_machine(chip, evt); + + fusb_state_swap_msg_process(chip, evt); + platform_fusb_notify(chip); +} + +static void fusb_state_snk_send_hardreset(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + chip->tx_state = tx_idle; + chip->sub_state++; + default: + tmp = policy_send_hardrst(chip, evt); + if (tmp == tx_success) { + chip->hardrst_count++; + set_state(chip, policy_snk_transition_default); + } else if (tmp == tx_failed) { + set_state(chip, error_recovery); + } + break; + } +} + +static void fusb_state_send_swap(struct fusb30x_chip *chip, u32 evt, int cmd) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, cmd, CONTROLMESSAGE); + chip->sub_state = 1; + chip->tx_state = tx_idle; + /* fallthrough */ + case 1: + tmp = policy_send_data(chip); + + if (tmp == tx_success) { + chip->timer_state = T_SENDER_RESPONSE; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->sub_state++; + } else if (tmp == tx_failed) { + if (cmd == CMT_DR_SWAP) { + set_state(chip, error_recovery); + return; + } + + if (chip->notify.power_role) + set_state(chip, policy_src_send_softrst); + else + set_state(chip, policy_snk_send_softrst); + } + break; + case 2: + if (evt & EVENT_RX) { + if (PACKET_IS_CONTROL_MSG(chip->rec_head, + CMT_ACCEPT)) { + chip->timer_state = T_DISABLED; + if (cmd == CMT_VCONN_SWAP) { + set_state(chip, chip->vconn_enabled ? + policy_vcs_dfp_wait_for_ufp_vconn : + policy_vcs_dfp_turn_on_vconn); + } else if (cmd == CMT_PR_SWAP) { + if (chip->notify.power_role) + set_state(chip, policy_src_prs_transition_to_off); + else + set_state(chip, policy_snk_prs_transition_to_off); + chip->notify.power_role = POWER_ROLE_SOURCE; + tcpm_set_msg_header(chip); + } else if (cmd == CMT_DR_SWAP) { + set_state(chip, chip->notify.data_role ? + policy_drs_dfp_change : + policy_drs_ufp_change); + } + } else if (PACKET_IS_CONTROL_MSG(chip->rec_head, + CMT_REJECT) || + PACKET_IS_CONTROL_MSG(chip->rec_head, + CMT_WAIT)) { + chip->timer_state = T_DISABLED; + if (chip->notify.power_role) + set_state(chip, policy_src_ready); + else + set_state(chip, policy_snk_ready); + } + } else if (evt & EVENT_TIMER_STATE) { + if (chip->notify.power_role) + set_state(chip, policy_src_ready); + else + set_state(chip, policy_snk_ready); + } + } +} + +static void fusb_state_snk_prs_transition_to_off(struct fusb30x_chip *chip, + u32 evt) +{ + switch (chip->sub_state) { + case 0: + chip->timer_state = T_PD_SOURCE_OFF; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->sub_state++; + /* fallthrough */ + case 1: + if (evt & EVENT_RX) { + if (PACKET_IS_CONTROL_MSG(chip->rec_head, + CMT_PS_RDY)) { + if (chip->role == ROLE_MODE_DRP) + set_state(chip, + policy_snk_prs_assert_rp); + else + set_state(chip, + policy_snk_prs_source_on); + } else { + dev_dbg(chip->dev, + "rec careless msg: head %x\n", + chip->rec_head); + } + } else if (evt & EVENT_TIMER_STATE) { + chip->notify.power_role = POWER_ROLE_SINK; + tcpm_set_msg_header(chip); + set_state(chip, policy_snk_send_hardrst); + } + break; + } +} + +static void fusb_state_snk_prs_assert_rp(struct fusb30x_chip *chip, u32 evt) +{ + tcpm_set_cc_pull_mode(chip, CC_PULL_UP); + set_state(chip, policy_snk_prs_source_on); +} + +static void fusb_state_snk_prs_source_on(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + /* supply power in 50ms */ + platform_set_vbus_lvl_enable(chip, 1, 0); + chip->sub_state++; + chip->work_continue |= EVENT_WORK_CONTINUE; + break; + case 1: + set_mesg(chip, CMT_PS_RDY, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + /* fallthrough */ + case 2: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + /* PD spe 6.5.10.2 */ + chip->timer_state = T_PD_SWAP_SOURCE_START; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + chip->sub_state++; + } else if (tmp == tx_failed) { + chip->notify.power_role = POWER_ROLE_SINK; + tcpm_set_msg_header(chip); + set_state(chip, policy_snk_send_hardrst); + } + break; + case 3: + if (evt & EVENT_TIMER_STATE) { + chip->cc_state &= ~CC_STATE_TOGSS_IS_UFP; + regmap_update_bits(chip->regmap, FUSB_REG_MASK, + MASK_M_COMP_CHNG, 0); + set_state(chip, policy_src_send_caps); + } + break; + } +} + +static void fusb_state_snk_softreset(struct fusb30x_chip *chip) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, CMT_ACCEPT, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + /* without break */ + default: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + fusb_soft_reset_parameter(chip); + chip->timer_state = T_TYPEC_SINK_WAIT_CAP; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + set_state(chip, policy_snk_wait_caps); + } else if (tmp == tx_failed) { + set_state(chip, policy_snk_send_hardrst); + } + break; + } +} + +static void fusb_state_snk_send_softreset(struct fusb30x_chip *chip, u32 evt) +{ + u32 tmp; + + switch (chip->sub_state) { + case 0: + set_mesg(chip, CMT_SOFTRESET, CONTROLMESSAGE); + chip->tx_state = tx_idle; + chip->sub_state++; + case 1: + tmp = policy_send_data(chip); + if (tmp == tx_success) { + chip->timer_state = T_SENDER_RESPONSE; + chip->sub_state++; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + } else if (tmp == tx_failed) { + /* can't reach here */ + set_state(chip, policy_snk_send_hardrst); + } + + if (!(evt & FLAG_EVENT)) + break; + default: + if (evt & EVENT_RX) { + if ((!PD_HEADER_CNT(chip->rec_head)) && + (PD_HEADER_TYPE(chip->rec_head) == CMT_ACCEPT)) { + fusb_soft_reset_parameter(chip); + chip->timer_state = T_TYPEC_SINK_WAIT_CAP; + fusb_timer_start(&chip->timer_state_machine, + chip->timer_state); + set_state(chip, policy_snk_wait_caps); + } + } else if (evt & EVENT_TIMER_STATE) { + set_state(chip, policy_snk_send_hardrst); + } + break; + } +} + +static void fusb_try_detach(struct fusb30x_chip *chip) +{ + int cc1, cc2; + + if ((chip->cc_state & CC_STATE_TOGSS_IS_UFP) && + (chip->conn_state != + policy_snk_transition_default) && + (chip->conn_state != + policy_src_prs_source_off) && + (chip->conn_state != policy_snk_prs_send_swap) && + (chip->conn_state != policy_snk_prs_assert_rp) && + (chip->conn_state != policy_snk_prs_source_on) && + (chip->conn_state != policy_snk_prs_transition_to_off)) { + if (!tcpm_check_vbus(chip)) + set_state_unattached(chip); + } else if ((chip->conn_state != + policy_src_transition_default) && + (chip->conn_state != + policy_src_prs_source_off) && + (chip->conn_state != policy_snk_prs_source_on)) { + tcpm_get_cc(chip, &cc1, &cc2); + if (chip->cc_state & CC_STATE_TOGSS_CC2) + cc1 = cc2; + if (cc1 == TYPEC_CC_VOLT_OPEN) + set_state_unattached(chip); + } else { + /* + * Detached may occurred at swap operations. So, DON'T ignore + * the EVENT_CC during swapping at all, check the connection + * after it. + */ + chip->work_continue |= EVENT_DELAY_CC; + } +} + +static void state_machine_typec(struct fusb30x_chip *chip) +{ + u32 evt = 0; + + tcpc_alert(chip, &evt); + mux_alert(chip, &evt); + if (!evt) + goto BACK; + + if (chip->notify.is_cc_connected) + if (evt & (EVENT_CC | EVENT_DELAY_CC)) + fusb_try_detach(chip); + + if (evt & EVENT_RX) { + tcpm_get_message(chip); + if (PACKET_IS_CONTROL_MSG(chip->rec_head, CMT_SOFTRESET)) { + if (chip->notify.power_role) + set_state(chip, policy_src_softrst); + else + set_state(chip, policy_snk_softrst); + } + } + + if (evt & EVENT_TX) { + if (chip->tx_state == tx_success) + chip->msg_id++; + } + switch (chip->conn_state) { + case disabled: + fusb_state_disabled(chip, evt); + break; + case error_recovery: + set_state_unattached(chip); + break; + case unattached: + fusb_state_unattached(chip, evt); + break; + case attach_wait_sink: + fusb_state_attach_wait_sink(chip, evt); + break; + case attach_wait_source: + fusb_state_attach_wait_source(chip, evt); + break; + case attached_source: + fusb_state_attached_source(chip, evt); + break; + case attached_sink: + fusb_state_attached_sink(chip, evt); + break; + case attach_try_src: + fusb_state_try_attach(chip, evt, ROLE_MODE_DFP); + break; + case attach_try_snk: + fusb_state_try_attach(chip, evt, ROLE_MODE_UFP); + break; + + /* POWER DELIVERY */ + case policy_src_startup: + fusb_state_src_startup(chip, evt); + break; + case policy_src_discovery: + fusb_state_src_discovery(chip, evt); + break; + case policy_src_send_caps: + fusb_state_src_send_caps(chip, evt); + if (chip->conn_state != policy_src_negotiate_cap) + break; + case policy_src_negotiate_cap: + fusb_state_src_negotiate_cap(chip, evt); + + case policy_src_transition_supply: + fusb_state_src_transition_supply(chip, evt); + break; + case policy_src_cap_response: + fusb_state_src_cap_response(chip, evt); + break; + case policy_src_transition_default: + fusb_state_src_transition_default(chip, evt); + break; + case policy_src_ready: + fusb_state_src_ready(chip, evt); + break; + case policy_src_get_sink_caps: + fusb_state_src_get_sink_cap(chip, evt); + break; + case policy_src_send_hardrst: + fusb_state_src_send_hardreset(chip, evt); + break; + case policy_src_send_softrst: + fusb_state_src_send_softreset(chip, evt); + break; + case policy_src_softrst: + fusb_state_src_softreset(chip); + break; + + /* UFP */ + case policy_snk_startup: + fusb_state_snk_startup(chip, evt); + break; + case policy_snk_discovery: + fusb_state_snk_discovery(chip, evt); + break; + case policy_snk_wait_caps: + fusb_state_snk_wait_caps(chip, evt); + break; + case policy_snk_evaluate_caps: + fusb_state_snk_evaluate_caps(chip, evt); + /* without break */ + case policy_snk_select_cap: + fusb_state_snk_select_cap(chip, evt); + break; + case policy_snk_transition_sink: + fusb_state_snk_transition_sink(chip, evt); + break; + case policy_snk_transition_default: + fusb_state_snk_transition_default(chip, evt); + break; + case policy_snk_ready: + fusb_state_snk_ready(chip, evt); + break; + case policy_snk_send_hardrst: + fusb_state_snk_send_hardreset(chip, evt); + break; + case policy_snk_send_softrst: + fusb_state_snk_send_softreset(chip, evt); + break; + case policy_snk_softrst: + fusb_state_snk_softreset(chip); + break; + + /* + * PD Spec 1.0: PR SWAP: chap 8.3.3.6.3.1/2 + * VC SWAP: chap 8.3.3.7.1/2 + */ + case policy_src_prs_evaluate: + case policy_snk_prs_evaluate: + fusb_state_prs_evaluate(chip, evt); + break; + case policy_snk_prs_accept: + case policy_src_prs_accept: + fusb_state_prs_accept(chip, evt); + break; + case policy_snk_prs_reject: + case policy_src_prs_reject: + case policy_vcs_ufp_reject: + case policy_drs_dfp_reject: + case policy_drs_ufp_reject: + fusb_state_prs_reject(chip, evt); + break; + case policy_src_prs_transition_to_off: + fusb_state_src_prs_transition_to_off(chip, evt); + break; + case policy_src_prs_assert_rd: + fusb_state_src_prs_assert_rd(chip, evt); + break; + case policy_src_prs_source_off: + fusb_state_src_prs_source_off(chip, evt); + break; + case policy_snk_prs_send_swap: + case policy_src_prs_send_swap: + fusb_state_send_swap(chip, evt, CMT_PR_SWAP); + break; + case policy_snk_prs_transition_to_off: + fusb_state_snk_prs_transition_to_off(chip, evt); + break; + case policy_snk_prs_assert_rp: + fusb_state_snk_prs_assert_rp(chip, evt); + break; + case policy_snk_prs_source_on: + fusb_state_snk_prs_source_on(chip, evt); + break; + case policy_vcs_ufp_evaluate_swap: + fusb_state_vcs_ufp_evaluate_swap(chip, evt); + break; + case policy_vcs_ufp_accept: + fusb_state_vcs_ufp_accept(chip, evt); + break; + case policy_vcs_ufp_wait_for_dfp_vconn: + case policy_vcs_dfp_wait_for_ufp_vconn: + fusb_state_vcs_wait_for_vconn(chip, evt); + break; + case policy_vcs_ufp_turn_off_vconn: + case policy_vcs_dfp_turn_off_vconn: + fusb_state_vcs_set_vconn(chip, evt, false); + break; + case policy_vcs_ufp_turn_on_vconn: + case policy_vcs_dfp_turn_on_vconn: + fusb_state_vcs_set_vconn(chip, evt, true); + break; + case policy_vcs_ufp_send_ps_rdy: + case policy_vcs_dfp_send_ps_rdy: + fusb_state_vcs_send_ps_rdy(chip, evt); + break; + case policy_vcs_dfp_send_swap: + fusb_state_send_swap(chip, evt, CMT_VCONN_SWAP); + break; + case policy_drs_ufp_evaluate: + case policy_drs_dfp_evaluate: + fusb_state_drs_evaluate(chip, evt); + break; + case policy_drs_dfp_accept: + case policy_drs_ufp_accept: + fusb_state_drs_send_accept(chip, evt); + break; + case policy_drs_dfp_change: + case policy_drs_ufp_change: + fusb_state_drs_role_change(chip, evt); + break; + case policy_drs_ufp_send_swap: + case policy_drs_dfp_send_swap: + fusb_state_send_swap(chip, evt, CMT_DR_SWAP); + break; + + default: + break; + } + +BACK: + if (chip->work_continue) { + queue_work(chip->fusb30x_wq, &chip->work); + return; + } + + if (!platform_get_device_irq_state(chip)) + fusb_irq_enable(chip); + else + queue_work(chip->fusb30x_wq, &chip->work); +} + +static irqreturn_t cc_interrupt_handler(int irq, void *dev_id) +{ + struct fusb30x_chip *chip = dev_id; + + queue_work(chip->fusb30x_wq, &chip->work); + fusb_irq_disable(chip); + return IRQ_HANDLED; +} + +static int fusb_initialize_gpio(struct fusb30x_chip *chip) +{ + chip->gpio_int = devm_gpiod_get_optional(chip->dev, "int-n", GPIOD_IN); + if (IS_ERR(chip->gpio_int)) + return PTR_ERR(chip->gpio_int); + + /* some board support vbus with other ways */ + chip->gpio_vbus_5v = devm_gpiod_get_optional(chip->dev, "vbus-5v", + GPIOD_OUT_LOW); + if (IS_ERR(chip->gpio_vbus_5v)) + dev_warn(chip->dev, + "Could not get named GPIO for VBus5V!\n"); + else + gpiod_set_raw_value(chip->gpio_vbus_5v, 0); + + chip->gpio_vbus_other = devm_gpiod_get_optional(chip->dev, + "vbus-other", + GPIOD_OUT_LOW); + if (IS_ERR(chip->gpio_vbus_other)) + dev_warn(chip->dev, + "Could not get named GPIO for VBusOther!\n"); + else + gpiod_set_raw_value(chip->gpio_vbus_other, 0); + + chip->gpio_discharge = devm_gpiod_get_optional(chip->dev, "discharge", + GPIOD_OUT_LOW); + if (IS_ERR(chip->gpio_discharge)) { + dev_warn(chip->dev, + "Could not get named GPIO for discharge!\n"); + chip->gpio_discharge = NULL; + } + + return 0; +} + +static enum hrtimer_restart fusb_timer_handler(struct hrtimer *timer) +{ + int i; + + for (i = 0; i < fusb30x_port_used; i++) { + if (timer == &fusb30x_port_info[i]->timer_state_machine) { + if (fusb30x_port_info[i]->timer_state != T_DISABLED) + fusb30x_port_info[i]->timer_state = 0; + break; + } + + if (timer == &fusb30x_port_info[i]->timer_mux_machine) { + if (fusb30x_port_info[i]->timer_mux != T_DISABLED) + fusb30x_port_info[i]->timer_mux = 0; + break; + } + } + + if (i != fusb30x_port_used) + queue_work(fusb30x_port_info[i]->fusb30x_wq, + &fusb30x_port_info[i]->work); + + return HRTIMER_NORESTART; +} + +static void fusb_initialize_timer(struct fusb30x_chip *chip) +{ + hrtimer_init(&chip->timer_state_machine, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + chip->timer_state_machine.function = fusb_timer_handler; + + hrtimer_init(&chip->timer_mux_machine, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + chip->timer_mux_machine.function = fusb_timer_handler; + + chip->timer_state = T_DISABLED; + chip->timer_mux = T_DISABLED; +} + +static void fusb302_work_func(struct work_struct *work) +{ + struct fusb30x_chip *chip; + + chip = container_of(work, struct fusb30x_chip, work); + state_machine_typec(chip); +} + +static int fusb30x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct fusb30x_chip *chip; + struct PD_CAP_INFO *pd_cap_info; + int ret; + char *string[2]; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + if (fusb30x_port_used == 0xff) + return -1; + + chip->port_num = fusb30x_port_used++; + fusb30x_port_info[chip->port_num] = chip; + + chip->dev = &client->dev; + chip->regmap = devm_regmap_init_i2c(client, &fusb302_regmap_config); + if (IS_ERR(chip->regmap)) { + dev_err(&client->dev, "Failed to allocate regmap!\n"); + return PTR_ERR(chip->regmap); + } + + ret = fusb_initialize_gpio(chip); + if (ret) + return ret; + + fusb_initialize_timer(chip); + + chip->fusb30x_wq = create_workqueue("fusb302_wq"); + INIT_WORK(&chip->work, fusb302_work_func); + + chip->role = ROLE_MODE_NONE; + chip->try_role = ROLE_MODE_NONE; + if (!of_property_read_string(chip->dev->of_node, "fusb302,role", + (const char **)&string[0])) { + if (!strcmp(string[0], "ROLE_MODE_DRP")) + chip->role = ROLE_MODE_DRP; + else if (!strcmp(string[0], "ROLE_MODE_DFP")) + chip->role = ROLE_MODE_DFP; + else if (!strcmp(string[0], "ROLE_MODE_UFP")) + chip->role = ROLE_MODE_UFP; + } + + if (chip->role == ROLE_MODE_NONE) { + dev_warn(chip->dev, + "Can't get property of role, set role to default DRP\n"); + chip->role = ROLE_MODE_DRP; + string[0] = "ROLE_MODE_DRP"; + } + + if (!of_property_read_string(chip->dev->of_node, "fusb302,try_role", + (const char **)&string[1])) { + if (!strcmp(string[1], "ROLE_MODE_DFP")) + chip->try_role = ROLE_MODE_DFP; + else if (!strcmp(string[1], "ROLE_MODE_UFP")) + chip->try_role = ROLE_MODE_UFP; + } + + if (chip->try_role == ROLE_MODE_NONE) + string[1] = "ROLE_MODE_NONE"; + + chip->vconn_supported = true; + tcpm_init(chip); + tcpm_set_rx_enable(chip, 0); + chip->conn_state = unattached; + tcpm_set_cc(chip, chip->role); + + chip->n_caps_used = 1; + chip->source_power_supply[0] = 0x64; + chip->source_max_current[0] = 0x96; + + pd_cap_info = &chip->pd_cap_info; + pd_cap_info->dual_role_power = 1; + pd_cap_info->data_role_swap = 1; + + pd_cap_info->externally_powered = 1; + pd_cap_info->usb_suspend_support = 0; + pd_cap_info->usb_communications_cap = 0; + pd_cap_info->supply_type = 0; + pd_cap_info->peak_current = 0; + + chip->extcon = devm_extcon_dev_allocate(&client->dev, fusb302_cable); + if (IS_ERR(chip->extcon)) { + dev_err(&client->dev, "allocat extcon failed\n"); + return PTR_ERR(chip->extcon); + } + + ret = devm_extcon_dev_register(&client->dev, chip->extcon); + if (ret) { + dev_err(&client->dev, "failed to register extcon: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(chip->extcon, EXTCON_USB, + EXTCON_PROP_USB_TYPEC_POLARITY); + if (ret) { + dev_err(&client->dev, + "failed to set USB property capability: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(chip->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY); + if (ret) { + dev_err(&client->dev, + "failed to set USB_HOST property capability: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(chip->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_TYPEC_POLARITY); + if (ret) { + dev_err(&client->dev, + "failed to set DISP_DP property capability: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(chip->extcon, EXTCON_USB, + EXTCON_PROP_USB_SS); + if (ret) { + dev_err(&client->dev, + "failed to set USB USB_SS property capability: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(chip->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_SS); + if (ret) { + dev_err(&client->dev, + "failed to set USB_HOST USB_SS property capability: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(chip->extcon, EXTCON_DISP_DP, + EXTCON_PROP_USB_SS); + if (ret) { + dev_err(&client->dev, + "failed to set DISP_DP USB_SS property capability: %d\n", + ret); + return ret; + } + + ret = extcon_set_property_capability(chip->extcon, EXTCON_CHG_USB_FAST, + EXTCON_PROP_USB_TYPEC_POLARITY); + if (ret) { + dev_err(&client->dev, + "failed to set USB_PD property capability: %d\n", ret); + return ret; + } + + i2c_set_clientdata(client, chip); + + spin_lock_init(&chip->irq_lock); + chip->enable_irq = 1; + + chip->gpio_int_irq = gpiod_to_irq(chip->gpio_int); + if (chip->gpio_int_irq < 0) { + dev_err(&client->dev, + "Unable to request IRQ for INT_N GPIO! %d\n", + ret); + ret = chip->gpio_int_irq; + goto IRQ_ERR; + } + + ret = devm_request_threaded_irq(&client->dev, + chip->gpio_int_irq, + NULL, + cc_interrupt_handler, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + client->name, + chip); + if (ret) { + dev_err(&client->dev, "irq request failed\n"); + goto IRQ_ERR; + } + + dev_info(chip->dev, + "port %d probe success with role %s, try_role %s\n", + chip->port_num, string[0], string[1]); + + return 0; + +IRQ_ERR: + destroy_workqueue(chip->fusb30x_wq); + return ret; +} + +static int fusb30x_remove(struct i2c_client *client) +{ + struct fusb30x_chip *chip = i2c_get_clientdata(client); + + destroy_workqueue(chip->fusb30x_wq); + return 0; +} + +static void fusb30x_shutdown(struct i2c_client *client) +{ + struct fusb30x_chip *chip = i2c_get_clientdata(client); + + if (chip->gpio_vbus_5v) + gpiod_set_value(chip->gpio_vbus_5v, 0); + if (chip->gpio_discharge) { + gpiod_set_value(chip->gpio_discharge, 1); + msleep(100); + gpiod_set_value(chip->gpio_discharge, 0); + } +} + +static const struct of_device_id fusb30x_dt_match[] = { + { .compatible = FUSB30X_I2C_DEVICETREE_NAME }, + {}, +}; +MODULE_DEVICE_TABLE(of, fusb30x_dt_match); + +static const struct i2c_device_id fusb30x_i2c_device_id[] = { + { FUSB30X_I2C_DRIVER_NAME, 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, fusb30x_i2c_device_id); + +static struct i2c_driver fusb30x_driver = { + .driver = { + .name = FUSB30X_I2C_DRIVER_NAME, + .of_match_table = of_match_ptr(fusb30x_dt_match), + }, + .probe = fusb30x_probe, + .remove = fusb30x_remove, + .shutdown = fusb30x_shutdown, + .id_table = fusb30x_i2c_device_id, +}; + +module_i2c_driver(fusb30x_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("zain wang "); +MODULE_DESCRIPTION("fusb302 typec pd driver"); diff --git a/drivers/staging/fusb30x/fusb30x.h b/drivers/staging/fusb30x/fusb30x.h new file mode 100644 index 000000000..4f5ca64f7 --- /dev/null +++ b/drivers/staging/fusb30x/fusb30x.h @@ -0,0 +1,552 @@ +/* + * Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd + * Author: Zain Wang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * Some ideas are from chrome ec and fairchild GPL fusb302 driver. + */ + +#ifndef FUSB302_H +#define FUSB302_H + +#include +#include + +const char *FUSB_DT_INTERRUPT_INTN = "fsc_interrupt_int_n"; +#define FUSB_DT_GPIO_INTN "fairchild,int_n" +#define FUSB_DT_GPIO_VBUS_5V "fairchild,vbus5v" +#define FUSB_DT_GPIO_VBUS_OTHER "fairchild,vbusOther" + +#define FUSB30X_I2C_DRIVER_NAME "fusb302" +#define FUSB30X_I2C_DEVICETREE_NAME "fairchild,fusb302" + +/* FUSB300 Register Addresses */ +#define FUSB_REG_DEVICEID 0x01 +#define FUSB_REG_SWITCHES0 0x02 +#define FUSB_REG_SWITCHES1 0x03 +#define FUSB_REG_MEASURE 0x04 +#define FUSB_REG_SLICE 0x05 +#define FUSB_REG_CONTROL0 0x06 +#define FUSB_REG_CONTROL1 0x07 +#define FUSB_REG_CONTROL2 0x08 +#define FUSB_REG_CONTROL3 0x09 +#define FUSB_REG_MASK 0x0A +#define FUSB_REG_POWER 0x0B +#define FUSB_REG_RESET 0x0C +#define FUSB_REG_OCPREG 0x0D +#define FUSB_REG_MASKA 0x0E +#define FUSB_REG_MASKB 0x0F +#define FUSB_REG_CONTROL4 0x10 +#define FUSB_REG_STATUS0A 0x3C +#define FUSB_REG_STATUS1A 0x3D +#define FUSB_REG_INTERRUPTA 0x3E +#define FUSB_REG_INTERRUPTB 0x3F +#define FUSB_REG_STATUS0 0x40 +#define FUSB_REG_STATUS1 0x41 +#define FUSB_REG_INTERRUPT 0x42 +#define FUSB_REG_FIFO 0x43 + +enum connection_state { + disabled = 0, + error_recovery, + unattached, + attach_wait_sink, + attach_wait_source, + attached_source, + attached_sink, + + policy_src_startup, + policy_src_send_caps, + policy_src_discovery, + policy_src_negotiate_cap, + policy_src_cap_response, + policy_src_transition_supply, + policy_src_transition_default, + + policy_src_ready, + policy_src_get_sink_caps, + + policy_src_send_softrst, + policy_src_softrst, + policy_src_send_hardrst, + + policy_snk_startup, + policy_snk_discovery, + policy_snk_wait_caps, + policy_snk_evaluate_caps, + policy_snk_select_cap, + policy_snk_transition_sink, + policy_snk_ready, + + policy_snk_send_softrst, + policy_snk_softrst, + policy_snk_send_hardrst, + + policy_snk_transition_default, + + /* PR SWAP */ + policy_src_prs_evaluate, + policy_src_prs_accept, + policy_src_prs_transition_to_off, + policy_src_prs_source_off, + policy_src_prs_assert_rd, + policy_src_prs_reject, + policy_src_prs_send_swap, + + policy_snk_prs_evaluate, + policy_snk_prs_accept, + policy_snk_prs_transition_to_off, + policy_snk_prs_source_on, + policy_snk_prs_assert_rp, + policy_snk_prs_reject, + policy_snk_prs_send_swap, + + /* VC SWAP */ + policy_vcs_dfp_send_swap, + policy_vcs_dfp_wait_for_ufp_vconn, + policy_vcs_dfp_turn_off_vconn, + policy_vcs_dfp_turn_on_vconn, + policy_vcs_dfp_send_ps_rdy, + + policy_vcs_ufp_evaluate_swap, + policy_vcs_ufp_reject, + policy_vcs_ufp_accept, + policy_vcs_ufp_wait_for_dfp_vconn, + policy_vcs_ufp_turn_off_vconn, + policy_vcs_ufp_turn_on_vconn, + policy_vcs_ufp_send_ps_rdy, + + policy_drs_ufp_evaluate, + policy_drs_ufp_accept, + policy_drs_ufp_reject, + policy_drs_ufp_change, + policy_drs_ufp_send_swap, + + policy_drs_dfp_evaluate, + policy_drs_dfp_accept, + policy_drs_dfp_reject, + policy_drs_dfp_change, + policy_drs_dfp_send_swap, + + attach_try_src, + attach_try_snk, +}; + +enum vdm_state { + VDM_STATE_DISCOVERY_ID, + VDM_STATE_DISCOVERY_SVID, + VDM_STATE_DISCOVERY_MODES, + VDM_STATE_ENTER_MODE, + VDM_STATE_UPDATE_STATUS, + VDM_STATE_DP_CONFIG, + VDM_STATE_NOTIFY, + VDM_STATE_READY, + VDM_STATE_ERR, +}; + +enum tcpm_rp_value { + TYPEC_RP_USB = 0, + TYPEC_RP_1A5 = 1, + TYPEC_RP_3A0 = 2, + TYPEC_RP_RESERVED = 3, +}; + +enum role_mode { + ROLE_MODE_NONE, + ROLE_MODE_DRP, + ROLE_MODE_UFP, + ROLE_MODE_DFP, + ROLE_MODE_ASS, +}; + +#define SBF(s, v) ((s) << (v)) +#define SWITCHES0_PDWN1 SBF(1, 0) +#define SWITCHES0_PDWN2 SBF(1, 1) +#define SWITCHES0_MEAS_CC1 SBF(1, 2) +#define SWITCHES0_MEAS_CC2 SBF(1, 3) +#define SWITCHES0_VCONN_CC1 SBF(1, 4) +#define SWITCHES0_VCONN_CC2 SBF(1, 5) +#define SWITCHES0_PU_EN1 SBF(1, 6) +#define SWITCHES0_PU_EN2 SBF(1, 7) + +#define SWITCHES1_TXCC1 SBF(1, 0) +#define SWITCHES1_TXCC2 SBF(1, 1) +#define SWITCHES1_AUTO_CRC SBF(1, 2) +#define SWITCHES1_DATAROLE SBF(1, 4) +#define SWITCHES1_SPECREV SBF(3, 5) +#define SWITCHES1_POWERROLE SBF(1, 7) + +#define MEASURE_MDAC SBF(0x3f, 0) +#define MEASURE_VBUS SBF(1, 6) + +#define SLICE_SDAC SBF(0x3f, 0) +#define SLICE_SDAC_HYS SBF(3, 6) + +#define CONTROL0_TX_START SBF(1, 0) +#define CONTROL0_AUTO_PRE SBF(1, 1) +#define CONTROL0_HOST_CUR SBF(3, 2) +#define CONTROL0_HOST_CUR_USB SBF(1, 2) +#define CONTROL0_HOST_CUR_1A5 SBF(2, 2) +#define CONTROL0_HOST_CUR_3A0 SBF(3, 2) +#define CONTROL0_INT_MASK SBF(1, 5) +#define CONTROL0_TX_FLUSH SBF(1, 6) + +#define CONTROL1_ENSOP1 SBF(1, 0) +#define CONTROL1_ENSOP2 SBF(1, 1) +#define CONTROL1_RX_FLUSH SBF(1, 2) +#define CONTROL1_BIST_MODE2 SBF(1, 4) +#define CONTROL1_ENSOP1DB SBF(1, 5) +#define CONTROL1_ENSOP2DB SBF(1, 6) + +#define CONTROL2_TOGGLE SBF(1, 0) +#define CONTROL2_MODE SBF(3, 1) +#define CONTROL2_MODE_NONE 0 +#define CONTROL2_MODE_DFP SBF(3, 1) +#define CONTROL2_MODE_UFP SBF(2, 1) +#define CONTROL2_MODE_DRP SBF(1, 1) +#define CONTROL2_WAKE_EN SBF(1, 3) +#define CONTROL2_TOG_RD_ONLY SBF(1, 5) +#define CONTROL2_TOG_SAVE_PWR1 SBF(1, 6) +#define CONTROL2_TOG_SAVE_PWR2 SBF(1, 7) + +#define CONTROL3_AUTO_RETRY SBF(1, 0) +#define CONTROL3_N_RETRIES SBF(3, 1) +#define CONTROL3_AUTO_SOFTRESET SBF(1, 3) +#define CONTROL3_AUTO_HARDRESET SBF(1, 4) +#define CONTROL3_SEND_HARDRESET SBF(1, 6) + +#define MASK_M_BC_LVL SBF(1, 0) +#define MASK_M_COLLISION SBF(1, 1) +#define MASK_M_WAKE SBF(1, 2) +#define MASK_M_ALERT SBF(1, 3) +#define MASK_M_CRC_CHK SBF(1, 4) +#define MASK_M_COMP_CHNG SBF(1, 5) +#define MASK_M_ACTIVITY SBF(1, 6) +#define MASK_M_VBUSOK SBF(1, 7) + +#define POWER_PWR SBF(0xf, 0) + +#define RESET_SW_RESET SBF(1, 0) +#define RESET_PD_RESET SBF(1, 1) + +#define MASKA_M_HARDRST SBF(1, 0) +#define MASKA_M_SOFTRST SBF(1, 1) +#define MASKA_M_TXSENT SBF(1, 2) +#define MASKA_M_HARDSENT SBF(1, 3) +#define MASKA_M_RETRYFAIL SBF(1, 4) +#define MASKA_M_SOFTFAIL SBF(1, 5) +#define MASKA_M_TOGDONE SBF(1, 6) +#define MASKA_M_OCP_TEMP SBF(1, 7) + +#define MASKB_M_GCRCSEND SBF(1, 0) + +#define CONTROL4_TOG_USRC_EXIT SBF(1, 0) + +#define MDAC_1P6V 0x26 + +#define STATUS0A_HARDRST SBF(1, 0) +#define STATUS0A_SOFTRST SBF(1, 1) +#define STATUS0A_POWER23 SBF(3, 2) +#define STATUS0A_RETRYFAIL SBF(1, 4) +#define STATUS0A_SOFTFAIL SBF(1, 5) +#define STATUS0A_TOGDONE SBF(1, 6) +#define STATUS0A_M_OCP_TEMP SBF(1, 7) + +#define STATUS1A_RXSOP SBF(1, 0) +#define STATUS1A_RXSOP1DB SBF(1, 1) +#define STATUS1A_RXSOP2DB SBF(1, 2) +#define STATUS1A_TOGSS SBF(7, 3) +#define CC_STATE_TOGSS_CC1 SBF(1, 0) +#define CC_STATE_TOGSS_CC2 SBF(1, 1) +#define CC_STATE_TOGSS_IS_UFP SBF(1, 2) + +#define INTERRUPTA_HARDRST SBF(1, 0) +#define INTERRUPTA_SOFTRST SBF(1, 1) +#define INTERRUPTA_TXSENT SBF(1, 2) +#define INTERRUPTA_HARDSENT SBF(1, 3) +#define INTERRUPTA_RETRYFAIL SBF(1, 4) +#define INTERRUPTA_SOFTFAIL SBF(1, 5) +#define INTERRUPTA_TOGDONE SBF(1, 6) +#define INTERRUPTA_OCP_TEMP SBF(1, 7) + +#define INTERRUPTB_GCRCSENT SBF(1, 0) + +#define STATUS0_BC_LVL SBF(3, 0) +#define STATUS0_WAKE SBF(1, 2) +#define STATUS0_ALERT SBF(1, 3) +#define STATUS0_CRC_CHK SBF(1, 4) +#define STATUS0_COMP SBF(1, 5) +#define STATUS0_ACTIVITY SBF(1, 6) +#define STATUS0_VBUSOK SBF(1, 7) + +#define STATUS1_OCP SBF(1, 0) +#define STATUS1_OVRTEMP SBF(1, 1) +#define STATUS1_TX_FULL SBF(1, 2) +#define STATUS1_TX_EMPTY SBF(1, 3) +#define STATUS1_RX_FULL SBF(1, 4) +#define STATUS1_RX_EMPTY SBF(1, 5) +#define STATUS1_RXSOP1 SBF(1, 6) +#define STATUS1_RXSOP2 SBF(1, 7) + +#define INTERRUPT_BC_LVL SBF(1, 0) +#define INTERRUPT_COLLISION SBF(1, 1) +#define INTERRUPT_WAKE SBF(1, 2) +#define INTERRUPT_ALERT SBF(1, 3) +#define INTERRUPT_CRC_CHK SBF(1, 4) +#define INTERRUPT_COMP_CHNG SBF(1, 5) +#define INTERRUPT_ACTIVITY SBF(1, 6) +#define INTERRUPT_VBUSOK SBF(1, 7) + +#define FUSB_TKN_TXON 0xa1 +#define FUSB_TKN_SYNC1 0x12 +#define FUSB_TKN_SYNC2 0x13 +#define FUSB_TKN_SYNC3 0x1b +#define FUSB_TKN_RST1 0x15 +#define FUSB_TKN_RST2 0x16 +#define FUSB_TKN_PACKSYM 0x80 +#define FUSB_TKN_JAMCRC 0xff +#define FUSB_TKN_EOP 0x14 +#define FUSB_TKN_TXOFF 0xfe + +/* USB PD Control Message Types */ +#define CONTROLMESSAGE 0 +#define CMT_GOODCRC 1 +#define CMT_GOTOMIN 2 +#define CMT_ACCEPT 3 +#define CMT_REJECT 4 +#define CMT_PING 5 +#define CMT_PS_RDY 6 +#define CMT_GETSOURCECAP 7 +#define CMT_GETSINKCAP 8 +#define CMT_DR_SWAP 9 +#define CMT_PR_SWAP 10 +#define CMT_VCONN_SWAP 11 +#define CMT_WAIT 12 +#define CMT_SOFTRESET 13 + +/* USB PD Data Message Types */ +#define DATAMESSAGE 1 +#define DMT_SOURCECAPABILITIES 1 +#define DMT_REQUEST 2 +#define DMT_BIST 3 +#define DMT_SINKCAPABILITIES 4 +#define DMT_VENDERDEFINED 15 + +/* VDM Command Types */ +#define VDM_DISCOVERY_ID 0X01 +#define VDM_DISCOVERY_SVIDS 0X02 +#define VDM_DISCOVERY_MODES 0X03 +#define VDM_ENTER_MODE 0X04 +#define VDM_EXIT_MODE 0X05 +#define VDM_ATTENTION 0X06 +#define VDM_DP_STATUS_UPDATE 0X10 +#define VDM_DP_CONFIG 0X11 + +#define VDM_TYPE_INIT 0 +#define VDM_TYPE_ACK 1 +#define VDM_TYPE_NACK 2 +#define VDM_TYPE_BUSY 3 + +/* 200ms at least, 1 cycle about 6ms */ +#define N_DEBOUNCE_CNT 33 +#define N_CAPS_COUNT 50 +#define N_HARDRESET_COUNT 0 + +#define T_NO_RESPONSE 5000 +#define T_SRC_RECOVER 830 +#define T_TYPEC_SEND_SOURCECAP 100 +#define T_SENDER_RESPONSE 30 +#define T_SRC_TRANSITION 30 +#define T_TYPEC_SINK_WAIT_CAP 500 +#define T_PS_TRANSITION 500 +#define T_BMC_TIMEOUT 5 +#define T_PS_HARD_RESET_MAX 35 +#define T_SAFE_0V 650 +#define T_SRC_TURN_ON 275 +#define T_SRC_RECOVER_MAX 1000 +#define T_PD_SOURCE_OFF 920 +#define T_PD_SOURCE_ON 480 +#define T_PD_SWAP_SOURCE_START 20 +#define T_PD_VCONN_SRC_ON 100 +#define T_PD_TRY_DRP 75 + +#define T_NO_TRIGGER 500 +#define T_DISABLED 0xffff + +#define PD_HEADER_CNT(header) (((header) >> 12) & 7) +#define PD_HEADER_TYPE(header) ((header) & 0xF) +#define PD_HEADER_ID(header) (((header) >> 9) & 7) + +#define VDM_HEADER_TYPE(header) (((header) >> 6) & 3) +#define VDMHEAD_CMD_TYPE_MASK (3 << 6) +#define VDMHEAD_CMD_MASK (0x1f << 0) +#define VDMHEAD_STRUCT_TYPE_MASK BIT(15) + +#define GET_VDMHEAD_CMD_TYPE(head) ((head & VDMHEAD_CMD_TYPE_MASK) >> 6) +#define GET_VDMHEAD_CMD(head) (head & VDMHEAD_CMD_MASK) +#define GET_VDMHEAD_STRUCT_TYPE(head) ((head & VDMHEAD_STRUCT_TYPE_MASK) >> 15) + +#define DP_STATUS_MASK 0x000000ff +#define DP_STATUS_HPD_STATE BIT(7) + +#define GET_DP_STATUS(status) (status & DP_STATUS_MASK) +#define GET_DP_STATUS_HPD(status) ((status & DP_STATUS_HPD_STATE) >> 7) + +#define VDM_IDHEAD_USBVID_MASK (0xffff << 0) +#define VDM_IDHEAD_MODALSUPPORT_MASK BIT(26) +#define VDM_IDHEAD_PRODUCTTYPE (7 << 27) +#define VDM_IDHEAD_USBDEVICE BIT(30) +#define VDM_IDHEAD_USBHOST BIT(30) + +#define CAP_POWER_TYPE(PDO) ((PDO >> 30) & 3) +#define CAP_FPDO_VOLTAGE(PDO) ((PDO >> 10) & 0x3ff) +#define CAP_VPDO_VOLTAGE(PDO) ((PDO >> 20) & 0x3ff) +#define CAP_FPDO_CURRENT(PDO) ((PDO >> 0) & 0x3ff) +#define CAP_VPDO_CURRENT(PDO) ((PDO >> 0) & 0x3ff) + +enum CC_ORIENTATION { + NONE, + CC1, + CC2, +}; + +enum typec_cc_polarity { + TYPEC_POLARITY_CC1, + TYPEC_POLARITY_CC2, +}; + +enum CC_MODE { + CC_PULL_UP, + CC_PULL_DOWN, + CC_PULL_NONE, +}; + +enum typec_power_role { + POWER_ROLE_SINK = 0, + POWER_ROLE_SOURCE, +}; + +enum typec_data_role { + DATA_ROLE_UFP = 0, + DATA_ROLE_DFP, +}; + +struct notify_info { + enum CC_ORIENTATION orientation; + /* 0 UFP : 1 DFP */ + enum typec_power_role power_role; + enum typec_data_role data_role; + + bool is_cc_connected; + bool is_pd_connected; + + bool is_enter_mode; + int pin_assignment_support; + int pin_assignment_def; + bool attention; + u32 dp_status; + u32 dp_caps; +}; + +enum tx_state { + tx_idle, + tx_busy, + tx_failed, + tx_success +}; + +struct PD_CAP_INFO { + u32 peak_current; + u32 specification_revision; + u32 externally_powered; + u32 usb_suspend_support; + u32 usb_communications_cap; + u32 dual_role_power; + u32 data_role_swap; + u32 supply_type; +}; + +struct fusb30x_chip { + struct i2c_client *client; + struct device *dev; + struct regmap *regmap; + struct work_struct work; + struct workqueue_struct *fusb30x_wq; + struct hrtimer timer_state_machine; + struct hrtimer timer_mux_machine; + struct PD_CAP_INFO pd_cap_info; + struct notify_info notify; + struct notify_info notify_cmp; + struct extcon_dev *extcon; + enum connection_state conn_state; + struct gpio_desc *gpio_vbus_5v; + struct gpio_desc *gpio_vbus_other; + struct gpio_desc *gpio_int; + struct gpio_desc *gpio_discharge; + int timer_state; + int timer_mux; + int port_num; + u32 work_continue; + spinlock_t irq_lock; + int gpio_int_irq; + int enable_irq; + + /* + * --------------------------------- + * | role 0x03 << 2, | cc_use 0x03 | + * | src 1 << 2, | cc1 1 | + * | snk 2 << 2, | cc2 2 | + * --------------------------------- + */ + u8 cc_state; + int cc1; + int cc2; + enum typec_cc_polarity cc_polarity; + u8 val_tmp; + u8 debounce_cnt; + int sub_state; + int caps_counter; + u32 send_load[7]; + u32 rec_load[7]; + u16 send_head; + u16 rec_head; + int msg_id; + enum tx_state tx_state; + int hardrst_count; + u32 source_power_supply[7]; + /* 50mv unit */ + u32 source_max_current[7]; + /* 10ma uint*/ + int pos_power; + /* + * if PartnerCap[0] == 0xffffffff + * show Partner Device do not support supply + */ + u32 partner_cap[7]; + int n_caps_used; + int vdm_state; + int vdm_substate; + int vdm_send_state; + u16 vdm_svid[12]; + int vdm_svid_num; + u32 vdm_id; + u8 chip_id; + bool vconn_enabled; + bool is_pd_support; + int pd_output_vol; + int pd_output_cur; + int cc_meas_high; + int cc_meas_low; + bool vbus_begin; + + enum role_mode role; + bool vconn_supported; + bool try_role_complete; + enum role_mode try_role; +}; + +#endif /* FUSB302_H */ +