基于tiny4412开发板的I2C子系统写法

I2C总线知识


I2C子系统框架

这里写图片描述
I2C是一个不是很复杂的通信协议,在裸机上能够轻易实现,但是linux系统下的I2C系统框架复杂的,简单控制IO电平输出不行,这是因为与裸机相比,linux是一个多进程系统,在进行I2C通信过程中很可能被其他进程打断,这样就会造成数据丢失,为了避免此种情况,便有了现在的I2C子系统,除了保证数据不丢失,可移植性也大大增强。

I2C核心

I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法(),相关数据结构,I2C通信方法(algorithm)上层的、与具体适配器无关的的代码以及探测设备、检测设备地址的上层代码等


I2C总线驱动(I2C adapter/algorithm driver)

I2C总线驱动是对I2C硬件控制驱动代码的实现,实现I2C基本时序的产生以及数据的传输,是真正的硬件数据收到实现的地方,这层一般都是芯片厂家实现的,一般是使用平台模型实现,做二次开发不需要实现这一层代码,顶多是移植。


I2C客户驱动程序

I2C客户驱动程序对系统中I2C设备操作具体实现代码,使用两个结构体来描述:
i2c_client:用来表示i2c设备信息的,提供给i2c设备驱动使用(与platform_device相似)
i2c_driver:用来表示i2c设备的具体驱动代码实现(与platform_driver相似)
要实现一个i2c设备的设备驱动就是分别实现i2c_client,i2c_driver结构,并且使用核心层提供的函数分别注册。但是实际编写过程,并不需要自己去创建i2c_client,而是给内核提供i2c_client所需要的信息,然后内核通过i2c_board_info结构传递i2c_client所需要的信息给内核动态创建注册i2c_client结构。这一点和平台设备模型有点区别。
这一层是做二次开发的驱动工程师必须掌握的的编程


I2C相关重要数据结构

struct i2c_client –I2C设备结构

路径:i2c.h linux-3.5\include\linux
作用:用来描述一个i2c设备的基本信息:器件地址、所绑定的适配器、使用到的中断编号、需要传递给i2c_driver使用的平台数据等

struct i2c_client {
    unsigned short flags;       /* div., see below      */
    unsigned short addr;        /* chip address - NOTE: 7bit    */
                    /* addresses are stored in the  */
                    /* _LOWER_ 7 bits       */
    char name[I2C_NAME_SIZE];
    struct i2c_adapter *adapter;    /* the adapter we sit on    */
    struct i2c_driver *driver;  /* and our access routines  */
    struct device dev;      /* the device structure     */
    int irq;            /* irq issued by device     */
    struct list_head detected;
};

重要成员:
flags:地址长度,如是10位还是7位地址,默认是7位地址。如果是10位地址器件,则设置为I2C_CLIENT_TEN
addr:具体I2C器件如(at24c02),设备地址
name:设备名,用于和i2c_driver层匹配使用的,可以和平台模型中的平台设备层platform_driver中的name作用是一样的。
adapter:本设备所绑定的适配器结构(CPU有很多I2C适配器,类似单片机有串口1串口2等等,在linux中每个适配器都用一个结构描述)
driver:指向匹配的i2c_driver结构,不需要自己填充,匹配上后内核会完成这个赋值操作
dev:内嵌的设备模型,可以使用其中的platform_data成员传递给任何数据给i2c_driver使用。
irq:设备需要使用到中断时,把中断编号传递给i2c_driver进行注册中断,如果没有就不需要填充。(有的I2C器件有中断引脚编号,与CPU相连)


i2c_driver–I2C驱动结构

路径:i2c.h linux-3.5\include\linux
作用:内核使用这个结构来描述一个i2c设备驱动层,要编写设备驱动层代码,核心就是实现该结构,并且使用核心层提供注册函数进行注册

struct i2c_driver {
    unsigned int class;

