1*380f91cdSJean-Michel Hautbois // SPDX-License-Identifier: GPL-2.0 2*380f91cdSJean-Michel Hautbois /* 3*380f91cdSJean-Michel Hautbois * Freescale ColdFire MCF5441x RCM power-on reason driver 4*380f91cdSJean-Michel Hautbois * 5*380f91cdSJean-Michel Hautbois * Copyright (C) 2026 Jean-Michel Hautbois <jeanmichel.hautbois@yoseli.org> 6*380f91cdSJean-Michel Hautbois */ 7*380f91cdSJean-Michel Hautbois 8*380f91cdSJean-Michel Hautbois #include <linux/io.h> 9*380f91cdSJean-Michel Hautbois #include <linux/mod_devicetable.h> 10*380f91cdSJean-Michel Hautbois #include <linux/module.h> 11*380f91cdSJean-Michel Hautbois #include <linux/platform_device.h> 12*380f91cdSJean-Michel Hautbois #include <linux/power/power_on_reason.h> 13*380f91cdSJean-Michel Hautbois 14*380f91cdSJean-Michel Hautbois /* 15*380f91cdSJean-Michel Hautbois * Reset Status Register (RSR) layout, MCF54418 Reference Manual chapter 16*380f91cdSJean-Michel Hautbois * 12.3.2. The register is 8-bit read-only and latches the cause (or 17*380f91cdSJean-Michel Hautbois * causes) of the most recent reset until the next one occurs. 18*380f91cdSJean-Michel Hautbois */ 19*380f91cdSJean-Michel Hautbois #define MCF_RSR_SOFT 0x20 /* Last reset caused by software */ 20*380f91cdSJean-Michel Hautbois #define MCF_RSR_LOC 0x10 /* Last reset caused by PLL loss of clock */ 21*380f91cdSJean-Michel Hautbois #define MCF_RSR_POR 0x08 /* Last reset caused by power-on */ 22*380f91cdSJean-Michel Hautbois #define MCF_RSR_EXT 0x04 /* Last reset caused by external pin */ 23*380f91cdSJean-Michel Hautbois #define MCF_RSR_WDRCORE 0x02 /* Last reset caused by core watchdog */ 24*380f91cdSJean-Michel Hautbois #define MCF_RSR_LOL 0x01 /* Last reset caused by PLL loss of lock */ 25*380f91cdSJean-Michel Hautbois 26*380f91cdSJean-Michel Hautbois #define MCF_RSR_KNOWN_CAUSES (MCF_RSR_POR | MCF_RSR_EXT | MCF_RSR_WDRCORE | \ 27*380f91cdSJean-Michel Hautbois MCF_RSR_LOC | MCF_RSR_LOL | MCF_RSR_SOFT) 28*380f91cdSJean-Michel Hautbois 29*380f91cdSJean-Michel Hautbois struct mcf_rcm { 30*380f91cdSJean-Michel Hautbois const char *reason; 31*380f91cdSJean-Michel Hautbois }; 32*380f91cdSJean-Michel Hautbois 33*380f91cdSJean-Michel Hautbois /* 34*380f91cdSJean-Michel Hautbois * Decode RSR into a power_on_reason string. 35*380f91cdSJean-Michel Hautbois * 36*380f91cdSJean-Michel Hautbois * The MCF5441x Reset Status Register can latch several cause bits at the 37*380f91cdSJean-Michel Hautbois * same time (Reference Manual chapter 12.3.2: "one or more status bits 38*380f91cdSJean-Michel Hautbois * may be set at the same time"). A power-on, for example, also resets 39*380f91cdSJean-Michel Hautbois * the PLL and may co-flag LOC and LOL during the boot sequence. The 40*380f91cdSJean-Michel Hautbois * power_on_reason ABI carries a single string, so this routine picks 41*380f91cdSJean-Michel Hautbois * one cause; the chosen priority surfaces the most explanatory one for 42*380f91cdSJean-Michel Hautbois * diagnostics: 43*380f91cdSJean-Michel Hautbois * 44*380f91cdSJean-Michel Hautbois * POR cold boot dominates any spurious co-flagged cause 45*380f91cdSJean-Michel Hautbois * EXT operator action via the RESET pin 46*380f91cdSJean-Michel Hautbois * WDRCORE core watchdog timeout, a fault to investigate 47*380f91cdSJean-Michel Hautbois * LOC, LOL PLL clock or lock failure, hardware fault 48*380f91cdSJean-Michel Hautbois * SOFT explicit software-requested reset 49*380f91cdSJean-Michel Hautbois */ 50*380f91cdSJean-Michel Hautbois static const char *mcf_rcm_decode(u8 rsr) 51*380f91cdSJean-Michel Hautbois { 52*380f91cdSJean-Michel Hautbois if (rsr & MCF_RSR_POR) 53*380f91cdSJean-Michel Hautbois return POWER_ON_REASON_REGULAR; 54*380f91cdSJean-Michel Hautbois if (rsr & MCF_RSR_EXT) 55*380f91cdSJean-Michel Hautbois return POWER_ON_REASON_RST_BTN; 56*380f91cdSJean-Michel Hautbois if (rsr & MCF_RSR_WDRCORE) 57*380f91cdSJean-Michel Hautbois return POWER_ON_REASON_WATCHDOG; 58*380f91cdSJean-Michel Hautbois if (rsr & (MCF_RSR_LOC | MCF_RSR_LOL)) 59*380f91cdSJean-Michel Hautbois return POWER_ON_REASON_CPU_CLK_FAIL; 60*380f91cdSJean-Michel Hautbois if (rsr & MCF_RSR_SOFT) 61*380f91cdSJean-Michel Hautbois return POWER_ON_REASON_SOFTWARE; 62*380f91cdSJean-Michel Hautbois return POWER_ON_REASON_UNKNOWN; 63*380f91cdSJean-Michel Hautbois } 64*380f91cdSJean-Michel Hautbois 65*380f91cdSJean-Michel Hautbois static ssize_t power_on_reason_show(struct device *dev, 66*380f91cdSJean-Michel Hautbois struct device_attribute *attr, char *buf) 67*380f91cdSJean-Michel Hautbois { 68*380f91cdSJean-Michel Hautbois struct mcf_rcm *rcm = platform_get_drvdata(to_platform_device(dev)); 69*380f91cdSJean-Michel Hautbois 70*380f91cdSJean-Michel Hautbois return sysfs_emit(buf, "%s\n", rcm->reason); 71*380f91cdSJean-Michel Hautbois } 72*380f91cdSJean-Michel Hautbois static DEVICE_ATTR_RO(power_on_reason); 73*380f91cdSJean-Michel Hautbois 74*380f91cdSJean-Michel Hautbois static struct attribute *mcf_rcm_attrs[] = { 75*380f91cdSJean-Michel Hautbois &dev_attr_power_on_reason.attr, 76*380f91cdSJean-Michel Hautbois NULL, 77*380f91cdSJean-Michel Hautbois }; 78*380f91cdSJean-Michel Hautbois ATTRIBUTE_GROUPS(mcf_rcm); 79*380f91cdSJean-Michel Hautbois 80*380f91cdSJean-Michel Hautbois static int mcf_rcm_probe(struct platform_device *pdev) 81*380f91cdSJean-Michel Hautbois { 82*380f91cdSJean-Michel Hautbois struct mcf_rcm *rcm; 83*380f91cdSJean-Michel Hautbois void __iomem *base; 84*380f91cdSJean-Michel Hautbois u8 rsr; 85*380f91cdSJean-Michel Hautbois 86*380f91cdSJean-Michel Hautbois rcm = devm_kzalloc(&pdev->dev, sizeof(*rcm), GFP_KERNEL); 87*380f91cdSJean-Michel Hautbois if (!rcm) 88*380f91cdSJean-Michel Hautbois return -ENOMEM; 89*380f91cdSJean-Michel Hautbois 90*380f91cdSJean-Michel Hautbois base = devm_platform_ioremap_resource(pdev, 0); 91*380f91cdSJean-Michel Hautbois if (IS_ERR(base)) 92*380f91cdSJean-Michel Hautbois return PTR_ERR(base); 93*380f91cdSJean-Michel Hautbois 94*380f91cdSJean-Michel Hautbois rsr = readb_relaxed(base); 95*380f91cdSJean-Michel Hautbois rcm->reason = mcf_rcm_decode(rsr); 96*380f91cdSJean-Michel Hautbois 97*380f91cdSJean-Michel Hautbois platform_set_drvdata(pdev, rcm); 98*380f91cdSJean-Michel Hautbois 99*380f91cdSJean-Michel Hautbois if (!(rsr & MCF_RSR_KNOWN_CAUSES)) 100*380f91cdSJean-Michel Hautbois dev_warn(&pdev->dev, "Unknown reset cause (RSR=0x%02x)\n", rsr); 101*380f91cdSJean-Michel Hautbois else 102*380f91cdSJean-Michel Hautbois dev_info(&pdev->dev, "Starting after %s (RSR=0x%02x)\n", 103*380f91cdSJean-Michel Hautbois rcm->reason, rsr); 104*380f91cdSJean-Michel Hautbois 105*380f91cdSJean-Michel Hautbois return 0; 106*380f91cdSJean-Michel Hautbois } 107*380f91cdSJean-Michel Hautbois 108*380f91cdSJean-Michel Hautbois static const struct platform_device_id mcf_rcm_id[] = { 109*380f91cdSJean-Michel Hautbois { "mcf-rcm-reset" }, 110*380f91cdSJean-Michel Hautbois { } 111*380f91cdSJean-Michel Hautbois }; 112*380f91cdSJean-Michel Hautbois MODULE_DEVICE_TABLE(platform, mcf_rcm_id); 113*380f91cdSJean-Michel Hautbois 114*380f91cdSJean-Michel Hautbois static struct platform_driver mcf_rcm_driver = { 115*380f91cdSJean-Michel Hautbois .probe = mcf_rcm_probe, 116*380f91cdSJean-Michel Hautbois .id_table = mcf_rcm_id, 117*380f91cdSJean-Michel Hautbois .driver = { 118*380f91cdSJean-Michel Hautbois .name = "mcf-rcm-reset", 119*380f91cdSJean-Michel Hautbois .dev_groups = mcf_rcm_groups, 120*380f91cdSJean-Michel Hautbois }, 121*380f91cdSJean-Michel Hautbois }; 122*380f91cdSJean-Michel Hautbois module_platform_driver(mcf_rcm_driver); 123*380f91cdSJean-Michel Hautbois 124*380f91cdSJean-Michel Hautbois MODULE_AUTHOR("Jean-Michel Hautbois <jeanmichel.hautbois@yoseli.org>"); 125*380f91cdSJean-Michel Hautbois MODULE_DESCRIPTION("Freescale ColdFire RCM power-on reason driver"); 126*380f91cdSJean-Michel Hautbois MODULE_LICENSE("GPL"); 127