How to add DT support for a driver

5 minute read

The platform device scheme has been extensively used to describe hardware platforms. But its main disadvantage is the need to instantiate each device by code. A modern way to describe the hardware is via device tree (DT). There are kernel drivers that only support platform code, but the usage of device tree support on describing SoCs has been increasing. The idea of this post is to show a real example of how to add device tree support for a driver using the drivers/hwmon/sht15.c, as an example. The code below shows how the file arch/arm/mach-pxa/stargate2.c uses the sht15 driver in platform data:

static struct sht15_platform_data platform_data_sht15 = {
	.gpio_data =  100,
	.gpio_sck  =  98,
};

static struct platform_device sht15 = {
	.name = "sht15",
	.id = -1,
	.dev = {
		.platform_data = &platform_data_sht15,
	},
};

static struct regulator_consumer_supply stargate2_sensor_3_con[] = {
	REGULATOR_SUPPLY("vcc", "sht15"),
};

DT support

The SHT11 sensor is present in the Click board SHT1x Click Module. This Click board can be plugged into the mikroBUS connector of the i.MX 7Dual SABRE-SD, a board that only support device tree. For a better understanding please open the link below containing the patch that enables the communication between the i.MX 7Dual SABRE-SD and the SHT1x Click Module: Patch SHT1x Click Module.

Documentation

All DT support needs a document to be accepted (and understood):

Documentation/devicetree/bindings/hwmon/sht15.txt

This document has to describe:

  1. Device name;
  2. Required properties (tip: if necessary, use “-“ to separate names);
  3. Optional properties, when necessary; and
  4. Example of the device tree source (dts file).

When a new device is added, if not present, its vendor needs to be included in the file below:

Documentation/devicetree/bindings/vendor-prefixes.txt

In this example, the vendor is Sensirion AG, and the patch below shows how to add it: Patch Vendor Sensirion

Driver modification

The driver can be found in:

drivers/hwmon/sht15.c

There are three modifications to implement in the driver:

1. Open firmware

For device tree support the Open Firmware header of.h is needed. As this driver works with gpio, this was included:

#include<of_gpio.h>

2. Probe function modifications

All drivers have a probe function, which works like a main function in a C program. This function gets all the hardware resources, such as memory, interrupts, GPIOS, etc. In order to add device tree support into the driver the probe function needs to be changed, so that it can parse the device tree properties. The “clean” way to do this is creating a secondary probe function to parse the DT properties. Define the device tree user case (OF_CONFIG), the .compatible device and the device values MODULE_DEVICE_TABLE, including the secondary probe function to deal with this case:

#ifdef CONFIG_OF
static const struct of_device_id sht15_dt_match[] = {
	{ .compatible = "sensirion,sht15" },
	{ },
};
MODULE_DEVICE_TABLE(of, sht15_dt_match);

static struct sht15_platform_data *sht15_probe_dt(struct device *dev)
{
	struct device_node *np = dev->of_node;
	struct sht15_platform_data *pdata;

	/* no device tree device */
	if (!np)
		return NULL;

	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
	if (!pdata)
		return ERR_PTR(-ENOMEM);

	pdata->gpio_data = of_get_named_gpio(np, "data-gpios", 0);
	if (pdata->gpio_data < 0) {
		if (pdata->gpio_data != -EPROBE_DEFER)
			dev_err(dev, "data-gpios not found\n");
		return ERR_PTR(pdata->gpio_data);
	}

	pdata->gpio_sck = of_get_named_gpio(np, "clk-gpios", 0);
	if (pdata->gpio_sck < 0) {
		if (pdata->gpio_sck != -EPROBE_DEFER)
			dev_err(dev, "clk-gpios not found\n");
		return ERR_PTR(pdata->gpio_sck);
	}

	return pdata;
}
#else
static inline struct sht15_platform_data *sht15_probe_dt(struct device *dev)
{
	return NULL;
}
#endif

Finally, change the original probe function to work with both cases. It is recommended to use the order platform data -> device tree in a logical way, to be able to handle errors too:

data->pdata = sht15_probe_dt(&pdev->dev);
	if (IS_ERR(data->pdata))
		return PTR_ERR(data->pdata);
	if (data->pdata == NULL) {
		data->pdata = dev_get_platdata(&pdev->dev);
		if (data->pdata == NULL) {
			dev_err(&pdev->dev, "no platform data supplied\n");
			return -EINVAL;
		}
 	}

3. Driver struct

In the last lines of the file, add the of_match_table into the struct platform_driver:

static struct platform_driver sht15_driver = {
	.driver = {
		.name = "sht15",
		.of_match_table = of_match_ptr(sht15_dt_match),
 	},

Device Tree Source

With the probe functions finished, remains to describe the properties parsed in the device tree case. The responsible for this is the dts file, and the example for this driver can be found in the link: Patch dts file. The dts file is the device tree hardware description itself. Most of the time the needed pins are already in use and it is common to create a new dts file for each “external” device, as complement of the SoC.dts file. This way, the dts file has at least two sections:

1. Driver Description

The driver description needs to inform exactly which device is being enabled and an overview about it:

sensor {
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_sensor>;
		compatible = "sensirion,sht15";
		clk-gpios = <&gpio4 12 0>;
		data-gpios = <&gpio4 13 0>;
		vcc-supply = <&reg_sht15>;
};

This description needs to be the same used in the documentation example.

2. Driver pins

In the driver description there is a pincrtl value, which is used to configure the iomux function. When an iomux is set, the PAD been able to lead with 8 muxing options (called ALT modes). The communication between the Click Module and the i.MX 7Dual SABRE-SD depends of the correctly ALT mode configuration. The image below shows a demonstration of how to find the SHT1x Click Board SCL pin correspond communication in the i.MX 7Dual SABRE-SD schematic:

Demonstration of how to find the i2c3 SCL in the schematic
Demonstration of how to find the i2c3 SCL in the schematic - Rev. D, 09/03/2016

SHT11 does not work with I2C protocol, so in this case the CLK and DATA pins need to be configured as GPIO functionality. Search for I2C3_SCL in the i.MX 7Dual Reference Manual to find those GPIOs numbers:

I2c3 SCL field descriptions
I2c3 SCL field descriptions example - RM Rev. 0.1, 08/2016

The image shows all the ALT modes possibilities, including how to use the i2c3 SCL as GPIO. This way, set as disabled the i2c3 and configure the iomux whit the GPIO number:

&i2c3 {
	status = "disabled";
};

&iomuxc {
	pinctrl_sensor: sensorgrp {
		fsl,pins = <
			MX7D_PAD_I2C3_SDA__GPIO4_IO13	0x4000007f
			MX7D_PAD_I2C3_SCL__GPIO4_IO12	0x4000007f
		>;
	};
};

Testing the new driver

After building the kernel, set up the new dts file:

=> setenv fdt_file 'imx7d-sdb-sht11.dtb'
=> saveenv
=> reset

Enter the command line below to get the local temperature:

cat /sys/bus/platform/device/sensor/temp1_input

Enter the command line below to get the local humidity:

cat /sys/bus/platform/device/sensor/humidity1_input