2006-12-09 02:59:56

by David Brownell

[permalink] [raw]
Subject: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

Update the rtc-rs5c372 driver:

Bugfixes:
- Handle RTCs which are configured to use 12-hour mode.
- Never report bogus/un-initialized times.
- Displaying "raw trim" requires not masking it first!
- Fix the procfs display of crystal and trim data.

Features:
- Handle other RTCs in this family, notably rv5c386/rv5c387.
- Declare the other registers.
- Provide alarm get/set functionality.
- Handle AIE and UIE; but no IRQ handling yet.

Cleanup:
- We don't need no steenkin' forward declarations. (Except one.)

Until the I2C framework merges "new style" driver support, matching
the driver model better, using rv5c chips or the chip IRQs requires
a seperate board-specific patch. (And an IRQ handler, handing off
labor through a work_struct...)

Note that this reverts part of a previous patch, which seems to have
included key parts of the initial version of this one. I suspect the
issue wasn't that "mode 1" didn't work on that board; the original
code to fetch the trim was broken. If "mode 1" really won't work,
that's almost certainly a bug in that board's I2C driver.

Signed-off-by: David Brownell <[email protected]>

Index: g26/drivers/rtc/rtc-rs5c372.c
===================================================================
--- g26.orig/drivers/rtc/rtc-rs5c372.c 2006-12-08 16:44:05.000000000 -0800
+++ g26/drivers/rtc/rtc-rs5c372.c 2006-12-08 17:08:19.000000000 -0800
@@ -1,5 +1,5 @@
/*
- * An I2C driver for the Ricoh RS5C372 RTC
+ * An I2C driver for Ricoh RS5C372 and RV5C38[67] RTCs
*
* Copyright (C) 2005 Pavel Mironchik <[email protected]>
* Copyright (C) 2006 Tower Technologies
@@ -13,7 +13,7 @@
#include <linux/rtc.h>
#include <linux/bcd.h>

-#define DRV_VERSION "0.3"
+#define DRV_VERSION "0.4"

/* Addresses to scan */
static unsigned short normal_i2c[] = { /* 0x32,*/ I2C_CLIENT_END };
@@ -21,6 +21,13 @@ static unsigned short normal_i2c[] = { /
/* Insmod parameters */
I2C_CLIENT_INSMOD;

+
+/*
+ * Ricoh has a family of I2C based RTCs, which differ only slightly from
+ * each other. Differences center on pinout (e.g. how many interrupts,
+ * output clock, etc) and how the control registers are used. The '372
+ * is significant only because that's the one this driver first supported.
+ */
#define RS5C372_REG_SECS 0
#define RS5C372_REG_MINS 1
#define RS5C372_REG_HOURS 2
@@ -29,59 +36,140 @@ I2C_CLIENT_INSMOD;
#define RS5C372_REG_MONTH 5
#define RS5C372_REG_YEAR 6
#define RS5C372_REG_TRIM 7
+# define RS5C372_TRIM_XSL 0x80
+# define RS5C372_TRIM_MASK 0x7F

-#define RS5C372_TRIM_XSL 0x80
-#define RS5C372_TRIM_MASK 0x7F
-
-#define RS5C372_REG_BASE 0
-
-static int rs5c372_attach(struct i2c_adapter *adapter);
-static int rs5c372_detach(struct i2c_client *client);
-static int rs5c372_probe(struct i2c_adapter *adapter, int address, int kind);
+#define RS5C_REG_ALARM_A_MIN 8 /* or ALARM_W */
+#define RS5C_REG_ALARM_A_HOURS 9
+#define RS5C_REG_ALARM_A_WDAY 10
+
+#define RS5C_REG_ALARM_B_MIN 11 /* or ALARM_D */
+#define RS5C_REG_ALARM_B_HOURS 12
+#define RS5C_REG_ALARM_B_WDAY 13 /* (if available) */
+
+#define RS5C_REG_CTRL1 14
+# define RS5C_CTRL1_AALE (1 << 7) /* or WALE */
+# define RS5C_CTRL1_BALE (1 << 6) /* or DALE */
+# define RV5C387_CTRL1_24 (1 << 5)
+# define RS5C372A_CTRL1_SL1 (1 << 5)
+# define RS5C_CTRL1_CT_MASK (7 << 0)
+# 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 RS5C372_CTRL2_24 (1 << 5)
+# define RS5C_CTRL2_XSTP (1 << 4)
+# define RS5C_CTRL2_CTFG (1 << 2)
+# define RS5C_CTRL2_AAFG (1 << 1) /* or WAFG */
+# define RS5C_CTRL2_BAFG (1 << 0) /* or DAFG */
+
+
+/* to read (style 1) or write registers starting at R */
+#define RS5C_ADDR(R) (((R) << 4) | 0)
+
+
+enum rtc_type {
+ rtc_undef = 0,
+ rtc_rs5c372a,
+ rtc_rs5c372b,
+ rtc_rv5c386,
+ rtc_rv5c387,
+};

+/* REVISIT: this assumes that:
+ * - we're in the 21st century, so it's safe to ignore the century
+ * bit for rv5c38[67] (REG_MONTH bit 7);
+ * - we should use ALARM_A not ALARM_B
+ */
struct rs5c372 {
- u8 reg_addr;
- u8 regs[17];
- struct i2c_msg msg[1];
- struct i2c_client client;
- struct rtc_device *rtc;
-};
+ struct i2c_client *client;
+ struct rtc_device *rtc;
+ enum rtc_type type;
+ unsigned time24:1;
+ unsigned has_irq:1;
+ char regs[16];

-static struct i2c_driver rs5c372_driver = {
- .driver = {
- .name = "rs5c372",
- },
- .attach_adapter = &rs5c372_attach,
- .detach_client = &rs5c372_detach,
+ /* on conversion to a "new style" i2c driver, this vanishes */
+ struct i2c_client dev;
};

-static int rs5c372_get_datetime(struct i2c_client *client, struct rtc_time *tm)
+static int rs5c_get_regs(struct rs5c372 *rs5c)
{
+ unsigned char reg;
+ struct i2c_client *client = rs5c->client;
+ struct i2c_msg msgs[] = {
+ { client->addr, 0, 1, &reg },
+ { client->addr, I2C_M_RD, sizeof rs5c->regs, rs5c->regs },
+ };

- struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
- u8 *buf = &(rs5c372->regs[1]);
-
- /* this implements the 3rd reading method, according
- * to the datasheet. rs5c372 defaults to internal
- * address 0xF, so 0x0 is in regs[1]
+ /* this implements the first (most portable) reading method
+ * specified in the datasheet.
*/
-
- if ((i2c_transfer(client->adapter, rs5c372->msg, 1)) != 1) {
- dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
+ reg = RS5C_ADDR(RS5C372_REG_SECS);
+ if ((i2c_transfer(client->adapter, msgs, 2)) != 2) {
+ pr_debug("%s: can't read registers\n", rs5c->rtc->name);
return -EIO;
}

- tm->tm_sec = BCD2BIN(buf[RS5C372_REG_SECS] & 0x7f);
- tm->tm_min = BCD2BIN(buf[RS5C372_REG_MINS] & 0x7f);
- tm->tm_hour = BCD2BIN(buf[RS5C372_REG_HOURS] & 0x3f);
- tm->tm_wday = BCD2BIN(buf[RS5C372_REG_WDAY] & 0x07);
- tm->tm_mday = BCD2BIN(buf[RS5C372_REG_DAY] & 0x3f);
+ dev_dbg(&client->dev,
+ "%02x %02x %02x (%02x) %02x %02x %02x (%02x), "
+ "%02x %02x %02x, %02x %02x %02x; %02x %02x\n",
+ rs5c->regs[0], rs5c->regs[1], rs5c->regs[2], rs5c->regs[3],
+ rs5c->regs[4], rs5c->regs[5], rs5c->regs[6], rs5c->regs[7],
+ rs5c->regs[8], rs5c->regs[9], rs5c->regs[10], rs5c->regs[11],
+ rs5c->regs[12], rs5c->regs[13], rs5c->regs[14], rs5c->regs[15]);
+
+ return 0;
+}
+
+static unsigned rs5c_reg2hr(struct rs5c372 *rs5c, unsigned reg)
+{
+ unsigned hour;
+
+ if (rs5c->time24)
+ return BCD2BIN(reg & 0x3f);
+
+ hour = BCD2BIN(reg & 0x1f);
+ if (hour == 12)
+ hour = 0;
+ if (reg & 0x20)
+ hour += 12;
+ return hour;
+}
+
+static unsigned rs5c_hr2reg(struct rs5c372 *rs5c, unsigned hour)
+{
+ if (rs5c->time24)
+ return BIN2BCD(hour);
+
+ if (hour > 12)
+ return 0x20 | BIN2BCD(hour - 12);
+ if (hour == 12)
+ return 0x20 | BIN2BCD(12);
+ if (hour == 0)
+ return BIN2BCD(12);
+ return BIN2BCD(hour);
+}
+
+static int rs5c372_get_datetime(struct i2c_client *client, struct rtc_time *tm)
+{
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ int status = rs5c_get_regs(rs5c);
+
+ if (status < 0)
+ return status;
+
+ 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]);
+
+ tm->tm_wday = BCD2BIN(rs5c->regs[RS5C372_REG_WDAY] & 0x07);
+ tm->tm_mday = BCD2BIN(rs5c->regs[RS5C372_REG_DAY] & 0x3f);

/* tm->tm_mon is zero-based */
- tm->tm_mon = BCD2BIN(buf[RS5C372_REG_MONTH] & 0x1f) - 1;
+ tm->tm_mon = BCD2BIN(rs5c->regs[RS5C372_REG_MONTH] & 0x1f) - 1;

/* year is 1900 + tm->tm_year */
- tm->tm_year = BCD2BIN(buf[RS5C372_REG_YEAR]) + 100;
+ tm->tm_year = BCD2BIN(rs5c->regs[RS5C372_REG_YEAR]) + 100;

dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
"mday=%d, mon=%d, year=%d, wday=%d\n",
@@ -89,22 +177,25 @@ static int rs5c372_get_datetime(struct i
tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

- return 0;
+ /* rtc might need initialization */
+ return rtc_valid_tm(tm);
}

static int rs5c372_set_datetime(struct i2c_client *client, struct rtc_time *tm)
{
- unsigned char buf[8] = { RS5C372_REG_BASE };
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ unsigned char buf[8];

- dev_dbg(&client->dev,
- "%s: secs=%d, mins=%d, hours=%d "
+ dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d "
"mday=%d, mon=%d, year=%d, wday=%d\n",
- __FUNCTION__, tm->tm_sec, tm->tm_min, tm->tm_hour,
+ __FUNCTION__,
+ tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

+ buf[0] = RS5C_ADDR(RS5C372_REG_SECS);
buf[1] = BIN2BCD(tm->tm_sec);
buf[2] = BIN2BCD(tm->tm_min);
- buf[3] = BIN2BCD(tm->tm_hour);
+ buf[3] = rs5c_hr2reg(rs5c, tm->tm_hour);
buf[4] = BIN2BCD(tm->tm_wday);
buf[5] = BIN2BCD(tm->tm_mday);
buf[6] = BIN2BCD(tm->tm_mon + 1);
@@ -121,14 +212,14 @@ static int rs5c372_set_datetime(struct i
static int rs5c372_get_trim(struct i2c_client *client, int *osc, int *trim)
{
struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
- u8 tmp = rs5c372->regs[RS5C372_REG_TRIM + 1];
+ u8 tmp = rs5c372->regs[RS5C372_REG_TRIM];

if (osc)
*osc = (tmp & RS5C372_TRIM_XSL) ? 32000 : 32768;

if (trim) {
+ dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, tmp);
*trim = tmp & RS5C372_TRIM_MASK;
- dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, *trim);
}

return 0;
@@ -144,23 +235,195 @@ static int rs5c372_rtc_set_time(struct d
return rs5c372_set_datetime(to_i2c_client(dev), tm);
}

+#if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE)
+
+static int
+rs5c_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ unsigned char buf[2];
+ int status;
+
+ buf[1] = rs5c->regs[RS5C_REG_CTRL1];
+ switch (cmd) {
+ case RTC_UIE_OFF:
+ case RTC_UIE_ON:
+ /* some 327a modes use a different IRQ pin for 1Hz irqs */
+ if (rs5c->type == rtc_rs5c372a
+ && (buf[1] & RS5C372A_CTRL1_SL1))
+ return -ENOIOCTLCMD;
+ case RTC_AIE_OFF:
+ case RTC_AIE_ON:
+ /* these irq management calls only make sense for chips
+ * which are wired up to an IRQ.
+ */
+ if (!rs5c->has_irq)
+ return -ENOIOCTLCMD;
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ status = rs5c_get_regs(rs5c);
+ if (status < 0)
+ return status;
+
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ switch (cmd) {
+ case RTC_AIE_OFF: /* alarm off */
+ buf[1] &= ~RS5C_CTRL1_AALE;
+ break;
+ case RTC_AIE_ON: /* alarm on */
+ buf[1] |= RS5C_CTRL1_AALE;
+ break;
+ case RTC_UIE_OFF: /* update off */
+ buf[1] &= ~RS5C_CTRL1_CT_MASK;
+ break;
+ case RTC_UIE_ON: /* update on */
+ buf[1] &= ~RS5C_CTRL1_CT_MASK;
+ buf[1] |= RS5C_CTRL1_CT4;
+ break;
+ }
+ if ((i2c_master_send(client, buf, 2)) != 2) {
+ printk(KERN_WARNING "%s: can't update alarm\n",
+ rs5c->rtc->name);
+ status = -EIO;
+ } else
+ rs5c->regs[RS5C_REG_CTRL1] = buf[1];
+ return status;
+}
+
+#else
+#define rs5c_rtc_ioctl NULL
+#endif
+
+
+/* NOTE: Since RTC_WKALM_{RD,SET} were originally defined for EFI,
+ * which only exposes a polled programming interface; and since
+ * these calls map directly to those EFI requests; we don't demand
+ * we have an IRQ for this chip when we go through this API.
+ *
+ * The older x86_pc derived RTC_ALM_{READ,SET} calls require irqs
+ * though, managed through RTC_AIE_{ON,OFF} requests.
+ */
+
+static int rs5c_read_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ int status;
+
+ status = rs5c_get_regs(rs5c);
+ if (status < 0)
+ return status;
+
+ /* report alarm time */
+ t->time.tm_sec = 0;
+ t->time.tm_min = BCD2BIN(rs5c->regs[RS5C_REG_ALARM_A_MIN] & 0x7f);
+ t->time.tm_hour = rs5c_reg2hr(rs5c, rs5c->regs[RS5C_REG_ALARM_A_HOURS]);
+ t->time.tm_mday = -1;
+ t->time.tm_mon = -1;
+ t->time.tm_year = -1;
+ t->time.tm_wday = -1;
+ t->time.tm_yday = -1;
+ t->time.tm_isdst = -1;
+
+ /* ... and status */
+ t->enabled = !!(rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE);
+ t->pending = !!(rs5c->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_AAFG);
+
+ return 0;
+}
+
+static int rs5c_set_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ int status;
+ unsigned char buf[4];
+
+ /* only handle up to 24 hours in the future, like RTC_ALM_SET */
+ if (t->time.tm_mday != -1
+ || t->time.tm_mon != -1
+ || t->time.tm_year != -1)
+ return -EINVAL;
+
+ /* if needed, disable irq (clears pending status) */
+ status = rs5c_get_regs(rs5c);
+ if (status < 0)
+ return status;
+ if (rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE) {
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ buf[1] = rs5c->regs[RS5C_REG_CTRL1] & ~RS5C_CTRL1_AALE;
+ if (i2c_master_send(client, buf, 2) != 2) {
+ pr_debug("%s: can't disable alarm\n", rs5c->rtc->name);
+ return -EIO;
+ }
+ rs5c->regs[RS5C_REG_CTRL1] = buf[1];
+ }
+
+ /* set alarm */
+ buf[0] = RS5C_ADDR(RS5C_REG_ALARM_A_MIN);
+ buf[1] = BIN2BCD(t->time.tm_min);
+ buf[2] = rs5c_hr2reg(rs5c, t->time.tm_hour);
+ buf[3] = 0x7f; /* any/all days */
+ if ((i2c_master_send(client, buf, 4)) != 4) {
+ pr_debug("%s: can't set alarm time\n", rs5c->rtc->name);
+ return -EIO;
+ }
+
+ /* ... and maybe enable its irq */
+ if (t->enabled) {
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ buf[1] = rs5c->regs[RS5C_REG_CTRL1] | RS5C_CTRL1_AALE;
+ if ((i2c_master_send(client, buf, 2)) != 2)
+ printk(KERN_WARNING "%s: can't enable alarm\n",
+ rs5c->rtc->name);
+ rs5c->regs[RS5C_REG_CTRL1] = buf[1];
+ }
+
+ return 0;
+}
+
+#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
+
static int rs5c372_rtc_proc(struct device *dev, struct seq_file *seq)
{
int err, osc, trim;

err = rs5c372_get_trim(to_i2c_client(dev), &osc, &trim);
if (err == 0) {
- seq_printf(seq, "%d.%03d KHz\n", osc / 1000, osc % 1000);
- seq_printf(seq, "trim\t: %d\n", trim);
+ seq_printf(seq, "crystal\t\t: %d.%03d KHz\n",
+ osc / 1000, osc % 1000);
+ if (trim & 0x3e) {
+ int t = trim & 0x3f;
+
+ if (trim & 0x40)
+ t = (~t | (s8)0xc0) + 1;
+ else
+ t = t - 1;
+
+ trim = t * 2;
+ } else
+ trim = 0;
+ seq_printf(seq, "trim\t\t: %d\n", trim);
}

return 0;
}

+#else
+#define rs5c372_rtc_proc NULL
+#endif
+
static const struct rtc_class_ops rs5c372_rtc_ops = {
.proc = rs5c372_rtc_proc,
+ .ioctl = rs5c_rtc_ioctl,
.read_time = rs5c372_rtc_read_time,
.set_time = rs5c372_rtc_set_time,
+ .read_alarm = rs5c_read_alarm,
+ .set_alarm = rs5c_set_alarm,
};

static ssize_t rs5c372_sysfs_show_trim(struct device *dev,
@@ -189,10 +452,7 @@ static ssize_t rs5c372_sysfs_show_osc(st
}
static DEVICE_ATTR(osc, S_IRUGO, rs5c372_sysfs_show_osc, NULL);

-static int rs5c372_attach(struct i2c_adapter *adapter)
-{
- return i2c_probe(adapter, &addr_data, rs5c372_probe);
-}
+static struct i2c_driver rs5c372_driver;

static int rs5c372_probe(struct i2c_adapter *adapter, int address, int kind)
{
@@ -211,7 +471,22 @@ static int rs5c372_probe(struct i2c_adap
err = -ENOMEM;
goto exit;
}
- client = &rs5c372->client;
+
+ /* On conversion to a "new style" i2c driver, we'll be handed
+ * the i2c_client (we won't create it)
+ */
+ client = &rs5c372->dev;
+ rs5c372->client = client;
+
+ /* For "new style" drivers, irq is in i2c_client and chip type
+ * info comes from i2c_client.dev.platform_data. Meanwhile:
+ *
+ * STICK BOARD-SPECIFIC SETUP CODE RIGHT HERE
+ */
+ if (rs5c372->type == rtc_undef) {
+ rs5c372->type = rtc_rs5c372b;
+ dev_warn(&client->dev, "assuming rs5c372b\n");
+ }

/* I2C client */
client->addr = address;
@@ -222,16 +497,87 @@ static int rs5c372_probe(struct i2c_adap

i2c_set_clientdata(client, rs5c372);

- rs5c372->msg[0].addr = address;
- rs5c372->msg[0].flags = I2C_M_RD;
- rs5c372->msg[0].len = sizeof(rs5c372->regs);
- rs5c372->msg[0].buf = rs5c372->regs;
-
/* Inform the i2c layer */
if ((err = i2c_attach_client(client)))
goto exit_kfree;

- dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
+ err = rs5c_get_regs(rs5c372);
+ if (err < 0)
+ goto exit_detach;
+
+ /* clock may be set for am/pm or 24 hr time */
+ switch (rs5c372->type) {
+ case rtc_rs5c372a:
+ case rtc_rs5c372b:
+ /* 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;
+ break;
+ case rtc_rv5c386:
+ case rtc_rv5c387:
+ if (rs5c372->regs[RS5C_REG_CTRL1] & RV5C387_CTRL1_24)
+ rs5c372->time24 = 1;
+ /* alarm uses ALARM_W; and nINTRB for alarm and periodic
+ * irq, on both 386 and 387
+ */
+ break;
+ default:
+ dev_err(&client->dev, "unknown RTC type\n");
+ goto exit_detach;
+ }
+
+ /* if the oscillator lost power and no other software (like
+ * the bootloader) set it up, do it here.
+ */
+ if (rs5c372->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_XSTP) {
+ unsigned char buf[3];
+
+ rs5c372->regs[RS5C_REG_CTRL2] &= ~RS5C_CTRL2_XSTP;
+
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ buf[1] = rs5c372->regs[RS5C_REG_CTRL1];
+ buf[2] = rs5c372->regs[RS5C_REG_CTRL2];
+
+ /* use 24hr mode */
+ switch (rs5c372->type) {
+ case rtc_rs5c372a:
+ case rtc_rs5c372b:
+ buf[2] |= RS5C372_CTRL2_24;
+ rs5c372->time24 = 1;
+ break;
+ case rtc_rv5c386:
+ case rtc_rv5c387:
+ buf[1] |= RV5C387_CTRL1_24;
+ rs5c372->time24 = 1;
+ break;
+ default:
+ /* impossible */
+ break;
+ }
+
+ if ((i2c_master_send(client, buf, 3)) != 3) {
+ dev_err(&client->dev, "setup error\n");
+ goto exit_detach;
+ }
+ rs5c372->regs[RS5C_REG_CTRL1] = buf[1];
+ rs5c372->regs[RS5C_REG_CTRL2] = buf[2];
+
+ dev_warn(&client->dev, "clock needs to be set\n");
+ }
+
+ dev_info(&client->dev, "%s found, driver version " DRV_VERSION "\n",
+ ({ char *s; switch (rs5c372->type) {
+ case rtc_rs5c372a: s = "rs5c372a"; break;
+ case rtc_rs5c372b: s = "rs5c372b"; break;
+ case rtc_rv5c386: s = "rv5c386"; break;
+ case rtc_rv5c387: s = "rv5c387"; break;
+ default: s = "chip"; break;
+ }; s;})
+ );
+
+ /* FIXME when client->irq exists, use it to register alarm irq */

rs5c372->rtc = rtc_device_register(rs5c372_driver.driver.name,
&client->dev, &rs5c372_rtc_ops, THIS_MODULE);
@@ -266,6 +612,11 @@ exit:
return err;
}

+static int rs5c372_attach(struct i2c_adapter *adapter)
+{
+ return i2c_probe(adapter, &addr_data, rs5c372_probe);
+}
+
static int rs5c372_detach(struct i2c_client *client)
{
int err;
@@ -281,6 +632,14 @@ static int rs5c372_detach(struct i2c_cli
return 0;
}

+static struct i2c_driver rs5c372_driver = {
+ .driver = {
+ .name = "rtc-rs5c372",
+ },
+ .attach_adapter = &rs5c372_attach,
+ .detach_client = &rs5c372_detach,
+};
+
static __init int rs5c372_init(void)
{
return i2c_add_driver(&rs5c372_driver);
Index: g26/drivers/rtc/Kconfig
===================================================================
--- g26.orig/drivers/rtc/Kconfig 2006-12-08 16:44:05.000000000 -0800
+++ g26/drivers/rtc/Kconfig 2006-12-08 16:46:55.000000000 -0800
@@ -222,11 +222,11 @@ config RTC_DRV_RS5C348
will be called rtc-rs5c348.

config RTC_DRV_RS5C372
- tristate "Ricoh RS5C372A/B"
+ tristate "Ricoh RS5C372A/B, RV5C386, RV5C387"
depends on RTC_CLASS && I2C
help
If you say yes here you get support for the
- Ricoh RS5C372A and RS5C372B RTC chips.
+ Ricoh RS5C372A, RS5C372B, RV5C386, and RV5C387 RTC chips.

This driver can also be built as a module. If so, the module
will be called rtc-rs5c372.


2006-12-09 09:38:13

by Alessandro Zummo

[permalink] [raw]
Subject: Re: [rtc-linux] [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

On Fri, 8 Dec 2006 18:59:41 -0800
David Brownell <[email protected]> wrote:

>
> Note that this reverts part of a previous patch, which seems to have
> included key parts of the initial version of this one. I suspect the
> issue wasn't that "mode 1" didn't work on that board; the original
> code to fetch the trim was broken. If "mode 1" really won't work,
> that's almost certainly a bug in that board's I2C driver.

Riku, can you please test it on your platform to see
if mode 1 works nicely?

--

Best regards,

Alessandro Zummo,
Tower Technologies - Turin, Italy

http://www.towertech.it

2006-12-11 06:27:38

by Riku Voipio

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

> Update the rtc-rs5c372 driver:
> I suspect the
> issue wasn't that "mode 1" didn't work on that board; the original
> code to fetch the trim was broken. If "mode 1" really won't work,
> that's almost certainly a bug in that board's I2C driver.

It was not related to trim fetching. Yes, it very likely that the boards
i2c controller (i2c-iop3xx) is has a bug, but I'm not competent enough to
find out what it is actually sending out to the wire.

With your patch, the rtc acts like the chip would completely ignore the
"address" transfer, and starts reading from the last (default) register
anyway. Thus all the regs look shifted by one in the driver. With current
git driver results look like:

# cat /proc/driver/rtc ; sleep 1 ; echo one sec gone; cat /proc/driver/rtc
rtc_time : 06:16:55
rtc_date : 2006-12-11
24hr : yes
32.768 KHz
trim : 1
one sec gone
rtc_time : 06:16:56
rtc_date : 2006-12-11
24hr : yes
32.768 KHz
trim : 1

And same after your patch:

rtc_time : 16:23:28
rtc_date : 2012-11-01
alrm_time : **:01:00
alrm_date : ****-**-**
alrm_wakeup : no
alrm_pending : no
24hr : yes
crystal : 32.768 KHz
trim : 10
one sec gone
rtc_time : 16:24:28
rtc_date : 2012-11-01
alrm_time : **:01:00
alrm_date : ****-**-**
alrm_wakeup : no
alrm_pending : no
24hr : yes
crystal : 32.768 KHz
trim : 10


> + /* this implements the first (most portable) reading method
> + * specified in the datasheet.
> */

Why is this method considered more portable? Howabout making the read
method a module parameter?


2006-12-11 20:43:31

by David Brownell

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

On Sunday 10 December 2006 10:27 pm, Voipio Riku wrote:
> > Update the rtc-rs5c372 driver:
> > I suspect the
> > issue wasn't that "mode 1" didn't work on that board; the original
> > code to fetch the trim was broken. If "mode 1" really won't work,
> > that's almost certainly a bug in that board's I2C driver.
>
> It was not related to trim fetching. Yes, it very likely that the boards
> i2c controller (i2c-iop3xx) is has a bug, but I'm not competent enough to
> find out what it is actually sending out to the wire.

I'd expect that would be the controller _driver_ ... although it would
not surprise me to know there were also (unfixed) silicon bugs to cope
with, like version-specific differences. One hopes errata are published
for the chip you're using, and that they don't lie.

Have you asked around for anyone who may have insights about i2c-iop3xx
driver bugs? Maybe the driver maintainers, or arm-linux folk, or on
the i2c list. I did notice the changelog for that driver included
changes (e.g. July 1 this year) for chip-specific differences ... there
could easily be issues like that still lingering.


> With your patch, the rtc acts like the chip would completely ignore the
> "address" transfer, and starts reading from the last (default) register
> anyway. Thus all the regs look shifted by one in the driver.

That's quite strange. The docs on the RTC are quite clear about what's
supposed to happen with what I2C messages. And I'd expect them to be
right ... especially since they behaved for me, and the original author
of that code! That makes me suspect that your particular I2C controller
driver must not be issuing the protocol requests it should be, at least
on your hardware and revision.


> > + /* this implements the first (most portable) reading method
> > + * specified in the datasheet.
> > */
>
> Why is this method considered more portable? Howabout making the read
> method a module parameter?

Of the three methods, #2 depends on messages that not all I2C masters
are necessarily going to be able to issue, and #3 assumes that there's
no other I2C master accessing that chip.

Plus, if I understand things correctly, using mode #3 would break when
writing e.g. just REG_CTRL1 to enable alarms or force an uninitialized
chip into 24 hr mode ... or when writing the alarm. Because, just as
with the multi-master case, the implicit register pointer would no
longer stay at "15" all the time. So I don't see how having an option
to choose the mode would be a good solution.

- Dave


2006-12-11 22:23:09

by Riku Voipio

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

> On Sunday 10 December 2006 10:27 pm, Voipio Riku wrote:
>> > Update the rtc-rs5c372 driver:
>> > I suspect the
>> > issue wasn't that "mode 1" didn't work on that board; the original
>> > code to fetch the trim was broken. If "mode 1" really won't work,
>> > that's almost certainly a bug in that board's I2C driver.

>> It was not related to trim fetching. Yes, it very likely that the boards
>> i2c controller (i2c-iop3xx) is has a bug, but I'm not competent enough
>> to
>> find out what it is actually sending out to the wire.

> I'd expect that would be the controller _driver_ ... although it would
> not surprise me to know there were also (unfixed) silicon bugs to cope
> with, like version-specific differences. One hopes errata are published
> for the chip you're using, and that they don't lie.

from what I saw, the driver simply passes messages over to the i2c
controller. It even specifically mentiones that it supports repeated start
conditions, as needed for read method #1. Comparing to 80219 manual[1], I
did not spot anything obviously wrong.

> Have you asked around for anyone who may have insights about i2c-iop3xx
> driver bugs? Maybe the driver maintainers, or arm-linux folk, or on
> the i2c list.

I was told to contact Dan Williams, I didn't get any response.

>> With your patch, the rtc acts like the chip would completely ignore the
>> "address" transfer, and starts reading from the last (default) register
>> anyway. Thus all the regs look shifted by one in the driver.

> That's quite strange. The docs on the RTC are quite clear about what's
> supposed to happen with what I2C messages. And I'd expect them to be
> right ... especially since they behaved for me, and the original author
> of that code! That makes me suspect that your particular I2C controller
> driver must not be issuing the protocol requests it should be, at least
> on your hardware and revision.

Well at least I'm happy that there is now someone more experienced working
on this driver. When I tried to get it working I could not find anyone
with another board to verify if the original and/or my patch works for
them..

>> > + /* this implements the first (most portable) reading method
>> > + * specified in the datasheet.
>> > */

>> Why is this method considered more portable? Howabout making the read
>> method a module parameter?

> Of the three methods, #2 depends on messages that not all I2C masters
> are necessarily going to be able to issue, and #3 assumes that there's
> no other I2C master accessing that chip.

Agreed, I wouldn't consider method #2 either.

> Plus, if I understand things correctly, using mode #3 would break when
> writing

I should not. Writing isn't related to reading methods according the
datasheet[2]. It provides one addressing method for writing, and writing
works fine our Thecus/Allnet hardware.

[1] http://www.intel.com/design/iio/manuals/274017.htm
[2] http://www.ricoh.com/LSI/product_rtc/2wire/5c372/5c372a-e.pdf

2006-12-11 23:33:29

by Dan Williams

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

On 12/11/06, Voipio Riku <[email protected]> wrote:
<snip>
> > Have you asked around for anyone who may have insights about i2c-iop3xx
> > driver bugs? Maybe the driver maintainers, or arm-linux folk, or on
> > the i2c list.
>
> I was told to contact Dan Williams, I didn't get any response.
>
Hi Riku, this is the first message I have received.

According to the latest specification update
(http://www.intel.com/design/iio/specupdt/27351910.pdf) there are no
known issues with the i2c. I looked through the thread and did not
see what board you are using, can you send those details?

I have not dealt with the i2c-iop3xx driver in the past. Have you
tried contacting the last person to make functional changes to the
driver?
http://kernel.org/git/gitweb.cgi?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=39288e1ac10b3b9a68a629be67d81a0b53512c4e

Regards,
Dan

2006-12-12 08:20:30

by David Brownell

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

On Monday 11 December 2006 3:33 pm, Dan Williams wrote:
>
> According to the latest specification update
> (http://www.intel.com/design/iio/specupdt/27351910.pdf) there are no
> known issues with the i2c.

That's for the 80319 ... Riku said he was using 80219, that
could imply some differences. And I distinctly recall Intel's
XScale docs having a few problems whereby "live" silicon bugs
didn't always stay in the "spec update" documents, so it's
possible that older docs would be needed.

At this point all we really _know_ is that requests made
through 80219's i2c controller don't give the correct result
(at least on Riku's 80219 board) and moreover seem to have a
consistent failure mode ... whereas ones made with OMAP's
controller (and presumably that of the original driver author,
for a Synology DS-101 presumably with IXP420) do act ok.

- Dave

2006-12-12 08:30:48

by David Brownell

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

On Monday 11 December 2006 2:23 pm, Voipio Riku wrote:

> from what I saw, the driver simply passes messages over to the i2c
> controller. It even specifically mentiones that it supports repeated start
> conditions, as needed for read method #1. Comparing to 80219 manual[1], I
> did not spot anything obviously wrong.

I skimmed i2c-ixp3xx too, but have never spent much time with I2C controller
drivers or Intel's fancier XScales.


> >> With your patch, the rtc acts like the chip would completely ignore the
> >> "address" transfer, and starts reading from the last (default) register
> >> anyway. Thus all the regs look shifted by one in the driver.
>
> > That's quite strange. The docs on the RTC are quite clear about what's
> > supposed to happen with what I2C messages. And I'd expect them to be
> > right ... especially since they behaved for me, and the original author
> > of that code! That makes me suspect that your particular I2C controller
> > driver must not be issuing the protocol requests it should be, at least
> > on your hardware and revision.
>
> Well at least I'm happy that there is now someone more experienced working
> on this driver. When I tried to get it working I could not find anyone
> with another board to verify if the original and/or my patch works for
> them..

Unfortunately our patches collided. The original code worked for me
(other than bugs in the trim register).


> > Plus, if I understand things correctly, using mode #3 would break when
> > writing
>
> I should not. Writing isn't related to reading methods according the
> datasheet[2]. It provides one addressing method for writing, and writing
> works fine our Thecus/Allnet hardware.

I see ... reading more closely "the internal address pointer is set to Fh
when the stop condition is met", namely right after each transaction. It's
not like other chips with such pointers that I've used (they never reset it).

I don't mind if the "read all the registers" operation uses mode 3. I'll
have to see if your updated version behaves (albeit without handling 12 hour
time, the alarm, etc) for me.

But I'm still concerned that switching to mode 3 seems to be just working
around a bug in some other driver, and that _other_ bug should be fixed.

- Dave

2006-12-12 08:50:57

by Riku Voipio

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

Executive summary for the new in CC list: Is it possible that i2c-iop3xx
driver in current mainline
Linux is buggy regarding repeated start conditions?

Dan Williams wrote:
> According to the latest specification update
> (http://www.intel.com/design/iio/specupdt/27351910.pdf) there are no
> known issues with the i2c. I looked through the thread and did not
> see what board you are using, can you send those details?
We are using Thecus n2100, which has a IOP 80219. The vendor
itself patched iq31244 board file, so presumably it's very similar.

http://www.debonaras.org/wiki/Info/ThecusN2100Internals

Any more specific information you want?
>
> I have not dealt with the i2c-iop3xx driver in the past. Have you
> tried contacting the last person to make functional changes to the
> driver?
Well, now we have :)

> Hi Riku, this is the first message I have received.

This what I sent then:

-snip-
We have an Ricoh 5c372 RTC [1] which uses "repeated start" for
internal register access. On a ixp-4xx device (Synology DS101),
the mainline linux driver suposedly worked fine. On our device,
a thecus n2100 with IOP 80219, the driver almost works.

See page 31 on the datasheet[1] for example read transfer.

After some debugging, it seems the RTC is ignoring the internal
address setting request. On iop, we allways get the contents of
beginning from internal register 0xF, which is the default internal
address it sets after a stop condition. I'm not equipped with a scope,
so I don't see what's happening on the wire..

I have a workaround for this specific case, but I don't like
it, and this could be problem with other chips as well.
-snip-

Since then I learned the same rtc is in use with Kuro Boxes, which
use a freescale ppc soc. There both mainline driver and my patched
driver works fine.

[1] http://www.ricoh.com/LSI/product_rtc/2wire/5c372/5c372a-e.pdf
[2]
http://www.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;hb=HEAD;f=drivers/rtc/rtc-rs5c372.c


2007-01-03 03:07:31

by David Brownell

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

Hi Voipio,

Yes, the patch you sent (switching to "method 3" to work around the
evident bug in the i2c-ixp3xx driver) works on the platform I was
using too (after unrelated tweaks).

Here's an updated patch, using "method 3". If it still behaves
for you, it'd seem ready to merge...

- Dave


============ CUT HERE
Update the rtc-rs5c372 driver:

Bugfixes:
- Handle RTCs which are configured to use 12-hour mode.
- Never report bogus/un-initialized times.
- Displaying "raw trim" requires not masking it first!
- Fix the and sysfs procfs display of crystal and trim data.

Features:
- Handle other RTCs in this family, notably rv5c386/rv5c387.
- Declare the other registers.
- Provide alarm get/set functionality.
- Handle AIE and UIE; but no IRQ handling yet.
- Warn if the clock needs to be set.

Cleanup:
- Shrink object by not including needless sysfs or procfs support
- We don't need no steenkin' forward declarations. (Except one.)

Until the I2C framework merges "new style" driver support, matching
the driver model better, using rv5c chips or alarm IRQs requires a
separate board-specific patch. (And an IRQ handler, handing off labor
through a work_struct...)

This uses the "method 3" register reads, but notes that it's done
to work around an evident i2c adapter driver bug, and the curious
issue where the chip behavior disagrees with the chip docs.

Signed-off-by: David Brownell <[email protected]>

Index: g26/drivers/rtc/rtc-rs5c372.c
===================================================================
--- g26.orig/drivers/rtc/rtc-rs5c372.c 2006-12-27 17:19:55.000000000 -0800
+++ g26/drivers/rtc/rtc-rs5c372.c 2007-01-02 18:44:35.000000000 -0800
@@ -1,5 +1,5 @@
/*
- * An I2C driver for the Ricoh RS5C372 RTC
+ * An I2C driver for Ricoh RS5C372 and RV5C38[67] RTCs
*
* Copyright (C) 2005 Pavel Mironchik <[email protected]>
* Copyright (C) 2006 Tower Technologies
@@ -13,7 +13,7 @@
#include <linux/rtc.h>
#include <linux/bcd.h>

-#define DRV_VERSION "0.3"
+#define DRV_VERSION "0.4"

/* Addresses to scan */
static unsigned short normal_i2c[] = { /* 0x32,*/ I2C_CLIENT_END };
@@ -21,6 +21,13 @@ static unsigned short normal_i2c[] = { /
/* Insmod parameters */
I2C_CLIENT_INSMOD;

+
+/*
+ * Ricoh has a family of I2C based RTCs, which differ only slightly from
+ * each other. Differences center on pinout (e.g. how many interrupts,
+ * output clock, etc) and how the control registers are used. The '372
+ * is significant only because that's the one this driver first supported.
+ */
#define RS5C372_REG_SECS 0
#define RS5C372_REG_MINS 1
#define RS5C372_REG_HOURS 2
@@ -29,59 +36,142 @@ I2C_CLIENT_INSMOD;
#define RS5C372_REG_MONTH 5
#define RS5C372_REG_YEAR 6
#define RS5C372_REG_TRIM 7
+# define RS5C372_TRIM_XSL 0x80
+# define RS5C372_TRIM_MASK 0x7F

-#define RS5C372_TRIM_XSL 0x80
-#define RS5C372_TRIM_MASK 0x7F
-
-#define RS5C372_REG_BASE 0
-
-static int rs5c372_attach(struct i2c_adapter *adapter);
-static int rs5c372_detach(struct i2c_client *client);
-static int rs5c372_probe(struct i2c_adapter *adapter, int address, int kind);
+#define RS5C_REG_ALARM_A_MIN 8 /* or ALARM_W */
+#define RS5C_REG_ALARM_A_HOURS 9
+#define RS5C_REG_ALARM_A_WDAY 10
+
+#define RS5C_REG_ALARM_B_MIN 11 /* or ALARM_D */
+#define RS5C_REG_ALARM_B_HOURS 12
+#define RS5C_REG_ALARM_B_WDAY 13 /* (ALARM_B only) */
+
+#define RS5C_REG_CTRL1 14
+# define RS5C_CTRL1_AALE (1 << 7) /* or WALE */
+# define RS5C_CTRL1_BALE (1 << 6) /* or DALE */
+# define RV5C387_CTRL1_24 (1 << 5)
+# define RS5C372A_CTRL1_SL1 (1 << 5)
+# define RS5C_CTRL1_CT_MASK (7 << 0)
+# 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 RS5C372_CTRL2_24 (1 << 5)
+# define RS5C_CTRL2_XSTP (1 << 4)
+# define RS5C_CTRL2_CTFG (1 << 2)
+# define RS5C_CTRL2_AAFG (1 << 1) /* or WAFG */
+# define RS5C_CTRL2_BAFG (1 << 0) /* or DAFG */
+
+
+/* to read (style 1) or write registers starting at R */
+#define RS5C_ADDR(R) (((R) << 4) | 0)
+
+
+enum rtc_type {
+ rtc_undef = 0,
+ rtc_rs5c372a,
+ rtc_rs5c372b,
+ rtc_rv5c386,
+ rtc_rv5c387a,
+};

+/* REVISIT: this assumes that:
+ * - we're in the 21st century, so it's safe to ignore the century
+ * bit for rv5c38[67] (REG_MONTH bit 7);
+ * - we should use ALARM_A not ALARM_B (may be wrong on some boards)
+ */
struct rs5c372 {
- u8 reg_addr;
- u8 regs[17];
- struct i2c_msg msg[1];
- struct i2c_client client;
- struct rtc_device *rtc;
-};
+ struct i2c_client *client;
+ struct rtc_device *rtc;
+ enum rtc_type type;
+ unsigned time24:1;
+ unsigned has_irq:1;
+ char buf[17];
+ char *regs;

-static struct i2c_driver rs5c372_driver = {
- .driver = {
- .name = "rs5c372",
- },
- .attach_adapter = &rs5c372_attach,
- .detach_client = &rs5c372_detach,
+ /* on conversion to a "new style" i2c driver, this vanishes */
+ struct i2c_client dev;
};

-static int rs5c372_get_datetime(struct i2c_client *client, struct rtc_time *tm)
+static int rs5c_get_regs(struct rs5c372 *rs5c)
{
-
- struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
- u8 *buf = &(rs5c372->regs[1]);
-
- /* this implements the 3rd reading method, according
- * to the datasheet. rs5c372 defaults to internal
- * address 0xF, so 0x0 is in regs[1]
+ struct i2c_client *client = rs5c->client;
+ struct i2c_msg msgs[] = {
+ { client->addr, I2C_M_RD, sizeof rs5c->buf, rs5c->buf },
+ };
+
+ /* This implements the third reading method from the datasheet, using
+ * an internal address that's reset after each transaction (by STOP)
+ * to 0x0f ... so we read extra registers, and skip the first one.
+ *
+ * The first method doesn't work with the iop3xx adapter driver, on at
+ * least 80219 chips; this works around that bug.
*/
-
- if ((i2c_transfer(client->adapter, rs5c372->msg, 1)) != 1) {
- dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
+ if ((i2c_transfer(client->adapter, msgs, 1)) != 1) {
+ pr_debug("%s: can't read registers\n", rs5c->rtc->name);
return -EIO;
}

- tm->tm_sec = BCD2BIN(buf[RS5C372_REG_SECS] & 0x7f);
- tm->tm_min = BCD2BIN(buf[RS5C372_REG_MINS] & 0x7f);
- tm->tm_hour = BCD2BIN(buf[RS5C372_REG_HOURS] & 0x3f);
- tm->tm_wday = BCD2BIN(buf[RS5C372_REG_WDAY] & 0x07);
- tm->tm_mday = BCD2BIN(buf[RS5C372_REG_DAY] & 0x3f);
+ dev_dbg(&client->dev,
+ "%02x %02x %02x (%02x) %02x %02x %02x (%02x), "
+ "%02x %02x %02x, %02x %02x %02x; %02x %02x\n",
+ rs5c->regs[0], rs5c->regs[1], rs5c->regs[2], rs5c->regs[3],
+ rs5c->regs[4], rs5c->regs[5], rs5c->regs[6], rs5c->regs[7],
+ rs5c->regs[8], rs5c->regs[9], rs5c->regs[10], rs5c->regs[11],
+ rs5c->regs[12], rs5c->regs[13], rs5c->regs[14], rs5c->regs[15]);
+
+ return 0;
+}
+
+static unsigned rs5c_reg2hr(struct rs5c372 *rs5c, unsigned reg)
+{
+ unsigned hour;
+
+ if (rs5c->time24)
+ return BCD2BIN(reg & 0x3f);
+
+ hour = BCD2BIN(reg & 0x1f);
+ if (hour == 12)
+ hour = 0;
+ if (reg & 0x20)
+ hour += 12;
+ return hour;
+}
+
+static unsigned rs5c_hr2reg(struct rs5c372 *rs5c, unsigned hour)
+{
+ if (rs5c->time24)
+ return BIN2BCD(hour);
+
+ if (hour > 12)
+ return 0x20 | BIN2BCD(hour - 12);
+ if (hour == 12)
+ return 0x20 | BIN2BCD(12);
+ if (hour == 0)
+ return BIN2BCD(12);
+ return BIN2BCD(hour);
+}
+
+static int rs5c372_get_datetime(struct i2c_client *client, struct rtc_time *tm)
+{
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ int status = rs5c_get_regs(rs5c);
+
+ if (status < 0)
+ return status;
+
+ 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]);
+
+ tm->tm_wday = BCD2BIN(rs5c->regs[RS5C372_REG_WDAY] & 0x07);
+ tm->tm_mday = BCD2BIN(rs5c->regs[RS5C372_REG_DAY] & 0x3f);

/* tm->tm_mon is zero-based */
- tm->tm_mon = BCD2BIN(buf[RS5C372_REG_MONTH] & 0x1f) - 1;
+ tm->tm_mon = BCD2BIN(rs5c->regs[RS5C372_REG_MONTH] & 0x1f) - 1;

/* year is 1900 + tm->tm_year */
- tm->tm_year = BCD2BIN(buf[RS5C372_REG_YEAR]) + 100;
+ tm->tm_year = BCD2BIN(rs5c->regs[RS5C372_REG_YEAR]) + 100;

dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
"mday=%d, mon=%d, year=%d, wday=%d\n",
@@ -89,22 +179,25 @@ static int rs5c372_get_datetime(struct i
tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

- return 0;
+ /* rtc might need initialization */
+ return rtc_valid_tm(tm);
}

static int rs5c372_set_datetime(struct i2c_client *client, struct rtc_time *tm)
{
- unsigned char buf[8] = { RS5C372_REG_BASE };
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ unsigned char buf[8];

- dev_dbg(&client->dev,
- "%s: secs=%d, mins=%d, hours=%d "
+ dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d "
"mday=%d, mon=%d, year=%d, wday=%d\n",
- __FUNCTION__, tm->tm_sec, tm->tm_min, tm->tm_hour,
+ __FUNCTION__,
+ tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

+ buf[0] = RS5C_ADDR(RS5C372_REG_SECS);
buf[1] = BIN2BCD(tm->tm_sec);
buf[2] = BIN2BCD(tm->tm_min);
- buf[3] = BIN2BCD(tm->tm_hour);
+ buf[3] = rs5c_hr2reg(rs5c, tm->tm_hour);
buf[4] = BIN2BCD(tm->tm_wday);
buf[5] = BIN2BCD(tm->tm_mday);
buf[6] = BIN2BCD(tm->tm_mon + 1);
@@ -118,21 +211,43 @@ static int rs5c372_set_datetime(struct i
return 0;
}

+#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
+#define NEED_TRIM
+#endif
+
+#if defined(CONFIG_RTC_INTF_SYSFS) || defined(CONFIG_RTC_INTF_SYSFS_MODULE)
+#define NEED_TRIM
+#endif
+
+#ifdef NEED_TRIM
static int rs5c372_get_trim(struct i2c_client *client, int *osc, int *trim)
{
struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
- u8 tmp = rs5c372->regs[RS5C372_REG_TRIM + 1];
+ u8 tmp = rs5c372->regs[RS5C372_REG_TRIM];

if (osc)
*osc = (tmp & RS5C372_TRIM_XSL) ? 32000 : 32768;

if (trim) {
- *trim = tmp & RS5C372_TRIM_MASK;
- dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, *trim);
+ dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, tmp);
+ tmp &= RS5C372_TRIM_MASK;
+ if (tmp & 0x3e) {
+ int t = tmp & 0x3f;
+
+ if (tmp & 0x40)
+ t = (~t | (s8)0xc0) + 1;
+ else
+ t = t - 1;
+
+ tmp = t * 2;
+ } else
+ tmp = 0;
+ *trim = tmp;
}

return 0;
}
+#endif

static int rs5c372_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
@@ -144,25 +259,190 @@ static int rs5c372_rtc_set_time(struct d
return rs5c372_set_datetime(to_i2c_client(dev), tm);
}

+#if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE)
+
+static int
+rs5c_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ unsigned char buf[2];
+ int status;
+
+ buf[1] = rs5c->regs[RS5C_REG_CTRL1];
+ switch (cmd) {
+ case RTC_UIE_OFF:
+ case RTC_UIE_ON:
+ /* some 327a modes use a different IRQ pin for 1Hz irqs */
+ if (rs5c->type == rtc_rs5c372a
+ && (buf[1] & RS5C372A_CTRL1_SL1))
+ return -ENOIOCTLCMD;
+ case RTC_AIE_OFF:
+ case RTC_AIE_ON:
+ /* these irq management calls only make sense for chips
+ * which are wired up to an IRQ.
+ */
+ if (!rs5c->has_irq)
+ return -ENOIOCTLCMD;
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ status = rs5c_get_regs(rs5c);
+ if (status < 0)
+ return status;
+
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ switch (cmd) {
+ case RTC_AIE_OFF: /* alarm off */
+ buf[1] &= ~RS5C_CTRL1_AALE;
+ break;
+ case RTC_AIE_ON: /* alarm on */
+ buf[1] |= RS5C_CTRL1_AALE;
+ break;
+ case RTC_UIE_OFF: /* update off */
+ buf[1] &= ~RS5C_CTRL1_CT_MASK;
+ break;
+ case RTC_UIE_ON: /* update on */
+ buf[1] &= ~RS5C_CTRL1_CT_MASK;
+ buf[1] |= RS5C_CTRL1_CT4;
+ break;
+ }
+ if ((i2c_master_send(client, buf, 2)) != 2) {
+ printk(KERN_WARNING "%s: can't update alarm\n",
+ rs5c->rtc->name);
+ status = -EIO;
+ } else
+ rs5c->regs[RS5C_REG_CTRL1] = buf[1];
+ return status;
+}
+
+#else
+#define rs5c_rtc_ioctl NULL
+#endif
+
+
+/* NOTE: Since RTC_WKALM_{RD,SET} were originally defined for EFI,
+ * which only exposes a polled programming interface; and since
+ * these calls map directly to those EFI requests; we don't demand
+ * we have an IRQ for this chip when we go through this API.
+ *
+ * The older x86_pc derived RTC_ALM_{READ,SET} calls require irqs
+ * though, managed through RTC_AIE_{ON,OFF} requests.
+ */
+
+static int rs5c_read_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ int status;
+
+ status = rs5c_get_regs(rs5c);
+ if (status < 0)
+ return status;
+
+ /* report alarm time */
+ t->time.tm_sec = 0;
+ t->time.tm_min = BCD2BIN(rs5c->regs[RS5C_REG_ALARM_A_MIN] & 0x7f);
+ t->time.tm_hour = rs5c_reg2hr(rs5c, rs5c->regs[RS5C_REG_ALARM_A_HOURS]);
+ t->time.tm_mday = -1;
+ t->time.tm_mon = -1;
+ t->time.tm_year = -1;
+ t->time.tm_wday = -1;
+ t->time.tm_yday = -1;
+ t->time.tm_isdst = -1;
+
+ /* ... and status */
+ t->enabled = !!(rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE);
+ t->pending = !!(rs5c->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_AAFG);
+
+ return 0;
+}
+
+static int rs5c_set_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ int status;
+ unsigned char buf[4];
+
+ /* only handle up to 24 hours in the future, like RTC_ALM_SET */
+ if (t->time.tm_mday != -1
+ || t->time.tm_mon != -1
+ || t->time.tm_year != -1)
+ return -EINVAL;
+
+ /* REVISIT: round up tm_sec */
+
+ /* if needed, disable irq (clears pending status) */
+ status = rs5c_get_regs(rs5c);
+ if (status < 0)
+ return status;
+ if (rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE) {
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ buf[1] = rs5c->regs[RS5C_REG_CTRL1] & ~RS5C_CTRL1_AALE;
+ if (i2c_master_send(client, buf, 2) != 2) {
+ pr_debug("%s: can't disable alarm\n", rs5c->rtc->name);
+ return -EIO;
+ }
+ rs5c->regs[RS5C_REG_CTRL1] = buf[1];
+ }
+
+ /* set alarm */
+ buf[0] = RS5C_ADDR(RS5C_REG_ALARM_A_MIN);
+ buf[1] = BIN2BCD(t->time.tm_min);
+ buf[2] = rs5c_hr2reg(rs5c, t->time.tm_hour);
+ buf[3] = 0x7f; /* any/all days */
+ if ((i2c_master_send(client, buf, 4)) != 4) {
+ pr_debug("%s: can't set alarm time\n", rs5c->rtc->name);
+ return -EIO;
+ }
+
+ /* ... and maybe enable its irq */
+ if (t->enabled) {
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ buf[1] = rs5c->regs[RS5C_REG_CTRL1] | RS5C_CTRL1_AALE;
+ if ((i2c_master_send(client, buf, 2)) != 2)
+ printk(KERN_WARNING "%s: can't enable alarm\n",
+ rs5c->rtc->name);
+ rs5c->regs[RS5C_REG_CTRL1] = buf[1];
+ }
+
+ return 0;
+}
+
+#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
+
static int rs5c372_rtc_proc(struct device *dev, struct seq_file *seq)
{
int err, osc, trim;

err = rs5c372_get_trim(to_i2c_client(dev), &osc, &trim);
if (err == 0) {
- seq_printf(seq, "%d.%03d KHz\n", osc / 1000, osc % 1000);
- seq_printf(seq, "trim\t: %d\n", trim);
+ seq_printf(seq, "crystal\t\t: %d.%03d KHz\n",
+ osc / 1000, osc % 1000);
+ seq_printf(seq, "trim\t\t: %d\n", trim);
}

return 0;
}

+#else
+#define rs5c372_rtc_proc NULL
+#endif
+
static const struct rtc_class_ops rs5c372_rtc_ops = {
.proc = rs5c372_rtc_proc,
+ .ioctl = rs5c_rtc_ioctl,
.read_time = rs5c372_rtc_read_time,
.set_time = rs5c372_rtc_set_time,
+ .read_alarm = rs5c_read_alarm,
+ .set_alarm = rs5c_set_alarm,
};

+#if defined(CONFIG_RTC_INTF_SYSFS) || defined(CONFIG_RTC_INTF_SYSFS_MODULE)
+
static ssize_t rs5c372_sysfs_show_trim(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -172,7 +452,7 @@ static ssize_t rs5c372_sysfs_show_trim(s
if (err)
return err;

- return sprintf(buf, "0x%2x\n", trim);
+ return sprintf(buf, "%d\n", trim);
}
static DEVICE_ATTR(trim, S_IRUGO, rs5c372_sysfs_show_trim, NULL);

@@ -189,16 +469,35 @@ static ssize_t rs5c372_sysfs_show_osc(st
}
static DEVICE_ATTR(osc, S_IRUGO, rs5c372_sysfs_show_osc, NULL);

-static int rs5c372_attach(struct i2c_adapter *adapter)
+static int rs5c_sysfs_register(struct device *dev)
{
- return i2c_probe(adapter, &addr_data, rs5c372_probe);
+ int err;
+
+ err = device_create_file(dev, &dev_attr_trim);
+ if (err)
+ return err;
+ err = device_create_file(dev, &dev_attr_osc);
+ if (err)
+ device_remove_file(dev, &dev_attr_trim);
+
+ return err;
+}
+
+#else
+static int rs5c_sysfs_register(struct device *dev)
+{
+ return 0;
}
+#endif /* SYSFS */
+
+static struct i2c_driver rs5c372_driver;

static int rs5c372_probe(struct i2c_adapter *adapter, int address, int kind)
{
int err = 0;
struct i2c_client *client;
struct rs5c372 *rs5c372;
+ struct rtc_time tm;

dev_dbg(adapter->class_dev.dev, "%s\n", __FUNCTION__);

@@ -211,7 +510,15 @@ static int rs5c372_probe(struct i2c_adap
err = -ENOMEM;
goto exit;
}
- client = &rs5c372->client;
+
+ /* we read registers 0x0f then 0x00-0x0f; skip the first one */
+ rs5c372->regs=&rs5c372->buf[1];
+
+ /* On conversion to a "new style" i2c driver, we'll be handed
+ * the i2c_client (we won't create it)
+ */
+ client = &rs5c372->dev;
+ rs5c372->client = client;

/* I2C client */
client->addr = address;
@@ -222,16 +529,99 @@ static int rs5c372_probe(struct i2c_adap

i2c_set_clientdata(client, rs5c372);

- rs5c372->msg[0].addr = address;
- rs5c372->msg[0].flags = I2C_M_RD;
- rs5c372->msg[0].len = sizeof(rs5c372->regs);
- rs5c372->msg[0].buf = rs5c372->regs;
-
/* Inform the i2c layer */
if ((err = i2c_attach_client(client)))
goto exit_kfree;

- dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
+ err = rs5c_get_regs(rs5c372);
+ if (err < 0)
+ goto exit_detach;
+
+ /* For "new style" drivers, irq is in i2c_client and chip type
+ * info comes from i2c_client.dev.platform_data. Meanwhile:
+ *
+ * STICK BOARD-SPECIFIC SETUP CODE RIGHT HERE
+ */
+ if (rs5c372->type == rtc_undef) {
+ rs5c372->type = rtc_rs5c372b;
+ dev_warn(&client->dev, "assuming rs5c372b\n");
+ }
+
+ /* clock may be set for am/pm or 24 hr time */
+ switch (rs5c372->type) {
+ case rtc_rs5c372a:
+ case rtc_rs5c372b:
+ /* 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;
+ break;
+ case rtc_rv5c386:
+ case rtc_rv5c387a:
+ if (rs5c372->regs[RS5C_REG_CTRL1] & RV5C387_CTRL1_24)
+ rs5c372->time24 = 1;
+ /* alarm uses ALARM_W; and nINTRB for alarm and periodic
+ * irq, on both 386 and 387
+ */
+ break;
+ default:
+ dev_err(&client->dev, "unknown RTC type\n");
+ goto exit_detach;
+ }
+
+ /* if the oscillator lost power and no other software (like
+ * the bootloader) set it up, do it here.
+ */
+ if (rs5c372->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_XSTP) {
+ unsigned char buf[3];
+
+ rs5c372->regs[RS5C_REG_CTRL2] &= ~RS5C_CTRL2_XSTP;
+
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ buf[1] = rs5c372->regs[RS5C_REG_CTRL1];
+ buf[2] = rs5c372->regs[RS5C_REG_CTRL2];
+
+ /* use 24hr mode */
+ switch (rs5c372->type) {
+ case rtc_rs5c372a:
+ case rtc_rs5c372b:
+ buf[2] |= RS5C372_CTRL2_24;
+ rs5c372->time24 = 1;
+ break;
+ case rtc_rv5c386:
+ case rtc_rv5c387a:
+ buf[1] |= RV5C387_CTRL1_24;
+ rs5c372->time24 = 1;
+ break;
+ default:
+ /* impossible */
+ break;
+ }
+
+ if ((i2c_master_send(client, buf, 3)) != 3) {
+ dev_err(&client->dev, "setup error\n");
+ goto exit_detach;
+ }
+ rs5c372->regs[RS5C_REG_CTRL1] = buf[1];
+ rs5c372->regs[RS5C_REG_CTRL2] = buf[2];
+ }
+
+ if (rs5c372_get_datetime(client, &tm) < 0)
+ dev_warn(&client->dev, "clock needs to be set\n");
+
+ dev_info(&client->dev, "%s found, %s, driver version " DRV_VERSION "\n",
+ ({ char *s; switch (rs5c372->type) {
+ case rtc_rs5c372a: s = "rs5c372a"; break;
+ case rtc_rs5c372b: s = "rs5c372b"; break;
+ case rtc_rv5c386: s = "rv5c386"; break;
+ case rtc_rv5c387a: s = "rv5c387a"; break;
+ default: s = "chip"; break;
+ }; s;}),
+ rs5c372->time24 ? "24hr" : "am/pm"
+ );
+
+ /* FIXME when client->irq exists, use it to register alarm irq */

rs5c372->rtc = rtc_device_register(rs5c372_driver.driver.name,
&client->dev, &rs5c372_rtc_ops, THIS_MODULE);
@@ -241,18 +631,12 @@ static int rs5c372_probe(struct i2c_adap
goto exit_detach;
}

- err = device_create_file(&client->dev, &dev_attr_trim);
+ err = rs5c_sysfs_register(&client->dev);
if (err)
goto exit_devreg;
- err = device_create_file(&client->dev, &dev_attr_osc);
- if (err)
- goto exit_trim;

return 0;

-exit_trim:
- device_remove_file(&client->dev, &dev_attr_trim);
-
exit_devreg:
rtc_device_unregister(rs5c372->rtc);

@@ -266,6 +650,11 @@ exit:
return err;
}

+static int rs5c372_attach(struct i2c_adapter *adapter)
+{
+ return i2c_probe(adapter, &addr_data, rs5c372_probe);
+}
+
static int rs5c372_detach(struct i2c_client *client)
{
int err;
@@ -274,6 +663,8 @@ static int rs5c372_detach(struct i2c_cli
if (rs5c372->rtc)
rtc_device_unregister(rs5c372->rtc);

+ /* REVISIT properly destroy the sysfs files ... */
+
if ((err = i2c_detach_client(client)))
return err;

@@ -281,6 +672,14 @@ static int rs5c372_detach(struct i2c_cli
return 0;
}

+static struct i2c_driver rs5c372_driver = {
+ .driver = {
+ .name = "rtc-rs5c372",
+ },
+ .attach_adapter = &rs5c372_attach,
+ .detach_client = &rs5c372_detach,
+};
+
static __init int rs5c372_init(void)
{
return i2c_add_driver(&rs5c372_driver);

2007-01-03 23:12:28

by Riku Voipio

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

Hi David,

> Yes, the patch you sent (switching to "method 3" to work around the
> evident bug in the i2c-ixp3xx driver) works on the platform I was
> using too (after unrelated tweaks).

> Here's an updated patch, using "method 3". If it still behaves
> for you, it'd seem ready to merge...

Works fine, thanks! Unfortunately the i2c-ixp3xx issue has not advanced in
the meantime, so we still need the third method.

> ============ CUT HERE
> Update the rtc-rs5c372 driver:
>
> Bugfixes:
> - Handle RTCs which are configured to use 12-hour mode.
> - Never report bogus/un-initialized times.
> - Displaying "raw trim" requires not masking it first!
> - Fix the and sysfs procfs display of crystal and trim data.
>
> Features:
> - Handle other RTCs in this family, notably rv5c386/rv5c387.
> - Declare the other registers.
> - Provide alarm get/set functionality.
> - Handle AIE and UIE; but no IRQ handling yet.
> - Warn if the clock needs to be set.
>
> Cleanup:
> - Shrink object by not including needless sysfs or procfs support
> - We don't need no steenkin' forward declarations. (Except one.)
>
> Until the I2C framework merges "new style" driver support, matching
> the driver model better, using rv5c chips or alarm IRQs requires a
> separate board-specific patch. (And an IRQ handler, handing off labor
> through a work_struct...)
>
> This uses the "method 3" register reads, but notes that it's done
> to work around an evident i2c adapter driver bug, and the curious
> issue where the chip behavior disagrees with the chip docs.
>
> Signed-off-by: David Brownell <[email protected]>
>
> Index: g26/drivers/rtc/rtc-rs5c372.c
> ===================================================================
> --- g26.orig/drivers/rtc/rtc-rs5c372.c 2006-12-27 17:19:55.000000000 -0800
> +++ g26/drivers/rtc/rtc-rs5c372.c 2007-01-02 18:44:35.000000000 -0800
> @@ -1,5 +1,5 @@
> /*
> - * An I2C driver for the Ricoh RS5C372 RTC
> + * An I2C driver for Ricoh RS5C372 and RV5C38[67] RTCs
> *
> * Copyright (C) 2005 Pavel Mironchik <[email protected]>
> * Copyright (C) 2006 Tower Technologies
> @@ -13,7 +13,7 @@
> #include <linux/rtc.h>
> #include <linux/bcd.h>
>
> -#define DRV_VERSION "0.3"
> +#define DRV_VERSION "0.4"
>
> /* Addresses to scan */
> static unsigned short normal_i2c[] = { /* 0x32,*/ I2C_CLIENT_END };
> @@ -21,6 +21,13 @@ static unsigned short normal_i2c[] = { /
> /* Insmod parameters */
> I2C_CLIENT_INSMOD;
>
> +
> +/*
> + * Ricoh has a family of I2C based RTCs, which differ only slightly from
> + * each other. Differences center on pinout (e.g. how many interrupts,
> + * output clock, etc) and how the control registers are used. The '372
> + * is significant only because that's the one this driver first
> supported.
> + */
> #define RS5C372_REG_SECS 0
> #define RS5C372_REG_MINS 1
> #define RS5C372_REG_HOURS 2
> @@ -29,59 +36,142 @@ I2C_CLIENT_INSMOD;
> #define RS5C372_REG_MONTH 5
> #define RS5C372_REG_YEAR 6
> #define RS5C372_REG_TRIM 7
> +# define RS5C372_TRIM_XSL 0x80
> +# define RS5C372_TRIM_MASK 0x7F
>
> -#define RS5C372_TRIM_XSL 0x80
> -#define RS5C372_TRIM_MASK 0x7F
> -
> -#define RS5C372_REG_BASE 0
> -
> -static int rs5c372_attach(struct i2c_adapter *adapter);
> -static int rs5c372_detach(struct i2c_client *client);
> -static int rs5c372_probe(struct i2c_adapter *adapter, int address, int
> kind);
> +#define RS5C_REG_ALARM_A_MIN 8 /* or ALARM_W */
> +#define RS5C_REG_ALARM_A_HOURS 9
> +#define RS5C_REG_ALARM_A_WDAY 10
> +
> +#define RS5C_REG_ALARM_B_MIN 11 /* or ALARM_D */
> +#define RS5C_REG_ALARM_B_HOURS 12
> +#define RS5C_REG_ALARM_B_WDAY 13 /* (ALARM_B only) */
> +
> +#define RS5C_REG_CTRL1 14
> +# define RS5C_CTRL1_AALE (1 << 7) /* or WALE */
> +# define RS5C_CTRL1_BALE (1 << 6) /* or DALE */
> +# define RV5C387_CTRL1_24 (1 << 5)
> +# define RS5C372A_CTRL1_SL1 (1 << 5)
> +# define RS5C_CTRL1_CT_MASK (7 << 0)
> +# 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 RS5C372_CTRL2_24 (1 << 5)
> +# define RS5C_CTRL2_XSTP (1 << 4)
> +# define RS5C_CTRL2_CTFG (1 << 2)
> +# define RS5C_CTRL2_AAFG (1 << 1) /* or WAFG */
> +# define RS5C_CTRL2_BAFG (1 << 0) /* or DAFG */
> +
> +
> +/* to read (style 1) or write registers starting at R */
> +#define RS5C_ADDR(R) (((R) << 4) | 0)
> +
> +
> +enum rtc_type {
> + rtc_undef = 0,
> + rtc_rs5c372a,
> + rtc_rs5c372b,
> + rtc_rv5c386,
> + rtc_rv5c387a,
> +};
>
> +/* REVISIT: this assumes that:
> + * - we're in the 21st century, so it's safe to ignore the century
> + * bit for rv5c38[67] (REG_MONTH bit 7);
> + * - we should use ALARM_A not ALARM_B (may be wrong on some boards)
> + */
> struct rs5c372 {
> - u8 reg_addr;
> - u8 regs[17];
> - struct i2c_msg msg[1];
> - struct i2c_client client;
> - struct rtc_device *rtc;
> -};
> + struct i2c_client *client;
> + struct rtc_device *rtc;
> + enum rtc_type type;
> + unsigned time24:1;
> + unsigned has_irq:1;
> + char buf[17];
> + char *regs;
>
> -static struct i2c_driver rs5c372_driver = {
> - .driver = {
> - .name = "rs5c372",
> - },
> - .attach_adapter = &rs5c372_attach,
> - .detach_client = &rs5c372_detach,
> + /* on conversion to a "new style" i2c driver, this vanishes */
> + struct i2c_client dev;
> };
>
> -static int rs5c372_get_datetime(struct i2c_client *client, struct
> rtc_time *tm)
> +static int rs5c_get_regs(struct rs5c372 *rs5c)
> {
> -
> - struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
> - u8 *buf = &(rs5c372->regs[1]);
> -
> - /* this implements the 3rd reading method, according
> - * to the datasheet. rs5c372 defaults to internal
> - * address 0xF, so 0x0 is in regs[1]
> + struct i2c_client *client = rs5c->client;
> + struct i2c_msg msgs[] = {
> + { client->addr, I2C_M_RD, sizeof rs5c->buf, rs5c->buf },
> + };
> +
> + /* This implements the third reading method from the datasheet, using
> + * an internal address that's reset after each transaction (by STOP)
> + * to 0x0f ... so we read extra registers, and skip the first one.
> + *
> + * The first method doesn't work with the iop3xx adapter driver, on at
> + * least 80219 chips; this works around that bug.
> */
> -
> - if ((i2c_transfer(client->adapter, rs5c372->msg, 1)) != 1) {
> - dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
> + if ((i2c_transfer(client->adapter, msgs, 1)) != 1) {
> + pr_debug("%s: can't read registers\n", rs5c->rtc->name);
> return -EIO;
> }
>
> - tm->tm_sec = BCD2BIN(buf[RS5C372_REG_SECS] & 0x7f);
> - tm->tm_min = BCD2BIN(buf[RS5C372_REG_MINS] & 0x7f);
> - tm->tm_hour = BCD2BIN(buf[RS5C372_REG_HOURS] & 0x3f);
> - tm->tm_wday = BCD2BIN(buf[RS5C372_REG_WDAY] & 0x07);
> - tm->tm_mday = BCD2BIN(buf[RS5C372_REG_DAY] & 0x3f);
> + dev_dbg(&client->dev,
> + "%02x %02x %02x (%02x) %02x %02x %02x (%02x), "
> + "%02x %02x %02x, %02x %02x %02x; %02x %02x\n",
> + rs5c->regs[0], rs5c->regs[1], rs5c->regs[2], rs5c->regs[3],
> + rs5c->regs[4], rs5c->regs[5], rs5c->regs[6], rs5c->regs[7],
> + rs5c->regs[8], rs5c->regs[9], rs5c->regs[10], rs5c->regs[11],
> + rs5c->regs[12], rs5c->regs[13], rs5c->regs[14], rs5c->regs[15]);
> +
> + return 0;
> +}
> +
> +static unsigned rs5c_reg2hr(struct rs5c372 *rs5c, unsigned reg)
> +{
> + unsigned hour;
> +
> + if (rs5c->time24)
> + return BCD2BIN(reg & 0x3f);
> +
> + hour = BCD2BIN(reg & 0x1f);
> + if (hour == 12)
> + hour = 0;
> + if (reg & 0x20)
> + hour += 12;
> + return hour;
> +}
> +
> +static unsigned rs5c_hr2reg(struct rs5c372 *rs5c, unsigned hour)
> +{
> + if (rs5c->time24)
> + return BIN2BCD(hour);
> +
> + if (hour > 12)
> + return 0x20 | BIN2BCD(hour - 12);
> + if (hour == 12)
> + return 0x20 | BIN2BCD(12);
> + if (hour == 0)
> + return BIN2BCD(12);
> + return BIN2BCD(hour);
> +}
> +
> +static int rs5c372_get_datetime(struct i2c_client *client, struct
> rtc_time *tm)
> +{
> + struct rs5c372 *rs5c = i2c_get_clientdata(client);
> + int status = rs5c_get_regs(rs5c);
> +
> + if (status < 0)
> + return status;
> +
> + 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]);
> +
> + tm->tm_wday = BCD2BIN(rs5c->regs[RS5C372_REG_WDAY] & 0x07);
> + tm->tm_mday = BCD2BIN(rs5c->regs[RS5C372_REG_DAY] & 0x3f);
>
> /* tm->tm_mon is zero-based */
> - tm->tm_mon = BCD2BIN(buf[RS5C372_REG_MONTH] & 0x1f) - 1;
> + tm->tm_mon = BCD2BIN(rs5c->regs[RS5C372_REG_MONTH] & 0x1f) - 1;
>
> /* year is 1900 + tm->tm_year */
> - tm->tm_year = BCD2BIN(buf[RS5C372_REG_YEAR]) + 100;
> + tm->tm_year = BCD2BIN(rs5c->regs[RS5C372_REG_YEAR]) + 100;
>
> dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
> "mday=%d, mon=%d, year=%d, wday=%d\n",
> @@ -89,22 +179,25 @@ static int rs5c372_get_datetime(struct i
> tm->tm_sec, tm->tm_min, tm->tm_hour,
> tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
>
> - return 0;
> + /* rtc might need initialization */
> + return rtc_valid_tm(tm);
> }
>
> static int rs5c372_set_datetime(struct i2c_client *client, struct
> rtc_time *tm)
> {
> - unsigned char buf[8] = { RS5C372_REG_BASE };
> + struct rs5c372 *rs5c = i2c_get_clientdata(client);
> + unsigned char buf[8];
>
> - dev_dbg(&client->dev,
> - "%s: secs=%d, mins=%d, hours=%d "
> + dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d "
> "mday=%d, mon=%d, year=%d, wday=%d\n",
> - __FUNCTION__, tm->tm_sec, tm->tm_min, tm->tm_hour,
> + __FUNCTION__,
> + tm->tm_sec, tm->tm_min, tm->tm_hour,
> tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
>
> + buf[0] = RS5C_ADDR(RS5C372_REG_SECS);
> buf[1] = BIN2BCD(tm->tm_sec);
> buf[2] = BIN2BCD(tm->tm_min);
> - buf[3] = BIN2BCD(tm->tm_hour);
> + buf[3] = rs5c_hr2reg(rs5c, tm->tm_hour);
> buf[4] = BIN2BCD(tm->tm_wday);
> buf[5] = BIN2BCD(tm->tm_mday);
> buf[6] = BIN2BCD(tm->tm_mon + 1);
> @@ -118,21 +211,43 @@ static int rs5c372_set_datetime(struct i
> return 0;
> }
>
> +#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
> +#define NEED_TRIM
> +#endif
> +
> +#if defined(CONFIG_RTC_INTF_SYSFS) ||
> defined(CONFIG_RTC_INTF_SYSFS_MODULE)
> +#define NEED_TRIM
> +#endif
> +
> +#ifdef NEED_TRIM
> static int rs5c372_get_trim(struct i2c_client *client, int *osc, int
> *trim)
> {
> struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
> - u8 tmp = rs5c372->regs[RS5C372_REG_TRIM + 1];
> + u8 tmp = rs5c372->regs[RS5C372_REG_TRIM];
>
> if (osc)
> *osc = (tmp & RS5C372_TRIM_XSL) ? 32000 : 32768;
>
> if (trim) {
> - *trim = tmp & RS5C372_TRIM_MASK;
> - dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, *trim);
> + dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, tmp);
> + tmp &= RS5C372_TRIM_MASK;
> + if (tmp & 0x3e) {
> + int t = tmp & 0x3f;
> +
> + if (tmp & 0x40)
> + t = (~t | (s8)0xc0) + 1;
> + else
> + t = t - 1;
> +
> + tmp = t * 2;
> + } else
> + tmp = 0;
> + *trim = tmp;
> }
>
> return 0;
> }
> +#endif
>
> static int rs5c372_rtc_read_time(struct device *dev, struct rtc_time *tm)
> {
> @@ -144,25 +259,190 @@ static int rs5c372_rtc_set_time(struct d
> return rs5c372_set_datetime(to_i2c_client(dev), tm);
> }
>
> +#if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE)
> +
> +static int
> +rs5c_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct rs5c372 *rs5c = i2c_get_clientdata(client);
> + unsigned char buf[2];
> + int status;
> +
> + buf[1] = rs5c->regs[RS5C_REG_CTRL1];
> + switch (cmd) {
> + case RTC_UIE_OFF:
> + case RTC_UIE_ON:
> + /* some 327a modes use a different IRQ pin for 1Hz irqs */
> + if (rs5c->type == rtc_rs5c372a
> + && (buf[1] & RS5C372A_CTRL1_SL1))
> + return -ENOIOCTLCMD;
> + case RTC_AIE_OFF:
> + case RTC_AIE_ON:
> + /* these irq management calls only make sense for chips
> + * which are wired up to an IRQ.
> + */
> + if (!rs5c->has_irq)
> + return -ENOIOCTLCMD;
> + break;
> + default:
> + return -ENOIOCTLCMD;
> + }
> +
> + status = rs5c_get_regs(rs5c);
> + if (status < 0)
> + return status;
> +
> + buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
> + switch (cmd) {
> + case RTC_AIE_OFF: /* alarm off */
> + buf[1] &= ~RS5C_CTRL1_AALE;
> + break;
> + case RTC_AIE_ON: /* alarm on */
> + buf[1] |= RS5C_CTRL1_AALE;
> + break;
> + case RTC_UIE_OFF: /* update off */
> + buf[1] &= ~RS5C_CTRL1_CT_MASK;
> + break;
> + case RTC_UIE_ON: /* update on */
> + buf[1] &= ~RS5C_CTRL1_CT_MASK;
> + buf[1] |= RS5C_CTRL1_CT4;
> + break;
> + }
> + if ((i2c_master_send(client, buf, 2)) != 2) {
> + printk(KERN_WARNING "%s: can't update alarm\n",
> + rs5c->rtc->name);
> + status = -EIO;
> + } else
> + rs5c->regs[RS5C_REG_CTRL1] = buf[1];
> + return status;
> +}
> +
> +#else
> +#define rs5c_rtc_ioctl NULL
> +#endif
> +
> +
> +/* NOTE: Since RTC_WKALM_{RD,SET} were originally defined for EFI,
> + * which only exposes a polled programming interface; and since
> + * these calls map directly to those EFI requests; we don't demand
> + * we have an IRQ for this chip when we go through this API.
> + *
> + * The older x86_pc derived RTC_ALM_{READ,SET} calls require irqs
> + * though, managed through RTC_AIE_{ON,OFF} requests.
> + */
> +
> +static int rs5c_read_alarm(struct device *dev, struct rtc_wkalrm *t)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct rs5c372 *rs5c = i2c_get_clientdata(client);
> + int status;
> +
> + status = rs5c_get_regs(rs5c);
> + if (status < 0)
> + return status;
> +
> + /* report alarm time */
> + t->time.tm_sec = 0;
> + t->time.tm_min = BCD2BIN(rs5c->regs[RS5C_REG_ALARM_A_MIN] & 0x7f);
> + t->time.tm_hour = rs5c_reg2hr(rs5c, rs5c->regs[RS5C_REG_ALARM_A_HOURS]);
> + t->time.tm_mday = -1;
> + t->time.tm_mon = -1;
> + t->time.tm_year = -1;
> + t->time.tm_wday = -1;
> + t->time.tm_yday = -1;
> + t->time.tm_isdst = -1;
> +
> + /* ... and status */
> + t->enabled = !!(rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE);
> + t->pending = !!(rs5c->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_AAFG);
> +
> + return 0;
> +}
> +
> +static int rs5c_set_alarm(struct device *dev, struct rtc_wkalrm *t)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct rs5c372 *rs5c = i2c_get_clientdata(client);
> + int status;
> + unsigned char buf[4];
> +
> + /* only handle up to 24 hours in the future, like RTC_ALM_SET */
> + if (t->time.tm_mday != -1
> + || t->time.tm_mon != -1
> + || t->time.tm_year != -1)
> + return -EINVAL;
> +
> + /* REVISIT: round up tm_sec */
> +
> + /* if needed, disable irq (clears pending status) */
> + status = rs5c_get_regs(rs5c);
> + if (status < 0)
> + return status;
> + if (rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE) {
> + buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
> + buf[1] = rs5c->regs[RS5C_REG_CTRL1] & ~RS5C_CTRL1_AALE;
> + if (i2c_master_send(client, buf, 2) != 2) {
> + pr_debug("%s: can't disable alarm\n", rs5c->rtc->name);
> + return -EIO;
> + }
> + rs5c->regs[RS5C_REG_CTRL1] = buf[1];
> + }
> +
> + /* set alarm */
> + buf[0] = RS5C_ADDR(RS5C_REG_ALARM_A_MIN);
> + buf[1] = BIN2BCD(t->time.tm_min);
> + buf[2] = rs5c_hr2reg(rs5c, t->time.tm_hour);
> + buf[3] = 0x7f; /* any/all days */
> + if ((i2c_master_send(client, buf, 4)) != 4) {
> + pr_debug("%s: can't set alarm time\n", rs5c->rtc->name);
> + return -EIO;
> + }
> +
> + /* ... and maybe enable its irq */
> + if (t->enabled) {
> + buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
> + buf[1] = rs5c->regs[RS5C_REG_CTRL1] | RS5C_CTRL1_AALE;
> + if ((i2c_master_send(client, buf, 2)) != 2)
> + printk(KERN_WARNING "%s: can't enable alarm\n",
> + rs5c->rtc->name);
> + rs5c->regs[RS5C_REG_CTRL1] = buf[1];
> + }
> +
> + return 0;
> +}
> +
> +#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
> +
> static int rs5c372_rtc_proc(struct device *dev, struct seq_file *seq)
> {
> int err, osc, trim;
>
> err = rs5c372_get_trim(to_i2c_client(dev), &osc, &trim);
> if (err == 0) {
> - seq_printf(seq, "%d.%03d KHz\n", osc / 1000, osc % 1000);
> - seq_printf(seq, "trim\t: %d\n", trim);
> + seq_printf(seq, "crystal\t\t: %d.%03d KHz\n",
> + osc / 1000, osc % 1000);
> + seq_printf(seq, "trim\t\t: %d\n", trim);
> }
>
> return 0;
> }
>
> +#else
> +#define rs5c372_rtc_proc NULL
> +#endif
> +
> static const struct rtc_class_ops rs5c372_rtc_ops = {
> .proc = rs5c372_rtc_proc,
> + .ioctl = rs5c_rtc_ioctl,
> .read_time = rs5c372_rtc_read_time,
> .set_time = rs5c372_rtc_set_time,
> + .read_alarm = rs5c_read_alarm,
> + .set_alarm = rs5c_set_alarm,
> };
>
> +#if defined(CONFIG_RTC_INTF_SYSFS) ||
> defined(CONFIG_RTC_INTF_SYSFS_MODULE)
> +
> static ssize_t rs5c372_sysfs_show_trim(struct device *dev,
> struct device_attribute *attr, char *buf)
> {
> @@ -172,7 +452,7 @@ static ssize_t rs5c372_sysfs_show_trim(s
> if (err)
> return err;
>
> - return sprintf(buf, "0x%2x\n", trim);
> + return sprintf(buf, "%d\n", trim);
> }
> static DEVICE_ATTR(trim, S_IRUGO, rs5c372_sysfs_show_trim, NULL);
>
> @@ -189,16 +469,35 @@ static ssize_t rs5c372_sysfs_show_osc(st
> }
> static DEVICE_ATTR(osc, S_IRUGO, rs5c372_sysfs_show_osc, NULL);
>
> -static int rs5c372_attach(struct i2c_adapter *adapter)
> +static int rs5c_sysfs_register(struct device *dev)
> {
> - return i2c_probe(adapter, &addr_data, rs5c372_probe);
> + int err;
> +
> + err = device_create_file(dev, &dev_attr_trim);
> + if (err)
> + return err;
> + err = device_create_file(dev, &dev_attr_osc);
> + if (err)
> + device_remove_file(dev, &dev_attr_trim);
> +
> + return err;
> +}
> +
> +#else
> +static int rs5c_sysfs_register(struct device *dev)
> +{
> + return 0;
> }
> +#endif /* SYSFS */
> +
> +static struct i2c_driver rs5c372_driver;
>
> static int rs5c372_probe(struct i2c_adapter *adapter, int address, int
> kind)
> {
> int err = 0;
> struct i2c_client *client;
> struct rs5c372 *rs5c372;
> + struct rtc_time tm;
>
> dev_dbg(adapter->class_dev.dev, "%s\n", __FUNCTION__);
>
> @@ -211,7 +510,15 @@ static int rs5c372_probe(struct i2c_adap
> err = -ENOMEM;
> goto exit;
> }
> - client = &rs5c372->client;
> +
> + /* we read registers 0x0f then 0x00-0x0f; skip the first one */
> + rs5c372->regs=&rs5c372->buf[1];
> +
> + /* On conversion to a "new style" i2c driver, we'll be handed
> + * the i2c_client (we won't create it)
> + */
> + client = &rs5c372->dev;
> + rs5c372->client = client;
>
> /* I2C client */
> client->addr = address;
> @@ -222,16 +529,99 @@ static int rs5c372_probe(struct i2c_adap
>
> i2c_set_clientdata(client, rs5c372);
>
> - rs5c372->msg[0].addr = address;
> - rs5c372->msg[0].flags = I2C_M_RD;
> - rs5c372->msg[0].len = sizeof(rs5c372->regs);
> - rs5c372->msg[0].buf = rs5c372->regs;
> -
> /* Inform the i2c layer */
> if ((err = i2c_attach_client(client)))
> goto exit_kfree;
>
> - dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
> + err = rs5c_get_regs(rs5c372);
> + if (err < 0)
> + goto exit_detach;
> +
> + /* For "new style" drivers, irq is in i2c_client and chip type
> + * info comes from i2c_client.dev.platform_data. Meanwhile:
> + *
> + * STICK BOARD-SPECIFIC SETUP CODE RIGHT HERE
> + */
> + if (rs5c372->type == rtc_undef) {
> + rs5c372->type = rtc_rs5c372b;
> + dev_warn(&client->dev, "assuming rs5c372b\n");
> + }
> +
> + /* clock may be set for am/pm or 24 hr time */
> + switch (rs5c372->type) {
> + case rtc_rs5c372a:
> + case rtc_rs5c372b:
> + /* 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;
> + break;
> + case rtc_rv5c386:
> + case rtc_rv5c387a:
> + if (rs5c372->regs[RS5C_REG_CTRL1] & RV5C387_CTRL1_24)
> + rs5c372->time24 = 1;
> + /* alarm uses ALARM_W; and nINTRB for alarm and periodic
> + * irq, on both 386 and 387
> + */
> + break;
> + default:
> + dev_err(&client->dev, "unknown RTC type\n");
> + goto exit_detach;
> + }
> +
> + /* if the oscillator lost power and no other software (like
> + * the bootloader) set it up, do it here.
> + */
> + if (rs5c372->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_XSTP) {
> + unsigned char buf[3];
> +
> + rs5c372->regs[RS5C_REG_CTRL2] &= ~RS5C_CTRL2_XSTP;
> +
> + buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
> + buf[1] = rs5c372->regs[RS5C_REG_CTRL1];
> + buf[2] = rs5c372->regs[RS5C_REG_CTRL2];
> +
> + /* use 24hr mode */
> + switch (rs5c372->type) {
> + case rtc_rs5c372a:
> + case rtc_rs5c372b:
> + buf[2] |= RS5C372_CTRL2_24;
> + rs5c372->time24 = 1;
> + break;
> + case rtc_rv5c386:
> + case rtc_rv5c387a:
> + buf[1] |= RV5C387_CTRL1_24;
> + rs5c372->time24 = 1;
> + break;
> + default:
> + /* impossible */
> + break;
> + }
> +
> + if ((i2c_master_send(client, buf, 3)) != 3) {
> + dev_err(&client->dev, "setup error\n");
> + goto exit_detach;
> + }
> + rs5c372->regs[RS5C_REG_CTRL1] = buf[1];
> + rs5c372->regs[RS5C_REG_CTRL2] = buf[2];
> + }
> +
> + if (rs5c372_get_datetime(client, &tm) < 0)
> + dev_warn(&client->dev, "clock needs to be set\n");
> +
> + dev_info(&client->dev, "%s found, %s, driver version " DRV_VERSION "\n",
> + ({ char *s; switch (rs5c372->type) {
> + case rtc_rs5c372a: s = "rs5c372a"; break;
> + case rtc_rs5c372b: s = "rs5c372b"; break;
> + case rtc_rv5c386: s = "rv5c386"; break;
> + case rtc_rv5c387a: s = "rv5c387a"; break;
> + default: s = "chip"; break;
> + }; s;}),
> + rs5c372->time24 ? "24hr" : "am/pm"
> + );
> +
> + /* FIXME when client->irq exists, use it to register alarm irq */
>
> rs5c372->rtc = rtc_device_register(rs5c372_driver.driver.name,
> &client->dev, &rs5c372_rtc_ops, THIS_MODULE);
> @@ -241,18 +631,12 @@ static int rs5c372_probe(struct i2c_adap
> goto exit_detach;
> }
>
> - err = device_create_file(&client->dev, &dev_attr_trim);
> + err = rs5c_sysfs_register(&client->dev);
> if (err)
> goto exit_devreg;
> - err = device_create_file(&client->dev, &dev_attr_osc);
> - if (err)
> - goto exit_trim;
>
> return 0;
>
> -exit_trim:
> - device_remove_file(&client->dev, &dev_attr_trim);
> -
> exit_devreg:
> rtc_device_unregister(rs5c372->rtc);
>
> @@ -266,6 +650,11 @@ exit:
> return err;
> }
>
> +static int rs5c372_attach(struct i2c_adapter *adapter)
> +{
> + return i2c_probe(adapter, &addr_data, rs5c372_probe);
> +}
> +
> static int rs5c372_detach(struct i2c_client *client)
> {
> int err;
> @@ -274,6 +663,8 @@ static int rs5c372_detach(struct i2c_cli
> if (rs5c372->rtc)
> rtc_device_unregister(rs5c372->rtc);
>
> + /* REVISIT properly destroy the sysfs files ... */
> +
> if ((err = i2c_detach_client(client)))
> return err;
>
> @@ -281,6 +672,14 @@ static int rs5c372_detach(struct i2c_cli
> return 0;
> }
>
> +static struct i2c_driver rs5c372_driver = {
> + .driver = {
> + .name = "rtc-rs5c372",
> + },
> + .attach_adapter = &rs5c372_attach,
> + .detach_client = &rs5c372_detach,
> +};
> +
> static __init int rs5c372_init(void)
> {
> return i2c_add_driver(&rs5c372_driver);
>

2007-01-04 06:28:44

by David Brownell

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

On Wednesday 03 January 2007 2:51 pm, Voipio Riku wrote:
>
> > Yes, the patch you sent (switching to "method 3" to work around the
> > evident bug in the i2c-ixp3xx driver) works on the platform I was
> > using too (after unrelated tweaks).
>
> > Here's an updated patch, using "method 3". If it still behaves
> > for you, it'd seem ready to merge...
>
> Works fine, thanks! Unfortunately the i2c-ixp3xx issue has not advanced in
> the meantime, so we still need the third method.

Right. Thanks for confirming this! Alessandro?

Here's a version with corrected patch comments. (Against 2.6.20-rc3 now,
but I didn't change $SUBJECT).

- Dave

=========== CUT HERE
Update the rtc-rs5c372 driver:

Bugfixes:
- Handle RTCs which are configured to use 12-hour mode.
- Never report bogus/un-initialized times.
- Displaying "raw trim" requires not masking it first!
- Fix the sysfs and procfs display of crystal and trim data.

Features:
- Handle other RTCs in this family, notably rv5c386/rv5c387.
- Declare the other registers.
- Provide alarm get/set functionality.
- Handle AIE and UIE; but no IRQ handling yet.

Cleanup:
- Shrink object by not including needless sysfs or procfs support
- We don't need no steenkin' forward declarations. (Except one.)

Until the I2C framework merges "new style" driver support, matching
the driver model better, using rv5c chips or alarm IRQs requires a
separate board-specific patch. (And an IRQ handler, handing off labor
through a work_struct...)

This uses the "method 3" register reads, but notes that it's done
to work around an evident i2c adapter driver bug.

Signed-off-by: David Brownell <[email protected]>

Index: g26/drivers/rtc/rtc-rs5c372.c
===================================================================
--- g26.orig/drivers/rtc/rtc-rs5c372.c 2006-12-27 17:19:55.000000000 -0800
+++ g26/drivers/rtc/rtc-rs5c372.c 2007-01-02 18:44:35.000000000 -0800
@@ -1,5 +1,5 @@
/*
- * An I2C driver for the Ricoh RS5C372 RTC
+ * An I2C driver for Ricoh RS5C372 and RV5C38[67] RTCs
*
* Copyright (C) 2005 Pavel Mironchik <[email protected]>
* Copyright (C) 2006 Tower Technologies
@@ -13,7 +13,7 @@
#include <linux/rtc.h>
#include <linux/bcd.h>

-#define DRV_VERSION "0.3"
+#define DRV_VERSION "0.4"

/* Addresses to scan */
static unsigned short normal_i2c[] = { /* 0x32,*/ I2C_CLIENT_END };
@@ -21,6 +21,13 @@ static unsigned short normal_i2c[] = { /
/* Insmod parameters */
I2C_CLIENT_INSMOD;

+
+/*
+ * Ricoh has a family of I2C based RTCs, which differ only slightly from
+ * each other. Differences center on pinout (e.g. how many interrupts,
+ * output clock, etc) and how the control registers are used. The '372
+ * is significant only because that's the one this driver first supported.
+ */
#define RS5C372_REG_SECS 0
#define RS5C372_REG_MINS 1
#define RS5C372_REG_HOURS 2
@@ -29,59 +36,142 @@ I2C_CLIENT_INSMOD;
#define RS5C372_REG_MONTH 5
#define RS5C372_REG_YEAR 6
#define RS5C372_REG_TRIM 7
+# define RS5C372_TRIM_XSL 0x80
+# define RS5C372_TRIM_MASK 0x7F

-#define RS5C372_TRIM_XSL 0x80
-#define RS5C372_TRIM_MASK 0x7F
-
-#define RS5C372_REG_BASE 0
-
-static int rs5c372_attach(struct i2c_adapter *adapter);
-static int rs5c372_detach(struct i2c_client *client);
-static int rs5c372_probe(struct i2c_adapter *adapter, int address, int kind);
+#define RS5C_REG_ALARM_A_MIN 8 /* or ALARM_W */
+#define RS5C_REG_ALARM_A_HOURS 9
+#define RS5C_REG_ALARM_A_WDAY 10
+
+#define RS5C_REG_ALARM_B_MIN 11 /* or ALARM_D */
+#define RS5C_REG_ALARM_B_HOURS 12
+#define RS5C_REG_ALARM_B_WDAY 13 /* (ALARM_B only) */
+
+#define RS5C_REG_CTRL1 14
+# define RS5C_CTRL1_AALE (1 << 7) /* or WALE */
+# define RS5C_CTRL1_BALE (1 << 6) /* or DALE */
+# define RV5C387_CTRL1_24 (1 << 5)
+# define RS5C372A_CTRL1_SL1 (1 << 5)
+# define RS5C_CTRL1_CT_MASK (7 << 0)
+# 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 RS5C372_CTRL2_24 (1 << 5)
+# define RS5C_CTRL2_XSTP (1 << 4)
+# define RS5C_CTRL2_CTFG (1 << 2)
+# define RS5C_CTRL2_AAFG (1 << 1) /* or WAFG */
+# define RS5C_CTRL2_BAFG (1 << 0) /* or DAFG */
+
+
+/* to read (style 1) or write registers starting at R */
+#define RS5C_ADDR(R) (((R) << 4) | 0)
+
+
+enum rtc_type {
+ rtc_undef = 0,
+ rtc_rs5c372a,
+ rtc_rs5c372b,
+ rtc_rv5c386,
+ rtc_rv5c387a,
+};

+/* REVISIT: this assumes that:
+ * - we're in the 21st century, so it's safe to ignore the century
+ * bit for rv5c38[67] (REG_MONTH bit 7);
+ * - we should use ALARM_A not ALARM_B (may be wrong on some boards)
+ */
struct rs5c372 {
- u8 reg_addr;
- u8 regs[17];
- struct i2c_msg msg[1];
- struct i2c_client client;
- struct rtc_device *rtc;
-};
+ struct i2c_client *client;
+ struct rtc_device *rtc;
+ enum rtc_type type;
+ unsigned time24:1;
+ unsigned has_irq:1;
+ char buf[17];
+ char *regs;

-static struct i2c_driver rs5c372_driver = {
- .driver = {
- .name = "rs5c372",
- },
- .attach_adapter = &rs5c372_attach,
- .detach_client = &rs5c372_detach,
+ /* on conversion to a "new style" i2c driver, this vanishes */
+ struct i2c_client dev;
};

-static int rs5c372_get_datetime(struct i2c_client *client, struct rtc_time *tm)
+static int rs5c_get_regs(struct rs5c372 *rs5c)
{
-
- struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
- u8 *buf = &(rs5c372->regs[1]);
-
- /* this implements the 3rd reading method, according
- * to the datasheet. rs5c372 defaults to internal
- * address 0xF, so 0x0 is in regs[1]
+ struct i2c_client *client = rs5c->client;
+ struct i2c_msg msgs[] = {
+ { client->addr, I2C_M_RD, sizeof rs5c->buf, rs5c->buf },
+ };
+
+ /* This implements the third reading method from the datasheet, using
+ * an internal address that's reset after each transaction (by STOP)
+ * to 0x0f ... so we read extra registers, and skip the first one.
+ *
+ * The first method doesn't work with the iop3xx adapter driver, on at
+ * least 80219 chips; this works around that bug.
*/
-
- if ((i2c_transfer(client->adapter, rs5c372->msg, 1)) != 1) {
- dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
+ if ((i2c_transfer(client->adapter, msgs, 1)) != 1) {
+ pr_debug("%s: can't read registers\n", rs5c->rtc->name);
return -EIO;
}

- tm->tm_sec = BCD2BIN(buf[RS5C372_REG_SECS] & 0x7f);
- tm->tm_min = BCD2BIN(buf[RS5C372_REG_MINS] & 0x7f);
- tm->tm_hour = BCD2BIN(buf[RS5C372_REG_HOURS] & 0x3f);
- tm->tm_wday = BCD2BIN(buf[RS5C372_REG_WDAY] & 0x07);
- tm->tm_mday = BCD2BIN(buf[RS5C372_REG_DAY] & 0x3f);
+ dev_dbg(&client->dev,
+ "%02x %02x %02x (%02x) %02x %02x %02x (%02x), "
+ "%02x %02x %02x, %02x %02x %02x; %02x %02x\n",
+ rs5c->regs[0], rs5c->regs[1], rs5c->regs[2], rs5c->regs[3],
+ rs5c->regs[4], rs5c->regs[5], rs5c->regs[6], rs5c->regs[7],
+ rs5c->regs[8], rs5c->regs[9], rs5c->regs[10], rs5c->regs[11],
+ rs5c->regs[12], rs5c->regs[13], rs5c->regs[14], rs5c->regs[15]);
+
+ return 0;
+}
+
+static unsigned rs5c_reg2hr(struct rs5c372 *rs5c, unsigned reg)
+{
+ unsigned hour;
+
+ if (rs5c->time24)
+ return BCD2BIN(reg & 0x3f);
+
+ hour = BCD2BIN(reg & 0x1f);
+ if (hour == 12)
+ hour = 0;
+ if (reg & 0x20)
+ hour += 12;
+ return hour;
+}
+
+static unsigned rs5c_hr2reg(struct rs5c372 *rs5c, unsigned hour)
+{
+ if (rs5c->time24)
+ return BIN2BCD(hour);
+
+ if (hour > 12)
+ return 0x20 | BIN2BCD(hour - 12);
+ if (hour == 12)
+ return 0x20 | BIN2BCD(12);
+ if (hour == 0)
+ return BIN2BCD(12);
+ return BIN2BCD(hour);
+}
+
+static int rs5c372_get_datetime(struct i2c_client *client, struct rtc_time *tm)
+{
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ int status = rs5c_get_regs(rs5c);
+
+ if (status < 0)
+ return status;
+
+ 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]);
+
+ tm->tm_wday = BCD2BIN(rs5c->regs[RS5C372_REG_WDAY] & 0x07);
+ tm->tm_mday = BCD2BIN(rs5c->regs[RS5C372_REG_DAY] & 0x3f);

/* tm->tm_mon is zero-based */
- tm->tm_mon = BCD2BIN(buf[RS5C372_REG_MONTH] & 0x1f) - 1;
+ tm->tm_mon = BCD2BIN(rs5c->regs[RS5C372_REG_MONTH] & 0x1f) - 1;

/* year is 1900 + tm->tm_year */
- tm->tm_year = BCD2BIN(buf[RS5C372_REG_YEAR]) + 100;
+ tm->tm_year = BCD2BIN(rs5c->regs[RS5C372_REG_YEAR]) + 100;

dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
"mday=%d, mon=%d, year=%d, wday=%d\n",
@@ -89,22 +179,25 @@ static int rs5c372_get_datetime(struct i
tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

- return 0;
+ /* rtc might need initialization */
+ return rtc_valid_tm(tm);
}

static int rs5c372_set_datetime(struct i2c_client *client, struct rtc_time *tm)
{
- unsigned char buf[8] = { RS5C372_REG_BASE };
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ unsigned char buf[8];

- dev_dbg(&client->dev,
- "%s: secs=%d, mins=%d, hours=%d "
+ dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d "
"mday=%d, mon=%d, year=%d, wday=%d\n",
- __FUNCTION__, tm->tm_sec, tm->tm_min, tm->tm_hour,
+ __FUNCTION__,
+ tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

+ buf[0] = RS5C_ADDR(RS5C372_REG_SECS);
buf[1] = BIN2BCD(tm->tm_sec);
buf[2] = BIN2BCD(tm->tm_min);
- buf[3] = BIN2BCD(tm->tm_hour);
+ buf[3] = rs5c_hr2reg(rs5c, tm->tm_hour);
buf[4] = BIN2BCD(tm->tm_wday);
buf[5] = BIN2BCD(tm->tm_mday);
buf[6] = BIN2BCD(tm->tm_mon + 1);
@@ -118,21 +211,43 @@ static int rs5c372_set_datetime(struct i
return 0;
}

+#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
+#define NEED_TRIM
+#endif
+
+#if defined(CONFIG_RTC_INTF_SYSFS) || defined(CONFIG_RTC_INTF_SYSFS_MODULE)
+#define NEED_TRIM
+#endif
+
+#ifdef NEED_TRIM
static int rs5c372_get_trim(struct i2c_client *client, int *osc, int *trim)
{
struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
- u8 tmp = rs5c372->regs[RS5C372_REG_TRIM + 1];
+ u8 tmp = rs5c372->regs[RS5C372_REG_TRIM];

if (osc)
*osc = (tmp & RS5C372_TRIM_XSL) ? 32000 : 32768;

if (trim) {
- *trim = tmp & RS5C372_TRIM_MASK;
- dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, *trim);
+ dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, tmp);
+ tmp &= RS5C372_TRIM_MASK;
+ if (tmp & 0x3e) {
+ int t = tmp & 0x3f;
+
+ if (tmp & 0x40)
+ t = (~t | (s8)0xc0) + 1;
+ else
+ t = t - 1;
+
+ tmp = t * 2;
+ } else
+ tmp = 0;
+ *trim = tmp;
}

return 0;
}
+#endif

static int rs5c372_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
@@ -144,25 +259,190 @@ static int rs5c372_rtc_set_time(struct d
return rs5c372_set_datetime(to_i2c_client(dev), tm);
}

+#if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE)
+
+static int
+rs5c_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ unsigned char buf[2];
+ int status;
+
+ buf[1] = rs5c->regs[RS5C_REG_CTRL1];
+ switch (cmd) {
+ case RTC_UIE_OFF:
+ case RTC_UIE_ON:
+ /* some 327a modes use a different IRQ pin for 1Hz irqs */
+ if (rs5c->type == rtc_rs5c372a
+ && (buf[1] & RS5C372A_CTRL1_SL1))
+ return -ENOIOCTLCMD;
+ case RTC_AIE_OFF:
+ case RTC_AIE_ON:
+ /* these irq management calls only make sense for chips
+ * which are wired up to an IRQ.
+ */
+ if (!rs5c->has_irq)
+ return -ENOIOCTLCMD;
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ status = rs5c_get_regs(rs5c);
+ if (status < 0)
+ return status;
+
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ switch (cmd) {
+ case RTC_AIE_OFF: /* alarm off */
+ buf[1] &= ~RS5C_CTRL1_AALE;
+ break;
+ case RTC_AIE_ON: /* alarm on */
+ buf[1] |= RS5C_CTRL1_AALE;
+ break;
+ case RTC_UIE_OFF: /* update off */
+ buf[1] &= ~RS5C_CTRL1_CT_MASK;
+ break;
+ case RTC_UIE_ON: /* update on */
+ buf[1] &= ~RS5C_CTRL1_CT_MASK;
+ buf[1] |= RS5C_CTRL1_CT4;
+ break;
+ }
+ if ((i2c_master_send(client, buf, 2)) != 2) {
+ printk(KERN_WARNING "%s: can't update alarm\n",
+ rs5c->rtc->name);
+ status = -EIO;
+ } else
+ rs5c->regs[RS5C_REG_CTRL1] = buf[1];
+ return status;
+}
+
+#else
+#define rs5c_rtc_ioctl NULL
+#endif
+
+
+/* NOTE: Since RTC_WKALM_{RD,SET} were originally defined for EFI,
+ * which only exposes a polled programming interface; and since
+ * these calls map directly to those EFI requests; we don't demand
+ * we have an IRQ for this chip when we go through this API.
+ *
+ * The older x86_pc derived RTC_ALM_{READ,SET} calls require irqs
+ * though, managed through RTC_AIE_{ON,OFF} requests.
+ */
+
+static int rs5c_read_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ int status;
+
+ status = rs5c_get_regs(rs5c);
+ if (status < 0)
+ return status;
+
+ /* report alarm time */
+ t->time.tm_sec = 0;
+ t->time.tm_min = BCD2BIN(rs5c->regs[RS5C_REG_ALARM_A_MIN] & 0x7f);
+ t->time.tm_hour = rs5c_reg2hr(rs5c, rs5c->regs[RS5C_REG_ALARM_A_HOURS]);
+ t->time.tm_mday = -1;
+ t->time.tm_mon = -1;
+ t->time.tm_year = -1;
+ t->time.tm_wday = -1;
+ t->time.tm_yday = -1;
+ t->time.tm_isdst = -1;
+
+ /* ... and status */
+ t->enabled = !!(rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE);
+ t->pending = !!(rs5c->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_AAFG);
+
+ return 0;
+}
+
+static int rs5c_set_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rs5c372 *rs5c = i2c_get_clientdata(client);
+ int status;
+ unsigned char buf[4];
+
+ /* only handle up to 24 hours in the future, like RTC_ALM_SET */
+ if (t->time.tm_mday != -1
+ || t->time.tm_mon != -1
+ || t->time.tm_year != -1)
+ return -EINVAL;
+
+ /* REVISIT: round up tm_sec */
+
+ /* if needed, disable irq (clears pending status) */
+ status = rs5c_get_regs(rs5c);
+ if (status < 0)
+ return status;
+ if (rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE) {
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ buf[1] = rs5c->regs[RS5C_REG_CTRL1] & ~RS5C_CTRL1_AALE;
+ if (i2c_master_send(client, buf, 2) != 2) {
+ pr_debug("%s: can't disable alarm\n", rs5c->rtc->name);
+ return -EIO;
+ }
+ rs5c->regs[RS5C_REG_CTRL1] = buf[1];
+ }
+
+ /* set alarm */
+ buf[0] = RS5C_ADDR(RS5C_REG_ALARM_A_MIN);
+ buf[1] = BIN2BCD(t->time.tm_min);
+ buf[2] = rs5c_hr2reg(rs5c, t->time.tm_hour);
+ buf[3] = 0x7f; /* any/all days */
+ if ((i2c_master_send(client, buf, 4)) != 4) {
+ pr_debug("%s: can't set alarm time\n", rs5c->rtc->name);
+ return -EIO;
+ }
+
+ /* ... and maybe enable its irq */
+ if (t->enabled) {
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ buf[1] = rs5c->regs[RS5C_REG_CTRL1] | RS5C_CTRL1_AALE;
+ if ((i2c_master_send(client, buf, 2)) != 2)
+ printk(KERN_WARNING "%s: can't enable alarm\n",
+ rs5c->rtc->name);
+ rs5c->regs[RS5C_REG_CTRL1] = buf[1];
+ }
+
+ return 0;
+}
+
+#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
+
static int rs5c372_rtc_proc(struct device *dev, struct seq_file *seq)
{
int err, osc, trim;

err = rs5c372_get_trim(to_i2c_client(dev), &osc, &trim);
if (err == 0) {
- seq_printf(seq, "%d.%03d KHz\n", osc / 1000, osc % 1000);
- seq_printf(seq, "trim\t: %d\n", trim);
+ seq_printf(seq, "crystal\t\t: %d.%03d KHz\n",
+ osc / 1000, osc % 1000);
+ seq_printf(seq, "trim\t\t: %d\n", trim);
}

return 0;
}

+#else
+#define rs5c372_rtc_proc NULL
+#endif
+
static const struct rtc_class_ops rs5c372_rtc_ops = {
.proc = rs5c372_rtc_proc,
+ .ioctl = rs5c_rtc_ioctl,
.read_time = rs5c372_rtc_read_time,
.set_time = rs5c372_rtc_set_time,
+ .read_alarm = rs5c_read_alarm,
+ .set_alarm = rs5c_set_alarm,
};

+#if defined(CONFIG_RTC_INTF_SYSFS) || defined(CONFIG_RTC_INTF_SYSFS_MODULE)
+
static ssize_t rs5c372_sysfs_show_trim(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -172,7 +452,7 @@ static ssize_t rs5c372_sysfs_show_trim(s
if (err)
return err;

- return sprintf(buf, "0x%2x\n", trim);
+ return sprintf(buf, "%d\n", trim);
}
static DEVICE_ATTR(trim, S_IRUGO, rs5c372_sysfs_show_trim, NULL);

@@ -189,16 +469,35 @@ static ssize_t rs5c372_sysfs_show_osc(st
}
static DEVICE_ATTR(osc, S_IRUGO, rs5c372_sysfs_show_osc, NULL);

-static int rs5c372_attach(struct i2c_adapter *adapter)
+static int rs5c_sysfs_register(struct device *dev)
{
- return i2c_probe(adapter, &addr_data, rs5c372_probe);
+ int err;
+
+ err = device_create_file(dev, &dev_attr_trim);
+ if (err)
+ return err;
+ err = device_create_file(dev, &dev_attr_osc);
+ if (err)
+ device_remove_file(dev, &dev_attr_trim);
+
+ return err;
+}
+
+#else
+static int rs5c_sysfs_register(struct device *dev)
+{
+ return 0;
}
+#endif /* SYSFS */
+
+static struct i2c_driver rs5c372_driver;

static int rs5c372_probe(struct i2c_adapter *adapter, int address, int kind)
{
int err = 0;
struct i2c_client *client;
struct rs5c372 *rs5c372;
+ struct rtc_time tm;

dev_dbg(adapter->class_dev.dev, "%s\n", __FUNCTION__);

@@ -211,7 +510,15 @@ static int rs5c372_probe(struct i2c_adap
err = -ENOMEM;
goto exit;
}
- client = &rs5c372->client;
+
+ /* we read registers 0x0f then 0x00-0x0f; skip the first one */
+ rs5c372->regs=&rs5c372->buf[1];
+
+ /* On conversion to a "new style" i2c driver, we'll be handed
+ * the i2c_client (we won't create it)
+ */
+ client = &rs5c372->dev;
+ rs5c372->client = client;

/* I2C client */
client->addr = address;
@@ -222,16 +529,99 @@ static int rs5c372_probe(struct i2c_adap

i2c_set_clientdata(client, rs5c372);

- rs5c372->msg[0].addr = address;
- rs5c372->msg[0].flags = I2C_M_RD;
- rs5c372->msg[0].len = sizeof(rs5c372->regs);
- rs5c372->msg[0].buf = rs5c372->regs;
-
/* Inform the i2c layer */
if ((err = i2c_attach_client(client)))
goto exit_kfree;

- dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
+ err = rs5c_get_regs(rs5c372);
+ if (err < 0)
+ goto exit_detach;
+
+ /* For "new style" drivers, irq is in i2c_client and chip type
+ * info comes from i2c_client.dev.platform_data. Meanwhile:
+ *
+ * STICK BOARD-SPECIFIC SETUP CODE RIGHT HERE
+ */
+ if (rs5c372->type == rtc_undef) {
+ rs5c372->type = rtc_rs5c372b;
+ dev_warn(&client->dev, "assuming rs5c372b\n");
+ }
+
+ /* clock may be set for am/pm or 24 hr time */
+ switch (rs5c372->type) {
+ case rtc_rs5c372a:
+ case rtc_rs5c372b:
+ /* 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;
+ break;
+ case rtc_rv5c386:
+ case rtc_rv5c387a:
+ if (rs5c372->regs[RS5C_REG_CTRL1] & RV5C387_CTRL1_24)
+ rs5c372->time24 = 1;
+ /* alarm uses ALARM_W; and nINTRB for alarm and periodic
+ * irq, on both 386 and 387
+ */
+ break;
+ default:
+ dev_err(&client->dev, "unknown RTC type\n");
+ goto exit_detach;
+ }
+
+ /* if the oscillator lost power and no other software (like
+ * the bootloader) set it up, do it here.
+ */
+ if (rs5c372->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_XSTP) {
+ unsigned char buf[3];
+
+ rs5c372->regs[RS5C_REG_CTRL2] &= ~RS5C_CTRL2_XSTP;
+
+ buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
+ buf[1] = rs5c372->regs[RS5C_REG_CTRL1];
+ buf[2] = rs5c372->regs[RS5C_REG_CTRL2];
+
+ /* use 24hr mode */
+ switch (rs5c372->type) {
+ case rtc_rs5c372a:
+ case rtc_rs5c372b:
+ buf[2] |= RS5C372_CTRL2_24;
+ rs5c372->time24 = 1;
+ break;
+ case rtc_rv5c386:
+ case rtc_rv5c387a:
+ buf[1] |= RV5C387_CTRL1_24;
+ rs5c372->time24 = 1;
+ break;
+ default:
+ /* impossible */
+ break;
+ }
+
+ if ((i2c_master_send(client, buf, 3)) != 3) {
+ dev_err(&client->dev, "setup error\n");
+ goto exit_detach;
+ }
+ rs5c372->regs[RS5C_REG_CTRL1] = buf[1];
+ rs5c372->regs[RS5C_REG_CTRL2] = buf[2];
+ }
+
+ if (rs5c372_get_datetime(client, &tm) < 0)
+ dev_warn(&client->dev, "clock needs to be set\n");
+
+ dev_info(&client->dev, "%s found, %s, driver version " DRV_VERSION "\n",
+ ({ char *s; switch (rs5c372->type) {
+ case rtc_rs5c372a: s = "rs5c372a"; break;
+ case rtc_rs5c372b: s = "rs5c372b"; break;
+ case rtc_rv5c386: s = "rv5c386"; break;
+ case rtc_rv5c387a: s = "rv5c387a"; break;
+ default: s = "chip"; break;
+ }; s;}),
+ rs5c372->time24 ? "24hr" : "am/pm"
+ );
+
+ /* FIXME when client->irq exists, use it to register alarm irq */

rs5c372->rtc = rtc_device_register(rs5c372_driver.driver.name,
&client->dev, &rs5c372_rtc_ops, THIS_MODULE);
@@ -241,18 +631,12 @@ static int rs5c372_probe(struct i2c_adap
goto exit_detach;
}

- err = device_create_file(&client->dev, &dev_attr_trim);
+ err = rs5c_sysfs_register(&client->dev);
if (err)
goto exit_devreg;
- err = device_create_file(&client->dev, &dev_attr_osc);
- if (err)
- goto exit_trim;

return 0;

-exit_trim:
- device_remove_file(&client->dev, &dev_attr_trim);
-
exit_devreg:
rtc_device_unregister(rs5c372->rtc);

@@ -266,6 +650,11 @@ exit:
return err;
}

+static int rs5c372_attach(struct i2c_adapter *adapter)
+{
+ return i2c_probe(adapter, &addr_data, rs5c372_probe);
+}
+
static int rs5c372_detach(struct i2c_client *client)
{
int err;
@@ -274,6 +663,8 @@ static int rs5c372_detach(struct i2c_cli
if (rs5c372->rtc)
rtc_device_unregister(rs5c372->rtc);

+ /* REVISIT properly destroy the sysfs files ... */
+
if ((err = i2c_detach_client(client)))
return err;

@@ -281,6 +672,14 @@ static int rs5c372_detach(struct i2c_cli
return 0;
}

+static struct i2c_driver rs5c372_driver = {
+ .driver = {
+ .name = "rtc-rs5c372",
+ },
+ .attach_adapter = &rs5c372_attach,
+ .detach_client = &rs5c372_detach,
+};
+
static __init int rs5c372_init(void)
{
return i2c_add_driver(&rs5c372_driver);

2007-01-04 12:05:25

by Alessandro Zummo

[permalink] [raw]
Subject: Re: [patch 2.6.19-git] rts-rs5c372 updates: more chips, alarm, 12hr mode, etc

On Wed, 3 Jan 2007 20:23:35 -0800
David Brownell <[email protected]> wrote:

> >
> > Works fine, thanks! Unfortunately the i2c-ixp3xx issue has not advanced in
> > the meantime, so we still need the third method.
>
> Right. Thanks for confirming this! Alessandro?

Given that we can not test on more boards, I'd keep
the most compatible method.

Acked-by: Alessandro Zummo <[email protected]>

--

Best regards,

Alessandro Zummo,
Tower Technologies - Torino, Italy

http://www.towertech.it