Building the ADIN1110 Linux Kernel Module on RPi

On this page

Penguin building a kernel module

Building the ADIN1110 Linux Kernel Module on Raspberry Pi

This article shows how to build the ADIN1110 10BASE-T1L Ethernet MAC-PHY Linux driver as an external kernel module on a Raspberry Pi.

The goal is simple:

  • use Raspberry Pi OS 64-bit
  • use the running Raspberry Pi kernel
  • download the matching ADIN1110 driver source
  • build adin1110.ko
  • install it into /lib/modules
  • load it with modprobe

This is useful when the driver is not available as a ready-to-use module in the installed Raspberry Pi kernel.


1. Why Build Only the Module?

Many tutorials explain how to rebuild the complete Raspberry Pi kernel.

That is useful for learning, but it is not always practical for deployment.

For many use cases, it is better to build only the required kernel module:

Manual setup for understanding.
Installer-based setup for deployment.

This approach avoids a full kernel rebuild and keeps the system close to the installed Raspberry Pi OS kernel.


2. Background

The ADIN1110 is a 10BASE-T1L Ethernet MAC-PHY from Analog Devices.

On Linux, the ADIN1110 driver is located in the kernel source tree under:

drivers/net/ethernet/adi/adin1110.c

Instead of rebuilding the complete Raspberry Pi kernel, the driver can be built as an external kernel module.

This is helpful for:

  • development
  • testing
  • installer-based deployment
  • Raspberry Pi based industrial Ethernet gateways

There is already a good article that explains the ADIN1110 kernel configuration and overlay setup for Raspberry Pi:

https://www.hackster.io/pier8283/adin1110-linux-kernel-configuration-for-raspberry-pi-2f4a26

That article focuses on configuring and rebuilding the Raspberry Pi kernel.

This article focuses on a different approach:

Build only the ADIN1110 driver as an external kernel module.

This can be useful when the Raspberry Pi kernel itself should stay unchanged.


4. Requirements

This setup is intended for:

Raspberry Pi OS 64-bit

Raspberry Pi OS 64-bit is required because the needed networking kernel configuration is already available there.

The script also requires:

Linux kernel >= 6.12

Required tools:

uname
wget
make
gcc
sudo

The matching kernel build directory must exist:

/lib/modules/$(uname -r)/build

On Raspberry Pi OS this usually comes from the installed kernel headers.


5. Install Required Packages

Install the required build tools:

sudo apt update
sudo apt install -y raspberrypi-kernel-headers build-essential wget

Check that the kernel build directory exists:

ls -l /lib/modules/$(uname -r)/build

Expected result:

/lib/modules/.../build -> /usr/src/...

6. Build Script

Create a file:

mkdir -p scripts/adin1110
vim scripts/adin1110/build_adin1110_module.sh

Insert the following script:

#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

die() {
    echo "Error: $1" >&2
    exit 1
}

say() {
    step="$1"
    msg="$2"
    printf "%s: %s\n" "$step" "$msg"
}

require_command() {
    cmd="$1"
    command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: $cmd"
}

STEP="STEP0"
say "$STEP" "Checking required tools"

require_command uname
require_command wget
require_command make
require_command gcc
require_command sudo

KVER="$(uname -r)"
KDIR="/lib/modules/$KVER/build"

say "$STEP" "Running kernel: $KVER"
say "$STEP" "Kernel build directory: $KDIR"

STEP="STEP1"
say "$STEP" "Checking kernel compatibility"

case "$KVER" in
    6.1[2-9].*|6.[2-9]*)
        ;;
    *)
        die "Unsupported kernel version: $KVER (requires >= 6.12)"
        ;;
esac

[ -d "$KDIR" ] || die "Kernel build directory not found: $KDIR"
[ -f "$KDIR/include/generated/autoconf.h" ] || die "Missing autoconf.h in $KDIR"
[ -f "$KDIR/Makefile" ] || die "Missing kernel Makefile in $KDIR"

STEP="STEP2"
say "$STEP" "Preparing build directory"

BUILD_DIR="${REPO_ROOT}/build/adin1110"
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"

STEP="STEP3"
say "$STEP" "Downloading sources"

KMAJOR="$(echo "$KVER" | cut -d. -f1)"
KMINOR="$(echo "$KVER" | cut -d. -f2)"
KBRANCH="rpi-${KMAJOR}.${KMINOR}.y"

BASE_URL="https://raw.githubusercontent.com/raspberrypi/linux/${KBRANCH}/drivers/net/ethernet/adi"

say "$STEP" "Using Raspberry Pi kernel branch: $KBRANCH"

