How to make an embedded Linux device driver (10)

10th: Create a device driver using I2C

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 <----------------- ---- Contents of this time __ -11th time: Add I2C device to device tree -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/10_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_02 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_03 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/10_04

Contents of this time

Last time, I made a device driver that controls LEDs / buttons using GPIO control functions. This time, we will create a device driver for the device connected by I2C. It is not a device driver for I2C itself. Similar to the previous GPIO, the I2C driver itself is prepared by the SoC manufacturer. And the device driver can be called by the standard function declared in <linux / i2c.h>. Therefore, you should use it positively. Increases portability, versatility and reliability.

The target board is Raspberry Pi 2. The target I2C device is described as an accelerometer (LIS3DH). However, this is not particularly important as the purpose is not to control the device itself. I use an accelerometer, but this time I only get the device ID. This is because I just want to check the communication. no special meaning. If there is other information that is easy to check, that is fine. Please fix only the following information in advance.

--Connect I2C_1 of Raspberry Pi and SCL, SDA of LIS3DH --LIS3DH slave address is 0x18 --To read the device ID of LIS3DH, read the register at address 0x0F. If you can read it correctly, 0x33 will be returned.

Advance preparation and confirmation (for Raspberry Pi)

Please enable I2C with raspi-config. After that, check that communication is possible with the following command. You can check the information of all devices connected to I2C_1 with ʻi2cdetect. ʻI get the device ID of LIS3DH with i2cget. If 0x33 is output, it is OK. If not, something is wrong and you should check the connection etc.

i2cdetect -y 1
i2cget -y 1 0x18 0x0f b
  0x33

Story around I2C

Like GPIO, it would be nice if it could be easily controlled with a single function call, but I2C is a bit complicated. Some prior knowledge is required.

Control of I2C devices is realized by modules divided into multiple layers. Let's start with the hardware. First, there is I2C itself (I2C Bus). In the case of Raspberry Pi, there are three I2Cs in total, I2C_0, I2C_1, and I2C_2, respectively, which are separate I2C buses. There are I2C devices connected to each bus. For example, if you want to connect an accelerometer (LIS3DH, address = 0x18) to the bus of I2C_1, it will be one I2C Client.

Let's look at the software side from below. First, there is an I2C Adapter that controls the I2C Bus. Control of I2C itself is an SoC-dependent process. Therefore, there is a separate source code for each SoC. In the case of Raspberry Pi, it will be i2c-bcm2835.c. There is also i2c-algo, which describes the algorithms used during communication. i2c-core.c is the main process and provides general-purpose functions such as ʻi2c_smbus_read_byte (). The I2C Client Drivers control each I2C device using its ʻi2c_smbus_read_byte () etc. There is also i2c-dev.c to prepare a generic i2c device file (/ dev / i2c-1). These are all kernel-side code.

layer name Description
SW I2C Client Drivers Device driver for each I2C device, i2c-dev
i2c-core Main processing of I2C communication
i2c-algo-XXX Communication algorithm
I2C Adapters Where I2C itself is controlled. Tweaking registers and chip-dependent processing
HW - I2C Bus For example, I2C_0、I2C_1
- I2C Client Each I2C device(For example, an accelerometer(LIS3DH))

This time, we will create the device driver for the I2C Client Driver in the table above, specifically the I2C-connected accelerometer (LIS3DH). Therefore, you may think, "You just call the function of i2c-core, right?", But it is not so easy. We'll see what happens later, but for now, just understand the relationships in the table above.

Simple I2C device device driver

The most basic device driver code for I2C devices is as follows. There are various things that I am not familiar with. Let's start from the bottom of the code. mydevice_init () and mydevice_exit () are the processes when this device driver is loaded / unloaded. In it, we call ʻi2c_add_driver () and ʻi2c_del_driver (). Somehow a table is passed as an argument.

