754 lines
21 KiB
C
754 lines
21 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/usb_c/tcpci_priv.h>
|
|
#include <zephyr/usb_c/usbc.h>
|
|
#include <zephyr/usb_c/tcpci.h>
|
|
#include <zephyr/shell/shell.h>
|
|
#include "ps8xxx_priv.h"
|
|
|
|
#define DT_DRV_COMPAT parade_ps8xxx
|
|
LOG_MODULE_REGISTER(ps8xxx, CONFIG_USBC_LOG_LEVEL);
|
|
|
|
/** Data structure for device instances */
|
|
struct ps8xxx_data {
|
|
/** Device structure used to retrieve it in k_work functions */
|
|
const struct device *const dev;
|
|
/** Delayable work item for chip initialization */
|
|
struct k_work_delayable init_dwork;
|
|
/** Initialization retries counter */
|
|
int init_retries;
|
|
/** Boolean value if chip was successfully initialized */
|
|
bool initialized;
|
|
|
|
/** Callback for alert GPIO */
|
|
struct gpio_callback alert_cb;
|
|
/** Work item for alert handling out of interrupt context */
|
|
struct k_work alert_work;
|
|
/** Boolean value if there is a message pending */
|
|
bool msg_pending;
|
|
|
|
/** Alert handler set by USB-C stack */
|
|
tcpc_alert_handler_cb_t alert_handler;
|
|
/** Alert handler data set by USB-C stack */
|
|
void *alert_handler_data;
|
|
|
|
/** VCONN discharge callback set by USB-C stack */
|
|
tcpc_vconn_discharge_cb_t vconn_discharge_cb;
|
|
/** VCONN discharge callback data set by USB-C stack */
|
|
tcpc_vconn_control_cb_t vconn_cb;
|
|
/** Polarity of CC lines for PD and VCONN */
|
|
enum tc_cc_polarity cc_polarity;
|
|
|
|
/** Boolean value if there was a change on the CC lines since last check */
|
|
bool cc_changed;
|
|
/** State of CC1 line */
|
|
enum tc_cc_voltage_state cc1;
|
|
/** State of CC2 line */
|
|
enum tc_cc_voltage_state cc2;
|
|
};
|
|
|
|
/** Configuration structure for device instances */
|
|
struct ps8xxx_cfg {
|
|
/** I2C bus and address used for communication */
|
|
const struct i2c_dt_spec bus;
|
|
/** GPIO specification for alert pin */
|
|
const struct gpio_dt_spec alert_gpio;
|
|
/** Maximum number of packet retransmissions done by TCPC */
|
|
const uint8_t transmit_retries;
|
|
};
|
|
|
|
static int tcpci_init_alert_mask(const struct device *dev)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
int ret;
|
|
|
|
uint16_t mask = TCPC_REG_ALERT_TX_COMPLETE | TCPC_REG_ALERT_RX_STATUS |
|
|
TCPC_REG_ALERT_RX_HARD_RST | TCPC_REG_ALERT_CC_STATUS |
|
|
TCPC_REG_ALERT_FAULT | TCPC_REG_ALERT_POWER_STATUS;
|
|
|
|
ret = tcpci_write_reg16(&cfg->bus, TCPC_REG_ALERT_MASK, mask);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ps8xxx_tcpc_init(const struct device *dev)
|
|
{
|
|
struct ps8xxx_data *data = dev->data;
|
|
|
|
if (!data->initialized) {
|
|
if (data->init_retries > CONFIG_USBC_TCPC_PS8XXX_INIT_RETRIES) {
|
|
LOG_ERR("TCPC was not initialized correctly");
|
|
return -EIO;
|
|
}
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
LOG_INF("PS8xxx TCPC initialized");
|
|
return 0;
|
|
}
|
|
|
|
int ps8xxx_tcpc_get_cc(const struct device *dev, enum tc_cc_voltage_state *cc1,
|
|
enum tc_cc_voltage_state *cc2)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
struct ps8xxx_data *data = dev->data;
|
|
int ret;
|
|
|
|
if (!data->initialized) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_USBC_CSM_SINK_ONLY) && !data->cc_changed) {
|
|
*cc1 = data->cc1;
|
|
*cc2 = data->cc2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
data->cc_changed = false;
|
|
|
|
ret = tcpci_tcpm_get_cc(&cfg->bus, cc1, cc2);
|
|
|
|
if (IS_ENABLED(CONFIG_USBC_CSM_SINK_ONLY) || *cc1 != data->cc1 || *cc2 != data->cc2) {
|
|
LOG_DBG("CC changed values: %d->%d, %d->%d", data->cc1, *cc1, data->cc2, *cc2);
|
|
data->cc1 = *cc1;
|
|
data->cc2 = *cc2;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ps8xxx_tcpc_select_rp_value(const struct device *dev, enum tc_rp_value rp)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
struct ps8xxx_data *data = dev->data;
|
|
|
|
data->cc_changed = true;
|
|
|
|
return tcpci_update_reg8(&cfg->bus, TCPC_REG_ROLE_CTRL, TCPC_REG_ROLE_CTRL_RP_MASK,
|
|
TCPC_REG_ROLE_CTRL_SET(0, rp, 0, 0));
|
|
}
|
|
|
|
int ps8xxx_tcpc_get_rp_value(const struct device *dev, enum tc_rp_value *rp)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
uint8_t reg_value = 0;
|
|
int ret;
|
|
|
|
ret = tcpci_read_reg8(&cfg->bus, TCPC_REG_ROLE_CTRL, ®_value);
|
|
*rp = TCPC_REG_ROLE_CTRL_RP(reg_value);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_cc(const struct device *dev, enum tc_cc_pull pull)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
struct ps8xxx_data *data = dev->data;
|
|
|
|
if (!data->initialized) {
|
|
return -EIO;
|
|
}
|
|
|
|
data->cc_changed = true;
|
|
|
|
return tcpci_update_reg8(&cfg->bus, TCPC_REG_ROLE_CTRL,
|
|
TCPC_REG_ROLE_CTRL_CC1_MASK | TCPC_REG_ROLE_CTRL_CC2_MASK,
|
|
TCPC_REG_ROLE_CTRL_SET(0, 0, pull, pull));
|
|
}
|
|
|
|
void ps8xxx_tcpc_set_vconn_discharge_cb(const struct device *dev, tcpc_vconn_discharge_cb_t cb)
|
|
{
|
|
struct ps8xxx_data *data = dev->data;
|
|
|
|
data->vconn_discharge_cb = cb;
|
|
}
|
|
|
|
void ps8xxx_tcpc_set_vconn_cb(const struct device *dev, tcpc_vconn_control_cb_t vconn_cb)
|
|
{
|
|
struct ps8xxx_data *data = dev->data;
|
|
|
|
data->vconn_cb = vconn_cb;
|
|
}
|
|
|
|
int ps8xxx_tcpc_vconn_discharge(const struct device *dev, bool enable)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
|
|
return tcpci_update_reg8(&cfg->bus, TCPC_REG_POWER_CTRL,
|
|
TCPC_REG_POWER_CTRL_AUTO_DISCHARGE_DISCONNECT,
|
|
(enable) ? TCPC_REG_POWER_CTRL_AUTO_DISCHARGE_DISCONNECT : 0);
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_vconn(const struct device *dev, bool enable)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
struct ps8xxx_data *data = dev->data;
|
|
int ret;
|
|
|
|
if (!data->initialized) {
|
|
return -EIO;
|
|
}
|
|
|
|
data->cc_changed = true;
|
|
ret = tcpci_update_reg8(&cfg->bus, TCPC_REG_POWER_CTRL, TCPC_REG_POWER_CTRL_VCONN_EN,
|
|
enable ? TCPC_REG_POWER_CTRL_VCONN_EN : 0);
|
|
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (data->vconn_cb != NULL) {
|
|
ret = data->vconn_cb(dev, data->cc_polarity, enable);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_roles(const struct device *dev, enum tc_power_role power_role,
|
|
enum tc_data_role data_role)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
|
|
return tcpci_update_reg8(&cfg->bus, TCPC_REG_MSG_HDR_INFO, TCPC_REG_MSG_HDR_INFO_ROLES_MASK,
|
|
TCPC_REG_MSG_HDR_INFO_SET(PD_REV30, data_role, power_role));
|
|
}
|
|
|
|
int ps8xxx_tcpc_get_rx_pending_msg(const struct device *dev, struct pd_msg *msg)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
struct ps8xxx_data *data = dev->data;
|
|
struct i2c_msg buf[5];
|
|
uint8_t msg_len = 0;
|
|
uint8_t unused;
|
|
int buf_count;
|
|
int reg = TCPC_REG_RX_BUFFER;
|
|
int ret;
|
|
|
|
if (!data->msg_pending) {
|
|
return -ENODATA;
|
|
}
|
|
|
|
if (msg == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
data->msg_pending = false;
|
|
|
|
buf[0].buf = (uint8_t *)®
|
|
buf[0].len = 1;
|
|
buf[0].flags = I2C_MSG_WRITE;
|
|
|
|
buf[1].buf = &msg_len;
|
|
buf[1].len = 1;
|
|
buf[1].flags = I2C_MSG_RESTART | I2C_MSG_READ | I2C_MSG_STOP;
|
|
ret = i2c_transfer(cfg->bus.bus, buf, 2, cfg->bus.addr);
|
|
|
|
if (msg_len <= 1) {
|
|
return 0;
|
|
}
|
|
|
|
buf[1].buf = &unused;
|
|
buf[1].len = 1;
|
|
buf[1].flags = I2C_MSG_RESTART | I2C_MSG_READ;
|
|
|
|
buf[2].buf = &msg->type;
|
|
buf[2].len = 1;
|
|
buf[2].flags = I2C_MSG_RESTART | I2C_MSG_READ;
|
|
|
|
msg->header.raw_value = 0;
|
|
buf[3].buf = (uint8_t *)&msg->header.raw_value;
|
|
buf[3].len = 2;
|
|
buf[3].flags = I2C_MSG_RESTART | I2C_MSG_READ;
|
|
|
|
if (msg_len > 3) {
|
|
buf[4].buf = msg->data;
|
|
buf[4].len = msg_len - 3;
|
|
buf[4].flags = I2C_MSG_RESTART | I2C_MSG_READ | I2C_MSG_STOP;
|
|
buf_count = 5;
|
|
} else if (msg_len == 3) {
|
|
buf[3].flags |= I2C_MSG_STOP;
|
|
buf_count = 4;
|
|
} else {
|
|
buf[2].flags |= I2C_MSG_STOP;
|
|
buf_count = 3;
|
|
}
|
|
|
|
ret = i2c_transfer(cfg->bus.bus, buf, buf_count, cfg->bus.addr);
|
|
if (ret != 0) {
|
|
LOG_ERR("I2C transfer error: %d", ret);
|
|
} else {
|
|
msg->len = (msg_len > 3) ? msg_len - 3 : 0;
|
|
ret = sizeof(msg->header.raw_value) + msg->len;
|
|
}
|
|
|
|
tcpci_write_reg16(&cfg->bus, TCPC_REG_ALERT, TCPC_REG_ALERT_RX_STATUS);
|
|
return ret;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_rx_enable(const struct device *dev, bool enable)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
int detect_sop_en = enable ? TCPC_REG_RX_DETECT_SOP_HRST_MASK : 0;
|
|
|
|
return tcpci_write_reg8(&cfg->bus, TCPC_REG_RX_DETECT, detect_sop_en);
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_cc_polarity(const struct device *dev, enum tc_cc_polarity polarity)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
struct ps8xxx_data *data = dev->data;
|
|
int ret;
|
|
|
|
if (!data->initialized) {
|
|
return -EIO;
|
|
}
|
|
|
|
ret = tcpci_update_reg8(
|
|
&cfg->bus, TCPC_REG_TCPC_CTRL, TCPC_REG_TCPC_CTRL_PLUG_ORIENTATION,
|
|
(polarity == TC_POLARITY_CC1) ? 0 : TCPC_REG_TCPC_CTRL_PLUG_ORIENTATION);
|
|
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
data->cc_changed = true;
|
|
data->cc_polarity = polarity;
|
|
return 0;
|
|
}
|
|
|
|
int ps8xxx_tcpc_transmit_data(const struct device *dev, struct pd_msg *msg)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
|
|
int reg = TCPC_REG_TX_BUFFER;
|
|
int rv;
|
|
int cnt = 4 * msg->header.number_of_data_objects;
|
|
|
|
/* If not SOP* transmission, just write to the transmit register */
|
|
if (msg->header.message_type >= NUM_SOP_STAR_TYPES) {
|
|
/*
|
|
* Per TCPCI spec, do not specify retry (although the TCPC
|
|
* should ignore retry field for these 3 types).
|
|
*/
|
|
return tcpci_write_reg8(
|
|
&cfg->bus, TCPC_REG_TRANSMIT,
|
|
TCPC_REG_TRANSMIT_SET_WITHOUT_RETRY(msg->header.message_type));
|
|
}
|
|
|
|
if (cnt > 0) {
|
|
reg = TCPC_REG_TX_BUFFER;
|
|
/* TX_BYTE_CNT includes extra bytes for message header */
|
|
cnt += sizeof(msg->header.raw_value);
|
|
|
|
struct i2c_msg buf[3];
|
|
|
|
uint8_t tmp[2] = {TCPC_REG_TX_BUFFER, cnt};
|
|
|
|
buf[0].buf = tmp;
|
|
buf[0].len = 2;
|
|
buf[0].flags = I2C_MSG_WRITE;
|
|
|
|
buf[1].buf = (uint8_t *)&msg->header.raw_value;
|
|
buf[1].len = sizeof(msg->header.raw_value);
|
|
buf[1].flags = I2C_MSG_WRITE;
|
|
|
|
buf[2].buf = (uint8_t *)msg->data;
|
|
buf[2].len = msg->len;
|
|
buf[2].flags = I2C_MSG_WRITE | I2C_MSG_STOP;
|
|
|
|
if (cnt > sizeof(msg->header.raw_value)) {
|
|
rv = i2c_transfer(cfg->bus.bus, buf, 3, cfg->bus.addr);
|
|
} else {
|
|
buf[1].flags |= I2C_MSG_STOP;
|
|
rv = i2c_transfer(cfg->bus.bus, buf, 2, cfg->bus.addr);
|
|
}
|
|
|
|
/* If tcpc write fails, return error */
|
|
if (rv) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We always retry in TCPC hardware since the TCPM is too slow to
|
|
* respond within tRetry (~195 usec).
|
|
*
|
|
* The retry count used is dependent on the maximum PD revision
|
|
* supported at build time.
|
|
*/
|
|
rv = tcpci_write_reg8(&cfg->bus, TCPC_REG_TRANSMIT,
|
|
TCPC_REG_TRANSMIT_SET_WITH_RETRY(cfg->transmit_retries, msg->type));
|
|
|
|
return rv;
|
|
}
|
|
|
|
int ps8xxx_tcpc_dump_std_reg(const struct device *dev)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
uint16_t value;
|
|
|
|
LOG_INF("TCPC %s:%s registers:", cfg->bus.bus->name, dev->name);
|
|
for (unsigned int a = 0; a < TCPCI_STD_REGS_SIZE; a++) {
|
|
switch (tcpci_std_regs[a].size) {
|
|
case 1:
|
|
tcpci_read_reg8(&cfg->bus, tcpci_std_regs[a].addr, (uint8_t *)&value);
|
|
LOG_INF("- %-30s(0x%02x) = 0x%02x", tcpci_std_regs[a].name,
|
|
tcpci_std_regs[a].addr, (uint8_t)value);
|
|
break;
|
|
case 2:
|
|
tcpci_read_reg16(&cfg->bus, tcpci_std_regs[a].addr, &value);
|
|
LOG_INF("- %-30s(0x%02x) = 0x%04x", tcpci_std_regs[a].name,
|
|
tcpci_std_regs[a].addr, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ps8xxx_tcpc_alert_handler_cb(const struct device *dev, void *data, enum tcpc_alert alert)
|
|
{
|
|
}
|
|
|
|
int ps8xxx_tcpc_get_status_register(const struct device *dev, enum tcpc_status_reg reg,
|
|
int32_t *status)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_clear_status_register(const struct device *dev, enum tcpc_status_reg reg,
|
|
uint32_t mask)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_mask_status_register(const struct device *dev, enum tcpc_status_reg reg,
|
|
uint32_t mask)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_debug_accessory(const struct device *dev, bool enable)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_debug_detach(const struct device *dev)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_drp_toggle(const struct device *dev, bool enable)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_get_snk_ctrl(const struct device *dev)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_snk_ctrl(const struct device *dev, bool enable)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
uint8_t cmd = (enable) ? TCPC_REG_COMMAND_SNK_CTRL_HIGH : TCPC_REG_COMMAND_SNK_CTRL_LOW;
|
|
|
|
return tcpci_write_reg8(&cfg->bus, TCPC_REG_COMMAND, cmd);
|
|
}
|
|
|
|
int ps8xxx_tcpc_get_src_ctrl(const struct device *dev)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_src_ctrl(const struct device *dev, bool enable)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
uint8_t cmd = (enable) ? TCPC_REG_COMMAND_SRC_CTRL_DEF : TCPC_REG_COMMAND_SRC_CTRL_LOW;
|
|
|
|
return tcpci_write_reg8(&cfg->bus, TCPC_REG_COMMAND, cmd);
|
|
}
|
|
|
|
int ps8xxx_tcpc_get_chip_info(const struct device *dev, struct tcpc_chip_info *chip_info)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
int ret = 0;
|
|
|
|
if (chip_info == NULL) {
|
|
return -EIO;
|
|
}
|
|
|
|
ret |= tcpci_read_reg16(&cfg->bus, TCPC_REG_VENDOR_ID, &chip_info->vendor_id);
|
|
ret |= tcpci_read_reg16(&cfg->bus, TCPC_REG_PRODUCT_ID, &chip_info->product_id);
|
|
ret |= tcpci_read_reg16(&cfg->bus, TCPC_REG_BCD_DEV, &chip_info->device_id);
|
|
|
|
/* Vendor specific register for PS8815 model only */
|
|
if (chip_info->product_id == PS8815_PRODUCT_ID) {
|
|
uint8_t fw_ver;
|
|
|
|
ret |= tcpci_read_reg8(&cfg->bus, PS8815_REG_FW_VER, &fw_ver);
|
|
chip_info->fw_version_number = fw_ver;
|
|
} else {
|
|
chip_info->fw_version_number = 0;
|
|
}
|
|
|
|
chip_info->min_req_fw_version_number = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_low_power_mode(const struct device *dev, bool enable)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
|
|
return tcpci_write_reg8(&cfg->bus, TCPC_REG_COMMAND, TCPC_REG_COMMAND_I2CIDLE);
|
|
}
|
|
|
|
int ps8xxx_tcpc_sop_prime_enable(const struct device *dev, bool enable)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_bist_test_mode(const struct device *dev, bool enable)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int ps8xxx_tcpc_set_alert_handler_cb(const struct device *dev, tcpc_alert_handler_cb_t handler,
|
|
void *handler_data)
|
|
{
|
|
struct ps8xxx_data *data = dev->data;
|
|
|
|
if (data->alert_handler == handler && data->alert_handler_data == handler_data) {
|
|
return 0;
|
|
}
|
|
|
|
data->alert_handler = handler;
|
|
data->alert_handler_data = handler_data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Functions not assigned to the driver API but used by device */
|
|
|
|
static const struct tcpc_driver_api ps8xxx_driver_api = {
|
|
.init = ps8xxx_tcpc_init,
|
|
.get_cc = ps8xxx_tcpc_get_cc,
|
|
.select_rp_value = ps8xxx_tcpc_select_rp_value,
|
|
.get_rp_value = ps8xxx_tcpc_get_rp_value,
|
|
.set_cc = ps8xxx_tcpc_set_cc,
|
|
.set_vconn_discharge_cb = ps8xxx_tcpc_set_vconn_discharge_cb,
|
|
.set_vconn_cb = ps8xxx_tcpc_set_vconn_cb,
|
|
.vconn_discharge = ps8xxx_tcpc_vconn_discharge,
|
|
.set_vconn = ps8xxx_tcpc_set_vconn,
|
|
.set_roles = ps8xxx_tcpc_set_roles,
|
|
.get_rx_pending_msg = ps8xxx_tcpc_get_rx_pending_msg,
|
|
.set_rx_enable = ps8xxx_tcpc_set_rx_enable,
|
|
.set_cc_polarity = ps8xxx_tcpc_set_cc_polarity,
|
|
.transmit_data = ps8xxx_tcpc_transmit_data,
|
|
.dump_std_reg = ps8xxx_tcpc_dump_std_reg,
|
|
.alert_handler_cb = ps8xxx_tcpc_alert_handler_cb,
|
|
.get_status_register = ps8xxx_tcpc_get_status_register,
|
|
.clear_status_register = ps8xxx_tcpc_clear_status_register,
|
|
.mask_status_register = ps8xxx_tcpc_mask_status_register,
|
|
.set_debug_accessory = ps8xxx_tcpc_set_debug_accessory,
|
|
.set_debug_detach = ps8xxx_tcpc_set_debug_detach,
|
|
.set_drp_toggle = ps8xxx_tcpc_set_drp_toggle,
|
|
.get_snk_ctrl = ps8xxx_tcpc_get_snk_ctrl,
|
|
.set_snk_ctrl = ps8xxx_tcpc_set_snk_ctrl,
|
|
.get_src_ctrl = ps8xxx_tcpc_get_src_ctrl,
|
|
.set_src_ctrl = ps8xxx_tcpc_set_src_ctrl,
|
|
.get_chip_info = ps8xxx_tcpc_get_chip_info,
|
|
.set_low_power_mode = ps8xxx_tcpc_set_low_power_mode,
|
|
.sop_prime_enable = ps8xxx_tcpc_sop_prime_enable,
|
|
.set_bist_test_mode = ps8xxx_tcpc_set_bist_test_mode,
|
|
.set_alert_handler_cb = ps8xxx_tcpc_set_alert_handler_cb,
|
|
};
|
|
|
|
void ps8xxx_alert_cb(const struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins)
|
|
{
|
|
struct ps8xxx_data *data = CONTAINER_OF(cb, struct ps8xxx_data, alert_cb);
|
|
|
|
k_work_submit(&data->alert_work);
|
|
}
|
|
|
|
void ps8xxx_alert_work_cb(struct k_work *work)
|
|
{
|
|
struct ps8xxx_data *data = CONTAINER_OF(work, struct ps8xxx_data, alert_work);
|
|
const struct device *dev = data->dev;
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
uint16_t alert_reg = 0;
|
|
uint16_t clear_flags = 0;
|
|
|
|
if (!data->initialized) {
|
|
return;
|
|
}
|
|
|
|
tcpci_read_reg16(&cfg->bus, TCPC_REG_ALERT, &alert_reg);
|
|
|
|
while (alert_reg != 0) {
|
|
enum tcpc_alert alert_type = tcpci_alert_reg_to_enum(alert_reg);
|
|
|
|
if (alert_type == TCPC_ALERT_HARD_RESET_RECEIVED) {
|
|
LOG_DBG("PS8xxx hard rst received");
|
|
tcpci_init_alert_mask(dev);
|
|
data->cc_changed = true;
|
|
} else if (alert_type == TCPC_ALERT_FAULT_STATUS) {
|
|
uint8_t fault;
|
|
|
|
tcpci_read_reg8(&cfg->bus, TCPC_REG_FAULT_STATUS, &fault);
|
|
tcpci_write_reg8(&cfg->bus, TCPC_REG_FAULT_STATUS, fault);
|
|
|
|
LOG_DBG("PS8xxx fault: %02x", fault);
|
|
} else if (alert_type == TCPC_ALERT_EXTENDED_STATUS) {
|
|
uint8_t ext_status;
|
|
|
|
tcpci_read_reg8(&cfg->bus, TCPC_REG_EXT_STATUS, &ext_status);
|
|
tcpci_write_reg8(&cfg->bus, TCPC_REG_EXT_STATUS, ext_status);
|
|
|
|
data->cc_changed = true;
|
|
LOG_DBG("PS8xxx ext status: %02x", ext_status);
|
|
} else if (alert_type == TCPC_ALERT_POWER_STATUS) {
|
|
uint8_t pwr_status;
|
|
|
|
tcpci_read_reg8(&cfg->bus, TCPC_REG_POWER_STATUS, &pwr_status);
|
|
tcpci_write_reg8(&cfg->bus, TCPC_REG_POWER_STATUS, pwr_status);
|
|
|
|
LOG_DBG("PS8xxx power status: %02x", pwr_status);
|
|
} else if (alert_type == TCPC_ALERT_EXTENDED) {
|
|
uint8_t alert_status;
|
|
|
|
tcpci_read_reg8(&cfg->bus, TCPC_REG_ALERT_EXT, &alert_status);
|
|
tcpci_write_reg8(&cfg->bus, TCPC_REG_ALERT_EXT, alert_status);
|
|
|
|
LOG_DBG("PS8xxx ext alert: %02x", alert_status);
|
|
} else if (alert_type == TCPC_ALERT_MSG_STATUS) {
|
|
data->msg_pending = true;
|
|
} else if (alert_type == TCPC_ALERT_CC_STATUS) {
|
|
data->cc_changed = true;
|
|
}
|
|
|
|
if (data->alert_handler != NULL) {
|
|
data->alert_handler(data->dev, data->alert_handler_data, alert_type);
|
|
}
|
|
|
|
clear_flags |= BIT(alert_type);
|
|
alert_reg &= ~BIT(alert_type);
|
|
}
|
|
|
|
tcpci_write_reg16(&cfg->bus, TCPC_REG_ALERT, clear_flags);
|
|
|
|
tcpci_read_reg16(&cfg->bus, TCPC_REG_ALERT, &alert_reg);
|
|
if (alert_reg != 0) {
|
|
k_work_submit(work);
|
|
}
|
|
}
|
|
|
|
void ps8xxx_init_work_cb(struct k_work *work)
|
|
{
|
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
|
struct ps8xxx_data *data = CONTAINER_OF(dwork, struct ps8xxx_data, init_dwork);
|
|
|
|
const struct ps8xxx_cfg *cfg = data->dev->config;
|
|
uint8_t power_reg = 0;
|
|
uint16_t idVendor = 0;
|
|
uint16_t idProduct = 0;
|
|
uint16_t idDevice = 0;
|
|
int res;
|
|
|
|
LOG_INF("Initializing PS8xxx chip: %s", data->dev->name);
|
|
res = tcpci_read_reg8(&cfg->bus, TCPC_REG_POWER_STATUS, &power_reg);
|
|
if (res != 0 || (power_reg & TCPC_REG_POWER_STATUS_UNINIT)) {
|
|
data->init_retries++;
|
|
|
|
if (data->init_retries > CONFIG_USBC_TCPC_PS8XXX_INIT_RETRIES) {
|
|
LOG_ERR("Chip didn't respond");
|
|
return;
|
|
}
|
|
|
|
LOG_DBG("Postpone chip initialization %d", data->init_retries);
|
|
k_work_schedule_for_queue(&k_sys_work_q, &data->init_dwork,
|
|
K_MSEC(CONFIG_USBC_TCPC_PS8XXX_INIT_DELAY));
|
|
|
|
return;
|
|
}
|
|
|
|
tcpci_read_reg16(&cfg->bus, TCPC_REG_VENDOR_ID, &idVendor);
|
|
tcpci_read_reg16(&cfg->bus, TCPC_REG_PRODUCT_ID, &idProduct);
|
|
tcpci_read_reg16(&cfg->bus, TCPC_REG_BCD_DEV, &idDevice);
|
|
LOG_INF("Initialized chip is: %04x:%04x:%04x", idVendor, idProduct, idDevice);
|
|
|
|
/* Initialize alert interrupt */
|
|
gpio_pin_configure_dt(&cfg->alert_gpio, GPIO_INPUT);
|
|
|
|
gpio_init_callback(&data->alert_cb, ps8xxx_alert_cb, BIT(cfg->alert_gpio.pin));
|
|
gpio_add_callback(cfg->alert_gpio.port, &data->alert_cb);
|
|
gpio_pin_interrupt_configure_dt(&cfg->alert_gpio, GPIO_INT_EDGE_TO_ACTIVE);
|
|
|
|
tcpci_init_alert_mask(data->dev);
|
|
data->initialized = true;
|
|
|
|
/* Disable the vconn and open CC lines to reinitialize the communication with partner */
|
|
ps8xxx_tcpc_set_vconn(data->dev, false);
|
|
ps8xxx_tcpc_set_cc(data->dev, TC_CC_OPEN);
|
|
|
|
/* Check and clear any alert set after initialization */
|
|
k_work_submit(&data->alert_work);
|
|
}
|
|
|
|
static int ps8xxx_dev_init(const struct device *dev)
|
|
{
|
|
const struct ps8xxx_cfg *cfg = dev->config;
|
|
struct ps8xxx_data *data = dev->data;
|
|
|
|
if (!device_is_ready(cfg->bus.bus)) {
|
|
return -EIO;
|
|
}
|
|
|
|
k_work_init_delayable(&data->init_dwork, ps8xxx_init_work_cb);
|
|
k_work_schedule_for_queue(&k_sys_work_q, &data->init_dwork,
|
|
K_MSEC(CONFIG_USBC_TCPC_PS8XXX_INIT_DELAY));
|
|
|
|
k_work_init(&data->alert_work, ps8xxx_alert_work_cb);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define PS8XXX_DRIVER_DATA_INIT(node) \
|
|
{ \
|
|
.dev = DEVICE_DT_GET(node), \
|
|
.init_retries = 0, \
|
|
.cc_changed = true, \
|
|
}
|
|
|
|
#define PS8XXX_DRIVER_CFG_INIT(node) \
|
|
{ \
|
|
.bus = I2C_DT_SPEC_GET(node), \
|
|
.alert_gpio = GPIO_DT_SPEC_GET(node, irq_gpios), \
|
|
.transmit_retries = DT_PROP(node, transmit_retries), \
|
|
}
|
|
|
|
#define PS8XXX_DRIVER_INIT(inst) \
|
|
static struct ps8xxx_data drv_data_ps8xxx##inst = \
|
|
PS8XXX_DRIVER_DATA_INIT(DT_DRV_INST(inst)); \
|
|
static struct ps8xxx_cfg drv_cfg_ps8xxx##inst = PS8XXX_DRIVER_CFG_INIT(DT_DRV_INST(inst)); \
|
|
DEVICE_DT_INST_DEFINE(inst, &ps8xxx_dev_init, NULL, &drv_data_ps8xxx##inst, \
|
|
&drv_cfg_ps8xxx##inst, POST_KERNEL, CONFIG_USBC_TCPC_INIT_PRIORITY, \
|
|
&ps8xxx_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(PS8XXX_DRIVER_INIT)
|