Nuclear rtc drive analysis

Reference: weidongshan second classic video tutorial

1. Analysis of rtc kernel driver

The RTC driver in linux kernel is located under drivers/rtc. There are RTC drivers of many development platforms. S3C24xx is the main driver here, so its RTC driver is rtc-s3c c

1.1 ./drivers/rtc/rtc-s3c.c

  • First, enter the entry function, where a S3C2410 RTC platform device driver is registered. When the kernel matches the S3C2410 RTC platform device, it will be called Probe function s3c_rtc+probe:

  • The S3C2410 RTC platform device is defined in arch/arm/plat-s3c24xx/dev.c, but it is only defined at this time and has not been registered in the kernel, so the kernel cannot see it:

  • Next, go to S3C2410_ In the rtcdrv - > probe function, see its function:
static int s3c_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc;           //rtc equipment structure
struct resource *res;
int ret;

s3c_rtc_tickno = platform_get_irq(pdev, 1);          //Get IRQ_TICK beat interrupt resource
s3c_rtc_alarmno = platform_get_irq(pdev, 0);        //Get IRQ_RTC alarm clock interrupt resource
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   //Get memory resources

s3c_rtc_mem = request_mem_region(res->start,res->end-res->start+1,pdev->name);//Request memory resources

s3c_rtc_base = ioremap(res->start, res->end - res->start + 1);     //Remap memory


s3c_rtc_enable(pdev, 1);          //Set hardware related settings to enable RTC registers

s3c_rtc_setfreq(s3c_rtc_freq);      //Set TICONT register to enable beat interrupt and set beat count value

/*1.Register RTC device*/
rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);  
rtc->max_user_freq = 128;
platform_set_drvdata(pdev, rtc);
      return 0;
}

Obviously, probe will eventually call rtc_device_register() function to register RTC with the kernel_ Device. If the registration is successful, a registered RTC will be returned_ device.

And s3c_rtcops is an rtc_class_ops structure, which stores the functions of how to operate the RTC device, such as reading and writing RTC time, reading and writing alarm clock time, etc. After successful registration, it will be saved in RTC_ Device - > Ops

This function is in drivers / RTC / class C is defined in the file, class C file mainly defines RTC subsystem.

When the kernel initializes, it will enter class c. By executing RTC_ init()->rtc_ dev_ Init() to register character devices: err = alloc_ chrdev_ region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");

  • rtc_device_register()
struct rtc_device *rtc_device_register(const char *name, struct device *dev,const struct rtc_class_ops *ops,struct module *owner)
{
       struct rtc_device *rtc;    //Define an rtc_device structure
       ... ...
       rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);  //Assign RTC_ The device structure is a global variable

 
       /*Set rtc_device*/
    rtc->id = id;
       rtc->ops = ops;            //Set s3c_rtcops is saved in RTC_ Device - > Ops
       rtc->owner = owner;
       rtc->max_user_freq = 64;
       rtc->dev.parent = dev;
       rtc->dev.class = rtc_class;
       rtc->dev.release = rtc_device_release;
       ... ...

       rtc_dev_prepare(rtc);                   //1. Prepare in advance and initialize the cdev structure
       ... ...
       rtc_dev_add_device(rtc);               //2. Create rtc related files under / dev and add cdev to the system

       rtc_sysfs_add_device(rtc);             //Create rtc related files under / sysfs
       rtc_proc_add_device(rtc);             //Create rtc related files under / proc
       ... ...
    return rtc;
}

RTC above_ dev_ Prepare (RTC) and rtc_dev_add_device(rtc) mainly does the following two things (located in. / Drivers / RTC / RTC dev.c):

cdev_init(&rtc->char_dev, &rtc_dev_fops);          //Bind file_operations  

cdev_add(&rtc->char_dev, rtc->dev.devt, 1);    //Register RTC - > char_ Dev character device, add a slave device to the system