I2C is an interface that connects with "bus". Therefore, just because this device driver is loaded does not mean that the target I2C device (accelerometer in this case) is always connected. It's hard to imagine I2C, but I think it's easier to understand if you imagine USB. Therefore, it is necessary to take action when a device is connected / disconnected to the I2C bus. It is ʻi2c_add_driver () and ʻi2c_del_driver () to register / cancel the processing at that time. The type of table to be registered is struct i2c_driver. Register the I2C device information supported by this device driver in .id_table. The registered content will be the struct i2c_device_id mydevice_i2c_idtable [] declared at the beginning. The first member is the name of the corresponding device. The kernel looks for the corresponding driver by this name (char name []) and is a very important member (not a unique number, etc.!). The second member will be the private data used within this device driver. Anything is fine, but usually it seems to put a number for identification. Register the functions that the kernel calls when connecting / disconnecting I2C devices in .probe and .remove. Register the name of this device driver in .driver.

In mydevice_i2c_probe (), the ʻi2c_smbus_read_byte_data () function is called to communicate with the recognized I2C device. In this case, it is assumed that LIS3DH is connected, so the value at address 0x0F (device ID) is being read. ʻThe first argument passed to i2c_smbus_read_byte_data () is struct i2c_client, which stores the I2C bus information (I2C Adapter to be used) and slave address. You can get this information when this function mydevice_i2c_probe () is called.

myDeviceDriver.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>

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

/*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("i2c_lcd_probe\n");
	printk("id.name = %s, id.driver_data = %d", id->name, id->driver_data);
	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,
	},
	.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
};

/*Road(insmod)Functions sometimes called*/
static int mydevice_init(void)
{
	printk("mydevice_init\n");
	
	/*Register this device driver as a device driver that uses the I2C bus.*/
	i2c_add_driver(&mydevice_driver);
	return 0;
}

/*Unload(rmmod)Functions sometimes called*/
static void mydevice_exit(void)
{
	printk("mydevice_exit\n");
	i2c_del_driver(&mydevice_driver);
}

module_init(mydevice_init);
module_exit(mydevice_exit);

Note: Simplify routine processing

In the device driver for I2C equipment, the only processing to be performed at load / unload is to register struct i2c_driver in most cases. Therefore, it can be simplified as follows.

module_i2c_driver(mydevice_driver);

Try to move

Build, load and look at the log as shown below.

make && sudo insmod MyDeviceModule.ko
dmesg
[ 8589.545480] mydevice_init

Looking at the log, mydevice_init () was just called, not mydevice_i2c_probe (). This is because, as explained earlier, mydevice_i2c_probe () is called when the device is recognized, but no I2C device is connected at this time. If it is USB, it will probably be recognized automatically, but if it is I2C, you need to tell it manually. Make the kernel recognize the new device by writing a value to / sys / bus / i2c / devices / i2c-1 / new_device. This time, we want to assume that the device for this device driver is connected, so let's assume that the device named "MyI2CDevice" is connected with the slave address = 0x18. The information in this set is the substance of the "I2C Client" shown in the table above.

sudo bash -c 'echo MyI2CDevice 0x18 > /sys/bus/i2c/devices/i2c-1/new_device'
dmesg
[ 9179.410883] mydevice_i2c_probe
[ 9179.410902] id.name = MyI2CDevice, id.driver_data = 0
[ 9179.410912] slave address = 0x18
[ 9179.411301] id = 0x33
[ 9179.412045] i2c i2c-1: new_device: Instantiated device MyI2CDevice at 0x18

Then, as shown in the log above, mydevice_i2c_probe () is called. You can also see that the client parameter passed at that time contains the materialized I2C Client information. Then, using that information (cLient), you can see that you can communicate and get the correct id.

Note 1

This time, I decided to "handle a device named" MyI2CDevice "". Therefore, I set the name "MyI2CDevice" in the struct i2c_device_id table and registered it in the kernel. Then, because the kernel manually recognized the device "MyI2CDevice", the device driver mydevice_i2c_probe () was called. Since the kernel judges only by the name, it may try to control the wrong device if the name overlaps with another device. Please note that probe in mydevice_i2c_probe (), but what you should do with this function is to check whether this device driver handles this device. If it is a compatible device, it returns 0, otherwise it returns -1. To confirm this, the slave address is usually confirmed, or communication is performed to obtain the device ID and version information. This time, it is fixed and always returns 0.

