blob: e8b34c7a3370c5485611f555df71a3e219689af7 [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2016, Linaro Limited
*/
#include <assert.h>
#include <drivers/pl061_gpio.h>
#include <io.h>
#include <keep.h>
#include <trace.h>
#include <util.h>
#ifndef PLAT_PL061_MAX_GPIOS
# define PLAT_PL061_MAX_GPIOS 32
#endif /* PLAT_PL061_MAX_GPIOS */
#define MAX_GPIO_DEVICES ((PLAT_PL061_MAX_GPIOS + \
(GPIOS_PER_PL061 - 1)) / GPIOS_PER_PL061)
#define GPIOS_PER_PL061 8
/* gpio register offsets */
#define GPIODIR 0x400
#define GPIOIS 0x404
#define GPIOIBE 0x408
#define GPIOIEV 0x40C
#define GPIOIE 0x410
#define GPIORIS 0x414
#define GPIOMIS 0x418
#define GPIOIC 0x41C
#define GPIOAFSEL 0x420
/* gpio register masks */
#define GPIOIE_ENABLED SHIFT_U32(1, 0)
#define GPIOIE_MASKED SHIFT_U32(0, 0)
#define GPIOAFSEL_HW SHIFT_U32(1, 0)
#define GPIOAFSEL_SW SHIFT_U32(0, 0)
#define GPIODIR_OUT SHIFT_U32(1, 0)
#define GPIODIR_IN SHIFT_U32(0, 0)
static vaddr_t pl061_reg_base[MAX_GPIO_DEVICES];
static enum gpio_dir pl061_get_direction(unsigned int gpio_pin)
{
vaddr_t base_addr;
uint8_t data;
unsigned int offset;
assert(gpio_pin < PLAT_PL061_MAX_GPIOS);
base_addr = pl061_reg_base[gpio_pin / GPIOS_PER_PL061];
offset = gpio_pin % GPIOS_PER_PL061;
data = io_read8(base_addr + GPIODIR);
if (data & BIT(offset))
return GPIO_DIR_OUT;
return GPIO_DIR_IN;
}
static void pl061_set_direction(unsigned int gpio_pin, enum gpio_dir direction)
{
vaddr_t base_addr;
unsigned int offset;
assert(gpio_pin < PLAT_PL061_MAX_GPIOS);
base_addr = pl061_reg_base[gpio_pin / GPIOS_PER_PL061];
offset = gpio_pin % GPIOS_PER_PL061;
if (direction == GPIO_DIR_OUT)
io_setbits8(base_addr + GPIODIR, BIT(offset));
else
io_clrbits8(base_addr + GPIODIR, BIT(offset));
}
/*
* The offset of GPIODATA register is 0.
* The values read from GPIODATA are determined for each bit, by the mask bit
* derived from the address used to access the data register, PADDR[9:2].
* Bits that are 1 in the address mask cause the corresponding bits in GPIODATA
* to be read, and bits that are 0 in the address mask cause the corresponding
* bits in GPIODATA to be read as 0, regardless of their value.
*/
static enum gpio_level pl061_get_value(unsigned int gpio_pin)
{
vaddr_t base_addr;
unsigned int offset;
assert(gpio_pin < PLAT_PL061_MAX_GPIOS);
base_addr = pl061_reg_base[gpio_pin / GPIOS_PER_PL061];
offset = gpio_pin % GPIOS_PER_PL061;
if (io_read8(base_addr + BIT(offset + 2)))
return GPIO_LEVEL_HIGH;
return GPIO_LEVEL_LOW;
}
/*
* In order to write GPIODATA, the corresponding bits in the mask, resulting
* from the address bus, PADDR[9:2], must be HIGH. Otherwise the bit values
* remain unchanged by the write.
*/
static void pl061_set_value(unsigned int gpio_pin, enum gpio_level value)
{
vaddr_t base_addr;
unsigned int offset;
assert(gpio_pin < PLAT_PL061_MAX_GPIOS);
base_addr = pl061_reg_base[gpio_pin / GPIOS_PER_PL061];
offset = gpio_pin % GPIOS_PER_PL061;
if (value == GPIO_LEVEL_HIGH)
io_write8(base_addr + BIT(offset + 2), BIT(offset));
else
io_write8(base_addr + BIT(offset + 2), 0);
}
static enum gpio_interrupt pl061_get_interrupt(unsigned int gpio_pin)
{
vaddr_t base_addr;
uint8_t data;
unsigned int offset;
assert(gpio_pin < PLAT_PL061_MAX_GPIOS);
base_addr = pl061_reg_base[gpio_pin / GPIOS_PER_PL061];
offset = gpio_pin % GPIOS_PER_PL061;
data = io_read8(base_addr + GPIOIE);
if (data & BIT(offset))
return GPIO_INTERRUPT_ENABLE;
return GPIO_INTERRUPT_DISABLE;
}
static void pl061_set_interrupt(unsigned int gpio_pin,
enum gpio_interrupt ena_dis)
{
vaddr_t base_addr;
unsigned int offset;
assert(gpio_pin < PLAT_PL061_MAX_GPIOS);
base_addr = pl061_reg_base[gpio_pin / GPIOS_PER_PL061];
offset = gpio_pin % GPIOS_PER_PL061;
if (ena_dis == GPIO_INTERRUPT_ENABLE)
io_setbits8(base_addr + GPIOIE, BIT(offset));
else
io_clrbits8(base_addr + GPIOIE, BIT(offset));
}
/*
* Register the PL061 GPIO controller with a base address and the offset
* of start pin in this GPIO controller.
* This function is called after pl061_init().
*/
void pl061_register(vaddr_t base_addr, unsigned int gpio_dev)
{
assert(gpio_dev < MAX_GPIO_DEVICES);
pl061_reg_base[gpio_dev] = base_addr;
}
static const struct gpio_ops pl061_ops = {
.get_direction = pl061_get_direction,
.set_direction = pl061_set_direction,
.get_value = pl061_get_value,
.set_value = pl061_set_value,
.get_interrupt = pl061_get_interrupt,
.set_interrupt = pl061_set_interrupt,
};
KEEP_PAGER(pl061_ops);
/*
* Initialize PL061 GPIO controller
*/
void pl061_init(struct pl061_data *pd)
{
COMPILE_TIME_ASSERT(PLAT_PL061_MAX_GPIOS > 0);
assert(pd);
pd->chip.ops = &pl061_ops;
}
enum pl061_mode_control pl061_get_mode_control(unsigned int gpio_pin)
{
vaddr_t base_addr;
uint8_t data;
unsigned int offset;
assert(gpio_pin < PLAT_PL061_MAX_GPIOS);
base_addr = pl061_reg_base[gpio_pin / GPIOS_PER_PL061];
offset = gpio_pin % GPIOS_PER_PL061;
data = io_read8(base_addr + GPIOAFSEL);
if (data & BIT(offset))
return PL061_MC_HW;
return PL061_MC_SW;
}
void pl061_set_mode_control(unsigned int gpio_pin,
enum pl061_mode_control hw_sw)
{
vaddr_t base_addr;
unsigned int offset;
assert(gpio_pin < PLAT_PL061_MAX_GPIOS);
base_addr = pl061_reg_base[gpio_pin / GPIOS_PER_PL061];
offset = gpio_pin % GPIOS_PER_PL061;
if (hw_sw == PL061_MC_HW)
io_setbits8(base_addr + GPIOAFSEL, BIT(offset));
else
io_clrbits8(base_addr + GPIOAFSEL, BIT(offset));
}