platform driver writing under the device tree

Posted by whatwhat123 on Thu, 30 Dec 2021 22:01:43 +0100

1, Introduction to platform driver under device tree

platform driver framework is divided into bus, device and driver. The bus does not need to be managed by driver programmers. This is provided by Linux kernel. When writing drivers, we just need to focus on the specific implementation of devices and drivers.

In the Linux kernel without device tree, we need to write and register platform respectively_ Device and platform_driver stands for device and driver respectively.

When using the device tree, the device description is placed in the device tree, so the platform_ We don't need to write the device. We just need to implement the platform_driver.

When writing platform driver based on device tree, we need to pay attention to the following points:

1. Create a device node in the device tree

First create a device node in the device tree to describe the device information. The focus is to set the value of the compatible attribute, because the platform bus needs to match the driver through the compatible attribute value of the device node!

/* LED */
	gpioled{
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "atkalpha-gpioled";

		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_led>;
		/* Other node content */
		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
		status = "okay";
	};

Note that the value of the compatible attribute is "at Kalpha gpioled", so I will write the platform later
When driving_ match_ "Atkalpha gpioled" should be in the table attribute table.

2. Pay attention to compatibility attributes when writing platform drivers

When using the device tree, the platform driver will pass the of_match_table to save the compatibility value, which indicates which devices this driver is compatible with. So, of_match_table will be particularly important, such as platform in the platform driver of this routine_ The driver can be set as follows:

1 static const struct of_device_id leds_of_match[] = {
2 { .compatible = "atkalpha-gpioled" }, /*  Compatibility properties */
3 { /* Sentinel */ }
4 };
5
6 MODULE_DEVICE_TABLE(of, leds_of_match);
7
8 static struct platform_driver leds_platform_driver = {
9 .driver = {
10 .name = "imx6ul-led",
11 .of_match_table = leds_of_match,
12 },
13 .probe = leds_probe,
14 .remove = leds_remove,
15 };

Lines 1-4, of_ device_ The ID table, that is, the driver compatibility table, is an array, and each array element is of_device_id type. Each array element is a compatible attribute, which represents a compatible device. A driver can match multiple devices. Here we only matched one device, the gpioled device created.
The compatible value in line 2 is "at Kalpha gpioled". The compatible attribute in the driver matches the compatible attribute in the device, so the corresponding probe function in the driver will be executed.
Note that line 3 is an empty element, which is written in of_device_id, the last element must be empty!
Line 6, via MODULE_DEVICE_TABLE declare leds_of_match this device matching table.
Line 11, set the platform_ Of in driver_ match_ Table matches the LEDs created above_ of_ Match. So far, we have set the platform driven matching table.

3. Write platform driver

The platform driver based on device tree is basically the same as the platform driver without device tree in the previous chapter. Both of them will execute the probe function when the driver and device match successfully. We need to execute the character device driver in the probe function. When the driver module is logged off, the remove function will be executed. They are similar.

2, Hardware schematic analysis

3, Experimental programming

1. Modify the equipment tree file

Use previous.

2.platform driver programming

The device is ready. Next, you need to write the corresponding platform driver, create a new folder named "18_dtsplatform", and then_ Create a vscode project in the dtsplatform folder, and the workspace is named "dtsplatform". Create a new one named ledriver C driver file, in ledriver Enter the following in C:

3. Write test APP

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/errno.h>

#include <linux/gpio.h>

#include <linux/cdev.h>

#include <linux/device.h>

#include <linux/of_gpio.h>

#include <linux/semaphore.h>

#include <linux/timer.h>

#include <linux/irq.h>

#include <linux/wait.h>

#include <linux/poll.h>

#include <linux/fs.h>

#include <linux/fcntl.h>

#include <linux/platform_device.h>

#include <asm/mach/map.h>

#include <asm/uaccess.h>

#include <asm/io.h>

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

file name 		:  leddriver.c

author 	  	:  Zuo Zhongkai

edition 	   	:  V1.0

describe 	   	:  platform driver under the device tree

other 	   	:  nothing

Forum 	   	:  www.openedv.com

journal 	   	:  First edition v1 0 created by Zuo Zhongkai on August 13, 2019

***************************************************************/



#define LEDDEV_CNT 		 one 				/*  Equipment number length 	*/

#define LEDDEV_NAME 		 "dtsplatled" 	/*  Device name 	*/

#define LEDOFF 			0

#define LEDON 			1



/* leddev Equipment structure */

struct leddev_dev{

	dev_t devid;				/* Equipment number	*/

	struct cdev cdev;			/* cdev		*/

	struct class *class;		/* class 		*/

	struct device *device;		/* equipment		*/

	int major;					/* Main equipment No	*/	

	struct device_node *node;	/* LED Device node */

	int led0;					/* LED Lamp GPIO label */

};



struct leddev_dev leddev; 		/* led equipment */



/*

 * @description		: LED On / off

 * @param - sta 	: LEDON(0) Turn on the LED and LEDOFF(1) turns off the LED

 * @return 			: nothing

 */

void led0_switch(u8 sta)

{

	if (sta == LEDON )

		gpio_set_value(leddev.led0, 0);

	else if (sta == LEDOFF)

		gpio_set_value(leddev.led0, 1);	

}



/*

 * @description		: open device

 * @param - inode 	: inode passed to driver

 * @param - filp 	: The device file has a file structure called private_ Member variable of data

 * 					  Generally, private is used when open ing_ Data points to the device structure.

 * @return 			: 0 success; Other failures

 */

static int led_open(struct inode *inode, struct file *filp)

{

	filp->private_data = &leddev; /* Set private data  */

	return 0;

}