Note 2

Use the following command to remove the device.

sudo bash -c 'echo  0x18 > /sys/bus/i2c/devices/i2c-1/delete_device'

Recognize I2C devices on the code

In the previous example, the device was manually recognized from the shell script. Probably not recommended, but you can do the same from your code as follows:

myDeviceDriver.Part of c


/*Road(insmod)Functions sometimes called*/
static struct i2c_client *i2c_clie = NULL;
static int mydevice_init(void)
{
	printk("mydevice_init\n");
	
	/*Register this device driver as a device driver that uses the I2C bus.*/
	i2c_add_driver(&mydevice_driver);

	/*Dynamically create a device entity(https://www.kernel.org/doc/Documentation/i2c/instantiating-devices) */
	/*Connected to I2C1,"MyI2CDevice"Create a device with the name 0x18 slave address*/
	struct i2c_adapter *i2c_adap;
	i2c_adap = i2c_get_adapter(1);
	struct i2c_board_info i2c_board_info = {
		I2C_BOARD_INFO("MyI2CDevice", 0x18)
	};
	i2c_clie = i2c_new_device(i2c_adap, &i2c_board_info);
	i2c_put_adapter(i2c_adap);

	return 0;
}

/*Unload(rmmod)Functions sometimes called*/
static void mydevice_exit(void)
{
	printk("mydevice_exit\n");
	
	i2c_del_driver(&mydevice__driver);
	if(i2c_clie) i2c_unregister_device(i2c_clie);
}

module_init(mydevice_init);
module_exit(mydevice_exit);

