com.diozero.internal.soc.rockchip.RK3288MmapGpio Maven / Gradle / Ivy
The newest version!
package com.diozero.internal.soc.rockchip;
/*
* #%L
* Organisation: diozero
* Project: diozero - Core
* Filename: RK3288MmapGpio.java
*
* This file is part of the diozero project. More information about this project
* can be found at https://www.diozero.com/.
* %%
* Copyright (C) 2016 - 2024 diozero
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* #L%
*/
import java.nio.ByteOrder;
import org.tinylog.Logger;
import com.diozero.api.DeviceMode;
import com.diozero.api.GpioPullUpDown;
import com.diozero.internal.spi.MmapGpioInterface;
import com.diozero.util.LibraryLoader;
import com.diozero.util.MmapIntBuffer;
import com.diozero.util.SleepUtil;
/*-
* https://github.com/torvalds/linux/blob/master/arch/arm/boot/dts/rk3288.dtsi
* https://chromium.googlesource.com/chromiumos/third_party/coreboot/+/chromeos-2013.04/src/soc/rockchip/rk3288/gpio.c
* Note wiringTB was updated to use in-line asm for digital writes:
* https://github.com/TinkerBoard/gpio_lib_c/blob/sbc/tinkerboard/c/wiringPi/wiringTB.c#L735
*/
@SuppressWarnings("resource")
public class RK3288MmapGpio implements MmapGpioInterface {
private static final String GPIOMEM_DEVICE = "/dev/gpiomem";
private static final long PMU_BASE = 0xff730000L;
private static final long GPIO_BASE = 0xff75_0000L;
private static final long GRF_BASE = 0xff77_0000L;
private static final int GPIO_LENGTH = 0x00010000;
private static final int GPIO_CHANNEL = 0x00020000;
private static final int PAGE_SIZE = 4 * 0x400;
private static final int UNKNOWN = -1;
private static final int PMU_GPIO0C_IOMUX_INT_OFFSET = 0x008c / 4;
private static final int GRF_GPIO5B_IOMUX_INT_OFFSET = 0x0050 / 4;
private static final int GRF_GPIO5C_IOMUX_INT_OFFSET = 0x0054 / 4;
private static final int GRF_GPIO6A_IOMUX_INT_OFFSET = 0x005c / 4;
private static final int GRF_GPIO6B_IOMUX_INT_OFFSET = 0x0060 / 4;
private static final int GRF_GPIO6C_IOMUX_INT_OFFSET = 0x0064 / 4;
private static final int GRF_GPIO7A_IOMUX_INT_OFFSET = 0x006c / 4;
private static final int GRF_GPIO7B_IOMUX_INT_OFFSET = 0x0070 / 4;
private static final int GRF_GPIO7CL_IOMUX_INT_OFFSET = 0x0074 / 4;
private static final int GRF_GPIO7CH_IOMUX_INT_OFFSET = 0x0078 / 4;
private static final int GRF_GPIO8A_IOMUX_INT_OFFSET = 0x0080 / 4;
private static final int GRF_GPIO8B_IOMUX_INT_OFFSET = 0x0084 / 4;
// Pull up/down offsets
private static final int PMU_GPIO0C_P_INT_OFFSET = 0x006c / 4;
private static final int GRF_GPIO1D_P_INT_OFFSET = 0x014c / 4;
private static final int GRF_GPIO2A_P_INT_OFFSET = 0x0150 / 4;
private static final int GRF_GPIO2B_P_INT_OFFSET = 0x0154 / 4;
private static final int GRF_GPIO2C_P_INT_OFFSET = 0x0158 / 4;
private static final int GRF_GPIO3A_P_INT_OFFSET = 0x0160 / 4;
private static final int GRF_GPIO3B_P_INT_OFFSET = 0x0164 / 4;
private static final int GRF_GPIO3C_P_INT_OFFSET = 0x0168 / 4;
private static final int GRF_GPIO3D_P_INT_OFFSET = 0x016c / 4;
private static final int GRF_GPIO4A_P_INT_OFFSET = 0x0170 / 4;
private static final int GRF_GPIO4B_P_INT_OFFSET = 0x0174 / 4;
private static final int GRF_GPIO4C_P_INT_OFFSET = 0x0178 / 4;
private static final int GRF_GPIO4D_P_INT_OFFSET = 0x017c / 4;
private static final int GRF_GPIO5B_P_INT_OFFSET = 0x0184 / 4;
private static final int GRF_GPIO5C_P_INT_OFFSET = 0x0188 / 4;
private static final int GRF_GPIO6A_P_INT_OFFSET = 0x0190 / 4;
private static final int GRF_GPIO6B_P_INT_OFFSET = 0x0194 / 4;
private static final int GRF_GPIO6C_P_INT_OFFSET = 0x0198 / 4;
private static final int GRF_GPIO7A_P_INT_OFFSET = 0x01a0 / 4;
private static final int GRF_GPIO7B_P_INT_OFFSET = 0x01a4 / 4;
private static final int GRF_GPIO7C_P_INT_OFFSET = 0x01a8 / 4;
private static final int GRF_GPIO8A_P_INT_OFFSET = 0x01b0 / 4;
private static final int GRF_GPIO8B_P_INT_OFFSET = 0x01b4 / 4;
private static final int[][] PULL_TABLE = { //
{ UNKNOWN, UNKNOWN, PMU_GPIO0C_P_INT_OFFSET, UNKNOWN }, //
{ UNKNOWN, UNKNOWN, UNKNOWN, GRF_GPIO1D_P_INT_OFFSET }, //
{ GRF_GPIO2A_P_INT_OFFSET, GRF_GPIO2B_P_INT_OFFSET, GRF_GPIO2C_P_INT_OFFSET, UNKNOWN }, //
{ GRF_GPIO3A_P_INT_OFFSET, GRF_GPIO3B_P_INT_OFFSET, GRF_GPIO3C_P_INT_OFFSET, GRF_GPIO3D_P_INT_OFFSET }, //
{ GRF_GPIO4A_P_INT_OFFSET, GRF_GPIO4B_P_INT_OFFSET, GRF_GPIO4C_P_INT_OFFSET, GRF_GPIO4D_P_INT_OFFSET }, //
{ UNKNOWN, GRF_GPIO5B_P_INT_OFFSET, GRF_GPIO5C_P_INT_OFFSET, UNKNOWN }, //
{ GRF_GPIO6A_P_INT_OFFSET, GRF_GPIO6B_P_INT_OFFSET, GRF_GPIO6C_P_INT_OFFSET, UNKNOWN }, //
{ GRF_GPIO7A_P_INT_OFFSET, GRF_GPIO7B_P_INT_OFFSET, GRF_GPIO7C_P_INT_OFFSET, UNKNOWN }, //
{ GRF_GPIO8A_P_INT_OFFSET, GRF_GPIO8B_P_INT_OFFSET, UNKNOWN, UNKNOWN } //
};
private static final int GPIO_SWPORTA_DR_INT_OFFSET = 0x0000 / 4;
private static final int GPIO_SWPORTA_DDR_INT_OFFSET = 0x0004 / 4;
private static final int GPIO_EXT_PORTA_INT_OFFSET = 0x0050 / 4;
private static final int MUX_FUNC_GPIO = 0;
// Only for GPIO7_C6 and GPIO7_C7
private static final int MUX_FUNC_PWM = 3;
private MmapIntBuffer[] gpioBanks;
private MmapIntBuffer pmuMmapIntBuffer;
private MmapIntBuffer grfMmapIntBuffer;
@Override
public synchronized void initialise() {
if (gpioBanks == null) {
gpioBanks = new MmapIntBuffer[9];
for (int i = 0; i < gpioBanks.length; i++) {
gpioBanks[i] = new MmapIntBuffer(GPIOMEM_DEVICE, GPIO_BASE + i * GPIO_LENGTH + (i > 0 ? GPIO_CHANNEL : 0),
PAGE_SIZE, ByteOrder.LITTLE_ENDIAN);
}
pmuMmapIntBuffer = new MmapIntBuffer(GPIOMEM_DEVICE, PMU_BASE, PAGE_SIZE, ByteOrder.LITTLE_ENDIAN);
grfMmapIntBuffer = new MmapIntBuffer(GPIOMEM_DEVICE, GRF_BASE, PAGE_SIZE, ByteOrder.LITTLE_ENDIAN);
}
}
@Override
public synchronized void close() {
if (gpioBanks != null) {
for (int i = 0; i < gpioBanks.length; i++) {
gpioBanks[i].close();
}
gpioBanks = null;
}
if (pmuMmapIntBuffer != null) {
pmuMmapIntBuffer.close();
pmuMmapIntBuffer = null;
}
if (grfMmapIntBuffer != null) {
grfMmapIntBuffer.close();
grfMmapIntBuffer = null;
}
}
@Override
public DeviceMode getMode(int gpio) {
// https://github.com/TinkerBoard/gpio_lib_c/blob/sbc/tinkerboard/c/wiringPi/wiringTB.c#L370
MmapIntBuffer mux_int_buffer = (gpio < 24) ? pmuMmapIntBuffer : grfMmapIntBuffer;
int iomux_offset = getIoMuxOffsetForGpio(gpio);
if (iomux_offset == UNKNOWN) {
Logger.warn("Unknown IOMUX offset for GPIO #{}", Integer.valueOf(gpio));
return DeviceMode.UNKNOWN;
}
int mux_mask = getMuxMask(gpio);
if (mux_mask == UNKNOWN) {
Logger.warn("Unknown mux mode for GPIO #{}", Integer.valueOf(gpio));
return DeviceMode.UNKNOWN;
}
int config_val = mux_int_buffer.get(iomux_offset);
int mux_func;
if (gpio == 238 || gpio == 239) {
// The two PWM pins
mux_func = (config_val >> (((gpio - 4) % 8) * 4)) & mux_mask;
} else {
mux_func = (config_val >> ((gpio % 8) * 2)) & mux_mask;
}
DeviceMode mode;
if (mux_func == MUX_FUNC_GPIO) {
// gpioToBank -
// https://github.com/TinkerBoard/gpio_lib_c/blob/sbc/tinkerboard/c/wiringPi/wiringTB.c#L164
// gpioToBankPin -
// https://github.com/TinkerBoard/gpio_lib_c/blob/sbc/tinkerboard/c/wiringPi/wiringTB.c#L172
int bank;
int shift;
if (gpio < 24) {
bank = 0;
shift = gpio;
} else {
bank = (gpio + 8) / 32;
shift = (gpio + 8) % 32;
}
if ((gpioBanks[bank].get(GPIO_SWPORTA_DDR_INT_OFFSET) & 1 << shift) != 0) {
mode = DeviceMode.DIGITAL_OUTPUT;
} else {
mode = DeviceMode.DIGITAL_INPUT;
}
} else if ((gpio == 238 || gpio == 239) && mux_func == MUX_FUNC_PWM) {
// PWM can be selected on 238 / 239
mode = DeviceMode.PWM_OUTPUT;
} else {
mode = DeviceMode.UNKNOWN;
}
return mode;
}
@Override
public void setMode(int gpio, DeviceMode mode) {
MmapIntBuffer mux_int_buffer = (gpio < 24) ? pmuMmapIntBuffer : grfMmapIntBuffer;
int iomux_offset = getIoMuxOffsetForGpio(gpio);
if (iomux_offset == UNKNOWN) {
Logger.warn("Unknown IOMUX offset for GPIO #{}", Integer.valueOf(gpio));
return;
}
// Get the current mode register value
int config_val = mux_int_buffer.get(iomux_offset);
// Configure mode
switch (mode) {
case DIGITAL_INPUT:
case DIGITAL_OUTPUT:
// 1. Configure as GPIO
if (gpio == 233 || gpio == 234) {
// *(grf+GRF_GPIO7CL_IOMUX/4) = (*(grf+GRF_GPIO7CL_IOMUX/4) |
// (0x0f<<(16+(pin%8)*4))) & (~(0x0f<<((pin%8)*4)));
config_val = (config_val | (0x0f << (16 + (gpio % 8) * 4))) & (~(0x0f << ((gpio % 8) * 4)));
} else if (gpio == 238 || gpio == 239) {
config_val = (config_val | (0x0f << (16 + (gpio % 8 - 4) * 4))) & (~(0x0f << ((gpio % 8 - 4) * 4)));
} else {
config_val = (config_val | (0x03 << ((gpio % 8) * 2 + 16))) & (~(0x03 << ((gpio % 8) * 2)));
}
// Update the mode register
mux_int_buffer.put(iomux_offset, config_val);
// 2. Set the GPIO direction
int bank;
int shift;
if (gpio < 24) {
bank = 0;
shift = gpio;
} else {
bank = (gpio + 8) / 32;
shift = (gpio + 8) % 32;
}
// Get the current GPIO direction register
int gpio_val = gpioBanks[bank].get(GPIO_SWPORTA_DDR_INT_OFFSET);
if (mode == DeviceMode.DIGITAL_INPUT) {
gpio_val &= ~(1 << shift);
} else {
gpio_val |= (1 << shift);
}
// Update the GPIO direction register
gpioBanks[bank].put(GPIO_SWPORTA_DDR_INT_OFFSET, gpio_val);
break;
case PWM_OUTPUT:
Logger.warn("Mode {} not yet implemented for GPIO #{}", mode, Integer.valueOf(gpio));
return;
default:
Logger.warn("Invalid mode ({}) for GPIO #{}", mode, Integer.valueOf(gpio));
return;
}
// Seem to need a small delay...
SleepUtil.sleepMillis(1);
}
@Override
public void setModeUnchecked(int gpio, int mode) {
throw new UnsupportedOperationException();
}
@Override
public void setPullUpDown(int gpio, GpioPullUpDown pud) {
Logger.debug("setPullUpDown({}, {})", Integer.valueOf(gpio), pud);
int bank, shift;
if (gpio < 24) {
bank = 0;
shift = gpio;
} else {
bank = (gpio + 8) / 32;
shift = (gpio + 8) % 32;
}
int pud_offset = PULL_TABLE[bank][shift / 8];
if (pud_offset == UNKNOWN) {
Logger.error("Wrong offset for GPIO #{}", Integer.valueOf(gpio));
return;
}
int bit0, bit1;
switch (pud) {
case PULL_UP:
bit0 = 1;
bit1 = 0;
break;
case PULL_DOWN:
bit0 = 0;
bit1 = 1;
break;
case NONE:
default:
bit0 = 0;
bit1 = 0;
break;
}
MmapIntBuffer int_buffer = (gpio < 24) ? pmuMmapIntBuffer : grfMmapIntBuffer;
int_buffer.put(pud_offset,
(int_buffer.get(pud_offset) | (0x03 << ((gpio % 8) * 2 + 16))) & (~(0x03 << ((gpio % 8) * 2)))
| (bit1 << ((gpio % 8) * 2 + 1)) | (bit0 << ((gpio % 8) * 2)));
// *(grf+pud) = (*(grf+pud) | (0x03<<((pin%8)*2+16))) & (~(0x03<<((pin%8)*2))) |
// (bit1<<((pin%8)*2+1)) | (bit0<<((pin%8)*2));
}
@Override
public boolean gpioRead(int gpio) {
int bank;
int shift;
if (gpio < 24) {
bank = 0;
shift = gpio;
} else {
bank = (gpio + 8) / 32;
shift = (gpio + 8) % 32;
}
// (((*(gpio0[bank]+GPIO_EXT_PORTA_OFFSET/4)) & (1 << bank_pin)) >> bank_pin)
return (gpioBanks[bank].get(GPIO_EXT_PORTA_INT_OFFSET) & (1 << shift)) != 0;
}
@Override
public void gpioWrite(int gpio, boolean value) {
int bank;
int shift;
if (gpio < 24) {
bank = 0;
shift = gpio;
} else {
bank = (gpio + 8) / 32;
shift = (gpio + 8) % 32;
}
int reg_val = gpioBanks[bank].get(GPIO_SWPORTA_DR_INT_OFFSET);
if (value) {
// *(gpio0[bank]+GPIO_SWPORTA_DR_OFFSET/4) |= (1< 0) {
gpio = Integer.parseInt(args[0]);
} else {
gpio = 171;
}
try (RK3288MmapGpio mmap_gpio = new RK3288MmapGpio()) {
mmap_gpio.initialise();
DeviceMode mode = mmap_gpio.getMode(gpio);
Logger.debug("Mode for GPIO #{}: {}, value: {}", Integer.valueOf(gpio), mode,
Boolean.valueOf(mmap_gpio.gpioRead(gpio)));
for (int i = 0; i < 5; i++) {
// Toggle input/output mode
mode = (mode == DeviceMode.DIGITAL_INPUT) ? DeviceMode.DIGITAL_OUTPUT : DeviceMode.DIGITAL_INPUT;
Logger.debug("Setting mode for GPIO #{} to {}", Integer.valueOf(gpio), mode);
mmap_gpio.setMode(gpio, mode);
Logger.debug("Configured mode for GPIO #{}: {}, value: {}", Integer.valueOf(gpio),
mmap_gpio.getMode(gpio), Boolean.valueOf(mmap_gpio.gpioRead(gpio)));
SleepUtil.sleepMillis(500);
}
mmap_gpio.setMode(gpio, DeviceMode.DIGITAL_OUTPUT);
SleepUtil.sleepMillis(10);
for (int i = 0; i < 5; i++) {
Logger.debug("Setting GPIO #{} to On", Integer.valueOf(gpio));
mmap_gpio.gpioWrite(gpio, true);
Logger.debug("Mode for GPIO #{}: {}, value: {}", Integer.valueOf(gpio), mmap_gpio.getMode(gpio),
Boolean.valueOf(mmap_gpio.gpioRead(gpio)));
SleepUtil.sleepMillis(300);
Logger.debug("Setting GPIO #{} to Off", Integer.valueOf(gpio));
mmap_gpio.gpioWrite(gpio, false);
Logger.debug("Mode for GPIO #{}: {}, value: {}", Integer.valueOf(gpio), mmap_gpio.getMode(gpio),
Boolean.valueOf(mmap_gpio.gpioRead(gpio)));
SleepUtil.sleepMillis(300);
}
mmap_gpio.setMode(gpio, DeviceMode.DIGITAL_INPUT);
mmap_gpio.setPullUpDown(gpio, GpioPullUpDown.PULL_UP);
Logger.debug("Pull up, read: " + mmap_gpio.gpioRead(gpio));
mmap_gpio.setPullUpDown(gpio, GpioPullUpDown.PULL_DOWN);
Logger.debug("Pull down, read: " + mmap_gpio.gpioRead(gpio));
mmap_gpio.setPullUpDown(gpio, GpioPullUpDown.NONE);
Logger.debug("Pull none, read: " + mmap_gpio.gpioRead(gpio));
mmap_gpio.setMode(gpio, DeviceMode.DIGITAL_OUTPUT);
int iterations = 1_000_000;
for (int j = 0; j < 5; j++) {
long start_nano = System.nanoTime();
for (int i = 0; i < iterations; i++) {
mmap_gpio.gpioWrite(gpio, true);
mmap_gpio.gpioWrite(gpio, false);
}
long duration_ns = System.nanoTime() - start_nano;
Logger.info("Duration for {} iterations: {}s", Integer.valueOf(iterations),
String.format("%.4f", Float.valueOf(((float) duration_ns) / 1000 / 1000 / 1000)));
}
}
}
}