Cover image for Linux RTC

Linux RTC


Timeline

Timeline

2026-01-05

init

This article introduces the basic concepts, functional features, and application scenarios of the Linux Real-Time Clock (RTC), and discusses in detail the differences between internal RTC and external RTC in terms of definition, advantages, disadvantages, and applicable scenarios. Additionally, it summarizes the RTC peripheral circuit design using the iTOP-RK3568 development board's RX8010 as an example, explaining its I2C interface connection and the power switching mechanism between the main power supply and backup battery.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Competition
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hot Plug
11. pinctrl Subsystem
12. gpio Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

RTC Basics

RTC Introduction

RTC(Real-Time Clock) i.e.Real-Time Clock, is an integrated circuit or module used toprovide precise time information in electronic systems

Unlike the system’s main processor (CPU),the RTC is primarily responsible for maintaining the system’s real-time date and timeEven when the device is turned off or powered down,,RTC it can continue to operate and maintain time accuracy.。

Main functions of RTC

  1. Provide real-time time: RTC can continuously track the current date and time, typically including year, month, day, hour, minute, second, and other information.
  2. Power-off time retention: RTC is usually equipped with a backup battery (such as a coin cell battery), allowing it to continue running even when the main system loses power, ensuring that time information is not lost.

Application scenarios of RTC

  1. Electronic clocks: such as the clock function in devices like computers, phones, and smartwatches.
  2. Embedded systems: In industrial control and IoT devices, RTC is used to record the time of events.
  3. Data logging: In scenarios requiring timestamps, RTC provides accurate time information.
  4. Scheduled tasks: Used to wake up devices or perform specific operations at scheduled times.

Features of RTC

  1. Low power consumption: RTC is typically designed for low-power operation, making it suitable for long-term use.
  2. High precision: Capable of providing relatively accurate time information with small errors.
  3. Independence: The RTC can operate independently even when the main system is powered off.

Internal RTC and External RTC

Real-time clocks have two common implementation solutions: external RTC and internal RTC.

An external RTC is a dedicated RTC chip independent of the main controller, connected via communication interfaces such as I2C and SPI.

  • Advantages
    • It features high precision, providing more accurate time recording.
    • The external RTC has an independent power management circuit and can be equipped with a coin cell battery for long-term independent operation, ensuring continuity of time recording even if the main controller loses power or is damaged.
  • Disadvantages
    • The external RTC has higher costs, requiring additional purchase of chips and related components, while also increasing circuit design complexity and development difficulty, and consuming communication resources of the main controller.

Therefore, external RTC is more suitable for scenarios with high requirements for time accuracy, reliability, and functionality. The iTOP-RK3568 development board integrates an external RTC, using the RX8010 chip, as shown in the following figure:

External RTC
External RTC

An internal RTC is a real-time clock module integrated inside the main controller chip.

  • Advantages
    • It features hardware integration, requiring no additional hardware components, thus offering simple design, low cost, and ease of use.
  • Disadvantages
    • The internal RTC relies on the main chip’s power supply and typically requires a backup battery to maintain timekeeping after power loss.
    • Internal RTC Highly susceptible to temperature and voltage fluctuations,Resulting in significant time drift,Reliability is also relatively low

Therefore, the internal RTC is more suitable for scenarios with low time precision requirements and cost sensitivity, such as home appliances, simple IoT devices, or systems with low real-time demands.

The power management chip RK809 on the iTOP-RK3568 core board integrates an internal RTC by default, but due to the various disadvantages of the internal RTC mentioned above and the presence of an
external RTC on the baseboard, the internal RTC is not enabled. The internal RTC circuit of the RK809 power management chip is shown below:

RK809 Power Management Chip
RK809 Power Management Chip

Summary

CategoryInternal RTCExternal RTC
DefinitionRTC module integrated within the main control chipIndependent dedicated RTC chip connected via I²C/SPI interfaces
Features- Hardware integration
- Low-power mode support
- Relies on main power supply
- Limited accuracy
- Independent operation
- High accuracy
- Multi-function expansion
Advantages1. Low cost
2. Simple design
3. Easy to use
1. High precision
2. Strong independence
3. Feature-rich
4. High reliability
Disadvantages1. Insufficient precision
2. Lower reliability
3. Lack of independence
1. Higher cost
2. Complex design
3. Resource-intensive
Applicable scenariosSystems with low time precision requirements, cost sensitivity, and low real-time demandsScenarios requiring high time precision, long-term independent operation, and additional features

