Advantech dumb multi-port serial PCI boards
Driver for Linux 2.6
by Frank Rysanek of FCC prumyslove systemy s.r.o.

2014: it seems that PLX is going to phase out the Oxford UART's without a replacement by the end of 2014. R.I.P. Oxford :-(

Download locations

Kernel version

This driver is 2.6-only, as it's using the 2.6-specific serial API.

The driver does some Advantech and Oxford HW-specific PCI init, and hooks up the standard Linux 2.6 serial routines to the hardware. The "driver" is really just a shim for the PCI devices and for their HW support of the RS485 half-duplex protocol.

The hardware indeed contains some fairly standard serial UART's, 16C550A compatible, but in fact the vanilla kernel even recognizes the actual 16C950/954 UART and some of its special features.

Unfortunately, as the vanilla kernel lacks one or two tiny bits to properly support the RS485 hardware-based RX/TX switching feature of the OX16C950 et al., it's necessary to apply a tiny patch to the vanilla kernel (and recompile the kernel). Sorry about that... Otherwise, the driver is compiled outside of the kernel source tree (as a standalone module).

Building the driver

First of all, you'll have to apply a tiny patch to your vanilla kernel:

  cd /usr/src/linux-2.6.Y.Z/
  patch -p0 < /usr/src/pci-16xx-1.Y/kernel/8250.c.patch

Also, please check that the re-definition of 'struct uart_8250_port' within pci-16xx.c and pci-1604.c matches the one within your vanilla 8250.c. Manual copy'n'paste is needed e.g. around 2.6.24.

Note that we'll be using the standard 8250.ko driver, so you need to allow the "standard PC serial port" support in the vanilla kernel's compile-time configuration (make menuconfig). Remember to enable IRQ sharing for serial ports. On the other hand, you don't need "support for PCI serial ports" - we're not using that module, which is hardware-specific to other vendors. You can get to know more about additional relevant menuconfig entries in the following chapter on "Inserting the driver" (defaults are OK).

...and recompile your kernel.

Next, compile the driver module. If you're compiling the module against the currently running kernel, feel free to run ./build-2.6 to compile it.

  cd /usr/src/pci-16xx-1.Y/kernel/

If that fails, check your kernel source tree root directory specified in build-2.6. By default, the file tries to infer and use the source tree directory of the currently running kernel.

If you're compiling against a different kernel, get inspired by build-2.6, it's essentially a one-liner.

Alternatively, there's a second patch attached, that will add the necessary menuconfig entry to serial Kconfig and Makefile in your vanilla kernel. Add the the .h and .c files from this driver package and you have this driver internalized, for monolithic compilation.

Inserting the driver

At modprobe, the driver detects the PCI devices, and, in order to register their serial ports in the vanilla kernel, it calls some vanilla callbacks that are available for exactly that purpose. The point is to hook up the vanilla serial routines to the PCI-based serial UART's. Upon this vanilla registration, the vanilla serial driver obtains a base address and then probes and detects the UART type.

The point is, that your Advantech PCI serial ports will appear in the system as standard serial devices (which they really are): /dev/ttyS<something>, using the well-known major/minor numbers, and the well-known IOCTL's.

There are several important kernel configuration options to the vanilla kernel's serial driver, in menuconfig they're all located under "Extended 8250/16550 serial driver options"

= Support more than 4 legacy serial ports (enable this)

= Support for sharing serial interrupts (enable this)

Then there are some table spacing entries:

= Maximum number of 8250/16550 serial ports (default: 32)

= Number of 8250/16550 serial ports to register at runtime (default: 4)

