| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Freescale ColdFire MCF5441x RCM power-on reason driver |
| * |
| * Copyright (C) 2026 Jean-Michel Hautbois <jeanmichel.hautbois@yoseli.org> |
| */ |
| |
| #include <linux/io.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/power/power_on_reason.h> |
| |
| /* |
| * Reset Status Register (RSR) layout, MCF54418 Reference Manual chapter |
| * 12.3.2. The register is 8-bit read-only and latches the cause (or |
| * causes) of the most recent reset until the next one occurs. |
| */ |
| #define MCF_RSR_SOFT 0x20 /* Last reset caused by software */ |
| #define MCF_RSR_LOC 0x10 /* Last reset caused by PLL loss of clock */ |
| #define MCF_RSR_POR 0x08 /* Last reset caused by power-on */ |
| #define MCF_RSR_EXT 0x04 /* Last reset caused by external pin */ |
| #define MCF_RSR_WDRCORE 0x02 /* Last reset caused by core watchdog */ |
| #define MCF_RSR_LOL 0x01 /* Last reset caused by PLL loss of lock */ |
| |
| #define MCF_RSR_KNOWN_CAUSES (MCF_RSR_POR | MCF_RSR_EXT | MCF_RSR_WDRCORE | \ |
| MCF_RSR_LOC | MCF_RSR_LOL | MCF_RSR_SOFT) |
| |
| struct mcf_rcm { |
| const char *reason; |
| }; |
| |
| /* |
| * Decode RSR into a power_on_reason string. |
| * |
| * The MCF5441x Reset Status Register can latch several cause bits at the |
| * same time (Reference Manual chapter 12.3.2: "one or more status bits |
| * may be set at the same time"). A power-on, for example, also resets |
| * the PLL and may co-flag LOC and LOL during the boot sequence. The |
| * power_on_reason ABI carries a single string, so this routine picks |
| * one cause; the chosen priority surfaces the most explanatory one for |
| * diagnostics: |
| * |
| * POR cold boot dominates any spurious co-flagged cause |
| * EXT operator action via the RESET pin |
| * WDRCORE core watchdog timeout, a fault to investigate |
| * LOC, LOL PLL clock or lock failure, hardware fault |
| * SOFT explicit software-requested reset |
| */ |
| static const char *mcf_rcm_decode(u8 rsr) |
| { |
| if (rsr & MCF_RSR_POR) |
| return POWER_ON_REASON_REGULAR; |
| if (rsr & MCF_RSR_EXT) |
| return POWER_ON_REASON_RST_BTN; |
| if (rsr & MCF_RSR_WDRCORE) |
| return POWER_ON_REASON_WATCHDOG; |
| if (rsr & (MCF_RSR_LOC | MCF_RSR_LOL)) |
| return POWER_ON_REASON_CPU_CLK_FAIL; |
| if (rsr & MCF_RSR_SOFT) |
| return POWER_ON_REASON_SOFTWARE; |
| return POWER_ON_REASON_UNKNOWN; |
| } |
| |
| static ssize_t power_on_reason_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct mcf_rcm *rcm = platform_get_drvdata(to_platform_device(dev)); |
| |
| return sysfs_emit(buf, "%s\n", rcm->reason); |
| } |
| static DEVICE_ATTR_RO(power_on_reason); |
| |
| static struct attribute *mcf_rcm_attrs[] = { |
| &dev_attr_power_on_reason.attr, |
| NULL, |
| }; |
| ATTRIBUTE_GROUPS(mcf_rcm); |
| |
| static int mcf_rcm_probe(struct platform_device *pdev) |
| { |
| struct mcf_rcm *rcm; |
| void __iomem *base; |
| u8 rsr; |
| |
| rcm = devm_kzalloc(&pdev->dev, sizeof(*rcm), GFP_KERNEL); |
| if (!rcm) |
| return -ENOMEM; |
| |
| base = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(base)) |
| return PTR_ERR(base); |
| |
| rsr = readb_relaxed(base); |
| rcm->reason = mcf_rcm_decode(rsr); |
| |
| platform_set_drvdata(pdev, rcm); |
| |
| if (!(rsr & MCF_RSR_KNOWN_CAUSES)) |
| dev_warn(&pdev->dev, "Unknown reset cause (RSR=0x%02x)\n", rsr); |
| else |
| dev_info(&pdev->dev, "Starting after %s (RSR=0x%02x)\n", |
| rcm->reason, rsr); |
| |
| return 0; |
| } |
| |
| static const struct platform_device_id mcf_rcm_id[] = { |
| { "mcf-rcm-reset" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(platform, mcf_rcm_id); |
| |
| static struct platform_driver mcf_rcm_driver = { |
| .probe = mcf_rcm_probe, |
| .id_table = mcf_rcm_id, |
| .driver = { |
| .name = "mcf-rcm-reset", |
| .dev_groups = mcf_rcm_groups, |
| }, |
| }; |
| module_platform_driver(mcf_rcm_driver); |
| |
| MODULE_AUTHOR("Jean-Michel Hautbois <jeanmichel.hautbois@yoseli.org>"); |
| MODULE_DESCRIPTION("Freescale ColdFire RCM power-on reason driver"); |
| MODULE_LICENSE("GPL"); |