[zynq] Add support for GPIO interrupts

This commit is contained in:
Christopher Anderson
2015-04-23 12:04:44 -07:00
parent fdecb22f2e
commit 4a038ceef6
5 changed files with 161 additions and 9 deletions

View File

@@ -28,15 +28,92 @@
#include <string.h>
#include <dev/gpio.h>
#include <platform/gpio.h>
#include <platform/interrupts.h>
#include <target/gpioconfig.h>
#define MAX_GPIO 128
struct {
int_handler callback;
void *args;
} irq_callbacks[MAX_GPIO];
static enum handler_return gpio_int_handler(void *arg) {
/* The mask register uses 1 to respresent masked, 0 for unmasked. Comparing that
* register with the interrupt status register is the only way to determine
* which gpio triggered the interrupt in the gic. */
for (uint32_t bank = 0; bank < 4; bank++) {
uint32_t mask = *REG32(GPIO_INT_MASK(bank));
uint32_t stat = *REG32(GPIO_INT_STAT(bank));
uint32_t active = ~mask & stat;
if (active == 0) {
continue;
}
//printf("mask 0x%08x stat 0x%08x active 0x%08x\n", mask, stat, active);
while (active) {
/* Find the rightmost set bit, calculate the associated gpio, and call the callback */
uint16_t bit = 32 - clz(active) - 1;
uint16_t gpio = bit + (bank * 32);
active ^= (1 << bit);
if (irq_callbacks[gpio].callback) {
irq_callbacks[gpio].callback(irq_callbacks[gpio].args);
}
//printf("bit %u bank %u gpio %u was triggered\n", bit, bank, gpio);
}
*REG32(GPIO_INT_STAT(bank)) = stat;
}
return 0;
}
void zynq_unmask_gpio_interrupt(unsigned gpio)
{
uint16_t bank = gpio / 31;
uint16_t bit = gpio % 32;
RMWREG32(GPIO_INT_EN(bank), bit, 1, 1);
RMWREG32(GPIO_INT_STAT(bank), bit, 1, 1);
}
void zynq_mask_gpio_interrupt(unsigned gpio)
{
uint16_t bank = gpio / 31;
uint16_t bit = gpio % 32;
RMWREG32(GPIO_INT_DIS(bank), bit, 1, 1);
}
void zynq_gpio_init(void)
{
register_int_handler(GPIO_INT, gpio_int_handler, NULL);
unmask_interrupt(GPIO_INT);
}
void zynq_gpio_early_init(void)
{
}
void zynq_gpio_init(void)
void register_gpio_int_handler(unsigned gpio, int_handler handler, void *args)
{
DEBUG_ASSERT(gpio < MAX_GPIO);
DEBUG_ASSERT(handler);
irq_callbacks[gpio].callback = handler;
irq_callbacks[gpio].args = args;
}
void unregister_gpio_int_handler(unsigned gpio)
{
DEBUG_ASSERT(gpio < MAX_GPIO);
irq_callbacks[gpio].callback = NULL;
irq_callbacks[gpio].args = NULL;
}
int gpio_config(unsigned gpio, unsigned flags)
@@ -57,10 +134,40 @@ int gpio_config(unsigned gpio, unsigned flags)
}
if (flags & GPIO_OUTPUT || flags & GPIO_INPUT) {
if (flags & GPIO_OUTPUT && flags & GPIO_INPUT) {
printf("Cannot configure a gpio as both an input and output direction.\n");
return -1;
}
RMWREG32(GPIO_DIRM(bank), bit, 1, ((flags & GPIO_OUTPUT) > 0));
RMWREG32(GPIO_OEN(bank), bit, 1, ((flags & GPIO_OUTPUT) > 0));
}
if (flags & GPIO_EDGE || flags & GPIO_LEVEL) {
if (flags & GPIO_EDGE && flags & GPIO_LEVEL) {
printf("Cannot configure a gpio as both edge and level sensitive.\n");
return -1;
}
RMWREG32(GPIO_INT_TYPE(bank), bit, 1, ((flags & GPIO_EDGE) > 0));
}
if (flags & GPIO_RISING || flags & GPIO_FALLING) {
/* Zynq has a specific INT_ANY register for handling interrupts that trigger on both
* rising and falling edges, but it specifically must only be used in edge mode */
if (flags & GPIO_RISING && flags & GPIO_FALLING) {
if ((flags & GPIO_EDGE) == 0) {
printf("polarity must be rising or falling if level sensitivity is used.\n");
return -1;
}
RMWREG32(GPIO_INT_ANY(bank), bit, 1, 1);
} else {
RMWREG32(GPIO_INT_POLARITY(bank), bit, 1, ((flags & GPIO_RISING) > 0));
RMWREG32(GPIO_INT_ANY(bank), bit, 1, 0);
}
}
return 0;
}
@@ -83,3 +190,24 @@ int gpio_get(unsigned gpio)
return ((*REG32(GPIO_DATA_RO(bank)) & (1 << bit)) > 0);
}
#include <lib/console.h>
#ifdef WITH_LIB_CONSOLE
static int cmd_zynq_gpio(int argc, const cmd_args *argv)
{
for (unsigned int bank = 0; bank < 4; bank++) {
printf("DIRM_%u (0x%08x): 0x%08x\n", bank, GPIO_DIRM(bank), *REG32(GPIO_DIRM(bank)));
printf("OEN_%u (0x%08x): 0x%08x\n", bank, GPIO_OEN(bank), *REG32(GPIO_OEN(bank)));
printf("INT_MASK_%u (0x%08x): 0x%08x\n", bank, GPIO_INT_MASK(bank), *REG32(GPIO_INT_MASK(bank)));
printf("INT_STAT_%u (0x%08x): 0x%08x\n", bank, GPIO_INT_STAT(bank), *REG32(GPIO_INT_STAT(bank)));
printf("INT_TYPE_%u (0x%08x): 0x%08x\n", bank, GPIO_INT_TYPE(bank), *REG32(GPIO_INT_TYPE(bank)));
printf("INT_POLARITY_%u (0x%08x): 0x%08x\n", bank, GPIO_INT_POLARITY(bank), *REG32(GPIO_INT_POLARITY(bank)));
printf("INT_ANY_%u (0x%08x): 0x%08x\n", bank, GPIO_INT_ANY(bank), *REG32(GPIO_INT_ANY(bank)));
}
return 0;
}
STATIC_COMMAND_START
{ "zynq_gpio", "Dump Zynq GPIO registers", &cmd_zynq_gpio },
STATIC_COMMAND_END(zynq_gpio);
#endif

