gpio: Add Turris Omnia MCU driver
authorPali Rohár <pali@kernel.org>
Thu, 28 Jul 2022 11:06:24 +0000 (13:06 +0200)
committerStefan Roese <sr@denx.de>
Fri, 29 Jul 2022 08:02:43 +0000 (10:02 +0200)
This driver registers GPIO controller and allows U-Boot to control GPIO
pins on MCU which is connected to Turris Omnia via i2c.

Signed-off-by: Pali Rohár <pali@kernel.org>
Reviewed-by: Stefan Roese <sr@denx.de>
board/CZ.NIC/turris_omnia/MAINTAINERS
drivers/gpio/Kconfig
drivers/gpio/Makefile
drivers/gpio/turris_omnia_mcu.c [new file with mode: 0644]

index 8258f4f..8bff97c 100644 (file)
@@ -6,4 +6,5 @@ F:      arch/arm/dts/armada-385-turris-omnia*.dts*
 F:     board/CZ.NIC/turris_atsha_otp.*
 F:     board/CZ.NIC/turris_omnia/
 F:     configs/turris_omnia_defconfig
+F:     drivers/gpio/turris_omnia_mcu.c
 F:     include/configs/turris_omnia.h
index aaa152f..82a8bca 100644 (file)
@@ -598,4 +598,11 @@ config SLG7XL45106_I2C_GPO
           8-bit gpo expander, all gpo lines are controlled by writing
           value into data register.
 
+config TURRIS_OMNIA_MCU
+       bool "Turris Omnia MCU GPIO driver"
+       depends on DM_GPIO
+       default y if TARGET_TURRIS_OMNIA
+       help
+          Support for GPIOs on MCU connected to Turris Omnia via i2c.
+
 endif
index d755276..219f37e 100644 (file)
@@ -75,3 +75,4 @@ obj-$(CONFIG_MAX7320_GPIO)    += max7320_gpio.o
 obj-$(CONFIG_SL28CPLD_GPIO)    += sl28cpld-gpio.o
 obj-$(CONFIG_ZYNQMP_GPIO_MODEPIN)      += zynqmp_gpio_modepin.o
 obj-$(CONFIG_SLG7XL45106_I2C_GPO)      += gpio_slg7xl45106.o
