diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-07-16 11:35:27 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-07-16 11:35:27 -0700 |
| commit | cc0f7c3f97bc6e888bf4be28a9da9dbd3735d2b4 (patch) | |
| tree | ec328d17e2b9ddff11579e8759b9922b4e7a7f48 /drivers/platform | |
| parent | 99298eb615debd41c1fccff05b163d0a29091904 (diff) | |
| parent | 49e24c80d3c81c43e2a56101449e1eea32fcf292 (diff) | |
| download | linux-cc0f7c3f97bc6e888bf4be28a9da9dbd3735d2b4.tar.gz linux-cc0f7c3f97bc6e888bf4be28a9da9dbd3735d2b4.tar.bz2 linux-cc0f7c3f97bc6e888bf4be28a9da9dbd3735d2b4.zip | |
Merge tag 'soc-drivers-6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc
Pull SoC driver updates from Arnd Bergmann:
"The updates to the mediatek, allwinner, ti, tegra, microchip, stm32,
samsung, imx, zynq and amlogic platoforms are fairly small maintenance
changes, either addressing minor mistakes or enabling additional
hardware.
The qualcomm platform changes add a number of features and are larger
than the other ones combined, introducing the use of linux/cleanup.h
across several drivers, adding support for Snapdragon X1E and other
SoCs in platform drivers, a new "protection domain mapper" driver, and
a "shared memory bridge" driver.
The cznic "turris omnia" router based on Marvell Armada gets a
platform driver that talks to the board specific microcontroller.
The reset and cache subsystems get a few minor updates to SoC specific
drivers, while the ff-a, scmi and optee firmware drivers get some code
refactoring and new features"
* tag 'soc-drivers-6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc: (122 commits)
firmware: turris-mox-rwtm: Initialize completion before mailbox
firmware: turris-mox-rwtm: Fix checking return value of wait_for_completion_timeout()
firmware: turris-mox-rwtm: Do not complete if there are no waiters
MAINTAINERS: drop riscv list from cache controllers
platform: cznic: turris-omnia-mcu: fix Kconfig dependencies
bus: sunxi-rsb: Constify struct regmap_bus
soc: sunxi: sram: Constify struct regmap_config
platform: cznic: turris-omnia-mcu: Depend on WATCHDOG
platform: cznic: turris-omnia-mcu: Depend on OF
soc: samsung: exynos-pmu: add support for PMU_ALIVE non atomic registers
arm64: stm32: enable scmi regulator for stm32
firmware: qcom: tzmem: blacklist more platforms for SHM Bridge
soc: qcom: wcnss: simplify with cleanup.h
soc: qcom: pdr: simplify with cleanup.h
soc: qcom: ocmem: simplify with cleanup.h
soc: qcom: mdt_loader: simplify with cleanup.h
soc: qcom: llcc: simplify with cleanup.h
firmware: qcom: tzmem: simplify returning pointer without cleanup
soc: qcom: socinfo: Add PM6350 PMIC
arm64: dts: renesas: rz-smarc: Replace fixed regulator for USB VBUS
...
Diffstat (limited to 'drivers/platform')
| -rw-r--r-- | drivers/platform/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/platform/Makefile | 1 | ||||
| -rw-r--r-- | drivers/platform/cznic/Kconfig | 50 | ||||
| -rw-r--r-- | drivers/platform/cznic/Makefile | 8 | ||||
| -rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-base.c | 408 | ||||
| -rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-gpio.c | 1095 | ||||
| -rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-sys-off-wakeup.c | 260 | ||||
| -rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-trng.c | 103 | ||||
| -rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-watchdog.c | 130 | ||||
| -rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu.h | 194 |
10 files changed, 2251 insertions, 0 deletions
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 81a298517df2..960fd6a82450 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -7,6 +7,8 @@ source "drivers/platform/goldfish/Kconfig" source "drivers/platform/chrome/Kconfig" +source "drivers/platform/cznic/Kconfig" + source "drivers/platform/mellanox/Kconfig" source "drivers/platform/olpc/Kconfig" diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index fbbe4f77aa5d..bf69cc8d7429 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -10,5 +10,6 @@ obj-$(CONFIG_MIPS) += mips/ obj-$(CONFIG_OLPC_EC) += olpc/ obj-$(CONFIG_GOLDFISH) += goldfish/ obj-$(CONFIG_CHROME_PLATFORMS) += chrome/ +obj-$(CONFIG_CZNIC_PLATFORMS) += cznic/ obj-$(CONFIG_SURFACE_PLATFORMS) += surface/ obj-$(CONFIG_ARM64) += arm64/ diff --git a/drivers/platform/cznic/Kconfig b/drivers/platform/cznic/Kconfig new file mode 100644 index 000000000000..cb0d4d686d8a --- /dev/null +++ b/drivers/platform/cznic/Kconfig @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# For a description of the syntax of this configuration file, +# see Documentation/kbuild/kconfig-language.rst. +# + +menuconfig CZNIC_PLATFORMS + bool "Platform support for CZ.NIC's Turris hardware" + help + Say Y here to be able to choose driver support for CZ.NIC's Turris + devices. This option alone does not add any kernel code. + +if CZNIC_PLATFORMS + +config TURRIS_OMNIA_MCU + tristate "Turris Omnia MCU driver" + depends on MACH_ARMADA_38X || COMPILE_TEST + depends on I2C + depends on OF + depends on WATCHDOG + depends on GPIOLIB + depends on HW_RANDOM + depends on RTC_CLASS + depends on WATCHDOG_CORE + select GPIOLIB_IRQCHIP + help + Say Y here to add support for the features implemented by the + microcontroller on the CZ.NIC's Turris Omnia SOHO router. + The features include: + - board poweroff into true low power mode (with voltage regulators + disabled) and the ability to configure wake up from this mode (via + rtcwake) + - true random number generator (if available on the MCU) + - MCU watchdog + - GPIO pins + - to get front button press events (the front button can be + configured either to generate press events to the CPU or to change + front LEDs panel brightness) + - to enable / disable USB port voltage regulators and to detect + USB overcurrent + - to detect MiniPCIe / mSATA card presence in MiniPCIe port 0 + - to configure resets of various peripherals on board revisions 32+ + - to enable / disable the VHV voltage regulator to the SOC in order + to be able to program SOC's OTP on board revisions 32+ + - to get input from the LED output pins of the WAN ethernet PHY, LAN + switch and MiniPCIe ports + To compile this driver as a module, choose M here; the module will be + called turris-omnia-mcu. + +endif # CZNIC_PLATFORMS diff --git a/drivers/platform/cznic/Makefile b/drivers/platform/cznic/Makefile new file mode 100644 index 000000000000..eae4c6b341ff --- /dev/null +++ b/drivers/platform/cznic/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_TURRIS_OMNIA_MCU) += turris-omnia-mcu.o +turris-omnia-mcu-y := turris-omnia-mcu-base.o +turris-omnia-mcu-y += turris-omnia-mcu-gpio.o +turris-omnia-mcu-y += turris-omnia-mcu-sys-off-wakeup.o +turris-omnia-mcu-y += turris-omnia-mcu-trng.o +turris-omnia-mcu-y += turris-omnia-mcu-watchdog.o diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c new file mode 100644 index 000000000000..c68a7a84a951 --- /dev/null +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CZ.NIC's Turris Omnia MCU driver + * + * 2024 by Marek BehĂșn <kabel@kernel.org> + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/hex.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/types.h> + +#include <linux/turris-omnia-mcu-interface.h> +#include "turris-omnia-mcu.h" + +#define OMNIA_FW_VERSION_LEN 20 +#define OMNIA_FW_VERSION_HEX_LEN (2 * OMNIA_FW_VERSION_LEN + 1) +#define OMNIA_BOARD_INFO_LEN 16 + +int omnia_cmd_write_read(const struct i2c_client *client, + void *cmd, unsigned int cmd_len, + void *reply, unsigned int reply_len) +{ + struct i2c_msg msgs[2]; + int ret, num; + + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = cmd_len; + msgs[0].buf = cmd; + num = 1; + + if (reply_len) { + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = reply_len; + msgs[1].buf = reply; + num++; + } + + ret = i2c_transfer(client->adapter, msgs, num); + if (ret < 0) + return ret; + if (ret != num) + return -EIO; + + return 0; +} + +static int omnia_get_version_hash(struct omnia_mcu *mcu, bool bootloader, + char version[static OMNIA_FW_VERSION_HEX_LEN]) +{ + u8 reply[OMNIA_FW_VERSION_LEN]; + char *p; + int err; + + err = omnia_cmd_read(mcu->client, + bootloader ? OMNIA_CMD_GET_FW_VERSION_BOOT + : OMNIA_CMD_GET_FW_VERSION_APP, + reply, sizeof(reply)); + if (err) + return err; + + p = bin2hex(version, reply, OMNIA_FW_VERSION_LEN); + *p = '\0'; + + return 0; +} + +static ssize_t fw_version_hash_show(struct device *dev, char *buf, + bool bootloader) +{ + struct omnia_mcu *mcu = dev_get_drvdata(dev); + char version[OMNIA_FW_VERSION_HEX_LEN]; + int err; + + err = omnia_get_version_hash(mcu, bootloader, version); + if (err) + return err; + + return sysfs_emit(buf, "%s\n", version); +} + +static ssize_t fw_version_hash_application_show(struct device *dev, + struct device_attribute *a, + char *buf) +{ + return fw_version_hash_show(dev, buf, false); +} +static DEVICE_ATTR_RO(fw_version_hash_application); + +static ssize_t fw_version_hash_bootloader_show(struct device *dev, + struct device_attribute *a, + char *buf) +{ + return fw_version_hash_show(dev, buf, true); +} +static DEVICE_ATTR_RO(fw_version_hash_bootloader); + +static ssize_t fw_features_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct omnia_mcu *mcu = dev_get_drvdata(dev); + + return sysfs_emit(buf, "0x%x\n", mcu->features); +} +static DEVICE_ATTR_RO(fw_features); + +static ssize_t mcu_type_show(struct device *dev, struct device_attribute *a, + char *buf) +{ + struct omnia_mcu *mcu = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", mcu->type); +} +static DEVICE_ATTR_RO(mcu_type); + +static ssize_t reset_selector_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + u8 reply; + int err; + + err = omnia_cmd_read_u8(to_i2c_client(dev), OMNIA_CMD_GET_RESET, + &reply); + if (err) + return err; + + return sysfs_emit(buf, "%d\n", reply); +} +static DEVICE_ATTR_RO(reset_selector); + +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct omnia_mcu *mcu = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%016llX\n", mcu->board_serial_number); +} +static DEVICE_ATTR_RO(serial_number); + +static ssize_t first_mac_address_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct omnia_mcu *mcu = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%pM\n", mcu->board_first_mac); +} +static DEVICE_ATTR_RO(first_mac_address); + +static ssize_t board_revision_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct omnia_mcu *mcu = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%u\n", mcu->board_revision); +} +static DEVICE_ATTR_RO(board_revision); + +static struct attribute *omnia_mcu_base_attrs[] = { + &dev_attr_fw_version_hash_application.attr, + &dev_attr_fw_version_hash_bootloader.attr, + &dev_attr_fw_features.attr, + &dev_attr_mcu_type.attr, + &dev_attr_reset_selector.attr, + &dev_attr_serial_number.attr, + &dev_attr_first_mac_address.attr, + &dev_attr_board_revision.attr, + NULL +}; + +static umode_t omnia_mcu_base_attrs_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct omnia_mcu *mcu = dev_get_drvdata(dev); + + if ((a == &dev_attr_serial_number.attr || + a == &dev_attr_first_mac_address.attr || + a == &dev_attr_board_revision.attr) && + !(mcu->features & OMNIA_FEAT_BOARD_INFO)) + return 0; + + return a->mode; +} + +static const struct attribute_group omnia_mcu_base_group = { + .attrs = omnia_mcu_base_attrs, + .is_visible = omnia_mcu_base_attrs_visible, +}; + +static const struct attribute_group *omnia_mcu_groups[] = { + &omnia_mcu_base_group, + &omnia_mcu_gpio_group, + &omnia_mcu_poweroff_group, + NULL +}; + +static void omnia_mcu_print_version_hash(struct omnia_mcu *mcu, bool bootloader) +{ + const char *type = bootloader ? "bootloader" : "application"; + struct device *dev = &mcu->client->dev; + char version[OMNIA_FW_VERSION_HEX_LEN]; + int err; + + err = omnia_get_version_hash(mcu, bootloader, version); + if (err) { + dev_err(dev, "Cannot read MCU %s firmware version: %d\n", + type, err); + return; + } + + dev_info(dev, "MCU %s firmware version hash: %s\n", type, version); +} + +static const char *omnia_status_to_mcu_type(u16 status) +{ + switch (status & OMNIA_STS_MCU_TYPE_MASK) { + case OMNIA_STS_MCU_TYPE_STM32: + return "STM32"; + case OMNIA_STS_MCU_TYPE_GD32: + return "GD32"; + case OMNIA_STS_MCU_TYPE_MKL: + return "MKL"; + default: + return "unknown"; + } +} + +static void omnia_info_missing_feature(struct device *dev, const char *feature) +{ + dev_info(dev, + "Your board's MCU firmware does not support the %s feature.\n", + feature); +} + +static int omnia_mcu_read_features(struct omnia_mcu *mcu) +{ + static const struct { + u16 mask; + const char *name; + } features[] = { +#define _DEF_FEAT(_n, _m) { OMNIA_FEAT_ ## _n, _m } + _DEF_FEAT(EXT_CMDS, "extended control and status"), + _DEF_FEAT(WDT_PING, "watchdog pinging"), + _DEF_FEAT(LED_STATE_EXT_MASK, "peripheral LED pins reading"), + _DEF_FEAT(NEW_INT_API, "new interrupt API"), + _DEF_FEAT(POWEROFF_WAKEUP, "poweroff and wakeup"), + _DEF_FEAT(TRNG, "true random number generator"), +#undef _DEF_FEAT + }; + struct i2c_client *client = mcu->client; + struct device *dev = &client->dev; + bool suggest_fw_upgrade = false; + u16 status; + int err; + + /* status word holds MCU type, which we need below */ + err = omnia_cmd_read_u16(client, OMNIA_CMD_GET_STATUS_WORD, &status); + if (err) + return err; + + /* + * Check whether MCU firmware supports the OMNIA_CMD_GET_FEATURES + * command. + */ + if (status & OMNIA_STS_FEATURES_SUPPORTED) { + /* try read 32-bit features */ + err = omnia_cmd_read_u32(client, OMNIA_CMD_GET_FEATURES, + &mcu->features); + if (err) { + /* try read 16-bit features */ + u16 features16; + + err = omnia_cmd_read_u16(client, OMNIA_CMD_GET_FEATURES, + &features16); + if (err) + return err; + + mcu->features = features16; + } else { + if (mcu->features & OMNIA_FEAT_FROM_BIT_16_INVALID) + mcu->features &= GENMASK(15, 0); + } + } else { + dev_info(dev, + "Your board's MCU firmware does not support feature reading.\n"); + suggest_fw_upgrade = true; + } + + mcu->type = omnia_status_to_mcu_type(status); + dev_info(dev, "MCU type %s%s\n", mcu->type, + (mcu->features & OMNIA_FEAT_PERIPH_MCU) ? + ", with peripheral resets wired" : ""); + + omnia_mcu_print_version_hash(mcu, true); + + if (mcu->features & OMNIA_FEAT_BOOTLOADER) + dev_warn(dev, + "MCU is running bootloader firmware. Was firmware upgrade interrupted?\n"); + else + omnia_mcu_print_version_hash(mcu, false); + + for (unsigned int i = 0; i < ARRAY_SIZE(features); i++) { + if (mcu->features & features[i].mask) + continue; + + omnia_info_missing_feature(dev, features[i].name); + suggest_fw_upgrade = true; + } + + if (suggest_fw_upgrade) + dev_info(dev, + "Consider upgrading MCU firmware with the omnia-mcutool utility.\n"); + + return 0; +} + +static int omnia_mcu_read_board_info(struct omnia_mcu *mcu) +{ + u8 reply[1 + OMNIA_BOARD_INFO_LEN]; + int err; + + err = omnia_cmd_read(mcu->client, OMNIA_CMD_BOARD_INFO_GET, reply, + sizeof(reply)); + if (err) + return err; + + if (reply[0] != OMNIA_BOARD_INFO_LEN) + return -EIO; + + mcu->board_serial_number = get_unaligned_le64(&reply[1]); + + /* we can't use ether_addr_copy() because reply is not u16-aligned */ + memcpy(mcu->board_first_mac, &reply[9], sizeof(mcu->board_first_mac)); + + mcu->board_revision = reply[15]; + + return 0; +} + +static int omnia_mcu_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct omnia_mcu *mcu; + int err; + + if (!client->irq) + return dev_err_probe(dev, -EINVAL, "IRQ resource not found\n"); + + mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL); + if (!mcu) + return -ENOMEM; + + mcu->client = client; + i2c_set_clientdata(client, mcu); + + err = omnia_mcu_read_features(mcu); + if (err) + return dev_err_probe(dev, err, + "Cannot determine MCU supported features\n"); + + if (mcu->features & OMNIA_FEAT_BOARD_INFO) { + err = omnia_mcu_read_board_info(mcu); + if (err) + return dev_err_probe(dev, err, + "Cannot read board info\n"); + } + + err = omnia_mcu_register_sys_off_and_wakeup(mcu); + if (err) + return err; + + err = omnia_mcu_register_watchdog(mcu); + if (err) + return err; + + err = omnia_mcu_register_gpiochip(mcu); + if (err) + return err; + + return omnia_mcu_register_trng(mcu); +} + +static const struct of_device_id of_omnia_mcu_match[] = { + { .compatible = "cznic,turris-omnia-mcu" }, + {} +}; + +static struct i2c_driver omnia_mcu_driver = { + .probe = omnia_mcu_probe, + .driver = { + .name = "turris-omnia-mcu", + .of_match_table = of_omnia_mcu_match, + .dev_groups = omnia_mcu_groups, + }, +}; +module_i2c_driver(omnia_mcu_driver); + +MODULE_AUTHOR("Marek Behun <kabel@kernel.org>"); +MODULE_DESCRIPTION("CZ.NIC's Turris Omnia MCU"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/cznic/turris-omnia-mcu-gpio.c b/drivers/platform/cznic/turris-omnia-mcu-gpio.c new file mode 100644 index 000000000000..91da56a704c7 --- /dev/null +++ b/drivers/platform/cznic/turris-omnia-mcu-gpio.c @@ -0,0 +1,1095 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CZ.NIC's Turris Omnia MCU GPIO and IRQ driver + * + * 2024 by Marek BehĂșn <kabel@kernel.org> + */ + +#include <linux/array_size.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/bug.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/devm-helpers.h> +#include <linux/errno.h> +#include <linux/gpio/driver.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <asm/unaligned.h> + +#include <linux/turris-omnia-mcu-interface.h> +#include "turris-omnia-mcu.h" + +#define OMNIA_CMD_INT_ARG_LEN 8 +#define FRONT_BUTTON_RELEASE_DELAY_MS 50 + +static const char * const omnia_mcu_gpio_templates[64] = { + /* GPIOs with value read from the 16-bit wide status */ + [4] = "MiniPCIe0 Card Detect", + [5] = "MiniPCIe0 mSATA Indicator", + [6] = "Front USB3 port over-current", + [7] = "Rear USB3 port over-current", + [8] = "Front USB3 port power", + [9] = "Rear USB3 port power", + [12] = "Front Button", + + /* GPIOs with value read from the 32-bit wide extended status */ + [16] = "SFP nDET", + [28] = "MiniPCIe0 LED", + [29] = "MiniPCIe1 LED", + [30] = "MiniPCIe2 LED", + [31] = "MiniPCIe0 PAN LED", + [32] = "MiniPCIe1 PAN LED", + [33] = "MiniPCIe2 PAN LED", + [34] = "WAN PHY LED0", + [35] = "WAN PHY LED1", + [36] = "LAN switch p0 LED0", + [37] = "LAN switch p0 LED1", + [38] = "LAN switch p1 LED0", + [39] = "LAN switch p1 LED1", + [40] = "LAN switch p2 LED0", + [41] = "LAN switch p2 LED1", + [42] = "LAN switch p3 LED0", + [43] = "LAN switch p3 LED1", + [44] = "LAN switch p4 LED0", + [45] = "LAN switch p4 LED1", + [46] = "LAN switch p5 LED0", + [47] = "LAN switch p5 LED1", + + /* GPIOs with value read from the 16-bit wide extended control status */ + [48] = "eMMC nRESET", + [49] = "LAN switch nRESET", + [50] = "WAN PHY nRESET", + [51] = "MiniPCIe0 nPERST", + [52] = "MiniPCIe1 nPERST", + [53] = "MiniPCIe2 nPERST", + [54] = "WAN PHY SFP mux", + [56] = "VHV power disable", +}; + +struct omnia_gpio { + u8 cmd; + u8 ctl_cmd; + u8 bit; + u8 ctl_bit; + u8 int_bit; + u16 feat; + u16 feat_mask; +}; + +#define OMNIA_GPIO_INVALID_INT_BIT 0xff + +#define _DEF_GPIO(_cmd, _ctl_cmd, _bit, _ctl_bit, _int_bit, _feat, _feat_mask) \ + { \ + .cmd = _cmd, \ + .ctl_cmd = _ctl_cmd, \ + .bit = _bit, \ + .ctl_bit = _ctl_bit, \ + .int_bit = (_int_bit) < 0 ? OMNIA_GPIO_INVALID_INT_BIT \ + : (_int_bit), \ + .feat = _feat, \ + .feat_mask = _feat_mask, \ + } + +#define _DEF_GPIO_STS(_name) \ + _DEF_GPIO(OMNIA_CMD_GET_STATUS_WORD, 0, __bf_shf(OMNIA_STS_ ## _name), \ + 0, __bf_shf(OMNIA_INT_ ## _name), 0, 0) + +#define _DEF_GPIO_CTL(_name) \ + _DEF_GPIO(OMNIA_CMD_GET_STATUS_WORD, OMNIA_CMD_GENERAL_CONTROL, \ + __bf_shf(OMNIA_STS_ ## _name), __bf_shf(OMNIA_CTL_ ## _name), \ + -1, 0, 0) + +#define _DEF_GPIO_EXT_STS(_name, _feat) \ + _DEF_GPIO(OMNIA_CMD_GET_EXT_STATUS_DWORD, 0, \ + __bf_shf(OMNIA_EXT_STS_ ## _name), 0, \ + __bf_shf(OMNIA_INT_ ## _name), \ + OMNIA_FEAT_ ## _feat | OMNIA_FEAT_EXT_CMDS, \ + OMNIA_FEAT_ ## _feat | OMNIA_FEAT_EXT_CMDS) + +#define _DEF_GPIO_EXT_STS_LED(_name, _ledext) \ + _DEF_GPIO(OMNIA_CMD_GET_EXT_STATUS_DWORD, 0, \ + __bf_shf(OMNIA_EXT_STS_ ## _name), 0, \ + __bf_shf(OMNIA_INT_ ## _name), \ + OMNIA_FEAT_LED_STATE_ ## _ledext, \ + OMNIA_FEAT_LED_STATE_EXT_MASK) + +#define _DEF_GPIO_EXT_STS_LEDALL(_name) \ + _DEF_GPIO(OMNIA_CMD_GET_EXT_STATUS_DWORD, 0, \ + __bf_shf(OMNIA_EXT_STS_ ## _name), 0, \ + __bf_shf(OMNIA_INT_ ## _name), \ + OMNIA_FEAT_LED_STATE_EXT_MASK, 0) + +#define _DEF_GPIO_EXT_CTL(_name, _feat) \ + _DEF_GPIO(OMNIA_CMD_GET_EXT_CONTROL_STATUS, OMNIA_CMD_EXT_CONTROL, \ + __bf_shf(OMNIA_EXT_CTL_ ## _name), \ + __bf_shf(OMNIA_EXT_CTL_ ## _name), -1, \ + OMNIA_FEAT_ ## _feat | OMNIA_FEAT_EXT_CMDS, \ + OMNIA_FEAT_ ## _feat | OMNIA_FEAT_EXT_CMDS) + +#define _DEF_INT(_name) \ + _DEF_GPIO(0, 0, 0, 0, __bf_shf(OMNIA_INT_ ## _name), 0, 0) + +static inline bool is_int_bit_valid(const struct omnia_gpio *gpio) +{ + return gpio->int_bit != OMNIA_GPIO_INVALID_INT_BIT; +} + +static const struct omnia_gpio omnia_gpios[64] = { + /* GPIOs with value read from the 16-bit wide status */ + [4] = _DEF_GPIO_STS(CARD_DET), + [5] = _DEF_GPIO_STS(MSATA_IND), + [6] = _DEF_GPIO_STS(USB30_OVC), + [7] = _DEF_GPIO_STS(USB31_OVC), + [8] = _DEF_GPIO_CTL(USB30_PWRON), + [9] = _DEF_GPIO_CTL(USB31_PWRON), + + /* brightness changed interrupt, no GPIO */ + [11] = _DEF_INT(BRIGHTNESS_CHANGED), + + [12] = _DEF_GPIO_STS(BUTTON_PRESSED), + + /* TRNG interrupt, no GPIO */ + [13] = _DEF_INT(TRNG), + + /* MESSAGE_SIGNED interrupt, no GPIO */ + [14] = _DEF_INT(MESSAGE_SIGNED), + + /* GPIOs with value read from the 32-bit wide extended status */ + [16] = _DEF_GPIO_EXT_STS(SFP_nDET, PERIPH_MCU), + [28] = _DEF_GPIO_EXT_STS_LEDALL(WLAN0_MSATA_LED), + [29] = _DEF_GPIO_EXT_STS_LEDALL(WLAN1_LED), + [30] = _DEF_GPIO_EXT_STS_LEDALL(WLAN2_LED), + [31] = _DEF_GPIO_EXT_STS_LED(WPAN0_LED, EXT), + [32] = _DEF_GPIO_EXT_STS_LED(WPAN1_LED, EXT), + [33] = _DEF_GPIO_EXT_STS_LED(WPAN2_LED, EXT), + [34] = _DEF_GPIO_EXT_STS_LEDALL(WAN_LED0), + [35] = _DEF_GPIO_EXT_STS_LED(WAN_LED1, EXT_V32), + [36] = _DEF_GPIO_EXT_STS_LEDALL(LAN0_LED0), + [37] = _DEF_GPIO_EXT_STS_LEDALL(LAN0_LED1), + [38] = _DEF_GPIO_EXT_STS_LEDALL(LAN1_LED0), + [39] = _DEF_GPIO_EXT_STS_LEDALL(LAN1_LED1), + [40] = _DEF_GPIO_EXT_STS_LEDALL(LAN2_LED0), + [41] = _DEF_GPIO_EXT_STS_LEDALL(LAN2_LED1), + [42] = _DEF_GPIO_EXT_STS_LEDALL(LAN3_LED0), + [43] = _DEF_GPIO_EXT_STS_LEDALL(LAN3_LED1), + [44] = _DEF_GPIO_EXT_STS_LEDALL(LAN4_LED0), + [45] = _DEF_GPIO_EXT_STS_LEDALL(LAN4_LED1), + [46] = _DEF_GPIO_EXT_STS_LEDALL(LAN5_LED0), + [47] = _DEF_GPIO_EXT_STS_LEDALL(LAN5_LED1), + + /* GPIOs with value read from the 16-bit wide extended control status */ + [48] = _DEF_GPIO_EXT_CTL(nRES_MMC, PERIPH_MCU), + [49] = _DEF_GPIO_EXT_CTL(nRES_LAN, PERIPH_MCU), + [50] = _DEF_GPIO_EXT_CTL(nRES_PHY, PERIPH_MCU), + [51] = _DEF_GPIO_EXT_CTL(nPERST0, PERIPH_MCU), + [52] = _DEF_GPIO_EXT_CTL(nPERST1, PERIPH_MCU), + [53] = _DEF_GPIO_EXT_CTL(nPERST2, PERIPH_MCU), + [54] = _DEF_GPIO_EXT_CTL(PHY_SFP, PERIPH_MCU), + [56] = _DEF_GPIO_EXT_CTL(nVHV_CTRL, PERIPH_MCU), +}; + +/* mapping from interrupts to indexes of GPIOs in the omnia_gpios array */ +const u8 omnia_int_to_gpio_idx[32] = { + [__bf_shf(OMNIA_INT_CARD_DET)] = 4, + [__bf_shf(OMNIA_INT_MSATA_IND)] = 5, + [__bf_shf(OMNIA_INT_USB30_OVC)] = 6, + [__bf_shf(OMNIA_INT_USB31_OVC)] = 7, + [__bf_shf(OMNIA_INT_BUTTON_PRESSED)] = 12, + [__bf_shf(OMNIA_INT_TRNG)] = 13, + [__bf_shf(OMNIA_INT_MESSAGE_SIGNED)] = 14, + [__bf_shf(OMNIA_INT_SFP_nDET)] = 16, + [__bf_shf(OMNIA_INT_BRIGHTNESS_CHANGED)] = 11, + [__bf_shf(OMNIA_INT_WLAN0_MSATA_LED)] = 28, + [__bf_shf(OMNIA_INT_WLAN1_LED)] = 29, + [__bf_shf(OMNIA_INT_WLAN2_LED)] = 30, + [__bf_shf(OMNIA_INT_WPAN0_LED)] = 31, + [__bf_shf(OMNIA_INT_WPAN1_LED)] = 32, + [__bf_shf(OMNIA_INT_WPAN2_LED)] = 33, + [__bf_shf(OMNIA_INT_WAN_LED0)] = 34, + [__bf_shf(OMNIA_INT_WAN_LED1)] = 35, + [__bf_shf(OMNIA_INT_LAN0_LED0)] = 36, + [__bf_shf(OMNIA_INT_LAN0_LED1)] = 37, + [__bf_shf(OMNIA_INT_LAN1_LED0)] = 38, + [__bf_shf(OMNIA_INT_LAN1_LED1)] = 39, + [__bf_shf(OMNIA_INT_LAN2_LED0)] = 40, + [__bf_shf(OMNIA_INT_LAN2_LED1)] = 41, + [__bf_shf(OMNIA_INT_LAN3_LED0)] = 42, + [__bf_shf(OMNIA_INT_LAN3_LED1)] = 43, + [__bf_shf(OMNIA_INT_LAN4_LED0)] = 44, + [__bf_shf(OMNIA_INT_LAN4_LED1)] = 45, + [__bf_shf(OMNIA_INT_LAN5_LED0)] = 46, + [__bf_shf(OMNIA_INT_LAN5_LED1)] = 47, +}; + +/* index of PHY_SFP GPIO in the omnia_gpios array */ +#define OMNIA_GPIO_PHY_SFP_OFFSET 54 + +static int omnia_ctl_cmd_locked(struct omnia_mcu *mcu, u8 cmd, u16 val, u16 mask) +{ + unsigned int len; + u8 buf[5]; + + buf[0] = cmd; + + switch (cmd) { + case OMNIA_CMD_GENERAL_CONTROL: + buf[1] = val; + buf[2] = mask; + len = 3; + break; + + case OMNIA_CMD_EXT_CONTROL: + put_unaligned_le16(val, &buf[1]); + put_unaligned_le16(mask, &buf[3]); + len = 5; + break; + + default: + BUG(); + } + + return omnia_cmd_write(mcu->client, buf, len); +} + +static int omnia_ctl_cmd(struct omnia_mcu *mcu, u8 cmd, u16 val, u16 mask) +{ + guard(mutex)(&mcu->lock); + + return omnia_ctl_cmd_locked(mcu, cmd, val, mask); +} + +static int omnia_gpio_request(struct gpio_chip *gc, unsigned int offset) +{ + if (!omnia_gpios[offset].cmd) + return -EINVAL; + + return 0; +} + +static int omnia_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct omnia_mcu *mcu = gpiochip_get_data(gc); + + if (offset == OMNIA_GPIO_PHY_SFP_OFFSET) { + int val; + + scoped_guard(mutex, &mcu->lock) { + val = omnia_cmd_read_bit(mcu->client, + OMNIA_CMD_GET_EXT_CONTROL_STATUS, + OMNIA_EXT_CTL_PHY_SFP_AUTO); + if (val < 0) + return val; + } + + if (val) + return GPIO_LINE_DIRECTION_IN; + + return GPIO_LINE_DIRECTION_OUT; + } + + if (omnia_gpios[offset].ctl_cmd) + return GPIO_LINE_DIRECTION_OUT; + + return GPIO_LINE_DIRECTION_IN; +} + +static int omnia_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + const struct omnia_gpio *gpio = &omnia_gpios[offset]; + struct omnia_mcu *mcu = gpiochip_get_data(gc); + + if (offset == OMNIA_GPIO_PHY_SFP_OFFSET) + return omnia_ctl_cmd(mcu, OMNIA_CMD_EXT_CONTROL, + OMNIA_EXT_CTL_PHY_SFP_AUTO, + OMNIA_EXT_CTL_PHY_SFP_AUTO); + + if (gpio->ctl_cmd) + return -ENOTSUPP; + + return 0; +} + +static int omnia_gpio_direction_output(struct gpio_chip *gc, + unsigned int offset, int value) +{ + const struct omnia_gpio *gpio = &omnia_gpios[offset]; + struct omnia_mcu *mcu = gpiochip_get_data(gc); + u16 val, mask; + + if (!gpio->ctl_cmd) + return -ENOTSUPP; + + mask = BIT(gpio->ctl_bit); + val = value ? mask : 0; + + if (offset == OMNIA_GPIO_PHY_SFP_OFFSET) + mask |= OMNIA_EXT_CTL_PHY_SFP_AUTO; + + return omnia_ctl_cmd(mcu, gpio->ctl_cmd, val, mask); +} + +static int omnia_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + const struct omnia_gpio *gpio = &omnia_gpios[offset]; + struct omnia_mcu *mcu = gpiochip_get_data(gc); + + /* + * If firmware does not support the new interrupt API, we are informed + * of every change of the status word by an interrupt from MCU and save + * its value in the interrupt service routine. Simply return the saved + * value. + */ + if (gpio->cmd == OMNIA_CMD_GET_STATUS_WORD && + !(mcu->features & OMNIA_FEAT_NEW_INT_API)) + return test_bit(gpio->bit, &mcu->last_status); + + guard(mutex)(&mcu->lock); + + /* + * If firmware does support the new interrupt API, we may have cached + * the value of a GPIO in the interrupt service routine. If not, read + * the relevant bit now. + */ + if (is_int_bit_valid(gpio) && test_bit(gpio->int_bit, &mcu->is_cached)) + return test_bit(gpio->int_bit, &mcu->cached); + + return omnia_cmd_read_bit(mcu->client, gpio->cmd, BIT(gpio->bit)); +} + +static unsigned long * +_relevant_field_for_sts_cmd(u8 cmd, unsigned long *sts, unsigned long *ext_sts, + unsigned long *ext_ctl) +{ + switch (cmd) { + case OMNIA_CMD_GET_STATUS_WORD: + return sts; + case OMNIA_CMD_GET_EXT_STATUS_DWORD: + return ext_sts; + case OMNIA_CMD_GET_EXT_CONTROL_STATUS: + return ext_ctl; + default: + return NULL; + } +} + +static int omnia_gpio_get_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + unsigned long sts = 0, ext_sts = 0, ext_ctl = 0, *field; + struct omnia_mcu *mcu = gpiochip_get_data(gc); + struct i2c_client *client = mcu->client; + unsigned int i; + int err; + + /* determine which bits to read from the 3 possible commands */ + for_each_set_bit(i, mask, ARRAY_SIZE(omnia_gpios)) { + field = _relevant_field_for_sts_cmd(omnia_gpios[i].cmd, + &sts, &ext_sts, &ext_ctl); + if (!field) + continue; + + __set_bit(omnia_gpios[i].bit, field); + } + + guard(mutex)(&mcu->lock); + + if (mcu->features & OMNIA_FEAT_NEW_INT_API) { + /* read relevant bits from status */ + err = omnia_cmd_read_bits(client, OMNIA_CMD_GET_STATUS_WORD, + sts, &sts); + if (err) + return err; + } else { + /* + * Use status word value cached in the interrupt service routine + * if firmware does not support the new interrupt API. + */ + sts = mcu->last_status; + } + + /* read relevant bits from extended status */ + err = omnia_cmd_read_bits(client, OMNIA_CMD_GET_EXT_STATUS_DWORD, + ext_sts, &ext_sts); + if (err) + return err; + + /* read relevant bits from extended control */ + err = omnia_cmd_read_bits(client, OMNIA_CMD_GET_EXT_CONTROL_STATUS, + ext_ctl, &ext_ctl); + if (err) + return err; + + /* assign relevant bits in result */ + for_each_set_bit(i, mask, ARRAY_SIZE(omnia_gpios)) { + field = _relevant_field_for_sts_cmd(omnia_gpios[i].cmd, + &sts, &ext_sts, &ext_ctl); + if (!field) + continue; + + __assign_bit(i, bits, test_bit(omnia_gpios[i].bit, field)); + } + + return 0; +} + +static void omnia_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + const struct omnia_gpio *gpio = &omnia_gpios[offset]; + struct omnia_mcu *mcu = gpiochip_get_data(gc); + u16 val, mask; + + if (!gpio->ctl_cmd) + return; + + mask = BIT(gpio->ctl_bit); + val = value ? mask : 0; + + omnia_ctl_cmd(mcu, gpio->ctl_cmd, val, mask); +} + +static void omnia_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + unsigned long ctl = 0, ctl_mask = 0, ext_ctl = 0, ext_ctl_mask = 0; + struct omnia_mcu *mcu = gpiochip_get_data(gc); + unsigned int i; + + for_each_set_bit(i, mask, ARRAY_SIZ |
