2015-12-07 13:49:32

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH] RTC/PCF85063: fix reading/setting time/date

The PCF85063 RTC needs special treatment while setting or reading the
time/date:

- while reading the 7 time/date registers they are blocked from updating by
the one second pulse internally. So reading all time/date registers should
happen in one turn to ensure reading is an atomic operation

- to let setting the time/date be an atomic operation as well, the clock
dividers must be kept in reset state to avoid a one second pulse during
writing the 7 time/date registers

Patch 1 and 2 change reading the time/date. I have kept them separately for
easier review. Squasching both into one patch render it more or less
unreadable.
Patch 3 changes setting the time/date.

Comments are welcome
Juergen

Please keep me on CC as I'm not subscribed to the list.


2015-12-07 13:49:35

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH 1/3] RTC/PCF85063: fix time/date reading (part 1)

When reading the seconds register, all time/date registers are frozen
until the year register is read. So, read all time/date registers in one
turn.

Signed-off-by: Juergen Borleis <[email protected]>
---
drivers/rtc/rtc-pcf85063.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)

diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c
index 63334cb..75f2866 100644
--- a/drivers/rtc/rtc-pcf85063.c
+++ b/drivers/rtc/rtc-pcf85063.c
@@ -39,6 +39,38 @@ struct pcf85063 {
int voltage_low; /* indicates if a low_voltage was detected */
};

+static int pcf85063_read_time(struct i2c_client *client, u8 *buf, u16 size)
+{
+ int rc;
+ u8 clk_ctrl = PCF85063_REG_SC;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .len = sizeof(clk_ctrl),
+ .buf = &clk_ctrl,
+ }, {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = size,
+ .buf = buf
+ },
+ };
+
+ /*
+ * 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_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+ if (rc != ARRAY_SIZE(msgs)) {
+ dev_err(&client->dev, "date/time register read error\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
/*
* In the routines that deal directly with the pcf85063 hardware, we use
* rtc_time -- month 0-11, hour 0-23, yr = calendar year-epoch.
--
2.6.2

2015-12-07 13:49:31

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH 2/3] RTC/PCF85063: fix time/date reading (part II)

Use the function to read the whole time/date register in one turn and now
check if the RTC signals an invalid time/date (due to a battery power loss
for example). In this case ignore the time/date until it is really set
again.

Signed-off-by: Juergen Borleis <[email protected]>
---
drivers/rtc/rtc-pcf85063.c | 45 +++++++++++++++++++--------------------------
1 file changed, 19 insertions(+), 26 deletions(-)

diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c
index 75f2866..abed934 100644
--- a/drivers/rtc/rtc-pcf85063.c
+++ b/drivers/rtc/rtc-pcf85063.c
@@ -22,6 +22,7 @@
#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
@@ -77,39 +78,31 @@ static int pcf85063_read_time(struct i2c_client *client, u8 *buf, u16 size)
*/
static int pcf85063_get_datetime(struct i2c_client *client, struct rtc_time *tm)
{
+ int rc;
struct pcf85063 *pcf85063 = i2c_get_clientdata(client);
- unsigned char buf[13] = { PCF85063_REG_CTRL1 };
- struct i2c_msg msgs[] = {
- {/* setup read ptr */
- .addr = client->addr,
- .len = 1,
- .buf = buf
- },
- {/* read status + date */
- .addr = client->addr,
- .flags = I2C_M_RD,
- .len = 13,
- .buf = buf
- },
- };
+ u8 regs[7];

- /* read registers */
- if ((i2c_transfer(client->adapter, msgs, 2)) != 2) {
- dev_err(&client->dev, "%s: read error\n", __func__);
- return -EIO;
+ rc = pcf85063_read_time(client, regs, sizeof(regs));
+ if (rc < 0)
+ return rc;
+
+ /* 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(buf[PCF85063_REG_SC] & 0x7F);
- tm->tm_min = bcd2bin(buf[PCF85063_REG_MN] & 0x7F);
- tm->tm_hour = bcd2bin(buf[PCF85063_REG_HR] & 0x3F); /* rtc hr 0-23 */
- tm->tm_mday = bcd2bin(buf[PCF85063_REG_DM] & 0x3F);
- tm->tm_wday = buf[PCF85063_REG_DW] & 0x07;
- tm->tm_mon = bcd2bin(buf[PCF85063_REG_MO] & 0x1F) - 1; /* rtc mn 1-12 */
- tm->tm_year = bcd2bin(buf[PCF85063_REG_YR]);
+ 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]);
if (tm->tm_year < 70)
tm->tm_year += 100; /* assume we are in 1970...2069 */
/* detect the polarity heuristically. see note above. */
- pcf85063->c_polarity = (buf[PCF85063_REG_MO] & PCF85063_MO_C) ?
+ pcf85063->c_polarity = (regs[5] & PCF85063_MO_C) ?
(tm->tm_year >= 100) : (tm->tm_year < 100);

