]> Git Repo - J-linux.git/blob - drivers/platform/cznic/turris-omnia-mcu-base.c
Merge tag 'vfs-6.13-rc7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
[J-linux.git] / drivers / platform / cznic / turris-omnia-mcu-base.c
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 <[email protected]>");
411 MODULE_DESCRIPTION("CZ.NIC's Turris Omnia MCU");
412 MODULE_LICENSE("GPL");
This page took 0.051916 seconds and 4 git commands to generate.