platform: cznic: Add preliminary support for Turris Omnia MCU
authorMarek Behún <kabel@kernel.org>
Mon, 1 Jul 2024 11:30:04 +0000 (13:30 +0200)
committerArnd Bergmann <arnd@arndb.de>
Mon, 1 Jul 2024 13:46:36 +0000 (15:46 +0200)
Add the basic skeleton for a new platform driver for the microcontroller
found on the Turris Omnia board.

Signed-off-by: Marek Behún <kabel@kernel.org>
Reviewed-by: Andy Shevchenko <andy@kernel.org>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Link: https://lore.kernel.org/r/20240701113010.16447-3-kabel@kernel.org
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu [new file with mode: 0644]
MAINTAINERS
drivers/platform/Kconfig
drivers/platform/Makefile
drivers/platform/cznic/Kconfig [new file with mode: 0644]
drivers/platform/cznic/Makefile [new file with mode: 0644]
drivers/platform/cznic/turris-omnia-mcu-base.c [new file with mode: 0644]
drivers/platform/cznic/turris-omnia-mcu.h [new file with mode: 0644]
include/linux/turris-omnia-mcu-interface.h [new file with mode: 0644]

diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu b/Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu
new file mode 100644 (file)
index 0000000..9bc5aad
--- /dev/null
@@ -0,0 +1,81 @@
+What:          /sys/bus/i2c/devices/<mcu_device>/board_revision
+Date:          September 2024
+KernelVersion: 6.11
+Contact:       Marek Behún <kabel@kernel.org>
+Description:   (RO) Contains board revision number.
+
+               Only available if board information is burned in the MCU (older
+               revisions have board information burned in the ATSHA204-A chip).
+
+               Format: %u.
+
+What:          /sys/bus/i2c/devices/<mcu_device>/first_mac_address
+Date:          September 2024
+KernelVersion: 6.11
+Contact:       Marek Behún <kabel@kernel.org>
+Description:   (RO) Contains device first MAC address. Each Turris Omnia is
+               allocated 3 MAC addresses. The two additional addresses are
+               computed from the first one by incrementing it.
+
+               Only available if board information is burned in the MCU (older
+               revisions have board information burned in the ATSHA204-A chip).
+
+               Format: %pM.
+
+What:          /sys/bus/i2c/devices/<mcu_device>/fw_features
+Date:          September 2024
+KernelVersion: 6.11
+Contact:       Marek Behún <kabel@kernel.org>
+Description:   (RO) Newer versions of the microcontroller firmware report the
+               features they support. These can be read from this file. If the
+               MCU firmware is too old, this file reads 0x0.
+
+               Format: 0x%x.
+
+What:          /sys/bus/i2c/devices/<mcu_device>/fw_version_hash_application
+Date:          September 2024
+KernelVersion: 6.11
+Contact:       Marek Behún <kabel@kernel.org>
+Description:   (RO) Contains the version hash (commit hash) of the application
+               part of the microcontroller firmware.
+
+               Format: %s.
+
+What:          /sys/bus/i2c/devices/<mcu_device>/fw_version_hash_bootloader
+Date:          September 2024
+KernelVersion: 6.11
+Contact:       Marek Behún <kabel@kernel.org>
+Description:   (RO) Contains the version hash (commit hash) of the bootloader
+               part of the microcontroller firmware.
+
+               Format: %s.
+
+What:          /sys/bus/i2c/devices/<mcu_device>/mcu_type
+Date:          September 2024
+KernelVersion: 6.11
+Contact:       Marek Behún <kabel@kernel.org>
+Description:   (RO) Contains the microcontroller type (STM32, GD32, MKL).
+
+               Format: %s.
+
+What:          /sys/bus/i2c/devices/<mcu_device>/reset_selector
+Date:          September 2024
+KernelVersion: 6.11
+Contact:       Marek Behún <kabel@kernel.org>
+Description:   (RO) Contains the selected factory reset level, determined by
+               how long the rear reset button was held by the user during board
+               reset.
+
+               Format: %i.
+
+What:          /sys/bus/i2c/devices/<mcu_device>/serial_number
+Date:          September 2024
+KernelVersion: 6.11
+Contact:       Marek Behún <kabel@kernel.org>
+Description:   (RO) Contains the 64-bit board serial number in hexadecimal
+               format.
+
+               Only available if board information is burned in the MCU (older
+               revisions have board information burned in the ATSHA204-A chip).
+
+               Format: %016X.
index e13f3f8ad4aedb630527095485eaf6e916847cf3..143824bd4be50f1b308584a9f0e4e9568a37e2f8 100644 (file)
@@ -2206,6 +2206,7 @@ M:        Marek Behún <kabel@kernel.org>
 S:     Maintained
 W:     https://www.turris.cz/
 F:     Documentation/ABI/testing/debugfs-moxtet
