打开芯片参考手册,找到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
总结: