TS-9370 FPGA

From embeddedTS Manuals

FPGA Registers

The TS-9370's FPGA is connected to the CPU over the FlexSPI bus. This provides 32-bit access to the FPGA, mapped at 0x2800_0000.

Offset Description
0x0000 Model/Rev Info[1]
0x0020 IRQ core
0x0040 FPGA GPIO block #0
0x0080 FPGA GPIO block #1
0x00C0 FPGA GPIO block #2
  1. See the #FPGA Revisions section for how to interpret these registers.

FPGA GPIO Instances

Note: Unlike GPIO into the CPU, at present the FPGA GPIOs do not support interrupts.

Each of the three GPIO blocks can manage up to 32 IO lines. The 32-bit registers controlling each block are defined as follows:

Offset Read Function Write Function
0x00 Output Enables Set OE Bits
0x04 Reserved Clear OE Bits
0x08 Output Data Set Data Bits
0x0c Input Data Clear Data Bits
0x10 Reserved Reserved
0x14 Reserved Reserved
0x18 Reserved Reserved
0x1c Reserved Reserved

FPGA PWM

This system includes PWM that supports 10-bit duty/period, a 66.666666mhz input clock, and 12 values of input clock shift.

Linux supports this API through the /sys/ interface using file I/O. First export the pwm channel to enable it:

# Export PWM channel 0
echo 0 > /sys/class/pwm/pwmchip0/export
File Description
/sys/class/pwm/pwmchip0/pwm0/period Period in nanoseconds. Must be bigger than the duty cycle or writes will fail. Can only change when the pwm is disabled.
/sys/class/pwm/pwmchip0/pwm0/duty_cycle Duty cycle in nanoseconds. Can change at any time, must be less than period.
/sys/class/pwm/pwmchip0/pwm0/enable When 1, pwm is outputting. When 0, outputs idle state of the PWM.
/sys/class/pwm/pwmchip0/pwm0/polarity When "normal", idle high and duty cycle low. When "inversed", idle low and duty cycle high. A valid period must be set before this can be changed.

For example, for a 50hz signal with 25% duty cycle:

# Set Period to 20ms
echo 20000000 > /sys/class/pwm/pwmchip0/pwm0/period
# Set duty cycle to 5ms
echo 5000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
# Enable PWM and output 50hz signal
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable

# Duty cycle can be changed while it is enabled
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle

The Linux PWM API will attempt to arrive at the exact period at the cost of the duty cycle resolution. For the most possible duty cycle resolution use one of the max period ns values from the table below.

Shift PWM Input Frequency (Hz) Duty Cycle units (ns) Max Period (ns) Max Period (Hz)
0 66666666 15 15345 65167
1 33333333 30 30690 32583
2 16666666 60 61380 16291
3 8333333 120 122760 8145
4 4166666 240 245520 4072
5 2083333 480 491040 2036
6 1041666 960 982080 1018
7 520833 1920 1964161 509
8 260416 3840 3928330 254
9 130208 7860 7856660 127
10 65104 15360 15713320 63
11 32552 30720 31426640 31

If period is set to one of these values, the full 10 bits of duty cycle is available. Past that, the Linux API will use the closest available value. Debug output can be enabled with:

echo "file pwm-ts.c +p" > /sys/kernel/debug/dynamic_debug/control

If this is enabled, the kernel can output additional information after setting a frequency:

echo 0 > /sys/class/pwm/pwmchip0/export
# 10ms period:
echo 10000000 > /sys/class/pwm/pwmchip0/pwm0/period
# 5ms duty cycle:
echo 5000000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
dmesg | tail

This will output:

[   75.758146] ts-pwm 500001a8.mikro_pwm: cycle=1293661 shift=10 cnt=773
[   75.758184] ts-pwm 500001a8.mikro_pwm: shift=10 cnt=773 duty_cnt=387

The last value in cnt indicates how much resolution is available for the duty cycle at this given period. In the best case there are 10 bits (0-2047) to specify duty cycle, but this above example is 0-773 to arrive at this particular period. You can determine the duty cycle increments with period / cnt. From the above example:

10000000 / 773 = 12936.61

The duty cycle can then be configured in increments of 12936ns. Smaller values will round to the closest value.

This PWM will allow a max speed of 79.2MHz / 3 = 26.4MHz, but this will sacrifice all of the available duty cycle except an on/50%/off. The slowest speed is highest divisor at 38hz.

While the Linux driver is recommended for most users, the PWM core is located at 0x28000400, each PWM are 0x20 registers apart.

Address Bits Description
0x00 (CONFIG) 31:2 Reserved
1 PWM Inversion (0 = Idle high, duty cycle low), (1 = Idle low, duty cycle high)
0 PWM Enable (1 = Enable PWM output, 0 = Drive default state)
0x04 (PERIOD) 31:10 Reserved
9:0 PWM Period (0–1023 steps)
0x08 (DUTY) 31:10 Reserved
9:0 PWM Duty Cycle (0–1023 steps)
0x0C (SHIFT) 31:12 Reserved
11:0 Shift (Clock frequency = 66666666 / (1 >> shift))

FPGA Circuit Breaker

This board includes:

  • 1x High side switches capable of sourcing 300mA
  • 4x Low side switches capable of sinking 500mA each, or 1A total.

If these sink/source values are exceeded, the "DIO_FAULT#" IO will trip to prevent damage. Any detected edge on DIO_FAULT# will trigger a latch in the FPGA, and the "circuit_breaker_fault" will assert. As soon as this signal is asserted, all of the high side and low side switches will disable, regardless of the data out value in the GPIO.

The circuit_breaker_fault will stay high until the fault condition is cleared. The fault condition is cleared by creating a rising edge on clear_fault.

For example:

# Set EN_LS_OUT_1 high
gpioset 6 17=1

If this then exceeds the current limit:

# Read circuit_breaker_fault
gpioget 6 21

This pin can be read as an interrupt or polled. If it returns 1, then the circuit breaker has tripped. Once the issue causing the overcurrent condition is cleared, the clear_fault IO must be pulsed. The circuit_breaker_fault signal is cleared on the rising edge.

# Set clear_fault low
gpioset 6 15=0
# Set clear_fault high
gpioset 6 15=1

# re-read circuit_breaker_fault, and it should now return 0 if the condition has not re-tripped.
gpioget 6 21

FPGA Updates

For most Linux users, the FPGA can be updated with:

curl -sSL http://files.embeddedts.com/ts-arm-sbc/ts-9370-linux/fpga/update-fpga.sh | sh

Then reboot.

This FPGA supports multiple application loads. On startup, it always starts the first image which is a bootloader that is capable of rewriting the FPGA's flash. If there is no update, it sets a register that switches the FPGA to the second image which contains the common functionality like GPIO, IRQs, etc.

U-boot checks checks for a valid 'fit' image in the eMMC boot 1 partition (mmcblk0boot1) at offset 0x280000. If it finds this valid update, then before switching out of the FPGA bootloader it rewrites the second application load. This take approximately 40 seconds. After writing, it then erases the region of mmcblk0boot1 containing the update, and then sets the register in the bootloader that updates the FPGA.

This update process is designed so the typical case of updating the application load is safe even in the case of sudden power loss. The FPGA bootloader is in a separate erase block of the FPGA's internal flash, so if we are interrupted in the middle of erasing/writing flash it only affects the application load. On the next start up, since the update has not been completed and still exists on mmcblk0boot1, it will restart the update and try again until it succeeds.