All of lore.kernel.org
 help / color / mirror / Atom feed
From: Titus Rwantare <titusr@google.com>
To: peter.maydell@linaro.org
Cc: qemu-arm@nongnu.org, qemu-devel@nongnu.org, kfting@nuvoton.com,
	 imaginos32@gmail.com, wuhaotsh@google.com, philmd@mailo.com,
	 fanjason@google.com, Titus Rwantare <titusr@google.com>
Subject: [PATCH 4/8] hw/sensor: update adm1266 block transfers
Date: Wed, 29 Jul 2026 23:13:19 +0000	[thread overview]
Message-ID: <20260729231325.3808993-5-titusr@google.com> (raw)
In-Reply-To: <20260729231325.3808993-1-titusr@google.com>

Fixes an issue with reading the MFR_* registers on the ADM1266, this
device has an unconventional access pattern where a block write of
length 1 is written to the MFR_* register with the length of the data
to be read back. Simultaneously, it is possible to write to the contents
of these registers so long as the block write is longer than 1.

Signed-off-by: Titus Rwantare <titusr@google.com>
---
 hw/sensor/adm1266.c        | 124 ++++++++++++++++++++++++++++++++-----
 tests/qtest/adm1266-test.c |  25 +++++---
 2 files changed, 126 insertions(+), 23 deletions(-)

diff --git a/hw/sensor/adm1266.c b/hw/sensor/adm1266.c
index 37d1cffd57..2979557309 100644
--- a/hw/sensor/adm1266.c
+++ b/hw/sensor/adm1266.c
@@ -37,13 +37,14 @@ OBJECT_DECLARE_SIMPLE_TYPE(ADM1266State, ADM1266)
 #define ADM1266_CAPABILITY_NO_PEC               0x20
 #define ADM1266_PMBUS_REVISION_DEFAULT          0x22
 #define ADM1266_MFR_ID_DEFAULT                  "ADI"
-#define ADM1266_MFR_ID_DEFAULT_LEN              32
 #define ADM1266_MFR_MODEL_DEFAULT               "ADM1266-A1"
-#define ADM1266_MFR_MODEL_DEFAULT_LEN           32
 #define ADM1266_MFR_REVISION_DEFAULT            "25"
-#define ADM1266_MFR_REVISION_DEFAULT_LEN        8
+#define ADM1266_MFR_LOCATION_DEFAULT            "0000"
+#define ADM1266_MFR_DATE_DEFAULT                "0000"
+#define ADM1266_MFR_SERIAL_DEFAULT              "0000"
 
-#define ADM1266_NUM_PAGES               17
+#define ADM1266_NUM_PAGES                       17
+#define ADM1266_READ_LENGTH_DEFAULT             48
 /**
  * PAGE Index
  * Page 0 VH1.
@@ -66,10 +67,14 @@ OBJECT_DECLARE_SIMPLE_TYPE(ADM1266State, ADM1266)
  */
 typedef struct ADM1266State {
     PMBusDevice parent;
+    uint8_t read_length;
 
     char mfr_id[32];
     char mfr_model[32];
     char mfr_rev[8];
+    char mfr_location[48];
+    char mfr_date[16];
+    char mfr_serial[32];
 } ADM1266State;
 
 static const uint8_t adm1266_ic_device_id[] = {0x03, 0x41, 0x12, 0x66};
@@ -95,9 +100,34 @@ static void adm1266_exit_reset(Object *obj, ResetType type)
         pmdev->pages[i].revision = ADM1266_PMBUS_REVISION_DEFAULT;
     }
 
-    strncpy(s->mfr_id, ADM1266_MFR_ID_DEFAULT, 4);
-    strncpy(s->mfr_model, ADM1266_MFR_MODEL_DEFAULT, 11);
-    strncpy(s->mfr_rev, ADM1266_MFR_REVISION_DEFAULT, 3);
+    memcpy(s->mfr_id, ADM1266_MFR_ID_DEFAULT, 4);
+    memcpy(s->mfr_model, ADM1266_MFR_MODEL_DEFAULT, 11);
+    memcpy(s->mfr_rev, ADM1266_MFR_REVISION_DEFAULT, 3);
+    memcpy(s->mfr_location, ADM1266_MFR_LOCATION_DEFAULT, 5);
+    memcpy(s->mfr_date, ADM1266_MFR_DATE_DEFAULT, 5);
+    memcpy(s->mfr_serial, ADM1266_MFR_SERIAL_DEFAULT, 5);
+    s->read_length = ADM1266_READ_LENGTH_DEFAULT;
+}
+
+static void adm1266_send_string(PMBusDevice *pmdev, const char *str)
+{
+    ADM1266State *s = ADM1266(pmdev);
+    size_t len = strlen(str);
+
+    if (s->read_length < len) {
+        len = s->read_length;
+    }
+
+    g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);
+    pmdev->out_buf[len + pmdev->out_buf_len] = len;
+
+    for (int i = len - 1; i >= 0; i--) {
+        pmdev->out_buf[i + pmdev->out_buf_len] = str[len - 1 - i];
+    }
+    pmdev->out_buf_len += len + 1;
+
+    /* reset read length */
+    s->read_length = ADM1266_READ_LENGTH_DEFAULT;
 }
 
 static uint8_t adm1266_read_byte(PMBusDevice *pmdev)