RK3568 RTC Peripheral

External RTC
External RTC

This circuit can be divided into the RTC power supply section and the RTC chip circuit section. According to the RTC chip circuit section, the RX8010 is mounted on I2C5.

VCC_RTC
VCC_RTC

The main function of this system is to achieveswitching between main power supply mode and backup battery power supply mode, ensuring that the RTC module can continue to work through the backup battery (VCC3V3_SYS) when the main power (CR1220) is cut off, thereby preventing time information loss. It is divided into the following two cases.

  1. When the system is operating normally,VCC3V3_SYSprovides 3.3V power, diode D2 is forward-biased to supply power to the RTC module, while diode D3 is reverse-biased to prevent the main power from charging the battery or draining battery power;
  2. When the system is powered off (VCC3V3_SYSfails), diode D2 is reverse-biased to prevent battery power from being consumed through the main power circuit, while diode D3 is forward-biased, and theCR1220battery supplies power to the RTC module, ensuring the RTC continues to operate.

RX8010 Driver Analysis and Porting

RTC Subsystem Framework

RTC subsystem framework
RTC subsystem framework

In the figure above, the RTC subsystem is divided into three layers: user space, device driver layer, and hardware layer. The device driver layer further includes RTC device drivers and the PWM core layer:

  • User space is the layer where applications run. At this layer, applications interact with the system through different interfaces, such as accessing/dev/xxxdevice nodes to read and write RTC time, or throughsysfs andprocfile systems to obtain or set hardware status.

  • The middle device driver layer is further divided into RTC device drivers and the RTC core layer.

    • The RTC device driver is responsible for operating the specific details of the hardware by directly communicating with the hardware (RTC chip) to achieve hardware control. The device driver is also responsible for exposing the hardware device to the upper-layer system, such asdevice(device files) anddriver(drivers).
      • /dev/xxxrefers to the interface files used to operate the device, through which user applications interact with the RTC hardware.
      • driverThe part is a module of the system kernel, responsible for specific hardware control operations.
    • The RTC core layer is responsible for managing and coordinating the time management functions of the RTC. It ensures that the system correctly reads and sets the time, while also ensuring that the clock continues to run during power outages.
  • The RTC hardware layer represents the actual RTC hardware, communicating with the device driver layer through hardware interfaces (such as I2C, SPI, etc.) and providing actual timekeeping functions.

RTC driver source code analysis

Device Tree

1
2
3
4
5
6
7
8
9
10
11
12
//RTC Chip Enable
&i2c5 {
status = "okay";

rx8010: rx8010@32 {
compatible = "epson,rx8010";
reg = <0x32>;
status = "okay";
#clock-cells = <0>;
};
};

Then through the compatible propertyepson,rx8010Find the matching driver file, the specific path of the driver isdrivers/rtc/rtc-rx8010.c, first find the driver’s entry function:

1
2
3
4
5
6
7
8
9
10
static struct i2c_driver rx8010_driver = {
.driver = {
.name = "rtc-rx8010",
.of_match_table = of_match_ptr(rx8010_of_match),
},
.probe = rx8010_probe,
.id_table = rx8010_id,
};

module_i2c_driver(rx8010_driver);

module_i2c_driver()

This macro is used to simplify the registration and deregistration process of I2C device drivers, defined ininclude/linux/i2c.hfile, as shown below

1
2
3
4
5
6
7
8
9
10
11
12
/**
* module_i2c_driver() - Helper macro for registering a modular I2C driver
* @__i2c_driver: i2c_driver struct
*
* Helper macro for I2C drivers which do not do anything special in module
* init/exit. This eliminates a lot of boilerplate. Each module may only
* use this macro once, and calling it replaces module_init() and module_exit()
*/
#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, \
i2c_del_driver)

module_driver()

