linux设备树-pin控制器驱动

Posted 大奥特曼打小怪兽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux设备树-pin控制器驱动相关的知识,希望对你有一定的参考价值。

----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

上一节我们已经分析了pinctrl subsystem,这一节将会介绍pin controller driver的编写。其主要包含两个步骤:

  • 为SoC pin controller分配一个pinctrl_desc,并进行初始化;
  • 调用pinctrl_register将pinctrl_desc注册pinctrl subsystem;

一、pin controller设备注册

1.1 注册

我们定位到drivers/pinctrl/samsung/pinctrl-samsung.c文件:

 

二、注册pinctrl_desc

 

 

参考文章

[1]linux内核pinctrl子系统分析

[2]Linux驱动之 pinctrl和GPIO子系统

[3]linux pinctrl子系统

[4]基于Linux的Pinctrl子系统框架源码分析

[5]pinctrl 子系统介绍

[6]pinctrl子系统----从provider出发

 

NanoPi NEO Air使用十:自己编写驱动来控制LED

修改设备树

打开/home/ql/linux/H3/linux/arch/arm/boot/dts/sunxi-h3-h5.dtsi文件,发现H3的pin功能都是由pinctrl子系统控制的:

		pio: pinctrl@01c20800 {
			/* compatible is in per SoC .dtsi file */
			reg = <0x01c20800 0x400>;
			interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
			clock-names = "apb", "hosc", "losc";
			gpio-controller;
			#gpio-cells = <3>;
			interrupt-controller;
			#interrupt-cells = <3>;

			csi_pins: csi {
				pins = "PE0", "PE1", "PE2", "PE3", "PE4",
				       "PE5", "PE6", "PE7", "PE8", "PE9",
				       "PE10", "PE11";
				function = "csi";
			};

			emac_rgmii_pins: emac0 {
				pins = "PD0", "PD1", "PD2", "PD3", "PD4",
				       "PD5", "PD7", "PD8", "PD9", "PD10",
				       "PD12", "PD13", "PD15", "PD16", "PD17";
				function = "emac";
				drive-strength = <40>;
			};

			i2s0_pins: i2s0 {
				pins = "PA18", "PA19", "PA20", "PA21";
				function = "i2s0";
			};

			i2c0_pins: i2c0 {
				pins = "PA11", "PA12";
				function = "i2c0";
			};

			i2c1_pins: i2c1 {
				pins = "PA18", "PA19";
				function = "i2c1";
			};

			i2c2_pins: i2c2 {
				pins = "PE12", "PE13";
				function = "i2c2";
			};

			mmc0_pins_a: mmc0@0 {
				pins = "PF0", "PF1", "PF2", "PF3",
				       "PF4", "PF5";
				function = "mmc0";
				drive-strength = <30>;
				bias-pull-up;
			};

			mmc0_cd_pin: mmc0_cd_pin@0 {
				pins = "PF6";
				function = "gpio_in";
				bias-pull-up;
			};

			mmc1_pins_a: mmc1@0 {
				pins = "PG0", "PG1", "PG2", "PG3",
				       "PG4", "PG5";
				function = "mmc1";
				drive-strength = <30>;
				bias-pull-up;
			};

			mmc2_8bit_pins: mmc2_8bit {
				pins = "PC5", "PC6", "PC8",
				       "PC9", "PC10", "PC11",
				       "PC12", "PC13", "PC14",
				       "PC15", "PC16";
				function = "mmc2";
				drive-strength = <30>;
				bias-pull-up;
			};

			spdif_tx_pins_a: spdif@0 {
				pins = "PA17";
				function = "spdif";
			};

			spi0_pins: spi0 {
				pins = "PC0", "PC1", "PC2", "PC3";
				function = "spi0";
			};

			spi1_pins: spi1 {
				pins = "PA15", "PA16", "PA14", "PA13";
				function = "spi1";
			};

			uart0_pins_a: uart0@0 {
				pins = "PA4", "PA5";
				function = "uart0";
			};

			uart1_pins: uart1 {
				pins = "PG6", "PG7";
				function = "uart1";
			};

			uart1_rts_cts_pins: uart1_rts_cts {
				pins = "PG8", "PG9";
				function = "uart1";
			};

			uart2_pins: uart2 {
				pins = "PA0", "PA1";
				function = "uart2";
			};

            uart2_rts_cts_pins: uart2_rts_cts {
                pins = "PA2", "PA3";
                function = "uart2";
            };

			uart3_pins: uart3 {
				pins = "PA13", "PA14";
				function = "uart3";
			};
			uart3_rts_cts_pins: uart3_rts_cts {
				pins = "PA15", "PA16";
				function = "uart3";
			};

			pwm0_pins: pwm0 {
				pins = "PA5";
				function = "pwm0";
			};
		};

我们要把开发板上的状态灯作为普通输出IO来使用,该pin的功能也应该在这里定义。打开开发板原理图,发现状态灯的IO为PA10。

因此回到/home/ql/linux/H3/linux/arch/arm/boot/dts/sun8i-h3-nanopi.dtsi,添加如下内容:

&pio {
	leds_npi: led_pins {
		pins = "PA10";
		function = "gpio_out";
	};
};

但是我们发现该文件下,开发板自己已经有了:

&pio {
	leds_npi: led_pins {
		pins = "PA10";
		function = "gpio_out";
	};
	spi0_cs_pins: spi0_cs_pins {
		pins = "PC3", "PA6";
		function = "gpio_out";
	};
};

