Detailed explanation of Linux character device driver VII ("plug in" device tree realizes RGB lamp driver)

Catalogue of series articles

Detailed explanation of Linux character device driver
Detailed explanation of Linux character device driver II (using device driver model)
Detailed explanation of Linux character device driver III (using class)

preface

This article mainly comes from punctual atom, wildfire Linux tutorial and my understanding. If there is infringement, please contact me in time to delete it.

text

Device Tree Overlays: plug in device tree

Traditional device tree

Batch management of hardware resources, rigid mechanism

Plug in device tree

Modular management of hardware resources, flexible customization

Use premise

  • Kernel configuration
    • CONFIG_OF_OVERLAY = y
    • CONFIG_OF_CONFIGFS = y
  • Mount ConfigFS
mount x /sys/kernel/config -t configfs

Case description

Device tree: foo.dts

	/ {
		compatible = "corp,foo";
		
		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };
		}
	};

Plug in device tree: bar.dts

/dts-v1/;
/plugin/;
/ {
	....	
	fragment@0 {
		target = <&ocp>;
		__overlay__ {
			/* bar peripheral */
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			}
		};
	};
};
  • /dts-v1 /: Specifies the dts version number
  • /plugin /: allows undefined nodes to be referenced in the device tree
  • Target = < & XXX >: Specifies the parent node of the plug-in device tree
  • Target path = "xxx": Specifies the path of the parent node of the plug-in device tree

Device tree + "plug in device tree": foo.dts + bar.dts

/ {
		compatible = "corp,foo";

		/* shared resources */
		res: res {
		};

		/* On chip peripherals */
		ocp: ocp {
			/* peripherals that are always instantiated */
			peripheral1 { ... };

			/* bar peripheral */
			bar {
				compatible = "corp,bar";
				... /* various properties and child nodes */
			}
		}
	};

Compilation mode

./scripts/dtc/dtc -I dts -O dtb -o xxx.dtbo arch/arm/boot/dts/xxx.dts // Compile dts as dtbo
./scripts/dtc/dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtbo // Decompile dtbo to dts

APT download dtc tool

sudo apt install device-tree-compiler

Mode of use

Kernel running state loading (general)

1. Create a new directory under / sys / kernel / config / device tree / overlays /

mkdir /sys/kernel/config/device-tree/overlays/xxx

2. The dtbo firmware echo to the path property file (the first method)

echo xxx.dtbo >/sys/kernel/config/device-tree/overlays/xxx/path

Or cat the contents of dtbo to the dtbo properties file (the second method)

cat xxx.dtbo >/sys/kernel/config/device-tree/overlays/xxx/dtbo

3. The node will be created to view the kernel device tree

ls /proc/device-tree

4. Delete the plug-in device tree

rmdir /sys/kernel/config/device-tree/overlays/xxx

uboot loading (wildfire linux development board)

Modify / boot/uEnv.txt

The "plug-in" device tree realizes RGB lamp driving

Add node information to the device tree

RGB lamp related register

/*
*CCM_CCGR1                         0x020C406C
*IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04  0x020E006C
*IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04  0x020E02F8
*GPIO1_GD                          0x0209C000
*GPIO1_GDIR                        0x0209C004
*/


/*
*CCM_CCGR3                         0x020C4074
*IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC   0x020E01E0
*IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC   0x020E046C
*GPIO4_GD                          0x020A8000
*GPIO4_GDIR                        0x020A8004
*/


