linux pinctrl 和 gpio 子系统 LED驱动

pinctrl 和 gpio 子系统

借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发

pinctrl 子系统

pinctrl 子系统(drivers/pinctrl)的主要工作内容:

①、获取设备树中 pin 信息。

②、根据获取到的 pin 信息来设置 pin 的复用功能

③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。

I.MX6ULL pinctrl 子系统驱动

在设备树里面创建一个节点来描述 PIN 的配置信息。在 imx6ull.dts 中有 iomux 节点

iomuxc: iomuxc@020e0000 {
    compatible = "fsl,imx6ul-iomuxc";
    reg = <0x020e0000 0x4000>;  /* 起始地址 0x020e0000 长度 0x4000 */
};

iomuxc 节点就是 I.MX6ULLIOMUXC 外设对应的节点。在你使用的imx6ull-14*14-evk.dts文件中还有其他内容:

&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
    /*将某个外设所使用的所有 PIN 都组织在一个子节点里面*/
	imx6ul-evk {
		pinctrl_hog_1: hoggrp-1 { /* 和热插拔有关的 PIN 集合, */
			fsl,pins = <
				MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	0x17059 /* SD1 CD */
				MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT	0x17059 /* SD1 VSELECT */
				MX6UL_PAD_GPIO1_IO09__GPIO1_IO09        0x17059 /* SD1 RESET */
			>;
		};
		.........
		pinctrl_flexcan1: flexcan1grp{  /* flexcan1 这个外设所使用的 PIN */
			fsl,pins = <
				MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX	0x1b020
				MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX	0x1b020
			>;
		};
		.........
		pinctrl_wdog: wdoggrp {
			fsl,pins = <
				MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY    0x30b0
			>;
		};
	};
};

注意点

IO 复用功能选择器(IOMUXC)的寄存器非常多,主要可以分为四组:
① IOMUXC_GPR 寄存器组,用于通用控制设置。
② IOMUXC_SNVS 组,主要用于GPIO5 的控制。
③ IOMUXC_SNVS_GPR 寄存器组
④ IOMUXC 组,用于指定IO 的复用功能选择和IO 属性设置。
我们一般使用的主要是②和④。

分析

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19	0x17059 /* SD1 CD */
/*UART1_RTS_B 这个 PIN 是作为 SD 卡的检测引脚,也就是通过此 PIN 就可以检测到 SD 卡是否有插入*/
/* MX6UL_PAD_UART1_RTS_B__GPIO1_IO19是一个宏定义,imx6ull.dtsi引用imx6ull-pinfunc.h,而imx6ull-pinfunc.h引用imx6ul-pinfunc.h这个头文件。这个宏定义就定义在imx6ul-infunc.h中。*/
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19              0x0090 0x031C 0x0000 0x5 0x0
/* 具体含义是 <mux_reg conf_reg input_reg mux_mode input_val> */
 
- mux_reg:IOMUX 外设寄存器起始地址 0x020e0000 + mux_reg 0x0090 = 0x020e0090 
    和参考手册中的IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 地址一致。该地址也就是 PIN 的复用寄存器地址
- conf_reg:IOMUX 外设寄存器起始地址 0x020e0000 + conf_reg 0x031C = 0x020e031c 
    也就是 IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的地址。该地址也就是 PIN 的电气属性寄存器地址
    其中他对应的值就是 宏后面传入的值:
- input_reg: 有 input_reg 寄存器的外设需要配置 input_reg 寄存器。没有的话就不需要设置
- mux_mode:mux_reg 寄存器的值 0x17059
- input_val:input_reg 寄存器的值

在这里插入图片描述

在这里插入图片描述

设备树中添加 pinctrl 节点模板

参考文件Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt

/* 在 imx6ull-14x14-evk.dts 中的 iomuxc 节点下 imx6ul-evk 节点添加自己的节点 */
pinctrl_test:my_led {
  	fsl,pins = <
      	MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config  /* 其中 config 是具体的设定值 */
    >;
};

