linux pinctrl 和 gpio 子系统 LED驱动

news2024/11/25 18:53:34

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 // 卸载模块

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/513829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

校招失败后,4面字节跳动软件测试工程师,竭尽全力....

下面是我面试字节跳动软件测试工程师的面试经验总结&#xff0c;希望能帮助到你们。 面试一 简单做一下自我介绍简要介绍一下项目/你负责的模块/选一个模块说一下你设计的用例get请求和post请求的区别如何判断前后端bug/3xx是什么意思说一下XXX项目中你做的接口测试/做了多少次…

免费润色文章的软件-自动修改文章润色的软件

免费润色文章的软件 免费润色文章的软件可以帮助用户快速地改善文本质量&#xff0c;进一步提高语言表达能力和流畅性&#xff0c;以下是其主要优势&#xff1a; 高效性&#xff1a;免费润色文章的软件能够快速进行润色处理&#xff0c;为用户节省时间和精力。相比手动润色的方…

瑞芯微RK3588核心板远程会诊等医学解决方案

RK3588处理器在医学领域的应用中&#xff0c;可以为远程会诊提供高性能和可靠的解决方案。以下是基于RK3588的远程会诊医学方面的解决方案&#xff1a; 远程高清图像传输&#xff1a; 利用RK3588处理器的高性能图像处理能力和高速网络接口&#xff0c;实现高清医学图像的实时传…

Dubbo 基于xml文件分析主流程源码 (4)

目录 前提 JDK实现SPI Dubbo实现SPI Dubbo源码 1. 找到Dubbo的命名空间处理类&#xff0c;也就是Dubbo的入口类 2. 将dubbo标签交给spring进行管理&#xff0c;就是从 BeanDefinition----> Bean的过程。 3. 服务暴露 4. 服务引入 总结 仿写Dubbo 前提 1. Dubbo源码…

CentOS7 yum update y更新后黑屏解决方案

解决方法 一 可以ssh访问 因为update的时候更新了系统内核&#xff0c;导致驱动问题&#xff0c;所以会黑屏。 更改一下yum的配置即可解决: vi /etc/yum.conf#增加&#xff1a;excludecentos-release*excludekernel*如果以上问题还未解决&#xff0c;可以试试下面的方法 其…

架构模式之分层模式

1 概念 分层架构模式是一种非常常见的架构设计模式&#xff0c;很多人都在用&#xff0c;可能不知道它的概念。分层模式背后的理念是&#xff0c;具有相同功能的组件将被组织成水平层。因此&#xff0c;每一层在应用程序中都扮演着特定的角色。 在这种模式中&#xff0c;…

自学自动化测试,第一份工作就18K,因为掌握了这些技术

我个人的情况是有1年自动化测试工作经验半年的实习经验&#xff0c;2020年毕业&#xff0c;专业通信工程&#xff0c;大一的时候学过C语言&#xff0c;所以一直对于编程感兴趣&#xff0c;之所以毕业后没做通信的工作&#xff0c;通信行业的朋友应该都明白&#xff0c;通信的天…

DolphinScheduler3.1.5安装部署

1.下载 DolphinScheduler下载地址&#xff1a;https://dolphinscheduler.apache.org/zh-cn/download/3.1.5 选择二进制包 下载&#xff0c;点击 jar 名称 就行 ​ 2.环境 CentOS Linux release 7.5.1804 (Core)java version "1.8.0_212"mysql version 5.7.16-log…

信息化 VS 数字化,哪个更适合当代企业?

各位数据的朋友&#xff0c;大家好&#xff0c;我是老周道数据&#xff0c;和你一起&#xff0c;用常人思维数据分析&#xff0c;通过数据讲故事。 现在大家都在谈数字化转型升级&#xff0c;那到底什么是数字化&#xff0c;是不是新瓶装旧酒呢&#xff1f;今天就和大家来谈谈…

【华为机考】专题突破 第二周:前缀和与差分 1109

刷题顺序参考于 《2023华为机考刷题指南&#xff1a;八周机考速通车》 前言 前缀和是指某序列的前n项和&#xff0c;可以把它理解为数学上的数列的前n项和&#xff0c;而差分可以看成前缀和的逆运算。合理的使用前缀和与差分&#xff0c;可以将某些复杂的问题简单化。 关于各类…

