驱动程序开发:设备树下新字符设备驱动之LED点灯

驱动程序开发:设备树下新字符设备驱动之LED点灯

dtsled.c

/*
 *	根据linux内核的程序查找所使用函数的对应头文件。
 */
#include 	// MODULE_LICENSE,MODULE_AUTHOR
#include 		// module_init,module_exit
#include 	// printk
#include 		// struct file_operations
#include 		//kmalloc, kfree
#include 	// copy_to_user,copy_from_user
#include 		//ioremap,iounmap
#include 		//struct cdev,cdev_init,cdev_add,cdev_del
#include 	//class
#include 		//关于of函数
#include 

#define DTSLED_COUNT	1			/* 注册设备个数 */
#define DTSLED_NAME		"dtsled"	/* 注册的设备名字 */

/****** 6.1 物理地址映射后定义虚拟地址的指针,其类型是根据ioremap函数返回值类型定义的 ******/
static void __iomem *IMX6U_CCM_CCGR1; 	//对应IO的时钟寄存器的映射虚拟地址
static void __iomem *SW_MUX_GPIO1_IO03; //对应IO的复用寄存器的映射虚拟地址
static void __iomem *SW_PAD_GPIO1_IO03; //对应IO的电气属性寄存器的映射虚拟地址
static void __iomem *GPIO1_GDIR;		//对应IO的输出方向寄存器的映射虚拟地址
static void __iomem *GPIO1_DR;			//对应IO的输出电平寄存器的映射虚拟地址
/*********************************************************************************/


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

/* dtsled设备结构体 */
struct dtsled_dev {
	dev_t devid;			/* 设备号 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
	struct cdev cdev;		/* 字符设备结构体 */
	struct class *class;	/* 自动创建设备节点的类 */
	struct device *device;	/* 自动创建设备节点的设备 */
	struct device_node *nd;	/* 设备节点 */
};

struct dtsled_dev dtsled;	/* 定义dtsled设备变量 */

/************** 6.4 开/关灯的函数 **************/
void led_switch(u8 state)
{
	u32 val;					//操作的是32位的寄存器
	if(state == LEDON) {
		/* 开灯 */
		val = readl(GPIO1_DR);	//读取寄存器
		val &= ~(1 << 3);		//清零
		writel(val, GPIO1_DR);	//写入寄存器
	} else if(state == LEDOFF) {
		/* 关灯 */
		val = readl(GPIO1_DR);	//读取寄存器
		val |= 1 << 3;		
		writel(val, GPIO1_DR);	//写入寄存器		
	}
}
/*********************************************/

/* 4.1 打开字符设备文件 */
static int dtsled_open(struct inode *inde, struct file *filp) {
	/* 设置私有属性数据,在之后read、write等函数中可直接读取private_data,即可得到设备结构体 */
	filp->private_data = &dtsled;
	return 0;
}

/* 4.2 关闭字符设备 */
static int dtsled_release(struct inode *inde, struct file *filp) {
	/* 提取私有类属性数据 */
	struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;
	return 0;
}

/* 4.3 向字符设备文件写数据 */
static ssize_t dtsled_write(struct file *filp, const char __user *buf,
 			size_t count, loff_t *ppos) {
	/* 提取私有类属性数据 */
	struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;

	/*********** 6.3 应用程序写入参数控制LED ***********/
	int ret;		//保存调用函数返回值
	u8 databuf[1];	//保存应用程序写入打数据
	ret = copy_from_user(databuf, buf, count);	//将应用程序传入过来打buf数据写入驱动程序databuf里
	if(ret < 0) {
		printk("write kernel failed!\r\n");
		return -EFAULT;
	}
	/* 判断开关灯 */
	led_switch(databuf[0]);
	/*************************************************/

	return 0;
}

/* 2.2.1 dtsled字符设备操作集 */
static const struct file_operations dtsled_fops = {
	.owner		= THIS_MODULE,
	.write		= dtsled_write,
	.open		= dtsled_open,
	.release 	= dtsled_release,
};

