IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试

时间:2021-08-20 07:47:04

IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试

目的不是为了编写TSC2007驱动,是为了学习IIC驱动的编写,读一下TSC2007的ADC数据进行练习,,

Linux主机驱动和外设驱动分离思想

外设驱动→API→主机驱动→板级逻辑--具体的i2c设备(camera,ts,eeprom等等)

  • 主机驱动:根据控制器硬件手册,配置SOC的I2C寄存器产生波形,这个不在我的研究范围之内

  • linux应用工程师不需要驱动和硬件的细节.

    linux驱动工程师:不需要考虑硬件!由BSP工程师提供标准的主机驱动,驱动工程师只需要完成“外设驱动”

    内核函数接口:(API)。主机驱动提供给外设驱动的函数接口。

Tip : 主机驱动 由 BSP工程师去完成就行了 ,我们不需要管 ,不需要亲自去设置I2C的寄存器去产生波形,我们要做的就是使用内核中提供的I2C函数去配置声卡这些外部设备就OK!

外设驱动与板级裸机

  • 外设驱动:针对具体的外部器件的代码。

    例如,摄像头以及声卡中i2c用来配置外部设备(声卡和摄像头)→地址和配置的内容都不一样!就是说,不同的 外部设备对应着不同的板级驱动,就是说,它指的是设备相关的代码,通常建立在内核提供的函数的基础上,这些函数由主机驱动实现毋庸赘言!

  • 板级逻辑:描述主机和外部设备是怎么连接的,描述设备与SOC是如何连接的部分代码(使用那组IIC等信息).

所以说 最主要的东西就是,..... 就是 ... 就是 ... 就是 ..... 如何使用内核提供的接口来熟练操作IIC

对于3.0版本的KERNEL来说,I2C涉及到的API函数

注册i2c设备:i2c_board_info

驱动注册函数和结构体:i2c_del_driver/i2c_add_driver,i2c_driver(描述I2C驱动的结构体)

读写函数和结构体:i2c_transfer(传输数据的函数,收/发),i2c_msg(放置传输数据的结构体)


设备-i2c设备注册以及设备注册之后的查询方法

查询i2c设备地址:ls /sys/bus/i2c/devices/

​ 怎么和原理图以及外部设备对应:3-0038→I2C_3_SCL(addr:datasheet中查0x38,注意Linux内核中使用的 I2C地址是7位地址,即前7位,(不包括读写标志位)而datasheet中一般给出的是8位的I2C地址,包括读写标志位)

查询i2c设备名称:cat /sys/bus/i2c/devices/3-0038/name

[root@iTOP-4412]# ls sys/bus/i2c/devices/
0-003a 1-0066 3-005d 5-0068 7-0048 i2c-1 i2c-4 i2c-7
1-0006 3-0038 5-0018 7-0038 i2c-0 i2c-3 i2c-5

3-0038 3表示第三组IIC ,, 0038表示该设备的前7位地址为0038


********************************华丽的分割线************************

实验 ,通过I2C总线读取触摸屏的X,Y轴坐标

硬件资源

讯为ITOP4412 SCP开发板

4.3寸LCD屏,主控TSC2007

软件版本

内核版本: KERNEL3.0

编译器版本: gcc version 4.4.1 (Sourcery G++ Lite 2009q3-67)

1. 在make menuconfig中取消已有的驱动程序,以便于我们编写自己的

menuconfig中去掉触摸主控TSC2007的驱动

│ Symbol: TOUCHSCREEN_TSC2007 [=n]                                                                                                     │
│ Type : tristate │
│ Prompt: TSC2007 based touchscreens │
│ Defined at drivers/input/touchscreen/Kconfig:692 │
│ Depends on: !S390 && INPUT [=y] && INPUT_TOUCHSCREEN [=y] && I2C [=y] │
│ Location: │
│ -> Device Drivers │
│ -> Input device support │
│ -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y]) │
│ -> Touchscreens (INPUT_TOUCHSCREEN [=y])
2. 添加设备,平台文件的方式,注册我们的I2C设备

添加i2c设备:i2c_devs7[]中添加

