基于 GPIO 、Pinctl子系统、设备树的LED 驱动程序

理论部分:

pinctrl 子系统重点是设置 PIN( 有的 SOC 叫做 PAD) 的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio ,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO

编写思路:

GPIO 的地位跟其他模块,比如 I2C UART 的地方是一样的,要使用某个引脚,需要先把引脚配置为 GPIO 功能,这要使用 Pinctrl 子系统,只需要在设备 树里指定就可以。在驱动代码上不需要我们做任何事情。 GPIO 本身需要确定引脚,这也需要在设备树里指定。
设备树节点会被内核转换为 platform_device 。 对应的,驱动代码中要注册一个 platform_driver ,在 probe 函数中:获得引脚、注册 file_operations。在 file_operations 中:设置方向、读值 / 写值。

在设备树中添加 Pinctrl 信息:

有些芯片提供了
1)设备树生成工具,在 GUI 界面中选择引脚功能和配置信息, 就可以自动生成 Pinctrl 子结点。把它复制到你的设备树文件中,再在 client device 结点中引用就可以。
2)有 些 芯 片 只 提 供 文 档 , 那 就 去 阅 读 文 档 , 一 般 在 内 核 源 码的绑定目 录 Documentation\devicetree\bindings\pinctrl 下面,保存有该厂家的文档,。
如果连文档都没有,那只能参考内核源码中的设备树文件,在内核源码目录 arch/arm/boot/dts 目录下。
Pinctrl 子节点的样式如下:

在设备树中添加 GPIO 信息

先查看电路原理图确定所用引脚,再在设备树中指定:添加 ”[name]-gpios” 属性,指定使用的是哪一个 GPIO Controller 里的哪一个引脚,还有其他 Flag 信息,比如 GPIO_ACTIVE_LOW 等。具体需要多少个 cell 来描述一个引脚,需要查看设备树中这个 GPIO Controller 节点里的“ #gpio-cells ”属性值,也 可以查看内核文档。

在驱动代码中调用 GPIO 子系统函数访问设备的gpio

实践部分:

创建pinctl sever节点:

 修改imx6ull-myboard.dts,利用节点生成工具配置引脚后将生成的对应代码片段替换到dts文件内对应位置,节点内容如下:

pinctrl_gpioled: ledgrp{
   fsl,pins = <
       MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03    0x10b0
       >;
};
  • MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 表示将该io复用为GPIO
  • 0x10b0 表示对PAD寄存器的配置值,具体含义为如下,
    /*寄存器SW_PAD_SNVS_TAMPER3设置IO属性
        *bit 16:0 HYS关闭
        *bit [15:14]: 00 默认下拉
        *bit [13]: 0 kepper功能
        *bit [12]: 1 pull/keeper使能
        *bit [11]: 0 关闭开路输出
        *bit [7:6]: 10 速度100Mhz
        *bit [5:3]: 110 R0/6驱动能力
        *bit [0]: 0 低转换率
    

Pinctrl节点示例:

创建pinctrl client节点并添加GPIO信息:

在根节点下创建名为gpioled的LED节点,内容如下:

/*pinctrl led*/
gpioled {
   compatible = "myboard,gpioled";
   pinctrl-names = "default";
   pinctrl-0 = <&pinctrl_gpioled>;
   led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
   status = "okay";
};
  • pinctrl-0 设置 LED所使用的PIN对应的pinctrl节点
  • led-gpio 指定了LED所使用的GPIO,这里是GPIO5的IO03,低电平有效 

注意事项:

因为我的开发板使用的设备树文件(imx6ull-myboard.dts)是从NXP官方提供的设备树文件(imx6ull-14x14-evk.dts)上修改而来的,可能某些引脚的配置与自己的开发板不一样,需要检查一下是否有使用冲突。

本次添加的这个MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03与文件中的其它引脚没有出现冲突,因此无需修改。 

修改LED驱动文件 

重要步骤:

1 步 定义、注册一个 platform_driver
2 步 在它的 probe 函数里:
a) 根据 platform_device 的设备树信息确定 GPIO gpiod_get
b)创建设备节点
c)注册一个 file_operations 结构体register_chrdev()
 file_operarions 中使用 GPIO 子系统的函数操作 GPIO: .open()、.write()         (也就是gpiod_direction_output、 gpiod_set_value) 
全部代码如下:
/* 1. 确定主设备号                                                                 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;


#define MIN(a, b) (a < b ? a : b)


void led_class_create_device(int minor)
{
	device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */
}
void led_class_destroy_device(int minor)
{
	device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{
	p_led_opr = opr;
}

EXPORT_SYMBOL(led_class_create_device);
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);



/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode *inode = file_inode(file);
	int minor = iminor(inode);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status控制LED */
	p_led_opr->ctl(minor, status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	p_led_opr->init(minor);
	
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
	int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */


	led_class = class_create(THIS_MODULE, "100ask_led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		return -1;
	}
	
	return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值