1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Toradex Embedded Controller driver 4 * 5 * Copyright (C) 2025 Toradex 6 * 7 * Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com> 8 */ 9 10 #include <linux/array_size.h> 11 #include <linux/bug.h> 12 #include <linux/delay.h> 13 #include <linux/device.h> 14 #include <linux/dev_printk.h> 15 #include <linux/err.h> 16 #include <linux/i2c.h> 17 #include <linux/mod_devicetable.h> 18 #include <linux/module.h> 19 #include <linux/reboot.h> 20 #include <linux/regmap.h> 21 #include <linux/types.h> 22 23 #define EC_CHIP_ID_REG 0x00 24 #define EC_CHIP_ID_SMARC_IMX95 0x11 25 #define EC_CHIP_ID_SMARC_IMX8MP 0x12 26 27 #define EC_VERSION_REG_MAJOR 0x01 28 #define EC_VERSION_REG_MINOR 0x02 29 #define EC_ID_VERSION_LEN 3 30 31 #define EC_CMD_REG 0xD0 32 #define EC_CMD_POWEROFF 0x01 33 #define EC_CMD_RESET 0x02 34 35 #define EC_REG_MAX 0xD0 36 37 #define EC_CMD_TIMEOUT_MS 1000 38 39 static const struct regmap_range volatile_ranges[] = { 40 regmap_reg_range(EC_CMD_REG, EC_CMD_REG), 41 }; 42 43 static const struct regmap_access_table volatile_table = { 44 .yes_ranges = volatile_ranges, 45 .n_yes_ranges = ARRAY_SIZE(volatile_ranges), 46 }; 47 48 static const struct regmap_range read_ranges[] = { 49 regmap_reg_range(EC_CHIP_ID_REG, EC_VERSION_REG_MINOR), 50 }; 51 52 static const struct regmap_access_table read_table = { 53 .yes_ranges = read_ranges, 54 .n_yes_ranges = ARRAY_SIZE(read_ranges), 55 }; 56 57 static const struct regmap_config regmap_config = { 58 .reg_bits = 8, 59 .val_bits = 8, 60 .max_register = EC_REG_MAX, 61 .cache_type = REGCACHE_RBTREE, 62 .rd_table = &read_table, 63 .volatile_table = &volatile_table, 64 }; 65 66 static int tdx_ec_cmd(struct regmap *regmap, u8 cmd) 67 { 68 int err = regmap_write(regmap, EC_CMD_REG, cmd); 69 70 if (err) 71 dev_err(regmap_get_device(regmap), "Failed to send command 0x%02X: %d\n", cmd, err); 72 73 return err; 74 } 75 76 static int tdx_ec_power_off(struct sys_off_data *data) 77 { 78 struct regmap *regmap = data->cb_data; 79 int err; 80 81 err = tdx_ec_cmd(regmap, EC_CMD_POWEROFF); 82 83 if (err) { 84 dev_err(data->dev, "Failed to send power off command\n"); 85 } else { 86 mdelay(EC_CMD_TIMEOUT_MS); 87 WARN_ONCE(1, "Unable to power off system\n"); 88 } 89 90 return err ? NOTIFY_BAD : NOTIFY_DONE; 91 } 92 93 static int tdx_ec_restart(struct sys_off_data *data) 94 { 95 struct regmap *regmap = data->cb_data; 96 int err; 97 98 err = tdx_ec_cmd(regmap, EC_CMD_RESET); 99 100 if (err) { 101 dev_err(data->dev, "Failed to send restart command\n"); 102 } else { 103 mdelay(EC_CMD_TIMEOUT_MS); 104 WARN_ONCE(1, "Unable to restart system\n"); 105 } 106 107 return err ? NOTIFY_BAD : NOTIFY_DONE; 108 } 109 110 static int tdx_ec_register_power_off_restart(struct device *dev, struct regmap *regmap) 111 { 112 int err; 113 114 err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART, 115 SYS_OFF_PRIO_FIRMWARE, 116 tdx_ec_restart, regmap); 117 if (err) 118 return err; 119 120 return devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF, 121 SYS_OFF_PRIO_FIRMWARE, 122 tdx_ec_power_off, regmap); 123 } 124 125 static int tdx_ec_probe(struct i2c_client *client) 126 { 127 struct device *dev = &client->dev; 128 u8 reg_val[EC_ID_VERSION_LEN]; 129 struct regmap *regmap; 130 int err; 131 132 regmap = devm_regmap_init_i2c(client, ®map_config); 133 if (IS_ERR(regmap)) 134 return PTR_ERR(regmap); 135 136 err = regmap_bulk_read(regmap, EC_CHIP_ID_REG, ®_val, EC_ID_VERSION_LEN); 137 if (err) 138 return dev_err_probe(dev, err, 139 "Cannot read id and version registers\n"); 140 141 dev_info(dev, "Toradex Embedded Controller id %x - Firmware %u.%u\n", 142 reg_val[0], reg_val[1], reg_val[2]); 143 144 err = tdx_ec_register_power_off_restart(dev, regmap); 145 if (err) 146 return dev_err_probe(dev, err, 147 "Cannot register system restart handler\n"); 148 149 return 0; 150 } 151 152 static const struct of_device_id __maybe_unused of_tdx_ec_match[] = { 153 { .compatible = "toradex,smarc-ec" }, 154 {} 155 }; 156 MODULE_DEVICE_TABLE(of, of_tdx_ec_match); 157 158 static struct i2c_driver tdx_ec_driver = { 159 .probe = tdx_ec_probe, 160 .driver = { 161 .name = "toradex-smarc-ec", 162 .of_match_table = of_tdx_ec_match, 163 }, 164 }; 165 module_i2c_driver(tdx_ec_driver); 166 167 MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>"); 168 MODULE_DESCRIPTION("Toradex SMARC Embedded Controller driver"); 169 MODULE_LICENSE("GPL"); 170