/*
*CCM_CCGR3                         0x020C4074
*IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC   0x020E01DC
*IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC   0x020E0468
*GPIO4_GD                          0x020A8000
*GPIO4_GDIR                        0x020A8004
*/
	/*Add led node*/
	rgb_led{
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "fire,rgb_led";

		/*Red node*/
		ranges;
		rgb_led_red@0x020C406C{
			reg = <0x020C406C 0x00000004
			       0x020E006C 0x00000004
			       0x020E02F8 0x00000004
				   0x0209C000 0x00000004
			       0x0209C004 0x00000004>;
			status = "okay";
		};

		/*Green node*/
		rgb_led_green@0x020C4074{
			reg = <0x020C4074 0x00000004
			       0x020E01E0 0x00000004
			       0x020E046C 0x00000004
				   0x020A8000 0x00000004
			       0x020A8004 0x00000004>;
			status = "okay";
		};

		/*Blue light node*/
		rgb_led_blue@0x020C4074{
			reg = <0x020C4074 0x00000004
			       0x020E01DC 0x00000004
			       0x020E0468 0x00000004
				   0x020A8000 0x00000004
			       0x020A8004 0x00000004>;
			status = "okay";
		};
	};

reg property memory mapping

of_iomap() function

Converts the physical address of the reg attribute value to a virtual address

void __iomem *of_iomap(struct device_node *np,
int index)

Parameters:

  • np: device_node represents the node
  • index: generally, the reg attribute contains multiple segments. index is used to specify which segment to map. The label starts from 0.

Code example

Take the wildfire code as an example
led.dts

/dts-v1/;
/plugin/;
/ {
	fragment@0 {
		target-path = "/";
		__overlay__ {
			/* bar peripheral */
			rgb_led{
            #address-cells = <1>;
            #size-cells = <1>;
            compatible = "fire,rgb_led";

            /*Red node*/
            ranges;
            rgb_led_red@0x020C406C{
                reg = <0x020C406C 0x00000004
                    0x020E006C 0x00000004
                    0x020E02F8 0x00000004
                    0x0209C000 0x00000004
                    0x0209C004 0x00000004>;
                status = "okay";
            };

            /*Green node*/
            rgb_led_green@0x020C4074{
                reg = <0x020C4074 0x00000004
                    0x020E01E0 0x00000004
                    0x020E046C 0x00000004
                    0x020A8000 0x00000004
                    0x020A8004 0x00000004>;
                status = "okay";
            };

            /*Blue light node*/
            rgb_led_blue@0x020C4074{
                reg = <0x020C4074 0x00000004
                    0x020E01DC 0x00000004
                    0x020E0468 0x00000004
                    0x020A8000 0x00000004
                    0x020A8004 0x00000004>;
                status = "okay";
            };
        };
		};
	};
};

dts_led.c, the code is the same as the previous one

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>

#include <linux/platform_device.h>

/*------------------Character device content----------------------*/
#define DEV_NAME "rgb_led"
#define DEV_CNT (1)

/*Define the led resource structure and save the obtained node information and the converted virtual register address*/
struct led_resource
{
	struct device_node *device_node; //rgb_led_red device tree node
	void __iomem *virtual_CCM_CCGR;
	void __iomem *virtual_IOMUXC_SW_MUX_CTL_PAD;
	void __iomem *virtual_IOMUXC_SW_PAD_CTL_PAD;
	void __iomem *virtual_DR;
	void __iomem *virtual_GDIR;
};

static dev_t led_devno;					 //Defines the device number of the character device
static struct cdev led_chr_dev;			 //Define character device structure chr_dev
struct class *class_led;				 //Save the created class
struct device *device;					 // Save the created device
struct device_node *rgb_led_device_node; //rgb_led device tree node structure

/*Define the LEDs of the three lamps R, G and B_ The resource structure holds the obtained node information*/
struct led_resource led_red;
struct led_resource led_green;
struct led_resource led_blue;

