linux驱动
第一章 linux驱动之设备与驱动
第二章 linux驱动之设备树与GPIO子系统
linux驱动之总线详解
一、总线bus
1.bus在linux中文件结构
在linux系统中,在sys/bus文件夹下存放的文件夹都是实例化的总线,比如platform,i2c等等,而这些文件夹下又存放着device和driver文件夹,这些则是存放在该总线下的设备与驱动。
每个实例化的bus会规定在该bus下的device和driver的匹配方式,并建立链表将所有device和driver建立联系,
2.bus_type
1.每个sys/bus文件夹下的文件夹都是bus_type的实例化对象,其中match指向device和driver和匹配方式
每个sys/bus文件夹下的文件夹都是bus_type的实例化对象,其中match指向device和driver和匹配方式函数
struct bus_type {
const char *name; /* 总线名字 */
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs;
const struct attribute_group **bus_groups; /* 总线属性 */
const struct attribute_group **dev_groups; /* 设备属性 */
const struct attribute_group **drv_groups; /* 驱动属性 */
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state); /*当设备收到 suspend 命令的时候,就会执行这个函数*/
int (*resume)(struct device *dev); /*当设备收到 resume 命令的时候,就会执行这个函数*/
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
}
2.每个实例化的bus(xbus)都会有这样的一个结构,xbus通过此结构与device和driver建立联系,同时此结构也规定了在xbus下的匹配方式等。
3.常用的系统函数
总线初始化: buses_init()
- 开机运行
- 创建kset,后面的相关操作会将所有的实例化的bus(xbus)连到kset
总线注册:bus_register()函数int bus_register(struct bus_type *bus)
- 在/sys/bus下建立xbus目录项,并创建属性文件
- 在/sys/bus/xbus下建立devices目录项,并创建属性文件
- 在/sys/bus/xbus下建立drivers目录项,并创建属性文件
- 初始化 priv->klist_devices链表头
- 初始化priv->klist_drivers链表头
设备注册:int device_register(struct device *dev)
- 在/sys/bus/xbus/devices下建立yyy目录项
- 加入bus-> priv->devices_kset链表
- 加入bus-> priv->klist_devices链表
- 遍历bus-> priv->klist_drivers,执行bus->match()寻找合适的drv
- dev关联driv,执行drv->probe()
驱动注册:int driver_register(struct device_driver *drv)
- 在/sys/bus/xbus/drivers下建立zzz目录项
- 加入bus-> priv->drivers_kset链表
- 加入bus-> priv->klist_drivers链表
- 遍历bus-> priv->klist_klist_devices链表,执行bus->match()寻找合适的dev
- dev关联dev,执行drv->probe()
二、Platform设备驱动
1.platform
platform作为bus的一个实例化的总线,系统提供了platform_device_register(&beep_device)和platform_driver_register(&beep_driver)向platform注册,同时系统也提供了platform_device_unregister(&beep_device)和platform_driver_unregister(&beep_device)向platform删除device和driver。每次注册和删除device和diver时,paltform的device和driver文件夹下都会添加和删除对应的文件。
struct bus_type platform_bus_type =
{
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
2.platform设备
Platform设备常用函数和基本流程
1.定义平台设备机构体:struct platform_device。
2.注册平台设备:platform_device_register(&beep_device);
3.卸载平台设备:platform_device_unregister(&beep_device);
/*
* @Author: topeet
* @Description: 基于平台设备模型的device.c
*/
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/platform_device.h> //平台设备所需要的头文件
/**
* @description: 释放 flatform 设备模块的时候此函数会执行
* @param {structdevice} *dev:要释放的设备
* @return {*}
*/
void beep_release(struct device *dev)
{
printk("beep_release \n");
}
// 设备资源信息,也就是蜂鸣器所使用的所有寄存器
struct resource beep_res[] = {
[0] = {
.start = 0x020AC000,//寄存器组的起始地址
.end = 0x020AC003,//寄存器组的终止地址
.flags = IORESOURCE_MEM,
.name = "GPIO5_DR",
}
};
// platform 设备结构体
struct platform_device beep_device = {
.name = "beep_test",
.id = -1,
.resource = beep_res,
.num_resources = ARRAY_SIZE(beep_res),
.dev = {
.release = beep_release}};
/**
* @description: 设备模块加载
* @param {*}无
* @return {*}无
*/
static int device_init(void)
{
// 设备信息注册到 Linux 内核
platform_device_register(&beep_device);
printk("platform_device_register ok \n");
return 0;
}
/**
* @description: 设备模块注销
* @param {*}无
* @return {*}无
*/
static void device_exit(void)
{
// 设备信息卸载
platform_device_unregister(&beep_device);
printk("gooodbye! \n");
}
module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");
3.platform驱动
Platform设备常用函数和基本流程
1.定义平台设备机构体:struct platform_driver。
2.注册平台设备:platform_driver_register(&beep_driver);
3.卸载平台设备:platform_driver_unregister(&beep_device);
/*
* @Author:topeet
* @Description: 基于平台设备模型的driver.c
*/
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/platform_device.h>//平台设备所需要的头文件
/**
* @description: beep_probe,驱动和设备匹配成功会进入此函数
* @param {structplatform_device} *pdev
* @return {*}
*/
int beep_probe(struct platform_device *pdev){
printk("beep_probe\n");
return 0;
}
/**
* @description: beep_remove,当driver和device任意一个remove的时候,就会执行这个函数
* @param {structplatform_device} *pdev
* @return {*}
*/
int beep_remove(struct platform_device *pdev){
printk("beep_remove\n");
return 0;
}
// 该设备驱动支持的设备的列表 ,他是通过这个指针去指向 platform_device_id 类型的数组
const struct platform_device_id beep_idtable = {
.name = "beep_test", //设备名字叫“beep_test”
};
// platform 驱动结构体
struct platform_driver beep_driver = {
.probe = beep_probe,
.remove = beep_remove,
.driver={
.owner = THIS_MODULE,
.name = "beep_test"
},
.id_table = &beep_idtable
};
/**
* @description: 设备模块加载
* @param {*}无
* @return {*}无
*/
static int beep_driver_init(void){
int ret = 0;
// platform驱动注册到 Linux 内核
ret = platform_driver_register(&beep_driver);
if(ret<0)
{
printk("platform_driver_register error \n");
}
printk("platform_driver_register ok \n");
return 0;
}
/**
* @description: 设备模块注销
* @param {*}无
* @return {*}无
*/
static void beep_driver_exit(void){
// platform驱动卸载
platform_driver_unregister(&beep_driver);
printk("gooodbye! \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
4.platform设备驱动细节
Platform注意要点:
1.Platform_driver中的.driver,.id_table都含有name变量,会自动与Platform_device中的name匹配,
.id_table的优先级要高于.driver,只有当.id_table没有匹配上时,才会匹配.driver。
2.Platform_driver和Platform_device匹配成功时会自动调用Platform中.probe指向的函数。
三、I2C
1.IIC总线
IIC作为BUS_TYPE中的一个实例化的对象,也继承了BUS_TYPE的结构。
linux启动之后,默认执行i2c_init,在i2c_init中调用bus_register(&i2c_bus_type)将IIC总线实例化。
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
2.I2C在linux下的结构
每个IIC设备都会指向一个IIC适配器,适配器结构体中存在Algorithm,Algorithm中存在一系列函数指针,这些函数指针指向真正硬件操作代码。
每个client都指向IIC适配器,每个适配器都是一个设备,都有对应的驱动。适配器的驱动是在platform下定义的,系统在开机后会自动完成每个适配器的驱动,并将适配器的信息记录到imx_i2c_struct中,此结构体不仅有适配器的信息,同时海记录了IIC的其他信息,供驱动开发人员查看。
2.I2C中主要的结构体
适配器:struct i2c_adapter
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
i2c从设备:struct i2c_client
adapter指向负责该设备的adapter
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct device dev; /* the device structure */
int init_irq; /* irq set at initialization */
int irq; /* irq issued by device */
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
flags: :I2C_CLIENT_TEN表示设备使用10位芯片地址,I2C客户端PEC表示它使用SMBus数据包错误检查
addr: addr在连接到父适配器的I2C总线上使用的地址。
name: 表示设备的类型,通常是芯片名。
adapter: struct i2c_adapter 结构体,管理托管这个I2C设备的总线段。
dev: Driver model设备节点。
init_irq: 作为从设备时的发送函数。
irq: 表示该设备生成的中断号。
detected: struct list_head i2c的成员_驱动程序.客户端列表或i2c核心的用户空间设备列表。
slave_cb: 使用适配器的I2C从模式时回调。适配器调用它来将从属事件传递给从属驱动程序。i2c_客户端识别连接到i2c总线的单个设备(即芯片)。暴露在Linux下的行为是由管理设备的驱动程序定义的。
i2c设备驱动:struct i2c_driver
struct i2c_driver {
unsigned int class;
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
...
};
probe: i2c设备和i2c驱动匹配后,回调该函数指针。
id_table: struct i2c_device_id 要匹配的从设备信息。
address_list: 设备地址
clients: 设备链表
detect: 设备探测函数
开机时,系统内核会调用i2c_adap_imx_init,开机后的系统已经注册了i2c_imx_driver驱动,此驱动会和IIC的设备树配对,完成adapter的设置。同时创建imx_i2c_struct结构体。此结构体记录了IIC的一些配置,系统可以通过此结构体获得IIC的参数。
I.MX6U 的 I2C 适配器驱动是个标准的 platform 驱动,由此可以看出,虽然 I2C 总线为别的设备提供了一种总线驱动框架,但是 I2C 适配器却是 platform驱动。
static int __init i2c_adap_imx_init(void)
{
return platform_driver_register(&i2c_imx_driver);
}
struct imx_i2c_struct {
struct i2c_adapter adapter;//适配器
struct clk *clk;
void __iomem *base;
wait_queue_head_t queue;
unsigned long i2csr;
unsigned int disable_delay;
int stopped;
unsigned int ifdr; /* IMX_I2C_IFDR */
unsigned int cur_clk;
unsigned int bitrate;
const struct imx_i2c_hwdata *hwdata;
struct imx_i2c_dma *dma;
};
clk: clk结构体保存时钟相关信息
bitrate: 保存i2c的波特率
dma: struct imx_i2c_dma 结构体 dam相关信息等等
3.i2c的系统调用函数
IIC核心提供一些API
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
struct i2c_adapter *i2c_get_adapter(int nr);//获取编号为nr的I2C适配器
void i2c_put_adapter(struct i2c_adapter *adap);//释放adap指向的适配器
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);//把 I2C 适配器和 I2C 器件关联起来
void i2c_unregister_device(struct i2c_client *client)//功能 注销一个 client。
数据传输系统函数,函数最终就是调用我们前面讲到的i2c_imx_xfer()函数来实现数据传输
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags; //flags: 消息传输方向和特性。I2C_M_RD:表示读取消息;0:表示发送消息。
...
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
int i2c_master_send(const struct i2c_client *client,const char *buf, int count)
int i2c_master_recv(const struct i2c_client *client,char *buf, int count)
int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf,
int count, u16 flags)
{
int ret;
struct i2c_msg msg = {
.addr = client->addr,
.flags = flags | (client->flags & I2C_M_TEN),
.len = count,
.buf = buf,
};
ret = i2c_transfer(client->adapter, &msg, 1);
/*
* If everything went ok (i.e. 1 msg transferred), return #bytes
* transferred, else error code.
*/
return (ret == 1) ? count : ret;
}