How to make an embedded Linux device driver (11)

11th: Add I2C device to device tree

About this series

This is a HowTo article for developing embedded Linux device drivers as kernel modules. All the content of this article can be run on a Raspberry Pi.

-1st time: Build environment preparation and simple kernel module creation -Second time: System call handler and driver registration (static method) -Third time: System call handler and driver registration (dynamic method) -4th: Read / write implementation and memory story -5th time: Implementation of GPIO driver for Raspberry Pi -6th time: Implementation of ioctl -7th time: Interface for procfs -8th time: Interface for debugfs -9th time: Call a function of another kernel module / Use GPIO control function -10th time: Create a device driver using I2C --__ 11th time: Add I2C device to device tree <----------------- ---- Contents of this time __ -12th time: Load the created device driver at startup

The entire source code that appears in this article

https://github.com/take-iwiw/DeviceDriverLesson/tree/master/11_01

Contents of this time

So far, we have implemented several device drivers. However, every device driver had "hardware-specific information" directly within the device driver.

For example, in 5th time, I implemented the GPIO driver by hitting the memory-mapped register directly. At this time, I had chip (SoC) specific information such as offset address in the driver. This is not good. (In the first place, GPIO itself is not created as a driver, and Linux kernel standard functions are usually used.)

Even if you use the standard functions of the Linux kernel, the device driver can still have hardware-specific information. 10th time accessed the I2C device using the standard I2C control functions of the Linux kernel. However, the board-specific information such as which I2C bus the target I2C device is connected to and the slave address was described in the driver.

This is no longer allowed by the rules. The reason is that the code increases steadily with each variation of SoC and board. Today, this hardware-specific information is managed by the device tree.

Last time, I made a device driver for an accelerometer (LIS3DH) connected via I2C. However, at this time, it was necessary to manually provide the kernel with information on the I2C device. This time, I'll try to incorporate this information into the device tree. This article is for Raspberry Pi2 Model B

Files related to the device tree

Although it is called a "device tree", it is actually a binary firmware. The extension will be .dtb. It's under / boot on the Raspberry Pi. For example, for RaspberryPi2 Model B, it would be /boot/bcm2709-rpi-2-b.dtb.

ls /boot/*.dtb
/boot/bcm2708-rpi-0-w.dtb     /boot/bcm2709-rpi-2-b.dtb  /boot/bcm2835-rpi-a-plus.dtb  /boot/bcm2835-rpi-zero.dtb
/boot/bcm2708-rpi-b.dtb       /boot/bcm2710-rpi-3-b.dtb  /boot/bcm2835-rpi-b.dtb       /boot/bcm2836-rpi-2-b.dtb
/boot/bcm2708-rpi-b-plus.dtb  /boot/bcm2710-rpi-cm3.dtb  /boot/bcm2835-rpi-b-plus.dtb
/boot/bcm2708-rpi-cm.dtb      /boot/bcm2835-rpi-a.dtb    /boot/bcm2835-rpi-b-rev2.dtb

SoC-specific information is to be included in the DTSI file, and board-specific information is to be included in the DTS file. By compiling these with a special compiler called DTC, the DTB file mentioned earlier is created. For Raspberry Pi 2, it is located at /linux/arch/arm/boot/dts/bcm2709-rpi-2-b.dts.

Directly rewrite the dtb file (cut-out version)

Add to device tree

As I'll write later, the correct way is to bring the Linux source tree, edit /linux/arch/arm/boot/dts/bcm2709-rpi-2-b.dts and compile it. However, in this series, I have checked it on Raspberry Pi as easily as possible. Again, I'll try it easily first.

Please back up bcm2709-rpi-2-b.dts in the SD card to a PC etc. in advance. In the worst case, it may not start. It also installs the compiler (DTC) for the device tree. sudo apt-get install device-tree-compiler

It then decompiles the currently used DTB file back into DTS.

dtc -I dtb -O dts /boot/bcm2709-rpi-2-b.dtb > dis_bcm2709-rpi-2-b.dts

This will create a text file called dis_bcm2709-rpi-2-b.dts. This file contains tree-like information about device connections on this board (Raspy 2). In addition, the register address etc. are also listed. This time, suppose you connect LIS3DH (slave address = 0x18) to I2C_1. First, find the node for I2C1. It is written in the address, but it is at ʻi2c @ 7e804000. Add a node called mydevicethere. By settingcompatible to mycompany, myoriginaldevice, it is specified as "a device named myoriginaldevice made by a manufacturer named mycompany". Also, set the slave address in place of reg`. This causes the kernel to recognize that this board has a device called "mycompany, myoriginaldevice" connected to 0x18 of I2C_1.

dis_bcm2709-rpi-2-b.dts


abridgement
i2c@7e804000 {
	compatible = "brcm,bcm2835-i2c";
	reg = <0x7e804000 0x1000>;
	interrupts = <0x2 0x15>;
	clocks = <0x7 0x14>;
	#address-cells = <0x1>;
	#size-cells = <0x0>;
	status = "disabled";
	pinctrl-names = "default";
	pinctrl-0 = <0x10>;
	clock-frequency = <0x186a0>;
	phandle = <0x20>;

	/*Add your own device*/
	mydevice@18 {
		compatible = "mycompany,myoriginaldevice";
		reg = <0x18>;
	};
};
abridgement