gpio 子系统

设备树中的 GPIO 信息

上面的 pinctrl 子系统主要就是设置 PIN(PAD) 的复用以及电气属性的。gpio 子系统主要就是初始化 GPIO 并且提供相应的 API 函数。设置 GPIO为输入输出,读取 GPIO 的值等。下面是SD卡设备节点:

// usdhc1 节点作为 SD 卡设备总节点,usdhc1 节点需要描述 SD 卡所有的信息
&usdhc1 {
	pinctrl-names = "default", "state_100mhz", "state_200mhz";
	pinctrl-0 = <&pinctrl_usdhc1>;
	pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
	pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
    // 这里没有写 pinctrl-3 = <&pinctrl_hog_1> 
    // 是因为iomux节点下面引用了这么一个节点 所以Linux 内核中的 iomuxc 驱动
    // 就会自动初始化 pinctrl_hog_1节点下的所有 PIN
    
    // 设置SD卡的CD引脚,也就是用于检测SD卡是否存在,
    // 连接在gpio1_io19,GPIO_ACTIVE_LOW表示低电平有效、GPIO_ACTIVE_HIGH 高电平有效
	cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
	keep-power-in-suspend;
	enable-sdio-wakeup;
	vmmc-supply = <&reg_sd1_vmmc>;
	status = "okay";
};

imx6ull.dts中有相应内容,相关查看Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt

