| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- // 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,
- #ifdef CONFIG_TURRIS_OMNIA_MCU_GPIO
- &omnia_mcu_gpio_group,
- #endif
- #ifdef CONFIG_TURRIS_OMNIA_MCU_SYSOFF_WAKEUP
- &omnia_mcu_poweroff_group,
- #endif
- 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");
|