可以一个驱动连多个设备
led-char.c
#include "led.h"
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#define LED_DEVICE_MAJOR 230
#define LED_DEVICE_MINOR 0
#define LED_ON_CMD 0x11
#define LED_OFF_CMD 0x22
#define LED_CON_OFFSET 0
#define LED_DAT_OFFSET 4
MODULE_LICENSE("GPL v2");
//前面都是在应用层打开的时候会运行
struct led_device *pled;
void led_init(struct led_device *pled)
{
//gpio ouput mode
int pin = pled->des.pin;
int bits = pled->des.bits;
int mode = pled->des.mode;
int mask = (1 << bits) - 1;//计算小方法
int regval = readl(pled->regaddr + LED_CON_OFFSET);
//readl() 从内存映射的 I/O 空间读取数据,readl 从 I/O 读取 32 位数据 ( 4 字节 )。
regval = regval & ~(mask << (pin * bits));//修改IO空间里面的值
regval = regval | (mode << (pin * bits));
writel(regval,pled->regaddr + LED_CON_OFFSET);
//往内存映射的 I/O 空间上写数据,wirtel() I/O 上写入 32 位数据 (4字节)。
return;
}
void led_on(struct led_device *pled)
{
//都是在虚拟地址上,在驱动当中映射过,因此需要用虚拟地址
int level = pled->des.level;
int pin = pled->des.pin;
int regval = readl(pled->regaddr + LED_DAT_OFFSET);
regval = regval & ~(0x1 << pin);
regval = regval | (level << pin);
writel(regval,pled->regaddr + LED_DAT_OFFSET);
return;
}
void led_off(struct led_device *pled)
{
int level = !pled->des.level;
int pin = pled->des.pin;
int regval = readl(pled->regaddr + LED_DAT_OFFSET);
regval = regval & ~(0x1 << pin);
regval = regval | (level << pin);
writel(regval,pled->regaddr + LED_DAT_OFFSET);
return;
}
//当应用层open的时候,通过设备地址找到设备的inode(只要是文件都会有inode),同时也会给文件
//创建file对象,通过inode里面的信息得到是字符设备后,寻找对应的字符设备,然后将cdev里面的
//地址拷贝到inode当中,也会将驱动当中的file_operation拷贝到文件虚拟系统层文件对象file当中,
//内核中用inode结构表示具体的文件,而用file结构表示打开的文件描述符。因此上层应用必须传fd、
//这个参数,只有open的时候是不需要的
//文件最多打开1024次,每次打开都会重新找设备
static int led_device_open(struct inode *inode, struct file *file)
{
//inode里面记录了cdev的首地址,通过此函数可以推算出led_device的首地址
struct led_device *pled = container_of(inode->cdev,struct led_device,led_cdev);
file->private_data = pled;
printk("led device open\n");
led_init(pled);
return 0;
}
static int led_device_close( struct inode *inode, struct file *file )
{
printk("led device close\n");
iounmap(pled->regaddr);//关闭映射
return 0;
}
//上次应用的ioctl函数的第二个参数会传入cmd
static long led_device_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct led_device *pled = file->private_data;
switch(cmd){
case LED_ON_CMD:
printk("led cmd on\n");
led_on(pled);
break;
case LED_OFF_CMD:
printk("led cmd off\n");
led_off(pled);
break;
}
return 0;
}
//从这个结构体之下都是在加载驱动或者卸载驱动的时候有关系
//设备操作的函数接口对象
static const struct file_operations led_device_ops = {
.owner = THIS_MODULE,
.open = led_device_open,
.release = led_device_close,
.unlocked_ioctl = led_device_ioctl,
};
//手动注册字符设备
struct led_device *register_led_chardev(void)
{
int err;
static int devnum = 0;
struct led_device *pled;
printk("led driver init\n");
//创建cdev对象
pled = kmalloc(sizeof(*pled),GFP_KERNEL);
if(!pled){
printk("Fail to kmalloc\n");
err = -ENOMEM;
goto err_kmalloc;
}
//pled->led_cdev.ops = &led_device_ops;
//这个作用是将实现的函数放到cdev成员当中,最终会将拷贝到文件虚拟
//层当中的file_operation当中,实现向上层提供设备的操作函数接口
cdev_init(&pled->led_cdev,&led_device_ops);
//注册设备号
pled->devid = MKDEV(LED_DEVICE_MAJOR,LED_DEVICE_MINOR + devnum);
err = register_chrdev_region(pled->devid,1,"led-device");
if(err){
printk("Fail to register_chrdev_region\n");
goto err_register_chrdev_region;
}
devnum ++;
//根据设备号来添加字符设备
err = cdev_add(&pled->led_cdev,pled->devid,1);
if(err){
printk("Fail to cdev_add\n");
goto err_cdev_add;
}
//创建类:在/sys/class/目录 因为只能创建一次因此需要放到
//模块初始化函数当中比较合适
#if 0
if(devnum){
pled->cls = class_create(THIS_MODULE, "fs4412-leds");
if (IS_ERR(pled->cls)){
printk("Fail to class_create\n");
err = PTR_ERR(pled->cls);
goto err_class_create;
}
}
#endif
//创建设备:在/sys/class/目录/设备,第五个参数决定设备文件名,设备树上面那个只是为了匹配驱动
//这里的名字一定要与用户空间保持一致
pled->dev = device_create(led_cls,NULL,pled->devid,NULL,"led%d",devnum);
if(IS_ERR(pled->dev)){
printk("Fail to device_create\n");
err = PTR_ERR(pled->dev);
goto err_device_create;
}
return pled;
err_device_create:
cdev_del(&pled->led_cdev);
err_cdev_add:
unregister_chrdev_region(pled->devid,1);
err_register_chrdev_region:
kfree(pled);
err_kmalloc:
return ERR_PTR(err);
}
void unregister_led_chardev(struct led_device *pled)
{
printk("led driver exit\n");
#if 0
device_destroy(pled->cls,pled->devid);
#endif
cdev_del(&pled->led_cdev);
unregister_chrdev_region(pled->devid,1);
kfree(pled);
return;
}
led-driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include "led.h"
#include <linux/io.h>
//平台总线上面挂驱动,
MODULE_LICENSE("GPL v2");
struct class *led_cls;
int of_parse_led_dt(struct device_node *np,struct led_des *info)
{
int err;
int value;
//手动解析内核未规定的。这些都是自己在设备树上规定的
err = of_property_read_u32(np, "pin", &value);
if(err) {
printk("fail to get property pin\n");
return err;
}
printk("pin : %d\n",value);
info->pin = value;
err = of_property_read_u32(np, "bits", &value);
if(err) {
printk("fail to get property bits\n");
return err;
}
info->bits = value;
printk("bits : %d\n",value);
err = of_property_read_u32(np, "mode", &value);
if(err) {
printk("fail to get property mode\n");
return err;
}
info->mode = value;
printk("mode : %d\n",value);
err = of_property_read_u32(np, "level", &value);
if(err) {
printk("fail to get property level\n");
return err;
}
info->level = value;
printk("level : %d\n",value);
return 0;
}
static int led_probe(struct platform_device *pdev)
{
int err;
void *regaddr;
struct led_des info;
struct resource *res;
struct led_device *pled;//在另外一个文件可以定义指针(4字节)
printk("led probe,device name:%s\n",pdev->name);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//
if(!res){
printk("Fail to platform_get_resource\n");
return -ENODEV;
}
printk("resource reg addr : %#x\n",(unsigned int)res->start);
//把物理地址映射成虚拟地址
regaddr = devm_ioremap(&pdev->dev,res->start,resource_size(res));
if(!regaddr){
printk("fail to devm_ioremap\n");
return -ENOMEM;
}
//解析设备树节点的属性
err = of_parse_led_dt(pdev->dev.of_node,&info);
if(err){
printk("fail to of_parse_led_dt\n");
return err;
}
//注册字符设备
pled = register_led_chardev();
if(IS_ERR(pled)){
printk("fail to register_led_chardev\n");
return PTR_ERR(pled);
}
//记录设备信息
pled->regaddr = regaddr;
pled->des = info;
//把自定义设备数据放到平台设备结构体当中
platform_set_drvdata(pdev, pled);
return 0;
}
static int led_remove(struct platform_device *pdev)
{ //通过平台设备也能获得相应的自定义设备
struct led_device *pled = platform_get_drvdata(pdev);
unregister_led_chardev(pled);//注销设备
return 0;
}
static const struct of_device_id led_of_match[] = {
{.compatible = "led1"},
{.compatible = "led2"},
{.compatible = "led3"},
{.compatible = "led4"},
{/*Sentinel */} //这东西不能丢
};
MODULE_DEVICE_TABLE(of, led_of_match);
struct platform_driver led_driver = {
.probe = led_probe, //加载的时候也就是匹配成功自动调用
.remove = led_remove,//卸载的时候也调用
.driver = {
.name = "fs4412-led",
.owner = THIS_MODULE,
.of_match_table = led_of_match,
},
};
int led_driver_init(void)
{
//创建设备的类,之前是放到添加设备之前。现在提前建好,是为了与
//多设备进行匹配。防止多次建立类和多次销毁类
led_cls = class_create(THIS_MODULE, "fs4412-leds");
if (IS_ERR(led_cls)){
printk("Fail to class_create\n");
return PTR_ERR(led_cls);
}
platform_driver_register(&led_driver);
return 0;
}
void led_driver_exit(void)
{
platform_driver_unregister(&led_driver);
class_destroy(led_cls);
return;
}
module_init(led_driver_init);
module_exit(led_driver_exit);
led-app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define LED_ON_CMD 0x11
#define LED_OFF_CMD 0x22
#define MAX_LEDS 4
int main(int argc, const char *argv[])
{
int fds[MAX_LEDS];
int i;
char device_name[1024];
//,这里的设备名字要与创建的设备名保持一致才行,并不是设备树上的名字
for (i = 0;i < MAX_LEDS; ++i){
sprintf(device_name,"/dev/led%d",i + 1);
fds[i] = open(device_name,O_RDWR);
if(fds[i] < 0){
perror("fail to open");
return -1;
}
}
while(1){
for(i = 0;i < MAX_LEDS;i++){
ioctl(fds[i],LED_ON_CMD);
sleep(1);
ioctl(fds[i],LED_OFF_CMD);
sleep(1);
}
}
for(i = 0;i < MAX_LEDS;i++){
close(fds[i]);
}
return 0;
}
led.h
#ifndef __LED_HEAD_H__
#define __LED_HEAD_H__
#include <linux/types.h>
#include <linux/cdev.h>
struct led_des
{
int pin;
int bits;
int mode;
int level;
};
struct led_device{
dev_t devid;
void *regaddr;
struct led_des des;
struct device *dev;
struct cdev led_cdev;
};
extern struct class *led_cls;//led-char.c和led-driver.c都需要用
extern struct led_device *register_led_chardev(void);
extern void unregister_led_chardev(struct led_device *pled);
#endif
Makefile文件
ifeq ($(KERNELRELEASE),)
X86_KERNEL_BUILD = /lib/modules/$(shell uname -r)/build
ARM_KERNEL_BUILD = /home/linux/fs4412/kernel/linux-3.14
MODULE_PATH = $(shell pwd)
x86_Module:
make -C $(X86_KERNEL_BUILD) M=$(MODULE_PATH) modules
arm_module:
make -C $(ARM_KERNEL_BUILD) M=$(MODULE_PATH) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
cp led-char-driver.ko /home/linux/fs4412/rootfs
arm-none-linux-gnueabi-gcc led-app.c -o /home/linux/fs4412/rootfs/led-app
clean:
make -C $(X86_KERNEL_BUILD) M=$(MODULE_PATH) clean
else
obj-m = led-char-driver.o
led-char-driver-objs = led-char.o led-driver.o
endif