/* I2C7 */
static struct i2c_board_info i2c_devs7[] __initdata = { /*********略*****/
//这个已经取消配置勾选了,不会生效
/* add by cym 20130417 for TSC2007 TouchScreen */
#ifdef CONFIG_TOUCHSCREEN_TSC2007
{
I2C_BOARD_INFO("tsc2007", 0x48),
.type = "tsc2007",
.platform_data = &tsc2007_info,
.irq = IRQ_EINT(0),
},
#endif
//添加我们自定义的平台设备信息
//CONFIG_TOUCHSCREEN_TSC2007,然后添加下面这个
//如果地址冲突,那么是无法注册进去的
{
I2C_BOARD_INFO("tsc2007", 0x48),
},
/* end add */
#endif /*********略*****/ };

编译=>烧写内核,然后 查看设备信息是否生效

cat /sys/bus/i2c/devices/7-0048/name结果是tsc2007

3.驱动-i2c驱动注册和卸载。i2c设备驱动初始化完成-进入probe函数。
  • 用到的内核API : i2c_del_driver/i2c_add_driver,i2c_driver
  • module_init和late_initcall的区别:module_init先运行,late_initcall后运行
static const struct i2c_device_id i2c_test_id[] = {
//第一个参数,设备名,和.name一样,第二个参数是自定义硬件版本,用的很少
//I2C提供了一种机制,用于区分不同的硬件版本,很少用的
//像下面这样,不用管那个参数,写0就行了
{ I2C_DEVICE_NAME, 0 },
{ }
}; static struct i2c_driver i2c_test_driver = {
.probe = i2c_test_probe,
.remove = __devexit_p(i2c_test_remove),
//用于区分不同的硬件版本,这个用到很少,
.id_table = i2c_test_id,
.driver = {
.name = I2C_DEVICE_NAME,
.owner = THIS_MODULE,
},
};

大多数情况下IIC都是在内核中完成工作的,向触摸屏的话通过输入子系统与用户空间进行交互,向声卡的话,一般通过中断触发IIC进行调整音量等操作

4.驱动-i2c数据的传输
  • 用到的内核API: i2c_transfer,i2c_msg
	struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_RD 0x0001 /* read data, from slave to master */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};

要完成i2c的读,必须要先写再读!写的时候,你要通知从机,你要读哪个寄存器!,I2C协议的,,,基本原则

5.Liux-i2c利用杂项驱动完成应用层对i2c的读和写

直接看后面源码就好了...


内核中关于i2c_msg的说明

/**
* struct i2c_msg - an I2C transaction segment beginning with START
* @addr: Slave address, either seven or ten bits. When this is a ten
* bit address, I2C_M_TEN must be set in @flags and the adapter
* must support I2C_FUNC_10BIT_ADDR.
* @flags: I2C_M_RD is handled by all adapters. No other flags may be
* provided unless the adapter exported the relevant I2C_FUNC_*
* flags through i2c_check_functionality().
* @len: Number of data bytes in @buf being read from or written to the
* I2C slave address. For read transactions where I2C_M_RECV_LEN
* is set, the caller guarantees that this buffer can hold up to
* 32 bytes in addition to the initial length byte sent by the
* slave (plus, if used, the SMBus PEC); and this value will be
* incremented by the number of block data bytes received.
* @buf: The buffer into which data is read, or from which it's written.
*
* An i2c_msg is the low level representation of one segment of an I2C
* transaction. It is visible to drivers in the @i2c_transfer() procedure,
* to userspace from i2c-dev, and to I2C adapter drivers through the
* @i2c_adapter.@master_xfer() method.
*
* Except when I2C "protocol mangling" is used, all I2C adapters implement
* the standard rules for I2C transactions. Each transaction begins with a
* START. That is followed by the slave address, and a bit encoding read
* versus write. Then follow all the data bytes, possibly including a byte
* with SMBus PEC. The transfer terminates with a NAK, or when all those
* bytes have been transferred and ACKed. If this is the last message in a
* group, it is followed by a STOP. Otherwise it is followed by the next
* @i2c_msg transaction segment, beginning with a (repeated) START.
*
* Alternatively, when the adapter supports I2C_FUNC_PROTOCOL_MANGLING then
* passing certain @flags may have changed those standard protocol behaviors.
* Those flags are only for use with broken/nonconforming slaves, and with
* adapters which are known to support the specific mangling options they
* need (one or more of IGNORE_NAK, NO_RD_ACK, NOSTART, and REV_DIR_ADDR).
*/
struct i2c_msg {
__u16 addr; /* slave address ,在平台文件中已经注册了,直接使用平台文件的注册信息就行 */
__u16 flags; //标志位 下面这些宏 都是标志
//用于选择使用8位地址还是10位地址,不选择 就是8位地址
#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_PROTOCOL_MANGLING */
#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 */
//长度 ,I2C的传输的数据长度,给i2c_transfer函数使用
__u16 len; /* msg length */
//传输数据用的buf ,, 将要传输的值赋给buf就ok
__u8 *buf; /* pointer to msg data */
};

