Orange Pi 2G-IOTのGPIOをフル活用する

# はじめに
Orange Piの中でも2G-IOTはちょっと特殊な扱いで、まずCPUからして他のシリーズとは全然毛色の異なるRDA8810。H3などとは違いCPUについての情報がほとんどありません。唯一の手掛かりはカーネルのソースのみ。しかも1コアなので貧弱。とは言え、安価なラインながらもNANDフラッシュを内蔵していて数秒でLinuxが起動するのは魅力的です。まぁ、これも非公式対応（[参考ページ](https://surfero.blogspot.jp/2017/09/orange-pi-iot-2g-flashear-memoria-nand.html)、ただしスペイン語）で、オフィシャルにはNANDからはAndroidしか起動しないのですが。そして、もう1つの弱点がGPIO。WiringPiなどの対応も不完全で、使えるピン数も少ない。ところが、これも頑張れば28個くらいまで使えることがわかったので、それについてのメモになります。

# ピンアウト
## 標準設定
| GPIO# | Function         | # |   | # | Function         | GPIO# |
|------:|-----------------:|--:|---|:--|:-----------------|:------|
|       | 2.8V             |  1|   |2  | 5V               |       |
|       | /dev/i2c.0 - SDA |  3|   |4  | 5V               |       |
|       | /dev/i2c.0 - SCL |  5|   |6  | GND              |       |
| 56    | GPIO B24         |  7|   |8  | /dev/ttyS2 - TxD |       |
|       | GND              |  9|   |10 | /dev/ttyS2 - RxD |       |
|       | /dev/ttyS1 - RxD | 11|   |12 | GPIO B5               | 37    |
|       | /dev/ttyS1 - TxD | 13|   |14 | GND              |       |
|       | /dev/ttyS1 - CTS | 15|   |16 | GPIO C5          | 101   |
|       | 2.8V             | 17|   |18 | GPIO C25         | 121   |
| 4     | SPI2 - MOSI/DI   | 19|   |20 | GND              |       |
| 3     | SPI2 - MISO/DIO  | 21|   |22 | /dev/ttyS1 - RTS |       |
| 2     | SPI2 - SCLK/CLK  | 23|   |24 | SPI2 - CS0       | 5     |
|       | GND              | 25|   |26 | SPI2 - CS1       | 6     |
| 1     | I2C2 - SDA       | 27|   |28 | I2C2 - SCL       | 0     |
| 122   | GPIO C26         | 29|   |30 | GND              |       |
| 123   | GPIO C27         | 31|   |32 | /dev/ttyS2 - RTS |       |
| 124   | GPIO C28         | 33|   |34 | /dev/ttyS2 - CTS |       |
| 125   | GPIO C29         | 35|   |36 | GND              |       |
| 126   | GPIO C30         | 37|   |38 | /dev/i2c-2 - SCL |       |
|       | GND              | 39|   |40 | /dev/i2c-2 - SDA |       |

たったこれだけ。ここに書かれたGPIOは、おおよそ/sys/class/gpioのインタフェースを使って制御できるようです。あと、GPIOはA/B/C系列ありますが、C系列はCPUから見えるI/Oには直接ぶらさがっておらず、速度を要する用途には不向きかもしれません。あと自前で制御しようとすると面倒。そう考えると実質9ピンしかない。

## GPIOに強制的に切り替えた設定
| GPIO# | Function         | # |   | # | Function         | GPIO# |
|------:|-----------------:|--:|---|:--|:-----------------|:------|
|       | 2.8V             |  1|   |2  | 5V               |       |
| 62    | /dev/i2c.0 - SDA |  3|   |4  | 5V               |       |
| 63    | /dev/i2c.0 - SCL |  5|   |6  | GND              |       |
| 56    | GPIO B24         |  7|   |8  | /dev/ttyS2 - TxD | 72    |
|       | GND              |  9|   |10 | /dev/ttyS2 - RxD | 71    |
| 70    | /dev/ttyS1 - RxD | 11|   |12 | GPIO B5               | 37    |
| 14    | /dev/ttyS1 - TxD | 13|   |14 | GND              |       |
| 15    | /dev/ttyS1 - CTS | 15|   |16 | GPIO C5          | 101   |
|       | 2.8V             | 17|   |18 | GPIO C25         | 121   |
| 4     | SPI2 - MOSI/DI   | 19|   |20 | GND              |       |
| 3     | SPI2 - MISO/DIO  | 21|   |22 | /dev/ttyS1 - RTS | 16    |
| 2     | SPI2 - SCLK/CLK  | 23|   |24 | SPI2 - CS0       | 5     |
|       | GND              | 25|   |26 | SPI2 - CS1       | 6     |
| 1     | I2C2 - SDA       | 27|   |28 | I2C2 - SCL       | 0     |
| 122   | GPIO C26         | 29|   |30 | GND              |       |
| 123   | GPIO C27         | 31|   |32 | /dev/ttyS2 - RTS | 41    |
| 124   | GPIO C28         | 33|   |34 | /dev/ttyS2 - CTS | 40    |
| 125   | GPIO C29         | 35|   |36 | GND              |       |
| 126   | GPIO C30         | 37|   |38 | /dev/i2c-2 - SCL | 38    |
|       | GND              | 39|   |40 | /dev/i2c-2 - SDA | 39    |

こんな感じで、実は電源以外は強制的にGPIOに切り替え可能。ただし、デバイスファイル名が書いてあるところなんかはドライバが見てたりするので取り扱い注意。うっかり競合すると最悪panicする事もあるかもしれません。

# 制御方法
## GPIOの有効化
以下のレジスタを使って制御できます。

| 物理アドレス | 名前           | 機能            |
|-------------|----------------|----------------|
| 0x11A09008  | BB_GPIO_MODE   | C系列のGPIO設定 |
| 0x11A0900C  | AP_GPIO_A_MODE | A系列のGPIO設定 |
| 0x11A09010  | AP_GPIO_B_MODE | B系列のGPIO設定 |

A系列はGPIO#0-31、B系列はGPIO#32-63、C系列はGPIO#96-127に対応します。各GPIO設定はbit Nが各系列のN番目のGPIOの設定となり、1を入れた時にGPIOが有効となります。例えば

```c
*AP_GPIO_A_MODE |= (1 << 14);
*AP_GPIO_B_MODE |= (1 << 6);
```
などとすれば、GPIO A14がpin 15で、GPIO B6がpin 38で有効になります。

## GPIOの制御
上記のレジスタから強制的に有効化してあげたGPIOは、それだけで/sys/class/gpioから使えるようです。また以下のレジスタを使い直接制御する事も可能です。

| GPIO系列 | ベースアドレス |
|----------|--------------|
| GPIO A   | 0x20930000   |
| GPIO B   | 0x20931000   |

| オフセット | 名前         | 機能                                 |
|-----------|--------------|-------------------------------------|
| +0x00     | OEN_VAL      | 未調査                               |
| +0x04     | OEN_SET_OUT  | 対応するGPIOをアウトプットモードに設定 |
| +0x08     | OEN_SET_IN   | 対応するGPIOをインプットモードに設定   |
| +0x0C     | VAL          | GPIOの入力値読み取り                  |
| +0x10     | SET          | 対応するGPIOをHIGHに設定              |
| +0x14     | CLR          | 対応するGPIOをLOWに設定               |
| +0x18     | INT_CTRL_SET | 未調査                               |
| +0x1C     | INT_CTRL_CLR | 未調査                               |
| +0x20     | INT_CLR      | 未調査                               |
| +0x24     | INT_STATUS   | 未調査                               |
| +0x28     | CHG_CTRL     | 未調査                               |
| +0x2C     | CHG_CMD      | 未調査                               |

OEN_SET_OUT/OEN_SET_INは書き込んだ際に立っていたbitに対応するGPIOがそれぞれアウトプットモード、インプットモードに切り替わります。SET/CLRも同様に立っていたbitに対応するGPIOの出力がそれぞれHIGH/LOWに切り替わります。

# 物理レジスタへのアクセス
[Orange Pi Zero Armbian導入後の設定とmmapなLチカ](https://qiita.com/toyoshim/items/f097579a032af6047243)で紹介した/dev/memをmmapする方法が使えます。

```c
#define RDA_CONFIG_REGS 0x11a09000

volatile struct config_regs {
  uint32_t chip_id;
  uint32_t build_version;
  uint32_t bb_gpio_mode;
  uint32_t ap_gpio_a_mode;
  uint32_t ap_gpio_b_mode;
  uint32_t ap_gpio_d_mode;
  // ...
}* cfg;

#define GPIOA_BASE 0x20930000

volatile struct pio_cfg {
  uint32_t oen_val;
  uint32_t oen_set_out;
  uint32_t oen_set_in;
  uint32_t val;
  uint32_t set;
  uint32_t clr;
  // ...
}* pa;

int main(int argc, char** argv) {
  int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
  if (mem_fd < 0) {
    perror("can not open /dev/mem");
    exit(EXIT_FAILURE);
  }

  int prot = PROT_READ | PROT_WRITE;
  size_t size = sysconf(_SC_PAGE_SIZE);
  cfg = mmap(NULL, size, prot, MAP_SHARED, mem_fd, RDA_CONFIG_REGS);

  cfg->ap_gpio_a_mode |= (1 << 14) | (1 << 15);  // Enable GPIO 14, 15

  pa = mmap(NULL, size, prot, MAP_SHARED, mem_fd, GPIOA_BASE);

  pa->oen_set_out = 1 << 15;  // Setup GPIO 15 as output pin
  pa->clr = 1 << 15;  // Drive GPIO 15 as LOW
  sleep(1);
  pa->set = 1 << 15;  // Drive GPIO 15 as HIGH
  return 0;
}
```

# まとめ
という事で、少々乱暴ですがI/O空間をマップしてあげて内部レジスタを叩いてあげる事で、ほぼ全てのピンがGPIOとして利用できるようになります。ただし利用に際しては、くれぐれもドライバなどと競合しないよう注意が必要です。