blob: 2abd551534d0099031ae2b2ef2df0d6606c15db0 [file] [log] [blame]
/*
* Copyright 2012, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* gpio.c: GPIO functions for ICH chipsets
*/
#include <inttypes.h>
#include "mosys/alloc.h"
#include "mosys/platform.h"
#include "mosys/log.h"
#include "intf/io.h"
#include "intf/pci.h"
#include "drivers/gpio.h"
#include "drivers/intel/ich_generic.h"
/* For ports 0, 1, and 2 */
static uint8_t ich_use_sel_offsets[] = { 0x00, 0x30, 0x40 };
static uint8_t ich_io_sel_offsets[] = { 0x04, 0x34, 0x44 };
static uint8_t ich_lvl_offsets[] = { 0x0c, 0x38, 0x48 };
static uint8_t ich_gpio_inv_offset = 0x2c; /* for port 1 only */
struct {
uint16_t gpio_base;
uint8_t use_sel;
uint8_t io_sel;
uint8_t lvl;
} ich_gpio_offsets;
/* returns 1 if GPIO is valid, 0 otherwise */
static int ich_gpio_valid(enum ich_generation gen, struct gpio_map *gpio)
{
/* GP_LVL registers represent 32 pins each */
if (gpio->pin > 31) {
lprintf(LOG_DEBUG, "ICH GPIO: invalid pin %d\n", gpio->pin);
return 0;
}
switch(gen) {
case ICH7:
case ICH8:
case ICH9:
case ICH10: /* ICH10 corporate vs. consumer sku */
if (gpio->port > 1) {
lprintf(LOG_DEBUG, "ICH GPIO: invalid port %d\n",
gpio->port);
return 0;
}
break;
case ICH_6_SERIES:
case ICH_7_SERIES:
if (gpio->port > 2) {
lprintf(LOG_DEBUG, "ICH GPIO: invalid port %d\n",
gpio->port);
return 0;
}
break;
default:
lprintf(LOG_DEBUG, "ICH GPIO: invalid port %d\n",
gpio->port);
return 0;
}
return 1;
}
int ich_get_gpio_base(struct platform_intf *intf, uint16_t *val)
{
static uint16_t ich_gpio_base = 0;
if (ich_gpio_base) {
*val = ich_gpio_base;
return 0;
}
if (pci_read16(intf, 0, 31, 0, 0x48, &ich_gpio_base) < 0) {
lprintf(LOG_DEBUG, "ICH GPIO: unable to find base\n");
return -1;
}
/* lsb is enable bit */
ich_gpio_base &= ~1;
lprintf(LOG_DEBUG, "ICH GPIO: base is 0x%08x\n", ich_gpio_base);
*val = ich_gpio_base;
return 0;
}
/* select register set for given port */
static int ich_get_regs(struct platform_intf *intf,
enum ich_generation gen, struct gpio_map *gpio)
{
if (!ich_gpio_valid(gen, gpio))
return -1;
if (ich_get_gpio_base(intf, &ich_gpio_offsets.gpio_base) < 0)
return -1;
ich_gpio_offsets.use_sel = ich_use_sel_offsets[gpio->port];
ich_gpio_offsets.io_sel = ich_io_sel_offsets[gpio->port];
ich_gpio_offsets.lvl = ich_lvl_offsets[gpio->port];
return 0;
}
int ich_read_gpio(struct platform_intf *intf,
enum ich_generation gen, struct gpio_map *gpio)
{
uint32_t addr, val;
if (ich_get_regs(intf, gen, gpio) < 0)
return -1;
addr = ich_gpio_offsets.gpio_base + ich_gpio_offsets.lvl;
if (io_read32(intf, addr, &val) < 0)
return -1;
return ((val >> gpio->pin) & 1);
}
int ich_set_gpio(struct platform_intf *intf, enum ich_generation gen,
struct gpio_map *gpio, int state)
{
uint32_t addr, val;
if (gpio->type != GPIO_OUT) {
lprintf(LOG_DEBUG, "Must set \"%s\" to output mode to change "
"its value\n", gpio->name ? gpio->name : "Unknown");
return -1;
}
if (ich_get_regs(intf, gen, gpio) < 0)
return -1;
addr = ich_gpio_offsets.gpio_base + ich_gpio_offsets.use_sel;
if (io_read32(intf, addr, &val) < 0)
return -1;
if (!(val & (1 << gpio->pin))) {
lprintf(LOG_DEBUG, "Changing port %d, pin %d from native to "
"GPIO mode\n", gpio->port, gpio->pin);
val |= (1 << gpio->pin);
if (io_write32(intf, addr, val) < 0)
return -1;
}
addr = ich_gpio_offsets.gpio_base + ich_gpio_offsets.io_sel;
if (io_read32(intf, addr, &val) < 0)
return -1;
if (val & (1 << gpio->pin)) {
lprintf(LOG_DEBUG, "Changing port %d, pin %d from input to "
"output\n", gpio->port, gpio->pin);
val &= ~(1 << gpio->pin);
if (io_write32(intf, addr, val) < 0)
return -1;
}
addr = ich_gpio_offsets.gpio_base + ich_gpio_offsets.lvl;
if (io_read32(intf, addr, &val) < 0)
return -1;
switch (state) {
case 0:
if (!(val & (1 << gpio->pin))) {
lprintf(LOG_DEBUG, "GPIO '%s' already 0\n", gpio->name);
} else {
if (io_write32(intf, addr, val & ~(1 << gpio->pin)) < 0)
return -1;
}
break;
case 1:
if (val & (1 << gpio->pin)) {
lprintf(LOG_DEBUG, "GPIO '%s' already 1\n", gpio->name);
} else {
if (io_write32(intf, addr, val | (1 << gpio->pin)) < 0)
return -1;
}
break;
default:
lprintf(LOG_ERR, "Invaild state %d\n", state);
return -1;
}
return 0;
}
int ich_gpio_list(struct platform_intf *intf, enum ich_generation gen,
int port, int gpio_pins[], int num_gpios)
{
int i, state;
uint32_t addr, val;
for (i = 0; i < num_gpios; i++) {
struct gpio_map gpio;
char tmp[16];
memset(&gpio, 0, sizeof(gpio));
gpio.port = port; /* port in device */
gpio.pin = gpio_pins[i]; /* pin in port in device */
gpio.id = gpio.port + gpio.pin;
if (ich_get_regs(intf, gen, &gpio) < 0)
return -1;
/* skip if signal is "native function" instead of GPIO */
addr = ich_gpio_offsets.gpio_base + ich_gpio_offsets.use_sel;
if (io_read32(intf, addr, &val) < 0)
return -1;
if ((val & (1 << gpio.pin)) == 0)
continue;
addr = ich_gpio_offsets.gpio_base + ich_gpio_offsets.io_sel;
if (io_read32(intf, addr, &val) < 0)
return -1;
if ((val & (1 << gpio.pin)) == 0)
gpio.type = GPIO_OUT;
else
gpio.type = GPIO_IN;
/* gpio negation only applies to pins 15:0 in port 1 (GP_LVL2
set) and only if the pin is configured as an input */
if (port == 1 && gpio.pin < 16 && gpio.type == GPIO_IN) {
addr = ich_gpio_offsets.gpio_base + ich_gpio_inv_offset;
if (io_read32(intf, addr, &val) < 0)
return -1;
gpio.neg = (val >> gpio.pin) & 1;
} else {
gpio.neg = 0;
}
sprintf(tmp, "ICH");
gpio.devname = mosys_strdup(tmp);
sprintf(tmp, "GPIO%02d", gpio.id);
gpio.name = mosys_strdup(tmp);
state = ich_read_gpio(intf, gen, &gpio);
if (state < 0)
continue;
kv_pair_print_gpio(&gpio, state);
free((void *)gpio.devname);
free((void *)gpio.name);
}
return 0;
}