I.MX6U嵌入式Linux驱动开发(5)pintcl和gpio子系统实验

打开芯片参考手册,找到iomux,Linux内核源码,打开imxull.dtsi,搜索iomux
有3个寄存器地址:IOMUXC SNVS寄存器、IOMUXC控制器、gpr控制器。
打开板子对应的设备树文件,imx6ull-alientek-emmc.dts,搜索iomux。

1、pintcl/gpio子系统原理

1.1、pintcl

如何找到IMX6UL对应的pinctrl子系统设备?通过compatible,此属性是字符串列表。驱动文件里面有一个描述驱动兼容性的东西,当设备树节点的compatible属性和驱动里面的兼容性字符串匹配时,也就是一摸一样的时候就表示设备和驱动匹配了。所以只需要全局搜索,设备节点里面的compatible属性的值,看看在哪个.c我呢见里面有,那么此.c文件就是驱动文件。找到pinctrl-imx6ull.c文件,此文件就是6ULL/6UL的pinctrl驱动文件。

1.2、gpio子系统

如何从设备树中获取要使用的GPIO信息。of函数。
驱动中对gpio的操作函数:
(1)首先呢,获取到GPIO所处的设备节点。比如:of_find_node_by_path。
(2)获取GPIO编号,of_get_named_gpio函数,返回值就是GPIO编号。
(3)请求此编号的GPIO,gpio_request函数。
(4)设置GPIO,输入或者输出,gpio_direction_input/output。
(5)如果是输入,那么通过gpio_get_value函数读取GPIO值。如果是输出,通过gpio_set_value设置GPIO值。

应用层API:gpio_request();
链接层:gpiolib,在linux内核中的GPIO核心;
底层:原厂,比如6ull、stm,在这里是gpio_mxc.c(NXP)

2、实验

Ubuntu下:

cd /limux/IMX6ULL/Linux_Drivers/
mkdir 6_gpioled
cp 5_dtsled/ * 6_gpioled/
cp 5_dtsled/.vscode 6_gpioled/ -r
rm dtsled.code-workspace
rm dtsled.c

打开vscode,新建一个gpioled.c文件。将上一节的头文件拷贝过来。

2.1、修改设备树

打开linux源码,在imx6ull-alientek-emmc.dts文件。在根节点的最下面添加一个子节点。参考其他外设(使用引脚的)来添加。

alphaled{
}
gpioled{
	compatible = "alientek, gpioled";
	pinctrl-name = "default";
	pinctrl-0 = <&pinctrl_gpioled>;
	led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;//gpio子系统
	status = "okay";
};

在305行,&iomuxc{}中的318行pinctrl_csil:csilgrp{}前面添加(创建一个节点):
在imx6ull-pinfun.h文件中找到GPIO1_IO03复用为gpio功能。

pinctrl_gpioled: ledgrp{
	fsl, pins = <
		MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0
	>;
};

编译设备树,用新编译的设备树启动我们的系统:

make dtbs
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /home/yang/linux/tftpboot/ -f

启动开发板,查看创建的节点(gpioled)存在不?

/ #
/ # cd /proc/device-tree/
/proc/device-tree/ # ls
/sys/firmwave/devicetree/base # cd gpioled/
/sys/firmwave/devicetree/base/gpioled/ # ls

2.2、编写驱动框架

将Makefile中的.o文件改为gpioled.o

/* 驱动入口函数 */
static int __init led_init(void)
{
	return 0;
}
/* 驱动出口函数 */
static void __exit led_exit(void)
{

}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");

make编译一下,检车有没有错误;
要注册,就要创建设备结构体。
注册设备号;
初始化cdev;在结构体里面添加一个cdev,使用cdev_init(),这个函数的第二个参数是一个操作集,因此要定义一个操作集,
添加一个cdev_add();这个函数有返回值。
退出的时候要注销字符设备驱动,释放申请的设备号;

#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
/* gpioled设备结构体 */
struct gpioled_dev{
	dev_t devid;
	int major;
	int minor;
	struct cdev cdev;
	
};
struct gpioled_dev gpioled;/* LED */

/* 操作集 */
static const struct file_operations led_fops = {
	.owner = THIS_MODULE,
	
};

/* 驱动入口函数 */
static int __init led_init(void)
{
	/* 1 注册字符设备驱动 */
	gpioled.major = 0;//不给定,说明由系统分配
	if(gpioled.major){//如果为真,说明这个设备号是我们给定的
		gpioled.devid = MKDEV(gpioled.major, 0);
		register_chardev_region(gpioled.devid, GPIOLED_CNT,GPIOLED_NAME);
	} else {//没给定设备号
		alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
		gpioled.major = MAJOR(gpioled.devid);
		gpioled.minor = MINOR(gpioled.devid);
	}
	printk("gpioled major = %d, mimor = %d\r\n", gpioled.major, gpioled.minor);
	
	/* 2、初始化cdev */
	gpioled.cdev.owner = THIS_MOUDLE;
	cdev_init(&gpioled.cdev, &led_fogs);
	/* 3、添加cdev */
	cdev_add(&gpioled,cdev, gpioled.devid, GPIOLED_CNT);
	
	return 0;
}
/* 驱动出口函数 */
static void __exit led_exit(void)
{
	/* 注销字符设备驱动 */
	cdev_del(&gpioled.cdev);
	unregister_chardev_region(gpioled.devid, GPIOLED_CNT);
}

