blob: 39371e45e3cfb243839f91e39e979f489e0f4921 [file] [log] [blame]
// 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, &reg);
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, &reg);
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*) &reg, 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*) &reg, 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),
};