CnOpenData中国标准数据

一、数据简介 按照《中华人民共和国标准化法》的定义&#xff0c;标准是指农业、工业、服务业以及社会事业等领域需要统一的技术要求。标准作为一种通用性的规范语言&#xff0c;在合理利用国家资源、保障产品质量、提高市场信任度、促进商品流通、维护公平竞争、保障安全等方面…

机器学习-9 降维算法——PCA降维

降维算法 算法概述降维的概念降维的作用降维的本质常见算法分类主成分分析&#xff08;PCA&#xff09;降维分析 算法流程PCA算法的流程图PCA算法的实现步骤协方差矩阵 算法应用sklearn库中的主成分分析PCA实现高维数据可视化鸢尾花案例手写体数字图像识别案例 算法总结PCA算法…

MyBatis缓存-一级缓存--二级缓存的非常详细的介绍

目录 MyBatis-缓存-提高检索效率的利器 缓存-官方文档 一级缓存 基本说明 一级缓存原理图 代码演示 修改MonsterMapperTest.java, 增加测试方法 结果 debug 一级缓存执行流程 一级缓存失效分析 关闭sqlSession会话后 , 一级缓存失效 如果执行sqlSession.clearCache(…

linux安装nacos步骤

安装前提&#xff1a;服务器已安装JDK 一、nacos下载 Nacos下载地址&#xff1a;Releases alibaba/nacos GitHub 根据springboot版本选择nacos版本 版本说明 alibaba/spring-cloud-alibaba Wiki GitHub 二、nacos解压、修改配置文件 #选择安装目录 cd /home/dxhy/appl…

一款基于 Spring Cloud 开源的医疗信息系统

今天给大家介绍一个医院信息系统开源项目&#xff0c;相对比较完整&#xff0c;采用的技术栈是 Spring cloud和Spring boot 2.x&#xff0c;比较主流&#xff0c;正在做这方面系统的童鞋们可以参考一下&#xff01; 主要功能按照数据流量、流向及处理过程分为临床诊疗、药品管…

云原生|详解Kubernetes Operator在项目中的开发应用

目录 一、使用场景 &#xff08;一&#xff09;client-go中处理逻辑 &#xff08;二&#xff09;controller-runtime中处理逻辑 二、使用controller-runtime开发operator项目 &#xff08;一&#xff09;生成框架代码 &#xff08;二&#xff09;定义crd字段 &#xff0…

分布式消息队列RocketMQ概念详解

目录 1.MQ概述 1.1 RocketMQ简介 1.2 MQ用途 1.3 常见MQ产品 2.RocketMQ 基本概念 2.1 消息 2.2 主题 2.3 标签 2.4 队列 2.5 Producer 2.6 Consumer 2.7 NameServer 2.8 Broker 2.9 RocketMQ 工作流程 1.MQ概述 1.1 RocketMQ简介 RocketMQ 是阿里开源的分布式消…

云原生:从基本概念到实践,解析演进与现状

文章目录 云原生&#xff1a;从基本概念到实践&#xff0c;解析演进与现状概念演进之路DockerKubernetesCloud NativeServerless 业界现状总结 结语 云原生&#xff1a;从基本概念到实践&#xff0c;解析演进与现状 本文仅用于简单普及&#xff0c;达到的目的是给没接触过或者很…

苹果手机无法开机?黑屏打不开怎么办?出现这种问题的解决办法分享!

各位在使用苹果手机的小伙伴有没有遇到苹果手机突然就黑屏开不了机&#xff0c;打电话也没有任何反应&#xff0c;手机也无法关机重启&#xff0c;这是什么问题呢&#xff1f;我们遇到这种问题该如何去处理呢&#xff1f; 小编今天就来跟大家说说苹果手机突然开不了机的原因以及…

【Linux命令】脚本里常用的几个命令

脚本里常用的命令 一、SORT命令1.1、语法格式1.2常用选项 二、uniq命令2.1命令格式2.2常用选项2.3小实验&#xff0c;过滤出现三次以上的IP地址 三、tr命令3.1语法格式3.2常用选项3.3实验 四、cut命令4.1语法格式4.2常用选项 五、split命令5.1语法格式5.2常用选项 六、eval七、…