firmweaver: update dtsi generation Support unmodified EC pinout sheets and match arbitrage output. BUG=b:427556454 TEST=go test -v -race ./... TEST=compared generated kenai dtsi between arb and firmweaver Change-Id: Id7c95bb62edc558472b774f19303ad5466e2474c Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/firmweaver/+/8522577 Reviewed-by: Karthik Ramasubramanian <kramasub@google.com> Commit-Queue: Kshitij Shah <tij@google.com> Tested-by: Kshitij Shah <tij@google.com>
diff --git a/zephyr_dtsi/chips.go b/zephyr_dtsi/chips.go index a9cf881..1d65c6c 100644 --- a/zephyr_dtsi/chips.go +++ b/zephyr_dtsi/chips.go
@@ -31,6 +31,7 @@ iteSda = regexp.MustCompile(`SMDAT([0-9]+[A-Z]*)`) npcxTachRegex = regexp.MustCompile(`^T([A-Z])([0-9])$`) iteTachRegex = regexp.MustCompile(`^TACH([0-9])([A-Z])$`) + iteSoutRegex = regexp.MustCompile(`^SOUT([0-9]+)$`) ) type ChipData struct { @@ -219,6 +220,25 @@ return 0, "", fmt.Errorf("pin %s (%s) does not support TACH", pinNum, pinDef) } +// ResolveITEUART returns the 0-based SOUT index (e.g. 0 for SOUT0, 1 for SOUT1) if the pin supports ITE UART TX. +func (c *Chip) ResolveITEUART(pinNum string) (int, error) { + pinDef, ok := c.PinMap[pinNum] + if !ok { + return 0, fmt.Errorf("pin %s not found in chip %s", pinNum, c.Name) + } + for _, part := range strings.Split(pinDef, "/") { + part = strings.TrimSpace(part) + if m := iteSoutRegex.FindStringSubmatch(part); m != nil { + idx, err := strconv.Atoi(m[1]) + if err != nil { + return 0, fmt.Errorf("invalid SOUT index %q in %s: %v", m[1], pinDef, err) + } + return idx, nil + } + } + return 0, fmt.Errorf("pin %s (%s) does not support UART TX", pinNum, pinDef) +} + var chips = map[string]*Chip{} // GetChip returns the registered Chip by name or alias.
diff --git a/zephyr_dtsi/chips_test.go b/zephyr_dtsi/chips_test.go index f2db0b2..0383778 100644 --- a/zephyr_dtsi/chips_test.go +++ b/zephyr_dtsi/chips_test.go
@@ -348,3 +348,32 @@ } } } + +func TestChips_ResolveITEUART(t *testing.T) { + ite := getChipOrFatal(t, "it82002") + npcx := getChipOrFatal(t, "npcx99") + + tests := []struct { + chip *Chip + pinNum string + expectedPort int + expectErr bool + }{ + {ite, "A3", 0, false}, // SOUT0/GPB1 + {ite, "B11", 1, false}, // SOUT1/PS2DAT0/GPF1 + {ite, "A4", 0, true}, // SIN0/GPB0 (RX, not SOUT) + {ite, "Z99", 0, true}, // Pin not found + {npcx, "F4", 0, true}, // Not an ITE chip + } + + for _, tt := range tests { + port, err := tt.chip.ResolveITEUART(tt.pinNum) + if (err != nil) != tt.expectErr { + t.Errorf("%s ResolveITEUART(%s) error status: expected err=%t, got %v", tt.chip.Name, tt.pinNum, tt.expectErr, err) + continue + } + if !tt.expectErr && port != tt.expectedPort { + t.Errorf("%s ResolveITEUART(%s) expected port=%d, got %d", tt.chip.Name, tt.pinNum, tt.expectedPort, port) + } + } +}
diff --git a/zephyr_dtsi/generator.go b/zephyr_dtsi/generator.go index 030ef17..27a8708 100644 --- a/zephyr_dtsi/generator.go +++ b/zephyr_dtsi/generator.go
@@ -9,6 +9,7 @@ "fmt" "io" "maps" + "os" "regexp" "slices" "strconv" @@ -31,6 +32,13 @@ enum string } +type unusedPin struct { + comment string + port string + pinNum int + flags string +} + type adcPin struct { label string pin string @@ -46,20 +54,29 @@ enums []string } +type iteUartNode struct { + rxGP string + txGP string +} + // dtsiGenerator holds the state for generating the DTSI file. type dtsiGenerator struct { w io.Writer chip *Chip gpios []gpioPin + unusedPins []unusedPin adcs []adcPin i2cPorts []i2cPort i2cEnables map[string][]string + i2cCtrls map[string]bool pwmEnables map[int]string adcEnabled bool adcPinctrls []string tachEnables map[int]string tachPorts map[int]string + iteUarts map[int]iteUartNode kbPins []string + kbSleepPins []string maxKsi int maxKso int } @@ -69,9 +86,11 @@ w: w, chip: chip, i2cEnables: make(map[string][]string), + i2cCtrls: make(map[string]bool), pwmEnables: make(map[int]string), tachEnables: make(map[int]string), tachPorts: make(map[int]string), + iteUarts: make(map[int]iteUartNode), maxKsi: -1, maxKso: -1, } @@ -88,7 +107,7 @@ } processedPins[cfg.PinNumber] = true - if cfg.NetName == "" { + if cfg.NetName == "" && cfg.PinTypeOverride != "UNUSED_IN" && cfg.PinTypeOverride != "UNUSED_OUT" { continue } @@ -99,7 +118,7 @@ netName = cleanNetName(netName) // Check excluded net names - if strings.HasPrefix(netName, "ESPI_") || strings.HasPrefix(netName, "UART_") || netName == "EC_RST_ODL" { + if strings.HasPrefix(netName, "ESPI_") || strings.HasSuffix(netName, "EC_RST_ODL") { continue } @@ -113,6 +132,18 @@ gpName = fmt.Sprintf("gp%s%d", port, pinNum) } + if cfg.PinTypeOverride == "UNUSED_IN" || cfg.PinTypeOverride == "UNUSED_OUT" { + if gpioErr != nil { + if def, ok := chip.PinMap[cfg.PinNumber]; ok { + fmt.Fprintf(os.Stderr, "Warning: skipping %s: pin %s (%s) has no GPIO function\n", netName, cfg.PinNumber, def) + continue + } + return fmt.Errorf("failed to resolve GPIO for pin %s: %v", cfg.PinNumber, gpioErr) + } + g.processUnusedPin(cfg, netName, port, pinNum) + continue + } + // Try processing as Keyboard matrix if g.tryProcessKeyboard(cfg, netName, gpName) { continue @@ -154,9 +185,22 @@ continue } + // Try processing as UART + processedUART, err := g.tryProcessUART(cfg, netName, gpName) + if err != nil { + return err + } + if processedUART || strings.HasPrefix(netName, "UART_") { + continue + } + // Process as regular GPIO if isGPIO(cfg) { if gpioErr != nil { + if def, ok := chip.PinMap[cfg.PinNumber]; ok { + fmt.Fprintf(os.Stderr, "Warning: skipping %s: pin %s (%s) has no GPIO function\n", netName, cfg.PinNumber, def) + continue + } return fmt.Errorf("failed to resolve GPIO for pin %s: %v", cfg.PinNumber, gpioErr) } g.processGPIO(cfg, netName, port, pinNum, gpName) @@ -205,9 +249,11 @@ if g.chip.IsNPCX() { if gpName != "" { g.kbPins = append(g.kbPins, fmt.Sprintf("&%s%s_%s", prefix, idx, gpName)) + g.kbSleepPins = append(g.kbSleepPins, fmt.Sprintf("&%s%s_gpio_%s", prefix, idx, gpName)) } } else { g.kbPins = append(g.kbPins, fmt.Sprintf("&%s%s_default", prefix, idx)) + g.kbSleepPins = append(g.kbSleepPins, fmt.Sprintf("&%s%s_sleep", prefix, idx)) } return true } @@ -286,7 +332,7 @@ } func (g *dtsiGenerator) tryProcessI2C(cfg PinConfig, netName string, port string, pinNum int, gpName string) (bool, error) { - isI2C := cfg.IO == "OD" && (cfg.PinTypeOverride == "I2C_CLOCK" || cfg.PinTypeOverride == "I2C_DATA" || cfg.PinTypeOverride == "I2C") + isI2C := cfg.IO == "I2C" || cfg.PinTypeOverride == "I2C_CLOCK" || cfg.PinTypeOverride == "I2C_DATA" || cfg.PinTypeOverride == "I2C" if !isI2C { return false, nil } @@ -341,6 +387,7 @@ pinctrl := fmt.Sprintf("&i2c%s_%d_sda_scl_%s_%s", i2cChan, idx, first, second) nodeName := fmt.Sprintf("i2c%s_%d", i2cChan, idx) g.i2cEnables[nodeName] = []string{pinctrl} + g.i2cCtrls["i2c_ctrl"+i2cChan] = true } else { // ITE sdaGP, err := findITESDAGPIO(g.chip, cfg.PinNumber) @@ -356,6 +403,40 @@ return true, nil } +func (g *dtsiGenerator) tryProcessUART(cfg PinConfig, netName, gpName string) (bool, error) { + if cfg.PinTypeOverride == "UART_RX" { + return true, nil + } + if cfg.PinTypeOverride != "UART_TX" { + return false, nil + } + if g.chip.IsNPCX() { + def, ok := g.chip.PinMap[cfg.PinNumber] + if !ok { + return false, fmt.Errorf("failed to resolve UART for pin %s: pin %s not found in chip %s", cfg.PinNumber, cfg.PinNumber, g.chip.Name) + } + if !hasFunction(def, "CR_SOUT1") { + fmt.Fprintf(os.Stderr, "Warning: skipping %s: NPCX only supports default uart1 (CR_SOUT1), pin %s is %s\n", netName, cfg.PinNumber, def) + } + return true, nil + } + soutIdx, err := g.chip.ResolveITEUART(cfg.PinNumber) + if err != nil { + return false, fmt.Errorf("failed to resolve UART for pin %s: %v", cfg.PinNumber, err) + } + uartNum := soutIdx + 1 + if uartNum == 1 { + g.iteUarts[1] = iteUartNode{} + return true, nil + } + rxGP, err := findFuncGPIO(g.chip, fmt.Sprintf("SIN%d", soutIdx)) + if err != nil { + return false, fmt.Errorf("failed to find ITE UART RX GPIO for pin %s: %v", cfg.PinNumber, err) + } + g.iteUarts[uartNum] = iteUartNode{rxGP: rxGP, txGP: gpName} + return true, nil +} + func (g *dtsiGenerator) processGPIO(cfg PinConfig, netName, port string, pinNum int, gpName string) { if gpName != "" { label := fmt.Sprintf("gpio_%s", strings.ToLower(netName)) @@ -371,6 +452,15 @@ } } +func (g *dtsiGenerator) processUnusedPin(cfg PinConfig, netName, port string, pinNum int) { + g.unusedPins = append(g.unusedPins, unusedPin{ + comment: netName, + port: port, + pinNum: pinNum, + flags: determineUnusedFlags(cfg), + }) +} + func (g *dtsiGenerator) write() error { ew := &errWriter{w: g.w} g.w = ew @@ -379,6 +469,9 @@ slices.SortStableFunc(g.gpios, func(a, b gpioPin) int { return cmp.Compare(a.pin, b.pin) }) slices.SortStableFunc(g.adcs, func(a, b adcPin) int { return cmp.Compare(a.pin, b.pin) }) slices.SortStableFunc(g.i2cPorts, func(a, b i2cPort) int { return cmp.Compare(a.pin, b.pin) }) + slices.SortStableFunc(g.unusedPins, func(a, b unusedPin) int { + return cmp.Or(cmp.Compare(a.port, b.port), cmp.Compare(a.pinNum, b.pinNum)) + }) slices.Sort(g.adcPinctrls) // Write header @@ -443,6 +536,25 @@ fmt.Fprintln(g.w) } + // Write Unused pins + if len(g.unusedPins) > 0 { + fmt.Fprintln(g.w, "\tunused-pins {") + fmt.Fprintln(g.w, "\t\tcompatible = \"unused-gpios\";") + fmt.Fprintln(g.w, "\t\tunused-gpios =") + for i, up := range g.unusedPins { + if up.comment != "" { + fmt.Fprintf(g.w, "\t\t\t/* %s */\n", up.comment) + } + sep := "," + if i == len(g.unusedPins)-1 { + sep = ";" + } + fmt.Fprintf(g.w, "\t\t\t<&gpio%s %d %s>%s\n", up.port, up.pinNum, up.flags, sep) + } + fmt.Fprintln(g.w, "\t};") + fmt.Fprintln(g.w) + } + fmt.Fprintln(g.w, "};") fmt.Fprintln(g.w) @@ -486,6 +598,16 @@ fmt.Fprintln(g.w) } + i2cCtrls := slices.Collect(maps.Keys(g.i2cCtrls)) + slices.Sort(i2cCtrls) + + for _, ctrlNode := range i2cCtrls { + fmt.Fprintf(g.w, "&%s {\n", ctrlNode) + fmt.Fprintln(g.w, "\tstatus = \"okay\";") + fmt.Fprintln(g.w, "};") + fmt.Fprintln(g.w) + } + // Write PWM pinctrl pwmChans := slices.Collect(maps.Keys(g.pwmEnables)) slices.Sort(pwmChans) @@ -504,19 +626,49 @@ slices.Sort(tachChans) for _, ch := range tachChans { + portLetter := strings.ToUpper(g.tachPorts[ch]) + fmt.Fprintf(g.w, "&tach%d {\n", ch) + fmt.Fprintln(g.w, "\tstatus = \"okay\";") + fmt.Fprintf(g.w, "\tpinctrl-0 = <%s>;\n", g.tachEnables[ch]) + fmt.Fprintln(g.w, "\tpinctrl-names = \"default\";") if g.chip.IsNPCX() { - portLetter := g.tachPorts[ch] - fmt.Fprintf(g.w, "&tach%d {\n", ch) + fmt.Fprintf(g.w, "\tport = <NPCX_TACH_PORT_%s>;\n", portLetter) + fmt.Fprintln(g.w, "\tsample-clk = <NPCX_TACH_FREQ_LFCLK>;") + fmt.Fprintln(g.w, "\tpulses-per-round = <2>;") + } else { + fmt.Fprintf(g.w, "\tchannel = <IT8XXX2_TACH_CHANNEL_%s>;\n", portLetter) + fmt.Fprintln(g.w, "\tpulses-per-round = <2>;") + } + fmt.Fprintln(g.w, "};") + fmt.Fprintln(g.w) + } + + // Write UART pinctrl + if _, hasUart1 := g.iteUarts[1]; len(g.iteUarts) > 0 && !hasUart1 { + fmt.Fprintln(g.w, "&uart1 {") + fmt.Fprintln(g.w, "\tstatus = \"disabled\";") + fmt.Fprintln(g.w, "};") + fmt.Fprintln(g.w) + fmt.Fprintln(g.w, "&ite_uart1_wrapper {") + fmt.Fprintln(g.w, "\tstatus = \"disabled\";") + fmt.Fprintln(g.w, "};") + fmt.Fprintln(g.w) + + uartNums := slices.Collect(maps.Keys(g.iteUarts)) + slices.Sort(uartNums) + for _, num := range uartNums { + u := g.iteUarts[num] + fmt.Fprintf(g.w, "&uart%d {\n", num) fmt.Fprintln(g.w, "\tstatus = \"okay\";") - fmt.Fprintf(g.w, "\tpinctrl-0 = <%s>;\n", g.tachEnables[ch]) - fmt.Fprintln(g.w, "\tpinctrl-names = \"default\";") - fmt.Fprintf(g.w, "\tport = <&gpio%s>;\n", strings.ToLower(portLetter)) + fmt.Fprintln(g.w, "\tcurrent-speed = <115200>;") fmt.Fprintln(g.w, "};") fmt.Fprintln(g.w) - } else { - fmt.Fprintf(g.w, "&tach%d {\n", ch) + fmt.Fprintf(g.w, "&ite_uart%d_wrapper {\n", num) fmt.Fprintln(g.w, "\tstatus = \"okay\";") - fmt.Fprintf(g.w, "\tpinctrl-0 = <%s>;\n", g.tachEnables[ch]) + fmt.Fprintln(g.w, "\tpinctrl-0 = <") + fmt.Fprintf(g.w, "\t\t&uart%d_rx_%s_default\n", num, u.rxGP) + fmt.Fprintf(g.w, "\t\t&uart%d_tx_%s_default\n", num, u.txGP) + fmt.Fprintln(g.w, "\t>;") fmt.Fprintln(g.w, "\tpinctrl-names = \"default\";") fmt.Fprintln(g.w, "};") fmt.Fprintln(g.w) @@ -526,6 +678,7 @@ // Write Keyboard matrix pinctrl if len(g.kbPins) > 0 { slices.Sort(g.kbPins) + slices.Sort(g.kbSleepPins) fmt.Fprintln(g.w, "&kbd {") fmt.Fprintln(g.w, "\tstatus = \"okay\";") fmt.Fprintln(g.w, "\tpinctrl-0 = <") @@ -533,7 +686,12 @@ fmt.Fprintf(g.w, "\t\t%s\n", p) } fmt.Fprintln(g.w, "\t>;") - fmt.Fprintln(g.w, "\tpinctrl-names = \"default\";") + fmt.Fprintln(g.w, "\tpinctrl-1 = <") + for _, p := range g.kbSleepPins { + fmt.Fprintf(g.w, "\t\t%s\n", p) + } + fmt.Fprintln(g.w, "\t>;") + fmt.Fprintln(g.w, "\tpinctrl-names = \"default\", \"sleep\";") rowSize := 0 if g.maxKsi >= 0 { rowSize = g.maxKsi + 1 @@ -550,7 +708,7 @@ return ew.err } -func findSDAGPIO(chip *Chip, sdaFunc string) (string, error) { +func findFuncGPIO(chip *Chip, fn string) (string, error) { var pins []string if len(chip.PkgPins) > 0 { pins = slices.Clone(chip.PkgPins) @@ -561,7 +719,7 @@ for _, pinNum := range pins { def := chip.PinMap[pinNum] - if hasFunction(def, sdaFunc) { + if hasFunction(def, fn) { port, pin, err := chip.ResolveGPIO(pinNum) if err != nil { return "", err @@ -569,7 +727,7 @@ return fmt.Sprintf("gp%s%d", port, pin), nil } } - return "", fmt.Errorf("matching SDA pin function %s not found in chip map", sdaFunc) + return "", fmt.Errorf("matching pin function %s not found in chip map", fn) } func findNPCXSDAGPIO(chip *Chip, sclPinNum string) (string, error) { @@ -579,7 +737,7 @@ return "", fmt.Errorf("not an NPCX SCL pin definition: %s", sclDef) } sdaFunc := fmt.Sprintf("I2C%s_SDA%s", m[1], m[2]) - return findSDAGPIO(chip, sdaFunc) + return findFuncGPIO(chip, sdaFunc) } func findITESDAGPIO(chip *Chip, sclPinNum string) (string, error) { @@ -589,7 +747,7 @@ return "", fmt.Errorf("not an ITE SCL pin definition: %s", sclDef) } sdaFunc := fmt.Sprintf("SMDAT%s", m[1]) - return findSDAGPIO(chip, sdaFunc) + return findFuncGPIO(chip, sdaFunc) } func extractNPCXI2CIndex(pinDef string) int { @@ -608,6 +766,9 @@ } func isGPIO(cfg PinConfig) bool { + if cfg.IO == "UNUSED" || cfg.PinTypeOverride == "UNUSED" { + return false + } if cfg.Dir == "IN" || cfg.Dir == "OUT" { return true } @@ -618,6 +779,29 @@ return false } +func determineUnusedFlags(cfg PinConfig) string { + var flags []string + isOutput := cfg.PinTypeOverride == "UNUSED_OUT" + if isOutput { + flags = append(flags, "GPIO_OUTPUT", "GPIO_ACTIVE_HIGH") + } else { + flags = append(flags, "GPIO_INPUT") + } + if cfg.Pull == "UP" { + flags = append(flags, "GPIO_PULL_UP") + } else if cfg.Pull == "DOWN" { + flags = append(flags, "GPIO_PULL_DOWN") + } + if isOutput { + if cfg.InitialCondition == "HIGH" { + flags = append(flags, "GPIO_OUTPUT_INIT_HIGH") + } else if cfg.InitialCondition == "LOW" { + flags = append(flags, "GPIO_OUTPUT_INIT_LOW") + } + } + return "(" + strings.Join(flags, " | ") + ")" +} + func hasFunction(pinDef, function string) bool { parts := strings.Split(pinDef, "/") for _, part := range parts {
diff --git a/zephyr_dtsi/generator_test.go b/zephyr_dtsi/generator_test.go index aa304cc..e647f9e 100644 --- a/zephyr_dtsi/generator_test.go +++ b/zephyr_dtsi/generator_test.go
@@ -330,19 +330,12 @@ t.Fatalf("GenerateDTSI failed: %v", err) } - output := buf.String() - requiredNodes := []string{ - "named-adc-channels", - "named-gpios", - "named-i2c-ports", - "&adc0", - "&pwm0", + expected, err := os.ReadFile("test/generated_npcx.dtsi") + if err != nil { + t.Fatalf("Failed to read test/generated_npcx.dtsi: %v", err) } - - for _, req := range requiredNodes { - if !strings.Contains(output, req) { - t.Errorf("Generated DTSI missing expected node structure %q", req) - } + 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)) } }) @@ -362,23 +355,12 @@ t.Fatalf("GenerateDTSI failed: %v", err) } - output := buf.String() - requiredNodes := []string{ - "named-adc-channels", - "named-gpios", - "named-i2c-ports", - "&adc0", - "&i2c1", - "&pwm1", - "&pwm6", - "&tach0", - "&kbd", + expected, err := os.ReadFile("test/generated_ite.dtsi") + if err != nil { + t.Fatalf("Failed to read test/generated_ite.dtsi: %v", err) } - - for _, req := range requiredNodes { - if !strings.Contains(output, req) { - t.Errorf("Generated DTSI missing expected node structure %q", req) - } + 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)) } }) } @@ -419,3 +401,190 @@ 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) + } + }) +}
diff --git a/zephyr_dtsi/parser.go b/zephyr_dtsi/parser.go index 83b15a3..451f2dc 100644 --- a/zephyr_dtsi/parser.go +++ b/zephyr_dtsi/parser.go
@@ -71,6 +71,30 @@ return -1 } +func findPinCol(header []string, pinCol string) int { + if pinCol != "" { + return findCol(header, pinCol) + } + for _, cand := range columnAliases["PinNumber"] { + normCand := normalize(cand) + for i, name := range header { + if strings.EqualFold(normalize(name), normCand) { + return i + } + } + } + for i, name := range header { + lower := strings.ToLower(name) + if strings.Contains(lower, "name") || strings.Contains(lower, "type") || strings.Contains(lower, "desc") { + continue + } + if strings.Contains(lower, "pin") || strings.Contains(lower, "bga") || strings.Contains(lower, "ball") { + return i + } + } + return -1 +} + // findHeaderRow scans the first 10 rows for a row containing both Pin and Net columns. func findHeaderRow(records [][]string, pinCol string) ([]string, [][]string, error) { if len(records) == 0 { @@ -82,14 +106,13 @@ limit = len(records) } - pinCandidates := append([]string{pinCol}, columnAliases["PinNumber"]...) for i := 0; i < limit; i++ { row := records[i] if len(row) < 2 { continue } - pinIdx := findCol(row, pinCandidates...) + pinIdx := findPinCol(row, pinCol) netIdx := findCol(row, columnAliases["NetName"]...) if pinIdx >= 0 && netIdx >= 0 { @@ -101,9 +124,12 @@ for i := 0; i < limit; i++ { row := records[i] netIdx := findCol(row, columnAliases["NetName"]...) - pinIdx := findCol(row, pinCandidates...) + pinIdx := findPinCol(row, pinCol) if netIdx >= 0 && pinIdx == -1 { - return nil, nil, fmt.Errorf("missing required column for Pin Number (checked --pin-col=%q, 'PinNumber', 'Pin')", pinCol) + if pinCol != "" { + return nil, nil, fmt.Errorf("missing required column for Pin Number (checked --pin-col=%q)", pinCol) + } + return nil, nil, fmt.Errorf("missing required column for Pin Number (checked 'PinNumber', 'Pin', 'Ball', package headers)") } if pinIdx >= 0 && netIdx == -1 { return nil, nil, fmt.Errorf("missing required column for Net Name (checked 'NetName', 'Signal Name')") @@ -277,15 +303,11 @@ } func parseRows(header []string, rows [][]string, pinCol string) ([]PinConfig, error) { - col := func(key string, extra ...string) int { - cands := columnAliases[key] - if len(extra) > 0 { - cands = append(extra, cands...) - } - return findCol(header, cands...) + col := func(key string) int { + return findCol(header, columnAliases[key]...) } - pinIdx := col("PinNumber", pinCol) + pinIdx := findPinCol(header, pinCol) netIdx := col("NetName") if pinIdx < 0 || pinIdx >= len(header) { @@ -335,12 +357,12 @@ var configs []PinConfig for _, row := range rows { netName := cell(row, netIdx) - if isIgnoredSignal(netName) { + rawIO := strings.ToUpper(cell(row, ioIdx)) + sigType := strings.ToUpper(cell(row, typeIdx)) + if sigType != "UNUSED_IN" && sigType != "UNUSED_OUT" && isIgnoredSignal(netName) { continue } - rawIO := strings.ToUpper(cell(row, ioIdx)) - sigType := strings.ToUpper(cell(row, typeIdx)) rawDir := cell(row, dirIdx) enumVal := strings.Trim(strings.ReplaceAll(cell(row, enumIdx), `""`, `"`), `"`)
diff --git a/zephyr_dtsi/parser_test.go b/zephyr_dtsi/parser_test.go index 17c9889..27a605e 100644 --- a/zephyr_dtsi/parser_test.go +++ b/zephyr_dtsi/parser_test.go
@@ -251,3 +251,34 @@ t.Fatalf("Expected fallback pin B3, got: %+v", configs) } } + +func TestParseCSV_PackageHeaderAutoDetection(t *testing.T) { + csvData := `"128-pin VFBGA",IT82002 Pin Name,GPIO,Signal Name,Dir,Type +M5,PWM0/GPA0,GPA0,EC_VOLDN_BTN_ODL,,UNUSED_IN +M7,PWM6/SSCK/GPA6,GPA6,NC,,UNUSED_IN +C1,SMDAT2ALT/GPF7,GPF7,NC,, +L1,WRST#,WRST#,EC_ERST_L,IN,INPUT_L +` + configs, err := ParseCSV(strings.NewReader(csvData), "") + if err != nil { + t.Fatalf("ParseCSV failed: %v", err) + } + if len(configs) != 3 { + t.Fatalf("Expected 3 configs (NC with UNUSED_IN kept, plain NC skipped), got %d: %+v", len(configs), configs) + } + if configs[0].PinNumber != "M5" || configs[0].PinName != "PWM0/GPA0" || configs[0].PinTypeOverride != "UNUSED_IN" { + t.Errorf("Unexpected config[0]: %+v", configs[0]) + } + if configs[1].PinNumber != "M7" || configs[1].NetName != "NC" || configs[1].PinTypeOverride != "UNUSED_IN" { + t.Errorf("Unexpected config[1]: %+v", configs[1]) + } + if configs[2].PinNumber != "L1" || configs[2].PinName != "WRST#" || configs[2].NetName != "EC_ERST_L" { + t.Errorf("Unexpected config[2]: %+v", configs[2]) + } + + // Ensure "IT82002 Pin Name" alone is not mistaken for a PinNumber column + noPinNumCSV := "IT82002 Pin Name,Signal Name\nPWM0/GPA0,EC_FOO\n" + if _, err := ParseCSV(strings.NewReader(noPinNumCSV), ""); err == nil || !strings.Contains(err.Error(), "missing required column for Pin Number") { + t.Errorf("Expected missing Pin Number column error when only Pin Name is present, got %v", err) + } +}
diff --git a/zephyr_dtsi/test/generated.dtsi b/zephyr_dtsi/test/generated.dtsi index 36d2893..7b9ae4e 100644 --- a/zephyr_dtsi/test/generated.dtsi +++ b/zephyr_dtsi/test/generated.dtsi
@@ -66,6 +66,10 @@ pinctrl-names = "default"; }; +&i2c_ctrl0 { + status = "okay"; +}; + &pwm0 { status = "okay"; pinctrl-0 = <&pwm0_gpc3>; @@ -82,7 +86,9 @@ status = "okay"; pinctrl-0 = <&ta1_1_in_gp40>; pinctrl-names = "default"; - port = <&gpioa>; + port = <NPCX_TACH_PORT_A>; + sample-clk = <NPCX_TACH_FREQ_LFCLK>; + pulses-per-round = <2>; }; &kbd { @@ -93,7 +99,13 @@ &kso00_gp21 &kso01_gp20 >; - pinctrl-names = "default"; + pinctrl-1 = < + &ksi0_gpio_gp31 + &ksi1_gpio_gp30 + &kso00_gpio_gp21 + &kso01_gpio_gp20 + >; + pinctrl-names = "default", "sleep"; row-size = <2>; col-size = <2>; };
diff --git a/zephyr_dtsi/test/generated_ite.dtsi b/zephyr_dtsi/test/generated_ite.dtsi index ed0b9d4..845e774 100644 --- a/zephyr_dtsi/test/generated_ite.dtsi +++ b/zephyr_dtsi/test/generated_ite.dtsi
@@ -88,6 +88,8 @@ status = "okay"; pinctrl-0 = <&tach0a_gpd6_default>; pinctrl-names = "default"; + channel = <IT8XXX2_TACH_CHANNEL_A>; + pulses-per-round = <2>; }; &kbd { @@ -98,7 +100,13 @@ &kso0_default &kso1_default >; - pinctrl-names = "default"; + pinctrl-1 = < + &ksi0_sleep + &ksi1_sleep + &kso0_sleep + &kso1_sleep + >; + pinctrl-names = "default", "sleep"; row-size = <2>; col-size = <2>; };
diff --git a/zephyr_dtsi/test/generated_npcx.dtsi b/zephyr_dtsi/test/generated_npcx.dtsi index 36d2893..7b9ae4e 100644 --- a/zephyr_dtsi/test/generated_npcx.dtsi +++ b/zephyr_dtsi/test/generated_npcx.dtsi
@@ -66,6 +66,10 @@ pinctrl-names = "default"; }; +&i2c_ctrl0 { + status = "okay"; +}; + &pwm0 { status = "okay"; pinctrl-0 = <&pwm0_gpc3>; @@ -82,7 +86,9 @@ status = "okay"; pinctrl-0 = <&ta1_1_in_gp40>; pinctrl-names = "default"; - port = <&gpioa>; + port = <NPCX_TACH_PORT_A>; + sample-clk = <NPCX_TACH_FREQ_LFCLK>; + pulses-per-round = <2>; }; &kbd { @@ -93,7 +99,13 @@ &kso00_gp21 &kso01_gp20 >; - pinctrl-names = "default"; + pinctrl-1 = < + &ksi0_gpio_gp31 + &ksi1_gpio_gp30 + &kso00_gpio_gp21 + &kso01_gpio_gp20 + >; + pinctrl-names = "default", "sleep"; row-size = <2>; col-size = <2>; };