@@ -106,15 +136,27 @@ static uint8_t adm1266_read_byte(PMBusDevice *pmdev)
 
     switch (pmdev->code) {
     case PMBUS_MFR_ID:                    /* R/W block */
-        pmbus_send_string(pmdev, s->mfr_id);
+        adm1266_send_string(pmdev, s->mfr_id);
         break;
 
     case PMBUS_MFR_MODEL:                 /* R/W block */
-        pmbus_send_string(pmdev, s->mfr_model);
+        adm1266_send_string(pmdev, s->mfr_model);
         break;
 
     case PMBUS_MFR_REVISION:              /* R/W block */
-        pmbus_send_string(pmdev, s->mfr_rev);
+        adm1266_send_string(pmdev, s->mfr_rev);
+        break;
+
+    case PMBUS_MFR_LOCATION:              /* R/W block */
+        adm1266_send_string(pmdev, s->mfr_location);
+        break;
+
+    case PMBUS_MFR_DATE:                  /* R/W block */
+        adm1266_send_string(pmdev, s->mfr_date);
+        break;
+
+    case PMBUS_MFR_SERIAL:                /* R/W block */
+        adm1266_send_string(pmdev, s->mfr_serial);
         break;
 
     case PMBUS_IC_DEVICE_ID:
@@ -135,6 +177,43 @@ static uint8_t adm1266_read_byte(PMBusDevice *pmdev)
     return 0;
 }
 
