[Beijing Xunwei] i.MX6ULL Terminator Linux RTC driver experiment RTC driver under i.MX6UL

RTC is a very common device, so most manufacturers will write it, so NXP has already implemented it officially, so we don't need to write it ourselves. But we need to understand how the RTC driver is implemented, let's analyze it below.
As a device, RTC needs device information and drivers. Let's first look at how device information is implemented in the device tree. Open the imx6ull.dtsi file and find the snvs_rtc device node. The content of the node is as follows:

1 snvs_rtc: snvs-rtc-lp {
    
     
2 		compatible = "fsl,sec-v4.0-mon-rtc-lp"; 
3 		regmap = <&snvs>; 
4 		offset = <0x34>;
5 		interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 20 
IRQ_TYPE_LEVEL_HIGH>; 
6 };

In line 2, the information specifying the compatible attribute value is "fsl,sec-v4.0-mon-rtc-lp", so the corresponding driver file can be found in the Linux kernel according to the compatible attribute value information, this file is drivers /rtc/rtc-snvs.c, the content in the rtc-snvs.c file is as follows:

380 static const struct of_device_id snvs_dt_ids[] = {
    
     
381 		{
    
     .compatible = "fsl,sec-v4.0-mon-rtc-lp", }, 
382 		{
    
     /* sentinel */ } 
383 }; 
384 MODULE_DEVICE_TABLE(of, snvs_dt_ids); 
385 
386 static struct platform_driver snvs_rtc_driver = {
    
     
387 		.driver = {
    
     
388 			.name = "snvs_rtc", 
389 			.pm = SNVS_RTC_PM_OPS, 
390 			.of_match_table = snvs_dt_ids, 
391 		}, 
392 		.probe = snvs_rtc_probe, 
393 }; 
394 module_platform_driver(snvs_rtc_driver);

In line 381, the value of compatible is also "fsl,sec-v4.0-mon-rtc-lp", so that the match can be successful.
The other content is the content of the standard platform framework. Then, let’s take a look at the snvs_rtc_probe function executed when the device and driver are successfully matched:

