blob: 7007069bfe3a3fcd4d63192399eebb0f8164cf4d [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 (
"embed"
"encoding/json"
"fmt"
"io/fs"
"maps"
"os"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
)
//go:embed data/chips/*.json
var embeddedChipsFS embed.FS
var (
npcxRegex = regexp.MustCompile(`GP[IO]{0,2}([0-9A-F])([0-7])`)
iteRegex = regexp.MustCompile(`GP([A-Z])([0-7])`)
adcRegex = regexp.MustCompile(`ADC([0-9]+)`)
pwmRegex = regexp.MustCompile(`PWM([0-9]+)`)
npcxI2C = regexp.MustCompile(`I2C([0-9]+)_(SCL|SDA)`)
iteScl = regexp.MustCompile(`SMCLK([0-9]+[A-Z]*)`)
iteSda = regexp.MustCompile(`SMDAT([0-9]+[A-Z]*)`)
npcxTachRegex = regexp.MustCompile(`^T([A-Z])([0-9])$`)
iteTachRegex = regexp.MustCompile(`^TACH([0-9])([A-Z])$`)
)
type ChipData struct {
Name string `json:"name"`
Vendor string `json:"vendor"`
Aliases []string `json:"aliases"`
PinMap map[string]string `json:"pin_map"`
}
type Chip struct {
Name string
Vendor string
Aliases []string
PinMap map[string]string
PkgPins []string // List of sorted pin numbers (keys of PinMap)
}
func (c *Chip) IsNPCX() bool {
if c.Vendor != "" {
return strings.EqualFold(c.Vendor, "npcx")
}
return strings.HasPrefix(strings.ToLower(c.Name), "npcx")
}
func (c *Chip) IsITE() bool {
if c.Vendor != "" {
return strings.EqualFold(c.Vendor, "ite")
}
return strings.HasPrefix(strings.ToLower(c.Name), "it")
}
// ResolveGPIO returns the Zephyr GPIO port (e.g., "0", "e", "a") and pin number (0-7)
func (c *Chip) ResolveGPIO(pinNum string) (string, int, error) {
pinDef, ok := c.PinMap[pinNum]
if !ok {
return "", 0, fmt.Errorf("pin %s not found in chip %s", pinNum, c.Name)
}
parts := strings.Split(pinDef, "/")
for _, part := range parts {
part = strings.TrimSpace(part)
if m := npcxRegex.FindStringSubmatch(part); m != nil {
port := strings.ToLower(m[1])
pin, err := strconv.Atoi(m[2])
if err != nil {
return "", 0, fmt.Errorf("invalid GPIO pin number %q in %s: %v", m[2], pinDef, err)
}
return port, pin, nil
}
}
for _, part := range parts {
part = strings.TrimSpace(part)
if m := iteRegex.FindStringSubmatch(part); m != nil {
port := strings.ToLower(m[1])
pin, err := strconv.Atoi(m[2])
if err != nil {
return "", 0, fmt.Errorf("invalid GPIO pin number %q in %s: %v", m[2], pinDef, err)
}
return port, pin, nil
}
}
if c.IsITE() {
for _, part := range parts {
part = strings.TrimSpace(part)
if strings.HasPrefix(part, "KSO") {
idxStr := strings.TrimPrefix(part, "KSO")
idx, err := strconv.Atoi(idxStr)
if err == nil {
if idx >= 0 && idx <= 7 {
return "ksol", idx, nil
} else if idx >= 8 && idx <= 15 {
return "ksoh", idx - 8, nil
}
}
}
}
}
return "", 0, fmt.Errorf("could not resolve GPIO for pin %s (%s)", pinNum, pinDef)
}
// ResolveADC returns the ADC channel (0-7) if the pin supports ADC
func (c *Chip) ResolveADC(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)
}
parts := strings.Split(pinDef, "/")
for _, part := range parts {
part = strings.TrimSpace(part)
if m := adcRegex.FindStringSubmatch(part); m != nil {
channel, err := strconv.Atoi(m[1])
if err != nil {
return 0, fmt.Errorf("invalid ADC channel %q in %s: %v", m[1], pinDef, err)
}
return channel, nil
}
}
return 0, fmt.Errorf("pin %s (%s) does not support ADC", pinNum, pinDef)
}
// ResolvePWM returns the PWM channel if the pin supports PWM
func (c *Chip) ResolvePWM(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)
}
parts := strings.Split(pinDef, "/")
for _, part := range parts {
part = strings.TrimSpace(part)
if m := pwmRegex.FindStringSubmatch(part); m != nil {
channel, err := strconv.Atoi(m[1])
if err != nil {
return 0, fmt.Errorf("invalid PWM channel %q in %s: %v", m[1], pinDef, err)
}
return channel, nil
}
}
return 0, fmt.Errorf("pin %s (%s) does not support PWM", pinNum, pinDef)
}
// ResolveI2C returns the I2C channel (as string, e.g. "0" or "2ALT") and whether it is SCL (true) or SDA (false)
func (c *Chip) ResolveI2C(pinNum string) (string, bool, error) {
pinDef, ok := c.PinMap[pinNum]
if !ok {
return "", false, fmt.Errorf("pin %s not found in chip %s", pinNum, c.Name)
}
parts := strings.Split(pinDef, "/")
for _, part := range parts {
part = strings.TrimSpace(part)
if m := npcxI2C.FindStringSubmatch(part); m != nil {
isScl := m[2] == "SCL"
return m[1], isScl, nil
}
if m := iteScl.FindStringSubmatch(part); m != nil {
return m[1], true, nil
}
if m := iteSda.FindStringSubmatch(part); m != nil {
return m[1], false, nil
}
}
return "", false, fmt.Errorf("pin %s (%s) does not support I2C", pinNum, pinDef)
}
// ResolveTACH returns the TACH channel and port letter if the pin supports TACH.
func (c *Chip) ResolveTACH(pinNum string) (int, string, error) {
pinDef, ok := c.PinMap[pinNum]
if !ok {
return 0, "", fmt.Errorf("pin %s not found in chip %s", pinNum, c.Name)
}
parts := strings.Split(pinDef, "/")
if c.IsNPCX() {
for _, part := range parts {
part = strings.TrimSpace(part)
if m := npcxTachRegex.FindStringSubmatch(part); m != nil {
portLetter := m[1]
chanNum, err := strconv.Atoi(m[2])
if err != nil {
return 0, "", fmt.Errorf("invalid TACH channel %q in %s: %v", m[2], pinDef, err)
}
return chanNum, portLetter, nil
}
}
} else if c.IsITE() {
for _, part := range parts {
part = strings.TrimSpace(part)
if m := iteTachRegex.FindStringSubmatch(part); m != nil {
chanNum, err := strconv.Atoi(m[1])
if err != nil {
return 0, "", fmt.Errorf("invalid TACH channel %q in %s: %v", m[1], pinDef, err)
}
portLetter := m[2]
return chanNum, portLetter, nil
}
}
}
return 0, "", fmt.Errorf("pin %s (%s) does not support TACH", pinNum, pinDef)
}
var chips = map[string]*Chip{}
// GetChip returns the registered Chip by name or alias.
func GetChip(name string) (*Chip, bool) {
chip, ok := chips[strings.ToLower(name)]
return chip, ok
}
// GetAvailableChips returns a sorted, comma-separated list of all registered chip names and aliases.
func GetAvailableChips() string {
names := slices.Collect(maps.Keys(chips))
slices.Sort(names)
return strings.Join(names, ", ")
}
func RegisterChipData(data []byte) error {
var cd ChipData
if err := json.Unmarshal(data, &cd); err != nil {
return fmt.Errorf("failed to unmarshal chip json: %v", err)
}
var pins []string
for k := range cd.PinMap {
if k != "" {
pins = append(pins, k)
}
}
slices.Sort(pins)
chip := &Chip{
Name: cd.Name,
Vendor: cd.Vendor,
Aliases: cd.Aliases,
PinMap: cd.PinMap,
PkgPins: pins,
}
chips[strings.ToLower(cd.Name)] = chip
for _, alias := range cd.Aliases {
chips[strings.ToLower(alias)] = chip
}
return nil
}
// LoadExternalChipDir loads custom chip definitions from an external directory
func LoadExternalChipDir(dirPath string) error {
entries, err := os.ReadDir(dirPath)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".json") {
fullPath := filepath.Join(dirPath, entry.Name())
content, err := os.ReadFile(fullPath)
if err != nil {
return fmt.Errorf("failed to read chip file %s: %v", fullPath, err)
}
if err := RegisterChipData(content); err != nil {
return fmt.Errorf("error loading chip file %s: %v", fullPath, err)
}
}
}
return nil
}
func init() {
files, err := fs.ReadDir(embeddedChipsFS, "data/chips")
if err != nil {
panic(fmt.Sprintf("failed to read embedded chip dir: %v", err))
}
for _, file := range files {
if strings.HasSuffix(file.Name(), ".json") {
content, err := embeddedChipsFS.ReadFile(filepath.Join("data/chips", file.Name()))
if err != nil {
panic(fmt.Sprintf("failed to read embedded chip file %s: %v", file.Name(), err))
}
if err := RegisterChipData(content); err != nil {
panic(fmt.Sprintf("failed to register embedded chip file %s: %v", file.Name(), err))
}
}
}
}