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
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
omnia_get_version_hash(struct omnia_mcu * mcu,bool bootloader,char version[static OMNIA_FW_VERSION_HEX_LEN])56 static 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_BOOT
65 : 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
fw_version_hash_show(struct device * dev,char * buf,bool bootloader)76 static 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
fw_version_hash_application_show(struct device * dev,struct device_attribute * a,char * buf)90 static 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 }
96 static DEVICE_ATTR_RO(fw_version_hash_application);
97
fw_version_hash_bootloader_show(struct device * dev,struct device_attribute * a,char * buf)98 static 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 }
104 static DEVICE_ATTR_RO(fw_version_hash_bootloader);
105
fw_features_show(struct device * dev,struct device_attribute * a,char * buf)106 static 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 }
113 static DEVICE_ATTR_RO(fw_features);
114
mcu_type_show(struct device * dev,struct device_attribute * a,char * buf)115 static 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 }
122 static DEVICE_ATTR_RO(mcu_type);
123
reset_selector_show(struct device * dev,struct device_attribute * a,char * buf)124 static 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 }
137 static DEVICE_ATTR_RO(reset_selector);
138
serial_number_show(struct device * dev,struct device_attribute * a,char * buf)139 static 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 }
146 static DEVICE_ATTR_RO(serial_number);
147
first_mac_address_show(struct device * dev,struct device_attribute * a,char * buf)148 static 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 }
155 static DEVICE_ATTR_RO(first_mac_address);
156
board_revision_show(struct device * dev,struct device_attribute * a,char * buf)157 static 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 }
164 static DEVICE_ATTR_RO(board_revision);
165
166 static 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 NULL
176 };
177
omnia_mcu_base_attrs_visible(struct kobject * kobj,struct attribute * a,int n)178 static 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
193 static const struct attribute_group omnia_mcu_base_group = {
194 .attrs = omnia_mcu_base_attrs,
195 .is_visible = omnia_mcu_base_attrs_visible,
196 };
197
198 static const struct attribute_group *omnia_mcu_groups[] = {
199 &omnia_mcu_base_group,
200 #ifdef CONFIG_TURRIS_OMNIA_MCU_GPIO
201 &omnia_mcu_gpio_group,
202 #endif
203 #ifdef CONFIG_TURRIS_OMNIA_MCU_SYSOFF_WAKEUP
204 &omnia_mcu_poweroff_group,
205 #endif
206 NULL
207 };
208
omnia_mcu_print_version_hash(struct omnia_mcu * mcu,bool bootloader)209 static 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
omnia_status_to_mcu_type(u16 status)226 static 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
omnia_info_missing_feature(struct device * dev,const char * feature)240 static 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
omnia_mcu_read_features(struct omnia_mcu * mcu)247 static 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_FEAT
261 };
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_FEATURES
275 * 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 else
312 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
omnia_mcu_read_board_info(struct omnia_mcu * mcu)329 static 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
omnia_mcu_probe(struct i2c_client * client)352 static 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
395 static const struct of_device_id of_omnia_mcu_match[] = {
396 { .compatible = "cznic,turris-omnia-mcu" },
397 {}
398 };
399
400 static 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 };
408 module_i2c_driver(omnia_mcu_driver);
409
410 MODULE_AUTHOR("Marek Behun <kabel@kernel.org>");
411 MODULE_DESCRIPTION("CZ.NIC's Turris Omnia MCU");
412 MODULE_LICENSE("GPL");
413