    /* Notifies the driver that a new bus has appeared or is about to be
     * removed. You should avoid using this, it will be removed in a
     * near future.
     */
    int (*attach_adapter)(struct i2c_adapter *) __deprecated;
    int (*detach_adapter)(struct i2c_adapter *) __deprecated;

    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);

    /* driver model interfaces that don't relate to enumeration  */
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);

    /* Alert callback, for example for the SMBus alert protocol.
     * The format and meaning of the data value depends on the protocol.
     * For the SMBus alert protocol, there is a single bit of data passed
     * as the alert response's low bit ("event flag").
     */
    void (*alert)(struct i2c_client *, unsigned int data);

    /* a ioctl like command that can be used to perform specific functions
     * with the device.
     */
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

    struct device_driver driver;
    const struct i2c_device_id *id_table;

    /* Device detection callback for automatic device creation */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};

重要成员:
attach_adapter:i2c_clienti2c_driver匹配后执行该函数,但这个接口不久后会消失,不建议使用这个接口
detach_adapter:在取消i2c_clienti2c_driver匹配绑定后后执行该函数,但这个接口不久后会消失,不建议使用这个接口
attach_adapterdetach_adapter是一对函数,功能相反
probe:新版本内核接口,用来代替attach_adapter,在i2c_clienti2c_driver匹配后执行该函数
remove:新版本内核接口,用来代替 detach_adapter,在取消i2c_clienti2c_driver匹配绑定后后执行该函数
driver:这个成员类型在平台设备驱动层中也有,而且使用其中的name成员来实现平台设备匹配,但是i2c子系统中不使用其中的name进行匹配,这也是i2c设备驱动模型和平台设备模型匹配方法的一点区别
id_table:用来实现i2c_clienti2c_driver匹配绑定,当i2c_client中的name成员和i2c_driverid_tablename成员相同的时候,就匹配上了。
补充:i2c_clienti2c_driver匹配问题
- i2c_client中的name成员和i2c_driverid_tablename成员相同的时候
- i2c_client指定的信息在物理上真实存放对应的硬件,并且工作是正常的才会绑定上,并执行其中的probe接口函数
这第二点要求和平台模型匹配有区别,平台模型不要求设备层指定信息在物理上真实存在就能匹配


struct i2c_adapter适配器结构

路径:i2c.h linux-3.5\include\linux
作用:内核使用该结构来描述一个物理上的i2c总线控制器(i2c适配器),要实现适配器驱动就是要实现该结构,然后使用核心层参数进行注册

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;
};

重要成员
algo:通信算法结构,其中包含了真正指向硬件收发函数的指针
algo_data:通信算法私有数据,自定义
timeout:超时时间,单位是jiffies当发送数据或接收数据超过该时间就会认定失败
retries:传输失败后,再尝试的次数
dev:内嵌的设备模型,可以使用其中的platform_data成员保存平台设备层传递下来的参数(一般情况下适配器驱动都是以平台模型实现的)。
nr:芯片集成的I2C控制器一般有好几个,在软件层面的每个控制器都用一个结构体描述,系统中他们靠链表衔接在一起,区分每个适配器就是靠查找控制器链表成员的nr来区分,物理上i2c总线编号,I2C0对应填0I2C1对应填1······


struct i2c_algorithm

路径:i2c.h linux-3.5\include\linux
作用:该结构属于struct i2c_adapter结构成员,内核使用该结构来描述适配器使用什么样方式实现数据收发

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 *);
};

重要成员
master_xfer:标准i2c接口真正的收发函数,必须实现
smbus_xfer:标准smbus接口真正的收发函数,根据需求
补充:
smbusi2c协议非常类似,就在i2c协议的基础上衍生而来的,大部分场合是兼容的,所以linux系统的i2c子系统中把smbus总线和i2c总线合在一起


struct i2c_msg

路径:i2c.h linux-3.5\include\linux
作用:该结构属于struct i2c_algorithmmaster_xfer函数的参数,内核使用该结构来传递一则消息给适配器驱动层,适配器根据该结构中的信息,执行相应的代码操作i2c硬件控制器的寄存器,实现对数据的收发。
要发送的数据或者要接受的数据信息,操作设备的地址就包含在这个结构中。

struct i2c_msg {
    __u16 addr; /* slave address            */
    __u16 flags;
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_RD        0x0001  /* read data, from slave to master */
#define I2C_M_NOSTART       0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR  0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK    0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK     0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN      0x0400  /* length will be first received byte */
    __u16 len;      /* msg length               */
    __u8 *buf;      /* pointer to msg data          */
};

