一、编写dtsled.c驱动程序
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
/********************************************************************************
* 文件 :dtsled.c
* 作者 :Lv Jiang
* 版本 :V1.0
* 描述 :通过设备树编写新字符设备驱动LED
* 其他 :无
* 日志 :2023/04/18
********************************************************************************/
#define DTSLED_CNT 1 /*设备号个数*/
#define DTSLED_NAME "dtsled" /*设备名称*/
#define DTSLED_ON 1 /*开灯*/
#define DTSLED_OFF 0 /*关灯*/
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* dtsled设备结构体 */
struct dtsled_dev
{
dev_t devid; /*设备号*/
struct cdev cdev; /*字符设备*/
struct class *class; /*类*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
struct device_node *node; /*设备节点*/
};
struct dtsled_dev dtsled; /* 定义dtsled设备 */
/* LED打开/关闭函数 */
void led_switch(u8 state)
{
u32 value = 0;
if (state == DTSLED_ON) {
value = readl(GPIO1_DR);
value &= ~(1 << 3);
writel(value, GPIO1_DR);
}
else if (state == DTSLED_OFF) {
value = readl(GPIO1_DR);
value |= (1 << 3);
writel(value, GPIO1_DR);
}
}
/* open函数 */
static int dtsled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &dtsled;
return 0;
}
/* read函数 */
static ssize_t dtsled_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
/* write函数 */
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;
int retValue = 0;
unsigned char ledState;
unsigned char dataBuffer[1];
retValue = copy_from_user(dataBuffer, buf, count);
if (retValue < 0) {
printk("Kernel write failed!\r\n");
return -EFAULT;
}
ledState = dataBuffer[0];
if (ledState == DTSLED_ON) {
led_switch(DTSLED_ON);
}
else if (ledState == DTSLED_OFF) {
led_switch(DTSLED_OFF);
}
return 0;
}
/* release函数 */
static int dtsled_release(struct inode *inode, struct file *filp)
{
struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
return 0;
}
/* dtsled字符设备操作集合 */
static const struct file_operations dtsled_fops =
{
.owner = THIS_MODULE,
.open = dtsled_open,
.read = dtsled_read,
.write = dtsled_write,
.release = dtsled_release,
};
/********************************************************************************
* @Description : 模块入口函数
* @Parameter : 无
* @Return : 无
********************************************************************************/
static int __init dtsled_init(void)
{
int ret = 0;
const char *str;
u32 regData[10];
u8 i = 0;
unsigned int val = 0;
/* 注册字符设备 */
/* 1、添加设备号 */
dtsled.major = 0; /*无设备号,则由内核分配*/
if (dtsled.major) { /*已定义设备号*/
dtsled.devid = MKDEV(dtsled.major, 0);
ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
}
else { /*未定义设备号*/
ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.devid);
}
if (ret < 0) {
goto fail_devid;
}
/* 2、添加字符设备 */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
if (ret < 0) {
goto fail_cdev;
}
/* 3、自动创建设备节点 */
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if (IS_ERR(dtsled.class)) {
ret = PTR_ERR(dtsled.class);
goto fail_class;
}
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;
}
/* 获取设备树中的属性数据 */
/* 1、获取设备节点:alphaled */
dtsled.node = of_find_node_by_path("/alphaled");
if (dtsled.node == NULL) {
return -EINVAL;
goto fail_findnode;
}
else {
printk("alphaled node has been found!\r\n");
}
/* 2、获取设备属性内容:compatible */
ret = of_property_read_string(dtsled.node, "compatible", &str);
if (ret < 0) {
goto fail_readcompatible;
}
else {
printk("compatible = %s\r\n", str);
}
/* 3、获取设备属性内容:status */
ret = of_property_read_string(dtsled.node, "status", &str);
if (ret < 0) {
goto fail_readstatus;
}
else {
printk("status = %s\r\n", str);
}
#if 0
/* 4、获取设备属性内容:reg */
ret = of_property_read_u32_array(dtsled.node, "reg", regData, 10);
if (ret < 0) {
goto fail_readreg;
}
else {
printk("reg data:\r\n");
for (i = 0; i < 10; i++) {
printk("%#x ", regData[i]);
}
printk("\r\n");
}
/* LED初始化 */
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(regData[0], regData[1]);
SW_MUX_GPIO1_IO03 = ioremap(regData[2], regData[3]);
SW_PAD_GPIO1_IO03 = ioremap(regData[4], regData[5]);
GPIO1_DR = ioremap(regData[6], regData[7]);
GPIO1_GDIR = ioremap(regData[8], regData[9]);
#else
/* LED初始化 */
/* 1、寄存器地址映射 + 获取设备属性内容:reg */
IMX6U_CCM_CCGR1 = of_iomap(dtsled.node, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.node, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.node, 2);
GPIO1_DR = of_iomap(dtsled.node, 3);
GPIO1_GDIR = of_iomap(dtsled.node, 4);
#endif
/* 2、使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val, IMX6U_CCM_CCGR1);
/* 3、设置GPIO1复用功能 */
writel(5, SW_MUX_GPIO1_IO03);
/* 4、设置GPIO1电气属性 */
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 5、设置GPIO1输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val, GPIO1_GDIR);
/* 6、默认关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
return 0;
/* 错误处理 */
fail_readreg:
fail_readcompatible:
fail_readstatus:
fail_findnode:
device_destroy(dtsled.class, dtsled.devid);
fail_device:
class_destroy(dtsled.class);
fail_class:
cdev_del(&dtsled.cdev);
fail_cdev:
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_devid:
return ret;
}
/********************************************************************************
* @Description : 模块出口函数
* @Parameter : 无
* @Return : 无
********************************************************************************/
static void __exit dtsled_exit(void)
{
unsigned int val = 0;
/* 退出前关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
/* 取消地址映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 删除字符设备 */
cdev_del(&dtsled.cdev);
/* 释放设备号*/
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
/* 摧毁设备 */
device_destroy(dtsled.class, dtsled.devid);
/* 摧毁类 */
class_destroy(dtsled.class);
}
/********************************************************************************
* @Description : 注册驱动和卸载驱动
********************************************************************************/
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lv Jiang");
二、编写dtsledAPP.c应用程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/********************************************************************************
* 文件 :dtsledAPP.c
* 作者 :Lv Jiang
* 版本 :V1.0
* 描述 :LED驱动测试
* 其他 :无
* 用法 :./dtsledAPP /dev/dtsled 0 关闭LED
* ./dtsledAPP /dev/dtsled 1 打开LED
********************************************************************************/
#define DTSLED_OFF 0
#define DTSLED_ON 1
/********************************************************************************
* @Description : main主程序
* @Parameter - argc : argc数组元素个数
* @Parameter - argv : argv具体参数
* @Return : 0,成功;1,失败
********************************************************************************/
int main(int argc, char *argv[])
{
int fd;
int retValue;
char *fileName;
unsigned char dataBuf[1];
/* 数组元素个数必须为3 */
if (argc != 3) {
printf("Error usage!\r\n");
return -1;
}
fileName = argv[1];
/* 打开LED设备驱动 */
fd = open(fileName, O_RDWR);
if (fd < 0) {
printf("Open file %s failed!\r\n", fileName);
return -1;
}
/* 要执行的操作:打开/关闭LED */
dataBuf[0] = atoi(argv[2]);
/* 向/dev/newchrled设备驱动文件写入数据 */
retValue = write(fd, dataBuf, sizeof(dataBuf));
if (retValue < 0) {
printf("Write data failed!\r\n");
close(fd);
return -1;
}
/* 关闭LED设备驱动 */
retValue = close(fd);
if (retValue < 0) {
printf("Close file %s failed!\r\n", fileName);
return -1;
}
return 0;
}
三、编写Makefile文件
KERNELDIR := /home/lvjiang/Linux/IMX6ULL/Linux/linux-imx-alientek
CURRENT_PATH := $(shell pwd)
obj-m := dtsled.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean