BigW Consortium Gitlab

Commit 5e0e80b9 by Mirac Chen Committed by David Frey

Introduce rtc-pcf85063 and rtc_sync kernel modules

rtc-pcf85063 is an I2C RTC chip that exists on the mangOH Yellow. This driver was copied from the mainline Linux kernel. rtc_sync is a kernel module that sets the system time from rtc1 when the module is loaded and then periodically syncs the system time to the RTC if the system time is changed.
parent b90edecf
RTC-PCF85063 Linux Kernel Driver
==========================
This version of the RTC-PCF85063 Linux kernel driver was extracted from the mainline kernel at tag v4.9.125. The content were copied from `drivers/rtc/rtc-pcf85063.c`.
/*
* An I2C driver for the PCF85063 RTC
* Copyright 2014 Rose Technology
*
* Author: Søren Andersen <san@rosetechnology.dk>
* Maintainers: http://www.nslu2-linux.org/
*
* based on the other drivers in this same directory.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/i2c.h>
#include <linux/bcd.h>
#include <linux/rtc.h>
#include <linux/module.h>
/*
* Information for this driver was pulled from the following datasheets.
*
* http://www.nxp.com/documents/data_sheet/PCF85063A.pdf
* http://www.nxp.com/documents/data_sheet/PCF85063TP.pdf
*
* PCF85063A -- Rev. 6 — 18 November 2015
* PCF85063TP -- Rev. 4 — 6 May 2015
*/
#define PCF85063_REG_CTRL1 0x00 /* status */
#define PCF85063_REG_CTRL1_STOP BIT(5)
#define PCF85063_REG_CTRL2 0x01
#define PCF85063_REG_SC 0x04 /* datetime */
#define PCF85063_REG_SC_OS 0x80
#define PCF85063_REG_MN 0x05
#define PCF85063_REG_HR 0x06
#define PCF85063_REG_DM 0x07
#define PCF85063_REG_DW 0x08
#define PCF85063_REG_MO 0x09
#define PCF85063_REG_YR 0x0A
static struct i2c_driver pcf85063_driver;
static int pcf85063_stop_clock(struct i2c_client *client, u8 *ctrl1)
{
s32 ret;
ret = i2c_smbus_read_byte_data(client, PCF85063_REG_CTRL1);
if (ret < 0) {
dev_err(&client->dev, "Failing to stop the clock\n");
return -EIO;
}
/* stop the clock */
ret |= PCF85063_REG_CTRL1_STOP;
ret = i2c_smbus_write_byte_data(client, PCF85063_REG_CTRL1, ret);
if (ret < 0) {
dev_err(&client->dev, "Failing to stop the clock\n");
return -EIO;
}
*ctrl1 = ret;
return 0;
}
static int pcf85063_start_clock(struct i2c_client *client, u8 ctrl1)
{
s32 ret;
/* start the clock */
ctrl1 &= ~PCF85063_REG_CTRL1_STOP;
ret = i2c_smbus_write_byte_data(client, PCF85063_REG_CTRL1, ctrl1);
if (ret < 0) {
dev_err(&client->dev, "Failing to start the clock\n");
return -EIO;
}
return 0;
}
static int pcf85063_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct i2c_client *client = to_i2c_client(dev);
int rc;
u8 regs[7];
/*
* while reading, the time/date registers are blocked and not updated
* anymore until the access is finished. To not lose a second
* event, the access must be finished within one second. So, read all
* time/date registers in one turn.
*/
rc = i2c_smbus_read_i2c_block_data(client, PCF85063_REG_SC,
sizeof(regs), regs);
if (rc != sizeof(regs)) {
dev_err(&client->dev, "date/time register read error\n");
return -EIO;
}
/* if the clock has lost its power it makes no sense to use its time */
if (regs[0] & PCF85063_REG_SC_OS) {
dev_warn(&client->dev, "Power loss detected, invalid time\n");
return -EINVAL;
}
tm->tm_sec = bcd2bin(regs[0] & 0x7F);
tm->tm_min = bcd2bin(regs[1] & 0x7F);
tm->tm_hour = bcd2bin(regs[2] & 0x3F); /* rtc hr 0-23 */
tm->tm_mday = bcd2bin(regs[3] & 0x3F);
tm->tm_wday = regs[4] & 0x07;
tm->tm_mon = bcd2bin(regs[5] & 0x1F) - 1; /* rtc mn 1-12 */
tm->tm_year = bcd2bin(regs[6]);
tm->tm_year += 100;
return 0;
}
static int pcf85063_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
struct i2c_client *client = to_i2c_client(dev);
int rc;
u8 regs[7];
u8 ctrl1;
if ((tm->tm_year < 100) || (tm->tm_year > 199))
return -EINVAL;
/*
* to accurately set the time, reset the divider chain and keep it in
* reset state until all time/date registers are written
*/
rc = pcf85063_stop_clock(client, &ctrl1);
if (rc != 0)
return rc;
/* hours, minutes and seconds */
regs[0] = bin2bcd(tm->tm_sec) & 0x7F; /* clear OS flag */
regs[1] = bin2bcd(tm->tm_min);
regs[2] = bin2bcd(tm->tm_hour);
/* Day of month, 1 - 31 */
regs[3] = bin2bcd(tm->tm_mday);
/* Day, 0 - 6 */
regs[4] = tm->tm_wday & 0x07;
/* month, 1 - 12 */
regs[5] = bin2bcd(tm->tm_mon + 1);
/* year and century */
regs[6] = bin2bcd(tm->tm_year - 100);
/* write all registers at once */
rc = i2c_smbus_write_i2c_block_data(client, PCF85063_REG_SC,
sizeof(regs), regs);
if (rc < 0) {
dev_err(&client->dev, "date/time register write error\n");
return rc;
}
/*
* Write the control register as a separate action since the size of
* the register space is different between the PCF85063TP and
* PCF85063A devices. The rollover point can not be used.
*/
rc = pcf85063_start_clock(client, ctrl1);
if (rc != 0)
return rc;
return 0;
}
static const struct rtc_class_ops pcf85063_rtc_ops = {
.read_time = pcf85063_rtc_read_time,
.set_time = pcf85063_rtc_set_time
};
static int pcf85063_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct rtc_device *rtc;
int err;
dev_dbg(&client->dev, "%s\n", __func__);
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -ENODEV;
err = i2c_smbus_read_byte_data(client, PCF85063_REG_CTRL1);
if (err < 0) {
dev_err(&client->dev, "RTC chip is not present\n");
return err;
}
rtc = devm_rtc_device_register(&client->dev,
pcf85063_driver.driver.name,
&pcf85063_rtc_ops, THIS_MODULE);
return PTR_ERR_OR_ZERO(rtc);
}
static const struct i2c_device_id pcf85063_id[] = {
{ "pcf85063", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, pcf85063_id);
#ifdef CONFIG_OF
static const struct of_device_id pcf85063_of_match[] = {
{ .compatible = "nxp,pcf85063" },
{}
};
MODULE_DEVICE_TABLE(of, pcf85063_of_match);
#endif
static struct i2c_driver pcf85063_driver = {
.driver = {
.name = "rtc-pcf85063",
.of_match_table = of_match_ptr(pcf85063_of_match),
},
.probe = pcf85063_probe,
.id_table = pcf85063_id,
};
module_i2c_driver(pcf85063_driver);
MODULE_AUTHOR("Søren Andersen <san@rosetechnology.dk>");
MODULE_DESCRIPTION("PCF85063 RTC driver");
MODULE_LICENSE("GPL");
RTC-SYNC KERNEL MODULE
==========================
The rtc_hctosys function was taken from this page: https://github.com/spotify/linux/blob/master/drivers/rtc/hctosys.c
The systohc function was modified from Linux source code 'drivers/rtc/systohc.c' to fit 32-bit system
The rest of the functions were written by Sierra Wireless
/*
* rtc_sync.c: module that syncs system time with rtc time
* and vice versa with 10 seconds interval
*
* Copyright (C) 2018 Sierra Wireless.
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
*/
/* IMPORTANT: the RTC only stores whole seconds. It is arbitrary
* whether it stores the most close value or the value with partial
* seconds truncated. However, it is important that we use it to store
* the truncated value. This is because otherwise it is necessary,
* in an rtc sync function, to read both xtime.tv_sec and
* xtime.tv_nsec. On some processors (i.e. ARM), an atomic read
* of >32bits is not possible. So storing the most close value would
* slow down the sync API. So here we have the truncated value and
* the best guess is to add 0.5s.
*/
#include <linux/module.h>
#include <linux/rtc.h>
#include <linux/timer.h>
#include <linux/time.h>
#include <linux/sched.h>
#include <linux/kthread.h>
static struct task_struct *time_check_thread;
/*
* this function syncs system time with RTC when startup
* if there is valid time store in RTC static int rtc_hctosys(void)
*/
static int rtc_hctosys(void)
{
int err;
struct rtc_time tm;
struct rtc_device *rtc = rtc_class_open("rtc1");
if (rtc == NULL) {
printk("%s: unable to open rtc device (rtc1) for rtc_hctosys\n",
__FILE__);
return -ENODEV;
}
err = rtc_read_time(rtc, &tm);
if (err == 0) {
err = rtc_valid_tm(&tm);
if (err == 0) {
struct timespec tv;
tv.tv_nsec = NSEC_PER_SEC >> 1;
rtc_tm_to_time(&tm, &tv.tv_sec);
do_settimeofday(&tv);
dev_info(rtc->dev.parent,
"setting system clock to "
"%d-%02d-%02d %02d:%02d:%02d UTC (%u)\n",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec,
(unsigned int) tv.tv_sec);
} else {
dev_err(rtc->dev.parent,
"hctosys: invalid date/time\n");
}
} else {
dev_err(rtc->dev.parent,
"hctosys: unable to read the hardware clock\n");
}
rtc_class_close(rtc);
return 0;
}
/*systohc sets RTC with system time*/
static int systohc(struct timespec now)
{
struct rtc_device *rtc;
struct rtc_time tm;
int res = -ENODEV;
printk("Setting time to %lld\n", timespec_to_ns(&now));
if (now.tv_nsec < (NSEC_PER_SEC >> 1))
rtc_time_to_tm(now.tv_sec, &tm);
else
rtc_time_to_tm(now.tv_sec + 1, &tm);
rtc = rtc_class_open("rtc1");
if (rtc == NULL) {
printk("%s: unable to open rtc device (rtc1) for systohc\n",
__FILE__);
return res;
}
/* rtc_hctosys exclusively uses UTC, so we call set_time here,
* not set_mmss. */
if (rtc->ops && (rtc->ops->set_time || rtc->ops->set_mmss))
res = rtc_set_time(rtc, &tm);
rtc_class_close(rtc);
return res;
}
/*
* time_check chekcs if the system time has been modified during
* the last checking period by comparing the expected time after
* checking period with a time allowance static int time_check(void *data)
*/
static int time_check(void *data)
{
while (!kthread_should_stop()) {
const long long expected_delta_ns = 10 * NSEC_PER_SEC;
long timeout = 10 * HZ;
struct timespec before;
struct timespec after;
long long delta_ns;
ktime_get_real_ts(&before);
while (timeout > 0)
timeout = schedule_timeout(timeout);
ktime_get_real_ts(&after);
delta_ns = timespec_to_ns(&after) - timespec_to_ns(&before);
if (abs64(delta_ns - expected_delta_ns) > 1 * NSEC_PER_SEC)
systohc(after);
}
return 0;
}
static int __init rtc_sync_init(void)
{
rtc_hctosys();
time_check_thread = kthread_run(time_check, NULL, "check_time");
return 0;
}
static void __exit rtc_sync_exit(void)
{
if (time_check_thread) {
kthread_stop(time_check_thread);
}
}
module_init(rtc_sync_init);
module_exit(rtc_sync_exit);
MODULE_DESCRIPTION("Syncs system time with rtc time when waked up from ULPM. Checks if system time has been changed during a given period, if changed, saves system time to rtc.");
MODULE_LICENSE("GPL v2");
sources:
{
rtc_sync.c
}
requires:
{
kernelModules:
{
$CURDIR/../rtc-pcf85063/rtc-pcf85063
$CURDIR/../mangoh/mangoh_yellow
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment