FTDI FT4232H 用の Linux ドライバ
»
年末の話しです。
Armadillo と組み合わせて使うインターフェースボード上のRS-232Cコントローラとして、
FTDI FT4232Hを採用しました。
今まで FTDI FT232RL を複数個使っていたのですが、FT4232H は内部に4チャンネルもっているので、
かなり実装面積を減らすことができます。
ソフトウェア的には大きな違いはないだろうと思っていたのですが、繋いでみてびっくり!
全く認識しませんでした・・・
理由は簡単な話で、Armadillo の最新カーネルである Linux 2.6.26 はまだ FT4232H に対応していないからです。
パソコンであればカーネルのバージョンアップや、
メーカーサイトで配布されているドライバを利用できますが、
Armadillo は多少特殊なハードなので、それは面倒です。
そんなときは、ドライバの移植が簡単ですね!
ということで、1時間程度で対応カーネルを参考にドライバを移植しました。
USBタイプのデバイスで、同じような制御のはずなのに認識しない場合、
デバイスのID(Product ID)を追加するだけで動作することがあります。
今回もご多分に漏れず、
FT4232H の ID である 0xFBFA を追加したところ、認識はするようになりました。
しかし、内部の2チャンネルしか動かない!
このあたりは、地道に変更点を調べて直すしか有りません。
ということで、パッチです。
誰も使う人はいないかもしれませんが・・・
無保証なので、万が一使う人はちゃんと吟味してから使ってくださいね。
もし、間違いがあったら教えていただければ大変うれしいです。
ちなみに、結構手抜きで、
「
} else if (inter == 2) { priv->interface = 3; } else if (inter == 3) { priv->interface = 4; }
」
みたいな感じになっていたりします・・・
--- ftdi_sio.c.orig 2013-12-29 23:52:44.000000000 +0900 +++ ftdi_sio.c 2014-01-07 19:51:19.000000000 +0900 @@ -151,6 +151,7 @@ { USB_DEVICE(FTDI_VID, FTDI_8U232AM_ALT_PID) }, { USB_DEVICE(FTDI_VID, FTDI_232RL_PID) }, { USB_DEVICE(FTDI_VID, FTDI_8U2232C_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_4232H_PID) }, { USB_DEVICE(FTDI_VID, FTDI_MICRO_CHAMELEON_PID) }, { USB_DEVICE(FTDI_VID, FTDI_RELAIS_PID) }, { USB_DEVICE(FTDI_VID, FTDI_OPENDCC_PID) }, @@ -663,6 +664,8 @@ [FT232BM] = "FT232BM", [FT2232C] = "FT2232C", [FT232RL] = "FT232RL", + [FT2232H] = "FT2232H", + [FT4232H] = "FT4232H" }; @@ -707,6 +710,8 @@ static unsigned short int ftdi_232am_baud_to_divisor (int baud); static __u32 ftdi_232bm_baud_base_to_divisor (int baud, int base); static __u32 ftdi_232bm_baud_to_divisor (int baud); +static __u32 ftdi_2232h_baud_base_to_divisor(int baud, int base); +static __u32 ftdi_2232h_baud_to_divisor(int baud); static struct usb_serial_driver ftdi_sio_device = { .driver = { @@ -791,6 +796,36 @@ return(ftdi_232bm_baud_base_to_divisor(baud, 48000000)); } +static __u32 ftdi_2232h_baud_base_to_divisor(int baud, int base) +{ + static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 }; + __u32 divisor; + int divisor3; + + /* hi-speed baud rate is 10-bit sampling instead of 16-bit */ + divisor3 = (base / 10 / baud) * 8; + + divisor = divisor3 >> 3; + divisor |= (__u32)divfrac[divisor3 & 0x7] << 14; + /* Deal with special cases for highest baud rates. */ + if (divisor == 1) + divisor = 0; + else if (divisor == 0x4001) + divisor = 1; + /* + * Set this bit to turn off a divide by 2.5 on baud rate generator + * This enables baud rates up to 12Mbaud but cannot reach below 1200 + * baud with this bit set + */ + divisor |= 0x00020000; + return divisor; +} + +static __u32 ftdi_2232h_baud_to_divisor(int baud) +{ + return ftdi_2232h_baud_base_to_divisor(baud, 120000000); +} + #define set_mctrl(port, set) update_mctrl((port), (set), 0) #define clear_mctrl(port, clear) update_mctrl((port), 0, (clear)) @@ -973,6 +1008,19 @@ baud = 9600; } break; + case FT2232H: /* FT2232H chip */ + case FT4232H: /* FT4232H chip */ + if ((baud = 1200)) { + div_value = ftdi_2232h_baud_to_divisor(baud); + } else if (baud < 1200) { + div_value = ftdi_232bm_baud_to_divisor(baud); + } else { + dbg("%s - Baud rate too high!", __func__); + div_value = ftdi_232bm_baud_to_divisor(9600); + div_okay = 0; + baud = 9600; + } + break; } /* priv->chip_type */ if (div_okay) { @@ -1083,14 +1131,28 @@ if (interfaces > 1) { int inter; - /* Multiple interfaces. Assume FT2232C. */ - priv->chip_type = FT2232C; + /* Multiple interfaces.*/ + if (version == 0x0800) { + priv->chip_type = FT4232H; + /* Hi-speed - baud clock runs at 120MHz */ + priv->baud_base = 120000000 / 2; + } else if (version == 0x0700) { + priv->chip_type = FT2232H; + /* Hi-speed - baud clock runs at 120MHz */ + priv->baud_base = 120000000 / 2; + } else + priv->chip_type = FT2232C; + /* Determine interface code. */ inter = serial->interface->altsetting->desc.bInterfaceNumber; if (inter == 0) { priv->interface = PIT_SIOA; - } else { + } else if (inter == 1) { priv->interface = PIT_SIOB; + } else if (inter == 2) { + priv->interface = 3; + } else if (inter == 3) { + priv->interface = 4; } /* BM-type devices have a bug where bcdDevice gets set * to 0x200 when iSerialNumber is 0. */ @@ -1225,7 +1287,9 @@ if ((!retval) && (priv->chip_type == FT232BM || priv->chip_type == FT2232C || - priv->chip_type == FT232RL)) { + priv->chip_type == FT232RL || + priv->chip_type == FT2232H || + priv->chip_type == FT4232H)) { retval = device_create_file(&port->dev, &dev_attr_latency_timer); } @@ -1244,7 +1308,9 @@ device_remove_file(&port->dev, &dev_attr_event_char); if (priv->chip_type == FT232BM || priv->chip_type == FT2232C || - priv->chip_type == FT232RL) { + priv->chip_type == FT232RL || + priv->chip_type == FT2232H || + priv->chip_type == FT4232H) { device_remove_file(&port->dev, &dev_attr_latency_timer); } } @@ -2201,6 +2267,8 @@ case FT232BM: case FT2232C: case FT232RL: + case FT2232H: + case FT4232H: /* the 8U232AM returns a two byte value (the sio is a 1 byte value) - in the same format as the data returned from the in point */ if ((ret = usb_control_msg(port->serial->dev, --- ftdi_sio.h.orig 2013-12-29 23:52:44.000000000 +0900 +++ ftdi_sio.h 2013-12-29 23:52:44.000000000 +0900 @@ -27,6 +27,7 @@ #define FTDI_8U232AM_PID 0x6001 /* Similar device to SIO above */ #define FTDI_8U232AM_ALT_PID 0x6006 /* FTDI's alternate PID for above */ #define FTDI_8U2232C_PID 0x6010 /* Dual channel device */ +#define FTDI_4232H_PID 0x6011 /* Quad channel hi-speed device */ #define FTDI_232RL_PID 0xFBFA /* Product ID for FT232RL */ #define FTDI_RELAIS_PID 0xFA10 /* Relais device from Rudolf Gugler */ #define FTDI_NF_RIC_VID 0x0DCD /* Vendor Id */ @@ -974,6 +975,8 @@ FT232BM = 3, FT2232C = 4, FT232RL = 5, + FT2232H = 6, + FT4232H = 7 } ftdi_chip_type_t; typedef enum {
SpecialPR