借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发
pinctrl 子系统(drivers/pinctrl
)的主要工作内容:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
在设备树里面创建一个节点来描述 PIN 的配置信息。在 imx6ull.dts
中有 iomux
节点
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>; /* 起始地址 0x020e0000 长度 0x4000 */
};
iomuxc
节点就是 I.MX6ULL
的 IOMUXC
外设对应的节点。在你使用的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: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 寄存器的值
参考文件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 是具体的设定值 */
>;
};
上面的 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 = <®_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>;
};
在内核中搜索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 管脚,在使用一个 GPIO 之前一定要使用 gpio_request 进行申请
int gpio_request(unsigned gpio, const char *label)
gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,
此函数会返回这个 GPIO 的标号
label:给 gpio 设置个名字
返回值:0,申请成功;其他值,申请失败
// 如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。
void gpio_free(unsigned gpio)
gpio:要释放的 gpio 标号
// 用于设置某个 GPIO 为输出,并且设置默认输出值
int gpio_direction_output(unsigned gpio, int value)
gpio:要设置为输出的 GPIO 标号
value:GPIO 默认输出值
返回值:0,设置成功;负值,设置失败
// 用于设置某个 GPIO 为输入
int gpio_direction_input(unsigned gpio)
gpio:要设置为输出的 GPIO 标号
返回值:0,设置成功;负值,设置失败
// 获取某个 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 的值,此函数是个宏
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:要设置的值。
返回值:无
// 和上面的 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 属性内容,Linux 内核提供了几个与 GPIO 有关的 OF 函数
// 用于获取设备树某个属性里面定义了几个 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个,即使第一个和第二个为空
// 统计“gpios”这个属性的 GPIO 数量
static inline int of_gpio_count(struct device_node *np)
{
return of_gpio_named_count(np, "gpios");
}
np:设备节点
返回值:正值,统计到的 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 编号;负值,失败
/* 在 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 灯节点 */
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” */
};
如果 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 // module_init、module_exit函数的头文件所在地
#include //添加文件描述符,设备注册注销头文件
#include // 添加module_XXX宏定义头文件
#include
#include
#include
#include
#include
#include // 包含readl等IO操作函数
#include
#include
#include
#include
#include
#include
#include
#include
#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 // 卸载模块