blob: 63f947c612bc3998c394ebaaee33ac42d7e426a8 [file] [log] [blame]
/*
* Copyright (C) 2015 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc.
*/
#include <stdint.h>
#include "base/algorithm.h"
#include "base/cleanup.h"
#include "base/container_of.h"
#include "base/io.h"
#include "base/xalloc.h"
#include "drivers/gpio/gpio.h"
#include "drivers/gpio/skylake.h"
/* Skylake PCR access to GPIO registers. */
#define PCH_PCR_BASE_ADDRESS 0xfd000000
/* Port Id lives in bits 23:16 and register offset lives in 15:0 of address. */
#define PCH_PCR_PORTID_SHIFT 16
/* PCR PIDs for the GPIO communities. */
#define PCH_PCR_PID_GPIOCOM3 0xAC
#define PCH_PCR_PID_GPIOCOM2 0xAD
#define PCH_PCR_PID_GPIOCOM1 0xAE
#define PCH_PCR_PID_GPIOCOM0 0xAF
static uint8_t *pcr_port_regs(uint8_t pid)
{
uintptr_t reg_addr;
/* Create an address based off of port id and offset. */
reg_addr = PCH_PCR_BASE_ADDRESS;
reg_addr += ((uintptr_t)pid) << PCH_PCR_PORTID_SHIFT;
return (uint8_t *)reg_addr;
}
/* There are 4 communities with 8 GPIO groups (GPP_[A:G] and GPD) */
struct gpio_community {
int port_id;
/* Inclusive pads within the community. */
int min;
int max;
};
/* This is ordered to match ACPI and OS driver. */
static const struct gpio_community communities[] = {
{
.port_id = PCH_PCR_PID_GPIOCOM0,
.min = GPP_A0,
.max = GPP_B23,
},
{
.port_id = PCH_PCR_PID_GPIOCOM1,
.min = GPP_C0,
.max = GPP_E23,
},
{
.port_id = PCH_PCR_PID_GPIOCOM3,
.min = GPP_F0,
.max = GPP_G7,
},
{
.port_id = PCH_PCR_PID_GPIOCOM2,
.min = GPD0,
.max = GPD11,
},
};
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(int gpio)
{
if (gpio >= GPD0 && gpio <= GPD11)
return 1;
return 0;
}
static const struct gpio_community *gpio_get_community(int 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 void *gpio_dw_regs(int 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 * 8];
}
static void *gpio_hostsw_reg(int 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);
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 gpio_configure_pad(const struct pad_config *cfg)
{
uint32_t *dw_regs;
uint32_t reg;
uint32_t termination;
uint32_t dw0;
const uint32_t termination_mask = PAD_TERM_MASK << PAD_TERM_SHIFT;
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]);
reg &= ~termination_mask;
termination = cfg->attrs;
termination &= termination_mask;
reg |= termination;
write32(&dw_regs[1], reg);
gpio_handle_pad_mode(cfg);
}
static int __skylake_gpio_get(int 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;
}
static void __skylake_gpio_set(int 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. */
}
/*
* Depthcharge GPIO interface.
*/
static int skylake_gpio_configure(struct GpioCfg *gpio,
const struct pad_config *pad)
{
gpio_configure_pad(pad);
return 0;
}
static int skylake_gpio_get(struct GpioOps *me)
{
GpioCfg *gpio = container_of(me, GpioCfg, ops);
return __skylake_gpio_get(gpio->gpio_num);
}
static int skylake_gpio_set(struct GpioOps *me, unsigned value)
{
GpioCfg *gpio = container_of(me, GpioCfg, ops);
__skylake_gpio_set(gpio->gpio_num, value);
return 0;
}
static int skylake_gpio_get_fast(struct GpioOps *me)
{
GpioCfg *gpio = container_of(me, GpioCfg, ops);
return (gpio->current_dw0 >> GPIORXSTATE_SHIFT) & GPIORXSTATE_MASK;
}
static int skylake_gpio_set_fast(struct GpioOps *me, unsigned value)
{
GpioCfg *gpio = container_of(me, GpioCfg, ops);
gpio->current_dw0 &= ~PAD_FIELD(GPIOTXSTATE, MASK);
gpio->current_dw0 |= PAD_FIELD_VAL(GPIOTXSTATE, value);
write32(&gpio->dw_regs[0], gpio->current_dw0);
return 0;
}
GpioCfg *new_skylake_gpio(int gpio_num)
{
GpioCfg *gpio = xzalloc(sizeof(GpioCfg));
gpio->ops.get = &skylake_gpio_get;
gpio->ops.set = &skylake_gpio_set;
gpio->configure = &skylake_gpio_configure;
gpio->gpio_num = gpio_num;
gpio->dw_regs = gpio_dw_regs(gpio_num);
return gpio;
}
static int skylake_gpio_cleanup(DcEvent *event)
{
GpioCfg *gpio = container_of(event, GpioCfg, cleanup.event);
write32(&gpio->dw_regs[0], gpio->save_dw0);
write32(&gpio->dw_regs[1], gpio->save_dw1);
return 0;
}
static void register_skylake_gpio_cleanup(GpioCfg *gpio)
{
/* Save initial GPIO state */
gpio->save_dw0 = read32(&gpio->dw_regs[0]);
gpio->save_dw1 = read32(&gpio->dw_regs[1]);
/* Register callback to restore GPIO state on exit */
gpio->cleanup.event.trigger = &skylake_gpio_cleanup;
gpio->cleanup.types = CleanupOnHandoff | CleanupOnLegacy;
cleanup_add(&gpio->cleanup);
}
GpioCfg *new_skylake_gpio_input(int gpio_num)
{
GpioCfg *gpio = new_skylake_gpio(gpio_num);
/* Restore this GPIO to original state on exit */
register_skylake_gpio_cleanup(gpio);
/* Configure the GPIO as a standard input pin */
struct pad_config pad = PAD_CFG_GPI(gpio_num, NONE, DEEP);
gpio_configure_pad(&pad);
return gpio;
}
GpioCfg *new_skylake_gpio_output(int gpio_num, unsigned value)
{
GpioCfg *gpio = new_skylake_gpio(gpio_num);
/* Restore this GPIO to original state on exit */
register_skylake_gpio_cleanup(gpio);
/* Configure the GPIO as a standard output pin */
struct pad_config pad = PAD_CFG_GPO(gpio_num, value, DEEP);
gpio_configure_pad(&pad);
/* Store the current DW0 register state */
gpio->current_dw0 = read32(&gpio->dw_regs[0]);
/* Use the fast methods that cache the register contents */
gpio->ops.get = &skylake_gpio_get_fast;
gpio->ops.set = &skylake_gpio_set_fast;
return gpio;
}