238 static int snvs_rtc_probe(struct platform_device *pdev) 
239 {
    
     
240        struct snvs_rtc_data *data; 
241        struct resource *res; 
242        int ret; 
243        void __iomem *mmio; 
244 
245        data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 
246        if (!data) 
247            return -ENOMEM; 
248 
249        data->regmap = 
syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap"); 
250 
251     if (IS_ERR(data->regmap)) {
    
     
252            dev_warn(&pdev->dev, "snvs rtc: you use old dts file,
please update it\n"); 
253            res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 
254 
255            mmio = devm_ioremap_resource(&pdev->dev, res); 
256            if (IS_ERR(mmio)) 
257                return PTR_ERR(mmio); 
258
259        data->regmap = devm_regmap_init_mmio(&pdev->dev, mmio, 
&snvs_rtc_config); 
260      } else {
    
     
261            data->offset = SNVS_LPREGISTER_OFFSET; 
262            of_property_read_u32(pdev->dev.of_node, "offset", &data->offset); 
263        } 
264 
265        if (!data->regmap) {
    
     
266            dev_err(&pdev->dev, "Can't find snvs syscon\n"); 
267            return -ENODEV; 
268        } 
269 
270        data->irq = platform_get_irq(pdev, 0); 
271        if (data->irq < 0) 
272            return data->irq;
...... 
285 
286        platform_set_drvdata(pdev, data); 
287 
288        /* Initialize glitch detect */ 
289        regmap_write(data->regmap, data->offset + SNVS_LPPGDR, 
SNVS_LPPGDR_INIT); 
290 
291        /* Clear interrupt status */ 
292        regmap_write(data->regmap, data->offset + SNVS_LPSR, 0xffffffff); 
293 
294        /* Enable RTC */ 
295        snvs_rtc_enable(data, true); 
296 
297        device_init_wakeup(&pdev->dev, true); 
298 
299        ret = devm_request_irq(&pdev->dev, data->irq, snvs_rtc_irq_handler, 
300                IRQF_SHARED, "rtc alarm", &pdev->dev);
301        if (ret) {
    
     
302            dev_err(&pdev->dev, "failed to request irq %d: %d\n", 
303                    data->irq, ret); 
304            goto error_rtc_device_register; 
305        } 
306 
307        data->rtc = devm_rtc_device_register(&pdev->dev, pdev->name, 
308                        &snvs_rtc_ops, THIS_MODULE); 
309        if (IS_ERR(data->rtc)) {
    
     
310            ret = PTR_ERR(data->rtc); 
311            dev_err(&pdev->dev, "failed to register rtc: %d\n", ret); 
312            goto error_rtc_device_register; 
313        } 
314 
315        return 0; 
316 
317    error_rtc_device_register: 
318        if (data->clk) 
319            clk_disable_unprepare(data->clk); 
320 
321        return ret; 
322 }

In line 253, call the platform_get_resource function to get the RTC peripheral register base address from the device tree.
In line 255, the function devm_ioremap_resource is called to complete the memory mapping and obtain the virtual address corresponding to the physical base address of the RTC peripheral register.
In line 259, Linux 3.1 introduces a brand new regmap mechanism. Regmap is used to provide a set of convenient API functions to manipulate the underlying hardware registers to improve code reusability. The snvs-rtc.c file will use the regmap mechanism to read and write the RTC low-level hardware registers. Here, use devm_regmap_init_mmio function to convert RTC hardware registers into regmap form, so that API functions such as regmap_write and regmap_read of regmap mechanism can operate registers.
In line 270, get the RTC interrupt number from the device tree.
Line 289, set the RTC_LPPGDR register value to SNVS_LPPGDR_INIT = 0x41736166, here is the regmap_write function of the regmap mechanism to complete the write operation to the register.
Line 292, set the RTC_LPSR register, write 0xffffffff, LPSR is the RTC status register, write 1 to clear it, so this step is to clear the LPSR register.
On line 295, call the snvs_rtc_enable function to enable RTC, which will set the RTC_LPCR register.
In line 299, the devm_request_irq function is called to request RTC interrupt. The interrupt service function is snvs_rtc_irq_handler, which is used for RTC alarm interrupt.
In line 307, the devm_rtc_device_register function is called to register rtc_devcie with the system. The RTC underlying driver set is snvs_rtc_ops. The snvs_rtc_ops operation set includes functions such as reading/setting RTC time, reading/setting alarm clock and so on. The content of snvs_rtc_ops is as follows:

200 static const struct rtc_class_ops snvs_rtc_ops = {
    
     
201 		.read_time = snvs_rtc_read_time,
202 		.set_time = snvs_rtc_set_time, 
203 		.read_alarm = snvs_rtc_read_alarm, 
204 		.set_alarm = snvs_rtc_set_alarm, 
205 		.alarm_irq_enable = snvs_rtc_alarm_irq_enable, 
206 }; 

Let's take the snvs_rtc_read_time function on line 201 as an example to explain how to write each RTC low-level operation function of rtc_class_ops. The snvs_rtc_read_time function is used to read the RTC time value. The content of this function is as follows:

126 static int snvs_rtc_read_time(struct device *dev, struct rtc_time *tm) 
127 {
    
     
128        struct snvs_rtc_data *data = dev_get_drvdata(dev); 
129        unsigned long time = rtc_read_lp_counter(data); 
130 
131        rtc_time_to_tm(time, tm); 
132 
133        return 0; 
134 }

In line 129, call rtc_read_lp_counter to get the RTC count value, which is the number of seconds.
On line 131, the rtc_time_to_tm function is called to convert the obtained number of seconds into a time value, which is the rtc_time structure type. The rtc_time structure is defined as follows:

20 struct rtc_time {
    
     
21  int tm_sec; 
22     int tm_min; 
23     int tm_hour; 
24     int tm_mday; 
25     int tm_mon; 
26     int tm_year; 
27     int tm_wday; 
28     int tm_yday; 
29  int tm_isdst; 
30 };

Finally, let's take a look at the rtc_read_lp_counter function, this function is used to read the RTC count value, part of the function is as follows:

50 static u32 rtc_read_lp_counter(struct snvs_rtc_data *data) 
51 {
    
     
52         u64 read1, read2; 
53         u32 val; 
54 
55         do {
    
     
56             regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR,&val); 
57             read1 = val; 
58             read1 <<= 32; 
59             regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val); 
60             read1 |= val; 
61 
62             regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &val);
63             read2 = val; 
64             read2 <<= 32; 
65             regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &val); 
66             read2 |= val; 
67             /* 
68             * when CPU/BUS are running at low speed, there is chance that 
69             * we never get same value during two consecutive read, so here 
70             * we only
71             */ 
72         } while ((read1 >> CNTR_TO_SECS_SH) != (read2 >> CNTR_TO_SECS_SH)); 
73 
74         /* Convert 47-bit counter to 32-bit raw second count */ 
75         return (u32) (read1 >> CNTR_TO_SECS_SH); 
76 }

Lines 56~72, read the two registers RTC_LPSRTCMR and RTC_LPSRTCLR to get the RTC count value in seconds, which is the current time. The RTC count value is read twice here. Because two registers are to be read, the time data may be updated when the second register is read, resulting in a time mismatch. Therefore, read twice in succession here. If the time values ​​are equal, then the time data is valid.
Line 75, return the time value. Note that the RTC count value read earlier is shifted by 15 bits to the right.
This is how the snvs_rtc_read_time function reads the RTC time value. The implementation of other underlying operation functions is similar, so I won’t analyze it here.

Insert picture description here

Guess you like

Origin blog.csdn.net/BeiJingXunWei/article/details/112358851