blob: d91d6d26c9e838f8a107fb6a760d78fce97f4e0b [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 main
import (
"encoding/csv"
"fmt"
"io"
"strings"
)
type PinConfig struct {
PinNumber string
PinName string
NetName string
Pull string // UP, DOWN, N, ""
Dir string // IN, OUT, IO, ""
IO string // PWM, ADC, OD, TTL, UNUSED, ""
Enum string
PinTypeOverride string
InitialCondition string // HIGH, LOW, ""
NetNameOverride string
Voltage string // 1.8V, 3.3V, ""
}
// Column aliases for CSV parsing. The first entry in each slice is the canonical column name,
// and the rest are alternative names that may appear in different CSV files.
// Add variants to the list as needed to support different CSV formats.
var columnAliases = map[string][]string{
"PinNumber": {"PinNumber", "Pin Number", "Pin", "Ball"},
"PinName": {"PinName", "Pin Name"},
"NetName": {"NetName", "Signal Name", "Net Name", "Net"},
"Dir": {"Dir", "DIR", "Direction"},
"IO": {"IO", "I/O Type", "IO Type", "I/O"},
"Pull": {"Pull", "Internal PU", "Pull Up", "Pull-Up"},
"Voltage": {"Voltage", "I/O Voltage", "Volts"},
"Enum": {"Enum", "Enum Name"},
"PinTypeOverride": {"PinTypeOverride", "Type", "SigType", "Pin Type Override"},
"InitialCondition": {"InitialCondition", "Initial Condition", "Init State", "Initial State"},
"NetNameOverride": {"NetNameOverride", "Net Name Override"},
}
// ParseCSV parses EC pinout CSV data from an io.Reader.
func ParseCSV(reader io.Reader, pinCol string) ([]PinConfig, error) {
r := csv.NewReader(reader)
r.FieldsPerRecord = -1 // Allow variable number of fields per record
// Read header
header, err := r.Read()
if err != nil {
return nil, fmt.Errorf("failed to read header: %v", err)
}
findCol := func(candidates ...string) int {
normalize := func(s string) string {
return strings.Join(strings.Fields(s), " ")
}
// Pass 1: Exact match against header in original slice order
for _, cand := range candidates {
if cand == "" {
continue
}
normCand := normalize(cand)
for i, name := range header {
if strings.EqualFold(normalize(name), normCand) {
return i
}
}
}
// Pass 2: Substring match against header in original slice order
for _, cand := range candidates {
if cand == "" {
continue
}
normCand := strings.ToLower(normalize(cand))
for i, name := range header {
if strings.Contains(strings.ToLower(normalize(name)), normCand) {
return i
}
}
}
return -1
}
findColByAlias := func(key string, extraCandidates ...string) int {
candidates := columnAliases[key]
if len(extraCandidates) > 0 {
candidates = append(extraCandidates, candidates...)
}
return findCol(candidates...)
}
primaryPinIdx := findColByAlias("PinNumber", pinCol)
var fallbackPinIndices []int
if primaryPinIdx >= 0 {
primaryHeader := header[primaryPinIdx]
parts := strings.Split(primaryHeader, "(")
chipPrefix := strings.TrimSpace(parts[0])
for i, name := range header {
if i == primaryPinIdx {
continue
}
nameLower := strings.ToLower(name)
if (chipPrefix != "" && len(chipPrefix) > 2 && strings.Contains(nameLower, strings.ToLower(chipPrefix))) ||
(pinCol != "" && strings.Contains(nameLower, strings.ToLower(pinCol))) {
fallbackPinIndices = append(fallbackPinIndices, i)
}
}
}
netNameIdx := findColByAlias("NetName")
dirIdx := findColByAlias("Dir")
ioIdx := findColByAlias("IO")
pullIdx := findColByAlias("Pull")
voltageIdx := findColByAlias("Voltage")
enumIdx := findColByAlias("Enum")
pinTypeOverrideIdx := findColByAlias("PinTypeOverride")
initialCondIdx := findColByAlias("InitialCondition")
netNameOverrideIdx := findColByAlias("NetNameOverride")
pinNameIdx := findColByAlias("PinName")
if primaryPinIdx == -1 {
return nil, fmt.Errorf("missing required column for Pin Number (checked --pin-col=%q, 'PinNumber', 'Pin')", pinCol)
}
if netNameIdx == -1 {
return nil, fmt.Errorf("missing required column for Net Name (checked 'NetName', 'Signal Name')")
}
var configs []PinConfig
lineNum := 1
for {
row, err := r.Read()
lineNum++
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("failed to read row at line %d: %v", lineNum, err)
}
getValue := func(idx int) string {
if idx >= 0 && idx < len(row) {
return strings.TrimSpace(row[idx])
}
return ""
}
netName := getValue(netNameIdx)
netUpper := strings.ToUpper(netName)
if netName == "" || netUpper == "NC" || netUpper == "NC (TP)" || netUpper == "NC(TP)" ||
netUpper == "NC_TP" || strings.HasPrefix(netUpper, "NC_") || strings.HasPrefix(netUpper, "NC ") ||
netUpper == "OPEN" || netUpper == "(OPEN)" || netUpper == "FLOATING" || netUpper == "(OPEN, FLOATING)" ||
netUpper == "N/A" || netUpper == "NA" {
continue
}
rawDir := getValue(dirIdx)
rawIO := getValue(ioIdx)
rawPull := getValue(pullIdx)
rawVoltage := getValue(voltageIdx)
enumVal := getValue(enumIdx)
sigType := getValue(pinTypeOverrideIdx)
initialCond := getValue(initialCondIdx)
netNameOverride := getValue(netNameOverrideIdx)
pinName := getValue(pinNameIdx)
pinNum := getValue(primaryPinIdx)
if pinNum == "" || pinNum == "N/A" || pinNum == "#N/A" || pinNum == "--" {
// Fall back to sibling variant columns for required peripheral or explicit enum signals
isPeripheralOrEnum := enumVal != "" || rawIO == "ADC" || rawIO == "I2C" || rawIO == "PWM" || rawIO == "TACH" ||
sigType == "ADC" || sigType == "I2C_CLOCK" || sigType == "I2C_DATA" || sigType == "PWM"
if isPeripheralOrEnum {
for _, fbIdx := range fallbackPinIndices {
val := getValue(fbIdx)
if val != "" && val != "N/A" && val != "#N/A" && val != "--" {
pinNum = val
break
}
}
}
}
if pinNum == "" || pinNum == "N/A" || pinNum == "#N/A" || pinNum == "--" {
continue
}
// Normalize Direction
direction := ""
if rawDir == "OUT" {
direction = "OUT"
} else if rawDir == "IN" {
direction = "IN"
} else if rawDir == "I/O" || rawDir == "IO" {
if strings.HasPrefix(sigType, "OUTPUT") || strings.HasPrefix(sigType, "ODR") {
direction = "OUT"
} else {
direction = "IN"
}
}
// Normalize Pull
pull := rawPull
if rawPull == "Y" {
pull = "UP"
} else if rawPull == "N" {
pull = "N"
}
// Normalize Voltage
voltage := rawVoltage
if strings.Contains(rawVoltage, "1.8") {
voltage = "1.8V"
} else if strings.Contains(rawVoltage, "3.3") {
voltage = "3.3V"
}
// Normalize Pin Type & Initial Condition
pinType := sigType
if pinType == "INPUT_PU" {
pinType = "INPUT"
pull = "UP"
if strings.HasSuffix(netName, "_L") || strings.HasSuffix(netName, "_ODL") || strings.HasSuffix(netName, "_LOW") {
pinType = "INPUT_L"
}
} else if pinType == "INPUT" && direction == "IN" {
if strings.HasSuffix(netName, "_L") || strings.HasSuffix(netName, "_ODL") || strings.HasSuffix(netName, "_LOW") {
pinType = "INPUT_L"
}
} else if pinType == "OUTPUT_L" && initialCond == "" {
initialCond = "LOW"
} else if pinType == "OUTPUT_H" && initialCond == "" {
initialCond = "HIGH"
} else if pinType == "OUTPUT_ODL" && initialCond == "" {
initialCond = "LOW"
} else if pinType == "OUTPUT_ODR" && initialCond == "" {
initialCond = "HIGH"
}
// Fix known signal defaults where ODR outputs are active high init in Zephyr
if netName == "EC_PCH_WAKE_ODL" && initialCond == "" {
initialCond = "HIGH"
}
// Clean double quotes in enum
enumVal = strings.ReplaceAll(enumVal, `""`, `"`)
enumVal = strings.Trim(enumVal, `"`)
config := PinConfig{
PinNumber: pinNum,
PinName: pinName,
NetName: netName,
Pull: pull,
Dir: direction,
IO: rawIO,
Enum: enumVal,
PinTypeOverride: pinType,
InitialCondition: initialCond,
NetNameOverride: netNameOverride,
Voltage: voltage,
}
configs = append(configs, config)
}
return configs, nil
}