summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbdurrahman Hussain <abdurrahman@nexthop.ai>2026-05-21 01:42:42 +0300
committerGuenter Roeck <linux@roeck-us.net>2026-06-09 18:23:09 +0300
commit22c1992642ebe95f4ec9f6456cf56ae330d2571a (patch)
treeaaac234d0422d5b82c3c533c19a39e7f0951f997
parente8d55e1f6ebf6d22727fe46ce8714906b3afe23e (diff)
downloadlinux-22c1992642ebe95f4ec9f6456cf56ae330d2571a.tar.xz
hwmon: (pmbus/adm1266) add rtc debugfs entry
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>
-rw-r--r--drivers/hwmon/pmbus/adm1266.c90
1 files changed, 69 insertions, 21 deletions
diff --git a/drivers/hwmon/pmbus/adm1266.c b/drivers/hwmon/pmbus/adm1266.c
index 0ef8a6ccd919..22ccb721dd51 100644
--- a/drivers/hwmon/pmbus/adm1266.c
+++ b/drivers/hwmon/pmbus/adm1266.c
@@ -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;