+obj-$(CONFIG_$(SPL_TPL_)TURRIS_OMNIA_MCU)      += turris_omnia_mcu.o
diff --git a/drivers/gpio/turris_omnia_mcu.c b/drivers/gpio/turris_omnia_mcu.c
new file mode 100644 (file)
index 0000000..3e5d74e
--- /dev/null
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0+
+// (C) 2022 Pali Rohár <pali@kernel.org>
+
+#include <common.h>
+#include <dm.h>
+#include <i2c.h>
+#include <asm/gpio.h>
+#include <linux/log2.h>
+
+enum commands_e {
+       CMD_GET_STATUS_WORD                 = 0x01,
+       CMD_GENERAL_CONTROL                 = 0x02,
+
+       /* available if STS_FEATURES_SUPPORTED bit set in status word */
+       CMD_GET_FEATURES                    = 0x10,
+
+       /* available if FEAT_EXT_CMDS bit is set in features */
+       CMD_GET_EXT_STATUS_DWORD            = 0x11,
+       CMD_EXT_CONTROL                     = 0x12,
+       CMD_GET_EXT_CONTROL_STATUS          = 0x13,
+};
+
+/* CMD_GET_STATUS_WORD */
+enum sts_word_e {
+       STS_MCU_TYPE_MASK                = GENMASK(1, 0),
+       STS_MCU_TYPE_STM32               = 0,
+       STS_MCU_TYPE_GD32                = 1,
+       STS_MCU_TYPE_MKL                 = 2,
+       STS_FEATURES_SUPPORTED           = BIT(2),
+       STS_USER_REGULATOR_NOT_SUPPORTED = BIT(3),
+       STS_CARD_DET                     = BIT(4),
+       STS_MSATA_IND                    = BIT(5),
+       STS_USB30_OVC                    = BIT(6),
+       STS_USB31_OVC                    = BIT(7),
+       STS_USB30_PWRON                  = BIT(8),
+       STS_USB31_PWRON                  = BIT(9),
+       STS_ENABLE_4V5                   = BIT(10),
+       STS_BUTTON_MODE                  = BIT(11),
+       STS_BUTTON_PRESSED               = BIT(12),
+       STS_BUTTON_COUNTER_MASK          = GENMASK(15, 13)
+};
+
+/* CMD_GENERAL_CONTROL */
+enum ctl_byte_e {
+       CTL_LIGHT_RST   = BIT(0),
+       CTL_HARD_RST    = BIT(1),
+       /*CTL_RESERVED    = BIT(2),*/
+       CTL_USB30_PWRON = BIT(3),
+       CTL_USB31_PWRON = BIT(4),
+       CTL_ENABLE_4V5  = BIT(5),
+       CTL_BUTTON_MODE = BIT(6),
+       CTL_BOOTLOADER  = BIT(7)
+};
+
+/* CMD_GET_FEATURES */
+enum features_e {
+       FEAT_EXT_CMDS           = BIT(1),
+};
+
+struct turris_omnia_mcu_info {
+       u16 features;
+};
+
+static int turris_omnia_mcu_get_function(struct udevice *dev, uint offset)
+{
+       struct turris_omnia_mcu_info *info = dev_get_plat(dev);
+
+       switch (offset) {
+       /* bank 0 */
+       case 0 ... 15:
+               switch (offset) {
+               case ilog2(STS_USB30_PWRON):
+               case ilog2(STS_USB31_PWRON):
+               case ilog2(STS_ENABLE_4V5):
+               case ilog2(STS_BUTTON_MODE):
+                       return GPIOF_OUTPUT;
+               default:
+                       return GPIOF_INPUT;
+               }
+
+       /* bank 1 - supported only when FEAT_EXT_CMDS is set */
+       case (16 + 0) ... (16 + 31):
+               if (!(info->features & FEAT_EXT_CMDS))
+                       return -EINVAL;
+               return GPIOF_INPUT;
+
+       /* bank 2 - supported only when FEAT_EXT_CMDS is set */
+       case (16 + 32 + 0) ... (16 + 32 + 15):
+               if (!(info->features & FEAT_EXT_CMDS))
+                       return -EINVAL;
+               return GPIOF_OUTPUT;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int turris_omnia_mcu_get_value(struct udevice *dev, uint offset)
+{
+       struct turris_omnia_mcu_info *info = dev_get_plat(dev);
+       u8 val16[2];
+       u8 val32[4];
+       int ret;
+
+       switch (offset) {
+       /* bank 0 */
+       case 0 ... 15:
+               ret = dm_i2c_read(dev, CMD_GET_STATUS_WORD, val16, 2);
+               if (ret)
+                       return ret;
+               return ((((u16)val16[1] << 8) | val16[0]) >> offset) & 0x1;
+
+       /* bank 1 - supported only when FEAT_EXT_CMDS is set */
+       case (16 + 0) ... (16 + 31):
+               if (!(info->features & FEAT_EXT_CMDS))
+                       return -EINVAL;
+               ret = dm_i2c_read(dev, CMD_GET_EXT_STATUS_DWORD, val32, 4);
+               if (ret)
+                       return ret;
+               return ((((u32)val32[3] << 24) | ((u32)val32[2] << 16) |
+                        ((u32)val32[1] << 8) | val32[0]) >> (offset - 16)) & 0x1;
+
+       /* bank 2 - supported only when FEAT_EXT_CMDS is set */
+       case (16 + 32 + 0) ... (16 + 32 + 15):
+               if (!(info->features & FEAT_EXT_CMDS))
+                       return -EINVAL;
+               ret = dm_i2c_read(dev, CMD_GET_EXT_CONTROL_STATUS, val16, 2);
+               if (ret)
+                       return ret;
+               return ((((u16)val16[1] << 8) | val16[0]) >> (offset - 16 - 32)) & 0x1;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int turris_omnia_mcu_set_value(struct udevice *dev, uint offset, int value)
+{
+       struct turris_omnia_mcu_info *info = dev_get_plat(dev);
+       u8 val[2];
+       int ret;
+       u8 reg;
+
+       switch (offset) {
+       /* bank 0 */
+       case ilog2(STS_USB30_PWRON):
+               reg = CMD_GENERAL_CONTROL;
+               val[1] = CTL_USB30_PWRON;
+               break;
+       case ilog2(STS_USB31_PWRON):
+               reg = CMD_GENERAL_CONTROL;
+               val[1] = CTL_USB31_PWRON;
+               break;
+       case ilog2(STS_ENABLE_4V5):
+               reg = CMD_GENERAL_CONTROL;
+               val[1] = CTL_ENABLE_4V5;
+               break;
+       case ilog2(STS_BUTTON_MODE):
+               reg = CMD_GENERAL_CONTROL;
+               val[1] = CTL_BUTTON_MODE;
+               break;
+
+       /* bank 2 - supported only when FEAT_EXT_CMDS is set */
+       case (16 + 32 + 0) ... (16 + 32 + 15):
+               if (!(info->features & FEAT_EXT_CMDS))
+                       return -EINVAL;
+               reg = CMD_EXT_CONTROL;
+               val[1] = BIT(offset - 16 - 32);
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       val[0] = value ? val[1] : 0;
+
+       ret = dm_i2c_write(dev, reg, val, 2);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int turris_omnia_mcu_direction_input(struct udevice *dev, uint offset)
+{
+       int ret;
+
+       ret = turris_omnia_mcu_get_function(dev, offset);
+       if (ret < 0)
+               return ret;
+       else if (ret != GPIOF_INPUT)
+               return -EOPNOTSUPP;
+
+       return 0;
+}
+
+static int turris_omnia_mcu_direction_output(struct udevice *dev, uint offset, int value)
+{
+       int ret;
+
+       ret = turris_omnia_mcu_get_function(dev, offset);
+       if (ret < 0)
+               return ret;
+       else if (ret != GPIOF_OUTPUT)
+               return -EOPNOTSUPP;
+
+       return turris_omnia_mcu_set_value(dev, offset, value);
+}
+
+static int turris_omnia_mcu_xlate(struct udevice *dev, struct gpio_desc *desc,
+                                 struct ofnode_phandle_args *args)
+{
+       uint bank, gpio, flags, offset;
+       int ret;
+
+       if (args->args_count != 3)
+               return -EINVAL;
+
+       bank = args->args[0];
+       gpio = args->args[1];
+       flags = args->args[2];
+
+       switch (bank) {
+       case 0:
+               if (gpio >= 16)
+                       return -EINVAL;
+               offset = gpio;
+               break;
+       case 1:
+               if (gpio >= 32)
+                       return -EINVAL;
+               offset = 16 + gpio;
+               break;
+       case 2:
+               if (gpio >= 16)
+                       return -EINVAL;
+               offset = 16 + 32 + gpio;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       ret = turris_omnia_mcu_get_function(dev, offset);
+       if (ret < 0)
+               return ret;
+
+       desc->offset = offset;
+       desc->flags = gpio_flags_xlate(flags);
+
+       return 0;
+}
+
+static const struct dm_gpio_ops turris_omnia_mcu_ops = {
+       .direction_input        = turris_omnia_mcu_direction_input,
+       .direction_output       = turris_omnia_mcu_direction_output,
+       .get_value              = turris_omnia_mcu_get_value,
+       .set_value              = turris_omnia_mcu_set_value,
+       .get_function           = turris_omnia_mcu_get_function,
+       .xlate                  = turris_omnia_mcu_xlate,
+};
+
+static int turris_omnia_mcu_probe(struct udevice *dev)
+{
+       struct turris_omnia_mcu_info *info = dev_get_plat(dev);
+       struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+       u16 status;
+       u8 val[2];
+       int ret;
+
+       ret = dm_i2c_read(dev, CMD_GET_STATUS_WORD, val, 2);
+       if (ret) {
+               printf("Error: turris_omnia_mcu CMD_GET_STATUS_WORD failed: %d\n", ret);
+               return ret;
+       }
+
+       status = ((u16)val[1] << 8) | val[0];
+
+       if (status & STS_FEATURES_SUPPORTED) {
+               ret = dm_i2c_read(dev, CMD_GET_FEATURES, val, 2);
+               if (ret) {
+                       printf("Error: turris_omnia_mcu CMD_GET_FEATURES failed: %d\n", ret);
+                       return ret;
+               }
+               info->features = ((u16)val[1] << 8) | val[0];
+       }
+
+       uc_priv->bank_name = "mcu_";
+
+       if (info->features & FEAT_EXT_CMDS)
+               uc_priv->gpio_count = 16 + 32 + 16;
+       else
+               uc_priv->gpio_count = 16;
+
+       return 0;
+}
+
+static const struct udevice_id turris_omnia_mcu_ids[] = {
+       { .compatible = "cznic,turris-omnia-mcu" },
+       { }
+};
+
+U_BOOT_DRIVER(turris_omnia_mcu) = {
+       .name           = "turris-omnia-mcu",
+       .id             = UCLASS_GPIO,
+       .ops            = &turris_omnia_mcu_ops,
+       .probe          = turris_omnia_mcu_probe,
+       .plat_auto      = sizeof(struct turris_omnia_mcu_info),
+       .of_match       = turris_omnia_mcu_ids,
+};