说道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驱动就写完了,需要注意的事,在写内核相关函数的,一定要注意错误处理,任何警告一类的信息都不能放过,否则会引起内核崩溃的
写的比较乱了,想到什么写什么了,有什么问题大家可以告诉我,学习了