Here, the process is similar to that of registering a character device driver.

  • Summary: so the "S3C2410 RTC" platform is device driven probe mainly does the following things:
    • 1. Set RTC related registers
    • 2. Assign rtc_device structure
    • 3. Set rtc_device structure
      • ->3.1 set struct rtc_class_ops s3c_rtcops into RTC_ Device - > OPS, which implements operations such as reading and writing time of RTC
    • 4 register RTC - > char_ Dev character device, and the operation structure of the character device is: struct file_operations rtc_dev_fops

1.2 rtc_dev_fops

  • When we apply layer open ("/ dev/rtcXX"), RTC will be called_ dev_ fops-> rtc_ dev_ open():
static int rtc_dev_open(struct inode *inode, struct file *file)
{
   struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);//Get the corresponding rtc_device
   const struct rtc_class_ops *ops = rtc->ops;                            //Eventually equal to s3c_rtcops

   file->private_data = rtc;                     //Set the private member of the file structure equal to rtc_device, when executing ioctl and other functions again, you can directly extract file - > private_ Data

   err = ops->open ? ops->open(rtc->dev.parent) : 0;  //Call s3c_ rtcops->open

   mutex_unlock(&rtc->char_lock);
   return err;
}

Obviously, RTC will eventually be called_ S3c under device_ rtcops->open:

And s3c_ rtc_ The open() function mainly applies for two interrupts, one alarm clock interrupt and one timing interrupt:

static int s3c_rtc_open(struct device *dev)
{     
 struct platform_device *pdev = to_platform_device(dev);    
 struct rtc_device *rtc_dev = platform_get_drvdata(pdev);      
 int ret;

 ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,IRQF_DISABLED,  "s3c2410-rtc alarm", rtc_dev);        //Request alarm clock interrupt                      
              if (ret) {
              dev_err(dev, "IRQ%d error %d\n", s3c_rtc_alarmno, ret);
              return ret;
       }

 

 ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,IRQF_DISABLED,  "s3c2410-rtc tick", rtc_dev);//Request timing interrupt   
       if (ret) {
              dev_err(dev, "IRQ%d error %d\n", s3c_rtc_tickno, ret);
              goto tick_err;
       }

       return ret;

 tick_err:
       free_irq(s3c_rtc_alarmno, rtc_dev);
       return ret;
}
  • When we open the application layer and use ioctl(int fd, unsigned long cmd,...), RTC will be called_ dev_ fops-> rtc_ dev_ ioctl ():
static int rtc_dev_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
{
struct rtc_device *rtc = file->private_data;  //Extract rtc_device
 void __user *uarg = (void __user *) arg;
  ... ...

 switch (cmd) {
       case RTC_EPOCH_SET:
       case RTC_SET_TIME:      //Set time
              if (!capable(CAP_SYS_TIME))
                     return -EACCES;
              break;
       case RTC_IRQP_SET:   //Change interrupt trigger speed
       ... ...}
       ... ...
       switch (cmd) {
       case RTC_ALM_READ:    //Read alarm time
              err = rtc_read_alarm(rtc, &alarm);              //Call s3c_ rtcops-> read_ alarm
              if (err < 0)
                     return err;

              if (copy_to_user(uarg, &alarm.time, sizeof(tm)))  //Long transmission time data
                     return -EFAULT;
                     break;

       case RTC_ALM_SET:              //Set the alarm time and call s3c_ rtcops-> set_ alarm
              ... ...

       case RTC_RD_TIME:              //Read RTC time and call s3c_ rtcops-> read_ alarm
              ... ...

       case RTC_SET_TIME:      //Write RTC time, call s3c_ rtcops-> set_ time
              ... ...

       case RTC_IRQP_SET:      //Change the interrupt trigger frequency and call s3c_ rtcops-> irq_ set_ freq
              ... ...

}

Finally, call s3c_ For the member function under rtcops, we use s3c_rtcops-> read_ Take the alarm() function as an example to see how to read the time:

static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
       unsigned int have_retried = 0;
       void __iomem *base = s3c_rtc_base;    //Get RTC related register base address
retry_get_time:

       /*Get the year, month, day, hour, minute and second registers*/
       rtc_tm->tm_min  = readb(base + S3C2410_RTCMIN);     
       rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);
       rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);
       rtc_tm->tm_mon  = readb(base + S3C2410_RTCMON);
       rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);
       rtc_tm->tm_sec  = readb(base + S3C2410_RTCSEC);

      
       /*  If it is judged that the second register is 0, it means that one minute has passed, then the values in the hour, day, month and other registers may have changed, and the values of these registers need to be read again*/
