您的位置:首页 > 运维架构 > Linux

工程中的Linux设备驱动

2013-11-27 20:19 176 查看
一、platform 设备驱动

1.1 platform 总线、设备与驱动

在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

Linux发明了一种虚拟的总线,称为platform总线, 相应的设备成为platform_device, 而驱动成为platform_driver。

platform_device结构体

struct platform_device {

const char *name; //设备名

u32id;

struct device dev;

u32num_resource; //设备所使用的各类资源数量

struct resource * resource; //资源

};

platform_driver结构体

struct platform_driver {

int (*probe) (struct platform_device *);

int (*remove) (struct platform_device *);

void (*shutdown) (struct platform_device *);

int (*suspend) (struct platform_device *, pm_message_t state);

int (*suspend_late) (struct platform_device *, pm_message_t state);

int (*resume_early) (struct plartform_device *);

int (*resume) (struct platform_device *);

struct pm_ext_ops *pm;

struct device_driver driver;

};

系统中为platform总线定义了一个bus_type的实例platform_bus_type,其定义如代码清单:

struct bus_type platform_bus_type = {

.name= "platform",

.dev_attrs= platform_dev_attrs,

.match = platform_match,

.uevent= platform_uevent,

.pm= PLATFORM_PM_OPS_PTR,

};

EXPORT_SYMBOL_GPL(platform_bus_type);

这里要重点关注其match() 成员函数, 正是此成员函数确定了platform_device 和 platform_driver之间如何匹配,代码如下:

static int platform_match (struct device *dev, struct device_driver *drv)

{

struct platform_device *pdev;

pdev = container_of(dev, struct platform_device, dev);

return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);

};    //匹配platform_device和platform_driver主要看两者的name字段是否相同。

对platform_device的定义通常在BSP的板文件中实现,在板文件中,将platform_device归纳为一个数组,最终通过platform_add_devices()函数统一注册。platform_add_devices()函数可以将平台设备添加到系统中,这个函数的原型为:

int platform_add_devices(struct platform_device **devs, int num); //该函数的第一个参数为平台设备数组的指针,第二个参数为平台设备的数量,它的内部调用了platform_device_register() 函数用于注册单个的平台设备。

1.2 输入设备驱动

输入核心提供了底层输入设备驱动程序所需的API,如分配/释放一个输入设备:

struct input_dev *input_allocate_device(void);

void input_free_device(struct input_dev * dev);

input_allocate_device()返回的是一个input_dev 的结构体,此结构体用于表征1个输入设备。

注册/注销输入设备用的接口如下:

int _ _must_check input_register_device(struct input_dev *);

void input_unregister_device(struct input_dev *);

报告输入事件用的接口如下:

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value); //报告指定type、code的输入事件

void input_report_key(struct input_dev *dev, unsigned int code, int value); //报告键值

void input_report_rel(struct input_dev *dev, unsigned int code, int value); //报告相对坐标

void input_report_abs(struct input_dev *dev, unsigned int code, int value); //报告绝对坐标

void input_sync(struct input_dev *dev); //报告同步事件

而所有的输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event, 形如代码如下:

struct input_event {

struct timeval time;

_ _u16 type;

_ _u16 code;

_ _s32 value;

};

二:输入设备驱动

输入核心提供了底层输入设备驱动程序所需的API,如分配、释放一个初入设备:

struct input_dev *input_allocate_device(void);

void input_free_device(struct input_dev *dev);

注册/注销输入设备用的接口如下:

int _ _must_check input_register_device(struct input_dev *);

void input_unregister_device(struct input_dev *);

报告输入事件用的接口如下:

/*报告制定type、code的输入事件*/

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

/*报告键值*/

void input_report_key(struct input_dev *dev, unsigned int code, int value);

/*报告相对坐标*/

void input_report_rel(struct input_dev *dev, unsigned int code, int value);

/*报告绝对坐标*/

void input_report_abs(struct input_dev *dev, unsigned int code, int value);

