Rock Pi S Direct GPIO under Ubuntu

I’m attempting to port a simple LED matrix driver from Arduino to a Rock Pi S HW revision 1.3. I first used the available device files under /sys/class/gpio and that worked but the speed was much too slow. The fastest I could get with this was around 5fps which is not enough to remove flicker. But I know it is at least possible, because I proved that it did work (but slowly).

I am trying to instead directly control the GPIO through mmap registers and reading the application note for the RK3308. I’ve also studied what I could from the pinctrl and gpio drivers in the linux kernel. I am having no luck, maybe I am missing something simple? I have done a fair amount of embedded programming but never directly under linux so perhaps I am doing something stupid?

If I run the following under root, I can see Pin #3 toggling state on an Oscilloscope:

root@rockpis:/sys/class/gpio# echo 11 > export
root@rockpis:/sys/class/gpio# cd gpio11
root@rockpis:/sys/class/gpio/gpio11# echo out > direction
root@rockpis:/sys/class/gpio/gpio11# echo 1 > value
root@rockpis:/sys/class/gpio/gpio11# echo 0 > value

If I attempt to compile and run this test program as root, I see no effect. As if the registers weren’t mapped properly? I noticed that wiringX does not have a port to Rock Pi S or I would simply use that.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

#define INPUT 1
#define OUTPUT 2

#define HIGH 1
#define LOW 0

void *GRU_base = NULL;
void *GPIO0_base = NULL;
void *GPIO1_base = NULL;
void *GPIO2_base = NULL;
void *GPIO3_base = NULL;
void *GPIO4_base = NULL;

static inline unsigned int raw_read(const volatile void *addr) {
	return *(const volatile unsigned int *) addr;
}

static inline void raw_write(unsigned int data, volatile void *addr) {
	*(volatile unsigned int *) addr = data;
}

#define GPIO_SWPORT_DR 0x00
#define GPIO_SWPORT_DDR 0x04

void *getControlReg(int base, int pin) {
	switch (base) {
		case 0:
			return GPIO0_base;
		case 1:
			return GPIO1_base;
		case 2:
			return GPIO2_base;
		case 3:
			return GPIO3_base;
		case 4:
			return GPIO4_base;
		default:
			return 0;
	}
}

unsigned int getGRUMuxOffset(int base, int pin) {
	switch (base) {
		case 0: {
			switch(pin / 8) {
				case 0:
					return 0x0;
				case 1:
					return 0x8;
				case 2:
					return 0x10;
			}
			break;
		}
	}

	// Unmapped for now.
	return 0;
}

unsigned int getGRUMuxMask(int base, int pin) {
	switch (base) {
		case 0: {
			switch (pin % 8) {
				case 0:
					return 0x00030000;
				case 1:
					return 0x000C0000;
				case 2:
					return 0x00300000;
				case 3:
					return 0x00C00000;
				case 4:
					return 0x03000000;
				case 5:
					return 0x0C000000;
				case 6:
					return 0x30000000;
				case 7:
					return 0xC0000000;
			}
			break;
		}
	}

	// Unmapped for now.
	return 0;
}

unsigned int getGRUEnableOffset(int base, int pin) {
	switch (base) {
		case 0: {
			switch(pin / 8) {
				case 0:
					return 0xa0;
				case 1:
					return 0xa4;
				case 2:
					return 0xa8;
			}
			break;
		}
	}

	// Unmapped for now.
	return 0;
}

unsigned int getGRUEnableMask(int base, int pin) {
	switch (base) {
		case 0: {
			switch (pin % 8) {
				case 0:
					return 0x00030000;
				case 1:
					return 0x000C0000;
				case 2:
					return 0x00300000;
				case 3:
					return 0x00C00000;
				case 4:
					return 0x03000000;
				case 5:
					return 0x0C000000;
				case 6:
					return 0x30000000;
				case 7:
					return 0xC0000000;
			}
			break;
		}
	}

	// Unmapped for now.
	return 0;
}

int pinMode(unsigned int gpio, unsigned int direction) {
	unsigned int base = gpio / 32;
	unsigned int pin = gpio % 32;
	unsigned int offset = 1UL << pin;

	// Mux settings for raw GPIO.
	unsigned int gru_off = getGRUMuxOffset(base, pin);
	unsigned int gru_mask = getGRUMuxMask(base, pin);
	raw_write(gru_mask, GRU_base + gru_off);

	// Enable settinsg to make sure its not a weak pull-up/pull-down.
	gru_off = getGRUEnableOffset(base, pin);
	gru_mask = getGRUEnableMask(base, pin);
	raw_write(gru_mask, GRU_base + gru_off);

	void *reg = getControlReg(base, pin);
	if (reg == 0) {
		return -1;
	}

	unsigned int val = raw_read(reg + GPIO_SWPORT_DDR);
	if (direction == INPUT) {
		val &= ~offset;
	} else {
		val |= offset;
	}
	printf("pinMode: %08X\n", val);
	raw_write(val, reg + GPIO_SWPORT_DDR);

	return 0;
}

int digitalWrite(int gpio, int value) {
	unsigned int base = gpio / 32;
	unsigned int pin = gpio % 32;
	unsigned int offset = 1UL << pin;

	void *reg = getControlReg(base, pin);
	if (reg == 0) {
		return -1;
	}

	unsigned int val = raw_read(reg + GPIO_SWPORT_DR);
	if (value == 0) {
		val &= ~offset;
	} else {
		val |= offset;
	}
	printf("digitalWrite: %08X\n", val);
	raw_write(val, reg + GPIO_SWPORT_DR);

	return 0;
}

int mapReg(void *reg, void **reg_mapped) {
	int fd;
	unsigned int addr_start, addr_offset;
	unsigned int pagesize, pagemask;
	void *pc;

	if((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
		return -1;
	}

	pagesize = sysconf(_SC_PAGESIZE);
	pagemask = (~(pagesize - 1));

	addr_start = (unsigned int)reg & pagemask;
	addr_offset = (unsigned int)reg & ~pagemask;

	pc = (void *)mmap(0, pagesize * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr_start);

	if(pc == MAP_FAILED) {
		return -2;
	}

	close(fd);

	*reg_mapped = (pc + addr_offset);

	return 0;
}

int gpioInit() {
	// Map GPIO0-4 for bit twiddling.
	if (mapReg((void *)0xff220000, &GPIO0_base) != 0) {
		return -1;
	}
	if (mapReg((void *)0xff230000, &GPIO1_base) != 0) {
		return -1;
	}
	if (mapReg((void *)0xff240000, &GPIO2_base) != 0) {
		return -1;
	}
	if (mapReg((void *)0xff250000, &GPIO3_base) != 0) {
		return -1;
	}
	if (mapReg((void *)0xff260000, &GPIO4_base) != 0) {
		return -1;
	}

	// Map GRU for MUX settings.
	if (mapReg((void *)0xff000000, &GRU_base) != 0) {
		return -2;
	}

	return 0;
}

void main() {
	printf("Initializing GPIO...\n");
	if (gpioInit() != 0) {
		printf("Failed to init GPIO!\n");
		return;
	}

	pinMode(11, OUTPUT);

	printf("Toggling pin...\n");

	while( 1 ) {
		printf("On\n");
		digitalWrite(11, HIGH);
		sleep(1);
		printf("Off\n");
		digitalWrite(11, LOW);
		sleep(1);
	}
}