summaryrefslogtreecommitdiff
path: root/drivers/power/reset
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/reset')
-rw-r--r--drivers/power/reset/Kconfig22
-rw-r--r--drivers/power/reset/Makefile2
-rw-r--r--drivers/power/reset/at91-reset.c5
-rw-r--r--drivers/power/reset/macsmc-reboot.c290
-rw-r--r--drivers/power/reset/reboot-mode.c25
-rw-r--r--drivers/power/reset/syscon-reboot.c98
-rw-r--r--drivers/power/reset/tdx-ec-poweroff.c150
7 files changed, 560 insertions, 32 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 60bf0ca64cf3..733d81262159 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -128,6 +128,15 @@ config POWER_RESET_LINKSTATION
Say Y here if you have a Buffalo LinkStation LS421D/E.
+config POWER_RESET_MACSMC
+ tristate "Apple SMC reset/power-off driver"
+ depends on MFD_MACSMC
+ help
+ This driver supports reset and power-off on Apple Mac machines
+ that implement this functionality via the SMC.
+
+ Say Y here if you have an Apple Silicon Mac.
+
config POWER_RESET_MSM
bool "Qualcomm MSM power-off driver"
depends on ARCH_QCOM
@@ -216,6 +225,19 @@ config POWER_RESET_ST
help
Reset support for STMicroelectronics boards.
+config POWER_RESET_TORADEX_EC
+ tristate "Toradex Embedded Controller power-off and reset driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ This driver supports power-off and reset for SMARC Toradex SoMs,
+ for example the SMARC iMX8MP and SMARC iMX95, using Toradex
+ Embedded Controller (EC).
+
+ Say Y here if you have a Toradex SMARC SoM.
+
+ If unsure, say N.
+
config POWER_RESET_TPS65086
bool "TPS65086 restart driver"
depends on MFD_TPS65086
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 10782d32e1da..b7c2b5940be9 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o
+obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
@@ -24,6 +25,7 @@ obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
obj-$(CONFIG_POWER_RESET_REGULATOR) += regulator-poweroff.o
obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o
+obj-$(CONFIG_POWER_RESET_TORADEX_EC) += tdx-ec-poweroff.o
obj-$(CONFIG_POWER_RESET_TPS65086) += tps65086-restart.o
obj-$(CONFIG_POWER_RESET_VERSATILE) += arm-versatile-reboot.o
obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o
diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c
index 036b18a1f90f..511f5a8f8961 100644
--- a/drivers/power/reset/at91-reset.c
+++ b/drivers/power/reset/at91-reset.c
@@ -129,12 +129,11 @@ static int at91_reset(struct notifier_block *this, unsigned long mode,
" str %4, [%0, %6]\n\t"
/* Disable SDRAM1 accesses */
"1: tst %1, #0\n\t"
- " beq 2f\n\t"
" strne %3, [%1, #" __stringify(AT91_DDRSDRC_RTR) "]\n\t"
/* Power down SDRAM1 */
" strne %4, [%1, %6]\n\t"
/* Reset CPU */
- "2: str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t"
+ " str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t"
" b .\n\t"
:
@@ -145,7 +144,7 @@ static int at91_reset(struct notifier_block *this, unsigned long mode,
"r" cpu_to_le32(AT91_DDRSDRC_LPCB_POWER_DOWN),
"r" (reset->data->reset_args),
"r" (reset->ramc_lpr)
- : "r4");
+ );
return NOTIFY_DONE;
}
diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c
new file mode 100644
index 000000000000..e9702acdd366
--- /dev/null
+++ b/drivers/power/reset/macsmc-reboot.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC Reboot/Poweroff Handler
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+
+struct macsmc_reboot_nvmem {
+ struct nvmem_cell *shutdown_flag;
+ struct nvmem_cell *boot_stage;
+ struct nvmem_cell *boot_error_count;
+ struct nvmem_cell *panic_count;
+};
+
+static const char * const nvmem_names[] = {
+ "shutdown_flag",
+ "boot_stage",
+ "boot_error_count",
+ "panic_count",
+};
+
+enum boot_stage {
+ BOOT_STAGE_SHUTDOWN = 0x00, /* Clean shutdown */
+ BOOT_STAGE_IBOOT_DONE = 0x2f, /* Last stage of bootloader */
+ BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */
+};
+
+struct macsmc_reboot {
+ struct device *dev;
+ struct apple_smc *smc;
+ struct notifier_block reboot_notify;
+
+ union {
+ struct macsmc_reboot_nvmem nvm;
+ struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)];
+ };
+};
+
+/* Helpers to read/write a u8 given a struct nvmem_cell */
+static int nvmem_cell_get_u8(struct nvmem_cell *cell)
+{
+ size_t len;
+ void *bfr;
+ u8 val;
+
+ bfr = nvmem_cell_read(cell, &len);
+ if (IS_ERR(bfr))
+ return PTR_ERR(bfr);
+
+ if (len < 1) {
+ kfree(bfr);
+ return -EINVAL;
+ }
+
+ val = *(u8 *)bfr;
+ kfree(bfr);
+ return val;
+}
+
+static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val)
+{
+ return nvmem_cell_write(cell, &val, sizeof(val));
+}
+
+/*
+ * SMC 'MBSE' key actions:
+ *
+ * 'offw' - shutdown warning
+ * 'slpw' - sleep warning
+ * 'rest' - restart warning
+ * 'off1' - shutdown (needs PMU bit set to stay on)
+ * 'susp' - suspend
+ * 'phra' - restart ("PE Halt Restart Action"?)
+ * 'panb' - panic beginning
+ * 'pane' - panic end
+ */
+
+static int macsmc_prepare_atomic(struct sys_off_data *data)
+{
+ struct macsmc_reboot *reboot = data->cb_data;
+
+ dev_info(reboot->dev, "Preparing SMC for atomic mode\n");
+
+ apple_smc_enter_atomic(reboot->smc);
+ return NOTIFY_OK;
+}
+
+static int macsmc_power_off(struct sys_off_data *data)
+{
+ struct macsmc_reboot *reboot = data->cb_data;
+
+ dev_info(reboot->dev, "Issuing power off (off1)\n");
+
+ if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) {
+ dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n");
+ } else {
+ mdelay(100);
+ WARN_ONCE(1, "Unable to power off system\n");
+ }
+
+ return NOTIFY_OK;
+}
+
+static int macsmc_restart(struct sys_off_data *data)
+{
+ struct macsmc_reboot *reboot = data->cb_data;
+
+ dev_info(reboot->dev, "Issuing restart (phra)\n");
+
+ if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) {
+ dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n");
+ } else {
+ mdelay(100);
+ WARN_ONCE(1, "Unable to restart system\n");
+ }
+
+ return NOTIFY_OK;
+}
+
+static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data)
+{
+ struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify);
+ u8 shutdown_flag;
+ u32 val;
+
+ switch (action) {
+ case SYS_RESTART:
+ val = SMC_KEY(rest);
+ shutdown_flag = 0;
+ break;
+ case SYS_POWER_OFF:
+ val = SMC_KEY(offw);
+ shutdown_flag = 1;
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val);
+
+ /* On the Mac Mini, this will turn off the LED for power off */
+ if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0)
+ dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val);
+
+ /* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */
+ if (reboot->nvm.boot_stage &&
+ nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0)
+ dev_err(reboot->dev, "Failed to write boot_stage\n");
+
+ /*
+ * Set the PMU flag to actually reboot into the off state.
+ * Without this, the device will just reboot. We make it optional in case it is no longer
+ * necessary on newer hardware.
+ */
+ if (reboot->nvm.shutdown_flag &&
+ nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0)
+ dev_err(reboot->dev, "Failed to write shutdown_flag\n");
+
+ return NOTIFY_OK;
+}
+
+static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot)
+{
+ int boot_error_count, panic_count;
+
+ if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count)
+ return;
+
+ boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count);
+ if (boot_error_count < 0) {
+ dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count);
+ return;
+ }
+
+ panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count);
+ if (panic_count < 0) {
+ dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count);
+ return;
+ }
+
+ if (!boot_error_count && !panic_count)
+ return;
+
+ dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n",
+ boot_error_count, panic_count);
+
+ if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0)
+ dev_err(reboot->dev, "Failed to reset panic_count\n");
+ if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0)
+ dev_err(reboot->dev, "Failed to reset boot_error_count\n");
+}
+
+static int macsmc_reboot_probe(struct platform_device *pdev)
+{
+ struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+ struct macsmc_reboot *reboot;
+ int ret, i;
+
+ reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL);
+ if (!reboot)
+ return -ENOMEM;
+
+ reboot->dev = &pdev->dev;
+ reboot->smc = smc;
+
+ platform_set_drvdata(pdev, reboot);
+
+ for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) {
+ struct nvmem_cell *cell;
+
+ cell = devm_nvmem_cell_get(&pdev->dev,
+ nvmem_names[i]);
+ if (IS_ERR(cell)) {
+ if (PTR_ERR(cell) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n",
+ nvmem_names[i], PTR_ERR(cell));
+ /* Non fatal, we'll deal with it */
+ cell = NULL;
+ }
+ reboot->nvm_cells[i] = cell;
+ }
+
+ /* Set the boot_stage to indicate we're running the OS kernel */
+ if (reboot->nvm.boot_stage &&
+ nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0)
+ dev_err(reboot->dev, "Failed to write boot_stage\n");
+
+ /* Display and clear the error counts */
+ macsmc_power_init_error_counts(reboot);
+
+ reboot->reboot_notify.notifier_call = macsmc_reboot_notify;
+
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF_PREPARE,
+ SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to register power-off prepare handler\n");
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH,
+ macsmc_power_off, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to register power-off handler\n");
+
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART_PREPARE,
+ SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to register restart prepare handler\n");
+ ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH,
+ macsmc_restart, reboot);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n");
+
+ ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n");
+
+ dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n");
+
+ return 0;
+}
+
+static const struct of_device_id macsmc_reboot_of_table[] = {
+ { .compatible = "apple,smc-reboot", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, macsmc_reboot_of_table);
+
+static struct platform_driver macsmc_reboot_driver = {
+ .driver = {
+ .name = "macsmc-reboot",
+ .of_match_table = macsmc_reboot_of_table,
+ },
+ .probe = macsmc_reboot_probe,
+};
+module_platform_driver(macsmc_reboot_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c
index b4076b10b893..fba53f638da0 100644
--- a/drivers/power/reset/reboot-mode.c
+++ b/drivers/power/reset/reboot-mode.c
@@ -23,20 +23,29 @@ static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
const char *cmd)
{
const char *normal = "normal";
- int magic = 0;
struct mode_info *info;
+ char cmd_[110];
if (!cmd)
cmd = normal;
- list_for_each_entry(info, &reboot->head, list) {
- if (!strcmp(info->mode, cmd)) {
- magic = info->magic;
- break;
- }
- }
+ list_for_each_entry(info, &reboot->head, list)
+ if (!strcmp(info->mode, cmd))
+ return info->magic;
+
+ /* try to match again, replacing characters impossible in DT */
+ if (strscpy(cmd_, cmd, sizeof(cmd_)) == -E2BIG)
+ return 0;
- return magic;
+ strreplace(cmd_, ' ', '-');
+ strreplace(cmd_, ',', '-');
+ strreplace(cmd_, '/', '-');
+
+ list_for_each_entry(info, &reboot->head, list)
+ if (!strcmp(info->mode, cmd_))
+ return info->magic;
+
+ return 0;
}
static int reboot_mode_notify(struct notifier_block *this,
diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c
index d623d77e657e..2e2cf5f62d73 100644
--- a/drivers/power/reset/syscon-reboot.c
+++ b/drivers/power/reset/syscon-reboot.c
@@ -14,11 +14,24 @@
#include <linux/reboot.h>
#include <linux/regmap.h>
-struct syscon_reboot_context {
- struct regmap *map;
+struct reboot_mode_bits {
u32 offset;
- u32 value;
u32 mask;
+ u32 value;
+ bool valid;
+};
+
+struct reboot_data {
+ struct reboot_mode_bits mode_bits[REBOOT_SOFT + 1];
+ struct reboot_mode_bits catchall;
+};
+
+struct syscon_reboot_context {
+ struct regmap *map;
+
+ const struct reboot_data *rd; /* from of match data, if any */
+ struct reboot_mode_bits catchall; /* from DT */
+
struct notifier_block restart_handler;
};
@@ -28,9 +41,21 @@ static int syscon_restart_handle(struct notifier_block *this,
struct syscon_reboot_context *ctx =
container_of(this, struct syscon_reboot_context,
restart_handler);
+ const struct reboot_mode_bits *mode_bits;
+
+ if (ctx->rd) {
+ if (mode < ARRAY_SIZE(ctx->rd->mode_bits) &&
+ ctx->rd->mode_bits[mode].valid)
+ mode_bits = &ctx->rd->mode_bits[mode];
+ else
+ mode_bits = &ctx->rd->catchall;
+ } else {
+ mode_bits = &ctx->catchall;
+ }
/* Issue the reboot */
- regmap_update_bits(ctx->map, ctx->offset, ctx->mask, ctx->value);
+ regmap_update_bits(ctx->map, mode_bits->offset, mode_bits->mask,
+ mode_bits->value);
mdelay(1000);
@@ -42,7 +67,6 @@ static int syscon_reboot_probe(struct platform_device *pdev)
{
struct syscon_reboot_context *ctx;
struct device *dev = &pdev->dev;
- int mask_err, value_err;
int priority;
int err;
@@ -60,24 +84,33 @@ static int syscon_reboot_probe(struct platform_device *pdev)
if (of_property_read_s32(pdev->dev.of_node, "priority", &priority))
priority = 192;
- if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset))
- if (of_property_read_u32(pdev->dev.of_node, "reg", &ctx->offset))
- return -EINVAL;
+ ctx->rd = of_device_get_match_data(dev);
+ if (!ctx->rd) {
+ int mask_err, value_err;
- value_err = of_property_read_u32(pdev->dev.of_node, "value", &ctx->value);
- mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask);
- if (value_err && mask_err) {
- dev_err(dev, "unable to read 'value' and 'mask'");
- return -EINVAL;
- }
+ if (of_property_read_u32(pdev->dev.of_node, "offset",
+ &ctx->catchall.offset) &&
+ of_property_read_u32(pdev->dev.of_node, "reg",
+ &ctx->catchall.offset))
+ return -EINVAL;
- if (value_err) {
- /* support old binding */
- ctx->value = ctx->mask;
- ctx->mask = 0xFFFFFFFF;
- } else if (mask_err) {
- /* support value without mask*/
- ctx->mask = 0xFFFFFFFF;
+ value_err = of_property_read_u32(pdev->dev.of_node, "value",
+ &ctx->catchall.value);
+ mask_err = of_property_read_u32(pdev->dev.of_node, "mask",
+ &ctx->catchall.mask);
+ if (value_err && mask_err) {
+ dev_err(dev, "unable to read 'value' and 'mask'");
+ return -EINVAL;
+ }
+
+ if (value_err) {
+ /* support old binding */
+ ctx->catchall.value = ctx->catchall.mask;
+ ctx->catchall.mask = 0xFFFFFFFF;
+ } else if (mask_err) {
+ /* support value without mask */
+ ctx->catchall.mask = 0xFFFFFFFF;
+ }
}
ctx->restart_handler.notifier_call = syscon_restart_handle;
@@ -89,7 +122,30 @@ static int syscon_reboot_probe(struct platform_device *pdev)
return err;
}
+static const struct reboot_data gs101_reboot_data = {
+ .mode_bits = {
+ [REBOOT_WARM] = {
+ .offset = 0x3a00, /* SYSTEM_CONFIGURATION */
+ .mask = 0x00000002, /* SWRESET_SYSTEM */
+ .value = 0x00000002,
+ .valid = true,
+ },
+ [REBOOT_SOFT] = {
+ .offset = 0x3a00, /* SYSTEM_CONFIGURATION */
+ .mask = 0x00000002, /* SWRESET_SYSTEM */
+ .value = 0x00000002,
+ .valid = true,
+ },
+ },
+ .catchall = {
+ .offset = 0x3e9c, /* PAD_CTRL_PWR_HOLD */
+ .mask = 0x00000100,
+ .value = 0x00000000,
+ },
+};
+
static const struct of_device_id syscon_reboot_of_match[] = {
+ { .compatible = "google,gs101-reboot", .data = &gs101_reboot_data },
{ .compatible = "syscon-reboot" },
{}
};
diff --git a/drivers/power/reset/tdx-ec-poweroff.c b/drivers/power/reset/tdx-ec-poweroff.c
new file mode 100644
index 000000000000..3302a127fce5
--- /dev/null
+++ b/drivers/power/reset/tdx-ec-poweroff.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Toradex Embedded Controller driver
+ *
+ * Copyright (C) 2025 Toradex
+ *
+ * Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define EC_CHIP_ID_REG 0x00
+#define EC_CHIP_ID_SMARC_IMX95 0x11
+#define EC_CHIP_ID_SMARC_IMX8MP 0x12
+
+#define EC_VERSION_REG_MAJOR 0x01
+#define EC_VERSION_REG_MINOR 0x02
+#define EC_ID_VERSION_LEN 3
+
+#define EC_CMD_REG 0xD0
+#define EC_CMD_POWEROFF 0x01
+#define EC_CMD_RESET 0x02
+
+#define EC_REG_MAX 0xD0
+
+static const struct regmap_range volatile_ranges[] = {
+ regmap_reg_range(EC_CMD_REG, EC_CMD_REG),
+};
+
+static const struct regmap_access_table volatile_table = {
+ .yes_ranges = volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(volatile_ranges),
+};
+
+static const struct regmap_range read_ranges[] = {
+ regmap_reg_range(EC_CHIP_ID_REG, EC_VERSION_REG_MINOR),
+};
+
+static const struct regmap_access_table read_table = {
+ .yes_ranges = read_ranges,
+ .n_yes_ranges = ARRAY_SIZE(read_ranges),
+};
+
+static const struct regmap_config regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = EC_REG_MAX,
+ .cache_type = REGCACHE_RBTREE,
+ .rd_table = &read_table,
+ .volatile_table = &volatile_table,
+};
+
+static int tdx_ec_cmd(struct regmap *regmap, u8 cmd)
+{
+ int err = regmap_write(regmap, EC_CMD_REG, cmd);
+
+ if (err)
+ dev_err(regmap_get_device(regmap), "Failed to send command 0x%02X: %d\n", cmd, err);
+
+ return err;
+}
+
+static int tdx_ec_power_off(struct sys_off_data *data)
+{
+ struct regmap *regmap = data->cb_data;
+ int err;
+
+ err = tdx_ec_cmd(regmap, EC_CMD_POWEROFF);
+
+ return err ? NOTIFY_BAD : NOTIFY_DONE;
+}
+
+static int tdx_ec_restart(struct sys_off_data *data)
+{
+ struct regmap *regmap = data->cb_data;
+ int err;
+
+ err = tdx_ec_cmd(regmap, EC_CMD_RESET);
+
+ return err ? NOTIFY_BAD : NOTIFY_DONE;
+}
+
+static int tdx_ec_register_power_off_restart(struct device *dev, struct regmap *regmap)
+{
+ int err;
+
+ err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART,
+ SYS_OFF_PRIO_FIRMWARE,
+ tdx_ec_restart, regmap);
+ if (err)
+ return err;
+
+ return devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF,
+ SYS_OFF_PRIO_FIRMWARE,
+ tdx_ec_power_off, regmap);
+}
+
+static int tdx_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ u8 reg_val[EC_ID_VERSION_LEN];
+ struct regmap *regmap;
+ int err;
+
+ regmap = devm_regmap_init_i2c(client, &regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ err = regmap_bulk_read(regmap, EC_CHIP_ID_REG, &reg_val, EC_ID_VERSION_LEN);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Cannot read id and version registers\n");
+
+ dev_info(dev, "Toradex Embedded Controller id %x - Firmware %u.%u\n",
+ reg_val[0], reg_val[1], reg_val[2]);
+
+ err = tdx_ec_register_power_off_restart(dev, regmap);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Cannot register system restart handler\n");
+
+ return 0;
+}
+
+static const struct of_device_id __maybe_unused of_tdx_ec_match[] = {
+ { .compatible = "toradex,smarc-ec" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_tdx_ec_match);
+
+static struct i2c_driver tdx_ec_driver = {
+ .probe = tdx_ec_probe,
+ .driver = {
+ .name = "toradex-smarc-ec",
+ .of_match_table = of_tdx_ec_match,
+ },
+};
+module_i2c_driver(tdx_ec_driver);
+
+MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>");
+MODULE_DESCRIPTION("Toradex SMARC Embedded Controller driver");
+MODULE_LICENSE("GPL");