blob: 6334b3e9bdf78f69df7ec5b03669ed4b6992fba3 [file] [log] [blame]
/*
* This file is part of the coreboot project.
*
* Copyright (C) 2014 Google Inc.
* Copyright (C) 2015 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <stdint.h>
#include <string.h>
#include <arch/io.h>
#include <console/console.h>
#include <device/device.h>
#include <device/pci.h>
#include <gpio.h>
#include <soc/pcr.h>
#include <soc/iomap.h>
#include <soc/pm.h>
static const int gpio_debug = 0;
/* There are 4 communities with 8 GPIO groups (GPP_[A:G] and GPD) */
struct gpio_community {
int port_id;
/* Inclusive pads within the community. */
gpio_t min;
gpio_t max;
};
/* This is ordered to match ACPI and OS driver. */
static const struct gpio_community communities[] = {
{
.port_id = PID_GPIOCOM0,
.min = GPP_A0,
.max = GPP_B23,
},
{
.port_id = PID_GPIOCOM1,
.min = GPP_C0,
.max = GPP_E23,
},
{
.port_id = PID_GPIOCOM3,
.min = GPP_F0,
.max = GPP_G7,
},
{
.port_id = PID_GPIOCOM2,
.min = GPD0,
.max = GPD11,
},
};
static const char *gpio_group_names[GPIO_NUM_GROUPS] = {
"GPP_A",
"GPP_B",
"GPP_C",
"GPP_D",
"GPP_E",
"GPP_F",
"GPP_G",
"GPD",
};
static inline size_t gpios_in_community(const struct gpio_community *comm)
{
/* max is inclusive */
return comm->max - comm->min + 1;
}
static inline size_t groups_in_community(const struct gpio_community *comm)
{
size_t n = gpios_in_community(comm) + GPIO_MAX_NUM_PER_GROUP - 1;
return n / GPIO_MAX_NUM_PER_GROUP;
}
static inline int gpio_index_gpd(gpio_t gpio)
{
if (gpio >= GPD0 && gpio <= GPD11)
return 1;
return 0;
}
static const struct gpio_community *gpio_get_community(gpio_t pad)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(communities); i++) {
const struct gpio_community *c = &communities[i];
if (pad >= c->min && pad <= c->max)
return c;
}
return NULL;
}
static size_t community_clr_get_smi_sts(const struct gpio_community *comm,
uint32_t *sts)
{
uint8_t *regs;
size_t i;
uint32_t *gpi_status_reg;
uint32_t *gpi_en_reg;
const size_t num_grps = groups_in_community(comm);
/* Not all groups can be routed to SMI. However, the registers
* read as 0. In order to simplify the logic read everything from
* each community. */
regs = pcr_port_regs(comm->port_id);
gpi_status_reg = (void *)&regs[GPI_SMI_STS_OFFSET];
gpi_en_reg = (void *)&regs[GPI_SMI_EN_OFFSET];
for (i = 0; i < num_grps; i++) {
sts[i] = read32(gpi_status_reg + i) & read32(gpi_en_reg + i);
/* Clear the enabled and set status bits. */
write32(gpi_status_reg + i, sts[i]);
}
return num_grps;
}
static void print_gpi_status(uint32_t status, const char *grp_name)
{
int i;
if (!status)
return;
for (i = 31; i >= 0; i--) {
if (status & (1 << i))
printk(BIOS_DEBUG, "%s%d ", grp_name, i);
}
}
void gpi_clear_get_smi_status(struct gpi_status *sts)
{
int i;
int do_print;
size_t sts_index = 0;
for (i = 0; i < ARRAY_SIZE(communities); i++) {
const struct gpio_community *comm = &communities[i];
sts_index += community_clr_get_smi_sts(comm,
&sts->grp[sts_index]);
}
do_print = 0;
for (i = 0; i < ARRAY_SIZE(sts->grp); i++) {
if (sts->grp[i] == 0)
continue;
do_print = 1;
break;
}
if (!do_print)
return;
printk(BIOS_DEBUG, "GPI_SMI_STS: ");
for (i = 0; i < ARRAY_SIZE(sts->grp); i++)
print_gpi_status(sts->grp[i], gpio_group_names[i]);
printk(BIOS_DEBUG, "\n");
}
int gpi_status_get(const struct gpi_status *sts, gpio_t gpi)
{
const uint32_t *gpi_sts;
/* Check if valid gpi */
if (gpio_get_community(gpi) == NULL)
return 0;
/* If not in GPD group the index is a linear function based on
* GPI number and GPIO_MAX_NUM_PER_GROUP. */
if (gpio_index_gpd(gpi))
gpi_sts = &sts->grp[GPD];
else
gpi_sts = &sts->grp[gpi / GPIO_MAX_NUM_PER_GROUP];
return !!(*gpi_sts & (1 << (gpi % GPIO_MAX_NUM_PER_GROUP)));
}
void gpio_route_gpe(uint16_t gpe0_route)
{
int i;
uint32_t misc_cfg;
const uint32_t misc_cfg_reg_mask = GPE_DW_MASK;
misc_cfg = (uint32_t)gpe0_route << GPE_DW_SHIFT;
misc_cfg &= misc_cfg_reg_mask;
for (i = 0; i < ARRAY_SIZE(communities); i++) {
uint8_t *regs;
uint32_t reg;
const struct gpio_community *comm = &communities[i];
regs = pcr_port_regs(comm->port_id);
reg = read32(regs + MISCCFG_OFFSET);
reg &= ~misc_cfg_reg_mask;
reg |= misc_cfg;
write32(regs + MISCCFG_OFFSET, reg);
}
}
static void *gpio_dw_regs(gpio_t pad)
{
const struct gpio_community *comm;
uint8_t *regs;
size_t pad_relative;
comm = gpio_get_community(pad);
if (comm == NULL)
return NULL;
regs = pcr_port_regs(comm->port_id);
pad_relative = pad - comm->min;
/* DW0 and DW1 regs are 4 bytes each. */
return &regs[PAD_CFG_DW_OFFSET + pad_relative *
GPIO_DWx_SIZE(GPIO_DWx_COUNT)];
}
static void *gpio_hostsw_reg(gpio_t pad, size_t *bit)
{
const struct gpio_community *comm;
uint8_t *regs;
size_t pad_relative;
comm = gpio_get_community(pad);
if (comm == NULL)
return NULL;
regs = pcr_port_regs(comm->port_id);
pad_relative = pad - comm->min;
/* Update the bit for this pad. */
*bit = (pad_relative % HOSTSW_OWN_PADS_PER);
/* HostSw regs are 4 bytes each. */
regs = &regs[HOSTSW_OWN_REG_OFFSET];
return &regs[(pad_relative / HOSTSW_OWN_PADS_PER) * 4];
}
static void gpio_handle_pad_mode(const struct pad_config *cfg)
{
size_t bit;
uint32_t *hostsw_own_reg;
uint32_t reg;
bit = 0;
hostsw_own_reg = gpio_hostsw_reg(cfg->pad, &bit);
if (hostsw_own_reg == NULL)
return;
reg = read32(hostsw_own_reg);
reg &= ~(1U << bit);
if ((cfg->attrs & PAD_FIELD(HOSTSW, GPIO)) == PAD_FIELD(HOSTSW, GPIO))
reg |= (HOSTSW_GPIO << bit);
else
reg |= (HOSTSW_ACPI << bit);
write32(hostsw_own_reg, reg);
}
static void gpi_enable_smi(gpio_t pad)
{
const struct gpio_community *comm;
uint8_t *regs;
uint32_t *gpi_status_reg;
uint32_t *gpi_en_reg;
size_t group_offset;
uint32_t pad_mask;
comm = gpio_get_community(pad);
if (comm == NULL)
return;
regs = pcr_port_regs(comm->port_id);
gpi_status_reg = (void *)&regs[GPI_SMI_STS_OFFSET];
gpi_en_reg = (void *)&regs[GPI_SMI_EN_OFFSET];
/* Offset of SMI STS/EN for this pad's group within the community. */
group_offset = (pad - comm->min) / GPIO_MAX_NUM_PER_GROUP;
/* Clear status then set enable. */
pad_mask = 1 << ((pad - comm->min) % GPIO_MAX_NUM_PER_GROUP);
write32(&gpi_status_reg[group_offset], pad_mask);
write32(&gpi_en_reg[group_offset],
read32(&gpi_en_reg[group_offset]) | pad_mask);
}
static void gpio_configure_pad(const struct pad_config *cfg)
{
uint32_t *dw_regs;
uint32_t reg;
uint32_t dw0;
uint32_t mask;
dw_regs = gpio_dw_regs(cfg->pad);
if (dw_regs == NULL)
return;
dw0 = cfg->dw0;
write32(&dw_regs[0], dw0);
reg = read32(&dw_regs[1]);
/* Apply termination field */
mask = PAD_TERM_MASK << PAD_TERM_SHIFT;
reg &= ~mask;
reg |= cfg->attrs & mask;
/* Apply voltage tolerance field */
mask = PAD_TOL_MASK << PAD_TOL_SHIFT;
reg &= ~mask;
reg |= cfg->attrs & mask;
write32(&dw_regs[1], reg);
gpio_handle_pad_mode(cfg);
if ((dw0 & PAD_FIELD(GPIROUTSMI, MASK)) == PAD_FIELD(GPIROUTSMI, YES))
gpi_enable_smi(cfg->pad);
if (gpio_debug)
printk(BIOS_DEBUG,
"Write Pad: Base(%p) - conf0 = %x conf1= %x pad # = %d\n",
&dw_regs[0], dw0, reg, cfg->pad);
}
void gpio_configure_pads(const struct pad_config *cfgs, size_t num)
{
size_t i;
for (i = 0; i < num; i++)
gpio_configure_pad(&cfgs[i]);
}
void gpio_input_pulldown(gpio_t gpio)
{
struct pad_config cfg = PAD_CFG_GPI(gpio, 5K_PD, DEEP);
gpio_configure_pad(&cfg);
}
void gpio_input_pullup(gpio_t gpio)
{
struct pad_config cfg = PAD_CFG_GPI(gpio, 5K_PU, DEEP);
gpio_configure_pad(&cfg);
}
void gpio_input(gpio_t gpio)
{
struct pad_config cfg = PAD_CFG_GPI(gpio, NONE, DEEP);
gpio_configure_pad(&cfg);
}
void gpio_output(gpio_t gpio, int value)
{
struct pad_config cfg = PAD_CFG_GPO(gpio, value, DEEP);
gpio_configure_pad(&cfg);
}
int gpio_get(gpio_t gpio_num)
{
uint32_t *dw_regs;
uint32_t reg;
dw_regs = gpio_dw_regs(gpio_num);
if (dw_regs == NULL)
return -1;
reg = read32(&dw_regs[0]);
return (reg >> GPIORXSTATE_SHIFT) & GPIORXSTATE_MASK;
}
void gpio_set(gpio_t gpio_num, int value)
{
uint32_t *dw_regs;
uint32_t reg;
dw_regs = gpio_dw_regs(gpio_num);
if (dw_regs == NULL)
return;
reg = read32(&dw_regs[0]);
reg &= ~PAD_FIELD(GPIOTXSTATE, MASK);
reg |= PAD_FIELD_VAL(GPIOTXSTATE, value);
write32(&dw_regs[0], reg);
/* GPIO port ids support posted write semantics. */
}
const char *gpio_acpi_path(gpio_t gpio_num)
{
return "\\_SB.PCI0.GPIO";
}
uint16_t gpio_acpi_pin(gpio_t gpio_num)
{
return gpio_num;
}