嵌入式linux驱动学习笔记04

Pinctrl子系统和GPIO子系统

回顾

今天还是继续点灯,做一名合格的一灯大师。
笔记03中,我用从设备树里获得了led灯对应的引脚的寄存器物理地址进行IO初始化。但这只是把物理寄存器的定义从驱动源码中移到了设备树里,驱动还是要自己实现IO的配置,我还是要熟悉芯片的IO相关的各个寄存器每个位的功能,这任然是一部分工作量,毕竟密密麻麻的寄存器文档可不是谁都喜欢看。因此linux内核加入pinctrl子系统,让内核替我们完成一部分“无聊”的工作。

pinctrl子系统

SOC基本上都有几十上百个pin(引脚),这些pin基本上都可以复用成各种不同的功能如GPIO、UART、SPI、IIC等。在单片机(裸机)开发中,都是由工程师来手动管理和配置这些复用功能,他得心里很清楚自己把哪个引脚设置成了什么功能。
pinctrl子系统就是用来帮工程师完成部分事情的。它的主要就干下边两件事情。
1:收集所有可一控制的pin的信息。进性分类、分组。很多外设都是由好多个pin组合起来完成功能的,比如IIC就至少是2个pin。
2:管理pin的分配,驱动程序在使用某个(组)pin时,要向pinctrl子系统申请。
3:配置pin的特性,包括复用,电气特性等。即完成基本的pin初始化。
至于到哪里搜集pin信息,到设备树里,因此设备树要按特定格式写好pin信息。
至于怎么完成pin分配,内核源码写好,目前水平有限,不纠结。
至于怎么设置pin的特性,芯片原厂会写好相应底层程序,随pinctrl子系统一起编译进内核。同时原厂会给出设备树的pin信息格式,照这写就行。

GPIO子系统

pinctrl子系统只是完成了pin的复用,并不进行进一步的控制。控制由后级的子系统完成。如GPIO子系统就是用来对复用成GPIO功能的pin进性控制的。这点就类似单片机的GPIO控制了。
常用函数:

// 测试gpio端口是否合法
 int gpio_is_valid(int number); 
// 申请gpio,如果被其他程序占用,会申请失败
 int gpio_request(unsigned gpio, const char *label)
// 设置gpio输入
 int gpio_direction_input(unsigned gpio); 
 //设置gpio输出
 int gpio_direction_output(unsigned gpio, int value); 
//读取gpio的值
 int gpio_get_value(unsigned gpio);
 //设置gpio的值
 void gpio_set_value(unsigned gpio, int value); 
// 设置当作中断口使用
 int gpio_to_irq(unsigned gpio); 

设备树pin信息节点添加

首先在iomuxc复用节点下添加一个节点pinctrl_ledgpio,
其中属性fsl,pins<MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 >,是原厂定义的复用信息格式,MX6UL_PAD_GPIO1_IO03__GPIO1_IO03是原厂定义好的宏,里边包含了把MX6UL_PAD_GPIO1_IO03这个pin配置成GPIO1_IO03的寄存器参数,0x10B0是电器属性,有我们用户配置。
在这里插入图片描述
在这里插入图片描述
然后在根节点下加入一个led节点,方式和笔记03里相同,只是把pinctrl的信息引入了,其中pinctrl-0属性就是引用了pinctrl_ledgpio。led-gpio属性是控制信息,表示led的IO口对应的是GPIO1_3,且led驱动低有效。
在这里插入图片描述
然后就是要找到设备树中其他用到了这个引脚的地方,去掉它们的pinctrl信息和控制信息,防止冲突。
在这里插入图片描述
在这里插入图片描述

驱动编写

驱动的主体思路和之前的一样,只是端口的复用部分已经由pinctrl子系统完成了,不需要再一个个操作寄存器了。端口的输出高低也由gpio子系统函数实现。

static int __init devled_init(void)
{
  int ret, resut = 0;

  devled.nod = of_find_node_by_path("/gpioled");//找到gpioledjiedian
  if (devled.nod == NULL)
  {
    resut = -EINVAL;
    printk("gpioled node can not found!\r\n");
    goto fail_node;
  }
  devled.led_gpio = of_get_named_gpio(devled.nod, "led-gpio", 0);//分配编号
  if (devled.led_gpio < 0)
  {
    resut = -EINVAL;
    printk("get led-gpio failed!\r\n");
    goto fail_node;
  }
  ret = gpio_request(devled.led_gpio, "led0");//申请GPIO
  if (ret < 0)
  {
    resut = -EINVAL;
    printk("gpio_request failed!\r\n");
    goto fail_node;
  }
  ret = gpio_direction_output(devled.led_gpio, 1);//设置输出
  if (ret < 0)
  {
    resut = -EINVAL;
    printk("set led_gpio failed!\r\n");
    goto fail_gpio_set;
  }

  devled.major = 0; //清0保证系统分配设备号

  if (devled.major)
  {
    devled.devid = MKDEV(devled.major, 0);                                 //获取设备号
    ret = register_chrdev_region(devled.devid, DEVICE_COUNT, DEVICE_NAME); //让系统设定分配设备号
  }
  /*
  ......
  */
  }
static int led_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos)
{
int ret;
int resut;
struct chardev_str *dev = (struct chardev_str *)filp->private_data;
uint8_t databuf[1];
ret = gpio_get_value(dev->led_gpio);//读取端口
if (ret < 0)
{
  printk("read gpio fialed");
  resut = -EFAULT;
  goto fail_get_gpio;
}
else
{
  databuf[0] = ret;
  printk("read_data= %d", databuf[0]);
}
ret = copy_to_user(buf, databuf, count);
if (ret < 0)
{
  printk("kernel_read_error!\r\n");
  resut = -EFAULT;
  goto fail_copy_to_user;
}
return 0;
fail_get_gpio:
fail_copy_to_user:
dev = NULL;
return resut;
}

static int led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret;
int resut;
struct chardev_str *dev = (struct chardev_str *)filp->private_data; //其实用不到私有数据
uint8_t databuf[1];
if (count > 1) //自允许写一字节数据。0 开 1关
{
  printk("user data error!\r\n");
  resut = -EFAULT;
  goto fail_datacheck;
}
ret = copy_from_user(databuf, buf, count); //复制buf里的数据
if (ret < 0)
{
  printk("get data error!\r\n");
  resut = -EFAULT;
  goto fail_copy_from_user;
}
if (databuf[0] != LED_ON) //关灯
{
  gpio_set_value(dev->led_gpio, LED_OFF);
}
else //开灯
{
  gpio_set_value(dev->led_gpio, LED_ON);
}
return 0;
fail_datacheck:

fail_copy_from_user:
dev = NULL;
return resut;
}

测试

同笔记02、03.
不过目前测读取gpio_get_value时,返回值恒为0。原因未知。

嵌入式linux驱动学习笔记系列是我自己一边学一边写的笔记,主要目的是自己学习一段后梳理巩固,不保证知识点的理解和方法使用的准确性。硬件基于原子IMX6U-MINI开发板。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值