- LED字符设备驱动程序改进——构建led_drv.c文件和led_dev.c文件
- 将二者都用到的公共部分提取出来,放入到led_dev.h文件当中去
/**
* @file led_dev.h
* @author glen (glen_cao@126.com)
* @brief
* @version 0.1
* @date 2021-12-17
*
* @copyright Copyright (c) 2021
*/
#ifndef __LED_DEV_H__
#define __LED_DEV_H__
#include <linux/types.h>
struct led_ops {
u8 num; /* LED数量 */
int (* led_init)(u8 idx);
int (* led_exit)(u8 idx);
void (* led_ctrl)(u8 idx, u8 status);
};
extern void * get_led_ops(void);
#endif // !__LED_DEV_H__
- led_dev.c为设备相关函数
/*
* 文件名 : led_dev.c
* 作者 : glen
* 描述 : led_dev驱动文件
*/
#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/device.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "led_dev.h"
#define LED_NUM 2 /* LED数量 */
#define HIGH 1
#define LOW 0
#define OFF 'C' /* 关灯 */
#define ON 'O' /* 开灯 */
struct phy_regs {
phys_addr_t ccgr;
phys_addr_t sw_mux;
phys_addr_t sw_pad;
phys_addr_t dr;
phys_addr_t dir;
};
struct vir_regs {
void __iomem *ccgr;
void __iomem *sw_mux;
void __iomem *sw_pad;
void __iomem *dr;
void __iomem *dir;
};
/* LED设备结构体 */
struct gled_device {
char *name; /* 设备名称 */
u8 dft_status[LED_NUM]; /* 默认状态: 打开或关闭 */
u8 on_level[LED_NUM]; /* 打开时电平: 高或低 */
u8 clk_shft_bits[LED_NUM];
u32 pin[LED_NUM]; /* led引脚信息 */
struct vir_regs vir_io[LED_NUM];
struct phy_regs phy_io[LED_NUM];
struct led_ops ops;
};
static void led_ctrl(u8 idx, u8 status);
static int led_init(u8 idx);
static int led_exit(u8 idx);
/* gled设备 */
static struct gled_device gled_dev = {
.name = "gled_dev",
.dft_status = { OFF, ON },
.on_level = {LOW, LOW },
.clk_shft_bits = {26, 30},
.pin = {3, 1},
.phy_io = {
{ /* IO物理地址 */
.ccgr = 0x020C406C,
.sw_mux = 0x020E0068,
.sw_pad = 0x020E02F4,
.dr = 0x0209C000,
.dir = 0x0209C004
},
{
.ccgr = 0x020C406C,
.sw_mux = 0x0229000C,
.sw_pad = 0x02290050,
.dr = 0x020AC000,
.dir = 0x020AC004
},
},
.ops = {
.num = LED_NUM,
.led_init = led_init,
.led_exit = led_exit,
.led_ctrl = led_ctrl,
},
};
/**
* @brief : 打开/关闭LED
* @par : status LEDON('O') 打开LED, LEDOFF('C') 关闭LED
* @retval : 无
*/
static void led_ctrl(u8 idx, u8 status)
{
u32 val = 0;
if (gled_dev.vir_io[idx].ccgr == NULL)
return;
if (status == ON) {
val = readl(gled_dev.vir_io[idx].dr);
if (gled_dev.on_level[idx] == HIGH) {
val |= (1 << (gled_dev.pin[idx]));
} else if (gled_dev.on_level[idx] == LOW) {
val &= ~(1 << gled_dev.pin[idx]);
}
writel(val, gled_dev.vir_io[idx].dr);
printk("The led%d is openned!\r\n", idx);
} else if (status == OFF) {
val = readl(gled_dev.vir_io[idx].dr);
if (gled_dev.on_level[idx] == HIGH) {
val &= ~(1 << gled_dev.pin[idx]);
} else if (gled_dev.on_level[idx] == LOW) {
val |= (1 << (gled_dev.pin[idx]));
}
writel(val, gled_dev.vir_io[idx].dr);
printk("The led%d is closed!\r\n", idx);
} else {
printk("Recived parameter is error!\r\n");
}
}
/**
* @brief LED状态初始化
* @param idx LED
*/
static int led_init(u8 idx)
{
u32 val;
if (idx >= gled_dev.ops.num) {
return -EIO;
}
/* 寄存器地址映射 */
gled_dev.vir_io[idx].ccgr = ioremap(gled_dev.phy_io[idx].ccgr , 4);
gled_dev.vir_io[idx].sw_mux = ioremap(gled_dev.phy_io[idx].sw_mux, 4);
gled_dev.vir_io[idx].sw_pad = ioremap(gled_dev.phy_io[idx].sw_pad, 4);
gled_dev.vir_io[idx].dr = ioremap(gled_dev.phy_io[idx].dr , 4);
gled_dev.vir_io[idx].dir = ioremap(gled_dev.phy_io[idx].dir , 4);
/* 使能GPIO时钟 */
val = readl(gled_dev.vir_io[idx].ccgr);
val |= (3 << gled_dev.clk_shft_bits[idx]);
writel(val, gled_dev.vir_io[idx].ccgr);
/* 设置gpio1_io03的复用功能 */
writel(5, gled_dev.vir_io[idx].sw_mux);
/* 寄存器sw_mux1_io03设置IO属性 */
writel(0x10B0, gled_dev.vir_io[idx].sw_pad);
/* 设置gpio1_gdir为输出功能 */
val = readl(gled_dev.vir_io[idx].dir);
val |= (1 << gled_dev.pin[idx]);
writel(val, gled_dev.vir_io[idx].dir);
/* 设置led为默认状态 */
val = readl(gled_dev.vir_io[idx].dr);
if (gled_dev.dft_status[idx] == ON) {
if (gled_dev.on_level[idx] == HIGH) {
val |= (1 << gled_dev.pin[idx]);
} else if (gled_dev.on_level[idx] == LOW) {
val &= ~(1 << gled_dev.pin[idx]);
}
} else if (gled_dev.dft_status[idx] == OFF) {
if (gled_dev.on_level[idx] == HIGH) {
val &= ~(1 << gled_dev.pin[idx]);
} else if (gled_dev.on_level[idx] == LOW) {
val |= (1 << gled_dev.pin[idx]);
}
}
writel(val, gled_dev.vir_io[idx].dr);
return 0;
}
/**
* @brief LED状态解初始化
* @param idx LED
*/
static int led_exit(u8 idx)
{
if (idx >= gled_dev.ops.num) {
return -EIO;
}
iounmap(gled_dev.vir_io[idx].ccgr);
iounmap(gled_dev.vir_io[idx].sw_mux);
iounmap(gled_dev.vir_io[idx].sw_pad);
iounmap(gled_dev.vir_io[idx].dr);
iounmap(gled_dev.vir_io[idx].dir);
return 0;
}
/**
* @brief Get the led ops object
*/
void * get_led_ops(void)
{
return (void *)(&(gled_dev.ops));
}
/* 模块的许可证声明 */
MODULE_LICENSE("GPL");
/* 模块的作者声明 */
MODULE_AUTHOR("glen");
- led_drv.c为驱动相关函数
/*
* 文件名 : led_drv.c
* 作者 : glen
* 描述 : led_drv驱动文件
*/
#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/device.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "led_dev.h"
/* LED驱动结构体 */
struct gled_driver {
struct class *drv_class;
char *name;
dev_t major;
struct led_ops *ops;
};
/* gled驱动 */
static struct gled_driver gled_drv = {
.name = "gled_drv",
.major = 0,
.ops = NULL,
};
/**
* @brief Get the led ops object
*/
// __weak void *get_led_ops(void)
// {
// return NULL;
// }
/**
* @brief : 打开设备
* @par : inode 传递给驱动的inode
* filp 设备文件, file结构体有个private_data的成员变量, 一般
* 在open的时候将private_data指向设备结构体
* @retval : 0 成功, 其它 失败
*/
static int led_drv_open(struct inode *inode, struct file *filp)
{
int minor = iminor(inode);
struct led_ops *ops = gled_drv.ops;
if (ops == NULL) {
printk("Please register led_ops instance! \n");
return 0;
}
if (ops->led_init)
if (ops->led_init(minor) == 0)
printk("The led%d has openned successfully! \n", minor);
else
printk("The led%d has openned failure! \n", minor);
else
printk("Please register led_ops instance! \n");
return 0;
}
/**
* @brief : 从设备读取数据
* @par : filp 要打开的设备文件(文件描述符)
* buf 返回给用户空间的数据缓冲区
* cnt 要读取的数据长度
* offt 相对于文件首地址的偏移
* @retval : 读取数据长度, 若为负值则表示读取失败
*/
static ssize_t led_drv_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/**
* @brief : 向字符设备写入数据
* @par : filp 设备文件, 表示打开的文件描述符
* buf 要写给设备的数据
* cnt 要写入的数据长度
* offt 相对于文件首地址的偏移
* @retval : 写入数据长度, 若为负值则表示读取失败
*/
static ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
u8 data_buf[1];
u8 led_sta;
int minor;
struct inode *inode = file_inode(filp);
struct led_ops *ops = gled_drv.ops;
if (ops == NULL) {
printk("Please register led_ops instance! \n");
return 0;
}
minor = iminor(inode);
ret = copy_from_user(data_buf, buf, 1);
if (ret < 0) {
printk("kenel recive data: %s\r\n", buf);
return -EFAULT;
}
/* 获取状态值 */
led_sta = data_buf[0];
if (ops->led_ctrl)
ops->led_ctrl(minor, led_sta);
return 0;
}
/**
* @brief : 打开设备
* @par : inode 传递给驱动的inode
* filp 要关闭的设备文件(文件描述符)
* @retval : 0 成功, 其它 失败
*/
static int led_drv_release(struct inode *inode, struct file *filp)
{
u8 minor = iminor(inode);
struct led_ops *ops = gled_drv.ops;
if (ops == NULL) {
printk("Please register led_ops instance! \n");
return 0;
}
if (minor >= ops->num)
return -EINVAL;
/* 虚拟地址映射销 */
if (ops->led_exit)
ops->led_exit(minor);
printk("The led driver is closed! %s, %s, line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static struct file_operations led_drv_fops = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_release,
};
/**
* @brief : 驱动初始化函数
* @par : 无
* @retval : 无
*/
static int __init led_drv_init(void)
{
u8 err = 0;
u8 i;
/* register char device driver */
gled_drv.major = register_chrdev(gled_drv.major, gled_drv.name, &led_drv_fops);
if (gled_drv.major == 0) {
printk("gled driver: Unable to register driver!\n");
return -EIO;
}
printk("gled driver: Register driver successfully!\n");
gled_drv.drv_class = class_create(THIS_MODULE, "gled");
if (IS_ERR(gled_drv.drv_class)) {
err = PTR_ERR(gled_drv.drv_class);
goto out_chrdev;
}
gled_drv.ops = (struct led_ops *)get_led_ops();
if (gled_drv.ops == NULL) {
printk("Please register led_ops instance! \n");
err = PTR_ERR(gled_drv.ops);
goto out_chrdev;
}
for (i = 0; i < gled_drv.ops->num; i++)
device_create(gled_drv.drv_class, NULL, MKDEV(gled_drv.major, i), NULL, "gled%d", i);
return 0;
out_chrdev:
unregister_chrdev(gled_drv.major, gled_drv.name);
return err;
}
/**
* @brief : 驱动退出函数
* @par : 无
* @retval : 无
*/
static void __exit led_drv_exit(void)
{
u8 i;
for (i = 0; i < gled_drv.ops->num; i++)
/* 销毁设备 */
device_destroy(gled_drv.drv_class, MKDEV(gled_drv.major, i));
class_destroy(gled_drv.drv_class);
/* 注销字符设备驱动 */
unregister_chrdev(gled_drv.major, gled_drv.name);
}
/* assign the functions as driver's import function and export function*/
module_init(led_drv_init);
module_exit(led_drv_exit);
/* insert license for module */
MODULE_LICENSE("GPL");
/* insert author information for module */
MODULE_AUTHOR("glen");
- Makefile稍作更改,将两个led_drv.c led_dev.c编译进 led_driver.ko
KERNELDIR := /home/glen/linux/imx6ull/linux/glen_linux
CURRENT_PATH := $(shell pwd)
led_driver-y := led_drv.o led_dev.o
obj-m += led_driver.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
【注】:本来是要编译成两个.ko文件,结果led_drv.ko死活insmod不成功,应该是依赖关系没整好,后面再研究吧。
4. 测试程序不做更改,验证参考Linux学习笔记(15)——LED设备驱动