******************五彩缤纷的分割线*******************

外设驱动_TSC2007

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif
#include <linux/regulator/consumer.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <plat/ft5x0x_touch.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h> #define I2C_DEVICE_NAME "tsc2007" //便于传递probe中获得的client结构体
static struct i2c_client * this_client; static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id);
static int __devexit i2c_test_remove(struct i2c_client *client);
static int __init i2c_test_init(void);
static void __exit i2c_test_exit(void);
static struct i2c_driver i2c_test_driver; /*
TSC2007的操作步骤
发送地址,写的方式
发送一条设置命令
停止
发送地址,读的方式
读取一个数据
停止
*/
//测试 读 不是我们设置的val的默认值 那么 就是 Okay的了 应该 static int i2c_tes_read_reg(struct i2c_client *client,u8 addr, u8 *pdata) {
u8 buf1[4] = { 0 };
u8 buf2[4] = { 0 };//读的时候,读到的值存储在这里
//这个结构体中包含一组读的时候用的,和一组写的时候用的
//这个结构体,读的时候需要两组(先写地址后读,so需要两组),写的时候只需要一组
struct i2c_msg msgs[] = {
{
//可以打印出来 看看 ,这个是设备的地址,从设备注册的平台信息中获得的
.addr = client->addr, //0x38
.flags = 0, //写 0是写标志
//长度是1 ,就是说读/写 一次 ,8位数据 ,
.len = 1, //要写的数据的长度
.buf = buf1,
},
{
.addr = client->addr,//设备地址 7位的应该是
.flags = I2C_M_RD, //R/W标志位
.len = 1,// 读取的长度 ,,1个字节 (8位一般)
.buf = buf2,//读取到的值存储到这个buf中
},
};
int ret;
//这个addr的参数,不是设备地址 ,设备地址是这个client->addr,
//这个addr就是要写入的数据
buf1[0] = addr;//要写的内容 先写I2C内部寄存器的地址,告诉设备我们要读哪个寄存器
//buf1[1]的话 ,那就是要写的数据了 ,
//i2c_transfer数据传输函数
//client->adapter从匹配的平台设备信息中读取出使用的那组I2C接口
//msgs
//先写入I2C设备内部寄存器地址(buf1),然后读取数据到buf2
//最后一个参数 ,表示msg这样的结构体数组,有几个成员
//就是说msg这样的结构体有几个 ,我们设置了两个!
//
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0) {
pr_err("read reg (0x%02x) error, %d\n", addr, ret);
} else {
*pdata = buf2[0];
}
return ret;
} static int i2c_tes_read_fw_reg(struct i2c_client *client,unsigned char *val)
{
int ret;
//设定一个默认值,如果出错,那么val为默认值0xff
*val = 0xff;
ret = i2c_tes_read_reg(client,0xcf, val);
return ret;
} int tsc2007_open(struct inode * inode_point, struct file * file_ponit){
printk("%s\n",__FUNCTION__);
return 0;
} int tsc2007_release(struct inode * inode_point, struct file * file_ponit){
printk("%s\n",__FUNCTION__);
return 0;
} //从buf传入一个数据进去(指定读取I2C设备内部的内容/内部寄存器地址)
//然后根据buf传入的这个数据,读取I2C设备内部相应的位置,将数据读取出来,再存入buf
ssize_t tsc2007_read(struct file * filp_ponit, char __user * buf, size_t size, loff_t * loff){
int retval;
u8 reg_data; //通过this_client这个全局变量,将probe中得到的client传递给read函数
//这个结构体中包含一组读的时候用的,和一组写的时候用的
//这个结构体,读的时候需要两组(先写地址后读,so需要两组),写的时候只需要一组
struct i2c_msg msgs[] = {
{
//可以打印出来 看看 ,这个是设备的地址,从设备注册的平台信息中获得的
.addr = this_client->addr, //0x38
.flags = 0, //写 0是写标志
//长度是1 ,就是说读/写 一次 ,8位数据 ,
.len = 1, //要写的数据的长度
.buf = &reg_data,
},
{
.addr = this_client->addr,//设备地址 7位的应该是
.flags = I2C_M_RD, //R/W标志位
.len = 1,// 读取的长度 ,,1个字节 (8位一般)
.buf = &reg_data,//读取到的值存储到这个buf中
},
}; retval = copy_from_user(&reg_data,buf,1);
if(retval<0)
return -EFAULT;
//msgs
//先写入I2C设备内部寄存器地址(buf1),然后读取数据到buf2
//最后一个参数 ,表示msg这样的结构体数组,有几个成员
//就是说msg这样的结构体有几个 ,我们设置了两个!
//
retval = i2c_transfer(this_client->adapter, msgs, 2);
if (retval < 0) {
pr_err("read retval (0x%02x) error, %d\n", reg_data, retval);
return retval;
}
retval = copy_to_user(buf,&reg_data,1);
return retval;
} /*读的流程*/
//写I2C设备地址,写设备内部寄存器的地址,读该位置的数据
//除了写设备地址,要写一次(位置),读一次(数据)
/*写的流程*/
//写I2C设备地址,写设备内部寄存器的地址,向该位置写数据
//除了写设备地址,要写一次(位置),再写一次(数据)
//所以写函数,要写入两次数据
ssize_t tsc2007_write(struct file * filp_ponit, const char __user * buf, size_t size, loff_t * loff){
int retval;
u8 buffer[2];
//从用户控件拷贝来两个数据,一次,
//第一个数据用来指定写入I2C设备的内部地址
//第二个数据用来指定向第一个参数指定的地址写入的内容 //结构体的另一种赋值方法
struct i2c_msg msgs[1];
msgs[0].addr = this_client->addr;//设备地址 7位的应该是
msgs[0].flags = 0; //写标志
msgs[0].len = 2; // 要写入的数据的长度
msgs[0].buf = buffer; retval = copy_from_user(buffer,buffer,2);
if(retval<0)
return -EFAULT; //1个 msgs 第三个参数的说明
//transfer应该会按照msgs结构体的内容去按顺序传输数据
retval = i2c_transfer(this_client->adapter, msgs, 1);
if (retval < 0) {
pr_err("read retval (0x%02x) error, %d\n", buffer[0], retval);
} return retval;
} static struct file_operations tsc2007_ops = {
.owner = THIS_MODULE,
.open = tsc2007_open,
.release= tsc2007_release,
.write = tsc2007_write,
.read = tsc2007_read,
}; static struct miscdevice tsc2007_dev = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &tsc2007_ops,
.name = "i2c_tsc2007",
}; static int i2c_test_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
unsigned char val;
printk("==%s:\n", __FUNCTION__); i2c_tes_read_fw_reg(client,&val); this_client = client ; misc_register(&tsc2007_dev); return 0;
} static int __devexit i2c_test_remove(struct i2c_client *client)
{
//移除设备的时候,将client清空(赋值NULL),移除的干净
//clear i2c data ... it will be NULL.
i2c_set_clientdata(client, NULL);
printk("==%s:\n", __FUNCTION__);
return 0;
} static const struct i2c_device_id i2c_test_id[] = {
{ I2C_DEVICE_NAME, 0 },
{ }
}; static int __init i2c_test_init(void)
{
//print fuction name
printk(KERN_EMERG "==%s\n",__FUNCTION__);
//register I2C driver
return i2c_add_driver(&i2c_test_driver);
}
static void __exit i2c_test_exit(void)
{
printk("==%s:\n", __FUNCTION__);
//unload i2c driver
i2c_del_driver(&i2c_test_driver);
} static struct i2c_driver i2c_test_driver = {
.probe = i2c_test_probe,
.remove = __devexit_p(i2c_test_remove),
.id_table = i2c_test_id,
.driver = {
.name = I2C_DEVICE_NAME,
.owner = THIS_MODULE,
},
}; //load module ,in the end.
late_initcall(i2c_test_init);
module_exit(i2c_test_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("rather_dog");
MODULE_DESCRIPTION("I2C_TEST");

应用程序

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h> //表示要读取的x轴还是y轴的数据
#define TSC2007_X_POSITION 0xcf
#define TSC2007_Y_POSITION 0xdf /*
读取触摸屏的x和y坐标,并打印出来,不停的循环打印
*/
int main(int argc,char **argv){
int fd,retval;
unsigned int x_val,y_val;
//字符串指针常量,不需要申请空间
//驱动的节点
const char *i2c_device = "/dev/i2c_tsc2007";
unsigned char buffer[1]; printf("open %s!\n",i2c_device);
if((fd = open(i2c_device,O_RDWR|O_NDELAY))<0)
printf("APP open %s failed",i2c_device);
else{
printf("APP open %s success!\n",i2c_device);
} while(1){
buffer[0] = TSC2007_X_POSITION;
read(fd,buffer,1);
x_val = buffer[0];
buffer[0] = TSC2007_Y_POSITION;
read(fd,buffer,1);
y_val = buffer[0];
printf("x= %d\ty= %d\n",x_val,y_val);
sleep(1);
} close(fd);
return 0;
}