@@ -13,6 +13,7 @@
#include <linux/i2c.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
+#include <linux/moduleparam.h>
#define DRV_VERSION "0.6"
@@ -51,18 +52,27 @@
# define RS5C_CTRL1_CT0 (0 << 0) /* no periodic irq */
# define RS5C_CTRL1_CT4 (4 << 0) /* 1 Hz level irq */
#define RS5C_REG_CTRL2 15
+# define R2025_CTRL2_VDET (1 << 6) /* VDD low */
# define RS5C372_CTRL2_24 (1 << 5)
-# define R2025_CTRL2_XST (1 << 5)
+# define R2025_CTRL2_XST (1 << 5) /* OSC stopped */
# define RS5C_CTRL2_XSTP (1 << 4) /* only if !R2025S/D */
+# define R2025_CTRL2_PON (1 << 4) /* power loss */
# define RS5C_CTRL2_CTFG (1 << 2)
# define RS5C_CTRL2_AAFG (1 << 1) /* or WAFG */
# define RS5C_CTRL2_BAFG (1 << 0) /* or DAFG */
+#define R2025_TIME_VALIDMASK \
+ (R2025_CTRL2_VDET | R2025_CTRL2_XST | R2025_CTRL2_PON)
/* to read (style 1) or write registers starting at R */
#define RS5C_ADDR(R) (((R) << 4) | 0)
+static int epoch_if_invalid;
+module_param(epoch_if_invalid, bool, 0644);
+MODULE_PARM_DESC(reset_if_invalid, "If invalid time, return the epoch");
+
+
enum rtc_type {
rtc_undef = 0,
rtc_r2025sd,
@@ -89,6 +99,8 @@ struct rs5c372 {
struct i2c_client *client;
struct rtc_device *rtc;
enum rtc_type type;
+ int (*is_valid_time)(const struct rs5c372 *rs5c);
+ int (*clear_invalid)(const struct rs5c372 *rs5c);
unsigned time24:1;
unsigned has_irq:1;
unsigned smbus:1;
@@ -96,6 +108,40 @@ struct rs5c372 {
char *regs;
};
+static int rs5c372_is_valid_time(const struct rs5c372 *rs5c)
+{
+ return (rs5c->regs[RS5C_REG_CTRL2] | RS5C_CTRL2_XSTP) ? 1 : 0;
+}
+
+static int rs5c372_clear_invalid(const struct rs5c372 *rs5c)
+{
+ int ret = 0, addr = RS5C_ADDR(RS5C_REG_CTRL2);
+
+ ret = i2c_smbus_read_byte_data(rs5c->client, addr);
+ return (0 <= ret) ?
+ i2c_smbus_write_byte_data(
+ rs5c->client, addr, (u8) ret | RS5C_CTRL2_XSTP) :
+ ret;
+}
+
+static int r2025_is_valid_time(const struct rs5c372 *rs5c)
+{
+ return (rs5c->regs[RS5C_REG_CTRL2] & R2025_TIME_VALIDMASK) ==
+ R2025_CTRL2_XST;
+}
+
+static int r2025_clear_invalid(const struct rs5c372 *rs5c)
+{
+ int ret, addr = RS5C_ADDR(RS5C_REG_CTRL2);
+
+ ret = i2c_smbus_read_byte_data(rs5c->client, addr);
+ return (0 <= ret) ?
+ i2c_smbus_write_byte_data(
+ rs5c->client, addr,
+ (u8) (ret & ~R2025_TIME_VALIDMASK) | R2025_CTRL2_XST) :
+ ret;
+}
+
static int rs5c_get_regs(struct rs5c372 *rs5c)
{
struct i2c_client *client = rs5c->client;
@@ -178,6 +224,11 @@ static int rs5c372_get_datetime(struct i2c_client *client, struct rtc_time *tm)
if (status < 0)
return status;
+ if (epoch_if_invalid && !rs5c->is_valid_time(rs5c)) {
+ rtc_time_to_tm(0, tm);
+ return 0;
+ };
+
tm->tm_sec = bcd2bin(rs5c->regs[RS5C372_REG_SECS] & 0x7f);
tm->tm_min = bcd2bin(rs5c->regs[RS5C372_REG_MINS] & 0x7f);
tm->tm_hour = rs5c_reg2hr(rs5c, rs5c->regs[RS5C372_REG_HOURS]);
@@ -227,7 +278,9 @@ static int rs5c372_set_datetime(struct i2c_client *client, struct rtc_time *tm)
buf[6] = bin2bcd(tm->tm_year);
}
- if (i2c_smbus_write_i2c_block_data(client, addr, sizeof(buf), buf) < 0) {
+ if ((i2c_smbus_write_i2c_block_data(client, addr,
+ sizeof(buf), buf) < 0) ||
+ (rs5c->clear_invalid(rs5c) < 0)) {
dev_err(&client->dev, "%s: write error\n", __func__);
return -EIO;
}
@@ -532,20 +585,41 @@ static void rs5c_sysfs_unregister(struct device *dev)
static struct i2c_driver rs5c372_driver;
-static int rs5c_oscillator_setup(struct rs5c372 *rs5c372)
+static int rs5c_oscillator_setup_pon(struct rs5c372 *rs5c372)
+{
+ u8 buf, invalid;
+ int ret;
+
+ invalid = (rs5c372->regs[RS5C_REG_CTRL2] & R2025_TIME_VALIDMASK) ^
+ R2025_CTRL2_XST;
+
+ if (invalid & R2025_CTRL2_PON)
+ dev_warn(&rs5c372->client->dev, "Time invalid: power lost\n");
+ else if (invalid & R2025_CTRL2_VDET)
+ dev_warn(&rs5c372->client->dev, "Time invalid: VDD low\n");
+ else if (invalid & R2025_CTRL2_XST)
+ dev_warn(&rs5c372->client->dev, "Time invalid: osc. stalled\n");
+ else
+ return 0;
+
+ buf = rs5c372->regs[RS5C_REG_CTRL1] | RV5C387_CTRL1_24;
+ ret = i2c_smbus_write_byte_data(rs5c372->client,
+ RS5C_ADDR(RS5C_REG_CTRL1), buf);
+ if (unlikely(ret < 0))
+ return ret;
+ rs5c372->regs[RS5C_REG_CTRL1] = buf;
+ rs5c372->time24 = 1;
+
+ return 0;
+}
+
+static int rs5c_oscillator_setup_xstp(struct rs5c372 *rs5c372)
{
unsigned char buf[2];
int addr, i, ret = 0;
- if (rs5c372->type == rtc_r2025sd) {
- if (rs5c372->regs[RS5C_REG_CTRL2] & R2025_CTRL2_XST)
- return ret;
- rs5c372->regs[RS5C_REG_CTRL2] |= R2025_CTRL2_XST;
- } else {
- if (!(rs5c372->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_XSTP))
- return ret;
- rs5c372->regs[RS5C_REG_CTRL2] &= ~RS5C_CTRL2_XSTP;
- }
+ if (!(rs5c372->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_XSTP))
+ return ret;
addr = RS5C_ADDR(RS5C_REG_CTRL1);
buf[0] = rs5c372->regs[RS5C_REG_CTRL1];
@@ -558,7 +632,6 @@ static int rs5c_oscillator_setup(struct rs5c372 *rs5c372)
buf[1] |= RS5C372_CTRL2_24;
rs5c372->time24 = 1;
break;
- case rtc_r2025sd:
case rtc_rv5c386:
case rtc_rv5c387a:
buf[0] |= RV5C387_CTRL1_24;
@@ -582,6 +655,32 @@ static int rs5c_oscillator_setup(struct rs5c372 *rs5c372)
return 0;
}
+static int rs5c_oscillator_setup(struct rs5c372 *rs5c372)
+{
+ int res;
+
+ switch (rs5c372->type) {
+ case rtc_r2025sd:
+ rs5c372->is_valid_time = r2025_is_valid_time;
+ rs5c372->clear_invalid = r2025_clear_invalid;
+ res = rs5c_oscillator_setup_pon(rs5c372);
+ break;
+ case rtc_rs5c372a:
+ case rtc_rs5c372b:
+ case rtc_rv5c386:
+ case rtc_rv5c387a:
+ rs5c372->is_valid_time = rs5c372_is_valid_time;
+ rs5c372->clear_invalid = rs5c372_clear_invalid;
+ res = rs5c_oscillator_setup_xstp(rs5c372);
+ break;
+ default:
+ res = -EINVAL;
+ break;
+ }
+
+ return res;
+}
+
static int rs5c372_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -633,14 +732,14 @@ static int rs5c372_probe(struct i2c_client *client,
/* alarm uses ALARM_A; and nINTRA on 372a, nINTR on 372b.
* so does periodic irq, except some 327a modes.
*/
- if (rs5c372->regs[RS5C_REG_CTRL2] & RS5C372_CTRL2_24)
- rs5c372->time24 = 1;
+ rs5c372->time24 =
+ (rs5c372->regs[RS5C_REG_CTRL2] & RS5C372_CTRL2_24) != 0;
break;
case rtc_r2025sd:
case rtc_rv5c386:
case rtc_rv5c387a:
- if (rs5c372->regs[RS5C_REG_CTRL1] & RV5C387_CTRL1_24)
- rs5c372->time24 = 1;
+ rs5c372->time24 =
+ (rs5c372->regs[RS5C_REG_CTRL1] & RV5C387_CTRL1_24) != 0;
/* alarm uses ALARM_W; and nINTRB for alarm and periodic
* irq, on both 386 and 387
*/