+static uint8_t adm1266_receive_block(PMBusDevice *pmdev, uint8_t *dest,
+                                     size_t len)
+{
+    ADM1266State *s = ADM1266(pmdev);
+    uint8_t sent_len;
+
+    /* Exclude command code from return value */
+    pmdev->in_buf++;
+    pmdev->in_buf_len--;
+
+    /* The byte after the command code denotes the length */
+    sent_len = pmdev->in_buf[0];
+
+    /* Block writes with length 1 are read requests */
+    if (sent_len == 1) {
+        s->read_length = pmdev->in_buf[1];
+        return 0;
+    }
+
+    /* exclude length byte */
+    pmdev->in_buf++;
+    pmdev->in_buf_len--;
+
+    /* Be as conservative as possible on how much data to receive */
+    if (pmdev->in_buf_len < len) {
+        len = pmdev->in_buf_len;
+    }
+    if (sent_len < len) {
+        len = sent_len;
+    }
+
+    /* dest may contain data from previous writes */
+    memset(dest, 0, len);
+    memcpy(dest, pmdev->in_buf, len);
+    return len;
+}
+
 static int adm1266_write_data(PMBusDevice *pmdev, const uint8_t *buf,
                               uint8_t len)
 {
@@ -142,16 +221,31 @@ static int adm1266_write_data(PMBusDevice *pmdev, const uint8_t *buf,
 
     switch (pmdev->code) {
     case PMBUS_MFR_ID:                    /* R/W block */
-        pmbus_receive_block(pmdev, (uint8_t *)s->mfr_id, sizeof(s->mfr_id));
+        adm1266_receive_block(pmdev, (uint8_t *)s->mfr_id, sizeof(s->mfr_id));
         break;
 
     case PMBUS_MFR_MODEL:                 /* R/W block */
-        pmbus_receive_block(pmdev, (uint8_t *)s->mfr_model,
-                            sizeof(s->mfr_model));
+        adm1266_receive_block(pmdev, (uint8_t *)s->mfr_model,
+                              sizeof(s->mfr_model));
         break;
 
     case PMBUS_MFR_REVISION:               /* R/W block*/
-        pmbus_receive_block(pmdev, (uint8_t *)s->mfr_rev, sizeof(s->mfr_rev));
+        adm1266_receive_block(pmdev, (uint8_t *)s->mfr_rev, sizeof(s->mfr_rev));
+        break;
+
+    case PMBUS_MFR_LOCATION:               /* R/W block*/
+        adm1266_receive_block(pmdev, (uint8_t *)s->mfr_location,
+                              sizeof(s->mfr_location));
+        break;
+
+    case PMBUS_MFR_DATE:                   /* R/W block*/
+        adm1266_receive_block(pmdev, (uint8_t *)s->mfr_date,
+                              sizeof(s->mfr_date));
+        break;
+
+    case PMBUS_MFR_SERIAL:                 /* R/W block*/
+        adm1266_receive_block(pmdev, (uint8_t *)s->mfr_serial,
+                              sizeof(s->mfr_serial));
         break;
 
     case ADM1266_SET_RTC:   /* do nothing */
@@ -212,7 +306,7 @@ static void adm1266_init(Object *obj)
 {
     PMBusDevice *pmdev = PMBUS_DEVICE(obj);
     uint64_t flags = PB_HAS_VOUT_MODE | PB_HAS_VOUT | PB_HAS_VOUT_MARGIN |
-                     PB_HAS_VOUT_RATING | PB_HAS_STATUS_MFR_SPECIFIC;
+                     PB_HAS_VOUT_RATING;
 
     for (int i = 0; i < ADM1266_NUM_PAGES; i++) {
         pmbus_page_config(pmdev, i, flags);
diff --git a/tests/qtest/adm1266-test.c b/tests/qtest/adm1266-test.c
index 726e475938..fa8bbc5795 100644
--- a/tests/qtest/adm1266-test.c
+++ b/tests/qtest/adm1266-test.c
@@ -83,20 +83,28 @@ static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
     compare_string(i2cdev, PMBUS_MFR_REVISION, ADM1266_MFR_REVISION_DEFAULT);
 }
 
-/* test r/w registers */
-static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
+static void test_partial_reads(void *obj, void *data, QGuestAllocator *alloc)
 {
     QI2CDevice *i2cdev = (QI2CDevice *)obj;
+    /* 1 byte block write requesting 7 byte response */
+    uint8_t req_len[] = {0x01, 0x7};
 
-    /* empty strings */
-    i2c_set8(i2cdev, PMBUS_MFR_ID, 0);
-    compare_string(i2cdev, PMBUS_MFR_ID, "");
+    i2c_write_block(i2cdev, PMBUS_MFR_MODEL, req_len, sizeof(req_len));
+    compare_string(i2cdev, PMBUS_MFR_MODEL, "ADM1266");
 
-    i2c_set8(i2cdev, PMBUS_MFR_MODEL, 0);
+    req_len[1] = 0;
+    i2c_write_block(i2cdev, PMBUS_MFR_MODEL, req_len, sizeof(req_len));
     compare_string(i2cdev, PMBUS_MFR_MODEL, "");
 
-    i2c_set8(i2cdev, PMBUS_MFR_REVISION, 0);
-    compare_string(i2cdev, PMBUS_MFR_REVISION, "");
+    req_len[1] = 100;
+    i2c_write_block(i2cdev, PMBUS_MFR_MODEL, req_len, sizeof(req_len));
+    compare_string(i2cdev, PMBUS_MFR_MODEL, ADM1266_MFR_MODEL_DEFAULT);
+}
+
+/* test r/w registers */
+static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
 
     /* test strings */
     write_and_compare_string(i2cdev, PMBUS_MFR_ID, TEST_STRING_A,
@@ -118,6 +126,7 @@ static void adm1266_register_nodes(void)
     qos_node_consumes("adm1266", "i2c-bus", &opts);
 
     qos_add_test("test_defaults", "adm1266", test_defaults, NULL);
+    qos_add_test("test_partial_reads", "adm1266", test_partial_reads, NULL);
     qos_add_test("test_rw_regs", "adm1266", test_rw_regs, NULL);
 }
 
-- 
2.55.0.508.g3f0d502094-goog



  parent reply	other threads:[~2026-07-29 23:14 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-29 23:13 [PATCH 0/8] hw/i2c: PMBus updates and adm1266 fixes Titus Rwantare
2026-07-29 23:13 ` [PATCH 1/8] osdep: add DIV_ROUND_CLOSEST Titus Rwantare
2026-07-29 23:13 ` [PATCH 2/8] hw/i2c: pmbus: add milliunits linear mode functions Titus Rwantare
2026-07-29 23:13 ` [PATCH 3/8] hw/i2c: smbus: increase MAX_DATA_LEN Titus Rwantare
2026-07-29 23:13 ` Titus Rwantare [this message]
2026-07-29 23:13 ` [PATCH 5/8] hw/sensor: switch adm1266 to millivolts vout Titus Rwantare
2026-07-29 23:13 ` [PATCH 6/8] hw/i2c: fix VOUT_MODE representation on little-endian machines Titus Rwantare
2026-07-29 23:13 ` [PATCH 7/8] hw/sensor: adm1266: expose vout_mode over QMP Titus Rwantare
2026-07-29 23:13 ` [PATCH 8/8] hw/sensor: adm1266: set default VOUT mode Titus Rwantare

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260729231325.3808993-5-titusr@google.com \
    --to=titusr@google.com \
    --cc=fanjason@google.com \
    --cc=imaginos32@gmail.com \
    --cc=kfting@nuvoton.com \
    --cc=peter.maydell@linaro.org \
    --cc=philmd@mailo.com \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=wuhaotsh@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.