@@ -110,6 +110,19 @@ config TYPEC_WUSB3801
If you choose to build this driver as a dynamically linked module, the
module will be called wusb3801.ko.
+config TYPEC_TPS25750
+ tristate "TI TPS25750 USB Power Delivery controller driver"
+ depends on I2C
+ select REGMAP_I2C
+ select USB_ROLE_SWITCH
+ select POWER_SUPPLY
+ help
+ Say Y or M here if your system has TI TPS25750 USB Power
+ Delivery controller.
+
+ If you choose to build this driver as a dynamically linked module, the
+ module will be called tps25750.ko.
+
source "drivers/usb/typec/mux/Kconfig"
source "drivers/usb/typec/altmodes/Kconfig"
@@ -12,3 +12,4 @@ obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o
obj-$(CONFIG_TYPEC_RT1719) += rt1719.o
obj-$(CONFIG_TYPEC_WUSB3801) += wusb3801.o
obj-$(CONFIG_TYPEC) += mux/
+obj-$(CONFIG_TYPEC_TPS25750) += tps25750.o
new file mode 100644
@@ -0,0 +1,1065 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for TI TPS25750 USB Power Delivery controller family
+ *
+ * Copyright (C) 2023, Geotab Inc.
+ * Author: Abdel Alkuor <abdelalkuor@geotab.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/interrupt.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/role.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/minmax.h>
+
+#include "tps25750.h"
+
+/* Register offsets */
+#define TPS_REG_MODE 0x03
+#define TPS_REG_TYPE 0x04
+#define TPS_REG_CUSTUSE 0x06
+#define TPS_REG_CMD1 0x08
+#define TPS_REG_DATA1 0x09
+#define TPS_REG_DEVICE_CAPABILITIES 0x0D
+#define TPS_REG_VERSION 0x0F
+#define TPS_REG_INT_EVENT1 0x14
+#define TPS_REG_INT_MASK1 0x16
+#define TPS_REG_INT_CLEAR1 0x18
+#define TPS_REG_STATUS 0x1A
+#define TPS_REG_POWER_PATH_STATUS 0x26
+#define TPS_REG_PORT_CONTROL 0x29
+#define TPS_REG_BOOT_STATUS 0x2D
+#define TPS_REG_BUILD_DESCRIPTION 0x2E
+#define TPS_REG_DEVICE_INFO 0x2F
+#define TPS_REG_RX_SOURCE_CAPS 0x30
+#define TPS_REG_RX_SINK_CAPS 0x31
+#define TPS_REG_TX_SOURCE_CAPS 0x32
+#define TPS_REG_TX_SINK_CAPS 0x33
+#define TPS_REG_ACTIVE_CONTRACT_PDO 0x34
+#define TPS_REG_ACTIVE_CONTRACT_RDO 0x35
+#define TPS_REG_POWER_STATUS 0x3F
+#define TPS_REG_PD_STATUS 0x40
+#define TPS_REG_TYPEC_STATE 0x69
+#define TPS_REG_GPIO_STATUS 0x72
+#define TPS_REG_MAX TPS_REG_GPIO_STATUS
+
+#define TPS_MAX_LEN 64
+
+/* 4CC (4 Char Command) Task return code */
+#define TPS_TASK_COMPLETED_SUCCESSFULLY 0x0
+#define TPS_TASK_TIMEOUT_OR_ABRT 0x1
+#define TPS_TASK_REJECTED 0x3
+#define TPS_TASK_REJECTED_RX_BUF_LOCKED 0x4
+
+#define TPS_TASK_BPMS_INVALID_BUNDLE_SIZE 0x4
+#define TPS_TASK_BPMS_INVALID_SLAVE_ADDR 0x5
+#define TPS_TASK_BPMS_INVALID_TIMEOUT 0x6
+
+/* PBMc data out */
+#define TPS_PBMC_RC 0 /* Return code */
+#define TPS_PBMC_DPCS 2 /* device patch complete status */
+
+/* invalid cmd == !CMD */
+#define INVALID_CMD(_cmd_) (_cmd_ == 0x444d4321)
+
+/* 4 Characters Commands (4CC)*/
+#define TPS_4CC_PBMS "PBMs" /* Start Patch Burst Mode Download Sequence */
+#define TPS_4CC_PBMC "PBMc" /* Patch Burst Mode Download Complete */
+#define TPS_4CC_PBME "PBMe" /* End Patch Burst Mode Download Sequence */
+#define TPS_4CC_SWSK "SWSk" /* Swap to sink power role */
+#define TPS_4CC_SWSR "SWSr" /* Swap to source power role */
+#define TPS_4CC_SWUF "SWUF" /* Swap to up facing stream (device) */
+#define TPS_4CC_SWDF "SWDF" /* Swap to down facing stream (host) */
+#define TPS_4CC_DBFG "DBfg" /* Clear dead battery flag */
+#define TPS_4CC_GAID "GAID" /* Cold reset */
+
+/* Address used in PBMs command where address would be invalid when
+ * 0x00 or I2C client slave address based on ADCINx.
+ * pg.48 TPS2575 Host Interface Technical Reference
+ * Manual (Rev. A)
+ */
+#define TPS_BUNDLE_SLAVE_ADDR 0x0F
+
+/* BPMs task timeout, recommended 5 seconds
+ * pg.48 TPS2575 Host Interface Technical Reference
+ * Manual (Rev. A)
+ */
+#define TPS_BUNDLE_TIMEOUT 0x32
+
+enum {
+ TPS_MODE_APP,
+ TPS_MODE_BOOT,
+ TPS_MODE_PTCH,
+};
+
+static const char *const modes[] = {
+ [TPS_MODE_APP] = "APP ",
+ [TPS_MODE_BOOT] = "BOOT",
+ [TPS_MODE_PTCH] = "PTCH",
+};
+
+struct tps25750 {
+ struct device *dev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct mutex lock;
+
+ struct typec_port *port;
+ struct typec_partner *partner;
+ struct usb_role_switch *role_sw;
+
+ struct power_supply *psy;
+
+ u32 status_reg;
+ u32 max_source_current;
+};
+
+static const struct regmap_config tps25750_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = TPS_REG_MAX,
+};
+
+static enum power_supply_property tps25750_psy_props[] = {
+ POWER_SUPPLY_PROP_USB_TYPE,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static enum power_supply_usb_type tps25750_psy_usb_types[] = {
+ POWER_SUPPLY_USB_TYPE_C,
+ POWER_SUPPLY_USB_TYPE_PD,
+};
+
+static int
+tps25750_block_write_raw(struct tps25750 *tps, const u8 *data, size_t len)
+{
+ if (len < 2)
+ return -EINVAL;
+
+ return regmap_raw_write(tps->regmap, data[0], &data[1], len - 1);
+}
+
+static int
+tps25750_block_read(struct tps25750 *tps, u8 reg, void *val, size_t len)
+{
+ int ret;
+ u8 data[TPS_MAX_LEN + 1];
+
+ if (len + 1 > TPS_MAX_LEN)
+ return -EINVAL;
+
+ ret = regmap_raw_read(tps->regmap, reg, data, len + 1);
+ if (ret)
+ return ret;
+
+ if (data[0] < len)
+ return -EIO;
+
+ memcpy(val, &data[1], len);
+ return 0;
+}
+
+static int
+tps25750_block_write(struct tps25750 *tps, u8 reg, const void *val, size_t len)
+{
+ u8 data[TPS_MAX_LEN + 1];
+
+ if (len + 1 > TPS_MAX_LEN)
+ return -EINVAL;
+
+ data[0] = len;
+ memcpy(&data[1], val, len);
+
+ return regmap_raw_write(tps->regmap, reg, data, len + 1);
+}
+
+static int tps25750_read(struct tps25750 *tps, u8 reg, u8 *val)
+{
+ return tps25750_block_read(tps, reg, val, sizeof(u8));
+}
+
+static int tps25750_read16(struct tps25750 *tps, u8 reg, u16 *val)
+{
+ return tps25750_block_read(tps, reg, val, sizeof(u16));
+}
+
+static int tps25750_read32(struct tps25750 *tps, u8 reg, u32 *val)
+{
+ return tps25750_block_read(tps, reg, val, sizeof(u32));
+}
+
+static int
+tps25750_wait_cmd_complete(struct tps25750 *tps, unsigned long timeout)
+{
+ int ret;
+ u32 val;
+
+ timeout = jiffies + msecs_to_jiffies(timeout);
+
+ do {
+ ret = tps25750_read32(tps, TPS_REG_CMD1, &val);
+ if (ret)
+ return ret;
+
+ if (INVALID_CMD(val))
+ return -EINVAL;
+
+ if (time_is_before_jiffies(timeout))
+ return -ETIMEDOUT;
+
+ mdelay(10);
+ } while (val);
+
+ return 0;
+}
+
+static int tps25750_exec_cmd(struct tps25750 *tps, const char *cmd,
+ size_t in_len, u8 *in_data,
+ size_t out_len, u8 *out_data,
+ u8 response_delay_ms,
+ u32 cmd_timeout_ms)
+{
+ int ret;
+ u8 dummy[3] = { };
+
+ if (!out_len || !out_data)
+ return -EINVAL;
+
+ if (in_len) {
+ ret = tps25750_block_write(tps, TPS_REG_DATA1,
+ in_data, in_len);
+ if (ret)
+ return ret;
+ } else {
+ /* For some reason, if no data is written to
+ * TPS_REG_DATA1 before sending 4CC, then 4CC would fail
+ */
+ dummy[0] = TPS_REG_DATA1;
+ ret = tps25750_block_write_raw(tps, dummy, sizeof(dummy));
+ if (ret)
+ return ret;
+ }
+
+ ret = tps25750_block_write(tps, TPS_REG_CMD1, cmd, 4);
+ if (ret)
+ return ret;
+
+ ret = tps25750_wait_cmd_complete(tps, cmd_timeout_ms);
+
+ if (ret)
+ return ret;
+
+ mdelay(response_delay_ms);
+
+ ret = tps25750_block_read(tps, TPS_REG_DATA1, out_data, out_len);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int tps25750_exec_normal_cmd(struct tps25750 *tps, const char *cmd)
+{
+ int ret;
+ u8 rc;
+
+ ret = tps25750_exec_cmd(tps, cmd, 0, NULL, 1, &rc, 0, 1000);
+ if (ret)
+ return ret;
+
+ switch (rc) {
+ case TPS_TASK_COMPLETED_SUCCESSFULLY:
+ return 0;
+ case TPS_TASK_TIMEOUT_OR_ABRT:
+ return -ETIMEDOUT;
+ case TPS_TASK_REJECTED:
+ return -EPERM;
+ case TPS_TASK_REJECTED_RX_BUF_LOCKED:
+ return -EBUSY;
+ default:
+ break;
+ }
+
+ return -EIO;
+}
+
+static int
+tps25750_exec_patch_cmd_pbms(struct tps25750 *tps, u8 *in_data, size_t in_len)
+{
+ int ret;
+ u8 rc;
+
+ ret = tps25750_exec_cmd(tps, TPS_4CC_PBMS, in_len, in_data,
+ 1, &rc, 0, TPS_BUNDLE_TIMEOUT * 100);
+ if (ret)
+ return ret;
+
+ switch (rc) {
+ case TPS_TASK_BPMS_INVALID_BUNDLE_SIZE:
+ dev_err(tps->dev, "%s: invalid fw size\n", __func__);
+ return -EINVAL;
+ case TPS_TASK_BPMS_INVALID_SLAVE_ADDR:
+ dev_err(tps->dev, "%s: invalid slave address\n", __func__);
+ return -EINVAL;
+ case TPS_TASK_BPMS_INVALID_TIMEOUT:
+ dev_err(tps->dev, "%s: timed out\n", __func__);
+ return -ETIMEDOUT;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int tps25750_complete_patch_process(struct tps25750 *tps)
+{
+ int ret;
+ u8 out_data[40];
+
+ ret = tps25750_exec_cmd(tps, TPS_4CC_PBMC, 0, NULL,
+ sizeof(out_data), out_data, 20, 2000);
+ if (ret)
+ return ret;
+
+ if (out_data[TPS_PBMC_RC]) {
+ dev_err(tps->dev,
+ "%s: pbmc failed: %u\n", __func__,
+ out_data[TPS_PBMC_RC]);
+ return -EIO;
+ }
+
+ if (out_data[TPS_PBMC_DPCS]) {
+ dev_err(tps->dev,
+ "%s: failed device patch complete status: %u\n",
+ __func__, out_data[TPS_PBMC_DPCS]);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int tps25750_get_reg_boot_status(struct tps25750 *tps, u64 *status)
+{
+ int ret;
+
+ ret = tps25750_block_read(tps, TPS_REG_BOOT_STATUS, status, 5);
+ if (ret) {
+ dev_err(tps->dev,
+ "failed to get boot status %d", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tps25750_get_mode(struct tps25750 *tps)
+{
+ char mode[5] = { };
+ int ret;
+
+ ret = tps25750_read32(tps, TPS_REG_MODE, (void *)mode);
+ if (ret)
+ return ret;
+
+ ret = match_string(modes, ARRAY_SIZE(modes), mode);
+
+ if (ret < 0) {
+ dev_err(tps->dev, "unsupported mode \"%s\"\n",
+ mode);
+ return -ENODEV;
+ }
+
+ return ret;
+}
+
+static int tps2750_is_mode(struct tps25750 *tps, u8 mode)
+{
+ int ret;
+
+ ret = tps25750_get_mode(tps);
+ if (ret < 0)
+ return ret;
+
+ return ret == mode;
+}
+
+static int tps25750_abort_patch_process(struct tps25750 *tps)
+{
+ int ret;
+
+ ret = tps25750_exec_normal_cmd(tps, TPS_4CC_PBME);
+ if (ret)
+ return ret;
+
+ ret = tps2750_is_mode(tps, TPS_MODE_PTCH);
+ if (ret != 1) {
+ dev_err(tps->dev, "%s: failed to switch to \"PTCH\" mode\n",
+ __func__);
+ if (ret < 0)
+ return ret;
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static void tps25750_set_data_role(struct tps25750 *tps,
+ enum typec_data_role role, bool connected)
+{
+ enum usb_role role_val;
+
+ if (role == TYPEC_HOST)
+ role_val = USB_ROLE_HOST;
+ else
+ role_val = USB_ROLE_DEVICE;
+
+ if (!connected)
+ role_val = USB_ROLE_NONE;
+
+ usb_role_switch_set_role(tps->role_sw, role_val);
+ typec_set_data_role(tps->port, role);
+}
+
+static int tps25750_connect(struct tps25750 *tps, u32 status)
+{
+ struct typec_partner_desc desc;
+ enum typec_pwr_opmode mode;
+ u16 pwr_status;
+ int ret;
+
+ if (!IS_ERR(tps->partner))
+ typec_unregister_partner(tps->partner);
+
+ ret = tps25750_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
+ if (ret < 0)
+ return ret;
+
+ mode = TPS_REG_POWER_STATUS_TYPEC_CURRENT(pwr_status);
+
+ desc.usb_pd = mode == TYPEC_PWR_MODE_PD;
+ desc.accessory = TYPEC_ACCESSORY_NONE;
+ desc.identity = NULL;
+
+ typec_set_pwr_opmode(tps->port, mode);
+ typec_set_pwr_role(tps->port, TPS_REG_STATUS_PORT_ROLE(status));
+ tps25750_set_data_role(tps, TPS_REG_STATUS_DATA_ROLE(status), true);
+
+ tps->partner = typec_register_partner(tps->port, &desc);
+ if (IS_ERR(tps->partner))
+ return PTR_ERR(tps->partner);
+
+ power_supply_changed(tps->psy);
+
+ return 0;
+}
+
+static void tps25750_disconnect(struct tps25750 *tps, u32 status)
+{
+ if (!IS_ERR(tps->partner))
+ typec_unregister_partner(tps->partner);
+ tps->partner = NULL;
+ typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB);
+ typec_set_pwr_role(tps->port, TPS_REG_STATUS_PORT_ROLE(status));
+ tps25750_set_data_role(tps, TPS_REG_STATUS_DATA_ROLE(status), false);
+ power_supply_changed(tps->psy);
+}
+
+static void tps25750_handle_plug_event(struct tps25750 *tps, u32 status)
+{
+ int ret;
+
+ if (TPS_REG_STATUS_PLUG_PRESENT(status)) {
+ ret = tps25750_connect(tps, status);
+ if (ret)
+ dev_err(tps->dev,
+ "%s: failed to register partner\n",
+ __func__);
+ } else {
+ tps25750_disconnect(tps, status);
+ }
+}
+
+static bool tps25750_has_role_changed(struct tps25750 *tps, u32 status)
+{
+ status ^= tps->status_reg;
+
+ return TPS_REG_STATUS_PORT_ROLE(status) ||
+ TPS_REG_STATUS_DATA_ROLE(status);
+}
+
+static irqreturn_t tps25750_interrupt(int irq, void *data)
+{
+ struct tps25750 *tps = data;
+ u32 status;
+ int ret;
+ u32 event[3] = { };
+ u8 no_events = 0;
+
+ mutex_lock(&tps->lock);
+
+ /* events reg size is 11 bytes */
+ ret = tps25750_block_read(tps, TPS_REG_INT_EVENT1, event,
+ sizeof(event) - 1);
+
+ if (ret) {
+ dev_err(tps->dev,
+ "%s: failed to read events ret: %d\n", __func__, ret);
+ goto err_unlock;
+ }
+
+ if (!event[0] && !event[1] && !event[2]) {
+ no_events = 1;
+ goto err_unlock;
+ }
+
+ if (!(TPS_REG_EVENT1_STATUS_UPDATE & event[0]))
+ goto clear_events;
+
+ ret = tps25750_read32(tps, TPS_REG_STATUS, &status);
+ if (ret) {
+ dev_err(tps->dev,
+ "%s: failed to read status\n", __func__);
+ goto clear_events;
+ }
+
+ /* data/port roles could be updated independently after
+ * a plug event. Therefore, we need to check
+ * for pr/dr status change to set TypeC dr/pr accordingly.
+ */
+ if (TPS_REG_EVENT1_PLUG_INSERT_OR_REMOVAL & event[0] ||
+ tps25750_has_role_changed(tps, status))
+ tps25750_handle_plug_event(tps, status);
+
+ tps->status_reg = status;
+clear_events:
+ /* clear reg size is 11 bytes */
+ tps25750_block_write(tps, TPS_REG_INT_CLEAR1, event,
+ sizeof(event) - 1);
+
+err_unlock:
+ mutex_unlock(&tps->lock);
+
+ if (no_events)
+ return IRQ_NONE;
+
+ return IRQ_HANDLED;
+}
+
+static int tps25750_dr_set(struct typec_port *port, enum typec_data_role role)
+{
+ struct tps25750 *tps = typec_get_drvdata(port);
+ const char *cmd = (role == TYPEC_DEVICE) ? TPS_4CC_SWUF : TPS_4CC_SWDF;
+ int ret;
+ u32 status;
+
+ mutex_lock(&tps->lock);
+
+ ret = tps25750_exec_normal_cmd(tps, cmd);
+ if (ret)
+ goto release_lock;
+
+ ret = tps25750_read32(tps, TPS_REG_STATUS, &status);
+ if (ret)
+ goto release_lock;
+
+ if (role != TPS_REG_STATUS_DATA_ROLE(status)) {
+ ret = -EPROTO;
+ goto release_lock;
+ }
+
+ tps25750_set_data_role(tps, role, true);
+
+release_lock:
+ mutex_unlock(&tps->lock);
+
+ return ret;
+}
+
+static int
+tps25750_write_firmware(struct tps25750 *tps, const u8 *data, size_t len)
+{
+ int ret;
+ u8 addr;
+ int timeout;
+
+ addr = tps->client->addr;
+ timeout = tps->client->adapter->timeout;
+
+ /* Writing the patch might take some time as we are
+ * writing the whole patch at once.
+ * Tested using 5 seconds at 100kHz seems to work
+ */
+ tps->client->adapter->timeout = msecs_to_jiffies(5000);
+ tps->client->addr = TPS_BUNDLE_SLAVE_ADDR;
+
+ ret = tps25750_block_write_raw(tps, data, len);
+
+ tps->client->addr = addr;
+ tps->client->adapter->timeout = timeout;
+
+ return ret;
+}
+
+static int tps25750_start_patch_burst_mode(struct tps25750 *tps)
+{
+ int ret;
+ const struct firmware *fw;
+ const char *firmware_name;
+ struct {
+ u32 fw_size;
+ u8 i2c_slave_addr;
+ u8 timeout;
+ } __packed pbms_in_data;
+
+ ret = device_property_read_string(tps->dev, "firmware-name",
+ &firmware_name);
+ if (ret)
+ return ret;
+
+ ret = request_firmware(&fw, firmware_name, tps->dev);
+ if (ret) {
+ dev_err(tps->dev, "failed to retrieve \"%s\"\n", firmware_name);
+ return ret;
+ }
+
+ if (fw->size == 0) {
+ ret = -EINVAL;
+ goto release_fw;
+ }
+
+ pbms_in_data.fw_size = fw->size;
+ pbms_in_data.i2c_slave_addr = TPS_BUNDLE_SLAVE_ADDR;
+ pbms_in_data.timeout = TPS_BUNDLE_TIMEOUT;
+
+ ret = tps25750_exec_patch_cmd_pbms(tps, (u8 *)&pbms_in_data,
+ sizeof(pbms_in_data));
+ if (ret)
+ goto release_fw;
+
+ ret = tps25750_write_firmware(tps, fw->data, fw->size);
+ if (ret) {
+ dev_err(tps->dev, "Failed to write patch %s of %lu bytes\n",
+ firmware_name, fw->size);
+ } else {
+ /* A delay f 500us is required after the firmware is written
+ * based on pg.62 in TPS25750 Host Interface Technical
+ * Reference Manual
+ */
+ udelay(500);
+ ret = 0;
+ }
+
+release_fw:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static int tps25750_apply_patch(struct tps25750 *tps)
+{
+ int ret;
+ unsigned long timeout;
+ u64 boot_status;
+
+ ret = tps2750_is_mode(tps, TPS_MODE_PTCH);
+ if (ret != 1)
+ return ret;
+
+ ret = tps25750_get_reg_boot_status(tps, &boot_status);
+ if (ret)
+ return ret;
+
+ /* Nothing to be done if the configuration
+ * is being loaded from EERPOM
+ */
+ if (TPS_REG_BOOT_STATUS_I2C_EEPROM_PRESENT(boot_status))
+ goto wait_for_app;
+
+ ret = tps25750_start_patch_burst_mode(tps);
+ if (ret) {
+ tps25750_abort_patch_process(tps);
+ return ret;
+ }
+
+ ret = tps25750_complete_patch_process(tps);
+ if (ret)
+ return ret;
+
+wait_for_app:
+ timeout = jiffies + msecs_to_jiffies(1000);
+
+ do {
+ ret = tps2750_is_mode(tps, TPS_MODE_APP);
+ if (ret < 0)
+ return ret;
+
+ if (time_is_before_jiffies(timeout))
+ return -ETIMEDOUT;
+
+ mdelay(10);
+
+ } while (ret != 1);
+
+ if (ret == 0)
+ return -EPROTO;
+
+ dev_info(tps->dev, "controller switched to \"APP\" mode\n");
+
+ return 0;
+};
+
+static int tps25750_pr_set(struct typec_port *port, enum typec_role role)
+{
+ struct tps25750 *tps = typec_get_drvdata(port);
+ const char *cmd = (role == TYPEC_SINK) ? TPS_4CC_SWSK : TPS_4CC_SWSR;
+ int ret;
+ u32 status;
+
+ mutex_lock(&tps->lock);
+
+ ret = tps25750_exec_normal_cmd(tps, cmd);
+ if (ret)
+ goto release_lock;
+
+ ret = tps25750_read32(tps, TPS_REG_STATUS, &status);
+ if (ret)
+ goto release_lock;
+
+ if (role != TPS_REG_STATUS_PORT_ROLE(status)) {
+ ret = -EPROTO;
+ goto release_lock;
+ }
+
+ typec_set_pwr_role(tps->port, role);
+
+release_lock:
+ mutex_unlock(&tps->lock);
+
+ return ret;
+}
+
+static int tps25750_find_max_source_curr(struct tps25750 *tps)
+{
+ int ret;
+ int i;
+ u8 buf[31];
+ u8 npdo;
+ u32 pdo;
+
+ ret = tps25750_block_read(tps, TPS_REG_TX_SOURCE_CAPS,
+ buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ /* The first byte is header which contains number of
+ * valid PDOs. Up to 7.
+ */
+ npdo = TPS_PDO_NUM_VALID_PDOS(buf[0]);
+
+ /* Each PDO is 4 byte in length where each PDO starts
+ * as following:
+ * PDO1: byte 3
+ * PDO2: byte 7
+ * ...
+ * PDO7: byte 27
+ * See pg.28 in TPS25750 Host Interface Technical Reference
+ * Manual (Rev. A)
+ */
+ for (i = 1; i <= npdo; i++) {
+ memcpy(&pdo, &buf[i*4 - 1], sizeof(pdo));
+ tps->max_source_current = max_t(u32,
+ tps->max_source_current,
+ TPS_PDO_MAX_CURRENT(pdo));
+ }
+
+ return 0;
+}
+
+static int tps25750_clear_dead_battery(struct tps25750 *tps)
+{
+ int ret;
+
+ ret = tps25750_exec_normal_cmd(tps, TPS_4CC_DBFG);
+ if (ret) {
+ dev_err(tps->dev,
+ "failed to clear dead battery %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tps25750_init(struct tps25750 *tps)
+{
+ int ret;
+ u64 boot_status;
+
+ tps->status_reg = 0;
+
+ ret = tps2750_is_mode(tps, TPS_MODE_BOOT);
+ if (ret == 1) {
+ dev_warn(tps->dev, "Device booting in dead battery");
+ return 0;
+ }
+
+ ret = tps25750_apply_patch(tps);
+ if (ret)
+ return ret;
+
+ /* The dead battery flag may be triggered when the controller
+ * port is connected to a device that can source power and
+ * attempts to power up both the controller and the board it is on.
+ * To restore controller functionality, it is necessary to clear
+ * this flag
+ */
+ ret = tps25750_get_reg_boot_status(tps, &boot_status);
+ if (ret)
+ return ret;
+
+ if (TPS_REG_BOOT_STATUS_DEAD_BATTERY_FLAG(boot_status))
+ return tps25750_clear_dead_battery(tps);
+
+ return 0;
+}
+
+static int tps25750_psy_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct tps25750 *tps = power_supply_get_drvdata(psy);
+ u16 pwr_status;
+ int ret;
+
+ ret = tps25750_read16(tps, TPS_REG_POWER_STATUS, &pwr_status);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ if (TPS_REG_POWER_STATUS_TYPEC_CURRENT(pwr_status) ==
+ TPS_TYPEC_CURRENT_PD)
+ val->intval = POWER_SUPPLY_USB_TYPE_PD;
+ else
+ val->intval = POWER_SUPPLY_USB_TYPE_C;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = TPS_REG_POWER_STATUS_POWER_CONNECTION(pwr_status);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ /* maximum possible pdo source current is
+ * 1023 (10.23) A.
+ */
+ val->intval = tps->max_source_current * 10000;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct power_supply_desc tps25750_psy_desc = {
+ .name = "tps25750-psy",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .usb_types = tps25750_psy_usb_types,
+ .num_usb_types = ARRAY_SIZE(tps25750_psy_usb_types),
+ .properties = tps25750_psy_props,
+ .num_properties = ARRAY_SIZE(tps25750_psy_props),
+ .get_property = tps25750_psy_get_prop,
+};
+
+static const struct typec_operations tps25750_ops = {
+ .dr_set = tps25750_dr_set,
+ .pr_set = tps25750_pr_set,
+};
+
+static int tps25750_probe(struct i2c_client *client)
+{
+ struct power_supply_config psy_cfg = { };
+ struct typec_capability typec_cap = { };
+ struct tps25750 *tps;
+ struct fwnode_handle *fwnode;
+ int ret;
+ u8 pd_status;
+ const char *data_role;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EPROTO;
+
+ tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL);
+ if (!tps)
+ return -ENOMEM;
+
+ mutex_init(&tps->lock);
+
+ tps->client = client;
+ tps->dev = &client->dev;
+
+ tps->regmap = devm_regmap_init_i2c(client, &tps25750_regmap_config);
+ if (IS_ERR(tps->regmap))
+ return PTR_ERR(tps->regmap);
+
+ ret = tps25750_init(tps);
+ if (ret)
+ return ret;
+
+ ret = tps25750_read(tps, TPS_REG_PD_STATUS, &pd_status);
+ if (ret)
+ goto err_remove_patch;
+
+ fwnode = device_get_named_child_node(&client->dev, "connector");
+ if (!fwnode) {
+ ret = -ENODEV;
+ goto err_remove_patch;
+ }
+
+ tps->role_sw = fwnode_usb_role_switch_get(fwnode);
+ if (IS_ERR(tps->role_sw)) {
+ ret = PTR_ERR(tps->role_sw);
+ goto err_fwnode_put;
+ }
+
+ ret = fwnode_property_read_string(fwnode, "data-role", &data_role);
+ if (ret) {
+ dev_err(tps->dev, "data-role not found: %d\n", ret);
+ goto err_role_put;
+ }
+
+ ret = typec_find_port_data_role(data_role);
+ if (ret < 0) {
+ dev_err(tps->dev, "unknown data-role: %s\n", data_role);
+ goto err_role_put;
+ }
+
+ typec_cap.data = ret;
+ typec_cap.revision = USB_TYPEC_REV_1_3;
+ typec_cap.pd_revision = 0x300;
+ typec_cap.driver_data = tps;
+ typec_cap.ops = &tps25750_ops;
+ typec_cap.fwnode = fwnode;
+ typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
+
+ switch (TPS_REG_PD_STATUS_PORT_TYPE(pd_status)) {
+ case TPS_PORT_TYPE_SINK_SOURCE:
+ case TPS_PORT_TYPE_SOURCE_SINK:
+ typec_cap.type = TYPEC_PORT_DRP;
+ break;
+ case TPS_PORT_TYPE_SINK:
+ typec_cap.type = TYPEC_PORT_SNK;
+ break;
+ case TPS_PORT_TYPE_SOURCE:
+ typec_cap.type = TYPEC_PORT_SRC;
+ break;
+ default:
+ ret = -ENODEV;
+ goto err_role_put;
+ }
+
+ psy_cfg.fwnode = dev_fwnode(tps->dev);
+ psy_cfg.drv_data = tps;
+
+ tps->psy = devm_power_supply_register(tps->dev,
+ &tps25750_psy_desc,
+ &psy_cfg);
+ if (IS_ERR(tps->psy)) {
+ ret = PTR_ERR(tps->psy);
+ goto err_role_put;
+ }
+
+ tps->port = typec_register_port(&client->dev, &typec_cap);
+ if (IS_ERR(tps->port)) {
+ ret = PTR_ERR(tps->port);
+ goto err_role_put;
+ }
+
+
+ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ tps25750_interrupt,
+ IRQF_SHARED | IRQF_ONESHOT,
+ dev_name(&client->dev), tps);
+ if (ret)
+ goto err_port_unregister;
+
+ tps25750_find_max_source_curr(tps);
+ i2c_set_clientdata(client, tps);
+ fwnode_handle_put(fwnode);
+
+ return 0;
+
+err_port_unregister:
+ typec_unregister_port(tps->port);
+err_role_put:
+ usb_role_switch_put(tps->role_sw);
+err_fwnode_put:
+ fwnode_handle_put(fwnode);
+err_remove_patch:
+ tps25750_exec_normal_cmd(tps, TPS_4CC_GAID);
+
+ return ret;
+}
+
+static void tps25750_remove(struct i2c_client *client)
+{
+ struct tps25750 *tps = i2c_get_clientdata(client);
+
+ tps25750_disconnect(tps, 0);
+ typec_unregister_port(tps->port);
+ usb_role_switch_put(tps->role_sw);
+
+ /* clear the patch by a hard reset */
+ tps25750_exec_normal_cmd(tps, TPS_4CC_GAID);
+}
+
+static int __maybe_unused tps25750_suspend(struct device *dev)
+{
+ /* TODO: should we remove the patch? */
+ return 0;
+}
+
+static int __maybe_unused tps25750_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct tps25750 *tps = i2c_get_clientdata(client);
+
+ /* The controller needs to be reinitialized again
+ * if it is powered off as config is not persisted
+ */
+ return tps25750_init(tps);
+}
+
+static const struct dev_pm_ops tps25750_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(tps25750_suspend, tps25750_resume)
+};
+
+static const struct of_device_id tps25750_of_match[] = {
+ { .compatible = "ti,tps25750", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, tps25750_of_match);
+
+static const struct i2c_device_id tps25750_id[] = {
+ { "tps25750" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, tps25750_id);
+
+static struct i2c_driver tps25750_i2c_driver = {
+ .driver = {
+ .name = "tps25750",
+ .pm = pm_ptr(&tps25750_pm_ops),
+ .of_match_table = of_match_ptr(tps25750_of_match),
+ },
+ .probe_new = tps25750_probe,
+ .remove = tps25750_remove,
+ .id_table = tps25750_id,
+};
+module_i2c_driver(tps25750_i2c_driver);
+
+MODULE_AUTHOR("Abdel Alkuor <abdelalkuor@geotab.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TI TPS25750 USB Power Delivery Controller Driver");
new file mode 100644
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Driver for TI TPS25750 USB Power Delivery controller family
+ *
+ * Copyright (C) 2023, Geotab Inc.
+ * Author: Abdel Alkuor <abdelalkuor@geotab.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+
+#ifndef __TPS25750_H__
+#define __TPS25750_H__
+
+/* events 1-4 */
+#define TPS_REG_EVENT1_PD_HARD_RESET BIT(1)
+#define TPS_REG_EVENT1_PLUG_INSERT_OR_REMOVAL BIT(3)
+#define TPS_REG_EVENT1_PR_SWAP_COMPLETE BIT(4)
+#define TPS_REG_EVENT1_DR_SWAP_COMPLETE BIT(5)
+#define TPS_REG_EVENT1_NEW_CONTRACT_AS_CONS BIT(12)
+#define TPS_REG_EVENT1_NEW_CONTRACT_AS_PROV BIT(13)
+#define TPS_REG_EVENT1_SOURCE_CAP_MSG_RCVD BIT(14)
+#define TPS_REG_EVENT1_PR_SWAP_REQUESTED BIT(17)
+#define TPS_REG_EVENT1_DR_SWAP_REQUESTED BIT(18)
+#define TPS_REG_EVENT1_USB_HOST_PRESENT BIT(20)
+#define TPS_REG_EVENT1_USB_HOST_PRESENT_NO_LONGER BIT(21)
+#define TPS_REG_EVENT1_PP_SWITCH_CHANGED BIT(23)
+#define TPS_REG_EVENT1_POWER_STATUS_UPDATE BIT(24)
+#define TPS_REG_EVENT1_STATUS_UPDATE BIT(26)
+#define TPS_REG_EVENT1_PD_STATUS_UPDATE BIT(27)
+#define TPS_REG_EVENT1_CMD_COMPLETE BIT(30)
+
+/* events 5-8 */
+#define TPS_REG_EVENT1_ERROR_DEVICE_INCOMPLETE BIT(0)
+#define TPS_REG_EVENT1_ERROR_CANNOT_PROVIDE_VOLATAGE_OR_CURRENT BIT(1)
+#define TPS_REG_EVENT1_ERROR_CAN_PROVIDE_VOLATAGE_OR_CURRENT_LATER BIT(2)
+#define TPS_REG_EVENT1_ERROR_POWER_EVENT_OCCURRED BIT(3)
+#define TPS_REG_EVENT1_ERROR_MISSING_GET_CAP_MESSAGE BIT(4)
+#define TPS_REG_EVENT1_ERROR_PROTOCOL_ERROR BIT(6)
+#define TPS_REG_EVENT1_ERROR_MESSAGAE_DATA BIT(7)
+#define TPS_REG_EVENT1_SINK_TRANSITION_COMPLETE BIT(9)
+#define TPS_REG_EVENT1_PLUG_EARLY_NOTIFICATION BIT(10)
+#define TPS_REG_EVENT1_ERROR_PLUG_EARLY_NOTIFICATION BIT(11)
+#define TPS_REG_EVENT1_ERROR_UNABLE_TO_SOURCE BIT(14)
+
+/* events 9-11 */
+#define TPS_REG_EVENT1_TX_MEM_BUFFER_EMPTY BIT(1)
+#define TPS_REG_EVENT1_PATCH_LOADED BIT(16)
+#define TPS_REG_EVENT1_READY_FOR_PATCH BIT(17)
+#define TPS_REG_EVENT1_I2C_MASTER_NACKED BIT(18)
+
+/* STATUS Register */
+#define TPS_REG_STATUS_PLUG_MASK BIT(0)
+#define TPS_REG_STATUS_PLUG_PRESENT(_s) \
+ FIELD_GET(TPS_REG_STATUS_PLUG_MASK, _s)
+#define TPS_REG_STATUS_CONN_STATE_MASK GENMASK(3, 1)
+#define TPS_REG_STATUS_CONN_STATE(_s) \
+ FIELD_GET(TPS_REG_STATUS_CONN_STATE_MASK, _s)
+#define TPS_REG_STATUS_PLUG_ORIENTATION_MASK BIT(4)
+#define TPS_REG_STATUS_PLUG_ORIENTATION(_s) \
+ FIELD_GET(TPS_REG_STATUS_PLUG_ORIENTATION_MASK, _s)
+#define TPS_REG_STATUS_PORT_ROLE_MASK BIT(5)
+#define TPS_REG_STATUS_PORT_ROLE(_s) \
+ FIELD_GET(TPS_REG_STATUS_PORT_ROLE_MASK, _s)
+#define TPS_REG_STATUS_DATA_ROLE_MASK BIT(6)
+#define TPS_REG_STATUS_DATA_ROLE(_s) \
+ FIELD_GET(TPS_REG_STATUS_DATA_ROLE_MASK, _s)
+#define TPS_REG_STATUS_VBUS_STATUS_MASK GENMASK(21, 20)
+#define TPS_REG_STATUS_VBUS_STATUS(_s) \
+ FIELD_GET(TPS_REG_STATUS_VBUS_STATUS_MASK, _s)
+#define TPS_REG_STATUS_USB_HOST_PRESENT_MASK GENMASK(23, 22)
+#define TPS_REG_STATUS_USB_HOST_PRESENT(_s) \
+ FIELD_GET(TPS_REG_STATUS_USB_HOST_PRESENT_MASK, _s)
+#define TPS_REG_STATUS_ACTING_AS_LEGACY_MASK GENMASK(25, 24)
+#define TPS_REG_STATUS_ACTING_AS_LEGACY(_s) \
+ FIELD_GET(TPS_REG_STATUS_ACTING_AS_LEGACY_MASK, _s)
+#define TPS_REG_STATUS_BIST_MASK BIT(27)
+#define TPS_REG_STATUS_BIST(_s) \
+ FIELD_GET(TPS_REG_STATUS_BIST_MASK, _s)
+
+/* BOOT STATUS REG */
+#define TPS_REG_BOOT_STATUS_PATCH_HEADER_ERR_MASK BIT(0)
+#define TPS_REG_BOOT_STATUS_PATCH_HEADER_ERR(_s) \
+ FIELD_GET(TPS_REG_BOOT_STATUS_PATCH_HEADER_ERR_MASK, _s)
+#define TPS_REG_BOOT_STATUS_DEAD_BATTERY_FLAG_MASK BIT(2)
+#define TPS_REG_BOOT_STATUS_DEAD_BATTERY_FLAG(_s) \
+ FIELD_GET(TPS_REG_BOOT_STATUS_DEAD_BATTERY_FLAG_MASK, _s)
+#define TPS_REG_BOOT_STATUS_I2C_EEPROM_PRESENT_MASK BIT(3)
+#define TPS_REG_BOOT_STATUS_I2C_EEPROM_PRESENT(_s) \
+ FIELD_GET(TPS_REG_BOOT_STATUS_I2C_EEPROM_PRESENT_MASK, _s)
+#define TPS_REG_BOOT_STATUS_PATCH_DOWNLOAD_ERR_MASK BIT(10)
+#define TPS_REG_BOOT_STATUS_PATCH_DOWNLOAD_ERR(_s) \
+ FIELD_GET(TPS_REG_BOOT_STATUS_PATCH_DOWNLOAD_ERR_MASK, _s)
+#define TPS_REG_BOOT_STATUS_MASTER_TSD_MASK BIT(19)
+#define TPS_REG_BOOT_STATUS_MASTER_TSD(_s) \
+ FIELD_GET(TPS_REG_BOOT_STATUS_MASTER_TSD_MASK, _s)
+#define TPS_REG_BOOT_STATUS_PATCH_CONFIG_SOURCE_MASK GENMASK(31, 29)
+#define TPS_REG_BOOT_STATUS_PATCH_CONFIG_SOURCE(_s) \
+ FIELD_GET(TPS_REG_BOOT_STATUS_PATCH_CONFIG_SOURCE_MASK, _s)
+#define TPS_REG_BOOT_STATUS_REV_ID_MASK GENMASK_ULL(39, 32)
+#define TPS_REG_BOOT_STATUS_REV_ID(_s) \
+ FIELD_GET(TPS_REG_BOOT_STATUS_REV_ID_MASK, _s)
+
+/* POWER STATUS REG */
+#define TPS_REG_POWER_STATUS_POWER_CONNECTION_MASK BIT(0)
+#define TPS_REG_POWER_STATUS_POWER_CONNECTION(_s) \
+ FIELD_GET(TPS_REG_POWER_STATUS_POWER_CONNECTION_MASK, _s)
+#define TPS_REG_POWER_STATUS_SOURCE_SINK_MASK BIT(1)
+#define TPS_REG_POWER_STATUS_SOURCE_SINK(_s) \
+ FIELD_GET(TPS_REG_POWER_STATUS_SOURCE_SINK_MASK, _s)
+#define TPS_REG_POWER_STATUS_TYPEC_CURRENT_MASK GENMASK(3, 2)
+#define TPS_REG_POWER_STATUS_TYPEC_CURRENT(_s) \
+ FIELD_GET(TPS_REG_POWER_STATUS_TYPEC_CURRENT_MASK, _s)
+#define TPS_REG_POWER_STATUS_CHARGER_DETECT_MASK GENMASK(7, 4)
+#define TPS_REG_POWER_STATUS_CHARGER_DETECT(_s) \
+ FIELD_GET(TPS_REG_POWER_STATUS_CHARGER_DETECT_MASK, _s)
+#define TPS_REG_POWER_STATUS_CHARGER_ADVERTISE_MASK GENMASK(9, 8)
+#define TPS_REG_POWER_STATUS_CHARGER_ADVERTISE(_s) \
+ FIELD_GET(TPS_REG_POWER_STATUS_CHARGER_ADVERTISE_MASK, _s)
+
+/* PD STATUS REG */
+#define TPS_REG_PD_STATUS_CC_PULL_UP_MASK GENMASK(3, 2)
+#define TPS_REG_PD_STATUS_CC_PULL_UP(_s) \
+ FIELD_GET(TPS_REG_PD_STATUS_CC_PULL_UP_MASK, _s)
+#define TPS_REG_PD_STATUS_PORT_TYPE_MASK GENMASK(5, 4)
+#define TPS_REG_PD_STATUS_PORT_TYPE(_s) \
+ FIELD_GET(TPS_REG_PD_STATUS_PORT_TYPE_MASK, _s)
+#define TPS_REG_PD_STATUS_PERESENT_PD_ROLE_MASK BIT(6)
+#define TPS_REG_PD_STATUS_PD_CONN(_s) \
+ FIELD_GET(TPS_REG_PD_STATUS_PERESENT_PD_ROLE_MASK, _s)
+#define TPS_REG_PD_STATUS_SOFT_RESET_DETAILS_MASK GENMASK(12, 8)
+#define TPS_REG_PD_STATUS_SOFT_RESET_DETAILS(_s) \
+ FIELD_GET(TPS_REG_PD_STATUS_SOFT_RESET_DETAILS_MASK, _s)
+#define TPS_REG_PD_STATUS_HARD_RESET_DETAILS_MASK GENMASK(21, 16)
+#define TPS_REG_PD_STATUS_HARD_RESET_DETAILS(_s) \
+ FIELD_GET(TPS_REG_PD_STATUS_HARD_RESET_DETAILS_MASK, _s)
+#define TPS_REG_PD_STATUS_ERR_RECOVERY_DETAILS_MASK GENMASK(27, 22)
+#define TPS_REG_PD_STATUS_ERR_RECOVERY_DETAILS(_s) \
+ FIELD_GET(TPS_REG_PD_STATUS_ERR_RECOVERY_DETAILS_MASK, _s)
+#define TPS_REG_PD_STATUS_DATA_RESET_DETAILS_MASK GENMASK(30, 28)
+#define TPS_REG_PD_STATUS_DATA_RESET_DETAILS(_s) \
+ FIELD_GET(TPS_REG_PD_STATUS_DATA_RESET_DETAILS_MASK, _s)
+
+/* TX_SOURCE_CAPS REG */
+#define TPS_PDO_NUM_VALID_PDOS_MASK GENMASK(2, 0)
+#define TPS_PDO_NUM_VALID_PDOS(_header) \
+ FIELD_GET(TPS_PDO_NUM_VALID_PDOS_MASK, _header)
+#define TPS_PDO_MAX_CURRENT_MASK GENMASK(9, 0)
+#define TPS_PDO_MAX_CURRENT(_pdo) \
+ FIELD_GET(TPS_PDO_MAX_CURRENT_MASK, _pdo)
+
+#define TPS_PORT_TYPE_SINK_SOURCE 0
+#define TPS_PORT_TYPE_SINK 1
+#define TPS_PORT_TYPE_SOURCE 2
+#define TPS_PORT_TYPE_SOURCE_SINK 3
+
+#define TPS_TYPEC_CURRENT_USB_DEFAULT 0
+#define TPS_TYPEC_CURRENT_1_5_A 1
+#define TPS_TYPEC_CURRENT_3_0_A 2
+#define TPS_TYPEC_CURRENT_PD 3
+
+#endif /* __TPS25750_H__ */