module_drivermacro is also a macro used for simplification, and its corresponding macro definition is as follows

include/linux/device/driver.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* module_driver() - Helper macro for drivers that don't do anything
* special in module init/exit. This eliminates a lot of boilerplate.
* Each module may only use this macro once, and calling it replaces
* module_init() and module_exit().
*
* @__driver: driver name
* @__register: register function for this driver type
* @__unregister: unregister function for this driver type
* @...: Additional arguments to be passed to __register and __unregister.
*
* Use this macro to construct bus specific macros for registering
* drivers, and do not use it on its own.
*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

Finally, substitute the above macro, and expanding it yields the following content:

1
2
3
4
5
6
7
8
9
10
11
static int __init rx8010_driver_init(void)
{
return i2c_add_driver(&rx8010_driver);
}
module_init(rx8010_driver_init);

static void __exit rx8010_driver_exit(void)
{
i2c_del_driver(&rx8010_driver);
}
module_exit(rx8010_driver_exit);
  • rx8010_driver_initfunction is called when the module is loaded, and it uses thei2c_add_driverfunction torx8010_driverregister into the kernel.

  • rx8010_driver_exitfunction is called when the module is unloaded, and it uses thei2c_del_driverfunction torx8010_driverunregister from the kernel.

rx8010_probe()

After the compatible match, the probe function is entered. The content of the probe function is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
static int rx8010_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);// Get the I2C adapter object to check function support.
const struct rtc_class_ops *rtc_ops;// RTC operation function pointer.
struct rx8010_data *rx8010;// Structure for storing device private data.
int err = 0;// Error code initialized to 0.

// Check whether the I2C adapter supports the required SMBus functions.
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
| I2C_FUNC_SMBUS_I2C_BLOCK)) {
dev_err(&adapter->dev, "doesn't support required functionality\n");
return -EIO;// If not supported, print an error message and return -EIO (input/output error).
}

// Allocate a zeroed memory block and bind it to the device lifecycle.
rx8010 = devm_kzalloc(&client->dev, sizeof(struct rx8010_data),
GFP_KERNEL);
if (!rx8010)// If memory allocation fails, return -ENOMEM (out of memory error).
return -ENOMEM;
// Initialize device private data.
rx8010->client = client;
i2c_set_clientdata(client, rx8010); // Associate private data with the I2C client.
// Initialize the RX8010 device.
err = rx8010_init_client(client);
if (err)
return err;// If initialization fails, return the error code directly.

if (client->irq > 0) {// Check whether the device provides an interrupt number.
dev_info(&client->dev, "IRQ %d supplied\n", client->irq);
// Request a threaded interrupt handler.
err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
rx8010_irq_1_handler,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"rx8010", client);

if (err) {
// If the request interrupt fails, print an error message and return an error code.
dev_err(&client->dev, "unable to request IRQ\n");
return err;
}
// If interrupt support is available, use the RTC operation function with alarm capability.
rtc_ops = &rx8010_rtc_ops_alarm;
} else {
// If interrupt support is not available, use the default RTC operation function.
rtc_ops = &rx8010_rtc_ops_default;
}

// Register the RTC device.
rx8010->rtc = devm_rtc_device_register(&client->dev, client->name,
rtc_ops, THIS_MODULE);

if (IS_ERR(rx8010->rtc)) {
// If registration fails, print an error message and return an error code.
dev_err(&client->dev, "unable to register the class device\n");
return PTR_ERR(rx8010->rtc);
}

rx8010->rtc->max_user_freq = 1;// Set the maximum user frequency of the RTC device.

return err;// Return a success status.
}

In the probe function, arx8010_datastructure variable rx8010 is defined for storing device private data. The structure is defined as follows:

struct rx8010_data

1
2
3
4
5
struct rx8010_data {
struct i2c_client *client; // I2C client, used for communication with the RX8010 device.
struct rtc_device *rtc; // RTC device, used for managing the real-time clock function.
u8 ctrlreg; // Control register value, used to store or configure the control status of the RX8010.
};

i2c_clientis a pointer to the I2C client, used for communication with the RX8010 device, and thertc_devicestructure represents the RTC (Real-Time
Clock) device associated with the RX8010. The structure is defined as follows:

