© 2023 DiyTronic

Как запустить UART на Banana Pi BPI-M5

По ходу работы над очередной поделкой, для которой я воспользовался завалявшейся у меня платой Banana Pi BPI-M5, мне потребовалось подключить к ней внешнее устройство по UART.

Вроде бы ничего хитрого, но неожиданно получил геморрой на ровном месте, в связи с чем пришлось углубиться в дебри маппинга пинов к устройствам чипа на плате.

Собственно этой информацией я и хочу поделиться, так как такое может возникнуть в любой момент, а нормальной информации по теме не так много. Статья получилась довольно занудной и сумбурной. Но уж как умею.

Итак Banana Pi BPI-M5. Я в курсе, что это довольно ублюдская поделка с кривой поддержкой и косяками, но что есть то есть.

На плату я решил накатить свой любимый Armbian, который тоже не отличается стабильностью, но для китайских плат нет ничего стабильного, а Raspberry PI я не хочу использовать в силу ряда причин.
Так что Armbian. Образ есть на сайте Armbian https://www.armbian.com/bananapi-m5/. Каких-то затруднений с установкой не было.

Где мой UART?

На сайте с документацией есть распиновка, согласно которой у нас есть два UART-а.
Один отладочный для вывода сообщений при загрузке, а второй выведен на гребёнку GPIO пинов (UART_A) и типа может быть настроен по своему усмотрению — он то мне и нужен.

По умолчанию работает только отладочный UART и это нормально. Для подключения дополнительного оборудования у нас есть так называемые оверлеи, которые можно или прописать в файл /boot/armbianEnv.txt или воспользоваться программой armbian-config, в меню которой System -> Hardware мы можем увидеть такую картину.

Вроде всё нормально, правда гложут сомнения какого чорта тут делает uartC. И как оказалось сомнения были не напрасны — действительно ни хрена не работает даже с включёнными оверлеями.

Поищем что-ли в логах загрузки.

# dmesg | grep uart
ff803000.serial: ttyAML0 at MMIO 0xff803000 (irq = 14, base_baud = 1500000) is a meson_uart

Только одинокий ttyAML0. Это наш отладочный друг. Ни второго и ни уж тем более 3-го не видать.

Погружаемся в оверлеи

На самом деле это просто devicetree файлы с настройками устройств, применяемыми при загрузке. Эта штука работает в каждом компьютере (в частности я патчил свой ноутбук таким образом для более корректной работы в Linux) и кстати любимый мной ZephyrRTOS так-же использует эту систему.

Но вернёмся к нашей ситуации. У нас эти оверлеи лежат тут:

# ls -la /boot/dtb/amlogic/overlay/
total 44
drwxr-xr-x 2 root root 4096 Jan  7 21:27 .
drwxr-xr-x 3 root root 4096 Oct 20 12:00 ..
-rwxr-xr-x 1 root root  232 Oct 20 12:00 meson-fixup.scr
-rwxr-xr-x 1 root root  377 Oct 20 12:00 meson-g12-gxl-cma-pool-896MB.dtbo
-rwxr-xr-x 1 root root  343 Oct 20 12:00 meson-i2cA.dtbo
-rwxr-xr-x 1 root root  343 Oct 20 12:00 meson-i2cB.dtbo
-rwxr-xr-x 1 root root  238 Oct 20 12:00 meson-uartA.dtbo  # <==== вот наш пациент
-rwxr-xr-x 1 root root  238 Oct 20 12:00 meson-uartC.dtbo
-rwxr-xr-x 1 root root  759 Oct 20 12:00 meson-w1AB-gpio.dtbo
-rwxr-xr-x 1 root root  490 Oct 20 12:00 meson-w1-gpio.dtbo
-rwxr-xr-x 1 root root  339 Oct 20 12:00 README.meson-overlays

Но они в скомпилированом формате. Чтобы его декомпилировать воспользуемся командой dtc

# dtc -I dtb -O dts /boot/dtb/amlogic/overlay/meson-uartA.dtbo
/dts-v1/;

/ {
        compatible = "amlogic,meson-gxbb";

        fragment@0 {
                target-path = "/soc/bus@c1100000/serial@84c0";

                __overlay__ {
                        status = "okay";
                };
        };
};

Ну и для интереса глянем, что это за девайс такой. Для этого залезем в живой devicetree устройства, расположенный в файловой системе в папке/sys/firmware/devicetree/base/

# ls -la /sys/firmware/devicetree/base/soc
...
drwxr-xr-x 16 root root  0 Jan  6 22:17  bus@ff600000
drwxr-xr-x 14 root root  0 Jan  6 22:17  bus@ff800000
drwxr-xr-x 19 root root  0 Jan  6 22:17  bus@ffd00000
...

Как видим никакого bus@c1100000 у нас нет и в помине. В общем не буду томить — оверлеи в данной плате от Odroid C4. Видимо разрабы Armbian не стали заморачиваться и просто взяли оверлеи от похожего устройства на этом же чипе. Но не фортануло.

Ну что — изучим что же есть у нас.

