第一章 linux驱动之设备与驱动
第二章 linux驱动之设备树与GPIO子系统
linux驱动之总线详解
- linux驱动
- 一、总线bus
- 1.bus在linux中文件结构
- 2.bus_type
- 3.常用的系统函数
- 二、Platform设备驱动
- 1.platform
- 2.platform设备
- 3.platform驱动
- 4.platform设备驱动细节
- 三、I2C
- 1.IIC总线
- 2.I2C在linux下的结构
- 2.I2C中主要的结构体
- 3.i2c的系统调用函数
一、总线bus 1.bus在linux中文件结构
在linux系统中,在sys/bus文件夹下存放的文件夹都是实例化的总线,比如platform,i2c等等,而这些文件夹下又存放着device和driver文件夹,这些则是存放在该总线下的设备与驱动。
每个实例化的bus会规定在该bus下的device和driver的匹配方式,并建立链表将所有device和driver建立联系,
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);
int (*resume)(struct device *dev);
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下的匹配方式等。
总线初始化: 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);
#include3.platform驱动//初始化头文件 #include //最基本的文件,支持动态添加和卸载模块。 #include //平台设备所需要的头文件 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}}; static int device_init(void) { // 设备信息注册到 Linux 内核 platform_device_register(&beep_device); printk("platform_device_register ok n"); return 0; } static void device_exit(void) { // 设备信息卸载 platform_device_unregister(&beep_device); printk("gooodbye! n"); } module_init(device_init); module_exit(device_exit); MODULE_LICENSE("GPL");
Platform设备常用函数和基本流程
1.定义平台设备机构体:struct platform_driver。
2.注册平台设备:platform_driver_register(&beep_driver);
3.卸载平台设备:platform_driver_unregister(&beep_device);
#include4.platform设备驱动细节//初始化头文件 #include //最基本的文件,支持动态添加和卸载模块。 #include //平台设备所需要的头文件 int beep_probe(struct platform_device *pdev){ printk("beep_proben"); return 0; } int beep_remove(struct platform_device *pdev){ printk("beep_removen"); 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 }; 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; } 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");
Platform注意要点:
1.Platform_driver中的.driver,.id_table都含有name变量,会自动与Platform_device中的name匹配,
.id_table的优先级要高于.driver,只有当.id_table没有匹配上时,才会匹配.driver。
2.Platform_driver和Platform_device匹配成功时会自动调用Platform中.probe指向的函数。
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的其他信息,供驱动开发人员查看。
适配器:struct i2c_adapter
struct i2c_adapter {
struct module *owner;
unsigned int class;
const struct i2c_algorithm *algo;
void *algo_data;
struct rt_mutex bus_lock;
int timeout;
int retries;
struct device dev;
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 {
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);
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;
unsigned short addr;
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter;
struct device dev;
int init_irq;
int irq;
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb;
#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;
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;
__u16 flags; //flags: 消息传输方向和特性。I2C_M_RD:表示读取消息;0:表示发送消息。
...
__u16 len;
__u8 *buf;
};
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);
return (ret == 1) ? count : ret;
}



