From nobody Mon Jun 8 04:26:42 2026 Received: from mslow3.mail.gandi.net (mslow3.mail.gandi.net [217.70.178.249]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 28E1F3C9ECF; Tue, 2 Jun 2026 09:04:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.70.178.249 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780391049; cv=none; b=f3Hcs2JoQaweJLumljdsTcvLXFMqzAsLYr6JGx9Qr3PTB0fVCo6Ye7M7n6nWBDsDJg2+qU7pWDAVjcQ/p0qaco2n1hXhoy4MVjYW854rfcFzK9LYv9oTxVkNgLdjOKws425S9/VXSfQdsBpP1VNMZiCMKwkQPefBhlBonLNVsqQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780391049; c=relaxed/simple; bh=0KQv6IrTM2a7bXH5lPJBbxaxouWJOHa0ZQXoIE3nwSM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=mw8IMHBFZe8tZsg7OCBUxFORGLbDfIGscydUZMZIPx+H1lSDWctB8ZFlyyLz72qAigSrTQ8ZiSjOG9IySvq4CRgY+WjekANttmRz5IYFDpSD6YcxxZOB37VqLFYZ8vK58HVRSWnOrwuPjhQm468Od/FlgywGo01FOiLWPwlC648= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=yoseli.org; spf=pass smtp.mailfrom=yoseli.org; dkim=pass (2048-bit key) header.d=yoseli.org header.i=@yoseli.org header.b=baKCLg+t; arc=none smtp.client-ip=217.70.178.249 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=yoseli.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=yoseli.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=yoseli.org header.i=@yoseli.org header.b="baKCLg+t" Received: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net [IPv6:2001:4b98:dc4:8::224]) by mslow3.mail.gandi.net (Postfix) with ESMTP id 7D613583068; Tue, 2 Jun 2026 08:36:43 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id B38103E9B8; Tue, 2 Jun 2026 08:36:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yoseli.org; s=gm1; t=1780389396; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=JqU7jnWcqn7yOT1QyQWxmDU8aE2xP84u1qsBofkWMpw=; b=baKCLg+tqPXwgTpAqoziHbXhW/n/fqtwMamEsLwfPSOXCIdsUxNqct1LkIaf2zhFDwks/6 XiLBomLs/fA9Rm7j16L8afoNqnncoXLV/lC8/GDpI2WjHXRHRrMSXyj/IhpX+b+kKCzebj 79/F6pQl2HF+WwdiNqs+EfeEbovCW3zDjNTfN7AZYFevVU9YWcqvGHH0l9WgXpyHI9dFjR duzuatDCqdZauzije22/DaRiDgMGNWpwY6zjPx7FeznCqIG3f5QmpfAZovVpCpB0qscJ2O 70OiO6XgpiwSXfPSsF9bBzCI1bxEPa9bjzpPzeKoyGOh5aBZ9dMbEDnBeYbcgA== From: Jean-Michel Hautbois Date: Tue, 02 Jun 2026 10:36:17 +0200 Subject: [PATCH 1/2] rtc: m5441x: add MCF5441x on-chip RTC driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260602-jmh-upstream-coldfire-rtc-v1-1-1e129a177d2f@yoseli.org> References: <20260602-jmh-upstream-coldfire-rtc-v1-0-1e129a177d2f@yoseli.org> In-Reply-To: <20260602-jmh-upstream-coldfire-rtc-v1-0-1e129a177d2f@yoseli.org> To: Alexandre Belloni , Greg Ungerer , Geert Uytterhoeven Cc: linux-kernel@vger.kernel.org, linux-rtc@vger.kernel.org, linux-m68k@lists.linux-m68k.org, Jean-Michel Hautbois X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=ed25519-sha256; t=1780389394; l=22521; i=jeanmichel.hautbois@yoseli.org; s=20240925; h=from:subject:message-id; bh=0KQv6IrTM2a7bXH5lPJBbxaxouWJOHa0ZQXoIE3nwSM=; b=pP8JFcihecuGX3Cudb9gXfQJ2gLK7l4oIjwH1Rwb2tIw4YSJf1HId0KEVVeRTd4xoVtp4IXDo S6VDrVRxDX8ANcAxJ3cDbyG1CViUsxx4rDdIBWz4FmMIOmAqprXMnAg X-Developer-Key: i=jeanmichel.hautbois@yoseli.org; a=ed25519; pk=MsMTVmoV69wLIlSkHlFoACIMVNQFyvJzvsJSQsn/kq4= X-GND-Sasl: jeanmichel.hautbois@yoseli.org X-GND-State: clean X-GND-Score: -100 X-GND-Cause: dmFkZTGvNDiYa/hx9c8hdRhhDnkwsvMQ5gAnCz3mUnCTXhBfGqEYbcoiPInf3ZzfYD8pCTytpullW1eS8dtPS4emIEf++eMS7TPzTKnJAELf7NeTdYngTyAOXKhbIh3DXKXDcMEPpMeEiiryNpNd7NxkqwwUoOtZQGTNUXGzmxWIZfjfDaXflcR9LrWfNI64s+a8PZYgyimCHSW3cqkKb63BJXv1zK6eufxGuQ92S8huPZTajV26/G4dcm1C/8GrINIm6P1DivwYYUDu6+tlGpW4RvGf/WDSApZt7/dD1Q02bnmLlA9k6q4tdaiPuAgyBzUc7EfLMfw9XfP/v72KBp58Mx+XY9Pozy5yMonPziz37oMFo0hxybLuFjjIDoaFT4wIjb3v7BZhbvgVi3ek/dwUH7txIdRCIp+F1ecOkb7SK1N1MYFYxHiGOLrnN8meWMDk0cGhEhQlF+d275gYeOelABvutJmqJLk5SL4pIXKn+wW6RLubOn3wyuzgO/wGzEjaePgDjTjFf6g6ETrHDL962RO3Hyc/r0JVvfBleSLeisvZ1VPg+A3RmVjVPNCHX/hkmktmk/0zKUkjh/HbuZbfTXKld0f/fucU19F8gLsSF5JaTueJjnUuSTi8uR5Qv2wBpGb74XlKau4YlH/rJergw8tp53OtCKaw9T52cVhZVvOUKQ Add an rtc-class driver for the Freescale MCF5441x on-chip "robust" RTC. It provides the time/calendar and alarm, and exposes the 2KB battery-backed standby RAM through the nvmem framework so userspace can preserve data across a main-power loss (the RAM is retained while VSTBY_RTC is supplied). Register and standby-RAM writes go through the RTC_CR[WE] knock sequence; the base-2112 year encoding and register map follow the MCF54418 reference manual. Based on the out-of-tree Freescale 3.0.x rtc-m5441x driver, rewritten for the current RTC and nvmem APIs. Signed-off-by: Jean-Michel Hautbois --- MAINTAINERS | 6 + drivers/rtc/Kconfig | 12 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-m5441x.c | 582 +++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 601 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 9ec290e38b44..e8001adfb6ca 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10196,6 +10196,12 @@ S: Maintained F: drivers/mmc/host/sdhci-esdhc-mcf.c F: include/linux/platform_data/mmc-esdhc-mcf.h =20 +FREESCALE COLDFIRE M5441X RTC DRIVER +M: Jean-Michel Hautbois +L: linux-rtc@vger.kernel.org +S: Maintained +F: drivers/rtc/rtc-m5441x.c + FREESCALE DIU FRAMEBUFFER DRIVER M: Timur Tabi L: linux-fbdev@vger.kernel.org diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 364afc73f8ab..45362247a56c 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1427,6 +1427,18 @@ config RTC_DRV_NTXEC =20 comment "on-CPU RTC drivers" =20 +config RTC_DRV_M5441X + tristate "Freescale MCF5441x on-chip RTC" + depends on M5441x || COMPILE_TEST + help + If you say yes here you get support for the on-chip "robust" + real time clock found on Freescale MCF5441x ColdFire SoCs, + including its 2KB battery-backed standby RAM exposed through the + nvmem framework. + + This driver can also be built as a module. If so, the module + will be called rtc-m5441x. + config RTC_DRV_ASM9260 tristate "Alphascale asm9260 RTC" depends on MACH_ASM9260 || COMPILE_TEST diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6cf7e066314e..4c7cb67fbc3f 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -93,6 +93,7 @@ obj-$(CONFIG_RTC_DRV_M41T94) +=3D rtc-m41t94.o obj-$(CONFIG_RTC_DRV_M48T35) +=3D rtc-m48t35.o obj-$(CONFIG_RTC_DRV_M48T59) +=3D rtc-m48t59.o obj-$(CONFIG_RTC_DRV_M48T86) +=3D rtc-m48t86.o +obj-$(CONFIG_RTC_DRV_M5441X) +=3D rtc-m5441x.o obj-$(CONFIG_RTC_DRV_MA35D1) +=3D rtc-ma35d1.o obj-$(CONFIG_RTC_DRV_MACSMC) +=3D rtc-macsmc.o obj-$(CONFIG_RTC_DRV_MAX31335) +=3D rtc-max31335.o diff --git a/drivers/rtc/rtc-m5441x.c b/drivers/rtc/rtc-m5441x.c new file mode 100644 index 000000000000..b3d1bf4c1153 --- /dev/null +++ b/drivers/rtc/rtc-m5441x.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RTC driver for the Freescale MCF5441x on-chip "robust" RTC. + * + * Exposes the time/calendar and alarm, plus the 2KB battery-backed + * standby RAM as an nvmem device. The standby RAM survives main-power + * loss while VSTBY_RTC is supplied, which makes it usable to persist a + * reset/reboot counter across power cycles. + * + * Register layout and the base-2112 year encoding are taken from the + * MCF54418 reference manual (chapter "Robust Real Time Clock"). + * + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2026 Jean-Michel Hautbois + * + * Based on the out-of-tree Freescale 3.0.x rtc-m5441x driver by + * Lanttor Guo , rewritten for the current + * RTC and nvmem frameworks. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register offsets from the RTC base. */ +#define M5441X_RTC_YEARMON 0x00 +#define M5441X_RTC_DAYS 0x02 +#define M5441X_RTC_HOURMIN 0x04 +#define M5441X_RTC_SECONDS 0x06 +#define M5441X_RTC_ALM_YRMON 0x08 +#define M5441X_RTC_ALM_DAYS 0x0a +#define M5441X_RTC_ALM_HM 0x0c +#define M5441X_RTC_ALM_SEC 0x0e +#define M5441X_RTC_CR 0x10 +#define M5441X_RTC_SR 0x12 +#define M5441X_RTC_ISR 0x14 +#define M5441X_RTC_IER 0x16 +#define M5441X_RTC_CFG_DATA 0x20 + +/* RTC_CR bits. Only WE is freely writable; the rest need the WE knock. */ +#define M5441X_RTC_CR_RSVD 0x8000 /* bit 15, reserved, must be set */ +#define M5441X_RTC_CR_BCDEN 0x0080 /* 0 =3D binary, 1 =3D BCD time/date */ +#define M5441X_RTC_CR_DSTEN 0x0040 /* daylight-saving auto-adjust */ +#define M5441X_RTC_CR_AM_MASK 0x000c /* alarm match field */ +#define M5441X_RTC_CR_AM_FULL 0x000c /* match s,m,h,day,month,year */ +#define M5441X_RTC_CR_WE_MASK 0x0003 /* write-enable knock field */ + +#define M5441X_RTC_SR_INVAL 0x0001 /* time invalid / changing */ +#define M5441X_RTC_SR_WPE 0x0010 /* write protection enabled */ +#define M5441X_RTC_ISR_ALM 0x0004 /* alarm interrupt status/enable */ +#define M5441X_RTC_IER_RSVD 0x0001 /* IER/ISR bit 0 reserved, must be set = */ +#define M5441X_RTC_CFG_OSCEN 0x0008 /* oscillator enable */ + +/* Battery-backed standby RAM: 2KB at this offset within the block. */ +#define M5441X_RTC_SRAM_OFFSET 0x40 +#define M5441X_RTC_SRAM_SIZE 2048 +#define M5441X_RTC_SRAM_CHUNK 64 /* lock-drop granularity for SRAM I/O */ + +#define M5441X_RTC_YEAR_BASE 2112 + +struct m5441x_rtc { + struct rtc_device *rtc; + void __iomem *base; + spinlock_t lock; /* serialises register access + WP window */ + bool osc_dead; /* 32kHz oscillator never started; no timekeeping */ +}; + +static inline u16 rtc_rd(struct m5441x_rtc *p, unsigned int reg) +{ + return ioread16be(p->base + reg); +} + +static inline void rtc_wr(struct m5441x_rtc *p, unsigned int reg, u16 val) +{ + iowrite16be(val, p->base + reg); +} + +/* + * Issue the RTC_CR[WE] knock (00 -> 01 -> 11 -> 10) that opens the + * write-protection window. Writes to the registers and the standby RAM + * are rejected unless this sequence has been issued; the space then + * stays writable for ~2 seconds. Caller holds p->lock. + */ +static void m5441x_rtc_knock(struct m5441x_rtc *p) +{ + rtc_wr(p, M5441X_RTC_CR, 0x0000); + rtc_wr(p, M5441X_RTC_CR, 0x0001); + rtc_wr(p, M5441X_RTC_CR, 0x0003); + rtc_wr(p, M5441X_RTC_CR, 0x0002); +} + +/* Open the write window only if protection is currently enabled. */ +static void m5441x_rtc_unlock(struct m5441x_rtc *p) +{ + if (rtc_rd(p, M5441X_RTC_SR) & M5441X_RTC_SR_WPE) + m5441x_rtc_knock(p); +} + +/* + * Write the data bits of RTC_CR (BCDEN/DSTEN/AM). Caller holds p->lock + * and must have opened the write window first. The reserved bit 15 is + * kept set and the WE field kept clear so the write updates the data + * bits without re-arming write protection (a lone WE=3D10 would). + */ +static void m5441x_rtc_write_cr(struct m5441x_rtc *p, u16 val) +{ + val &=3D ~M5441X_RTC_CR_WE_MASK; + val |=3D M5441X_RTC_CR_RSVD; + rtc_wr(p, M5441X_RTC_CR, val); +} + +/* + * The time counters are unstable for an oscillator cycle either side of + * the one-second boundary. RTC_SR[INVAL] flags this; reads during the + * window return 0xffff and writes are nullified. Spin until it clears. + * The window is only a couple of 32kHz cycles (~60us), so bound the + * busy-wait tightly: it runs with the lock held and interrupts off. + * Caller holds p->lock. + */ +static int m5441x_rtc_wait_valid(struct m5441x_rtc *p) +{ + unsigned int tries =3D 10; + + while (rtc_rd(p, M5441X_RTC_SR) & M5441X_RTC_SR_INVAL) { + if (!--tries) + return -EIO; + udelay(10); + } + + return 0; +} + +static int m5441x_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct m5441x_rtc *p =3D dev_get_drvdata(dev); + u16 yearmon, days, hourmin, seconds; + unsigned int tries =3D 3; + unsigned long flags; + int ret; + + /* + * Without a running oscillator the counters are frozen; refuse the + * read rather than hand userspace a stale time that hwclock --hctosys + * would propagate into the system clock. + */ + if (p->osc_dead) + return -ENODATA; + + /* + * INVAL only guarantees the counters are stable at the moment it is + * checked; it does not freeze them across the four reads. If a 1Hz + * tick re-asserts INVAL mid-burst the fields can straddle a second + * boundary (e.g. 23:59:59 -> 00:00:00), so re-check it afterwards and + * retry the whole burst on observed re-entry. + */ + spin_lock_irqsave(&p->lock, flags); + do { + ret =3D m5441x_rtc_wait_valid(p); + if (ret) + break; + yearmon =3D rtc_rd(p, M5441X_RTC_YEARMON); + days =3D rtc_rd(p, M5441X_RTC_DAYS); + hourmin =3D rtc_rd(p, M5441X_RTC_HOURMIN); + seconds =3D rtc_rd(p, M5441X_RTC_SECONDS); + /* Stable across the whole burst -> the snapshot is coherent. */ + if (!(rtc_rd(p, M5441X_RTC_SR) & M5441X_RTC_SR_INVAL)) + break; + ret =3D -EAGAIN; + } while (--tries); + spin_unlock_irqrestore(&p->lock, flags); + + if (ret) + return ret; + + tm->tm_year =3D M5441X_RTC_YEAR_BASE + (s8)((yearmon >> 8) & 0xff) - 1900; + tm->tm_mon =3D (yearmon & 0xff) - 1; + tm->tm_mday =3D days & 0xff; + tm->tm_wday =3D (days >> 8) & 0x7; + tm->tm_hour =3D (hourmin >> 8) & 0x1f; + tm->tm_min =3D hourmin & 0x3f; + tm->tm_sec =3D seconds & 0x3f; + + return 0; +} + +static int m5441x_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct m5441x_rtc *p =3D dev_get_drvdata(dev); + s8 yoff =3D (tm->tm_year + 1900) - M5441X_RTC_YEAR_BASE; + unsigned int tries =3D 3; + unsigned long flags; + int ret; + + /* A frozen oscillator would never latch the new time; reject it. */ + if (p->osc_dead) + return -ENODATA; + + /* + * Writes are nullified while INVAL is asserted. wait_valid() clears + * the path before the burst, but the knock plus four writes can cross + * a second boundary on a slow RTC bus, silently dropping fields. + * Re-check INVAL afterwards and rewrite on observed re-entry. + */ + spin_lock_irqsave(&p->lock, flags); + do { + ret =3D m5441x_rtc_wait_valid(p); + if (ret) + break; + m5441x_rtc_unlock(p); + rtc_wr(p, M5441X_RTC_YEARMON, + ((u16)((u8)yoff) << 8) | ((tm->tm_mon + 1) & 0xff)); + rtc_wr(p, M5441X_RTC_DAYS, + ((tm->tm_wday & 0x7) << 8) | (tm->tm_mday & 0x1f)); + rtc_wr(p, M5441X_RTC_HOURMIN, + ((tm->tm_hour & 0x1f) << 8) | (tm->tm_min & 0x3f)); + rtc_wr(p, M5441X_RTC_SECONDS, tm->tm_sec & 0x3f); + /* No boundary crossed mid-burst -> the writes all took. */ + if (!(rtc_rd(p, M5441X_RTC_SR) & M5441X_RTC_SR_INVAL)) + break; + ret =3D -EAGAIN; + } while (--tries); + spin_unlock_irqrestore(&p->lock, flags); + + return ret; +} + +static int m5441x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *al= rm) +{ + struct m5441x_rtc *p =3D dev_get_drvdata(dev); + u16 yearmon, days, hourmin, seconds, ier, isr; + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + yearmon =3D rtc_rd(p, M5441X_RTC_ALM_YRMON); + days =3D rtc_rd(p, M5441X_RTC_ALM_DAYS); + hourmin =3D rtc_rd(p, M5441X_RTC_ALM_HM); + seconds =3D rtc_rd(p, M5441X_RTC_ALM_SEC); + ier =3D rtc_rd(p, M5441X_RTC_IER); + isr =3D rtc_rd(p, M5441X_RTC_ISR); + spin_unlock_irqrestore(&p->lock, flags); + + alrm->time.tm_year =3D M5441X_RTC_YEAR_BASE + + (s8)((yearmon >> 8) & 0xff) - 1900; + alrm->time.tm_mon =3D (yearmon & 0xff) - 1; + alrm->time.tm_mday =3D days & 0xff; + alrm->time.tm_hour =3D (hourmin >> 8) & 0x1f; + alrm->time.tm_min =3D hourmin & 0x3f; + alrm->time.tm_sec =3D seconds & 0x3f; + alrm->enabled =3D !!(ier & M5441X_RTC_ISR_ALM); + alrm->pending =3D !!(isr & M5441X_RTC_ISR_ALM); + + return 0; +} + +static int m5441x_rtc_alarm_irq_enable(struct device *dev, unsigned int en= abled) +{ + struct m5441x_rtc *p =3D dev_get_drvdata(dev); + unsigned long flags; + u16 ier; + + spin_lock_irqsave(&p->lock, flags); + m5441x_rtc_unlock(p); + ier =3D rtc_rd(p, M5441X_RTC_IER); + if (enabled) { + /* + * The comparator latches ISR_ALM even while IER masks delivery, + * so a match from a previous arming (or one surviving a reboot) + * would fire the moment IER is unmasked. Clear it first. + */ + rtc_wr(p, M5441X_RTC_ISR, M5441X_RTC_ISR_ALM); + ier |=3D M5441X_RTC_ISR_ALM; + } else { + ier &=3D ~M5441X_RTC_ISR_ALM; + } + rtc_wr(p, M5441X_RTC_IER, ier); + spin_unlock_irqrestore(&p->lock, flags); + + return 0; +} + +static int m5441x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alr= m) +{ + struct m5441x_rtc *p =3D dev_get_drvdata(dev); + s8 yoff =3D (alrm->time.tm_year + 1900) - M5441X_RTC_YEAR_BASE; + unsigned long flags; + u16 ier, cr; + + spin_lock_irqsave(&p->lock, flags); + m5441x_rtc_unlock(p); + /* acknowledge any pending alarm (write-1-to-clear) */ + rtc_wr(p, M5441X_RTC_ISR, M5441X_RTC_ISR_ALM); + + /* + * Match the full date so the alarm is a one-shot rather than the + * hardware default of a daily hour:minute:second match. + */ + cr =3D rtc_rd(p, M5441X_RTC_CR); + m5441x_rtc_write_cr(p, (cr & ~M5441X_RTC_CR_AM_MASK) | + M5441X_RTC_CR_AM_FULL); + + rtc_wr(p, M5441X_RTC_ALM_YRMON, + ((u16)((u8)yoff) << 8) | ((alrm->time.tm_mon + 1) & 0xff)); + /* RTC_ALM_DAYS has no day-of-week field; bits [15:8] are reserved. */ + rtc_wr(p, M5441X_RTC_ALM_DAYS, alrm->time.tm_mday & 0x1f); + rtc_wr(p, M5441X_RTC_ALM_HM, + ((alrm->time.tm_hour & 0x1f) << 8) | (alrm->time.tm_min & 0x3f)); + rtc_wr(p, M5441X_RTC_ALM_SEC, alrm->time.tm_sec & 0x3f); + + ier =3D rtc_rd(p, M5441X_RTC_IER); + if (alrm->enabled) + ier |=3D M5441X_RTC_ISR_ALM; + else + ier &=3D ~M5441X_RTC_ISR_ALM; + rtc_wr(p, M5441X_RTC_IER, ier); + spin_unlock_irqrestore(&p->lock, flags); + + return 0; +} + +static irqreturn_t m5441x_rtc_irq(int irq, void *dev_id) +{ + struct m5441x_rtc *p =3D dev_id; + u16 status; + + spin_lock(&p->lock); + /* + * Mask the reserved bit 0 (always reads set) so only a real source + * can claim the interrupt. + */ + status =3D rtc_rd(p, M5441X_RTC_ISR) & rtc_rd(p, M5441X_RTC_IER) & + ~M5441X_RTC_IER_RSVD; + if (status) { + m5441x_rtc_unlock(p); + rtc_wr(p, M5441X_RTC_ISR, status); /* write-1-to-clear */ + } + spin_unlock(&p->lock); + + if (!status) + return IRQ_NONE; + + if (status & M5441X_RTC_ISR_ALM) + rtc_update_irq(p->rtc, 1, RTC_AF | RTC_IRQF); + + /* + * A hardware source was acknowledged above; claim the IRQ even when it + * was not the alarm so genirq's spurious detector does not disable the + * shared line. + */ + return IRQ_HANDLED; +} + +static int m5441x_rtc_nvram_read(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct m5441x_rtc *p =3D priv; + u8 *buf =3D val; + size_t done; + + /* + * In-kernel nvmem_device_read() forwards offset/bytes verbatim, so + * range-check here rather than trust the caller. + */ + if (offset >=3D M5441X_RTC_SRAM_SIZE || + bytes > M5441X_RTC_SRAM_SIZE - offset) + return -EINVAL; + + /* + * Process the transfer in chunks, releasing the lock between them, so + * a full 2KB access does not keep hard interrupts disabled across + * thousands of slow on-chip MMIO cycles and wreck IRQ latency. + */ + for (done =3D 0; done < bytes; done +=3D M5441X_RTC_SRAM_CHUNK) { + size_t chunk =3D min_t(size_t, bytes - done, M5441X_RTC_SRAM_CHUNK); + unsigned long flags; + size_t i; + + spin_lock_irqsave(&p->lock, flags); + for (i =3D 0; i < chunk; i++) + buf[done + i] =3D ioread8(p->base + M5441X_RTC_SRAM_OFFSET + + offset + done + i); + spin_unlock_irqrestore(&p->lock, flags); + } + + return 0; +} + +static int m5441x_rtc_nvram_write(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct m5441x_rtc *p =3D priv; + u8 *buf =3D val; + size_t done; + + /* See the read path: validate the range, the core does not. */ + if (offset >=3D M5441X_RTC_SRAM_SIZE || + bytes > M5441X_RTC_SRAM_SIZE - offset) + return -EINVAL; + + /* + * Chunk the write and drop the lock between chunks (see the read + * path). The write window stays open for ~2 seconds, far longer than + * one chunk takes, so re-knock at the start of each chunk to keep it + * open across the lock-drop. + */ + for (done =3D 0; done < bytes; done +=3D M5441X_RTC_SRAM_CHUNK) { + size_t chunk =3D min_t(size_t, bytes - done, M5441X_RTC_SRAM_CHUNK); + unsigned long flags; + size_t i; + + spin_lock_irqsave(&p->lock, flags); + m5441x_rtc_knock(p); + for (i =3D 0; i < chunk; i++) + iowrite8(buf[done + i], p->base + M5441X_RTC_SRAM_OFFSET + + offset + done + i); + spin_unlock_irqrestore(&p->lock, flags); + } + + return 0; +} + +static const struct rtc_class_ops m5441x_rtc_ops =3D { + .read_time =3D m5441x_rtc_read_time, + .set_time =3D m5441x_rtc_set_time, + .read_alarm =3D m5441x_rtc_read_alarm, + .set_alarm =3D m5441x_rtc_set_alarm, + .alarm_irq_enable =3D m5441x_rtc_alarm_irq_enable, +}; + +static int m5441x_rtc_probe(struct platform_device *pdev) +{ + struct nvmem_config nvmem_cfg =3D { + .name =3D "m5441x_rtc_sram", + .type =3D NVMEM_TYPE_BATTERY_BACKED, + .word_size =3D 1, + .stride =3D 1, + .size =3D M5441X_RTC_SRAM_SIZE, + .reg_read =3D m5441x_rtc_nvram_read, + .reg_write =3D m5441x_rtc_nvram_write, + }; + struct m5441x_rtc *p; + unsigned long flags; + bool osc_dead =3D false; + int irq, ret; + u16 cr, cfg; + + p =3D devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + spin_lock_init(&p->lock); + + p->base =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(p->base)) + return PTR_ERR(p->base); + + platform_set_drvdata(pdev, p); + + /* Enable the oscillator if needed and mask/clear stale interrupts. */ + spin_lock_irqsave(&p->lock, flags); + m5441x_rtc_unlock(p); + /* + * Force binary, DST-free time/date encoding. The driver does not + * handle the BCD format, and Linux expects the RTC to keep monotonic + * time with local-time/DST conversion done in userspace, so clear + * BCDEN and DSTEN in case a bootloader or prior OS left them set. Also + * default the alarm match field to a full one-shot match: the reset + * value (AM=3D00) is a daily hh:mm:ss match, which would fire on the + * wrong day if userspace enables the alarm via RTC_AIE_ON without + * first programming it through set_alarm(). + */ + cr =3D rtc_rd(p, M5441X_RTC_CR); + cr &=3D ~(M5441X_RTC_CR_BCDEN | M5441X_RTC_CR_DSTEN | M5441X_RTC_CR_AM_MA= SK); + cr |=3D M5441X_RTC_CR_AM_FULL; + m5441x_rtc_write_cr(p, cr); + + /* + * The alarm registers are battery-backed and survive a power cycle, + * so a previously armed match could re-fire before set_alarm() runs. + * Clear them now (the write window is open). + */ + rtc_wr(p, M5441X_RTC_ALM_YRMON, 0); + rtc_wr(p, M5441X_RTC_ALM_DAYS, 0); + rtc_wr(p, M5441X_RTC_ALM_HM, 0); + rtc_wr(p, M5441X_RTC_ALM_SEC, 0); + + /* + * Enable the 32kHz oscillator that drives the time counters. This only + * checks that the OSCEN enable bit latches: when it reads back clear + * the oscillator block rejected the enable, which in practice means no + * RTC crystal is fitted and the counters will never advance. It cannot + * tell a dead crystal from one still starting up, so it is a best- + * effort hint, not a liveness guarantee. Warn but keep probing, since + * the battery-backed standby RAM stays usable without the oscillator. + */ + cfg =3D rtc_rd(p, M5441X_RTC_CFG_DATA); + if (!(cfg & M5441X_RTC_CFG_OSCEN)) { + rtc_wr(p, M5441X_RTC_CFG_DATA, cfg | M5441X_RTC_CFG_OSCEN); + if (!(rtc_rd(p, M5441X_RTC_CFG_DATA) & M5441X_RTC_CFG_OSCEN)) + osc_dead =3D true; + } + + /* Mask every source but keep the reserved bit 0 set as required. */ + rtc_wr(p, M5441X_RTC_IER, M5441X_RTC_IER_RSVD); + rtc_wr(p, M5441X_RTC_ISR, rtc_rd(p, M5441X_RTC_ISR)); + spin_unlock_irqrestore(&p->lock, flags); + + /* + * Keep the device (and its standby-RAM nvmem) usable, but record the + * dead oscillator so read_time/set_time refuse to expose a clock that + * cannot tick. + */ + p->osc_dead =3D osc_dead; + if (osc_dead) + dev_warn(&pdev->dev, + "RTC oscillator enable did not latch; timekeeping unavailable (no 32kH= z crystal?)\n"); + + p->rtc =3D devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(p->rtc)) + return PTR_ERR(p->rtc); + + p->rtc->ops =3D &m5441x_rtc_ops; + p->rtc->range_min =3D mktime64(1984, 1, 1, 0, 0, 0); + p->rtc->range_max =3D mktime64(2239, 12, 31, 23, 59, 59); + + irq =3D platform_get_irq_optional(pdev, 0); + if (irq > 0) { + ret =3D devm_request_irq(&pdev->dev, irq, m5441x_rtc_irq, 0, + dev_name(&pdev->dev), p); + if (ret) { + dev_err(&pdev->dev, + "failed to request IRQ %d: %d, alarm disabled\n", + irq, ret); + clear_bit(RTC_FEATURE_ALARM, p->rtc->features); + } else { + /* + * The alarm comparator runs on standby power, so let it + * wake a suspended system. The PM core keeps the IRQ + * enabled across suspend once it is the device's wake + * IRQ; both are device-managed. + */ + devm_device_init_wakeup(&pdev->dev); + devm_pm_set_wake_irq(&pdev->dev, irq); + } + } else { + clear_bit(RTC_FEATURE_ALARM, p->rtc->features); + } + + /* + * Register the RTC device before the nvmem provider: + * devm_rtc_nvmem_register() copies rtc->owner into the nvmem config, + * and that field is only assigned by devm_rtc_register_device(). + * Registering nvmem first would leave its owner NULL and fail to pin + * this module while standby-RAM consumers hold references. + */ + ret =3D devm_rtc_register_device(p->rtc); + if (ret) + return ret; + + nvmem_cfg.priv =3D p; + ret =3D devm_rtc_nvmem_register(p->rtc, &nvmem_cfg); + if (ret) + dev_warn(&pdev->dev, "standby RAM nvmem unavailable: %d\n", ret); + + return 0; +} + +static struct platform_driver m5441x_rtc_driver =3D { + .driver =3D { + .name =3D "rtc-m5441x", + }, + .probe =3D m5441x_rtc_probe, +}; +module_platform_driver(m5441x_rtc_driver); + +MODULE_DESCRIPTION("Freescale MCF5441x on-chip RTC driver"); +MODULE_AUTHOR("Jean-Michel Hautbois "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rtc-m5441x"); --=20 2.39.5 From nobody Mon Jun 8 04:26:42 2026 Received: from mslow3.mail.gandi.net (mslow3.mail.gandi.net [217.70.178.249]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2E68E3CAA48; Tue, 2 Jun 2026 09:04:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.70.178.249 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780391047; cv=none; b=LJZXazI6SDU9dU49pRoR43B6JOqsKK2akquoKOq1pfooPXNpUEWuTgLAIsunucO3PovG6b5ZXvDTljJ8ninZAMI9qBnZgn7qOi/hnhkp+UDZ4NlV9aV+tmDuk5wEqsLPL67d3JaM0yfSEgC7F6ydAx4XT2O3COttyqtlPrE422I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780391047; c=relaxed/simple; bh=TmwNvRU5JGmuk7QhxlzVsKnPW8fkOD3xhGVhBTM2hrQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qIaBERD2iNmSj6Zr4/lmRKZIUmSiFp5vrP3lw+vIOH1g4NgP28MTeQfJCRNvpiN49XAoe0q9tAn0uOke9upJMNbRqDf4rcj4EbKJHvM8l6WR17Lll2msIwhGcISLmbl6jGCuBLwxE3uHb4gKumx3yfYOC2F5ZNjJuUWyUlG4618= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=yoseli.org; spf=pass smtp.mailfrom=yoseli.org; dkim=pass (2048-bit key) header.d=yoseli.org header.i=@yoseli.org header.b=EI0Za1s5; arc=none smtp.client-ip=217.70.178.249 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=yoseli.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=yoseli.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=yoseli.org header.i=@yoseli.org header.b="EI0Za1s5" Received: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net [217.70.183.196]) by mslow3.mail.gandi.net (Postfix) with ESMTP id 8382C58307D; Tue, 2 Jun 2026 08:36:45 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPSA id BACF53E9F5; Tue, 2 Jun 2026 08:36:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yoseli.org; s=gm1; t=1780389397; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=2StCb0XxuWRTxii1OsJ7SwSKkwi3VYbBfMK9IvuOBs8=; b=EI0Za1s539XXcbzJBV91lODyBEe427IGklIpvTyJBmiT6Xpuq8gGFmwRbNaQpFmN30t64E l0WROorbefEcDtSLiZmpBz1a0HY8pddHrkFDuZCgci7xdXJSqFRZVyrQaWngumJbD3yquc h748jn7nxk2gtxhqC7rpH9aYnYY4EKYEG1rtFwTlEdx2JrKQTFLzBueHyYEjZf7MRNWxrI jdKNiY2y+yveyJgeVva+HKSaIqH9bmCI7b3AHXQt8O0D/dmkRbbHFlpFVEsj3cCimat49b QtewVgfxXfM/2QQaz16k+0SFitKl4MuXpWLEIzJftlR5WEDkn3kY5le9fS+Ujg== From: Jean-Michel Hautbois Date: Tue, 02 Jun 2026 10:36:18 +0200 Subject: [PATCH 2/2] m68k: coldfire/m5441x: register the on-chip RTC Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260602-jmh-upstream-coldfire-rtc-v1-2-1e129a177d2f@yoseli.org> References: <20260602-jmh-upstream-coldfire-rtc-v1-0-1e129a177d2f@yoseli.org> In-Reply-To: <20260602-jmh-upstream-coldfire-rtc-v1-0-1e129a177d2f@yoseli.org> To: Alexandre Belloni , Greg Ungerer , Geert Uytterhoeven Cc: linux-kernel@vger.kernel.org, linux-rtc@vger.kernel.org, linux-m68k@lists.linux-m68k.org, Jean-Michel Hautbois X-Mailer: b4 0.15-dev-47773 X-Developer-Signature: v=1; a=ed25519-sha256; t=1780389394; l=1672; i=jeanmichel.hautbois@yoseli.org; s=20240925; h=from:subject:message-id; bh=TmwNvRU5JGmuk7QhxlzVsKnPW8fkOD3xhGVhBTM2hrQ=; b=+ZMktkeaBEl4mUAlWw8z2OCWI9g1nhRUCzhSy/1Bg1zdW3dXlHeA+j9FtM6/IradO5loOavKJ FHYu7r7xqywCYq8uqeZ9N2l5pJpN0PBv8e+Ha40GkRs1C5YG5OVGCz6 X-Developer-Key: i=jeanmichel.hautbois@yoseli.org; a=ed25519; pk=MsMTVmoV69wLIlSkHlFoACIMVNQFyvJzvsJSQsn/kq4= X-GND-Sasl: jeanmichel.hautbois@yoseli.org X-GND-State: clean X-GND-Score: -100 X-GND-Cause: dmFkZTEsnfONNs296jSZuB3SroO9mv8NDw0zsJVF00UexSuwOsMW85G0//OCxbGa+0b/Pc7F4frE2obwOtkyy7cUUtBCoxR+DtZRV3KYgq2nWJ61fO6JvHVMcGwfQQlPQ61H1g2ybjvkwfHtAbn4BFLjC2BgSkmClKA1Rq4RPH2qUXuAxpvXhVzQ8X0e9oPyFovYzOvj/+M/CUiboM+bPWlKwEgSHrSG5ifeFVfOSPTUEBE7yKrZPV62zERrnZUgX1kb3rWcYUP1GNs/AC9rz/7bUsBd0iZyWWDYLCqLCx2e9wi6/iDBJjPZnhyGnVKh3l/A3uf7s4roGFRW068pQYd2jgHbs26YItc8h1n4gzSRAKtxinCFFaR8slG+4ei8dlxyz6M28mR8zso+Mmi3thRxX/IbnFVUbVjfWHl0QezXlJsCM207nqPiIRo7aqaV7Mzm963Z4rwTeUeBMIxbYPQRAXNedsGxWDRTvSY7VEEwtJ3fMDwkPmNxKvEZOWkIQsJAEBm35ybfUJbLzC4qYSMwqlGclONU2XJjM58FshqrmRPv0YIVz2QlWUVoqwsduYkVKL+I3yOwvToWYF8wvaOy/rqM/NFhg+nIIrLVvGNb24Wp+BdA7se2Ss5yXGDgttDm0OWm4yTJHwM+enc2NiwGVMYI0tdJdflMGCFgvzU3jq33CA Register the MCF5441x on-chip RTC platform device from the SoC code so every MCF5441x board gets the rtc-m5441x driver (time/calendar plus the battery-backed standby RAM via nvmem) without a per-board file. Signed-off-by: Jean-Michel Hautbois --- arch/m68k/coldfire/m5441x.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/arch/m68k/coldfire/m5441x.c b/arch/m68k/coldfire/m5441x.c index 7a25cfc7ac07..b79aebdfd567 100644 --- a/arch/m68k/coldfire/m5441x.c +++ b/arch/m68k/coldfire/m5441x.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -239,6 +240,34 @@ static void __init m5441x_fec_init(void) __raw_writeb(0x03, MCFGPIO_PAR_FEC); } =20 +/* + * On-chip "robust" RTC. Exposes the time/calendar and the 2KB + * battery-backed standby RAM (rtc-m5441x driver). + */ +static struct resource m5441x_rtc_resource[] =3D { + { + .start =3D MCFRTC_BASE, + .end =3D MCFRTC_BASE + MCFRTC_SIZE - 1, + .flags =3D IORESOURCE_MEM, + }, + { + .start =3D MCF_IRQ_RTC, + .end =3D MCF_IRQ_RTC, + .flags =3D IORESOURCE_IRQ, + }, +}; + +static int __init m5441x_rtc_init(void) +{ + struct platform_device *pdev; + + pdev =3D platform_device_register_simple("rtc-m5441x", -1, + m5441x_rtc_resource, + ARRAY_SIZE(m5441x_rtc_resource)); + return PTR_ERR_OR_ZERO(pdev); +} +arch_initcall(m5441x_rtc_init); + void __init config_BSP(char *commandp, int size) { m5441x_clk_init(); --=20 2.39.5