LinuxでHM-TL10Tのタッチパネルを認識させる

HanwhaのHM-TL10Tには、4線抵抗式タッチパネルが搭載されているが、git://gitorious.org/rowboat/manifest.gitのAndroidに搭載されているLinuxカーネル(2.6.32)は、そのままでは認識しない。
今回は、タッチパネルをLinuxに認識させる為に行った、Linuxデバイスドライバの修正記録。

HM-TL10Tのタッチパネルコントローラ

完成品のディスプレイを購入すると、仕様が公開されている範囲でしか内部の構造を知ることが出来ない。
付属の取扱説明書や、Web上の製品上を見ても、HM-TL10T内でどこのタッチパネルコントローラを使っているかを知ることはできなかった。
ただ、Web上のWindows向けタッチパネルドライバをダウンロードして中身を確認すると、eGalaxというところのコントローラを使っているらしい事がわかる。
eGalaxのWebページを見ていると、Linux向けのデバイスドライバのソースも公開されているのを発見。まずは、これを使ってみることにする。

Ubuntu上で動作確認

最初からドライバをBeagleBoard上のAndroidで試すのはハードルが高いので、まずはホストPC上で動いているUbuntuで試すことにしてみる。
デバイスドライバに同梱されていたPDFのインストラクションに従ってドライバをインストールしたところ、無事タッチパネルを認識。Ubuntu上では利用できるようになった。
ただ、ドライバのソースや利用方法を見てみると、このドライバは、X Window Systemを前提としている様子。AndroidLinuxカーネルは利用しているものの、Xは使っていないようなので、このドライバがAndroid上で正しく動作するかは怪しい。
自分でドライバを書く必要があるのかもしれない。

カーネルコンフィギュレーションをチェック

Androidでタッチパネルを認識させた実績があるかどうかをWeb上で探していたところ、hdk_embeddedさんのブログの中に、

<*> USB Touchscreen Driver
[ ] eGalax, eTurboTouch CT-410/510/700 device support
...

という記述を発見。これは、Linuxカーネルのmenuconfigの設定画面。eGalax向けのデバイスドライバは既に存在しているらしい。このドライバを有効にしてカーネルイメージを作成すれば、認識されるようになるかも知れない。
そこで、以下のコマンドを実行して、menuconfigを起動してみる:

# RowboatのAndroidソース内のLinuxカーネルのディレクトリにcdしている前提
$ make ARCH=arm CROSS_COMPILE=../prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- opam3_beagle_android_defconfig
$ make ARCH=arm CROSS_COMPILE=../prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- menuconfig

1行目のコマンドは、BeagleBoard Android向けのデフォルトカーネルコンフィギュレーションを使用する為に必要。
起動した設定画面中で、

Device Drivers > Input Device Support > Touchscreens

を有効化し、さらに、

Device Drivers > Input Device Support > Touchscreens > USB Touchscreen Driver
Device Drivers > Input Device Support > Touchscreens > USB Touchscreen Driver > eGalax, eTurboTouch CT-410/510/700 device support

の二つを有効化して、カーネルコンフィギュレーションを更新する。
次に、以下のコマンドを実行して、設定したコンフィギュレーションを基にカーネルイメージを作成する。

$ make ARCH=arm CROSS_COMPILE=../prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi- uImage

このイメージをmicroSDにコピーし、BeagleBoad上で起動し、タッチパネルを接続してみたものの、正しく動作しない、、、
画面を色々とタッチしていると、たまにタッチを検出しているようなので、コントローラ自体は認識できているものの、検出している情報は全く正しくない様子。
BeagleBoardと、ホストPCをUSBケーブルで接続して、認識されているコントローラの情報を確認してみる:

$ cd ~/android-sdk-linux_x86/platform-tools
# BeagleBoardがホストPCから見えているか確認
# "20100720"というのが、BeagleBoardのデバイスID
$ ./adb devices
List of devices attached
20100720        device
# デバイスIDを指定して、リモートシェルにログインする
$ sudo ./adb -s 20100720 shell
# BeagleBoard上のLinuxで認識されているUSBデバイスの一覧を表示
# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/gpio-keys/input/input0
U: Uniq=
H: Handlers=event0 
B: EV=3
B: KEY=100000 0 0 0 0 0 0 0 0

