blob: e647f9ecb2e5474323782b42f4144fdddf19fa30 [file] [edit]
// Copyright 2026 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package zephyr_dtsi
import (
"bytes"
"os"
"strings"
"testing"
)
func TestGenerateDTSI_Mock(t *testing.T) {
mockPinMap := map[string]string{
"A1": "ADC0",
"B2": "GPIOB4/I2C0_SDA0",
"B3": "GPIOB5/I2C0_SCL0",
"C3": "GPIOC3/PWM0",
"D4": "GPIOD4",
"E5": "GPIOE5",
"F5": "GPIOF5/TB1",
"G1": "KSI0/GPIO31",
"G2": "KSO01/GPIO21",
}
chip := &Chip{
Name: "npcx-mock",
Vendor: "npcx",
PinMap: mockPinMap,
}
configs := []PinConfig{
{
PinNumber: "A1",
PinName: "ADC_0",
NetName: "EC_ADC0",
IO: "ADC",
Enum: "ADC_EVB_CH_0",
},
{
PinNumber: "B3",
PinName: "I2C0_SCL",
NetName: "EC_I2C_SCL",
Enum: "I2C_PORT_0",
IO: "OD",
PinTypeOverride: "I2C_CLOCK",
},
{
PinNumber: "B2",
PinName: "I2C0_SDA",
NetName: "EC_I2C_SDA",
IO: "OD",
PinTypeOverride: "I2C_DATA",
},
{
PinNumber: "C3",
PinName: "PWM_0",
NetName: "EC_PWM0",
IO: "PWM",
},
{
PinNumber: "D4",
PinName: "GPIO_D4",
NetName: "EC_GPIO_D4",
Dir: "OUT",
IO: "OD",
},
{
PinNumber: "E5",
PinName: "GPIO_E5",
NetName: "EC_GPIO_E5_L",
Dir: "IN",
Pull: "UP",
PinTypeOverride: "INPUT_L",
},
{
PinNumber: "F5",
PinName: "TACH_1",
NetName: "EC_FAN_TACH",
},
{
PinNumber: "G1",
PinName: "KSI0",
NetName: "EC_KSI0",
PinTypeOverride: "KEYBOARD",
},
{
PinNumber: "G2",
PinName: "KSO01",
NetName: "EC_KSO01",
PinTypeOverride: "KEYBOARD",
},
}
var out bytes.Buffer
err := GenerateDTSI(&out, configs, chip)
if err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
got := out.String()
requiredSnippets := []string{
"named-adc-channels",
"named-gpios",
"named-i2c-ports",
"&adc0",
"&pwm0",
"&tach1",
"&kbd",
}
for _, snip := range requiredSnippets {
if !strings.Contains(got, snip) {
t.Errorf("GenerateDTSI output missing required snippet %q", snip)
}
}
}
func TestGenerateDTSI_ITE_Mock(t *testing.T) {
mockPinMap := map[string]string{
"A1": "ADC0/GPA0",
"B3": "SMCLK0/GPB3",
"B4": "SMDAT0/GPB4",
"C3": "PWM0/GPC3",
"D4": "GPIOD4",
"D6": "TACH0A/GPD6",
"G1": "KSI0/GPG1",
"G2": "KSO0/GPG2",
}
chip := &Chip{
Name: "ite-mock",
Vendor: "ite",
PinMap: mockPinMap,
}
configs := []PinConfig{
{
PinNumber: "A1",
PinName: "ADC_0",
NetName: "EC_ADC0",
IO: "ADC",
Enum: "ADC_EVB_CH_0",
},
{
PinNumber: "B3",
PinName: "I2C0_SCL",
NetName: "EC_I2C",
Enum: "I2C_PORT_0",
IO: "OD",
PinTypeOverride: "I2C_CLOCK",
},
{
PinNumber: "B4",
PinName: "I2C0_SDA",
NetName: "EC_I2C",
IO: "OD",
PinTypeOverride: "I2C_DATA",
},
{
PinNumber: "C3",
PinName: "PWM_0",
NetName: "EC_PWM0",
IO: "PWM",
},
{
PinNumber: "D6",
PinName: "TACH_0",
NetName: "EC_FAN_TACH",
PinTypeOverride: "TACH",
},
{
PinNumber: "G1",
PinName: "KSI0",
NetName: "EC_KSI0",
PinTypeOverride: "KEYBOARD",
},
{
PinNumber: "G2",
PinName: "KSO0",
NetName: "EC_KSO0",
PinTypeOverride: "KEYBOARD",
},
}
var out bytes.Buffer
err := GenerateDTSI(&out, configs, chip)
if err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
got := out.String()
requiredSnippets := []string{
"named-adc-channels",
"named-i2c-ports",
"&adc0",
"&i2c0",
"&pwm0",
"&tach0",
"&kbd",
}
for _, snip := range requiredSnippets {
if !strings.Contains(got, snip) {
t.Errorf("ITE DTSI output missing snippet %q", snip)
}
}
}
func TestGenerateDTSI_SubsystemErrorPaths(t *testing.T) {
chip := &Chip{
Name: "npcx-mock",
Vendor: "npcx",
PinMap: map[string]string{"D4": "GPIOD4"}, // D4 supports no peripheral
}
// Test ADC failure on non-ADC pin
adcConfig := []PinConfig{{PinNumber: "D4", NetName: "ADC_FAIL", IO: "ADC"}}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, adcConfig, chip); err == nil || !strings.Contains(err.Error(), "failed to resolve ADC") {
t.Errorf("Expected ADC resolution error, got %v", err)
}
// Test PWM failure on non-PWM pin
pwmConfig := []PinConfig{{PinNumber: "D4", NetName: "PWM_FAIL", IO: "PWM"}}
buf.Reset()
if err := GenerateDTSI(&buf, pwmConfig, chip); err == nil || !strings.Contains(err.Error(), "failed to resolve PWM") {
t.Errorf("Expected PWM resolution error, got %v", err)
}
// Test I2C failure on non-I2C pin
i2cConfig := []PinConfig{{PinNumber: "D4", NetName: "I2C_FAIL", IO: "OD", PinTypeOverride: "I2C_CLOCK"}}
buf.Reset()
if err := GenerateDTSI(&buf, i2cConfig, chip); err == nil || !strings.Contains(err.Error(), "failed to resolve I2C") {
t.Errorf("Expected I2C resolution error, got %v", err)
}
}
func TestGenerateDTSI_DuplicatePinsAndExclusions(t *testing.T) {
chip := &Chip{
Name: "npcx-mock",
Vendor: "npcx",
PinMap: map[string]string{
"D4": "GPIOD4",
"D5": "GPIOD5",
"D6": "GPIOD6",
},
}
configs := []PinConfig{
{PinNumber: "D4", NetName: "EC_GPIO_D4", Dir: "OUT"},
{PinNumber: "D4", NetName: "EC_DUPLICATE_D4", Dir: "OUT"}, // Should be ignored
{PinNumber: "D5", NetName: "ESPI_CLK", Dir: "IN"}, // Excluded prefix
{PinNumber: "D6", NetName: "EC_GPIO_D6", Dir: "OUT", PinTypeOverride: "OTHER"}, // Excluded type
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
out := buf.String()
if strings.Contains(out, "ec_duplicate_d4") {
t.Error("Duplicate pin D4 was not deduplicated")
}
if strings.Contains(out, "espi_clk") {
t.Error("ESPI_CLK signal was not excluded")
}
if strings.Contains(out, "ec_gpio_d6") {
t.Error("PinTypeOverride OTHER was not excluded")
}
if !strings.Contains(out, "gpio_ec_gpio_d4: ec_gpio_d4") {
t.Error("Primary pin D4 missing from output")
}
}
func TestGenerateDTSI_AllGPIOFlags(t *testing.T) {
chip := &Chip{
Name: "npcx-mock",
Vendor: "npcx",
PinMap: map[string]string{
"A1": "GPIOA1",
},
}
configs := []PinConfig{
{
PinNumber: "A1",
NetName: "EC_FLAG_TEST",
Dir: "OUT",
PinTypeOverride: "ODR_ACTIVE_L",
Voltage: "1.8V",
InitialCondition: "HIGH",
Pull: "DOWN",
},
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
expectedFlags := "(GPIO_OUTPUT | GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW | GPIO_PULL_DOWN | GPIO_OUTPUT_INIT_HIGH | GPIO_VOLTAGE_1P8)"
if !strings.Contains(buf.String(), expectedFlags) {
t.Errorf("Output missing expected flags %q.\nGot:\n%s", expectedFlags, buf.String())
}
}
func TestIntegration_ExampleEcPinout_With_RealChip(t *testing.T) {
csvFile := "test/ExampleEcPinout.csv"
data, err := os.ReadFile(csvFile)
if err != nil {
t.Skipf("Skipping integration test: %s not found", csvFile)
}
t.Run("NPCX99", func(t *testing.T) {
chip, ok := GetChip("npcx99")
if !ok {
t.Fatal("npcx99 chip definition missing")
}
configs, err := ParseCSV(bytes.NewReader(data), "NPCX99 (NPCX)")
if err != nil {
t.Fatalf("ParseCSV failed: %v", err)
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
expected, err := os.ReadFile("test/generated_npcx.dtsi")
if err != nil {
t.Fatalf("Failed to read test/generated_npcx.dtsi: %v", err)
}
if buf.String() != string(expected) {
t.Errorf("Generated NPCX99 DTSI differs from test/generated_npcx.dtsi.\nGot:\n%s\nWant:\n%s", buf.String(), string(expected))
}
})
t.Run("IT82002", func(t *testing.T) {
chip, ok := GetChip("it82002")
if !ok {
t.Fatal("it82002 chip definition missing")
}
configs, err := ParseCSV(bytes.NewReader(data), "IT82002 (ITE)")
if err != nil {
t.Fatalf("ParseCSV failed: %v", err)
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
expected, err := os.ReadFile("test/generated_ite.dtsi")
if err != nil {
t.Fatalf("Failed to read test/generated_ite.dtsi: %v", err)
}
if buf.String() != string(expected) {
t.Errorf("Generated IT82002 DTSI differs from test/generated_ite.dtsi.\nGot:\n%s\nWant:\n%s", buf.String(), string(expected))
}
})
}
func TestGenerateDTSI_IsGPIOBranches(t *testing.T) {
chip := &Chip{
Name: "npcx-mock",
Vendor: "npcx",
PinMap: map[string]string{
"D4": "GPIOD4",
"D5": "GPIOD5",
},
}
configs := []PinConfig{
{
PinNumber: "D4",
NetName: "EC_GPIO_D4",
PinTypeOverride: "INPUT",
},
{
PinNumber: "D5",
NetName: "EC_GPIO_D5",
PinTypeOverride: "INVALID_GPIO",
},
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
out := buf.String()
if !strings.Contains(out, "gpio_ec_gpio_d4: ec_gpio_d4") {
t.Error("Expected D4 to be processed as GPIO because PinTypeOverride is INPUT")
}
if strings.Contains(out, "gpio_ec_gpio_d5") {
t.Error("Expected D5 to NOT be processed as GPIO because PinTypeOverride is INVALID_GPIO")
}
}
func TestGenerateDTSI_NonGPIOHardwarePinsAndUnusedPins(t *testing.T) {
chip := &Chip{
Name: "ite-mock",
Vendor: "ite",
PinMap: map[string]string{
"L1": "WRST#",
"G6": "VCORE",
"K6": "VCC1_RST#/GPO77",
"M5": "PWM0/GPA0",
"A6": "FMISO/GPG5",
"M7": "PWM6/SSCK/GPA6",
},
}
configs := []PinConfig{
{PinNumber: "L1", NetName: "EC_ERST_L", Dir: "IN", PinTypeOverride: "INPUT_L"},
{PinNumber: "G6", NetName: "VCORE_CAP", PinTypeOverride: "UNUSED_IN"},
{PinNumber: "K6", NetName: "PLT_EC_RST_ODL", Dir: "IN", PinTypeOverride: "INPUT_L"},
{PinNumber: "A6", NetName: "EN_PP5000_USBA_VBUS", Dir: "OUT", PinTypeOverride: "UNUSED_OUT", InitialCondition: "LOW", Pull: "DOWN"},
{PinNumber: "M7", NetName: "NC", PinTypeOverride: "UNUSED_IN", Pull: "UP"},
{PinNumber: "M5", NetName: "EC_VOLDN_BTN_ODL", PinTypeOverride: "UNUSED_IN"},
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
out := buf.String()
if strings.Contains(out, "ec_erst_l") || strings.Contains(out, "plt_ec_rst_odl") || strings.Contains(out, "en_pp5000_usba_vbus") {
t.Errorf("Expected non-GPIO hardware pins, *EC_RST_ODL, and UNUSED_OUT to be excluded from named-gpios:\n%s", out)
}
expectedUnused := strings.Join([]string{
"\tunused-pins {",
"\t\tcompatible = \"unused-gpios\";",
"\t\tunused-gpios =",
"\t\t\t/* EC_VOLDN_BTN_ODL */",
"\t\t\t<&gpioa 0 (GPIO_INPUT)>,",
"\t\t\t/* NC */",
"\t\t\t<&gpioa 6 (GPIO_INPUT | GPIO_PULL_UP)>,",
"\t\t\t/* EN_PP5000_USBA_VBUS */",
"\t\t\t<&gpiog 5 (GPIO_OUTPUT | GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN | GPIO_OUTPUT_INIT_LOW)>;",
"\t};",
}, "\n")
if !strings.Contains(out, expectedUnused) {
t.Errorf("Expected unused-pins block:\n%s\nGot:\n%s", expectedUnused, out)
}
// Unknown pin not in chip.PinMap should still fail
badConfigs := []PinConfig{{PinNumber: "Z99", NetName: "BAD_PIN", Dir: "IN"}}
buf.Reset()
if err := GenerateDTSI(&buf, badConfigs, chip); err == nil || !strings.Contains(err.Error(), "failed to resolve GPIO for pin Z99") {
t.Errorf("Expected error for unknown pin Z99, got %v", err)
}
}
func TestGenerateDTSI_PeripheralParity(t *testing.T) {
t.Run("NPCX_KbdSleep_Tach_I2CCtrl", func(t *testing.T) {
chip := &Chip{
Name: "npcx-mock",
Vendor: "npcx",
PinMap: map[string]string{
"B2": "GPIOB4/I2C0_SDA0",
"B3": "GPIOB5/I2C0_SCL0",
"F5": "GPIOF5/TB1",
"G1": "KSI0/GPIO31",
"G2": "KSO01/GPIO21",
},
}
configs := []PinConfig{
// I2C_CLOCK without IO="OD" (e.g. Fatcat NPCX C12)
{PinNumber: "B3", NetName: "EC_I2C_PD_SCL_R", Enum: "I2C_PORT_PD", PinTypeOverride: "I2C_CLOCK"},
{PinNumber: "B2", NetName: "EC_I2C_PD_SDA_R", IO: "OD", PinTypeOverride: "I2C_DATA"},
{PinNumber: "F5", NetName: "EC_FAN_TACH", PinTypeOverride: "TACH"},
{PinNumber: "G1", NetName: "EC_KSI0", PinTypeOverride: "KEYBOARD"},
{PinNumber: "G2", NetName: "EC_KSO01", PinTypeOverride: "KEYBOARD"},
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
out := buf.String()
expectedSnippets := []string{
"&i2c_ctrl0 {\n\tstatus = \"okay\";\n};",
"&i2c0_0 {",
"port = <NPCX_TACH_PORT_B>;",
"sample-clk = <NPCX_TACH_FREQ_LFCLK>;",
"pulses-per-round = <2>;",
"pinctrl-1 = <\n\t\t&ksi0_gpio_gp31\n\t\t&kso01_gpio_gp21\n\t>;",
"pinctrl-names = \"default\", \"sleep\";",
}
for _, snip := range expectedSnippets {
if !strings.Contains(out, snip) {
t.Errorf("NPCX output missing expected snippet:\n%s\nGot:\n%s", snip, out)
}
}
})
t.Run("ITE_KbdSleep_Tach", func(t *testing.T) {
chip := &Chip{
Name: "ite-mock",
Vendor: "ite",
PinMap: map[string]string{
"D6": "TACH0A/GPD6",
"G1": "KSI0/GPG1",
"G2": "KSO0/GPG2",
},
}
configs := []PinConfig{
{PinNumber: "D6", NetName: "EC_FAN_TACH", PinTypeOverride: "TACH"},
{PinNumber: "G1", NetName: "EC_KSI0", PinTypeOverride: "KEYBOARD"},
{PinNumber: "G2", NetName: "EC_KSO0", PinTypeOverride: "KEYBOARD"},
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
out := buf.String()
expectedSnippets := []string{
"channel = <IT8XXX2_TACH_CHANNEL_A>;",
"pulses-per-round = <2>;",
"pinctrl-1 = <\n\t\t&ksi0_sleep\n\t\t&kso0_sleep\n\t>;",
"pinctrl-names = \"default\", \"sleep\";",
}
for _, snip := range expectedSnippets {
if !strings.Contains(out, snip) {
t.Errorf("ITE output missing expected snippet:\n%s\nGot:\n%s", snip, out)
}
}
})
}
func TestGenerateDTSI_ITE_UART(t *testing.T) {
chip := &Chip{
Name: "ite-mock",
Vendor: "ite",
PinMap: map[string]string{
"A3": "SOUT0/GPB1",
"A4": "SIN0/GPB0",
"N3": "SOUT1/GPH2/ID2",
"M3": "SIN1/GPH1/ID1",
},
}
t.Run("NonDefaultUART2Only", func(t *testing.T) {
configs := []PinConfig{
{PinNumber: "N3", NetName: "UART_EC_TX_DBG_RX", PinTypeOverride: "UART_TX"},
{PinNumber: "M3", NetName: "UART_DBG_TX_EC_RX", PinTypeOverride: "UART_RX"},
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
out := buf.String()
for _, snip := range []string{
"&uart1 {\n\tstatus = \"disabled\";\n};",
"&ite_uart1_wrapper {\n\tstatus = \"disabled\";\n};",
"&uart2 {\n\tstatus = \"okay\";\n\tcurrent-speed = <115200>;\n};",
"&ite_uart2_wrapper {\n\tstatus = \"okay\";\n\tpinctrl-0 = <\n\t\t&uart2_rx_gph1_default\n\t\t&uart2_tx_gph2_default\n\t>;\n\tpinctrl-names = \"default\";\n};",
} {
if !strings.Contains(out, snip) {
t.Errorf("Expected non-default UART2 snippet:\n%s\nGot:\n%s", snip, out)
}
}
})
t.Run("DualUART_ConsoleOnUART1", func(t *testing.T) {
configs := []PinConfig{
{PinNumber: "A3", NetName: "UART_EC_TX_DBG_RX", PinTypeOverride: "UART_TX"},
{PinNumber: "N3", NetName: "EC_BASE_TX", PinTypeOverride: "UART_TX"},
}
var buf bytes.Buffer
if err := GenerateDTSI(&buf, configs, chip); err != nil {
t.Fatalf("GenerateDTSI failed: %v", err)
}
out := buf.String()
if strings.Contains(out, "&uart1") || strings.Contains(out, "&uart2") {
t.Errorf("Expected no UART override when SOUT0 (uart1) is present, got:\n%s", out)
}
})
}