diff mbox

rtc: sunxi: use external oscillator

Message ID 577CC494.8020103@roslen.de
State Rejected
Headers show

Commit Message

Stephan Roslen July 6, 2016, 8:43 a.m. UTC
We noticed some serious drift problems in Allwinner A20 RTCs. I found out, that the oscillator source needs to be set to an external 32.768 KHz oscillator instead of the internal 32.000 KHz oscillator.

Signed-off-by: Stephan Roslen <stephan@roslen.de>
---
 drivers/rtc/rtc-sunxi.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

Comments

Maxime Ripard July 6, 2016, 8:20 p.m. UTC | #1
On Wed, Jul 06, 2016 at 10:43:00AM +0200, Stephan Roslen wrote:
> We noticed some serious drift problems in Allwinner A20 RTCs. I
> found out, that the oscillator source needs to be set to an external
> 32.768 KHz oscillator instead of the internal 32.000 KHz oscillator.

You should wrap the commit description.

> 
> Signed-off-by: Stephan Roslen <stephan@roslen.de>
> ---
>  drivers/rtc/rtc-sunxi.c | 25 +++++++++++++++++++++++++
>  1 file changed, 25 insertions(+)
> 
> diff --git a/drivers/rtc/rtc-sunxi.c b/drivers/rtc/rtc-sunxi.c
> index abada60..001256e 100644
> --- a/drivers/rtc/rtc-sunxi.c
> +++ b/drivers/rtc/rtc-sunxi.c
> @@ -34,9 +34,19 @@
>  #include <linux/types.h>
>  
>  #define SUNXI_LOSC_CTRL				0x0000
> +
> +/* magic number required to set bit 0 */
> +#define SUNXI_LOSC_CTRL_KEY			0x16AA0000
> +
> +#define SUNXI_LOSC_CTRL_AUTO_SWT_EN		BIT(14)
> +
>  #define SUNXI_LOSC_CTRL_RTC_HMS_ACC		BIT(8)
>  #define SUNXI_LOSC_CTRL_RTC_YMD_ACC		BIT(7)
>  
> +#define SUNXI_LOSC_CTRL_EXT_GSM1		BIT(3)
> +#define SUNXI_LOSC_CTRL_EXT_GSM0		BIT(2)
> +#define SUNXI_LOSC_CTRL_SRC_SEL			BIT(0)
> +
>  #define SUNXI_RTC_YMD				0x0004
>  
>  #define SUNXI_RTC_HMS				0x0008
> @@ -438,6 +448,8 @@ static int sunxi_rtc_probe(struct platform_device *pdev)
>  	struct resource *res;
>  	int ret;
>  
> +	uint32_t loscctrl;
> +
>  	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
>  	if (!chip)
>  		return -ENOMEM;
> @@ -468,6 +480,19 @@ static int sunxi_rtc_probe(struct platform_device *pdev)
>  		return -ENODEV;
>  	}
>  
> +	loscctrl = readl(chip->base + SUNXI_LOSC_CTRL);
> +	loscctrl &= ~(SUNXI_LOSC_CTRL_AUTO_SWT_EN);
> +	loscctrl |= (SUNXI_LOSC_CTRL_SRC_SEL | SUNXI_LOSC_CTRL_KEY);
> +	loscctrl |= SUNXI_LOSC_CTRL_EXT_GSM1;
> +	writel(loscctrl, chip->base + SUNXI_LOSC_CTRL);
> +	udelay(100);

Why is that udelay needed?

> +
> +	loscctrl = readl(chip->base + SUNXI_LOSC_CTRL);
> +	if (!(loscctrl & SUNXI_LOSC_CTRL_SRC_SEL)) {
> +		dev_err(&pdev->dev, "Error: Set LOSC to external failed.\n");
> +		dev_err(&pdev->dev, "Warning: RTC time will be wrong!\n");
> +	}
> +

This isn't needed

The issue is actually worse than that.

