From 5e2cb1e6d714c2cf1364e93397a27ccc9ae90307 Mon Sep 17 00:00:00 2001 From: Alba Mendez Date: Sat, 9 Nov 2024 03:44:01 +0100 Subject: [PATCH] tps6598x: add SPMI transport generalizes the HPM driver over different transports, implements the SPMI transport, and takes care of waking up the device upon initialization so that commands can be sent to it. then, reverts #365 and makes the necessary changes to the usb driver to use the appropriate transport as necessary. the SPMI transport is pretty much a C translation of the hpm_spmi experiment, but has more retry loops for robustness, since in my experience the device sometimes doesn't actually perform the read / write to the logical register even if it ACKs the SPMI transactions. --- src/tps6598x.c | 204 +++++++++++++++++++++++++++++++++++++++++-------- src/tps6598x.h | 4 +- src/usb.c | 4 +- 3 files changed, 179 insertions(+), 33 deletions(-) diff --git a/src/tps6598x.c b/src/tps6598x.c index 29a18fd0a..7eb844c72 100644 --- a/src/tps6598x.c +++ b/src/tps6598x.c @@ -3,6 +3,7 @@ #include "tps6598x.h" #include "adt.h" #include "i2c.h" +#include "spmi.h" #include "iodev.h" #include "malloc.h" #include "types.h" @@ -17,11 +18,134 @@ #define TPS_CMD_INVALID 0x21434d44 // !CMD struct tps6598x_dev { - i2c_dev_t *i2c; + bool is_spmi; + union { + i2c_dev_t *i2c; + spmi_dev_t *spmi; + }; u8 addr; }; -tps6598x_dev_t *tps6598x_init(const char *adt_node, i2c_dev_t *i2c) +static int tps6598x_spmi_select(tps6598x_dev_t *dev, u8 reg) +{ + int status; + + for (size_t attempt = 0; attempt < 5; attempt++) { + if ((status = spmi_reg0_write(dev->spmi, dev->addr, reg)) == 0) + continue; + if (status < 0) + return status; + + for (size_t i = 0; i < 50; i++) { + u8 got; + if ((status = spmi_ext_read(dev->spmi, dev->addr, 0, &got, 1)) == -2) + continue; + if (status < 0) + return status; + + if ((got & MASK(7)) != reg) { + printf("tps6598x: reg0 write succeeded but read returned 0x%x, retrying\n", got); + break; + } + if (got == reg) + return 0; + udelay(100); + } + } + + return -1; +} + +static int tps6598x_spmi_select_checked(tps6598x_dev_t *dev, u8 reg, size_t len) +{ + if (tps6598x_spmi_select(dev, reg) < 0) + return -1; + + u8 reg_size; + if (spmi_ext_read(dev->spmi, dev->addr, 0x1F, ®_size, 1) < 0) + return -1; + + if (len > reg_size) { + printf("tps6598x: length of register 0x%x is %u (expected at least %lu)\n", reg, reg_size, len); + return -1; + } + return 0; +} + +static int tps6598x_read(tps6598x_dev_t *dev, u8 reg, u8 *bfr, size_t len) +{ + if (!dev->is_spmi) + return i2c_smbus_read(dev->i2c, dev->addr, reg, bfr, len) == (int)len ? -1 : 0; + + if (tps6598x_spmi_select_checked(dev, reg, len) < 0) + return -1; + + u8 addr = 0x20; + while (len) { + size_t block = min(len, 16); + if (spmi_ext_read(dev->spmi, dev->addr, addr, bfr, block) < 0) + return -1; + addr += block, bfr += block, len -= block; + } + + return 0; +} + +static int tps6598x_write(tps6598x_dev_t *dev, u8 reg, const u8 *bfr, size_t len) +{ + if (!dev->is_spmi) + return i2c_smbus_write(dev->i2c, dev->addr, reg, bfr, len) == (int)len ? -1 : 0; + + if (tps6598x_spmi_select_checked(dev, reg, len) < 0) + return -1; + + u8 addr = 0xa0; + while (len) { + size_t block = min(len, 16); + if (spmi_ext_write(dev->spmi, dev->addr, addr, bfr, block) < 0) + return -1; + addr += block, bfr += block, len -= block; + } + + // re-select the register to issue the write + if (tps6598x_spmi_select(dev, reg) < 0) + return -1; + return 0; +} + +static int tps6598x_wakeup(tps6598x_dev_t *dev) +{ + int status; + + if (!dev->is_spmi) + // not implemented, but so far we haven't seen any device + // with an I2C HPM supporting wake/sleep + return -1; + + if ((status = spmi_send_wakeup(dev->spmi, dev->addr)) != 1) + return -1; + + // wait for it to wake up by reading any register != 0, we use 3 + for (size_t attempt = 0; attempt < 50; attempt++) { + if ((status = spmi_reg0_write(dev->spmi, dev->addr, 3)) == 0) + continue; + if (status < 0) + return status; + u8 got; + if ((status = spmi_ext_read(dev->spmi, dev->addr, 0, &got, 1)) == -2) + continue; + if (status < 0) + return status; + if (got == 3) + return 0; + mdelay(1); + } + + printf("tps6598x: Timeout waiting for device to wake up\n"); + return -1; +} + +tps6598x_dev_t *tps6598x_init_i2c(const char *adt_node, i2c_dev_t *i2c) { int adt_offset; adt_offset = adt_path_offset(adt, adt_node); @@ -40,11 +164,45 @@ tps6598x_dev_t *tps6598x_init(const char *adt_node, i2c_dev_t *i2c) if (!dev) return NULL; + dev->is_spmi = false; dev->i2c = i2c; dev->addr = *iic_addr; return dev; } +tps6598x_dev_t *tps6598x_init_spmi(const char *adt_node, spmi_dev_t *spmi) +{ + int adt_offset; + adt_offset = adt_path_offset(adt, adt_node); + if (adt_offset < 0) { + printf("tps6598x: Error getting %s node\n", adt_node); + return NULL; + } + + u32 spmi_addr_len; + const u8 *spmi_addr = adt_getprop(adt, adt_offset, "reg", &spmi_addr_len); + if (spmi_addr == NULL || spmi_addr_len < 1) { + printf("tps6598x: Error getting %s spmi address\n", adt_node); + return NULL; + } + + tps6598x_dev_t *dev = calloc(1, sizeof(*dev)); + if (!dev) + return NULL; + + dev->is_spmi = true; + dev->spmi = spmi; + dev->addr = *spmi_addr; + + if (tps6598x_wakeup(dev) < 0) { + printf("tps6598x: Failed to wake up SPMI device %s\n", adt_node); + tps6598x_shutdown(dev); + return NULL; + } + + return dev; +} + void tps6598x_shutdown(tps6598x_dev_t *dev) { free(dev); @@ -54,16 +212,16 @@ int tps6598x_command(tps6598x_dev_t *dev, const char *cmd, const u8 *data_in, si u8 *data_out, size_t len_out) { if (len_in) { - if (i2c_smbus_write(dev->i2c, dev->addr, TPS_REG_DATA1, data_in, len_in) < 0) + if (tps6598x_write(dev, TPS_REG_DATA1, data_in, len_in) < 0) return -1; } - if (i2c_smbus_write(dev->i2c, dev->addr, TPS_REG_CMD1, (const u8 *)cmd, 4) < 0) + if (tps6598x_write(dev, TPS_REG_CMD1, (const u8 *)cmd, 4) < 0) return -1; u32 cmd_status; do { - if (i2c_smbus_read32(dev->i2c, dev->addr, TPS_REG_CMD1, &cmd_status)) + if (tps6598x_read(dev, TPS_REG_CMD1, (u8 *)&cmd_status, 4) < 0) return -1; if (cmd_status == TPS_CMD_INVALID) return -1; @@ -71,8 +229,7 @@ int tps6598x_command(tps6598x_dev_t *dev, const char *cmd, const u8 *data_in, si } while (cmd_status != 0); if (len_out) { - if (i2c_smbus_read(dev->i2c, dev->addr, TPS_REG_DATA1, data_out, len_out) != - (ssize_t)len_out) + if (tps6598x_read(dev, TPS_REG_DATA1, data_out, len_out) < 0) return -1; } @@ -81,37 +238,30 @@ int tps6598x_command(tps6598x_dev_t *dev, const char *cmd, const u8 *data_in, si int tps6598x_disable_irqs(tps6598x_dev_t *dev, tps6598x_irq_state_t *state) { - size_t read; - int written; static const u8 zeros[CD3218B12_IRQ_WIDTH] = {0x00}; static const u8 ones[CD3218B12_IRQ_WIDTH] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // store IntEvent 1 to restore it later - read = i2c_smbus_read(dev->i2c, dev->addr, TPS_REG_INT_MASK1, state->int_mask1, - sizeof(state->int_mask1)); - if (read != CD3218B12_IRQ_WIDTH) { + if (tps6598x_read(dev, TPS_REG_INT_MASK1, state->int_mask1, sizeof(state->int_mask1)) < 0) { printf("tps6598x: reading TPS_REG_INT_MASK1 failed\n"); return -1; } state->valid = 1; // mask interrupts and ack all interrupt flags - written = i2c_smbus_write(dev->i2c, dev->addr, TPS_REG_INT_CLEAR1, ones, sizeof(ones)); - if (written != sizeof(zeros)) { - printf("tps6598x: writing TPS_REG_INT_CLEAR1 failed, written: %d\n", written); + if (tps6598x_write(dev, TPS_REG_INT_CLEAR1, ones, sizeof(ones)) < 0) { + printf("tps6598x: writing TPS_REG_INT_CLEAR1 failed\n"); return -1; } - written = i2c_smbus_write(dev->i2c, dev->addr, TPS_REG_INT_MASK1, zeros, sizeof(zeros)); - if (written != sizeof(ones)) { - printf("tps6598x: writing TPS_REG_INT_MASK1 failed, written: %d\n", written); + if (tps6598x_write(dev, TPS_REG_INT_MASK1, zeros, sizeof(zeros)) < 0) { + printf("tps6598x: writing TPS_REG_INT_MASK1 failed\n"); return -1; } #ifdef DEBUG u8 tmp[CD3218B12_IRQ_WIDTH] = {0x00}; - read = i2c_smbus_read(dev->i2c, dev->addr, TPS_REG_INT_MASK1, tmp, CD3218B12_IRQ_WIDTH); - if (read != CD3218B12_IRQ_WIDTH) + if (tps6598x_read(dev, TPS_REG_INT_MASK1, tmp, CD3218B12_IRQ_WIDTH) < 0) printf("tps6598x: failed verification, can't read TPS_REG_INT_MASK1\n"); else { printf("tps6598x: verify: TPS_REG_INT_MASK1 vs. saved IntMask1\n"); @@ -124,20 +274,14 @@ int tps6598x_disable_irqs(tps6598x_dev_t *dev, tps6598x_irq_state_t *state) int tps6598x_restore_irqs(tps6598x_dev_t *dev, tps6598x_irq_state_t *state) { - int written; - - written = i2c_smbus_write(dev->i2c, dev->addr, TPS_REG_INT_MASK1, state->int_mask1, - sizeof(state->int_mask1)); - if (written != sizeof(state->int_mask1)) { + if (tps6598x_write(dev, TPS_REG_INT_MASK1, state->int_mask1, sizeof(state->int_mask1)) < 0) { printf("tps6598x: restoring TPS_REG_INT_MASK1 failed\n"); return -1; } #ifdef DEBUG - int read; u8 tmp[CD3218B12_IRQ_WIDTH]; - read = i2c_smbus_read(dev->i2c, dev->addr, TPS_REG_INT_MASK1, tmp, sizeof(tmp)); - if (read != sizeof(tmp)) + if (tps6598x_read(dev, TPS_REG_INT_MASK1, tmp, sizeof(tmp)) < 0) printf("tps6598x: failed verification, can't read TPS_REG_INT_MASK1\n"); else { printf("tps6598x: verify saved IntMask1 vs. TPS_REG_INT_MASK1:\n"); @@ -153,7 +297,7 @@ int tps6598x_powerup(tps6598x_dev_t *dev) { u8 power_state; - if (i2c_smbus_read8(dev->i2c, dev->addr, TPS_REG_POWER_STATE, &power_state)) + if (tps6598x_read(dev, TPS_REG_POWER_STATE, &power_state, 1) < 0) return -1; if (power_state == 0) @@ -162,7 +306,7 @@ int tps6598x_powerup(tps6598x_dev_t *dev) const u8 data = 0; tps6598x_command(dev, "SSPS", &data, 1, NULL, 0); - if (i2c_smbus_read8(dev->i2c, dev->addr, TPS_REG_POWER_STATE, &power_state)) + if (tps6598x_read(dev, TPS_REG_POWER_STATE, &power_state, 1) < 0) return -1; if (power_state != 0) diff --git a/src/tps6598x.h b/src/tps6598x.h index 9e6d26a23..f3bb12a8e 100644 --- a/src/tps6598x.h +++ b/src/tps6598x.h @@ -4,11 +4,13 @@ #define TPS6598X_H #include "i2c.h" +#include "spmi.h" #include "types.h" typedef struct tps6598x_dev tps6598x_dev_t; -tps6598x_dev_t *tps6598x_init(const char *adt_path, i2c_dev_t *i2c); +tps6598x_dev_t *tps6598x_init_i2c(const char *adt_node, i2c_dev_t *i2c); +tps6598x_dev_t *tps6598x_init_spmi(const char *adt_node, spmi_dev_t *spmi); void tps6598x_shutdown(tps6598x_dev_t *dev); int tps6598x_command(tps6598x_dev_t *dev, const char *cmd, const u8 *data_in, size_t len_in, diff --git a/src/usb.c b/src/usb.c index 49d27674f..123301761 100644 --- a/src/usb.c +++ b/src/usb.c @@ -214,9 +214,9 @@ struct iodev iodev_usb_vuart = { static tps6598x_dev_t *hpm_init(i2c_dev_t *i2c, const char *hpm_path) { - tps6598x_dev_t *tps = tps6598x_init(hpm_path, i2c); + tps6598x_dev_t *tps = tps6598x_init_i2c(hpm_path, i2c); if (!tps) { - printf("usb: tps6598x_init failed for %s.\n", hpm_path); + printf("usb: tps6598x_init_i2c failed for %s.\n", hpm_path); return NULL; }