Jetson TX2 + PTP capture synchronization

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);