编译验证;

make
sudo cp gpioled.ko /home/yang/linux/nfs/rootfs/lib/modules/4.1.15 -f

开发板上电加载一下:

/lib/modules/4.1.15 # ls
/lib/modules/4.1.15 # depmod
/lib/modules/4.1.15 # modprobe gpioled.ko
/lib/modules/4.1.15 # lsmod
/lib/modules/4.1.15 # rmmod gpioled.ko

创建类,在gpioled设备结构体中添加类和设备,退出的时候把类和设备摧毁掉。

struct gpioled_dev{
	.....
	struct class *class;
    struct device *device;
};

/* 4、创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)){
	return PTR_ERR(gpioled.class);
}
/* 5、创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){
	return PTR_ERR(gpioled.device);
}


device_destory(gpioled.class, gpioled.devid);
class_destory(gpioled.class);

编译一下,

下面补全操作集的函数:

static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &gpioled;
	return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{	
	return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
	
}
/* 操作集 */
static const struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.write = led_write,
	.open = led_open,
	.release = led_release,
};

到此,设备驱动框架已经添加完成。
在gpioled设备结构体中,添加一个成员变量
struct deice_node *nd;

	/* 6、获取设备节点 */
	gpioled.nd = of_find_node_by_path("/gpioled");//路径就是gpioled设备树
	if(gpioled.nd == NULL){
		ret = -EINVAL;
		goto fail_findnode;
	}
	
	/* 7、获取LED所对应的GPIO */
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led.gpios", 0); //gpio子系统
	if(gpioled.led_gpio < 0){
		printk("can't find led gpio\r\n");
		ret = -EINVAL;
		goto fail_findnode;
	}
	printk("led gpio num = %d\r\n", gpioled.led_gpio);
	
	/* 8、申请IO */
	ret = gpio_request(gpioled.led_gpio, "led.gpio");
	if(ret){
		prink("failed to request the led gpio\r\n");
		ret = -EINVAL;
		goto fail_findnode;
	}
	/* 9、使用IO,设置为输出 (高电平、不点亮)*/
	ret = gpio_direction_output(gpioled.led_gpio, 1);
	if(ret){
		goto fail_setoutput;//这个地方失败,但前面成功了,所以要单独处理一下。
	}
	
	/* 10、输出低电平,点亮LED灯 */
	gpio_set_value(gpioled.led_gpio, 0);
	
	return 0;
fail_setoutput:
	gpio_free(gpioled.led_gpio);
fail_findnode:
	return ret;


//释放IO
static void __exit led_exit(void)
{
	...
	gpio_free(gpioled.led_gpio);
}

添加头文件

#include <linux/gpio.h>
#include <linux/of_gpio.h>

编译一下,直接加载驱动:

make
sudo cp gpioled.ko /home/yang/linux/nfs/rootfs/lib/modules/4.1.15 -f
/lib/modules/4.1.15 # lsmod
/lib/modules/4.1.15 # 

出现申请失败,说明写的驱动有问题。申请IO的时候失败,大部分的原因是这个IO被其他外设占用了。怎么判断被占用了,要看设备树,查找有哪些使用IO的,
(1)搜索”GPIO1_IO03“发现tsc(触摸屏)也使用了这个,把这一行给屏蔽掉。
(2)检查GPIO1 3,搜索,发现tsc这个节点下也有,把这一行给屏蔽掉。
//xnur.gpio = <&gpio1 3 GPIO_AVTIVE_LOW>;

重新编译设备树:

make dtbs
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /home/yang/linux/tftpboot/ -f

重新启动,加载设备树:

/lib/modules/4.1.15 # modprobe gpioled.ko //灯亮
/lib/modules/4.1.15 # rmmod gpioled.ko //卸载灯不灭,因为我们没有在退出的时候关灯

可以在驱动里面关灯,一般是先关灯再销毁

static void __exit led_exit(void)
{
	gpio_set_value(gpioled.led_gpio, 1);
}

2.3、完善驱动框架

在操作集的函数里面控制灯

#define LEDON 1
#define LEDOFF 0

static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
	int ret;
	unsigned char databuf[1];
	struct gpioled_dev *dev = filp->private_data;
	
	ret = copy_from_user(databuf, buf, count);
	if(ret < 0){
		return -EINVAL;
	}
	if(databuf[0] == LEDON){
		gpio_set_value(dev->led_gpio, 0);
	}else if(databuf[0] == LEDOFF){
		gpio_set_value(dev->led_gpio, 1);
	}
	return 0;
}

现在我们就可以用应用程序ledAPP来控制开关灯。

/lib/modules/4.1.15 # ls
/lib/modules/4.1.15 # ./ledAPP /dev/gpioled 1
/lib/modules/4.1.15 # ./ledAPP /dev/gpioled 0

总结:

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值