From nobody Fri Sep 25 17:49:11 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (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 27F8A3BBA18; Thu, 10 Sep 2026 06:09:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789020578; cv=none; b=bj+5olqCDDghiM5VRpUFqpV9BrsV1ZuZ4txAHtzUoy83nRQ4oDVTqMvWXFEgsWSUAImaEx+jBgCVOwA8p0Vw8nCf/TooCll3bKjN5/9hx9M2bK223fcVE9DrNyKNeHCMTEwvJw+nueHUN0sPU8aQ4V+ppmNxfLvuqqDzyadWklc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789020578; c=relaxed/simple; bh=WxRlZjVNETTMSTcclpLOqw9plGQYEebMiGINfrHFxDU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=G5srDpvgd/j3+B//PPcCDK2KOT37sw2O7d2ooZPge/WmspmJ9LgLLyUbh6d5vjExFNwdClZGqUvn92KS9l09P8WZ2gvzmFSiMqBRX8yT9g190gDCPQNNBfKSysFjW3dLJ4Auh/JbuAIHO3a0t8wVj0nVurq0pbG6qVaXxeoVemA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 138B72002AF; Thu, 10 Sep 2026 08:09:31 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id CE2F1200369; Thu, 10 Sep 2026 08:09:30 +0200 (CEST) Received: from lsv031125.swis.in-blr01.nxp.com (lsv031125.swis.in-blr01.nxp.com [10.12.177.172]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 9B7ED18000B5; Thu, 10 Sep 2026 14:09:27 +0800 (+08) From: Shiv Prakash Gupta To: linux-kernel@vger.kernel.org, linux-rtc@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-watchdog@vger.kernel.org, devicetree@vger.kernel.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, linux@roeck-us.net, wim@linux-watchdog.org Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, lakshay.piplani@nxp.com, Shiv Prakash Gupta Subject: [PATCH v3 1/4] dt-bindings: rtc: Add NXP PCF8525 RTC Date: Thu, 10 Sep 2026 11:39:09 +0530 Message-Id: <20260910060912.2443551-2-shivprakash.gupta@nxp.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260910060912.2443551-1-shivprakash.gupta@nxp.com> References: <20260910060912.2443551-1-shivprakash.gupta@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" Add a Devicetree binding for the NXP PCF8525 I2C RTC. Describe the optional INTA interrupt used for RTC alarm and timestamp events, and the INTB interrupt used by the watchdog. Add properties to select the external crystal load capacitance and temperature-compensation crystal model. Also allow the common start-year, wakeup-source and reset-source properties, with reset-source enabling watchdog registration Signed-off-by: Lakshay Piplani Signed-off-by: Shiv Prakash Gupta Reviewed-by: Rob Herring (Arm) --- Changes in v2: - No changes to DT binding --- .../devicetree/bindings/rtc/nxp,pcf8525.yaml | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Documentation/devicetree/bindings/rtc/nxp,pcf8525.yaml diff --git a/Documentation/devicetree/bindings/rtc/nxp,pcf8525.yaml b/Docum= entation/devicetree/bindings/rtc/nxp,pcf8525.yaml new file mode 100644 index 000000000000..bee71ec3da80 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/nxp,pcf8525.yaml @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/nxp,pcf8525.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP PCF8525 Real Time Clock + +description: + The PCF8525 is an I2C real-time clock with alarm, timestamp, watchdog, + backup battery switch-over, and temperature compensation for an external + 32.768 kHz crystal. + +allOf: + - $ref: rtc.yaml# + +maintainers: + - Lakshay Piplani + - Shiv Prakash Gupta + +properties: + compatible: + enum: + - nxp,pcf8525 + + reg: + maxItems: 1 + + interrupts: + minItems: 1 + maxItems: 2 + description: + INTA, followed by INTB when both interrupt outputs are connected. + + interrupt-names: + minItems: 1 + items: + - const: inta + - const: intb + + quartz-load-femtofarads: + description: + Effective load capacitance of the external 32.768 kHz crystal. + enum: [6000, 7000] + default: 6000 + + nxp,xtal-type: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Crystal model used by the temperature compensation engine. + Value 1 selects -0.035 ppm/degC^2 and value 2 selects + -0.04 ppm/degC^2. + enum: [1, 2] + default: 1 + + start-year: true + + wakeup-source: true + + reset-source: true + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + rtc@51 { + compatible =3D "nxp,pcf8525"; + reg =3D <0x51>; + interrupt-parent =3D <&gpio2>; + interrupts =3D <2 IRQ_TYPE_EDGE_FALLING>, + <3 IRQ_TYPE_EDGE_FALLING>; + interrupt-names =3D "inta", "intb"; + quartz-load-femtofarads =3D <7000>; + nxp,xtal-type =3D <2>; + reset-source; + }; + }; --=20 2.34.1 From nobody Fri Sep 25 17:49:11 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (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 9057D3B3C0F; Thu, 10 Sep 2026 06:09:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789020586; cv=none; b=qPgAcRBkH4nRALUIvtHap2BQI73YaT9C0ZkP+Oq1GNrI68TmGWl4lIrLpVpdZ5v8BwHn370OSu2MXn8uHjdsgRK5R7p8Idjiohd9B4GPcOwyExpEXwkcIu4lLHNnx5n8lNQ6ks8uRPszluUfQgM5upgo05YbezfJhvljW4Y/a14= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789020586; c=relaxed/simple; bh=k3Be0/u76ldrMfrEXnkcoggGqYcslbOx5zndGILNz8I=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=hHh6oNq6ponZzJNHswhEzzDbMDFHJakrfgoBR1EsVj0ZtGti4zxCHOwr2X+iWhneMO4IL4+Hn+bmbbupocU7GXXZYC2c+dy2MpI+bvN1oxadqjDW3T4H8i/qPCOR+sfWrDa5AB/3zsN2+5YWZDRXqRoilKb7irFRfMqqTlNv5/M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 49F0C200369; Thu, 10 Sep 2026 08:09:34 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id DC82B200382; Thu, 10 Sep 2026 08:09:33 +0200 (CEST) Received: from lsv031125.swis.in-blr01.nxp.com (lsv031125.swis.in-blr01.nxp.com [10.12.177.172]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 849B81800068; Thu, 10 Sep 2026 14:09:30 +0800 (+08) From: Shiv Prakash Gupta To: linux-kernel@vger.kernel.org, linux-rtc@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-watchdog@vger.kernel.org, devicetree@vger.kernel.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, linux@roeck-us.net, wim@linux-watchdog.org Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, lakshay.piplani@nxp.com, Shiv Prakash Gupta Subject: [PATCH v3 2/4] rtc: pcf8525: Add NXP PCF8525 RTC driver Date: Thu, 10 Sep 2026 11:39:10 +0530 Message-Id: <20260910060912.2443551-3-shivprakash.gupta@nxp.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260910060912.2443551-1-shivprakash.gupta@nxp.com> References: <20260910060912.2443551-1-shivprakash.gupta@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" Add support for time and date, alarms, timestamp capture, backup switch-over configuration, external crystal configuration and clock offset correction. Signed-off-by: Lakshay Piplani Signed-off-by: Shiv Prakash Gupta --- drivers/rtc/Kconfig | 10 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-pcf8525.c | 1026 +++++++++++++++++++++++++++++++++++++ 3 files changed, 1037 insertions(+) create mode 100644 drivers/rtc/rtc-pcf8525.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 01def8231873..89ff23ffb9ef 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -513,6 +513,16 @@ config RTC_DRV_PCF8523 This driver can also be built as a module. If so, the module will be called rtc-pcf8523. =20 +config RTC_DRV_PCF8525 + tristate "NXP PCF8525" + select REGMAP_I2C + help + If you say yes here you get support for the NXP PCF8525 RTC + chips. + + This driver can also be built as a module. If so, the module + will be called rtc-pcf8525. + config RTC_DRV_PCF85363 tristate "NXP PCF85363" select REGMAP_I2C diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 0347645b021f..2b90d28235c6 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -132,6 +132,7 @@ obj-$(CONFIG_RTC_DRV_PCF2123) +=3D rtc-pcf2123.o obj-$(CONFIG_RTC_DRV_PCF2127) +=3D rtc-pcf2127.o obj-$(CONFIG_RTC_DRV_PCF85063) +=3D rtc-pcf85063.o obj-$(CONFIG_RTC_DRV_PCF8523) +=3D rtc-pcf8523.o +obj-$(CONFIG_RTC_DRV_PCF8525) +=3D rtc-pcf8525.o obj-$(CONFIG_RTC_DRV_PCF85363) +=3D rtc-pcf85363.o obj-$(CONFIG_RTC_DRV_PCF8563) +=3D rtc-pcf8563.o obj-$(CONFIG_RTC_DRV_PCF8583) +=3D rtc-pcf8583.o diff --git a/drivers/rtc/rtc-pcf8525.c b/drivers/rtc/rtc-pcf8525.c new file mode 100644 index 000000000000..9da8bcde7ef1 --- /dev/null +++ b/drivers/rtc/rtc-pcf8525.c @@ -0,0 +1,1026 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C driver for NXP PCF8525 RTC + * Features: + * - Time/date read + set + * - Alarm (sec/min/hour/day) using RTC wkalrm API + * - Optional IRQ support (alarm + timestamp) on INTA + * - Timestamp1/2 exported via custom sysfs attributes: timestamp0/1 + * - Crystal load capacitance and crystal type configuration + * - Crystal aging offset correction through the RTC offset interface + * + * Register map summary (key ones): + * 0x00 Control_1: STOP, 12_24, MI, SI, etc + * 0x01 Control_2: MSF, TI_TP, WDTF, AF, OSFE[1:0], AIE, SMBUS_TIMEOUT + * 0x02 Control_3: PWRMNG[1:0], BF, OSIE, BIE + * 0x03 Control_4: TSF + * 0x04 Control_5: TSIE, TEMP_RD_EN, CL, XTL_TYP + * 0x05 Reset: command register (CPR, CTS, SR) + * 0x06 100th seconds + * 0x07 seconds (OSF bit7) + * 0x08 minutes (VLF bit7) + * 0x09 hours + * 0x0A days + * 0x0B weekdays + * 0x0C months + * 0x0D years + * 0x0E..0x14 alarm registers (sec/min/hour/day/weekday/month/year) + * 0x16 timestamp control + * 0x17..0x1C TS1 (sec/min/hour/day/month/year) + * 0x1D..0x23 TS2 (subsec/sec/min/hour/day/month/year) + * 0x26..0x27 aging offset registers + * 0x28..0x2B interrupt mask regs (INTA/B) + * 0x2E temperature (read-only), gated by TEMP_RD_EN + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Registers */ +#define PCF8525_REG_CTRL1 0x00 +#define PCF8525_REG_CTRL2 0x01 +#define PCF8525_REG_CTRL3 0x02 +#define PCF8525_REG_CTRL4 0x03 +#define PCF8525_REG_CTRL5 0x04 +#define PCF8525_REG_RESET 0x05 + +#define PCF8525_REG_100THS 0x06 +#define PCF8525_REG_SECONDS 0x07 +#define PCF8525_REG_MINUTES 0x08 +#define PCF8525_REG_HOURS 0x09 +#define PCF8525_REG_DAYS 0x0A +#define PCF8525_REG_WEEKDAYS 0x0B +#define PCF8525_REG_MONTHS 0x0C +#define PCF8525_REG_YEARS 0x0D + +#define PCF8525_REG_ALM_SEC 0x0E +#define PCF8525_REG_ALM_MIN 0x0F +#define PCF8525_REG_ALM_HOUR 0x10 +#define PCF8525_REG_ALM_DAY 0x11 +#define PCF8525_REG_ALM_WDAY 0x12 +#define PCF8525_REG_ALM_MONTH 0x13 +#define PCF8525_REG_ALM_YEAR 0x14 + +#define PCF8525_REG_CLKOUT 0x15 + +#define PCF8525_REG_TS_CTL1 0x16 +#define PCF8525_REG_TS1_SEC 0x17 +#define PCF8525_REG_TS1_MIN 0x18 +#define PCF8525_REG_TS1_HOUR 0x19 +#define PCF8525_REG_TS1_DAY 0x1A +#define PCF8525_REG_TS1_MONTH 0x1B +#define PCF8525_REG_TS1_YEAR 0x1C + +#define PCF8525_REG_TS2_SUBSEC 0x1D +#define PCF8525_REG_TS2_SEC 0x1E +#define PCF8525_REG_TS2_MIN 0x1F +#define PCF8525_REG_TS2_HOUR 0x20 +#define PCF8525_REG_TS2_DAY 0x21 +#define PCF8525_REG_TS2_MONTH 0x22 +#define PCF8525_REG_TS2_YEAR 0x23 + +#define PCF8525_REG_INTA_MASK1 0x28 +#define PCF8525_REG_INTA_MASK2 0x29 +#define PCF8525_REG_INTB_MASK1 0x2A +#define PCF8525_REG_INTB_MASK2 0x2B + +#define PCF8525_REG_TEMP 0x2E + +/* CTRL1 */ +#define PCF8525_CTRL1_STOP BIT(5) +#define PCF8525_CTRL1_12_24 BIT(2) +#define PCF8525_CTRL1_MI BIT(1) +#define PCF8525_CTRL1_SI BIT(0) +/* CTRL2 */ +#define PCF8525_CTRL2_MSF BIT(7) +#define PCF8525_CTRL2_TI_TP BIT(6) +#define PCF8525_CTRL2_WDTF BIT(5) +#define PCF8525_CTRL2_AF BIT(4) +#define PCF8525_CTRL2_OSFE_MASK GENMASK(3, 2) +#define PCF8525_CTRL2_AIE BIT(1) +#define PCF8525_CTRL2_SMBTO BIT(0) +/* + * All W0C status bits in CTRL2 (writing 1 =3D no effect, writing 0 =3D cl= ear). + * Include WDTF here so that any regmap_update_bits touching CTRL2 control + * bits (AIE, TI_TP, etc.) does not accidentally clear a pending watchdog + * timer interrupt by writing 0 to WDTF in the RMW cycle. + */ +#define PCF8525_CTRL2_W0C_FLAGS (PCF8525_CTRL2_MSF | \ + PCF8525_CTRL2_WDTF | \ + PCF8525_CTRL2_AF) +/* CTRL3 */ +#define PCF8525_CTRL3_PWRMNG_MASK GENMASK(6, 5) +#define PCF8525_CTRL3_BF BIT(3) +#define PCF8525_CTRL3_OSIE BIT(2) +#define PCF8525_CTRL3_BIE BIT(1) +/* CTRL4/5 */ +#define PCF8525_CTRL4_TSF BIT(7) +#define PCF8525_CTRL5_TSIE BIT(7) +#define PCF8525_CTRL5_TEMP_RD_EN BIT(2) +#define PCF8525_CTRL5_CL BIT(1) +#define PCF8525_CTRL5_XTL_TYP BIT(0) + +/* Timestamp control */ +#define PCF8525_TS_CTL1_TSM BIT(7) +#define PCF8525_TS_CTL1_TSOFF BIT(6) + +/* Seconds/Minutes flags */ +#define PCF8525_SC_OSF BIT(7) +#define PCF8525_MN_VLF BIT(7) + +/* Alarm AE bits */ +#define PCF8525_ALM_AE BIT(7) + +/* RESET commands */ +#define PCF8525_RESET_CPR_CMD 0xA4 /* clear prescaler */ +#define PCF8525_RESET_CTS_CMD 0x25 /* clear timestamp */ +#define PCF8525_RESET_CPR_CTS_CMD 0xA5 /* clear prescaler + timestamp */ +#define PCF8525_RESET_SR_CMD 0x2C /* software reset */ + +/* Interrupt masks (mask bit =3D 1 means masked/disabled) - INTA_MASK1 bit= s */ +#define PCF8525_MASK1_BIE BIT(1) +#define PCF8525_MASK1_AIE BIT(2) +#define PCF8525_MASK1_OSIE BIT(3) +#define PCF8525_MASK1_SI BIT(4) +#define PCF8525_MASK1_MI BIT(5) +/* INTA_MASK2 - timestamp mask bit3 */ +#define PCF8525_MASK2_TSIE BIT(3) + +#define PCF8525_ALM_AE_MONTH BIT(7) +#define PCF8525_ALM_AE_YEAR BIT(6) + +#define PCF8525_REG_AGING_OFFSET_HI 0x26 +#define PCF8525_REG_AGING_OFFSET_LO 0x27 +/* + * One aging-offset register step is 0.0298 ppm, or 29.8 ppb. + * The RTC core offset interface uses ppb. + */ +#define PCF8525_AGING_OFFSET_STEP_NUM 298L +#define PCF8525_AGING_OFFSET_STEP_DEN 10L + +#define PCF8525_AGING_OFFSET_MIN (-32768L) +#define PCF8525_AGING_OFFSET_MAX 32767L + +struct pcf8525 { + struct rtc_device *rtc; + struct regmap *regmap; + struct mutex ts_mutex; /* protects ts[] and ts_valid[] in IRQ mode */ + bool irq_enabled; + time64_t ts[2]; + bool ts_valid[2]; +}; + +static const struct regmap_config pcf8525_regmap_cfg =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D PCF8525_REG_TEMP, +}; + +/* + * Configure only the crystal fields explicitly provided by firmware. + * If a property is absent, preserve the current hardware setting. + * + * quartz-load-femtofarads: + * 6000 -> Control_5.CL =3D 0 + * 7000 -> Control_5.CL =3D 1 + * + * nxp,xtal-type: + * 1 -> Control_5.XTL_TYP =3D 0 (-0.035 ppm/degree C^2 model) + * 2 -> Control_5.XTL_TYP =3D 1 (-0.04 ppm/degree C^2 model) + */ +static int pcf8525_configure_crystal(struct device *dev, + struct pcf8525 *pcf8525) +{ + unsigned int mask =3D 0; + unsigned int value =3D 0; + u32 xtal_type; + u32 load; + int ret; + + ret =3D device_property_read_u32(dev, + "quartz-load-femtofarads", + &load); + if (!ret) { + switch (load) { + case 6000: + mask |=3D PCF8525_CTRL5_CL; + break; + + case 7000: + mask |=3D PCF8525_CTRL5_CL; + value |=3D PCF8525_CTRL5_CL; + break; + + default: + dev_warn(dev, + "unsupported quartz-load-femtofarads=3D%u; " + "preserving CL bit\n", + load); + break; + } + } + + ret =3D device_property_read_u32(dev, + "nxp,xtal-type", + &xtal_type); + if (!ret) { + switch (xtal_type) { + case 1: + mask |=3D PCF8525_CTRL5_XTL_TYP; + break; + + case 2: + mask |=3D PCF8525_CTRL5_XTL_TYP; + value |=3D PCF8525_CTRL5_XTL_TYP; + break; + + default: + dev_warn(dev, + "unsupported nxp,xtal-type=3D%u; " + "preserving XTL_TYP bit\n", + xtal_type); + break; + } + } + + if (!mask) + return 0; + + return regmap_update_bits(pcf8525->regmap, + PCF8525_REG_CTRL5, + mask, value); +} + +static int pcf8525_read_aging_offset(struct pcf8525 *pcf8525, s16 *offset) +{ + u8 buf[2]; + int ret; + + ret =3D regmap_bulk_read(pcf8525->regmap, + PCF8525_REG_AGING_OFFSET_HI, + buf, sizeof(buf)); + if (ret) + return ret; + + *offset =3D (s16)((buf[0] << 8) | buf[1]); + + return 0; +} + +static int pcf8525_write_aging_offset(struct pcf8525 *pcf8525, s16 offset) +{ + u8 buf[2]; + + buf[0] =3D (offset >> 8) & 0xff; + buf[1] =3D offset & 0xff; + + /* + * Write high byte first and low byte last. The new aging correction + * starts after AgingOffset_Low is written. + */ + return regmap_bulk_write(pcf8525->regmap, + PCF8525_REG_AGING_OFFSET_HI, + buf, sizeof(buf)); +} + +static int pcf8525_rtc_read_offset(struct device *dev, long *offset) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + s16 raw; + int ret; + + ret =3D pcf8525_read_aging_offset(pcf8525, &raw); + if (ret) + return ret; + + /* + * Convert the signed 16-bit hardware value to ppb: + * + * one raw step =3D 0.0298 ppm =3D 29.8 ppb + */ + *offset =3D DIV_ROUND_CLOSEST((long)raw * + PCF8525_AGING_OFFSET_STEP_NUM, + PCF8525_AGING_OFFSET_STEP_DEN); + + return 0; +} + +static int pcf8525_rtc_set_offset(struct device *dev, long offset) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + long min_offset; + long max_offset; + long raw; + + min_offset =3D + DIV_ROUND_CLOSEST(PCF8525_AGING_OFFSET_MIN * + PCF8525_AGING_OFFSET_STEP_NUM, + PCF8525_AGING_OFFSET_STEP_DEN); + + max_offset =3D + DIV_ROUND_CLOSEST(PCF8525_AGING_OFFSET_MAX * + PCF8525_AGING_OFFSET_STEP_NUM, + PCF8525_AGING_OFFSET_STEP_DEN); + + if (offset < min_offset || offset > max_offset) + return -ERANGE; + + /* + * Convert the requested ppb value to the closest signed + * 16-bit hardware value. + */ + raw =3D DIV_ROUND_CLOSEST(offset * + PCF8525_AGING_OFFSET_STEP_DEN, + PCF8525_AGING_OFFSET_STEP_NUM); + + if (raw < PCF8525_AGING_OFFSET_MIN || + raw > PCF8525_AGING_OFFSET_MAX) + return -ERANGE; + + return pcf8525_write_aging_offset(pcf8525, (s16)raw); +} + +static int pcf8525_read_time(struct device *dev, struct rtc_time *tm) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + u8 buf[7]; + int ret; + + /* Read seconds..years (0x07..0x0D) */ + ret =3D regmap_bulk_read(pcf8525->regmap, PCF8525_REG_SECONDS, buf, sizeo= f(buf)); + if (ret) + return ret; + + /* OSF: oscillator stop =3D> time not reliable */ + if (buf[0] & PCF8525_SC_OSF) + return -EINVAL; + + /* + * VLF is on minutes register bit7; + * Keep policy conservative: warn but still allow read. + */ + if (buf[1] & PCF8525_MN_VLF) + dev_warn(dev, "VLF set: clock integrity not guaranteed\n"); + + tm->tm_sec =3D bcd2bin(buf[0] & 0x7f); + tm->tm_min =3D bcd2bin(buf[1] & 0x7f); + tm->tm_hour =3D bcd2bin(buf[2] & 0x3f); + tm->tm_mday =3D bcd2bin(buf[3] & 0x3f); + tm->tm_wday =3D buf[4] & 0x07; + tm->tm_mon =3D bcd2bin(buf[5] & 0x1f) - 1; + tm->tm_year =3D bcd2bin(buf[6]) + 100; /* 20xx */ + + return rtc_valid_tm(tm); +} + +static int pcf8525_set_time(struct device *dev, struct rtc_time *tm) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + u8 buf[7]; + int ret, ret2; + + ret =3D rtc_valid_tm(tm); + if (ret) + return ret; + + /* + * Use STOP + clear prescaler (CPR) like RESET register describes. + */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL1, + PCF8525_CTRL1_STOP, PCF8525_CTRL1_STOP); + if (ret) + return ret; + + ret =3D regmap_write(pcf8525->regmap, PCF8525_REG_RESET, PCF8525_RESET_CP= R_CMD); + if (ret) + goto out_start; + + buf[0] =3D bin2bcd(tm->tm_sec); /* writing seconds also clears OSF per ma= ny NXP RTCs */ + buf[1] =3D bin2bcd(tm->tm_min); + buf[2] =3D bin2bcd(tm->tm_hour); + buf[3] =3D bin2bcd(tm->tm_mday); + buf[4] =3D tm->tm_wday & 0x07; + buf[5] =3D bin2bcd(tm->tm_mon + 1); + buf[6] =3D bin2bcd(tm->tm_year - 100); + + ret =3D regmap_bulk_write(pcf8525->regmap, PCF8525_REG_SECONDS, buf, size= of(buf)); + +out_start: + /* Clear STOP regardless of bulk write outcome */ + ret2 =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL1, PCF8525_C= TRL1_STOP, 0); + if (ret2) + return ret2; + return ret; +} + +static int pcf8525_rtc_ioctl(struct device *dev, + unsigned int cmd, unsigned long arg) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + unsigned int val; + unsigned int flags =3D 0; + int ret; + + switch (cmd) { + case RTC_VL_READ: + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_CTRL3, &val); + if (ret) + return ret; + + if (val & PCF8525_CTRL3_BF) + flags |=3D RTC_VL_BACKUP_SWITCH; + + return put_user(flags, (unsigned int __user *)arg); + + case RTC_VL_CLR: + /* Clear BF by writing 0 (AND semantics respected) */ + return regmap_update_bits(pcf8525->regmap, + PCF8525_REG_CTRL3, + PCF8525_CTRL3_BF, + 0); + + default: + return -ENOIOCTLCMD; + } +} + +static int pcf8525_param_get(struct device *dev, struct rtc_param *param) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + unsigned int val; + unsigned int mode; + int ret; + + switch (param->param) { + case RTC_PARAM_BACKUP_SWITCH_MODE: + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_CTRL3, &val); + if (ret) + return ret; + + mode =3D FIELD_GET(PCF8525_CTRL3_PWRMNG_MASK, val); + + switch (mode) { + case 0: + param->uvalue =3D RTC_BSM_LEVEL; + break; + case 1: + param->uvalue =3D RTC_BSM_DIRECT; + break; + default: + param->uvalue =3D RTC_BSM_DISABLED; + break; + } + return 0; + + default: + return -EINVAL; + } +} + +static int pcf8525_param_set(struct device *dev, struct rtc_param *param) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + unsigned int mode; + + switch (param->param) { + case RTC_PARAM_BACKUP_SWITCH_MODE: + switch (param->uvalue) { + case RTC_BSM_LEVEL: + mode =3D 0; + break; + case RTC_BSM_DIRECT: + mode =3D 1; + break; + case RTC_BSM_DISABLED: + mode =3D 2; + break; + default: + return -EINVAL; + } + + /* + * CTRL3 BF is a W0C bit: include it in the mask with value=3D1 so the + * RMW cycle does not accidentally clear it if it asserted between the + * regmap read and write. + */ + return regmap_update_bits(pcf8525->regmap, + PCF8525_REG_CTRL3, + PCF8525_CTRL3_PWRMNG_MASK | PCF8525_CTRL3_BF, + FIELD_PREP(PCF8525_CTRL3_PWRMNG_MASK, mode) | + PCF8525_CTRL3_BF); + default: + return -EINVAL; + } +} + +/* Alarm uses RTC wkalrm: map to sec/min/hour/day and disable others */ +static int pcf8525_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + unsigned int ctrl2; + u8 buf[7]; + int ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_CTRL2, &ctrl2); + if (ret) + return ret; + + ret =3D regmap_bulk_read(pcf8525->regmap, PCF8525_REG_ALM_SEC, buf, sizeo= f(buf)); + if (ret) + return ret; + + alrm->enabled =3D !!(ctrl2 & PCF8525_CTRL2_AIE); + alrm->pending =3D !!(ctrl2 & PCF8525_CTRL2_AF); + + alrm->time.tm_sec =3D bcd2bin(buf[0] & 0x7f); + alrm->time.tm_min =3D bcd2bin(buf[1] & 0x7f); + alrm->time.tm_hour =3D bcd2bin(buf[2] & 0x3f); + alrm->time.tm_mday =3D bcd2bin(buf[3] & 0x3f); + + return 0; +} + +static int pcf8525_alarm_irq_enable(struct device *dev, unsigned int enabl= e) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + int ret; + + /* + * Include all CTRL2 W0C flags in the mask and keep them set to 1 in the + * value so the RMW does not accidentally clear a concurrently asserted + * interrupt flag (AF or MSF). + */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL2, + PCF8525_CTRL2_AIE | PCF8525_CTRL2_W0C_FLAGS, + (enable ? PCF8525_CTRL2_AIE : 0) | + PCF8525_CTRL2_W0C_FLAGS); + if (ret) + dev_err(dev, "alarm_irq_enable: failed ret=3D%d\n", ret); + + return ret; +} + +static int pcf8525_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + u8 buf[7]; + int ret; + + /* + * Clear AF (W0C). Include MSF in the mask and write 1 to preserve it: + * writing 0 to a W0C bit that asserted between the read and write would + * silently drop the event. + */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL2, + PCF8525_CTRL2_AF | PCF8525_CTRL2_MSF, + PCF8525_CTRL2_MSF); + if (ret) + return ret; + + buf[0] =3D bin2bcd(alrm->time.tm_sec) & 0x7f; + buf[1] =3D bin2bcd(alrm->time.tm_min) & 0x7f; + buf[2] =3D bin2bcd(alrm->time.tm_hour) & 0x3f; + buf[3] =3D bin2bcd(alrm->time.tm_mday) & 0x3f; + + /* Disable match on weekday/month/year by setting AE bits */ + buf[4] =3D PCF8525_ALM_AE; /* weekday alarm ignored */ + buf[5] =3D PCF8525_ALM_AE_MONTH | PCF8525_ALM_AE_YEAR; /* disable month = + year alarm */ + buf[6] =3D 0x00; /* year alarm value (ignored if month/year disabled) */ + + ret =3D regmap_bulk_write(pcf8525->regmap, PCF8525_REG_ALM_SEC, buf, size= of(buf)); + if (ret) + return ret; + + return pcf8525_alarm_irq_enable(dev, alrm->enabled); +} + +/* ---- Timestamp handling (sysfs) ---- */ +static int pcf8525_ts_read(struct device *dev, int id, time64_t *ts_out) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + struct rtc_time tm; + u8 buf[6]; + int ret; + u8 base; + + /* + * TS0 maps to TS1 (0x17..0x1C) + * TS1 maps to TS2 (0x1E..0x23) (skip subsec at 0x1D) + */ + if (id =3D=3D 0) + base =3D PCF8525_REG_TS1_SEC; + else if (id =3D=3D 1) + base =3D PCF8525_REG_TS2_SEC; + else + return -EINVAL; + + ret =3D regmap_bulk_read(pcf8525->regmap, base, buf, sizeof(buf)); + if (ret) + return ret; + + tm.tm_sec =3D bcd2bin(buf[0] & 0x7f); + tm.tm_min =3D bcd2bin(buf[1] & 0x7f); + tm.tm_hour =3D bcd2bin(buf[2] & 0x3f); + tm.tm_mday =3D bcd2bin(buf[3] & 0x3f); + tm.tm_mon =3D bcd2bin(buf[4] & 0x1f) - 1; + tm.tm_year =3D bcd2bin(buf[5]) + 100; + + ret =3D rtc_valid_tm(&tm); + if (ret) + return ret; + + *ts_out =3D rtc_tm_to_time64(&tm); + return 0; +} + +static ssize_t timestamp_show_common(struct device *dev, char *buf, int id) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev->parent); + unsigned int ctrl4; + time64_t ts; + int ret; + + if (id < 0 || id > 1) + return 0; + + if (pcf8525->irq_enabled) { + bool valid; + + mutex_lock(&pcf8525->ts_mutex); + valid =3D pcf8525->ts_valid[id]; + if (valid) + ts =3D pcf8525->ts[id]; + mutex_unlock(&pcf8525->ts_mutex); + + if (!valid) + return 0; + return sysfs_emit(buf, "%llu\n", (unsigned long long)ts); + } + + /* polling mode: only report if TSF is currently set */ + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_CTRL4, &ctrl4); + if (ret) + return 0; + + if (!(ctrl4 & PCF8525_CTRL4_TSF)) + return 0; + + ret =3D pcf8525_ts_read(dev->parent, id, &ts); + if (ret) + return 0; + + return sysfs_emit(buf, "%llu\n", (unsigned long long)ts); +} + +static ssize_t timestamp_store_common(struct device *dev, const char *buf, + size_t count, int id) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev->parent); + int ret; + + if (id < 0 || id > 1) + return -EINVAL; + + if (pcf8525->irq_enabled) { + /* + * Hold the mutex across the software state update and the + * hardware CTS + TSF-clear sequence. + * + * pcf8525_irq() re-reads CTRL4 under the same mutex before + * touching the timestamp registers. Clearing TSF here ensures + * that if the IRQ handler is blocked waiting for the mutex, it + * will see TSF=3D0 on its re-read and skip reading the now-zeroed + * CTS registers. Without this clear the IRQ could read all- + * zeros from the CTS-wiped registers and cache garbage. + */ + mutex_lock(&pcf8525->ts_mutex); + pcf8525->ts_valid[id] =3D false; + regmap_write(pcf8525->regmap, PCF8525_REG_RESET, + PCF8525_RESET_CTS_CMD); + regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL4, + PCF8525_CTRL4_TSF, 0); + mutex_unlock(&pcf8525->ts_mutex); + return count; + } + + /* + * Clear timestamp via RESET CTS command. + * This also clears the timestamp flag path. + */ + ret =3D regmap_write(pcf8525->regmap, PCF8525_REG_RESET, PCF8525_RESET_CT= S_CMD); + if (ret) + return ret; + + /* Also clear TSF flag (belt-and-suspenders) */ + regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL4, PCF8525_CTRL4_TSF,= 0); + + return count; +} + +static ssize_t timestamp0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return timestamp_show_common(dev, buf, 0); +} + +static ssize_t timestamp1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return timestamp_show_common(dev, buf, 1); +} + +static ssize_t timestamp0_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return timestamp_store_common(dev, buf, count, 0); +} + +static ssize_t timestamp1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return timestamp_store_common(dev, buf, count, 1); +} + +static DEVICE_ATTR_RW(timestamp0); +static DEVICE_ATTR_RW(timestamp1); + +static struct attribute *pcf8525_attrs[] =3D { + &dev_attr_timestamp0.attr, + &dev_attr_timestamp1.attr, + NULL +}; + +static const struct attribute_group pcf8525_attr_group =3D { + .attrs =3D pcf8525_attrs, +}; + +static irqreturn_t pcf8525_irq(int irq, void *data) +{ + struct device *dev =3D data; + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + unsigned int ctrl2, ctrl4; + int ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_CTRL2, &ctrl2); + if (ret) + return IRQ_NONE; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_CTRL4, &ctrl4); + if (ret) + return IRQ_NONE; + + if (!(ctrl2 & (PCF8525_CTRL2_AF | PCF8525_CTRL2_MSF)) && + !(ctrl4 & PCF8525_CTRL4_TSF)) + return IRQ_NONE; + + /* Timestamp */ + if (ctrl4 & PCF8525_CTRL4_TSF) { + time64_t ts0, ts1; + unsigned int ctrl4_now; + bool ok0, ok1; + + /* + * Acquire the mutex before touching TSF or the timestamp + * registers. timestamp_store_common() holds the same mutex + * while issuing the CTS command and clearing TSF, so the + * entire check-clear-read sequence is serialized with any + * concurrent userspace store. + */ + mutex_lock(&pcf8525->ts_mutex); + + /* + * Re-read CTRL4 under the mutex. If timestamp_store_common() + * ran while we were waiting and already issued CTS and cleared + * TSF, the hardware timestamp registers now contain zeros. + * Skip the read to avoid caching garbage. + */ + if (regmap_read(pcf8525->regmap, PCF8525_REG_CTRL4, + &ctrl4_now) || + !(ctrl4_now & PCF8525_CTRL4_TSF)) { + mutex_unlock(&pcf8525->ts_mutex); + goto handle_alarm; + } + + /* + * TSF is still set: clear it (W0C) before reading the + * timestamp registers so a new event that fires during the + * I2C read is not lost. + */ + regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL4, + PCF8525_CTRL4_TSF, 0); + + ok0 =3D !pcf8525_ts_read(dev, 0, &ts0); + ok1 =3D !pcf8525_ts_read(dev, 1, &ts1); + if (ok0 && !pcf8525->ts_valid[0]) { + pcf8525->ts[0] =3D ts0; + pcf8525->ts_valid[0] =3D true; + } + if (ok1 && !pcf8525->ts_valid[1]) { + pcf8525->ts[1] =3D ts1; + pcf8525->ts_valid[1] =3D true; + } + mutex_unlock(&pcf8525->ts_mutex); + } + +handle_alarm: + /* Alarm */ + if (ctrl2 & PCF8525_CTRL2_AF) { + /* + * W0C: clear AF before notifying so that a new alarm event + * that fires during rtc_update_irq() is not lost. Include MSF + * in the mask with value=3D1 so the RMW does not accidentally + * clear a concurrently asserted minute/second flag. + */ + regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL2, + PCF8525_CTRL2_AF | PCF8525_CTRL2_MSF, + PCF8525_CTRL2_MSF); + rtc_update_irq(pcf8525->rtc, 1, RTC_IRQF | RTC_AF); + } + + if (ctrl2 & PCF8525_CTRL2_MSF) { + /* + * W0C: clear MSF, protect AF from being accidentally cleared + * by writing 1 to it. + */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL2, + PCF8525_CTRL2_MSF | PCF8525_CTRL2_AF, + PCF8525_CTRL2_AF); + if (ret) + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static const struct rtc_class_ops pcf8525_rtc_ops =3D { + .ioctl =3D pcf8525_rtc_ioctl, + .read_time =3D pcf8525_read_time, + .set_time =3D pcf8525_set_time, + .read_alarm =3D pcf8525_read_alarm, + .set_alarm =3D pcf8525_set_alarm, + .alarm_irq_enable =3D pcf8525_alarm_irq_enable, + .read_offset =3D pcf8525_rtc_read_offset, + .set_offset =3D pcf8525_rtc_set_offset, + .param_get =3D pcf8525_param_get, + .param_set =3D pcf8525_param_set, +}; + +static int pcf8525_unmask_irqs_intA(struct pcf8525 *pcf8525) +{ + /* + * Mask registers: bit=3D1 means masked/disabled. + * We want alarm + timestamp unmasked on INTA by default: + * - AIE =3D> mask bit2 must be 0 + * - TSIE =3D> mask2 bit3 must be 0 + */ + unsigned int m1, m2; + int ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_INTA_MASK1, &m1); + if (ret) + return ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_INTA_MASK2, &m2); + if (ret) + return ret; + + m1 &=3D ~PCF8525_MASK1_AIE; /* unmask alarm interrupt on INTA */ + m2 &=3D ~PCF8525_MASK2_TSIE; /* unmask timestamp interrupt on INTA */ + + ret =3D regmap_write(pcf8525->regmap, PCF8525_REG_INTA_MASK1, m1); + if (ret) + return ret; + + return regmap_write(pcf8525->regmap, PCF8525_REG_INTA_MASK2, m2); +} + +static int pcf8525_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct pcf8525 *pcf8525; + int ret; + + pcf8525 =3D devm_kzalloc(dev, sizeof(*pcf8525), GFP_KERNEL); + if (!pcf8525) + return -ENOMEM; + + mutex_init(&pcf8525->ts_mutex); + + pcf8525->regmap =3D devm_regmap_init_i2c(client, &pcf8525_regmap_cfg); + if (IS_ERR(pcf8525->regmap)) + return dev_err_probe(dev, PTR_ERR(pcf8525->regmap), + "failed to init regmap\n"); + + i2c_set_clientdata(client, pcf8525); + + pcf8525->rtc =3D devm_rtc_allocate_device(dev); + if (IS_ERR(pcf8525->rtc)) + return dev_err_probe(dev, PTR_ERR(pcf8525->rtc), + "failed to allocate RTC device\n"); + + pcf8525->rtc->ops =3D &pcf8525_rtc_ops; + pcf8525->rtc->range_min =3D RTC_TIMESTAMP_BEGIN_2000; + pcf8525->rtc->range_max =3D RTC_TIMESTAMP_END_2099; + pcf8525->rtc->set_start_time =3D true; + + clear_bit(RTC_FEATURE_ALARM, pcf8525->rtc->features); + set_bit(RTC_FEATURE_UPDATE_INTERRUPT, pcf8525->rtc->features); + /* Use 24-hour mode */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL1, + PCF8525_CTRL1_12_24, 0); + if (ret) + return ret; + + ret =3D pcf8525_configure_crystal(dev, pcf8525); + if (ret) + return ret; + /* + * Enable timestamp engine + timestamp interrupt (TSIE). + */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_TS_CTL1, + PCF8525_TS_CTL1_TSM | PCF8525_TS_CTL1_TSOFF, + PCF8525_TS_CTL1_TSM); + if (ret) + return ret; + + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL5, + PCF8525_CTRL5_TSIE, PCF8525_CTRL5_TSIE); + if (ret) + return ret; + + /* Create sysfs timestamp0/1 */ + ret =3D rtc_add_group(pcf8525->rtc, &pcf8525_attr_group); + if (ret) + return ret; + + /* Optional IRQ */ + if (client->irq > 0) { + ret =3D devm_request_threaded_irq(dev, client->irq, + NULL, pcf8525_irq, + IRQF_ONESHOT, + dev_name(dev), dev); + if (ret) + return dev_err_probe(dev, ret, + "failed to request INTA IRQ\n"); + + pcf8525->irq_enabled =3D true; + + /* Route/unmask alarm + timestamp to INTA by default */ + ret =3D pcf8525_unmask_irqs_intA(pcf8525); + if (ret) + return ret; + + device_init_wakeup(dev, true); + dev_pm_set_wake_irq(dev, client->irq); + set_bit(RTC_FEATURE_ALARM, pcf8525->rtc->features); + } + + /* Register device */ + ret =3D devm_rtc_register_device(pcf8525->rtc); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id pcf8525_of_match[] =3D { + { .compatible =3D "nxp,pcf8525" }, + { } +}; +MODULE_DEVICE_TABLE(of, pcf8525_of_match); + +static const struct i2c_device_id pcf8525_i2c_id[] =3D { + { .name =3D "pcf8525" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcf8525_i2c_id); + +static struct i2c_driver pcf8525_driver =3D { + .driver =3D { + .name =3D "rtc-pcf8525", + .of_match_table =3D pcf8525_of_match, + }, + .probe =3D pcf8525_probe, + .id_table =3D pcf8525_i2c_id, +}; +module_i2c_driver(pcf8525_driver); + +MODULE_AUTHOR("Lakshay Piplani "); +MODULE_AUTHOR("Shiv Prakash Gupta "); +MODULE_DESCRIPTION("NXP PCF8525 RTC driver"); +MODULE_LICENSE("GPL"); + --=20 2.34.1 From nobody Fri Sep 25 17:49:11 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (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 907F13BB135; Thu, 10 Sep 2026 06:09:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789020586; cv=none; b=de/SCXjd/8XEMwP4TBrihHTi8ZXQFlhu4CbNbVKegX01CoV/xl/vmL/pKCrAyTkaE+T1lqjtSJxuTEM7WMY04nNL/sv5e1nxrAjZNinrnpSbTmVzm8NKGRa4Sn3uqyXQnh8fEOYuDvi2d2zkMCaCkE2C0k0WN0ziZhBJz3Avnxw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789020586; c=relaxed/simple; bh=x/1eCG0YShWHNVrn1MpvOJB/Gz1gHGwkt7mTFIzXR4U=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Nfrrh4Nwn1L+lNFMlEBW46KbVSOdHkhPDjuvP1l5k3919peFdAUTaNAEr4f825IHZGUqM4cP44n5nVAExBIIwr+mhyvTbyS9ooCktuHCePIByRc4XI2KtpCa3b7H7NqNoBfBB0g8ipX4B7W+nJ5J3OIiNi+CPGV4ZJ7xp5jO3Uc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 2DC70200386; Thu, 10 Sep 2026 08:09:37 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id C173720037D; Thu, 10 Sep 2026 08:09:36 +0200 (CEST) Received: from lsv031125.swis.in-blr01.nxp.com (lsv031125.swis.in-blr01.nxp.com [10.12.177.172]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 91A4B180006C; Thu, 10 Sep 2026 14:09:33 +0800 (+08) From: Shiv Prakash Gupta To: linux-kernel@vger.kernel.org, linux-rtc@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-watchdog@vger.kernel.org, devicetree@vger.kernel.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, linux@roeck-us.net, wim@linux-watchdog.org Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, lakshay.piplani@nxp.com, Shiv Prakash Gupta Subject: [PATCH v3 3/4] rtc: pcf8525: Add watchdog support Date: Thu, 10 Sep 2026 11:39:11 +0530 Message-Id: <20260910060912.2443551-4-shivprakash.gupta@nxp.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260910060912.2443551-1-shivprakash.gupta@nxp.com> References: <20260910060912.2443551-1-shivprakash.gupta@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" Add watchdog support for NXP PCF8525 real time clock(RTC) device Signed-off-by: Lakshay Piplani Signed-off-by: Shiv Prakash Gupta --- drivers/rtc/Kconfig | 1 + drivers/rtc/rtc-pcf8525.c | 366 +++++++++++++++++++++++++++++++++++++- 2 files changed, 362 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 89ff23ffb9ef..356c2d91e387 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -516,6 +516,7 @@ config RTC_DRV_PCF8523 config RTC_DRV_PCF8525 tristate "NXP PCF8525" select REGMAP_I2C + select WATCHDOG_CORE if WATCHDOG help If you say yes here you get support for the NXP PCF8525 RTC chips. diff --git a/drivers/rtc/rtc-pcf8525.c b/drivers/rtc/rtc-pcf8525.c index 9da8bcde7ef1..c2eab983f1ff 100644 --- a/drivers/rtc/rtc-pcf8525.c +++ b/drivers/rtc/rtc-pcf8525.c @@ -47,6 +47,9 @@ #include #include #include +#include +#include +#include =20 /* Registers */ #define PCF8525_REG_CTRL1 0x00 @@ -96,6 +99,8 @@ #define PCF8525_REG_INTB_MASK1 0x2A #define PCF8525_REG_INTB_MASK2 0x2B =20 +#define PCF8525_REG_WD_CTL 0x2C +#define PCF8525_REG_WD_VAL 0x2D #define PCF8525_REG_TEMP 0x2E =20 /* CTRL1 */ @@ -132,6 +137,9 @@ #define PCF8525_CTRL5_CL BIT(1) #define PCF8525_CTRL5_XTL_TYP BIT(0) =20 +/* CLKOUT_ctl */ +#define PCF8525_CLKOUT_CLKOE BIT(3) + /* Timestamp control */ #define PCF8525_TS_CTL1_TSM BIT(7) #define PCF8525_TS_CTL1_TSOFF BIT(6) @@ -150,6 +158,7 @@ #define PCF8525_RESET_SR_CMD 0x2C /* software reset */ =20 /* Interrupt masks (mask bit =3D 1 means masked/disabled) - INTA_MASK1 bit= s */ +#define PCF8525_MASK1_WD_CD BIT(0) #define PCF8525_MASK1_BIE BIT(1) #define PCF8525_MASK1_AIE BIT(2) #define PCF8525_MASK1_OSIE BIT(3) @@ -161,6 +170,16 @@ #define PCF8525_ALM_AE_MONTH BIT(7) #define PCF8525_ALM_AE_YEAR BIT(6) =20 +/* Watchdog control register bits */ +#define PCF8525_WD_CTL_WD_CD BIT(7) +#define PCF8525_WD_CTL_TF0 BIT(0) +#define PCF8525_WD_CTL_TF1 BIT(1) + +#define PCF8525_WD_CLOCK_HZ_X1000 250 /* 1/4 Hz */ +#define PCF8525_WD_MIN_HW_HEARTBEAT_MS 4000 +#define PCF8525_WD_VAL_STOP 0 +#define PCF8525_WD_DEFAULT_TIMEOUT_S 60 + #define PCF8525_REG_AGING_OFFSET_HI 0x26 #define PCF8525_REG_AGING_OFFSET_LO 0x27 /* @@ -175,11 +194,14 @@ =20 struct pcf8525 { struct rtc_device *rtc; + struct watchdog_device wdd; struct regmap *regmap; struct mutex ts_mutex; /* protects ts[] and ts_valid[] in IRQ mode */ bool irq_enabled; time64_t ts[2]; bool ts_valid[2]; + int irq_inta; + int irq_intb; }; =20 static const struct regmap_config pcf8525_regmap_cfg =3D { @@ -879,6 +901,296 @@ static const struct rtc_class_ops pcf8525_rtc_ops =3D= { .param_set =3D pcf8525_param_set, }; =20 +static unsigned int pcf8525_wdt_timeout_to_val(unsigned int timeout) +{ + unsigned int val; + + val =3D DIV_ROUND_UP(timeout * PCF8525_WD_CLOCK_HZ_X1000, 1000) + 1; + + if (val < 2) + return 2; + if (val > 255) + return 255; + + return val; +} + +static int pcf8525_wdt_ping(struct watchdog_device *wdd) +{ + struct pcf8525 *pcf8525 =3D watchdog_get_drvdata(wdd); + unsigned int wd_val; + + wd_val =3D pcf8525_wdt_timeout_to_val(wdd->timeout); + + return regmap_write(pcf8525->regmap, PCF8525_REG_WD_VAL, wd_val); +} + +static int pcf8525_wdt_active_ping(struct watchdog_device *wdd) +{ + if (watchdog_active(wdd)) + return pcf8525_wdt_ping(wdd); + + return 0; +} + +static int pcf8525_wdt_start(struct watchdog_device *wdd) +{ + return pcf8525_wdt_ping(wdd); +} + +static int pcf8525_wdt_stop(struct watchdog_device *wdd) +{ + struct pcf8525 *pcf8525 =3D watchdog_get_drvdata(wdd); + + return regmap_write(pcf8525->regmap, PCF8525_REG_WD_VAL, + PCF8525_WD_VAL_STOP); +} + +static int pcf8525_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + wdd->timeout =3D timeout; + + return pcf8525_wdt_active_ping(wdd); +} + +static const struct watchdog_info pcf8525_wdt_info =3D { + .identity =3D "NXP PCF8525 Watchdog", + .options =3D WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_PRETIMEOUT, +}; + +static const struct watchdog_ops pcf8525_watchdog_ops =3D { + .owner =3D THIS_MODULE, + .start =3D pcf8525_wdt_start, + .stop =3D pcf8525_wdt_stop, + .ping =3D pcf8525_wdt_ping, + .set_timeout =3D pcf8525_wdt_set_timeout, +}; + +static int pcf8525_watchdog_get_period(int n, int f1000) +{ + return (1000 * (n - 1)) / f1000; +} + +static irqreturn_t pcf8525_wdt_irq(int irq, void *data) +{ + struct device *dev =3D data; + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + unsigned int ctrl2; + int ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_CTRL2, &ctrl2); + if (ret) + return IRQ_NONE; + + if (!(ctrl2 & PCF8525_CTRL2_WDTF)) + return IRQ_NONE; + + /* + * W0C: clear WDTF. Include AF and MSF in the mask and write 1 to them + * so the RMW does not accidentally clear a concurrently asserted alarm + * or minute/second interrupt flag. + */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL2, + PCF8525_CTRL2_WDTF | PCF8525_CTRL2_AF | + PCF8525_CTRL2_MSF, + PCF8525_CTRL2_AF | PCF8525_CTRL2_MSF); + if (ret) + return IRQ_NONE; + + watchdog_notify_pretimeout(&pcf8525->wdd); + return IRQ_HANDLED; +} + +static int pcf8525_watchdog_config(struct device *dev, + struct pcf8525 *pcf8525) +{ + unsigned int m1, m2; + int ret; + + /* Configure nINTB/CLKOUT as nINTB open-drain interrupt output. */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CLKOUT, + PCF8525_CLKOUT_CLKOE, 0); + if (ret) + return ret; + + /* Enable watchdog interrupt and select 1/4 Hz source: TF[1:0] =3D 10. */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_WD_CTL, + PCF8525_WD_CTL_WD_CD | + PCF8525_WD_CTL_TF1 | + PCF8525_WD_CTL_TF0, + PCF8525_WD_CTL_WD_CD | + PCF8525_WD_CTL_TF1); + if (ret) + return ret; + + /* Keep watchdog masked on INTA. */ + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_INTA_MASK1, &m1); + if (ret) + return ret; + + m1 |=3D PCF8525_MASK1_WD_CD; + ret =3D regmap_write(pcf8525->regmap, PCF8525_REG_INTA_MASK1, m1); + if (ret) + return ret; + + /* Route watchdog only to INTB, and keep RTC interrupts masked on INTB. */ + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_INTB_MASK1, &m1); + if (ret) + return ret; + + /* + * Clear any stale WDTF before unmasking the watchdog on INTB. + * With battery backup the flag survives a power cycle and would + * assert INTB immediately on the next boot, causing an infinite + * reset loop if INTB is wired to a hardware reset line. + * Include AF and MSF in the mask with value=3D1 to protect them from + * the RMW clearing a concurrently asserted flag. + */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL2, + PCF8525_CTRL2_WDTF | PCF8525_CTRL2_AF | + PCF8525_CTRL2_MSF, + PCF8525_CTRL2_AF | PCF8525_CTRL2_MSF); + if (ret) + return ret; + + m1 |=3D PCF8525_MASK1_BIE | + PCF8525_MASK1_AIE | + PCF8525_MASK1_OSIE | + PCF8525_MASK1_SI | + PCF8525_MASK1_MI; + m1 &=3D ~PCF8525_MASK1_WD_CD; + + ret =3D regmap_write(pcf8525->regmap, PCF8525_REG_INTB_MASK1, m1); + if (ret) + return ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_INTB_MASK2, &m2); + if (ret) + return ret; + + m2 |=3D PCF8525_MASK2_TSIE; + + return regmap_write(pcf8525->regmap, PCF8525_REG_INTB_MASK2, m2); +} + +static void pcf8525_mask_intb(void *data) +{ + struct pcf8525 *pcf8525 =3D data; + unsigned int m1; + int ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_INTB_MASK1, &m1); + if (!ret) { + m1 |=3D PCF8525_MASK1_WD_CD; + regmap_write(pcf8525->regmap, PCF8525_REG_INTB_MASK1, m1); + } +} + +static int pcf8525_watchdog_init(struct device *dev, + struct pcf8525 *pcf8525) +{ + int ret; + + if (!IS_ENABLED(CONFIG_WATCHDOG_CORE) || + !device_property_read_bool(dev, "reset-source")) + return 0; + + /* + * Configure the hardware first: this switches the nINTB/CLKOUT pin + * from clock output (CLKOE=3D1) to open-drain interrupt output before + * the IRQ handler is installed. Requesting the IRQ while CLKOE=3D1 + * would flood the CPU with spurious interrupts and may cause the + * Linux spurious-interrupt detector to permanently disable the IRQ. + */ + ret =3D pcf8525_watchdog_config(dev, pcf8525); + if (ret) + return ret; + + /* Register a cleanup action to re-mask INTB on driver unbind. */ + ret =3D devm_add_action_or_reset(dev, pcf8525_mask_intb, pcf8525); + if (ret) + return ret; + + if (pcf8525->irq_intb > 0) { + ret =3D devm_request_threaded_irq(dev, pcf8525->irq_intb, + NULL, pcf8525_wdt_irq, + IRQF_ONESHOT, + "pcf8525-wdt", dev); + if (ret) + return ret; + } + + pcf8525->wdd.parent =3D dev; + pcf8525->wdd.info =3D &pcf8525_wdt_info; + pcf8525->wdd.ops =3D &pcf8525_watchdog_ops; + pcf8525->wdd.min_timeout =3D pcf8525_watchdog_get_period(2, + PCF8525_WD_CLOCK_HZ_X1000); + pcf8525->wdd.max_timeout =3D pcf8525_watchdog_get_period(255, + PCF8525_WD_CLOCK_HZ_X1000); + pcf8525->wdd.timeout =3D PCF8525_WD_DEFAULT_TIMEOUT_S; + watchdog_init_timeout(&pcf8525->wdd, 0, dev); + pcf8525->wdd.min_hw_heartbeat_ms =3D PCF8525_WD_MIN_HW_HEARTBEAT_MS; + pcf8525->wdd.status =3D WATCHDOG_NOWAYOUT_INIT_STATUS; + + watchdog_set_drvdata(&pcf8525->wdd, pcf8525); + watchdog_stop_on_reboot(&pcf8525->wdd); + + { + unsigned int wd_val; + + if (!regmap_read(pcf8525->regmap, PCF8525_REG_WD_VAL, &wd_val) && + wd_val !=3D 0) + set_bit(WDOG_HW_RUNNING, &pcf8525->wdd.status); + } + + return devm_watchdog_register_device(dev, &pcf8525->wdd); +} + +static int pcf8525_get_irqs(struct i2c_client *client, struct pcf8525 *pcf= 8525) +{ + struct device *dev =3D &client->dev; + int irq; + + irq =3D fwnode_irq_get_byname(dev_fwnode(dev), "inta"); + if (irq =3D=3D -ENOENT || irq =3D=3D -EINVAL) + irq =3D client->irq; + else if (irq < 0) + return irq; + pcf8525->irq_inta =3D irq; + + irq =3D fwnode_irq_get_byname(dev_fwnode(dev), "intb"); + if (irq =3D=3D -ENOENT || irq =3D=3D -EINVAL) + irq =3D 0; + else if (irq < 0) + return irq; + pcf8525->irq_intb =3D irq; + + return 0; +} + +/* + * pcf8525_mask_inta - devm cleanup: re-mask INTA alarm and timestamp + * interrupts on driver unbind so the open-drain line is not left asserted. + */ +static void pcf8525_mask_inta(void *data) +{ + struct pcf8525 *pcf8525 =3D data; + unsigned int m1, m2; + int ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_INTA_MASK1, &m1); + if (!ret) { + m1 |=3D PCF8525_MASK1_AIE; + regmap_write(pcf8525->regmap, PCF8525_REG_INTA_MASK1, m1); + } + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_INTA_MASK2, &m2); + if (!ret) { + m2 |=3D PCF8525_MASK2_TSIE; + regmap_write(pcf8525->regmap, PCF8525_REG_INTA_MASK2, m2); + } +} + static int pcf8525_unmask_irqs_intA(struct pcf8525 *pcf8525) { /* @@ -898,8 +1210,9 @@ static int pcf8525_unmask_irqs_intA(struct pcf8525 *pc= f8525) if (ret) return ret; =20 - m1 &=3D ~PCF8525_MASK1_AIE; /* unmask alarm interrupt on INTA */ - m2 &=3D ~PCF8525_MASK2_TSIE; /* unmask timestamp interrupt on INTA */ + m1 |=3D PCF8525_MASK1_WD_CD; /* keep watchdog masked on INTA */ + m1 &=3D ~PCF8525_MASK1_AIE; /* unmask alarm interrupt on INTA */ + m2 &=3D ~PCF8525_MASK2_TSIE; /* unmask timestamp interrupt on INTA */ =20 ret =3D regmap_write(pcf8525->regmap, PCF8525_REG_INTA_MASK1, m1); if (ret) @@ -908,6 +1221,32 @@ static int pcf8525_unmask_irqs_intA(struct pcf8525 *p= cf8525) return regmap_write(pcf8525->regmap, PCF8525_REG_INTA_MASK2, m2); } =20 +static int pcf8525_suspend(struct device *dev) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + + if (IS_ENABLED(CONFIG_WATCHDOG_CORE) && + device_property_read_bool(dev, "reset-source") && + watchdog_active(&pcf8525->wdd)) + return pcf8525_wdt_stop(&pcf8525->wdd); + + return 0; +} + +static int pcf8525_resume(struct device *dev) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + + if (IS_ENABLED(CONFIG_WATCHDOG_CORE) && + device_property_read_bool(dev, "reset-source") && + watchdog_active(&pcf8525->wdd)) + return pcf8525_wdt_ping(&pcf8525->wdd); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(pcf8525_pm_ops, pcf8525_suspend, pcf8525_r= esume); + static int pcf8525_probe(struct i2c_client *client) { struct device *dev =3D &client->dev; @@ -927,6 +1266,10 @@ static int pcf8525_probe(struct i2c_client *client) =20 i2c_set_clientdata(client, pcf8525); =20 + ret =3D pcf8525_get_irqs(client, pcf8525); + if (ret) + return dev_err_probe(dev, ret, "failed to get IRQs\n"); + pcf8525->rtc =3D devm_rtc_allocate_device(dev); if (IS_ERR(pcf8525->rtc)) return dev_err_probe(dev, PTR_ERR(pcf8525->rtc), @@ -968,8 +1311,8 @@ static int pcf8525_probe(struct i2c_client *client) return ret; =20 /* Optional IRQ */ - if (client->irq > 0) { - ret =3D devm_request_threaded_irq(dev, client->irq, + if (pcf8525->irq_inta > 0) { + ret =3D devm_request_threaded_irq(dev, pcf8525->irq_inta, NULL, pcf8525_irq, IRQF_ONESHOT, dev_name(dev), dev); @@ -984,11 +1327,23 @@ static int pcf8525_probe(struct i2c_client *client) if (ret) return ret; =20 + /* + * Register a cleanup action to re-mask INTA interrupts on + * driver unbind so the open-drain line is not left asserted. + */ + ret =3D devm_add_action_or_reset(dev, pcf8525_mask_inta, pcf8525); + if (ret) + return ret; + device_init_wakeup(dev, true); - dev_pm_set_wake_irq(dev, client->irq); + dev_pm_set_wake_irq(dev, pcf8525->irq_inta); set_bit(RTC_FEATURE_ALARM, pcf8525->rtc->features); } =20 + ret =3D pcf8525_watchdog_init(dev, pcf8525); + if (ret) + return ret; + /* Register device */ ret =3D devm_rtc_register_device(pcf8525->rtc); if (ret) @@ -1013,6 +1368,7 @@ static struct i2c_driver pcf8525_driver =3D { .driver =3D { .name =3D "rtc-pcf8525", .of_match_table =3D pcf8525_of_match, + .pm =3D pm_sleep_ptr(&pcf8525_pm_ops), }, .probe =3D pcf8525_probe, .id_table =3D pcf8525_i2c_id, --=20 2.34.1 From nobody Fri Sep 25 17:49:11 2026 Received: from inva020.nxp.com (inva020.nxp.com [92.121.34.13]) (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 EFB833C37A3; Thu, 10 Sep 2026 06:09:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.13 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789020597; cv=none; b=nlclO+GKDE5+4JkoQAaR5CjPngYkVEDrG8lGf0v7GgF7PJriTe7RoeAiw6wgCSVx4PjowL5hCuZ8NOb7DLAKLq2KIqI63RYS157hfdBxdOlAQE1iHGw/YFya6fHCnpuI7mjR6x0RlQtR+0g/fFGWv082fkrRcGqt6gYY3bkVyg4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789020597; c=relaxed/simple; bh=X0ylNUfyY2EdluJvR7UhHDvA6CCL61dTk8TBqJlBf5Y=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=fjBsa5jj9MKBgOuFQ59QINo+rcdLTvIYdP5i1fj+ZV7NfHq8rqHYl49ikPrZIPd1CVE+UhkqA81eC0DTNn7ofNmEVBhIreRrbKO5fLdtDUJ88jFY/x7Bqvq0Cxa9GLVsvgTcMfQ3J4oxCf//vKFwgAi3KnNjyE4a6g/ERlyr4Ow= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.13 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva020.nxp.com (localhost [127.0.0.1]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id E06BE1A00B1; Thu, 10 Sep 2026 08:09:39 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id A8CAE1A00C1; Thu, 10 Sep 2026 08:09:39 +0200 (CEST) Received: from lsv031125.swis.in-blr01.nxp.com (lsv031125.swis.in-blr01.nxp.com [10.12.177.172]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 773F418000B6; Thu, 10 Sep 2026 14:09:36 +0800 (+08) From: Shiv Prakash Gupta To: linux-kernel@vger.kernel.org, linux-rtc@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-watchdog@vger.kernel.org, devicetree@vger.kernel.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, linux@roeck-us.net, wim@linux-watchdog.org Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, lakshay.piplani@nxp.com, Shiv Prakash Gupta Subject: [PATCH v3 4/4] rtc: pcf8525: Add temperature sensor support via hwmon Date: Thu, 10 Sep 2026 11:39:12 +0530 Message-Id: <20260910060912.2443551-5-shivprakash.gupta@nxp.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260910060912.2443551-1-shivprakash.gupta@nxp.com> References: <20260910060912.2443551-1-shivprakash.gupta@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" Register the PCF8525 internal temperature sensor through the hwmon interface when CONFIG_RTC_DRV_PCF8525_HWMON is enabled. Exposes temp1_input (millidegrees Celsius, read-only) and update_interval (milliseconds, read/write). Signed-off-by: Lakshay Piplani Signed-off-by: Shiv Prakash Gupta --- drivers/rtc/Kconfig | 15 +++ drivers/rtc/rtc-pcf8525.c | 218 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 356c2d91e387..7b4195e712b0 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -524,6 +524,21 @@ config RTC_DRV_PCF8525 This driver can also be built as a module. If so, the module will be called rtc-pcf8525. =20 +config RTC_DRV_PCF8525_HWMON + bool "HWMON support for NXP PCF8525" + depends on RTC_DRV_PCF8525 && HWMON && \ + !(RTC_DRV_PCF8525=3Dy && HWMON=3Dm) + default y + help + Say Y here to expose the PCF8525 internal temperature + sensor through the HWMON interface. + + This option provides temperature input reporting and allows + the temperature measurement update interval to be configured + from userspace. + + The interface is registered only when HWMON support is enabled + config RTC_DRV_PCF85363 tristate "NXP PCF85363" select REGMAP_I2C diff --git a/drivers/rtc/rtc-pcf8525.c b/drivers/rtc/rtc-pcf8525.c index c2eab983f1ff..a2958eb3a502 100644 --- a/drivers/rtc/rtc-pcf8525.c +++ b/drivers/rtc/rtc-pcf8525.c @@ -34,6 +34,7 @@ */ =20 #include +#include #include #include #include @@ -179,6 +180,7 @@ #define PCF8525_WD_MIN_HW_HEARTBEAT_MS 4000 #define PCF8525_WD_VAL_STOP 0 #define PCF8525_WD_DEFAULT_TIMEOUT_S 60 +#define PCF8525_CLKOUT_TCR_MASK GENMASK(7, 5) =20 #define PCF8525_REG_AGING_OFFSET_HI 0x26 #define PCF8525_REG_AGING_OFFSET_LO 0x27 @@ -374,6 +376,220 @@ static int pcf8525_rtc_set_offset(struct device *dev,= long offset) return pcf8525_write_aging_offset(pcf8525, (s16)raw); } =20 +static int pcf8525_hwmon_read_temp(struct device *dev, long *temp) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + unsigned int regval; + int ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_TEMP, ®val); + if (ret) + return ret; + + /* + * PCF8525: signed 8-bit, 1 degree C per LSB. + * HWMON requires millidegree Celsius. + */ + *temp =3D (long)(s8)(u8)regval * 1000L; + + return 0; +} + +static int pcf8525_hwmon_read_update_interval(struct device *dev, long *va= l) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + unsigned int regval; + unsigned int tcr; + int ret; + + ret =3D regmap_read(pcf8525->regmap, PCF8525_REG_CLKOUT, ®val); + if (ret) + return ret; + + tcr =3D FIELD_GET(PCF8525_CLKOUT_TCR_MASK, regval); + + switch (tcr) { + case 0: + *val =3D 32 * 60 * 1000L; + break; + case 1: + *val =3D 16 * 60 * 1000L; + break; + case 2: + *val =3D 8 * 60 * 1000L; + break; + case 3: + *val =3D 4 * 60 * 1000L; + break; + case 4: + *val =3D 2 * 60 * 1000L; + break; + default: + *val =3D 60 * 1000L; + break; + } + + return 0; +} + +static int pcf8525_hwmon_write_update_interval(struct device *dev, long va= l) +{ + struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); + unsigned int tcr; + + switch (val) { + case 32 * 60 * 1000L: + tcr =3D 0; + break; + case 16 * 60 * 1000L: + tcr =3D 1; + break; + case 8 * 60 * 1000L: + tcr =3D 2; + break; + case 4 * 60 * 1000L: + tcr =3D 3; + break; + case 2 * 60 * 1000L: + tcr =3D 4; + break; + case 60 * 1000L: + tcr =3D 5; + break; + default: + return -EINVAL; + } + + /* Update only TCR[2:0]; preserve OTPR, CLKOE and COF[2:0]. */ + return regmap_update_bits(pcf8525->regmap, PCF8525_REG_CLKOUT, + PCF8525_CLKOUT_TCR_MASK, + FIELD_PREP(PCF8525_CLKOUT_TCR_MASK, tcr)); +} + +static umode_t pcf8525_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + if (attr =3D=3D hwmon_chip_update_interval) + return 0644; + break; + case hwmon_temp: + if (attr =3D=3D hwmon_temp_input && channel =3D=3D 0) + return 0444; + break; + default: + break; + } + + return 0; +} + +static int pcf8525_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + if (attr =3D=3D hwmon_chip_update_interval) + return pcf8525_hwmon_read_update_interval(dev, val); + break; + case hwmon_temp: + if (attr =3D=3D hwmon_temp_input && channel =3D=3D 0) + return pcf8525_hwmon_read_temp(dev, val); + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static int pcf8525_hwmon_write(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + if (type =3D=3D hwmon_chip && attr =3D=3D hwmon_chip_update_interval) + return pcf8525_hwmon_write_update_interval(dev, val); + + return -EOPNOTSUPP; +} + +static const struct hwmon_channel_info * const pcf8525_hwmon_info[] =3D { + HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL +}; + +static const struct hwmon_ops pcf8525_hwmon_ops =3D { + .is_visible =3D pcf8525_hwmon_is_visible, + .read =3D pcf8525_hwmon_read, + .write =3D pcf8525_hwmon_write, +}; + +static const struct hwmon_chip_info pcf8525_hwmon_chip_info =3D { + .ops =3D &pcf8525_hwmon_ops, + .info =3D pcf8525_hwmon_info, +}; + +static void pcf8525_hwmon_disable_temp(void *data) +{ + struct pcf8525 *pcf8525 =3D data; + + /* Disable temperature sensor to avoid draining the backup battery. */ + regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL5, + PCF8525_CTRL5_TEMP_RD_EN, 0); +} + +/* + * Keep HWMON optional and non-fatal so RTC and watchdog registration rema= in + * usable even if the temperature interface cannot be registered. + */ +static void pcf8525_hwmon_register(struct device *dev, + struct pcf8525 *pcf8525) +{ + struct device *hwmon_dev; + int ret; + + if (!IS_ENABLED(CONFIG_RTC_DRV_PCF8525_HWMON)) + return; + + /* + * Enable digital temperature readout first so that the hardware is + * ready before the sysfs attributes are exposed to userspace. The + * previous order (register hwmon device first) left a window where + * a concurrent sysfs read would return invalid data, and left the + * device permanently broken if this step subsequently failed. + */ + ret =3D regmap_update_bits(pcf8525->regmap, PCF8525_REG_CTRL5, + PCF8525_CTRL5_TEMP_RD_EN, + PCF8525_CTRL5_TEMP_RD_EN); + if (ret) { + dev_warn(dev, "failed to enable temperature readout: %d\n", ret); + return; + } + + /* + * Register a devm cleanup action to disable TEMP_RD_EN when the + * driver is unbound, preventing the analog sensor from draining + * the backup battery after driver removal. + */ + if (devm_add_action_or_reset(dev, pcf8525_hwmon_disable_temp, pcf8525)) { + dev_warn(dev, "failed to register HWMON cleanup action\n"); + return; + } + + /* Only expose sysfs attributes once the hardware is fully ready. */ + hwmon_dev =3D devm_hwmon_device_register_with_info(dev, "pcf8525", + pcf8525, + &pcf8525_hwmon_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + dev_warn(dev, "failed to register HWMON device: %ld\n", + PTR_ERR(hwmon_dev)); +} + static int pcf8525_read_time(struct device *dev, struct rtc_time *tm) { struct pcf8525 *pcf8525 =3D dev_get_drvdata(dev); @@ -1349,6 +1565,8 @@ static int pcf8525_probe(struct i2c_client *client) if (ret) return ret; =20 + pcf8525_hwmon_register(dev, pcf8525); + return 0; } =20 --=20 2.34.1