1. 程式人生 > >Exynos4412 IIC匯流排驅動開發(二)—— IIC 驅動開發

Exynos4412 IIC匯流排驅動開發(二)—— IIC 驅動開發

         首先看一張程式碼層次圖,有助於我們的理解


       上面這些程式碼的展示是告訴我們:linux核心和晶片提供商為我們的的驅動程式提供了 i2c驅動的框架,以及框架底層與硬體相關的程式碼的實現

   剩下的就是針對掛載在i2c兩線上的i2c裝置了device,而編寫的即具體裝置驅動了,這裡的裝置就是硬體介面外掛載的裝置,而非硬體介面本身(soc硬體介面本身的驅動可以理解為匯流排驅動)

一、編寫驅動需要完成的工作

       編寫具體的I2C驅動時,工程師需要處理的主要工作如下:

1)、提供I2C介面卡的硬體驅動,探測,初始化I2C介面卡(如申請I2C的I/O地址和中斷號),驅動CPU控制的I2C介面卡從硬體上產生。

2)、提供I2C控制的algorithm, 用具體介面卡的xxx_xfer()函式填充i2c_algorithm的master_xfer指標,並把i2c_algorithm指標賦給i2c_adapter的algo指標。

3)、實現I2C裝置驅動中的i2c_driver介面,用具體yyy的yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函式指標和i2c_device_id裝置ID表賦給i2c_driver的probe,remove,suspend,resume和id_table指標。

4)、實現I2C裝置所對應型別的具體驅動,i2c_driver只是實現裝置與匯流排的掛接。

  上面的工作中前兩個屬於I2C匯流排驅動,後面兩個屬於I2C裝置驅動。

二、開發例項

-------------------------------------------------------------------

開發板:Exynos4412-fs4412

Linux 核心版本:Linux 3.14

IIC 從機物件:陀螺儀MPU6050

--------------------------------------------------------------------

1、檢視原理圖

對應核心板pin


從機地址


可以獲取的資訊:

1、MPU6050 對應 IIC 通道5;

2、對應中斷 EINT27  父節點 GPX3  3

3、因為ad0接地,所以從裝置地址0x68

      base address 0x138B0000

2、建立裝置樹節點

      通過上面獲取的資訊,可以寫出

[email protected] {
        #address-cells = <1>;
        #size-cells = <0>;

        samsung,i2c-sda-delay = <100>;
        samsung,i2c-max-bus-freq = <20000>;
        pinctrl-0 = <&i2c5_bus>;
        pinctrl-names = "default";
        status = "okay";
   
        [email protected] {
               compatible = "invense,mpu6050";
               reg = <0x68>;
              interrupt-parent = <&gpx3>;
              interrupts = <3 2>;
        };
};

3、MPU6050相應暫存器

#define SMPLRT_DIV 0x19 //取樣率分頻,典型值: 0x07(125Hz) */
#define CONFIG 0x1A // 低通濾波頻率,典型值: 0x06(5Hz) */
#define GYRO_CONFIG 0x1B // 陀螺儀自檢及測量範圍,典型值: 0x18(不自檢,2000deg/s) */
#define ACCEL_CONFIG 0x1C // 加速計自檢、測量範圍及高通濾波頻率,典型值: 0x01(不自檢, 2G, 5Hz) */
#define ACCEL_XOUT_H 0x3B // 儲存最近的 X 軸、 Y 軸、 Z 軸加速度感應器的測量值 */
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41 // 儲存的最近溫度感測器的測量值 */
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43 // 儲存最近的 X 軸、 Y 軸、 Z 軸陀螺儀感應器的測量值 */
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define PWR_MGMT_1 0x6B // 電源管理,典型值: 0x00(正常啟用) */
#define WHO_AM_I 0x75 //IIC 地址暫存器(預設數值 0x68,只讀) */

4、具體程式

mpu6050.h

#ifndef MPU6050_HHHH
#define MPU6050_HHHH

#define MPU6050_MAGIC 'K'

union mpu6050_data
{
	struct {
		unsigned short x;
		unsigned short y;
		unsigned short z;
	}accel;
	struct {
		unsigned short x;
		unsigned short y;
		unsigned short z;
	}gyro;
	unsigned short temp;
};

#define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union mpu6050_data)
#define GET_GYRO  _IOR(MPU6050_MAGIC, 1, union mpu6050_data) 
#define GET_TEMP  _IOR(MPU6050_MAGIC, 2, union mpu6050_data)

#endif

i2c_driver

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/delay.h>

#include <asm/uaccess.h>

#include "mpu6050.h"

MODULE_LICENSE("GPL");

#define SMPLRT_DIV		0x19
#define CONFIG			0x1A
#define GYRO_CONFIG		0x1B
#define ACCEL_CONFIG	0x1C
#define ACCEL_XOUT_H	0x3B
#define ACCEL_XOUT_L	0x3C
#define ACCEL_YOUT_H	0x3D
#define ACCEL_YOUT_L	0x3E
#define ACCEL_ZOUT_H	0x3F
#define ACCEL_ZOUT_L	0x40
#define TEMP_OUT_H		0x41
#define TEMP_OUT_L		0x42
#define GYRO_XOUT_H		0x43
#define GYRO_XOUT_L		0x44
#define GYRO_YOUT_H		0x45
#define GYRO_YOUT_L		0x46
#define GYRO_ZOUT_H		0x47
#define GYRO_ZOUT_L		0x48
#define PWR_MGMT_1		0x6B

#define MPU6050_MAJOR 500
#define MPU6050_MINOR 0

struct mpu6050_device {
	struct cdev cdev;
	struct i2c_client *client;
};
struct mpu6050_device *mpu6050; 

