// Raspberry Pi DMA utility definitions; see https://iosoft.blog for details
//
// Copyright (c) 2020 Jeremy P Bentham
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include <stdint.h>

// Location of peripheral registers in physical memory
#define PHYS_REG_BASE PI_23_REG_BASE
#define PI_01_REG_BASE 0x20000000 // Pi Zero or 1
#define PI_23_REG_BASE 0x3F000000 // Pi 2 or 3
#define PI_4_REG_BASE 0xFE000000  // Pi 4

#define CLOCK_HZ 250000000 // Pi 2 - 4
//#define CLOCK_HZ        400000000   // Pi Zero

// Location of peripheral registers in bus memory
#define BUS_REG_BASE 0x7E000000

// If non-zero, print debug information
#define DEBUG 0

// If non-zero, set PWM clock using VideoCore mailbox
#define USE_VC_CLOCK_SET 0

// Size of memory page
#define PAGE_SIZE 0x1000
// Round up to nearest page
#define PAGE_ROUNDUP(n) ((n) % PAGE_SIZE == 0 ? (n) : ((n) + PAGE_SIZE) & ~(PAGE_SIZE - 1))

// Structure for mapped peripheral or memory
typedef struct {
  int fd,    // File descriptor
      h,     // Memory handle
      size;  // Memory size
  void *bus, // Bus address
      *virt, // Virtual address
      *phys; // Physical address
} MEM_MAP;

// Get virtual 8 and 32-bit pointers to register
#define REG8(m, x) ((volatile uint8_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
#define REG32(m, x) ((volatile uint32_t *)((uint32_t)(m.virt) + (uint32_t)(x)))
// Get bus address of register
#define REG_BUS_ADDR(m, x) ((uint32_t)(m.bus) + (uint32_t)(x))
// Convert uncached memory virtual address to bus address
#define MEM_BUS_ADDR(mp, a) ((uint32_t)a - (uint32_t)mp->virt + (uint32_t)mp->bus)
// Convert bus address to physical address (for mmap)
#define BUS_PHYS_ADDR(a) ((void *)((uint32_t)(a) & ~0xC0000000))

// 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
// GPIO I/O definitions
#define GPIO_IN 0
#define GPIO_OUT 1
#define GPIO_ALT0 4
#define GPIO_ALT1 5
#define GPIO_ALT2 6
#define GPIO_ALT3 7
#define GPIO_ALT4 3
#define GPIO_ALT5 2
#define GPIO_MODE_STRS "IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3"
#define GPIO_NOPULL 0
#define GPIO_PULLDN 1
#define GPIO_PULLUP 2

// Videocore mailbox memory allocation flags, see:
//     https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
typedef enum {
  MEM_FLAG_DISCARDABLE = 1 << 0,    // can be resized to 0 at any time. Use for cached data
  MEM_FLAG_NORMAL = 0 << 2,         // normal allocating alias. Don't use from ARM
  MEM_FLAG_DIRECT = 1 << 2,         // 0xC alias uncached
  MEM_FLAG_COHERENT = 2 << 2,       // 0x8 alias. Non-allocating in L2 but coherent
  MEM_FLAG_ZERO = 1 << 4,           // initialise buffer to all zeros
  MEM_FLAG_NO_INIT = 1 << 5,        // don't initialise (default is initialise to all ones)
  MEM_FLAG_HINT_PERMALOCK = 1 << 6, // Likely to be locked for long periods of time
  MEM_FLAG_L1_NONALLOCATING = (MEM_FLAG_DIRECT | MEM_FLAG_COHERENT) // Allocating in L2
} VC_ALLOC_FLAGS;
// VC flags for unchached DMA memory
#define DMA_MEM_FLAGS (MEM_FLAG_DIRECT | MEM_FLAG_ZERO)

// Mailbox command/response structure
typedef struct {
  uint32_t len,           // Overall length (bytes)
      req,                // Zero for request, 1<<31 for response
      tag,                // Command number
      blen,               // Buffer length (bytes)
      dlen;               // Data length (bytes)
  uint32_t uints[32 - 5]; // Data (108 bytes maximum)
} VC_MSG __attribute__((aligned(16)));