/* 1.4 驱动入口函数 */
static int __init dtsled_init(void) {
	int ret = 0;		//保存调用函数的放回值的临时变量
	const char *str;	//节点status属性
	u32 elemsize = 0;	//节点reg属性的元素个数
	u32 *regval;		//保存reg属性各个元素值
	u8 i = 0;			//用于循环打印
	u32 val = 0;		//操作GPIO寄存器是保存的临时变量

	/* 2.1 注册字符设备号 */
	dtsled.major = 0;	/* 置零,让系统自动分配设备号 */
	if(dtsled.major) {	//如果设置了设备号
		dtsled.devid = MKDEV(dtsled.major,0);	//将主设备号和次设备号整合成设备号信息
		ret = register_chrdev_region(dtsled.devid,DTSLED_COUNT,DTSLED_NAME);	//手动字符注册设备号
	} else {
		alloc_chrdev_region(&dtsled.devid,0,DTSLED_COUNT,DTSLED_NAME);	//自动注册字符设备号
		dtsled.major = MAJOR(dtsled.devid);		//提取注册的主设备号
		dtsled.minor = MINOR(dtsled.devid);		//提取注册打次设备号
	}
	if(ret < 0) {
		goto fail_devid;
	}
	/* 2.2 注册字符设备 */
	cdev_init(&dtsled.cdev,&dtsled_fops);	//初始化cdev结构体变量,dtsled_fops->dtsled.cdev
	ret = cdev_add(&dtsled.cdev,dtsled.devid,DTSLED_COUNT);	//linux内核添加字符设备
	if(ret < 0) {
		goto fail_cdev;
	}
	/* 3 自动创建设备节点 */
	/* 3.1 创建一个类 */
	dtsled.class = class_create(THIS_MODULE,DTSLED_NAME);
	if(IS_ERR(dtsled.class)) {
		ret = PTR_ERR(dtsled.class);
		goto fail_class;
	}
	/* 3.2 创建设备,实现自动创建设备节点 */
	dtsled.device = device_create(dtsled.class,NULL,dtsled.devid,NULL,DTSLED_NAME);
	if(IS_ERR(dtsled.device)) {
		ret = PTR_ERR(dtsled.device);
		goto fail_device;
	}
	/* 5.1获取设备树的属性内容 */
	dtsled.nd = of_find_node_by_path("/dtsled");
	if(dtsled.nd == NULL) {
		ret = -EINVAL;
		goto fail_findnd;
	}
	/* 5.2 获取reg数组元素的个数 */
	elemsize = of_property_count_elems_of_size(dtsled.nd, "reg", sizeof(u32));	//读取brightness-levels属性元素个数
	if(ret < 0) {
		ret = -EINVAL;
		goto fail_read_elem_size;
	} else {
		printk("brightness-levels elems size:%d\r\n",elemsize);
	}
	regval = kmalloc(elemsize * sizeof(u32), GFP_KERNEL);	//申请动态内存
	if(!regval) {
		ret = -EINVAL;
		goto fail_mem;	//内存申请失败
	}
	/* 5.3 获取字符串属性值,改字符串是设备树中的reg分支属性,其属性保存的是GPIO寄存器地址 */
	ret = of_property_read_string(dtsled.nd,"status",&str);
	if(ret < 0) {
		goto readstring;
	} else {
		printk("status = %s\r\n",str);
	}
	ret = of_property_read_string(dtsled.nd,"compatible",&str);
	if(ret < 0) {
		goto readstring;
	} else {
		printk("compatible = %s\r\n",str);
	}
	ret = of_property_read_u32_array(dtsled.nd,"reg",regval,elemsize);		//获取数组元素的值
	if(ret < 0) {
		goto fail_readU32array;
	} else {
		printk("reg data:\r\n");
		for(i=0;i<elemsize;i++) {
			//printk("%#X  ",*(regval+i));
			printk("%#X  ",regval[i]);
		}
		printk("\r\n");
	}
	/* 6.2 LED初始化 */
	/***************** 6.2.1 内存地址映射 *****************/
	/* 
	 *	of_iomap函数直接对设备数的reg属性进行内存映射,如果采用of_iomap
	 *	进行内存映射就无需在申请内存空间来保存of_property_read_u32_array
	 *	函数获取的reg数组元素值了
	 */
#if 0
	IMX6U_CCM_CCGR1 = ioremap(regval[0], regval[1]);
	SW_MUX_GPIO1_IO03 = ioremap(regval[2], regval[3]);
	SW_PAD_GPIO1_IO03 = ioremap(regval[4], regval[5]);
	GPIO1_GDIR = ioremap(regval[6], regval[7]);
	GPIO1_DR = ioremap(regval[8], regval[9]);	
#else
	IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd,0);
	SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd,1);
	SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd,2);
	GPIO1_GDIR = of_iomap(dtsled.nd,3);
	GPIO1_DR = of_iomap(dtsled.nd,4);