/*报告同步事件*/

void input_sync(struct input_dev *dev);

而所有的输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event,形如代码清单:

struct input_event {

struct timeval time;

  _ _u16 type;

_ _u16 code;

_ _s32 value;

};

三、Linux SPI主机和设备驱动

SPI(同步外设接口)是由摩托罗拉公司开发的全双工同步串行总线,其接口由MISO(串行数据输入)、MOSI(串行数据输出)、SCK(串行移位时钟)、SS(从使能信号)4种信号构成,SS决定了惟一的与主设备通信的从设备,主设备通过产生移位时钟来发起通信。通信时,数据由MOSI输出,MISO输入,数据在时钟的上升或下降沿由MOSI输出,在紧接着的下降或上升沿由MISO读入,这样经过8/16次时钟的改变,完成8/16位数据的传输。

SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。如果CPOL = 0,串行同步时钟的空闲状态为低电平;如果CPOL = 1,串行同步时钟的空闲状态为高电平。如果CPHA = 0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA =  1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。

在Linux中,用代码清单的spi_master结构体来描述一个SPI主机控制器驱动,其主要成员是主机控制器的序号(系统中可能存在多个SPI主机控制器)、片选数量、SPI模式和时钟设置用到的函数、数据传输用到的函数等。

struct spi_master {

struct device
dev;

s16
bus_num;

u16
num_chipselect;

int
(*setup) (struct spi_device *spi); //设置模式和时钟

int
(*transfer) (struct spi_device *spi, struct spi_message *mesg); //双向数据传输

void
(*cleanup) (struct spi_device *spi);

}

分配、注册和注销SPI主机的API由SPI核心提供:

struct spi_master * spi_alloc_master(struct device *host, unsigned size);

int spi_register_master(struct spi_master *master);

void spi_unregister_master(struct spi_master *master);

在Linux中,用代码清单spi_driver结构体来描述一个SPI外设驱动,可以认为是spi_master的client驱动。

struct spi_driver {

int
(*probe)(struct spi_device *spi);

int
(*remove)(sturct spi_device *spi);

int
(*shutdown)(struct spi_device *spi);

int 
(*suspend)(struct spi_device *spi);

int 
(*resume)(struct spi_device *spi);

struct device_driver
driver;

};

在SPI外设驱动中,当透过SPI总线进行数据传输的时候,使用了一套与CPU无关的统一的接口。这套接口的第一个关键数据结构就是spi_transfer,它用于描述SPI传输,如代码清单所示:

struct spi_transfer {

const void 
*tx_buf;

void 
*rx_buf;

unsigned 
len;

  dma_addr_t
tx_dma;

dma_addr_t
rx_dma;

unsigned 
cs_chang:1;

u8 
bits_per_word;

u16
delay_usecs;

u32
speed_hz;

struct list_head
stransfer_list;

};

而一次完整的SPI传输流程可能不只包含一次spi_transfer, 他可能包含一个或多个spi_transfer, 这些spi_transfer最终通过spi_message组织在一起,定义代码如下:

struct spi_message {

struct list_head
transfers;

struct spi_device
*spi;

unsigned 
is_dma_mapped:1;

/*完成被一个callback报告*/

void
(*complete) (void *context);

void 
*context;

unsigned 
actual_length;

int 
status;

struct list_head
queue;

void 
*state;

};

通过spi_message_init() 可以初始化spi_message,而将spi_transfer添加到spi_message队列的方法则是:

void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);

发起一次spi_message的传输有同步和异步两种方式,同步API时,会阻塞等待这个消息被处理完。同步操作时使用的API是:

int spi_sync( struct spi_device *spi, struct spi_message *message);

使用异步API时,不会阻塞等待这个消息被处理完,但是可以在spi_message的complete字段接一个回调函数,当消息被处理完成后,该函数会被调用。异步操作时使用的API是:

int spi_async(struct spi_device *spi, struct spi_message *message);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  platform设备驱动