#
# ...(途中省略)...
#

I: Bus=0003 Vendor=0eef Product=0001 Version=0100
N: Name="eGalax Inc. USB TouchController"
P: Phys=usb-ehci-omap.0-2.3.4/input0
S: Sysfs=/devices/platform/ehci-omap.0/usb1/1-2/1-2.3/1-2.3.4/1-2.3.4:1.0/input/input4
U: Uniq=
H: Handlers=mouse1 event4 
B: EV=b
B: KEY=400 0 0 0 0 0 0 0 0 0 0
B: ABS=3

最後のName="eGalax Inc. USB TouchController"が認識されたコントローラの様子。
やはり、コントローラ自体はeGalaxのものの様だが、デバイスドライバ側の処理が正しくないのかもしれない。
そこで、デバイスドライバの中身を確認してみる。

デバイスドライバの解析

カーネルのmenuconfig内の

Device Drivers > Input Device Support > Touchscreens > USB Touchscreen Driver > eGalax, eTurboTouch CT-410/510/700 device support

で、Helpを表示すると、

There is no help available for this kernel option.
Symbol: TOUCHSCREEN_USB_EGALAX [=y]
Prompt: eGalax, eTurboTouch CT-410/510/700 device support

という記述がある。シンボル"CONFIG_TOUCHSCREEN_USB_EGALAX"("CONFIG_"は、カーネルコンフィグのプレフィックス)を検索してみると、

