#include "rpi_gpio.h"

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

#include "../common.h"

// GPIO register definitions
#define GPIO_BASE (PHYS_REG_BASE + 0x200000)
#define GPIO_MODE0 0x00
#define GPIO_SET0 0x1c
#define GPIO_CLR0 0x28
#define GPIO_LEV0 0x34
#define GPIO_GPPUD 0x94
#define GPIO_GPPUDCLK0 0x98

#define GPIO_MODE_STRS "IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3"

bool isInitialized = false;

// Virtual memory pointers to acceess GPIO, DMA and PWM from user space
MEM_MAP gpio_regs;

char *gpio_mode_strs[] = {GPIO_MODE_STRS};

// definitions

void gpio_setup() {
  if (!isInitialized) {
    map_periph(&gpio_regs, (void *)GPIO_BASE, PAGE_SIZE);
    isInitialized = true;
  }
}

void gpio_close() {
  if (isInitialized) {
    unmap_periph_mem(&gpio_regs);
    isInitialized = true;
  }
}

// Set input or output with pullups
void gpio_set(int pin, int mode, int pull) {
  gpio_mode(pin, mode);
  gpio_pull(pin, pull);
}
// Set I/P pullup or pulldown
void gpio_pull(int pin, int pull) {
  volatile uint32_t *reg = REG32(gpio_regs, GPIO_GPPUDCLK0) + pin / 32;

  *REG32(gpio_regs, GPIO_GPPUD) = pull;
  usleep(2);
  *reg = pin << (pin % 32);
  usleep(2);
  *REG32(gpio_regs, GPIO_GPPUD) = 0;
  *reg = 0;
}

// Set input or output
void gpio_mode(int pin, int mode) {
  if (gpio_regs.virt) {
    volatile uint32_t *reg = REG32(gpio_regs, GPIO_MODE0) + pin / 10, shift = (pin % 10) * 3;
    *reg = (*reg & ~(7 << shift)) | (mode << shift);
  }
}

// Set an O/P pin
void gpio_out(int pin, int val) {
  volatile uint32_t *reg = REG32(gpio_regs, val ? GPIO_SET0 : GPIO_CLR0) + pin / 32;
  *reg = 1 << (pin % 32);
}

// Get an I/P pin value
uint8_t gpio_in(int pin) {
  volatile uint32_t *reg = REG32(gpio_regs, GPIO_LEV0) + pin / 32;
  return (((*reg) >> (pin % 32)) & 1);
}

// Display the values in a GPIO mode register
void disp_mode_vals(uint32_t mode) {
  int i;

  for (i = 0; i < 10; i++)
    printf("%u:%-4s ", i, gpio_mode_strs[(mode >> (i * 3)) & 7]);
  printf("\n");
}