]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
hwmon: (pmbus/adm1266) add rtc debugfs entry
authorAbdurrahman Hussain <abdurrahman@nexthop.ai>
Wed, 20 May 2026 22:42:42 +0000 (15:42 -0700)
committerGuenter Roeck <linux@roeck-us.net>
Tue, 9 Jun 2026 15:23:09 +0000 (08:23 -0700)
The driver seeds the chip's SET_RTC register once at probe with
ktime_get_real_seconds().  Over a long uptime the chip's internal
seconds counter drifts away from the host's wall-clock time, so the
timestamp embedded in each blackbox record stops being meaningful
in wall-clock terms.  The datasheet recommends that the host
periodically resynchronise the counter to address this; today the
driver has no userspace-facing knob for that.

Expose SET_RTC via an rtc debugfs file alongside the other adm1266
debugfs entries:

  read  -- returns the chip's current SET_RTC seconds counter, so
           userspace can observe how far the chip has drifted from
           host wall-clock without writing anything.
  write -- the kernel re-reads ktime_get_real_seconds() itself and
           pushes it to the chip.  The write payload is ignored;
           userspace does not get to supply its own timestamp
           value, so there is no way for it to push a wrong time
           into the chip.

A small userspace agent (chrony hook, systemd-timesyncd dispatch
script, or a periodic cron job) can write to this file to keep the
chip's counter aligned with wall-clock across long uptimes.

Both the read and write paths take pmbus_lock to serialise against
the pmbus_core's own PAGE+register sequences and against the other
adm1266 debugfs accessors that already run under the same lock.

While at it, drop the now-redundant adm1266_set_rtc() probe-time
helper.  The new adm1266_rtc_set() callback does exactly the same
byte-packing and write; probe just calls adm1266_rtc_set(client, 0)
(the ignored @val argument) after pmbus_do_probe() so the
pmbus_lock acquired by the new helper has a live mutex to take.

Signed-off-by: Abdurrahman Hussain <abdurrahman@nexthop.ai>
Assisted-by: Claude-Code:claude-opus-4-7
Assisted-by: sashiko:gemini-3.1-pro-preview
Link: https://lore.kernel.org/r/20260520-adm1266-v5-3-c72ef1fac1ea@nexthop.ai
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
drivers/hwmon/pmbus/adm1266.c

index 0ef8a6ccd919c2d5dc4466cc882c782e14e445bb..22ccb721dd514c50b2617e1a5295269b939c4bf6 100644 (file)
@@ -432,6 +432,69 @@ static const struct file_operations adm1266_clear_blackbox_fops = {
        .llseek = noop_llseek,
 };
 
+/*
+ * SET_RTC (0xDF) is a 6-byte block (datasheet Rev. D, Table 84):
+ *   bytes [1:0] - fractional seconds (1/65536 s, written as zero)
+ *   bytes [5:2] - seconds since 1970-01-01 UTC, little-endian
+ *
+ * The driver seeds it once at probe via adm1266_rtc_set().  Over a
+ * long uptime the chip's counter drifts away from host wall-clock,
+ * so expose it via debugfs:
+ *
+ *   read  -- returns the chip's current seconds counter, which lets
+ *            userspace observe host-vs-chip drift.
+ *   write -- the kernel re-reads ktime_get_real_seconds() and writes
+ *            it to SET_RTC.  The write payload is ignored; userspace
+ *            does not get to supply its own timestamp value, so
+ *            there is no way to push a wrong time into the chip.
+ *
+ * A small userspace agent (chrony hook, systemd-timesyncd script,
+ * or a periodic cron job) can write to this file to keep the
+ * timestamp embedded in each blackbox record aligned with
+ * wall-clock across long uptimes.
+ */
+static int adm1266_rtc_get(void *data, u64 *val)
+{
+       struct i2c_client *client = data;
+       u8 buf[I2C_SMBUS_BLOCK_MAX];
+       u32 seconds = 0;
+       int ret, i;
+
+       guard(pmbus_lock)(client);
+       ret = i2c_smbus_read_block_data(client, ADM1266_SET_RTC, buf);
+       if (ret < 0)
+               return ret;
+       if (ret < 6)
+               return -EIO;
+
+       for (i = 0; i < 4; i++)
+               seconds |= (u32)buf[2 + i] << (i * 8);
+
+       *val = seconds;
+
+       return 0;
+}
+
+static int adm1266_rtc_set(void *data, u64 val)
+{
+       struct i2c_client *client = data;
+       time64_t kt = ktime_get_real_seconds();
+       u8 write_buf[6] = { 0 };
+       int i;
+
+       /* User-supplied @val is ignored on purpose; the kernel owns the
+        * time source so userspace cannot push a wrong value into the chip.
+        */
+       for (i = 0; i < 4; i++)
+               write_buf[2 + i] = (kt >> (i * 8)) & 0xFF;
+
+       guard(pmbus_lock)(client);
+       return i2c_smbus_write_block_data(client, ADM1266_SET_RTC,
+                                         sizeof(write_buf), write_buf);
+}
+DEFINE_DEBUGFS_ATTRIBUTE(adm1266_rtc_fops,
+                        adm1266_rtc_get, adm1266_rtc_set, "%llu\n");
+
 static void adm1266_init_debugfs(struct adm1266_data *data)
 {
        struct dentry *root;
@@ -450,6 +513,8 @@ static void adm1266_init_debugfs(struct adm1266_data *data)
                                    adm1266_powerup_counter_read);
        debugfs_create_file("clear_blackbox", 0200, data->debugfs_dir, data->client,
                            &adm1266_clear_blackbox_fops);
+       debugfs_create_file("rtc", 0600, data->debugfs_dir, data->client,
+                           &adm1266_rtc_fops);
 }
 
 static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *read_buff)
@@ -539,23 +604,6 @@ static int adm1266_config_nvmem(struct adm1266_data *data)
        return 0;
 }
 
-static int adm1266_set_rtc(struct adm1266_data *data)
-{
-       time64_t kt;
-       char write_buf[6];
-       int i;
-
-       kt = ktime_get_real_seconds();
-
-       memset(write_buf, 0, sizeof(write_buf));
-
-       for (i = 0; i < 4; i++)
-               write_buf[2 + i] = (kt >> (i * 8)) & 0xFF;
-
-       return i2c_smbus_write_block_data(data->client, ADM1266_SET_RTC, sizeof(write_buf),
-                                         write_buf);
-}
-
 static int adm1266_probe(struct i2c_client *client)
 {
        struct adm1266_data *data;
@@ -575,14 +623,14 @@ static int adm1266_probe(struct i2c_client *client)
        crc8_populate_msb(pmbus_crc_table, 0x7);
        mutex_init(&data->buf_mutex);
 
-       ret = adm1266_set_rtc(data);
-       if (ret < 0)
-               return ret;
-
        ret = pmbus_do_probe(client, &data->info);
        if (ret)
                return ret;
 
+       ret = adm1266_rtc_set(client, 0);
+       if (ret < 0)
+               return ret;
+
        ret = adm1266_config_nvmem(data);
        if (ret < 0)
                return ret;