wget -nv -O "$BUILD_DIR/adin1110.c" "$BASE_URL/adin1110.c"

STEP="STEP4"
say "$STEP" "Creating Makefile"

printf 'obj-m := adin1110.o\n\nall:\n\t$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules\n\nclean:\n\t$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean\n' > "$BUILD_DIR/Makefile"

STEP="STEP5"
say "$STEP" "Building module"

make -C "$KDIR" M="$BUILD_DIR" modules

[ -f "$BUILD_DIR/adin1110.ko" ] || die "adin1110.ko was not created"

STEP="STEP6"
say "$STEP" "Installing module"

sudo install -D -m 644 "$BUILD_DIR/adin1110.ko" "/lib/modules/$KVER/updates/adin1110.ko"
sudo depmod "$KVER"

STEP="STEP7"
say "$STEP" "Done"
say "$STEP" "Try loading with:"
say "$STEP" "sudo modprobe adin1110"

Make the script executable:

chmod +x scripts/adin1110/build_adin1110_module.sh

Run it:

./scripts/adin1110/build_adin1110_module.sh

7. What the Script Does

The script performs the following steps:

  1. check the required build tools
  2. detect the running kernel version
  3. check the kernel build directory
  4. download the matching ADIN1110 driver source
  5. create a minimal module Makefile
  6. build adin1110.ko
  7. install the module
  8. run depmod

8. STEP0: Check Required Tools

The script first checks that the required command line tools are available:

require_command uname
require_command wget
require_command make
require_command gcc
require_command sudo

It also reads the currently running kernel version:

KVER="$(uname -r)"
KDIR="/lib/modules/$KVER/build"

The module is always built against the currently running kernel.

This is important because Linux kernel modules must match the kernel they are loaded into.


9. STEP1: Check Kernel Compatibility

The script requires Linux kernel version 6.12 or newer:

case "$KVER" in
    6.1[2-9].*|6.[2-9]*)
        ;;
    *)
        die "Unsupported kernel version: $KVER (requires >= 6.12)"
        ;;
esac

It also checks that the kernel build files exist:

[ -d "$KDIR" ] || die "Kernel build directory not found: $KDIR"
[ -f "$KDIR/include/generated/autoconf.h" ] || die "Missing autoconf.h in $KDIR"
[ -f "$KDIR/Makefile" ] || die "Missing kernel Makefile in $KDIR"

Without these files, external kernel module builds are not possible.


10. STEP2: Prepare the Build Directory

The module is built in:

build/adin1110

The directory is removed and recreated on every run:

BUILD_DIR="${REPO_ROOT}/build/adin1110"
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"

This avoids problems with old object files from previous builds.


11. STEP3: Download the ADIN1110 Driver Source

The script detects the kernel major and minor version:

KMAJOR="$(echo "$KVER" | cut -d. -f1)"
KMINOR="$(echo "$KVER" | cut -d. -f2)"
KBRANCH="rpi-${KMAJOR}.${KMINOR}.y"

For example:

6.12.x -> rpi-6.12.y
6.13.x -> rpi-6.13.y

Then it downloads the ADIN1110 driver from the matching Raspberry Pi kernel branch:

BASE_URL="https://raw.githubusercontent.com/raspberrypi/linux/${KBRANCH}/drivers/net/ethernet/adi"

wget -nv -O "$BUILD_DIR/adin1110.c" "$BASE_URL/adin1110.c"

This keeps the driver source close to the running Raspberry Pi kernel version.


12. STEP4: Create the Module Makefile

The script creates a minimal external kernel module Makefile:

obj-m := adin1110.o

all:
	$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

This tells the kernel build system to build:

adin1110.ko

from:

adin1110.c

13. STEP5: Build the Kernel Module

The module is built with:

make -C "$KDIR" M="$BUILD_DIR" modules

This means:

-C "$KDIR"

Use the running kernel build directory.

M="$BUILD_DIR"

Build the external module from the ADIN1110 build directory.

After the build, the script checks that the module exists:

[ -f "$BUILD_DIR/adin1110.ko" ] || die "adin1110.ko was not created"

The expected output file is:

build/adin1110/adin1110.ko

14. STEP6: Install the Module

The module is installed into:

/lib/modules/$(uname -r)/updates/adin1110.ko

The script uses:

sudo install -D -m 644 "$BUILD_DIR/adin1110.ko" "/lib/modules/$KVER/updates/adin1110.ko"
sudo depmod "$KVER"

depmod updates the kernel module dependency database.

After this step, the module can be loaded by name.


15. STEP7: Load the Module

Load the module with:

sudo modprobe adin1110

Check that it is loaded:

lsmod | grep adin1110

