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卸载驱动模块。