# RowboatのAndroidソース内のLinuxカーネルのディレクトリにcdしている前提
$ find . -name '*.[ch]' | xargs grep -3 CONFIG_TOUCHSCREEN_USB_EGALAX
./drivers/input/touchscreen/usbtouchscreen.c-    .bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE
./drivers/input/touchscreen/usbtouchscreen.c-
./drivers/input/touchscreen/usbtouchscreen.c-static struct usb_device_id usbtouch_devices[] = {
./drivers/input/touchscreen/usbtouchscreen.c:#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX
./drivers/input/touchscreen/usbtouchscreen.c-    /* ignore the HID capable devices, handled by usbhid */
./drivers/input/touchscreen/usbtouchscreen.c-    {USB_DEVICE_HID_CLASS(0x0eef, 0x0001), .driver_info = DEVTYPE_IGNORE},
./drivers/input/touchscreen/usbtouchscreen.c-    {USB_DEVICE_HID_CLASS(0x0eef, 0x0002), .driver_info = DEVTYPE_IGNORE},
--
./drivers/input/touchscreen/usbtouchscreen.c- * eGalax part
./drivers/input/touchscreen/usbtouchscreen.c- */
./drivers/input/touchscreen/usbtouchscreen.c-
./drivers/input/touchscreen/usbtouchscreen.c:#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX
./drivers/input/touchscreen/usbtouchscreen.c-
./drivers/input/touchscreen/usbtouchscreen.c-#ifndef MULTI_PACKET
./drivers/input/touchscreen/usbtouchscreen.c-#define MULTI_PACKET
--
./drivers/input/touchscreen/usbtouchscreen.c-#endif
./drivers/input/touchscreen/usbtouchscreen.c-
./drivers/input/touchscreen/usbtouchscreen.c-static struct usbtouch_device_info usbtouch_dev_info[] = {
./drivers/input/touchscreen/usbtouchscreen.c:#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX
./drivers/input/touchscreen/usbtouchscreen.c-    [DEVTYPE_EGALAX] = {
./drivers/input/touchscreen/usbtouchscreen.c-        .min_xc        = 0x0,
./drivers/input/touchscreen/usbtouchscreen.c-        .max_xc        = 0x07ff,

どうやら、usbtouchscreen.cというソースの中で使われている様子。
usbtouchscreen.cのCONFIG_TOUCHSCREEN_USB_EGALAXのパートと、eGalaxコントローラのパケットプロトコルの仕様を比較すると、実装に不備はないように見える。

一旦、usbtouchscreen.cに以下の修正を施してイメージを作成し、どのようなパケットがタッチコントローラから流れてきているかを監視してみることにする:

--- usbtouchscreen.c    2011-02-14 05:02:01.000000000 +0900
+++ usbtouchscreen.c.modified    2011-03-29 10:53:27.000000000 +0900
@@ -952,6 +952,9 @@
     /* loop over the received packet, process */
     pos = 0;
     while (pos < buf_len) {
+        pr_info("buffer + %d = %x", pos, buffer + pos);
+        pos++;
+        continue;
+
         /* get packet len */
         pkt_len = usbtouch->type->get_pkt_len(buffer + pos,
                             buf_len - pos);

※pr_info()関数は、printk(KERN_INFO ...)関数のマクロ。include/linux/kernel.hでdefineされている。
この修正を施した状態でカーネルイメージを作成し、BeagleBoard上でAndroidを起動して、タッチパネル上をタッチしてみる。その後、BeagleBoadにホストPCにリモートログインし、ログを出力してみると、以下のようなログが得られる:

$ cd ~/android-sdk-linux_x86/platform-tools
$ sudo ./adb -s 20100720 shell dmesg
#
# ...(途中省略)...
#
<6>buffer + 0 = 2
<6>buffer + 1 = 3
<6>buffer + 2 = f0
<6>buffer + 3 = e
<6>buffer + 4 = 6f
<6>buffer + 5 = 0
<6>buffer + 0 = 2
<6>buffer + 1 = 3
<6>buffer + 2 = f0
<6>buffer + 3 = e
<6>buffer + 4 = 6e
<6>buffer + 5 = 0
<6>buffer + 0 = 2
<6>buffer + 1 = 3
<6>buffer + 2 = f1
<6>buffer + 3 = e
<6>buffer + 4 = 6f
<6>buffer + 5 = 0
<6>buffer + 0 = 2
<6>buffer + 1 = 3
<6>buffer + 2 = f2
<6>buffer + 3 = e
<6>buffer + 4 = 6e
<6>buffer + 5 = 0
<6>buffer + 0 = 2
<6>buffer + 1 = 3
<6>buffer + 2 = f2
<6>buffer + 3 = e
<6>buffer + 4 = 6d
<6>buffer + 5 = 0
<6>buffer + 0 = 2
<6>buffer + 1 = 2
<6>buffer + 2 = f2
<6>buffer + 3 = e
<6>buffer + 4 = 6d
<6>buffer + 5 = 0

どうやらパケット長は6 byteらしい。色々な場所をタッチしながらパケットの挙動を調べた結果、各バイトの持つ意味は、以下の通りであることがわかった:

byte purpose
0 0x02: ?
1 0x03: press, 0x02: release
2 lower byte of y
3 upper byte of y (0~3bit)
4 lower byte of x
5 upper byte of x (0~3bit)

0 byte目は、常に0x02のまま。パケットの種類を表しているのか。
eGalaxのプロトコルによると、パケットには2種類あり、診断パケットの場合は、0 byte目が0x0aレポートパケット(タッチ情報パケット)の場合は、0 byte目の最上位ビットが1。0x02はどちらにも該当しない。仕様書とパケットの内容が異なるとは、どういうことなのか、、、
ただ、タッチ情報を正しく検出するのが目的なので、今回は1〜5 byte目の情報だけ処理出来れば良い。0 byte目は無視することにする。
タッチパネルの四隅の座標は以下の様になっている事がわかった:

これらの情報は、usbtouchscreen.c中のusbtouch_device_info構造体のmin_xc,max_xc,min_yc,max_ycにセットする。座標は、画面左上が原点であることが前提。

デバイスドライバの修正

以上の解析結果を踏まえると、実は今回のタッチパネルは、CONFIG_TOUCHSCREEN_USB_EGALAXのソースではなくて、TOUCHSCREEN_USB_GENERAL_TOUCHのソースで十分処理できることがわかる。
TOUCHSCREEN_USB_EGALAXでは、usbtouch_device_info構造体のprocess_pkt、get_pkt_len関数ポインタメンバ変数に関数を渡しているが、これはパケットが複数種類(eGalaxの場合、診断パケットとレポートパケット)存在する場合に、必要なメンバ変数。
今回のタッチパネルコントローラは、パケットを監視している限りでは、1種類しかパケットが存在していないようなので、これらのメンバ変数を指定する必要がない。このようなコントローラに対しては、TOUCHSCREEN_USB_GENERAL_TOUCHでドライバが実装できる。
結局、usbtoushscreen.cに対する変更は、以下の様になる:

--- usbtouchscreen.c    2011-02-14 05:02:01.000000000 +0900
+++ usbtouchscreen.c.modified    2011-03-28 12:32:58.000000000 +0900
@@ -198,6 +198,7 @@
 
 #ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH
     {USB_DEVICE(0x0dfc, 0x0001), .driver_info = DEVTYPE_GENERAL_TOUCH},
+    {USB_DEVICE(0x0eef, 0x0001), .driver_info = DEVTYPE_GENERAL_TOUCH},
 #endif
 
 #ifdef CONFIG_TOUCHSCREEN_USB_GOTOP
@@ -618,10 +619,10 @@
 #ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH
 static int general_touch_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
 {
-    dev->x = ((pkt[2] & 0x0F) << 8) | pkt[1] ;
-    dev->y = ((pkt[4] & 0x0F) << 8) | pkt[3] ;
-    dev->press = pkt[5] & 0xff;
-    dev->touch = pkt[0] & 0x01;
+    dev->x = ((pkt[5] & 0x0F) << 8) | (pkt[4] & 0xFF);
+    dev->y = ((pkt[3] & 0x0F) << 8) | (pkt[2] & 0xFF);
+    dev->y = 2 * 0x07c8 - dev->y;
+    dev->touch = pkt[1] & 0x01;
 
     return 1;
 }
@@ -808,11 +809,11 @@
 
 #ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH
     [DEVTYPE_GENERAL_TOUCH] = {
-        .min_xc        = 0x0,
-        .max_xc        = 0x0500,
-        .min_yc        = 0x0,
-        .max_yc        = 0x0500,
-        .rept_size    = 7,
+        .min_xc        = 0x05c,
+        .max_xc        = 0x0f92,
+        .min_yc        = 0x070,
+        .max_yc        = 0x0f20,
+        .rept_size    = 6,
         .read_data    = general_touch_read_data,
     },
 #endif
+    {USB_DEVICE(0x0eef, 0x0001), .driver_info = DEVTYPE_GENERAL_TOUCH},

は、今回のコントローラが認識されるように、Vendor IDとProduct IDを登録している部分。

+    dev->x = ((pkt[5] & 0x0F) << 8) | (pkt[4] & 0xFF);
+    dev->y = ((pkt[3] & 0x0F) << 8) | (pkt[2] & 0xFF);
+    dev->y = 2 * 0x07c8 - dev->y;
+    dev->touch = pkt[1] & 0x01;

は、デバイス側で受け取ったパケットを、座標情報や、タッチ情報に変換している部分。これは、パケットを解析した結果を基に実装している。
"dev->y = 2 * 0x07c8 - dev->y"は、ドライバは、画面左上が原点であることを前提としているが、タッチパネルコントローラは、左下が原点とした位置情報を送信してくるので、ドライバの前提に合うように、座標情報を変換している部分。

+        .min_xc        = 0x05c,
+        .max_xc        = 0x0f92,
+        .min_yc        = 0x070,
+        .max_yc        = 0x0f20,
+        .rept_size    = 6,

は、タッチパネルの四隅の座標を検出結果を反映させている。rept_sizeメンバ変数は、パケットの長さを示している。今回は、6 byte。
以上の修正をして、今度は以下のカーネルコンフィギュレーションを有効にする。

Device Drivers > Input Device Support > Touchscreens > USB Touchscreen Driver
Device Drivers > Input Device Support > Touchscreens > USB Touchscreen Driver > GeneralTouch Touchscreen device support

逆に、eGalaxタッチスクリーンの設定は無効化。
設定の変更を保存し、再度カーネルイメージを作成し、BeagleBoadで起動すると、無事にタッチスクリーンが認識され、位置情報も正しく検出できるようになった。

参考文献

Linuxデバイスドライバ全般に関しての見識を得るためには、必携:

Linuxデバイスドライバ 第3版

Linuxデバイスドライバ 第3版

今後

ハードウェア的には最低限の環境が整ったので、今後はAndroid上のソフトウェアを解析していく予定。