# ls -ls /sys/firmware/devicetree/base/soc/bus@ff800000/
...
0 drwxr-xr-x 2 root root  0 Jan  6 22:17  serial@3000
0 drwxr-xr-x 2 root root  0 Jan  6 22:17  serial@4000
...

Уже что-то — есть два устройства с подозрительным именем serial

# ls -ls /sys/firmware/devicetree/base/soc/bus@ffd00000
...
0 drwxr-xr-x 2 root root  0 Jan  6 22:17  serial@22000
0 drwxr-xr-x 2 root root  0 Jan  6 22:17  serial@23000
0 drwxr-xr-x 2 root root  0 Jan  6 22:17  serial@24000
...

Ну и три штуки по этому адресу.

В общем надо разобраться что тут что.

Документация к чипу

В нашем случае это Amlogic S905×3 datasheet

Ищем раздел 13.5 Universal Asynchronious Receiver And Transmitter и оттуда мы узнаём, что у нас и правда 5 UART-ов.

Так-же оттуда мы узнаём, что 3 обычных UART-а у нас расположены по базовому адресу 0xffd00000.

Кроме того у нас есть 2 always on UART-а. Скажем прямо — негусто.

Исходники Linux

Далее я полез в исходники dts файлов для данной платы.

Вот родной dts файл для Banana Pi BPI-M5 meson-sm1-bananapi-m5.dts

aliases {
  serial0 = &uart_AO;
};

&uart_AO {
	status = "okay"; /* uart_AO включается */
};

В котором у нас есть ссылка на некий uart_AO, который очень напоминает нам наш единственный доступный в системе дебаговый UART. То есть dts для Banana Pi BPI-M5 просто включает uart_AO. Осталось найти где этот uart_AO описан, т. к. здесь просто ссылка на него.

Ну и у нас нет остальных UART-ов, поэтому открываем по очереди файлы из include и в конце концов натыкаемся на россыпь UART-ов в файле meson-g12-common.dtsi.

aobus: bus@ff800000 {          
    uart_AO: serial@3000 { /* <=== этот адрес мы видели в логе загрузки */
      ...
      status = "disabled";
    };

    uart_AO_B: serial@4000 {
      ...
      status = "disabled";
    };
}

cbus: bus@ffd00000 {
    uart_C: serial@22000 {
      ...
      status = "disabled";
    };

    uart_B: serial@23000 {
      ...
      status = "disabled";
    };

    uart_A: serial@24000 {
      ...
      fifo-size = <128>; /* в доке к чипу упоминалось что UART_A имеет буффер в 128k */
      status = "disabled";
    };
}

Видим, что количество UART-ов и адреса шин подозрительно совпадают с тем, что мы обнаружили изучая devicetree работающего устройства.

Так же видим, что все устройства в состоянии disabled. Включен (см. выше про meson-sm1-bananapi-m5.dts) только uart_AO который имеет адрес aobus: bus@ff800000 serial@3000, то есть то самое MMIO 0xff803000 из лога загрузки. Короче это точно наш дебаговый UART.

Надо попробовать включить uart_A. Для этого сохраняем декомпилированный оверлей в файл с расширением dts и меняем там адрес UART-а на обнаруженный нами, то есть получим что-то вот такое

# dtc -I dtb -O dts -f /boot/dtb/amlogic/overlay/meson-uartA.dtbo -o /root/meson-uartA.dts

Далее редактируем meson-uartA.dts

/dts-v1/;
/plugin/;  /* <=== это надо добавить т.к. у нас оверлей */

/ {
        compatible = "amlogic,meson-gxbb";

        fragment@0 {
                target-path = "/soc/bus@ffd00000/serial@24000";  /* <=== меняем адрес */

                __overlay__ {
                        status = "okay";
                };
        };
};

И устанавливаем, предварительно удалив старые uart оверлеи.

# armbian-add-overlay meson-uartA.dts
Compiling the overlay
Copying the compiled overlay file to /boot/overlay-user/
Reboot is required to apply the changes

Как вариант с помощью dtc можно скомпилировать и подменить файлы вручную, но так проще.

После перезагрузки видим 2 устройства. Работает!

% sudo dmesg | grep _uart
ff803000.serial: ttyAML0 at MMIO 0xff803000 (irq = 14, base_baud = 1500000) is a meson_uart
ffd24000.serial: ttyAML1 at MMIO 0xffd24000 (irq = 15, base_baud = 1500000) is a meson_uart

Но радость была недолгой. При подключении к порту реального устройства оказалось, что обмен идёт только в одну сторону. Короче RX работает, а TX — нет.

Разборки с пинами

Очевидно, что какая-то проблема с настройкой пинов. Подключаемая железка гарантировано работала. На пины я убил 3 вечера.

Проверим, что у нас с пинами. По счастью эта информация есть в нашем текущем devicetree

Итак начнём. Поищем какие пины и как назначены нашим устройствам. Конкретно нас интересует UART_A, который как мы выяснили имеет адрес ffd24000.

# cat /sys/kernel/debug/pinctrl/pinctrl-maps | grep ffd24000 -A 8
device ffd24000.serial
state default
type MUX_GROUP (2)
controlling device ff634400.bus:pinctrl@40  # <== вот это нам и надо
group uart_a_tx                             # <== и вот это
function uart_a

