pinctrl子系統和gpio子系統
pinctrl子系統和gpio子系統作用
Linux 驅動講究驅動分離與分層,pinctrl 和 gpio 子系統就是驅動分離與分層思想下的產物,將驅動與底層IO電氣屬性配置分開。
pinctrl子系統
- 無pinctrl子系統時設備初始化流程
1.修改設備樹,添加節點,設置reg屬性,主要爲相關寄存器。
2.獲取reg屬性寄存器,並配置寄存器設置IO功能。
3.獲取reg寄存器,配置IO輸入輸出。初始化狀態。 - pinctrl子系統設備初始化流程
1.獲取設備樹中的pin信息。
2.根據pin信息設置pin的複用功能
3.根據獲取的pin信息設置pin電氣疼。
對於使用者來說,只需要在設備樹中定義好某個pin相關屬性即可,其他的初始化工作均由pinctrl子系統完成。
PIN配置信息詳解
使用pinctrl子系統時我們需要修改的只有pin配置信息。例如:
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
pinctrl-names = "default";
imx6ul-evk {
pinctrl_gpmi_nand: gpmi-nand {
fsl,pins = <
MX6UL_PAD_NAND_CLE__RAWNAND_CLE 0xb0b1
MX6UL_PAD_NAND_ALE__RAWNAND_ALE 0xb0b1
MX6UL_PAD_NAND_WP_B__RAWNAND_WP_B 0xb0b1
MX6UL_PAD_NAND_READY_B__RAWNAND_READY_B 0xb000
MX6UL_PAD_NAND_CE0_B__RAWNAND_CE0_B 0xb0b1
MX6UL_PAD_NAND_CE1_B__RAWNAND_CE1_B 0xb0b1
MX6UL_PAD_NAND_RE_B__RAWNAND_RE_B 0xb0b1
MX6UL_PAD_NAND_WE_B__RAWNAND_WE_B 0xb0b1
MX6UL_PAD_NAND_DATA00__RAWNAND_DATA00 0xb0b1
MX6UL_PAD_NAND_DATA01__RAWNAND_DATA01 0xb0b1
MX6UL_PAD_NAND_DATA02__RAWNAND_DATA02 0xb0b1
MX6UL_PAD_NAND_DATA03__RAWNAND_DATA03 0xb0b1
MX6UL_PAD_NAND_DATA04__RAWNAND_DATA04 0xb0b1
MX6UL_PAD_NAND_DATA05__RAWNAND_DATA05 0xb0b1
MX6UL_PAD_NAND_DATA06__RAWNAND_DATA06 0xb0b1
MX6UL_PAD_NAND_DATA07__RAWNAND_DATA07 0xb0b1
>;
};
.........
}
}
這裏我們把一個模塊所用到的所有GPIO配置組織到一起。如果需要在iomuxc中添加自定義外設的pin,需要新建子節點,然後將自定義外設的所有pin配置信息都放到這個子節點中。
端口配置信息如下:
MX6UL_PAD_NAND_CLE__RAWNAND_CLE 0xb0b1
其中MX6UL_PAD_NAND_CLE__RAWNAND_CLE 是一個宏定義,作用是配置端口爲RAWNAND_CLE 功能。在 imx6ul-pinfunc.h文件中定義如下:
#define MX6UL_PAD_NAND_CLE__RAWNAND_CLE 0x01B4 0x0440 0x0000 0x0 0x0
尾部五個參數含義是
<mux_reg conf_reg input_reg mux_mode input_val>
- mux_reg: 複用寄存器地址偏移量
- conf_reg: conf_reg 寄存器偏移地址偏移量
- input_reg: input_reg 寄存器偏移地址
- mux_mode: 複用功能選擇寄存器
- input_val:input_reg寄存器值,這裏無效。
0xb0b1 爲conf_reg寄存器的值,有客戶定義,用於配置端口的電氣屬性。
設備樹添加pinctrl節點
我們用MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 端口做演示。
- 創建對應的節點
同一個外設用到的pin都放在一個節點下,打開 imx6ull-14x14-evk.dts 文件,在iomuxc下的imx6ul-evk 節點中添加如下信息:
pinctrl_light: lightgrp {
};
- 添加 fsl,pins 屬性
pinctrl_light: lightgrp {
fsl,pins = <
>;
- 在 fsl,pins 屬性下添加pin配置信息
pinctrl_light: lightgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x1b0b0
>;
至此已經完成向設備樹中添加pin配置信息。
GPIO子系統
GPIO子系統用於初始化GPIO,提供GPIO操作API函數,如:設置GPIO輸入輸出,讀取GPIO值等。驅動開發者在設備樹中添加gpio相關信息,就可以在驅動程序中使用gpio子系統提供的API操作GPIO。不需要開發者自己再編寫GPIO操作函數。
向設備樹中添加GPIO信息
- 向設備樹中添加設備節點
light {
};
- 添加pinctrl信息
light {
#address-cells = <1>;
#size-cells = <1>;
compatible = "gpio-light";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_light>; //表示設備所使用的pin信息保存在pinctrl_light中。
};
- 添加GPIO信息
light {
#address-cells = <1>;
#size-cells = <1>;
compatible = "gpio-light";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_light>;
light-gpio = <&gpio1 2 GPIO_ACTIVE_LOW>; //要用的到gpio配置信息,gpio1_2,默認低電平
status = "okay";
};
至此,設備樹配置信息添加完畢,接下來編寫驅動程序。
驅動開發
GPIO相關的of函數
-
int of_gpio_named_count(struct device_node *np, const char *propname)
返回統計到的GPIO數量。 -
int of_gpio_count(struct device_node *np)
返回統計到的GPIO數量,指定屬性的GPIO。
gpioled.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.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define GPIOLED_CNT 1 //設備號個數
#define GPIOLED_NAME "gpioled"
#define LEDOFF 0 //關燈
#define LEDON 1 //開燈
//gpioled設備結構體
struct gpioled_dev
{
/* data */
dev_t devid; //設備號
struct cdev cdev; //cdev
struct class *class; //類
struct device *device; //設備
int major; //主設備號
int minor; //次設備號
struct device_node *nd; //設備節點
int led_gpio; //led使用的GPIO編號
};
struct gpioled_dev gpioled;//led設備
/*
@description : 打開設備
@param - inode : 傳遞給驅動的inode
@param - filp : 設備文件
@return : 0 成功; 其他 失敗
*/
static int led_open(struct inode *inode, struct file *file)
{
file->private_data = &gpioled; //設置私有數據
return 0;
}
/*
@description : 從設備讀取數據
@param - filp : 要打開的設備文件
@param - buf : 返回給用戶控件的數據緩衝區
@param - cnt : 要讀取的數據長度
@param - offt : 相對於文件首地址的偏移
@return : 讀取的字節數,若爲負值,讀取失敗
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt,loff_t *offt)
{
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 datebuf[1];
unsigned char ledstat;
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(datebuf, buf, cnt);
if(retvalue < 0)
{
printk("kernel write failed!!\r\n");
return -EFAULT;
}
ledstat = datebuf[0];
if(ledstat == LEDON)
{
gpio_set_value(dev->led_gpio,0);//打開led燈
}
else if (ledstat == LEDOFF)
{
gpio_set_value(dev->led_gpio,1);//關閉led燈
}
return 0;
}
/*
@description : 關閉/釋放設備
@param - filp : 要關閉的設備文件
@return : 0 成功; 其他 失敗
*/
static int led_release(struct inode *inode, struct file *file)
{
return 0;
}
/* 設備操作函數集合 */
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
@description : 驅動入口函數
@param - : 無
@return : 無
*/
static int __init led_init(void)
{
int ret;
/* 獲取設備樹中的屬性數據 */
/* 1.獲取設備節點:light */
gpioled.nd = of_find_node_by_path("/light");
if(gpioled.nd == NULL){
printk("lightled node can not found!\r\n");
return -EINVAL;
}
else
{
printk("lightled node has been found!\r\n");
}
/* 2.獲取gpio屬性內容,得到led使用的gpio編號 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"light-gpio",0);
if(gpioled.led_gpio<0)
{
printk("can't get led-gpio.");
return -EINVAL;
}
printk("led-gpio num = %d\r\n",gpioled.led_gpio);
/* 3.設置GPIO1_IO3爲輸出,並輸出高電平,默認關閉led燈 */
ret = gpio_direction_output(gpioled.led_gpio,1);
if(ret < 0)
{
printk("can't set gpio!\r\n");
}
//註冊字符設備驅動
//1.創建設備號
if(gpioled.major)
{
gpioled.devid = MKDEV(gpioled.major,0);
register_chrdev_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,minor=%d\r\n",gpioled.major,gpioled.minor);
//2.初始化cdev
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev,&gpioled_fops);
//3.添加cdev
cdev_add(&gpioled.cdev,gpioled.devid,GPIOLED_CNT);
//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);
}
return 0;
}
static void __exit led_exit(void)
{
//註銷字符驅動
cdev_del(&gpioled.cdev);//刪除cdev
unregister_chrdev_region(gpioled.devid,GPIOLED_CNT);
device_destroy(gpioled.class,gpioled.devid);
class_destroy(gpioled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");
dtbs
設備樹節點:
pinctrl節點:
};
設備節點:
light {
#address-cells = <1>;
#size-cells = <1>;
compatible = "gpio-light";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_light>;
light-gpio = <&gpio1 2 GPIO_ACTIVE_LOW>;
status = "okay";
};