Jumpnow Technologies

home code consulting contact

Duovero Real-time Clock

16 Aug 2015

The Gumxstix Duovero COMs have a power management unit (PMIC) that supports a battery backed real-time clock. The Parlor expansion boards come with a battery holder, but the default Gumstix 3.6 kernel does not support:

  1. Communication with the RTC
  2. Trickle charging the RTC battery from the PMIC

Here are some notes to get it working.

Enabling communication with the RTC

First off, you should make sure you have /sbin/hwclock installed. It comes from the busybox package for my systems.

You can check whether or not the hwclock communication is working this way

root@duovero:~# hwclock -r
Sun Feb 23 15:33:36 2014  0.000000 seconds

This is from a fixed system with the patches referenced below. A broken system will return zeros for the date and time.

The following summary comes from a variety of web postings mainly from upstream android and ti-omap kernel developers.

It’s difficult to get information about the Duovero PMIC, the TWL6030. The full programming reference manual is not publicly available the way it is for the TWL4030.

This manual has some information about the RTC. The REAL-TIME CLOCK section starting on page 32 has some information about the actual time registers.

The important information for this problem is under the CONTROL INTERFACE (I2C, MSECURE, INTERRUPTS) section and in particular the Secure Registers subsection starting on page 85.

The MSECURE control signal determines whether the RTC can be set or cleared.

Gumstix doesn’t provide a schematic of the signals between the OMAP4 and the TWL6030, but assuming they copied the pandaboard design (or that they both copied some other reference design), pin N2 MSECURE of the TWL6030 goes directly to pin AD2, the FREF_CLK0_OUT pad of the OMAP4.

Mode 3 of this pad is SYS_DRM_MSECURE which is what is required.

The default u-boot muxes this pad in mode 7, safe mode.

{PAD0_FREF_CLK0_OUT, (M7)},          /* safe mode */

The change needed to fix the pad muxing can be done in u-boot or in the kernel.

This backported patch to the Gumstix 3.6 kernel does the job.

Enabling trickle charging the RTC battery from the PMIC

I found this TWL6030 Register Manual on a non-TI site.

From section 2.9, the BBSPOR_CFG register is used to enable the RTC backup battery trickle charge. By default the BB_CHG_EN bit is off.

I’m using Panasonic ML-621S/ZTN batteries in the Duoveros, so I wanted the trickle charge cutoff to be at 3.15V. These batteries accept up to 3.2V. Charging info is here.

Given that, this kernel patch enables trickle charging the Duovero RTC backup battery.

diff --git a/drivers/rtc/rtc-twl.c b/drivers/rtc/rtc-twl.c
index 9277d94..2a9d467 100644
--- a/drivers/rtc/rtc-twl.c
+++ b/drivers/rtc/rtc-twl.c
@@ -163,6 +163,41 @@ static int twl_rtc_write_u8(u8 data, u8 reg)
        return ret;

+#define REG_BBSPOR_CFG 0xE6
+#define VRTC_EN_SLP_STS        (1 << 6)
+#define VRTC_EN_OFF_STS        (1 << 5)
+#define VRTC_PWEN      (1 << 4)
+#define BB_CHG_EN      (1 << 3)
+#define BB_SEL_1       (1 << 2)
+#define BB_SEL_0       (1 << 1)
+static int enable_rtc_battery_charging(void)
+       int ret;
+       u8 data;
+       ret = twl_i2c_read_u8(TWL6030_MODULE_ID0, &data, REG_BBSPOR_CFG);
+       if (ret < 0) {
+               pr_err("twl_rtc: read bbspor_cfg failed: %d\n", ret);
+               return ret;
+       }
+       /*
+        * Charge battery to 3.15v
+        * TWL6030 Register Map, Table 224, BBSPOR_CFG Register
+        */
+       data &= ~BB_SEL_0;
+       data |= (BB_SEL_1 | BB_CHG_EN);
+       ret = twl_i2c_write_u8(TWL6030_MODULE_ID0, data, REG_BBSPOR_CFG);
+       if (ret < 0)
+               pr_err("twl_rtc: write bbspor_cfg failed: %d\n", ret);
+       else
+               pr_info("twl_rtc: enabled rtc battery charging\n");
+       return ret;
  * Cache the value for timer/alarm interrupts register; this is
  * only changed by callers holding rtc ops lock (or resume).
@@ -505,6 +540,10 @@ static int __devinit twl_rtc_probe(struct platform_device *pdev)
        if (ret < 0)
                goto out1;

+       ret = enable_rtc_battery_charging();
+       if (ret < 0)
+               dev_err(&pdev->dev, "Failed to enable rtc battery charging)\n");
        rtc = rtc_device_register(pdev->name,
                                  &pdev->dev, &twl_rtc_ops, THIS_MODULE);
        if (IS_ERR(rtc)) {

It’s loosely based on a similar patch to the TWL4030 for the Overo kernels.

So where does that TWL6030_MODULE_ID0 constant come from and why is it needed?

The twl_i2c_read_u8() and twl_i2c_write_u8() functions, in order to be more generic, use a twl_map table to find the index into a twl_module table to then get the correct device address to use on the I2C bus.

The BBSPOR_CFG register is at I2C physical address 0x48 and register address 0xE6. You can get this from section 2.9.5 of the TWL6030 Register Manual.

TWL6030_MODULE_IDO is defined in include/linux/i2c/twl.h

#define TWL6030_MODULE_ID0      0x0D
#define TWL6030_MODULE_ID1      0x0E
#define TWL6030_MODULE_ID2      0x0F

Which in drivers/mfd/twl-core.c from the twl6030_map[] table maps to

twl6030_map[0x0D] = { SUB_CHIP_ID0, TWL6030_BASEADD_ZERO }

This results in

The sid (slave id) is an index into the twl_modules[] table which is a list of twl_client structures where the actual addresses of I2C slave devices are kept.

The twl_client structures get assigned in the twl-core probe() function using data from the twl4030_platform_data which can eventually be traced back to arch/arm/mach-omap2/twl-common.c.

static struct i2c_board_info __initdata omap4_i2c1_board_info[] = {
        .addr           = 0x48,
        .flags          = I2C_CLIENT_WAKE,
        I2C_BOARD_INFO("twl6040", 0x4b),

So twl_module[0].address = 0x48 which is what we want.

Easy enough.

The initial patch I had for this was wrong. I used TWL6030_MODULE_ID1 instead of TWL6030_MODULE_ID0. Thanks to Alex Ray for catching this.

Init scripts

You need some additional userland software to ensure that

With Yocto built systems the busybox-hwclock package adds an init.d script to do this.

The script is called /etc/init.d/hwclock.sh.