package main

import (
	"fmt"
	"os"
	"strings"
)

const (
	PIRQI = 0
	PIRQJ = 1
	PIRQK = 2
	PIRQL = 3
	PIRQM = 4
	PIRQN = 5
	PIRQO = 6
	PIRQP = 7
	PIRQQ = 8
	PIRQR = 9
	PIRQS = 10
	PIRQT = 11
	PIRQU = 12
	PIRQV = 13
	PIRQW = 14
	PIRQX = 15
)

/* from sb/intel/lynxpoint/lp_gpio.c */
func lp_gpio_to_pirq(gpioNum uint16) int {
	switch gpioNum {
	case 8:
		return PIRQI
	case 9:
		return PIRQJ
	case 10:
		return PIRQK
	case 13:
		return PIRQL
	case 14:
		return PIRQM
	case 45:
		return PIRQN
	case 46:
		return PIRQO
	case 47:
		return PIRQP
	case 48:
		return PIRQQ
	case 49:
		return PIRQR
	case 50:
		return PIRQS
	case 51:
		return PIRQT
	case 52:
		return PIRQU
	case 53:
		return PIRQV
	case 54:
		return PIRQW
	case 55:
		return PIRQX
	default:
		return -1
	}
}

func conf0str(conf0 uint32) string {
	if (conf0 & 1) == 0 {
		return "GPIO_MODE_NATIVE"
	} else {
		s := []string{"GPIO_MODE_GPIO"}
		var gpio_output bool
		if ((conf0 >> 2) & 1) == 1 {
			s = append(s, "GPIO_DIR_INPUT")
			gpio_output = false
		} else {
			s = append(s, "GPIO_DIR_OUTPUT")
			gpio_output = true
		}
		if ((conf0 >> 3) & 1) == 1 {
			s = append(s, "GPIO_INVERT")
		}
		if ((conf0 >> 4) & 1) == 1 {
			s = append(s, "GPIO_IRQ_LEVEL")
		}
		if gpio_output {
			if ((conf0 >> 31) & 1) == 1 {
				s = append(s, "GPO_LEVEL_HIGH")
			} else {
				s = append(s, "GPO_LEVEL_LOW")
			}
		}
		return strings.Join(s, " | ")
	}
}

func lpgpio_preset(conf0 uint32, owner uint32, route uint32, irqen uint32, pirq uint32) string {
	if conf0 == 0xd { /* 0b1101: MODE_GPIO | INPUT | INVERT */
		if owner == 0 { /* OWNER_ACPI */
			if irqen == 0 && pirq == 0 {
				if route == 0 { /* SCI */
					return "GPIO_ACPI_SCI"
				} else {
					return "GPIO_ACPI_SMI"
				}
			}
			return ""
		} else { /* OWNER_GPIO */
			if route == 0 && irqen == 0 && pirq != 0 {
				return "GPIO_INPUT_INVERT"
			}
			return ""
		}
	}

	if conf0 == 0x5 && owner == 1 { /* 0b101: MODE_GPIO | INPUT, OWNER_GPIO */
		if route == 0 && irqen == 0 {
			if pirq == 1 {
				return "GPIO_PIRQ"
			} else {
				return "GPIO_INPUT"
			}
		}
		return ""
	}

	if owner == 1 && irqen == 1 {
		if route == 0 && pirq == 0 {
			if conf0 == 0x5 { /* 0b00101 */
				return "GPIO_IRQ_EDGE"
			}
			if conf0 == 0x15 { /* 0b10101 */
				return "GPIO_IRQ_LEVEL"
			}
		}
		return ""
	}
	return ""
}