if (rtc_tm->tm_sec == 0 && !have_retried) {
              have_retried = 1;
              goto retry_get_time;
       }

       /*Convert the obtained register value into real time data*/
       BCD_TO_BIN(rtc_tm->tm_sec);
       BCD_TO_BIN(rtc_tm->tm_min);
       BCD_TO_BIN(rtc_tm->tm_hour);
       BCD_TO_BIN(rtc_tm->tm_mday);
       BCD_TO_BIN(rtc_tm->tm_mon);
       BCD_TO_BIN(rtc_tm->tm_year);

    rtc_tm->tm_year += 100;    //The memory stores the time since 1900, so add 100 
    rtc_tm->tm_mon -= 1;
    return 0;
}

Similarly, in s3c_ rtcops-> set_ In the time() function, the RTC time is also written to the relevant register

Therefore, it is summarized as follows:

  • rtc_ device->char_ Dev: character device, dealing with application layer and lower level functions
  • rtc_ Device - > Ops: a lower level operation function that directly operates hardware related registers, which is called RTC_ device->char_ Dev call

2. Modify the kernel and turn on rtc device support

We can't find the character device using ls /dev/rtc * on the board because only s3c is defined in the kernel_ device_ RTC, the RTC platform device, is not registered, so the platform driver is not matched. Next, let's modify the registration array in the kernel:

  • Enter arch / arm / plat-s3c24xx / common smdk c. After kernel version 2.6, enter arch / arm / mach-s3c24xx / common smdk c
  • In smdk_ Add RTC platform devices in devices [] (when the kernel starts, it will call this array and register all platform_device s in it)

  • Recompile and load new kernel

3. Testing

After startup, use ls /dev/rtc *. If you can see the character device rtc0, it means that the modification is successful

3.1 viewing time

There are two clocks in linux:

Hardware clock (clock in 2440 register), system clock (clock in kernel)

Therefore, there are two different commands: date command and hwlock command

Enter the date command to view the system clock:

# date
Wed Nov	3 12:23:20	UTC 2021

You can also specify the format to display the date, date "+% Y /% m /% d% H:% m:% s"

# date "+ %Y/%m/%d %H:%M:%S"
2021/11/03	12:23:20

3.2 setting time

Command format: date, month, day, hour, year second

# date 081512302021.30
Sun Aug 15 12:30:30	UTC 2021

3.3 synchronization to hardware time

Command: hwlock

Parameters:

- r, --show read hardware clock and print result
- s, --hctosys synchronize the hardware clock to the system clock (set the system time from the hardware clock)
- w, --systohc set the hardware clock to the current system time

# hwclock -r 
Wed Nov	3 12:23:20	 2021 0.000000 seconds	//Time before synchronization
#
# date 081512302021.30
Sun Aug 15 12:30:30	UTC 2021		//Current system time
#
# hwclock -w 						// Sync to hardware time
#
# hwclock -r
Sun Aug 15 12:30:33	 2021 0.000000 seconds	//Hardware time synchronized with system time

set the hardware clock to the current system time

# hwclock -r 
Wed Nov	3 12:23:20	 2021 0.000000 seconds	//Time before synchronization
#
# date 081512302021.30
Sun Aug 15 12:30:30	UTC 2021		//Current system time
#
# hwclock -w 						// Sync to hardware time
#
# hwclock -r
Sun Aug 15 12:30:33	 2021 0.000000 seconds	//Hardware time synchronized with system time

Keywords: rtc

Added by reub77 on Tue, 04 Jan 2022 16:19:06 +0200