struct rtc_device
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
struct rtc_device {
struct device dev;// Device structure for integration with the device model
struct module *owner;// Module that owns this RTC device

int id;// ID of the RTC device

const struct rtc_class_ops *ops;// Set of operation functions for the RTC device
struct mutex ops_lock;// Mutex for protecting the operation function set

struct cdev char_dev;// Character device for user-space access to the RTC device
unsigned long flags;// Flags of the RTC device

unsigned long irq_data;// Interrupt-related data
spinlock_t irq_lock;// Spinlock for protecting interrupt data
wait_queue_head_t irq_queue;// Wait queue for handling interrupt events
struct fasync_struct *async_queue;// Asynchronous notification queue

int irq_freq;// Interrupt frequency
int max_user_freq;// Maximum interrupt frequency allowed by user space

struct timerqueue_head timerqueue;// Timer queue for managing timer events
struct rtc_timer aie_timer; // Alarm interrupt timer
struct rtc_timer uie_rtctimer; // Update interrupt timer
struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */ // High-precision timer for handling sub-second interrupts
int pie_enabled;// Whether the high-precision timer is enabled
struct work_struct irqwork;// Work queue for interrupt handling
/* Some hardware can't support UIE mode */
int uie_unsupported;// Whether the hardware does not support update interrupt mode

/* Number of nsec it takes to set the RTC clock. This influences when
* the set ops are called. An offset:
* - of 0.5 s will call RTC set for wall clock time 10.0 s at 9.5 s
* - of 1.5 s will call RTC set for wall clock time 10.0 s at 8.5 s
* - of -0.5 s will call RTC set for wall clock time 10.0 s at 10.5 s
*/
long set_offset_nsec;/* Offset time (nanoseconds) when setting the RTC clock */

bool registered;// Whether the device is registered

/* Old ABI support */
bool nvram_old_abi;// Whether to use the old NVRAM ABI
struct bin_attribute *nvram;// Binary attributes of NVRAM

time64_t range_min;// Minimum time range supported by the RTC device
timeu64_t range_max;// Maximum time range supported by the RTC device
time64_t start_secs;// Start time (seconds) of the RTC device
time64_t offset_secs;// Offset time (seconds) of the RTC device
bool set_start_time;// Whether the start time is set

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
struct work_struct uie_task;// Update interrupt simulation task
struct timer_list uie_timer;// Update interrupt simulation timer
/* Those fields are protected by rtc->irq_lock */ /* 以下字段由 rtc->irq_lock 保护 */
unsigned int oldsecs;// Last read seconds
unsigned int uie_irq_active:1;// Whether update interrupt is active
unsigned int stop_uie_polling:1;// Whether to stop update interrupt polling
unsigned int uie_task_active:1;// Whether update interrupt task is active
unsigned int uie_timer_active:1;// Whether update interrupt timer is active
#endif
};

This structure is the core data structure of the RTC device in the Linux kernel, covering all functions and states of the RTC device.

rx8010_init_client()

