[RFC] rtc/nxp: add FTM alarm driver as the wakeup source

Submitted by Meng Yi on Oct. 24, 2016, 1:42 a.m.

Details

Message ID 1477273336-48449-1-git-send-email-meng.yi@nxp.com
State New
Headers show

Commit Message

Meng Yi Oct. 24, 2016, 1:42 a.m.
For the platforms including LS1021A, LS1043A that has the
flextimer module, implementing alarm functions within RTC
subsystem to wakeup the system when system going to sleep.
Only Ftm0 can be used to wakeup the system.

Signed-off-by: Meng Yi <meng.yi@nxp.com>
---
 drivers/rtc/Kconfig       |   9 ++
 drivers/rtc/Makefile      |   1 +
 drivers/rtc/rtc-nxp-ftm.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 265 insertions(+)
 create mode 100644 drivers/rtc/rtc-nxp-ftm.c

Comments

Meng Yi Nov. 2, 2016, 8:23 a.m.
> +
> +static int ftm_alarm_irq_enable(struct device *dev, unsigned int
> +enabled) {
> +	if (enabled)
> +		ftm_irq_enable(true);
> +	else
> +		ftm_irq_enable(false);
> +
> +	return 0;
> +}
> +
> +static int nxp_ftm_rtc_read_time(struct device *dev, struct rtc_time
> +*tm) {
> +	struct timeval time;
> +	unsigned long local_time;
> +
> +	do_gettimeofday(&time);
> +	local_time = (u32)(time.tv_sec - (sys_tz.tz_minuteswest * 60));
> +	rtc_time_to_tm(local_time, tm);
> +
> +	return 0;
> +}
> +

This is not really getting time from the RTC since FTM is not a RTC device,
But we need to get the time to setup alarm, so we are using system time for now.
Anybody have better idea?

Meng
Alexandre Belloni Dec. 1, 2016, 12:32 a.m.
On 02/11/2016 at 08:23:32 +0000, Meng Yi wrote :
> > +
> > +static int ftm_alarm_irq_enable(struct device *dev, unsigned int
> > +enabled) {
> > +	if (enabled)
> > +		ftm_irq_enable(true);
> > +	else
> > +		ftm_irq_enable(false);
> > +
> > +	return 0;
> > +}
> > +
> > +static int nxp_ftm_rtc_read_time(struct device *dev, struct rtc_time
> > +*tm) {
> > +	struct timeval time;
> > +	unsigned long local_time;
> > +
> > +	do_gettimeofday(&time);
> > +	local_time = (u32)(time.tv_sec - (sys_tz.tz_minuteswest * 60));
> > +	rtc_time_to_tm(local_time, tm);
> > +
> > +	return 0;
> > +}
> > +
> 
> This is not really getting time from the RTC since FTM is not a RTC device,
> But we need to get the time to setup alarm, so we are using system time for now.
> Anybody have better idea?
> 

No, that seems fine to me. I'll review the rest of the driver.
Meng Yi Dec. 2, 2016, 2:27 a.m.
> > > +
> > > +static int nxp_ftm_rtc_read_time(struct device *dev, struct
> > > +rtc_time
> > > +*tm) {
> > > +	struct timeval time;
> > > +	unsigned long local_time;
> > > +
> > > +	do_gettimeofday(&time);
> > > +	local_time = (u32)(time.tv_sec - (sys_tz.tz_minuteswest * 60));
> > > +	rtc_time_to_tm(local_time, tm);
> > > +
> > > +	return 0;
> > > +}
> > > +
> >
> > This is not really getting time from the RTC since FTM is not a RTC
> > device, But we need to get the time to setup alarm, so we are using system
> time for now.
> > Anybody have better idea?
> >
> 
> No, that seems fine to me. I'll review the rest of the driver.

Thanks for your review, any feedback is welcomed!

> 
> 
> --
> Alexandre Belloni, Free Electrons
> Embedded Linux and Kernel engineering
> http://free-electrons.com

