上一篇寫的是如何用himm工具來控制寄存器點燈,這次寫個驅動試試!
- 驅動源碼,利用網上模板修改:
/************************led_drv.c*******************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#define HELLO_MAGIC 12
#define LED_NUM_ON _IOW(HELLO_MAGIC,0, int)//設置復位,這個命令不帶參數
#define LED_NUM_OFF _IOW(HELLO_MAGIC,1, int)//獲取當前設備的語言類型參數,參數是int型
//設計一個全局的設備對象類
struct hi3559av100_led
{
int dev_major ;
struct class *cls;
struct device *dev;
int value; // 用於存放用戶的數據
};
//聲明一個對象
struct hi3559av100_led *led_dev;
//定義 gpio管腳的輸出方向和data寄存器變量
volatile unsigned long *gpio3_6_conf;
volatile unsigned char *gpio3_6_data;
int led_drv_open(struct inode *inode, struct file *filp)
{
printk("-------^_^ %s-------\n", __FUNCTION__);
*gpio3_6_conf |= (0x1<<6);
return 0;
}
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
int ret;
printk("-------^_^ %s-------\n", __FUNCTION__);
// 區分應用的需求
//如果要從用戶空間獲取數據,需要用特定的函數
// 從用戶空間獲取數據, 一般都用在驅動中寫操作中-- xxx_write
// 參數1---目標地址---內核中的空間的地址
//參數2---原地址---用戶空間的地址
//參數3---拷貝數據個數
//返回值--沒有拷貝成功的數據個數, 0表示成功
ret = copy_from_user(&led_dev->value, buf, count);
if(ret > 0)
{
printk(KERN_ERR "copy_from_user error\n");
return -EFAULT;
}
printk("-------value:%d-------\n", led_dev->value);
if(led_dev->value)
{
//點燈
*gpio3_6_data |= (0x1<<6);
printk("-------^_^ 點燈-------\n");
}
else
{
//滅燈
*gpio3_6_data &= ~(0x1<<6);
printk("-------^_^ 滅燈-------\n");
}
//返回傳遞的數據個數
return count;
}
int led_drv_close(struct inode *inode, struct file *filp)
{
printk("-------^_^ %s-------\n", __FUNCTION__);
return 0;
}
long led_drv_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
switch(cmd)
{
case LED_NUM_ON :
//點燈
printk("點燈 cmd\n");
*gpio3_6_data |= (0x1<<6);
break;
case LED_NUM_OFF:
//滅燈
printk("滅燈 cmd\n");
*gpio3_6_data &= ~(0x1<<6);
break;
default :
printk("unkown cmd\n");
return -EINVAL;
}
return 0;
}
const struct file_operations led_fops = {
.open = led_drv_open,
.write = led_drv_write,
.release = led_drv_close,
.unlocked_ioctl = led_drv_ioctl,
};
static int __init led_drv_init(void)
{
/*
編寫驅動的套路
0, 實例化全局的設備對象-- kzalloc
1, 申請主設備號---register_chrdev
2, 自動創建設備節點---class_create, device_create
3, 初始化硬件--ioremap
4,實現 file_operation
*/
// 模塊加載函數中主要完成系統資源的申請
printk("-------^_^ %s-------\n", __FUNCTION__);
int ret;
// 0, 實例化全局的設備對象
//參數1---分配大小
//參數2--分配的標誌, GFP_KERNEL--如果當前暫時沒有內存,會嘗試等待
led_dev = kzalloc(sizeof(struct hi3559av100_led), GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR"kzalloc error\n");
return -ENOMEM;
}
// 1, 申請主設備號
led_dev->dev_major = 0;
ret = register_chrdev(led_dev->dev_major, "led_drv", &led_fops);
if(ret < 0)
{
printk("register_chrdev error\n");
ret = -EINVAL;
goto err_free;
}
// 2 ---自動創建設備節點
//創建一個類
// 參數1---當前模塊--THIS_MODULE
// 參數2---字符串,表示類的名字
//返回值--struct class指針類型
led_dev->cls = class_create(THIS_MODULE,"led_cls");
if(IS_ERR(led_dev->cls))
{
printk("class_create error\n");
ret = PTR_ERR(led_dev->cls);
goto err_unregister;
}
//創建一個設備節點
// 參數1---class_create返回的指針
// 參數2---該設備非父類--一般都是填NULL
//參數3--設備號--包含了主設備號major和次設備號minor
//參數4---私有數據指針---一般都是填NULL
//參數5---設備節點的名字
//結果 /dev/led
// 返回值--struct device指針
led_dev->dev = device_create(led_dev->cls, NULL,MKDEV(led_dev->dev_major, 0), NULL, "led");
if(IS_ERR(led_dev->dev))
{
printk("device_create error\n");
ret = PTR_ERR(led_dev->dev);
goto err_class_destroy;
}
// 3, 初始化硬件
//參數1---物理地址
//參數2--映射的長度
//返回值--映射之後的虛擬地址
gpio3_6_conf = ioremap(0x180D3400, 16);
gpio3_6_data = ioremap(0x180D3100, 8);
*gpio3_6_conf |= (0x1<<6);
*gpio3_6_data |= (0x1<<6);
return 0;
err_class_destroy:
class_destroy(led_dev->cls);
err_unregister:
unregister_chrdev(led_dev->dev_major, "led_drv");
err_free:
kfree(led_dev);
return ret;
}
static void __exit led_drv_exit(void)
{
printk("-------^_^ %s-------\n", __FUNCTION__);
*gpio3_6_data &= ~(0x1<<6);
iounmap(gpio3_6_conf);
iounmap(gpio3_6_data);
// 模塊卸載函數中主要完成系統資源的釋放
device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
class_destroy(led_dev->cls);
//參數1---已經申請到的設備號
//參數2--字符串--描述設備驅動信息--自定義
unregister_chrdev(led_dev->dev_major, "led_drv");
kfree(led_dev);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
- makefile :
# Makefile 4.0
# CC = aarch64-himix100-linux-gcc
ARCH := arm64
CROSS_COMPILE := aarch64-himix100-linux-gcc
CURRENT_PATH := $(shell pwd)
# LINUX_KERNEL := $(shell uname -r)
#LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
LINUX_KERNEL_PATH := /home/xiangang/work/haisi/Hi3559AV100R001C02SPC030/01.software/board/Hi3559AV100_SDK_V2.0.3.0/osdrv/opensource/kernel/linux-4.9.y_multi-core
obj-m := hello_ko.o
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
- 測試應用程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#define HELLO_MAGIC 12
#define LED_NUM_ON _IOW(HELLO_MAGIC,0,int)//
#define LED_NUM_OFF _IOW(HELLO_MAGIC,1,int)//
int main(int argc, char **argv)
{
int fd;
char* filename=NULL;
char val;
filename = argv[1];
fd = open(filename, O_RDWR);//打開dev/led設備文件
if (fd < 0)//小於0說明沒有成功
{
printf("error, can't open %s\n", filename);
return 0;
}
if(!strcmp(argv[2], "on"))
{
val = 1;
ioctl(fd, LED_NUM_ON, &val);
}
else
{
val = 0;
ioctl(fd, LED_NUM_OFF, &val);
}
// write(fd, &val, 1);//操作LED
return 0;
}
- 實驗
insmod hello_ko.ko //加載驅動
cat /proc/devices //查看驅動主設備號
mknod /dev/led c 253 0 //創建設備節點
//應用測試
./test /dev/led on //點燈
./test /dev/led off //滅燈