#endif
	/****** 6.2.3 始化时钟,一般操作寄存器使用读改写操作步骤 ******/
	val = readl(IMX6U_CCM_CCGR1);	//读取寄存器
	val &= ~(3 << 26);				//bit26:27清零
	val |= 3 << 26;					//bit26:27置一
	writel(val, IMX6U_CCM_CCGR1);	//写入寄存器,时钟
	writel(0x5,SW_MUX_GPIO1_IO03);	//复用为IO
	writel(0x10B0,SW_PAD_GPIO1_IO03);//电气属性
	val = readl(GPIO1_GDIR);		//读取寄存器
	val |= 1 << 3;					//置位
	writel(val, GPIO1_GDIR);		//写入寄存器,输出方向
	/* 初始状态为关灯 */
	val = readl(GPIO1_DR);			//读取寄存器
	val |= 1 << 3;					//置1,关灯
	writel(val, GPIO1_DR);			//写入寄存器
	/***********************************************************/
	kfree(regval);	//释放内存	
	return 0;

/* 5.3.2 */
fail_readU32array:
	kfree(regval);	//释放内存	
/* 5.3.1 */
readstring:
/* 5.2.2 */
fail_mem:
/* 5.2.1 */
fail_read_elem_size:
/* 5.1.1 */
fail_findnd:
	device_destroy(dtsled.class,dtsled.devid);		//摧毁设备
/* 3.2.1 */
fail_device:
	class_destroy(dtsled.class);	//销毁|释放类
/* 3.1.1 */
fail_class:
	cdev_del(&dtsled.cdev);		// 注销|删除字符设备
/* 2.2.2 */
fail_cdev:		//注册字符设备出错
	unregister_chrdev_region(dtsled.devid,DTSLED_COUNT);	//释放|注销字符设备号
/* 2.1.1 */
fail_devid:		//申请字符设备号失败
	printk("dtsled chrdev apply for devid failed!\r\n");
	return ret;

}
/* 1.5 驱动出口函数 */
static void __exit dtsled_exit(void) {

	u32 val = 0;
	printk("newchrdev_exit\r\n");
	/*********** 6.5 关灯 ********************/
	val = readl(GPIO1_DR);	//读取寄存器
	val |= 1 << 3;		
	writel(val, GPIO1_DR);	//写入寄存器
	/********** 6.2.2 注销地址映射 *************/
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_GDIR);
	iounmap(GPIO1_DR);
	/*****************************************/

	/* 2.3.1 注销|删除字符设备  */
	cdev_del(&dtsled.cdev);
	/* 2.3.2 注销|释放字符设备号 */
	unregister_chrdev_region(dtsled.devid,DTSLED_COUNT);
	/* 3.3.1 摧毁设备 */
	device_destroy(dtsled.class,dtsled.devid);
	/* 3.3.2 摧毁类 */
	class_destroy(dtsled.class);
}

/* 1.1 注册加载驱动模块 */
module_init(dtsled_init);
/* 1.2 注册卸载驱动模块 */
module_exit(dtsled_exit);
/* 1.3 驱动许可证 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("djw");

ledAPP.c

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

/*
 * argc:应用程序参数个数 
 * argv[]:具体打参数内容,字符串形式 
 * ./ledAPP  <0:1> 0为关,1为开
 * ./ledAPP /dev/dtsled 0   表示关灯
 * ./ledAPP /dev/dtsled 1   表示开灯
 */

