Raspberry PiのUARTで、non-standardなbaud rateを設定する

# きっかけ
[前回](http://qiita.com/air-gh/items/678c162d36ad2b1547dd)、FTDIのUSB-UART変換で、100Kbps対応ができたが、やっぱり当初の目的だったRaspberry Pi(以下RPi)でも、non-standardなbaud rateを実現したい。

# 下調べ
## ググる
前回も参照した[こちら](https://www.raspberrypi.org/forums/viewtopic.php?f=28&t=77275)によると、`init_uart_clock`でクロックの設定ができるらしい。が、sttyは

```shell-session
$ sudo stty -F /dev/ttyAMA0 100000
stty: invalid argument '100000'
```

と、失敗。
さらにこの`init_uart_clock`でググると、他にも`init_uart_baud`や、/boot/cmdline.txt(?)に`bcm2708.uart_clock`追加、などの情報もある。

いろいろ試してオシロで測ったが、どれでやっても実際のbaud rateには変化が無い。
Raspbianは2016-02-26で、kernelは4.1だ。

## sttyのbaud rate指定
sttyのbaud rateにはどんな値が設定できるのか。[coreutils](http://www.gnu.org/software/coreutils/coreutils.html)のソースを読む。
mainのコマンドラインパーサーには、

```c:coreutils-8.25/src/stty.c
          else if (string_to_baud (arg) != (speed_t) -1)
```

とある。さらに、この`string_to_baud`は、

```c:coreutils-8.25/src/stty.c
string_to_baud (const char *arg)
{
  int i;

  for (i = 0; speeds[i].string != NULL; ++i)
    if (STREQ (arg, speeds[i].string))
      return speeds[i].speed;
  return (speed_t) -1;
}
```

となっており、`speeds[i]`がポイントか。

```c:coreutils-8.25/src/stty.c
static struct speed_map const speeds[] =
{
  {"0", B0, 0},
  {"50", B50, 50},
  {"75", B75, 75},
```

(以下略)となっており、standardなbaud rateがテーブルになっているように見える。

## kernelのbaud rate指定
```c:linux/drivers/tty/tty_ioctl.c
static const speed_t baud_table[] = {
        0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
        9600, 19200, 38400, 57600, 115200, 230400, 460800,
```

(以下略)となっており、sttyと同じようなテーブルが定義されているように見える。

これでは、non-standardなbaud rateをstty経由でkernelに設定することは難しそうだ。


## RPiのUARTドライバー
そもそも、clockを変えれば、実際のbaud rateに変化があってもいいじゃないか。君は何故変化が無いんだ。

```c:linux/drivers/tty/serial/amba-pl011.c
        /* Set baud rate */
        writew(quot & 0x3f, port->membase + UART011_FBRD);
        writew(quot >> 6, port->membase + UART011_IBRD);
```

で、レジスターにbaud rateを設定している。`quot`は、

```c:linux/drivers/tty/serial/amba-pl011.c
        if (baud > port->uartclk/16)
                quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud);
        else
                quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud);
```

で、baudに応じた適切な値が設定される。baudは、

```c:linux/drivers/tty/serial/amba-pl011.c
	/*
	 * Ask the core to calculate the divisor for us.
	 */
	baud = uart_get_baud_rate(port, termios, old, 0,
                   		  port->uartclk / clkdiv);
```

で、簡単に言うと、termiosから`tty_termios_baud_rate()`で持ってきている。

つまり、kernelのcmdlineが何であれ、clockがどうであれ、誰かがsttyなどでbaud rateを指定した初期化を行ってしまえば、standardなbaud rateを元に、適切なレジスタ値が計算されてしまいそうな気がする。
昔は、/etc/inittabの修正などによって、ttyを初期化する人を抹殺して、kernelのcmdlineがそのまま`/dev/ttyAMA0`に適用され続け、うまく行っていたのかもしれない(妄想)。ちなみにRaspbianは既にsystemdだった。

## setserial
### setserialによる設定
FTDIのドライバへは、setserialでnon-standardなbaud rateを設定できた。これは、どんな仕組みなのか。

```c:setserial-2.17/setserial.c
        if (ioctl(fd, TIOCSSERIAL, &new_serinfo) < 0) {
	        perror("Cannot set serial info");
                exit(1);
        }
```

となっており、`TIOCSSERIAL`で設定される。

### setserialによる設定取得
試しに、RPi上で、`/dev/ttyAMA0`にsetserialで設定してみる。

```shell-session
$ setserial /dev/ttyAMA0
/dev/ttyAMA0, UART: undefined, Port: 0x0000, IRQ: 83
$ setserial /dev/ttyAMA0 spd_cust
$ setserial /dev/ttyAMA0
/dev/ttyAMA0, UART: undefined, Port: 0x0000, IRQ: 83, Flags: spd_cust
```

君は、spd_custを覚える能力があるではないか。FTDIのドライバーではなく、ARMのUARTドライバーなのに。
設定取得はどうなっているのか。kernelの`TIOCGSERIAL`の実装を追う。

```c:linux/drivers/tty/serial/serial_core.c
static void do_uart_get_info(struct tty_port *port,
                        struct serial_struct *retinfo)
{
        struct uart_state *state = container_of(port, struct uart_state, port);
        struct uart_port *uport = state->uart_port;

        memset(retinfo, 0, sizeof(*retinfo));
:
        retinfo->flags      = uport->flags;
:
        retinfo->baud_base          = uport->uartclk / 16;
:
        retinfo->custom_divisor  = uport->custom_divisor;
```

(:=中略)`flags`も`custom_divisor`も、`struct uart_port`の情報を元にしていそうじゃないか。
つまり、`struct uart_port`の`flags`や`custom_divisor`なら、setserialから設定できそうじゃないか。しかも、`uartclk`なんていうclockそのままっぽいメンバーも、`struct uart_port`に居るじゃないか。

# 設計
- ユーザーは、前回と同じように、setserialからspd_custとdivisorを設定する
- UARTドライバーは、setserialから設定された値を`struct uart_port`で参照して、non-standardなbaud rateを計算する
- non-standardなbaud rateを元に、既存のドライバーの処理で、レジスター値の計算等を行う

# 実装
```c:linux/drivers/tty/serial/amba-pl011.c
        /*
         * Ask the core to calculate the divisor for us.
         */
        baud = uart_get_baud_rate(port, termios, old, 0,
                                  port->uartclk / clkdiv);
#if 1
        if (baud == 38400 &&
            ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST) &&
             (port->custom_divisor)) {
                baud = port->uartclk / port->custom_divisor;
        }
#endif
```

`#if 1`〜`#endif`が差分で、ftdi_sio.cのコードを元にしている。ただ、このブロックで参照できるのは`struct uart_port`なので、これにあわせてメンバーを調整している。
serial_core.cによると、`baud_base = uport->uartclk / 16`なので、`port->uartclk * 16`のほうが良いのかもしれないが、その時は`divisor`も16倍することになる。まぁこのままでいいや。
`port->uartclk`には、一番最初に出てきた`init_uart_clock`の値が入っていることを期待している(ぶっつけ本番未確認)。

# 確認
## kernelのbuild
本家には[こんなに丁寧なドキュメント](https://www.raspberrypi.org/documentation/linux/kernel/building.md)がある。何も困らなかった。

## init_uart_clockの変更
```ini:/boot/config.txt
init_uart_clock=1600000
```

編集後のrebootで反映される模様。編集後にkernelを置き換えると、設定がリセットされてしまうようなので注意。

```shell-session:dmesg
[    0.132315] bcm2708.uart_clock = 1600000
```

## 設定
```shell-session
# stty -F /dev/ttyAMA0 38400
# setserial /dev/ttyAMA0 spd_cust divisor 16
# echo -n U > /dev/ttyAMA0
```

1600000 / 16 = 100000 = 100Kbps、になるはず…。

## 測定
<img width="1090" alt="100KHz-RPi.png" src="https://qiita-image-store.s3.amazonaws.com/0/104225/e02217bb-a825-f24c-06ed-f4fee626d476.png">

# 蛇足
当初、devmem2で/dev/memをmmapし、ユーザー空間からレジスター設定しようと思ったが、設定手順や計算が面倒そうだったので、ドライバーに押し込んでしまった。