device ffd24000.serial
state default
type CONFIGS_GROUP (4)
controlling device ff634400.bus:pinctrl@40
group uart_a_tx
config 00000001

device ffd24000.serial
state default
type MUX_GROUP (2)
controlling device ff634400.bus:pinctrl@40
group uart_a_rx
function uart_a

device ffd24000.serial
state default
type CONFIGS_GROUP (4)
controlling device ff634400.bus:pinctrl@40
group uart_a_rx
config 00000001

device ffd24000.serial
state default
type MUX_GROUP (2)
controlling device ff634400.bus:pinctrl@40
group uart_a_tx
function uart_a

device ffd24000.serial
state default
type CONFIGS_GROUP (4)
controlling device ff634400.bus:pinctrl@40
group uart_a_tx
config 000fa00a
config 0000000c
config 00000112

Теперь мы знаем адрес устройства на шине, которое заведует нужными нам пинами и конкретные группы пинов. Теперь лезем обратно в dts meson-g12-common.dtsi и ищем это устройство.

soc {
	apb: bus@ff600000 {    /* <=== вот адрес шины */
    ...
	periphs: bus@34400 { /* <=== вот адрес внутри шины */
      ...
		periphs_pinctrl: pinctrl@40 { /* <=== устройство */
        ...
			uart_a_pins: uart-a {
				mux {
					groups = "uart_a_tx", "uart_a_rx"; /* <==== пины */
					function = "uart_a";
					bias-disable;
				};
			};

И что же мы тут видим? А видим мы, что и TX и RX объединены в одну группу и имеют общие настройки, что довольно странно.

Поищем ещё поглубже

# ls -la /sys/kernel/debug/pinctrl
total 0
drwxr-xr-x  2 ff634400.bus:pinctrl@40-pinctrl-meson  # <== вот эта папка что-то напоминает
drwxr-xr-x  2 ff800000.sys-ctrl:pinctrl@14-pinctrl-meson
-r--r--r--  1 pinctrl-devices
-r--r--r--  1 pinctrl-handles
-r--r--r--  1 pinctrl-maps  # <== это мы только что смотрели

Нас интересует файл pinconf-pins

# cat /sys/kernel/debug/pinctrl/ff634400.bus:pinctrl@40-pinctrl-meson/pinconf-pins | grep GPIOX_1
...
pin 77 (GPIOX_12): input bias disabled, output drive strength (2500 uA)
pin 78 (GPIOX_13): input bias disabled, output drive strength (2500 uA)
...

GPIOX_12 и GPIOX_13 упоминаются в распиновке по ссылке выше как соответственно UART_A_TX и UART_A_RX.

Вроде и всё бы ничего, но некоторые другие пины имеют среди прочего output enabled. Видимо стоит это как-то указать. Ну и забегая вперёд кроме добавления TX пину возможности работать на вывод, нужно ещё убрать ему возможность работать на вход. Оба условия оказались необходимы, иначе ничего не работало.

Полный набор возможных опций для пинов можно посмотреть тут pinconf-generic.c

В итоге наш финальный оверлей принял следующий вид.

/dts-v1/;
/plugin/;

/ {
    compatible = "amlogic,meson-gxbb";

    fragment@0 {
        target-path = "/soc/bus@ff600000/bus@34400/pinctrl@40"; /* для данного устройства */

        __overlay__ {
            uart_a_tx_pin: uart-a-tx { /* добавляем настройки пинов */
                mux {
                    groups = "uart_a_tx"; /* для группы пинов "uart_a_tx"
                    function = "uart_a";  /* это как было в оригинальной группе */
                    input-disable;        /* для TX выключаем input - обязательно! */
                    output-enable;        /* для TX включаем output - обязательно! */
                    drive-strength-microamp = <4000>; /* это наверно необязательно */
                };
            };
        };
    };

    fragment@1 {
        target-path = "/soc/bus@ffd00000/serial@24000";  /* <=== меняем адрес (см. выше) */

        __overlay__ {
            status = "okay";                             /* включаем UART */
            pinctrl-0 = <&uart_a_pins &uart_a_tx_pin>;   /* добавляем uart_a_tx_pin из блока выше */
            pinctrl-names = "default";                   /* это возможно необязательно */
        };
    };
};

Устанавливаем этот оверлей и после перезагрузки получаем полноценно работающий UART_A согласно официальной распиновке.

Настройки пинов при этом приняли следующий вид. Видим, что у TX (GPIOX_12) появился output-enabled

# cat /sys/kernel/debug/pinctrl/ff634400.bus:pinctrl@40-pinctrl-meson/pinconf-pins | grep GPIOX_1
...
pin 77 (GPIOX_12): input bias disabled, output drive strength (4000 uA), output enabled, pin output (1 level)
pin 78 (GPIOX_13): input bias disabled, output drive strength (2500 uA)
...

Итого

После всех злоключений удалось таки запустить этот чортов UART и успешно подключить к нему железяку. Я счастлив, особенно учитывая количество убитого времени. Надеюсь кому-нибудь будет полезно.

Комментарии