In the probe function, throughrx8010_init_clientfunction, the RX8010 chip is initialized and configured. Since different RTC chips have different registers, the initialization code also differs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
static int rx8010_init_client(struct i2c_client *client)
{
struct rx8010_data *rx8010 = i2c_get_clientdata(client);
u8 ctrl[2];
int need_clear = 0, err = 0;

u8 flag;
flag = i2c_smbus_read_byte_data(client, RX8010_FLAG);
if(flag < 0)
return flag;

flag &= ~(RX8010_FLAG_VLF);
err = i2c_smbus_write_byte_data(client, RX8010_FLAG, flag);

/* Initialize reserved registers as specified in datasheet */
err = i2c_smbus_write_byte_data(client, RX8010_RESV17, 0xD8);
if (err < 0)
return err;

err = i2c_smbus_write_byte_data(client, RX8010_RESV30, 0x00);
if (err < 0)
return err;

err = i2c_smbus_write_byte_data(client, RX8010_RESV31, 0x08);
if (err < 0)
return err;

err = i2c_smbus_write_byte_data(client, RX8010_IRQ, 0x00);
if (err < 0)
return err;

err = i2c_smbus_read_i2c_block_data(rx8010->client, RX8010_FLAG,
2, ctrl);
if (err != 2)
return err < 0 ? err : -EIO;

if (ctrl[0] & RX8010_FLAG_VLF)
dev_warn(&client->dev, "Frequency stop was detected\n");

if (ctrl[0] & RX8010_FLAG_AF) {
dev_warn(&client->dev, "Alarm was detected\n");
need_clear = 1;
}

if (ctrl[0] & RX8010_FLAG_TF)
need_clear = 1;

if (ctrl[0] & RX8010_FLAG_UF)
need_clear = 1;

if (need_clear) {
ctrl[0] &= ~(RX8010_FLAG_AF | RX8010_FLAG_TF | RX8010_FLAG_UF);
err = i2c_smbus_write_byte_data(client, RX8010_FLAG, ctrl[0]);
if (err < 0)
return err;
}

rx8010->ctrlreg = (ctrl[1] & ~RX8010_CTRL_TEST);

return 0;
}

struct rtc_class_ops

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (client->irq > 0) {// Check whether the device provides an interrupt number
dev_info(&client->dev, "IRQ %d supplied\n", client->irq);
// Request a threaded interrupt handler
err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
rx8010_irq_1_handler,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"rx8010", client);

if (err) {
// If the interrupt request fails, print an error message and return an error code
dev_err(&client->dev, "unable to request IRQ\n");
return err;
}
// If interrupt support is available, use RTC operation functions with alarm capability
rtc_ops = &rx8010_rtc_ops_alarm;
} else {
// If no interrupt support is available, use the default RTC operation functions
rtc_ops = &rx8010_rtc_ops_default;
}

In the probe function, the operation mode of the RTC chip is determined based on whether the device provides an interrupt number. However, since interrupts are not used in either the hardware connection or the device tree configuration, the second condition is entered, which isrtc_ops = &rx8010_rtc_ops_default;

1
2
3
4
5
static const struct rtc_class_ops rx8010_rtc_ops_default = {
.read_time = rx8010_get_time, // Function pointer to read the current time
.set_time = rx8010_set_time, // Function pointer to set the current time
.ioctl = rx8010_ioctl, // Function pointer to provide additional control functions
};

rx8010_rtc_ops_defaultProvides a set of default operation function interfaces for the RX8010 real-time clock chip, including a function pointer to read the current time, a function pointer to set the current time, and ioctl external control functions

devm_rtc_device_register()

In the probe function, usedevm_rtc_device_registerfunction to register the RTC device, which is in thedrivers/rtc/class.cfile, and the specific content of the function is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* devm_rtc_device_register - resource managed rtc_device_register()
* @dev: the device to register
* @name: the name of the device (unused)
* @ops: the rtc operations structure
* @owner: the module owner
*
* @return a struct rtc on success, or an ERR_PTR on error
*
* Managed rtc_device_register(). The rtc_device returned from this function
* are automatically freed on driver detach.
* This function is deprecated, use devm_rtc_allocate_device and
* rtc_register_device instead
*/
struct rtc_device *devm_rtc_device_register(struct device *dev,
const char *name,
const struct rtc_class_ops *ops,
struct module *owner)
{
struct rtc_device *rtc;
int err;
// Allocate memory for the device resource manager to store the RTC device pointer
rtc = devm_rtc_allocate_device(dev);
if (IS_ERR(rtc))
return rtc;// If allocation fails, return an error pointer (insufficient memory)

rtc->ops = ops;

err = __rtc_register_device(owner, rtc);// Call rtc_device_register to register the RTC device
if (err)
return ERR_PTR(err);

return rtc;
}
EXPORT_SYMBOL_GPL(devm_rtc_device_register);
__rtc_register_device()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int __rtc_register_device(struct module *owner, struct rtc_device *rtc)
{
struct rtc_wkalrm alrm;// Used to store alarm information
int err;

if (!rtc->ops) {
dev_dbg(&rtc->dev, "no ops set\n");
return -EINVAL;
}

rtc->owner = owner;// Set the module owner
rtc_device_get_offset(rtc);// Get the time offset of the RTC device

/* Check to see if there is an ALARM already set in hw */
// Check if an alarm has been set in the hardware
err = __rtc_read_alarm(rtc, &alrm);// Attempt to read alarm information from the hardware
if (!err && !rtc_valid_tm(&alrm.time))// If the read is successful and the time is valid
rtc_initialize_alarm(rtc, &alrm);// Initialize alarm

rtc_dev_prepare(rtc);// Prepare the character device interface for the RTC device

// Add the character device and device structure of the RTC device to the kernel
err = cdev_device_add(&rtc->char_dev, &rtc->dev);
if (err)
dev_warn(rtc->dev.parent, "failed to add char device %d:%d\n",
MAJOR(rtc->dev.devt), rtc->id);
else
dev_dbg(rtc->dev.parent, "char device (%d:%d)\n",
MAJOR(rtc->dev.devt), rtc->id);

rtc_proc_add_device(rtc);// Add the RTC device to the proc file system

rtc->registered = true;
dev_info(rtc->dev.parent, "registered as %s\n",
dev_name(&rtc->dev));

#ifdef CONFIG_RTC_HCTOSYS_DEVICE// If RTC is configured as the system time source and the device name matches, synchronize the hardware clock to the system clock
if (!strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE))
rtc_hctosys(rtc);
#endif

