IOT RTC rv-3028 driver is written for 5.13 and Torizon is 5.5

We have a low power RTC (a RV-3028) and supercap on my new board.

RV-3028-C7 - Micro Crystal

Following this approach, which is absolutely fine.

https://developer.toradex.com/knowledge-base/building-external-kernel-modules-with-torizon

To provision it we have a driver to add to Torizon

rv3028 (1).zip (1.4 MB)

The issue is that this driver has features that appear in a later version of the kernel

Specifically, the identifier RTC_FEATURE_ALARM has been added in kernel 5.13. That
means that the source code we have is too new for the current Torizon
kernel.

make: Entering directory ‘/workdir/rv3028-mod’
make -C /storage/linux M=/workdir/rv3028-mod
make[1]: Entering directory ‘/storage/linux’
AR /workdir/rv3028-mod/built-in.a
CC [M] /workdir/rv3028-mod/rtc-rv3028.o
/workdir/rv3028-mod/rtc-rv3028.c: In function ‘rv3028_ioctl’:
/workdir/rv3028-mod/rtc-rv3028.c:526:42: error: ‘RTC_VL_DATA_INVALID’
undeclared (first use in this function)
526 | status = status & RV3028_STATUS_PORF ? RTC_VL_DATA_INVALID : 0;
| ^~~~~~~~~~~~~~~~~~~
/workdir/rv3028-mod/rtc-rv3028.c:526:42: note: each undeclared
identifier is reported only once for each function it appears in
/workdir/rv3028-mod/rtc-rv3028.c: In function ‘rv3028_probe’:
/workdir/rv3028-mod/rtc-rv3028.c:844:13: error: ‘RTC_FEATURE_ALARM’
undeclared (first use in this function)
844 | clear_bit(RTC_FEATURE_ALARM, rv3028->rtc->features);
| ^~~~~~~~~~~~~~~~~
/workdir/rv3028-mod/rtc-rv3028.c:844:43: error: ‘struct rtc_device’
has no member named ‘features’
844 | clear_bit(RTC_FEATURE_ALARM, rv3028->rtc->features);
| ^~
/workdir/rv3028-mod/rtc-rv3028.c:884:8: error: implicit declaration of
function ‘devm_rtc_register_device’; did you mean
‘rtc_register_device’? [-Werror=implicit-function-declaration]
884 | ret = devm_rtc_register_device(rv3028->rtc);
| ^~~~~~~~~~~~~~~~~~~~~~~~
| rtc_register_device
/workdir/rv3028-mod/rtc-rv3028.c:889:2: error: implicit declaration of
function ‘devm_rtc_nvmem_register’; did you mean
‘devm_nvmem_register’? [-Werror=implicit-function-declaration]
889 | devm_rtc_nvmem_register(rv3028->rtc, &nvmem_cfg);
| ^~~~~~~~~~~~~~~~~~~~~~~
| devm_nvmem_register
cc1: some warnings being treated as errors
make[2]: *** [scripts/Makefile.build:262:
/workdir/rv3028-mod/rtc-rv3028.o] Error 1
make[1]: *** [Makefile:1734: /workdir/rv3028-mod] Error 2
make[1]: Leaving directory ‘/storage/linux’
make: *** [Makefile:6: all] Error 2
make: Leaving directory ‘/workdir/rv3028-mod’
Traceback (most recent call last):
File “/builder/tcbuilder/backend/kernel.py”, line 64, in build_module
subprocess.run(f"""PATH=$PATH:{toolchain} KERNEL_SRC={linux_src}
KDIR={linux_src}
File “/usr/lib/python3.9/subprocess.py”, line 528, in run
raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command
‘PATH=$PATH:/storage/toolchain/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin
KERNEL_SRC=/storage/linux KDIR=/storage/linux
CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=arm64 make
-C /workdir/rv3028-mod’ returned non-zero exit status 2.

Error: Error building kernel module(s)!

Can you recommend what we do now? We don’t actually use the “wake up” processor event feature, so do we try to re-write the driver; dump the very smart RTC chip and the design we have; or is there a possibility of a newer Torizon on the horizon ?

Thanks

Fat Linux.

Greetings @FatLinux,

Sometime this year we plan to release a 6.0 of our BSP and TorizonCore software. Tentatively speaking the plan is that in this 6.0 the kernel version will then be 5.15.

With that said, the most straightforward option would be to just wait. However, this depends on your use-case and requirements of course. Also you’ll need some kind of work-around in the short-term.

Best Regards,
Jeremias

Hi,

Thank you for getting back to me.

I wish I could wait until next year, I have been taking a roasting for a few months now for being late (other stuff!).

So the device tree is like this:

torizon@Thinkcentrei9:~/torizoncore/custom_overlays$ cat re*
// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
/*

  • Copyright 2020-2021 Toradex
    */

// Enable the parallel RGB interface on Colibri iMX8X

/dts-v1/;
/plugin/;

/ {
compatible = “toradex,colibri-imx8x”;
};

&i2c1 {
status = “okay”;

    rtc_i2c: rv3028@52 {
            compatible = "microcrystal,rv3028";
            reg = <0x52>;
            status = "okay";
            trickle-resistor-ohms = <11000>;
    };

};

So the trickle resistor is on, and I measured the supercap with a probe and I get 3.1V which is nice.

I can then boot up the Imx8 module and

root@colibri-imx8x-07021201:~# dmesg | grep rtc2
[ 7.603848] rtc-rv3028 17-0052: registered as rtc2

I can set the clock

root@colibri-imx8x-07021201:~# hwclock -w -f /dev/rtc2

I check it …

root@colibri-imx8x-07021201:~# hwclock -r -f /dev/rtc2 --verbose
hwclock from util-linux 2.35.1
System Time: 1649452925.107593
Using the rtc interface to the clock.
Last drift adjustment done at 1649452817 seconds after 1969
Last calibration done at 1649452817 seconds after 1969
Hardware clock is on UTC time
Assuming hardware clock is kept in UTC time.
Waiting for clock tick…
…got clock tick
Time read from Hardware Clock: 2022/04/08 21:22:06
Hw clock time : 2022/04/08 21:22:06 = 1649452926 seconds since 1969
Time since last adjustment is 109 seconds
Calculated Hardware Clock drift is 0.000000 seconds
2022-04-08 21:22:05.103527+00:00

root@colibri-imx8x-07021201:~# shutdown now

And then pull the socket, I then put a meter on the supercap and it reads 3.2V.

Boot up again.

colibri-imx8x-07021201:~$ sudo su -
Password:
root@colibri-imx8x-07021201:~# hwclock -r -f /dev/rtc2 --verbose
hwclock from util-linux 2.35.1
System Time: 1649453129.848951
Using the rtc interface to the clock.
Last drift adjustment done at 1649452817 seconds after 1969
Last calibration done at 1649452817 seconds after 1969
Hardware clock is on UTC time
Assuming hardware clock is kept in UTC time.
Waiting for clock tick…
ioctl(3, RTC_UIE_ON, 0): Invalid argument
Waiting in loop for time from /dev/rtc2 to change
hwclock: ioctl(RTC_RD_TIME) to /dev/rtc2 to read the time failed: Invalid argument
…synchronization failed

root@colibri-imx8x-07021201:~# dmesg | grep rtc2
[ 6.947201] rtc-rv3028 17-0052: registered as rtc2

So the rtc backup is on, the trickle charger is on, but the power switch over is not working I think and the time is being lost. I will pursue Micro Crystal Switzerland for some help next week, maybe we get lucky and if so I will post the solution on here!

Meanwhile, any ideas are gladly appreciated.

Regards

Fat Linux

Hi,

What we need is a kernel patch for Torizon if you want to use the RV-3028 RTC module (which to be fair is a new product, but 45nA consumption and low ppm).

The kernel is rtc-rv3028.c

When supplied by Micro Crystal Switzerland the module has the battery trickle charge switched off. This is easily rectified in the device tree with a single line: you say the resistance to put in place to specify the trickle and it works.

However, there is a second part to this. The RV-3028 is shipped with the automatic VBACKUP switch over set to “off”. So the battery or cap backup charges but won’t power the module.

The onboard EEPROM has a register 0x37 which as 8 bits, bits 2 and 3 are both 0 when the module is supplied, but the smooth voltage threshold switchover option needs both these to be 1.

So patch for rtc-rv3028.c I think will be needed to write a pair of 1’s to the register 0x37, probably at the point where and when the trickle charge bits are being set. Finger crossed.

So are you now just attempting to patch or backport the driver to make it work for the older kernel that TorizonCore is running?

I didn’t reply to this because I made some practical evaluations (i.e. I built a rig to test that the supercap was charging as predicted, and to compare the two different switch over techniques.

I got some contradictions to the documentation and have written to the manufacturer to get some confirmations. At the moment the RTC is definitely being powered (so it keeps the time), but I haven’t finished my code to probe the registers to know which bits are enabled on x37. The docs definitely say these devices are shipped with these two bits off.

Will keep you posted.

Fat Linux.

Thank you for keeping us posted.

Hi Jeremias,
I didn’t get a reply from Micro Crystal. How far does version Torizon 5.6 get to with regard to kernel versions? We are just updating the time every time the battery is changed which is OK.

This is what we get when we boot up.

colibri-imx8x-07021198:~$ dmesg | grep “rtc”
[ 1.783486] imx-sc-rtc scu:rtc: registered as rtc1
[ 2.025443] rtc-ds1307: probe of 17-0068 failed with error -5
[ 2.050936] imx-drm display-subsystem: bound imx-dpu-crtc.0 (ops dpu_crtc_ops)
[ 2.051871] imx-drm display-subsystem: bound imx-dpu-crtc.1 (ops dpu_crtc_ops)
[ 2.273795] hctosys: unable to open rtc device (rtc0)
[ 6.855734] rtc-rv3028 17-0052: registered as rtc0
colibri-imx8x-07021198:~$ sudo hwclock -r -f /dev/rtc0 --verbose
Password:
hwclock from util-linux 2.35.1
System Time: 423.065600
Using the rtc interface to the clock.
Last drift adjustment done at 1656319588 seconds after 1969
Last calibration done at 1656319588 seconds after 1969
Hardware clock is on UTC time
Assuming hardware clock is kept in UTC time.
Waiting for clock tick…
…got clock tick
Time read from Hardware Clock: 2022/06/28 02:56:19
Hw clock time : 2022/06/28 02:56:19 = 1656384979 seconds since 1969
Time since last adjustment is 65391 seconds
Calculated Hardware Clock drift is 0.000000 seconds
2022-06-28 03:56:18.237319+01:00
colibri-imx8x-07021198:~$

Cheers

FatLinux

How far does version Torizon 5.6 get to with regard to kernel versions?

We don’t majorly change the Linux Kernel version in our images unless we also change the major version of our BSP. Therefore TorizonCore 5.6 is still based on Linux kernel version 5.4. After 5.6 we plan to have one more 5.X version, 5.7, before we move to BSP 6.X where we will upgrade the kernel version to 5.15 I believe.

Best Regards,
Jeremias

Hi,

Could Toradex do some NRE work and make a patch for me ?

Cheers

Richard

Hi @FatLinux !

Toradex usually doesn’t execute development services. But we do have a network of partners that can perform development services.

Here is the link to our list of partners.

And I would like to ask you to get in touch with your account manager, as he can effectively help you to pick the best partner for your needs.

Best regards,

Hi,

Thanks for your help. We are going to do it in-house instead. It’s a
few hours.

Hi @FatLinux !

Thanks for the feedback.

Best regards,

RE: Cheeky workaround for the RV-3028

Postscript:

I forgot to post this bit of information.

For the time being, the RV3028 values can be manually poked into the EEPROM. It only needs doing once, and it takes 30 seconds.

Set the date on the module (On my board I only have one RTC, the RV3028 and no internet clock)

sudo date --set=“20221107 0436”

Get yourself Debian with root access to the hardware

docker run -it --privileged torizon/debian:$CT_TAG_DEBIAN /bin/bash

Check the rtc hardware is there

apt update && apt install i2c-tools
root@1bb8c$46bb0e:/# i2cdetect -y -r 17
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: – – – – – 0d – –
10: – – – – – – – – – – – – – – – –
20: – – – – – – – – – – – – – – – –
30: – – – – – – – – – – – – – – – –
40: – – – – – – – – – – – – – – – –
50: – – UU – – – – – – – – – – – – –
60: – – – – – – – – UU – – – – – – –
70: – – – – – – – –

The RV3028 is on 0x52 and it is right there and ready. So refer to your manual to get the settings you want, but I need the trickle charge on, backup switchover on, and backup to switch over when the main power is off = 0x3d.

poke this value into the eeprom

i2cset -f -y -a 17 0x52 0x37 0x3d

and sync the time

sudo hwclock -w -f /dev/rtc0

You should (provided the battery backup is charged up, or in my case the supercap) now have a RV3028 that will keep time.

cheers

Fat Linux

2 Likes

Dear all,
Finally I got around to checking out the Torizon rev 6 (which is Linux 5.15), and the RV-3028 driver is now updated with lots of features, and completely built into the kernel. Life is a lot easier than before, there is no need to get Torizoncore builder to install a module or tool chain.

So here is an example overlay for the rv3028

/dts-v1/;
/plugin/;

/ {
	compatible = "toradex,colibri-imx8x";
};

&i2c1 {
	status = "okay";
	rtc_i2c: rv3028@52 {
		compatible = "microcrystal,rv3028";
		reg = <0x52>;
		status = "okay";
		
		/* pinctrl-0 = <&rtc_nint_pins>;   commented out - not for our board
		interrupts-extended = <&gpio1 XX GPIO_ACTIVE_HIGH>; */
		
		trickle-resistor-ohms = <3000>;
	};
};

/*  			This is for the interrupt pin, not wired up on our board
 &iomuxc {   
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_hog0>, <&pinctrl_hog1>, <&pinctrl_hog2>,
                    <&pinctrl_ext_io0>, <&pinctrl_lpspi2_cs2>;
        colibri-imx8qxp {
                pinctrl_pwroff: pwroff {
                        fsl,pins = <
                                IMX8QXP_QSPI0B_X         0x20  /* SODIMM X */
                        >;
                };
        };
}; 
*/

This was tested with a Torizoncore builder build process with the following YAML file


input:
  easy-installer:
    local: images/torizon-core-docker-colibri-imx8x-Tezi_6.3.0+build.4.tar

customization: 
  splash-screen: splash/splash.png
  device-tree:
    include-dirs: 
        - linux/include
      
    custom: linux/arch/arm64/boot/dts/freescale/imx8qxp-colibri-eval-v3.dts
    
    overlays:
      clear: true
      add:  
        # Assign the RTC to the I2C-17
        - custom_overlays/real_time_clock_rv_3028.dts
        
        # Enable the parallel RGB interface on Colibri iMX8X.
        - device-trees/overlays/colibri-imx8x_panel-res-touch-7inch_overlay.dts
       
output:
  easy-installer:
    local: images/torizon-core-docker-colibri-imx8x-Tezi_6.3.0-HBL
    name: "Torizon HBL CoaguScan"
    description: "Custom Linux OS rev5.15"

The system was booted up and the message log was examined.

torizon@colibri-imx8x-07021234:~$ dmesg | grep rv3028
[    7.887776] rtc-rv3028 1-0052: registered as rtc0

A bit more of a proper test. We set the clock, show the clock and see what the device did.

torizon@colibri-imx8x-07021199:~$ sudo hwclock --systohc
torizon@colibri-imx8x-07021199:~$ sudo hwclock --show
2023-09-30 17:25:31.964454+00:00
torizon@colibri-imx8x-07021199:~$ sudo hwclock --show --verbose
hwclock from util-linux 2.37.4
System Time: 1696094746.703475
Trying to open: /dev/rtc0
Using the rtc interface to the clock.
Last drift adjustment done at 1696094723 seconds after 1969
Last calibration done at 1696094723 seconds after 1969
Hardware clock is on UTC time
Assuming hardware clock is kept in UTC time.
Waiting for clock tick...
...got clock tick
Time read from Hardware Clock: 2023/09/30 17:25:47
Hw clock time : 2023/09/30 17:25:47 = 1696094747 seconds since 1969
Time since last adjustment is 24 seconds
Calculated Hardware Clock drift is 0.000000 seconds
2023-09-30 17:25:46.699795+00:00
torizon@colibri-imx8x-07021199:~$

All good. Now for a nice lay down.

Yours Truly

Fat Linux

1 Like

Glad to hear everything worked out for you!

1 Like

Thanks for all your help. It is well worth it. That tiny RTC really suits the Toradex platform, quite remarkable.

Cheers

Richard

Hello @FatLinux

We also plan on using the RV-3028 RTC. We’re also on BSP 6 with kernel 5.15 for the Colibri IMX8X.
Generally, the RTC works, but the backup switching doesn’t work initially (as you mentioned, it’s switched off by default). May I ask how you made this work?

Currently, I’m trying to set it up by using ioctl, but get an error. This is my code:

    if((fd=open("/dev/rtc0",O_RDWR)) < 0)
    {
        LOG_TEXT(LOG_ERR,"open() failed (%d): %s",errno,strerror(errno));
        return(ERR_INTERNAL);
    }

    if((ret=ioctl(fd,RTC_PARAM_SET,RTC_PARAM_BACKUP_SWITCH_MODE,RTC_BSM_LEVEL)) == -1)
    {
        LOG_TEXT(LOG_ERR,"ioctl() failed (%d): %s",errno,strerror(errno));
        close(fd);
        return(ERR_INTERNAL);
    }

Which outputs: ioctl() failed (25): Inappropriate ioctl for device

Thanks and regards,

Simon

Hello @SimonG,

It looks like the driver rv-3028 does not support the ioctl calls (RTC_PARAM_SET, RTC_PARAM_BACKUP_SWITCH_MODE etc.) you are trying to use. I looked into the driver code and it seems these symbols do not appear to be part of the Linux kernel’s standard RTC interface or the RV-3028 driver.

You might be able to implement these functionalities by writing directly to the device’s registers over I2C via i2c-tools or a custom C code. You might want to check on the datasheet and app manual of RV-3028 to find the exact register addresses. From a quick look at those documents, I see that the Backup Switchover Mode (BSM) register is located at register address 0x37. Please check page 45 on the app manual to see the switchover modes.

1 Like

Hello @rudhi.tx

Thanks for your response.

Indeed, kernel 5.15 doesn’t (yet) support these calls, but newer kernels do (e.g. 6.1 of the IMX6ULL works).

We decided to upgrade to BSP7 already, which brings new kernels for all our modules (6.6), so this issue should be resolved then.

I will also correct my code from above, so others can use this as reference, because it’s not easy to find such examples.

Regards,
Simon

Update: Seems I cannot edit my post above anymore, so here’s the working code (for recent enough kernels):

int fd;
struct rtc_param param={0};

LOG_TEXT(LOG_INFO,"Setting RTC switchover mode...");

if((fd=open("/dev/rtc0",O_RDWR)) == -1)
{
    LOG_TEXT(LOG_ERR,"open() failed (%d): %s",errno,strerror(errno));
    return(ERR_INTERNAL);
}

/**
 * https://github.com/torvalds/linux/blob/master/include/uapi/linux/rtc.h#L70
 */
param.param=RTC_PARAM_BACKUP_SWITCH_MODE;
if((ret=ioctl(fd,RTC_PARAM_GET,&param)) == -1)
{
    LOG_TEXT(LOG_ERR,"ioctl() failed (%d): %s",errno,strerror(errno));
    close(fd);
    return(ERR_INTERNAL);
}
LOG_TEXT(LOG_DEBUG,"Reading RTC_PARAM_BACKUP_SWITCH_MODE... is=%" PRIu64,param.uvalue);
if(param.uvalue != RTC_BSM_LEVEL)
{
    param.uvalue=RTC_BSM_LEVEL;

    LOG_TEXT(LOG_INFO,"Setting RTC_PARAM_BACKUP_SWITCH_MODE=%" PRIu64 "...",param.uvalue);
    if((ret=ioctl(fd,RTC_PARAM_SET,&param)) == -1)
    {
        LOG_TEXT(LOG_ERR,"ioctl() failed (%d): %s",errno,strerror(errno));
        close(fd);
        return(ERR_INTERNAL);
    }
}

close(fd);
2 Likes