The first one is OK, 32 should be enough (unless you know that it's not). Please note that the latter can be increased by a kernel command-line parameter (or a modprobe arg to serial.ko) - see below why and how.

On PC machines, the first four UART "slots" are actually pre-defined to the well-known ISA dumb COM port parameters, any later ports remain allocated but undefined (if you increased RUNTIME_UARTS). Check that with setserial. If you only have two physical COM ports onboard in your PC, and you have RUNTIME_UARTS at the default 4, the vanilla serial driver will recycle the pre-defined but undetected two slots, but any further PCI serial ports will remain unreachable.

If you get some or all PCI serial ports unassigned, try adding

   8250.nr_uarts=32 to lilo.conf


   nr_uarts=32 to modprobe args.

Or, modify the compile-time value preset in "make menuconfig" and recompile+reinstall your kernel.

The PCI-16xx driver package actually contains two driver modules: pci-16xx.ko and pci-1604.ko. See the hardware notes for details/reasons.

Both these modules accept an _optional_ command-line argument that can be used to override the HW-based RS485 steering flags. The syntax looks like this:

   modprobe pci-16xx.ko rs485_override=0x0C
   modprobe pci-16xx.ko rs485_override=0xFF,0x01
   or a lilo.conf append "pci-16xx.rs485_override=0x09"

The value is a bitmap of 8 bits. Bit 0 corresponds to port 0, bit 1 corresponds to port 1 etc. up to port 7 (= the 8th port) A logical 0 means HW-based RS485 disable, log.1 means RS485 enable. E.g., a value of 0x0C means

 RS485 OFF for ports 0,1
 RS485 ON for ports 2,3
 RS485 OFF for ports 4-7 (if present)

on the first board (board #0 in zero-based counting).

The modprobe cmdline arg is really an array, use a comma-separated list if you need to override several boards. You can also use a value of 0xFFFFFFFF (32 bits all log.1) to avoid overriding a particular board, so that the board will stick to the default.

The per-board default RS485 bitmask is read from the board's hardware RS485 capability register, if it has one, or the driver's internal default is used (board model specific).

You can use insmod or modprobe to insert the driver. If you use insmod, and you have all the serial drivers as modules, you'll have to insert them all explicitly, one by one:

  insmod serial_core
  insmod 8250 nr_uarts=32
  insmod pci-16xx

The warning that "detected caps 00000700 should be 00000100" is OK, the 16C950 UART indeed knows some additional tricks besides just the FIFO. The warning is somewhat inappropriate - at that particular point in the code, the driver expects only the basic 16C550A FIFO capability, and prints a cryptic warning when it detects a perfectly backward-compatible, yet more capable UART. That same source code file even knows the 16C950, knows the reported capabilities and can use them.

Supported hardware

This driver supports a family of cards, based on the Oxford Semiconductors OX16PCI954 (quad OX16C950 UART with a common PCI interface + a slave bridge), perhaps combined with the OX16C954 companion chip (ISA-based octal UART). The RS-485 board models also contain a universal "gate array" chip, used by Advantech to implement RS485 RX/TX switching logic.a

There's a separate driver module for boards containing an OX16PCI952, as this chip uses a different mapping of UARTs to PCI BARs and doesn't have an RS485 steering CPLD. This applies to the PCI-1603 and 1604.

The following models are explicitly supported:

Model     Tested?   Interface types               Driver module
PCI-1601 [tested] = 2x RS422/485 unisolated       pci-16xx.ko
PCI-1602          = 2x RS422/485 isolated         pci-16xx.ko
PCI-1603          = 2x RS232 or current loop      pci-1604.ko
PCI-1604 [tested] = 2x RS232                      pci-1604.ko
PCI-1610 [tested] = 4x RS232                      pci-16xx.ko
PCI-1611          = 4x RS422/485                  pci-16xx.ko
PCI-1612 [tested] = 4x RS232/422/485 unisolated   pci-16xx.ko
PCI-1620 [tested] = 8x RS232                      pci-16xx.ko
PCI-1622          = 8x RS422/485                  pci-16xx.ko
PCM-3642          = 8x RS232 PCI/104              pci-16xx.ko

The entries for PCI-1603, PCI-1611 and PCI-1622 were added based on a guess about the PCI ID's, let me know if the driver doesn't detect your board.

The driver specifically DOES NOT support other PCI-16xx models that don't follow this basic hardware setup, namely some "smart" multi-port boards marketed by Advantech under the following names:


Some embedded PC boxes by Advantech contain an onboard OX16PCI954, reporting itself as one of the PCI-16xx models. Known examples follow:

RS485 RX/TX steering

Boards that are capable of all RS232/422/485 have the following configuration controls, per channel:

  1. a bank of jumpers that toggles between only two options:

    (this is absent on models that are RS232-only or RS422/485-only)
  2. a single jumper that toggles between RS422/485. The RS422 position means, that the TX pair is strictly TX, TX always on, and the RX pair is always on. The RS485 position means, that the "TX" pair turns into a common half-duplex "DATA+/-" pair. The RX/TX steering in this RS485 half-duplex mode is handled by several possible means.
  3. an RS485 RX/TX flow control piano that toggles between two options:

At least, such is my interpretation based on experience. The descriptions in the manuals vary, and are usually somewhat magical / confusing / wrong. This means that the "ON" position is hardly any practical use, and the "AUTO" position is the only practicable option.

It seems that the #3) piano only has some effect in pure RS485 mode, i.e. not in RS422 mode (seems to be always a clear full duplex), let alone RS232 mode (uses a different set of line drivers/receivers).

The RS485 RX/TX steering works in such a way that both the line driver and receiver are controlled, i.e.:

=> if you ever need local echo in RS485 two-wire mode, set up your board for RS422 (full duplex) and wire RX and TX pairs in parallel.

The "auto" RS485 RX/TX steering is truly hardware-based, using a very simple trick. As has been published in the OX16PCI954 manual, and probably any OX16C950-based documentation as well, the OX16C950 UART has an option in the ACR register to re-configure the DTR output to follow the internal TEMT aka TSRE flag (Transmitter Empty). This flag follows the activity of the UART's output shift register (i.e., not the FIFO - the FIFO can be empty while the shift register is still hard at work). I.e., this TEMT flag provides better information than e.g. the "FIFO empty" IRQ source, and is really the only way to implement flawless RS485 RX/TX steering. If we get the TEMT flag mirrored to the DTR pin, we can use this to directly drive the enable/disable pin of an RS485 receiver to avoid local echo. Advantech's additional piano, OR'ed to the DTR output, is rather useless.

The TEMT can be mirrored to the DTR output in two ways: "active high" or "active low" Advantech uses "active high" i.e. when the transmitter is active, the DTR pin goes high (electrically log.1 on the output of the UART). This is what this Linux driver implements.

Please note that the original ~DTR signal, in its traditional functionality, is inverted - i.e., when your modem control software "drops DTR" to hang up the modem, the DTR pin goes electrically high. Actually the stock Linux serial driver "raises DTR" on device open(), which means that the output DTR pin goes electrically low. Thus, if you merely hack the driver to recognize the Advantech boards' UARTS, and don't cater for the ACR adjustment, and you jumper your Advantech board for "auto" RS485 mode, your software will be able to receive characters from the RS485 bus, but not send them - the RS485 Xceiver will be stuck in the RX mode.

The pci-16xx.ko and pci-1604.ko modules handle this right. If you call a device open() on a port with RS485 steering enabled, the TEMT-based DTR should work just right, and explicit DTR control from software is suppressed.

The remaining question is, exactly how does the driver know, that RS485 RX/TX steering is desired on a particular port? And the answer is: either the board hardware reports that, or it can be inferred from the board model. And, this auto-detected capability can be overridden at insmod or kernel command line (see above how that's done).

Boards equipped with the OX16PCI954 and four or less ports have an onboard CPLD, that's IOmapped to the OX16PCI954's PCI function 1 BAR 0. The RS485 capability is reported by individual bits in a single byte at offset 0x60.
The logical values of individual bits in the "bitmask register" correspond to RS485 capability of individual ports, and their meaning is the following:

Please note that:

The CPLD is not visible on a PCI-1620, but that's RS232 only. The CPLD is absent on PCI-1603/1604 (as their OX16PCI952 doesn't seem to have a slave bus), but these are RS232 only too. Can't say how this works on the PCI-1622, I don't have a sample - but this one's RS-422/485 only, so that's not a problem.

The RS422/485 capability register seems to be present on the UNO-2050 as well (at an unusual IO offset), but the flags don't follow the jumpered config for COM3/COM4 - they're just stuck at log.1 ! It's true that these ports don't have DTR physically available in the output pinout when in RS232 mode (RX/TX+RTS/CTS only), so the auto-DTR function of the 16C950 doesn't really matter. Feel free to override the detected capabilities if you need RS232 and you believe that this has any practical effect.

By overriding the detected/inferred RS485 capabilities at command-line, you can:


As for origins of all this information, in case someone was interested to know:

1) Oxford Semiconductors OX16PCI954 datasheet
2) reality, hands-on experience with the Advantech boards

The boards are really quite dumb, almost vanilla UART hardware. No software reverse-engineering was performed during the writing of this driver, no NDA-covered information was used.

The possible functions of FN1/BAR0, as well as all the other BARs, are well documented in the Oxford datasheet. The same is true about the ACR.3:4 configuration bits.

The rest can be observed on the board - by the IC partnumbers, by following the traces, by dumping the BARs, and by fiddling with the jumpers/switches, while looking at the output with an oscilloscope and some trivial software probes.

The bitmask register is not documented anywhere, but it's essentially the only non-zero byte in the whole FN1/BAR0 (an otherwise empty address space of 128 B).

It only took me a while to realize, that the two bitmask bits on a PCI-1601 don't correspond to the piano switches. For a while I even planned to just infer the port capabilities from board model or take them from the command line, before I realized that on some boards the two bits are simply hard-wired.