Meng
Alexandre Belloni Jan. 27, 2017, 12:03 a.m.
Hi,

On 24/10/2016 at 09:42:16 +0800, Meng Yi wrote:
> For the platforms including LS1021A, LS1043A that has the
> flextimer module, implementing alarm functions within RTC
> subsystem to wakeup the system when system going to sleep.
> Only Ftm0 can be used to wakeup the system.
> 
> Signed-off-by: Meng Yi <meng.yi@nxp.com>
> ---
>  drivers/rtc/Kconfig       |   9 ++
>  drivers/rtc/Makefile      |   1 +
>  drivers/rtc/rtc-nxp-ftm.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 265 insertions(+)
>  create mode 100644 drivers/rtc/rtc-nxp-ftm.c
> 
> +static const struct of_device_id nxp_ftm_rtc_of_match[] = {
> +	{
> +		.compatible	= "fsl,ftm-clock",
> +	}, {
> +		/* sentinel */
> +	}
> +};
> +MODULE_DEVICE_TABLE(of, nxp_ftm_rtc_of_match);
> 

I think the patch is generally fine but I think you will get an issue
later if anybody is trying to use an FTM to actually do something else
(e.g. PWM, counter, quad decoder,...).

I'm asking because there is exactly this issue on atmel SoCs.

I think we may be missing a proper subsystem.

> +static int nxp_ftm_rtc_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct resource *r;
> +	int irq;
> +	int ret;
> +
> +	rtc.alarm_freq = (u32)FIXED_FREQ_CLK / (u32)MAX_FREQ_DIV;
> +
> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!r)
> +		return -ENODEV;
> +
> +	rtc.base = devm_ioremap_resource(&pdev->dev, r);
> +	if (IS_ERR(rtc.base))
> +		return PTR_ERR(rtc.base);
> +
> +	irq = irq_of_parse_and_map(np, 0);
> +	if (irq <= 0) {
> +		pr_err("ftm: unable to get IRQ from DT, %d\n", irq);
> +		return -EINVAL;
> +	}
> +
> +	rtc.endian = of_property_read_bool(np, "big-endian");
> +
Doesn't the platform know the endianness, is it really necessary to have
that in the device tree?

Patch hide | download patch | download mbox

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index e859d14..0ba52b3 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1243,6 +1243,15 @@  config RTC_DRV_IMXDI
 	   This driver can also be built as a module, if so, the module
 	   will be called "rtc-imxdi".
 
+config RTC_DRV_NXP
+	tristate "NXP flextimer Real Time Alarm "
+	help
+	For the FlexTimer on LS1021A, LS1043A, we can use FTM0 as the wakeup
+	source.
+	Say y here to enable FTM alarm support.  The FTM alarm provides
+	alarm functions for wakeup system from deep sleep.  There is only
+	one FTM can be used in ALARM(FTM 0).
+
 config RTC_DRV_OMAP
 	tristate "TI OMAP Real Time Clock"
 	depends on ARCH_OMAP || ARCH_DAVINCI || COMPILE_TEST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 1ac694a..abbf22c 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -71,6 +71,7 @@  obj-$(CONFIG_RTC_DRV_GENERIC)	+= rtc-generic.o
 obj-$(CONFIG_RTC_DRV_HID_SENSOR_TIME) += rtc-hid-sensor-time.o
 obj-$(CONFIG_RTC_DRV_HYM8563)	+= rtc-hym8563.o
 obj-$(CONFIG_RTC_DRV_IMXDI)	+= rtc-imxdi.o
+obj-$(CONFIG_RTC_DRV_NXP)	+= rtc-nxp-ftm.o
 obj-$(CONFIG_RTC_DRV_ISL12022)	+= rtc-isl12022.o
 obj-$(CONFIG_RTC_DRV_ISL1208)	+= rtc-isl1208.o
 obj-$(CONFIG_RTC_DRV_JZ4740)	+= rtc-jz4740.o
