08-pinctrl和gpio子系统

pinctrl 和 gpio 子系统

Linux 驱动讲究驱动分离与分层,pinctrl 和 gpio 子系统就是驱动分离与分层思想下的产物。
对于 GPIO 的初始化我们可以大致分为两步,首先获取 GPIO 相应的寄存器,配置 GPIO 的复用功能,上下拉等,然后设置 GPIO 默认的电平状态。对于 linux 这种成熟的系统来说,像操作 GPIO 这种常规功能,我们不需要像开发裸机一样,去查找寄存器手册,定义寄存器地址,然后再去配置 GPIO,实现 GPIO 的读写操作,系统已经为相应的芯片写好了驱动代码,我们只需要在设备树中配置相应的引脚,即可实现对 GPIO 的操作

pinctrl 子系统

pinctrl 子系统工作内容

  1. 获取设备树中 pin 信息。
  2. 根据获取到的 pin 信息来设置 pin 的复用功能
  3. 根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等

pin 配置信息详解

要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,一般会在设备树里面创建一个节点来描述 PIN 的配置信
息,示例代码如下:

    am33xx_pinmux: pinmux@800 {
        compatible = "pinctrl-single";
        reg = <0x800 0x238>;
        #pinctrl-cells = <2>;
        pinctrl-single,register-width = <32>;
        pinctrl-single,function-mask = <0x7f>;
    };

由于不同的外设使用的 PIN 不同,其配置也不同,因此我们需要在此节点的基础上添加子节点,根据子节点的需求,在其中定义相应的 PIN 配置,如下:

&am33xx_pinmux {
    mmc1_pins: pinmux_mmc1_pins {
        pinctrl-single,pins = <
            AM33XX_PADCONF(AM335X_PIN_SPI0_CS1, PIN_INPUT, MUX_MODE7)        /* spio0_cs1.gpio0_6 */
            AM33XX_PADCONF(AM335X_PIN_MMC0_DAT0, PIN_INPUT_PULLUP, MUX_MODE0)
            AM33XX_PADCONF(AM335X_PIN_MMC0_DAT1, PIN_INPUT_PULLUP, MUX_MODE0)
            AM33XX_PADCONF(AM335X_PIN_MMC0_DAT2, PIN_INPUT_PULLUP, MUX_MODE0)
            AM33XX_PADCONF(AM335X_PIN_MMC0_DAT3, PIN_INPUT_PULLUP, MUX_MODE0)
            AM33XX_PADCONF(AM335X_PIN_MMC0_CMD, PIN_INPUT_PULLUP, MUX_MODE0)
            AM33XX_PADCONF(AM335X_PIN_MMC0_CLK, PIN_INPUT_PULLUP, MUX_MODE0)
        >;
    };

};

向 pinctrl 节点am33xx_pinmux追加子节点 pinmux_mmc1_pins,然后在其中定义 mmc1 所需要的引脚及其复用情况

GPIO 子系统

如果我们把 PIN 引脚复用功能设置为 GPIO 后,我们接下来需要设置 GPIO 的默认电平状态,以及在应用中去操作 GPIO,比如设置 GPIO 输入输出状态,查询 GPIO 电平状态等等,GPIO 子系统为我们提供了一系列的 API,可以让我们快速的操作 GPIO 而不用去考虑底层实现。

gpio 子系统工作内容

  1. 初始化 GPIO 口默认电平状态
  2. 提供操作 GPIO 的相关 API 函数

GPIO 配置信息详解

mmc1: mmc@0 {
    compatible = "ti,am335-sdhci";
    ti,needs-special-reset;
    dmas = <&edma_xbar 24 0 0
        &edma_xbar 25 0 0>;
    dma-names = "tx", "rx";
    interrupts = <64>;
    reg = <0x0 0x1000>;
    status = "disabled";
};

&mmc1 {
    status = "okay";
    bus-width = <0x4>;
    pinctrl-names = "default";
    pinctrl-0 = <&mmc1_pins>;
    cd-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
};