重要成员:
addr:要读写的设备地址
flags:表示地址的长度,读写功能。如果是10位地址必须设置I2C_M_TEN,如果是读操作必须设置有I2C_M_TEN······,可以使用或运算合成。
buf:要读写的数据指针。写操作:数据源 读操作:指定存放数据的缓存区
len:读写数据的数据长度


i2c_board_info

该结构表示一个i2c设备信息,系统初始化期间,通过函数i2c_register_board_info将设备信息添加到全局链表__i2c_board_list中,在适配器注册时,从__i2c_board_list取出该结构生成的i2c设备(struct i2c_client)
注册该结构使用的方法是使用下面的函数进行注册
int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info,unsigned len)

struct i2c_board_info {
    char        type[I2C_NAME_SIZE];
    unsigned short  flags;
    unsigned short  addr;
    void        *platform_data;
    struct dev_archdata *archdata;
    struct device_node *of_node;
    int     irq;
};

重要成员
type:用来初始化i2c_client结构中的name成员
flags:用来初始化i2c_client结构中的flags成员
addr:用来初始化i2c_client结构中的addr成员
platform_data:用来初始化i2c_client结构中的.dev.platform_data成员
archdata:用来初始化i2c_client结构中的.dev.archdata成员
irq:用来初始化i2c_client结构中的irq成员
核心就是记住该结构和i2c_client结构成员的对应关系。在i2c子系统不直接创建i2c_client结构,只是提供struct i2c_board_info结构信息,让子系统动态创建,并且注册。
注意
对比i2c_board_infoi2c_client结构的必须成员,i2c_board_info中还缺少一个很重要的成员adapteradapter成员可以通过注册函数:
int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info,unsigned len)
内部通过busnum 来查找对应编号的i2c_adapter结构


I2C子系统常用API

I2C适配器驱动层使用的API

EXYNOS4412芯片I2C驱动代码:i2c-s3c2410.c linux-3.5\drivers\i2c\busses
s3c2410是三星公司arm9芯片,EXYNOS4412是三星A9芯片,I2C作为一个外设,只是简单的从ARM9芯片上移动到A9芯片,三星公司的I2C驱动代码可以同时支持本公司ARM9ARM11,A8A9类芯片
i2c-s3c2410.c使用平台名实现,所以,你要想使用I2C总线,则需要根据这个驱动移植,完成平台设备层代码,配套的内核源码已经在mach-tiny4412.c中移植好
适配器简要说明:
驱动开发人员自定义了一个数据结构,其中内嵌了struct i2c_adapter结构,

struct s3c24xx_i2c {
    ········
    struct i2c_adapter  adap;

    ······
};

注册适配器
int i2c_add_numbered_adapter(struct i2c_adapter *adap)

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
    struct s3c24xx_i2c *i2c;
    ·······
    //分配自定义数据结构空间,其中适配器结构在这里面分配
    i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);

    strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
    i2c->adap.owner   = THIS_MODULE;
    //通信算法
    i2c->adap.algo    = &s3c24xx_i2c_algorithm;
    i2c->adap.retries = 2;
    i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
    i2c->tx_setup     = 50;

    ·······
    /* Note, previous versions of the driver used i2c_add_adapter()
     * to add the bus at any number. We now pass the bus number via
     * the platform data, so if unset it will now default to always
     * being bus 0.
     */

    i2c->adap.nr = i2c->pdata->bus_num;
    i2c->adap.dev.of_node = pdev->dev.of_node;
    //注册适配器核心结构,相当于实现了一个适配器
    ret = i2c_add_numbered_adapter(&i2c->adap);
    ··········
}

其中:通信算法

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
    .master_xfer        = s3c24xx_i2c_xfer,
    .functionality      = s3c24xx_i2c_func,
};

上面probe接口函数中最后使用 i2c_add_numbered_adapter已经实现i2c_adapter结构

注销适配器;
int i2c_del_adapter(struct i2c_adapter *adap)


I2C设备驱动层使用的API

增加/删除i2c_driver

路径:i2c.h linux-3.5\include\linux

#define i2c_add_driver(driver) \
    i2c_register_driver(THIS_MODULE, driver)
