内核版本:4.14.0,基于设备树
以读写实时时钟(RTC)为例
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/bcd.h>
#include <linux/uaccess.h>
#define DEVICE_CNT 1
#define DEVICE_NAME "pcf8563" /* Device name */
#define COMPAT_PROPT "navigator,pcf8563" /* Compatible property of the device matched with this driver. */
/*
* pcf8563 register define
*/
#define PCF8563_CTL_STATUS_1 0x00 /* 控制寄存器1 */
#define PCF8563_CTL_STATUS_2 0x01 /* 控制寄存器2 */
#define PCF8563_VL_SECONDS 0x02 /* 时间: 秒 */
#define PCF8563_MINUTES 0x03 /* 时间: 分 */
#define PCF8563_HOURS 0x04 /* 时间: 小时 */
#define PCF8563_DAYS 0x05 /* 日期: 天 */
#define PCF8563_WEEKDAYS 0x06 /* 日期: 星期 */
#define PCF8563_CENTURY_MONTHS 0x07 /* 日期: 月 */
#define PCF8563_YEARS 0x08 /* 日期: 年 */
#define YEAR_BASE 2000 /* 20xx 年 */
struct pcf8563_time {
int sec; // 秒
int min; // 分
int hour; // 小时
int day; // 日
int wday; // 星期
int mon; // 月份
int year; // 年
};
/* Device information structure. */
struct dev_info {
struct i2c_client *client;
dev_t devid; /* Device ID */
int major; /* Major device id */
int minor; /* Minor device id */
struct cdev cdev;
struct class *class;
struct device *device;
};
struct dev_info device_info;
/*
* @description : Writing data to multiple consecutive registers of the I2C slave device.
* @param – client : I2C slave device.
* @param – reg : The first address of register to write.
* @param – buf : The buffer of data to write.
* @param – len : The lenth of data to write.
* @return : 0 means successfully, negative means the write failed.
*/
static int device_write_reg(struct i2c_client *client, u8 reg, u8 *buf, u8 len)
{
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);
msg.addr = client->addr;
/* Label as "Write Data" */
msg.flags = 0;
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 : Reading data from multiple consecutive registers of the I2C slave device.
* @param – client : I2C slave device.
* @param – reg : The first address of register to read.
* @param – buf : The buffer of data to read.
* @param – len : The lenth of data to read.
* @return : 0 means successfully, negative means the read failed.
*/
static int device_read_reg(struct i2c_client *client, u8 reg, u8 *buf, u8 len)
{
struct i2c_msg msg[2];
int ret;
/* msg[0]: write data */
msg[0].addr = client->addr;
/* Label as "Write Data" */
msg[0].flags = 0;
msg[0].buf = ®
msg[0].len = 1;
/* msg[1]: read data */
msg[1].addr = client->addr;
/* Label as "Read Data" */
msg[1].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 : Open the device.
* @param – inode : The inode passed to the driver.
* @param - filp : Device file, the file structure has a member variable called 'private_data',
* which is normally pointed to the device structure when it is opened.
* @return : 0: Successful; Others: Failed.
*/
static int device_open(struct inode *inode, struct file *filp)
{
filp->private_data = &device_info; /* Set private data.*/
return 0;
}
/*
* @description : Read from the device.
* @param - filp : The device file (file descriptor) to open.
* @param - buf : The data buffer returns to the user space.
* @param - cnt : The length of the data to be read.
* @param - offt : Offset relative to the first address of the file.
* @return : The number of bytes read, negative means the read failed.
*/
static ssize_t device_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
struct dev_info *p_dev_info = filp->private_data;
struct i2c_client *client = p_dev_info->client;
struct pcf8563_time time = {0};
u8 read_buf[9] = {0};
int ret;
/* 读寄存器数据 */
ret = device_read_reg(client, PCF8563_CTL_STATUS_1, read_buf, 9);
if (ret)
return ret;
/* 校验时钟完整性 */
if (read_buf[PCF8563_VL_SECONDS] & 0x80)
{
dev_err(&client->dev, "low voltage detected, date/time is not reliable.\n");
return -EINVAL;
}
/* 将BCD 码转换为数据得到时间、日期 */
time.sec = bcd2bin(read_buf[PCF8563_VL_SECONDS] & 0x7F); // 秒
time.min = bcd2bin(read_buf[PCF8563_MINUTES] & 0x7F); // 分
time.hour = bcd2bin(read_buf[PCF8563_HOURS] & 0x3F); // 小时
time.day = bcd2bin(read_buf[PCF8563_DAYS] & 0x3F); // 日
time.wday = read_buf[PCF8563_WEEKDAYS] & 0x07; // 星期
time.mon = bcd2bin(read_buf[PCF8563_CENTURY_MONTHS] & 0x1F); // 月
time.year = bcd2bin(read_buf[PCF8563_YEARS]) + YEAR_BASE; // 年
/* 将数据拷贝到用户空间 */
return copy_to_user(buf, &time, sizeof(struct pcf8563_time));
}
/*
* @description : Write to the device
* @param - filp : Device file, indicating the opened file descriptor.
* @param - buf : The data to write to the device.
* @param - cnt : The length of the data to be written.
* @param - offt : Offset relative to the first address of the file.
* @return : The number of bytes written, negative means the write failed.
*/
static ssize_t device_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
struct dev_info *p_dev_info = filp->private_data;
struct pcf8563_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[PCF8563_VL_SECONDS] = bin2bcd(time.sec); // 秒
write_buf[PCF8563_MINUTES] = bin2bcd(time.min); // 分
write_buf[PCF8563_HOURS] = bin2bcd(time.hour); // 小时
write_buf[PCF8563_DAYS] = bin2bcd(time.day); // 日
write_buf[PCF8563_WEEKDAYS] = time.wday & 0x07; // 星期
write_buf[PCF8563_CENTURY_MONTHS] = bin2bcd(time.mon); // 月
write_buf[PCF8563_YEARS] = bin2bcd(time.year % 100); // 年
/* 将数据写入寄存器 */
ret = device_write_reg(p_dev_info->client, PCF8563_VL_SECONDS, &write_buf[PCF8563_VL_SECONDS], 7);
if (ret)
return ret;
return cnt;
}
/*
* @description : Close/Release the device.
* @param – inode : The inode passed to the driver.
* @param - filp : The device file (file descriptor) to close.
* @return : 0: Successful; Others: Failed.
*/
static int device_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* The device operation function structure. */
static const struct file_operations device_fops = {
.owner = THIS_MODULE,
.open = device_open,
.read = device_read,
.write = device_write,
.release = device_release,
};
/*
* @description : Initialize the device.
* @param -pdev: Pointer to client struct.
* @return : 0: Successful; Others: Failed.
*/
static int device_init(struct i2c_client *client)
{
int ret;
u8 val;
ret = device_read_reg(client, PCF8563_VL_SECONDS, &val, 1);
if (ret)
return ret;
val &= 0x7F; // 将寄存器最高一位清零,也就是将 VL 位清零
return device_write_reg(client, PCF8563_VL_SECONDS, &val, 1); // 写入 VL_SECONDS 寄存器
}
/*
* @description : Probe function of the platform, it will be executed when the
* platform driver and platform device matching successfully.
* @param -client : Pointer to i2c_client struct, means i2c client/device.
* @param -id : Pointer to i2c_device_id struct, include name and private data.
* @return : 0: Successful; Others: Failed.
*/
static int device_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
dev_info(&client->dev, "Driver and device matched successfully!\n");
device_info.client = client;
/* Store the dev_info pointer in client->dev.driver_data for later use */
i2c_set_clientdata(client, &device_info);
/* Device init */
ret = device_init(client);
if (ret)
return ret;
/* Creat device id. */
if (device_info.major)
{
device_info.devid = MKDEV(device_info.major, 0);
ret = register_chrdev_region(device_info.devid, DEVICE_CNT, DEVICE_NAME);
if (ret)
goto out1;
}
else
{
ret = alloc_chrdev_region(&device_info.devid, 0, DEVICE_CNT, DEVICE_NAME);
if (ret)
goto out1;
device_info.major = MAJOR(device_info.devid);
device_info.minor = MINOR(device_info.devid);
}
printk("%s: device id: major=%d, minor=%d\r\n", DEVICE_NAME, device_info.major, device_info.minor);
/* Initialize char device. */
device_info.cdev.owner = THIS_MODULE;
cdev_init(&device_info.cdev, &device_fops);
/* Add a char device. */
ret = cdev_add(&device_info.cdev, device_info.devid, DEVICE_CNT);
if (ret)
goto out2;
/* Creat class. */
device_info.class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(device_info.class))
{
ret = PTR_ERR(device_info.class);
goto out3;
}
/* Creat device (node) file. */
device_info.device = device_create(device_info.class, &client->dev, device_info.devid, NULL, DEVICE_NAME);
if (IS_ERR(device_info.device))
{
ret = PTR_ERR(device_info.device);
goto out4;
}
return 0;
out4:
class_destroy(device_info.class);
out3:
cdev_del(&device_info.cdev);
out2:
unregister_chrdev_region(device_info.devid, DEVICE_CNT);
out1:
/* Some code to release resources */
return ret;
}
/*
* @description : Release some resources. This function will be executed when the platform
* driver module is unloaded.
* @param -client : Pointer to i2c_client struct, means i2c client/device.
* @return : 0: Successful; Others: Failed.
*/
static int device_remove(struct i2c_client *client)
{
/* Logoff device (node) */
device_destroy(device_info.class, device_info.devid);
/* Logoff class */
class_destroy(device_info.class);
/* Delete char device */
cdev_del(&device_info.cdev);
/* Logoff device id */
unregister_chrdev_region(device_info.devid, DEVICE_CNT);
/* Some code to release resources */
printk(KERN_INFO "%s: Driver removed!\n", DEVICE_NAME);
return 0;
}
/* Match table */
static const struct of_device_id device_of_match[] = {
{.compatible = COMPAT_PROPT},
{/* Sentinel */}
};
/*
* Declare device matching table. Note that this macro is generally used to dynamically
* load and unload drivers for hot-pluggable devices such as USB devices.
*/
MODULE_DEVICE_TABLE(of, device_of_match);
/* i2c driver struct */
static struct i2c_driver device_driver = {
.driver = {
.name = DEVICE_NAME, //Drive name, used to match device who has the same name.
.of_match_table = device_of_match, //Used to match the device tree who has the same compatible property.
},
.probe = device_probe, //probe function
.remove = device_remove, //remove function
};
/*
* Register or unregister i2c driver,
* and Register the entry and exit functions of the Module.
*/
module_i2c_driver(device_driver);
/*
* Author, driver information and LICENSE.
*/
MODULE_AUTHOR("蒋楼丶");
MODULE_DESCRIPTION(DEVICE_NAME" Driver");
MODULE_LICENSE("GPL");