当我们设置好 PIN 的 GPIO 属性后,我们就需要在我们对应的功能节点(本例中的 mmc1)下设置 GPIO 的属性,我们重点关注pinctrl-0 = <&mmc1_pins>cd-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>
pinctrl-0 = <&mmc1_pins>:引用之前定义的 mmc1_pins(实际上 mmc1_pins 中定义的引脚会直接在 pinctrl 子系统中完成初始化,此处的引用应该有其它用途)
cd-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>:设置 GPIO0_6 引脚默认输出低电平(AM335X_PIN_SPI0_CS1 引脚)

LED驱动例程

修改设备树

am33xx_pinmux追加子节点,配置PIN属性

&am33xx_pinmux {
	user_leds_s0: user_leds_s0 {
		pinctrl-single,pins = <
			AM33XX_PADCONF(AM335X_PIN_GPMC_A5, PIN_OUTPUT_PULLDOWN, MUX_MODE7)	/* gpmc_a5.gpio1_21 */
		>;
	};
};

向根节点中添加gpioled节点,配置GPIO默认电平状态

	gpioled{
		pinctrl-names = "default";
		compatible = "user-gpioled0";
		pinctrl-0 = <&user_leds_s0>; //引用user_leds_s0节点
		led-gpio = <&gpio1 21 GPIO_ACTIVE_HIGH>; //配置GPIO默认电平状态
		linux,default-trigger = "none";
		default-state = "off";
	};

编写LED驱动

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名字 */

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *virtual_gpio1_oe_addr;
static void __iomem *virtual_gpio1_dataout_addr;

struct gpioled_dev {
	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 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->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 databuf[1];
	unsigned char ledstat;
	struct gpioled_dev *dev = filp->private_data;

	retvalue = copy_from_user(
		databuf, buf,
		cnt); //必须从用户空间(buf)复制块数据到内核空间(databuf),因为用户空间的地址可能是虚拟地址,不能长久保存
	if (retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0]; /* 获取状态值 */

	if (ledstat == LEDON) {
		gpio_set_value(dev->led_gpio, 1); /* 打开LED灯 */
	} else if (ledstat == LEDOFF) {
		gpio_set_value(dev->led_gpio, 0); /* 关闭LED灯 */
	}
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 设备操作函数 */
static struct file_operations dtsled_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、获取设备节点:alphaled */
	gpioled.nd = of_find_node_by_path("/gpioled");
	if (gpioled.nd == NULL) {
		printk("gpioled node not find!\r\n");
		return -EINVAL;
	} else {
		printk("gpioled node find!\r\n");
	}

	/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
	gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-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_IO21为输出,并且输出低电平,默认关闭LED灯 */
	ret = gpio_direction_output(gpioled.led_gpio, 0);
	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, 1, "gpioled");
	} else { /* 没有定义设备号 */
		alloc_chrdev_region(&gpioled.devid, 0, 1,
				    "gpioled"); /* 申请设备号 */
		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, &dtsled_fops);

	/* 3、添加一个cdev */
	cdev_add(&gpioled.cdev, gpioled.devid, 1);

	/* 4、创建类 */
	gpioled.class = class_create(THIS_MODULE, "gpioled");
	if (IS_ERR(gpioled.class)) {
		return PTR_ERR(gpioled.class);
	}

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

	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
	/* 取消映射 */
	iounmap(virtual_gpio1_oe_addr);
	iounmap(virtual_gpio1_dataout_addr);

	/* 注销字符设备驱动 */
	cdev_del(&gpioled.cdev); /*  删除cdev */
	unregister_chrdev_region(gpioled.devid, 1); /* 注销设备号 */

	device_destroy(gpioled.class, gpioled.devid);
	class_destroy(gpioled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxxxxxxx");

编写测试APP

同章节4

验证

同章节4

你可能感兴趣的:(beaglebone,black,linux,驱动开发,学习)