我的驱动开始之路-----LED驱动(作为字符设备)

       说道led,相信这是大家学习硬件第一个要做的实验,现在将其封装成驱动,下有硬件操作(点灯,灭灯),上有用户接口,一个驱动的基本模型页就搭建起来了,有什么错误或号的想法;

      大家一定要告诉我,大家一起学习了;

     开发环境:Linux;内核版本3.4.24;开发板,smdk6410

     我将LED基于字符设备写;

    先是头文件:s3c_led.h,在这里面就是定义了相关的信息,和需要的宏

    在写驱动的时候要尽量保证可移植性,所以只要改变一个头文件和设备文件的内容,就可移植到别的平台的上,这是比较好的效果。

#ifndef __S3C_LED_H
#define __S3C_LED_H

#include <linux/cdev.h>

//给led设备分配一个结构体,将器需要的全部信息放到这个结构体下
struct led_info {
	void __iomem *v;//虚拟地址
	int status;
	int user;
	dev_t no;//设备号
	struct cdev dev;//字符设备确定的结构体
	void (*on)(struct led_info *l, int no);//函数指针
	void (*off)(struct led_info *l, int no);
};
 //一下是相关宏定义
#define S3C_PA_LED	0x7f008000
#define S3C_SZ_LED	SZ_4K
#define GPMCON		0x820
#define GPMDAT		0x824

//status
#define S_LED_OFF_ALL	(0x0)
#define S_LED_OFF(x)	(~(0x1 << (x)))
#define S_LED_OFF0	(~0x1)
#define S_LED_OFF1	(~0x2)
#define S_LED_OFF2	(~0x4)
#define S_LED_OFF3	(~0x8)

#define S_LED_ON_ALL	(0xf) 
#define S_LED_ON(x)	(0x1 << (x))
#define S_LED_ON0	(0x1)
#define S_LED_ON1	(0x2)
#define S_LED_ON2	(0x4)
#define S_LED_ON3	(0x8)

//GPMCON
#define GPM0_3_MASK	~0xffff
#define GPM0_3_OUT	0x1111
#define GPM0_3_HIGH	0xf
#define GPM_LOW(x)	~(1 << (x))
#define GPM_HIGH(x)	(1 << (x))

#endif

接下来写设备相关的文件,嵌入式中由于各设备没有实际的物理总线对应,所以linux内核为了方便写,内核给虚拟了一条platform总线,我就是基于这条总线写的:

注意,现在是作为模块写的,到时候真正有用的驱动是要编入内核的,

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include "s3c_led.h"

void led_release(struct device *dev)
{
	printk("Device is released\n");
}
//设备资源
struct resource led_res[] = {
	[0] = {
		.start = S3C_PA_LED,
		.end = S3C_PA_LED + S3C_SZ_LED - 1,
		.flags = IORESOURCE_MEM,
		.name = "led_res",
	},
};
//platform总线上的设备结构体
struct platform_device dev = {
	.name = "s3c-led",//match
	.id = -1,	//-1代表自动分配
	.num_resources = ARRAY_SIZE(led_res),
	.resource = led_res,
	.dev = {
		.release = led_release,
	}	
};

static __init int module_test_init(void)
{
	return platform_device_register(&dev);
}

static __exit void module_test_exit(void)
{
	platform_device_unregister(&dev);
}

module_init(module_test_init);
module_exit(module_test_exit);

MODULE_LICENSE("GPL");//开源规则
MODULE_AUTHOR("Musesea");//作者
MODULE_VERSION("1.0");//版本
MODULE_DESCRIPTION("Test for module");//描述
写设备和头文件都是小事,这些都是很简单的,主要的就是写驱动了,一定要注意其移植性,文件中尽量不要出现数字等,都用宏定义在头文件中
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>

#include "s3c_led.h"

void s3c_led_on(struct led_info *l, int no)
{
        u32 tmp;

        tmp = readl(l->v + GPMDAT);
        tmp &= GPM_LOW(no);
        writel(tmp, l->v + GPMDAT);
}

void s3c_led_off(struct led_info *l, int no)
{
        u32 tmp;

        tmp = readl(l->v + GPMDAT);
        tmp |= GPM_HIGH(no);
        writel(tmp, l->v + GPMDAT);
}

void s3c_led_init(struct led_info *l)
{
//readl/ioread32  readw/ioread16  readb/ioread8
//writel/iowrite32 writew/iowrite16 writeb/iowrite8
        u32 tmp;

        tmp = readl(l->v + GPMCON);
        tmp &= GPM0_3_MASK;
        tmp |= GPM0_3_OUT;
        writel(tmp, l->v + GPMCON);

        tmp = readl(l->v + GPMDAT);
        tmp |= GPM0_3_HIGH;
        writel(tmp, l->v + GPMDAT);

        l->on = s3c_led_on;
        l->off = s3c_led_off;
}

