#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/errno.h>
#include <sys/lock.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include "opt_acpi.h"
#include "acpi.h"
#include <dev/acpica/acpivar.h>
#include "gpio_intel_var.h"
#include "gpio_if.h"
#define CHV_GPIO_REG_IS 0x300
#define CHV_GPIO_REG_MASK 0x380
#define CHV_GPIO_REG_PINS 0x4400
#define CHV_GPIO_REGOFF_CTL0 0x0
#define CHV_GPIO_REGOFF_CTL1 0x4
#define CHV_GPIO_CTL0_RXSTATE 0x00000001u
#define CHV_GPIO_CTL0_TXSTATE 0x00000002u
#define CHV_GPIO_CTL0_GPIOCFG_MASK 0x00000700u
#define CHV_GPIO_CTL0_GPIOEN 0x00008000u
#define CHV_GPIO_CTL0_PULLUP 0x00800000u
#define CHV_GPIO_CTL1_INTCFG_MASK 0x00000007u
#define CHV_GPIO_CTL1_INVRXDATA 0x00000040u
#define CHV_GPIO_PINSIZE 0x8
#define CHV_GPIO_PINCHUNK 15
#define CHV_GPIO_PININC 0x400
#define PIN_ADDRESS(x) \
(CHV_GPIO_REG_PINS + \
((x) / CHV_GPIO_PINCHUNK) * CHV_GPIO_PININC + \
((x) % CHV_GPIO_PINCHUNK) * CHV_GPIO_PINSIZE)
#define PIN_CTL0(x) (PIN_ADDRESS(x) + CHV_GPIO_REGOFF_CTL0)
#define PIN_CTL1(x) (PIN_ADDRESS(x) + CHV_GPIO_REGOFF_CTL1)
static void gpio_cherryview_init(struct gpio_intel_softc *sc);
static void gpio_cherryview_intr(void *arg);
static int gpio_cherryview_map_intr(struct gpio_intel_softc *sc,
uint16_t pin, int trigger, int polarity, int termination);
static void gpio_cherryview_unmap_intr(struct gpio_intel_softc *sc,
struct pin_intr_map *map);
static void gpio_cherryview_enable_intr(struct gpio_intel_softc *sc,
struct pin_intr_map *map);
static void gpio_cherryview_disable_intr(struct gpio_intel_softc *sc,
struct pin_intr_map *map);
static int gpio_cherryview_check_io_pin(struct gpio_intel_softc *sc,
uint16_t pin, int flags);
static int gpio_cherryview_read_pin(struct gpio_intel_softc *sc,
uint16_t pin);
static void gpio_cherryview_write_pin(struct gpio_intel_softc *sc,
uint16_t pin, int value);
static struct gpio_intel_fns gpio_cherryview_fns = {
.init = gpio_cherryview_init,
.intr = gpio_cherryview_intr,
.map_intr = gpio_cherryview_map_intr,
.unmap_intr = gpio_cherryview_unmap_intr,
.enable_intr = gpio_cherryview_enable_intr,
.disable_intr = gpio_cherryview_disable_intr,
.check_io_pin = gpio_cherryview_check_io_pin,
.read_pin = gpio_cherryview_read_pin,
.write_pin = gpio_cherryview_write_pin,
};
static struct pinrange chv_sw_ranges[] = {
{ 0, 7 },
{ 15, 22 },
{ 30, 37 },
{ 45, 52 },
{ 60, 67 },
{ 75, 82 },
{ 90, 97 },
{ -1, -1 }
};
static struct pinrange chv_n_ranges[] = {
{ 0, 8 },
{ 15, 27 },
{ 30, 41 },
{ 45, 56 },
{ 60, 72 },
{ -1, -1 }
};
static struct pinrange chv_e_ranges[] = {
{ 0, 11 },
{ 15, 26 },
{ -1, -1 }
};
static struct pinrange chv_se_ranges[] = {
{ 0, 7 },
{ 15, 26 },
{ 30, 35 },
{ 45, 52 },
{ 60, 69 },
{ 75, 85 },
{ -1, -1 }
};
static struct lock gpio_lk;
LOCK_SYSINIT(chvgpiolk, &gpio_lk, "chvgpio", 0);
static inline uint32_t
chvgpio_read(struct gpio_intel_softc *sc, bus_size_t offset)
{
uint32_t val;
lockmgr(&gpio_lk, LK_EXCLUSIVE);
val = bus_read_4(sc->mem_res, offset);
lockmgr(&gpio_lk, LK_RELEASE);
return val;
}
static inline void
chvgpio_write(struct gpio_intel_softc *sc, bus_size_t offset, uint32_t val)
{
lockmgr(&gpio_lk, LK_EXCLUSIVE);
bus_write_4(sc->mem_res, offset, val);
lockmgr(&gpio_lk, LK_RELEASE);
}
int
gpio_cherryview_matchuid(struct gpio_intel_softc *sc)
{
ACPI_HANDLE handle;
handle = acpi_get_handle(sc->dev);
if (acpi_MatchUid(handle, "1")) {
sc->ranges = chv_sw_ranges;
} else if (acpi_MatchUid(handle, "2")) {
sc->ranges = chv_n_ranges;
} else if (acpi_MatchUid(handle, "3")) {
sc->ranges = chv_e_ranges;
} else if (acpi_MatchUid(handle, "4")) {
sc->ranges = chv_se_ranges;
} else {
return (ENXIO);
}
sc->fns = &gpio_cherryview_fns;
return (0);
}
static void
gpio_cherryview_init(struct gpio_intel_softc *sc)
{
chvgpio_write(sc, CHV_GPIO_REG_MASK, 0);
chvgpio_write(sc, CHV_GPIO_REG_IS, 0xffff);
}
static void
gpio_cherryview_intr(void *arg)
{
struct gpio_intel_softc *sc = (struct gpio_intel_softc *)arg;
struct pin_intr_map *mapping;
uint32_t status;
int i;
status = chvgpio_read(sc, CHV_GPIO_REG_IS);
KKASSERT(NELEM(sc->intrmaps) >= 16);
for (i = 0; i < 16; i++) {
if (status & (1U << i)) {
mapping = &sc->intrmaps[i];
if (!mapping->is_level) {
chvgpio_write(sc, CHV_GPIO_REG_IS,
(1U << i));
}
if (mapping->pin != -1 && mapping->handler != NULL)
mapping->handler(mapping->arg);
if (mapping->is_level) {
chvgpio_write(sc, CHV_GPIO_REG_IS,
(1U << i));
}
}
}
}
static int
gpio_cherryview_map_intr(struct gpio_intel_softc *sc, uint16_t pin, int trigger,
int polarity, int termination)
{
uint32_t reg, reg1, reg2;
uint32_t intcfg, new_intcfg, gpiocfg, new_gpiocfg;
int i;
reg1 = chvgpio_read(sc, PIN_CTL0(pin));
reg2 = chvgpio_read(sc, PIN_CTL1(pin));
device_printf(sc->dev,
"pin=%d trigger=%d polarity=%d ctrl0=0x%08x ctrl1=0x%08x\n",
pin, trigger, polarity, reg1, reg2);
new_intcfg = intcfg = reg2 & CHV_GPIO_CTL1_INTCFG_MASK;
new_gpiocfg = gpiocfg = reg1 & CHV_GPIO_CTL0_GPIOCFG_MASK;
if (!(reg1 & CHV_GPIO_CTL0_GPIOEN)) {
device_printf(sc->dev, "GPIO mode is disabled\n");
return (ENXIO);
}
if (gpiocfg != 0x0 && gpiocfg != 0x200) {
device_printf(sc->dev, "RX is disabled\n");
if (gpiocfg == 0x100)
new_gpiocfg = 0x000;
else if (gpiocfg == 0x300)
new_gpiocfg = 0x200;
else
return (ENXIO);
}
if (trigger == ACPI_LEVEL_SENSITIVE) {
if (intcfg != 4) {
device_printf(sc->dev,
"trigger is %x, should be 4 (Level)\n", intcfg);
return (ENXIO);
}
if (polarity == ACPI_ACTIVE_BOTH) {
device_printf(sc->dev,
"ACTIVE_BOTH incompatible with level trigger\n");
return (ENXIO);
} else if (polarity == ACPI_ACTIVE_LOW) {
if (!(reg2 & CHV_GPIO_CTL1_INVRXDATA)) {
device_printf(sc->dev,
"Invert RX not enabled (needed for "
"level/low trigger/polarity)\n");
return (ENXIO);
}
} else {
if (reg2 & CHV_GPIO_CTL1_INVRXDATA) {
device_printf(sc->dev,
"Invert RX should not be enabled for "
"level/high trigger/polarity\n");
return (ENXIO);
}
}
} else {
if (polarity == ACPI_ACTIVE_HIGH && intcfg != 2) {
device_printf(sc->dev,
"Wrong interrupt configuration, is 0x%x should "
"be 0x%x\n", intcfg, 2);
if (intcfg == 1 || intcfg == 3)
new_intcfg = 2;
else
return (ENXIO);
} else if (polarity == ACPI_ACTIVE_LOW && intcfg != 1) {
device_printf(sc->dev,
"Wrong interrupt configuration, is 0x%x should "
"be 0x%x\n", intcfg, 1);
if (intcfg == 2 || intcfg == 3)
new_intcfg = 1;
else
return (ENXIO);
} else if (polarity == ACPI_ACTIVE_BOTH && intcfg != 3) {
device_printf(sc->dev,
"Wrong interrupt configuration, is 0x%x should "
"be 0x%x\n", intcfg, 3);
if (intcfg == 1 || intcfg == 2)
new_intcfg = 3;
else
return (ENXIO);
}
}
if (termination == ACPI_PIN_CONFIG_PULLUP &&
!(reg1 & CHV_GPIO_CTL0_PULLUP)) {
device_printf(sc->dev,
"Wrong termination, is pull-down, should be pull-up\n");
return (ENXIO);
} else if (termination == ACPI_PIN_CONFIG_PULLDOWN &&
(reg1 & CHV_GPIO_CTL0_PULLUP)) {
device_printf(sc->dev,
"Wrong termination, is pull-up, should be pull-down\n");
return (ENXIO);
}
i = (reg1 >> 28) & 0xf;
if (sc->intrmaps[i].pin != -1) {
device_printf(sc->dev, "Interrupt line %d already used\n", i);
return (ENXIO);
}
if (new_intcfg != intcfg) {
device_printf(sc->dev,
"Switching interrupt configuration from 0x%x to 0x%x\n",
intcfg, new_intcfg);
reg = reg2 & ~CHV_GPIO_CTL1_INTCFG_MASK;
reg |= (new_intcfg & CHV_GPIO_CTL1_INTCFG_MASK) << 0;
chvgpio_write(sc, PIN_CTL1(pin), reg);
}
if (new_gpiocfg != gpiocfg) {
device_printf(sc->dev,
"Switching gpio configuration from 0x%x to 0x%x\n",
gpiocfg, new_gpiocfg);
reg = reg1 & ~CHV_GPIO_CTL0_GPIOCFG_MASK;
reg |= (new_gpiocfg & CHV_GPIO_CTL0_GPIOCFG_MASK) << 0;
chvgpio_write(sc, PIN_CTL0(pin), reg);
}
sc->intrmaps[i].pin = pin;
sc->intrmaps[i].intidx = i;
sc->intrmaps[i].orig_intcfg = intcfg;
sc->intrmaps[i].orig_gpiocfg = gpiocfg;
if (trigger == ACPI_LEVEL_SENSITIVE)
sc->intrmaps[i].is_level = 1;
else
sc->intrmaps[i].is_level = 0;
return (0);
}
static void
gpio_cherryview_unmap_intr(struct gpio_intel_softc *sc,
struct pin_intr_map *map)
{
uint32_t reg, intcfg, gpiocfg;
uint16_t pin = map->pin;
intcfg = map->orig_intcfg;
intcfg &= CHV_GPIO_CTL1_INTCFG_MASK;
gpiocfg = map->orig_gpiocfg;
gpiocfg &= CHV_GPIO_CTL0_GPIOCFG_MASK;
map->pin = -1;
map->intidx = -1;
map->is_level = 0;
map->orig_intcfg = 0;
map->orig_gpiocfg = 0;
reg = chvgpio_read(sc, PIN_CTL1(pin));
if ((reg & CHV_GPIO_CTL1_INTCFG_MASK) != intcfg) {
reg &= ~CHV_GPIO_CTL1_INTCFG_MASK;
reg |= intcfg;
chvgpio_write(sc, PIN_CTL1(pin), reg);
}
reg = chvgpio_read(sc, PIN_CTL0(pin));
if ((reg & CHV_GPIO_CTL0_GPIOCFG_MASK) != gpiocfg) {
reg &= ~CHV_GPIO_CTL0_GPIOCFG_MASK;
reg |= gpiocfg;
chvgpio_write(sc, PIN_CTL0(pin), reg);
}
}
static void
gpio_cherryview_enable_intr(struct gpio_intel_softc *sc,
struct pin_intr_map *map)
{
uint32_t reg;
KKASSERT(map->intidx >= 0);
chvgpio_write(sc, CHV_GPIO_REG_IS, (1U << map->intidx));
reg = chvgpio_read(sc, CHV_GPIO_REG_MASK);
reg |= (1U << map->intidx);
chvgpio_write(sc, CHV_GPIO_REG_MASK, reg);
}
static void
gpio_cherryview_disable_intr(struct gpio_intel_softc *sc,
struct pin_intr_map *map)
{
uint32_t reg;
KKASSERT(map->intidx >= 0);
reg = chvgpio_read(sc, CHV_GPIO_REG_MASK);
reg &= ~(1U << map->intidx);
chvgpio_write(sc, CHV_GPIO_REG_MASK, reg);
}
static int
gpio_cherryview_check_io_pin(struct gpio_intel_softc *sc, uint16_t pin,
int flags)
{
uint32_t reg1, reg2;
reg1 = chvgpio_read(sc, PIN_CTL0(pin));
if (flags & (1U << 0)) {
if ((reg1 & CHV_GPIO_CTL0_GPIOCFG_MASK) != 0 &&
(reg1 & CHV_GPIO_CTL0_GPIOCFG_MASK) != 0x200) {
return (0);
}
}
reg2 = chvgpio_read(sc, PIN_CTL1(pin));
if (flags & (1U << 1)) {
if ((reg2 & CHV_GPIO_CTL1_INTCFG_MASK) != 0)
return (0);
if ((reg1 & CHV_GPIO_CTL0_GPIOCFG_MASK) != 0 &&
(reg1 & CHV_GPIO_CTL0_GPIOCFG_MASK) != 0x100) {
return (0);
}
}
return (1);
}
static int
gpio_cherryview_read_pin(struct gpio_intel_softc *sc, uint16_t pin)
{
uint32_t reg;
int val;
reg = chvgpio_read(sc, PIN_CTL0(pin));
KKASSERT((reg & CHV_GPIO_CTL0_GPIOCFG_MASK) == 0x0 ||
(reg & CHV_GPIO_CTL0_GPIOCFG_MASK) == 0x200);
if (reg & CHV_GPIO_CTL0_RXSTATE)
val = 1;
else
val = 0;
return (val);
}
static void
gpio_cherryview_write_pin(struct gpio_intel_softc *sc, uint16_t pin, int value)
{
uint32_t reg;
reg = chvgpio_read(sc, PIN_CTL0(pin));
KKASSERT((reg & CHV_GPIO_CTL0_GPIOCFG_MASK) == 0 ||
(reg & CHV_GPIO_CTL0_GPIOCFG_MASK) == 0x100);
if (value)
reg |= CHV_GPIO_CTL0_TXSTATE;
else
reg &= ~CHV_GPIO_CTL0_TXSTATE;
chvgpio_write(sc, PIN_CTL0(pin), reg);
}