View File

@@ -21,6 +21,7 @@
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <platform/interrupts.h>
#include <platform/zynq.h>
/* GPIO registers are not indexed in a particularly convenient manner, but can be calculated
@@ -46,3 +47,10 @@
#define GPIO_INT_TYPE(bank) (GPIO_REGS(bank) + 0x18)
#define GPIO_INT_POLARITY(bank) (GPIO_REGS(bank) + 0x1C)
#define GPIO_INT_ANY(bank) (GPIO_REGS(bank) + 0x20)
void zynq_unmask_gpio_interrupt(unsigned gpio);
void zynq_mask_gpio_interrupt(unsigned gpio);
void zynq_gpio_init(void);
void zynq_gpio_early_init(void);
void register_gpio_int_handler(unsigned gpio, int_handler handler, void *args);
void unregister_gpio_int_handler(unsigned gpio);

View File

@@ -305,13 +305,12 @@ void platform_init_mmu_mappings(void)
void platform_early_init(void)
{
/* Unlock the registers and leave them that way */
#if 0
ps7_init();
#else
/* Unlock the registers and leave them that way */
zynq_slcr_unlock();
zynq_mio_init();
zynq_gpio_init();
zynq_pll_init();
zynq_clk_init();
#if ZYNQ_SDRAM_INIT
@@ -332,6 +331,7 @@ void platform_early_init(void)
/* initialize the interrupt controller */
arm_gic_init();
zynq_gpio_init();
/* initialize the timer block */
arm_cortex_a9_timer_init(CPUPRIV_BASE, zynq_get_arm_timer_freq());

View File

@@ -24,4 +24,6 @@
/* gpios on the zybo target */
#define GPIO_LEDY (7)
#define ZYBO_BTN4 (50)
#define ZYBO_BTN5 (51)

View File

@@ -28,6 +28,8 @@
#include <kernel/vm.h>
#include <platform/zynq.h>
#include <platform/gem.h>
#include <platform/gpio.h>
#include <platform/interrupts.h>
#include <target/gpioconfig.h>
#define ZYNQ_PKTBUF_CNT 128
@@ -186,6 +188,21 @@ void target_early_init(void)
gpio_set(GPIO_LEDY, 0);
}
static enum handler_return toggle_ledy(void *arg) {
static bool on = false;
gpio_set(GPIO_LEDY, on);
on = !on;
return INT_NO_RESCHEDULE;
}
void target_set_debug_led(unsigned int led, bool on)
{
if (led == 0) {
gpio_set(GPIO_LEDY, on);
}
}
void target_init(void)
{
paddr_t buf_vaddr;
@@ -210,12 +227,9 @@ void target_init(void)
pktbuf_create_bufs((void *)buf_vaddr, ZYNQ_PKTBUF_CNT * sizeof(pktbuf_buf_t));
gem_init(GEM0_BASE);
register_gpio_int_handler(ZYBO_BTN5, toggle_ledy, NULL);
zynq_unmask_gpio_interrupt(ZYBO_BTN5);
}
void target_set_debug_led(unsigned int led, bool on)
{
if (led == 0) {
gpio_set(GPIO_LEDY, on);
}
}