This method was also described in the Linux documentation (instantiating-devices), so the implementation itself is OK, but the implementation location is not good. What kind of device is connected is board-dependent information, so it should be written around bcm2835_init (void) in board_bcm2835.c. Furthermore, it seems better to use ʻi2c_register_board_info () `.

Also, there seems to be a way to put it in the device tree (.dts), but that is not well understood yet.

Interface using sysfs

Up to this point, I2C device recognition and initial communication have been completed. Next, create an interface with the user or the program in the user space. Basically, as usual, it will be exchanged using device files. First, create an easy-to-use interface using sysfs. I briefly touched on sysfs (7th time) at [https://qiita.com/take-iwiw/items/548444999d2dfdc06f46#%E3%83%8E%E3%83%BC%E3%83%882-sysfs]. .. At this time, it was treated as a parameter of module. This time, it should be treated as a parameter of I2C equipment.

Consider a simple interface that only acquires the device ID (version information). The procedure is very simple: (1) define the handler function when it is read, (2) set the attributes of the device file to be created, and (3) register it in the kernel. For ②, use a helper macro called DEVICE_ATTR. For ③, call the device_create_file () function at the timing of probe.

Below is the code that creates a file called "version" and calls the function get_version () when read. The first argument of DEVICE_ATTR is the file name to be created. The second argument is access authority. The third argument is the handler function when reading, and the fourth argument is the handler function when writing. Since the write operation is not performed this time, NULL is specified. With get_version () set in the handler function at the time of read, as before, the device ID information is read, packed in buf and returned. In order to use the I2C function, we need the information of struct i2c_client, but struct device is passed to this function. However, since struct i2c_client is included in this, it can be easily obtained. Use device_create_file () at the timing of the probe to register. The first argument is also struct device. The second argument is a set of attributes of the file to be registered. This is created by the DEVICE_ATTR macro mentioned earlier. Pass the name set in the first argument of DEVICE_ATTR with the prefix" dev_attr_ ".

myDeviceDriver.Part of c


static ssize_t get_version(struct device *dev, struct device_attribute *dev_attr, char * buf)
{
	printk("get_version\n");
	struct i2c_client * client = to_i2c_client(dev);

	int version;
	version = i2c_smbus_read_byte_data(client, 0x0f);
	return sprintf(buf, "id = 0x%02X\n", version);
}
static DEVICE_ATTR(version, S_IRUGO, get_version, NULL);

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

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

	/*Create a sysfs file for reading and writing the attributes of this device driver*/
	/* (/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0018/version)make*/
	device_create_file(&client->dev, &dev_attr_version);

	return 0;
}

static int mydevice_i2c_remove(struct i2c_client *client)
{
	printk("mydevice_i2c_remove\n");
	device_remove_file(&client->dev, &dev_attr_version);
	return 0;
}

After that, build, load, and recognize the device as follows. Then, the file /sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0018/version is created, and if you read it with cat, you can see that the id has been obtained.

make && sudo insmod MyDeviceModule.ko
sudo bash -c 'echo MyI2CDevice 0x18 > /sys/bus/i2c/devices/i2c-1/new_device'
cat /sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0018/version
   id = 0x33

Interface using / dev

Although sysfs is easy and convenient, you may still want to use the usual open / close / read / write / ioctl (+ select / poll / seek, etc.). To do this, you need to create the device file in the same way as before. Until now, the device file was created at the timing of loading, but this time it is the timing of probe. Also, in order to use the I2C control function, struct i2c_client is required. It is necessary to keep the struct i2c_client received at the timing of the probe somewhere and refer to it when reading or writing. This can be easily done by holding it in a static variable, but I'll do it a little better. To achieve this, we use a helper macro called container_of ().

Prior knowledge

container_of container_of () is a helper macro defined below that is executed at compile time.

#define container_of(ptr, type, member) ({			\
	const typeof(((type *)0)->member) * __mptr = (ptr);	\
	(type *)((char *)__mptr - offsetof(type, member)); })
#endif

Enter the structure name in type and the member name in the structure in member. Then put the actual pointer of that member in ptr. Then, it returns the start address of the structure that stores the ptr.

i2c_set_clientdata ʻI2c_set_clientdata ()is a function defined below. You can freely save the information associated withstruct i2c_client. This information can be anything, but it usually holds a pointer to the device driver's own structure that was allocated during probe. Use ʻi2c_get_clientdata () to retrieve it.

void i2c_set_clientdata(struct i2c_client *dev, void *data)

code

Below is the code that makes it possible to refer to the struct i2c_client received at the time of probe at the time of open / close / read / write. First of all, I will put the code on it.

myDeviceDriver.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>

/***Information about this device***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice"				/* /proc/Device name displayed on devices etc.*/
static const unsigned int MINOR_BASE = 0;	/*Starting number and number of minor numbers used in this device driver(=Number of devices) */
static const unsigned int MINOR_NUM  = 1;	/*Minor number is 0*/

/***I2C device management information***/
/*I2C devices handled by this device driver*/
enum mydevice_i2c_model {
	MYDEVICE_MODEL_A = 0,
	MYDEVICE_MODEL_NUM,
};

/*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", MYDEVICE_MODEL_A},
	{ }
};
MODULE_DEVICE_TABLE(i2c, mydevice_i2c_idtable);

/*Each I2C device(client)Information associated with. Set at probe time i2c_set_Keep in clientdata*/
struct mydevice_device_info {
	struct cdev        cdev;			/*probed I2C device(client)Required to associate cdev with. container at open_Search by of*/
	unsigned int       mydevice_major;	/*Major number of this device driver(Dynamically decide) */
	struct class      *mydevice_class;	/*Device driver class object*/
	struct i2c_client *client;
	/*Add other if necessary. mutex etc.*/
};


/*Function called at open*/
static int mydevice_open(struct inode *inode, struct file *file)
{
	printk("mydevice_open");

	/*Cdev with this open(inode->i_cdev)With mydevice_device_Find info*/
	struct mydevice_device_info *dev_info;
	dev_info = container_of(inode->i_cdev, struct mydevice_device_info, cdev);
	if (dev_info  == NULL || dev_info->client  == NULL) {
		printk(KERN_ERR "container_of\n");
		return -EFAULT;
	}
	file->private_data = dev_info;
	printk("i2c address = %02X\n",dev_info->client->addr);

	return 0;
}

/*Function called at close*/
static int mydevice_close(struct inode *inode, struct file *file)
{
	printk("mydevice_close");
	return 0;
}

/*Function called when reading*/
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_read");

	struct mydevice_device_info *dev_info = filp->private_data;
	struct i2c_client * client = dev_info->client;

	int version;
	version = i2c_smbus_read_byte_data(client, 0x0f);
	return sprintf(buf, "id = 0x%02X\n", version);
}

/*Function called when writing*/
static ssize_t mydevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_write");
	return count;
}

/*Handler table for various system calls*/
struct file_operations s_mydevice_fops = {
	.open    = mydevice_open,
	.release = mydevice_close,
	.read    = mydevice_read,
	.write   = mydevice_write,
};


static int mydevice_i2c_create_cdev(struct mydevice_device_info *dev_info)
{
	int alloc_ret = 0;
	int cdev_err = 0;
	dev_t dev;

	/*Secure a free major number*/
	alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
	if (alloc_ret != 0) {
		printk(KERN_ERR  "alloc_chrdev_region = %d\n", alloc_ret);
		return -1;
	}

	/*Obtained dev( =Major number+Minor number)Get the major number from and keep it*/
	dev_info->mydevice_major = MAJOR(dev);
	dev = MKDEV(dev_info->mydevice_major, MINOR_BASE);	/*Unnecessary? */

	/*Initialization of cdev structure and registration of system call handler table*/
	cdev_init(&dev_info->cdev, &s_mydevice_fops);
	dev_info->cdev.owner = THIS_MODULE;

	/*This device driver(cdev)In the kernel*/
	cdev_err = cdev_add(&dev_info->cdev, dev, MINOR_NUM);
	if (cdev_err != 0) {
		printk(KERN_ERR  "cdev_add = %d\n", alloc_ret);
		unregister_chrdev_region(dev, MINOR_NUM);
		return -1;
	}

	/*Register the class of this device(/sys/class/mydevice/make) */
	dev_info->mydevice_class = class_create(THIS_MODULE, "mydevice");
	if (IS_ERR(dev_info->mydevice_class)) {
		printk(KERN_ERR  "class_create\n");
		cdev_del(&dev_info->cdev);
		unregister_chrdev_region(dev, MINOR_NUM);
		return -1;
	}

	/* /sys/class/mydevice/mydevice*make*/
	for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
		device_create(dev_info->mydevice_class, NULL, MKDEV(dev_info->mydevice_major, minor), NULL, "mydevice%d", minor);
	}

	return 0;
}

static void mydevice_i2c_delete_cdev(struct mydevice_device_info *dev_info)
{
	dev_t dev = MKDEV(dev_info->mydevice_major, MINOR_BASE);
	
	/* /sys/class/mydevice/mydevice*To delete*/
	for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
		device_destroy(dev_info->mydevice_class, MKDEV(dev_info->mydevice_major, minor));
	}

	/*Remove class registration for this device(/sys/class/mydevice/To delete) */
	class_destroy(dev_info->mydevice_class);

	/*This device driver(cdev)From the kernel*/
	cdev_del(&dev_info->cdev);

	/*Remove the major number registration used in this device driver*/
	unregister_chrdev_region(dev, MINOR_NUM);
}

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

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

	/* open/close/read/i2c even with write_The client is used, so keep it*/
	struct mydevice_device_info *dev_info;
	dev_info =  (struct mydevice_device_info*)devm_kzalloc(&client->dev, sizeof(struct mydevice_device_info), GFP_KERNEL);
	dev_info->client = client;
	i2c_set_clientdata(client, dev_info);
	

	/*Register this device driver as a character type in the kernel.(/sys/class/mydevice/mydevice*make) */
	if(mydevice_i2c_create_cdev(dev_info)) return -ENOMEM;

	return 0;
}

static int mydevice_i2c_remove(struct i2c_client *client)
{
	printk("mydevice_i2c_remove\n");
	struct mydevice_device_info *dev_info;
	dev_info = i2c_get_clientdata(client);
	mydevice_i2c_delete_cdev(dev_info);

	return 0;
}

static struct i2c_driver mydevice_driver = {
	.driver = {
		.name	= DRIVER_NAME,
		.owner = THIS_MODULE,
	},
	.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
};


/*Register this device driver as a device driver that uses the I2C bus.*/
module_i2c_driver(mydevice_driver);

At the beginning of the code, I define my own structure called struct mydevice_device_info. It stores struct i2c_client, that is, the information associated with each I2C device (client). ("Associating" here means that if struct i2c_client is given, the stored information (struct mydevice_device_info) can be obtained.)

This time, I will create a new device file (cdev) every time an I2C device is connected, that is, at the timing of probe. This is exactly the same process you used to do when loading. The device file creation process is extracted to the mydevice_i2c_create_cdev () function. Until now, struct cdev cdev;, ʻunsigned int mydevice_major;, and struct class * mydevice_class;were kept static. This time, put it inside thestruct mydevice_device_infostructure. Also, the area of this structure is dynamically allocated bydevm_kzallocat the timing of probe. By usingdevm_kzalloc, this memory will be automatically released when the device disappears. Also, in the same `` struct mydevice_device_info`` structure, save the struct i2c_client required for I2C control. Then, use ʻi2c_set_clientdata () to save this struct mydevice_device_info structure in the area associated with this I2C client (where ?? I think the kernel will do it well). All of these processes are performed when the device is connected (mydevice_i2c_probe ()).

Since I created a character device file (cdev) in mydevice_i2c_probe (), I can use open / close / read / write. First, look at mydevice_open (). A pointer to cdev is stored in the first argument struct inode * inode of this function. You can find it at ʻinode-> i_cdev. Earlier, when creating the device file at the probe timing, cdev saved it in its own structure struct mydevice_device_info. Therefore, you can use container_ofto find the start address of the structure in which this cdev is stored.dev_info = container_of (inode-> i_cdev, struct mydevice_device_info, cdev);. And since the information of struct i2c_clientis also included in this, I2C control is possible using this. Actually, it is used at the time of read / write, so I put it inprivate_data in the file` structure so that it can be referred at the timing of read / write.