/*
 * An i2c_driver is used with one or more i2c_client (device) nodes to access
 * i2c slave chips, on a bus instance associated with some i2c_adapter.
 */

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

功能:注册一个i2c_driver结构
返回值:0成功,负数 失败


注销i2c_driver
void i2c_del_driver(struct i2c_driver *driver);
功能:注销一个i2c_driver结构
返回值:


获得/释放 i2c_adapter

路径:i2c-core.c linux-3.5\drivers\i2c
struct i2c_adapter *i2c_get_adapter(int nr)
功能:通过i2c总线编号获得内核中的i2c_adapter结构地址,然后用户可以使用这个结构地址就可以给i2c_client结构使用,从而实现i2c_client进行总线绑定,从而增加适配器引用计数。
返回值:
NULL:没有找到指定总线编号适配器结构
非NULL:指定nr的适配器结构内存地址

减少引用计数:当使用·i2c_get_adapter·后,需要使用该函数减少引用计数。(如果你的适配器驱动不需要卸载,可以不使用)
void i2c_put_adapter(struct i2c_adapter *adap)


创建/删除 i2c_client

创建i2c_client

struct i2c_client *
i2c_new_probed_device(struct i2c_adapter *adap,
              struct i2c_board_info *info,
              unsigned short const *addr_list,
              int (*probe)(struct i2c_adapter *, unsigned short addr))

