FILE
turris-omnia-mcu-base.c
File preview with syntax highlighting.
linux-6.19.14/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 <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.