+F:     Documentation/ABI/testing/sysfs-bus-i2c-devices-turris-omnia-mcu
 F:     Documentation/ABI/testing/sysfs-bus-moxtet-devices
 F:     Documentation/ABI/testing/sysfs-firmware-turris-mox-rwtm
 F:     Documentation/devicetree/bindings/bus/moxtet.txt
@@ -2219,10 +2220,12 @@ F:      drivers/firmware/turris-mox-rwtm.c
 F:     drivers/gpio/gpio-moxtet.c
 F:     drivers/leds/leds-turris-omnia.c
 F:     drivers/mailbox/armada-37xx-rwtm-mailbox.c
+F:     drivers/platform/cznic/
 F:     drivers/watchdog/armada_37xx_wdt.c
 F:     include/dt-bindings/bus/moxtet.h
 F:     include/linux/armada-37xx-rwtm-mailbox.h
 F:     include/linux/moxtet.h
+F:     include/linux/turris-omnia-mcu-interface.h
 
 ARM/FARADAY FA526 PORT
 M:     Hans Ulli Kroll <ulli.kroll@googlemail.com>
index 81a298517df2db2041c7dae30604322b0258f098..960fd6a82450a4c7b97d51ea3bc29795ee74efd1 100644 (file)
@@ -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"
index fbbe4f77aa5d7df866b40862c1a7035373cfc094..bf69cc8d74290c97fac296e95964e24f486d41ed 100644 (file)
@@ -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 (file)
index 0000000..db5f4a6
--- /dev/null
@@ -0,0 +1,25 @@
+# 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
+       help
+         Say Y here to add support for the features implemented by the
+         microcontroller on the CZ.NIC's Turris Omnia SOHO router.
+         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 (file)
index 0000000..31adca7
--- /dev/null
@@ -0,0 +1,4 @@
+# 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
diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c
new file mode 100644 (file)
index 0000000..47513ba
--- /dev/null
@@ -0,0 +1,394 @@
+// 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,
+       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");
+       }
+
+       return 0;
+}
+
+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.h b/drivers/platform/cznic/turris-omnia-mcu.h
new file mode 100644 (file)
index 0000000..3d0daa6
--- /dev/null
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * CZ.NIC's Turris Omnia MCU driver
+ *
+ * 2024 by Marek Behún <kabel@kernel.org>
+ */
+
+#ifndef __TURRIS_OMNIA_MCU_H
+#define __TURRIS_OMNIA_MCU_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+#include <asm/byteorder.h>
+
+struct i2c_client;
+
+struct omnia_mcu {
+       struct i2c_client *client;
+       const char *type;
+       u32 features;
+
+       /* board information */
+       u64 board_serial_number;
+       u8 board_first_mac[ETH_ALEN];
+       u8 board_revision;
+};
+
+int omnia_cmd_write_read(const struct i2c_client *client,
+                        void *cmd, unsigned int cmd_len,
+                        void *reply, unsigned int reply_len);
+
+static inline int omnia_cmd_read(const struct i2c_client *client, u8 cmd,
+                                void *reply, unsigned int len)
+{
+       return omnia_cmd_write_read(client, &cmd, 1, reply, len);
+}
+
+static inline int omnia_cmd_read_u32(const struct i2c_client *client, u8 cmd,
+                                    u32 *dst)
+{
+       __le32 reply;
+       int err;
+
+       err = omnia_cmd_read(client, cmd, &reply, sizeof(reply));
+       if (err)
+               return err;
+
+       *dst = le32_to_cpu(reply);
+
+       return 0;
+}
+
+static inline int omnia_cmd_read_u16(const struct i2c_client *client, u8 cmd,
+                                    u16 *dst)
+{
+       __le16 reply;
+       int err;
+
+       err = omnia_cmd_read(client, cmd, &reply, sizeof(reply));
+       if (err)
+               return err;
+
+       *dst = le16_to_cpu(reply);
+
+       return 0;
+}
+
+static inline int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd,
+                                   u8 *reply)
+{
+       return omnia_cmd_read(client, cmd, reply, sizeof(*reply));
+}
+
+#endif /* __TURRIS_OMNIA_MCU_H */
diff --git a/include/linux/turris-omnia-mcu-interface.h b/include/linux/turris-omnia-mcu-interface.h
new file mode 100644 (file)
index 0000000..2da8cbe
--- /dev/null
@@ -0,0 +1,249 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * CZ.NIC's Turris Omnia MCU I2C interface commands definitions
+ *
+ * 2024 by Marek Behún <kabel@kernel.org>
+ */
+
+#ifndef __TURRIS_OMNIA_MCU_INTERFACE_H
+#define __TURRIS_OMNIA_MCU_INTERFACE_H
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+
+enum omnia_commands_e {
+       OMNIA_CMD_GET_STATUS_WORD               = 0x01, /* slave sends status word back */
+       OMNIA_CMD_GENERAL_CONTROL               = 0x02,
+       OMNIA_CMD_LED_MODE                      = 0x03, /* default/user */
+       OMNIA_CMD_LED_STATE                     = 0x04, /* LED on/off */
+       OMNIA_CMD_LED_COLOR                     = 0x05, /* LED number + RED + GREEN + BLUE */
+       OMNIA_CMD_USER_VOLTAGE                  = 0x06,
+       OMNIA_CMD_SET_BRIGHTNESS                = 0x07,
+       OMNIA_CMD_GET_BRIGHTNESS                = 0x08,
+       OMNIA_CMD_GET_RESET                     = 0x09,
+       OMNIA_CMD_GET_FW_VERSION_APP            = 0x0A, /* 20B git hash number */
+       OMNIA_CMD_SET_WATCHDOG_STATE            = 0x0B, /* 0 - disable
+                                                        * 1 - enable / ping
+                                                        * after boot watchdog is started
+                                                        * with 2 minutes timeout
+                                                        */
+
+       /* OMNIA_CMD_WATCHDOG_STATUS            = 0x0C, not implemented anymore */
+
+       OMNIA_CMD_GET_WATCHDOG_STATE            = 0x0D,
+       OMNIA_CMD_GET_FW_VERSION_BOOT           = 0x0E, /* 20B Git hash number */
+       OMNIA_CMD_GET_FW_CHECKSUM               = 0x0F, /* 4B length, 4B checksum */
+
+       /* available if FEATURES_SUPPORTED bit set in status word */
+       OMNIA_CMD_GET_FEATURES                  = 0x10,
+
+       /* available if EXT_CMD bit set in features */
+       OMNIA_CMD_GET_EXT_STATUS_DWORD          = 0x11,
+       OMNIA_CMD_EXT_CONTROL                   = 0x12,
+       OMNIA_CMD_GET_EXT_CONTROL_STATUS        = 0x13,
+
+       /* available if NEW_INT_API bit set in features */
+       OMNIA_CMD_GET_INT_AND_CLEAR             = 0x14,
+       OMNIA_CMD_GET_INT_MASK                  = 0x15,
+       OMNIA_CMD_SET_INT_MASK                  = 0x16,
+
+       /* available if FLASHING bit set in features */
+       OMNIA_CMD_FLASH                         = 0x19,
+
+       /* available if WDT_PING bit set in features */
+       OMNIA_CMD_SET_WDT_TIMEOUT               = 0x20,
+       OMNIA_CMD_GET_WDT_TIMELEFT              = 0x21,
+
+       /* available if POWEROFF_WAKEUP bit set in features */
+       OMNIA_CMD_SET_WAKEUP                    = 0x22,
+       OMNIA_CMD_GET_UPTIME_AND_WAKEUP         = 0x23,
+       OMNIA_CMD_POWER_OFF                     = 0x24,
+
+       /* available if USB_OVC_PROT_SETTING bit set in features */
+       OMNIA_CMD_SET_USB_OVC_PROT              = 0x25,
+       OMNIA_CMD_GET_USB_OVC_PROT              = 0x26,
+
+       /* available if TRNG bit set in features */
+       OMNIA_CMD_TRNG_COLLECT_ENTROPY          = 0x28,
+
+       /* available if CRYPTO bit set in features */
+       OMNIA_CMD_CRYPTO_GET_PUBLIC_KEY         = 0x29,
+       OMNIA_CMD_CRYPTO_SIGN_MESSAGE           = 0x2A,
+       OMNIA_CMD_CRYPTO_COLLECT_SIGNATURE      = 0x2B,
+
+       /* available if BOARD_INFO it set in features */
+       OMNIA_CMD_BOARD_INFO_GET                = 0x2C,
+       OMNIA_CMD_BOARD_INFO_BURN               = 0x2D,
+
+       /* available only at address 0x2b (LED-controller) */
+       /* available only if LED_GAMMA_CORRECTION bit set in features */
+       OMNIA_CMD_SET_GAMMA_CORRECTION          = 0x30,
+       OMNIA_CMD_GET_GAMMA_CORRECTION          = 0x31,
+
+       /* available only at address 0x2b (LED-controller) */
+       /* available only if PER_LED_CORRECTION bit set in features */
+       /* available only if FROM_BIT_16_INVALID bit NOT set in features */
+       OMNIA_CMD_SET_LED_CORRECTIONS           = 0x32,
+       OMNIA_CMD_GET_LED_CORRECTIONS           = 0x33,
+};
+
+enum omnia_flashing_commands_e {
+       OMNIA_FLASH_CMD_UNLOCK          = 0x01,
+       OMNIA_FLASH_CMD_SIZE_AND_CSUM   = 0x02,
+       OMNIA_FLASH_CMD_PROGRAM         = 0x03,
+       OMNIA_FLASH_CMD_RESET           = 0x04,
+};
+
+enum omnia_sts_word_e {
+       OMNIA_STS_MCU_TYPE_MASK                 = GENMASK(1, 0),
+       OMNIA_STS_MCU_TYPE_STM32                = FIELD_PREP_CONST(OMNIA_STS_MCU_TYPE_MASK, 0),
+       OMNIA_STS_MCU_TYPE_GD32                 = FIELD_PREP_CONST(OMNIA_STS_MCU_TYPE_MASK, 1),
+       OMNIA_STS_MCU_TYPE_MKL                  = FIELD_PREP_CONST(OMNIA_STS_MCU_TYPE_MASK, 2),
+       OMNIA_STS_FEATURES_SUPPORTED            = BIT(2),
+       OMNIA_STS_USER_REGULATOR_NOT_SUPPORTED  = BIT(3),
+       OMNIA_STS_CARD_DET                      = BIT(4),
+       OMNIA_STS_MSATA_IND                     = BIT(5),
+       OMNIA_STS_USB30_OVC                     = BIT(6),
+       OMNIA_STS_USB31_OVC                     = BIT(7),
+       OMNIA_STS_USB30_PWRON                   = BIT(8),
+       OMNIA_STS_USB31_PWRON                   = BIT(9),
+       OMNIA_STS_ENABLE_4V5                    = BIT(10),
+       OMNIA_STS_BUTTON_MODE                   = BIT(11),
+       OMNIA_STS_BUTTON_PRESSED                = BIT(12),
+       OMNIA_STS_BUTTON_COUNTER_MASK           = GENMASK(15, 13),
+};
+
+enum omnia_ctl_byte_e {
+       OMNIA_CTL_LIGHT_RST     = BIT(0),
+       OMNIA_CTL_HARD_RST      = BIT(1),
+       /* BIT(2) is currently reserved */
+       OMNIA_CTL_USB30_PWRON   = BIT(3),
+       OMNIA_CTL_USB31_PWRON   = BIT(4),
+       OMNIA_CTL_ENABLE_4V5    = BIT(5),
+       OMNIA_CTL_BUTTON_MODE   = BIT(6),
+       OMNIA_CTL_BOOTLOADER    = BIT(7),
+};
+
+enum omnia_features_e {
+       OMNIA_FEAT_PERIPH_MCU           = BIT(0),
+       OMNIA_FEAT_EXT_CMDS             = BIT(1),
+       OMNIA_FEAT_WDT_PING             = BIT(2),
+       OMNIA_FEAT_LED_STATE_EXT_MASK   = GENMASK(4, 3),
+       OMNIA_FEAT_LED_STATE_EXT        = FIELD_PREP_CONST(OMNIA_FEAT_LED_STATE_EXT_MASK, 1),
+       OMNIA_FEAT_LED_STATE_EXT_V32    = FIELD_PREP_CONST(OMNIA_FEAT_LED_STATE_EXT_MASK, 2),
+       OMNIA_FEAT_LED_GAMMA_CORRECTION = BIT(5),
+       OMNIA_FEAT_NEW_INT_API          = BIT(6),
+       OMNIA_FEAT_BOOTLOADER           = BIT(7),
+       OMNIA_FEAT_FLASHING             = BIT(8),
+       OMNIA_FEAT_NEW_MESSAGE_API      = BIT(9),
+       OMNIA_FEAT_BRIGHTNESS_INT       = BIT(10),
+       OMNIA_FEAT_POWEROFF_WAKEUP      = BIT(11),
+       OMNIA_FEAT_CAN_OLD_MESSAGE_API  = BIT(12),
+       OMNIA_FEAT_TRNG                 = BIT(13),
+       OMNIA_FEAT_CRYPTO               = BIT(14),
+       OMNIA_FEAT_BOARD_INFO           = BIT(15),
+
+       /*
+        * Orginally the features command replied only 16 bits. If more were
+        * read, either the I2C transaction failed or 0xff bytes were sent.
+        * Therefore to consider bits 16 - 31 valid, one bit (20) was reserved
+        * to be zero.
+        */
+
+       /* Bits 16 - 19 correspond to bits 0 - 3 of status word */
+       OMNIA_FEAT_MCU_TYPE_MASK                = GENMASK(17, 16),
+       OMNIA_FEAT_MCU_TYPE_STM32               = FIELD_PREP_CONST(OMNIA_FEAT_MCU_TYPE_MASK, 0),
+       OMNIA_FEAT_MCU_TYPE_GD32                = FIELD_PREP_CONST(OMNIA_FEAT_MCU_TYPE_MASK, 1),
+       OMNIA_FEAT_MCU_TYPE_MKL                 = FIELD_PREP_CONST(OMNIA_FEAT_MCU_TYPE_MASK, 2),
+       OMNIA_FEAT_FEATURES_SUPPORTED           = BIT(18),
+       OMNIA_FEAT_USER_REGULATOR_NOT_SUPPORTED = BIT(19),
+
+       /* must not be set */
+       OMNIA_FEAT_FROM_BIT_16_INVALID  = BIT(20),
+
+       OMNIA_FEAT_PER_LED_CORRECTION   = BIT(21),
+       OMNIA_FEAT_USB_OVC_PROT_SETTING = BIT(22),
+};
+
+enum omnia_ext_sts_dword_e {
+       OMNIA_EXT_STS_SFP_nDET          = BIT(0),
+       OMNIA_EXT_STS_LED_STATES_MASK   = GENMASK(31, 12),
+       OMNIA_EXT_STS_WLAN0_MSATA_LED   = BIT(12),
+       OMNIA_EXT_STS_WLAN1_LED         = BIT(13),
+       OMNIA_EXT_STS_WLAN2_LED         = BIT(14),
+       OMNIA_EXT_STS_WPAN0_LED         = BIT(15),
+       OMNIA_EXT_STS_WPAN1_LED         = BIT(16),
+       OMNIA_EXT_STS_WPAN2_LED         = BIT(17),
+       OMNIA_EXT_STS_WAN_LED0          = BIT(18),
+       OMNIA_EXT_STS_WAN_LED1          = BIT(19),
+       OMNIA_EXT_STS_LAN0_LED0         = BIT(20),
+       OMNIA_EXT_STS_LAN0_LED1         = BIT(21),
+       OMNIA_EXT_STS_LAN1_LED0         = BIT(22),
+       OMNIA_EXT_STS_LAN1_LED1         = BIT(23),
+       OMNIA_EXT_STS_LAN2_LED0         = BIT(24),
+       OMNIA_EXT_STS_LAN2_LED1         = BIT(25),
+       OMNIA_EXT_STS_LAN3_LED0         = BIT(26),
+       OMNIA_EXT_STS_LAN3_LED1         = BIT(27),
+       OMNIA_EXT_STS_LAN4_LED0         = BIT(28),
+       OMNIA_EXT_STS_LAN4_LED1         = BIT(29),
+       OMNIA_EXT_STS_LAN5_LED0         = BIT(30),
+       OMNIA_EXT_STS_LAN5_LED1         = BIT(31),
+};
+
+enum omnia_ext_ctl_e {
+       OMNIA_EXT_CTL_nRES_MMC          = BIT(0),
+       OMNIA_EXT_CTL_nRES_LAN          = BIT(1),
+       OMNIA_EXT_CTL_nRES_PHY          = BIT(2),
+       OMNIA_EXT_CTL_nPERST0           = BIT(3),
+       OMNIA_EXT_CTL_nPERST1           = BIT(4),
+       OMNIA_EXT_CTL_nPERST2           = BIT(5),
+       OMNIA_EXT_CTL_PHY_SFP           = BIT(6),
+       OMNIA_EXT_CTL_PHY_SFP_AUTO      = BIT(7),
+       OMNIA_EXT_CTL_nVHV_CTRL         = BIT(8),
+};
+
+enum omnia_int_e {
+       OMNIA_INT_CARD_DET              = BIT(0),
+       OMNIA_INT_MSATA_IND             = BIT(1),
+       OMNIA_INT_USB30_OVC             = BIT(2),
+       OMNIA_INT_USB31_OVC             = BIT(3),
+       OMNIA_INT_BUTTON_PRESSED        = BIT(4),
+       OMNIA_INT_SFP_nDET              = BIT(5),
+       OMNIA_INT_BRIGHTNESS_CHANGED    = BIT(6),
+       OMNIA_INT_TRNG                  = BIT(7),
+       OMNIA_INT_MESSAGE_SIGNED        = BIT(8),
+
+       OMNIA_INT_LED_STATES_MASK       = GENMASK(31, 12),
+       OMNIA_INT_WLAN0_MSATA_LED       = BIT(12),
+       OMNIA_INT_WLAN1_LED             = BIT(13),
+       OMNIA_INT_WLAN2_LED             = BIT(14),
+       OMNIA_INT_WPAN0_LED             = BIT(15),
+       OMNIA_INT_WPAN1_LED             = BIT(16),
+       OMNIA_INT_WPAN2_LED             = BIT(17),
+       OMNIA_INT_WAN_LED0              = BIT(18),
+       OMNIA_INT_WAN_LED1              = BIT(19),
+       OMNIA_INT_LAN0_LED0             = BIT(20),
+       OMNIA_INT_LAN0_LED1             = BIT(21),
+       OMNIA_INT_LAN1_LED0             = BIT(22),
+       OMNIA_INT_LAN1_LED1             = BIT(23),
+       OMNIA_INT_LAN2_LED0             = BIT(24),
+       OMNIA_INT_LAN2_LED1             = BIT(25),
+       OMNIA_INT_LAN3_LED0             = BIT(26),
+       OMNIA_INT_LAN3_LED1             = BIT(27),
+       OMNIA_INT_LAN4_LED0             = BIT(28),
+       OMNIA_INT_LAN4_LED1             = BIT(29),
+       OMNIA_INT_LAN5_LED0             = BIT(30),
+       OMNIA_INT_LAN5_LED1             = BIT(31),
+};
+
+enum omnia_cmd_poweroff_e {
+       OMNIA_CMD_POWER_OFF_POWERON_BUTTON      = BIT(0),
+       OMNIA_CMD_POWER_OFF_MAGIC               = 0xdead,
+};
+
+enum omnia_cmd_usb_ovc_prot_e {
+       OMNIA_CMD_xET_USB_OVC_PROT_PORT_MASK    = GENMASK(3, 0),
+       OMNIA_CMD_xET_USB_OVC_PROT_ENABLE       = BIT(4),
+};
+
+#endif /* __TURRIS_OMNIA_MCU_INTERFACE_H */