static int mpu6050_read_byte(struct i2c_client *client, unsigned char reg)
{
	int ret;

	char txbuf[1] = { reg };
	char rxbuf[1];

	struct i2c_msg msg[2] = {
		{client->addr, 0, 1, txbuf},
		{client->addr, I2C_M_RD, 1, rxbuf}
	};

	ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
	if (ret < 0) {
		printk("ret = %d\n", ret);
		return ret;
	}

	return rxbuf[0];
}

static int mpu6050_write_byte(struct i2c_client *client, unsigned char reg, unsigned char val)
{
	char txbuf[2] = {reg, val};

	struct i2c_msg msg[2] = {
		{client->addr, 0, 2, txbuf},
	};

	i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));

	return 0;
}


static int mpu6050_open(struct inode *inode, struct file *file) 
{
	return 0;
}

static int mpu6050_release(struct inode *inode, struct file *file) 
{
	return 0;
}

static long mpu6050_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	union mpu6050_data data;
	struct i2c_client *client = mpu6050->client;

	switch(cmd) {
	case GET_ACCEL:
		data.accel.x = mpu6050_read_byte(client, ACCEL_XOUT_L);
		data.accel.x |= mpu6050_read_byte(client, ACCEL_XOUT_H) << 8;

		data.accel.y = mpu6050_read_byte(client, ACCEL_YOUT_L);
		data.accel.y |= mpu6050_read_byte(client, ACCEL_YOUT_H) << 8;

		data.accel.z = mpu6050_read_byte(client, ACCEL_ZOUT_L);
		data.accel.z |= mpu6050_read_byte(client, ACCEL_ZOUT_H) << 8;
		break;

	case GET_GYRO:

		data.gyro.x = mpu6050_read_byte(client, GYRO_XOUT_L);
		data.gyro.x |= mpu6050_read_byte(client, GYRO_XOUT_H) << 8;

		data.gyro.y = mpu6050_read_byte(client, GYRO_YOUT_L);
		data.gyro.y |= mpu6050_read_byte(client, GYRO_YOUT_H) << 8;

		data.gyro.z = mpu6050_read_byte(client, GYRO_ZOUT_L);
		data.gyro.z |= mpu6050_read_byte(client, GYRO_ZOUT_H) << 8;
		break;

	case GET_TEMP:	
		data.temp = mpu6050_read_byte(client, TEMP_OUT_L);
		data.temp |= mpu6050_read_byte(client, TEMP_OUT_H) << 8;
		break;

	default:
		printk("invalid argument\n");
		return -EINVAL;
	}

	if (copy_to_user((void *)arg, &data, sizeof(data)))
		return -EFAULT;

	return sizeof(data);
}

struct file_operations mpu6050_fops = {
	.owner 		= THIS_MODULE,
	.open		= mpu6050_open,
	.release 	= mpu6050_release,
	.unlocked_ioctl = mpu6050_ioctl,
};

static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int ret;
	dev_t devno = MKDEV(MPU6050_MAJOR, MPU6050_MINOR);
	printk("match OK!\n");

	mpu6050 = kzalloc(sizeof(*mpu6050), GFP_KERNEL);
	if (mpu6050 == NULL) {
		return -ENOMEM;
	}

	mpu6050->client = client;

	ret = register_chrdev_region(devno, 1, "mpu6050");
	if (ret < 0) {
		printk("failed to register char device region!\n");
		goto err1;
	}

	cdev_init(&mpu6050->cdev, &mpu6050_fops);
	mpu6050->cdev.owner = THIS_MODULE;
	ret = cdev_add(&mpu6050->cdev, devno, 1);
	if (ret < 0) {
		printk("failed to add device\n");
		goto err2;
	}
	
	mpu6050_write_byte(client, PWR_MGMT_1, 0x00);
	mpu6050_write_byte(client, SMPLRT_DIV, 0x07);
	mpu6050_write_byte(client, CONFIG, 0x06);
	mpu6050_write_byte(client, GYRO_CONFIG, 0xF8);
	mpu6050_write_byte(client, ACCEL_CONFIG, 0x19);

	return 0;
err2:
	unregister_chrdev_region(devno, 1);
err1:
	kfree(mpu6050);
	return ret;
}

static int mpu6050_remove(struct i2c_client *client)
{
	dev_t devno = MKDEV(MPU6050_MAJOR, MPU6050_MINOR);
	cdev_del(&mpu6050->cdev);
	unregister_chrdev_region(devno, 1);
	kfree(mpu6050);

	return 0;
}

static const struct i2c_device_id mpu6050_id[] = {
	{ "mpu6050", 0},
	{}
}; 

static struct of_device_id mpu6050_dt_match[] = {
	{.compatible = "invense,mpu6050" },
	{/*northing to be done*/},
};

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

static init _init mpu6050_init(void)
{
	return i2c_add_driver(&mpu6050_driver);
}

static void _exit mpu6050_exit(void)
{
	return i2c_del_driver(&mpu6050_driver);
}

module_init(&mpu6050_init);
module_exit(&mpu6050_exit);
test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#include "mpu6050.h"

int main(int argc, const char *argv[])
{
	int fd;
	union mpu6050_data data; 
	
	fd = open("/dev/mpu6050", O_RDWR);
	if (fd < 0) {
		perror("open");
		exit(1);
	}

	while(1) {
		ioctl(fd, GET_ACCEL, &data);
		printf("acceleration data: x = %04x, y = %04x, z = %04x\n", 
				data.accel.x, data.accel.y, data.accel.z);

		ioctl(fd, GET_GYRO, &data);
		printf("gyroscope data: x = %04x, y = %04x, z = %04x\n", 
				data.accel.x, data.accel.y, data.accel.z);

		sleep(1);
	}

	close(fd);

	return 0;
}