Linux驱动开发之I2C

I2C 是NXP公司设计的一种总线协议,其使用两条数据线在主控制器和从机之间进行数据通信。一条是 SCL(串行时钟线),另外一条是 SDA(串行数据线),这两条数据线需要接上拉电阻,即总线空闲的时候 SCL 和 SDA 处于高电平。I2C协议包括起始信号、终止信号和应答信号等。I2C 支持一个主控制器下挂载多个从设备,主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C设备。

在Linux中,I2C驱动框架主要包括I2C总线驱动、I2C设备驱动和I2C核心三部分。I2C 总线和 platform 总线类似,只是platform 总线是虚拟出来的一条总线,而 I2C 总线是实际存在的。对于使用 I2C 通信的设备,在驱动中直接使用 I2C 总线挂载即可。I2C 总线驱动的重点是 I2C 适配器,也即SOC I2C 接口控制器,主要涉及到两个结构体:i2c_adapter 和 i2c_algorithm。在 Linux 内核中用 i2c_adapter 结构体来表示 I2C 适配器。i2c_algorithm 则提供I2C 适配器与 IIC 设备进行通信的方法,也即API函数。

I2C 设备驱动中主要也有两个重要的结构体:i2c_client i2c_driveri2c_client 用来描述设备信息,一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_clienti2c_driver 用来描述驱动具体内容,类似于 platform_driver

I2C核心层完成的则是I2C设备和I2C驱动的匹配过程。当设备和驱动匹配成功后,会执行 probe 函数,probe 函数中就可以选择执行字符设备驱动的一套流程了。初始化 I2C 设备即对 I2C 设备寄存器进行特定的读写操作,要用到i2c_transfer 函数实现数据收发。

本次实验采用设备树方式实现,从机选用开发板上的RTC芯片hym8563,其挂载在i2c4下面。本次实验仅使用该芯片进行I2C驱动通信的测试,不涉及RTC驱动相关内容。

第一步,设备树配置

hym8563挂载在i2c4下面,设备树中需要在i2c4节点下面添加hym8563相关配置,其器件地址为0x51,在主设备树中配置如下:

&i2c4 {

    status = "okay";

    pinctrl-names = "default";

    pinctrl-0 = <&i2c4m1_xfer>;

    hym8563: hym8563@51 {

        compatible = "haoyu,hym8563";

        reg = <0x51>;

        clock-frequency = <32768>;

        clock-output-names = "hym8563";

    };

};

由于开发板内核本身自带有hym8563的原厂驱动,为了实验顺利进行,需要在内核中将该驱动进行屏蔽。通过编译脚本打开图形化配置界面,在Device Drivers下面找到HYM8563相关配置项,取消其选中,如图所示,再次编译内核并烧写。

第二步,驱动程序编写

驱动程序采用字符设备流程实现读写hym8563的日期数据寄存器,包括秒,分,时等7个寄存器,在打开文件时分别向这些寄存器中写入1-7的数据,然后再分别由应用程序读出来并打印,验证I2C驱动的数据收发功能。

#define HYM8563_SEC 0x02

#define HYM8563_MIN 0x03

#define HYM8563_HOUR 0x04

#define HYM8563_DAY 0x05

#define HYM8563_WEEKDAY 0x06

#define HYM8563_MONTH 0x07

#define HYM8563_YEAR 0x08

#define HYM8563_CNT 1

#define HYM8563_NAME "hym8563"

struct hym8563_dev {

dev_t devid; 

struct cdev cdev; 

struct class *class; 

struct device *device; 

struct device_node *nd; 

int major; 

void *private_data;

unsigned char time[7]; 

};

static struct hym8563_dev hym8563cdev;

//读寄存器函数

static int hym8563_read_reg(struct hym8563_dev *dev,u8 reg_addr,int len)

{

u8 data;

int ret;

struct i2c_client *hym8563_client = (struct i2c_client *)dev->private_data;

struct i2c_msg msgs[] = {

[0] = {

.addr = hym8563_client->addr,

.flags = 0,

.len = sizeof(reg_addr),

.buf = ®_addr,

},

[1] = {

.addr = hym8563_client->addr,

.flags = 1,

.len = sizeof(data),

.buf = &data,

},

};

ret = i2c_transfer(hym8563_client->adapter, msgs, 2);

if(ret == 2) {

ret = 0;

} else {

printk("i2c rd failed=%d\n",ret);

ret = -EREMOTEIO;

}

return data;

}

//写寄存器函数

static void hym8563_write_reg(struct hym8563_dev *dev,u8 reg_addr, u8 data, u8 len)

{

u8 buff[256];

struct i2c_client *hym8563_client = (struct i2c_client *)dev->private_data;

struct i2c_msg msgs[] = {

[0] = {

.addr = hym8563_client->addr,

.flags = 0,

.len = len + 1,

.buf = buff,

},

};

buff[0] = reg_addr;

memcpy(&buff[1], &data, len);

i2c_transfer(hym8563_client->adapter, msgs, 1);

}

void hym8563_readdata(struct hym8563_dev *dev)

{

unsigned char i =0;

unsigned char buf[7];

/* 循环读取所有数据 */

for(i = 0; i < 7; i++)

{

buf[i] = hym8563_read_reg(dev, HYM8563_SEC + i,1);

dev->time[i] = buf[i];

}

}

static int hym8563_open(struct inode *inode, struct file *filp)

