Part 1 was about the hardware, now for the software

Step 1: pps-gpio-poll package

This architecture doesn't support device tree yet, so it has a special pps driver.  The repo for the driver package is at

I downloaded the lede-sdk for the ar71xx platform, and put the above git repo under the sdk's package/ dir.

# update the packages
./scripts/feeds update
# create a local signing key
./staging_dir/host/bin/usign -G -s ./key-build -p ./ -c "Local build key"
# build the new package
# new package should be in this dir
ls bin/targets/ar71xx/generic/packages/*pps*

Step 2: build openwrt/lede image

Then I downloaded the lede-imagebuilder and built a sysupgrade image for my router.  I already had openwrt on my TL-MR3020, so I'll be using sysupgrade to write the image to flash.

# change __LEDE_IMAGEBUILDER_DIR__ to whatever dir you put it in
# change __LEDE_SDK_DIR__ to whatever dir you put that in
cp __LEDE_SDK_DIR__/bin/targets/ar71xx/generic/packages/kmod-pps-gpio-poll*.ipk packages/
# build a TL-MR3020 image
make image PROFILE=tl-mr3020-v1 DEVICE_TYPE=lowrouter PACKAGES="kmod-pps chrony kmod-nbd nbd pps-tools kmod-pps-gpio-poll gpsd gpsd-clients kmod-usb-acm -iw -hostapd-common -kmod-ath -kmod-ath9k -kmod-ath9k-common -kmod-cfg80211 -kmod-mac80211 -swconfig -wpad-mini"
# check the image size
ls -l bin/targets/ar71xx/generic/*sysupgrade*
# copy the image to my router's ramdisk
scp bin/targets/ar71xx/generic/lede-17.01.6-ar71xx-generic-tl-mr3020-v1-squashfs-sysupgrade.bin root@mr3020.lan:/tmp/

I've removed all wireless to make room for gpsd and chrony.  My GPS shows up as a usb-acm serial device, you might need a different kmod-usb-* package for different modules.  Even with those changes, I only have 896kb free on the 4MB flash.  I included nbd so I could use network storage for other packages and logging.

Step 3: apply image

After using sysupgrade to apply the image, I waited for it to reboot.

Step 4: configure sysntpd & gpsd

After it came back, I disabled sysntpd in /etc/config/system under section config timeserver ntp with option enabled 0.  I configured gpsd in /etc/config/gpsd to auto start and listen on /dev/ttyACM0.  I started gpsd and verified that it was working with cgps.  

Step 5: enable and test pps

I then loaded the pps module with insmod pps-gpio-poll gpio=29.

Kernel messages:
[  146.578832] pps pps0: new PPS source pps_gpio_poll
[  146.582189] pps-gpio-poll: Registered GPIO 29 as PPS source (precision 85 ns)

Verify pps is returning results:
# ppstest /dev/pps0
source 0 - assert 1544389003.000044807, sequence: 33 - clear  0.000000000, sequence: 0

Step 6: chronyd

configure chrony in /etc/chrony/chrony.conf:

refclock PPS /dev/pps0 refid PPS poll 4 prefer
refclock SHM 0 delay 0.2 refid NMEA noselect poll 6 dpoll 0

Start chronyd and verify that the PPS source is working with chronyc sources:

MS Name/IP address         Stratum Poll Reach LastRx Last sample
#* PPS                           0   4   377    22    +62ns[ -264ns] +/-   16ns

Step 7: results

After setting up logging to the nbd drive and graphing with chrony-graph (instructions skipped because it'd make this long article even longer), this system stays in very close sync with the gps (+/- 60ns 50% of the time, +/-440ns 99% of the time):

Difference between GPS time and MR3020 time

Interestingly, there's a 55us offset between my two local clocks.  I am still figuring out where this is coming from.

Difference between MR3020 time and another NTP server on the LAN