gpio1: gpio@0209c000 {
    // 对应用于查找GPIO的驱动程序
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    // 0x0209c000代表寄存器基地址,长度为0x4000
    reg = <0x0209c000 0x4000>;
    interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
    <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
    // 说明gpio1 节点是个 GPIO 控制器
    gpio-controller;
    // #gpio-cells 为 2,表示一共有两个cell,
    // 第一个 cell 为 GPIO 编号,比如“&gpio1 3”就表示 GPIO1_IO03。
    // 第二个 cell 表示GPIO 极 性,如果为 0(GPIO_ACTIVE_HIGH) 的话表示高电平有效
    // 如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

GPIO 驱动程序

在内核中搜索fsl,imx35-gpio,就可以找到内核的GPIO驱动程序drivers/gpio/gpio-mxc.c

static struct platform_device_id mxc_gpio_devtype[] = {
	{
		.name = "imx1-gpio",
		.driver_data = IMX1_GPIO,
	}, {
		.name = "imx21-gpio",
		.driver_data = IMX21_GPIO,
	}, {
		.name = "imx31-gpio",
		.driver_data = IMX31_GPIO,
	}, {
        // 对应找到了 gpio1-19使用的gpio驱动
		.name = "imx35-gpio",
		.driver_data = IMX35_GPIO,
	}, {
		/* sentinel */
	}
};
static const struct of_device_id mxc_gpio_dt_ids[] = {
	{ .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
	{ .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
	{ .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
    // 对应找到了 gpio1-19使用的gpio驱动
	{ .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
	{ /* sentinel */ }
};
// 说明GPIO驱动也是属于平台设备驱动的,当设备树中的设备节点与驱动的
// of_device_id 匹配以后 probe 函数就会执行,也就是mxc_gpio_probe
static struct platform_driver mxc_gpio_driver = {
	.driver		= {
		.name	= "gpio-mxc",
		.of_match_table = mxc_gpio_dt_ids,
	},
    // of_device_id 对应后执行的函数 mxc_gpio_probe
	.probe		= mxc_gpio_probe,
    // 和上面的of_device_id 相对应起来
	.id_table	= mxc_gpio_devtype,
};

mxc_gpio_probe 函数的内容

static int mxc_gpio_probe(struct platform_device *pdev)
{
    // 设备树节点指针
	struct device_node *np = pdev->dev.of_node;
	struct mxc_gpio_port *port;/*mxc_gpio_port就是对IMX6ULL_GPIO的抽象*/
    /*
        struct mxc_gpio_port {
            struct list_head node;
            void __iomem *base;
            int irq;
            int irq_high;
            struct irq_domain *domain;
            struct bgpio_chip bgc;
            u32 both_edges;
        };
    */
	struct resource *iores;
	int irq_base;
	int err;

	mxc_gpio_get_hw(pdev);/*获取GPIO硬件相关信息 即gpio的寄存器组*/
    /*  IMX35_GPIO使用的寄存器组
    	static struct mxc_gpio_hwdata *mxc_gpio_hwdata; // 定义的全局变量
    	static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
            .dr_reg		= 0x00,
            .gdir_reg	= 0x04,
            .psr_reg	= 0x08,
            .icr1_reg	= 0x0c,
            .icr2_reg	= 0x10,
            .imr_reg	= 0x14,
            .isr_reg	= 0x18,
            .edge_sel_reg	= 0x1c,
            .low_level	= 0x00,
            .high_level	= 0x01,
            .rise_edge	= 0x02,
            .fall_edge	= 0x03,
		};
    */
	port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
	if (!port)
		return -ENOMEM;
	/*获取设备树中内存资源信息,也就是 reg 属性值*/
	iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    /*进行地址映射*/
	port->base = devm_ioremap_resource(&pdev->dev, iores);
	if (IS_ERR(port->base))
		return PTR_ERR(port->base);
	/*获取高16位中断号*/
	port->irq_high = platform_get_irq(pdev, 1);
    /*获取低16位中断号*/
	port->irq = platform_get_irq(pdev, 0);
	if (port->irq < 0)
		return port->irq;

	/* disable the interrupt and clear the status */
    /* 操作 GPIO1 的 IMR 和 ISR 这两个寄存器,
    	关闭 GPIO1所有IO中断,并且清除状态寄存器 */
	writel(0, port->base + GPIO_IMR);
	writel(~0, port->base + GPIO_ISR);

	if (mxc_gpio_hwtype == IMX21_GPIO) {
		/*
		 * Setup one handler for all GPIO interrupts. Actually setting
		 * the handler is needed only once, but doing it for every port
		 * is more robust and easier.
		 */
		irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
	} else {
		/* setup one handler for each entry */
        /* 设置对应 GPIO 的中断服务函数,不管是高 16 位还是低 16 位,
        	中断服务函数都是mx3_gpio_irq_handler */
		irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
		irq_set_handler_data(port->irq, port);
		if (port->irq_high > 0) {
			/* setup handler for GPIO 16 to 31 */
			irq_set_chained_handler(port->irq_high,
						mx3_gpio_irq_handler);
			irq_set_handler_data(port->irq_high, port);
		}
	}
	/* 初始化 gpio_chip */
	err = bgpio_init(&port->bgc, &pdev->dev, 4,
			 port->base + GPIO_PSR,
			 port->base + GPIO_DR, NULL,
			 port->base + GPIO_GDIR, NULL, 0);
	if (err)
		goto out_bgio;

	port->bgc.gc.to_irq = mxc_gpio_to_irq;
	port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
					     pdev->id * 32;
	
    /* 向Linux内核注册 gpio_chip,也就是 port->bgc.gc 
    	注册完成以后我们就可以在驱动中使用 gpiolib.c 提供的各个 API 函数 */
	err = gpiochip_add(&port->bgc.gc);
	if (err)
		goto out_bgpio_remove;

	irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
	if (irq_base < 0) {
		err = irq_base;
		goto out_gpiochip_remove;
	}

	port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
					     &irq_domain_simple_ops, NULL);
	if (!port->domain) {
		err = -ENODEV;
		goto out_irqdesc_free;
	}

	/* gpio-mxc can be a generic irq chip */
	mxc_gpio_init_gc(port, irq_base);

	list_add_tail(&port->node, &mxc_gpio_ports);

	return 0;

out_irqdesc_free:
	irq_free_descs(irq_base, 32);
out_gpiochip_remove:
	gpiochip_remove(&port->bgc.gc);
out_bgpio_remove:
	bgpio_remove(&port->bgc);
out_bgio:
	dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err);
	return err;
}

GPIO 子系统的 API 函数

  • gpio_request 函数
// 用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request 进行申请
int gpio_request(unsigned gpio, const char *label)
gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,
    此函数会返回这个 GPIO 的标号
label:给 gpio 设置个名字
返回值:0,申请成功;其他值,申请失败
  • gpio_free 函数
// 如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。
void gpio_free(unsigned gpio)
gpio:要释放的 gpio 标号
  • gpio_direction_output 函数
// 用于设置某个 GPIO 为输出,并且设置默认输出值
int gpio_direction_output(unsigned gpio, int value)
gpio:要设置为输出的 GPIO 标号
value:GPIO 默认输出值
返回值:0,设置成功;负值,设置失败
  • gpio_direction_input 函数
// 用于设置某个 GPIO 为输入
int gpio_direction_input(unsigned gpio)
gpio:要设置为输出的 GPIO 标号
返回值:0,设置成功;负值,设置失败   
  • gpio_get_value 函数
// 获取某个 GPIO 的值(0 或 1),此函数是个宏
int gpio_get_value(unsigned gpio)
#define gpio_get_value  __gpio_get_value
int __gpio_get_value(unsigned gpio)
gpio:要获取的 GPIO 标号
返回值:非负值,得到的 GPIO 值;负值,获取失败
  • gpio_set_value 函数
// 用于设置某个 GPIO 的值,此函数是个宏
void gpio_set_value(unsigned gpio, int value)
#define gpio_set_value  __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)
gpio:要设置的 GPIO 标号。
value:要设置的值。
返回值:无

设备树中添加 gpio 节点模板

// 和上面的 pinctrl 节点模板项相对应
test {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "dongfang-test";
    pinctrl-names = "default"; /* 描述 pinctrl 名字为default */
    /*添加 pinctrl-0 节点,tset 设备的 PIN 信息保存在 pinctrl_test 节点 */
    pinctrl-0 = <&pinctrl_test>; 
    gpio = <&gpio1 0 GPIO_ACTIVE_LOW>; /* test 设备所使用的 gpio 设置低电平 */
    status = "okay";
};

与gpio相关的几个 OF 函数

在驱动程序中需要读取 gpio 属性内容,Linux 内核提供了几个与 GPIO 有关的 OF 函数

  • of_gpio_named_count 函数
// 用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的是空的 GPIO 信息也会被统计到
int of_gpio_named_count(struct device_node *np, const char* propname)
np:设备节点
propname:要统计的 GPIO 属性
返回值:正值,统计到的 GPIO 数量;负值,失败
eg:
gpios = <0
	&gpio1 1 2
 	0
 	&gpio2 3 4>;
// 这样的节点会被为4个,即使第一个和第二个为空
  • of_gpio_count 函数
// 统计“gpios”这个属性的 GPIO 数量
static inline int of_gpio_count(struct device_node *np)
{
	return of_gpio_named_count(np, "gpios");
}
np:设备节点
返回值:正值,统计到的 GPIO 数量;负值,失败
  • of_name_gpio 函数
// 获取 GPIO 编号
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
np:设备节点
propname:包含要获取 GPIO 信息的属性名
index:GPIO 索引,因为一个属性里面可能包含多个 GPIO
    此参数指定要获取哪个 GPIO的编号
    如果只有一个GPIO 信息的话此参数为 0
返回值:正值,获取到的 GPIO 编号;负值,失败

gpioled 实验测试

修改设备树文件

  • 添加 pinctrl 节点
/* 在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点 */
/* 相应的模仿下面的格式来写 */
pinctrl_led: ledgrp {
    fsl,pins = <
        /* 将 GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03,电气属性值为 0X10B0 */
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
        >;
};
  • 添加 led 设备子节点
/* 在根节点“/”下创建 LED 灯节点 */
gpioled {
    #address-cells = <1>; /* reg 属性中起始地址占用一个字长 */
    #size-cells = <1>;	  /* 地址长度占用一个字长 */
    pinctrl-names = "default"; /* 描述 pinctrl 名字为default */
    compatible = "gpio-led"; /* 属性 compatbile 设置节点兼容性为“gpio-led”*/
    pinctrl-0 = <&pinctrl_led>; /* 设置 LED 灯所使用的 PIN 对应的 pinctrl 节点 */
    /* gpio-led属性指定了LED灯所使用的GPIO,在这里就是GPIO1的IO03,低电平有效 */
    gpio-led = <&gpio1 3 GPIO_ACTIVE_LOW>; 
    status = "okay";		/* 属性 status 设置状态为“okay” */
};
  • 检查PIN是否被其他外设使用

如果 A 引脚在设备树中配置为了 I2C 的 SDA 信号,那么 A 引脚就不能再配置为 GPIO,否则的话驱动程序在申请 GPIO 的时候就会失败。检查 PIN 有没有被其他外设使用包括两个方面:
①、检查 pinctrl 设置。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。

// 在imx6ull-14x14-evk.dts文件中搜索 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 将其他的给注释
pinctrl_tsc: tscgrp {
    /* pinctrl_tsc 节点是 TSC(电阻触摸屏接口)的 pinctrl 节点 */
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO01__GPIO1_IO01	0xb0
        MX6UL_PAD_GPIO1_IO02__GPIO1_IO02	0xb0
        /* MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0xb0 */
        MX6UL_PAD_GPIO1_IO04__GPIO1_IO04	0xb0
        >;
};
// 在imx6ull-14x14-evk.dts文件中搜索 gpio1 3 将其他的给注释,将其他使用该引脚的注释
&tsc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_tsc>;
	/* xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; */
	measure-delay-time = <0xffff>;
	pre-charge-time = <0xfff>;
	status = "okay";
};
// 使用 make dtbs 命令重新编译设备树,并将设备树烧写进去linux开发板中

编写驱动

#include <linux/init.h>   // module_init、module_exit函数的头文件所在地
#include <linux/fs.h>     //添加文件描述符,设备注册注销头文件
#include <linux/module.h> // 添加module_XXX宏定义头文件
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <asm/io.h>   // 包含readl等IO操作函数
#include <linux/gpio.h>
#include <linux/errno.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>

#define NEW_CHAR_CNT 1   /*设备号数量*/
#define MY_NAME "gpio_led" /*设备名字*/
#define LED_OFF     0   /* 关灯 */
#define LED_ON      1   /* 开灯 */


typedef struct my_dev{
    int major;  /*主设备号*/
	int minor;  /*次设备号*/
	dev_t dev;  /*设备号*/
	struct cdev cdev;  /* cdev */
	struct class *class; /*类*/
	struct device *device; /*设备*/
    struct device_node *nd; /* 设备树设备节点 */
    int led_gpio; /* led 所使用的 GPIO 编号 */
}my_dev_t;

my_dev_t my_new_dev;  /*新设备*/


/*打开设备*/
static int my_dev_open (struct inode *inode, struct file *file)
{
    file->private_data = &my_new_dev; /*设置私有数据*/
    return 0;
}
/*读设备*/
static ssize_t my_dev_read (struct file *file, char __user *buf, 
                            size_t cnt, loff_t * offt)
{
    return 0;
}
/*写设备*/
static ssize_t my_dev_write (struct file *file, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char data_buf[1];
    unsigned char led_stat;
    my_dev_t *dev = file->private_data;  /* 获取私有数据 GPIO 编号 */
    /*接收用户空间的数据*/
    ret = copy_from_user(data_buf,buf,cnt);
    if(ret < 0)
    {
        printk(KERN_INFO "write fail ...\n");
        return -EFAULT; 
    }

    led_stat = data_buf[0]; /* 获取状态值 */

    if(led_stat == LED_ON)
    {
        gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
    }else if(led_stat == LED_OFF)
    {
        gpio_set_value(dev->led_gpio, 1); /* 打开 LED 灯 */
    }

    return 0;
}
/*释放设备*/
static int my_dev_close (struct inode *inode, struct file *file)
{
    return 0;
}

static struct file_operations my_fops = {
    .owner		= THIS_MODULE,		// 惯例,直接写即可
	.open		= my_dev_open,		// 打开设备
    .read       = my_dev_read,		// 读设备
    .write		= my_dev_write,		// 写设备
	.release	= my_dev_close,		// 关闭设备
};

/*驱动入口函数 */
static int __init mydev_init(void)
{
    int ret;

    /* 获取设备树中的属性数据 */
    /* 1、获取设备节点:gpioled */
    my_new_dev.nd = of_find_node_by_path("/gpioled");

    if(my_new_dev.nd == NULL) {
        printk("gpioled node can not found!\r\n");
        return -EINVAL;
    } else {
        printk("gpioled node has been found!\r\n");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    my_new_dev.led_gpio = of_get_named_gpio(my_new_dev.nd, "gpio-led", 0);
    if(my_new_dev.led_gpio < 0) {
        printk("can't get gpio-led");
        return -EINVAL;
    }

    printk("gpio-led num = %d\r\n", my_new_dev.led_gpio);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(my_new_dev.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!\r\n");
    }

    /* 1、分配设备号 */
    if(my_new_dev.major) /*定义了主设备号*/
    {
        my_new_dev.dev = MKDEV(my_new_dev.major,0); /*次设备默认从0开始*/
        register_chrdev_region(my_new_dev.dev,NEW_CHAR_CNT,MY_NAME); /*静态申请设备号*/
    }else
    {
        alloc_chrdev_region(&my_new_dev.dev,0,NEW_CHAR_CNT,MY_NAME); /*动态申请设备号*/
        my_new_dev.major = MAJOR(my_new_dev.dev); /*主设备号*/
        my_new_dev.minor = MINOR(my_new_dev.dev); /*次设备号*/
    }
    /* 2、注册设备号 */
    my_new_dev.cdev.owner = THIS_MODULE;
    cdev_init(&my_new_dev.cdev,&my_fops); /*初始化cdev*/
    cdev_add(&my_new_dev.cdev,my_new_dev.dev,NEW_CHAR_CNT); /*添加一个dev*/
    /* 3、创建类 */
	my_new_dev.class = class_create(THIS_MODULE,MY_NAME);
    if(IS_ERR(my_new_dev.class))
    {
        return PTR_ERR(my_new_dev.class);
    }
    /* 4、创建设备 */
    my_new_dev.device = device_create(my_new_dev.class,NULL,my_new_dev.dev,NULL,MY_NAME);
    if(IS_ERR(my_new_dev.device))
    {
        return PTR_ERR(my_new_dev.device);
    }

    return 0;
}

/*驱动出口函数 */
static void __exit mydev_exit(void)
{
    /*注销字符设备*/
    cdev_del(&my_new_dev.cdev);
    unregister_chrdev_region(my_new_dev.dev, NEW_CHAR_CNT);

    /*注销掉类和设备*/
	device_destroy(my_new_dev.class,my_new_dev.dev);
    class_destroy(my_new_dev.class);
}

module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");             // 描述模块的许可证
MODULE_AUTHOR("dongfang");         // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("------");            // 描述模块的别名信息

编译错误解决

/* 编译遇到错误
error: implicit declaration of function ‘of_get_named_gpio’ [-Werror=implicit-function-declaration]
*/
查看 of_get_named_gpio 所在头文件是否定义 #include <linux/of_gpio.h>
depmod //第一次加载驱动的时候需要运行此命令
modprobe gpioled.ko //加载驱动
./ledApp /dev/gpioled 1 //打开 LED 灯
./ledApp /dev/gpioled 0 //关闭 LED 灯
rmmod gpioled.ko // 卸载模块
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值