Check kernel messages:

dmesg | tail -n 100

16. Device Tree Overlay

The kernel module alone is not enough.

The Raspberry Pi also needs a matching Device Tree overlay that describes the ADIN1110 SPI device.

Typical information required in the overlay:

  • SPI bus
  • chip select
  • interrupt GPIO
  • reset GPIO, if used
  • compatible string for ADIN1110
  • SPI frequency
  • MAC address

The MAC address must be provided in the Device Tree.

If no valid MAC address is configured, the driver may fail during initialization or print errors in dmesg.

Example structure:

/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2835";

    fragment@0 {
        target = <&spi0>;
        __overlay__ {
            status = "okay";

            adin1110: ethernet@0 {
                compatible = "adi,adin1110";
                reg = <0>;
                spi-max-frequency = <25000000>;

                interrupt-parent = <&gpio>;
                interrupts = <25 8>;

                local-mac-address = [ 02 00 00 00 00 01 ];

                status = "okay";
            };
        };
    };
};

The exact GPIO numbers depend on the hardware design.

The MAC address above is only an example. For real products, each device should use a unique and valid MAC address.

Device Tree overlays can also be built directly on the Raspberry Pi target.

If there is interest, I may write a separate article about building and installing Raspberry Pi Device Tree overlays on the target system.


17. Test the Network Interface

After loading the module and enabling the overlay, reboot:

sudo reboot

After reboot, check network interfaces:

ip link

You should see a new Ethernet interface.

Example:

eth1

or:

end0

Bring the interface up:

sudo ip link set eth1 up

Check link state:

ip addr show eth1
dmesg | grep -i adin

18. Troubleshooting

Kernel headers missing

Error:

Kernel build directory not found

Fix:

sudo apt install -y raspberrypi-kernel-headers

Module does not load

Check:

sudo modprobe adin1110
dmesg | tail -n 100

Possible causes:

  • wrong kernel version
  • module built for another kernel
  • missing dependency
  • incompatible kernel source version

SPI device not detected

Check whether SPI is enabled:

ls /dev/spidev*

Enable SPI:

sudo raspi-config

Then enable:

Interface Options -> SPI

Reboot afterwards.


Device Tree overlay missing or wrong

If the module loads but no network interface appears, the Device Tree overlay is usually missing or incorrect.

Check:

dmesg | grep -i spi
dmesg | grep -i adin

MAC address missing

The ADIN1110 needs a valid MAC address.

If the Device Tree does not provide a MAC address, the driver may fail or report errors.

Check kernel messages:

dmesg | grep -i mac
dmesg | grep -i adin

Fix the Device Tree overlay and add a valid local-mac-address.


19. Clean Build

To remove the local build directory:

rm -rf build/adin1110

To remove the installed module:

sudo rm -f /lib/modules/$(uname -r)/updates/adin1110.ko
sudo depmod "$(uname -r)"

20. Source of the Build Script

The build script used in this article is based on the open BE-IIS installer project:

https://github.com/be-iis/be-iis-installer/

The installer does more than just build the ADIN1110 module.

It is intended to prepare BE-IIS HAT++ boards on Raspberry Pi systems. It can install the required modules, overlays and configuration files for supported HATs.

The goal is that the user does not have to do this manual work for normal deployment.

The manual process in this article is mainly useful for understanding what happens under the hood.

Everything is open and traceable.


21. BE-IIS HAT++ Installer

BE-IIS offers Raspberry Pi HAT++ boards for industrial interfaces, including Single Pair Ethernet boards.

For deployment, BE-IIS provides an installer that prepares the supported HATs automatically.

The installer is designed to:

  • install required kernel modules
  • install required Device Tree overlays
  • prepare supported BE-IIS HAT++ boards
  • detect supported HATs automatically
  • reduce manual configuration work

This means that users of supported BE-IIS HAT++ boards normally do not need to build the module manually.

The manual steps in this article are still useful if you want to understand the Linux integration in detail.


22. Summary

This method builds the ADIN1110 driver as an external Raspberry Pi kernel module.

It avoids rebuilding the complete kernel and keeps the process simple:

./scripts/adin1110/build_adin1110_module.sh
sudo modprobe adin1110

The module build requires:

  • Raspberry Pi OS 64-bit
  • matching Raspberry Pi kernel headers
  • Linux kernel 6.12 or newer
  • ADIN1110 driver source from the matching Raspberry Pi kernel branch
  • a correct Device Tree overlay
  • a valid MAC address in the Device Tree

This approach is useful for manual setup, debugging and understanding the driver integration.

For deployment, the same logic is integrated into the open BE-IIS installer.


References