return rtc_valid_tm(tm);
--
2.6.2

2015-12-07 13:49:33

by Juergen Borleis

[permalink] [raw]
Subject: [PATCH 3/3] RTC/PCF85063: fix time/date setting

When setting a new time/date the RTC's clock must be stopped first, in
order to write the time/date registers in an atomic manner.
So, this change stops the clock first and then writes the time/date
registers and the clock control register (to re-enable the clock) in one
turn.

Signed-off-by: Juergen Borleis <[email protected]>
---
drivers/rtc/rtc-pcf85063.c | 96 +++++++++++++++++++++++++++++++++++-----------
1 file changed, 73 insertions(+), 23 deletions(-)

diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c
index abed934..0150e93 100644
--- a/drivers/rtc/rtc-pcf85063.c
+++ b/drivers/rtc/rtc-pcf85063.c
@@ -19,6 +19,7 @@
#define DRV_VERSION "0.0.1"

#define PCF85063_REG_CTRL1 0x00 /* status */
+#define PCF85063_REG_CTRL1_STOP BIT(5)
#define PCF85063_REG_CTRL2 0x01

#define PCF85063_REG_SC 0x04 /* datetime */
@@ -72,6 +73,47 @@ static int pcf85063_read_time(struct i2c_client *client, u8 *buf, u16 size)
return 0;
}

+static int pcf85063_stop_clock(struct i2c_client *client, u8 *ctrl1)
+{
+ int rc;
+ u8 clk_ctrl[2] = { PCF85063_REG_CTRL1, };
+ struct i2c_msg msgs[] = {
+ {
+ .addr = client->addr,
+ .len = 1,
+ .buf = &clk_ctrl[0],
+ }, {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = 1,
+ .buf = &clk_ctrl[1],
+ }, {
+ .addr = client->addr,
+ .len = sizeof(clk_ctrl),
+ .buf = &clk_ctrl[0],
+ },
+ };
+
+ rc = i2c_transfer(client->adapter, &msgs[0], 2);
+ if (rc != 2) {
+ dev_err(&client->dev, "Failing to stop the clock\n");
+ return -EIO;
+ }
+
+ /* stop the clock */
+ clk_ctrl[1] |= PCF85063_REG_CTRL1_STOP;
+
+ rc = i2c_transfer(client->adapter, &msgs[2], 1);
+ if (rc != 1) {
+ dev_err(&client->dev, "Failing to stop the clock\n");
+ return -EIO;
+ }
+
+ *ctrl1 = clk_ctrl[1];
+
+ return 0;
+}
+
/*
* In the routines that deal directly with the pcf85063 hardware, we use
* rtc_time -- month 0-11, hour 0-23, yr = calendar year-epoch.
@@ -110,41 +152,49 @@ static int pcf85063_get_datetime(struct i2c_client *client, struct rtc_time *tm)

static int pcf85063_set_datetime(struct i2c_client *client, struct rtc_time *tm)
{
- int i = 0, err = 0;
- unsigned char buf[11];
+ int rc;
+ u8 regs[9];

- /* Control & status */
- buf[PCF85063_REG_CTRL1] = 0;
- buf[PCF85063_REG_CTRL2] = 5;
+ /*
+ * 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, &regs[8]);
+ if (rc != 0)
+ return rc;

+ /* write start register */
+ regs[0] = PCF85063_REG_SC;
/* hours, minutes and seconds */
- buf[PCF85063_REG_SC] = bin2bcd(tm->tm_sec) & 0x7F;
+ regs[1] = bin2bcd(tm->tm_sec) & 0x7F; /* clear OS flag */

- buf[PCF85063_REG_MN] = bin2bcd(tm->tm_min);
- buf[PCF85063_REG_HR] = bin2bcd(tm->tm_hour);
+ regs[2] = bin2bcd(tm->tm_min);
+ regs[3] = bin2bcd(tm->tm_hour);

/* Day of month, 1 - 31 */
- buf[PCF85063_REG_DM] = bin2bcd(tm->tm_mday);
+ regs[4] = bin2bcd(tm->tm_mday);

