raspberry pi をいじるメモ

[menu]


raspberry pi 4B + ubuntu 64bit 環境を 2TB超えの USB HDD で運用する

[課題]
raspberry pi 4B の ubuntu はイメージとして配布されている。これを USB HDD に書き込むと MBR ディスクとして認識される。そのためこの HDD は 2TB を超える領域が使用不能になる。これを解決する。
また、USB HDD から OS を起動させ、microSD を使用しない環境として構築する。

[必要なもの]

  • raspberry pi os を起動できるメディア (あるいはここを参考に ubuntu にファームウェア用ツールをインストールして作業する)
  • とりあえず ubuntu をインストールする microSD
  • 最終的に ubuntu をインストールして運用するための USB HDD

    [ここでの前提]
    USB HDD は、ブートパーティーションとルートパーティーションの2つのパーティーションを作成して使う場合の記述をする。もっとパーティーションを作成したい場合は好みで変える

    [手順]
    主に以下を参考にする。
    (a) https://denor.jp/raspberry-pi-4%E3%81%A7usb%E6%8E%A5%E7%B6%9Ahdd%E3%81%8B%E3%82%8964%E3%83%93%E3%83%83%E3%83%88ubuntu-20-04-1%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%97%E3%81%A6%E3%81%BF%E3%81%BE%E3%81%97%E3%81%9F
    (b) https://www.raspberrypi.org/forums/viewtopic.php?t=278791

    1. raspberry pi os を起動し、ファームウェアのアップデートを行う。(a) のサイトを参考にして、

      1. 最新版への更新
      2. ブートローダー更新
      3. ブートローダー更新確認

      を行う。ブートローダーの更新では、

        /lib/firmware/raspberrypi/bootloader/critical/pieeprom-2020-09-03.bin
        
      をファイル指定して書き込めばブートローダーのオプションも書き換える必要のないものが設定される。(critical のものも stable のものも同じ)

      https://qiita.com/mt08/items/9799bb7ae760011832dc#%E3%81%9D%E3%81%AE%E4%BB%96 ここの情報を見るかぎり、critical のものが最も安定してるものだと思われる。beta, stable, critical のディレクトリの中身を見れば察しが付く。
      以上で rasberry pi os の役目は終了。

    2. raspberry pi 4B 用 Ubuntu Server 20.04.1 LTS 64bit をダウンロードする。次のサイトから。 https://ubuntu.com/download/raspberry-pi
      また、balenaEtcherraspberry pi imager などを使い、とりあえず microSD に ubuntu-20.04.1-preinstalled-server-arm64+raspi.img を書き込んで OS をインストールする

    3. ubuntu をインストールした microSD で raspberry pi を起動する
      初回ログインは user=ubuntu, password=ubuntu で行う。すると、新規パスワードの設定を求められるので、パスワードを設定して再ログインする。

    4. 最新版にアップデートする

      $ sudo apt update
      $ sudo apt upgrade
      

    5. ここで、USB HDD をコネクタに接続する。デバイス名を確認する。ここでは、microSD が /dev/mmcblk0 、USB HDD が /dev/sda として認識されているとする。

      $ sudo parted -l
      

    6. とりあえず microSD の boot パーティーション(/dev/mmcblk0p1)の情報を確かめておく。すると、2048セクタ〜526335セクタで、256MB のサイズであることがわかる。

      $ sudo fdisk -l /dev/mmcblk0
      

    7. /dev/sda のパーティーションを作成していく。parted コマンドを使う。
      1. gpt ディスクを作成
      2. boot パーティーションの作成。2048セクタ〜526335セクタ の fat32 パーティーションを作成。ワーニングは Ignore で対応。(本当はワーニングに従って設定したほうがパフォーマンスがよくなるのだろうが、今回はとりあえず見送る)
      3. root パーティーションの作成。526336セクタ以降の ext4 パーティーションを作成。ワーニングは、Yes、Ignore で対応。
      4. 作成を確認
      5. 終了

      $ sudo parted /dev/sda   # parted の対話モードに入る
         mklabel gpt
         mkpart primary fat32 2048s 526335s
         mkpart primary ext4 526336s -1s
         p
         q
      

    8. パーティーション作成状況を確認し、フォーマットする。

      $ sudo fdisk -l /dev/sda    # /dev/sda1 と /dev/sda2 が作成されてるのを確認
      $ sudo mkfs.vfat -F 32 -n system-boot /dev/sda1    # boot パーティーションを fat32 でフォーマット。ボリュームラベルは system-boot。ワーニングは無視する
      $ sudo mkfs.ext4 -L writable /dev/sda2    # root パーティーションを ext4 でフォーマット。ボリュームラベルは writable
      

    9. パーティーションのラベルが正しく設定されていることを確認する。

      $ sudo fatlabel /dev/sda1    # system-boot かどうか。細かいメッセージは気にしない
      $ sudo e2label /dev/sda2    # writable かどうか
      

    10. これから microSD の中身を HDD にコピーし、起動できるようにするための作業を行う。とりあえずホームディレクトリにでも適当にディレクトリを二個作成する

      $ cd
      $ mkdir sd_org sd_new   # マウント用のディレクトリを作る
      

    11. boot パーティーションのコピーを行う。microSD と USB HDD の boot パーティーションをマウントして、rsync でコピーする。rsync の使い方は以降を参考にした。https://qiita.com/204504bySE/items/fb4f0a890766c31b662d

      $ sudo mount /dev/mmcblk0p1 sd_org   # sd_org にコピー元をマウント
      $ sudo mount /dev/sda1 sd_new   # sd_new にコピー先をマウント
      $ sudo rsync -aHAXxSvP --numeric-ids sd_org/ sd_new/    # コピーを実行する
      $ sudo umount sd_org         # アンマウントしておく
      $ sudo umount sd_new
      

    12. 続いて root パーティーションのコピーを行う。microSD と USB HDD の root パーティーションをマウントして、rsync でコピーする。

      $ sudo mount /dev/mmcblk0p2 sd_org
      $ sudo mount /dev/sda2 sd_new
      $ sudo rsync -aHAXxSvP --numeric-ids sd_org/ sd_new/
      $ sudo umount sd_org
      $ sudo umount sd_new
      

    13. ディスクのクローンは完了したので、USB HDD から起動できるように設定を行う。(b) を参考に USB HDD のブート設定をいじる。https://www.raspberrypi.org/documentation/configuration/config-txt/boot.mdが参考になる。64bit カーネルは非圧縮でなければならないので展開する。メモリ上、カーネルの後に initrd.img の内容で ramfs を作成する

      $ sudo su -   # めんどくさいのでスーパーユーザーになる
      # mount /dev/sda1 /media   # boot パーティーションを適当なところにマウントする
      # cd /media
      # zcat vmlinuz > vmlinux   # カーネルの展開
      # nano config.txt   # vi なり nano なり、エディタで config.txt を開く
      
      # condig.txt の [pi4] エントリを以下のように書き換えてセーブする
      
      [pi4]
      #kernel=uboot_rpi_4.bin
      max_framebuffers=2
      dtoverlay=vc4-fkms-v3d
      boot_delay
      kernel=vmlinux
      initramfs initrd.img followkernel
      
      

    14. 以上で USB HDD から起動できるが、このままだとカーネルがアップデートしたときに vmlinux を作り直さないといけないので(作り直すのを忘れると起動しない)、自動的に vmlinux を作ってくれるようにする

      1. カーネルのハッシュ値を記録しておき、変化があったら展開するスクリプトを作成する

        # pwd     # /media にいるかどうか確認
        # cat > auto_decompress_kernel   # スクリプト作成。以下をコピペして ^d を押す。
        
        #!/bin/bash -e
        
        #Set Variables
        BTPATH=/boot/firmware
        CKPATH=$BTPATH/vmlinuz
        DKPATH=$BTPATH/vmlinux
        
        #Check if compression needs to be done.
        if [ -e $BTPATH/check.md5 ]; then
        	if md5sum --status --ignore-missing -c $BTPATH/check.md5; then
        	echo -e "\e[32mFiles have not changed, Decompression not needed\e[0m"
        	exit 0
        	else echo -e "\e[31mHash failed, kernel will be compressed\e[0m"
        	fi
        fi
        
        #Backup the old decompressed kernel
        mv $DKPATH $DKPATH.bak
        
        if [ ! $? == 0 ]; then
        	echo -e "\e[31mDECOMPRESSED KERNEL BACKUP FAILED!\e[0m"
        	exit 1
        else 	echo -e "\e[32mDecompressed kernel backup was successful\e[0m"
        fi
        
        #Decompress the new kernel
        echo "Decompressing kernel: "$CKPATH".............."
        
        zcat $CKPATH > $DKPATH
        
        if [ ! $? == 0 ]; then
        	echo -e "\e[31mKERNEL FAILED TO DECOMPRESS!\e[0m"
        	exit 1
        else
        	echo -e "\e[32mKernel Decompressed Succesfully\e[0m"
        fi
        
        #Hash the new kernel for checking
        md5sum $CKPATH $DKPATH > $BTPATH/check.md5
        
        if [ ! $? == 0 ]; then
        	echo -e "\e[31mMD5 GENERATION FAILED!\e[0m"
        	else echo -e "\e[32mMD5 generated Succesfully\e[0m"
        fi
        
        #Exit
        exit 0
        

      2. apt が実行されたときに、上で作成したスクリプトが実行されるようにする

        # cd
        # umount /media
        # mount /dev/sda2 /media # 今度は root パーティーションをいじる
        # cd /media/etc/apt/apt.conf.d/
        # cat > zz_decompress_rpi_kernel  # スクリプト作成。以下をコピペして ^d を押す。
        
        DPkg::Post-Invoke {"/bin/bash /boot/firmware/auto_decompress_kernel"; };
        

    15. 以上で完了。シャットダウンして microSD を抜き、電源の入った USB HDD だけを繋げて raspberry pi を起動する。ちなみにシステムを丸ごとコピーしてるので、ログインパスワードは microSD で起動したとき設定したものになっている。
      # logout
      $ sync
      $ sudo shutdown now
      # このあと microSD を抜いて USB HDD で起動する
      


    raspberry pi 4B + ubuntu 64bit で h264 ハードウェアエンコードする

    [課題]
    ubuntu や raspberry pi os には h264 をハードウェアエンコードできる ffmpeg がパッケージで用意されている(h264_omx オプション)。しかし、64bit OS 環境ではライブラリが足りず、使用できない。 (もしかすると、armコアは 64bit アーキテクチャだが videoコアは 32bit アーキテクチャなせいかもしれない。参考)
    この問題は ffmpeg release 4.3 で別のライブラリにて解決されているのでこれをビルドする。

    [注意]
    行程を思い出しながらメモしているので抜けがあるかも

    [手順]
    主に以下を参考にする。
    (a) https://www.willusher.io/general/2020/11/15/hw-accel-encoding-rpi4
    (b) https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu

    1. ビルドに必要なパッケージをインストールする

      $ sudo apt build-dep ffmpeg
      

      エラーになるときは、sources.list を編集してコメントアウトされてる部分を有効にする


      $ sudo vi /etc/apt/sources.list
      :%s/# deb-src/deb-src/
      :wq
      

      編集したあと、apt update してから再度インストールする


      $ sudo apt update
      $ sudo apt build-dep ffmpeg
      

    2. ffmpeg release4.3 のソースを取得する

      $ git clone --depth 1 --branch release/4.3 https://github.com/FFmpeg/FFmpeg.git
      

    3. configure する。デフォルトだと --prefix で指定したディレクトリ以下に、bin/ lib/ include/ share/ 等のビルド生成物がインストールされる。以下では --bindir で bin/* のインストール先だけ変えている

      $ ./configure --prefix=$HOME/ffmpeg --pkg-config-flags="--static" --bindir=$HOME/bin --extra-cflags="-I$HOME/ffmpeg/include" --extra-ldflags="-L$HOME/ffmpeg/lib" --extra-libs="-lpthread -lm" --disable-debug --enable-gpl --enable-nonfree --arch=aarch64 --enable-libaom --enable-libass --enable-libfdk-aac --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libwebp --enable-libdrm
      

      実行時に hogehoge ライブラリでエラーが出た場合、(b) のサイトを参考にインストールを行う(以下は例)2つくらいやった気がする。


      $ sudo apt-get install hogehoge-dev
      

    4. make と install をする

      $ make install
      

    5. h264_v4l2m2m オプションが正しく動作することを確認する。おおよそ x2 くらいのフレームレートでエンコードできる

      $ ffmpeg -codecs | grep h264
      $ ffmpeg -i test.ts -c:v h264_v4l2m2m -b:v 8000k test.mp4
      

      問題は一つあって、出力 1080p で yadif のフィルターをかけると画像がおかしくなる。720p なら大丈夫。バグかな?


      # ソースが地上波 1920x1080 のとき、
      $ ffmpeg -i test.ts -c:v h264_v4l2m2m -b:v 8000k -vf yadif test.mp4     # 画像がおかしくなる
      $ ffmpeg -i test.ts -c:v h264_v4l2m2m -b:v 3000k -vf yadif,scale=-2:720 test.mp4   # 大丈夫
      


    raspberry pi 4B + ubuntu 64bit 環境に rtc を繋いでタイマー起動させる

    [課題]
    raspberry pi 4B は rtc を持っておらず、タイマーによるパワーオンなどをすることが出来ない。外付け rtc を接続することでこれを実現する。録画用途などを想定する。


    [ハードウェア]

    想定するハードウェアを以下に示す
    実物の写真は以下の通り


    Revision 1.0

    Revision 1.1


    以下の回路で示すものを raspberry pi 4B に接続する

    revision 1.1用

    revision 1.0用

    revision 1.1/1.0 共通接続図

    RTC board のピンフレームを右手に置いて、ピンフレームの上端が raspberry pi のピンヘッダの 1, 2番に来るように挿す

    [配布サイト]

    [BOOTH] ねここはうす

    [ソフトウェア]

    RTC board Rev1.0/Rev1.1 を raspberrypi 4B で使うための linux 用 ドライバ及びデバイスツリーファイルを以下のサイトで公開している。Ubuntu Server 20.04.2 LTS 64bit で動作確認をしている。
    https://github.com/nekokomaru/pcf2127mod

    RTC board Rev1.1 を USB で接続して制御するための python スクリプトを以下のサイトで公開している。windows10、python 3.9.2 で動作確認をしている。また、ラズパイで動くサービスもあわせて公開している。Ubuntu Server 20.04.2 LTS 64bit で動作確認をしている。
    https://github.com/nekokomaru/hidctrl


    開発メモ

    1. ハードウェアの選択と接続
    2. 未検証:ハードウェアの選択と接続〜ちょっとお金がかかるがその後は楽かもしれない
    3. システムに用意されているドライバーを使う。目的は達成できない
    4. 最近の raspberrypi os からドライバのソースコードを持ってきて使う。とりあえず目的が達成できた方法
    5. 最近の raspberrypi os からドライバのソースコードを持ってきて使う。うまく行かなかった方法
    6. 改善1:ドライバロード時に割り込み信号をクリアするようにする
    7. 改善2:起動時に自動的にドライバをロードするように設定する
    8. 改善3:ドライバのバグを修正する
    9. 改善4:overlay 機能を使ってドライバを起動時にロードさせる
    10. 改善5:overlay dtbo のちょっとした汎用化
    11. 改善6:レジスタの初期化処理を見直す
    12. 改善7:overlay dtbo を改良して、オプションでレジスタの初期化処理実行するかどうか指定できるようにする
    13. 改善8:アラームで起動したかどうかをユーザーが知ることができる機能をファイルシステムとして実装する
    14. 改善9:バッテリーが切れていないかユーザーが知ることができる機能をファイルシステムとして実装する
    15. バッテリー動作中の注意点と rtc に時刻設定するときのコツ
    16. カーネルが11分周期で rtc 時間を書き換えに行くのをやめさせる

    ハードウェアの選択と接続

    # 以降、GPIO 番号はマイコン上の番号とする。ピンヘッダの番号ではない。

    rtc は入手性の良さ、実装部品の少なさ、インターフェイスの使いやすさ、linux のドライバサポートを考慮し、nxp の PCF2129 を使う。秋月電子 (https://akizukidenshi.com/) の I-09097。
    インターフェイスは I2C を選ぶ。ただし、後述の理由から raspberry pi の i2c6 バスを使用する。

    raspberry pi の起動には、GPIO 3 を使う。run ヘッダを使用する方法もあるが、アーマーケースを使っていると利用できないのでピンヘッダに出ている GPIO から選んだ。
    ここで問題が一つあり、raspberry pi の代表的 I2C インターフェイス(4B の場合は i2c1)は GPIO3 を使用するので、重複してしまって良くない。なので、他の GPIO を使う i2c6 インターフェイスを利用する。

    pcf2129 のデータシートを参考に、raspberry pi と pcf2129 を接続する。i2c モードとして接続すること。

    ここでは、330Ω抵抗を200Ωに、6.8uF コンデンサを 10uF にしている。電池をつないでいないときは VBAT は GND 接続とあるが、そうするとドライバーがうるさいので、ここでは VDD に接続している。raspberry pi の GPIO 22 を pcf2129 の SDA に、GPIO 23 を SCL に接続する。i2c バスは 4.7kΩでプルアップしている。pcf2129 の INT# は GPIO 3 に接続する。GPIO 3 は内部プルアップされているので繋げるだけでいい。

    未検証:ハードウェアの選択と接続〜ちょっとお金がかかるがドライバでは多分困らなくて楽かもしれない

    秋月電子で 700円の DS3234 を使えば、現状のバージョンでもドライバ(これはラズパイOSのやつ)が alarm 機能に対応していてるように見える。実際いじったわけではないので詳しくは知らない。インターフェイスは spi。
    秋月電子で 450 円の RX-8025 もドライバが alarm に対応しているように見える。(ラズパイOSのドライバ)しかし、アラーム機能で日にちを指定できない(データシート参照)ので自分の場合対象外。

    システムに用意されているドライバーを使う。目的は達成できない

    1. overlay を使ったデバイスのインストール

      raspberrypi os の /boot/overlays/README を参考にする。(残念ながら ubuntu には用意されてない)

      まず i2c6 を有効にするために、以下の記述が必要となる。


      dtoverlay=i2c6,pins_22_23,baudrate=400000
      

      さらに、i2c-rtc を追記すれば pcf2129 がデバイスツリーに追加され、ドライバが自動的にロードされるはずである。


      dtoverlay=i2c-rtc,pcf2129,wakeup-source,addr=0x51
      

      しかしこれはうまく行かない。/boot/firmware/overlays/i2c-rtc.dtbo には i2c_arm (4B の場合は i2c1) 以下に pcf2129 を始めとする rtc が接続しているデバイスツリーが記述されているためである。
      これを解決するため、i2c-rtc.dtbo をバイナリエディタで開き、i2c_arm の文字列を i2c6 で置換する。これを i2c6-rtc.dtbo としてセーブする。(本来は、i2c-rtc.dtbo を dts に変換して記述を修正し、再度 dtb に再構成するべき)

      以上を踏まえ、/boot/firmware/overlays/i2c6-rtc.dtbo を作成した上で、/boot/firmware/usercfg.txt に以下を追記する。


      dtoverlay=i2c6,pins_22_23,baudrate=400000
      dtoverlay=i2c6-rtc,pcf2129,wakeup-source,addr=0x51
      

    2. i2c-tools、hwclock を使った動作確認

      再起動すると i2c6 にデバイスが接続されて、ドライバがロードされている様子が確認できる。


      $ sudo apt install i2c-tools    # i2c ツールのインストール
      $ sudo i2cdetect -y 6           # 接続状況の確認
      $ ls /dev/rtc*                     # rtc デバイスが表示される
      

      i2cdetect を実行すると、マトリックスの 0x51 の部分が UU と表示されているのがわかる。UU はドライバーが適用されている状態。i2c デバイスとして認識してはいるがドライバーが適用されてないときは、51 というように slave address が表示される。UU として表示されているのであれば、rtc が利用できる。


      $ sudo hwclock -w    # システムクロック時間をハードウエアクロック(rtc)に書き込む
      $ sudo hwclock -r     # rtc 時間を読み出して表示する
      

    3. 問題点

      しかし、alarm 機能は使えない。


      $ sudo rtcwake --date +5min -m off    # 5分後に起動を指定して電源off する… でもエラーが返る
      $ cat /proc/driver/rtc     # rtc の日時は表示される。しかしそれだけである
      $ ls /sys/class/rtc/rtc0/    # wakeなんちゃら〜というデバイスは存在しない
      

      ubuntu のソースで、pcf2129 のドライバー(drivers/rtc/rtc-pcf2127.c)を眺めてみると、alarm レジスタへのアクセス記述が一切無いのがわかる。raspberrypi os 5.4 のドライバーでも同様で、現在のバージョンのドライバーでは pcf2129 を使った wakeup は出来ないことがわかる。

      将来的なドライバのアップデートを期待する。

    最近の raspberrypi os からドライバのソースコードを持ってきて使う。とりあえず目的が達成できた方法

    raspberrypi os 5.11.y での rtc-pcf2127 ドライバは alarm に対応してるようなので、これをビルドして使う。
    その前に、「システムに用意されているドライバーを使う。目的は達成できない」を参照し、i2c6 が使用できるようにしておく。/boot/firmware/usercfg.txt に一行の追加となる。
    1. raspberrypi os の 5.11.y のソースを拾ってくる

      $ git clone http://github.com/raspberrypi/linux/ -b rpi-5.11.y  #  ~/rasp5.11 以下にクローンした
      

    2. 適当にワークディレクトリを作り、そこにドライバのソースをコピーする

      $ cp ../rasp5.11/linux/drivers/rtc/rtc-pcf2127.c . 
      

    3. Makefile を作成する

      Makefile
      ----------------
      CFILES = rtc-pcf2127.c
      
      obj-m += rtc-pcf2127-11.o
      rtc-pcf2127-11-objs := $(CFILES:.c=.o)
      
      ccflags-y += -std=gnu99 -Wall -Wno-declaration-after-statement
      
      all:
              make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
      
      clean:
              make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
      ----------------
      

    4. ビルドが通るように、rtc-pcf2127.c を書き換える

      1. 足りない define 文を、ゲットしたソース linux/include/uapi/linux/rtc.h から引っ張ってくる

        $ cp ../rasp5.11/linux/include/uapi/linux/rtc.h .
        
        # この rtc.h の中の以下の2行を rtc-pcf2127.c に追記する
        # > 印は追記の印 # < 印は削除の印 26a27,29 > #define RTC_VL_BACKUP_LOW _BITUL(1) /* Backup voltage is low */ > #define RTC_VL_BACKUP_SWITCH _BITUL(4) /* Backup switchover happened */ >

      2. 5.11.y での新たな関数をコールする部分を、現在のカーネルの関数コールで置き換える。

        612c615,616
        <               ret = devm_rtc_nvmem_register(pcf2127->rtc, &nvmem_cfg);
        ---
        >               //!!ret = devm_rtc_nvmem_register(pcf2127->rtc, &nvmem_cfg);
        >               ret = rtc_nvmem_register(pcf2127->rtc, &nvmem_cfg);
        686c690,691
        <       return devm_rtc_register_device(pcf2127->rtc);
        ---
        >       //!!return devm_rtc_register_device(pcf2127->rtc);
        >       return rtc_register_device(pcf2127->rtc);
        

      3. システムにあるドライバと衝突しないように、一部の識別子を書き換える。ドライバの名前の変更のは必須なのかよくわからないが、デバイス識別子の変更は(システムが用意しているドライバを削除しないなら)必須。

        803,805c808,810
        <       { "pcf2127", 1 },
        <       { "pcf2129", 0 },
        <       { "pca2129", 0 },
        ---
        >       { "pcf2127_11", 1 },
        >       { "pcf2129_11", 0 },
        >       { "pca2129_11", 0 },
        812c817
        <               .name   = "rtc-pcf2127-i2c",
        ---
        >               .name   = "rtc-pcf2127-i2c-11",
        870,872c875,877
        <       { "pcf2127", 1 },
        <       { "pcf2129", 0 },
        <       { "pca2129", 0 },
        ---
        >       { "pcf2127_11", 1 },
        >       { "pcf2129_11", 0 },
        >       { "pca2129_11", 0 },
        879c884
        <               .name   = "rtc-pcf2127-spi",
        ---
        >               .name   = "rtc-pcf2127-spi-11",
        

      4. insmod でドライバをインストールして動作確認を行う予定だが、このままだとドライバが alarm を使用するモードに入らないので、パラメータ指定で alarm 使用を指示できるようにする。

        92a96,98
        > static int alarm_en = 0;
        > module_param(alarm_en, int, S_IRUGO);
        >
        599c605,606
        <       if (alarm_irq > 0 || device_property_read_bool(dev, "wakeup-source")) {
        ---
        >       //!!if (alarm_irq > 0 || device_property_read_bool(dev, "wakeup-source")) {
        >       if (alarm_irq > 0 || device_property_read_bool(dev, "wakeup-source") || (alarm_en != 0)) {
        

      5. 以上で書き換え終わり。変更点を全部載せると以下の通り。

        $ diff ../rasp5.11/linux/drivers/rtc/rtc-pcf2127.c rtc-pcf2127.c   # オリジナルからの差分出力
        
        26a27,29
        > #define RTC_VL_BACKUP_LOW       _BITUL(1) /* Backup voltage is low */
        > #define RTC_VL_BACKUP_SWITCH    _BITUL(4) /* Backup switchover happened */
        > 
        92a96,98
        > static int alarm_en = 0;
        > module_param(alarm_en, int, S_IRUGO);
        > 
        599c605,606
        < 	if (alarm_irq > 0 || device_property_read_bool(dev, "wakeup-source")) {
        ---
        > 	//!!if (alarm_irq > 0 || device_property_read_bool(dev, "wakeup-source")) {
        > 	if (alarm_irq > 0 || device_property_read_bool(dev, "wakeup-source") || (alarm_en != 0)) {
        612c619,620
        < 		ret = devm_rtc_nvmem_register(pcf2127->rtc, &nvmem_cfg);
        ---
        > 		//!!ret = devm_rtc_nvmem_register(pcf2127->rtc, &nvmem_cfg);
        > 		ret = rtc_nvmem_register(pcf2127->rtc, &nvmem_cfg);
        686c694,695
        < 	return devm_rtc_register_device(pcf2127->rtc);
        ---
        > 	//!!return devm_rtc_register_device(pcf2127->rtc);
        > 	return rtc_register_device(pcf2127->rtc);
        803,805c812,814
        < 	{ "pcf2127", 1 },
        < 	{ "pcf2129", 0 },
        < 	{ "pca2129", 0 },
        
        ---
        > 	{ "pcf2127_11", 1 },
        > 	{ "pcf2129_11", 0 },
        > 	{ "pca2129_11", 0 },
        812c821
        < 		.name	= "rtc-pcf2127-i2c",
        ---
        > 		.name	= "rtc-pcf2127-i2c-11",
        870,872c879,881
        < 	{ "pcf2127", 1 },
        < 	{ "pcf2129", 0 },
        < 	{ "pca2129", 0 },
        ---
        > 	{ "pcf2127_11", 1 },
        > 	{ "pcf2129_11", 0 },
        > 	{ "pca2129_11", 0 },
        879c888
        < 		.name	= "rtc-pcf2127-spi",
        ---
        > 		.name	= "rtc-pcf2127-spi-11",
        
        

    5. ドライバをビルドする

      $ make     # rtc-pcf2127-11.ko が作成される
      

    6. ドライバである rtc-pcf2127-11.ko が作成されたので、インストールしてみる。

      $ sudo insmod rtc-pcf2127-11.ko alarm_en=1     # パラメータを与えて、ドライバをインストール
      $ sudo bash -c 'echo pcf2129_11 0x51 > /sys/bus/i2c/devices/i2c-6/new_device'  # i2c-6 上、slave address 0x51 のデバイスに識別子 pcf2129_11 でドライバーを適用させる
      $ sudo i2cdetect -y 6    # ドライバが適用されているのがわかる
      

    7. rtc 時刻、及び alarm が使えることを確認してみる

      $ sudo hwclock -w      # システム時間を rtc に書き込む
      $ sudo hwclock -r      # rtc 時間を読み出して表示する
      $ cat /proc/driver/rtc  # alarm 時間や alarm_IRQ(アラームするか否か)などが表示される。
      $ ls /sys/class/rtc/rtc0/  # wakeup2 や wakealarm のデバイスが存在するのがわかる
      

    8. タイマー起動を試してみる。一つのやり方は、wakealarm デバイスを直接扱う方法。2分後にアラームをセットして、シャットダウンしてみる。2分後にパワーオンすることを確認する

      $ sudo sh -c "echo 0 > /sys/class/rtc/rtc0/wakealarm"	# アラームクリア
      $ sudo sh -c "echo `date '+%s' -d '+ 2 minutes'` > /sys/class/rtc/rtc0/wakealarm"	# 2分後にパワーオンするように alarm 時刻セット
      $ cat /sys/class/rtc/rtc0/wakealarm			# 確認 値が書き込まれている
      $ cat /proc/driver/rtc						# 確認 alarm 時刻がセットされている
      $ sudo shutdown now						# 即時シャットダウン
      

    9. 注意点が一つあって、alarm によってパワーオンした場合、pcf2129 のタイマー割り込み信号 == raspberrypi の GPIO 3 は LOW になったままなので、起動したらアラームを解除しておいたほうがいいかもしれない。
      # ドライバを適用した後…
      $ sudo sh -c "echo 0 > /sys/class/rtc/rtc0/wakealarm"	# アラームクリア
      
      # もしくは、ドライバを適用前に…
      $ sudo i2cset -y 6 0x51 0x01 0x00 b		# 直接レジスタをいじってアラーム有効、検出フラグを落とす。が、0x01 の部分は 0x00 でないとダメかもしれない。詳しくは上記参照

    10. 2つ目のやり方は、rtcwake コマンドを使う方法。同様に2分後にアラームをセットしてシャットダウンし、2分後にパワーオンすることを確認する。この場合も起動後にアラームを解除しておいたほうがいいかもしれない

      $ sudo rtcwake --date +2min -m off        # 2分後に alarm をセットしてシャットダウンする。
      

    11. 問題点

      • alarm による起動後、ドライバをインストルして cat /proc/driver/rtc で alarm 時刻を表示させると設定した覚えのない時刻になっている。ドライバがなにかしら初期値を与えているんだろうか。ここのバグフィクスで修正できる。ただし、pcf2129 は alarm の年月情報を持っていないので、ここの部分についてはカーネルが適当に値を設定してるのだろう。alarm の年月は無視して良いと思う。
      • rmmod でドライバのアンインストールが出来ない。稼働中のカーネルの関数で代用する書き換えをした副作用で、アンインストール処理が省かれてしまっているので仕方ない。rtc-pcf2127.c をきちんと記述すれば解決できるだろう。# まぁ、運用上はアンインストールする必要は無い気がするし…

    最近の raspberrypi os からドライバのソースコードを持ってきて使う。うまく行かなかった方法

    うまく行った方法では、5.11.y バージョンの rtc-2127.c 中の関数コール devm_rtc_nvmem_register()、devm_rtc_register_device() を稼働中のカーネル・ドライバが提供する関数で置き換えた。rmmod が出来ないことなどはその副作用である。ならばこの2つの関数を 5.11.y のソースから持ってくればもっときれいに動作するドライバがビルドできるのではないかと思って試してみた。結果から言えば、今回の手順では正常動作するものは作成できなかった。rmmod 動作はきちんとできるものの、insmod 時に tracefs 関係のエラーを出力し(dmesgで確認)、ドライバの適用もエラーを出力せず一見うまく行っているように見えるが /dev/rtc が作成されないものになった。内部動作を理解した上でいじらないとうまく行かないようだ。

    以上2通りの方法を試したが、どちらのドライバも正常には動作しなかった。

    改善1:ドライバロード時に割り込み信号をクリアするようにする

    ドライバが pcf2129 を認識してロードされるときに、割り込み信号をクリアするようにする。アラーム割り込みによるパワーオンと、手動の外部スイッチによるパワーオンを併用するための実装である。
    以降の改善では、最近の raspberrypi os からドライバのソースコードを持ってきて使う。とりあえず目的が達成できた方法のソースをもとに更に改変を加えていく。

    [注意] 原因を追求しておらず、検証不足だが、/proc/driver/rtc にアクセスすると alarm が正しく動作しなくなる場合があるような気がする。

    1. アラーム割り込みが立っていたときは、割り込みフラグと割り込み有効フラグを落とす。ウォッチドッグタイマの割り込みフラグもとりあえず処理する。これを rtc-pcf2127.c の pcf2127_probe() に追記する。

      693a694,724
      > 	//!! read interrupt flag and clear
      > 	unsigned int ctrl2 = 0;
      > 	ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL2, &ctrl2);
      > 	if(ret) {
      > 		dev_err(dev, "failed to read CTRL2\n");
      > 		return ret;
      > 	}
      > 	if((ctrl2 & PCF2127_BIT_CTRL2_AF) != 0 ) {
      > 		printk("AF is HI\n");
      > 		// clear alarm interrupt and enable
      > 	        ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL2,
      >         	        PCF2127_BIT_CTRL2_AF |
      >                         PCF2127_BIT_CTRL2_AIE, 0);
      >         	if(ret) {
      >                 	dev_err(dev, "failed to update AF/AIE\n");
      > 	                return ret;
      >         	}
      > 		printk("Clear AF/AIE\n");
      > 	}
      > 	if((ctrl2 & PCF2127_BIT_CTRL2_WDTF) != 0 ) {
      >                 printk("WDTF is HI\n");
      > 		// clear watch dog interrupt
      >                 ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL2,
      >                         PCF2127_BIT_CTRL2_WDTF, 0);
      >                 if(ret) {
      >                         dev_err(dev, "failed to update WDTF\n");
      >                         return ret;
      >                 }
      >                 printk("Clear WDTF\n");
      > 	}
      > 
      

    改善2:起動時に自動的にドライバをロードするように設定する

    ubuntu が起動したときに、i2c6 バスに接続された pcf2129 対して自動的にドライバをロードするように設定する。
    1. モジュールのロード時に設定できるオプションを増やす。i2c バス番号、i2c デバイスのスレーブアドレス、ドライバ名を指定できるように rtc-pcf2127.c に追記する。ドライバをアンロードするときに使う変数も宣言する。

      > // i2c bus no
      > static int bus_no = -1;
      > module_param(bus_no, int, S_IRUGO);
      > 
      > // driver name
      > static char *i2c_name = NULL;
      > module_param(i2c_name, charp, S_IRUGO);
      > 
      > // i2c slave address
      > static int slvaddr = -1;
      > module_param(slvaddr, int, S_IRUGO);
      > 
      > // client for unregister
      > static struct i2c_client *i2c_clie = NULL;
      

    2. オプション情報をもとに、i2c デバイス(pcf2129)を登録する処理を初期化関数 pcf2127_init() に追記する

      916a972,989
      > 	//!!}
      > 	//!!  regist i2c device
      >  	} else if((i2c_name != NULL) && (slvaddr > 0) && (bus_no > 0)) {
      > 
      >  	        // make i2c board info
      >  		struct i2c_board_info i2c_info;
      >  		memset(&i2c_info, 0, sizeof(struct i2c_board_info));
      >  		strscpy(i2c_info.type, i2c_name, sizeof(i2c_info.type));
      >  		i2c_info.addr = slvaddr;
      > 
      >  	        struct i2c_adapter *i2c_adap = NULL;
      >  	        i2c_adap = i2c_get_adapter(bus_no);
      >  	        if(i2c_adap == NULL) return ret;
      > 
      >  	        i2c_clie = i2c_new_device(i2c_adap, &i2c_info);
      >  	        if(i2c_clie == NULL) return ret;
      > 
      >  	        i2c_put_adapter(i2c_adap);
      

    3. i2c デバイス(pcf2129)を登録削除する処理を終了関数 pcf2127_exit() に追記する。(ただし、現状ドライバモジュールの削除ができないのであまり意味がない)

      931a1005,1009
      > 
      > 	//!! unregist
      > 	if(i2c_clie) {
      > 		i2c_unregister_device(i2c_clie);
      > 	}
      

    4. ビルドし、モジュールファイルをモジュール用ディレクトリにコピーし、モジュール情報を更新する

      $ make
      $ sudo cp rtc-pcf2127-11.ko /lib/modules/`uname -r`/kernel/drivers/rtc/
      $ sudo depmod -a          # module.dep を自動で再構成
      

    5. モジュールロード時に自動的にオプションが設定されるように、/etc/modprobe.d/ にファイルを作成する。ここでは myrtc.conf というファイル名にする(なんでもいい)
      以下の記述により my_pcf2129 の名前でモジュールをロードする際には指定したオプションが自動的に追加される。
      /etc/modprobe.d/myrtc.conf
      ------
      alias my_pcf2129 rtc-pcf2127-11
      options my_pcf2129 alarm_en=1 i2c_name="pcf2129_11" bus_no=6 slvaddr=0x51
      ------
      

    6. ドライバモジュール my_pcf2129 が起動時に読み込まれるように、/etc/modules-load.d/modules.conf に以下を追記する

      > my_pcf2129
      

    7. 再起動すると、自動的にドライバがロードされ、i2c6 の pcf2129 に適用されているのが確認できる

      $ sudo reboot   # 再起動
      $ sudo i2cdetect -y 6  # 再起動後に実行すると、ドライバが適用されているのがわかる
      

    改善3:ドライバのバグを修正する

    pcf2127 ドライバは、regmap を使ったアクセスを行っている。ここで、regmap_bulk_read() 及び regmap_bulk_write() は、入出力バッファに unsigned char の領域を想定して実装されているので、これに反した記述を修正する。
    1. pcf2127_rtc_read_alarm() 内 の変数宣言部分

      369c390,392
      < 	unsigned int buf[5], ctrl2;
      ---
      > 	//!!unsigned int buf[5], ctrl2;
      > 	unsigned char buf[5];
      > 	unsigned int ctrl2;
      

    2. ついでに、気になるので regmap_init に引き渡すパラメーターの記述を正確にしておく

      781,785c863,881
      < 	static const struct regmap_config config = {
      < 		.reg_bits = 8,
      < 		.val_bits = 8,
      < 		.max_register = 0x1d,
      < 	};
      ---
      >         //static const struct regmap_config config = {
      >         //        .reg_bits = 8,
      >         //        .val_bits = 8,
      >         //        .max_register = 0x1d,
      >         //};
      > 	//!! apply to register size
      > 	// val_bytes = val_bits/8, and regmap_bulk access is based on val_bytes.
      > 	// so, regmap_bulk_* functions need a buffer of unsigned char.
      > 	static struct regmap_config config;
      > 	memset(&config, 0, sizeof(struct regmap_config));
      >  	config.reg_bits = 8;
      > 	config.val_bits = 8;
      > 	if(id->driver_data == 0) {
      > 		printk("detect pcf2129\n");
      > 		config.max_register = 0x1b;
      > 	} else {
      > 		printk("detect pcf2127\n");
      > 		config.max_register = 0x1d;
      > 	}
      

      ここで宣言される config が __regmap_init() の中で評価され、val_bits 値が regmap 構造体の format.val_bytes という値に反映される。val_bytes = val_bits / 8 の切り上げ値となる。
      そして、val_bytes 値が regmap_bulk_read() および regmap_bulk_write() でのデータ入出力の大きさの単位になる。rtc-pcf2127 ドライバの中では1バイト単位となる。

    改善4:overlay 機能を使ってドライバを起動時にロードさせる

    /etc/modprobe.d/、/etc/modules-load.d/modules.conf を使用せずに、起動時にドライバをロードさせるように設定する。「システムに用意されているドライバーを使う。目的は達成できない」で 使ったデバイスツリーの設定ファイルを改変し、今回作成したドライバー用のものを用意する。
    1. まず、後に作成するデバイスツリー設定ファイル(以下 dtbo)用に、ドライバの記述を修正する

      691,693c773,775
      < 	{ .compatible = "nxp,pcf2127" },
      < 	{ .compatible = "nxp,pcf2129" },
      < 	{ .compatible = "nxp,pca2129" },
      ---
      > 	{ .compatible = "nxp,pcf2127_11" },
      > 	{ .compatible = "nxp,pcf2129_11" },
      > 	{ .compatible = "nxp,pca2129_11" },
      

    2. ビルドしてモジュールディレクトリにコピーし、dep を更新しておく

      $ make
      $ sudo cp rtc-pcf2127-11.ko /lib/modules/`uname -r`/kernel/drivers/rtc/
      $ sudo depmod -a          # module.dep を自動で再構成
      

    3. /etc/modules-load.d/modules.conf に以前の設定があったら削除しておく

      #my_pcf2129
      

    4. 書き換え元となる dts ファイルを github から取ってくる
    5. i2c6 バスにぶら下がるように設定して、上述で設定した compatible 設定を取り込むように書き換える。

      i2c-rtc-overlay.dts
      --------
      // Definitions for several I2C based Real Time Clocks
      /dts-v1/;
      /plugin/;
      
      / {
      	compatible = "brcm,bcm2835";
      
      	fragment@0 {
      		target = <&i2cbus>;
      		__dormant__ {
      			#address-cells = <1>;
      			#size-cells = <0>;
      
      			pcf2127_11: pcf2127_11@51 {
      				compatible = "nxp,pcf2127_11";
      				reg = <0x51>;
      			};
      		};
      	};
      
      	fragment@1 {
      		target = <&i2cbus>;
      		__dormant__ {
      			#address-cells = <1>;
      			#size-cells = <0>;
      
      			pcf2129_11: pcf2129_11@51 {
      				compatible = "nxp,pcf2129_11";
      				reg = <0x51>;
      			};
      		};
      	};
      
      	fragment@2 {
      		target = <&i2cbus>;
      		__dormant__ {
      			#address-cells = <1>;
      			#size-cells = <0>;
      
      			pca2129_11: pca2129_11@51 {
      				compatible = "nxp,pca2129_11";
      				reg = <0x51>;
      			};
      		};
      	};
      
      	frag100: fragment@100 {
      		target = <&i2c6>;
      		i2cbus: __overlay__ {
      			status = "okay";
      		};
      	};
      
      	__overrides__ {
      		pcf2127_11 = <0>,"+0";
      		pcf2129_11 = <0>,"+1";
      		pca2129_11 = <0>,"+2";
      
      		i2c0 = <&frag100>, "target:0=",<&i2c0>;
      		i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>;
      
      		wakeup-source = <&pcf2127_11>,"wakeup-source?",
      				<&pcf2129_11>,"wakeup-source?",
      				<&pca2129_11>,"wakeup-source?";
      	};
      };
      --------
      

    6. 書き換えた dts ファイルから dtbo ファイルを作成する

      $ dtc -@ -I dts -O dtb -o i2c6-rtc-pcf2127.dtbo i2c-rtc-overlay.dts   # 入力形式は dts、出力形式は dtb、他の dtb から参照できるようにシンボルをつける(これは特に必要ないけど)
      

    7. 作成した dtbo ファイルを overlays ディレクトリにコピーする

      $ sudo cp i2c6-rtc-pcf2127.dtbo /boot/firmware/overlays/    # raspberry pi os だと、コピー先は /boot/overlays/ かな?
      

    8. /boot/firmware/usercfg.txt を書き換える。i2c6 を使用する設定と合わせると、以下のようになる(raspberry pi os だと /boot/config.txt を書き換える感じかな)

      dtoverlay=i2c6,pins_22_23,baudrate=400000
      dtoverlay=i2c6-rtc-pcf2127,pcf2129_11,wakeup-source
      

    9. 再起動すると、ドライバがロードされて rtc が使えるようになっている
      $ sudo reboot
      $ sudo i2cdetect -y 6
      $ cat /proc/driver/rtc
      

    改善5:overlay dtbo のちょっとした汎用化

    改善4:overlay 機能を使ってドライバを起動時にロードさせる」では、i2c6 + pcf2129 用の dtbo ファイルを作成したが、pcf2129 が接続する i2c バスをパラメータで指定できるような dtbo を作成する。
    書き換え前の dts ファイルでは、デフォルトで i2c_arm にぶら下がる設定であり、オプションによって i2c0 (GPIO 0 and 1) もしくは i2c0 (GPIO 44 and 45) にぶら下がる設定が出来た。これを、追記により他の i2c バスをオプション指定できるようにする。ただし pcf2129 用 dtbo とは別に i2c バス用 dtbo を用いて、pcf2129 を接続する i2c バスを設定することが必要条件となる。(これは 改善4 でもそうしていた。)

    1. 書き換え元となる dts ファイルを github から取ってくる。改善4 で作成した dts を更に書き換えて使ってもいい。
    2. 以下のように書き換える

      i2c-rtc-overlay.dts
      ----------
      // Definitions for several I2C based Real Time Clocks
      /dts-v1/;
      /plugin/;
      
      / {
      	compatible = "brcm,bcm2835";
      
      	fragment@0 {
      		target = <&i2cbus>;
      		__dormant__ {
      			#address-cells = <1>;
      			#size-cells = <0>;
      
      			pcf2127_11: pcf2127_11@51 {
      				compatible = "nxp,pcf2127_11";
      				reg = <0x51>;
      			};
      		};
      	};
      
      	fragment@1 {
      		target = <&i2cbus>;
      		__dormant__ {
      			#address-cells = <1>;
      			#size-cells = <0>;
      
      			pcf2129_11: pcf2129_11@51 {
      				compatible = "nxp,pcf2129_11";
      				reg = <0x51>;
      			};
      		};
      	};
      
      	fragment@2 {
      		target = <&i2cbus>;
      		__dormant__ {
      			#address-cells = <1>;
      			#size-cells = <0>;
      
      			pca2129_11: pca2129_11@51 {
      				compatible = "nxp,pca2129_11";
      				reg = <0x51>;
      			};
      		};
      	};
      
      	frag100: fragment@100 {
      		target = <&i2c_arm>;
      		i2cbus: __overlay__ {
      			status = "okay";
      		};
      	};
      
      	__overrides__ {
      		pcf2127_11 = <0>,"+0";
      		pcf2129_11 = <0>,"+1";
      		pca2129_11 = <0>,"+2";
      
      		i2c0 = <&frag100>, "target:0=",<&i2c0>;
      		i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>;
      
      		i2c1 = <&frag100>, "target:0=",<&i2c1>;
      		i2c3 = <&frag100>, "target:0=",<&i2c3>;
      		i2c4 = <&frag100>, "target:0=",<&i2c4>;
      		i2c5 = <&frag100>, "target:0=",<&i2c5>;
      		i2c6 = <&frag100>, "target:0=",<&i2c6>;
      
      		wakeup-source = <&pcf2127_11>,"wakeup-source?",
      				<&pcf2129_11>,"wakeup-source?",
      				<&pca2129_11>,"wakeup-source?";
      	};
      };
      ----------
      

    3. 書き換えた dts ファイルから dtbo ファイルを作成し、overlays ディレクトリにコピーする

      $ dtc -@ -I dts -O dtb -o i2c-rtc-pcf2127.dtbo i2c-rtc-overlay.dts   # 入力形式は dts、出力形式は dtb、他の dtb から参照できるようにシンボルをつける(これは特に必要ないけど)
      $ sudo cp i2c-rtc-pcf2127.dtbo /boot/firmware/overlays/     # raspberry pi os だと、コピー先は /boot/overlays/ かな?
      

    4. /boot/firmware/usercfg.txt を書き換える。i2c6.dtbo で i2c6 を有効化及び設定をし、さらに i2c-rtc-pcf2127.dtbo では i2c6 をオプションで指定する(raspberry pi os だと /boot/config.txt を書き換える感じかな)

      dtoverlay=i2c6,pins_22_23,baudrate=400000
      dtoverlay=i2c-rtc-pcf2127,pcf2129_11,i2c6,wakeup-source
      

    5. 再起動すると、ドライバがロードされて rtc が使えるようになっている
      $ sudo reboot
      $ sudo i2cdetect -y 6
      $ cat /proc/driver/rtc
      

    改善6:レジスタの初期化処理を見直す

    公式のドライバは、必要な変更以外はリセット後のレジスタ初期値をそのまま使う方針であるように見える。rtc は電源投入後はずっと稼働し続けているので、例えば raspberry pi の起動時に初期値を設定するのは適当でないという考え方だろう。しかし、ここではあえて、デフォルト値を改めて設定したり、ドライバの動作を考慮して初期化しておいたほうがいい箇所を初期化したり、自分の用途の都合上特定のレジスタを初期化する処理を付け加える。(例えばドライバでは timestamp 処理が実装されているが、timestamp の使用場面が思いつかない自分は timestamp の割り込みフラグをどのタイミングでクリアするのが適当なのかわからない。かといって INT# ピンが LOW に落ちたままなのは自分の運用上困るので、とりあえず起動時にクリアするようにする)
    なお、この初期化処理は、insmod でのパラメータ指定、及び overlay によるオプション指定により処理の実行を選択できるようにする。

    1. rtc-pcf2127.c に、とりあえずあったら便利かもしれない定義を追加する

      > //!! MSF interrupt type
      > #define PCF2127_BIT_WD_CTL_TITP			BIT(5)
      > //!! MSF interupt flag
      > #define PCF2127_BIT_CTRL2_MSF                   BIT(7)
      

    2. insmod や modprobe でドライバをインストールするとき、パラメータで初期化処理の実行を選択できるように変数を追加する。

      > // Enable flag for initializeing registers
      > static int initreg_en = 0;
      > module_param(initreg_en, int, S_IRUGO);
      > 
      > // Enable flag for clear interrupt
      > static int clrint_en = 0;
      > module_param(clrint_en, int, S_IRUGO);
      

    3. 改善1:ドライバロード時に割り込み信号をクリアするようにする」で追加した割り込みフラグのクリア処理を独立させて関数にする。この関数の中では、以下の割り込みフラグ等をクリアする
      • レジスタアドレス 0x01 の AF(タイマー割り込み) 及び AIE(タイマー割り込み有効)
      • レジスタアドレス 0x01 の WDTF(ウオッチドッグ割り込み)
      • レジスタアドレス 0x01 の TSF2(タイムスタンプ割り込み)
      • レジスタアドレス 0x01 の MSF(分及び秒割り込み)
      • レジスタアドレス 0x00 の TSF1(タイムスタンプ割り込み)

      > //!! clearing interrupt func.
      > static int pcf2127_clear_int(struct device *dev, struct regmap *map)
      > {
      > 	int ret = 0;
      > 	unsigned int data = 0;
      > 
      >         ret = regmap_read(map, PCF2127_REG_CTRL2, &data);
      >         if(ret) {
      >                 dev_err(dev, "failed to read CTRL2\n");
      >                 return ret;
      >         }
      >         if((data & PCF2127_BIT_CTRL2_AF) != 0 ) {
      >                 printk("pcf2127:AF is HI\n");
      >                 // clear alarm interrupt and enable
      > 		data &= ~(PCF2127_BIT_CTRL2_AF | PCF2127_BIT_CTRL2_AIE);
      >                 printk("pcf2127:Clear AF/AIE\n");
      >         }
      >         if((data & PCF2127_BIT_CTRL2_WDTF) != 0 ) {
      >                 printk("pcf2127:WDTF is HI\n");
      >                 // clear watch dog interrupt
      > 		data &= ~PCF2127_BIT_CTRL2_WDTF;
      >                 printk("pcf2127:Clear WDTF\n");
      >         }
      >         if((data & PCF2127_BIT_CTRL2_TSF2) != 0 ) {
      >                 printk("pcf2127:TSF2 is HI\n");
      >                 // clear timestamp interrupt 2
      > 		data &= ~PCF2127_BIT_CTRL2_TSF2;
      >                 printk("pcf2127:Clear TSF2\n");
      >         }
      >         if((data & PCF2127_BIT_CTRL2_MSF) != 0 ) {
      >                 printk("pcf2127:MSF is HI\n");
      >                 // clear min/sec interrupt
      >                 data &= ~PCF2127_BIT_CTRL2_MSF;
      >                 printk("pcf2127:Clear MSF\n");
      >         }
      > 	ret = regmap_write(map, PCF2127_REG_CTRL2, data);
      >         if(ret) {
      >                 dev_err(dev, "failed to write CTRL2\n");
      >                 return ret;
      >         }
      > 
      > 	data = 0;
      >         ret = regmap_read(map, PCF2127_REG_CTRL1, &data);
      >         if(ret) {
      >                 dev_err(dev, "failed to read CTRL1\n");
      >                 return ret;
      >         }
      >         if((data & PCF2127_BIT_CTRL1_TSF1) != 0 ) {
      >                 printk("pcf2127:TSF1 is HI\n");
      >                 // clear timestamp imterrupt 1
      >                 data &= ~PCF2127_BIT_CTRL1_TSF1;
      >                 printk("pcf2127:Clear TSF1\n");
      >         }
      >         ret = regmap_write(map, PCF2127_REG_CTRL1, data);
      >         if(ret) {
      >                 dev_err(dev, "failed to write CTRL1\n");
      >                 return ret;
      >         }
      > 
      > 	return ret;
      > }
      

    4. 各種初期化を行う関数を作成する。この関数内では以下の初期化を行う
      • レジスタ 0x00…通常動作モード、RTC クロック作動、24時間モード、分及び秒割り込み無効
      • レジスタ 0x02…パワーマネジメント = バッテリー切り替え機能は標準モード、バッテリー切れ検知有効
      • レジスタ 0x0F…内部温度測定は4分ごと、キャリブレーション用出力クロック = 32768Hz
      • レジスタ 0x19…発振子調整 = +-0 ppm

      > //!! initialze registers
      > static int pcf2127_init_regs(struct device *dev, struct regmap *map)
      > {
      > 	int ret = 0;
      > 	unsigned int data = 0;
      > 
      > 	// reset EXT_TEST
      > 	// rtc is running
      > 	// 24 hour mode
      > 	// disable minute and second interrupt
      >         ret = regmap_read(map, PCF2127_REG_CTRL1, &data);
      >         if (ret) {
      >                 dev_err(dev, "failed to read CTRL1\n");
      >                 return ret;
      >         }
      > 
      > 	data &= 0b01011000;
      > 
      >         ret = regmap_write(map, PCF2127_REG_CTRL1, data);
      >         if (ret) {
      >                 dev_err(dev, "failed to write CTRL1\n");
      >                 return ret;
      >         }
      > 
      > 	// poqwer management is standard mode, enable low detection
      > 	data = 0;
      >         ret = regmap_read(map, PCF2127_REG_CTRL3, &data);
      >         if (ret) {
      >                 dev_err(dev, "failed to read CTRL3\n");
      >                 return ret;
      >         }
      > 
      > 	data &= 0b00011111;
      > 
      > 	ret = regmap_write(map, PCF2127_REG_CTRL3, data);
      >         if (ret) {
      >                 dev_err(dev, "failed to write CTRL3\n");
      >                 return ret;
      >         }
      > 
      > 	// CLKOUT is 32768Hz
      > 	// temp. mesurement period = 4min.
      > 	data = 0;
      >         ret = regmap_read(map, 0x0F, &data);
      >         if (ret) {
      >                 dev_err(dev, "failed to read CLKOUT_ctl\n");
      >                 return ret;
      >         }
      > 
      >         data &= 0b00111000;
      > 
      >         ret = regmap_write(map, 0x0F, data);
      >         if (ret) {
      >                 dev_err(dev, "failed to write CLKOUT_ctl\n");
      >                 return ret;
      >         }
      > 
      > 	// aging is default = 0b1000
      >         data = 0;
      >         ret = regmap_read(map, 0x19, &data);
      >         if (ret) {
      >                 dev_err(dev, "failed to read AGING_offset\n");
      >                 return ret;
      >         }
      > 
      >         data &= 0xf0;
      > 	data |= 0x08;
      > 
      >         ret = regmap_write(map, 0x19, data);
      >         if (ret) {
      >                 dev_err(dev, "failed to write AGING_offset\n");
      >                 return ret;
      >         }
      > 	return ret;
      > }
      > 
      

    5. 上記で作成した割り込みフラグ初期化関数とレジスタ初期化関数をコールする処理を pcf2127_probe() 内に追記する。insmod でのオプション及び overlay によるオプションに応じて実行するようにする。

      > 	//!! read interrupt flag and clear
      > 	if((clrint_en != 0) || device_property_read_bool(dev, "clear-ints")) {
      > 		ret = pcf2127_clear_int(dev, pcf2127->regmap);
      >         	if(ret) {
      >                 	return ret;
      > 	        }
      > 	}
      > 
      > 	//!! initialize registers
      > 	if((initreg_en != 0) || device_property_read_bool(dev, "init-regs")) {
      > 	        ret = pcf2127_init_regs(dev, pcf2127->regmap);
      >         	if(ret) {
      >                 	return ret;
      > 	        }
      > 	}
      

    6. pcf2127_probe() の中で watchdog 関係のレジスタ値をいじっている部分の記述を変更する。unused ビットの操作を省き、分及び秒割り込みフラグの変更を永続的なものにする(非インパルス)

              ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_WD_CTL,
                                       PCF2127_BIT_WD_CTL_CD1 |
              //!! unused              PCF2127_BIT_WD_CTL_CD0 |
                                       PCF2127_BIT_WD_CTL_TITP | //!! TI_TP added
                                       PCF2127_BIT_WD_CTL_TF1 |
                                       PCF2127_BIT_WD_CTL_TF0,
                                       PCF2127_BIT_WD_CTL_CD1 |
              //!! unused              PCF2127_BIT_WD_CTL_CD0 |
                                       PCF2127_BIT_WD_CTL_TF1);
              //!! watchdog int=enable, MSF int=disable, watchdog timer=1Hz
      

    7. pcf2127_probe() の中でバッテリー関係のレジスタ値をいじっている部分の記述を変更する。バッテリー割り込みが無効にされているので、割り込みフラグも落としておく

              ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL3,
                                       PCF2127_BIT_CTRL3_BTSE |
                                       PCF2127_BIT_CTRL3_BIE |
                                       PCF2127_BIT_CTRL3_BF | //!! added
                                       PCF2127_BIT_CTRL3_BLIE, 0);
      

    8. make して適切な位置にコピーする

      $ make
      $ sudo cp rtc-pcf2127-11.ko /lib/modules/`uname -r`/kernel/drivers/rtc/
      $ sudo depmod -a          # module.dep を自動で再構成
      

    改善7:overlay dtbo を改良して、オプションでレジスタの初期化処理実行するかどうか指定できるようにする

    dtbo にパラメータを与えることで、改善6 で作成したレジスタ初期化処理の実行可否を制御できるようにする。

    1. デバイスツリーを設定する overlay ファイルを書き換える
      i2c-rtc-overlay.dts
      ----------
      // Definitions for several I2C based Real Time Clocks
      /dts-v1/;
      /plugin/;
      
      / {
      	compatible = "brcm,bcm2835";
      
      	fragment@0 {
      		target = <&i2cbus>;
      		__dormant__ {
      			#address-cells = <1>;
      			#size-cells = <0>;
      
      			pcf2127_11: pcf2127_11@51 {
      				compatible = "nxp,pcf2127_11";
      				reg = <0x51>;
      			};
      		};
      	};
      
      	fragment@1 {
      		target = <&i2cbus>;
      		__dormant__ {
      			#address-cells = <1>;
      			#size-cells = <0>;
      
      			pcf2129_11: pcf2129_11@51 {
      				compatible = "nxp,pcf2129_11";
      				reg = <0x51>;
      			};
      		};
      	};
      
      	fragment@2 {
      		target = <&i2cbus>;
      		__dormant__ {
      			#address-cells = <1>;
      			#size-cells = <0>;
      
      			pca2129_11: pca2129_11@51 {
      				compatible = "nxp,pca2129_11";
      				reg = <0x51>;
      			};
      		};
      	};
      
      	frag100: fragment@100 {
      		target = <&i2c_arm>;
      		i2cbus: __overlay__ {
      			status = "okay";
      		};
      	};
      
      	__overrides__ {
      		pcf2127_11 = <0>,"+0";
      		pcf2129_11 = <0>,"+1";
      		pca2129_11 = <0>,"+2";
      
      		i2c0 = <&frag100>, "target:0=",<&i2c0>;
      		i2c_csi_dsi = <&frag100>, "target:0=",<&i2c_csi_dsi>;
      
      		i2c1 = <&frag100>, "target:0=",<&i2c1>;
      		i2c3 = <&frag100>, "target:0=",<&i2c3>;
      		i2c4 = <&frag100>, "target:0=",<&i2c4>;
      		i2c5 = <&frag100>, "target:0=",<&i2c5>;
      		i2c6 = <&frag100>, "target:0=",<&i2c6>;
      
      		wakeup-source = <&pcf2127_11>,"wakeup-source?",
      				<&pcf2129_11>,"wakeup-source?",
      				<&pca2129_11>,"wakeup-source?";
      
      		init-regs = <&pcf2127_11>,"init-regs?",
                                  <&pcf2129_11>,"init-regs?",
                                  <&pca2129_11>,"init-regs?";
      
                      clear-ints = <&pcf2127_11>,"clear-ints?",
                                   <&pcf2129_11>,"clear-ints?",
                                   <&pca2129_11>,"clear-ints?";
      	};
      };
      ----------
      

    2. dtbo ファイルを作成し、指定場所へコピーする

      $ dtc -@ -I dts -O dtb -o i2c-rtc-pcf2127.dtbo i2c-rtc-overlay.dts   # 入力形式は dts、出力形式は dtb、他の dtb から参照できるようにシンボルをつける(これは特に必要ないけど)
      $ sudo cp i2c-rtc-pcf2127.dtbo /boot/firmware/overlays/     # raspberry pi os だと、コピー先は /boot/overlays/ かな
      

    3. /boot/firmware/usercfg.txt を書き換える。i2c6.dtbo で i2c6 を有効化及び設定をし、さらに i2c-rtc-pcf2127.dtbo では i2c6 及び必要に応じて init-regs、clear-ints をオプションで指定する。init-regs でレジスタの初期化、clear-ints で割り込みのクリアが行われる。(raspberry pi os だと /boot/config.txt を書き換えるのかな)

      dtoverlay=i2c6,pins_22_23,baudrate=400000
      dtoverlay=i2c-rtc-pcf2127,pcf2129_11,i2c6,wakeup-source,init-regs,clear-ints
      

    改善8:アラームで起動したかどうかをユーザーが知ることができる機能をファイルシステムとして実装する

    ドライバモジュールがロードされた時、「rtc のアラーム割り込みフラグが立っている」かつ「アラーム有効フラグが立っている」ならば、アラームによって起動したと判断し、この情報をファイルシステムとしてユーザーに提供する

    1. アラーム起動の情報を持つグローバル変数を用意し、アラームの割り込みフラグと有効フラグを参照して、このグローバル変数に値をセットする関数を作成する

      > // waked up by alarm flag
      > static int waked_alarm = 0;
      
      > //!! find out if  PI is waked up by alarm
      > static int pcf2127_get_alarmwaked(struct device *dev, struct regmap *map)
      > {
      > 	int ret = 0;
      > 	unsigned int data = 0;
      > 
      >         ret = regmap_read(map, PCF2127_REG_CTRL2, &data);
      >         if(ret) {
      >                 dev_err(dev, "failed to read CTRL2\n");
      >                 return ret;
      >         }
      >         if((data & (PCF2127_BIT_CTRL2_AF | PCF2127_BIT_CTRL2_AIE))
      > 		== (PCF2127_BIT_CTRL2_AF | PCF2127_BIT_CTRL2_AIE)) {
      > 		waked_alarm = 1;	// waked up by alarm
      >         } else {
      > 		waked_alarm = 0;
      > 	}
      > 
      > 	return 0;
      > }
      

    2. pcf2127_probe() 内で上記の関数をコールする。ただし、改善6 で作成した、割り込みを初期化する関数よりも前にコールする

      > 	//!! get waked_alarm flag
      > 	ret = pcf2127_get_alarmwaked(dev, pcf2127->regmap);
      > 	if(ret) {
      >                 dev_err(dev, "%s: failed to get waked_alarm flag\n",
      >                         __func__);
      >                 return ret;
      > 	}
      

    3. アラーム起動情報を持っている変数を表示する関数を作成する

      > //!! make /sys/class/rtc/rtc?/alarmwaked interface
      > static ssize_t alarmwaked_show(struct device *dev,
      >         struct device_attribute *attr, char *buf)
      > {
      >         return sprintf(buf, "%d\n", waked_alarm);
      > }
      > static DEVICE_ATTR_RO(alarmwaked);
      

      include/device.h や sysfs.h に DEVICE_ATTR* のマクロに関する定義がされており、一部引用すると以下のようになる。
      これらの記述から、このマクロに対して例えば alarmwaked を引数に与えたときの動作がわかる

      • 作成するファイルシステムのファイル名は alarmwaked になる
      • struct device_attribute 構造体の、dev_attr_alarmwaked という変数が作成される
      • ファイルシステムを読みだしたときの処理関数は、alarmwaked_show という関数名で作成する必要がある
      • ファイルシステムに書き込みをしたときの処理関数は、alarmwaked_store という関数名で作成する必要がある
      • DEVICE_ATTR マクロを使ったときは、処理関数の名前や、作成されるファイルの属性を指定することができる

      ここで作成された dev_attr_alarmwaked 変数を、rtc_add_group() や device_create_file() に渡すと、目的のファイルシステムが作成される。


      device.h
      ----------
      #define DEVICE_ATTR_RO(_name) \
      	struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
      
      #define DEVICE_ATTR_RW(_name) \ 
      	struct device_attribute dev_attr_##_name = __ATTR_RW(_name) 
      
      #define DEVICE_ATTR(_name, _mode, _show, _store) \ 
      	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) 
      ----------
      
      sysfs.h
      ----------
      #define __ATTR_RO(_name) {						\
      	.attr	= { .name = __stringify(_name), .mode = 0444 },		\
      	.show	= _name##_show,						\
      }
      
      #define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)
      ----------
      

    4. 上記の関数が、ファイル読み出しのときに実行されるように登録する。ここでは rtc/sysfs.c で定義される rtc_add_group() を使った登録を行う

      static struct attribute *pcf2127_attrs[] = {
              &dev_attr_timestamp0.attr,
              &dev_attr_alarmwaked.attr,      //!! added
              NULL
      };
      

    5. make してモジュールディレクトリにコピーし、dep を更新する。
      $ make
      $ sudo cp rtc-pcf2127-11.ko /lib/modules/`uname -r`/kernel/drivers/rtc/
      $ sudo depmod -a          # module.dep を自動で再構成
      

    6. 再起動すると、/sys/class/rtc/rtc0/alarmwaked というファイルが作成されている。これを読み出すことでアラームで起動したかどうかわかる。アラームによって起動したときは 1。アラーム以外で起動したときは 0 が得られる。
      $ cat /sys/class/rtc/rtc0/alarmwaked
      0        # アラーム以外で起動した
      
      $ cat /sys/class/rtc/rtc0/alarmwaked
      1        # アラームで起動した
      

    [補足]
    linux は、システムクロックが正確でありかつ rtc が接続された状態のとき、現在時刻とアラーム時刻を 11分ごとに rtc に書き込みに行くという動作をする。この動作の副作用として、ユーザー及び rtc のドライバモジュールが想定していないタイミングで 割り込みフラグがリセットされる。結果として、ユーザーは現在の OS が rtc のアラーム機能によって起動したのか、それ以外の理由で起動したかの区別ができなくなる。これを解消するための実装である。

    改善9:バッテリーが切れていないかユーザーが知ることができる機能をファイルシステムとして実装する

    改善8 と同様の手法で、バッテリーが大丈夫かどうか(2.5V以上あるかどうか)をファイルシステムを使って読み出せるようにする。

    1. バッテリー切れの時 1、バッテリーが大丈夫の時 0 を返す関数を作成する

      > //!! make /sys/class/rtc/rtc?/batterylow interface
      > static ssize_t batterylow_show(struct device *dev,
      >         struct device_attribute *attr, char *buf)
      > {
      >         struct pcf2127 *pcf2127 = dev_get_drvdata(dev->parent);
      > 	int ret = 0;
      > 	unsigned int data = 0;
      > 
      >         ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL3, &data);
      >         if (ret) {
      >                 dev_err(dev, "failed to read CTRL3\n");
      >                 return ret;
      >         }
      > 
      > 	if((data & PCF2127_BIT_CTRL3_BLF) != 0 ) {
      > 		data = 1;
      > 	} else {
      > 		data = 0;
      > 	}
      > 
      >         return sprintf(buf, "%u\n", data);
      > }
      > static DEVICE_ATTR_RO(batterylow);
      

    2. 上記の関数が、インターフェイス読み出しのときに実行されるように登録する。ここでは rtc/sysfs.c で定義される rtc_add_group() を使った登録を行う

      static struct attribute *pcf2127_attrs[] = {
              &dev_attr_timestamp0.attr,
              &dev_attr_alarmwaked.attr,      //!! added
              &dev_attr_batterylow.attr,      //!! added
              NULL
      };
      

    3. make してモジュールディレクトリにコピーし、dep を更新する。
      $ make
      $ sudo cp rtc-pcf2127-11.ko /lib/modules/`uname -r`/kernel/drivers/rtc/
      $ sudo depmod -a          # module.dep を自動で再構成
      

    4. 再起動すると、/sys/class/rtc/rtc0/batterylow というファイルが作成されている。これを読み出すことでバッテリーが切れているかどうかわかる。バッテリーが切れているときは 1。大丈夫なときは 0 が得られる。
      $ cat /sys/class/rtc/rtc0/batterylow
      1        # バッテリーの電圧が 2.5V 以下
      
      $ cat /sys/class/rtc/rtc0/batterylow
      0        # バッテリーの電圧が 2.5V 以上
      

    バッテリー動作中の注意点と rtc に時刻設定するときのコツ

    以下はデータシートに記述がある内容。

    カーネルが11分周期で rtc 時間を書き換えに行くのをやめさせる

    ubuntu の場合。

    [参考] [ https://wiki.archlinux.jp/index.php/Systemd-timesyncd ] [ https://gist.github.com/261shimizu/e4b4ec94a02d57537192fd17c8d86c08 ] [ http://luozengbin.github.io/blog/2015-06-27-%5B%E3%81%BE%E3%81%A8%E3%82%81%5Dlinux%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E6%99%82%E5%88%BB%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6.html ] [ https://qiita.com/slug/items/5165d60860d3dc9d4f9c ]

    raspberru pi に rtc を接続するにあたって参考にしたサイト

    https://www.nxp.com/products/peripherals-and-logic/signal-chain/real-time-clocks/rtcs-with-spi/accurate-rtc-with-battery-backup-selectable-ic-bus-or-spi:PCF2129 https://docs.rs/rpi_embedded/0.1.0/rpi_embedded/i2c/index.html
    https://learn.adafruit.com/adding-a-real-time-clock-to-raspberry-pi/set-rtc-time
    https://qiita.com/progrunner17/items/d2ab0a85b3881a4b7ed8
    http://sweng.web.fc2.com/ja/linux/ubuntu/build-kernel-module.html
    https://qiita.com/iwatake2222/items/1fdd2e0faaaa868a2db2
    https://qiita.com/hana_shin/items/a412bc7b1023be807c82
    http://luozengbin.github.io/blog/2015-06-27-%5B%E3%81%BE%E3%81%A8%E3%82%81%5Dlinux%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E6%99%82%E5%88%BB%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6.html
    http://manpages.ubuntu.com/manpages/bionic/ja/man8/hwclock.8.html
    https://www7390uo.sakura.ne.jp/wordpress/archives/359
    https://qastack.jp/ubuntu/438576/shut-down-linux-server-and-turn-on-automatically-at-specific-time
    https://www.kernel.org/doc/Documentation/i2c/instantiating-devices
    https://qiita.com/mantaz/items/1c1b1ec7369cbd1afb68
    https://qiita.com/eofz/items/20cfa99601756fc98905
    https://www.raspberrypi.org/documentation/configuration/device-tree.md

    以上。


    raspberry pi 4B + ubuntu 64bit に rtc をつないだ環境で地デジ録画する

    以下の環境で、rtc を使って録画するときにラズパイを起動させ、録画もしくはエンコードが終わると halt 状態に移行するようなツール一式を作成した。 作成したツールは、EPGStation の録画予約・エンコード状況を取得し、シャットダウンの可否を判定したり、rtc にアラーム時刻を設定するスクリプトが主である。
    以下で配布している。

    https://github.com/nekokomaru/epgrtc-tools

    開発メモ


    その他雑多メモ


    Yachiyo. https://nekokohouse.sakura.ne.jp/