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; }