目录
3.5.2 平台设备对象部分程序示例(plat_led_pdev.c)
3.5.3 平台驱动对象部分程序示例(plat_led_pdrv.c)
3.5.4 平台驱动对象部分程序示例(led_test.c)
1.理解图
2. 相关API
2.1 总线对象
描述总线信息,管理设备和驱动匹配
struct bus_type
{
const char *name;//总线名字
int (*match)(struct device *dev, struct device_driver *drv);//总线进行匹配的
设备device:
驱动driver:
函数
//匹配时机:当总线上添加了新设备或新驱动,内核会调用一次或多次这个函数;匹配成功:
通过匹配函数的返回值来表示是否成功,返回值为0表示不成功,1表示成功
};
2.2 注册总线
int bus_register(struct bus_type *bus)
参数:
struct bus_type *bus:结构体的地址,结构体类型就是总线对象(描述总线)
返回值:
注册总线:成功返回0,失败返回错误码
2.3 注销总线
void bus_unregister(struct bus_type * bus)
2.4 设备对象信息
设备对象,描述一个设备,有设备信息内容,包括地址,中断号,甚至是其他自定义信息
struct device
{
struct kobject kobj;//所有对象的父对象
const char * init_name;//设备名字
struct bus_type *bus;//在设备对象中描述要注册到哪条总线(总线对象的地址)
void * platform_data;//自定义数据的地址,指向任意类型(表示设备信息内容)
void (*release)(struct device *dev);//在注销卸载设备时(device_unregister函数),就
会调用这个release函数
};
2.5 注册和注销设备
//注册设备到总线中
int device_register(struct device *dev)
参数:
struct device *dev:结构体的地址,结构体类型是设备对象(描述设备,包含设备信息)
返回值:
注册设备:成功返回0,失败返回错误码
//从总线注销设备
void device_unregister(struct device *dev)
2.6 驱动对象信息
描述驱动设备的方法
struct device_driver
{
const char * name;//驱动名字
struct bus_type * bus;//在驱动对象中描述要注册到哪条总线(总线对象的地址)3. 平台总线
int (*probe) (struct device *dev);//如果device和driver匹配成功,driver要做的事情
(驱动的方法:申请设备号、设备节点等)
int (*remove) (struct device *dev);//如果device或driver从总线移除,driver要做
的事情(驱动的方法:注销设备号、设备节点等)
};
2.7 驱动注册到总线或驱动注销
//注册驱动到总线中
int driver_register(struct device_driver *drv)
参数:
struct device_driver *drv:结构体的地址,结构体类型是驱动对象(描述驱动)
返回值:
注册驱动:成功返回0,失败返回错误码
//从总线注销驱动
void driver_unregister(struct device *dev)
2.8 程序示例
2.8.1 总线部分
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
int mybus_match(struct device *dev, struct device_driver *drv)
{
printk("-----------%s----------\n",__FUNCTION__);
//匹配
//dev:匹配的设备对象
//drv:匹配的驱动对象
if( strcmp(drv->name,dev->kobj.name) == 0 )//自定义的匹配方式
{
printk("match ok\n");
return 1;
}
else
return 0;
}
//总线对象
struct bus_type mybus = {
.name = "mybus",
.match = mybus_match,
};
EXPORT_SYMBOL(mybus);
static int __init mybus_init(void)
{
printk("-----------%s----------\n",__FUNCTION__);
//构建创建总线
return bus_register(&mybus);
}
static void __exit mybus_exit(void)
{
//注销总线
bus_unregister(&mybus);
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
2.8.2 设备部分
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
extern struct bus_type mybus;
struct devinfo//设备信息
{
int irqno;//中断号
char * name;//设备名字
unsigned long addr;//表示寄存器地址
unsigned int bit;
};
struct devinfo info = {
.name = "led3",
.addr = 0x11000c20,
.bit = 0,
};
void mydev_release(struct device *dev)
{
printk("------------%s------------\n",__FUNCTION__);
}
//要往总线上注册的设备对象(描述设备)
struct device mydev = {
.init_name = "myled",
.bus = &mybus,
.platform_data = &info,
.release = mydev_release,
};
static int __init mydev_init(void)
{
//在总线上注册设备
return device_register(&mydev);
}
static void __exit mydev_exit(void)
{
//注销设备
device_unregister(&mydev);
}
module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
2.8.3 驱动部分
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
extern struct bus_type mybus;
struct devinfo//设备信息
{
int irqno;//中断号
char * name;//设备名字
unsigned long addr;//表示寄存器地址
unsigned int bit;
};
struct myled
{
unsigned int major;
struct class * cls;
struct device * dev;
unsigned int * addr;
};
struct myled led;
const struct file_operations fops;
//创建驱动的方法
int mydrv_probe (struct device *dev)
{
//dev:匹配成功的设备对象
printk("---------%s--------\n",__FUNCTION__);
//驱动申请
//申请设备号
led.major = register_chrdev(0,"led",&fops);
//创建设备节点
led.cls = class_create(THIS_MODULE,"led cls");
device_create(led.cls,NULL,MKDEV(led.major,0),NULL,"led3");
//硬件初始化
struct devinfo * info = dev->platform_data;//设备信息的地址
led.addr = ioremap(info->addr,8);
//writel(led.addr,xxx);
//writel(led.addr+1,xxxx)
return 0;
}
//卸载驱动的方法
int mydrv_remove (struct device *dev)
{
printk("---------%s--------\n",__FUNCTION__);
//驱动卸载
//iounmap()
//device_destory();
//class_destory()l
return 0;
}
//驱动对象
struct device_driver mydrv = {
.name = "myled",
.bus = &mybus,
.probe = mydrv_probe,
.remove = mydrv_remove,
};
static int __init mydrv_init(void)
{
//注册驱动到总线
return driver_register(&mydrv);
}
static void __exit mydrv_exit(void)
{
driver_unregister(&mydrv);
}
module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
3. 平台总线
在内核代码当中,已经创建了平台总线,不需要自己创建,而是在内核调用,开机的时候自动创建。
3.1 平台总线对象
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
3.2 注册平台总线
bus_register(&platform_bus_type);
3.3 平台总线设备对象
3.3.1 设备信息
struct platform_device {
const char *name;//设备名字,用于匹配
int id;//一般直接写-1
struct device dev;//继承了device父类,包含设备对象的所有信息
u32 num_resources;//资源的个数
struct resource *resource;//设备信息资源的首地址
};
3.3.2 注册设备到平台总线
int platform_device_register(struct platform_device * pdev)
3.3.3 从平台总线注销设备
void platform_device_unregister(struct platform_device * pdev)
3.4 平台总线驱动对象
3.4.1 驱动对象信息
struct platform_driver {
int (*probe)(struct platform_device *);//匹配成功调用
int (*remove)(struct platform_device *);//移除时调用
struct device_driver driver;//平台驱动继承的父类(通用的驱动对象信息)
const char *name;//驱动名字
const struct platform_device_id *id_table;//如果driver支持多个平台,在列表中列
举支持平台,是一个数组,数组中每一个元素就是一个支持的平台信息,id_table就是存储首地址
};
3.4.2 注册驱动到平台总线
int platform_driver_register(struct platform_driver * pdrv)
3.4.3 从平台总线注销驱动
void platform_driver_unregister(struct platform_driver * drv)
3.5 在多个平台上使用的LED驱动
3.5.1 思路
首先注册platform_driver,实现操作设备方法 const struct platform_device_id *id_table:平
台设备支持
注册完毕,匹配成功调用probe函数:
probe方法: 对硬件进行操作
申请设备号
设备节点
初始化硬件
实现文件IO接口
然后 注册platform_device,设备信息资源
struct resource * resource;//设备信息资源的首地址
3.5.2 平台设备对象部分程序示例(plat_led_pdev.c)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define GPX1CON 0x11000c20
#define GPX1CON_SIZE 4
#define GPX1DAT 0x11000c24
#define GPX1DAT_SIZE 4
//数组的每一个成员是一个结构体(资源信息)
struct resource led_resources[] = {
[0] = {
.start = GPX1CON,
.end = GPX1CON + GPX1CON_SIZE - 1,
.flags = IORESOURCE_REG,
},
[1] = {
.start = GPX1DAT,
.end = GPX1DAT + GPX1DAT_SIZE - 1,
.flags = IORESOURCE_REG,
},
//假设led设备有中断,用资源来说明中断,注意在本驱动设备没有任何意义
[2] = {
.start = 67,
.end = 67,
.flags = IORESOURCE_IRQ,
},
};//当前平台的led设备的资源
//平台设备对象
struct platform_device ledpdev = {
.name = "exynos4412_led",
.id = -1,
.num_resources = ARRAY_SIZE(led_resources),
.resource = led_resources,
};
static int __init plat_led_init(void)
{
return platform_device_register(&ledpdev);
}
static void __exit plat_led_exit(void)
{
platform_device_unregister(&ledpdev);
}
module_init(plat_led_init);
module_exit(plat_led_exit);
MODULE_LICENSE("GPL");
3.5.3 平台驱动对象部分程序示例(plat_led_pdrv.c)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
struct led_drv
{
unsigned int major;
struct class * cls;
struct device * dev;
struct resource * con;
struct resource * dat;
unsigned int * led_con;
unsigned int * led_dat;
};
struct led_drv led;
ssize_t led_write (struct file *filp, const char __user * buf, size_t size, loff_t *fops)
{
printk("--------------%s------------\n",__FUNCTION__);
int num;
copy_from_user(&num,buf,size);
if(num == 1)
{
writel(readl(led.led_dat) | 1 , led.led_dat);
}
else
{
writel(readl(led.led_dat) & ~1 , led.led_dat);
}
return 0;
}
int led_open (struct inode * inode, struct file *filp)
{
printk("--------------%s------------\n",__FUNCTION__);
return 0;
}
int led_release (struct inode *inode, struct file *filp)
{
printk("--------------%s------------\n",__FUNCTION__);
return 0;
}
const struct file_operations fops = {
.release = led_release,
.open = led_open,
.write = led_write,
};
int plat_led_probe(struct platform_device * pdev)
{
printk("--------------%s------------\n",__FUNCTION__);
//得到设备信息,申请驱动------创建设备驱动
/*
1、注册设备号
2、创建设备节点
3、初始化硬件
ioremap();---readl()----writel()
register_irq();
4、实现各种文件io接口
*/
led.major = register_chrdev(0,"led",&fops);
//if(){}
led.cls = class_create(THIS_MODULE,"led cls");
//if()
led.dev = device_create(led.cls,NULL,MKDEV(led.major,0),NULL,"led");
//if()
//硬件初始化,需要设备信息资源
//pdev就是对应平台的设备
//获取平台对象中指定的资源,返回值就是需要的资源的地址
//参数1:从哪个pdev获取资源
//参数2:获取资源的类型
//参数3:同种资源的第几个
led.con = platform_get_resource(pdev,IORESOURCE_REG,0);
led.dat = platform_get_resource(pdev,IORESOURCE_REG,1);
//获取中断资源
//platform_get_resource(pdev,IORESOURCE_IRQ,0);
//resource_size(const struct resource * res)
led.led_con = ioremap(led.con->start,led.con->end - led.con->start + 1);
led.led_dat = ioremap(led.dat->start,resource_size(led.dat));
writel(readl(led.led_con) & ~0xf | 0x1 , led.led_con);
writel(readl(led.led_dat) | 1 , led.led_dat);
return 0;
}
int plat_led_remove(struct platform_device * pdev)
{
printk("--------------%s------------\n",__FUNCTION__);
return 0;
}
//支持 struct platform_device_id 平台信息
const struct platform_device_id led_id_table[] = {
{"exynos4412_led",0x1111},//平台信息-----平台名字
{"s5pv210_led",0x2222},
{"s3c2410_led",0x3333},
{"",0x0},
};
//驱动对象---平台驱动
struct platform_driver led_pdrv = {
.driver = {
.name = "led_pdrv",//驱动名字
},
.id_table = led_id_table,//支持的平台信息数组的首地址
.probe = plat_led_probe,
.remove = plat_led_remove,
};
static int __init plat_led_init(void)
{
printk("--------------%s------------\n",__FUNCTION__);
return platform_driver_register(&led_pdrv);//向平台总线注册一个驱动
}
static void __exit plat_led_exit(void)
{
printk("--------------%s------------\n",__FUNCTION__);
platform_driver_unregister(&led_pdrv);//从平台总线注销一个驱动
}
module_init(plat_led_init);
module_exit(plat_led_exit);
MODULE_LICENSE("GPL");
3.5.4 平台驱动对象部分程序示例(led_test.c)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd = open("/dev/led0",O_RDWR);
int value = 0;
while(1)
{
scanf("%d",&value);
if(value > 1)
break;
write(fd,&value,4);
}
close(fd);
return 0;
}
3.5.5 Makefile
KERNEL_PATH = /home/yky/Code/linux-3.14-fs4412//内核路径
CORSS_COMPILE = /home/yky/Tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-//交叉编译工具链
CC = $(CORSS_COMPILE)gcc
obj-m += plat_led_pdev.o//设备
obj-m += plat_led_pdrv.o//驱动
APP_NAME = led_test//应用程序
all:
make modules -C $(KERNEL_PATH) M=$(shell pwd)
$(CC) $(APP_NAME).c -o $(APP_NAME)
install:
cp *.ko $(APP_NAME) /source/rootfs //
cp $(APP_NAME) /source/rootfs
clean:
make clean -C $(KERNEL_PATH) M=$(shell pwd)
rm $(APP_NAME)
4.IIC驱动模型
4.1 理解图
4.2 设备树修改
一般买来的硬件有IIC的控制,在设备树中:例如exynos4.dtsi
存在对iic控制器的描述:
i2c_0: i2c@13860000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x13860000 0x100>;
interrupts = <0 58 0>;
clocks = <&clock 317>;
clock-names = "i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c0_bus>;
status = "disabled";
};
i2c_5: i2c@138B0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x138B0000 0x100>;
interrupts = <0 63 0>;
clocks = <&clock 322>;
clock-names = "i2c";
status = "disabled";
};
所以我们对iic从设备的信息描述进行添加
新增一个i2c5的从设备 exynos4412-fs4412.dts 添加包括i2c5控制器及从设备信息
i2c@138B0000 {
#address-cells = <1>;
#size-cells = <0>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <20000>;
pinctrl-0 = <&i2c5_bus>;
pinctrl-names = "default";
status = "okay";
mpu6050@68 {
compatible = "invensense,mpu6050";
reg = <0x68>;
};
};
4.3 编写框架
构造i2c_driver,注册到i2c总线
//i2c驱动对象
struct i2c_driver {
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
struct device_driver driver;//继承的父类(i2c驱动对象中包含的父类信息)
|
const char * name;//驱动名字
const struct of_device_id *of_match_table;//设备树的设备匹配
const struct i2c_device_id *id_table;//用于与设备对象进行匹配,非设备树
};
//注册iic驱动到iic总线
int i2c_add_driver(struct i2c_driver *driver);
//从i2c总线注销i2c驱动
void i2c_del_driver(struct i2c_driver * driver)
接下来就是:
实现probe函数
申请设备号
创建设备节点
iic控制器与从设备通信
实现文件io接口
4.4 程序示例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/device.h>
unsigned int major;
struct class * i2c_cls;
struct device * i2c_dev;
const struct file_operations fops;
int i2c_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
printk("------------%s-----------\n",__FUNCTION__);
major = register_chrdev(0,"i2c driver",&fops);
i2c_cls = class_create(THIS_MODULE,"cls");
i2c_dev = device_create(i2c_cls,NULL,MKDEV(major,0),NULL,"mpu605");
//i2c_transfer(struct i2c_adapter * adap,struct i2c_msg * msgs,int num)
return 0;
}
int i2c_remove(struct i2c_client * client)
{
return 0;
}
const struct of_device_id i2c_table[] = {
{
.compatible = "invensense,mpu6050",
},
{},//空元素
};
const struct i2c_device_id id_table[] = {
{"mpu6050drv",0x1111},
{},//空元素
};
//驱动对象
struct i2c_driver i2c5_driver = {
.probe = i2c_probe,
.remove = i2c_remove,
.driver = {
.name = "mpu6050drv",
.of_match_table = i2c_table,
},
.id_table = id_table,
};
static int __init iic_init(void)
{
//把i2c驱动注册到i2c总线
return i2c_add_driver(&i2c5_driver);
}
static void __exit iic_exit(void)
{
//从i2c总线注销i2c驱动
i2c_del_driver(&i2c5_driver);
}
module_init(iic_init);
module_exit(iic_exit);
MODULE_LICENSE("GPL");