Writing a Linux IIO Driver for the MS8607 Sensor
This post covers the development of a Linux kernel driver for the MS8607 pressure, humidity, and temperature sensor on the BeagleBone Black. The driver integrates with the IIO (Industrial I/O) subsystem to provide a standard interface for userspace applications.
Repository: https://github.com/billvanleeuwen424/ms8607-driver
Background
Device drivers bridge hardware and the operating system. Without a driver, the kernel can't communicate with hardware devices. For the MS8607, the driver needs to:
Communicate over I2C with two sensor addresses (0x76 for pressure/temp, 0x40 for humidity)
Read factory calibration data from PROM and validate with CRC
Convert raw ADC values to pressure (mbar), temperature (°C), and humidity (%RH)
Expose data through IIO's sysfs interface at
/sys/bus/iio/devices/
Hardware Setup
I utilized this pinout diagram from element14 here.
The MS8607 is mounted on an Adafruit breakout board and connected to the BeagleBone Black I2C2 bus:
P9_19 (I2C2_SCL) → SCL
P9_20 (I2C2_SDA) → SDA
P9_3 (3.3V) → VIN
P9_1 (GND) → GND
I had the board connected up like so on this very professional breadboard:

Validating I2C Communication
Standard practice is to run i2cdetect first:
sudo i2cdetect -y 2
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Nothing showed up. The MS8607 doesn't respond to i2cdetect probing—it requires specific command sequences. Manual validation with i2cget and i2cset confirmed both sensors were functional
# Test humidity sensor at 0x40
i2cget -y 2 0x40 0xE7
# Output: 0x02
# Test pressure/temp sensor at 0x76
i2cset -y 2 0x76 0x5A
i2ctransfer -y 2 w1@0x76 0x00 r3
# Output: 0x85 0x4f 0x37
Device Tree Configuration
I had to learn a lot about device trees for this project. Again, I recommend Bootlin documentation extremely useful! I utilized this slide deck on Device Tree 101 from Bootlin.
The MS8607 contains two sensors on one chip with different I2C addresses. This presented an architecture choice: write two separate drivers (one per sensor) or a single driver managing both.
Initial Approach
The first device tree had two nodes, both enabled:
ms8607-pt@76 {
compatible = "te,ms8607-pt";
reg = <0x76>;
status = "okay";
};
ms8607-h@40 {
compatible = "te,ms8607-h";
reg = <0x40>;
status = "okay";
};
The Problem
With both nodes at status = "okay", the kernel instantiates I2C devices at both addresses during boot. When the driver tried to create a dummy client for 0x40 using devm_i2c_new_dummy_device(), it failed with -EBUSY (error -16)—the address was already taken.
Solution
Only the primary sensor at 0x76 has status = "okay". The secondary sensor is documented but disabled:
/* Primary - driver binds here */
ms8607@76 {
compatible = "te,ms8607";
reg = <0x76>;
status = "okay";
};
/* Secondary - documented but disabled */
ms8607-h@40 {
compatible = "te,ms8607-humidity";
reg = <0x40>;
status = "disabled";
};
The driver creates the humidity sensor client internally. This exposes all three channels (pressure, temperature, humidity) through a single IIO device.
Driver Implementation
Development Tools
I used Claude Code for parts of this project—parsing datasheet formulas, generating the CI/CD pipeline, and discussing architecture trade-offs. It was useful for accelerating tasks that would normally require reading documentation or trial-and-error. Hardware debugging and sensor validation still required hands-on work with the BeagleBone.
Calibration and CRC Validation
The pressure/temperature sensor stores factory calibration coefficients (C1-C6) in PROM. These coefficients are used in the datasheet's formulas to convert raw ADC values into calibrated pressure and temperature readings. If the coefficients are corrupted, all sensor readings will be wrong.
The driver reads the PROM once during probe and validates the data with a 4-bit CRC:
static int ms8607_load_calibration(struct ms8607_data *data)
{
int ret;
u16 prom[8];
ret = ms8607_read_prom_words(data->client, prom);
if (ret)
return ret;
if (!ms8607_validate_prom_crc(prom)) {
dev_err(&data->client->dev, "PROM CRC validation failed\n");
return -EIO;
}
memcpy(data->calibration, prom, sizeof(data->calibration));
data->calibration_valid = true;
return 0;
}
Sensor Readings
Each sensor requires triggering an ADC conversion, waiting for completion, then reading the result.
Humidity (16-bit):
// Trigger no-hold measurement
i2c_smbus_write_byte(client_h, 0xF5);
msleep(9); // ADC conversion time
// Read result
adc = i2c_smbus_read_word_data(client_h, 0xE5);
// Convert to 0.01%RH units
humidity = -600 + ((12500 * adc) >> 16);
Pressure (24-bit, requires temperature compensation):
// Trigger temperature conversion
i2c_smbus_write_byte(client, 0x58);
msleep(9);
// Read D2 (raw temperature)
i2c_smbus_write_byte(client, 0x00);
i2c_smbus_read_i2c_block_data(client, 0x00, 3, buf);
D2 = (buf[0] << 16) | (buf[1] << 8) | buf[2];
// Trigger pressure conversion
i2c_smbus_write_byte(client, 0x48);
msleep(9);
// Read D1 (raw pressure)
i2c_smbus_write_byte(client, 0x00);
i2c_smbus_read_i2c_block_data(client, 0x00, 3, buf);
D1 = (buf[0] << 16) | (buf[1] << 8) | buf[2];
// Apply calibration (datasheet formulas)
dT = D2 - (C5 << 8);
TEMP = 2000 + ((dT * C6) >> 23);
OFF = (C2 << 17) + ((C4 * dT) >> 6);
SENS = (C1 << 16) + ((C3 * dT) >> 7);
P = (((D1 * SENS) >> 21) - OFF) >> 15;
IIO Integration
The driver registers three channels:
static const struct iio_chan_spec ms8607_channels[] = {
{
.type = IIO_PRESSURE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_HUMIDITYRELATIVE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
},
};
Userspace reads from /sys/bus/iio/devices/iio:device0/:
in_pressure_raw: 97983 → 979.83 mbarin_temp_raw: 2073 → 20.73°Cin_humidityrelative_raw: 3051 → 30.51%RH
Testing
An automated test script handles module loading and sensor validation:
#!/bin/bash
make clean && make
sudo insmod main.ko
# Find IIO device
IIO_DEV=$(find /sys/bus/iio/devices/ -name "*ms8607*")
# Read sensors
echo "Pressure: $(cat $IIO_DEV/in_pressure_raw) (raw)"
echo "Temperature: $(cat $IIO_DEV/in_temp_raw) (raw)"
echo "Humidity: $(cat $IIO_DEV/in_humidityrelative_raw) (raw)"
All three sensors reported valid readings on hardware.
CI/CD Pipeline
GitHub Actions runs static analysis and device tree validation on every push:
checkpatch: Linux kernel coding style verification
sparse: Static analyzer for kernel code
cppcheck: Additional static analysis
Device tree compilation: Validates overlay syntax
ARM cross-compilation builds run on manual trigger or release tags. The workflow builds vmlinux before modules to ensure proper symbol resolution—building only make modules caused missing symbol errors in modpost.
Platform-Specific Issues
ARM32 Division
The ARM32 kernel doesn't support 64-bit division. This issue showed up during CI cross-compilation—code that built fine on x86 failed with a link error on ARM:
// WRONG - link error on ARM32
s64 temp = 2345;
dev_info(&client->dev, "Temp: %lld°C\n", temp / 100);
// ERROR: "__aeabi_ldivmod" undefined!
Fix: cast to 32-bit before division:
// CORRECT
s64 temp = 2345;
int temp_int = (int)temp;
dev_info(&client->dev, "Temp: %d°C\n", temp_int / 100);
Resources
Hardware & Datasheets:
Linux Kernel Documentation:
Code References:
MS5637 Driver (similar sensor)
Learning Resources:
This project gave me hands-on experience with the driver layer of embedded Linux—something I haven't yet had the chance to work with. Writing a kernel module from scratch meant learning more about device trees, IIO subsystem internals, I2C protocol details, and platform-specific constraints like ARM32 arithmetic limitations.
If you're interested in embedded Linux development, the full source code and documentation are available in the GitHub repository.
Thanks for reading! :)