That register controls the losc source for the whole clock tree, so it
will affect every clock in the system.

In order to have that correctly propagated, you should register a new
mux here in the clock framework, and have all the other clocks using
that mux as a parent.

That's going to be tricky, because the clocks usually probe way
earlier than the RTC driver. So I'm guessing you could do a clock
driver that maps the registers, register its clock, and then when the
RTC probes just takes over what has been setup already by the clock
driver. This also means removing the ability for the RTC to be
compiled as a module.

Maxime
Stephan Roslen July 7, 2016, 4:50 a.m. UTC | #2
On 06.07.2016 22:20, Maxime Ripard wrote:
> On Wed, Jul 06, 2016 at 10:43:00AM +0200, Stephan Roslen wrote:
>> +	writel(loscctrl, chip->base + SUNXI_LOSC_CTRL);
>> +	udelay(100);
> 
> Why is that udelay needed?

I found that studying the sunxi 3.4 kernel code and considered it a necessary delay for the hardware setup and update SUNXI_LOSC_CTRL_SRC_SEL bit. My assumption was wrong, it seems. At least a few reboots show, that it is likely to work without.

> 
>> +
>> +	loscctrl = readl(chip->base + SUNXI_LOSC_CTRL);
>> +	if (!(loscctrl & SUNXI_LOSC_CTRL_SRC_SEL)) {
>> +		dev_err(&pdev->dev, "Error: Set LOSC to external failed.\n");
>> +		dev_err(&pdev->dev, "Warning: RTC time will be wrong!\n");
>> +	}
>> +
> 
> This isn't needed
> 
> The issue is actually worse than that.
> 
> That register controls the losc source for the whole clock tree, so it
> will affect every clock in the system.
> 
> In order to have that correctly propagated, you should register a new
> mux here in the clock framework, and have all the other clocks using
> that mux as a parent.

I agree. Checking the diagram in subsection 1.5.2 of the A20 manual it seems, that LOSC can be a source for clocks like CPU and some SoC busses. So my patch could indeed mess with the whole clock tree.

> 
> That's going to be tricky, because the clocks usually probe way
> earlier than the RTC driver. So I'm guessing you could do a clock
> driver that maps the registers, register its clock, and then when the
> RTC probes just takes over what has been setup already by the clock
> driver. This also means removing the ability for the RTC to be
> compiled as a module.

I agree again. Though I think, the RTC should still work as a module. The CLK driver could provide the regmap MFD style and the RTC driver may access it. Or rather the CLK driver should use an MFD, that is provided early, too. Eventually even a syscon? Actually the whole bunch described in subsection 1.9 (including timers, arlarms, rtc and even the watchdog) would have to rely on that MFD. Do I miss a point?

Stephan
Chen-Yu Tsai July 7, 2016, 4:53 a.m. UTC | #3
On Thu, Jul 7, 2016 at 12:50 PM, Stephan Roslen <stephan@roslen.de> wrote:
> On 06.07.2016 22:20, Maxime Ripard wrote:
>> On Wed, Jul 06, 2016 at 10:43:00AM +0200, Stephan Roslen wrote:
>>> +    writel(loscctrl, chip->base + SUNXI_LOSC_CTRL);
>>> +    udelay(100);
>>
>> Why is that udelay needed?
>
> I found that studying the sunxi 3.4 kernel code and considered it a necessary delay for the hardware setup and update SUNXI_LOSC_CTRL_SRC_SEL bit. My assumption was wrong, it seems. At least a few reboots show, that it is likely to work without.
>
>>
>>> +
>>> +    loscctrl = readl(chip->base + SUNXI_LOSC_CTRL);
>>> +    if (!(loscctrl & SUNXI_LOSC_CTRL_SRC_SEL)) {
>>> +            dev_err(&pdev->dev, "Error: Set LOSC to external failed.\n");
>>> +            dev_err(&pdev->dev, "Warning: RTC time will be wrong!\n");
>>> +    }
>>> +
>>
>> This isn't needed
>>
>> The issue is actually worse than that.
>>
>> That register controls the losc source for the whole clock tree, so it
>> will affect every clock in the system.
>>
>> In order to have that correctly propagated, you should register a new
>> mux here in the clock framework, and have all the other clocks using
>> that mux as a parent.
>
> I agree. Checking the diagram in subsection 1.5.2 of the A20 manual it seems, that LOSC can be a source for clocks like CPU and some SoC busses. So my patch could indeed mess with the whole clock tree.

