TS-9370 FPGA
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 |
- ↑ 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.