int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    unsigned char databuf[1];

    if(argc != 3) {
        printf("ERROR USAGE!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename,O_RDWR);
    if(fd < 0) {
        printf("file %s open failed!\r\n",filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]);
    ret = write(fd, databuf, sizeof(databuf));
    if(ret < 0) {
        printf("LED control failed!\r\n");
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

imx6ull-alientek-emmc.dts

/ {
	model = "Freescale i.MX6 ULL 14x14 EVK Board";
	compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

	chosen {
		stdout-path = &uart1;
	};

	memory {
		reg = <0x80000000 0x20000000>;
	};

	reserved-memory {
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;

		linux,cma {
			compatible = "shared-dma-pool";
			reusable;
			size = <0x14000000>;
			linux,cma-default;
		};
	};

	backlight {
		compatible = "pwm-backlight";
		pwms = <&pwm1 0 5000000>;
		brightness-levels = <0 4 8 16 32 64 128 255>;
		default-brightness-level = <6>;
		status = "okay";
	};

	pxp_v4l2 {
		compatible = "fsl,imx6ul-pxp-v4l2", "fsl,imx6sx-pxp-v4l2", "fsl,imx6sl-pxp-v4l2";
		status = "okay";
	};

	regulators {
		compatible = "simple-bus";
		#address-cells = <1>;
		#size-cells = <0>;

		reg_can_3v3: regulator@0 {
			compatible = "regulator-fixed";
			reg = <0>;
			regulator-name = "can-3v3";
			regulator-min-microvolt = <3300000>;
			regulator-max-microvolt = <3300000>;
			gpios = <&gpio_spi 3 GPIO_ACTIVE_LOW>;
		};

		reg_sd1_vmmc: regulator@1 {
			compatible = "regulator-fixed";
			regulator-name = "VSD_3V3";
			regulator-min-microvolt = <3300000>;
			regulator-max-microvolt = <3300000>;
			gpio = <&gpio1 9 GPIO_ACTIVE_HIGH>;
			enable-active-high;
		};

		reg_gpio_dvfs: regulator-gpio {
			compatible = "regulator-gpio";
			pinctrl-names = "default";
			pinctrl-0 = <&pinctrl_dvfs>;
			regulator-min-microvolt = <1300000>;
			regulator-max-microvolt = <1400000>;
			regulator-name = "gpio_dvfs";
			regulator-type = "voltage";
			gpios = <&gpio5 3 GPIO_ACTIVE_HIGH>;
			states = <1300000 0x1 1400000 0x0>;
		};
	};

	sound {
		compatible = "fsl,imx6ul-evk-wm8960",
			   "fsl,imx-audio-wm8960";
		model = "wm8960-audio";
		cpu-dai = <&sai2>;
		audio-codec = <&codec>;
		asrc-controller = <&asrc>;
		codec-master;
		gpr = <&gpr 4 0x100000 0x100000>;
		/*
                 * hp-det = ;
		 * hp-det-pin: JD1 JD2  or JD3
		 * hp-det-polarity = 0: hp detect high for headphone
		 * hp-det-polarity = 1: hp detect high for speaker
		 */
		hp-det = <3 0>;
		hp-det-gpios = <&gpio5 4 0>;
		mic-det-gpios = <&gpio5 4 0>;
		audio-routing =
			"Headphone Jack", "HP_L",
			"Headphone Jack", "HP_R",
			"Ext Spk", "SPK_LP",
			"Ext Spk", "SPK_LN",
			"Ext Spk", "SPK_RP",
			"Ext Spk", "SPK_RN",
			"LINPUT2", "Mic Jack",
			"LINPUT3", "Mic Jack",
			"RINPUT1", "Main MIC",
			"RINPUT2", "Main MIC",
			"Mic Jack", "MICB",
			"Main MIC", "MICB",
			"CPU-Playback", "ASRC-Playback",
			"Playback", "CPU-Playback",
			"ASRC-Capture", "CPU-Capture",
			"CPU-Capture", "Capture";
	};

	spi4 {
		compatible = "spi-gpio";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_spi4>;
/*		pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; */
		status = "okay";
		gpio-sck = <&gpio5 11 0>;
		gpio-mosi = <&gpio5 10 0>;
/*		cs-gpios = <&gpio5 7 0>; */
		num-chipselects = <1>;
		#address-cells = <1>;
		#size-cells = <0>;

		gpio_spi: gpio_spi@0 {
			compatible = "fairchild,74hc595";
			gpio-controller;
			#gpio-cells = <2>;
			reg = <0>;
			registers-number = <1>;
			registers-default = /bits/ 8 <0x57>;
			spi-max-frequency = <100000>;
		};
	};
	/* DJW 2022.3.29,修改地方 */
	dtsled {
		compatible = "alientek,dtsled";
		#address-cells = <1>;
		#size-cells = <1>;
		status = "okay";
		reg = < 0x020C406C 0x04		/* CCM_CCGR1 */
				0x020E0068 0X04		/* SW_MUX_GPIO1_IO03 */
				0x020E02F4 0X04 	/* SW_PAD_GPIO1_IO03 */
				0x0209C004 0X04 	/* GPIO1_GDIR */
				0x0209C000 0X04 >;	/* GPIO1_DR */
	};
};

实验操作步骤:
1、将imx6ull-alientek-emmc.dts设备树make成.dtb文件,在内核根文件目录下执行:make dtbs
2、将驱动程序进行make操作,编译生成xxx.ko文件
3、使用交叉编译器将应用程序编译成可执行文件,如:我这里使用 arm-linux-gnueabihf-gcc xxxAPP.c -o xxxAPP
4、将xxx.ko和xxxAPP两个文件拷贝到存放驱动模块的目录中。
5、打开开发板并使用Linux系统选择通过TFTP从网络启动和使用NFS挂载网络根文件系统。
6、在PC机的串口终端中先输入depmod,后输入modprobe xxx.ko加载驱动文件,最后输入lsmod查看当前系统中存在的模块。
7、在PC机的串口终端中输入cat /proc/devices查看当前系统的所有设备的对应的设备号及名称。
8、在PC机的串口终端中输入./xxxApp /dev/xxx 1或./xxxApp /dev/xxx 2指令来使用应用程序对驱动程序进行读写等操作。
9、在PC机的串口终端中输入rmmod xxx.ko卸载驱动模块。

你可能感兴趣的:(Linux驱动开发,驱动开发,linux)