/*Character device operation function set, open function*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
	printk("\n open form driver \n");
	return 0;
}

/*Character device operation function set, write function*/
static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{

	int ret,error;
	unsigned int register_data = 0; //Temporarily store the read register data
	unsigned char receive_data[10]; //Used to save received data
	unsigned int write_data; //Used to save received data

	if(cnt>10)
			cnt =10;

	error = copy_from_user(receive_data, buf, cnt);
	if (error < 0)
	{
		return -1;
	}

	ret = kstrtoint(receive_data, 16, &write_data);
	if (ret) {
		return -1;
        }

	/*Set GPIO1_04 output level*/
	if (write_data & 0x04)
	{
		register_data = ioread32(led_red.virtual_DR);
		register_data &= ~(0x01 << 4);
		iowrite32(register_data, led_red.virtual_DR); // GPIO1_ Pin 04 outputs low level, and the red light is on
	}
	else
	{
		register_data = ioread32(led_red.virtual_DR);
		register_data |= (0x01 << 4);
		iowrite32(register_data, led_red.virtual_DR); // GPIO1_ Pin 04 outputs high level, and the red light goes out
	}

	/*Set GPIO4_20 output level*/
	if (write_data & 0x02)
	{
		register_data = ioread32(led_green.virtual_DR);
		register_data &= ~(0x01 << 20);
		iowrite32(register_data, led_green.virtual_DR); // GPIO4_20 pin output low level, green light on
	}
	else
	{
		register_data = ioread32(led_green.virtual_DR);
		register_data |= (0x01 << 20);
		iowrite32(register_data, led_green.virtual_DR); // GPIO4_20 pin output high level, green light off
	}

	/*Set GPIO4_19 output level*/
	if (write_data & 0x01)
	{
		register_data = ioread32(led_blue.virtual_DR);
		register_data &= ~(0x01 << 19);
		iowrite32(register_data, led_blue.virtual_DR); //GPIO4_ Pin 19 output low level, blue light on
	}
	else
	{
		register_data = ioread32(led_blue.virtual_DR);
		register_data |= (0x01 << 19);
		iowrite32(register_data, led_blue.virtual_DR); //GPIO4_ Pin 19 output high level, blue light off
	}

	return cnt;
}

/*Character device operation function set*/
static struct file_operations led_chr_dev_fops =
	{
		.owner = THIS_MODULE,
		.open = led_chr_dev_open,
		.write = led_chr_dev_write,
};

