diff options
author | Pali Rohár | 2022-07-28 13:06:24 +0200 |
---|---|---|
committer | Stefan Roese | 2022-07-29 10:02:43 +0200 |
commit | 5e4d24ccc115215be9aa4cd20cda585e2792e7ff (patch) | |
tree | 138366faa838db76f1eaf0bf3e85ae3cac2fbbd3 /drivers/gpio | |
parent | ef6fcab85f2f924f44f9a504302343bf248ab2fe (diff) |
gpio: Add Turris Omnia MCU driver
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>
Diffstat (limited to 'drivers/gpio')
-rw-r--r-- | drivers/gpio/Kconfig | 7 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/turris_omnia_mcu.c | 309 |
3 files changed, 317 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index aaa152fae73..82a8bca2700 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -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 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index d7552762d06..219f37e0e43 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -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 index 00000000000..3e5d74e62cd --- /dev/null +++ b/drivers/gpio/turris_omnia_mcu.c @@ -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, +}; |