CINXE.COM

LKML: Ciprian Marian Costea: Re: [PATCH v6 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>LKML: Ciprian Marian Costea: Re: [PATCH v6 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support</title><link href="/css/message.css" rel="stylesheet" type="text/css" /><link href="/css/wrap.css" rel="alternate stylesheet" type="text/css" title="wrap" /><link href="/css/nowrap.css" rel="stylesheet" type="text/css" title="nowrap" /><link href="/favicon.ico" rel="shortcut icon" /><script src="/js/simple-calendar.js" type="text/javascript"></script><script src="/js/styleswitcher.js" type="text/javascript"></script><link rel="alternate" type="application/rss+xml" title="lkml.org : last 100 messages" href="/rss.php" /><link rel="alternate" type="application/rss+xml" title="lkml.org : last messages by Ciprian Marian Costea" href="/groupie.php?aid=" /><!--Matomo--><script> var _paq = window._paq = window._paq || []; /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ _paq.push(["setDoNotTrack", true]); _paq.push(["disableCookies"]); _paq.push(['trackPageView']); _paq.push(['enableLinkTracking']); (function() { var u="//m.lkml.org/"; _paq.push(['setTrackerUrl', u+'matomo.php']); _paq.push(['setSiteId', '1']); var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); })(); </script><!--End Matomo Code--></head><body onload="es.jasper.simpleCalendar.init();" itemscope="itemscope" itemtype="http://schema.org/BlogPosting"><table border="0" cellpadding="0" cellspacing="0"><tr><td width="180" align="center"><a href="/"><img style="border:0;width:135px;height:32px" src="/images/toprowlk.gif" alt="lkml.org" /></a></td><td width="32">聽</td><td class="nb"><div><a class="nb" href="/lkml"> [lkml]</a> 聽 <a class="nb" href="/lkml/2025"> [2025]</a> 聽 <a class="nb" href="/lkml/2025/2"> [Feb]</a> 聽 <a class="nb" href="/lkml/2025/2/6"> [6]</a> 聽 <a class="nb" href="/lkml/last100"> [last100]</a> 聽 <a href="/rss.php"><img src="/images/rss-or.gif" border="0" alt="RSS Feed" /></a></div><div>Views: <a href="#" class="nowrap" onclick="setActiveStyleSheet('wrap');return false;">[wrap]</a><a href="#" class="wrap" onclick="setActiveStyleSheet('nowrap');return false;">[no wrap]</a> 聽 <a class="nb" href="/lkml/mheaders/2025/2/6/528" onclick="this.href='/lkml/headers'+'/2025/2/6/528';">[headers]</a>聽 <a href="/lkml/bounce/2025/2/6/528">[forward]</a>聽 </div></td><td width="32">聽</td></tr><tr><td valign="top"><div class="es-jasper-simpleCalendar" baseurl="/lkml/"></div><div class="threadlist">Messages in this thread</div><ul class="threadlist"><li class="root"><a href="/lkml/2024/12/6/275">First message in thread</a></li><li><a href="/lkml/2024/12/6/277">Ciprian Costea</a><ul><li><a href="/lkml/2024/12/6/330">"Arnd Bergmann"</a><ul><li><a href="/lkml/2024/12/6/734">Ciprian Marian Costea</a><ul><li><a href="/lkml/2024/12/6/777">"Arnd Bergmann"</a><ul><li><a href="/lkml/2024/12/9/1479">Ciprian Marian Costea</a></li></ul></li></ul></li></ul></li><li><a href="/lkml/2024/12/10/239">kernel test robot</a></li><li><a href="/lkml/2024/12/11/59">Alexandre Belloni</a><ul><li class="origin"><a href="">Ciprian Marian Costea</a></li></ul></li></ul></li></ul></td><td width="32" rowspan="2" class="c" valign="top"><img src="/images/icornerl.gif" width="32" height="32" alt="/" /></td><td class="c" rowspan="2" valign="top" style="padding-top: 1em"><table><tr><td><table><tr><td class="lp">Date</td><td class="rp" itemprop="datePublished">Thu, 6 Feb 2025 12:36:02 +0200</td></tr><tr><td class="lp">Subject</td><td class="rp" itemprop="name">Re: [PATCH v6 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support</td></tr><tr><td class="lp">From</td><td class="rp" itemprop="author">Ciprian Marian Costea &lt;&gt;</td></tr></table></td><td></td></tr></table><pre itemprop="articleBody">On 12/11/2024 1:25 AM, Alexandre Belloni wrote:<br /><br />Hello Alexandre,<br /><br />Thank you for your detailed review on this driver.<br />Sorry for responding to this review so late, I've just now found some <br />time to continue work on this RTC driver.<br /><br />&gt; On 06/12/2024 09:09:53+0200, Ciprian Costea wrote:<br />&gt;&gt; +/*<br />&gt;&gt; + * S32G2 and S32G3 SoCs have RTC clock source1 reserved and<br />&gt;&gt; + * should not be used.<br />&gt;&gt; + */<br />&gt;&gt; +#define RTC_CLK_SRC1_RESERVED BIT(1)<br />&gt;&gt; +<br />&gt;&gt; +enum {<br />&gt;&gt; + DIV1 = 1,<br />&gt;&gt; + DIV32 = 32,<br />&gt;&gt; + DIV512 = 512,<br />&gt;&gt; + DIV512_32 = 16384<br />&gt;&gt; +};<br />&gt;&gt; +<br />&gt;&gt; +static const char *rtc_clk_src[RTC_CLK_MUX_SIZE] = {<br />&gt;&gt; + "source0",<br />&gt;&gt; + "source1",<br />&gt;&gt; + "source2",<br />&gt;&gt; + "source3"<br />&gt;&gt; +};<br />&gt;&gt; +<br />&gt;&gt; +struct rtc_time_base {<br />&gt;&gt; + s64 sec;<br />&gt;&gt; + u64 cycles;<br />&gt;&gt; + struct rtc_time tm;<br />&gt; &gt; I don't think storing an rtc_time is necessary. I don't even think<br />&gt; cycles is necessary.<br />&gt; <br /><br />Indeed, switching to usage of just APIVAL (as you've suggested) instead <br />of relying also on RTCVAL would make 'rtc_time' redundant. Will remove.<br /><br />&gt;&gt; +};<br />&gt;&gt; +<br />&gt;&gt; +struct rtc_priv {<br />&gt;&gt; + struct rtc_device *rdev;<br />&gt;&gt; + void __iomem *rtc_base;<br />&gt;&gt; + struct clk *ipg;<br />&gt;&gt; + struct clk *clk_src;<br />&gt;&gt; + const struct rtc_soc_data *rtc_data;<br />&gt;&gt; + struct rtc_time_base base;<br />&gt;&gt; + u64 rtc_hz;<br />&gt;&gt; + int irq;<br />&gt;&gt; + int clk_src_idx;<br />&gt;&gt; +};<br />&gt;&gt; +<br />&gt;&gt; +struct rtc_soc_data {<br />&gt;&gt; + u32 clk_div;<br />&gt;&gt; + u32 reserved_clk_mask;<br />&gt;&gt; +};<br />&gt;&gt; +<br />&gt;&gt; +static const struct rtc_soc_data rtc_s32g2_data = {<br />&gt;&gt; + .clk_div = DIV512,<br />&gt; <br />&gt; If you input clock rate is higher that 16kHz, why don't you divide by<br />&gt; 16384?<br />&gt; <br /><br />Yes, the default input clock rate is ~32KHz. I will enable both divisors <br />to increase the RTC counter resolution.<br /><br />&gt;&gt; + .reserved_clk_mask = RTC_CLK_SRC1_RESERVED,<br />&gt;&gt; +};<br />&gt;&gt; +<br />&gt;&gt; +static u64 cycles_to_sec(u64 hz, u64 cycles)<br />&gt;&gt; +{<br />&gt;&gt; + return div_u64(cycles, hz);<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +/**<br />&gt;&gt; + * sec_to_rtcval - Convert a number of seconds to a value suitable for<br />&gt;&gt; + * RTCVAL in our clock's<br />&gt;&gt; + * current configuration.<br />&gt;&gt; + * &#64;priv: Pointer to the 'rtc_priv' structure<br />&gt;&gt; + * &#64;seconds: Number of seconds to convert<br />&gt;&gt; + * &#64;rtcval: The value to go into RTCVAL[RTCVAL]<br />&gt;&gt; + *<br />&gt;&gt; + * Return: 0 for success, -EINVAL if &#64;seconds push the counter past the<br />&gt;&gt; + * 32bit register range<br />&gt;&gt; + */<br />&gt;&gt; +static int sec_to_rtcval(const struct rtc_priv *priv,<br />&gt;&gt; + unsigned long seconds, u32 *rtcval)<br />&gt;&gt; +{<br />&gt;&gt; + u32 delta_cnt;<br />&gt;&gt; +<br />&gt;&gt; + if (!seconds || seconds &gt; cycles_to_sec(priv-&gt;rtc_hz, RTCCNT_MAX_VAL))<br />&gt;&gt; + return -EINVAL;<br />&gt;&gt; +<br />&gt;&gt; + /*<br />&gt;&gt; + * RTCCNT is read-only; we must return a value relative to the<br />&gt;&gt; + * current value of the counter (and hope we don't linger around<br />&gt;&gt; + * too much before we get to enable the interrupt)<br />&gt;&gt; + */<br />&gt;&gt; + delta_cnt = seconds * priv-&gt;rtc_hz;<br />&gt;&gt; + *rtcval = delta_cnt + ioread32(priv-&gt;rtc_base + RTCCNT_OFFSET);<br />&gt;&gt; +<br />&gt;&gt; + return 0;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static irqreturn_t s32g_rtc_handler(int irq, void *dev)<br />&gt;&gt; +{<br />&gt;&gt; + struct rtc_priv *priv = platform_get_drvdata(dev);<br />&gt;&gt; + u32 status;<br />&gt;&gt; +<br />&gt;&gt; + status = ioread32(priv-&gt;rtc_base + RTCS_OFFSET);<br />&gt;&gt; +<br />&gt;&gt; + if (status &amp; RTCS_RTCF) {<br />&gt;&gt; + iowrite32(0x0, priv-&gt;rtc_base + RTCVAL_OFFSET);<br />&gt;&gt; + iowrite32(status | RTCS_RTCF, priv-&gt;rtc_base + RTCS_OFFSET);<br />&gt;&gt; + rtc_update_irq(priv-&gt;rdev, 1, RTC_AF);<br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + if (status &amp; RTCS_APIF) {<br />&gt;&gt; + iowrite32(status | RTCS_APIF, priv-&gt;rtc_base + RTCS_OFFSET);<br />&gt;&gt; + rtc_update_irq(priv-&gt;rdev, 1, RTC_PF);<br />&gt; <br />&gt; I don't think you use APIF as a periodic interrupt so it doesn't really<br />&gt; make sense to use RTC_PF instead of RTC_AF.<br />&gt; <br /><br />Correct. I will change to `rtc_update_irq(priv-&gt;rdev, 1, RTC_IRQF | <br />RTC_AF);`<br /><br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + return IRQ_HANDLED;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static s64 s32g_rtc_get_time_or_alrm(struct rtc_priv *priv,<br />&gt;&gt; + u32 offset)<br />&gt;&gt; +{<br />&gt;&gt; + u32 counter;<br />&gt;&gt; +<br />&gt;&gt; + counter = ioread32(priv-&gt;rtc_base + offset);<br />&gt;&gt; +<br />&gt;&gt; + if (counter &lt; priv-&gt;base.cycles)<br />&gt;&gt; + return -EINVAL;<br />&gt;&gt; +<br />&gt;&gt; + counter -= priv-&gt;base.cycles;<br />&gt;&gt; +<br />&gt;&gt; + return priv-&gt;base.sec + cycles_to_sec(priv-&gt;rtc_hz, counter);<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static int s32g_rtc_read_time(struct device *dev,<br />&gt;&gt; + struct rtc_time *tm)<br />&gt;&gt; +{<br />&gt;&gt; + struct rtc_priv *priv = dev_get_drvdata(dev);<br />&gt;&gt; + s64 sec;<br />&gt;&gt; +<br />&gt;&gt; + sec = s32g_rtc_get_time_or_alrm(priv, RTCCNT_OFFSET);<br />&gt;&gt; + if (sec &lt; 0)<br />&gt;&gt; + return -EINVAL;<br />&gt;&gt; +<br />&gt;&gt; + rtc_time64_to_tm(sec, tm);<br />&gt;&gt; +<br />&gt;&gt; + return 0;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static int s32g_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)<br />&gt;&gt; +{<br />&gt;&gt; + struct rtc_priv *priv = dev_get_drvdata(dev);<br />&gt;&gt; + u32 rtcc, rtccnt, rtcval;<br />&gt;&gt; + s64 sec;<br />&gt;&gt; +<br />&gt;&gt; + sec = s32g_rtc_get_time_or_alrm(priv, RTCVAL_OFFSET);<br />&gt;&gt; + if (sec &lt; 0)<br />&gt;&gt; + return -EINVAL;<br />&gt;&gt; +<br />&gt;&gt; + rtc_time64_to_tm(sec, &amp;alrm-&gt;time);<br />&gt;&gt; +<br />&gt;&gt; + rtcc = ioread32(priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; + alrm-&gt;enabled = sec &amp;&amp; (rtcc &amp; RTCC_RTCIE);<br />&gt;&gt; +<br />&gt;&gt; + alrm-&gt;pending = 0;<br />&gt;&gt; + if (alrm-&gt;enabled) {<br />&gt;&gt; + rtccnt = ioread32(priv-&gt;rtc_base + RTCCNT_OFFSET);<br />&gt;&gt; + rtcval = ioread32(priv-&gt;rtc_base + RTCVAL_OFFSET);<br />&gt;&gt; +<br />&gt;&gt; + if (rtccnt &lt; rtcval)<br />&gt;&gt; + alrm-&gt;pending = 1;<br />&gt; <br />&gt; This limits the range of your alarm, why don't you simply check whether<br />&gt; RTCF is set?<br />&gt; <br /><br />Nice idea. I will refactor in V7.<br /><br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + return 0;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static int s32g_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)<br />&gt;&gt; +{<br />&gt;&gt; + struct rtc_priv *priv = dev_get_drvdata(dev);<br />&gt;&gt; + u32 rtcc;<br />&gt;&gt; +<br />&gt;&gt; + if (!priv-&gt;irq)<br />&gt;&gt; + return -EIO;<br />&gt; <br />&gt; <br />&gt; This will never happen as you are not letting probe finish when you<br />&gt; can't request the irq.<br />&gt; &gt;<br /><br />Correct. I will remove this redundant check.<br /><br />&gt;&gt; +<br />&gt;&gt; + rtcc = ioread32(priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; + if (enabled)<br />&gt;&gt; + rtcc |= RTCC_RTCIE;<br />&gt;&gt; +<br />&gt;&gt; + iowrite32(rtcc, priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; +<br />&gt;&gt; + return 0;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static int s32g_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)<br />&gt;&gt; +{<br />&gt;&gt; + struct rtc_priv *priv = dev_get_drvdata(dev);<br />&gt;&gt; + struct rtc_time time_crt;<br />&gt;&gt; + long long t_crt, t_alrm;<br />&gt;&gt; + u32 rtcval, rtcs;<br />&gt;&gt; + int ret = 0;<br />&gt;&gt; +<br />&gt;&gt; + iowrite32(0x0, priv-&gt;rtc_base + RTCVAL_OFFSET);<br />&gt;&gt; +<br />&gt;&gt; + t_alrm = rtc_tm_to_time64(&amp;alrm-&gt;time);<br />&gt;&gt; +<br />&gt;&gt; + /*<br />&gt;&gt; + * Assuming the alarm is being set relative to the same time<br />&gt;&gt; + * returned by our s32g_rtc_read_time callback<br />&gt;&gt; + */<br />&gt;&gt; + ret = s32g_rtc_read_time(dev, &amp;time_crt);<br />&gt;&gt; + if (ret)<br />&gt;&gt; + return ret;<br />&gt;&gt; +<br />&gt;&gt; + t_crt = rtc_tm_to_time64(&amp;time_crt);<br />&gt;&gt; + ret = sec_to_rtcval(priv, t_alrm - t_crt, &amp;rtcval);<br />&gt;&gt; + if (ret) {<br />&gt;&gt; + dev_warn(dev, "Alarm is set too far in the future\n");<br />&gt;&gt; + return -ERANGE;<br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + ret = read_poll_timeout(ioread32, rtcs, !(rtcs &amp; RTCS_INV_RTC),<br />&gt;&gt; + 0, RTC_SYNCH_TIMEOUT, false, priv-&gt;rtc_base + RTCS_OFFSET);<br />&gt;&gt; + if (ret)<br />&gt;&gt; + return ret;<br />&gt;&gt; +<br />&gt;&gt; + iowrite32(rtcval, priv-&gt;rtc_base + RTCVAL_OFFSET);<br />&gt;&gt; +<br />&gt;&gt; + return 0;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static int s32g_rtc_set_time(struct device *dev,<br />&gt;&gt; + struct rtc_time *time)<br />&gt;&gt; +{<br />&gt;&gt; + struct rtc_priv *priv = dev_get_drvdata(dev);<br />&gt;&gt; +<br />&gt;&gt; + priv-&gt;base.cycles = ioread32(priv-&gt;rtc_base + RTCCNT_OFFSET);<br />&gt;&gt; + priv-&gt;base.sec = rtc_tm_to_time64(time);<br />&gt;&gt; +<br />&gt; <br />&gt; To simplify all the calculations you are doing, I suggest you reset<br />&gt; RTCCNT here and store the epoch of the rtc as a number of seconds.<br />&gt; <br />&gt; This wll allow you to avoid having to read the counter in set_alarm<br />&gt; also, you then get a direct conversion for RTCVAL as this will simply be<br />&gt; rtc_tm_to_time64(&amp;alrm-&gt;time) - epoch that you have to convert in cycles<br />&gt; <br />&gt; You will also then know right away whether this is too large to fit in a<br />&gt; 32bit register.<br />&gt; <br />&gt; <br /><br />Unfortunatelly, the RTCCNT register is not writable. Hence it cannot be <br />reset. The only way to reset it would be to disable &amp; enable the RTC <br />module via 'RTCC_CNTEN' which would not be acceptable in this callback <br />and in the end it is something to be avoided.<br /><br />Nevertheless, I believe by using just APIVAL (as you've suggested) <br />instead of relying also on RTCVAL would greatly reduce the complexity of <br />this driver. I will refactor in V7.<br /><br />&gt;&gt; + return 0;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +/*<br />&gt;&gt; + * Disable the 32-bit free running counter.<br />&gt;&gt; + * This allows Clock Source and Divisors selection<br />&gt;&gt; + * to be performed without causing synchronization issues.<br />&gt;&gt; + */<br />&gt;&gt; +static void s32g_rtc_disable(struct rtc_priv *priv)<br />&gt;&gt; +{<br />&gt;&gt; + u32 rtcc = ioread32(priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; +<br />&gt;&gt; + rtcc &amp;= ~RTCC_CNTEN;<br />&gt;&gt; + iowrite32(rtcc, priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static void s32g_rtc_enable(struct rtc_priv *priv)<br />&gt;&gt; +{<br />&gt;&gt; + u32 rtcc = ioread32(priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; +<br />&gt;&gt; + rtcc |= RTCC_CNTEN;<br />&gt;&gt; + iowrite32(rtcc, priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static int rtc_clk_src_setup(struct rtc_priv *priv)<br />&gt;&gt; +{<br />&gt;&gt; + u32 rtcc = 0;<br />&gt;&gt; +<br />&gt;&gt; + if (priv-&gt;rtc_data-&gt;reserved_clk_mask &amp; (1 &lt;&lt; priv-&gt;clk_src_idx))<br />&gt;&gt; + return -EOPNOTSUPP;<br />&gt;&gt; +<br />&gt;&gt; + rtcc = FIELD_PREP(RTCC_CLKSEL_MASK, priv-&gt;clk_src_idx);<br />&gt;&gt; +<br />&gt;&gt; + switch (priv-&gt;rtc_data-&gt;clk_div) {<br />&gt;&gt; + case DIV512_32:<br />&gt;&gt; + rtcc |= RTCC_DIV512EN;<br />&gt;&gt; + rtcc |= RTCC_DIV32EN;<br />&gt;&gt; + break;<br />&gt;&gt; + case DIV512:<br />&gt;&gt; + rtcc |= RTCC_DIV512EN;<br />&gt;&gt; + break;<br />&gt;&gt; + case DIV32:<br />&gt;&gt; + rtcc |= RTCC_DIV32EN;<br />&gt;&gt; + break;<br />&gt;&gt; + case DIV1:<br />&gt;&gt; + break;<br />&gt;&gt; + default:<br />&gt;&gt; + return -EINVAL;<br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + rtcc |= RTCC_RTCIE;<br />&gt;&gt; + /*<br />&gt;&gt; + * Make sure the CNTEN is 0 before we configure<br />&gt;&gt; + * the clock source and dividers.<br />&gt;&gt; + */<br />&gt;&gt; + s32g_rtc_disable(priv);<br />&gt;&gt; + iowrite32(rtcc, priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; + s32g_rtc_enable(priv);<br />&gt;&gt; +<br />&gt;&gt; + return 0;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static const struct rtc_class_ops rtc_ops = {<br />&gt;&gt; + .read_time = s32g_rtc_read_time,<br />&gt;&gt; + .set_time = s32g_rtc_set_time,<br />&gt;&gt; + .read_alarm = s32g_rtc_read_alarm,<br />&gt;&gt; + .set_alarm = s32g_rtc_set_alarm,<br />&gt;&gt; + .alarm_irq_enable = s32g_rtc_alarm_irq_enable,<br />&gt;&gt; +};<br />&gt;&gt; +<br />&gt;&gt; +static int rtc_clk_dts_setup(struct rtc_priv *priv,<br />&gt;&gt; + struct device *dev)<br />&gt;&gt; +{<br />&gt;&gt; + int i;<br />&gt;&gt; +<br />&gt;&gt; + priv-&gt;ipg = devm_clk_get_enabled(dev, "ipg");<br />&gt;&gt; + if (IS_ERR(priv-&gt;ipg))<br />&gt;&gt; + return dev_err_probe(dev, PTR_ERR(priv-&gt;ipg),<br />&gt;&gt; + "Failed to get 'ipg' clock\n");<br />&gt;&gt; +<br />&gt;&gt; + for (i = 0; i &lt; RTC_CLK_MUX_SIZE; i++) {<br />&gt;&gt; + priv-&gt;clk_src = devm_clk_get_enabled(dev, rtc_clk_src[i]);<br />&gt;&gt; + if (!IS_ERR(priv-&gt;clk_src)) {<br />&gt;&gt; + priv-&gt;clk_src_idx = i;<br />&gt;&gt; + break;<br />&gt;&gt; + }<br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + if (IS_ERR(priv-&gt;clk_src))<br />&gt;&gt; + return dev_err_probe(dev, PTR_ERR(priv-&gt;clk_src),<br />&gt;&gt; + "Failed to get rtc module clock source\n");<br />&gt;&gt; +<br />&gt;&gt; + return 0;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static int s32g_rtc_probe(struct platform_device *pdev)<br />&gt;&gt; +{<br />&gt;&gt; + struct device *dev = &amp;pdev-&gt;dev;<br />&gt;&gt; + struct rtc_priv *priv;<br />&gt;&gt; + int ret = 0;<br />&gt;&gt; +<br />&gt;&gt; + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);<br />&gt;&gt; + if (!priv)<br />&gt;&gt; + return -ENOMEM;<br />&gt;&gt; +<br />&gt;&gt; + priv-&gt;rtc_data = of_device_get_match_data(dev);<br />&gt;&gt; + if (!priv-&gt;rtc_data)<br />&gt;&gt; + return -ENODEV;<br />&gt;&gt; +<br />&gt;&gt; + priv-&gt;rtc_base = devm_platform_ioremap_resource(pdev, 0);<br />&gt;&gt; + if (IS_ERR(priv-&gt;rtc_base))<br />&gt;&gt; + return PTR_ERR(priv-&gt;rtc_base);<br />&gt;&gt; +<br />&gt;&gt; + device_init_wakeup(dev, true);<br />&gt;&gt; +<br />&gt;&gt; + ret = rtc_clk_dts_setup(priv, dev);<br />&gt;&gt; + if (ret)<br />&gt;&gt; + return ret;<br />&gt;&gt; +<br />&gt;&gt; + priv-&gt;rdev = devm_rtc_allocate_device(dev);<br />&gt;&gt; + if (IS_ERR(priv-&gt;rdev))<br />&gt;&gt; + return PTR_ERR(priv-&gt;rdev);<br />&gt;&gt; +<br />&gt;&gt; + ret = rtc_clk_src_setup(priv);<br />&gt;&gt; + if (ret)<br />&gt;&gt; + return ret;<br />&gt;&gt; +<br />&gt;&gt; + priv-&gt;rtc_hz = clk_get_rate(priv-&gt;clk_src);<br />&gt;&gt; + if (!priv-&gt;rtc_hz) {<br />&gt;&gt; + dev_err(dev, "Failed to get RTC frequency\n");<br />&gt;&gt; + ret = -EINVAL;<br />&gt;&gt; + goto disable_rtc;<br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + priv-&gt;rtc_hz /= priv-&gt;rtc_data-&gt;clk_div;<br />&gt;&gt; +<br />&gt;&gt; + platform_set_drvdata(pdev, priv);<br />&gt;&gt; + priv-&gt;rdev-&gt;ops = &amp;rtc_ops;<br />&gt;&gt; +<br />&gt;&gt; + priv-&gt;irq = platform_get_irq(pdev, 0);<br />&gt;&gt; + if (priv-&gt;irq &lt; 0) {<br />&gt;&gt; + ret = priv-&gt;irq;<br />&gt;&gt; + goto disable_rtc;<br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + ret = devm_request_irq(dev, priv-&gt;irq,<br />&gt;&gt; + s32g_rtc_handler, 0, dev_name(dev), pdev);<br />&gt;&gt; + if (ret) {<br />&gt;&gt; + dev_err(dev, "Request interrupt %d failed, error: %d\n",<br />&gt;&gt; + priv-&gt;irq, ret);<br />&gt;&gt; + goto disable_rtc;<br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + ret = devm_rtc_register_device(priv-&gt;rdev);<br />&gt;&gt; + if (ret)<br />&gt;&gt; + goto disable_rtc;<br />&gt;&gt; +<br />&gt;&gt; + return 0;<br />&gt;&gt; +<br />&gt;&gt; +disable_rtc:<br />&gt;&gt; + s32g_rtc_disable(priv);<br />&gt;&gt; + return ret;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static void s32g_enable_api_irq(struct device *dev, unsigned int enabled)<br />&gt;&gt; +{<br />&gt;&gt; + struct rtc_priv *priv = dev_get_drvdata(dev);<br />&gt;&gt; + u32 api_irq = RTCC_APIEN | RTCC_APIIE;<br />&gt;&gt; + u32 rtcc;<br />&gt;&gt; +<br />&gt;&gt; + rtcc = ioread32(priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; + if (enabled)<br />&gt;&gt; + rtcc |= api_irq;<br />&gt;&gt; + else<br />&gt;&gt; + rtcc &amp;= ~api_irq;<br />&gt;&gt; + iowrite32(rtcc, priv-&gt;rtc_base + RTCC_OFFSET);<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static int s32g_rtc_suspend(struct device *dev)<br />&gt;&gt; +{<br />&gt;&gt; + struct rtc_priv *init_priv = dev_get_drvdata(dev);<br />&gt;&gt; + struct rtc_priv priv;<br />&gt;&gt; + long long base_sec;<br />&gt;&gt; + u32 rtcval, rtccnt, offset;<br />&gt;&gt; + int ret = 0;<br />&gt;&gt; + u32 sec;<br />&gt;&gt; +<br />&gt;&gt; + if (!device_may_wakeup(dev))<br />&gt;&gt; + return 0;<br />&gt;&gt; +<br />&gt;&gt; + /* Save last known timestamp */<br />&gt;&gt; + ret = s32g_rtc_read_time(dev, &amp;init_priv-&gt;base.tm);<br />&gt;&gt; + if (ret)<br />&gt;&gt; + return ret;<br />&gt; <br />&gt; I don't think that whole calculation is necessary as you are never<br />&gt; actually resetting RTCCNT in suspend<br />&gt; <br /><br />The RTC module needs to be reinitialized during resume, because the RTC <br />registers are being reset during Standby/Suspend to RAM operations.<br /><br />Nevertheless, I will greatly reduce the complexity of these calculations <br />in V7.<br /><br />&gt;&gt; +<br />&gt;&gt; + /*<br />&gt;&gt; + * Use a local copy of the RTC control block to<br />&gt;&gt; + * avoid restoring it on resume path.<br />&gt;&gt; + */<br />&gt;&gt; + memcpy(&amp;priv, init_priv, sizeof(priv));<br />&gt;&gt; +<br />&gt;&gt; + rtccnt = ioread32(init_priv-&gt;rtc_base + RTCCNT_OFFSET);<br />&gt;&gt; + rtcval = ioread32(init_priv-&gt;rtc_base + RTCVAL_OFFSET);<br />&gt;&gt; + offset = rtcval - rtccnt;<br />&gt;&gt; + sec = cycles_to_sec(init_priv-&gt;rtc_hz, offset);<br />&gt;&gt; +<br />&gt;&gt; + /* Adjust for the number of seconds we'll be asleep */<br />&gt;&gt; + base_sec = rtc_tm_to_time64(&amp;init_priv-&gt;base.tm);<br />&gt;&gt; + base_sec += sec;<br />&gt;&gt; + rtc_time64_to_tm(base_sec, &amp;init_priv-&gt;base.tm);<br />&gt;&gt; +<br />&gt;&gt; + ret = sec_to_rtcval(&amp;priv, sec, &amp;rtcval);<br />&gt;&gt; + if (ret) {<br />&gt;&gt; + dev_warn(dev, "Alarm is too far in the future\n");<br />&gt;&gt; + return -ERANGE;<br />&gt;&gt; + }<br />&gt;&gt; +<br />&gt;&gt; + s32g_enable_api_irq(dev, 1);<br />&gt;&gt; + iowrite32(offset, priv.rtc_base + APIVAL_OFFSET);<br />&gt; <br />&gt; What about always using APIVAL instead of RTCVAL so you don't have<br />&gt; anything to do in s32g_rtc_suspend.<br />&gt; <br /><br />This is a great idea. I will update in V7.<br /><br />&gt; <br />&gt;&gt; +<br />&gt;&gt; + return ret;<br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static int s32g_rtc_resume(struct device *dev)<br />&gt;&gt; +{<br />&gt;&gt; + struct rtc_priv *priv = dev_get_drvdata(dev);<br />&gt;&gt; + int ret;<br />&gt;&gt; +<br />&gt;&gt; + if (!device_may_wakeup(dev))<br />&gt;&gt; + return 0;<br />&gt;&gt; +<br />&gt;&gt; + /* Disable wake-up interrupts */<br />&gt;&gt; + s32g_enable_api_irq(dev, 0);<br />&gt;&gt; +<br />&gt;&gt; + ret = rtc_clk_src_setup(priv);<br />&gt;&gt; + if (ret)<br />&gt;&gt; + return ret;<br />&gt; <br />&gt; I don't think this is necessary.<br /><br />It is, because RTC registers are being reset during Standby/Suspend to <br />RAM operations.<br /><br />&gt;&gt; +<br />&gt;&gt; + /*<br />&gt;&gt; + * Now RTCCNT has just been reset, and is out of sync with priv-&gt;base;<br />&gt;&gt; + * reapply the saved time settings.<br />&gt;&gt; + */<br />&gt;&gt; + return s32g_rtc_set_time(dev, &amp;priv-&gt;base.tm);<br />&gt; <br />&gt; And so this is useless too so yo udon't actually have anything to do in<br />&gt; s32g_rtc_resume.<br />&gt; <br /><br />I will refactor (simplify) the entire store of time logic from suspend &amp; <br />resume in V7.<br /><br />Regards,<br />Ciprian<br /><br />&gt;&gt; +}<br />&gt;&gt; +<br />&gt;&gt; +static const struct of_device_id rtc_dt_ids[] = {<br />&gt;&gt; + { .compatible = "nxp,s32g2-rtc", .data = &amp;rtc_s32g2_data},<br />&gt;&gt; + { /* sentinel */ },<br />&gt;&gt; +};<br />&gt;&gt; +<br />&gt;&gt; +static DEFINE_SIMPLE_DEV_PM_OPS(s32g_rtc_pm_ops,<br />&gt;&gt; + s32g_rtc_suspend, s32g_rtc_resume);<br />&gt;&gt; +<br />&gt;&gt; +static struct platform_driver s32g_rtc_driver = {<br />&gt;&gt; + .driver = {<br />&gt;&gt; + .name = "s32g-rtc",<br />&gt;&gt; + .pm = pm_sleep_ptr(&amp;s32g_rtc_pm_ops),<br />&gt;&gt; + .of_match_table = rtc_dt_ids,<br />&gt;&gt; + },<br />&gt;&gt; + .probe = s32g_rtc_probe,<br />&gt;&gt; +};<br />&gt;&gt; +module_platform_driver(s32g_rtc_driver);<br />&gt;&gt; +<br />&gt;&gt; +MODULE_AUTHOR("NXP");<br />&gt;&gt; +MODULE_DESCRIPTION("NXP RTC driver for S32G2/S32G3");<br />&gt;&gt; +MODULE_LICENSE("GPL");<br />&gt;&gt; -- <br />&gt;&gt; 2.45.2<br />&gt;&gt;<br />&gt; <br /><br /><br /></pre></td><td width="32" rowspan="2" class="c" valign="top"><img src="/images/icornerr.gif" width="32" height="32" alt="\" /></td></tr><tr><td align="right" valign="bottom"> 聽 </td></tr><tr><td align="right" valign="bottom">聽</td><td class="c" valign="bottom" style="padding-bottom: 0px"><img src="/images/bcornerl.gif" width="32" height="32" alt="\" /></td><td class="c">聽</td><td class="c" valign="bottom" style="padding-bottom: 0px"><img src="/images/bcornerr.gif" width="32" height="32" alt="/" /></td></tr><tr><td align="right" valign="top" colspan="2"> 聽 </td><td class="lm">Last update: 2025-02-06 11:36 聽聽 [W:0.616 / U:0.227 seconds]<br />漏2003-2020 <a href="http://blog.jasper.es/"><span itemprop="editor">Jasper Spaans</span></a>|hosted at <a href="https://www.digitalocean.com/?refcode=9a8e99d24cf9">Digital Ocean</a> and my Meterkast|<a href="http://blog.jasper.es/categories.html#lkml-ref">Read the blog</a></td><td>聽</td></tr></table><script language="javascript" src="/js/styleswitcher.js" type="text/javascript"></script></body></html>

Pages: 1 2 3 4 5 6 7 8 9 10