linux驱动(第十五课,I2CBUS)

I2CBUS,在协议标准里,分为MASTER和SLAVE。
linux里,I2CMASTER被称为adaptor,对应的,I2CSLAVE被称为client。
为了区别于SPIBUS。
I2C是可以允许多个主机的,所以叫做MASTER,显得并不合适。而且,I2C的节点,既可以作为MASTER,也可以切换为SLAVE。
SPI是只有一个主机的,所以叫做MASTER。
所有的访问操作,都是由MASTER发起的。

I2C的驱动架构,分为I2Cclient,I2Ccore,I2Cadaptor。
其中,core是内核中的通用部分,为上层提供统一的API,并对I2C子系统中的其他模块进行注册注销等管理。
I2Cadaptor,通常在SOC内部,用于控制I2C主控器产生时序。通常,SOC厂商会负责设计主控器的驱动。
I2C设备驱动,调用I2Ccore提供的统一API,控制I2C主控器的行为,发出不同的操作周期,从而实现对I2C从设备的访问。
I2Cdev,将I2C主控器实现为一个CDEV,这样,用户程序可以直接访问/dev/i2c-N设备节点,来访问I2C主控器,进而对I2C从设备访问。
I2Ccore,屏蔽了不同的I2C主控器,使得I2C设备驱动仅关心如何操作I2C从设备,而不需要了解I2C主控器的细节,这样,I2C设备驱动可以独立开来,适用于不同的硬件平台。

类似于struct platform_device,I2C的设备也有一个结构体来定义。
来看看I2C的设备的定义。

struct i2c_client{
	struct device dev;
	char name[I2C_NAME_SIZE];
	
	struct list_head detected;

	unsigned short addr;
	unsigned short flags;
	int irq;
	
	struct i2c_adapter* adapter;
};

它内嵌一个DEVICE对象,是一个DEVICE对象的衍生对象。它是I2CClient的控制块。
它关联着一个ADAPTER,这是CLIENT所在的BUS的主控器,用来产生时序。
它内嵌一个链节,所以可以形成链表。

类似于struct platform_device,I2C的驱动也有一个结构体来定义。
再来看看I2CDRIVER的结构体。

struct i2c_driver{
	struct device_driver driver;
	const struct i2c_device_id * id_table;
	
	int (*probe)(struct i2c_client *, const struct i2c_device_id*);
	int (*remove)(struct i2c_client *);
	...
};

module_i2c_driver(xxxdriver);

struct i2c_device_id{
	char name[I2C_NAME_SIZE];
	kernel_ulong_t driver_data;
};

MODULE_DEVICE_TABLE(i2c, xxx_idtable);

类似与platform_driver,宏拟函数module_i2c_driver用来将一个i2c_driver注册到内核中。宏MODULE_DEVICE_TABLE用来声明一个idtable。

与I2CDRIVER相关的内核API如下。
i2c_add_driver,
i2c_del_driver,

i2c_set_clientdata,
将一个通配句柄填充到client.dev.driver_data中。通常用它来形成回溯引用。
i2c_get_clientdata,
从client.dev.driver_data中获取通配句柄。

i2c_master_send,
利用主控器向client发送数据。将buf中的数据逐个发送出去。
i2c_master_recv,
利用主控器从client接收数据。将接收的数据存放在buf中。
i2c_transfer,
利用主控器,完成一个操作序列。操作序列放在一个VectorTable中,数组中的元素是i2c_msg。
其中用到了i2c_msg结构体,它是一个描述块。

struct i2c_msg{
	__u16 addr;
	__u16 flags;
	__u16 len;
	__u8* buf;
};

来看一个简单的例子。

static struct i2c_msg msgs[2];

msgs[0].addr  = 0x48;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = txbuf;
msgs[0].buf[0] = 0x0;


msgs[1].addr  = 0x48;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 2;
msgs[1].buf = rxbuf;
msgs[1].buf[0] = 0x0;
msgs[1].buf[1] = 0x0;

i2c_transfer(client->adapter, msgs, 2);

主控器发送了一个字节,接收了两个字节。

如果SMBUS,则I2C设备是兼容的。
内核提供了一些针对SMBUS的API。
i2c_smbus_read_byte_data,
i2c_smbus_write_byte_data,
i2c_smbus_read_block_data,
i2c_smbus_write_block_data,
这些函数将使主控器按照SMBUS的协议要求在产生操作时序。

推荐使用DTB,让内核自动创建I2C设备对象,填充,并注册。

整个驱动分为几个部分:
1)Derived_DEV_CB定义,并实例化。
2)UADEV的DRIVER的实例化,并填充。
3)UADEV的相关驱动操作函数编写,分为机制性函数的编写和事务性函数的编写。
4)I2CDRIVER的实例化,并填充,注册到内核中。
5)IDTABLE的实例化,注册到内核中。
6)probe函数编写,在其中注册用户可访问设备(User Accessable Device),如CDEV,BDEV,NDEV等。及其对应的DRIVER。
7)remove函数编写,在其中逆操作。
可以看出,与常规的UADEV的编写相比,多了几个部分,就是与BUSDEV相关的DRIVER。

来看一个实际的例子。

struct mpu6050_dev{
	struct cdev cdev;
	atomic_t available;
	struct i2c_client * client;
};

