]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
gpio: pca953x: Add support for TI TCA6418
authorMaria Garcia <mariagarcia7293@gmail.com>
Thu, 3 Jul 2025 20:57:40 +0000 (22:57 +0200)
committerBartosz Golaszewski <bartosz.golaszewski@linaro.org>
Mon, 7 Jul 2025 09:05:25 +0000 (11:05 +0200)
The TI TCA6418 is a 18-channel I2C I/O expander. It is slightly
different to other models from the same family, such as TCA6416,
but has enough in common with them to make it work with just a
few tweaks, which are explained in the code's documentation.

Signed-off-by: Maria Garcia <mariagarcia7293@gmail.com>
Link: https://lore.kernel.org/r/20250703205740.45385-3-mariagarcia7293@gmail.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
drivers/gpio/gpio-pca953x.c

index b852e49976294a4859ed909f4fa715af2e48d549..f5184860bd89faf4d0a6c9ae0e6995166934c650 100644 (file)
 #define PCA953X_INVERT         0x02
 #define PCA953X_DIRECTION      0x03
 
+#define TCA6418_INPUT          0x14
+#define TCA6418_OUTPUT         0x17
+#define TCA6418_DIRECTION      0x23
+
 #define REG_ADDR_MASK          GENMASK(5, 0)
 #define REG_ADDR_EXT           BIT(6)
 #define REG_ADDR_AI            BIT(7)
@@ -76,7 +80,8 @@
 #define PCA953X_TYPE           BIT(12)
 #define PCA957X_TYPE           BIT(13)
 #define PCAL653X_TYPE          BIT(14)
-#define PCA_TYPE_MASK          GENMASK(15, 12)
+#define TCA6418_TYPE           BIT(16)
+#define PCA_TYPE_MASK          GENMASK(16, 12)
 
 #define PCA_CHIP_TYPE(x)       ((x) & PCA_TYPE_MASK)
 
