Building a “device driver” on [Raspberry Pi](https://www.ampheo.com/c/raspberry-pi/raspberry-pi-boards) usually means writing a Linux kernel module (LKM) that talks to hardware (GPIO/I²C/SPI/USB/PCIe) and exposes a clean interface to user space (/dev/..., sysfs, netlink, etc.). On Raspberry Pi, Device Tree + overlays are also a big part of the workflow. ![elecrow-pitower-2](https://hackmd.io/_uploads/S1Jn_C2Sbl.jpg) **1) Decide what you actually need** Before you write kernel code, check if user-space is enough: **Use user-space (recommended if possible)** * GPIO via libgpiod * I²C via /dev/i2c-* * SPI via /dev/spidev* * USB devices often work via existing drivers If you can do it in user-space, you avoid kernel crashes and maintenance burden. **Write a kernel driver when…** * You need low latency / precise timing * You need interrupt handling in kernel space * You need a proper device node with custom ioctls * You’re adding support for new hardware not covered by existing drivers **2) Understand Raspberry Pi’s model: Device Tree first** On Raspberry Pi, hardware is described by Device Tree (DT), and extra hardware is typically enabled via Device Tree Overlays configured in config.txt. Examples (common): Enable I²C / SPI using dtparams in config.txt Your driver often becomes a platform driver that binds to a DT node using compatible = "...". **3) Set up the build environment** On Raspberry Pi OS, you generally build modules against the running kernel: 1. Check your kernel: `uname -r` 2. Install build tools and headers: ``` sudo apt update sudo apt install -y build-essential raspberrypi-kernel-headers ``` 3. Confirm headers are available: `ls /lib/modules/$(uname -r)/build` If /build is missing, you’ll need to match kernel/headers correctly (this happens sometimes after kernel upgrades on certain distros/images). **4) Pick a driver type (most common on Pi)** **A) Character device driver (common for sensors/custom hardware)** You create a /dev/mydev node and implement read/write/ioctl. A quick way is a misc device, which avoids manually allocating a major number. **B) I²C or SPI driver** * Use Linux subsystems: i2c_driver or spi_driver * Bind via Device Tree * Expose data via: * hwmon, iio, input subsystem, or a char device (depending on device class) **C) GPIO / interrupt-based driver** * Use gpiod_* APIs * Request IRQ via DT and handle it in the driver **5) Minimal “skeleton” approach (what you implement)** A typical Pi platform driver looks like: * probe() — runs when DT matches your compatible * remove() — cleanup * resource acquisition: * memory registers (devm_ioremap_resource) * GPIO (devm_gpiod_get) * IRQ (platform_get_irq, devm_request_threaded_irq) * expose user interface: * misc char device (simple) * or sysfs attributes * or register into iio/hwmon **6) Build, load, and test (workflow)** **Build (typical out-of-tree module)** In your module directory: `make -C /lib/modules/$(uname -r)/build M=$PWD modules` **Load/unload** ``` sudo insmod mydriver.ko dmesg | tail -n 50 sudo rmmod mydriver ``` **Make it load at boot** * install to /lib/modules/.../extra/ * run depmod * add to /etc/modules-load.d/*.conf * or package with DKMS if you want it to survive kernel updates (common for long-term deployments) **7) Device Tree overlay: how your driver “attaches” on Pi** You typically: 1. Write an overlay .dts with: * a node for your hardware * compatible = "yourvendor,yourdevice" * GPIO/I²C/SPI parameters 2. Compile to .dtbo 3. Enable in /boot/config.txt via dtoverlay=... **8) Debugging tips that save hours** * Use dmesg -w while inserting the module * Check whether your device is bound: * ls /sys/bus/platform/drivers/... * ls /sys/bus/i2c/drivers/... * ls /sys/bus/spi/drivers/... * Confirm overlays loaded and pins assigned (Pi DT/overlay tooling is central here).