summaryrefslogtreecommitdiff
path: root/drivers/platform
diff options
context:
space:
mode:
authorMarek Behún <kabel@kernel.org>2024-07-01 13:30:04 +0200
committerArnd Bergmann <arnd@arndb.de>2024-07-01 15:46:36 +0200
commit992f1a3d4e88498de04b0b13b94705d8540f3d81 (patch)
tree1d647916d385987c3f8eb9fc74a48993a60733b0 /drivers/platform
parentf5e6f47f2af07e14f29ae509cbab838df93d224c (diff)
downloadlwn-992f1a3d4e88498de04b0b13b94705d8540f3d81.tar.gz
lwn-992f1a3d4e88498de04b0b13b94705d8540f3d81.zip
platform: cznic: Add preliminary support for Turris Omnia MCU
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>
Diffstat (limited to 'drivers/platform')
-rw-r--r--drivers/platform/Kconfig2
-rw-r--r--drivers/platform/Makefile1
-rw-r--r--drivers/platform/cznic/Kconfig25
-rw-r--r--drivers/platform/cznic/Makefile4
-rw-r--r--drivers/platform/cznic/turris-omnia-mcu-base.c394
-rw-r--r--drivers/platform/cznic/turris-omnia-mcu.h74
6 files changed, 500 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..db5f4a673d28
--- /dev/null
+++ b/drivers/platform/cznic/Kconfig
@@ -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
index 000000000000..31adca73bb94
--- /dev/null
+++ b/drivers/platform/cznic/Makefile
@@ -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
index 000000000000..47513ba9b128
--- /dev/null
+++ b/drivers/platform/cznic/turris-omnia-mcu-base.c
@@ -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
index 000000000000..3d0daa6f13ef
--- /dev/null
+++ b/drivers/platform/cznic/turris-omnia-mcu.h
@@ -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 */