brintos

brintos / linux-shallow public Read only

0
0
Text · 9.9 KiB · 58f9afa Raw
413 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * CZ.NIC's Turris Omnia MCU driver4 *5 * 2024 by Marek Behún <kabel@kernel.org>6 */7 8#include <linux/array_size.h>9#include <linux/bits.h>10#include <linux/device.h>11#include <linux/errno.h>12#include <linux/hex.h>13#include <linux/i2c.h>14#include <linux/module.h>15#include <linux/string.h>16#include <linux/sysfs.h>17#include <linux/types.h>18 19#include <linux/turris-omnia-mcu-interface.h>20#include "turris-omnia-mcu.h"21 22#define OMNIA_FW_VERSION_LEN		2023#define OMNIA_FW_VERSION_HEX_LEN	(2 * OMNIA_FW_VERSION_LEN + 1)24#define OMNIA_BOARD_INFO_LEN		1625 26int omnia_cmd_write_read(const struct i2c_client *client,27			 void *cmd, unsigned int cmd_len,28			 void *reply, unsigned int reply_len)29{30	struct i2c_msg msgs[2];31	int ret, num;32 33	msgs[0].addr = client->addr;34	msgs[0].flags = 0;35	msgs[0].len = cmd_len;36	msgs[0].buf = cmd;37	num = 1;38 39	if (reply_len) {40		msgs[1].addr = client->addr;41		msgs[1].flags = I2C_M_RD;42		msgs[1].len = reply_len;43		msgs[1].buf = reply;44		num++;45	}46 47	ret = i2c_transfer(client->adapter, msgs, num);48	if (ret < 0)49		return ret;50	if (ret != num)51		return -EIO;52 53	return 0;54}55 56static int omnia_get_version_hash(struct omnia_mcu *mcu, bool bootloader,57				  char version[static OMNIA_FW_VERSION_HEX_LEN])58{59	u8 reply[OMNIA_FW_VERSION_LEN];60	char *p;61	int err;62 63	err = omnia_cmd_read(mcu->client,64			     bootloader ? OMNIA_CMD_GET_FW_VERSION_BOOT65					: OMNIA_CMD_GET_FW_VERSION_APP,66			     reply, sizeof(reply));67	if (err)68		return err;69 70	p = bin2hex(version, reply, OMNIA_FW_VERSION_LEN);71	*p = '\0';72 73	return 0;74}75 76static ssize_t fw_version_hash_show(struct device *dev, char *buf,77				    bool bootloader)78{79	struct omnia_mcu *mcu = dev_get_drvdata(dev);80	char version[OMNIA_FW_VERSION_HEX_LEN];81	int err;82 83	err = omnia_get_version_hash(mcu, bootloader, version);84	if (err)85		return err;86 87	return sysfs_emit(buf, "%s\n", version);88}89 90static ssize_t fw_version_hash_application_show(struct device *dev,91						struct device_attribute *a,92						char *buf)93{94	return fw_version_hash_show(dev, buf, false);95}96static DEVICE_ATTR_RO(fw_version_hash_application);97 98static ssize_t fw_version_hash_bootloader_show(struct device *dev,99					       struct device_attribute *a,100					       char *buf)101{102	return fw_version_hash_show(dev, buf, true);103}104static DEVICE_ATTR_RO(fw_version_hash_bootloader);105 106static ssize_t fw_features_show(struct device *dev, struct device_attribute *a,107				char *buf)108{109	struct omnia_mcu *mcu = dev_get_drvdata(dev);110 111	return sysfs_emit(buf, "0x%x\n", mcu->features);112}113static DEVICE_ATTR_RO(fw_features);114 115static ssize_t mcu_type_show(struct device *dev, struct device_attribute *a,116			     char *buf)117{118	struct omnia_mcu *mcu = dev_get_drvdata(dev);119 120	return sysfs_emit(buf, "%s\n", mcu->type);121}122static DEVICE_ATTR_RO(mcu_type);123 124static ssize_t reset_selector_show(struct device *dev,125				   struct device_attribute *a, char *buf)126{127	u8 reply;128	int err;129 130	err = omnia_cmd_read_u8(to_i2c_client(dev), OMNIA_CMD_GET_RESET,131				&reply);132	if (err)133		return err;134 135	return sysfs_emit(buf, "%d\n", reply);136}137static DEVICE_ATTR_RO(reset_selector);138 139static ssize_t serial_number_show(struct device *dev,140				  struct device_attribute *a, char *buf)141{142	struct omnia_mcu *mcu = dev_get_drvdata(dev);143 144	return sysfs_emit(buf, "%016llX\n", mcu->board_serial_number);145}146static DEVICE_ATTR_RO(serial_number);147 148static ssize_t first_mac_address_show(struct device *dev,149				      struct device_attribute *a, char *buf)150{151	struct omnia_mcu *mcu = dev_get_drvdata(dev);152 153	return sysfs_emit(buf, "%pM\n", mcu->board_first_mac);154}155static DEVICE_ATTR_RO(first_mac_address);156 157static ssize_t board_revision_show(struct device *dev,158				   struct device_attribute *a, char *buf)159{160	struct omnia_mcu *mcu = dev_get_drvdata(dev);161 162	return sysfs_emit(buf, "%u\n", mcu->board_revision);163}164static DEVICE_ATTR_RO(board_revision);165 166static struct attribute *omnia_mcu_base_attrs[] = {167	&dev_attr_fw_version_hash_application.attr,168	&dev_attr_fw_version_hash_bootloader.attr,169	&dev_attr_fw_features.attr,170	&dev_attr_mcu_type.attr,171	&dev_attr_reset_selector.attr,172	&dev_attr_serial_number.attr,173	&dev_attr_first_mac_address.attr,174	&dev_attr_board_revision.attr,175	NULL176};177 178static umode_t omnia_mcu_base_attrs_visible(struct kobject *kobj,179					    struct attribute *a, int n)180{181	struct device *dev = kobj_to_dev(kobj);182	struct omnia_mcu *mcu = dev_get_drvdata(dev);183 184	if ((a == &dev_attr_serial_number.attr ||185	     a == &dev_attr_first_mac_address.attr ||186	     a == &dev_attr_board_revision.attr) &&187	    !(mcu->features & OMNIA_FEAT_BOARD_INFO))188		return 0;189 190	return a->mode;191}192 193static const struct attribute_group omnia_mcu_base_group = {194	.attrs = omnia_mcu_base_attrs,195	.is_visible = omnia_mcu_base_attrs_visible,196};197 198static const struct attribute_group *omnia_mcu_groups[] = {199	&omnia_mcu_base_group,200#ifdef CONFIG_TURRIS_OMNIA_MCU_GPIO201	&omnia_mcu_gpio_group,202#endif203#ifdef CONFIG_TURRIS_OMNIA_MCU_SYSOFF_WAKEUP204	&omnia_mcu_poweroff_group,205#endif206	NULL207};208 209static void omnia_mcu_print_version_hash(struct omnia_mcu *mcu, bool bootloader)210{211	const char *type = bootloader ? "bootloader" : "application";212	struct device *dev = &mcu->client->dev;213	char version[OMNIA_FW_VERSION_HEX_LEN];214	int err;215 216	err = omnia_get_version_hash(mcu, bootloader, version);217	if (err) {218		dev_err(dev, "Cannot read MCU %s firmware version: %d\n",219			type, err);220		return;221	}222 223	dev_info(dev, "MCU %s firmware version hash: %s\n", type, version);224}225 226static const char *omnia_status_to_mcu_type(u16 status)227{228	switch (status & OMNIA_STS_MCU_TYPE_MASK) {229	case OMNIA_STS_MCU_TYPE_STM32:230		return "STM32";231	case OMNIA_STS_MCU_TYPE_GD32:232		return "GD32";233	case OMNIA_STS_MCU_TYPE_MKL:234		return "MKL";235	default:236		return "unknown";237	}238}239 240static void omnia_info_missing_feature(struct device *dev, const char *feature)241{242	dev_info(dev,243		 "Your board's MCU firmware does not support the %s feature.\n",244		 feature);245}246 247static int omnia_mcu_read_features(struct omnia_mcu *mcu)248{249	static const struct {250		u16 mask;251		const char *name;252	} features[] = {253#define _DEF_FEAT(_n, _m) { OMNIA_FEAT_ ## _n, _m }254		_DEF_FEAT(EXT_CMDS,		"extended control and status"),255		_DEF_FEAT(WDT_PING,		"watchdog pinging"),256		_DEF_FEAT(LED_STATE_EXT_MASK,	"peripheral LED pins reading"),257		_DEF_FEAT(NEW_INT_API,		"new interrupt API"),258		_DEF_FEAT(POWEROFF_WAKEUP,	"poweroff and wakeup"),259		_DEF_FEAT(TRNG,			"true random number generator"),260#undef _DEF_FEAT261	};262	struct i2c_client *client = mcu->client;263	struct device *dev = &client->dev;264	bool suggest_fw_upgrade = false;265	u16 status;266	int err;267 268	/* status word holds MCU type, which we need below */269	err = omnia_cmd_read_u16(client, OMNIA_CMD_GET_STATUS_WORD, &status);270	if (err)271		return err;272 273	/*274	 * Check whether MCU firmware supports the OMNIA_CMD_GET_FEATURES275	 * command.276	 */277	if (status & OMNIA_STS_FEATURES_SUPPORTED) {278		/* try read 32-bit features */279		err = omnia_cmd_read_u32(client, OMNIA_CMD_GET_FEATURES,280					 &mcu->features);281		if (err) {282			/* try read 16-bit features */283			u16 features16;284 285			err = omnia_cmd_read_u16(client, OMNIA_CMD_GET_FEATURES,286						 &features16);287			if (err)288				return err;289 290			mcu->features = features16;291		} else {292			if (mcu->features & OMNIA_FEAT_FROM_BIT_16_INVALID)293				mcu->features &= GENMASK(15, 0);294		}295	} else {296		dev_info(dev,297			 "Your board's MCU firmware does not support feature reading.\n");298		suggest_fw_upgrade = true;299	}300 301	mcu->type = omnia_status_to_mcu_type(status);302	dev_info(dev, "MCU type %s%s\n", mcu->type,303		 (mcu->features & OMNIA_FEAT_PERIPH_MCU) ?304			", with peripheral resets wired" : "");305 306	omnia_mcu_print_version_hash(mcu, true);307 308	if (mcu->features & OMNIA_FEAT_BOOTLOADER)309		dev_warn(dev,310			 "MCU is running bootloader firmware. Was firmware upgrade interrupted?\n");311	else312		omnia_mcu_print_version_hash(mcu, false);313 314	for (unsigned int i = 0; i < ARRAY_SIZE(features); i++) {315		if (mcu->features & features[i].mask)316			continue;317 318		omnia_info_missing_feature(dev, features[i].name);319		suggest_fw_upgrade = true;320	}321 322	if (suggest_fw_upgrade)323		dev_info(dev,324			 "Consider upgrading MCU firmware with the omnia-mcutool utility.\n");325 326	return 0;327}328 329static int omnia_mcu_read_board_info(struct omnia_mcu *mcu)330{331	u8 reply[1 + OMNIA_BOARD_INFO_LEN];332	int err;333 334	err = omnia_cmd_read(mcu->client, OMNIA_CMD_BOARD_INFO_GET, reply,335			     sizeof(reply));336	if (err)337		return err;338 339	if (reply[0] != OMNIA_BOARD_INFO_LEN)340		return -EIO;341 342	mcu->board_serial_number = get_unaligned_le64(&reply[1]);343 344	/* we can't use ether_addr_copy() because reply is not u16-aligned */345	memcpy(mcu->board_first_mac, &reply[9], sizeof(mcu->board_first_mac));346 347	mcu->board_revision = reply[15];348 349	return 0;350}351 352static int omnia_mcu_probe(struct i2c_client *client)353{354	struct device *dev = &client->dev;355	struct omnia_mcu *mcu;356	int err;357 358	if (!client->irq)359		return dev_err_probe(dev, -EINVAL, "IRQ resource not found\n");360 361	mcu = devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL);362	if (!mcu)363		return -ENOMEM;364 365	mcu->client = client;366	i2c_set_clientdata(client, mcu);367 368	err = omnia_mcu_read_features(mcu);369	if (err)370		return dev_err_probe(dev, err,371				     "Cannot determine MCU supported features\n");372 373	if (mcu->features & OMNIA_FEAT_BOARD_INFO) {374		err = omnia_mcu_read_board_info(mcu);375		if (err)376			return dev_err_probe(dev, err,377					     "Cannot read board info\n");378	}379 380	err = omnia_mcu_register_sys_off_and_wakeup(mcu);381	if (err)382		return err;383 384	err = omnia_mcu_register_watchdog(mcu);385	if (err)386		return err;387 388	err = omnia_mcu_register_gpiochip(mcu);389	if (err)390		return err;391 392	return omnia_mcu_register_trng(mcu);393}394 395static const struct of_device_id of_omnia_mcu_match[] = {396	{ .compatible = "cznic,turris-omnia-mcu" },397	{}398};399 400static struct i2c_driver omnia_mcu_driver = {401	.probe		= omnia_mcu_probe,402	.driver		= {403		.name	= "turris-omnia-mcu",404		.of_match_table = of_omnia_mcu_match,405		.dev_groups = omnia_mcu_groups,406	},407};408module_i2c_driver(omnia_mcu_driver);409 410MODULE_AUTHOR("Marek Behun <kabel@kernel.org>");411MODULE_DESCRIPTION("CZ.NIC's Turris Omnia MCU");412MODULE_LICENSE("GPL");413