In hopes this helps others, we spent some time to verify multiple (3) TX2 boards with PTP synchronization with the ultimate goal of taking images at a specific PTP timestamp.
We are still working on a viable camera option with a GPIO pin for triggering, but are able to show three TX2 boards toggling a GPIO pin on/off within a 1 microsecond tolerance. Note that the kernel pin toggling actually has up to 2 usec delay, but polling the system clock is quick (0.137 usec avg).
This is by no means a usable kernel (just a spin-loop), but demonstrates synchronization is possible, which can be confirmed on a scope. The kernel is trivial, it just watches the clock for a 1 second rollover and flips a pin.
// 'sudo apt install -y linuxptp'
//
// Run the daemon that sync's the NIC and system clock:
// sudo phc2sys -s /dev/ptp0 -m -w
//
// use:
// sudo insmod ptp4l_sync_test.ko && sudo rmmod ptp4l_sync_test.ko
// to install and remove
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/time.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("RS64");
MODULE_DESCRIPTION("Jetson TX2 PTP4L synchronization test.");
MODULE_VERSION("1.0");
// from: https://www.jetsonhacks.com/nvidia-jetson-tx2-j21-header-pinout/
static unsigned int gpio5 = 398;
static bool led5On = false;
static void runTest(void);
static int __init kern_init(void) {
int result = 0;
printk(KERN_INFO "PTP4L_TEST: Initializing the synchronization test\n");
if (!gpio_is_valid(gpio5)) {
printk(KERN_INFO "PTP4L_TEST: invalid GPIO\n");
return -ENODEV;
}
// we want to use the GPIO in kernel space, not user space, do not export it to user space
result = gpio_export(gpio5, false);
if (!result) {
printk(KERN_INFO "Could not export GPIO5: %d\n", result);
}
// sometimes this fails on the first try, not sure why?
result = gpio_request(gpio5, "sysfs");
if (!result) {
printk( KERN_INFO "Could not allocate GPIO5 result: %d, trying again\n", result );
result = gpio_request(gpio5, "sysfs");
if (!result) {
printk( KERN_INFO "Could not allocate GPIO5 result: %d\n", result );
return -ENODEV;
}
}
// led starts off
result = gpio_direction_output(gpio5, led5On);
if (result) {
printk(KERN_INFO "Could not set GPIO5 direction to output: %d\n", result);
return -ENODEV;
}
gpio_set_value(gpio5, false);
runTest();
return result;
}
static void __exit kern_exit(void) {
gpio_set_value(gpio5, 0);
gpio_free(gpio5);
printk(KERN_INFO "PTP4L_TEST: Exiting\n");
}
static void runTest(void)
{
int i = 0;
static int LOOPS = 30;
struct timespec ts_current;
struct timespec ts_last;
struct timespec captured[LOOPS];
int pollCnt = 0;
printk(KERN_INFO "PTP4L_TEST: Starting Test\n");
// read the system clock, every time we pass an even 1 second mark, toggle the GPIO pin
getnstimeofday(&ts_last);
do {
getnstimeofday(&ts_current);
++pollCnt;
// has the second value changed since the last time we checked?
if (ts_current.tv_sec != ts_last.tv_sec) {
// flash the GPIO
gpio_set_value(gpio5, true);
udelay(10);
gpio_set_value(gpio5, false);
captured[i] = ts_current;
++i;
}
ts_last = ts_current;
} while(i < LOOPS);
for(i = 0; i < LOOPS; ++i) {
printk("%d: %lld.%.9ld\n", i, (long long)captured[i].tv_sec, captured[i].tv_nsec);
}
printk("Average clock polls per second = %d\n\n", pollCnt / LOOPS);
printk(KERN_INFO "PTP4L_TEST: Ending Test\n");
}
module_init(kern_init);
module_exit(kern_exit);