return 0;
}
EXPORT_SYMBOL_GPL(__rtc_register_device);

The main function of this function is to register an RTC device into the RTC subsystem of the Linux kernel. It completes a series of initialization and resource allocation tasks, enabling the kernel to interact with the RTC device through standard interfaces.

The key point is calling thecdev_device_addfunction, which registers the character device and device structure of the RTC device into the kernel, making the RTC device officially part of the kernel and available for use by user space or other kernel modules.

Then there is thertc_proc_add_device, which adds the information of the RTC device to the proc subsystem for debugging and monitoring.

Porting the RX8010 driver

RX8010 chip
RX8010 chip

It can be confirmed that the RX8010 is mounted on I2C5, so it is necessary to add an rx8010 node under the i2c5 node in the device tree. The added content is as follows:

1
2
3
4
5
6
7
8
9
&i2c5 {
status = "okay";
rx8010: rx8010@32 {
compatible = "epson,rx8010";
reg = <0x32>;
status = "okay";
#clock-cells = <0>;
};
};

The reg attribute indicates that the address of RX8010 is 0x32. This address can be obtained from the RX8010 datasheet, as shown below:

RX8010 Datasheet
RX8010 Datasheet

There are a total of 8 bits of data here, because during the actual data transmission process,the first byte sent by the master device contains the slave device address and the read/write bit, where the slave device address occupies the high 7 bits of the byte (bit 7 to bit 1). Therefore, from the figure above, the address of RX8010 is 0110010, which converts to 0x32.

Then go to the root directory of the Linux kernel source code and use menuconfig to enable the RX8010 driver.

1
2
3
Device Drivers
Real Time Clock
<*> Epson RX8010SJ

Additionally, ensure that the internal RTC of the RK power management chip is not selected, to make sure there are no two RTC devices in the system.

1
2
3
Device Drivers
Real Time Clock
<*> Rockchip RK805/RK808/RK809/RK816/RK817/RK818 RTC

Time-Related Commands

date Command

date is a very powerful command-line tool in Linux systems, used to display or set the system’s date and time. It can not only view the current system time but also set the system time, format date and time output, parse time strings, and perform time calculations.

  • Display Current Date and Time

Running the date command directly will display the current system date and time in the default local time format, as shown below:

1
2
$ date
Mon Jan 5 09:37:49 PM CST 2026

CST indicates the time zone (China Standard Time)

  • Set System Time

The command format for setting the system time using the date command is as follows:

