AIO-3399J Buy Specs

Uses RK3399 Six-Core ( A72x2+A53x4 ) 64-Bit CPU, up to 2.0Ghz , integrated Quad-Core Mali-T864 GPU. Onboard M.2 PCIe, 4G LTE interface, multiple display interface and serial port. Support Android/Linux/Ubuntu system.


Update time:2018-04-16 Views:3180


GPIO, short for General-Purpose Input/Output, is a flexible software-controlled digital signal. RK3399 have 5 banks:GPIO0,GPIO1, ..., GPIO4, Each bit of the bank is numbered with A0~A7, B0~B7, C0~C7, D0~D7 (Not all banks have full numbers, for example, GPIO4 only has C0~C7, D0~D2). All the GPIO in the initial state after power are input mode, and it can be set to pull-up or pull-down by software, it also can be set to interrupt pin. In addition, their drive strength is programmable. Besides the general-purpose input/output, GPIO may be multiplexed with other functions. For example, GPIO2_A2, has the following extra functions:

  • GPIO2_A2

  • CIF_D2

The drive current, pull up/down and reset state of all the gpios are not neccessary the same. Please refer section titled "Chapter 10 GPIO" in RK3399 Datasheet. RK3399's GPIO driver is implemented in the following pinctrl file:


The core is to fill in the methods and parameters of the GPIO bank and call gpiochip_add to the kernel.
AIO-3399J development board is convenient for user development and does not lead to common GPIO ports, but other interfaces can be used as GPIO.

 For example, the LED extension interface can be used as a general GPIO port when it is not used, and its corresponding pins are as follows:


In this article, I used LED4(GPIO0_B4) and LCD_RST(GPIO4_D5) as an example to write a simple driver to operate GPIO. The example path in the SDK is


Next I will describe how to operate GPIO


To begin with, you need to add resource description to the dts (Device Tree) file kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly-demo.dtsi:

    gpio_demo: gpio_demo {
        status = "okay";
        compatible = "firefly,rk3399-gpio";
        firefly-gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>;          /* GPIO0_B4 */
        firefly-irq-gpio = <&gpio4 29 IRQ_TYPE_EDGE_RISING>;  /* GPIO4_D5 */                                                                                             

Here, GPIO0_B4 is defined as the general output input port:

firefly-gpio GPIO0_B4

The DTS of AIO-3399J is different from Firefly-RK3288, GPIO0_B4 is described as <&gpio0 12 GPIO_ACTIVE_HIGH>, Where 12 is derived from: 8 + 4 = 12, where 8 is because GPIO0_B4 is a group B belonging to GPIO0, 0 if it is a group A, and 16 if it is a group C, Group D is 24, with this recursive, and 4 because B4 behind 4. GPIO_ACTIVE_LOW means low level voltage is effective for light on. If high level voltage is effective, replace it with GPIO_ACTIVE_HIGH.

Next, add requesting and controlling of GPIO to the device driver:

 static int firefly_gpio_probe(struct platform_device *pdev)
     int ret; int gpio; enum of_gpio_flags flag; 
     struct firefly_gpio_info *gpio_info; struct device_node *firefly_gpio_node = pdev->dev.of_node; 
     printk("Firefly GPIO Test Program Probe\n"); gpio_info = devm_kzalloc(&pdev->dev,sizeof(struct firefly_gpio_info *), GFP_KERNEL); if (!gpio_info) { return -ENOMEM; } 
     gpio = of_get_named_gpio_flags(firefly_gpio_node, "firefly-gpio", 0, &flag); if (!gpio_is_valid(gpio)) { printk("firefly-gpio: %d is invalid\n", gpio); return -ENODEV; } if (gpio_request(gpio, "firefly-gpio")) { printk("gpio %d request failed!\n", gpio); gpio_free(gpio); return -ENODEV; } gpio_info->firefly_gpio = gpio; gpio_info->gpio_enable_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0:1; gpio_direction_output(gpio_info->firefly_gpio, gpio_info->gpio_enable_value); printk("Firefly gpio putout\n"); ...... }