After editing, compile with the following command and overwrite the original DTB file. For the time being, set the access right.

dtc -O dtb -o bcm2709-rpi-2-b.dtb dis_bcm2709-rpi-2-b.dts
chmod 755 bcm2709-rpi-2-b.dtb
sudo cp bcm2709-rpi-2-b.dtb /boot/bcm2709-rpi-2-b.dtb
sudo reboot yes

You can check the device tree information in / proc / device-tree. After rebooting, if mydevice is added under I2C1, it is successful.

ls /proc/device-tree/soc/i2c@7e804000/mydevice@18/
   compatible  name  reg

Support on the device driver side

On the device driver side, it is necessary to register the corresponding device. Earlier, I registered a device called "mycompany, myoriginaldevice" in the device tree, so I will make it correspond to it. To do this, set struct of_device_id to"mycompany, myoriginaldevice"and register it in .of_match_table of struct i2c_driver. Other than that, it is the same as the previous implementation. The source code is shown below.

myDeviceDriver.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/of_platform.h>

/***Information about this device***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice"				/* /proc/Device name displayed on devices etc.*/

/*Matching table of devices handled by this device driver*/
/*Corresponds to the following in dts
	i2c@7e804000 {
		mydevice@18 {
			compatible = "mycompany,myoriginaldevice";
			reg = <0x18>;
		};
*/
static const struct of_device_id mydevice_of_match_table[] = {
	{.compatible = "mycompany,myoriginaldevice",},
	{ },
};
MODULE_DEVICE_TABLE(of, mydevice_of_match_table);

/*Register the table that identifies the device handled by this device driver*/
/*The important thing is the first name field. This determines the device name. The back is the data that can be used freely with this driver. Insert pointers and identification numbers*/
static struct i2c_device_id mydevice_i2c_idtable[] = {
	{"MyI2CDevice", 0},
	{ }
};
MODULE_DEVICE_TABLE(i2c, mydevice_i2c_idtable);

static int mydevice_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("mydevice_i2c_probe\n");
	if(id != NULL)
		printk("id.name = %s, id.driver_data = %d", id->name, id->driver_data);
	if(client != NULL)
		printk("slave address = 0x%02X\n", client->addr);

	/*Usually here to check if the device is supported by this driver*/

	int version;
	version = i2c_smbus_read_byte_data(client, 0x0f);
	printk("id = 0x%02X\n", version);

	return 0;
}

static int mydevice_i2c_remove(struct i2c_client *client)
{
	printk("mydevice_i2c_remove\n");
	return 0;
}

static struct i2c_driver mydevice_driver = {
	.driver = {
		.name			= DRIVER_NAME,
		.owner			= THIS_MODULE,
		.of_match_table = mydevice_of_match_table,
	},
	.id_table		= mydevice_i2c_idtable,		//I2C devices supported by this device driver
	.probe			= mydevice_i2c_probe,		//Process called when the target I2C device is recognized
	.remove			= mydevice_i2c_remove,		//Process called when the target I2C device is removed
};

module_i2c_driver(mydevice_driver);

The kernel will call the corresponding device driver based on the connection information in the device tree. The slave address set in the device tree is stored in the struct i2c_client passed tomydevice_i2c_probe ().

One point to note is that in this case, struct i2c_device_id will contain NULL. I don't know if that's the reason, but it seems that .probe will be abolished soon. It seems to use .probe_new instead. In .probe_new, struct i2c_device_id is deleted.

Try to move

Build and load with the following command.

make && sudo insmod MyDeviceModule.ko
dmesg
[ 3507.003163] mydevice_i2c_probe
[ 3507.003175] slave address = 0x18
[ 3507.003656] id = 0x33

If you look at the log, you can see that probe is called properly and I2C communication is also possible. Last time, I needed to manually notify the device connection, but this time I didn't.

Make DTB from source code