func gpio_str(conf0 uint32, conf1 uint32, owner uint32, route uint32, irqen uint32, reset uint32, blink uint32, pirq uint32) string {
	s := []string{}
	s = append(s, fmt.Sprintf(".conf0 = %s", conf0str(conf0)))
	if conf1 != 0 {
		s = append(s, fmt.Sprintf(".conf1 = 0x%x", conf1))
	}
	if owner != 0 {
		s = append(s, ".owner = GPIO_OWNER_GPIO")
	}
	if route != 0 {
		s = append(s, ".route = GPIO_ROUTE_SMI")
	}
	if irqen != 0 {
		s = append(s, ".irqen = GPIO_IRQ_ENABLE")
	}
	if reset != 0 {
		s = append(s, ".reset = GPIO_RESET_RSMRST")
	}
	if blink != 0 {
		s = append(s, ".blink = GPO_BLINK")
	}
	if pirq != 0 {
		s = append(s, ".pirq = GPIO_PIRQ_APIC_ROUTE")
	}
	return strings.Join(s, ", ")
}

/* start addresses of GPIO registers */
const (
	GPIO_OWN        = 0x0
	GPIPIRQ2IOXAPIC = 0x10
	GPO_BLINK       = 0x18
	GPI_ROUT        = 0x30
	GP_RST_SEL      = 0x60
	GPI_IE          = 0x90
	GPnCONFIGA      = 0x100
	GPnCONFIGB      = 0x104
)

func PrintLPGPIO(gpio *os.File, inteltool InteltoolData) {
	for gpioNum := uint16(0); gpioNum <= 94; gpioNum++ {
		if gpioNum < 10 {
			fmt.Fprintf(gpio, "\t[%d]  = ", gpioNum)
		} else {
			fmt.Fprintf(gpio, "\t[%d] = ", gpioNum)
		}
		conf0 := inteltool.GPIO[GPnCONFIGA+gpioNum*8]
		conf1 := inteltool.GPIO[GPnCONFIGB+gpioNum*8]
		set := gpioNum / 32
		bit := gpioNum % 32
		/* owner only effective in GPIO mode */
		owner := (inteltool.GPIO[GPIO_OWN+set*4] >> bit) & 1
		route := (inteltool.GPIO[GPI_ROUT+set*4] >> bit) & 1
		irqen := (inteltool.GPIO[GPI_IE+set*4] >> bit) & 1
		reset := (inteltool.GPIO[GP_RST_SEL+set*4] >> bit) & 1
		var blink, pirq uint32
		/* blink only effective in GPIO output mode */
		if set == 0 {
			blink = (inteltool.GPIO[GPO_BLINK] >> bit) & 1
		} else {
			blink = 0
		}
		irqset := lp_gpio_to_pirq(gpioNum)
		if irqset >= 0 {
			pirq = (inteltool.GPIO[GPIPIRQ2IOXAPIC] >> uint(irqset)) & 1
		} else {
			pirq = 0
		}

		if (conf0 & 1) == 0 {
			fmt.Fprintf(gpio, "LP_GPIO_NATIVE,\n")
		} else if (conf0 & 4) == 0 {
			/* configured as output */
			if ((conf0 >> 31) & 1) == 0 {
				fmt.Fprintf(gpio, "LP_GPIO_OUT_LOW,\n")
			} else {
				fmt.Fprintf(gpio, "LP_GPIO_OUT_HIGH,\n")
			}
		} else if (conf1 & 4) != 0 {
			/* configured as input and sensing disabled */
			fmt.Fprintf(gpio, "LP_GPIO_UNUSED,\n")
		} else {
			is_preset := false
			if conf1 == 0 && reset == 0 && blink == 0 {
				preset := lpgpio_preset(conf0, owner, route, irqen, pirq)
				if preset != "" {
					fmt.Fprintf(gpio, "LP_%s,\n", preset)
					is_preset = true
				}
			}
			if !is_preset {
				fmt.Fprintf(gpio, "{ %s },\n", gpio_str(conf0, conf1, owner, route, irqen, reset, blink, pirq))
			}
		}
	}
}

func Lynxpoint_LP_GPIO(ctx Context, inteltool InteltoolData) {
	gpio := Create(ctx, "gpio.c")
	defer gpio.Close()

	AddROMStageFile("gpio.c", "")

	Add_gpl(gpio)
	gpio.WriteString(`#include <southbridge/intel/lynxpoint/lp_gpio.h>

const struct pch_lp_gpio_map mainboard_lp_gpio_map[] = {
`)
	PrintLPGPIO(gpio, inteltool)
	gpio.WriteString("\tLP_GPIO_END\n};\n")
}