static struct mpu6050_dev* mpu6050;

static const struct file_operations mpu6050_ops = {
	.ioctl = mpu6050_ioctl,
};

static long mpu6050_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
{
	struct mpu6050_dev* mpu6050p = filp->private_data;
	struct atg_val = val;	
	...
	switch(cmd){
	case MPU6050_GET_VAL:
		val.accelx = i2c_smbus_read_word_data(mpu6050p->client, ACCEL_XOUT_H);
		val.accely = i2c_smbus_read_word_data(mpu6050p->client, ACCEL_YOUT_H);
		val.accelz = i2c_smbus_read_word_data(mpu6050p->client, ACCEL_ZOUT_H);
		...
		ret = copy_to_user((struct atg_val __user *)arg, &val, sizeof(struct atg_val));
		
		break;
	default:
		return -ENOTTY;		
	}
	...
	return 0;
}


static struct i2c_driver mpu6050_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "mpu6050",
	},
	.probe = mpu6050_probe,
	.remove = mpu6050_remove,
	.id_table = mpu6050_id,
};
module_i2c_driver(mpu6050_driver);

static const struct i2c_device_id mpu6050_id[] = {
	{"mpu6050, 0"},
	{}
};
MODULE_DEVICE_TABLE(i2c, mpu6050_id);

static int mpu6050_probe(struct i2c_client* client, const struct i2c_device_id* id)
{
	...
	mpu6050 = kzalloc(sizeof(struct mpu6050_dev), GFP_KERNEL);
	
	i2c_set_clientdata(client, mpu6050);
	mpu6050->client = client;

	ret = i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA);

	i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x80);
	msleep(200);
	i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x40);
	i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x00);
	i2c_smbus_write_byte_data(client, SMPLRT_DIV, 0x7);
	i2c_smbus_write_byte_data(client, CONFIG, 0x6);
	i2c_smbus_write_byte_data(client, GYRO_CONFIG, 0x3 << 3);
	i2c_smbus_write_byte_data(client, ACCEL_CONFIG, 0x3 << 3 );
	
	atomic_set(&mpu6050->available, 1);
	...
	mpu6050_setup_cdev(mpu6050);
	...
}

static mpu6050_remove(struct i2c_client* client)
{
	...
}

首先,我们定义了一个衍生设备的控制块。
我们希望设备是用户可访问的,所以我们让设备衍生于CDEV。
所以设备内嵌了一个CDEV对象。另一方面,这个设备又是一个I2CBUS上的设备。所以它同样需要衍生于I2CClient,这里使用了实体标记,因为内核会根据DTB生成I2CClient对象,所以,只需要关联到I2CClient的对象即可,避免了内嵌I2CClient。

然后,我们实例化了一个FOPS,并填充。

然后,我们编写了FOPS中的对应操作函数。

然后,我们实例化了一个I2CDRIVER,并填充,注册。

然后,我们实例化了一个IDTable,并注册。

然后,我们编写了probe函数,在其中,我们创建了衍生设备的实体,并填充给全局的实体标签。
之后,为client和mpu6050_dev,建立了回溯引用。形成环路。
之后,利用内核服务向client 写入一系列数据,用来控制client进入初始化状态。

然后,我们编写了remove函数,在其中,进行了逆操作。

我们可以在SHELL中测试。

#depmod
#modprobe mpu6050
#mknod /dev/mpu6050 c 256 9

之后,就可以进行文件操作了。

前面说过,DTB是推荐的创建client的方式。
我们来看看,DTS里面究竟是如何定义的。

i2c@138B0000{
	samsung,i2c-sda-delay = <100>;
	samsung,i2c-max-bus-freq = <2000>;
	pinctrl-0 = <&i2c5_bus>;
	pinctrl-names = "default";
	status = "okay";
	
	mpu6050@68{
		compatible = "fs4412, mpu6050";
		reg = <0x68>;
	};
};

i2c@138B0000是一个I2C主控器,在这个主控器所控制的I2CBUS上,有一个I2CClient,即mpu6050@68。
在内核启动时,会遍历注册的DRIVER,找到匹配的i2c_driver。

这是一个典型的多机制协同工作的例子。在一个compound device中,例如本例中的mpu6050,它的一部分是CDEV,另一部分,又关联到一个I2CClient。
CDEV提供了用户界面,可以让用户访问到该设备。另一方面,这个设备又可以利用关联的I2CClient对象,请求I2CBUS上的服务。最终实现USERBUF和I2CClient之间的数据通信。
而I2CClient,又利用关联的I2CAdapter对象,来产生I2CBUS上的操作时序。最终实现I2CClient和I2CAdapter之间的数据通信。

注意,从内核角度看来,这里涉及到两种不同的设备对象,一种是用户可访问的UADEV,一种是伺服设备(Servo Device, 简写为SVDEV)。有的称呼是隐形设备(Stealth Device,简写为SDEV)或者影子设备(Shadow Device,简写为SDEV)。个人认为,伺服设备是更精确的表达。
CDEV是一种UADEV,而I2CClient和I2CAdapter则是SVDEV。
用户通过访问CDEV,来请求内核服务,内核在调用CDEV的驱动函数的时候,驱动函数里又调用了I2CClient的API提供的服务,而I2CClient的API又调用了I2CAdapter的API提供的服务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值