File Index
Files and directories
Sign in
FILE

turris-omnia-mcu-base.c

File preview with syntax highlighting.

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