/* Day, 0 - 6 */
- buf[PCF85063_REG_DW] = tm->tm_wday & 0x07;
+ regs[5] = tm->tm_wday & 0x07;

/* month, 1 - 12 */
- buf[PCF85063_REG_MO] = bin2bcd(tm->tm_mon + 1);
+ regs[6] = bin2bcd(tm->tm_mon + 1);

/* year and century */
- buf[PCF85063_REG_YR] = bin2bcd(tm->tm_year % 100);
-
- /* write register's data */
- for (i = 0; i < sizeof(buf); i++) {
- unsigned char data[2] = { i, buf[i] };
-
- err = i2c_master_send(client, data, sizeof(data));
- if (err != sizeof(data)) {
- dev_err(&client->dev, "%s: err=%d addr=%02x, data=%02x\n",
- __func__, err, data[0], data[1]);
- return -EIO;
- }
+ regs[7] = bin2bcd(tm->tm_year % 100);
+
+ /*
+ * after all time/date registers are written, let the address auto
+ * increment wrap around and write register CTRL1 to re-enable the
+ * clock divider chain again
+ */
+ regs[8] &= ~PCF85063_REG_CTRL1_STOP;
+
+ /* write all registers at once */
+ rc = i2c_master_send(client, regs, sizeof(regs));
+ if (rc != sizeof(regs)) {
+ dev_err(&client->dev, "date/time register write error\n");
+ return -EIO;
}

return 0;
--
2.6.2

2015-12-20 11:35:45

by Alexandre Belloni

[permalink] [raw]
Subject: Re: [PATCH 2/3] RTC/PCF85063: fix time/date reading (part II)

Hi,

On 07/12/2015 at 14:49:33 +0100, Juergen Borleis wrote :
> Use the function to read the whole time/date register in one turn and now
> check if the RTC signals an invalid time/date (due to a battery power loss
> for example). In this case ignore the time/date until it is really set
> again.
>
> Signed-off-by: Juergen Borleis <[email protected]>
> ---
> drivers/rtc/rtc-pcf85063.c | 45 +++++++++++++++++++--------------------------
> 1 file changed, 19 insertions(+), 26 deletions(-)
>
> diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c
> index 75f2866..abed934 100644
> --- a/drivers/rtc/rtc-pcf85063.c
> +++ b/drivers/rtc/rtc-pcf85063.c
> @@ -22,6 +22,7 @@
> #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
> @@ -77,39 +78,31 @@ static int pcf85063_read_time(struct i2c_client *client, u8 *buf, u16 size)
> */
> static int pcf85063_get_datetime(struct i2c_client *client, struct rtc_time *tm)
> {
> + int rc;
> struct pcf85063 *pcf85063 = i2c_get_clientdata(client);
> - unsigned char buf[13] = { PCF85063_REG_CTRL1 };
> - struct i2c_msg msgs[] = {
> - {/* setup read ptr */
> - .addr = client->addr,
> - .len = 1,
> - .buf = buf
> - },
> - {/* read status + date */
> - .addr = client->addr,
> - .flags = I2C_M_RD,
> - .len = 13,
> - .buf = buf
> - },
> - };
> + u8 regs[7];
>
> - /* read registers */
> - if ((i2c_transfer(client->adapter, msgs, 2)) != 2) {
> - dev_err(&client->dev, "%s: read error\n", __func__);
> - return -EIO;

Isn't that already reading the time and date register in one block? I'd
say you are simply reading less registers. Also, maybe you could use
i2c_smbus_read_block_data?


> + rc = pcf85063_read_time(client, regs, sizeof(regs));
> + if (rc < 0)
> + return rc;
> +
> + /* 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;
> }

That part is more useful and as I understand it is what you are trying
to fix.


--
Alexandre Belloni, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com

2015-12-20 11:46:23

by Alexandre Belloni

[permalink] [raw]
Subject: Re: [PATCH 3/3] RTC/PCF85063: fix time/date setting

On 07/12/2015 at 14:49:34 +0100, Juergen Borleis wrote :
> When setting a new time/date the RTC's clock must be stopped first, in
> order to write the time/date registers in an atomic manner.
> So, this change stops the clock first and then writes the time/date
> registers and the clock control register (to re-enable the clock) in one
> turn.
>

I'd have the same comment for that patch. Using
i2c_smbus_write_byte_data and i2c_smbus_write_block_data would make the
code clearer and also more robust because it takes care of
retransmissions for example.


--
Alexandre Belloni, Free Electrons
Embedded Linux, Kernel and Android engineering
http://free-electrons.com