{

unsigned char i = 0;

filp->private_data = &hym8563cdev;

/* 循环写入所有数据 */

for(i = 0;i < 7;i++)

{

hym8563_write_reg(&hym8563cdev,HYM8563_SEC + i,i+1,1);

}

return 0;

}

static ssize_t hym8563_read(struct file *filp, char __user *buf,size_t cnt, loff_t *off)

{

unsigned char data[7];

long err = 0;

unsigned char i = 0;

struct hym8563_dev *dev = (struct hym8563_dev *)filp->private_data;

hym8563_readdata(dev);

for(i = 0;i < 7;i++)

{

data[i] = dev->time[i];

}

err = copy_to_user(buf, data, sizeof(data));

return 0;

}

static int hym8563_release(struct inode *inode, struct file *filp)

{

return 0;

}

static const struct file_operations hym8563c_ops = {

.owner = THIS_MODULE,

.open = hym8563_open,

.read = hym8563_read,

.release = hym8563_release,

};

static const struct of_device_id hym8563_id[] = {

{.compatible = "haoyu,hym8563", 0},

{}};

int hym8563_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)

{

printk("This is hym8563_probe\n");

/* 1、构建设备号 */

if (hym8563cdev.major) {

hym8563cdev.devid = MKDEV(hym8563cdev.major, 0);

register_chrdev_region(hym8563cdev.devid, HYM8563_CNT,HYM8563_NAME);

} else {

alloc_chrdev_region(&hym8563cdev.devid, 0, HYM8563_CNT,HYM8563_NAME);

hym8563cdev.major = MAJOR(hym8563cdev.devid);

}

/* 2、注册设备 */

cdev_init(&hym8563cdev.cdev, &hym8563c_ops);

cdev_add(&hym8563cdev.cdev, hym8563cdev.devid, HYM8563_CNT);

/* 3、创建类 */

hym8563cdev.class = class_create(THIS_MODULE, HYM8563_NAME);

if (IS_ERR(hym8563cdev.class)) {

return PTR_ERR(hym8563cdev.class);

}

/* 4、创建设备 */

hym8563cdev.device = device_create(hym8563cdev.class, NULL,hym8563cdev.devid, NULL, HYM8563_NAME);

if (IS_ERR(hym8563cdev.device)) {

return PTR_ERR(hym8563cdev.device);

}

hym8563cdev.private_data = i2c_client;

return 0;

}

int hym8563_remove(struct i2c_client *i2c_client)

{

/* 删除设备 */

cdev_del(&hym8563cdev.cdev);

unregister_chrdev_region(hym8563cdev.devid, HYM8563_CNT);

/* 注销掉类和设备 */

device_destroy(hym8563cdev.class, hym8563cdev.devid);

class_destroy(hym8563cdev.class);

printk("Remove sucessed!\n");

return 0;

}

//定义一个 i2c_driver 的结构体

static struct i2c_driver hym8563_driver = {

.driver = {

.owner = THIS_MODULE,

.name = "hym8563_test",

.of_match_table = hym8563_id,

},

.probe = hym8563_probe,

.remove = hym8563_remove,

.id_table = hym8563_id_ts

};

static int hym8563_driver_init(void)

{

int ret;

// 注册 i2c_driver

ret = i2c_add_driver(&hym8563_driver);

if (ret < 0)

{

printk(" i2c_add_driver is error \n");

return ret;

}

printk("This is hym8563_driver_init\n");

return 0;

}

static void hym8563_driver_exit(void)

{

// 注销 i2c_driver 

i2c_del_driver(&hym8563_driver);

printk("This is hym8563_driver_exit\n");

}

module_init(hym8563_driver_init);

module_exit(hym8563_driver_exit);

MODULE_LICENSE("GPL");

第三步,测试程序编写

测试程序实现通过I2C读取上述7个寄存器中的值并打印,来验证I2C驱动写入数据是否成功。

int main(int argc, char *argv[])

{

    int fd;

    char *filename;

    unsigned char databuf[7];

    unsigned char sec, min, hour, day, week, mon, year;

    int ret = 0;

    if (argc != 2) {

        printf("Error Usage!\r\n");

        return -1;

    }

    filename = argv[1];

    fd = open(filename, O_RDWR);

    if(fd < 0) {

    printf("can't open file %s\r\n", filename);

    return -1;

    }

    while (1) {

    ret = read(fd, databuf, sizeof(databuf));

    if(ret == 0) { 

        sec = databuf[0]; 

        min = databuf[1]; 

        hour = databuf[2]; 

        day = databuf[3];

        week = databuf[4];

        mon = databuf[5];

        year = databuf[6];

        printf("sec = %d, min = %d, hour = %d, day = %d, week = %d, mon = %d, year = %d\r\n", sec, min, hour, day, week, mon, year);

    }

    usleep(200000); /* 200ms */

    }

    close(fd); 

    return 0;

}

第四步,运行测试

将驱动编译成功的模块文件和测试程序一并拷入开发板,在开发板上使用gcc工具编译测试程序。

查看hym8563设备是否存在

加载驱动

可以看出驱动成功加载并打印出提示信息,运行测试程序验证数据是否写入

可以看出,每次运行测试程序都会对上述寄存器写入1-7这7个数据,而读取出来的秒寄存器值会不断累加,间隔正好接近一秒(测试程序每0.2秒进行一次打印),这可能是由于RTC芯片工作后就会启动秒计时,其具体原因本篇不作分析。接下来卸载驱动。

总结

此次实验通过裁剪内核驱动,利用开发板自带的RTC芯片编写I2C驱动,实现了I2C从机读写寄存器,数据通信的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值