blob: 91eda5ca74d0424b6d829b602a1406ec9b0da1b5 [file]
/*
* QTest testcase for the ADC128D818 ADC
*
* Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qemu/bitops.h"
#include "libqos/i2c.h"
#include "libqos/qgraph.h"
#include "libqtest-single.h"
#include "qobject/qdict.h"
#define ADC128D818_TEST_ID "adc128d818-test"
#define ADC128D818_TEST_ADDR 0x1f
/* Register addresses */
#define REG_CONFIG 0x00
#define REG_INT_STATUS 0x01
#define REG_INT_MASK 0x03
#define REG_CONV_RATE 0x07
#define REG_CH_DISABLE 0x08
#define REG_ONE_SHOT 0x09
#define REG_DEEP_SHUTDOWN 0x0a
#define REG_ADV_CONFIG 0x0b
#define REG_BUSY_STATUS 0x0c
/* Channel Reading Registers (16-bit, read-only) */
#define REG_CH_READING_BASE 0x20
/* Limit Registers (8-bit, read/write) */
#define REG_LIMIT_BASE 0x2a
/* ID Registers (read-only) */
#define REG_MANUFACTURER_ID 0x3e
#define REG_REVISION_ID 0x3f
/* Configuration Register (0x00) bitfields */
#define CONFIG_START BIT(0)
#define CONFIG_INT_ENABLE BIT(1)
#define CONFIG_INT_CLEAR BIT(3)
#define CONFIG_INITIALIZATION BIT(7)
/* Advanced Configuration Register (0x0b) bitfields */
#define ADV_CONFIG_EXT_REF_EN BIT(0)
#define ADV_CONFIG_MODE_1 (1 << 1)
#define ADV_CONFIG_MODE_2 (2 << 1)
#define ADV_CONFIG_MODE_3 (3 << 1)
/* Number of channels */
#define NUM_CHANNELS 8
/* Internal VREF in mV */
#define INTERNAL_VREF_MV 2560
/* QMP helpers for setting device properties */
static void qmp_adc128d818_set(const char *property, int value)
{
QDict *resp;
resp = qmp("{ 'execute': 'qom-set', 'arguments':"
" { 'path': %s, 'property': %s, 'value': %d } }",
ADC128D818_TEST_ID, property, value);
g_assert(qdict_haskey(resp, "return"));
qobject_unref(resp);
}
static int qmp_adc128d818_get(const char *property)
{
QDict *resp;
int ret;
resp = qmp("{ 'execute': 'qom-get', 'arguments':"
" { 'path': %s, 'property': %s } }",
ADC128D818_TEST_ID, property);
g_assert(qdict_haskey(resp, "return"));
ret = qdict_get_int(resp, "return");
qobject_unref(resp);
return ret;
}
/* Manufacturer and Revision ID registers */
static void test_id_registers(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
g_assert_cmphex(i2c_get8(dev, REG_MANUFACTURER_ID), ==, 0x01);
g_assert_cmphex(i2c_get8(dev, REG_REVISION_ID), ==, 0x09);
}
/* Power-on-reset default values */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
unsigned ch;
g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08);
g_assert_cmphex(i2c_get8(dev, REG_INT_STATUS), ==, 0x00);
g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00);
g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x00);
g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00);
g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00);
g_assert_cmphex(i2c_get8(dev, REG_ADV_CONFIG), ==, 0x00);
g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02);
for (ch = 0u; ch < NUM_CHANNELS; ch++) {
g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u), ==, 0xFF);
g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u + 1u), ==, 0x00);
}
}
/* Software reset via INITIALIZATION bit */
static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
i2c_set8(dev, REG_INT_MASK, 0xAA);
i2c_set8(dev, REG_CH_DISABLE, 0x55);
i2c_set8(dev, REG_LIMIT_BASE, 0x42);
g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0xAA);
g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x55);
g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x42);
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08);
g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00);
g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00);
g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0xFF);
g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02);
}
/* Verify ain property readback via QMP */
static void test_ain_property(void *obj, void *data, QGuestAllocator *alloc)
{
int value;
qmp_adc128d818_set("ain3", 1500);
value = qmp_adc128d818_get("ain3");
g_test_message("Set ain3 = 1500 mV, readback = %d mV", value);
g_assert_cmpint(value, ==, 1500);
qmp_adc128d818_set("temperature", 37500);
value = qmp_adc128d818_get("temperature");
g_test_message("Set temperature = 37500 mC, readback = %d mC", value);
g_assert_cmpint(value, ==, 37500);
}
/* Voltage conversion */
static void test_voltage_conversion(void *obj, void *data,
QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
qmp_adc128d818_set("ain0", 1280);
g_test_message("Injected ain0 = 1280 mV");
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Read ch0: raw 0x%04x -> %u mV", reading,
(reading >> 4u) * INTERNAL_VREF_MV / 4096u);
g_assert_cmphex(reading, ==, 0x8000);
qmp_adc128d818_set("ain1", 2560);
g_test_message("Injected ain1 = 2560 mV");
reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
g_test_message("Read ch1: raw 0x%04x -> %u mV", reading,
(reading >> 4u) * INTERNAL_VREF_MV / 4096u);
g_assert_cmphex(reading, ==, 0xFFF0);
qmp_adc128d818_set("ain2", 0);
g_test_message("Injected ain2 = 0 mV");
reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
g_test_message("Read ch2: raw 0x%04x -> %u mV", reading,
(reading >> 4u) * INTERNAL_VREF_MV / 4096u);
g_assert_cmphex(reading, ==, 0x0000);
}
/* Temperature conversion (mode 0, channel 7 = temperature) */
static void
test_temperature_conversion(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
qmp_adc128d818_set("temperature", 25000);
g_test_message("Injected temperature = 25000 mC (25.0 deg C)");
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
g_test_message("Read ch7: raw 0x%04x -> %d mC", reading,
(int16_t)(reading & 0xFF80u) * 500 / 128);
g_assert_cmphex(reading, ==, 0x1900);
}
/* Channels with distinct voltages */
static void test_all_channels(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
static const uint16_t ain_mv[NUM_CHANNELS] = {
0, 320, 640, 960, 1280, 1920, 2240, 2560
};
static const uint16_t expect[NUM_CHANNELS] = {
0x0000, 0x2000, 0x4000, 0x6000, 0x8000, 0xC000, 0xE000, 0xFFF0
};
uint16_t reading;
unsigned ch;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
for (ch = 0u; ch < NUM_CHANNELS; ch++) {
char name[8];
snprintf(name, sizeof(name), "ain%u", ch);
qmp_adc128d818_set(name, ain_mv[ch]);
}
i2c_set8(dev, REG_CONFIG, CONFIG_START);
for (ch = 0u; ch < NUM_CHANNELS; ch++) {
reading = i2c_get16(dev, REG_CH_READING_BASE + ch);
g_test_message("ch%u: ain %u mV -> raw 0x%04x (expect 0x%04x)",
ch, ain_mv[ch], reading, expect[ch]);
g_assert_cmphex(reading, ==, expect[ch]);
}
}
/* Voltage conversion edge cases */
static void test_voltage_edges(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
qmp_adc128d818_set("ain0", 3000);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Over-range 3000 mV: raw 0x%04x (expect 0xFFF0)", reading);
g_assert_cmphex(reading, ==, 0xFFF0);
qmp_adc128d818_set("ain1", 1);
reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
g_test_message("1 mV: raw 0x%04x (expect 0x0010)", reading);
g_assert_cmphex(reading, ==, 0x0010);
qmp_adc128d818_set("ain2", 640);
reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
g_test_message("640 mV (quarter): raw 0x%04x (expect 0x4000)", reading);
g_assert_cmphex(reading, ==, 0x4000);
qmp_adc128d818_set("ain3", 1920);
reading = i2c_get16(dev, REG_CH_READING_BASE + 3u);
g_test_message("1920 mV (3/4): raw 0x%04x (expect 0xC000)", reading);
g_assert_cmphex(reading, ==, 0xC000);
}
/* Temperature conversion edge cases */
static void test_temperature_edges(void *obj, void *data,
QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
qmp_adc128d818_set("temperature", 0);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
g_test_message("0 C: raw 0x%04x (expect 0x0000)", reading);
g_assert_cmphex(reading, ==, 0x0000);
qmp_adc128d818_set("temperature", -25000);
reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
g_test_message("-25 C: raw 0x%04x (expect 0xE700)", reading);
g_assert_cmphex(reading, ==, 0xE700);
qmp_adc128d818_set("temperature", 127500);
reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
g_test_message("+127.5 C: raw 0x%04x (expect 0x7F80)", reading);
g_assert_cmphex(reading, ==, 0x7F80);
qmp_adc128d818_set("temperature", -128000);
reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
g_test_message("-128 C: raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
qmp_adc128d818_set("temperature", 200000);
reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
g_test_message("200 C (clamped): raw 0x%04x (expect 0x7F80)", reading);
g_assert_cmphex(reading, ==, 0x7F80);
qmp_adc128d818_set("temperature", -200000);
reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
g_test_message("-200 C (clamped): raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
}
/* External voltage reference */
static void test_ext_vref(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
qmp_adc128d818_set("ext-vref-mv", 4096);
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN | ADV_CONFIG_MODE_1);
qmp_adc128d818_set("ain0", 1000);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("1000 mV / 4096 mV VREF: raw 0x%04x (expect 0x3E80)",
reading);
g_assert_cmphex(reading, ==, 0x3E80);
qmp_adc128d818_set("ain1", 2048);
reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
g_test_message("2048 mV / 4096 mV VREF: raw 0x%04x (expect 0x8000)",
reading);
g_assert_cmphex(reading, ==, 0x8000);
}
/* Interrupt status set on limit violation; persists while fault remains */
static void test_interrupt_status(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint8_t status;
i2c_set8(dev, REG_LIMIT_BASE, 0x10);
g_test_message("Set ch0 high limit = 0x10");
qmp_adc128d818_set("ain0", 2560);
g_test_message("Injected ain0 = 2560 mV (exceeds limit)");
i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("INT_STATUS = 0x%02x (expect bit 0 set)", status);
g_assert_cmphex(status & 0x01u, ==, 0x01);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("INT_STATUS after re-read = 0x%02x (expect bit 0 still set)",
status);
g_assert_cmphex(status & 0x01u, ==, 0x01);
qmp_adc128d818_set("ain0", 80);
g_test_message("Injected ain0 = 80 mV (within limit)");
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("INT_STATUS after fault cleared = 0x%02x "
"(expect bit 0 clear)", status);
g_assert_cmphex(status & 0x01u, ==, 0x00);
}
/* INT_CLEAR stops the round-robin monitoring loop */
static void test_int_clear(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint8_t status;
i2c_set8(dev, REG_LIMIT_BASE, 0x10);
qmp_adc128d818_set("ain0", 2560);
i2c_set8(dev, REG_CONFIG,
CONFIG_START | CONFIG_INT_ENABLE | CONFIG_INT_CLEAR);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("INT_STATUS with INT_CLEAR set = 0x%02x (expect 0x00)",
status);
g_assert_cmphex(status, ==, 0x00);
i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("INT_STATUS after INT_CLEAR cleared = 0x%02x (expect bit 0)",
status);
g_assert_cmphex(status & 0x01u, ==, 0x01);
}
/* Low-limit interrupt triggers correctly */
static void test_low_limit(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint8_t status;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
i2c_set8(dev, REG_LIMIT_BASE + 3u, 0x80);
g_test_message("Set ch1 low limit = 0x80");
i2c_set8(dev, REG_LIMIT_BASE + 5u, 0x80);
g_test_message("Set ch2 low limit = 0x80");
qmp_adc128d818_set("ain1", 640);
qmp_adc128d818_set("ain2", 1280);
qmp_adc128d818_set("ain0", 1280);
i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("INT_STATUS = 0x%02x (expect bits 1 and 2 set)", status);
g_assert_cmphex(status & 0x02u, ==, 0x02);
g_assert_cmphex(status & 0x04u, ==, 0x04);
g_assert_cmphex(status & 0x01u, ==, 0x00);
}
/* Temperature high-limit interrupt with hysteresis */
static void test_temp_hysteresis(void *obj, void *data,
QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint8_t status;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u, 0x32);
i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u + 1u, 0x28);
qmp_adc128d818_set("temperature", 25000);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("25 C: INT_STATUS = 0x%02x (temp bit expect clear)", status);
g_assert_cmphex(status & 0x80u, ==, 0x00);
qmp_adc128d818_set("temperature", 55000);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("55 C: INT_STATUS = 0x%02x (temp bit expect set)", status);
g_assert_cmphex(status & 0x80u, ==, 0x80);
qmp_adc128d818_set("temperature", 45000);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("45 C (hysteresis): INT_STATUS = 0x%02x "
"(temp bit expect set)", status);
g_assert_cmphex(status & 0x80u, ==, 0x80);
qmp_adc128d818_set("temperature", 35000);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("35 C: INT_STATUS = 0x%02x (temp bit expect clear)", status);
g_assert_cmphex(status & 0x80u, ==, 0x00);
}
/* Channel disable prevents conversion */
static void test_channel_disable(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_CH_DISABLE, 0x01);
g_test_message("Disabled channel 0");
qmp_adc128d818_set("ain0", 1280);
g_test_message("Injected ain0 = 1280 mV (disabled)");
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Read ch0 (disabled): raw 0x%04x", reading);
g_assert_cmphex(reading, ==, 0x0000);
qmp_adc128d818_set("ain1", 1280);
g_test_message("Injected ain1 = 1280 mV (enabled)");
reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
g_test_message("Read ch1 (enabled): raw 0x%04x -> %u mV", reading,
(reading >> 4u) * INTERNAL_VREF_MV / 4096u);
g_assert_cmphex(reading, ==, 0x8000);
}
/* One-shot conversion in shutdown mode */
static void test_one_shot(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
qmp_adc128d818_set("ain0", 1280);
g_test_message("Injected ain0 = 1280 mV (device stopped)");
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Read ch0 before one-shot: raw 0x%04x", reading);
g_assert_cmphex(reading, ==, 0x0000);
g_assert_cmphex(i2c_get8(dev, REG_ONE_SHOT), ==, 0x00);
i2c_set8(dev, REG_ONE_SHOT, 0x00);
g_test_message("Triggered one-shot conversion with value 0x00");
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Read ch0 after one-shot: raw 0x%04x -> %u mV", reading,
(reading >> 4u) * INTERNAL_VREF_MV / 4096u);
g_assert_cmphex(reading, ==, 0x8000);
}
/* Mode 1 makes channel 7 a voltage input instead of temperature */
static void test_mode_selection(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
g_test_message("Set mode 1 (all voltage channels)");
qmp_adc128d818_set("ain7", 1280);
qmp_adc128d818_set("temperature", 50000);
g_test_message("Injected ain7 = 1280 mV, temperature = 50000 mC");
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
g_test_message("Read ch7 (mode 1): raw 0x%04x -> %u mV", reading,
(reading >> 4u) * INTERNAL_VREF_MV / 4096u);
g_assert_cmphex(reading, ==, 0x8000);
}
/* Mode 2 - 4 pseudo-differential pairs */
static void test_mode2_diff(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
g_test_message("Set mode 2 (4 pseudo-differential pairs)");
qmp_adc128d818_set("ain0", 2000);
qmp_adc128d818_set("ain1", 720);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Pair 0 (IN0-IN1): raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
qmp_adc128d818_set("ain3", 1920);
qmp_adc128d818_set("ain2", 640);
reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
g_test_message("Pair 1 (IN3-IN2): raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
qmp_adc128d818_set("ain4", 1500);
qmp_adc128d818_set("ain5", 220);
reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
g_test_message("Pair 2 (IN4-IN5): raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
qmp_adc128d818_set("ain7", 2560);
qmp_adc128d818_set("ain6", 1280);
reading = i2c_get16(dev, REG_CH_READING_BASE + 3u);
g_test_message("Pair 3 (IN7-IN6): raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
reading = i2c_get16(dev, REG_CH_READING_BASE + 4u);
g_test_message("Reserved ch4: raw 0x%04x (expect 0x0000)", reading);
g_assert_cmphex(reading, ==, 0x0000);
qmp_adc128d818_set("ain0", 500);
qmp_adc128d818_set("ain1", 1000);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Pair 0 negative dV: raw 0x%04x (expect 0x0000)", reading);
g_assert_cmphex(reading, ==, 0x0000);
}
/* Mode 3 - 4 single-ended + 2 pseudo-differential pairs */
static void test_mode3_mixed(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_3);
g_test_message("Set mode 3 (4 single-ended + 2 differential)");
qmp_adc128d818_set("ain0", 1280);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Ch0 single-ended: raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
qmp_adc128d818_set("ain4", 1500);
qmp_adc128d818_set("ain5", 220);
reading = i2c_get16(dev, REG_CH_READING_BASE + 4u);
g_test_message("Ch4 diff (IN4-IN5): raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
qmp_adc128d818_set("ain7", 2560);
qmp_adc128d818_set("ain6", 1280);
reading = i2c_get16(dev, REG_CH_READING_BASE + 5u);
g_test_message("Ch5 diff (IN7-IN6): raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
reading = i2c_get16(dev, REG_CH_READING_BASE + 6u);
g_test_message("Reserved ch6: raw 0x%04x (expect 0x0000)", reading);
g_assert_cmphex(reading, ==, 0x0000);
qmp_adc128d818_set("temperature", 25000);
reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
g_test_message("Ch7 temperature: raw 0x%04x (expect 0x1900)", reading);
g_assert_cmphex(reading, ==, 0x1900);
}
/* Mode change resets channel readings and interrupt status */
static void test_mode_change_reset(void *obj, void *data,
QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
uint8_t status;
qmp_adc128d818_set("ain0", 1280);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Before mode change, ch0: raw 0x%04x", reading);
g_assert_cmphex(reading, !=, 0x0000);
i2c_set8(dev, REG_CONFIG, 0x00);
i2c_set8(dev, REG_LIMIT_BASE, 0x10);
qmp_adc128d818_set("ain0", 2560);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
i2c_set8(dev, REG_CONFIG, 0x00);
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("After mode change, ch0: raw 0x%04x (expect 0x0000)",
reading);
g_assert_cmphex(reading, ==, 0x0000);
status = i2c_get8(dev, REG_INT_STATUS);
g_test_message("After mode change, INT_STATUS: 0x%02x (expect 0x00)",
status);
g_assert_cmphex(status, ==, 0x00);
g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x10);
g_test_message("Limit register preserved after mode change");
}
/* QOM property changes trigger correct differential conversion */
static void test_diff_qom_trigger(void *obj, void *data,
QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
qmp_adc128d818_set("ain0", 0);
qmp_adc128d818_set("ain1", 0);
qmp_adc128d818_set("ain2", 0);
qmp_adc128d818_set("ain3", 0);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
qmp_adc128d818_set("ain0", 2000);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("After ain0=2000, ain1=0: pair0 = 0x%04x (expect 0xC800)",
reading);
g_assert_cmphex(reading, ==, 0xC800);
qmp_adc128d818_set("ain1", 720);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("After ain1=720: pair0 = 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
qmp_adc128d818_set("ain3", 1920);
qmp_adc128d818_set("ain2", 640);
reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
g_test_message("Pair 1 (IN3-IN2) via QOM: 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
}
/* One-shot conversion works in deep shutdown */
static void test_deep_shutdown(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
qmp_adc128d818_set("ain0", 1280);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_assert_cmphex(reading, ==, 0x8000);
i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01);
g_test_message("DEEP_SHUTDOWN write while running rejected");
g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00);
i2c_set8(dev, REG_CONFIG, 0x00);
i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01);
qmp_adc128d818_set("ain0", 0);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Deep shutdown, no one-shot: raw 0x%04x (expect 0x8000)",
reading);
g_assert_cmphex(reading, ==, 0x8000);
i2c_set8(dev, REG_ONE_SHOT, 0x01);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("Deep shutdown one-shot: raw 0x%04x (expect 0x0000)",
reading);
g_assert_cmphex(reading, ==, 0x0000);
g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x01);
i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x00);
qmp_adc128d818_set("ain0", 1280);
i2c_set8(dev, REG_ONE_SHOT, 0x01);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("After exit shutdown: raw 0x%04x (expect 0x8000)", reading);
g_assert_cmphex(reading, ==, 0x8000);
}
/* BUSY_STATUS NOT_READY clears after first conversion */
static void test_busy_status(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x02);
g_test_message("After reset: BUSY_STATUS = 0x%02x (NOT_READY set)",
i2c_get8(dev, REG_BUSY_STATUS));
qmp_adc128d818_set("ain0", 0);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x00);
g_test_message("After conversion: BUSY_STATUS = 0x%02x (NOT_READY cleared)",
i2c_get8(dev, REG_BUSY_STATUS));
}
/* Programming Channel Disable resets channel readings */
static void test_chan_disable_clears(void *obj, void *data,
QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
qmp_adc128d818_set("ain0", 1280);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
i2c_set8(dev, REG_CONFIG, 0x00);
i2c_set8(dev, REG_CH_DISABLE, 0x02);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("After CH_DISABLE write: ch0 raw 0x%04x (expect 0x0000)",
reading);
g_assert_cmphex(reading, ==, 0x0000);
}
/* Programming Advanced Configuration always resets channel readings */
static void test_adv_config_clears(void *obj, void *data,
QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
uint16_t reading;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
qmp_adc128d818_set("ain0", 1280);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
i2c_set8(dev, REG_CONFIG, 0x00);
i2c_set8(dev, REG_ADV_CONFIG, 0x00);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("After same-mode ADV_CONFIG: ch0 0x%04x (expect 0x0000)",
reading);
g_assert_cmphex(reading, ==, 0x0000);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
i2c_set8(dev, REG_CONFIG, 0x00);
qmp_adc128d818_set("ext-vref-mv", 4096);
i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN);
reading = i2c_get16(dev, REG_CH_READING_BASE);
g_test_message("After ext-vref toggle: ch0 0x%04x (expect 0x0000)",
reading);
g_assert_cmphex(reading, ==, 0x0000);
}
/* Conversion Rate register may only be programmed while in shutdown */
static void test_conv_rate(void *obj, void *data, QGuestAllocator *alloc)
{
QI2CDevice *dev = (QI2CDevice *)obj;
i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
i2c_set8(dev, REG_CONV_RATE, 0x01);
g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01);
i2c_set8(dev, REG_CONFIG, CONFIG_START);
i2c_set8(dev, REG_CONV_RATE, 0x00);
g_test_message("CONV_RATE while running: 0x%02x (expect unchanged 0x01)",
i2c_get8(dev, REG_CONV_RATE));
g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01);
}
static void adc128d818_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
.extra_device_opts = "id=" ADC128D818_TEST_ID
",address=0x1f"
};
add_qi2c_address(&opts, &(QI2CAddress) { ADC128D818_TEST_ADDR });
qos_node_create_driver("adc128d818", i2c_device_create);
qos_node_consumes("adc128d818", "i2c-bus", &opts);
qos_add_test("id-registers", "adc128d818", test_id_registers, NULL);
qos_add_test("defaults", "adc128d818", test_defaults, NULL);
qos_add_test("soft-reset", "adc128d818", test_soft_reset, NULL);
qos_add_test("ain-property", "adc128d818", test_ain_property, NULL);
qos_add_test("voltage-conversion", "adc128d818", test_voltage_conversion,
NULL);
qos_add_test("temperature-conversion", "adc128d818",
test_temperature_conversion, NULL);
qos_add_test("all-channels", "adc128d818", test_all_channels, NULL);
qos_add_test("voltage-edges", "adc128d818", test_voltage_edges, NULL);
qos_add_test("temperature-edges", "adc128d818", test_temperature_edges,
NULL);
qos_add_test("ext-vref", "adc128d818", test_ext_vref, NULL);
qos_add_test("interrupt-status", "adc128d818", test_interrupt_status, NULL);
qos_add_test("int-clear", "adc128d818", test_int_clear, NULL);
qos_add_test("low-limit", "adc128d818", test_low_limit, NULL);
qos_add_test("temp-hysteresis", "adc128d818", test_temp_hysteresis, NULL);
qos_add_test("channel-disable", "adc128d818", test_channel_disable, NULL);
qos_add_test("one-shot", "adc128d818", test_one_shot, NULL);
qos_add_test("mode-selection", "adc128d818", test_mode_selection, NULL);
qos_add_test("mode2-diff", "adc128d818", test_mode2_diff, NULL);
qos_add_test("mode3-mixed", "adc128d818", test_mode3_mixed, NULL);
qos_add_test("mode-change-reset", "adc128d818", test_mode_change_reset,
NULL);
qos_add_test("diff-qom-trigger", "adc128d818", test_diff_qom_trigger, NULL);
qos_add_test("deep-shutdown", "adc128d818", test_deep_shutdown, NULL);
qos_add_test("busy-status", "adc128d818", test_busy_status, NULL);
qos_add_test("chan-disable-clears", "adc128d818", test_chan_disable_clears,
NULL);
qos_add_test("adv-config-clears", "adc128d818", test_adv_config_clears,
NULL);
qos_add_test("conv-rate", "adc128d818", test_conv_rate, NULL);
}
libqos_init(adc128d818_register_nodes);