功能:根据参数adapinfoaddraddr_list动态创建i2c_client并且进行注册
参数:
adap:i2c_client所依附的适配器结构地址
info:i2c_client基本信息
addt_list: i2c_client的地址(地址定义形式是固定的,一般是定义一个数组,数组必须以I2C_CLIENT_END结束,示例:unsigned short ft5x0x_i2c[]={0x38,I2C_CLIENT_END};
probe:回调函数指针,当创建好i2c_client后,会调用该函数,一般没有什么特殊需求传递NULL
返回值:
非NULL:创建成功,返回创建好的i2c_client结构地址
NULL:创建失败
示例:

struct i2c_adapter *ad;
struct i2c_board_info info=[0];
unsigned short ft5x0x_i2c[]={0x38,I2C_CLIENT_END};
//假设设备挂在i2c-2总线上
ad=i2c_get_adapter(2);

//自己填充board_info 
strcpy(inf.type,"ft5x0x");
info.flags=0;
//动态创建i2c_client并且注册
i2c_new_probed_device(ad,info,ft5x0x_i2c,NULL);

I2C传输、发送接收

路径:i2c-core.c linux-3.5\drivers\i2c
I2C读取数据函数
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
功能:实现标准的I2C读时序,数据可以是N个数据,这个函数调用时候默认已经包含发送从机地址+读方向这一环节了
参数:
client:设备结构
buf:读取数据存放缓冲区
count:读取数据大小 不大于64k
返回值:
失败:负数
成功:成功读取的字节数


I2C发送数据函数
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
功能:实现标准的I2C写时序,数据可以是N个数据,这个函数调用时候默认已经包含发送从机地址+写方向这一环节了
参数:
client:设备结构地址
buf:发送数据存放缓冲区
count:发送数据大小 不大于64k
返回值:
失败:负数
成功:成功发送的字节数


i2c收发一体化函数,收还是发由参数msgs的内存决定
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

功能:根据msgs进行手法控制
参数:
adap:使用哪一个适配器发送信息,一般是取i2c_client结构中的adapter指针作为参数
msgs:具体发送消息指针,一般情况下是一个数组
num:表示前一个参数msgs数组有多少个消息要发送的
返回值:
负数:失败
> 0 表示成功发送i2c_msg数量

补充i2c_msg结构:

struct i2c_msg {
    __u16 addr; /* slave address            */
    __u16 flags;
    __u16 len;      /* msg length               */
    __u8 *buf;      /* pointer to msg data          */
};

重要成员:
addr:要读写的设备地址
flags:表示地址的长度,读写功能。如果是10位地址必须设置I2C_M_TEN,如果是读操作必须设置有I2C_M_TEN······,可以使用或运算合成。
buf:要读写的数据指针。写操作:数据源 读操作:指定存放数据的缓存区
len:读写数据的数据长度


SMBus相关的API

SMBusintel发明的,可以看做是i2c总线的一个子集,很多场合下都可以使用SMBus函数控制i2c设备,他们是兼容的。
SMBus最高速度:100kbit/s
i2c: 100kbit/s 400kbit/s 3.4Mbit/s
内核提供给SMBus相当丰富的API接口
···············
这里不做介绍


I2C子系统编程

I2C子系统下设备驱动有两种模式;

  • 用户模式设备驱动编程:这种方式依赖I2C子系统中的i2c-dev.c这个总线字符设备驱动,这个驱动主设备号是89,这个驱动并没有绑定任何一个从设备,可以使用他控制任何设备,通过ioctl设置要控制的设备地址,然后通过readwrite方式操作i2c总线设备。
    这种方式对应用程序编写者对硬件认识要求高,必须掌握该设备的控制逻辑才可以正确和设备进行通信,实际开发中都是应用和驱动分别作的,写应用程序的人可能完全不懂硬件。

  • 按照前面的框架图,自己去实现一个i2c_clienti2c_driver结构,使用响应函数进行注册。这种方式是比较好的方式,把设备实际控制逻辑封装在驱动层,应用编程序只需要简单调用readwrite函数就可以实现和设备进行数据交互,对硬件只是没什么要求。

常规i2c设备驱动

i2c_client层的实现

有两种方法实现:静态注册动态注册

  • 静态注册方式(实际工作中最常用的方法)
    i2c_client结构所需的信息及注册直接编译在zImage中,不能以模块方式进行编写,对应的i2c_driver也不能通过编译成模块,后期加载。这种方式在调试驱动时候每修改一次代码就需要重新编译内核,然后下载内核测试,所以这种方式比较麻烦,一般是调试好驱动程序后,产品发布的时候在使用这种方式修改内核源码实现i2c设备驱动
  • 动态注册方式:
    可以把i2c_clienti2c_driver分别编写成独立的ko文件,系统启动使用insmod命令安装,这种发放在开发阶段会方便一些。
    静态注册
    使用的一个注册函数int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info,unsigned len)
    功能:i2c_register_board_info将设备信息添加到全局链表__i2c_board_list中,这个函数并不负责创建i2c_client结构,i2c-client结构是在,在适配器注册时,从__i2c_board_list取出该结构生成的i2c设备(struct i2c_client)
    参数:
    busnum:i2c设备所挂的总线编号,根据自己的硬件设备而定
    i2c_board_info:存放i2c物理设备基本信息的i2c_board_info结构指针,一般情况下是一个数组,这样,这个函数就可以一次性注册多个物理上的i2c设备到__i2c_board_list链表上去了。
    len:表明数组上多少要注册的i2c_board_info结构
    返回值:
    0:注册成功
    负数:注册失败
    注意:这个函数实现i2c-boardinfo.c linux-3.5\drivers\i2c中并没有使用EXPORT_SYMBOLEXPORT_SYMBOL_GPL导出,其他模块文件不能使用,也就是你不能在一个模块文件中调用这个函数,(不能把包含有该函数调用的文件编译为ko
    tiny4412开发板中的,触摸屏采用的芯片是(ft5x0x),在板级启动文件中已经有了i2c的驱动,具体文件在mach-tiny4412.c linux-3.5\arch\arm\mach-exynos
    我们可以看到在static void __init smdk4x12_machine_init(void)开发板启动函数中已经有了i2c_register_board_info驱动注册,具体定义的触摸芯片的borard_info定义为
static struct i2c_board_info smdk4x12_i2c_devs1[] __initdata = {
#ifdef CONFIG_TOUCHSCREEN_FT5X0X
    {
        I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)),
        //平台数据  定义的一些与芯片相关的信息
        .platform_data = &ft5x0x_pdata,
    },
