Advantech PCI-1784 driver for Linux 2.6

By: Frank Rysanek of FCC Prumyslove systemy s.r.o., Czech Republic
e-mail: rysanek AT fccps DOT cz

Latest version

This description:
Driver and library:

Hardware recap

The Advantech PCI-1784 is a 4-axis up/down counter and rotary input board for the PCI bus, based on some Advantech-programmed FPGA's. 4 axis = 4 independent channels = 4 motors.
The board is capable of the following, in hardware:

The board manual appears to be pretty exhaustive.

Driver and library

Level of abstraction

The driver and library are rather thin wrappers around the PCI-1784 hardware functionality. To use this software package, the application programmer should know the PCI-1784 registers and semantics. This software package aims to shield away the application programmer from the low-level coding details of the PC/Linux platform, but not much more. The functions and macros provided by this package accept the values in their native hardware types and ranges, related to the underlying hardware registers, as described by the PCI-1784 manuals. I.e., there's no "math behind the scenes" as to convert values in either direction.

There are only a few areas in this software that provide some non-zero processing:

The basic PCI-1784 functionality is handled by reasonably mnemonic wrapper functions and macros. The more advanced functionality, such as individual bits in the configuration registers, are only covered at the level of symbolic bit names and inb/outb wrappers. The application programmer will have to study the board manual and use these low-level functions and macros.

User space and kernel space

9 The software package consists of a driver in the form of a kernel-space module and a user-space library (for static or dynamic linking). The user-space library talks to the driver via a character device (opens an appropriate device node in the file system). The package is ready to work with multiple boards in the system, yet there's a single device node - multiple minor numbers would be more unix-like but perhaps unnecessarily complex to work with (depending on the particular real-world scenario at hand).

The header file is universal, all the function prototypes and macros work the same in kernel space and in user-space. In other words, the application programmer has freedom of choice, whether to compile the control application as a user-space executable binary, or as an insertable kernel-space module.

Obviously there are upsides and downsides to both kernel-space and user-space:

It's possible to strike a reasonable trade-off by splitting the application among kernel-space and user-space: timing-critical parts of the control task can run in a kernel module and the more relaxed parts can run as user-space apps, communicating among themselves from k-space to u-space via syscalls (read, write, ioctl) over device nodes (block or character type).

The header file


The header file contains a reasonable level of comments. If you're looking for a quick start, take a look at it.

The header file starts with mnemonic preprocessor macros (#defines) that substitute constant numeric identifiers of the various registers, axis labels, IRQ source bits etc.
There are just a few core functions, implementing key parts of the functionality: write a register, read a register etc. These in turn are used by a number of function-like wrapper macros, simplifying mnemonic access to the various PCI-1784 registers.

As mentioned above, the set of functions and macros is the same in kernel space and in user space. The only exception is the pci1784_opendev() prototype that takes no argument in kernel space and one argument in user space (the device node filename).

The identical function prototypes are implemented differently in the kernel-space module and in the user-space library. Any functionality is really implemented only in the kernel variety, the user-space library consists of mere wrapper functions that pass the arguments to their kernel-space counterparts via ioctl(). The bunch of function-like macros only live in the header file (no implementation in the C files) - hence they work the same way in kernel space and in user space.

The elements of the header file are stacked vaguely like this:

kernel spaceuser space
core functions<=-.  core functions
outw,inw,kernel code`-ioctl()

Except that, due to the inner dependencies of the header file, the contents flow from top to bottom, whereas the stack-style view above builds the layers from bottom to top.

In other words, in the header file, the most arcane functionality is located at the bottom. If you're after powerful macros and functions, try reading the header file from bottom to top :-)

Functions and macros, in order of appearance

For the named constants, take a look into the header file.


int pci1784_opendev(void);            // kernel space
int pci1784_opendev(char* name);      // user space
int pci1784_closedev(void);

int pci1784_write_reg(u8 board, u8 reg, u32 data);
int pci1784_write_reg_raw(u8 board, u8 reg, u32 data); // do not cache the data
int pci1784_read_reg(u8 board, u8 reg, u32* data);

int pci1784_bang_reg_bits(u8 board, u8 reg, u32 bits, u8 mode, u8 upload); // bits = bitmask
int pci1784_readback_reg(u8 board, u8 reg, u32* data);
int pci1784_readback_bit(u8 board, u8 reg, u8 bit);  // in this single case, bit = ordinal number (not a bitmask)
int pci1784_reset(u8 board);

int pci1784_wait4irq(u8 board, u32* data);

// in the kernel, you can hook a callback function to the stock ISR
int pci1784_register_irq_callback( u8 board, void (*irq_callback)(u32 irq_status, u8 board) );
int pci1784_unregister_irq_callback( u8 board );

// in the kernel, you can access the board directly, if you so desire
int pci1784_iobase(u8 board); 


pci1784_set_counter_mode(board, axis, data)
pci1784_read_counter_latch(board, axis, data)
pci1784_write_cmp_data(board, axis, data)
pci1784_set_irq_control(board, data)
pci1784_set_clk_control(board, data)
static inline int pci1784_set_clk_freq(u8 board, u16 freq); // needs simple math
pci1784_sw_latch(board, axis_bitmask)
pci1784_get_board_id(board, data)
pci1784_counter_reset(board, axis_bitmask)
pci1784_index_reset(board, double_axis_bitmask)
pci1784_digital_out(board, bitmask)
static inline int pci1784_digital_in(u8 board, u32* bitmask); // didn't work as a macro
pci1784_digital_in_raw(board, bitmask)

Compared to the PCI-1784 manual, it's clear that most of the macros are mere shorthands for controller registers and functions. All of them really return an "int" error code (0 if success). For more information about the argument types and semantics, consult the comments in the header file and examples.

IRQ handling

The IRQ functionality on the PCI-1784 is rather straight-forward, harmless and plausibly documented.

The driver deploys a generic ISR that aims to take a reasonable action in response to any IRQ that may occur - specifically, it properly ACKs (=masks) level-triggered sources, and tells the board that any edge-triggered sources are ACKed too. All the application programmer has to do is use the wait_for_irq() function. See also the IRQ handler in pci-1784.c.