1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * CZ.NIC's Turris Omnia MCU driver
4  *
5  * 2024 by Marek Behún <[email protected]>
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 
omnia_cmd_write_read(const struct i2c_client * client,void * cmd,unsigned int cmd_len,void * reply,unsigned int reply_len)26 int 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 EXPORT_SYMBOL_GPL(omnia_cmd_write_read);
56 
omnia_get_version_hash(struct omnia_mcu * mcu,bool bootloader,char version[static OMNIA_FW_VERSION_HEX_LEN])57 static 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 
fw_version_hash_show(struct device * dev,char * buf,bool bootloader)77 static 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 
fw_version_hash_application_show(struct device * dev,struct device_attribute * a,char * buf)91 static 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 }
97 static DEVICE_ATTR_RO(fw_version_hash_application);
98 
fw_version_hash_bootloader_show(struct device * dev,struct device_attribute * a,char * buf)99 static 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 }
105 static DEVICE_ATTR_RO(fw_version_hash_bootloader);
106 
fw_features_show(struct device * dev,struct device_attribute * a,char * buf)107 static 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 }
114 static DEVICE_ATTR_RO(fw_features);
115 
mcu_type_show(struct device * dev,struct device_attribute * a,char * buf)116 static 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 }
123 static DEVICE_ATTR_RO(mcu_type);
124 
reset_selector_show(struct device * dev,struct device_attribute * a,char * buf)125 static 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 }
138 static DEVICE_ATTR_RO(reset_selector);
139 
serial_number_show(struct device * dev,struct device_attribute * a,char * buf)140 static 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 }
147 static DEVICE_ATTR_RO(serial_number);
148 
first_mac_address_show(struct device * dev,struct device_attribute * a,char * buf)149 static 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 }
156 static DEVICE_ATTR_RO(first_mac_address);
157 
board_revision_show(struct device * dev,struct device_attribute * a,char * buf)158 static 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 }
165 static DEVICE_ATTR_RO(board_revision);
166 
167 static 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 
omnia_mcu_base_attrs_visible(struct kobject * kobj,struct attribute * a,int n)179 static 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 
194 static const struct attribute_group omnia_mcu_base_group = {
195 	.attrs = omnia_mcu_base_attrs,
196 	.is_visible = omnia_mcu_base_attrs_visible,
197 };
198 
199 static 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 
omnia_mcu_print_version_hash(struct omnia_mcu * mcu,bool bootloader)210 static 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 
omnia_status_to_mcu_type(u16 status)227 static 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 
omnia_info_missing_feature(struct device * dev,const char * feature)241 static 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 
omnia_mcu_read_features(struct omnia_mcu * mcu)248 static 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 
omnia_mcu_read_board_info(struct omnia_mcu * mcu)332 static 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 
omnia_mcu_probe(struct i2c_client * client)355 static 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 	return omnia_mcu_register_trng(mcu);
396 }
397 
398 static const struct of_device_id of_omnia_mcu_match[] = {
399 	{ .compatible = "cznic,turris-omnia-mcu" },
400 	{}
401 };
402 
403 static struct i2c_driver omnia_mcu_driver = {
404 	.probe		= omnia_mcu_probe,
405 	.driver		= {
406 		.name	= "turris-omnia-mcu",
407 		.of_match_table = of_omnia_mcu_match,
408 		.dev_groups = omnia_mcu_groups,
409 	},
410 };
411 module_i2c_driver(omnia_mcu_driver);
412 
413 MODULE_AUTHOR("Marek Behun <[email protected]>");
414 MODULE_DESCRIPTION("CZ.NIC's Turris Omnia MCU");
415 MODULE_LICENSE("GPL");
416