正点原子第44章
I2C
zynq I2C
pcf8563芯片
我们用的是ds3231.
Linux I2C总线框架
I2C总线驱动
这部分内容是半导体厂商编写的。
I2C总线设备
zynq I2C适配器驱动
I2C设备驱动编写
使用设备树
代码编写
设备树修改
设备驱动编写
因为用的是ds3231,所以先找compatible的信息。
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : ds3231.c
作者 : Skylar
版本 : V1.0
描述 : IIC
其他 : DS3231SN
论坛 : www.openedv.com
日志 : 初版V1.0 2024/10/10 创建
***************************************************************/
/************************ dts
/{ // 根节点
......
};
&i2c0 {
clock-frequency = <100000>; // 100KHz
rtc@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
};
};
************************/
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
// #include <linux/input.h>
// #include <linux/timer.h>
// #include <linux/of_irq.h>
// #include <linux/interrupt.h>
// #include <linux/input-event-codes.h>
#include <linux/i2c.h>
#include <linux/bcd.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <linux/kern_levels.h>
#define DEVICE_NAME "ds3231"
/*
* ds3231 内部寄存器定义
*/
// #define DS3231_CTL_STATUS_1 0x00 /* 控制寄存器 1 */
// #define DS3231_CTL_STATUS_2 0x01 /* 控制寄存器 2 */
#define DS3231_VL_SECONDS 0x00 /* 时间: 秒 */
#define DS3231_MINUTES 0x01 /* 时间: 分 */
#define DS3231_HOURS 0x02 /* 时间: 小时 */
#define DS3231_DAYS 0x04 /* 日期: 天 */
#define DS3231_WEEKDAYS 0x03 /* 日期: 星期 */
#define DS3231_CENTURY_MONTHS 0x04 /* 日期: 月 */
#define DS3231_YEARS 0x06 /* 日期: 年 */
#define YEAR_BASE 2000 /* 20xx 年 */
/*
* 自定义结构体,用于表示时间和日期信息
*/
struct ds3231_time {
int sec; // 秒
int min; // 分
int hour; // 小时
int day; // 日
int wday; // 星期
int mon; // 月份
int year; // 年
};
/*
* 自定义结构体 ds3231_dev
* 用于描述 ds3231 设备
*/
struct ds3231_dev {
struct i2c_client *client; /* i2c 次设备 */
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev 结构体 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
};
static struct ds3231_dev ds3231;
/*
* @description : 向 ds3231 设备多个连续的寄存器写入数据
* @param – dev : ds3231 设备
* @param – reg : 要写入的寄存器首地址
* @param – buf : 待写入的数据缓存区地址
* @param – len : 需要写入的字节长度
* @return : 成功返回 0,失败返回一个负数
*/
static int ds3231_write_reg(struct ds3231_dev *dev, u8 reg, u8 *buf, u8 len)
{
struct i2c_client *client = dev->client;
struct i2c_msg msg;
u8 send_buf[17] = {0};
int ret;
if (16 < len) {
dev_err(&client->dev, "%s: error: Invalid transfer byte length %d\n",
__func__, len);
return -EINVAL;
}
send_buf[0] = reg; // 寄存器首地址
memcpy(&send_buf[1], buf, len); // 将要写入的数据存放到数组 send_buf 后面
msg.addr = client->addr; // ds3231 从机地址
msg.flags = client->flags; // 标记为写数据
msg.buf = send_buf; // 要写入的数据缓冲区
msg.len = len + 1; // 要写入的数据长度
ret = i2c_transfer(client->adapter, &msg, 1);
if (1 != ret) {
dev_err(&client->dev, "%s: error: reg=0x%x, len=0x%x\n",
__func__, reg, len);
return -EIO;
}
return 0;
}
/*
* @description : 从 ds3231 设备中读取多个连续的寄存器数据
* @param – dev : ds3231 设备
* @param – reg : 要读取的寄存器首地址
* @param – buf : 数据存放缓存区地址
* @param – len : 读取的字节长度
* @return : 成功返回 0,失败返回一个负数
*/
static int ds3231_read_reg(struct ds3231_dev *dev, u8 reg, u8 *buf, u8 len)
{
struct i2c_client *client = dev->client;
struct i2c_msg msg[2];
int ret;
/* msg[0]: 发送消息 */
msg[0].addr = client->addr; // ds3231 从机地址
msg[0].flags = client->flags; // 标记为写数据
msg[0].buf = ® // 要写入的数据缓冲区
msg[0].len = 1; // 要写入的数据长度
/* msg[1]: 接收消息 */
msg[1].addr = client->addr; // ds3231 从机地址
msg[1].flags = client->flags | I2C_M_RD; // 标记为读数据
msg[1].buf = buf; // 存放读数据的缓冲区
msg[1].len = len; // 读取的字节长度
ret = i2c_transfer(client->adapter, msg, 2);
if (2 != ret) {
dev_err(&client->dev, "%s: error: reg=0x%x, len=0x%x\n",
__func__, reg, len);
return -EIO;
}
return 0;
}
/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param – filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ds3231_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ds3231;
return 0;
}
/*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param – buf : 返回给用户空间的数据缓冲区
* @param – cnt : 要读取的数据长度
* @param – off : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t ds3231_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *off)
{
struct ds3231_dev *dev = filp->private_data;
struct i2c_client *client = dev->client;
struct ds3231_time time = {0};
u8 read_buf[9] = {0};
int ret;
/* 读寄存器数据 */
// ret = ds3231_read_reg(dev, DS3231_CTL_STATUS_1,
// read_buf, 9);
// if (ret)
// return ret;
/* 校验时钟完整性 */
if (read_buf[DS3231_VL_SECONDS] & 0x80) {
dev_err(&client->dev,
"low voltage detected, date/time is not reliable.\n");
return -EINVAL;
}
/* 将 BCD 码转换为数据得到时间、日期 */
time.sec = bcd2bin(read_buf[DS3231_VL_SECONDS] & 0x7F); // 秒
time.min = bcd2bin(read_buf[DS3231_MINUTES] & 0x7F); // 分
time.hour = bcd2bin(read_buf[DS3231_HOURS] & 0x3F); // 小时
time.day = bcd2bin(read_buf[DS3231_DAYS] & 0x3F); // 日
time.wday = read_buf[DS3231_WEEKDAYS] & 0x07; // 星期
time.mon = bcd2bin(read_buf[DS3231_CENTURY_MONTHS] & 0x1F); // 月
time.year = bcd2bin(read_buf[DS3231_YEARS]) + YEAR_BASE; // 年
/* 将数据拷贝到用户空间 */
return copy_to_user(buf, &time, sizeof(struct ds3231_time));
}
/*
* @description : 向设备写数据
* @param – filp : 设备文件,表示打开的文件描述符
* @param – buf : 要写给设备写入的数据
* @param – cnt : 要写入的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t ds3231_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
struct ds3231_dev *dev = filp->private_data;
struct ds3231_time time = {0};
u8 write_buf[9] = {0};
int ret;
ret = copy_from_user(&time, buf, cnt); // 得到应用层传递过来的数据
if(0 > ret)
return -EFAULT;
/* 将数据转换为 BCD 码 */
write_buf[DS3231_VL_SECONDS] = bin2bcd(time.sec); // 秒
write_buf[DS3231_MINUTES] = bin2bcd(time.min); // 分
write_buf[DS3231_HOURS] = bin2bcd(time.hour); // 小时
write_buf[DS3231_DAYS] = bin2bcd(time.day); // 日
write_buf[DS3231_WEEKDAYS] = time.wday & 0x07; // 星期
write_buf[DS3231_CENTURY_MONTHS] = bin2bcd(time.mon); // 月
write_buf[DS3231_YEARS] = bin2bcd(time.year % 100); // 年
/* 将数据写入寄存器 */
ret = ds3231_write_reg(dev, DS3231_VL_SECONDS,
&write_buf[DS3231_VL_SECONDS], 7);
if (ret)
return ret;
return cnt;
}
/*
* @description : 关闭/释放设备
* @param – filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ds3231_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*
* file_operations 结构体变量
*/
static const struct file_operations ds3231_ops = {
.owner = THIS_MODULE,
.open = ds3231_open,
.read = ds3231_read,
.write = ds3231_write,
.release = ds3231_release,
};
/*
* @description : 初始化函数
* @param – pdev : platform 设备指针
* @return : 成功返回 0,失败返回负数
*/
static int ds3231_init(struct platform_device *pdev)
{
u8 val;
int ret;
ret = ds3231_read_reg(dev, DS3231_VL_SECONDS, &val, 1); // 读 VL_SECONDS 寄存器
if (ret)
return ret;
val &= 0x7F; // 将寄存器最高一位清零,也就是将 VL 位清零
return ds3231_write_reg(dev, DS3231_VL_SECONDS, &val, 1); // 写入VL_SECONDS寄存器
}
/*
* @description : platform 驱动的 probe 函数,当驱动与设备
* 匹配成功以后此函数会被执行
* @param – pdev : platform 设备指针
* @return : 0,成功;其他负值,失败
*/
static int ds3231_probe(struct platform_device *pdev)
{
int ret;
dev_info(&pdev->dev, "I2C driver and device have been matched\n");
/* 初始化 ds3231 */
ds3231.client = client;
ret = ds3231_init(&ds3231);
if (ret)
return ret;
/* 申请设备号 */
ret = alloc_chrdev_region(&ds3231.devid, 0, 1, DEVICE_NAME);
if (ret)
return ret;
/* 初始化字符设备 cdev */
ds3231.cdev.owner = THIS_MODULE;
cdev_init(&ds3231.cdev, &ds3231_ops);
/* 添加 cdev */
ret = cdev_add(&ds3231.cdev, ds3231.devid, 1);
if (ret)
goto out1;
/* 创建类 class */
ds3231.class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(ds3231.class)) {
ret = PTR_ERR(ds3231.class);
goto out2;
}
/* 创建设备 */
ds3231.device = device_create(ds3231.class, &client->dev,
ds3231.devid, NULL, DEVICE_NAME);
if (IS_ERR(ds3231.device)) {
ret = PTR_ERR(ds3231.device);
goto out3;
}
i2c_set_clientdata(client, &ds3231);
return 0;
out3:
class_destroy(ds3231.class);
out2:
cdev_del(&ds3231.cdev);
out1:
unregister_chrdev_region(ds3231.devid, 1);
return ret;
}
/*
* @description : platform 驱动的 remove 函数,当 platform 驱动模块
* 卸载时此函数会被执行
* @param – dev : platform 设备指针
* @return : 0,成功;其他负值,失败
*/
static int ds3231_remove(struct platform_device *pdev)
{
struct ds3231_dev *ds3231 = i2c_get_clientdata(client);
/* 注销设备 */
device_destroy(ds3231->class, ds3231->devid);
/* 注销类 */
class_destroy(ds3231->class);
/* 删除 cdev */
cdev_del(&ds3231->cdev);
/* 注销设备号 */
unregister_chrdev_region(ds3231->devid, 1);
return 0;
}
/* 匹配列表 */
static const struct of_device_id ds3231_of_match[] = {
{.compatible = "maxim,ds3231"},
{/* Sentinel */}
};
static struct platform_driver ds3231_driver = {
.driver = {
.name = "ds3231", /* platform_driver name*/
.of_match_table = ds3231_of_match,
},
.probe = ds3231_probe,
.remove = ds3231_remove,
};
module_platform_driver(ds3231_driver);
MODULE_AUTHOR("Skylar <Skylar@33.com>");
MODULE_DESCRIPTION("I2C Driver, Input Subsystem");
MODULE_LICENSE("GPL");
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : ds3231APP.c
作者 : Skylar
版本 : V1.0
描述 : I2C RTC
其他 : DS3231SN
使用方法 : ./ds3231APP /dev/ds3231 read
./ds3231APP /dev/ds3231 write
论坛 : www.openedv.com
日志 : 初版V1.0 2024/10/10 创建
***************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
// #include <sys/ioctl.h>
// #include <signal.h>
struct ds3231_time {
int sec; // 秒
int min; // 分
int hour; // 小时
int day; // 日
int wday; // 星期
int mon; // 月份
int year; // 年
};
/*
* @description : main 主程序
* @param – argc : argv 数组元素个数
* @param – argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[]
{
int fd, ret;
struct ds3231_time time = {0};
if(argc != 3){
printf("Usage:\n"
"\t./keyinputApp /dev/input/eventX @ Open Key\n"
);
return -1;
}
/* 打开设备 */
fd = open(argv[1], O_RDWR);
if(fd < 0){
printf("Erroe: file %s open failed\r\n", argv[1]);
return -1;
}
if(!strcmp(argv[2], "read")) // 读取时间
{
/* 读取RTC */
ret = read(fd, &time, sizeof(struct ds3231_time));
if(ret < 0){
printf("Error: file %s read failed\r\n", argv[1]);
goto out;
}
printf("%d-%d-%d %d:%d:%d ", time.year, time.mon, time.day,
time.hour, time.min, time.sec);
switch(time.wday){
case 0:
printf("Sunday\n");
break;
case 1:
printf("Monday\n");
break;
case 2:
printf("Tuesday\n");
break;
case 3:
printf("Wednesday\n");
break;
case 4:
printf("Thursday\n");
break;
case 5:
printf("Friday\n");
break;
case 6:
printf("Saturday\n");
break;
}
} else {
int data;
printf("Year: ");
scanf("%d", &data);
time.year = data;
printf("Month: ");
scanf("%d", &data);
time.mon = data;
printf("Day: ");
scanf("%d", &data);
time.day = data;
printf("Date: ");
scanf("%d", &data);
time.wday = data;
printf("Hour: ");
scanf("%d", &data);
time.hour = data;
printf("Minute: ");
scanf("%d", &data);
time.min = data;
printf("Second: ");
scanf("%d", &data);
time.sec = data;
write(fd, &time, sizeof(struct ds3231_time));
}
out:
close(fd);
return 0;
})
运行测试
modprobe ds3231.ko
但是:
没有/dev/ds3231:
dts:(因为是模拟i2c)
i2c_gpio0: i2c-gpio-0 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "dallas,ds3232","i2c-gpio";
// MIO56-SDA, MIO55-SCL
gpios = <&portc 2 0
&portc 1 0 >;
status = "okay";
i2c-gpio,delay-us = <5>; // 100k Hz
rtc@68 {
compatible = "maxim,ds3231";
reg = <0x68>; //ID
status = "okay";
};
};
对比教程的dts:
原来是因为,_of_match同时有了i2c-gpio0和rtc的匹配的compatible:(ds3231.c)
/* 匹配列表 */
static const struct of_device_id ds3231_of_match[] = {
{.compatible = "maxim,ds3231"},
{.compatible = "dallas,ds3232"},
{.compatible = "i2c-gpio"},
{/* Sentinel */}
};
static struct i2c_driver ds3231_driver = {
.driver = {
.name = "ds3231", /* platform_driver name*/
.of_match_table = ds3231_of_match,
},
.probe = ds3231_probe,
.remove = ds3231_remove,
};
修改成只匹配rtc@68的compatible属性。
RTC驱动框架
正点原子第45章。
AXI IIC
正点原子第46章。
(目前不需要这部分,就先不看了)
需求
板子上的i2c是模拟i2c,所以需要i2c-gpio.c
而i2c的用途是与ds3231通信,也就是rtc的功能,所以也需要rec-ds3232.c
但是在这两个c文件中,前者实现了gpio电平翻转,后者实现了时间的收发。
Linux I2C子系统分析之(一) ----- 用GPIO模拟I2C总线_i2c-gpio.c-CSDN博客
所以,要在i2c-gpio.c的基础上,添加i2c的通信协议(自己写吧?)。
所以是要在APP.c中实现?