diff --git a/drivers/rtc/rtc-nxp-ftm.c b/drivers/rtc/rtc-nxp-ftm.c
new file mode 100644
index 0000000..74c4358
--- /dev/null
+++ b/drivers/rtc/rtc-nxp-ftm.c
@@ -0,0 +1,255 @@ 
+/*
+ * Copyright 2016 NXP Semiconductor, Inc.
+ *
+ * NXP FTM alarm device driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/of_irq.h>
+
+#define FTM_SC			0x00
+#define FTM_SC_CLK_SHIFT	3
+#define FTM_SC_CLK_MASK		(0x3 << FTM_SC_CLK_SHIFT)
+#define FTM_SC_CLK(c)		((c) << FTM_SC_CLK_SHIFT)
+#define FTM_SC_PS_MASK		0x7
+#define FTM_SC_TOIE		BIT(6)
+#define FTM_SC_TOF		BIT(7)
+
+#define FTM_SC_CLKS_FIXED_FREQ	0x02
+
+#define FTM_CNT			0x04
+#define FTM_MOD			0x08
+#define FTM_CNTIN		0x4C
+
+#define FIXED_FREQ_CLK		32000
+#define MAX_FREQ_DIV		(1 << FTM_SC_PS_MASK)
+#define MAX_COUNT_VAL		0xffff
+
+struct ftm_rtc {
+	struct rtc_device *rtc_dev;
+	void __iomem *base;
+	bool endian;
+	u32 alarm_freq;
+};
+
+static struct ftm_rtc rtc;
+
+static inline u32 ftm_readl(void __iomem *addr)
+{
+	return rtc.endian ? ioread32be(addr) : ioread32(addr);
+}
+
+static inline void ftm_writel(u32 val, void __iomem *addr)
+{
+	return rtc.endian ? iowrite32be(val, addr) : iowrite32(val, addr);
+}
+
+static inline void ftm_counter_enable(bool enabled)
+{
+	u32 val;
+
+	/* select and enable counter clock source */
+	val = ftm_readl(rtc.base + FTM_SC);
+	if (enabled) {
+		val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
+		val |= (FTM_SC_PS_MASK | FTM_SC_CLK(FTM_SC_CLKS_FIXED_FREQ));
+	} else
+		val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
+	ftm_writel(val, rtc.base + FTM_SC);
+}
+
+static int ftm_irq_enable(bool enabled)
+{
+	u32 val;
+
+	val = ftm_readl(rtc.base + FTM_SC);
+	if (enabled)
+		val |= FTM_SC_TOIE;
+	else
+		val &= ~FTM_SC_TOIE;
+	ftm_writel(val, rtc.base + FTM_SC);
+
+	return 0;
+}
+
+static inline void ftm_irq_clear(void)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+
+	while ((FTM_SC_TOF & ftm_readl(rtc.base + FTM_SC)) &&
+		time_before(jiffies, timeout))
+		ftm_writel(ftm_readl(rtc.base + FTM_SC) & (~FTM_SC_TOF),
+			   rtc.base + FTM_SC);
+}
+
+static void ftm_clean_alarm(void)
+{
+	ftm_counter_enable(false);
+
+	ftm_writel(0x00, rtc.base + FTM_CNTIN);
+	ftm_writel(~0x00, rtc.base + FTM_MOD);
+
+	/*
+	 * The CNT register contains the FTM counter value.
+	 * Reset clears the CNT register. Writing any value to COUNT
+	 * updates the counter with its initial value, CNTIN.
+	 */
+	ftm_writel(0x00, rtc.base + FTM_CNT);
+}
+
+static irqreturn_t ftm_alarm_interrupt(int irq, void *dev_id)
+{
+	rtc_alarm_irq_enable(rtc.rtc_dev, false);
+	ftm_irq_clear();
+	ftm_irq_enable(false);
+	ftm_clean_alarm();
+
+	return IRQ_HANDLED;
+}
+
+static int ftm_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	if (enabled)
+		ftm_irq_enable(true);
+	else
+		ftm_irq_enable(false);
+
+	return 0;
+}
+
+static int nxp_ftm_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct timeval time;
+	unsigned long local_time;
+
+	do_gettimeofday(&time);
+	local_time = (u32)(time.tv_sec - (sys_tz.tz_minuteswest * 60));
+	rtc_time_to_tm(local_time, tm);
+
+	return 0;
+}
+
+/*250Hz, 65536 / 250 = 262 second max*/
+static int nxp_ftm_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	struct rtc_time tm;
+	unsigned long now, alm_time, cycle;
+
+	nxp_ftm_rtc_read_time(dev, &tm);
+	rtc_tm_to_time(&tm, &now);
+	rtc_tm_to_time(&alm->time, &alm_time);
+
+	ftm_clean_alarm();
+	cycle = (alm_time - now) * rtc.alarm_freq;
+	if (cycle > MAX_COUNT_VAL) {
+		pr_err("Out of alarm range.\n");
+		return -EINVAL;
+	}
+
+	ftm_irq_enable(false);
+	/*
+	 * The counter increments until the value of MOD is reached,
+	 * at which point the counter is reloaded with the value of CNTIN.
+	 * The TOF (the overflow flag) bit is set when the FTM counter
+	 * changes from MOD to CNTIN. So we should using the cycle - 1.
+	 */
+	ftm_writel(cycle - 1, rtc.base + FTM_MOD);
+
+	ftm_counter_enable(true);
+	ftm_irq_enable(true);
+
+	return 0;
+}
+
+static int nxp_ftm_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	return 0;
+}
+
+const struct rtc_class_ops nxp_ftm_rtc_ops = {
+	.read_time	= nxp_ftm_rtc_read_time,
+	.set_alarm	= nxp_ftm_rtc_set_alarm,
+	.read_alarm	= nxp_ftm_rtc_read_alarm,
+	.alarm_irq_enable	= ftm_alarm_irq_enable
+};
+
+static const struct of_device_id nxp_ftm_rtc_of_match[] = {
+	{
+		.compatible	= "fsl,ftm-clock",
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, nxp_ftm_rtc_of_match);
+
+static int nxp_ftm_rtc_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct resource *r;
+	int irq;
+	int ret;
+
+	rtc.alarm_freq = (u32)FIXED_FREQ_CLK / (u32)MAX_FREQ_DIV;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r)
+		return -ENODEV;
+
+	rtc.base = devm_ioremap_resource(&pdev->dev, r);
+	if (IS_ERR(rtc.base))
+		return PTR_ERR(rtc.base);
+
+	irq = irq_of_parse_and_map(np, 0);
+	if (irq <= 0) {
+		pr_err("ftm: unable to get IRQ from DT, %d\n", irq);
+		return -EINVAL;
+	}
+
+	rtc.endian = of_property_read_bool(np, "big-endian");
+
+	ret = devm_request_irq(&pdev->dev, irq, ftm_alarm_interrupt,
+			       IRQF_NO_SUSPEND, dev_name(&pdev->dev), NULL);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to request irq\n");
+		return ret;
+	}
+
+	device_init_wakeup(&pdev->dev, true);
+	rtc.rtc_dev = devm_rtc_device_register(&pdev->dev, "nxp-ftm",
+					       &nxp_ftm_rtc_ops,
+			THIS_MODULE);
+
+	ftm_clean_alarm();
+
+	return ret;
+}
+
+static struct platform_driver nxp_ftm_rtc_driver = {
+	.probe		= nxp_ftm_rtc_probe,
+	.driver		= {
+		.name	= "nxp_ftm_rtc",
+		.of_match_table = nxp_ftm_rtc_of_match,
+	},
+};
+
+module_platform_driver(nxp_ftm_rtc_driver);
+
+MODULE_DESCRIPTION("NXP/Freescale Flextimer RTC Driver");
+MODULE_LICENSE("GPL");