1
2
3
4
5
6
7
8
date [MMDDhhmm[[CC]YY][.ss]]
# MM: Month (two digits, e.g., 10 for October).
# DD: Day (two digits, e.g., 30 for the 30th).
# hh: Hour (24-hour format, two digits, e.g., 14 for 2 PM).
# mm: Minute (two digits, e.g., 23 for 23 minutes).
# CC: Century (optional, two digits, e.g., 20 for the 2000s).
# YY: Year (two digits, e.g., 23 for 2023).
# .ss: Seconds (optional, two digits, e.g., .45 for 45 seconds).

For example, to set the system time to January 20, 2025, 14:23:45:

1
date 012014232025.45
  • Formatted Output

The date command supports customizing the output date and time format using format strings. By adding a + and a format string after the date command, you can specify the output format.

Format SpecifierDescriptionExample
%YYear (four digits)2023
%mMonth (two digits)10
%dDay (two digits)30
%HHour (24-hour format, two digits)14
%IHour (12-hour format, two digits)02
%MMinute (two digits)23
%SSecond (two digits)45
%ADay of week (full name)Monday
%aDay of week (abbreviated name)Mon
%BMonth (full name)October
%bMonth (abbreviated name)Oct
%pAM/PMPM
%ZTime zoneCST

For example, format asYYYY-MM-DD HH:MM:SS, and output the current time:

1
date "+%Y-%m-%d %H:%M:%S"

hwclock command

hwclock is used in Linux systems toManage hardware clockcommand-line tool. It includes viewing, setting, and synchronizing the hardware clock with the system clock.

  • View hardware clock

Running the hwclock command directly displays the current hardware clock time.

1
2
$ sudo hwclock
2026-01-05 21:52:44.991760+08:00

The hwclock command displays the hardware clock time. The hardware clock typically stores UTC time by default. UTC is the global standard time, defined based on atomic clocks for precision, and is unaffected by variations in Earth’s rotation.

UTC is the global time reference and does not belong to any time zone, hence it is also known as Coordinated Universal Time.

The date command displays the system clock time, which adjusts according to the operating system’s time zone settings.

The printed time zone above is CST, which is China Standard Time, so the date command displays UTC+8. Therefore, the system clock and hardware clock use different time standards, resulting in a time difference between them. Assuming you are in China (UTC+8), the time difference is exactly 8 hours.

  • Synchronize hardware clock with system clock
    • hwclock -sSynchronize system clock to hardware clock
    • hwclock -wSynchronize hardware clock to system clock

Every time the system powers on and boots, the hardware time from the RTC is synchronized to the system time. The specific implementation is in the kerneldrivers/rtc/class.cfile’srtc_hctosysfunction. The specific content of this function is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#ifdef CONFIG_RTC_HCTOSYS_DEVICE
/* Result of the last RTC to system clock attempt. */
int rtc_hctosys_ret = -ENODEV;

/* IMPORTANT: the RTC only stores whole seconds. It is arbitrary
* whether it stores the most close value or the value with partial
* seconds truncated. However, it is important that we use it to store
* the truncated value. This is because otherwise it is necessary,
* in an rtc sync function, to read both xtime.tv_sec and
* xtime.tv_nsec. On some processors (i.e. ARM), an atomic read
* of >32bits is not possible. So storing the most close value would
* slow down the sync API. So here we have the truncated value and
* the best guess is to add 0.5s.
*/

static void rtc_hctosys(struct rtc_device *rtc)
{
int err;
struct rtc_time tm;// Used to store the time read from the RTC device
struct timespec64 tv64 = {
.tv_nsec = NSEC_PER_SEC >> 1, // Set the nanosecond part to 0.5 seconds (for time precision adjustment)
};

// Read the current time from the RTC device
err = rtc_read_time(rtc, &tm);
if (err) {// If reading the time fails
dev_err(rtc->dev.parent,
"hctosys: unable to read the hardware clock\n");
goto err_read;
}
// Convert the RTC time to a Unix timestamp (seconds since January 1, 1970)
tv64.tv_sec = rtc_tm_to_time64(&tm);

#if BITS_PER_LONG == 32
if (tv64.tv_sec > INT_MAX) {// On 32-bit systems, check if the timestamp is out of range
err = -ERANGE; // If out of range, set the error code to "out of range"
goto err_read;// Jump to the error handling label
}
#endif

err = do_settimeofday64(&tv64);// Set the timestamp to the system clock

dev_info(rtc->dev.parent, "setting system clock to %ptR UTC (%lld)\n",
&tm, (long long)tv64.tv_sec);

err_read:
rtc_hctosys_ret = err;
}
#endif