void s3c_led_exit(struct led_info *l)
{
        u32 tmp;

        tmp = readl(l->v + GPMDAT);
        tmp |= GPM0_3_HIGH;
        writel(tmp, l->v + GPMDAT);
}
//---------------------------------------------以下都是与硬件无关的,套路固定
ssize_t lread(struct file *fp, char __user *buffer, size_t count, loff_t *offset)
{
        char buf[32];
        int ret = 0;
        struct led_info *l = fp->private_data;

        ret += sprintf(buf + ret, "status:%x\n", l->status);
        if(copy_to_user(buffer, buf, ret))
                return -EFAULT;

        return ret;
}
//这是在用户态留下的函数接口,写文件时取得命令,第一个数字代表几号灯,第二个数字,1代表开,0 代表关;
ssize_t lwrite(struct file *fp, const char __user *buffer, size_t count, loff_t *offset)
{
        struct led_info *l = fp->private_data;

	if(buffer[1] == '1'){
                l->on(l, buffer[0] - '0');
                l->status |= S_LED_ON(buffer[0] - '0');
        }else{
                l->off(l, buffer[0] - '0');
                l->status &= S_LED_OFF(buffer[0] - '0');
        }
 
        return count;
}

int lopen(struct inode *no, struct file *fp)
{
        struct led_info *l = container_of(no->i_cdev, struct led_info, dev);

        fp->private_data = l;

        if(!l->user)
                l->user++;
        else
                return -EBUSY;

        return 0;
}

int lrelease(struct inode *no, struct file *fp)
{
        struct led_info *l = container_of(no->i_cdev, struct led_info, dev);

        if(l->user)
                l->user--;
        else
                return -ENODEV;

        return 0;
}
//文件操作集结构体
struct file_operations led_ops = {
        .open = lopen,
        .release = lrelease,
        .write = lwrite,
        .read = lread,
        //.ioctl = lioctl,
};

int led_probe(struct platform_device *pdev)
{
	struct resource *led_res;
	struct led_info *led;
	int ret;

	//1.获得资源
	led_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if(!led_res)
		return -EBUSY;
	//2.分配设备结构体	
	led = kzalloc(sizeof(struct led_info), GFP_KERNEL);
        if(!led)
                return -ENOMEM;

        led->status = S_LED_OFF_ALL;
        led->user = 0;
	//3.注册字符设备
        ret = alloc_chrdev_region(&led->no, 0, 1, pdev->name);
        if(ret)
                goto alloc_no_error;

        cdev_init(&led->dev, &led_ops);
        ret = cdev_add(&led->dev, led->no, 1);
        if(ret)
                goto cdev_add_error;

	//4.映射外设内存
        led->v = ioremap(led_res->start, led_res->end - led_res->start + 1);
        if(!led->v){
                ret = -ENOMEM;
                goto remap_error;
        }

        s3c_led_init(led);

	//把led保存到pdev中
	platform_set_drvdata(pdev, led);

	return 0;
remap_error:
	cdev_del(&led->dev);	
cdev_add_error:
	unregister_chrdev_region(led->no, 1);
alloc_no_error:
	kfree(led);
	return ret;
}
//就是probe函数的反操作,先执行的后释放的顺序
int led_remove(struct platform_device *pdev)
{
	struct led_info *led;

	led = platform_get_drvdata(pdev);
	s3c_led_exit(led);
	iounmap(led->v);	
	cdev_del(&led->dev);	
	unregister_chrdev_region(led->no, 1);
	kfree(led);

	return 0;
}

void led_shutdown(struct platform_device *pdev)
{

}

int led_suspend(struct platform_device *pdev, pm_message_t state)
{
	return 0;
}

int led_resume(struct platform_device *pdev)
{
	return 0;
}

struct platform_driver drv = {
	.probe = led_probe,//匹配成功
	.remove = led_remove,//设备或者驱动注销
	.shutdown = led_shutdown,//关机
	.suspend = led_suspend,//待机  睡眠
	.resume = led_resume,//唤醒
	.driver = {
		.name = "s3c-led",//在驱动和设备匹配的时候会用到这个名字,所以必须保证与设备名一致
	}
};

static __init int module_test_init(void)
{
	return platform_driver_register(&drv);
}

static __exit void module_test_exit(void)
{
	platform_driver_unregister(&drv);
}

module_init(module_test_init);
module_exit(module_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Musesea");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("Test for module");

至此一个完整的led驱动就写完了,需要注意的事,在写内核相关函数的,一定要注意错误处理,任何警告一类的信息都不能放过,否则会引起内核崩溃的

写的比较乱了,想到什么写什么了,有什么问题大家可以告诉我,学习了


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值