moving standalone mode to alsa rust direct binding crate & moving led_driver to a specific package
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2020 rxi
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the MIT license. See `log.c` for details.
|
||||
*/
|
||||
|
||||
#ifndef LOG_H
|
||||
#define LOG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
#define LOG_VERSION "0.1.0"
|
||||
|
||||
typedef struct {
|
||||
va_list ap;
|
||||
const char *fmt;
|
||||
const char *file;
|
||||
struct tm *time;
|
||||
void *udata;
|
||||
int line;
|
||||
int level;
|
||||
} log_Event;
|
||||
|
||||
typedef void (*log_LogFn)(log_Event *ev);
|
||||
typedef void (*log_LockFn)(bool lock, void *udata);
|
||||
|
||||
enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL };
|
||||
|
||||
#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
const char* log_level_string(int level);
|
||||
void log_set_lock(log_LockFn fn, void *udata);
|
||||
void log_set_level(int level);
|
||||
void log_set_quiet(bool enable);
|
||||
int log_add_callback(log_LogFn fn, void *udata, int level);
|
||||
int log_add_fp(FILE *fp, int level);
|
||||
|
||||
void log_log(int level, const char *file, int line, const char *fmt, ...);
|
||||
|
||||
#endif
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
# set the project name
|
||||
project(RpiLedBars VERSION 0.5 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
|
||||
add_subdirectory(libs)
|
||||
|
||||
# add the executable
|
||||
add_library(${PROJECT_NAME}_drivers
|
||||
common.c
|
||||
dma/rpi_dma.c
|
||||
dma/rpi_videocore.c
|
||||
gpio/rpi_gpio.c
|
||||
leddriver/rpi_leddriver.c
|
||||
smi/rpi_smi.c)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}_drivers PRIVATE wiringPi)
|
||||
target_link_libraries(${PROJECT_NAME}_drivers PRIVATE logc)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}_drivers PUBLIC dma gpio leddriver smi)
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
// Use mmap to obtain virtual address, given physical
|
||||
void *map_periph(MEM_MAP *mp, void *phys, int size) {
|
||||
mp->phys = phys;
|
||||
mp->size = PAGE_ROUNDUP(size);
|
||||
mp->bus = (void *)((uint32_t)phys - PHYS_REG_BASE + BUS_REG_BASE);
|
||||
mp->virt = map_segment(phys, mp->size);
|
||||
return (mp->virt);
|
||||
}
|
||||
|
||||
// Free mapped peripheral or memory
|
||||
void unmap_periph_mem(MEM_MAP *mp) {
|
||||
if (mp) {
|
||||
unmap_segment(mp->virt, mp->size);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- VIRTUAL MEMORY -----
|
||||
|
||||
// Get virtual memory segment for peripheral regs or physical mem
|
||||
void *map_segment(void *addr, int size) {
|
||||
int fd;
|
||||
void *mem;
|
||||
|
||||
size = PAGE_ROUNDUP(size);
|
||||
if ((fd = open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC)) < 0) {
|
||||
log_fatal("can't open /dev/mem, run using sudo");
|
||||
exit(-EXIT_FAILURE);
|
||||
}
|
||||
|
||||
mem = mmap(0, size, PROT_WRITE | PROT_READ, MAP_SHARED, fd, (uint32_t)addr);
|
||||
close(fd);
|
||||
log_info("Map %p -> %p", (void *)addr, mem);
|
||||
if (mem == MAP_FAILED) {
|
||||
log_fatal("can't map memory");
|
||||
exit(-EXIT_FAILURE);
|
||||
}
|
||||
return (mem);
|
||||
}
|
||||
|
||||
// Free mapped memory
|
||||
void unmap_segment(void *mem, int size) {
|
||||
if (mem) {
|
||||
munmap(mem, PAGE_ROUNDUP(size));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#if !defined(__COMMON_H__)
|
||||
#define __COMMON_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
|
||||
|
||||
// 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))
|
||||
|
||||
// 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;
|
||||
|
||||
// Use mmap to obtain virtual address, given physical
|
||||
void *map_periph(MEM_MAP *mp, void *phys, int size);
|
||||
|
||||
// Free mapped peripheral or memory
|
||||
void unmap_periph_mem(MEM_MAP *mp);
|
||||
|
||||
void *map_segment(void *addr, int size);
|
||||
void unmap_segment(void *addr, int size);
|
||||
|
||||
#endif // __COMMON_H__
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "rpi_dma.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "rpi_videocore.h"
|
||||
|
||||
// DMA channels and data requests
|
||||
#define DMA_SMI_DREQ 4
|
||||
#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)
|
||||
|
||||
// Virtual memory pointers to acceess GPIO, DMA and PWM from user space
|
||||
MEM_MAP dma_regs;
|
||||
|
||||
char *dma_regstrs[] = {"DMA CS", "CB_AD", "TI", "SRCE_AD", "DEST_AD",
|
||||
"TFR_LEN", "STRIDE", "NEXT_CB", "DEBUG", ""};
|
||||
|
||||
void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad) {
|
||||
map_periph(&dma_regs, (void *)DMA_BASE, PAGE_SIZE);
|
||||
|
||||
DMA_CB *cbs = mp->virt;
|
||||
|
||||
*txdata = (uint8_t *)(cbs + offset);
|
||||
enable_dma(chan);
|
||||
cbs[0].ti = DMA_DEST_DREQ | (DMA_SMI_DREQ << 16) | DMA_CB_SRCE_INC | DMA_WAIT_RESP;
|
||||
cbs[0].tfr_len = nsamp;
|
||||
cbs[0].srce_ad = MEM_BUS_ADDR(mp, *txdata);
|
||||
cbs[0].dest_ad = dest_ad;
|
||||
}
|
||||
|
||||
void dma_close() { unmap_periph_mem(&dma_regs); }
|
||||
|
||||
// Enable and reset DMA
|
||||
void enable_dma(int chan) {
|
||||
*REG32(dma_regs, DMA_ENABLE) |= (1 << chan);
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
|
||||
}
|
||||
|
||||
// Start DMA, given first control block
|
||||
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval) {
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CONBLK_AD)) = MEM_BUS_ADDR(mp, cbp);
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 2; // Clear 'end' flag
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_DEBUG)) = 7; // Clear error bits
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 | csval; // Start DMA
|
||||
}
|
||||
|
||||
// Return remaining transfer length
|
||||
uint32_t dma_transfer_len(int chan) { return (*REG32(dma_regs, DMA_REG(chan, DMA_TXFR_LEN))); }
|
||||
|
||||
// Check if DMA is active
|
||||
uint32_t dma_active(int chan) { return ((*REG32(dma_regs, DMA_REG(chan, DMA_CS))) & 1); }
|
||||
|
||||
// Halt current DMA operation by resetting controller
|
||||
void stop_dma(int chan) {
|
||||
if (dma_regs.virt)
|
||||
*REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
|
||||
}
|
||||
|
||||
// Display DMA registers
|
||||
void disp_dma(int chan) {
|
||||
volatile uint32_t *p = REG32(dma_regs, DMA_REG(chan, DMA_CS));
|
||||
int i = 0;
|
||||
|
||||
while (dma_regstrs[i][0]) {
|
||||
printf("%-7s %08X ", dma_regstrs[i++], *p++);
|
||||
if (i % 5 == 0 || dma_regstrs[i][0] == 0)
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#if !defined(__RPI_DMA_H__)
|
||||
#define __RPI_DMA_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#define DMA_CHAN_A 10
|
||||
#define DMA_CHAN_B 11
|
||||
|
||||
// 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)));
|
||||
|
||||
void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad);
|
||||
void dma_close();
|
||||
|
||||
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);
|
||||
|
||||
#endif // __RPI_DMA_H__
|
||||
@@ -0,0 +1,128 @@
|
||||
#include "rpi_videocore.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
// 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)));
|
||||
|
||||
void disp_vc_msg(VC_MSG *msgp);
|
||||
|
||||
int open_mbox(void);
|
||||
void close_mbox(int fd);
|
||||
uint32_t msg_mbox(int fd, VC_MSG *msgp);
|
||||
void *map_uncached_mem(MEM_MAP *mp, int size);
|
||||
|
||||
void videocore_setup(MEM_MAP *mp, int size) { map_uncached_mem(mp, size); }
|
||||
void videocore_close(MEM_MAP *mp) {
|
||||
unmap_periph_mem(mp);
|
||||
if (mp->fd) {
|
||||
unlock_vc_mem(mp->fd, mp->h);
|
||||
free_vc_mem(mp->fd, mp->h);
|
||||
close_mbox(mp->fd);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate uncached memory, get bus & phys addresses
|
||||
void *map_uncached_mem(MEM_MAP *mp, int size) {
|
||||
void *ret;
|
||||
mp->size = PAGE_ROUNDUP(size);
|
||||
mp->fd = open_mbox();
|
||||
ret = (mp->h = alloc_vc_mem(mp->fd, mp->size, DMA_MEM_FLAGS)) > 0 &&
|
||||
(mp->bus = lock_vc_mem(mp->fd, mp->h)) != 0 &&
|
||||
(mp->virt = map_segment(BUS_PHYS_ADDR(mp->bus), mp->size)) != 0
|
||||
? mp->virt
|
||||
: 0;
|
||||
log_info("VC mem handle %u, phys %p, virt %p", mp->h, mp->bus, mp->virt);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
// Allocate memory on PAGE_SIZE boundary, return handle
|
||||
uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags) {
|
||||
VC_MSG msg = {
|
||||
.tag = 0x3000c, .blen = 12, .dlen = 12, .uints = {PAGE_ROUNDUP(size), PAGE_SIZE, flags}};
|
||||
return (msg_mbox(fd, &msg));
|
||||
}
|
||||
// Lock allocated memory, return bus address
|
||||
void *lock_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000d, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? (void *)msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
// Unlock allocated memory
|
||||
uint32_t unlock_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000e, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
// Free memory
|
||||
uint32_t free_vc_mem(int fd, int h) {
|
||||
VC_MSG msg = {.tag = 0x3000f, .blen = 4, .dlen = 4, .uints = {h}};
|
||||
return (h ? msg_mbox(fd, &msg) : 0);
|
||||
}
|
||||
uint32_t set_vc_clock(int fd, int id, uint32_t freq) {
|
||||
VC_MSG msg1 = {.tag = 0x38001, .blen = 8, .dlen = 8, .uints = {id, 1}};
|
||||
VC_MSG msg2 = {.tag = 0x38002, .blen = 12, .dlen = 12, .uints = {id, freq, 0}};
|
||||
msg_mbox(fd, &msg1);
|
||||
disp_vc_msg(&msg1);
|
||||
msg_mbox(fd, &msg2);
|
||||
disp_vc_msg(&msg2);
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Display mailbox message
|
||||
void disp_vc_msg(VC_MSG *msgp) {
|
||||
int i;
|
||||
|
||||
printf("VC msg len=%X, req=%X, tag=%X, blen=%x, dlen=%x, data ", msgp->len, msgp->req, msgp->tag,
|
||||
msgp->blen, msgp->dlen);
|
||||
for (i = 0; i < msgp->blen / 4; i++)
|
||||
printf("%08X ", msgp->uints[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Open mailbox interface, return file descriptor
|
||||
int open_mbox(void) {
|
||||
int fd;
|
||||
|
||||
if ((fd = open("/dev/vcio", 0)) < 0)
|
||||
log_error("can't open VC mailbox");
|
||||
return (fd);
|
||||
}
|
||||
// Close mailbox interface
|
||||
void close_mbox(int fd) {
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// Send message to mailbox, return first response int, 0 if error
|
||||
uint32_t msg_mbox(int fd, VC_MSG *msgp) {
|
||||
uint32_t ret = 0, i;
|
||||
|
||||
for (i = msgp->dlen / 4; i <= msgp->blen / 4; i += 4)
|
||||
msgp->uints[i++] = 0;
|
||||
msgp->len = (msgp->blen + 6) * 4;
|
||||
msgp->req = 0;
|
||||
if (ioctl(fd, _IOWR(100, 0, void *), msgp) < 0) {
|
||||
log_error("VC IOCTL failed");
|
||||
} else if ((msgp->req & 0x80000000) == 0) {
|
||||
log_error("VC IOCTL error");
|
||||
} else if (msgp->req == 0x80000001) {
|
||||
log_error("VC IOCTL partial error");
|
||||
} else {
|
||||
ret = msgp->uints[0];
|
||||
}
|
||||
#if DEBUG
|
||||
disp_vc_msg(msgp);
|
||||
#endif
|
||||
return (ret);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#if !defined(__RPI_VIDEOCORE_H__)
|
||||
#define __RPI_VIDEOCORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
// 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)
|
||||
|
||||
void videocore_setup(MEM_MAP *mp, int size);
|
||||
void videocore_close(MEM_MAP *mp);
|
||||
|
||||
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);
|
||||
|
||||
#endif // __RPI_VIDEOCORE_H__
|
||||
@@ -0,0 +1,88 @@
|
||||
#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");
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#if !defined(__RPI_GPIO_H__)
|
||||
#define __RPI_GPIO_H__
|
||||
|
||||
// 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_NOPULL 0
|
||||
#define GPIO_PULLDN 1
|
||||
#define GPIO_PULLUP 2
|
||||
|
||||
void gpio_setup();
|
||||
|
||||
void gpio_close();
|
||||
|
||||
void gpio_pull(int pin, int pull);
|
||||
|
||||
void gpio_mode(int pin, int mode);
|
||||
|
||||
#endif // __RPI_GPIO_H__
|
||||
@@ -0,0 +1,210 @@
|
||||
#include "rpi_leddriver.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../rpi_param.h"
|
||||
#include "../common.h"
|
||||
|
||||
#include "../dma/rpi_dma.h"
|
||||
#include "../dma/rpi_videocore.h"
|
||||
#include "../gpio/rpi_gpio.h"
|
||||
#include "../smi/rpi_smi.h"
|
||||
|
||||
#if PHYS_REG_BASE == PI_4_REG_BASE // Timings for RPi v4 (1.5 GHz)
|
||||
#define SMI_TIMING 10, 15, 30, 15 // 400 ns cycle time
|
||||
#else // Timings for RPi v0-3 (1 GHz)
|
||||
#define SMI_TIMING 10, 10, 20, 10 // 400 ns cycle time
|
||||
#endif
|
||||
|
||||
#define TX_TEST 0 // If non-zero, use dummy Tx data
|
||||
#define LED_NBITS 24 // Number of data bits per LED
|
||||
#define LED_PREBITS 4 // Number of zero bits before LED data
|
||||
#define LED_POSTBITS 4 // Number of zero bits after LED data
|
||||
#define BIT_NPULSES 3 // Number of O/P pulses per LED bit
|
||||
|
||||
// Length of data for 1 row (1 LED on each channel)
|
||||
#define LED_DLEN (LED_NBITS * BIT_NPULSES)
|
||||
|
||||
// Transmit data type, 8 or 16 bits
|
||||
#if LED_NCHANS > 8
|
||||
#define TXDATA_T uint16_t
|
||||
#else
|
||||
#define TXDATA_T uint8_t
|
||||
#endif
|
||||
|
||||
// Ofset into Tx data buffer, given LED number in chan
|
||||
#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n)))
|
||||
|
||||
// Size of data buffers & NV memory, given number of LEDs per chan
|
||||
#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS)
|
||||
#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T))
|
||||
#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS))
|
||||
|
||||
/* Global */
|
||||
MEM_MAP vc_mem;
|
||||
TXDATA_T *txdata;
|
||||
TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)];
|
||||
|
||||
void swap_bytes();
|
||||
|
||||
void leddriver_setup() {
|
||||
videocore_setup(&vc_mem, VC_MEM_SIZE);
|
||||
gpio_setup();
|
||||
smi_setup(LED_NCHANS, SMI_TIMING, &vc_mem, TX_BUFF_LEN(CHAN_MAXLEDS), &txdata);
|
||||
}
|
||||
|
||||
void leddriver_close() {
|
||||
videocore_close(&vc_mem);
|
||||
smi_close(LED_NCHANS);
|
||||
gpio_close();
|
||||
}
|
||||
|
||||
void set_color(uint32_t rgb, int index) {
|
||||
int msk;
|
||||
TXDATA_T *txd = &(tx_buffer[LED_TX_OSET(index)]);
|
||||
|
||||
// For each bit of the 24-bit RGB values..
|
||||
for (size_t n = 0; n < LED_NBITS; n++) {
|
||||
// Mask to convert RGB to GRB, M.S bit first
|
||||
msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1;
|
||||
// 1st byte or word is a high pulse on all lines
|
||||
txd[0] = (TXDATA_T)0xffff;
|
||||
// 2nd has high or low bits from data
|
||||
// 3rd is a low pulse
|
||||
txd[1] = txd[2] = 0;
|
||||
if (rgb & msk) {
|
||||
txd[1] = (TXDATA_T)0xffff;
|
||||
}
|
||||
txd += BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
// Set Tx data for 8 or 16 chans, 1 LED per chan, given 1 RGB val per chan
|
||||
// Logic 1 is 0.8us high, 0.4 us low, logic 0 is 0.4us high, 0.8us low
|
||||
void rgb_txdata(int *rgbs, int index) {
|
||||
int i, n, msk;
|
||||
TXDATA_T *txd = &(tx_buffer[LED_TX_OSET(index)]);
|
||||
|
||||
// For each bit of the 24-bit RGB values..
|
||||
for (n = 0; n < LED_NBITS; n++) {
|
||||
// Mask to convert RGB to GRB, M.S bit first
|
||||
msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1;
|
||||
// 1st byte or word is a high pulse on all lines
|
||||
txd[0] = (TXDATA_T)0xffff;
|
||||
// 2nd has high or low bits from data
|
||||
// 3rd is a low pulse
|
||||
txd[1] = txd[2] = 0;
|
||||
for (i = 0; i < LED_NCHANS; i++) {
|
||||
if (rgbs[i] & msk)
|
||||
txd[1] |= (1 << i);
|
||||
}
|
||||
txd += BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
void leddriver_refresh() {
|
||||
#if LED_NCHANS <= 8
|
||||
swap_bytes();
|
||||
#endif
|
||||
|
||||
while (dma_active(DMA_CHAN_A)) {
|
||||
usleep(10);
|
||||
}
|
||||
|
||||
memcpy(txdata, tx_buffer, TX_BUFF_SIZE(CHAN_MAXLEDS));
|
||||
enable_dma(DMA_CHAN_A);
|
||||
start_smi(&vc_mem);
|
||||
usleep(10);
|
||||
}
|
||||
|
||||
// Swap adjacent bytes in transmit data
|
||||
void swap_bytes() {
|
||||
uint16_t *wp = (uint16_t *)tx_buffer;
|
||||
int len = TX_BUFF_SIZE(CHAN_MAXLEDS);
|
||||
|
||||
len = (len + 1) / 2;
|
||||
while (len-- > 0) {
|
||||
*wp = __builtin_bswap16(*wp);
|
||||
wp++;
|
||||
}
|
||||
}
|
||||
|
||||
/* source :
|
||||
* https://github.com/adafruit/Adafruit_NeoPixel/blob/216ccdbff399750f5b02d4cc804c598399e39713/Adafruit_NeoPixel.cpp#L2414
|
||||
*/
|
||||
uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) {
|
||||
|
||||
uint8_t r, g, b;
|
||||
|
||||
// Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
|
||||
// 0 is not the start of pure red, but the midpoint...a few values above
|
||||
// zero and a few below 65536 all yield pure red (similarly, 32768 is the
|
||||
// midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
|
||||
// each for red, green, blue) really only allows for 1530 distinct hues
|
||||
// (not 1536, more on that below), but the full unsigned 16-bit type was
|
||||
// chosen for hue so that one's code can easily handle a contiguous color
|
||||
// wheel by allowing hue to roll over in either direction.
|
||||
hue = (hue * 1530L + 32768) / 65536;
|
||||
// Because red is centered on the rollover point (the +32768 above,
|
||||
// essentially a fixed-point +0.5), the above actually yields 0 to 1530,
|
||||
// where 0 and 1530 would yield the same thing. Rather than apply a
|
||||
// costly modulo operator, 1530 is handled as a special case below.
|
||||
|
||||
// So you'd think that the color "hexcone" (the thing that ramps from
|
||||
// pure red, to pure yellow, to pure green and so forth back to red,
|
||||
// yielding six slices), and with each color component having 256
|
||||
// possible values (0-255), might have 1536 possible items (6*256),
|
||||
// but in reality there's 1530. This is because the last element in
|
||||
// each 256-element slice is equal to the first element of the next
|
||||
// slice, and keeping those in there this would create small
|
||||
// discontinuities in the color wheel. So the last element of each
|
||||
// slice is dropped...we regard only elements 0-254, with item 255
|
||||
// being picked up as element 0 of the next slice. Like this:
|
||||
// Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0
|
||||
// Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0
|
||||
// Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254
|
||||
// and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
|
||||
// the constants below are not the multiples of 256 you might expect.
|
||||
|
||||
// Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
|
||||
if (hue < 510) { // Red to Green-1
|
||||
b = 0;
|
||||
if (hue < 255) { // Red to Yellow-1
|
||||
r = 255;
|
||||
g = hue; // g = 0 to 254
|
||||
} else { // Yellow to Green-1
|
||||
r = 510 - hue; // r = 255 to 1
|
||||
g = 255;
|
||||
}
|
||||
} else if (hue < 1020) { // Green to Blue-1
|
||||
r = 0;
|
||||
if (hue < 765) { // Green to Cyan-1
|
||||
g = 255;
|
||||
b = hue - 510; // b = 0 to 254
|
||||
} else { // Cyan to Blue-1
|
||||
g = 1020 - hue; // g = 255 to 1
|
||||
b = 255;
|
||||
}
|
||||
} else if (hue < 1530) { // Blue to Red-1
|
||||
g = 0;
|
||||
if (hue < 1275) { // Blue to Magenta-1
|
||||
r = hue - 1020; // r = 0 to 254
|
||||
b = 255;
|
||||
} else { // Magenta to Red-1
|
||||
r = 255;
|
||||
b = 1530 - hue; // b = 255 to 1
|
||||
}
|
||||
} else { // Last 0.5 Red (quicker than % operator)
|
||||
r = 255;
|
||||
g = b = 0;
|
||||
}
|
||||
|
||||
// Apply saturation and value to R,G,B, pack into 32-bit result:
|
||||
uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255
|
||||
uint16_t s1 = 1 + sat; // 1 to 256; same reason
|
||||
uint8_t s2 = 255 - sat; // 255 to 0
|
||||
return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
|
||||
(((((g * s1) >> 8) + s2) * v1) & 0xff00) | (((((b * s1) >> 8) + s2) * v1) >> 8);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#if !defined(__RPI_LEDDRIVER_H__)
|
||||
#define __RPI_LEDDRIVER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void leddriver_setup();
|
||||
|
||||
void leddriver_close();
|
||||
|
||||
void set_color(uint32_t rgb, int index);
|
||||
|
||||
void rgb_txdata(int *rgbs, int index);
|
||||
|
||||
void leddriver_refresh();
|
||||
|
||||
uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val);
|
||||
|
||||
#endif // __RPI_LEDDRIVER_H__
|
||||
@@ -0,0 +1,13 @@
|
||||
include(FetchContent)
|
||||
|
||||
#set(FETCHCONTENT_FULLY_DISCONNECTED ON)
|
||||
|
||||
#set(BUILD_SHARED_LIBS off)
|
||||
|
||||
FetchContent_Declare(
|
||||
logc
|
||||
GIT_REPOSITORY "https://github.com/Tropicananass/log.c.git"
|
||||
)
|
||||
|
||||
# add the log.c
|
||||
FetchContent_MakeAvailable(logc)
|
||||
@@ -0,0 +1,223 @@
|
||||
#include "rpi_smi.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../dma/rpi_dma.h"
|
||||
#include "../gpio/rpi_gpio.h"
|
||||
|
||||
// GPIO first pin
|
||||
#define SMI_SD0_PIN 8
|
||||
|
||||
// Data widths
|
||||
#define SMI_8_BITS 0
|
||||
#define SMI_16_BITS 1
|
||||
#define SMI_18_BITS 2
|
||||
#define SMI_9_BITS 3
|
||||
|
||||
// Clock registers and values
|
||||
#define CLK_BASE (PHYS_REG_BASE + 0x101000)
|
||||
// #define CLK_PWM_CTL 0xa0
|
||||
// #define CLK_PWM_DIV 0xa4
|
||||
#define CLK_SMI_CTL 0xb0
|
||||
#define CLK_SMI_DIV 0xb4
|
||||
#define CLK_PASSWD 0x5a000000
|
||||
#define PWM_CLOCK_ID 0xa
|
||||
|
||||
// DMA request threshold
|
||||
#define REQUEST_THRESH 2
|
||||
|
||||
// Register definitions
|
||||
#define SMI_BASE (PHYS_REG_BASE + 0x600000)
|
||||
#define SMI_CS 0x00 // Control & status
|
||||
#define SMI_L 0x04 // Transfer length
|
||||
#define SMI_A 0x08 // Address
|
||||
#define SMI_D 0x0c // Data
|
||||
#define SMI_DSR0 0x10 // Read settings device 0
|
||||
#define SMI_DSW0 0x14 // Write settings device 0
|
||||
#define SMI_DSR1 0x18 // Read settings device 1
|
||||
#define SMI_DSW1 0x1c // Write settings device 1
|
||||
#define SMI_DSR2 0x20 // Read settings device 2
|
||||
#define SMI_DSW2 0x24 // Write settings device 2
|
||||
#define SMI_DSR3 0x28 // Read settings device 3
|
||||
#define SMI_DSW3 0x2c // Write settings device 3
|
||||
#define SMI_DMC 0x30 // DMA control
|
||||
#define SMI_DCS 0x34 // Direct control/status
|
||||
#define SMI_DCA 0x38 // Direct address
|
||||
#define SMI_DCD 0x3c // Direct data
|
||||
#define SMI_FD 0x40 // FIFO debug
|
||||
#define SMI_REGLEN (SMI_FD * 4)
|
||||
|
||||
// Union of 32-bit value with register bitfields
|
||||
#define REG_DEF(name, fields) \
|
||||
typedef union { \
|
||||
struct { \
|
||||
volatile uint32_t fields; \
|
||||
}; \
|
||||
volatile uint32_t value; \
|
||||
} name
|
||||
|
||||
// Control and status register
|
||||
#define SMI_CS_FIELDS \
|
||||
enable: \
|
||||
1, done : 1, active : 1, start : 1, clear : 1, write : 1, _x1 : 2, teen : 1, intd : 1, intt : 1, \
|
||||
intr : 1, pvmode : 1, seterr : 1, pxldat : 1, edreq : 1, _x2 : 8, _x3 : 1, aferr : 1, \
|
||||
txw : 1, rxr : 1, txd : 1, rxd : 1, txe : 1, rxf : 1
|
||||
REG_DEF(SMI_CS_REG, SMI_CS_FIELDS);
|
||||
|
||||
// Data length register
|
||||
#define SMI_L_FIELDS \
|
||||
len: \
|
||||
32
|
||||
REG_DEF(SMI_L_REG, SMI_L_FIELDS);
|
||||
|
||||
// Address & device number
|
||||
#define SMI_A_FIELDS \
|
||||
addr: \
|
||||
6, _x1 : 2, dev : 2
|
||||
REG_DEF(SMI_A_REG, SMI_A_FIELDS);
|
||||
|
||||
// Data FIFO
|
||||
#define SMI_D_FIELDS \
|
||||
data: \
|
||||
32
|
||||
REG_DEF(SMI_D_REG, SMI_D_FIELDS);
|
||||
|
||||
// DMA control register
|
||||
#define SMI_DMC_FIELDS \
|
||||
reqw: \
|
||||
6, reqr : 6, panicw : 6, panicr : 6, dmap : 1, _x1 : 3, dmaen : 1
|
||||
REG_DEF(SMI_DMC_REG, SMI_DMC_FIELDS);
|
||||
|
||||
// Device settings: read (1 of 4)
|
||||
#define SMI_DSR_FIELDS \
|
||||
rstrobe: \
|
||||
7, rdreq : 1, rpace : 7, rpaceall : 1, rhold : 6, fsetup : 1, mode68 : 1, rsetup : 6, rwidth : 2
|
||||
REG_DEF(SMI_DSR_REG, SMI_DSR_FIELDS);
|
||||
|
||||
// Device settings: write (1 of 4)
|
||||
#define SMI_DSW_FIELDS \
|
||||
wstrobe: \
|
||||
7, wdreq : 1, wpace : 7, wpaceall : 1, whold : 6, wswap : 1, wformat : 1, wsetup : 6, wwidth : 2
|
||||
REG_DEF(SMI_DSW_REG, SMI_DSW_FIELDS);
|
||||
|
||||
// Direct control register
|
||||
#define SMI_DCS_FIELDS \
|
||||
enable: \
|
||||
1, start : 1, done : 1, write : 1
|
||||
REG_DEF(SMI_DCS_REG, SMI_DCS_FIELDS);
|
||||
|
||||
// Direct control address & device number
|
||||
#define SMI_DCA_FIELDS \
|
||||
addr: \
|
||||
6, _x1 : 2, dev : 2
|
||||
REG_DEF(SMI_DCA_REG, SMI_DCA_FIELDS);
|
||||
|
||||
// Direct control data
|
||||
#define SMI_DCD_FIELDS \
|
||||
data: \
|
||||
32
|
||||
REG_DEF(SMI_DCD_REG, SMI_DCD_FIELDS);
|
||||
|
||||
// Debug register
|
||||
#define SMI_FLVL_FIELDS \
|
||||
fcnt: \
|
||||
6, _x1 : 2, flvl : 6
|
||||
REG_DEF(SMI_FLVL_REG, SMI_FLVL_FIELDS);
|
||||
|
||||
// Pointers to SMI registers
|
||||
volatile SMI_CS_REG *smi_cs;
|
||||
volatile SMI_L_REG *smi_l;
|
||||
volatile SMI_A_REG *smi_a;
|
||||
volatile SMI_D_REG *smi_d;
|
||||
volatile SMI_DMC_REG *smi_dmc;
|
||||
volatile SMI_DSR_REG *smi_dsr;
|
||||
volatile SMI_DSW_REG *smi_dsw;
|
||||
volatile SMI_DCS_REG *smi_dcs;
|
||||
volatile SMI_DCA_REG *smi_dca;
|
||||
volatile SMI_DCD_REG *smi_dcd;
|
||||
|
||||
MEM_MAP smi_regs, clk_regs;
|
||||
|
||||
void setup_smi_dma(MEM_MAP *mp, int nsamp, uint8_t **txdata, int len);
|
||||
|
||||
void smi_setup(int channels, int ns, int setup, int strobe, int hold, MEM_MAP *mp, int nsamp,
|
||||
uint8_t **txdata) {
|
||||
map_periph(&smi_regs, (void *)SMI_BASE, PAGE_SIZE);
|
||||
map_periph(&clk_regs, (void *)CLK_BASE, PAGE_SIZE);
|
||||
|
||||
int width = channels > 8 ? SMI_16_BITS : SMI_8_BITS;
|
||||
int i, divi = ns / 2;
|
||||
|
||||
smi_cs = (SMI_CS_REG *)REG32(smi_regs, SMI_CS);
|
||||
smi_l = (SMI_L_REG *)REG32(smi_regs, SMI_L);
|
||||
smi_a = (SMI_A_REG *)REG32(smi_regs, SMI_A);
|
||||
smi_d = (SMI_D_REG *)REG32(smi_regs, SMI_D);
|
||||
smi_dmc = (SMI_DMC_REG *)REG32(smi_regs, SMI_DMC);
|
||||
smi_dsr = (SMI_DSR_REG *)REG32(smi_regs, SMI_DSR0);
|
||||
smi_dsw = (SMI_DSW_REG *)REG32(smi_regs, SMI_DSW0);
|
||||
smi_dcs = (SMI_DCS_REG *)REG32(smi_regs, SMI_DCS);
|
||||
smi_dca = (SMI_DCA_REG *)REG32(smi_regs, SMI_DCA);
|
||||
smi_dcd = (SMI_DCD_REG *)REG32(smi_regs, SMI_DCD);
|
||||
smi_cs->value = smi_l->value = smi_a->value = 0;
|
||||
smi_dsr->value = smi_dsw->value = smi_dcs->value = smi_dca->value = 0;
|
||||
if (*REG32(clk_regs, CLK_SMI_DIV) != divi << 12) {
|
||||
*REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | (1 << 5);
|
||||
usleep(10);
|
||||
while (*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7))
|
||||
;
|
||||
usleep(10);
|
||||
*REG32(clk_regs, CLK_SMI_DIV) = CLK_PASSWD | (divi << 12);
|
||||
usleep(10);
|
||||
*REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | 6 | (1 << 4);
|
||||
usleep(10);
|
||||
while ((*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7)) == 0)
|
||||
;
|
||||
usleep(100);
|
||||
}
|
||||
if (smi_cs->seterr)
|
||||
smi_cs->seterr = 1;
|
||||
smi_dsr->rsetup = smi_dsw->wsetup = setup;
|
||||
smi_dsr->rstrobe = smi_dsw->wstrobe = strobe;
|
||||
smi_dsr->rhold = smi_dsw->whold = hold;
|
||||
smi_dmc->panicr = smi_dmc->panicw = 8;
|
||||
smi_dmc->reqr = smi_dmc->reqw = REQUEST_THRESH;
|
||||
smi_dsr->rwidth = smi_dsw->wwidth = width;
|
||||
for (i = 0; i < channels; i++)
|
||||
gpio_mode(SMI_SD0_PIN + i, GPIO_ALT1);
|
||||
|
||||
setup_smi_dma(mp, nsamp, txdata, width + 1);
|
||||
}
|
||||
|
||||
void smi_close(int channels) {
|
||||
for (size_t i = 0; i < channels; ++i)
|
||||
gpio_mode(SMI_SD0_PIN + i, GPIO_IN);
|
||||
if (smi_regs.virt) {
|
||||
*REG32(smi_regs, SMI_CS) = 0;
|
||||
}
|
||||
stop_dma(DMA_CHAN_A);
|
||||
unmap_periph_mem(&clk_regs);
|
||||
unmap_periph_mem(&smi_regs);
|
||||
dma_close();
|
||||
}
|
||||
|
||||
// Start SMI DMA transfers
|
||||
void start_smi(MEM_MAP *mp) {
|
||||
DMA_CB *cbs = mp->virt;
|
||||
|
||||
start_dma(mp, DMA_CHAN_A, &cbs[0], 0);
|
||||
smi_cs->start = 1;
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
// Set up SMI transfers using DMA
|
||||
void setup_smi_dma(MEM_MAP *mp, int nsamp, uint8_t **txdata, int len) {
|
||||
smi_dmc->dmaen = 1;
|
||||
smi_cs->enable = 1;
|
||||
smi_cs->clear = 1;
|
||||
smi_cs->pxldat = 1;
|
||||
smi_l->len = nsamp * len;
|
||||
smi_cs->write = 1;
|
||||
|
||||
dma_setup(mp, DMA_CHAN_A, nsamp, txdata, len, REG_BUS_ADDR(smi_regs, SMI_D));
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#if !defined(__RPI_SMI_H__)
|
||||
#define __RPI_SMI_H__
|
||||
|
||||
#include "../common.h"
|
||||
#include <stdint.h>
|
||||
|
||||
void smi_setup(int channels, int ns, int setup, int strobe, int hold, MEM_MAP *mp, int nsamp,
|
||||
uint8_t **txdata);
|
||||
|
||||
void smi_close(int channels);
|
||||
|
||||
void start_smi(MEM_MAP *mp);
|
||||
|
||||
#endif // __RPI_SMI_H__
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "include/log.h"
|
||||
#include "src/leddriver/rpi_leddriver.h"
|
||||
#include "src/common.h"
|
||||
|
||||
#define CHAN_MAXLEDS 60 // Maximum number of LEDs per channel
|
||||
#define LED_NCHANS 8 // Number of LED channels (8 or 16)
|
||||
|
||||
#define TX_TEST 0 // If non-zero, use dummy Tx data
|
||||
#define LED_NBITS 24 // Number of data bits per LED
|
||||
#define LED_PREBITS 4 // Number of zero bits before LED data
|
||||
#define LED_POSTBITS 4 // Number of zero bits after LED data
|
||||
#define BIT_NPULSES 3 // Number of O/P pulses per LED bit
|
||||
|
||||
// Length of data for 1 row (1 LED on each channel)
|
||||
#define LED_DLEN (LED_NBITS * BIT_NPULSES)
|
||||
|
||||
// Transmit data type, 8 or 16 bits
|
||||
#if LED_NCHANS > 8
|
||||
typedef uint16_t TXDATA_T;
|
||||
#else
|
||||
typedef uint8_t TXDATA_T;
|
||||
#endif
|
||||
|
||||
// Ofset into Tx data buffer, given LED number in chan
|
||||
#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n)))
|
||||
|
||||
// Size of data buffers & NV memory, given number of LEDs per chan
|
||||
#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS)
|
||||
#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T))
|
||||
#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS))
|
||||
|
||||
extern TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)];
|
||||
extern TXDATA_T *txdata;
|
||||
extern MEM_MAP vc_mem;
|
||||
Reference in New Issue
Block a user