Environmental preparation

I will make a DTB from the Linux source code for Raspberry Pi properly. First, get the source code. Also, install the tools required for the build. Basically it is as (https://www.raspberrypi.org/documentation/linux/kernel/building.md). Since it is a native build, no other preparation is required.

sudo apt-get install git bc
git clone https://github.com/raspberrypi/linux.git

Try editing the DTS file for Raspberry Pi

The DTS file for Raspberry Pi 2 Model B will be /linux/arch/arm/boot/dts/bcm2709-rpi-2-b.dts. (This file is a lot cleaner than the decompiled dts file earlier, because it's split into multiple files. This dts file includes bcm2709.dtsi at the beginning. The decompiled dts file is all included, so it seems to be messed up.)

Edit /linux/arch/arm/boot/dts/bcm2709-rpi-2-b.dts in the same way as before. Add the device in place of I2C_1.

/linux/arch/arm/boot/dts/bcm2709-rpi-2-b.dts


abridgement
&i2c1 {
	pinctrl-names = "default";
	pinctrl-0 = <&i2c1_pins>;
	clock-frequency = <100000>;

	/*Add your own device*/
	mydevice@18 {
		compatible = "mycompany,myoriginaldevice";
		reg = <0x18>;
	};
};
abridgement

Try to build DTB

After editing the DTS file, compile only the DTB with the following command. Compiling on a Raspberry Pi takes less than a minute. Copy the resulting DTB file to / boot.

cd ~/linux
KERNEL=kernel7
make bcm2709_defconfig
make -j4 dtbs
sudo cp arch/arm/boot/dts/*.dtb /boot/
sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/

Note

DTB for overlays seems to be used when the connection status changes dynamically after the kernel is booted. So you don't have to copy it.

Try to move

The device driver is the same as the previous cut-out version and is OK. A reboot should give the same result.

in conclusion

I tried playing with the device tree using an I2C device as an example. What I've touched on in this article is just a touch of the device tree. To find out more about the device tree, please refer to the article + link at here.

Recommended Posts

How to make an embedded Linux device driver (11)
How to make an embedded Linux device driver (8)
How to make an embedded Linux device driver (1)
How to make an embedded Linux device driver (4)
How to make an embedded Linux device driver (7)
How to make an embedded Linux device driver (2)
How to make an embedded Linux device driver (3)
How to make an embedded Linux device driver (6)
How to make an embedded Linux device driver (5)
How to make an embedded Linux device driver (10)
How to make an embedded Linux device driver (9)
How to make an embedded Linux device driver (12) (Complete)
[Blender x Python] How to make an animation
How to make Linux compatible with Japanese keyboard
How to make Yubico Yubikey recognized in Manjaro Linux
How to make an interactive CLI tool in Golang
How to make an HTTPS server with Go / Gin
[Python] How to make an adjacency matrix / adjacency list [Graph theory]
How to make a hacking lab-Kali Linux (2020.1) VirtualBox 64-bit Part 2-
How to make a hacking lab-Kali Linux (2020.1) VirtualBox 64-bit edition-
How to make a Python package (written for an intern)
How to create an ISO file (CD image) on Linux
How to make a Japanese-English translation
How to make a slack bot
How to install wkhtmltopdf (Amazon Linux2)
How to create an email user
How to install VMware-Tools on Linux
How to make a crawler --Advanced
How to make a recursive function
How to install MBDyn (Linux Ubuntu)
How to make a deadman's switch
[Blender] How to make a Blender plugin
[Blender] How to make Blender scripts multilingual
How to make a crawler --Basic
How to build MongoDB C driver
How to check Linux OS version
How to make a string into an array or an array into a string in Python
How to get the printer driver for Oki Mac into Linux
How to make Word Cloud characters monochromatic
How to build my own Linux server
How to make Selenium as light as possible
[Linux] How to subdivide files and folders
How to make an artificial intelligence LINE bot with Flask + LINE Messaging API
How to install aws-session-manager-plugin on Manajro Linux
[Python] How to make a class iterable
python3 How to install an external module
How to create an NVIDIA Docker environment
How to convert Python to an exe file
I want to know how LINUX works!
[Linux] How to use the echo command
How to use the Linux grep command
How to update php on Amazon linux 2
How to display emoji on Manjaro Linux
How to install packages on Alpine Linux
[Cocos2d-x] How to make Script Binding (Part 2)
How to install Anisble on Amazon Linux 2
How to operate Linux from the console
How to install Windows Subsystem For Linux
How to power off Linux with Ultra96-V2
How to update security on CentOS Linux 8
I want to make an automation program!