blob: 08bfb9d55fe804ef6c70ba4ae80f81871270800d [file] [log] [blame]
/*
* Copyright 2010-2016, NVIDIA Corporation
* Copyright 2019, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
* See "LICENSE_GPLv2.txt" for details.
*
* @TAG(DATA61_GPL)
*/
/*
* This GPIO driver is a port of U-Boot's tx2 driver which has been
* modified to fit into the interfaces that the platsupport GPIO interfaces
* expose.
*/
#include <stddef.h>
#include <stdbool.h>
#include <utils/arith.h>
#include <utils/attribute.h>
#include <platsupport/gpio.h>
#include <platsupportports/plat/gpio.h>
#define TX2_GPIO_PIN_STRIDE 0x20
#define TX2_GPIO_ENABLE_CONFIG_ENABLE BIT(0)
#define TX2_GPIO_ENABLE_CONFIG_OUT BIT(1)
#define TX2_GPIO_ENABLE_CONFIG_INTERRUPT_ENABLE BIT(6)
#define TX2_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_LEVEL BIT(2)
#define TX2_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE BIT(3)
#define TX2_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_DOUBLE_EDGE BIT(2) | BIT (3)
#define TX2_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL_HIGH BIT(4)
#define TX2_GPIO_ENABLE_CONFIG_TRIGGER_MASK BIT(2) | BIT(3) | BIT(4)
#define TX2_GPIO_OUTPUT_CONTROL_FLOATED BIT(0)
#define TX2_GPIO_OUTPUT_VALUE_HIGH BIT(0)
#define TX2_GPIO_INTERRUPT_CLEAR BIT(0)
enum gpio_reg_offset {
GPIO_ENABLE_CONFIG = 0x0,
GPIO_INPUT = 0x8,
GPIO_OUTPUT_CONTROL = 0xc,
GPIO_OUTPUT_VALUE = 0x10,
GPIO_INTERRUPT_CLEAR = 0x14,
GPIO_INTERRUPT_STATUS = 0x100
};
struct tx2_gpio_port {
uint32_t addr_offset;
enum gpio_pin start;
enum gpio_pin end;
};
/* This contains the offsets of the ports from the base of the controller */
static struct tx2_gpio_port tx2_ports[] = {
[GPIO_PORT_A] = { .addr_offset = 0x2000, .start = GPIO_PA0, .end = GPIO_PA6},
[GPIO_PORT_B] = { .addr_offset = 0x3000, .start = GPIO_PB0, .end = GPIO_PB6},
[GPIO_PORT_C] = { .addr_offset = 0x3200, .start = GPIO_PC0, .end = GPIO_PC6},
[GPIO_PORT_D] = { .addr_offset = 0x3400, .start = GPIO_PD0, .end = GPIO_PD5},
[GPIO_PORT_E] = { .addr_offset = 0x2200, .start = GPIO_PE0, .end = GPIO_PE7},
[GPIO_PORT_F] = { .addr_offset = 0x2400, .start = GPIO_PF0, .end = GPIO_PF5},
[GPIO_PORT_G] = { .addr_offset = 0x4200, .start = GPIO_PG0, .end = GPIO_PG5},
[GPIO_PORT_H] = { .addr_offset = 0x1000, .start = GPIO_PH0, .end = GPIO_PH6},
[GPIO_PORT_I] = { .addr_offset = 0x800, .start = GPIO_PI0, .end = GPIO_PI7},
[GPIO_PORT_J] = { .addr_offset = 0x5000, .start = GPIO_PJ0, .end = GPIO_PJ7},
[GPIO_PORT_K] = { .addr_offset = 0x5200, .start = GPIO_PK0, .end = GPIO_PK0},
[GPIO_PORT_L] = { .addr_offset = 0x1200, .start = GPIO_PL0, .end = GPIO_PL7},
[GPIO_PORT_M] = { .addr_offset = 0x5600, .start = GPIO_PM0, .end = GPIO_PM5},
[GPIO_PORT_N] = { .addr_offset = 0x0, .start = GPIO_PN0, .end = GPIO_PN6},
[GPIO_PORT_O] = { .addr_offset = 0x200, .start = GPIO_PO0, .end = GPIO_PO3},
[GPIO_PORT_P] = { .addr_offset = 0x4000, .start = GPIO_PP0, .end = GPIO_PP6},
[GPIO_PORT_Q] = { .addr_offset = 0x400, .start = GPIO_PQ0, .end = GPIO_PQ5},
[GPIO_PORT_R] = { .addr_offset = 0xa00, .start = GPIO_PR0, .end = GPIO_PR5},
[GPIO_PORT_T] = { .addr_offset = 0x600, .start = GPIO_PT0, .end = GPIO_PT3},
[GPIO_PORT_X] = { .addr_offset = 0x1400, .start = GPIO_PX0, .end = GPIO_PX7},
[GPIO_PORT_Y] = { .addr_offset = 0x1600, .start = GPIO_PY0, .end = GPIO_PY6},
[GPIO_PORT_BB] = { .addr_offset = 0x2600, .start = GPIO_PBB0, .end = GPIO_PBB1},
[GPIO_PORT_CC] = { .addr_offset = 0x5400, .start = GPIO_PCC0, .end = GPIO_PCC3}
};
static inline enum gpio_port tx2_pin_to_port(gpio_id_t id)
{
enum gpio_port port = id / 8;
return port;
}
static bool tx2_valid_pin(gpio_id_t id)
{
if (id < 0 || id > MAX_GPIO_ID) {
return false;
}
enum gpio_port port = tx2_pin_to_port(id);
return tx2_ports[port].start <= id && id <= tx2_ports[port].end;
}
static uint32_t *tx2_gpio_get_register(gpio_sys_t *gpio_sys, enum gpio_reg_offset reg_offset, enum gpio_pin pin)
{
uintptr_t vaddr_base = (uintptr_t) gpio_sys->priv;
enum gpio_port port = tx2_pin_to_port(pin);
int pin_index = pin - tx2_ports[port].start;
return (uint32_t *)(vaddr_base + tx2_ports[port].addr_offset + reg_offset + pin_index * TX2_GPIO_PIN_STRIDE);
}
static int tx2_gpio_set_direction(gpio_sys_t *gpio_sys, gpio_id_t gpio, enum gpio_dir dir)
{
uint32_t config_val = 0;
uint32_t output_control_val = 0;
volatile uint32_t *config_reg = tx2_gpio_get_register(gpio_sys, GPIO_ENABLE_CONFIG, gpio);
volatile uint32_t *output_control_reg = tx2_gpio_get_register(gpio_sys, GPIO_OUTPUT_CONTROL, gpio);
config_val = *config_reg;
output_control_val = *output_control_reg;
switch (dir) {
case GPIO_DIR_OUT_DEFAULT_HIGH:
case GPIO_DIR_OUT_DEFAULT_LOW:
config_val |= TX2_GPIO_ENABLE_CONFIG_OUT;
output_control_val &= ~TX2_GPIO_OUTPUT_CONTROL_FLOATED;
break;
case GPIO_DIR_IN:
config_val &= ~TX2_GPIO_ENABLE_CONFIG_OUT;
output_control_val |= TX2_GPIO_OUTPUT_CONTROL_FLOATED;
default:
return -EINVAL;
}
*config_reg = config_val;
*output_control_reg = output_control_val;
return 0;
}
static int tx2_gpio_set_interrupt_type(gpio_sys_t *gpio_sys, enum gpio_pin gpio, enum gpio_dir dir)
{
uint32_t val, lvl_type = 0;
volatile uint32_t *reg_vaddr = tx2_gpio_get_register(gpio_sys, GPIO_ENABLE_CONFIG, gpio);
val = *reg_vaddr;
switch (dir) {
case GPIO_DIR_IRQ_RISE:
lvl_type = TX2_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL_HIGH;
case GPIO_DIR_IRQ_FALL:
lvl_type |= TX2_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE;
break;
case GPIO_DIR_IRQ_EDGE:
lvl_type = TX2_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_DOUBLE_EDGE;
break;
case GPIO_DIR_IRQ_HIGH:
lvl_type = TX2_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL_HIGH;
case GPIO_DIR_IRQ_LOW:
lvl_type = TX2_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_LEVEL;
break;
default:
return -EINVAL;
}
val &= ~(TX2_GPIO_ENABLE_CONFIG_TRIGGER_MASK);
val |= lvl_type;
*reg_vaddr = val;
return 0;
}
static int tx2_gpio_irq_enable_disable(gpio_t *gpio, bool enable)
{
uint32_t val = 0;
volatile uint32_t *reg_vaddr = tx2_gpio_get_register(gpio->gpio_sys, GPIO_ENABLE_CONFIG, gpio->id);
val = *reg_vaddr;
if (enable) {
val |= TX2_GPIO_ENABLE_CONFIG_INTERRUPT_ENABLE;
} else {
val &= ~(TX2_GPIO_ENABLE_CONFIG_INTERRUPT_ENABLE);
}
*reg_vaddr = val;
return 0;
}
static int tx2_gpio_init(gpio_sys_t *gpio_sys, gpio_id_t id, enum gpio_dir dir, gpio_t *gpio)
{
int error = 0;
if (!tx2_valid_pin(id)) {
return EINVAL;
}
gpio->id = id;
gpio->gpio_sys = gpio_sys;
/* Set the gpio enable bit */
uint32_t val = 0;
volatile uint32_t *reg_vaddr = tx2_gpio_get_register(gpio_sys, GPIO_ENABLE_CONFIG, id);
val = *reg_vaddr;
val |= TX2_GPIO_ENABLE_CONFIG_ENABLE;
*reg_vaddr = val;
if (dir == GPIO_DIR_IN
|| dir == GPIO_DIR_OUT_DEFAULT_HIGH || dir == GPIO_DIR_OUT_DEFAULT_LOW) {
error = tx2_gpio_set_direction(gpio_sys, id, dir);
if (error) {
return error;
}
} else {
error = tx2_gpio_set_interrupt_type(gpio_sys, id, dir);
if (error) {
return error;
}
/* Leave the interrupt disabled and let the user enable it when they want */
error = tx2_gpio_irq_enable_disable(gpio, false);
if (error) {
return error;
}
}
return 0;
}
static int tx2_gpio_read_level(gpio_t *gpio)
{
if (!tx2_valid_pin(gpio->id)) {
return -EINVAL;
}
/* Maybe check if the pin is configured for output? */
uint32_t val = 0;
volatile uint32_t *reg_vaddr = tx2_gpio_get_register(gpio->gpio_sys, GPIO_INPUT, gpio->id);
val = *reg_vaddr;
return (!!val) ? GPIO_LEVEL_HIGH : GPIO_LEVEL_LOW;
}
static int tx2_gpio_set_level(gpio_t *gpio, enum gpio_level level)
{
if (!tx2_valid_pin(gpio->id)) {
return -EINVAL;
}
uint32_t val = 0;
volatile uint32_t *reg_vaddr = tx2_gpio_get_register(gpio->gpio_sys, GPIO_OUTPUT_VALUE, gpio->id);
val = *reg_vaddr;
if (GPIO_LEVEL_HIGH) {
val |= TX2_GPIO_OUTPUT_VALUE_HIGH;
} else {
val &= ~(TX2_GPIO_OUTPUT_VALUE_HIGH);
}
*reg_vaddr = val;
return 0;
}
static void tx2_gpio_int_clear(gpio_sys_t *gpio_sys, enum gpio_pin gpio)
{
volatile uint32_t *reg_vaddr = tx2_gpio_get_register(gpio_sys, GPIO_INTERRUPT_CLEAR, gpio);
*reg_vaddr |= TX2_GPIO_INTERRUPT_CLEAR;
}
static bool tx2_gpio_check_pending(gpio_sys_t *gpio_sys, enum gpio_pin gpio)
{
uint32_t val = 0;
volatile uint32_t *reg_vaddr = tx2_gpio_get_register(gpio_sys, GPIO_INTERRUPT_STATUS, gpio);
val = *reg_vaddr;
return !!val;
}
static int tx2_gpio_pending_status(gpio_t *gpio, bool clear)
{
int pending = 0;
if (gpio == NULL) {
return -EINVAL;
}
if (gpio->gpio_sys == NULL) {
return -EINVAL;
}
pending = tx2_gpio_check_pending(gpio->gpio_sys, gpio->id);
if (clear) {
tx2_gpio_int_clear(gpio->gpio_sys, gpio->id);
}
return pending;
}
int gpio_sys_init(ps_io_ops_t *io_ops, gpio_sys_t *gpio_sys)
{
if (io_ops == NULL) {
return -EINVAL;
}
if (gpio_sys == NULL) {
return -EINVAL;
}
/* TODO Do we need to clear the whole block?
* Uboot sets up some registers for us and we may not want this.
* If you get some output/input problems, this is probably it. */
gpio_sys->priv = ps_io_map(&io_ops->io_mapper, (uintptr_t) TX2_GPIO_PADDR, TX2_GPIO_SIZE, 0, PS_MEM_NORMAL);
if (gpio_sys->priv == NULL) {
ZF_LOGE("Failed to map TX2 GPIO frame.");
return -1;
}
gpio_sys->init = &tx2_gpio_init;
gpio_sys->set_level = &tx2_gpio_set_level;
gpio_sys->read_level = &tx2_gpio_read_level;
gpio_sys->pending_status = &tx2_gpio_pending_status;
gpio_sys->irq_enable_disable = &tx2_gpio_irq_enable_disable;
return 0;
}