(Please wrap your email)

Within the current sunxi clk structure, the LOSC is registered as a fixed
32.768 KHz clk. So this patch actually makes things right.

ChenYu

>
>>
>> That's going to be tricky, because the clocks usually probe way
>> earlier than the RTC driver. So I'm guessing you could do a clock
>> driver that maps the registers, register its clock, and then when the
>> RTC probes just takes over what has been setup already by the clock
>> driver. This also means removing the ability for the RTC to be
>> compiled as a module.
>
> I agree again. Though I think, the RTC should still work as a module. The CLK driver could provide the regmap MFD style and the RTC driver may access it. Or rather the CLK driver should use an MFD, that is provided early, too. Eventually even a syscon? Actually the whole bunch described in subsection 1.9 (including timers, arlarms, rtc and even the watchdog) would have to rely on that MFD. Do I miss a point?
>
> Stephan
Stephan Roslen July 7, 2016, 5:11 a.m. UTC | #4
On 07.07.2016 06:50, Stephan Roslen wrote:
> On 06.07.2016 22:20, Maxime Ripard wrote:
>> The issue is actually worse than that.
>>
>> That register controls the losc source for the whole clock tree, so it
>> will affect every clock in the system.
>>
>> In order to have that correctly propagated, you should register a new
>> mux here in the clock framework, and have all the other clocks using
>> that mux as a parent.
> 
> I agree. Checking the diagram in subsection 1.5.2 of the A20 manual it seems, that LOSC can be a source for clocks like CPU and some SoC busses. So my patch could indeed mess with the whole clock tree.

Sorry for the addition. The mess of course is not that huge, for the mux selects between a 32KHz Oscillator with +- 20% tolerance, thus ranging from 25.6 to 38.4 KHz and the 32.768KHz external oscillator. Hence muxing to 32.768KHz should be no problem for any clk, that was fine with the internal oscillator as clk source. Though still you're absolutely right, that this should be handled in code correctly.

Stephan
Stephan Roslen July 7, 2016, 6:12 a.m. UTC | #5
On 07.07.2016 06:53, Chen-Yu Tsai wrote:
> On Thu, Jul 7, 2016 at 12:50 PM, Stephan Roslen <stephan@roslen.de> wrote:
>> I agree. Checking the diagram in subsection 1.5.2 of the A20 manual it seems, that LOSC can be a source for clocks like CPU and some SoC busses. So my patch could indeed mess with the whole clock tree.
> 
> (Please wrap your email)

Sorry.

> 
> Within the current sunxi clk structure, the LOSC is registered as a fixed
> 32.768 KHz clk. So this patch actually makes things right.

In this case it should still be addressed somewhere in drivers/clk/sunxi, not 
in drivers/rtc, I guess? Would you prefer setting the external 32.768 KHz crystal
as hard coded default instead of providing the muxer? It could be considered okay,
for the data sheet seems to demand presence of the external oscillator and there
is absolutely no use in a 32KHz +- 20% oscillator, if a 32.768KHz crystal is
available.