Try to move

Build and load the I2C device as shown below. Then, / dev / mydevice0 is created. And if you read this device with cat, you can see that the mydevice_read () function is called, in which I2C communication is performed and the ID value is returned.

make && sudo insmod MyDeviceModule.ko
sudo bash -c 'echo MyI2CDevice 0x18 > /sys/bus/i2c/devices/i2c-1/new_device'
ls /dev/mydevice0
    /dev/mydevice0
cat /dev/mydevice0
    id = 0x33

in conclusion

It's a bit long article, but the simple code I mentioned at the beginning is the basic form, so I think it's okay if you suppress it first. I think that the basic idea can be applied to device drivers (SPI, USB, PCI, etc.) that use other buses. (I think that additional processing for detect and suspend will be required)

Also, in the article, there were redundant expressions such as "device driver for devices connected by I2C", "device driver for I2C devices", and "device driver for I2C devices". This is because I wanted to distinguish it from "the device driver of I2C itself". I wish I had defined the word somewhere, but I forgot.

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 (4)
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
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 recursive function
How to install MBDyn (Linux Ubuntu)
[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 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
[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 power off Linux with Ultra96-V2
How to update security on CentOS Linux 8
I want to make an automation program!
How to install php7.4 on Linux (Ubuntu)
How to make multi-boot USB (Windows 10 compatible)
How to make a Backtrader custom indicator
How to make a Pelican site map
[Cocos2d-x] How to make Script Binding (Part 1)
How to find large files on Linux
How to get started with laravel (Linux)
How to use JDBC driver with Redash