First call of_get_named_gpio_flags to read number and flag of the gpio named "firefly-gpio" and "firefly-irq-gpio". Use gpio_is_valid to check whether this gpio number is valid, then request this gpio from kernel with gpio_request. If errors occur, call gpio_free to free the gpio which was previously requested successfully. Call gpio_direction_output to set gpio in output direction, with high or low output. Here the flag is GPIO_ACTIVE_LOW, in order to turn on the led, write 0 to it. If you want to read from gpio, you need to set it in input direction before reading the value:

int val;
val = gpio_get_value(your_gpio);

Here are the definition of some commonly used GPIO APIs:

 #include <linux/gpio.h>
 #include <linux/of_gpio.h>  enum of_gpio_flags {OF_GPIO_ACTIVE_LOW = 0x1,
 };  int of_get_named_gpio_flags(struct device_node *np, const char *propname,   	                   int index, enum of_gpio_flags *flags);  int gpio_is_valid(int gpio);  int gpio_request(unsigned gpio, const char *label);  void gpio_free(unsigned gpio);  int gpio_direction_input(int gpio);  int gpio_direction_output(int gpio, int v)


The Firefly example program also includes an interrupt pin, and the Interrupts of the GPIO port are analogous to GPIO Input/Output, you need to add resource description to the dts (Device Tree) file first.

  gpio {
      compatible = "firefly-gpio";
      firefly-irq-gpio = <&gpio4 29 IRQ_TYPE_EDGE_RISING>;  /* GPIO4_D5 */

IRQ_TYPE_EDGE_RISING indicates that the interrupt is triggered by a rising edge, the interrupt function can be triggered when a rising edge signal is received on this pin. Here, it may be configured as follows:

IRQ_TYPE_NONE         //Default, no defined interrupt trigger type
IRQ_TYPE_EDGE_RISING  //Rising edge triggers                                                                                                                                  
IRQ_TYPE_EDGE_FALLING //Falling edge trigger
IRQ_TYPE_EDGE_BOTH    //Both rising and falling edges trigger
IRQ_TYPE_LEVEL_HIGH   //High level trigger
IRQ_TYPE_LEVEL_LOW    //Low level trigger

Then the probe function to resolve the DTS, and then apply for interrupt registration, the code is as follows:

 static int firefly_gpio_probe(struct platform_device *pdev)
     int ret; int gpio; enum of_gpio_flags flag; 
     struct firefly_gpio_info *gpio_info; struct device_node *firefly_gpio_node = pdev->dev.of_node; 
     ......     gpio_info->firefly_irq_gpio = gpio; gpio_info->firefly_irq_mode = flag; gpio_info->firefly_irq = gpio_to_irq(gpio_info->firefly_irq_gpio); if (gpio_info->firefly_irq) { if (gpio_request(gpio, "firefly-irq-gpio")) { printk("gpio %d request failed!\n", gpio); gpio_free(gpio); return IRQ_NONE; } 
         ret = request_irq(gpio_info->firefly_irq, firefly_gpio_irq, flag, "firefly-gpio", gpio_info); if (ret != 0) free_irq(gpio_info->firefly_irq, gpio_info); dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret); } return 0;
 static irqreturn_t firefly_gpio_irq(int irq, void *dev_id) //Interrupt function
 { printk("Enter firefly gpio irq test program!\n"); return IRQ_HANDLED;

Call gpio_to_irq to convert the PIN value of the GPIO to the corresponding IRQ value, and call gpio_request request to use the IO port, then call request_irq to request an interrupt, if it fails to call free_irq release. gpio_info-firefly_irq request the hardware interrupt number, firefly_gpio_irq is the interrupt function, gpio_info->firefly_irq_mode is the interrupt handling properties, "firefly-gpio" is the device driver name, gpio_info is the device's device structure


The gpio has multiplexing functions. How to declare it, and how to switch it at the runtime? We take I2C4 for a brief descrition.

Here is what we find in the data sheet:

Pad# func0 func1
I2C4_SDA/GPIO1_B3 gpio1b3 i2c4_sda
I2C4_SCL/GPIO1_B4 gpio1b4 i2c4_scl

In kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi i2c4 is declared as below:

     i2c4: i2c@ff3d0000 { compatible = "rockchip,rk3399-i2c"; reg = <0x0 0xff3d0000 0x0 0x1000>; clocks = <&pmucru SCLK_I2C4_PMU>, <&pmucru PCLK_I2C4_PMU>; clock-names = "i2c", "pclk"; interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH 0>; pinctrl-names = "default", "gpio"; pinctrl-0 = <&i2c4_xfer>; pinctrl-1 = <&i2c4_gpio>;   //The source code is not added here #address-cells = <1>;  #size-cells = <0>;  status = "disabled"; };

Properties prefixed with "pinctrl-" are relative with mutiplexing:

pinctrl-names defines a state name list: default (i2c) and gpio. pinctrl-0 defines the pinctrls which needs to apply in state 0: &i2c4_xfer pinctrl-1 defines the pinctrls which needs to apply in state 1: &i2c4_gpio

These pinctrls are defined in kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi as follows:

     ......     pinctrl: pinctrl { compatible = "rockchip,rk3399-pinctrl"; rockchip,grf = <&grf>; rockchip,pmu = <&pmugrf>; #address-cells = <0x2>; #size-cells = <0x2>; ranges; i2c4 {                                                                                                                                                           
             i2c4_xfer: i2c4-xfer { rockchip,pins = <1 12 RK_FUNC_1 &pcfg_pull_none>, <1 11 RK_FUNC_1 &pcfg_pull_none>; }; i2c4_gpio: i2c4-gpio { rockchip,pins = <1 12 RK_FUNC_GPIO &pcfg_pull_none>, <1 11 RK_FUNC_GPIO &pcfg_pull_none>; };          }; };

RK_FUNC_1,RK_FUNC_GPIO is defined in kernel/include/dt-bindings/pinctrl/rk.h

 #define RK_FUNC_GPIO    0
 #define RK_FUNC_1   1
 #define RK_FUNC_2   2
 #define RK_FUNC_3   3
 #define RK_FUNC_4   4
 #define RK_FUNC_5   5                                                                                                                                                    
 #define RK_FUNC_6   6
 #define RK_FUNC_7   7

In addition, values such as "1 11" and "1 12" have the same encoding rules as described in the previous section "Input Outputs", where "1 11" represents GPIO1_B3 and "1 12" represents GPIO1_B4.

Therefore, when using multiplexing, if you select "default" (I2C function here) state, the kernel will apply pinctrls of i2c4_xfer, which results in switching GPIO1_B3 pin and GPIO1_B4 pin to I2C functions; if you select "gpio" state, the kernel will apply the i2c4_gpio pinctrl, restoring the two pins back to general-purpose input/output function.

We look at the i2c driver kernel/drivers/i2c/busses/i2c-rockchip.c is how to switch multiplexing function:

 static int rockchip_i2c_probe(struct platform_device *pdev)
     struct rockchip_i2c *i2c = NULL; struct resource *res; struct device_node *np = pdev->dev.of_node; int ret;// ...
		i2c->sda_gpio = of_get_gpio(np, 0);if (!gpio_is_valid(i2c->sda_gpio)) {
			dev_err(&pdev->dev, "sda gpio is invalid\n");return -EINVAL;}
		ret = devm_gpio_request(&pdev->dev, i2c->sda_gpio, dev_name(&i2c->;if (ret) {
			dev_err(&pdev->dev, "failed to request sda gpio\n");return ret;}
		i2c->scl_gpio = of_get_gpio(np, 1);if (!gpio_is_valid(i2c->scl_gpio)) {
			dev_err(&pdev->dev, "scl gpio is invalid\n");return -EINVAL;}
		ret = devm_gpio_request(&pdev->dev, i2c->scl_gpio, dev_name(&i2c->;if (ret) {
			dev_err(&pdev->dev, "failed to request scl gpio\n");return ret;}
		i2c->gpio_state = pinctrl_lookup_state(i2c->dev->pins->p, "gpio");if (IS_ERR(i2c->gpio_state)) {
			dev_err(&pdev->dev, "no gpio pinctrl state\n");return PTR_ERR(i2c->gpio_state);}
		pinctrl_select_state(i2c->dev->pins->p, i2c->gpio_state);
		pinctrl_select_state(i2c->dev->pins->p, i2c->dev->pins->default_state);// ...}

First call of_get_gpio to get the gpio pins from the "gpios" list property in the i2c4 node in device tree:

gpios = <&gpio1 GPIO_B3 GPIO_ACTIVE_LOW>, <&gpio1 GPIO_B4 GPIO_ACTIVE_LOW>;

Then call devm_gpio_request request gpio, followed by calling pinctrl_lookup_state to look up the “gpio” state. The "default" state is already saved in i2c->dev-pins->default_state by the kernel device tree parsing.

Finally, call pinctrl_select_state to select between "default" or "gpio" state, which results in the correspoding multiplexing function.

Here are some commonly used multiplexing APIs:

#include <linux/pinctrl/consumer.h> struct device {//...#ifdef CONFIG_PINCTRLstruct dev_pin_info	*pins;#endif//...}; struct dev_pin_info {struct pinctrl *p;struct pinctrl_state *default_state;#ifdef CONFIG_PMstruct pinctrl_state *sleep_state;struct pinctrl_state *idle_state;#endif}; struct pinctrl_state * pinctrl_lookup_state(struct pinctrl *p, const char *name); int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);


In complex SOC systems, the designer typically divides the system's power supply into multiple independent blocks, known as the Power Domain, this has many advantages, such as:

  1. In the IO-Domain DTS node unified configuration voltage domain, do not need to configure each drive once, easy to manage;

  2. In accordance with the practice of Upstream, Upstream after more convenient if necessary;

  3. The IO-Domain driver supports dynamic adjustment of the voltage domain during operation. For example, a Regulator of the PMIC can switch between 1.8v and 3.3v dynamically. Once the Regulator voltage changes, the IO-Domain driver is notified to reset the voltage domain.

The Power Domain Map table and configuration on the Firefly-RK3399 schematic are shown in the following table.


RK3399 SDK schematic can be seen through the voltage domain connection bt656-supply is vcc18_dvp, vcc_io is from the PMIC RK808 VLDO1 out. In DTS which can be found vcc1v8_dvp, bt656-supply = <& vcc18_dvp>.
Other road configuration is similar, it should be noted that if this is the other PMIC, the Regulator is not the same, the specific situation to the actual circuit as the standard.

Debugging method


GPIO debugging has a very useful tool, that is, IO instructions, Firefly-RK3399 Android system default has built-in IO instruction, the use of IO instructions can be read or written in real time the status of each IO port, here a brief IO instructions usage of. First, see the io instructions for help:

#io --help
Unknown option: ?
Raw memory i/o utility - $Revision: 1.5 $

io -v -1|2|4 -r|w [-l <len>] [-f <file>] <addr> [<value>]

   -v         Verbose, asks for confirmation
   -1|2|4     Sets memory access size in bytes (default byte)
   -l <len>   Length in bytes of area to access (defaults to
              one access, or whole file length)
   -r|w       Read from or Write to memory (default read)
   -f <file>  File to write on memory read, or
              to read on memory write
   <addr>     The memory address to access
   <val>      The value to write (implies -w)

   io 0x1000                  Reads one byte from 0x1000
   io 0x1000 0x12             Writes 0x12 to location 0x1000
   io -2 -l 8 0x1000          Reads 8 words from 0x1000
   io -r -f dmp -l 100 200    Reads 100 bytes from addr 200 to file
   io -w -f img 0x10000       Writes the whole of file to memory

Note access size (-1|2|4) does not apply to file based accesses.

From the help you can know, if you want to read or write a register, you can use:

io -4 -r 0x1000 //Reads one byte from 0x1000
io -4 -w 0x1000 0x12 //Writes 0x12 to location 0x1000


  • Check the multiplexing of the GPIO1_B3 pin

  1. From the datasheet found in the base address of the GPIO1 register: 0xff320000

  2. From the datasheet found in the base address of the GPIO1B_IOMUX register: 0x00014

  3. The iomux register address of GPIO1_B3 is "Operational Base"+"offset"=0xff320000+0x00014=0xff320014

  4. Use the following command to see the multiplexing of GPIO1_B3:

# io -4 -r 0xff320014
ff320014:  0000816a
  1. Found this from the datasheet [7:6]:

GPIO1B[3] iomux select  
2'b00: gpio 
2'b01: i2c4sensor_sda 
2'b10: reserved 
2'b11: reserved

so, you can know GPIO1_B3 was used as i2c4sensor_sda

  1. If you want to use it as a GPIO, you can use the following instruction:

# io -4 -w 0xff320014 0x0000812a

GPIO debug interface

Debugfs file system is designed to provide developers with more core data for easy debugging. GPIO debugging here can also use the Debugfs file system, access to more of the kernel information. GPIO in the Debugfs file system interface /sys/kernel/debug/gpio, you can read the interface information

# cat /sys/kernel/debug/gpio
GPIOs 0-31, platform/pinctrl, gpio0:
 gpio-2   (                    |vcc3v3_3g           ) out hi    
 gpio-4   (                    |bt_default_wake_host) in  lo    
 gpio-5   (                    |power               ) in  hi    
 gpio-9   (                    |bt_default_reset    ) out lo    
 gpio-10  (                    |reset               ) out lo    
 gpio-13  (                    |?                   ) out lo    
GPIOs 32-63, platform/pinctrl, gpio1:
 gpio-32  (                    |vcc5v0_host         ) out hi    
 gpio-34  (                    |int-n               ) in  hi    
 gpio-35  (                    |vbus-5v             ) out lo    
 gpio-45  (                    |pmic-hold-gpio      ) out hi    
 gpio-49  (                    |vcc3v3_pcie         ) out hi    
 gpio-54  (                    |mpu6500             ) out hi    
 gpio-56  (                    |pmic-stby-gpio      ) out hi    
GPIOs 64-95, platform/pinctrl, gpio2:
 gpio-83  (                    |bt_default_rts      ) in  hi    
 gpio-90  (                    |bt_default_wake     ) in  lo    
 gpio-91  (                    |?                   ) out hi    
GPIOs 96-127, platform/pinctrl, gpio3:
 gpio-111 (                    |mdio-reset          ) out hi    
GPIOs 128-159, platform/pinctrl, gpio4:
 gpio-149 (                    |hp-con-gpio         ) out lo

From the above information, we can see that the kernel lists the current state of GPIO. Take GPIO0 group as example, gpio-2 (GPIO0_A2) is the power control pin (vcc3v3_3g) of 3G module, and output high level (out hi).


Q1: How do I change the PIN MUX value to a normal GPIO?

A1: When using the GPIO request, the MUX value of the PIN is forced to GPIO, so when using this pin for the GPIO function to ensure that the pin is not used by other modules.

Q2: Why do I read out with the IO command value is always 0x00000000?

A2: If the IO command to read a GPIO register, read out the value is abnormal, 0x00000000 or 0xffffffff, etc., please confirm that the GPIO is not closed CLK. The CLK of the GPIO is controlled by the CRU. You can check whether the CLK is enabled by reading the CRU_CLKGATE_CON * register. If it is not enabled, you can use the io command to set the corresponding register to open the corresponding CLK. After reading the CLK, you can read the correct Of the register value.

Q3: What should I do when I measure the voltage of the PIN pin incorrectly?

A3: If the voltage of this pin is not measured correctly, if the external factor is excluded, you can check whether the voltage source of the pin is correct and the IO-Domain configuration is correct.

Q4: What is the difference between gpio_set_value() and gpio_direction_output()?

A4: If you use the GPIO, do not dynamically switch the input and output, it is recommended to set the GPIO output direction at the beginning, followed by pulling down the use of gpio_set_value() interface, rather than the proposed gpio_direction_output(), because gpio_direction_output interface Mutex locks, there is an error exception to the interrupt context call, and gpio_direction_output does more and more waste than gpio_set_value.