#endif
#ifdef CONFIG_TOUCHSCREEN_IT7260
    {
        I2C_BOARD_INFO("IT7260", (0x8C>>1)),
        .irq = TS_INT_GPIO,     /* do gpio_to_irq() in driver */
    ·························

补充内容

#define I2C_BOARD_INFO(dev_type, dev_addr) \
    .type = dev_type, .addr = (dev_addr)

补充内容

static struct ft5x0x_i2c_platform_data ft5x0x_pdata = {
    .gpio_irq       = EXYNOS4_GPX1(6),
    .irq_cfg        = S3C_GPIO_SFN(0xf),
    .screen_max_x   = 800,
    .screen_max_y   = 1280,
    .pressure_max   = 255,
};

我们可看到已经定义了针对ft5x0xboard_info结构,填充了一些信息名称,地址,设备信息等,根据smdk4x12_i2c_devs1[]命名可以看出这是挂接在适配器1上的设备,由于适配器1上的i2c设备有很多,上面定义的结构以宏来区分,改结构定义很长只截取了一小段。
所以看出源码中已经有了对ft5x0x的定义以及注册,

static void __init smdk4x12_machine_init(void)
{
  ······
    s3c_i2c1_set_platdata(&tiny4412_i2c1_data);
    i2c_register_board_info(1, smdk4x12_i2c_devs1,
            ARRAY_SIZE(smdk4x12_i2c_devs1));
·················

}

内核已经实现了对i2c设备的注册,驱动其实在源码中也已经写好,具体是在ft5x06_ts.c linux-3.5\drivers\input\touchscreen,我们可以利用源码中自带的i2c平台设备驱动,在自己写一份自己的驱动,添加到内核,把系统自带的排除出去。试验一下效果。
具体做法:

//my_ft5x0x.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/irq.h>


//定义本驱动支持的设备列表,这里的name和client.name进行比较,相同表示支持
const struct i2c_device_id ft5x0x_id_table[]={

    {"ft5x0x_ts,0"},    //名字与设备层相同    匹配使用
    {}                  //空成员  表示结束
};

int ft5x0x_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
    printk("我写的驱动已经挂接上了");
   printk("client->name:%s client->addr:0x:%x\r\n",client->name,client->addr);
}
int ft5x0x_remove(struct i2c_client *client)
{

    printk("我写的驱动已经移除了");
}

//定义i2c_driver 结构并初始化成员
static struct i2c_driver ft5x0x_driver={
    .probe     =  ft5x0x_probe,
    .remove    =  ft5x0x_remove,
    .id_table =   ft5x0x_id_table,
    .driver    ={
        .name="my_i2c",   //名字不做匹配使用
        .owner=THIS_MODULE,
    },
};

//驱动初始化
static int __init ft5x0x_dev_init(void)
{
    int ret;
    ret=i2c_add_driver(&ft5x0x_driver);
    if(ret<0)
        {
        printk("驱动注册失败\r\n");
    }
    return 0;
}

//驱动卸载
static void __exit ft5x0x_dev_exit(void)
{

}

module_init(ft5x0x_dev_init);
module_exit(ft5x0x_dev_exit);
MODULE_LICENSE("GPL");

把自己写的驱动my_ft5x0x.c复制到linux-3.5\drivers\input\touchscreen,修改此目录下的Makefile将源码中的触摸屏芯片的驱动ft5x06_ts.c注释掉,添加自己的驱动my_ft5x0x.c

obj-$(CONFIG_TOUCHSCREEN_W90X900)  += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o
#obj-$(CONFIG_TOUCHSCREEN_FT5X0X)  += ft5x06_ts.o
obj-$(CONFIG_TOUCHSCREEN_FT5X0X)   += my_ft5x0x.o

回到linux-3.5根目录重新编译内核,进行内核烧写,启动开发板,查看开发板运行log日志,是否将自己写的i2c驱动模板成功进行挂接。

[    2.000000] usbcore: registered new interface driver btusb
[    2.000000] cpuidle: using governor ladder
[    1.920000] 我写的驱动已经挂接上了
[    2.020000]client->name:ft5x0x_ts client->addr:0x:38
[    3.025000] usb 1-2: New USB device strings: Mfr=0, Product=0, SerialNumber=0

在驱动代码中实现对触摸屏触控芯片固件数据的读取

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/irq.h>
//设置全局变量
struct i2c_client *clt;