/*----------------Platform driven function set-----------------*/
static int led_probe(struct platform_device *pdv)
{

	int ret = -1; //Save error status code
	unsigned int register_data = 0;

	printk(KERN_ALERT "\t  match successed  \n");

	/*Get rgb_led device tree node*/
	rgb_led_device_node = of_find_node_by_path("/rgb_led");
	if (rgb_led_device_node == NULL)
	{
		printk(KERN_ERR "\t  get rgb_led failed!  \n");
		return -1;
	}

	/*Get RGB_ Red light child node of LED node*/
	led_red.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_red");
	if (led_red.device_node == NULL)
	{
		printk(KERN_ERR "\n get rgb_led_red_device_node failed ! \n");
		return -1;
	}


	/*Get reg attribute and convert to virtual address*/
	led_red.virtual_CCM_CCGR = of_iomap(led_red.device_node, 0);
	led_red.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_red.device_node, 1);
	led_red.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_red.device_node, 2);
	led_red.virtual_DR = of_iomap(led_red.device_node, 3);
	led_red.virtual_GDIR = of_iomap(led_red.device_node, 4);

	/*Initialize red light*/
	register_data = ioread32(led_red.virtual_CCM_CCGR);
	register_data |= (0x03 << 26);
	iowrite32(register_data, led_red.virtual_CCM_CCGR); //Turn on the clock

	register_data = ioread32(led_red.virtual_IOMUXC_SW_MUX_CTL_PAD);
	register_data &= ~(0xf << 0);
	register_data |= (0x05 << 0);
	iowrite32(register_data, led_red.virtual_IOMUXC_SW_MUX_CTL_PAD); //Set multiplexing function

	register_data = ioread32(led_red.virtual_IOMUXC_SW_PAD_CTL_PAD);
	register_data = (0x10B0);
	iowrite32(register_data, led_red.virtual_IOMUXC_SW_PAD_CTL_PAD); //Set PAD properties

	register_data = ioread32(led_red.virtual_GDIR);
	register_data |= (0x01 << 4);
	iowrite32(register_data, led_red.virtual_GDIR); //Set GPIO1_04 is output mode

	register_data = ioread32(led_red.virtual_DR);
	register_data |= (0x01 << 4);
	iowrite32(register_data, led_red.virtual_DR); //Set GPIO1_04 default output high level






	/*Get RGB_ Green sub node of LED node*/
	led_green.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_green");
	if (led_green.device_node == NULL)
	{
		printk(KERN_ERR "\n get rgb_led_green_device_node failed ! \n");
		return -1;
	}

	/*Get reg attribute and convert to virtual address*/
	led_green.virtual_CCM_CCGR = of_iomap(led_green.device_node, 0);
	led_green.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_green.device_node, 1);
	led_green.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_green.device_node, 2);
	led_green.virtual_DR = of_iomap(led_green.device_node, 3);
	led_green.virtual_GDIR = of_iomap(led_green.device_node, 4);

	/*Initialize green light*/
	register_data = ioread32(led_green.virtual_CCM_CCGR);
	register_data |= (0x03 << 12);
	iowrite32(register_data, led_green.virtual_CCM_CCGR); //Turn on the clock

	register_data = ioread32(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD);
	register_data &= ~(0xf << 0);
	register_data |= (0x05 << 0);
	iowrite32(register_data, led_green.virtual_IOMUXC_SW_MUX_CTL_PAD); //Set multiplexing function

	register_data = ioread32(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD);
	register_data = (0x10B0);
	iowrite32(register_data, led_green.virtual_IOMUXC_SW_PAD_CTL_PAD); //Set PAD properties

	register_data = ioread32(led_green.virtual_GDIR);
	register_data |= (0x01 << 20);
	iowrite32(register_data, led_green.virtual_GDIR); //Set GPIO4_IO20 is output mode

	register_data = ioread32(led_green.virtual_DR);
	register_data |= (0x01 << 20);
	iowrite32(register_data, led_green.virtual_DR); //Set GPIO4_IO20 default output high level






	/*Get RGB_ Blue light child node of LED node*/
	led_blue.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_blue");
	if (led_blue.device_node == NULL)
	{
		printk(KERN_ERR "\n get rgb_led_blue_device_node failed ! \n");
		return -1;
	}

	/*Get reg attribute and convert to virtual address*/
	led_blue.virtual_CCM_CCGR = of_iomap(led_blue.device_node, 0);
	led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_blue.device_node, 1);
	led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_blue.device_node, 2);
	led_blue.virtual_DR = of_iomap(led_blue.device_node, 3);
	led_blue.virtual_GDIR = of_iomap(led_blue.device_node, 4);

	/*Initialize blue light*/
	register_data = ioread32(led_blue.virtual_CCM_CCGR);
	register_data |= (0x03 << 12);
	iowrite32(register_data, led_blue.virtual_CCM_CCGR); //Turn on the clock

	register_data = ioread32(led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD);
	register_data &= ~(0xf << 0);
	register_data |= (0x05 << 0);
	iowrite32(register_data, led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD); //Set multiplexing function

	register_data = ioread32(led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD);
	register_data = (0x10B0);
	iowrite32(register_data, led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD); //Set PAD properties

	register_data = ioread32(led_blue.virtual_GDIR);
	register_data |= (0x01 << 19);
	iowrite32(register_data, led_blue.virtual_GDIR); //Set GPIO4_IO19 is output mode

	register_data = ioread32(led_blue.virtual_DR);
	register_data |= (0x01 << 19);
	iowrite32(register_data, led_blue.virtual_DR); //Set GPIO4_IO19 default output high level








	/*---------------------Register character device section-----------------*/

	//First step
	//The equipment number is obtained by dynamic allocation, and the secondary equipment number is 0,
	//The device name is RGB LEDs, which can be viewed through the command cat / proc / devices
	//DEV_CNT is 1. Currently, only one equipment number is applied
	ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
	if (ret < 0)
	{
		printk("fail to alloc led_devno\n");
		goto alloc_err;
	}
	//Step 2
	//Associated character device structure cdev and file operation structure file_operations
	led_chr_dev.owner = THIS_MODULE;
	cdev_init(&led_chr_dev, &led_chr_dev_fops);
	//Step 3
	//Add device to cdev_map hash table
	ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
	if (ret < 0)
	{
		printk("fail to add cdev\n");
		goto add_err;
	}

	//Step 4
	/*Create class */
	class_led = class_create(THIS_MODULE, DEV_NAME);

	/*Create device*/
	device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);

	return 0;

