Advantech dumb multi-port serial PCI boards
Driver for Linux 2.6
by Frank Rysanek of FCC prumyslove systemy s.r.o.
<rysanek@fccps.cz>
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 :-(
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).
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/ ./build-2.6
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.
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"
SERIAL_8250_MANY_PORTS
= Support more than 4 legacy serial ports (enable this)
SERIAL_8250_SHARE_IRQ
= Support for sharing serial interrupts (enable this)
Then there are some table spacing entries:
CONFIG_SERIAL_8250_NR_UARTS
= Maximum number of 8250/16550 serial ports (default: 32)
CONFIG_SERIAL_8250_RUNTIME_UARTS
= 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
or
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.
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:
PCI-1625
PCI-1670
Some embedded PC boxes by Advantech contain an onboard OX16PCI954, reporting itself as one of the PCI-16xx models. Known examples follow:
UNO-2176 [tested] works like a PCI-1612
While COM1+COM2 are legacy RS232 COM ports, COM3..COM6 are PCI-based Oxford ports, very much like an add-on PCI-1612: 4x RS232/422/485. In contrast to the PCI-1612, the ports are galvanically isolated! The Oxford chip onboard the UNO-2176 has its own PCI ID's, different from the PCI-1612, but still vaguely PCI-16xx style.
The UNO-2176 implements inverted (active low) logic on the RX/TX steering pin of the UART (DTR) - inverted compared to the rest of the Advantech PCI-16xx family (active high). This is easily handled by configuring the UART's ICR appropriately. Thanks to Gillaume Bossard of UXP SA / France for reporting this difference and for sending an instant fix :-)
UNO-2059 [tested] reports a PCI-1612
Note that its RS232/422/485 line drivers are configured in a slightly different way than on a true PCI-1612. Not that it would make much of a difference, except maybe for the fact that COM1+COM2 are not capable of RS422 operation (balanced, full duplex), but only RS232 and RS485.
Furthermore, there are no legacy COM1..COM4, maybe the Geode has them but are disabled. The ports labeled COM1..COM4 on the chassis are detected in Linux as ttyS4 ... ttyS7 (COM5 - COM8), as they all belong to the OX16PCI954, and are configured by our driver.
UNO-3072 / UNO-3074 reports its own PCI ID's, works like a PCI-1604
COM1+COM2 are PCI-based Oxford ports, very much like an add-on PCI-1604, but capable of 2x RS232/422/485. The chip used is an OX16uPCI952. The Oxford chip onboard the UNO-3072/74 has its own PCI ID's, different from the PCI-1604, but still vaguely PCI-16xx style.
The board doesn't seem to have a flag that would allow the driver to auto-detect the jumpered (or BIOS-configured) configuration of RS485 support - you need to use the rs485_override argument. No need to use that if you're after RS232.
UNO-2050 [tested] reports its own PCI ID's, still vaguely PCI-16xx style
On this hardware, COM1 and COM2 are standard ISA-based 16C550A UART's of the Geode-based SOM - these are only capable of RS232.
COM3 and COM4 are isolated RS232(limited)/RS422/RS485, based on an OX16PCI954 quad UART (two ports are unused). In Linux, these are detected as ttyS4/ttyS5.
The second PCI FN of this Oxford chip is used as a generic slave bridge for some digital IO, a general purpose i8254 counter etc. (Maybe this is why they didn't use an OX16PCI952.)
The driver does provide optional support for the UNO-2050 "discrete digital inputs and outputs" along with marginal support for the onboard extra i8254 timer. This support should allow you to
To merely read and write the digital IO, open /dev/uno-2050-0 and read/write a single character at a time.
There's no IRQ handler yet. Ask me if you want one.
Artila MSM-4S : 4 ports, 2x RS232/422/485 + 2x RS232, pci-16xx.ko
This mini-PCI board contains an OX16PCI954 in a fairly basic setup. It is not known whether the jumpered configuration of ports is reflected somehow in the "mapped bus" PCI BAR, therefore the driver assumes by default that the first two ports need hardware RS485 RX/TX steering. Feel free to supply your own RS485 capability bitmap at modprobe or bootloader.
Boards that are capable of all RS232/422/485 have the following configuration controls, per channel:
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.