@@ -115,6 +120,7 @@ static const struct i2c_device_id pca953x_id[] = {
        { "pca6107", 8  | PCA953X_TYPE | PCA_INT, },
        { "tca6408", 8  | PCA953X_TYPE | PCA_INT, },
        { "tca6416", 16 | PCA953X_TYPE | PCA_INT, },
+       { "tca6418", 18 | TCA6418_TYPE | PCA_INT, },
        { "tca6424", 24 | PCA953X_TYPE | PCA_INT, },
        { "tca9538", 8  | PCA953X_TYPE | PCA_INT, },
        { "tca9539", 16 | PCA953X_TYPE | PCA_INT, },
@@ -204,6 +210,13 @@ static const struct pca953x_reg_config pca957x_regs = {
        .invert = PCA957X_INVRT,
 };
 
+static const struct pca953x_reg_config tca6418_regs = {
+       .direction = TCA6418_DIRECTION,
+       .output = TCA6418_OUTPUT,
+       .input = TCA6418_INPUT,
+       .invert = 0xFF, /* Does not apply */
+};
+
 struct pca953x_chip {
        unsigned gpio_start;
        struct mutex i2c_lock;
@@ -237,6 +250,22 @@ static int pca953x_bank_shift(struct pca953x_chip *chip)
        return fls((chip->gpio_chip.ngpio - 1) / BANK_SZ);
 }
 
+/*
+ * Helper function to get the correct bit mask for a given offset and chip type.
+ * The TCA6418's input, output, and direction banks have a peculiar bit order:
+ * the first byte uses reversed bit order, while the second byte uses standard order.
+ */
+static inline u8 pca953x_get_bit_mask(struct pca953x_chip *chip, unsigned int offset)
+{
+       unsigned int bit_pos_in_bank = offset % BANK_SZ;
+       int msb = BANK_SZ - 1;
+
+       if (PCA_CHIP_TYPE(chip->driver_data) == TCA6418_TYPE && offset <= msb)
+               return BIT(msb - bit_pos_in_bank);
+
+       return BIT(bit_pos_in_bank);
+}
+
 #define PCA953x_BANK_INPUT     BIT(0)
 #define PCA953x_BANK_OUTPUT    BIT(1)
 #define PCA953x_BANK_POLARITY  BIT(2)
@@ -353,18 +382,43 @@ static bool pcal6534_check_register(struct pca953x_chip *chip, unsigned int reg,
        return true;
 }
 
+/* TCA6418 breaks the PCA953x register order rule */
+static bool tca6418_check_register(struct pca953x_chip *chip, unsigned int reg,
+                                  u32 access_type_mask)
+{
+       /*  Valid Input Registers - BIT(0) for readable access */
+       if (reg >= TCA6418_INPUT && reg < (TCA6418_INPUT + NBANK(chip)))
+               return (access_type_mask & BIT(0));
+
+       /*  Valid Output Registers - BIT(1) for writeable access */
+       if (reg >= TCA6418_OUTPUT && reg < (TCA6418_OUTPUT + NBANK(chip)))
+               return (access_type_mask & (BIT(0) | BIT(1)));
+
+       /*  Valid Direction Registers - BIT(2) for volatile access */
+       if (reg >= TCA6418_DIRECTION && reg < (TCA6418_DIRECTION + NBANK(chip)))
+               return (access_type_mask & (BIT(0) | BIT(1)));
+
+       return false;
+}
+
 static bool pca953x_readable_register(struct device *dev, unsigned int reg)
 {
        struct pca953x_chip *chip = dev_get_drvdata(dev);
        u32 bank;
 
-       if (PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE) {
+       switch (PCA_CHIP_TYPE(chip->driver_data)) {
+       case PCA957X_TYPE:
                bank = PCA957x_BANK_INPUT | PCA957x_BANK_OUTPUT |
                       PCA957x_BANK_POLARITY | PCA957x_BANK_CONFIG |
                       PCA957x_BANK_BUSHOLD;
-       } else {
+               break;
+       case TCA6418_TYPE:
+               /* BIT(0) to indicate read access */
+               return tca6418_check_register(chip, reg, BIT(0));
+       default:
                bank = PCA953x_BANK_INPUT | PCA953x_BANK_OUTPUT |
                       PCA953x_BANK_POLARITY | PCA953x_BANK_CONFIG;
+               break;
        }
 
        if (chip->driver_data & PCA_PCAL) {
@@ -381,12 +435,18 @@ static bool pca953x_writeable_register(struct device *dev, unsigned int reg)
        struct pca953x_chip *chip = dev_get_drvdata(dev);
        u32 bank;
 
-       if (PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE) {
+       switch (PCA_CHIP_TYPE(chip->driver_data)) {
+       case PCA957X_TYPE:
                bank = PCA957x_BANK_OUTPUT | PCA957x_BANK_POLARITY |
                        PCA957x_BANK_CONFIG | PCA957x_BANK_BUSHOLD;
-       } else {
+               break;
+       case TCA6418_TYPE:
+               /* BIT(1) for write access */
+               return tca6418_check_register(chip, reg, BIT(1));
+       default:
                bank = PCA953x_BANK_OUTPUT | PCA953x_BANK_POLARITY |
                        PCA953x_BANK_CONFIG;
+               break;
        }
 
        if (chip->driver_data & PCA_PCAL)
@@ -401,10 +461,17 @@ static bool pca953x_volatile_register(struct device *dev, unsigned int reg)
        struct pca953x_chip *chip = dev_get_drvdata(dev);
        u32 bank;
 
-       if (PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE)
+       switch (PCA_CHIP_TYPE(chip->driver_data)) {
+       case PCA957X_TYPE:
                bank = PCA957x_BANK_INPUT;
-       else
+               break;
+       case TCA6418_TYPE:
+               /* BIT(2) for volatile access */
+               return tca6418_check_register(chip, reg, BIT(2));
+       default:
                bank = PCA953x_BANK_INPUT;
+               break;
+       }
 
        if (chip->driver_data & PCA_PCAL)
                bank |= PCAL9xxx_BANK_IRQ_STAT;
@@ -489,6 +556,16 @@ static u8 pcal6534_recalc_addr(struct pca953x_chip *chip, int reg, int off)
        return pinctrl + addr + (off / BANK_SZ);
 }
 
+static u8 tca6418_recalc_addr(struct pca953x_chip *chip, int reg_base, int offset)
+{
+       /*
+        * reg_base will be TCA6418_INPUT, TCA6418_OUTPUT, or TCA6418_DIRECTION
+        * offset is the global GPIO line offset (0-17)
+        * BANK_SZ is 8 for TCA6418 (8 bits per register bank)
+        */
+       return reg_base + (offset / BANK_SZ);
+}
+
 static int pca953x_write_regs(struct pca953x_chip *chip, int reg, unsigned long *val)
 {
        u8 regaddr = chip->recalc_addr(chip, reg, 0);
@@ -529,10 +606,13 @@ static int pca953x_gpio_direction_input(struct gpio_chip *gc, unsigned off)
 {
        struct pca953x_chip *chip = gpiochip_get_data(gc);
        u8 dirreg = chip->recalc_addr(chip, chip->regs->direction, off);
-       u8 bit = BIT(off % BANK_SZ);
+       u8 bit = pca953x_get_bit_mask(chip, off);
 
        guard(mutex)(&chip->i2c_lock);
 
+       if (PCA_CHIP_TYPE(chip->driver_data) == TCA6418_TYPE)
+               return regmap_write_bits(chip->regmap, dirreg, bit, 0);
+
        return regmap_write_bits(chip->regmap, dirreg, bit, bit);
 }
 
@@ -542,7 +622,7 @@ static int pca953x_gpio_direction_output(struct gpio_chip *gc,
        struct pca953x_chip *chip = gpiochip_get_data(gc);
        u8 dirreg = chip->recalc_addr(chip, chip->regs->direction, off);
        u8 outreg = chip->recalc_addr(chip, chip->regs->output, off);
-       u8 bit = BIT(off % BANK_SZ);
+       u8 bit = pca953x_get_bit_mask(chip, off);
        int ret;
 
        guard(mutex)(&chip->i2c_lock);
@@ -552,7 +632,13 @@ static int pca953x_gpio_direction_output(struct gpio_chip *gc,
        if (ret)
                return ret;
 
-       /* then direction */
+       /*
+        * then direction
+        * (in/out logic is inverted on TCA6418)
+        */
+       if (PCA_CHIP_TYPE(chip->driver_data) == TCA6418_TYPE)
+               return regmap_write_bits(chip->regmap, dirreg, bit, bit);
+
        return regmap_write_bits(chip->regmap, dirreg, bit, 0);
 }
 
@@ -560,7 +646,7 @@ static int pca953x_gpio_get_value(struct gpio_chip *gc, unsigned off)
 {
        struct pca953x_chip *chip = gpiochip_get_data(gc);
        u8 inreg = chip->recalc_addr(chip, chip->regs->input, off);
-       u8 bit = BIT(off % BANK_SZ);
+       u8 bit = pca953x_get_bit_mask(chip, off);
        u32 reg_val;
        int ret;
 
@@ -577,7 +663,7 @@ static int pca953x_gpio_set_value(struct gpio_chip *gc, unsigned int off,
 {
        struct pca953x_chip *chip = gpiochip_get_data(gc);
        u8 outreg = chip->recalc_addr(chip, chip->regs->output, off);
-       u8 bit = BIT(off % BANK_SZ);
+       u8 bit = pca953x_get_bit_mask(chip, off);
 
        guard(mutex)(&chip->i2c_lock);
 
@@ -588,7 +674,7 @@ static int pca953x_gpio_get_direction(struct gpio_chip *gc, unsigned off)
 {
        struct pca953x_chip *chip = gpiochip_get_data(gc);
        u8 dirreg = chip->recalc_addr(chip, chip->regs->direction, off);
-       u8 bit = BIT(off % BANK_SZ);
+       u8 bit = pca953x_get_bit_mask(chip, off);
        u32 reg_val;
        int ret;
 
@@ -597,7 +683,14 @@ static int pca953x_gpio_get_direction(struct gpio_chip *gc, unsigned off)
        if (ret < 0)
                return ret;
 
-       if (reg_val & bit)
+       /* (in/out logic is inverted on TCA6418) */
+       if (reg_val & bit) {
+               if (PCA_CHIP_TYPE(chip->driver_data) == TCA6418_TYPE)
+                       return GPIO_LINE_DIRECTION_OUT;
+
+               return GPIO_LINE_DIRECTION_IN;
+       }
+       if (PCA_CHIP_TYPE(chip->driver_data) == TCA6418_TYPE)
                return GPIO_LINE_DIRECTION_IN;
 
        return GPIO_LINE_DIRECTION_OUT;
@@ -1117,12 +1210,22 @@ static int pca953x_probe(struct i2c_client *client)
                regmap_config = &pca953x_i2c_regmap;
        }
 
-       if (PCA_CHIP_TYPE(chip->driver_data) == PCAL653X_TYPE) {
+       switch (PCA_CHIP_TYPE(chip->driver_data)) {
+       case PCAL653X_TYPE:
                chip->recalc_addr = pcal6534_recalc_addr;
                chip->check_reg = pcal6534_check_register;
-       } else {
+               break;
+       case TCA6418_TYPE:
+               chip->recalc_addr = tca6418_recalc_addr;
+               /*
+                * We don't assign chip->check_reg = tca6418_check_register directly here.
+                * Instead, the wrappers handle the dispatch based on PCA_CHIP_TYPE.
+                */
+               break;
+       default:
                chip->recalc_addr = pca953x_recalc_addr;
                chip->check_reg = pca953x_check_register;
+               break;
        }
 
        chip->regmap = devm_regmap_init_i2c(client, regmap_config);
@@ -1151,15 +1254,22 @@ static int pca953x_probe(struct i2c_client *client)
        lockdep_set_subclass(&chip->i2c_lock,
                             i2c_adapter_depth(client->adapter));
 
-       /* initialize cached registers from their original values.
+       /*
+        * initialize cached registers from their original values.
         * we can't share this chip with another i2c master.
         */
-       if (PCA_CHIP_TYPE(chip->driver_data) == PCA957X_TYPE) {
+       switch (PCA_CHIP_TYPE(chip->driver_data)) {
+       case PCA957X_TYPE:
                chip->regs = &pca957x_regs;
                ret = device_pca957x_init(chip);
-       } else {
+               break;
+       case TCA6418_TYPE:
+               chip->regs = &tca6418_regs;
+               break;
+       default:
                chip->regs = &pca953x_regs;
                ret = device_pca95xx_init(chip);
+               break;
        }
        if (ret)
                return ret;
@@ -1325,6 +1435,7 @@ static const struct of_device_id pca953x_dt_ids[] = {
        { .compatible = "ti,pca9536", .data = OF_953X( 4, 0), },
        { .compatible = "ti,tca6408", .data = OF_953X( 8, PCA_INT), },
        { .compatible = "ti,tca6416", .data = OF_953X(16, PCA_INT), },
+       { .compatible = "ti,tca6418", .data = (void *)(18 | TCA6418_TYPE | PCA_INT), },
        { .compatible = "ti,tca6424", .data = OF_953X(24, PCA_INT), },
        { .compatible = "ti,tca9535", .data = OF_953X(16, PCA_INT), },
        { .compatible = "ti,tca9538", .data = OF_953X( 8, PCA_INT), },
@@ -1355,7 +1466,9 @@ static int __init pca953x_init(void)
 {
        return i2c_add_driver(&pca953x_driver);
 }
-/* register after i2c postcore initcall and before
+
+/*
+ * register after i2c postcore initcall and before
  * subsys initcalls that may rely on these GPIOs
  */
 subsys_initcall(pca953x_init);