void test_and_set(struct i2c_client *clt)
{
    char subaddr = 0xA6;
    s32 val;
    struct i2c_msg msg[2]={0};
    msg[0].addr   = clt->addr;    //器件地址
    msg[0].flags  = 0;           //地址长度七位
    msg[0].len    = 1;           //数据长度 1位
    msg[0].buf    = &subaddr;    //发送数据缓冲区

    msg[1].addr   = clt->addr;    //器件地址
    msg[1].flags  = I2C_M_RD;            //地址长度七位
    msg[1].len    = 1;           //数据长度 1位
    msg[1].buf    = &val;    //发送数据缓冲区


    //第一种方式  利用smbus读取函数
    //读取器件ID      寄存器地址0XA6 
    val = i2c_smbus_read_byte(clt);
    printk("firmware ID:%d\r\n",val);

    //第二种方式
    i2c_master_send(clt,&subaddr,1 );
    i2c_master_recv(clt, (char *) val,1);
    printk("firmware ID:%d\r\n",val);


    //第三种方式
    i2c_transfer(clt->adapter,msg,2);
    printk("firmware ID:%d\r\n",val);
}

//定义本驱动支持的设备列表,这里的name和client.name进行比较,相同表示支持
const struct i2c_device_id ft5x0x_id_table[]={

    {"ft5x0x_ts",(0x70) >> 1},  //名字与设备层相同    匹配使用
    {}                  //空成员  表示结束
};

int ft5x0x_probe(struct i2c_client *client,const struct i2c_device_id *id)
{

    printk("我写的驱动已经挂接上了\r\n");
    printk("client->name:%s client->addr:0x:%x\r\n",client->name,client->addr);
    test_and_set(client);
}
int ft5x0x_remove(struct i2c_client *client)
{

    printk("我写的驱动已经移除了");
}

//定义i2c_driver 结构并初始化成员
static struct i2c_driver ft5x0x_driver={
    .probe     =  ft5x0x_probe,
    .remove    =  ft5x0x_remove,
    .id_table =   ft5x0x_id_table,
    .driver    ={
        .name="my_i2c",   //名字不做匹配使用
        .owner=THIS_MODULE,
    },
};

//驱动初始化
static int __init ft5x0x_dev_init(void)
{
    int ret;
    ret=i2c_add_driver(&ft5x0x_driver);
    if(ret<0)
        {
        printk("驱动注册失败\r\n");
    }

    return 0;
}

//驱动卸载
static void __exit ft5x0x_dev_exit(void)
{
  i2c_del_driver(&ft5x0x_driver);
}

module_init(ft5x0x_dev_init);
module_exit(ft5x0x_dev_exit);
MODULE_LICENSE("GPL");

重新编译内核开发板运行效果

[    1.920000] 我写的驱动已经挂接上了
[    1.920000] client->name:ft5x0x_ts client->addr:0x:38
[    1.920000] firmware ID:24
[    1.920000] firmware ID:24
[    1.920000] firmware ID:24

读取坐标

根据手册自己尝试,注意,此款触摸芯片,触发一次产生中断,可以读取数据信息,但是在中断函数中不要使用i2c读取函数,因为,这些函数会造成系统休眠,在中断函数中使用,会造成内核崩溃。最好采用工作队列、tasklet等方式。


动态注册

前面讲的方法是通过静态方式注册,自己并没有编写设备信息,只是简单的写了设备驱动层代码,而且只能编译进内核,不能以模块的方式添加进内核,所以在调试的时候会很麻烦,不断地烧写内核,编译内核,虽然I2C设备常用的驱动方式就是编译进内核的方式,但是在调试的时候可以采用以模块方式安装的办法(动态注册),方便调试,驱动模块成熟以后在编译进内核(修改为静态注册的方式)。

具体步骤

  • 系统源码中已经自带了触摸屏驱动,需要去除掉其驱动支持,在linux内核执行make menuconfig-》设备驱动-》触摸屏驱动-》ft5x0x将选项勾掉,重新编译内核,烧写内核。
    这是什么原理呢?我们在源码中可以看到,添加驱动是依赖宏来添加的,内核裁剪的时候就是将其对应的宏取消掉,这样它就不会出现在适配器队列中,也就不会创建对应的i2c_client,就是说内核找不到这个设备的信息,驱动也就失效了,我们就可以添加自己的触摸屏驱动了。
    编写i2c_client代码
    设备代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/irq.h>

struct i2c_client *clt;

