| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2018, Richtek Technology Corporation |
| * |
| * Richtek RT1711H Type-C Chip Driver |
| */ |
| |
| #include <linux/types.h> |
| #include <i2c.h> |
| #include <linux/kernel.h> |
| #include <linux/usb/tcpm.h> |
| #include <linux/delay.h> |
| #include <dm/device.h> |
| #include <dm/read.h> |
| #include <dm/lists.h> |
| #include <time.h> |
| #include "tcpci.h" |
| |
| #define RT1711H_VID 0x29CF |
| #define RT1711H_PID 0x1711 |
| #define RT1711H_DID_D 0x2173 |
| |
| #define RT1711H_RTCTRL8 0x9B |
| |
| /* Autoidle timeout = (tout * 2 + 1) * 6.4ms */ |
| #define RT1711H_RTCTRL8_SET(ck300, ship_off, en_ext_msg, auto_idle, tout) \ |
| (((ck300) << 7) | ((ship_off) << 5) | \ |
| ((en_ext_msg) << 4) | ((auto_idle) << 3) | \ |
| ((tout) & 0x07)) |
| |
| #define RT1711H_RTCTRL11 0x9E |
| |
| /* I2C timeout = (tout + 1) * 12.5ms */ |
| #define RT1711H_RTCTRL11_SET(en, tout) \ |
| (((en) << 7) | ((tout) & 0x0F)) |
| |
| #define RT1711H_RTRXDZSEL 0x93 |
| #define RT1711H_RTCTRL13 0xA0 |
| #define RT1711H_RTCTRL14 0xA1 |
| #define RT1711H_RTCTRL15 0xA2 |
| #define RT1711H_RTCTRL16 0xA3 |
| #define RT1711H_RTRXDZEN 0xAF |
| |
| struct rt1711h_chip { |
| struct tcpci_data data; |
| struct tcpci *tcpci; |
| struct udevice *dev; |
| u16 chip_id; |
| u8 deadzone_enable; |
| u8 deadzone_select; |
| }; |
| |
| struct tcpci_rt1711h_data { |
| // TODO |
| }; |
| |
| static int rt1711h_read16(struct rt1711h_chip *chip, unsigned int reg, u16 *val) |
| { |
| return dm_i2c_read(chip->dev, reg, (uint8_t*) val, sizeof(u16)); |
| } |
| |
| static int rt1711h_write16(struct rt1711h_chip *chip, unsigned int reg, u16 val) |
| { |
| return dm_i2c_write(chip->dev, reg, (uint8_t*) &val, sizeof(u16)); |
| } |
| |
| static int rt1711h_read8(struct rt1711h_chip *chip, unsigned int reg, u8 *val) |
| { |
| return dm_i2c_read(chip->dev, reg, val, sizeof(u8)); |
| } |
| |
| static int rt1711h_write8(struct rt1711h_chip *chip, unsigned int reg, u8 val) |
| { |
| return dm_i2c_write(chip->dev, reg, &val, sizeof(u8)); |
| } |
| |
| static struct rt1711h_chip *tdata_to_rt1711h(struct tcpci_data *tdata) |
| { |
| return container_of(tdata, struct rt1711h_chip, data); |
| } |
| |
| static int rt1711h_init(struct tcpci *tcpci, struct tcpci_data *tdata) |
| { |
| int ret; |
| struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); |
| |
| /* CK 300K from 320K, shipping off, auto_idle enable, tout = 32ms */ |
| ret = rt1711h_write8(chip, RT1711H_RTCTRL8, |
| RT1711H_RTCTRL8_SET(0, 1, 1, 1, 2)); |
| if (ret < 0) |
| return ret; |
| |
| /* I2C reset : (val + 1) * 12.5ms */ |
| ret = rt1711h_write8(chip, RT1711H_RTCTRL11, |
| RT1711H_RTCTRL11_SET(1, 0x0F)); |
| if (ret < 0) |
| return ret; |
| |
| /* tTCPCfilter : (26.7 * val) us */ |
| ret = rt1711h_write8(chip, RT1711H_RTCTRL14, 0x0F); |
| if (ret < 0) |
| return ret; |
| |
| /* tDRP : (51.2 + 6.4 * val) ms */ |
| ret = rt1711h_write8(chip, RT1711H_RTCTRL15, 0x04); |
| if (ret < 0) |
| return ret; |
| |
| /* dcSRC.DRP : 33% */ |
| return rt1711h_write16(chip, RT1711H_RTCTRL16, 330); |
| } |
| |
| static enum typec_cc_status rt1711h_reg_to_cc(unsigned int cc, bool sink) |
| { |
| switch (cc) { |
| case 1: |
| return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RD; |
| case 2: |
| return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RA; |
| case 3: |
| return sink ? TYPEC_CC_RP_3_0 : TYPEC_CC_OPEN; |
| default: |
| return TYPEC_CC_OPEN; |
| } |
| } |
| |
| static int rt1711h_get_cc(struct tcpci *tcpci, struct tcpci_data *tdata, |
| enum typec_cc_status *cc1, enum typec_cc_status *cc2) |
| { |
| struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); |
| u8 reg, cc1_state, cc2_state; |
| bool cc1_sink, cc2_sink, tcpc_presenting_rd; |
| enum typec_cc_status cc; |
| u8 sel; |
| int ret; |
| |
| /* Read CC1 & CC2 ROLE */ |
| ret = rt1711h_read8(chip, TCPC_ROLE_CTRL, ®); |
| if (ret < 0) |
| return ret; |
| |
| cc1_sink = ((reg >> TCPC_ROLE_CTRL_CC1_SHIFT) & |
| TCPC_ROLE_CTRL_CC1_MASK) == TCPC_ROLE_CTRL_CC_RD; |
| cc2_sink = ((reg >> TCPC_ROLE_CTRL_CC2_SHIFT) & |
| TCPC_ROLE_CTRL_CC2_MASK) == TCPC_ROLE_CTRL_CC_RD; |
| /* Read CC STATUS */ |
| ret = rt1711h_read8(chip, TCPC_CC_STATUS, ®); |
| if (ret < 0) |
| return ret; |
| |
| /* When auto toggling, BIT(4) is set on presenting Rd */ |
| tcpc_presenting_rd = !!(reg & TCPC_CC_STATUS_TERM); |
| cc1_sink |= tcpc_presenting_rd; |
| cc2_sink |= tcpc_presenting_rd; |
| cc1_state = (reg >> TCPC_CC_STATUS_CC1_SHIFT) & TCPC_CC_STATUS_CC1_MASK; |
| cc2_state = (reg >> TCPC_CC_STATUS_CC2_SHIFT) & TCPC_CC_STATUS_CC2_MASK; |
| |
| *cc1 = rt1711h_reg_to_cc(cc1_state, cc1_sink); |
| *cc2 = rt1711h_reg_to_cc(cc2_state, cc2_sink); |
| |
| /* Set RT1711H deadzone */ |
| cc = (*cc1 == TYPEC_CC_OPEN) ? *cc2 : *cc1; |
| |
| if (cc == TYPEC_CC_RP_DEF) { |
| if (chip->deadzone_enable != 0) { |
| chip->deadzone_enable = 0; |
| rt1711h_write8(chip, RT1711H_RTRXDZEN, 0); |
| } |
| if (chip->deadzone_select != 0x81) { |
| chip->deadzone_select = 0x81; |
| rt1711h_write8(chip, RT1711H_RTRXDZSEL, 0x81); |
| } |
| } else { |
| sel = (chip->chip_id >= RT1711H_DID_D) ? 0x81 : 0x80; |
| if (chip->deadzone_enable != 1) { |
| chip->deadzone_enable = 1; |
| rt1711h_write8(chip, RT1711H_RTRXDZEN, 1); |
| } |
| if (chip->deadzone_select != sel) { |
| chip->deadzone_select = sel; |
| rt1711h_write8(chip, RT1711H_RTRXDZSEL, sel); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int rt1711h_set_polarity(struct tcpci *tcpci, struct tcpci_data *tdata, |
| enum typec_cc_polarity polarity) |
| { |
| struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); |
| |
| return rt1711h_write8(chip, TCPC_TCPC_CTRL, |
| (polarity == TYPEC_POLARITY_CC2) ? |
| TCPC_TCPC_CTRL_ORIENTATION : 0); |
| } |
| |
| static int rt1711h_set_vconn(struct tcpci *tcpci, struct tcpci_data *tdata, |
| bool enable) |
| { |
| struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); |
| |
| return rt1711h_write8(chip, RT1711H_RTCTRL8, |
| RT1711H_RTCTRL8_SET(0, 1, 1, !enable, 2)); |
| } |
| |
| static int rt1711h_start_drp_toggling(struct tcpci *tcpci, |
| struct tcpci_data *tdata, |
| enum typec_cc_status cc) |
| { |
| struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); |
| int ret; |
| unsigned int reg = 0; |
| |
| switch (cc) { |
| default: |
| case TYPEC_CC_RP_DEF: |
| reg |= (TCPC_ROLE_CTRL_RP_VAL_DEF << |
| TCPC_ROLE_CTRL_RP_VAL_SHIFT); |
| break; |
| case TYPEC_CC_RP_1_5: |
| reg |= (TCPC_ROLE_CTRL_RP_VAL_1_5 << |
| TCPC_ROLE_CTRL_RP_VAL_SHIFT); |
| break; |
| case TYPEC_CC_RP_3_0: |
| reg |= (TCPC_ROLE_CTRL_RP_VAL_3_0 << |
| TCPC_ROLE_CTRL_RP_VAL_SHIFT); |
| break; |
| } |
| |
| if (cc == TYPEC_CC_RD) |
| reg |= (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) | |
| (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT); |
| else |
| reg |= (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | |
| (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT); |
| |
| ret = rt1711h_write8(chip, TCPC_ROLE_CTRL, reg); |
| if (ret < 0) |
| return ret; |
| udelay(500); |
| |
| return 0; |
| } |
| |
| // TODO Need to handle CC status by rt1711h???? |
| /*static irqreturn_t rt1711h_irq(int irq, void *dev_id)*/ |
| /*{*/ |
| /*int ret;*/ |
| /*u16 alert;*/ |
| /*u8 status;*/ |
| /*struct rt1711h_chip *chip = dev_id;*/ |
| |
| /*if (!chip->tcpci)*/ |
| /*return IRQ_HANDLED;*/ |
| |
| /*ret = rt1711h_read16(chip, TCPC_ALERT, &alert);*/ |
| /*if (ret < 0)*/ |
| /*goto out;*/ |
| |
| /*if (alert & TCPC_ALERT_CC_STATUS) {*/ |
| /*ret = rt1711h_read8(chip, TCPC_CC_STATUS, &status);*/ |
| /*if (ret < 0)*/ |
| /*goto out;*/ |
| /*[> Clear cc change event triggered by starting toggling <]*/ |
| /*if (status & TCPC_CC_STATUS_TOGGLING)*/ |
| /*rt1711h_write8(chip, TCPC_ALERT, TCPC_ALERT_CC_STATUS);*/ |
| /*}*/ |
| |
| /*out:*/ |
| /*return tcpci_irq(chip->tcpci);*/ |
| /*}*/ |
| |
| static int rt1711h_sw_reset(struct rt1711h_chip *chip) |
| { |
| int ret; |
| |
| ret = rt1711h_write8(chip, RT1711H_RTCTRL13, 0x01); |
| if (ret < 0) |
| return ret; |
| |
| udelay(1000); |
| return 0; |
| } |
| |
| static int rt1711h_check_revision(struct udevice *dev) |
| { |
| int ret; |
| uint16_t reg; |
| |
| ret = dm_i2c_read(dev, TCPC_VENDOR_ID, (uint8_t*) ®, 2); |
| if (ret) |
| return ret; |
| if (reg != RT1711H_VID) { |
| dev_err(dev, "vid is not correct, 0x%04x\n", reg); |
| return -ENODEV; |
| } |
| ret = dm_i2c_read(dev, TCPC_PRODUCT_ID, (uint8_t*) ®, 2); |
| if (ret) |
| return ret; |
| if (reg != RT1711H_PID) { |
| dev_err(dev, "pid is not correct, 0x%04x\n", reg); |
| return -ENODEV; |
| } |
| return 0; |
| } |
| |
| |
| static int tcpci_rt1711h_bind(struct udevice *parent) |
| { |
| pr_debug("tcpci_rt1711h_bind\n"); |
| return 0; |
| } |
| |
| static int tcpci_rt1711h_probe(struct udevice *dev) |
| { |
| int ret; |
| struct rt1711h_chip *chip; |
| u16 chip_id; |
| |
| pr_debug("%s\n", __func__); |
| |
| ret = rt1711h_check_revision(dev); |
| if (ret < 0) { |
| dev_err(dev, "check vid/pid fail\n"); |
| return ret; |
| } |
| |
| chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); |
| if (!chip) |
| return -ENOMEM; |
| |
| chip->data.i2c = dev; |
| chip->dev = dev; |
| |
| ret = rt1711h_sw_reset(chip); |
| if (ret < 0) |
| return ret; |
| |
| /* Get device id and initialize deadzone */ |
| ret = rt1711h_read16(chip, TCPC_BCD_DEV, &chip_id); |
| if (ret < 0) |
| return ret; |
| chip->chip_id = chip_id; |
| ret = rt1711h_read8(chip, RT1711H_RTRXDZEN, &chip->deadzone_enable); |
| if (ret < 0) |
| return ret; |
| ret = rt1711h_read8(chip, RT1711H_RTRXDZSEL, &chip->deadzone_select); |
| if (ret < 0) |
| return ret; |
| |
| chip->data.init = rt1711h_init; |
| chip->data.get_cc = rt1711h_get_cc; |
| chip->data.set_polarity = rt1711h_set_polarity; |
| chip->data.set_vconn = rt1711h_set_vconn; |
| chip->data.start_drp_toggling = rt1711h_start_drp_toggling; |
| chip->tcpci = tcpci_register_port(chip->dev, &chip->data); |
| if (IS_ERR_OR_NULL(chip->tcpci)) |
| return PTR_ERR(chip->tcpci); |
| |
| tcpci_main_loop(chip->tcpci); |
| pr_warn("after main_loop\n"); |
| |
| return 0; |
| } |
| |
| |
| |
| static const struct udevice_id tcpci_rt1711h_ids[] = { |
| { .compatible = "richtek,rt1711h" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(tcpci_rt1711h) = { |
| .name = "tcpci-rt1711h", |
| .id = UCLASS_USB_TCPC, |
| .of_match = tcpci_rt1711h_ids, |
| .bind = tcpci_rt1711h_bind, |
| .probe = tcpci_rt1711h_probe, |
| .platdata_auto_alloc_size = sizeof(struct tcpci_rt1711h_data), |
| }; |