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