Through thertc_read_timefunction, read the time stored in the RTC hardware, then through thedo_settimeofday64function, set the converted timestamp to the system clock.

RTC Application Programming

ioctlMacro Definition Analysis

RTC_RD_TIME

1
#define RTC_RD_TIME _IOR('p', 0x09, struct rtc_time)
  • _IOR(type, nr, data_type): IndicatesReading data from the kernel to user space
    • 'p': Magic number, used to distinguish different devicesioctlcommand (RTC is fixed to'p')。
    • 0x09: Command number.
    • struct rtc_time: Expected data type.

Function:Read the current time from RTC hardware(Note: Not the system time!)


RTC_SET_TIME

1
#define RTC_SET_TIME _IOW('p', 0x0a, struct rtc_time)
  • _IOW(type, nr, data_type): Indicateswriting data from user space to the kernel
  • Other parameters are the same as above.

Function:Write the user-specified time to RTC hardware(Usually requires root privileges).


struct rtc_timestructure

This is the standard time structure defined by the Linux kernel (located in<linux/rtc.h>), with the C standard library’sstruct tmalmost identical, butnot guaranteed to be fully compatible(especially when cross-platform).

FieldRangeDescription⚠️ Note
tm_sec0–59Seconds
tm_min0–59Minutes
tm_hour0–23Hours (24-hour)
tm_mday1–31Day of month (starting from 1)Not 0-based!
tm_mon0–11Month (0=Jan)Error-prone!
tm_yearYear - 1900e.g., 2023 →123Must subtract 1900!
tm_wday0–6Day of week (0=Sun)Driver usually auto-calculates
tm_yday0–365Day of yearDriver usually auto-calculates
tm_isdstUsually 0Daylight saving flagRTC hardware generally does not support

Key Pitfalls

  • Set February 15, 2023 →.tm_year = 123,.tm_mon = 1
  • Add back when printing:tm_year + 1900,tm_mon + 1

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

int main() {
int fd;
struct rtc_time tm;

// Must open with O_RDWR to write RTC
fd = open("/dev/rtc0", O_RDWR);
if (fd < 0) {
perror("Failed to open /dev/rtc0 (try running as root)");
return EXIT_FAILURE;
}

// Read current RTC time
if (ioctl(fd, RTC_RD_TIME, &tm) < 0) {
perror("RTC_RD_TIME");
close(fd);
return EXIT_FAILURE;
}

printf("Current RTC time: %d-%02d-%02d %02d:%02d:%02d\n",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);

// === Safe time modification example: set to 2023-06-15 12:00:00 ===
// Note: This assumes you want to set a complete, valid time
tm.tm_year = 123; // 2023
tm.tm_mon = 5; // June (0-based)
tm.tm_mday = 15; // 15th
tm.tm_hour = 12;
tm.tm_min = 0;
tm.tm_sec = 0;
// Optional: Clear other fields (driver usually auto-calculates wday/yday)
tm.tm_wday = tm.tm_yday = tm.tm_isdst = 0;

// Write to RTC
if (ioctl(fd, RTC_SET_TIME, &tm) < 0) {
if (errno == EPERM || errno == EACCES) {
fprintf(stderr, "Error: Permission denied. Run as root or use 'sudo'.\n");
} else {
perror("RTC_SET_TIME");
}
close(fd);
return EXIT_FAILURE;
}

// Verify write result
if (ioctl(fd, RTC_RD_TIME, &tm) < 0) {
perror("RTC_RD_TIME (after set)");
close(fd);
return EXIT_FAILURE;
}

printf("Modified RTC time: %d-%02d-%02d %02d:%02d:%02d\n",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);

close(fd);
return EXIT_SUCCESS;
}
  • Read: Usually readable by normal users (depends on udev rules).

  • Write (RTC_SET_TIME):requires root privilegesorCAP_SYS_TIMEcapability.