add_err:
	//When adding a device fails, you need to log off the device number
	unregister_chrdev_region(led_devno, DEV_CNT);
	printk("\n error! \n");
alloc_err:

	return -1;
}

static const struct of_device_id rgb_led[] = {
	{.compatible = "fire,rgb_led"},
	{/* sentinel */}};

/*Define platform equipment structure*/
struct platform_driver led_platform_driver = {
	.probe = led_probe,
	.driver = {
		.name = "rgb-leds-platform",
		.owner = THIS_MODULE,
		.of_match_table = rgb_led,
	}};

/*
*Driver initialization function
*/
static int __init led_platform_driver_init(void)
{
	int DriverState;
	DriverState = platform_driver_register(&led_platform_driver);
	printk(KERN_ALERT "\tDriverState is %d\n", DriverState);
	return 0;
}

/*
*Drive logoff function
*/
static void __exit led_platform_driver_exit(void)
{
	/*Unmap physical address to virtual address*/
	iounmap(led_green.virtual_CCM_CCGR);
	iounmap(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD);
	iounmap(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD);
	iounmap(led_green.virtual_DR);
	iounmap(led_green.virtual_GDIR);

	iounmap(led_red.virtual_CCM_CCGR);
	iounmap(led_red.virtual_IOMUXC_SW_MUX_CTL_PAD);
	iounmap(led_red.virtual_IOMUXC_SW_PAD_CTL_PAD);
	iounmap(led_red.virtual_DR);
	iounmap(led_red.virtual_GDIR);

	iounmap(led_blue.virtual_CCM_CCGR);
	iounmap(led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD);
	iounmap(led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD);
	iounmap(led_blue.virtual_DR);
	iounmap(led_blue.virtual_GDIR);

	/*Delete device*/
	device_destroy(class_led, led_devno);		  //Clear device
	class_destroy(class_led);					  //Clear class
	cdev_del(&led_chr_dev);						  //Clear device number
	unregister_chrdev_region(led_devno, DEV_CNT); //Unregister character device

	/*Unregister character device*/
	platform_driver_unregister(&led_platform_driver);

	printk(KERN_ALERT "led_platform_driver exit!\n");
}

module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);

MODULE_LICENSE("GPL");

/**/

summary

Compile led.dts into rgb.dtbo plug-in device tree, load the traditional device tree and plug-in device tree into the kernel, and use the following command to view the device tree after loading. At this time, you can see the new RGB in the number of devices_ Led node.

ls /sys/firmware/devicetree/base
 perhaps
ls /proc/device-tree

Recompile DTS_ The LED. C source file is dto.led.ko kernel module and loaded into the kernel. Then there is / dev/rgb_led node, and finally to / dev/rgb_led node can control rgb lamp by writing data.

sudo sh -c "ecoh '1' >/dev/rgb_led"
Bright blue light
sudo sh -c "ecoh '2' >/dev/rgb_led"
Green light
sudo sh -c "ecoh '4' >/dev/rgb_led"
on shaky ground
sudo sh -c "ecoh '7' >/dev/rgb_led"
Full bright

Keywords: Linux

Added by Fribbles on Sun, 28 Nov 2021 21:47:42 +0200