// DMA channels and data requests
#define DMA_CHAN_A 10
#define DMA_CHAN_B 11
#define DMA_PWM_DREQ 5
#define DMA_SPI_TX_DREQ 6
#define DMA_SPI_RX_DREQ 7
#define DMA_BASE (PHYS_REG_BASE + 0x007000)
// DMA register addresses offset by 0x100 * chan_num
#define DMA_CS 0x00
#define DMA_CONBLK_AD 0x04
#define DMA_TI 0x08
#define DMA_SRCE_AD 0x0c
#define DMA_DEST_AD 0x10
#define DMA_TXFR_LEN 0x14
#define DMA_STRIDE 0x18
#define DMA_NEXTCONBK 0x1c
#define DMA_DEBUG 0x20
#define DMA_REG(ch, r) ((r) == DMA_ENABLE ? DMA_ENABLE : (ch)*0x100 + (r))
#define DMA_ENABLE 0xff0
// DMA register values
#define DMA_WAIT_RESP (1 << 3)
#define DMA_CB_DEST_INC (1 << 4)
#define DMA_DEST_DREQ (1 << 6)
#define DMA_CB_SRCE_INC (1 << 8)
#define DMA_SRCE_DREQ (1 << 10)
#define DMA_PRIORITY(n) ((n) << 16)

// DMA control block (must be 32-byte aligned)
typedef struct {
  uint32_t ti, // Transfer info
      srce_ad, // Source address
      dest_ad, // Destination address
      tfr_len, // Transfer length
      stride,  // Transfer stride
      next_cb, // Next control block
      debug,   // Debug register, zero in control block
      unused;
} DMA_CB __attribute__((aligned(32)));

// PWM controller registers
#define PWM_BASE (PHYS_REG_BASE + 0x20C000)
#define PWM_CTL 0x00  // Control
#define PWM_STA 0x04  // Status
#define PWM_DMAC 0x08 // DMA control
#define PWM_RNG1 0x10 // Channel 1 range
#define PWM_DAT1 0x14 // Channel 1 data
#define PWM_FIF1 0x18 // Channel 1 fifo
#define PWM_RNG2 0x20 // Channel 2 range
#define PWM_DAT2 0x24 // Channel 2 data
// PWM register values
#define PWM_CTL_RPTL1 (1 << 2)  // Chan 1: repeat last data when FIFO empty
#define PWM_CTL_USEF1 (1 << 5)  // Chan 1: use FIFO
#define PWM_DMAC_ENAB (1 << 31) // Start PWM DMA
#define PWM_ENAB 1              // Enable PWM
#define PWM_PIN 12              // GPIO pin for PWM output, 12 or 18

// Clock registers and values
#define CLK_BASE (PHYS_REG_BASE + 0x101000)
#define CLK_PWM_CTL 0xa0
#define CLK_PWM_DIV 0xa4
#define CLK_PASSWD 0x5a000000
#define PWM_CLOCK_ID 0xa

void fail(char *s);
void *map_periph(MEM_MAP *mp, void *phys, int size);
void *map_uncached_mem(MEM_MAP *mp, int size);
void unmap_periph_mem(MEM_MAP *mp);
void gpio_set(int pin, int mode, int pull);
void gpio_pull(int pin, int pull);
void gpio_mode(int pin, int mode);
void gpio_out(int pin, int val);
uint8_t gpio_in(int pin);
void disp_mode_vals(uint32_t mode);
int open_mbox(void);
void close_mbox(int fd);
uint32_t msg_mbox(int fd, VC_MSG *msgp);
void *map_segment(void *addr, int size);
void unmap_segment(void *addr, int size);
uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags);
void *lock_vc_mem(int fd, int h);
uint32_t unlock_vc_mem(int fd, int h);
uint32_t free_vc_mem(int fd, int h);
uint32_t set_vc_clock(int fd, int id, uint32_t freq);
void disp_vc_msg(VC_MSG *msgp);
void enable_dma(int chan);
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval);
uint32_t dma_transfer_len(int chan);
uint32_t dma_active(int chan);
void stop_dma(int chan);
void disp_dma(int chan);
void init_pwm(int freq, int range, int val);
void start_pwm(void);
void stop_pwm(void);

// EOF