于是我们就不用再添加了。
继续打开该文件,在根节点下面把原来的leds 节点注释掉,新增testleds节点,修改后如下:

	/*leds {
		compatible = "gpio-leds";
		pinctrl-names = "default";
		pinctrl-0 = <&leds_npi>, <&leds_r_npi>;

		status {
			label = "status_led";
			gpios = <&pio 0 10 GPIO_ACTIVE_HIGH>;
			linux,default-trigger = "heartbeat";
		};

		pwr {
			label = "LED2";
			gpios = <&r_pio 0 10 GPIO_ACTIVE_HIGH>;
			default-state = "on";
		};
	};*/

	testleds {
		compatible = "test-gpio-leds";
		pinctrl-names = "default";
		pinctrl-0 = <&leds_npi>;
		gpios = <&pio 0 10 GPIO_ACTIVE_HIGH>;
		status = "okay";
	};

testleds节点的compatible 属性用来匹配驱动。pinctrl-0属性来指示使用的引脚和功能。

编写platform 驱动

新建02_led文件夹,在该文件夹下添加leddriver.c文件,内容为:

#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>


#define LEDDEV_CNT		1				/* 设备号长度 	*/
#define LEDDEV_NAME		"testled"	    /* 设备名字 	*/
#define LEDOFF 			0
#define LEDON 			1

/* leddev设备结构体 */
struct leddev_dev{
	dev_t devid;				/* 设备号	*/
	struct cdev cdev;			/* cdev		*/
	struct class *class;		/* 类 		*/
	struct device *device;		/* 设备		*/
	int major;					/* 主设备号	*/	
	struct device_node *node;	/* LED设备节点 */
	int led0;					/* LED灯GPIO标号 */
};

struct leddev_dev leddev; 		/* led设备 */

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
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		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &leddev; /* 设置私有数据  */
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
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;
}

/* 设备操作函数 */
static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.write = led_write,
};

/*
 * @description		: flatform驱动的probe函数,当驱动与
 * 					  设备匹配以后此函数就会执行
 * @param - dev 	: platform设备
 * @return 			: 0,成功;其他负值,失败
 */
static int led_probe(struct platform_device *dev)
{	
	printk("led driver and device was matched!\\r\\n");
	/* 1、设置设备号 */
	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、注册设备      */
	cdev_init(&leddev.cdev, &led_fops);
	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);

	/* 3、创建类      */
	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(leddev.class)) {
		return PTR_ERR(leddev.class);
	}

	/* 4、创建设备 */
	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
	if (IS_ERR(leddev.device)) {
		return PTR_ERR(leddev.device);
	}

	/* 5、获取设备节点:/testleds  */	
	leddev.node = of_find_node_by_path("/testleds");
	if (leddev.node == NULL){
		printk("testleds node nost find!\\r\\n");
		return -EINVAL;
	} 
	
	/* 6、获取设备树中testleds节点的 gpios 属性,得到 LED 所使用的 GPIO 编号  */
	leddev.led0 = of_get_named_gpio(leddev.node, "gpios", 0);
	if (leddev.led0 < 0) {
		printk("can't get gpios\\r\\n");
		return -EINVAL;
	}

	gpio_request(leddev.led0, "led0");     /* 申请GPIO,在使用一个 GPIO 之前一定要使用 gpio_request进行申请	*/
	gpio_direction_output(leddev.led0, 1); /* led0 IO设置为输出,默认高电平	*/
	return 0;
}

/*
 * @description		: platform驱动的remove函数,移除platform驱动的时候此函数会执行
 * @param - dev 	: platform设备
 * @return 			: 0,成功;其他负值,失败
 */
static int led_remove(struct platform_device *dev)
{
	gpio_set_value(leddev.led0, 1); 	/* 卸载驱动的时候关闭LED */

	cdev_del(&leddev.cdev);				/*  删除cdev */
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
	device_destroy(leddev.class, leddev.devid);
	class_destroy(leddev.class);
	return 0;
}

/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
	{ .compatible = "test-gpio-leds" },
	{ /* Sentinel */ }
};

/* platform驱动结构体 */
static struct platform_driver led_driver = {
	.driver		= {
		.name	= "test-led",			/* 驱动名字,用于和设备匹配 */
		.of_match_table	= led_of_match, /* 设备树匹配表 		 */
	},
	.probe		= led_probe,
	.remove		= led_remove,
};
		
/*
 * @description	: 驱动模块加载函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init leddriver_init(void)
{
	return platform_driver_register(&led_driver);
}

/*
 * @description	: 驱动模块卸载函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit leddriver_exit(void)
{
	platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("qlexcel");

添加应用程序

在该文件夹下添加ledApp.c文件,内容为:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

#define LEDOFF 	0
#define LEDON 	1

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char databuf[2];
	
	if(argc != 3){
		printf("Error Usage!\\r\\n");
		return -1;
	}

	filename = argv[1];

	/* 打开led驱动 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\\r\\n", argv[1]);
		return -1;
	}
	
	databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 */
	retvalue = write(fd, databuf, sizeof(databuf));
	if(retvalue < 0){
		printf("LED Control Failed!\\r\\n");
		close(fd);
		return -1;
	}

	retvalue = close(fd); /* 关闭文件 */
	if(retvalue < 0){
		printf("file %s close failed!\\r\\n", argv[1]);
		return -1;
	}
	return 0;
}

添加Makefile

在该文件夹下添加Makefile文件,内容为:

KERNELDIR := /home/qlLinux驱动开发pinctrl子系统和gpio子系统

linux设备树-LCD驱动程序

Linux kernel 有关 spi 设备树参数解析

linux设备树-中断控制器驱动

Linux驱动开发设备树

NanoPi NEO Air使用十:自己编写驱动来控制LED