unsigned short ft5x0x_i2c[]={0x38,I2C_CLIENT_END};

//驱动初始化
static int __init ft5x0x_client_init(void)
{
    int ret=0;
    //定义适配器结构体
    struct i2c_adapter *ad;
    struct i2c_board_info info={0};
    ad=i2c_get_adapter(1);
    //自己填充board_info 
    strcpy(info.type,"ft5x0x_ts");
    info.flags=0;
    //动态创建i2c_client并且注册
    clt=i2c_new_probed_device(ad,&info,ft5x0x_i2c,NULL);
    if(clt==NULL)
    {
        printk("creat is fail\n");
    }
    i2c_put_adapter(ad);
}
//驱动卸载
static void __exit ft5x0x_client_exit(void)
{
    i2c_unregister_device(clt);
    //i2c_del_adapter(ad);
}

module_init(ft5x0x_client_init);
module_exit(ft5x0x_client_exit);
MODULE_LICENSE("GPL");

驱动代码

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/irq.h>
//设置全局变量


void test_and_set(struct i2c_client *clt)
{
    char subaddr = 0xA6;
    s32 val;
    struct i2c_msg msg[2]={0};
    msg[0].addr   = clt->addr;    //器件地址
    msg[0].flags  = 0;           //地址长度七位
    msg[0].len    = 1;           //数据长度 1位
    msg[0].buf    = &subaddr;    //发送数据缓冲区

    msg[1].addr   = clt->addr;    //器件地址
    msg[1].flags  = I2C_M_RD;            //地址长度七位
    msg[1].len    = 1;           //数据长度 1位
    msg[1].buf    = &val;    //发送数据缓冲区


    //第一种方式  利用smbus读取函数
    //读取器件ID      寄存器地址0XA6 
    val = i2c_smbus_read_byte(clt);
    printk("firmware ID:%d\r\n",val);

    //第二种方式
    i2c_master_send(clt,&subaddr,1 );
    i2c_master_recv(clt, (char *)&val,1);
    printk("firmware ID:%d\r\n",val);


    //第三种方式
    i2c_transfer(clt->adapter,msg,2);
    printk("firmware ID:%d\r\n",val);
}

//定义本驱动支持的设备列表,这里的name和client.name进行比较,相同表示支持
const struct i2c_device_id ft5x0x_id_table[]={

    {"ft5x0x_ts",(0x70) >> 1},  //名字与设备层相同    匹配使用
    {}                  //空成员  表示结束
};

int ft5x0x_probe(struct i2c_client *client,const struct i2c_device_id *id)
{

    printk("我写的驱动已经挂接上了\r\n");
    printk("client->name:%s client->addr:0x:%x\r\n",client->name,client->addr);
    test_and_set(client);
}
int ft5x0x_remove(struct i2c_client *client)
{

    printk("我写的驱动已经移除了");
}

//定义i2c_driver 结构并初始化成员
static struct i2c_driver ft5x0x_driver={
    .probe     =  ft5x0x_probe,
    .remove    =  ft5x0x_remove,
    .id_table =   ft5x0x_id_table,
    .driver    ={
        .name="my_i2c",   //名字不做匹配使用
        .owner=THIS_MODULE,
    },
};

//驱动初始化
static int __init ft5x0x_dev_init(void)
{
    int ret;
    ret=i2c_add_driver(&ft5x0x_driver);
    if(ret<0)
        {
        printk("驱动注册失败\r\n");
    }

    return 0;
}

//驱动卸载
static void __exit ft5x0x_dev_exit(void)
{
    i2c_del_driver(&ft5x0x_driver);
}

module_init(ft5x0x_dev_init);
module_exit(ft5x0x_dev_exit);
MODULE_LICENSE("GPL");

Makefile

KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
    make -C $(KERN_DIR) M=`pwd` modules
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
obj-m += my_i2c_client.o
obj-m += my_i2c_driver.o

开发板运行效果

[   58.510000] 我写的驱动已经挂接上了
[   58.510000] client->name:ft5x0x_ts client->addr:0x:38
[   58.510000] firmware ID:24
[   58.510000] firmware ID:24
[   58.510000] firmware ID:24

猜你喜欢

转载自blog.csdn.net/z961968549/article/details/78821065
今日推荐