/*

 * @description		: Write data to device 

 * @param - filp 	: A device file that represents an open file descriptor

 * @param - buf 	: Data to write to device

 * @param - cnt 	: Length of data to write

 * @param - offt 	: Offset relative to the first address of the file

 * @return 			: The number of bytes written. If it is negative, it indicates that the write failed

 */

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)

{

	int retvalue;

	unsigned char databuf[2];

	unsigned char ledstat;



	retvalue = copy_from_user(databuf, buf, cnt);

	if(retvalue < 0) {



		printk("kernel write failed!\r\n");

		return -EFAULT;

	}

	

	ledstat = databuf[0];

	if (ledstat == LEDON) {

		led0_switch(LEDON);

	} else if (ledstat == LEDOFF) {

		led0_switch(LEDOFF);

	}

	return 0;

}



/* Device operation function */

static struct file_operations led_fops = {

	.owner = THIS_MODULE,

	.open = led_open,

	.write = led_write,

};



/*

 * @description		: flatform Driven probe function, when driven with

 * 					  This function will be executed after the device matches

 * @param - dev 	: platform equipment

 * @return 			: 0,success; Other negative values, failed

 */

static int led_probe(struct platform_device *dev)

{	

	printk("led driver and device was matched!\r\n");

	/* 1,Set device number */

	if (leddev.major) {

		leddev.devid = MKDEV(leddev.major, 0);

		register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);

	} else {

		alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);

		leddev.major = MAJOR(leddev.devid);

	}



	/* 2,Register device      */

	cdev_init(&leddev.cdev, &led_fops);

	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);



	/* 3,Create class      */

	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);

	if (IS_ERR(leddev.class)) {

		return PTR_ERR(leddev.class);

	}



	/* 4,Create device */

	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);

	if (IS_ERR(leddev.device)) {

		return PTR_ERR(leddev.device);

	}



	/* 5,Initialize IO */	

	leddev.node = of_find_node_by_path("/gpioled");

	if (leddev.node == NULL){

		printk("gpioled node nost find!\r\n");

		return -EINVAL;

	} 

	

	leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0);

	if (leddev.led0 < 0) {

		printk("can't get led-gpio\r\n");

		return -EINVAL;

	}



	gpio_request(leddev.led0, "led0");

	gpio_direction_output(leddev.led0, 1); /* led0 IO Set to output, default high level	*/

	return 0;

}



/*

 * @description		: platform The remove function of the platform driver. This function will be executed when the platform driver is removed

 * @param - dev 	: platform equipment

 * @return 			: 0,success; Other negative values, failed

 */

static int led_remove(struct platform_device *dev)

{

	gpio_set_value(leddev.led0, 1); 	/* Turn off the LED when unloading the drive */



	cdev_del(&leddev.cdev);				/*  Delete cdev */

	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* Logout equipment number */

	device_destroy(leddev.class, leddev.devid);

	class_destroy(leddev.class);

	return 0;

}



/* Match list */

static const struct of_device_id led_of_match[] = {

	{ .compatible = "atkalpha-gpioled" },

	{ /* Sentinel */ }

};



/* platform Drive structure */

static struct platform_driver led_driver = {

	.driver		= {

		.name	= "imx6ul-led",			/* Driver name, used to match the device */

		.of_match_table	= led_of_match, /* Device tree matching table 		 */

	},

	.probe		= led_probe,

	.remove		= led_remove,

};

		

/*

 * @description	: Driver module loading function

 * @param 		: nothing

 * @return 		: nothing

 */

static int __init leddriver_init(void)

{

	return platform_driver_register(&led_driver);

}



/*

 * @description	: Driver module unloading function

 * @param 		: nothing

 * @return 		: nothing

 */

static void __exit leddriver_exit(void)

{

	platform_driver_unregister(&led_driver);

}



module_init(leddriver_init);

module_exit(leddriver_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("zuozhongkai");

Lines 33-112, traditional character device driver, nothing to say.
Lines 120-164 are the probe function driven by platform. After the device node in the device tree is successfully matched with the driver, this function will be executed. All the work originally done in the driver loading function is now completed in the probe function.
Lines 171-180 are the remove function, which will be executed when the platform driver is unloaded. In this function, you can release memory, log off character devices, etc., that is, put all the work in the original driver unloading function into the remove function.
Lines 183-186, the matching table, describe what kind of devices this driver matches. Line 184 adds a compatible attribute value with the value of "atkalpha gpioled". When the compatible attribute value of a device node in the device tree is also "atkalpha gpioled", it will match this driver.
Lines 189-196, platform_driver driver structure, line 191 sets the name of the platform driver as "imx6ul led". Therefore, when the driver is loaded successfully, there will be a file named "imx6u led" in the / sys/bus/platform/drivers / directory.
Line 192 set of_match_table is the LED above_ of_ match.
Lines 203 ~ 206, the driver module loads the function, and the platform is used in this function_ driver_ Register registers LEDs with the Linux kernel_ Driver driver.
Lines 213 ~ 216, the driver module unloading function, in which the platform_driver_unregister uninstalls the LED from the Linux kernel_ Driver driver.

4, Run test

depmod //This command needs to be run when the driver is loaded for the first time
modprobe leddriver.ko //Load driver module

After the driver module is loaded, go to / sys/bus/platform/drivers / directory to check whether the driver exists. We can find it in ledriver Setting led in C_ The name field of driver (platform_driver type) is "imx6ul led", so there will be a file named "imx6ul led" in / sys/bus/platform/drivers / directory. The results are as follows:

Similarly, there are also led device files in the / sys/bus/platform/devices / directory, that is, the gpioled node in the device tree,

After the driver and equipment are matched successfully, you can test the LED lamp drive. Enter the following command to turn on the LED lamp:

./ledApp /dev/dtsplatled 1  //Turn on the LED

Topics: Linux Embedded system