| /* |
| * (C) Copyright 2019 Shenzhen Wesion Co., Ltd |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| #include <common.h> |
| #include <errno.h> |
| #include <fdtdec.h> |
| #include <i2c.h> |
| #include <malloc.h> |
| #include <asm/gpio.h> |
| #include <fusb302.h> |
| #include <asm/io.h> |
| |
| #define FUSB30X_I2C_DEVICETREE_NAME "fairchild,fusb302" |
| |
| #define FUSB302_I2C_SPEED 400000 |
| #define FUSB302_I2C_ADDR 0x22 |
| #define CHARGE_INPUT_DEFAULT_CUR 6000 |
| #define CHARGE_INPUT_DEFAULT_VOL 12000 |
| |
| #define FUSB302_INT_GPIO 8 /* GPIOAO_8 */ |
| |
| #define FUSB_MODE_DRP 0 |
| #define FUSB_MODE_UFP 1 |
| #define FUSB_MODE_DFP 2 |
| #define FUSB_MODE_ASS 3 |
| |
| #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 0x1 |
| #define EVENT_RX 0x2 |
| #define EVENT_TX 0x4 |
| #define EVENT_REC_RESET 0x8 |
| |
| static struct fusb30x_chip chip; |
| |
| static int fusb302_gpio_is_valid(int gpio) |
| { |
| if (-1 == gpio) |
| return 0; |
| else |
| return 1; |
| } |
| |
| static int fusb302_i2c_probe(u32 addr) |
| { |
| int ret; |
| |
| i2c_init(FUSB302_I2C_SPEED, 0); |
| ret = i2c_probe(addr); |
| if (ret < 0) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| int fusb302_i2c_read(u8 reg, u8 *val) |
| { |
| int ret; |
| |
| ret = i2c_read(chip.addr, reg, 1, val, 1); |
| if (ret != 0) |
| printf("fusb302 i2c read error!!!\n"); |
| |
| return ret; |
| } |
| |
| int fusb302_i2c_write(u8 reg, u8 val) |
| { |
| int ret; |
| |
| ret = i2c_write(chip.addr, reg, 1, (u8 *)&val, 1); |
| if (ret != 0) |
| printf("fusb302 i2c write error!!!\n"); |
| |
| return ret; |
| } |
| |
| int fusb302_i2c_raw_write(u8 reg, const void *val, size_t val_len) |
| { |
| int i; |
| |
| for (i=0; i<val_len; i++) |
| if (fusb302_i2c_write(reg, ((u8 *)val)[i])) { |
| printf("fusb302 fusb302_i2c_write error!!!\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int fusb302_i2c_raw_read(u8 reg, void *val, size_t val_len) |
| { |
| int ret; |
| |
| ret = i2c_read(chip.addr, reg, 1, val, val_len); |
| if (ret != 0) |
| printf("fusb302 i2c read error!!!\n"); |
| |
| return ret; |
| } |
| |
| int fusb302_i2c_update_bits(u8 reg, u8 mask, uchar val) |
| { |
| int ret; |
| u8 tmp, orig; |
| |
| ret = fusb302_i2c_read(reg, &orig); |
| if (ret != 0) |
| return ret; |
| |
| tmp = orig & ~mask; |
| tmp |= val & mask; |
| |
| if (tmp != orig) |
| ret = fusb302_i2c_write(reg, tmp); |
| |
| return ret; |
| } |
| |
| static void set_state(struct fusb30x_chip *chip, enum connection_state state) |
| { |
| if (!state) |
| debug("PD disabled\n"); |
| chip->conn_state = state; |
| chip->sub_state = 0; |
| chip->val_tmp = 0; |
| } |
| |
| static int tcpm_get_message(struct fusb30x_chip *chip) |
| { |
| u8 buf[32]; |
| int len; |
| |
| fusb302_i2c_raw_read(FUSB_REG_FIFO, buf, 3); |
| chip->rec_head = (buf[1] & 0xff) | ((buf[2] << 8) & 0xff00); |
| |
| len = PD_HEADER_CNT(chip->rec_head) << 2; |
| fusb302_i2c_raw_read(FUSB_REG_FIFO, buf, len + 4); |
| |
| memcpy(chip->rec_load, buf, len); |
| |
| return 0; |
| } |
| |
| static void fusb302_flush_rx_fifo(struct fusb30x_chip *chip) |
| { |
| tcpm_get_message(chip); |
| } |
| |
| static int tcpm_get_cc(struct fusb30x_chip *chip, int *CC1, int *CC2) |
| { |
| int *CC_MEASURE; |
| u8 store, val; |
| |
| *CC1 = TYPEC_CC_VOLT_OPEN; |
| *CC2 = TYPEC_CC_VOLT_OPEN; |
| |
| if (chip->cc_state & 0x01) |
| CC_MEASURE = CC1; |
| else |
| CC_MEASURE = CC2; |
| |
| if (chip->cc_state & 0x04) { |
| fusb302_i2c_read(FUSB_REG_SWITCHES0, &store); |
| /* measure cc1 first */ |
| fusb302_i2c_update_bits(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); |
| udelay(300); |
| |
| fusb302_i2c_read(FUSB_REG_STATUS0, &val); |
| val &= STATUS0_BC_LVL; |
| if (val) |
| *CC1 = val; |
| |
| fusb302_i2c_update_bits(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); |
| udelay(300); |
| |
| fusb302_i2c_read(FUSB_REG_STATUS0, &val); |
| val &= STATUS0_BC_LVL; |
| if (val) |
| *CC2 = val; |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES0, |
| SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2, |
| store); |
| } else { |
| fusb302_i2c_read(FUSB_REG_SWITCHES0, &store); |
| val = store; |
| val &= ~(SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2 | |
| SWITCHES0_PU_EN1 | SWITCHES0_PU_EN2); |
| if (chip->cc_state & 0x01) |
| val |= SWITCHES0_MEAS_CC1 | SWITCHES0_PU_EN1; |
| else |
| val |= SWITCHES0_MEAS_CC2 | SWITCHES0_PU_EN2; |
| |
| fusb302_i2c_write(FUSB_REG_SWITCHES0, val); |
| |
| fusb302_i2c_write(FUSB_REG_MEASURE, chip->cc_meas_high); |
| udelay(300); |
| |
| fusb302_i2c_read(FUSB_REG_STATUS0, &val); |
| if (val & STATUS0_COMP) { |
| int retry = 3; |
| int comp_times = 0; |
| |
| while (retry--) { |
| fusb302_i2c_write(FUSB_REG_MEASURE, chip->cc_meas_high); |
| udelay(300); |
| fusb302_i2c_read(FUSB_REG_STATUS0, &val); |
| if (val & STATUS0_COMP) { |
| comp_times++; |
| if (comp_times == 3) { |
| *CC_MEASURE = TYPEC_CC_VOLT_OPEN; |
| fusb302_i2c_write(FUSB_REG_SWITCHES0, store); |
| } |
| } |
| } |
| } else { |
| fusb302_i2c_write(FUSB_REG_MEASURE, chip->cc_meas_low); |
| fusb302_i2c_read(FUSB_REG_MEASURE, &val); |
| udelay(300); |
| |
| fusb302_i2c_read(FUSB_REG_STATUS0, &val); |
| |
| if (val & STATUS0_COMP) |
| *CC_MEASURE = TYPEC_CC_VOLT_RD; |
| else |
| *CC_MEASURE = TYPEC_CC_VOLT_RA; |
| fusb302_i2c_write(FUSB_REG_SWITCHES0, store); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int tcpm_set_cc(struct fusb30x_chip *chip, int mode) |
| { |
| u8 val = 0, mask; |
| |
| val &= ~(SWITCHES0_PU_EN1 | SWITCHES0_PU_EN2 | |
| SWITCHES0_PDWN1 | SWITCHES0_PDWN2); |
| |
| mask = ~val; |
| |
| switch (mode) { |
| case FUSB_MODE_DFP: |
| if (chip->togdone_pullup) |
| val |= SWITCHES0_PU_EN2; |
| else |
| val |= SWITCHES0_PU_EN1; |
| break; |
| case FUSB_MODE_UFP: |
| val |= SWITCHES0_PDWN1 | SWITCHES0_PDWN2; |
| break; |
| case FUSB_MODE_DRP: |
| val |= SWITCHES0_PDWN1 | SWITCHES0_PDWN2; |
| break; |
| case FUSB_MODE_ASS: |
| break; |
| } |
| |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES0, mask, val); |
| return 0; |
| } |
| |
| static int tcpm_set_rx_enable(struct fusb30x_chip *chip, int enable) |
| { |
| u8 val = 0; |
| |
| if (enable) { |
| if (chip->cc_polarity) |
| val |= SWITCHES0_MEAS_CC2; |
| else |
| val |= SWITCHES0_MEAS_CC1; |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES0, |
| SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2, |
| val); |
| fusb302_flush_rx_fifo(chip); |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES1, |
| SWITCHES1_AUTO_CRC, SWITCHES1_AUTO_CRC); |
| } else { |
| /* |
| * bit of a hack here. |
| * when this function is called to disable rx (enable=0) |
| * using it as an indication of detach (gulp!) |
| * to reset our knowledge of where |
| * the toggle state machine landed. |
| */ |
| chip->togdone_pullup = 0; |
| |
| #ifdef FUSB_HAVE_DRP |
| tcpm_set_cc(chip, FUSB_MODE_DRP); |
| fusb302_i2c_update_bits(FUSB_REG_CONTROL2, |
| CONTROL2_TOG_RD_ONLY, |
| CONTROL2_TOG_RD_ONLY); |
| #endif |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES0, |
| SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2, |
| 0); |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES1, SWITCHES1_AUTO_CRC, 0); |
| } |
| |
| return 0; |
| } |
| |
| static int tcpm_set_msg_header(struct fusb30x_chip *chip) |
| { |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES1, |
| SWITCHES1_POWERROLE | SWITCHES1_DATAROLE, |
| (chip->power_role << 7) | |
| (chip->data_role << 4)); |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES1, |
| SWITCHES1_SPECREV, 2 << 5); |
| return 0; |
| } |
| |
| static int tcpm_set_polarity(struct fusb30x_chip *chip, bool polarity) |
| { |
| u8 val = 0; |
| |
| #ifdef FUSB_VCONN_SUPPORT |
| if (chip->vconn_enabled) { |
| if (polarity) |
| val |= SWITCHES0_VCONN_CC1; |
| else |
| val |= SWITCHES0_VCONN_CC2; |
| } |
| #endif |
| |
| if (polarity) |
| val |= SWITCHES0_MEAS_CC2; |
| else |
| val |= SWITCHES0_MEAS_CC1; |
| |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES0, |
| SWITCHES0_VCONN_CC1 | SWITCHES0_VCONN_CC2 | |
| SWITCHES0_MEAS_CC1 | SWITCHES0_MEAS_CC2, |
| val); |
| |
| val = 0; |
| if (polarity) |
| val |= SWITCHES1_TXCC2; |
| else |
| val |= SWITCHES1_TXCC1; |
| fusb302_i2c_update_bits(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) { |
| tcpm_set_polarity(chip, chip->cc_polarity); |
| } else { |
| val &= ~(SWITCHES0_VCONN_CC1 | SWITCHES0_VCONN_CC2); |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES0, |
| SWITCHES0_VCONN_CC1 | SWITCHES0_VCONN_CC2, |
| val); |
| } |
| chip->vconn_enabled = enable; |
| return 0; |
| } |
| |
| static void fusb302_pd_reset(struct fusb30x_chip *chip) |
| { |
| fusb302_i2c_write(FUSB_REG_RESET, RESET_PD_RESET); |
| } |
| |
| static void tcpm_select_rp_value(struct fusb30x_chip *chip, u32 rp) |
| { |
| u8 control0_reg; |
| |
| fusb302_i2c_read(FUSB_REG_CONTROL0, &control0_reg); |
| |
| control0_reg &= ~CONTROL0_HOST_CUR; |
| /* |
| * according to the host current, the compare value is different |
| */ |
| 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 180ua , high voltage is 1.596v, low is 0.42v */ |
| case TYPEC_RP_1A5: |
| chip->cc_meas_high = 0x26; |
| chip->cc_meas_low = 0xa; |
| control0_reg |= CONTROL0_HOST_CUR_1A5; |
| break; |
| /* host pull up current is 330ua , high voltage is 2.604v, low is 0.798v*/ |
| case TYPEC_RP_3A0: |
| chip->cc_meas_high = 0x26; |
| chip->cc_meas_low = 0x13; |
| control0_reg |= CONTROL0_HOST_CUR_3A0; |
| break; |
| default: |
| chip->cc_meas_high = 0x26; |
| chip->cc_meas_low = 0xa; |
| control0_reg |= CONTROL0_HOST_CUR_1A5; |
| break; |
| } |
| |
| fusb302_i2c_write(FUSB_REG_CONTROL0, control0_reg); |
| } |
| |
| static void tcpm_init(struct fusb30x_chip *chip) |
| { |
| u8 val; |
| u8 tmp = 0; |
| |
| fusb302_i2c_read(FUSB_REG_DEVICEID, &tmp); |
| i2c_read(chip->addr, FUSB_REG_DEVICEID, 1, (u8 *)&tmp, 1); |
| chip->chip_id = (u8)tmp; |
| |
| chip->is_cc_connected = 0; |
| chip->cc_state = 0; |
| |
| /* restore default settings */ |
| fusb302_i2c_update_bits(FUSB_REG_RESET, RESET_SW_RESET, |
| RESET_SW_RESET); |
| fusb302_pd_reset(chip); |
| /* set auto_retry and number of retries */ |
| fusb302_i2c_update_bits(FUSB_REG_CONTROL3, |
| CONTROL3_AUTO_RETRY | CONTROL3_N_RETRIES, |
| CONTROL3_AUTO_RETRY | CONTROL3_N_RETRIES), |
| |
| /* set interrupts */ |
| val = 0xff; |
| val &= ~(MASK_M_BC_LVL | MASK_M_COLLISION | MASK_M_ALERT | |
| MASK_M_VBUSOK); |
| fusb302_i2c_write(FUSB_REG_MASK, val); |
| |
| val = 0xff; |
| val &= ~(MASKA_M_TOGDONE | MASKA_M_RETRYFAIL | MASKA_M_HARDSENT | |
| MASKA_M_TXSENT | MASKA_M_HARDRST); |
| fusb302_i2c_write(FUSB_REG_MASKA, val); |
| |
| val = 0xff; |
| val = ~MASKB_M_GCRCSEND; |
| fusb302_i2c_write(FUSB_REG_MASKB, val); |
| |
| #ifdef FUSB_HAVE_DRP |
| fusb302_i2c_update_bits(FUSB_REG_CONTROL2, |
| CONTROL2_MODE | CONTROL2_TOGGLE, |
| (1 << 1) | CONTROL2_TOGGLE); |
| |
| fusb302_i2c_update_bits(FUSB_REG_CONTROL2, |
| CONTROL2_TOG_RD_ONLY, |
| CONTROL2_TOG_RD_ONLY); |
| #endif |
| tcpm_select_rp_value(chip, TYPEC_RP_1A5); |
| /* Interrupts Enable */ |
| fusb302_i2c_update_bits(FUSB_REG_CONTROL0, CONTROL0_INT_MASK, |
| ~CONTROL0_INT_MASK); |
| |
| tcpm_set_polarity(chip, 0); |
| tcpm_set_vconn(chip, 0); |
| |
| fusb302_i2c_write(FUSB_REG_POWER, 0xf); |
| } |
| |
| static int tcpm_check_vbus(struct fusb30x_chip *chip) |
| { |
| u8 val; |
| |
| /* Read status register */ |
| fusb302_i2c_read(FUSB_REG_STATUS0, &val); |
| |
| return (val & STATUS0_VBUSOK) ? 1 : 0; |
| } |
| |
| 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->power_role & 0x1) << 8) | |
| (1 << 6) | |
| ((chip->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); |
| } |
| } |
| |
| static enum tx_state policy_send_data(struct fusb30x_chip *chip) |
| { |
| u8 senddata[40]; |
| int pos = 0; |
| u8 len; |
| |
| debug("%s: chip->tx_state=%d\n", __func__, chip->tx_state); |
| 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; |
| |
| fusb302_i2c_raw_write(FUSB_REG_FIFO, senddata, pos); |
| chip->tx_state = tx_busy; |
| break; |
| |
| default: |
| /* wait Tx result */ |
| break; |
| } |
| |
| return chip->tx_state; |
| } |
| |
| static void fusb_state_unattached(struct fusb30x_chip *chip, int evt) |
| { |
| chip->is_cc_connected = 0; |
| if ((evt & EVENT_CC) && chip->cc_state) { |
| if (chip->cc_state & 0x04) { |
| set_state(chip, attach_wait_sink); |
| debug("attach_wait_sink\n"); |
| |
| } else { |
| set_state(chip, attach_wait_source); |
| debug("attach_wait_source\n"); |
| } |
| tcpm_get_cc(chip, &chip->cc1, &chip->cc2); |
| chip->debounce_cnt = 0; |
| } |
| } |
| |
| static void set_state_unattached(struct fusb30x_chip *chip) |
| { |
| debug("connection has disconnected\n"); |
| tcpm_init(chip); |
| tcpm_set_rx_enable(chip, 0); |
| chip->conn_state = unattached; |
| tcpm_set_cc(chip, FUSB_MODE_DRP); |
| |
| if (fusb302_gpio_is_valid(chip->gpio_discharge.gpio)) { |
| gpio_set_value(chip->gpio_discharge.gpio, 1); |
| udelay(1000 * 1000); |
| gpio_set_value(chip->gpio_discharge.gpio, 0); |
| } |
| } |
| |
| static void fusb_state_attach_wait_sink(struct fusb30x_chip *chip) |
| { |
| int cc1, cc2, count = 10; |
| |
| while (count--) { |
| 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; |
| } |
| |
| udelay(1000 * 2); |
| if (chip->debounce_cnt > N_DEBOUNCE_CNT) { |
| if ((chip->cc1 != chip->cc2) && |
| ((!chip->cc1) || (!chip->cc2))) { |
| set_state(chip, attached_sink); |
| debug("%s attached_sink\n", __func__); |
| |
| } else { |
| set_state(chip, disabled); |
| debug("%s unattached_sink\n", __func__); |
| } |
| return; |
| } |
| } |
| } |
| |
| static void fusb_state_attached_sink(struct fusb30x_chip *chip) |
| { |
| chip->is_cc_connected = 1; |
| if (chip->cc_state & 0x01) |
| chip->cc_polarity = 0; |
| else |
| chip->cc_polarity = 1; |
| |
| chip->power_role = 0; |
| chip->data_role = 0; |
| chip->hardrst_count = 0; |
| set_state(chip, policy_snk_startup); |
| printf("CC connected in %d as UFP\n", chip->cc_polarity); |
| } |
| |
| static void fusb_state_snk_startup(struct fusb30x_chip *chip) |
| { |
| chip->is_pd_connected = 0; |
| chip->msg_id = 0; |
| chip->vdm_state = 0; |
| chip->vdm_substate = 0; |
| chip->vdm_send_state = 0; |
| chip->val_tmp = 0; |
| chip->pos_power = 0; |
| |
| 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); |
| } |
| |
| static void fusb_state_snk_discovery(struct fusb30x_chip *chip) |
| { |
| set_state(chip, policy_snk_wait_caps); |
| } |
| |
| static void fusb_state_snk_wait_caps(struct fusb30x_chip *chip, int evt) |
| { |
| if (evt & EVENT_RX) { |
| if (PD_HEADER_CNT(chip->rec_head) && |
| PD_HEADER_TYPE(chip->rec_head) == DMT_SOURCECAPABILITIES) { |
| set_state(chip, policy_snk_evaluate_caps); |
| } |
| } |
| } |
| |
| 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) |
| { |
| int max_vol = CHARGE_INPUT_DEFAULT_VOL, max_cur = CHARGE_INPUT_DEFAULT_CUR; |
| |
| if (max_vol > 0 && max_cur > 0) |
| fusb_set_pos_power(chip, max_vol, max_cur); |
| |
| printf("charge ic max_vol = %dmv max_cur = %dma\n", max_vol, max_cur); |
| return 0; |
| } |
| |
| static void fusb_state_snk_evaluate_caps(struct fusb30x_chip *chip, int 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); |
| |
| printf("chip->pos_power = %d, chip->pd_output_vol=%d chip->pd_output_cur=%d\n", |
| chip->pos_power, chip->pd_output_vol, chip->pd_output_cur); |
| 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, int evt) |
| { |
| u32 tmp; |
| |
| debug("%s chip->sub_state=%d %d\n", __func__, chip->sub_state, |
| PD_HEADER_TYPE(chip->rec_head)); |
| 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->sub_state++; |
| } else if (tmp == tx_failed) { |
| set_state(chip, policy_snk_discovery); |
| 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); |
| break; |
| case CMT_WAIT: |
| case CMT_REJECT: |
| if (chip->is_pd_connected) { |
| printf("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; |
| } |
| } |
| } |
| break; |
| } |
| } |
| |
| static void fusb_state_snk_transition_sink(struct fusb30x_chip *chip, int evt) |
| { |
| if (evt & EVENT_RX) { |
| if ((!PD_HEADER_CNT(chip->rec_head)) && |
| (PD_HEADER_TYPE(chip->rec_head) == CMT_PS_RDY)) { |
| chip->is_pd_connected = 1; |
| debug("PD connected as UFP, fetching 5V\n"); |
| set_state(chip, policy_snk_ready); |
| } else if ((PD_HEADER_CNT(chip->rec_head)) && |
| (PD_HEADER_TYPE(chip->rec_head) == |
| DMT_SOURCECAPABILITIES)) { |
| set_state(chip, policy_snk_evaluate_caps); |
| } |
| } |
| } |
| |
| static void tcpc_alert(struct fusb30x_chip *chip, int *evt) |
| { |
| u8 interrupt = 0, interrupta = 0, interruptb = 0; |
| u8 val; |
| |
| fusb302_i2c_read(FUSB_REG_INTERRUPT, &interrupt); |
| fusb302_i2c_read(FUSB_REG_INTERRUPTA, &interrupta); |
| fusb302_i2c_read(FUSB_REG_INTERRUPTB, &interruptb); |
| debug("interrupt=0x%x a=0x%x b=0x%x\n", interrupt , interrupta, interruptb); |
| |
| if (interrupt & INTERRUPT_BC_LVL) { |
| if (chip->is_cc_connected) |
| *evt |= EVENT_CC; |
| } |
| |
| if (interrupt & INTERRUPT_VBUSOK) { |
| if (chip->is_cc_connected) |
| *evt |= EVENT_CC; |
| } |
| |
| if (interrupta & INTERRUPTA_TOGDONE) { |
| *evt |= EVENT_CC; |
| fusb302_i2c_read(FUSB_REG_STATUS1A, &val); |
| chip->cc_state = ((u8)val >> 3) & 0x07; |
| |
| fusb302_i2c_update_bits(FUSB_REG_CONTROL2, |
| CONTROL2_TOGGLE, |
| 0); |
| |
| val &= ~(SWITCHES0_PU_EN1 | SWITCHES0_PU_EN2 | |
| SWITCHES0_PDWN1 | SWITCHES0_PDWN2); |
| |
| if (chip->cc_state & 0x01) |
| val |= SWITCHES0_PU_EN1; |
| else |
| val |= SWITCHES0_PU_EN2; |
| |
| fusb302_i2c_update_bits(FUSB_REG_SWITCHES0, |
| SWITCHES0_PU_EN1 | SWITCHES0_PU_EN2 | |
| SWITCHES0_PDWN1 | SWITCHES0_PDWN2, |
| val); |
| } |
| |
| if (interruptb & INTERRUPTB_GCRCSENT) |
| *evt |= EVENT_RX; |
| |
| if (interrupta & INTERRUPTA_TXSENT) { |
| *evt |= EVENT_TX; |
| fusb302_flush_rx_fifo(chip); |
| chip->tx_state = tx_success; |
| } |
| |
| if (interrupta & INTERRUPTA_HARDRST) { |
| |
| printf("HARDRESET>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); |
| |
| fusb302_pd_reset(chip); |
| *evt |= EVENT_REC_RESET; |
| } |
| |
| if (interrupta & INTERRUPTA_RETRYFAIL) { |
| *evt |= EVENT_TX; |
| chip->tx_state = tx_failed; |
| } |
| |
| if (interrupta & INTERRUPTA_HARDSENT) { |
| chip->tx_state = tx_success; |
| *evt |= EVENT_TX; |
| } |
| } |
| |
| static void state_machine_typec(struct fusb30x_chip *chip) |
| { |
| int evt = 0; |
| int cc1, cc2; |
| |
| tcpc_alert(chip, &evt); |
| |
| if (chip->is_cc_connected) { |
| if (evt & EVENT_CC) { |
| if ((chip->cc_state & 0x04) && |
| (chip->conn_state != |
| policy_snk_transition_default)) { |
| if (!tcpm_check_vbus(chip)) |
| set_state_unattached(chip); |
| } else if (chip->conn_state != |
| policy_src_transition_default) { |
| tcpm_get_cc(chip, &cc1, &cc2); |
| if (!(chip->cc_state & 0x01)) |
| cc1 = cc2; |
| if (cc1 == TYPEC_CC_VOLT_OPEN) |
| set_state_unattached(chip); |
| } |
| } |
| } |
| |
| if (evt & EVENT_RX) { |
| tcpm_get_message(chip); |
| if ((!PD_HEADER_CNT(chip->rec_head)) && |
| (PD_HEADER_TYPE(chip->rec_head) == CMT_SOFTRESET)) { |
| if (chip->power_role) |
| set_state(chip, policy_src_send_softrst); |
| else |
| set_state(chip, policy_snk_send_softrst); |
| } |
| } |
| |
| if (evt & EVENT_TX) { |
| if (chip->tx_state == tx_success) |
| chip->msg_id++; |
| } |
| |
| debug("conn_state=%d evt=%d rec_head=%d\n", chip->conn_state, evt, PD_HEADER_TYPE(chip->rec_head)); |
| switch (chip->conn_state) { |
| case disabled: |
| debug("%s:disabled\n", __func__); |
| break; |
| case error_recovery: |
| break; |
| case unattached: |
| fusb_state_unattached(chip, evt); |
| if (chip->conn_state != attach_wait_sink) |
| break; |
| case attach_wait_sink: |
| fusb_state_attach_wait_sink(chip); |
| if (chip->conn_state != attached_sink) |
| break; |
| case attached_sink: |
| fusb_state_attached_sink(chip); |
| |
| /* POWER DELIVERY */ |
| /* UFP */ |
| case policy_snk_startup: |
| fusb_state_snk_startup(chip); |
| case policy_snk_discovery: |
| fusb_state_snk_discovery(chip); |
| case policy_snk_wait_caps: |
| fusb_state_snk_wait_caps(chip, evt); |
| if (policy_snk_evaluate_caps != chip->conn_state) |
| break; |
| case policy_snk_evaluate_caps: |
| fusb_state_snk_evaluate_caps(chip, evt); |
| 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: |
| printf("%s policy_snk_transition_default ready\n", __func__); |
| break; |
| case policy_snk_ready: |
| printf("fusb302 sink ready\n"); |
| break; |
| case policy_snk_send_hardrst: |
| printf("%s policy_snk_send_hardrst\n", __func__); |
| break; |
| case policy_snk_send_softrst: |
| printf("%s policy_snk_send_softrst\n", __func__); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * The charge ic get adapter outprt vollage and current. |
| */ |
| int get_pd_output_val(int *pd_output_vol, int *pd_output_cur) |
| { |
| if (chip.pd_output_cur && chip.pd_output_vol) { |
| *pd_output_vol = chip.pd_output_vol; |
| *pd_output_cur = chip.pd_output_cur; |
| return 0; |
| } else { |
| return -1; |
| } |
| } |
| |
| /* |
| * The charge ic to contrl charge path depend on port_num. |
| */ |
| int get_pd_port_num(void) |
| { |
| return chip.port_num; |
| } |
| |
| static void update_port_num(void) |
| { |
| printf ("fusb302 detect chip.port_num = %d\n", chip.port_num); |
| } |
| |
| void typec_discharge(void) |
| { |
| if (fusb302_gpio_is_valid(chip.gpio_discharge.gpio)) { |
| gpio_set_value(chip.gpio_discharge.gpio, 1); |
| udelay(1000 * 1000); |
| gpio_set_value(chip.gpio_discharge.gpio, 0); |
| } |
| } |
| |
| int fusb302_init(void) |
| { |
| int ret, wait_for_complete; |
| u8 id; |
| struct PD_CAP_INFO *pd_cap_info; |
| |
| chip.gpio_cc_int.gpio = FUSB302_INT_GPIO; |
| chip.gpio_discharge.gpio = -1; /* unused */ |
| chip.gpio_vbus_5v.gpio = -1; /* unused */ |
| chip.addr = FUSB302_I2C_ADDR; |
| chip.port_num = 0; |
| |
| setenv("fusb302_state", "0"); |
| ret = fusb302_i2c_probe(chip.addr); |
| if (ret) { |
| printf("fusb302 i2c probe failed: %d\n", ret); |
| return ret; |
| } |
| |
| ret = fusb302_i2c_read(FUSB_REG_DEVICEID, &id); |
| if (ret) { |
| printf("fusb302 read device ID failed\n"); |
| |
| return ret; |
| } |
| printf("%s: Device ID: 0x%02X\n", __func__, id); |
| setenv("fusb302_state", "1"); |
| |
| wait_for_complete = 100;////////// |
| |
| tcpm_init(&chip); |
| tcpm_set_rx_enable(&chip, 0); |
| chip.conn_state = unattached; |
| tcpm_set_cc(&chip, FUSB_MODE_DRP); |
| |
| pd_cap_info = &chip.pd_cap_info; |
| pd_cap_info->dual_role_power = 0; |
| pd_cap_info->data_role_swap = 0; |
| 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; |
| |
| while (-- wait_for_complete) { |
| state_machine_typec(&chip); |
| if (chip.conn_state == policy_snk_ready) { |
| update_port_num(); |
| return 0; |
| } |
| if (chip.conn_state == attach_wait_source) |
| break; |
| udelay(1000 * 2); |
| } |
| if (chip.conn_state > attached_sink) { |
| update_port_num(); |
| } |
| |
| return 0; |
| } |