Stephan
Maxime Ripard July 7, 2016, 6:42 a.m. UTC | #6
On Thu, Jul 07, 2016 at 12:53:29PM +0800, Chen-Yu Tsai wrote:
> >> In order to have that correctly propagated, you should register a new
> >> mux here in the clock framework, and have all the other clocks using
> >> that mux as a parent.
> >
> > I agree. Checking the diagram in subsection 1.5.2 of the A20
> > manual it seems, that LOSC can be a source for clocks like CPU and
> > some SoC busses. So my patch could indeed mess with the whole
> > clock tree.
> 
> Within the current sunxi clk structure, the LOSC is registered as a fixed
> 32.768 KHz clk. So this patch actually makes things right.

On the condition that the driver gets loaded, which is not guaranteed.

Maxime
Maxime Ripard July 7, 2016, 7:08 a.m. UTC | #7
On Thu, Jul 07, 2016 at 08:12:09AM +0200, Stephan Roslen wrote:
> > Within the current sunxi clk structure, the LOSC is registered as a fixed
> > 32.768 KHz clk. So this patch actually makes things right.
> 
> In this case it should still be addressed somewhere in drivers/clk/sunxi, not 
> in drivers/rtc, I guess? Would you prefer setting the external 32.768 KHz crystal
> as hard coded default instead of providing the muxer? It could be considered okay,
> for the data sheet seems to demand presence of the external oscillator and there
> is absolutely no use in a 32KHz +- 20% oscillator, if a 32.768KHz crystal is
> available.

Yes, poking into the registers in drivers/clk would also work.

Maxime
diff mbox

Patch

diff --git a/drivers/rtc/rtc-sunxi.c b/drivers/rtc/rtc-sunxi.c
index abada60..001256e 100644
--- a/drivers/rtc/rtc-sunxi.c
+++ b/drivers/rtc/rtc-sunxi.c
@@ -34,9 +34,19 @@ 
 #include <linux/types.h>
 
 #define SUNXI_LOSC_CTRL				0x0000
+
+/* magic number required to set bit 0 */
+#define SUNXI_LOSC_CTRL_KEY			0x16AA0000
+
+#define SUNXI_LOSC_CTRL_AUTO_SWT_EN		BIT(14)
+
 #define SUNXI_LOSC_CTRL_RTC_HMS_ACC		BIT(8)
 #define SUNXI_LOSC_CTRL_RTC_YMD_ACC		BIT(7)
 
+#define SUNXI_LOSC_CTRL_EXT_GSM1		BIT(3)
+#define SUNXI_LOSC_CTRL_EXT_GSM0		BIT(2)
+#define SUNXI_LOSC_CTRL_SRC_SEL			BIT(0)
+
 #define SUNXI_RTC_YMD				0x0004
 
 #define SUNXI_RTC_HMS				0x0008
@@ -438,6 +448,8 @@  static int sunxi_rtc_probe(struct platform_device *pdev)
 	struct resource *res;
 	int ret;
 
+	uint32_t loscctrl;
+
 	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
 	if (!chip)
 		return -ENOMEM;
@@ -468,6 +480,19 @@  static int sunxi_rtc_probe(struct platform_device *pdev)
 		return -ENODEV;
 	}
 
+	loscctrl = readl(chip->base + SUNXI_LOSC_CTRL);
+	loscctrl &= ~(SUNXI_LOSC_CTRL_AUTO_SWT_EN);
+	loscctrl |= (SUNXI_LOSC_CTRL_SRC_SEL | SUNXI_LOSC_CTRL_KEY);
+	loscctrl |= SUNXI_LOSC_CTRL_EXT_GSM1;
+	writel(loscctrl, chip->base + SUNXI_LOSC_CTRL);
+	udelay(100);
+
+	loscctrl = readl(chip->base + SUNXI_LOSC_CTRL);
+	if (!(loscctrl & SUNXI_LOSC_CTRL_SRC_SEL)) {
+		dev_err(&pdev->dev, "Error: Set LOSC to external failed.\n");
+		dev_err(&pdev->dev, "Warning: RTC time will be wrong!\n");
+	}
+
 	/* clear the alarm count value */
 	writel(0, chip->base + SUNXI_ALRM_DHMS);