TS-7250-V3 PC104 bus

From embeddedTS Manuals

The TS-7250-V3 includes an ISA bus for compatibility with PC104 peripherals. Arm itself has not traditionally had an ISA bus as part of its architecture, so this behaves differently than an x86 where ISA is typically used.

To access the PC104 bus in userspace, open, read, and write the files here:

/sys/bus/platform/devices/50004050.fpgaisa/

File Description
io8 8-bit strobes on IOR/IOW
io16 16-bit strobes on IOR/IOW
ioalt16 16-bit strobes on IOR/IOW with an alternate pinout
mem8 8-bit strobes on MEMR/MEMW
mem16 16-bit strobes on MEMR/MEMW
memalt16 16-bit strobes on MEMR/MEMW with an alternate pinout

Any programming language can interface with these using standard file IO. Open a file descriptor to one or more of these files, and seek to the offset of the address you are accessing. Use read/write calls to access data on these busses.

For 16-bit accesses the address must always be aligned to an even byte, and reads/writes must always access multiple of 2 bytes at a time.

For C, or languages with a foreign function interface, we provide a library / header which can be used to handle these accesses.

For python, a simple pc104.py can access the bus:

#!/usr/bin/env python3

import os

class PC104:
    ISA_PATH = "/sys/bus/platform/devices/50004050.fpgaisa/"

    def __init__(self):
        self.io8fd = os.open(self.ISA_PATH + "io8", os.O_RDWR | os.O_SYNC)
        self.io16fd = os.open(self.ISA_PATH + "io16", os.O_RDWR | os.O_SYNC)
        self.io16altfd = os.open(self.ISA_PATH + "ioalt16", os.O_RDWR | os.O_SYNC)
        self.mem8fd = os.open(self.ISA_PATH + "mem8", os.O_RDWR | os.O_SYNC)
        self.mem16fd = os.open(self.ISA_PATH + "mem16", os.O_RDWR | os.O_SYNC)
        self.mem16altfd = os.open(self.ISA_PATH + "memalt16", os.O_RDWR | os.O_SYNC)

    def _read(self, fd, addr, size):
        os.lseek(fd, addr, os.SEEK_SET)
        return os.read(fd, size)

    def _write(self, fd, addr, data):
        os.lseek(fd, addr, os.SEEK_SET)
        os.write(fd, data)

    # IO 8-bit methods
    def io_8_read(self, addr):
        return int.from_bytes(self._read(self.io8fd, addr, 1))

    def io_8_write(self, addr, val):
        self._write(self.io8fd, addr, val.to_bytes(1))

    # IO 16-bit methods
    def io_16_read(self, addr):
        return int.from_bytes(self._read(self.io16fd, addr, 2))

    def io_16_write(self, addr, val):
        self._write(self.io16fd, addr, val.to_bytes(2))

    # IO 16-bit alternate methods
    def io_16_alt_read(self, addr):
        return int.from_bytes(self._read(self.io16altfd, addr, 2))

    def io_16_alt_write(self, addr, val):
        self._write(self.io16altfd, addr, val.to_bytes(2))

    # Memory 8-bit methods
    def mem_8_read(self, addr):
        return int.from_bytes(self._read(self.mem8fd, addr, 1))

    def mem_8_write(self, addr, val):
        self._write(self.mem8fd, addr, val.to_bytes(1))

    # Memory 16-bit methods
    def mem_16_read(self, addr):
        return int.from_bytes(self._read(self.mem16fd, addr, 2))

    def mem_16_write(self, addr, val):
        self._write(self.mem16fd, addr, val.to_bytes(2))

    # Memory 16-bit alternate methods
    def mem_16_alt_read(self, addr):
        return int.from_bytes(self._read(self.mem16altfd, addr, 2))

    def mem_16_alt_write(self, addr, val):
        self._write(self.mem16altfd, addr, val.to_bytes(2))

    def close(self):
        os.close(self.io8fd)
        os.close(self.io16fd)
        os.close(self.io16altfd)
        os.close(self.mem8fd)
        os.close(self.mem16fd)
        os.close(self.mem16altfd)


if __name__ == "__main__":
    pc104 = PC104()
    print(f'PC/104 io8 0x161 = {hex(pc104.io_8_read(0x161))}')

There is also a command line utility to access the pc104 bus:

root@tsimx6:~# pc104_peekpoke 
Usage pc104_peekpoke <io/mem> <8/16/alt16> <address> [value]
	Eg: pc104_peekpoke io 8 0x140

For kernel access to the PC104 bus see the header here. Using the existing kernel driver will handle locking between userspace and any kernel drivers such as PC104 based UARTs.

On PC104 peripherals these typically have IRQs such as IRQ5/IRQ6/IRQ7. On ARM these are mapped to other IRQ numbers. Using the fpga_intc driver, interrupts on 14/15/16 correspond with the pins on the PC104 header labelled IRQ 5, 6, and 7.

We strongly recommend using the methods described above for accessing the PC104 bus. For users needing to understand the implementation, the registers are described below.

The FPGA presents a 32-bit memory window at 0x50004050 which follows this format:

Bits Description
31 Busy/Go [1]
30 1 = IO, 0 = MEM [2]
29 1 = 8-bit, 0 = 16-bit
28 1 = read cycle, 0 = write cycle
27 0 = standard pinout, 1 = TS Pinout [3]
26:0 Address or Data [4]
  1. On read, a 1 indicates the existing transaction is already busy. A 0 indicates it is available. If written to 0 then bits 26:0 specify address. If written to 1 this starts the bus cycle.
  2. Cycle type indicates if we use IOW/IOR pins, or MEMR/MEMW.
  3. The TS pinout is used on some platforms to remove the need for the 40-pin connnector while still supporting 16-bit peripherals. This does not affect 8-bit accesses. When enabled this uses these pins for the upper 16-bit.
    PC104 pin Data bit
    B4 8
    B17 9
    B18 10
    B25 11
    B20 12
    B26 13
    B27 14
    B28 15
  4. Accepts a written addr when BUSY/GO = 0. This is data on a write when Busy/GO=1, or on a read after BUSY/GO reads 0 after being written to 1.