All of lore.kernel.org
 help / color / mirror / Atom feed
From: Eddie Huang <eddie.huang@mediatek.com>
To: Lee Jones <lee.jones@linaro.org>,
	Alessandro Zummo <a.zummo@towertech.it>,
	Matthias Brugger <matthias.bgg@gmail.com>
Cc: srv_heupstream@mediatek.com,
	"Samuel Ortiz" <sameo@linux.intel.com>,
	"Andrew Morton" <akpm@linux-foundation.org>,
	"David S. Miller" <davem@davemloft.net>,
	"Greg KH" <gregkh@linuxfoundation.org>,
	"Mauro Carvalho Chehab" <mchehab@osg.samsung.com>,
	"Joe Perches" <joe@perches.com>, "Tejun Heo" <tj@kernel.org>,
	"Jingoo Han" <jg1.han@samsung.com>,
	linux-kernel@vger.kernel.org, rtc-linux@googlegroups.com,
	linux-arm-kernel@lists.infradead.org,
	linux-mediatek@lists.infradead.org,
	"Uwe Kleine-König" <kernel@pengutronix.de>,
	"Tianping Fang" <tianping.fang@mediatek.com>,
	"Eddie Huang" <eddie.huang@mediatek.com>
Subject: [PATCH v2 2/3] rtc: mediatek: Add MT6397 RTC driver
Date: Wed, 18 Mar 2015 13:45:36 +0800	[thread overview]
Message-ID: <1426657537-41414-3-git-send-email-eddie.huang@mediatek.com> (raw)
In-Reply-To: <1426657537-41414-1-git-send-email-eddie.huang@mediatek.com>

From: Tianping Fang <tianping.fang@mediatek.com>

Add Mediatek MT6397 RTC driver

Signed-off-by: Tianping Fang <tianping.fang@mediatek.com>
Signed-off-by: Eddie Huang <eddie.huang@mediatek.com>
---
 drivers/rtc/Kconfig      |  10 ++
 drivers/rtc/Makefile     |   1 +
 drivers/rtc/rtc-mt6397.c | 454 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 465 insertions(+)
 create mode 100644 drivers/rtc/rtc-mt6397.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index b5b5c3d..dcb94af 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1510,6 +1510,16 @@ config RTC_DRV_MOXART
 	   This driver can also be built as a module. If so, the module
 	   will be called rtc-moxart
 
+config RTC_DRV_MT6397
+	tristate "Mediatek Real Time Clock driver"
+	depends on MFD_MT6397 || COMPILE_TEST
+	help
+	  This selects the Mediatek(R) RTC driver. RTC is part of Mediatek
+	  MT6397 PMIC. You should enable MT6397 PMIC MFD before select
+	  Mediatek(R) RTC driver.
+
+	  If you want to use Mediatek(R) RTC interface, select Y or M here.
+
 config RTC_DRV_XGENE
 	tristate "APM X-Gene RTC"
 	depends on HAS_IOMEM
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 69c8706..85c8bea 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -153,3 +153,4 @@ obj-$(CONFIG_RTC_DRV_X1205)	+= rtc-x1205.o
 obj-$(CONFIG_RTC_DRV_XGENE)	+= rtc-xgene.o
 obj-$(CONFIG_RTC_DRV_SIRFSOC)	+= rtc-sirfsoc.o
 obj-$(CONFIG_RTC_DRV_MOXART)	+= rtc-moxart.o
+obj-$(CONFIG_RTC_DRV_MT6397)	+= rtc-mt6397.o
diff --git a/drivers/rtc/rtc-mt6397.c b/drivers/rtc/rtc-mt6397.c
new file mode 100644
index 0000000..130480d
--- /dev/null
+++ b/drivers/rtc/rtc-mt6397.c
@@ -0,0 +1,454 @@
+/*
+* Copyright (c) 2014-2015 MediaTek Inc.
+* Author: Tianping.Fang <tianping.fang@mediatek.com>
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License version 2 as
+* published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*/
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+#include <linux/irqdomain.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/mfd/mt6397/core.h>
+
+#define RTC_BBPU		0x0000
+#define RTC_WRTGR		0x003c
+#define RTC_IRQ_EN		0x0004
+#define RTC_IRQ_STA		0x0002
+
+#define RTC_BBPU_CBUSY		(1 << 6)
+#define RTC_IRQ_STA_AL		(1 << 0)
+#define RTC_IRQ_STA_LP		(1 << 3)
+
+#define RTC_AL_MASK		0x0008
+#define RTC_TC_SEC		0x000a
+#define RTC_TC_MIN		0x000c
+#define RTC_TC_HOU		0x000e
+#define RTC_TC_DOM		0x0010
+#define RTC_TC_MTH		0x0014
+#define RTC_TC_YEA		0x0016
+#define RTC_AL_SEC		0x0018
+#define RTC_AL_MIN		0x001a
+
+#define RTC_IRQ_EN_AL		(1 << 0)
+#define RTC_IRQ_EN_ONESHOT	(1 << 2)
+#define RTC_IRQ_EN_LP		(1 << 3)
+#define RTC_IRQ_EN_ONESHOT_AL	(RTC_IRQ_EN_ONESHOT | RTC_IRQ_EN_AL)
+
+#define RTC_TC_MIN_MASK		0x003f
+#define RTC_TC_SEC_MASK		0x003f
+#define RTC_TC_HOU_MASK		0x001f
+#define RTC_TC_DOM_MASK		0x001f
+#define RTC_TC_MTH_MASK		0x000f
+#define RTC_TC_YEA_MASK		0x007f
+
+#define RTC_AL_SEC_MASK		0x003f
+#define RTC_AL_MIN_MASK		0x003f
+#define RTC_AL_MASK_DOW		(1 << 4)
+
+#define RTC_AL_HOU		0x001c
+#define RTC_AL_HOU_MASK		0x001f
+
+#define RTC_AL_DOM		0x001e
+#define RTC_AL_DOM_MASK		0x001f
+
+#define RTC_AL_MTH		0x0022
+#define RTC_AL_MTH_MASK		0x000f
+
+#define RTC_AL_YEA		0x0024
+#define RTC_AL_YEA_MASK		0x007f
+
+#define RTC_PDN2			0x002e
+#define RTC_PDN2_PWRON_MTH_MASK		0x000f
+#define RTC_PDN2_PWRON_MTH_SHIFT	0
+#define RTC_PDN2_PWRON_ALARM		(1 << 4)
+#define RTC_PDN2_UART_MASK		0x0060
+#define RTC_PDN2_UART_SHIFT		5
+#define RTC_PDN2_PWRON_YEA_MASK		0x7f00
+#define RTC_PDN2_PWRON_YEA_SHIFT	8
+#define RTC_PDN2_PWRON_LOGO		(1 << 15)
+
+#define RTC_MIN_YEAR		1968
+#define RTC_BASE_YEAR		1900
+#define RTC_NUM_YEARS		128
+#define RTC_MIN_YEAR_OFFSET	(RTC_MIN_YEAR - RTC_BASE_YEAR)
+#define RTC_RELPWR_WHEN_XRST	1
+
+struct mt6397_rtc {
+	struct device		*dev;
+	struct rtc_device	*rtc_dev;
+	struct mutex		lock;
+	struct regmap		*regmap;
+	int			irq;
+	u32			addr_base;
+	u32			addr_range;
+};
+
+static int mtk_rtc_read(struct mt6397_rtc *rtc, u32 offset, u32 *data)
+{
+	u32 addr = rtc->addr_base + offset;
+
+	if (offset < rtc->addr_range)
+		return regmap_read(rtc->regmap, addr, data);
+
+	return -EINVAL;
+}
+
+static int mtk_rtc_write(struct mt6397_rtc *rtc, u32 offset, u32 data)
+{
+	u32 addr;
+
+	addr = rtc->addr_base + offset;
+
+	if (offset < rtc->addr_range)
+		return regmap_write(rtc->regmap, addr, data);
+
+	return -EINVAL;
+}
+
+static int mtk_rtc_write_trigger(struct mt6397_rtc *rtc)
+{
+	int ret;
+	u32 data;
+
+	ret = mtk_rtc_write(rtc, RTC_WRTGR, 1);
+	if (ret < 0)
+		goto exit;
+
+	ret = mtk_rtc_read(rtc, RTC_BBPU, &data);
+	if (ret < 0)
+		goto exit;
+
+	while (data & RTC_BBPU_CBUSY) {
+		cpu_relax();
+		ret = mtk_rtc_read(rtc, RTC_BBPU, &data);
+		if (ret < 0)
+			goto exit;
+	}
+
+exit:
+	return ret;
+}
+
+static irqreturn_t mtk_rtc_irq_handler_thread(int irq, void *data)
+{
+	struct mt6397_rtc *rtc = data;
+	u32 irqsta, irqen;
+	int ret;
+
+	ret = mtk_rtc_read(rtc, RTC_IRQ_STA, &irqsta);
+
+	if ((ret >= 0) && (irqsta & RTC_IRQ_STA_AL)) {
+		rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+		irqen = irqsta & ~RTC_IRQ_EN_AL;
+		mutex_lock(&rtc->lock);
+		if (mtk_rtc_write(rtc, RTC_IRQ_EN, irqen) < 0)
+			mtk_rtc_write_trigger(rtc);
+		mutex_unlock(&rtc->lock);
+
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static int __mtk_rtc_read_time(struct mt6397_rtc *rtc,
+				struct rtc_time *tm, int *sec)
+{
+	int ret;
+
+	mutex_lock(&rtc->lock);
+	ret = mtk_rtc_read(rtc, RTC_TC_SEC, &tm->tm_sec);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_MIN, &tm->tm_min);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_HOU, &tm->tm_hour);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_DOM, &tm->tm_mday);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_MTH, &tm->tm_mon);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_YEA, &tm->tm_year);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_SEC, sec);
+
+exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static int mtk_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	time64_t time;
+	struct mt6397_rtc *rtc = dev_get_drvdata(dev);
+	int sec, ret;
+
+	do {
+		ret = __mtk_rtc_read_time(rtc, tm, &sec);
+		if (ret < 0)
+			goto exit;
+	} while (sec < tm->tm_sec);
+
+	tm->tm_year += RTC_MIN_YEAR_OFFSET;
+	tm->tm_mon--;
+	time = rtc_tm_to_time64(tm);
+
+	tm->tm_wday = (time / 86400 + 4) % 7;
+
+exit:
+	return ret;
+}
+
+static int mtk_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct mt6397_rtc *rtc = dev_get_drvdata(dev);
+	int ret;
+
+	tm->tm_year -= RTC_MIN_YEAR_OFFSET;
+	tm->tm_mon++;
+
+	mutex_lock(&rtc->lock);
+	ret = mtk_rtc_write(rtc, RTC_TC_YEA, tm->tm_year);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_MTH, tm->tm_mon);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_DOM, tm->tm_mday);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_HOU, tm->tm_hour);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_MIN, tm->tm_min);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_SEC, tm->tm_sec);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write_trigger(rtc);
+
+exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static int mtk_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	struct rtc_time *tm = &alm->time;
+	struct mt6397_rtc *rtc = dev_get_drvdata(dev);
+	u32 irqen, pdn2;
+	int ret;
+
+	mutex_lock(&rtc->lock);
+	ret = mtk_rtc_read(rtc, RTC_IRQ_EN, &irqen);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_PDN2, &pdn2);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_SEC, &tm->tm_sec);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_MIN, &tm->tm_min);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_HOU, &tm->tm_hour);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_DOM, &tm->tm_mday);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_MTH, &tm->tm_mon);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_YEA, &tm->tm_year);
+	if (ret < 0)
+		goto err_exit;
+
+	alm->enabled = !!(irqen & RTC_IRQ_EN_AL);
+	alm->pending = !!(pdn2 & RTC_PDN2_PWRON_ALARM);
+	mutex_unlock(&rtc->lock);
+
+	tm->tm_year += RTC_MIN_YEAR_OFFSET;
+	tm->tm_mon--;
+
+	return 0;
+err_exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static int mtk_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	struct rtc_time *tm = &alm->time;
+	struct mt6397_rtc *rtc = dev_get_drvdata(dev);
+	u32 irqen;
+	int ret;
+
+	tm->tm_year -= RTC_MIN_YEAR_OFFSET;
+	tm->tm_mon++;
+
+	mutex_lock(&rtc->lock);
+	if (alm->enabled) {
+		ret = mtk_rtc_write(rtc, RTC_AL_YEA, tm->tm_year);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_MTH, tm->tm_mon);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_DOM, tm->tm_mday);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_HOU, tm->tm_hour);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_MIN, tm->tm_min);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_SEC, tm->tm_sec);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_MASK, RTC_AL_MASK_DOW);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write_trigger(rtc);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_read(rtc, RTC_IRQ_EN, &irqen);
+		if (ret < 0)
+			goto exit;
+		irqen |= RTC_IRQ_EN_ONESHOT_AL;
+		ret = mtk_rtc_write(rtc, RTC_IRQ_EN, irqen);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write_trigger(rtc);
+		if (ret < 0)
+			goto exit;
+	} else {
+		ret = mtk_rtc_read(rtc, RTC_IRQ_EN, &irqen);
+		if (ret < 0)
+			goto exit;
+		irqen &= ~RTC_IRQ_EN_ONESHOT_AL;
+		ret = mtk_rtc_write(rtc, RTC_IRQ_EN, irqen);
+		if (ret < 0)
+			goto exit;
+	}
+
+exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static struct rtc_class_ops mtk_rtc_ops = {
+	.read_time  = mtk_rtc_read_time,
+	.set_time   = mtk_rtc_set_time,
+	.read_alarm = mtk_rtc_read_alarm,
+	.set_alarm  = mtk_rtc_set_alarm,
+};
+
+static int mtk_rtc_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct mt6397_chip *mt6397_chip = dev_get_drvdata(pdev->dev.parent);
+	struct mt6397_rtc *rtc;
+	int ret = 0;
+
+	rtc = devm_kzalloc(&pdev->dev, sizeof(struct mt6397_rtc), GFP_KERNEL);
+	if (!rtc)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	rtc->addr_base = res->start;
+	rtc->addr_range = res->end - res->start;
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	rtc->irq = irq_create_mapping(mt6397_chip->irq_domain, res->start);
+	if (rtc->irq <= 0)
+		goto out_rtc;
+
+	rtc->regmap = mt6397_chip->regmap;
+	rtc->dev = &pdev->dev;
+	mutex_init(&rtc->lock);
+
+	platform_set_drvdata(pdev, rtc);
+
+	ret = devm_request_threaded_irq(&pdev->dev, rtc->irq, NULL,
+			mtk_rtc_irq_handler_thread,
+			IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+			"mt6397-rtc", rtc);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
+			rtc->irq, ret);
+		goto out_rtc;
+	}
+
+	rtc->rtc_dev = rtc_device_register("mt6397-rtc", &pdev->dev,
+				&mtk_rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc->rtc_dev)) {
+		dev_err(&pdev->dev, "register rtc device failed\n");
+		return PTR_ERR(rtc->rtc_dev);
+	}
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	return 0;
+
+out_rtc:
+	rtc_device_unregister(rtc->rtc_dev);
+	return ret;
+
+}
+
+static int mtk_rtc_remove(struct platform_device *pdev)
+{
+	struct mt6397_rtc *rtc = platform_get_drvdata(pdev);
+
+	rtc_device_unregister(rtc->rtc_dev);
+	return 0;
+}
+
+static const struct of_device_id mt6397_rtc_of_match[] = {
+	{ .compatible = "mediatek,mt6397-rtc", },
+	{ }
+};
+
+static struct platform_driver mtk_rtc_driver = {
+	.driver = {
+		.name = "mt6397-rtc",
+		.of_match_table = mt6397_rtc_of_match,
+	},
+	.probe	= mtk_rtc_probe,
+	.remove = mtk_rtc_remove,
+};
+
+module_platform_driver(mtk_rtc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Tianping Fang <tianping.fang@mediatek.com>");
+MODULE_DESCRIPTION("RTC Driver for MediaTek MT6397 PMIC");
+MODULE_ALIAS("platform:mt6397-rtc");
-- 
1.8.1.1.dirty


WARNING: multiple messages have this Message-ID (diff)
From: eddie.huang@mediatek.com (Eddie Huang)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH v2 2/3] rtc: mediatek: Add MT6397 RTC driver
Date: Wed, 18 Mar 2015 13:45:36 +0800	[thread overview]
Message-ID: <1426657537-41414-3-git-send-email-eddie.huang@mediatek.com> (raw)
In-Reply-To: <1426657537-41414-1-git-send-email-eddie.huang@mediatek.com>

From: Tianping Fang <tianping.fang@mediatek.com>

Add Mediatek MT6397 RTC driver

Signed-off-by: Tianping Fang <tianping.fang@mediatek.com>
Signed-off-by: Eddie Huang <eddie.huang@mediatek.com>
---
 drivers/rtc/Kconfig      |  10 ++
 drivers/rtc/Makefile     |   1 +
 drivers/rtc/rtc-mt6397.c | 454 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 465 insertions(+)
 create mode 100644 drivers/rtc/rtc-mt6397.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index b5b5c3d..dcb94af 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1510,6 +1510,16 @@ config RTC_DRV_MOXART
 	   This driver can also be built as a module. If so, the module
 	   will be called rtc-moxart
 
+config RTC_DRV_MT6397
+	tristate "Mediatek Real Time Clock driver"
+	depends on MFD_MT6397 || COMPILE_TEST
+	help
+	  This selects the Mediatek(R) RTC driver. RTC is part of Mediatek
+	  MT6397 PMIC. You should enable MT6397 PMIC MFD before select
+	  Mediatek(R) RTC driver.
+
+	  If you want to use Mediatek(R) RTC interface, select Y or M here.
+
 config RTC_DRV_XGENE
 	tristate "APM X-Gene RTC"
 	depends on HAS_IOMEM
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 69c8706..85c8bea 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -153,3 +153,4 @@ obj-$(CONFIG_RTC_DRV_X1205)	+= rtc-x1205.o
 obj-$(CONFIG_RTC_DRV_XGENE)	+= rtc-xgene.o
 obj-$(CONFIG_RTC_DRV_SIRFSOC)	+= rtc-sirfsoc.o
 obj-$(CONFIG_RTC_DRV_MOXART)	+= rtc-moxart.o
+obj-$(CONFIG_RTC_DRV_MT6397)	+= rtc-mt6397.o
diff --git a/drivers/rtc/rtc-mt6397.c b/drivers/rtc/rtc-mt6397.c
new file mode 100644
index 0000000..130480d
--- /dev/null
+++ b/drivers/rtc/rtc-mt6397.c
@@ -0,0 +1,454 @@
+/*
+* Copyright (c) 2014-2015 MediaTek Inc.
+* Author: Tianping.Fang <tianping.fang@mediatek.com>
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License version 2 as
+* published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*/
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+#include <linux/irqdomain.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/mfd/mt6397/core.h>
+
+#define RTC_BBPU		0x0000
+#define RTC_WRTGR		0x003c
+#define RTC_IRQ_EN		0x0004
+#define RTC_IRQ_STA		0x0002
+
+#define RTC_BBPU_CBUSY		(1 << 6)
+#define RTC_IRQ_STA_AL		(1 << 0)
+#define RTC_IRQ_STA_LP		(1 << 3)
+
+#define RTC_AL_MASK		0x0008
+#define RTC_TC_SEC		0x000a
+#define RTC_TC_MIN		0x000c
+#define RTC_TC_HOU		0x000e
+#define RTC_TC_DOM		0x0010
+#define RTC_TC_MTH		0x0014
+#define RTC_TC_YEA		0x0016
+#define RTC_AL_SEC		0x0018
+#define RTC_AL_MIN		0x001a
+
+#define RTC_IRQ_EN_AL		(1 << 0)
+#define RTC_IRQ_EN_ONESHOT	(1 << 2)
+#define RTC_IRQ_EN_LP		(1 << 3)
+#define RTC_IRQ_EN_ONESHOT_AL	(RTC_IRQ_EN_ONESHOT | RTC_IRQ_EN_AL)
+
+#define RTC_TC_MIN_MASK		0x003f
+#define RTC_TC_SEC_MASK		0x003f
+#define RTC_TC_HOU_MASK		0x001f
+#define RTC_TC_DOM_MASK		0x001f
+#define RTC_TC_MTH_MASK		0x000f
+#define RTC_TC_YEA_MASK		0x007f
+
+#define RTC_AL_SEC_MASK		0x003f
+#define RTC_AL_MIN_MASK		0x003f
+#define RTC_AL_MASK_DOW		(1 << 4)
+
+#define RTC_AL_HOU		0x001c
+#define RTC_AL_HOU_MASK		0x001f
+
+#define RTC_AL_DOM		0x001e
+#define RTC_AL_DOM_MASK		0x001f
+
+#define RTC_AL_MTH		0x0022
+#define RTC_AL_MTH_MASK		0x000f
+
+#define RTC_AL_YEA		0x0024
+#define RTC_AL_YEA_MASK		0x007f
+
+#define RTC_PDN2			0x002e
+#define RTC_PDN2_PWRON_MTH_MASK		0x000f
+#define RTC_PDN2_PWRON_MTH_SHIFT	0
+#define RTC_PDN2_PWRON_ALARM		(1 << 4)
+#define RTC_PDN2_UART_MASK		0x0060
+#define RTC_PDN2_UART_SHIFT		5
+#define RTC_PDN2_PWRON_YEA_MASK		0x7f00
+#define RTC_PDN2_PWRON_YEA_SHIFT	8
+#define RTC_PDN2_PWRON_LOGO		(1 << 15)
+
+#define RTC_MIN_YEAR		1968
+#define RTC_BASE_YEAR		1900
+#define RTC_NUM_YEARS		128
+#define RTC_MIN_YEAR_OFFSET	(RTC_MIN_YEAR - RTC_BASE_YEAR)
+#define RTC_RELPWR_WHEN_XRST	1
+
+struct mt6397_rtc {
+	struct device		*dev;
+	struct rtc_device	*rtc_dev;
+	struct mutex		lock;
+	struct regmap		*regmap;
+	int			irq;
+	u32			addr_base;
+	u32			addr_range;
+};
+
+static int mtk_rtc_read(struct mt6397_rtc *rtc, u32 offset, u32 *data)
+{
+	u32 addr = rtc->addr_base + offset;
+
+	if (offset < rtc->addr_range)
+		return regmap_read(rtc->regmap, addr, data);
+
+	return -EINVAL;
+}
+
+static int mtk_rtc_write(struct mt6397_rtc *rtc, u32 offset, u32 data)
+{
+	u32 addr;
+
+	addr = rtc->addr_base + offset;
+
+	if (offset < rtc->addr_range)
+		return regmap_write(rtc->regmap, addr, data);
+
+	return -EINVAL;
+}
+
+static int mtk_rtc_write_trigger(struct mt6397_rtc *rtc)
+{
+	int ret;
+	u32 data;
+
+	ret = mtk_rtc_write(rtc, RTC_WRTGR, 1);
+	if (ret < 0)
+		goto exit;
+
+	ret = mtk_rtc_read(rtc, RTC_BBPU, &data);
+	if (ret < 0)
+		goto exit;
+
+	while (data & RTC_BBPU_CBUSY) {
+		cpu_relax();
+		ret = mtk_rtc_read(rtc, RTC_BBPU, &data);
+		if (ret < 0)
+			goto exit;
+	}
+
+exit:
+	return ret;
+}
+
+static irqreturn_t mtk_rtc_irq_handler_thread(int irq, void *data)
+{
+	struct mt6397_rtc *rtc = data;
+	u32 irqsta, irqen;
+	int ret;
+
+	ret = mtk_rtc_read(rtc, RTC_IRQ_STA, &irqsta);
+
+	if ((ret >= 0) && (irqsta & RTC_IRQ_STA_AL)) {
+		rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+		irqen = irqsta & ~RTC_IRQ_EN_AL;
+		mutex_lock(&rtc->lock);
+		if (mtk_rtc_write(rtc, RTC_IRQ_EN, irqen) < 0)
+			mtk_rtc_write_trigger(rtc);
+		mutex_unlock(&rtc->lock);
+
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static int __mtk_rtc_read_time(struct mt6397_rtc *rtc,
+				struct rtc_time *tm, int *sec)
+{
+	int ret;
+
+	mutex_lock(&rtc->lock);
+	ret = mtk_rtc_read(rtc, RTC_TC_SEC, &tm->tm_sec);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_MIN, &tm->tm_min);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_HOU, &tm->tm_hour);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_DOM, &tm->tm_mday);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_MTH, &tm->tm_mon);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_YEA, &tm->tm_year);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_read(rtc, RTC_TC_SEC, sec);
+
+exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static int mtk_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	time64_t time;
+	struct mt6397_rtc *rtc = dev_get_drvdata(dev);
+	int sec, ret;
+
+	do {
+		ret = __mtk_rtc_read_time(rtc, tm, &sec);
+		if (ret < 0)
+			goto exit;
+	} while (sec < tm->tm_sec);
+
+	tm->tm_year += RTC_MIN_YEAR_OFFSET;
+	tm->tm_mon--;
+	time = rtc_tm_to_time64(tm);
+
+	tm->tm_wday = (time / 86400 + 4) % 7;
+
+exit:
+	return ret;
+}
+
+static int mtk_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct mt6397_rtc *rtc = dev_get_drvdata(dev);
+	int ret;
+
+	tm->tm_year -= RTC_MIN_YEAR_OFFSET;
+	tm->tm_mon++;
+
+	mutex_lock(&rtc->lock);
+	ret = mtk_rtc_write(rtc, RTC_TC_YEA, tm->tm_year);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_MTH, tm->tm_mon);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_DOM, tm->tm_mday);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_HOU, tm->tm_hour);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_MIN, tm->tm_min);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write(rtc, RTC_TC_SEC, tm->tm_sec);
+	if (ret < 0)
+		goto exit;
+	ret = mtk_rtc_write_trigger(rtc);
+
+exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static int mtk_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	struct rtc_time *tm = &alm->time;
+	struct mt6397_rtc *rtc = dev_get_drvdata(dev);
+	u32 irqen, pdn2;
+	int ret;
+
+	mutex_lock(&rtc->lock);
+	ret = mtk_rtc_read(rtc, RTC_IRQ_EN, &irqen);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_PDN2, &pdn2);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_SEC, &tm->tm_sec);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_MIN, &tm->tm_min);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_HOU, &tm->tm_hour);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_DOM, &tm->tm_mday);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_MTH, &tm->tm_mon);
+	if (ret < 0)
+		goto err_exit;
+	ret = mtk_rtc_read(rtc, RTC_AL_YEA, &tm->tm_year);
+	if (ret < 0)
+		goto err_exit;
+
+	alm->enabled = !!(irqen & RTC_IRQ_EN_AL);
+	alm->pending = !!(pdn2 & RTC_PDN2_PWRON_ALARM);
+	mutex_unlock(&rtc->lock);
+
+	tm->tm_year += RTC_MIN_YEAR_OFFSET;
+	tm->tm_mon--;
+
+	return 0;
+err_exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static int mtk_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	struct rtc_time *tm = &alm->time;
+	struct mt6397_rtc *rtc = dev_get_drvdata(dev);
+	u32 irqen;
+	int ret;
+
+	tm->tm_year -= RTC_MIN_YEAR_OFFSET;
+	tm->tm_mon++;
+
+	mutex_lock(&rtc->lock);
+	if (alm->enabled) {
+		ret = mtk_rtc_write(rtc, RTC_AL_YEA, tm->tm_year);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_MTH, tm->tm_mon);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_DOM, tm->tm_mday);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_HOU, tm->tm_hour);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_MIN, tm->tm_min);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_SEC, tm->tm_sec);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write(rtc, RTC_AL_MASK, RTC_AL_MASK_DOW);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write_trigger(rtc);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_read(rtc, RTC_IRQ_EN, &irqen);
+		if (ret < 0)
+			goto exit;
+		irqen |= RTC_IRQ_EN_ONESHOT_AL;
+		ret = mtk_rtc_write(rtc, RTC_IRQ_EN, irqen);
+		if (ret < 0)
+			goto exit;
+		ret = mtk_rtc_write_trigger(rtc);
+		if (ret < 0)
+			goto exit;
+	} else {
+		ret = mtk_rtc_read(rtc, RTC_IRQ_EN, &irqen);
+		if (ret < 0)
+			goto exit;
+		irqen &= ~RTC_IRQ_EN_ONESHOT_AL;
+		ret = mtk_rtc_write(rtc, RTC_IRQ_EN, irqen);
+		if (ret < 0)
+			goto exit;
+	}
+
+exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static struct rtc_class_ops mtk_rtc_ops = {
+	.read_time  = mtk_rtc_read_time,
+	.set_time   = mtk_rtc_set_time,
+	.read_alarm = mtk_rtc_read_alarm,
+	.set_alarm  = mtk_rtc_set_alarm,
+};
+
+static int mtk_rtc_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct mt6397_chip *mt6397_chip = dev_get_drvdata(pdev->dev.parent);
+	struct mt6397_rtc *rtc;
+	int ret = 0;
+
+	rtc = devm_kzalloc(&pdev->dev, sizeof(struct mt6397_rtc), GFP_KERNEL);
+	if (!rtc)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	rtc->addr_base = res->start;
+	rtc->addr_range = res->end - res->start;
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	rtc->irq = irq_create_mapping(mt6397_chip->irq_domain, res->start);
+	if (rtc->irq <= 0)
+		goto out_rtc;
+
+	rtc->regmap = mt6397_chip->regmap;
+	rtc->dev = &pdev->dev;
+	mutex_init(&rtc->lock);
+
+	platform_set_drvdata(pdev, rtc);
+
+	ret = devm_request_threaded_irq(&pdev->dev, rtc->irq, NULL,
+			mtk_rtc_irq_handler_thread,
+			IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+			"mt6397-rtc", rtc);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
+			rtc->irq, ret);
+		goto out_rtc;
+	}
+
+	rtc->rtc_dev = rtc_device_register("mt6397-rtc", &pdev->dev,
+				&mtk_rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc->rtc_dev)) {
+		dev_err(&pdev->dev, "register rtc device failed\n");
+		return PTR_ERR(rtc->rtc_dev);
+	}
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	return 0;
+
+out_rtc:
+	rtc_device_unregister(rtc->rtc_dev);
+	return ret;
+
+}
+
+static int mtk_rtc_remove(struct platform_device *pdev)
+{
+	struct mt6397_rtc *rtc = platform_get_drvdata(pdev);
+
+	rtc_device_unregister(rtc->rtc_dev);
+	return 0;
+}
+
+static const struct of_device_id mt6397_rtc_of_match[] = {
+	{ .compatible = "mediatek,mt6397-rtc", },
+	{ }
+};
+
+static struct platform_driver mtk_rtc_driver = {
+	.driver = {
+		.name = "mt6397-rtc",
+		.of_match_table = mt6397_rtc_of_match,
+	},
+	.probe	= mtk_rtc_probe,
+	.remove = mtk_rtc_remove,
+};
+
+module_platform_driver(mtk_rtc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Tianping Fang <tianping.fang@mediatek.com>");
+MODULE_DESCRIPTION("RTC Driver for MediaTek MT6397 PMIC");
+MODULE_ALIAS("platform:mt6397-rtc");
-- 
1.8.1.1.dirty

  parent reply	other threads:[~2015-03-18  5:46 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-03-18  5:45 [PATCH v2 0/3] Add Mediatek SoC RTC driver Eddie Huang
2015-03-18  5:45 ` Eddie Huang
2015-03-18  5:45 ` [PATCH v2 1/3] mfd: provide RTC resource in MT6397 MFD Eddie Huang
2015-03-18  5:45   ` Eddie Huang
2015-03-18  7:30   ` Lee Jones
2015-03-18  7:30     ` Lee Jones
2015-03-30  8:17     ` Eddie Huang
2015-03-30  8:17       ` Eddie Huang
2015-03-30  8:17       ` [rtc-linux] " Eddie Huang
2015-03-18  5:45 ` Eddie Huang [this message]
2015-03-18  5:45   ` [PATCH v2 2/3] rtc: mediatek: Add MT6397 RTC driver Eddie Huang
2015-03-21  5:25   ` Dmitry Torokhov
2015-03-21  5:25     ` Dmitry Torokhov
2015-03-30  8:15     ` Eddie Huang
2015-03-30  8:15       ` Eddie Huang
2015-03-30  8:15       ` [rtc-linux] " Eddie Huang
2015-03-31 22:01       ` Andrew Morton
2015-03-31 22:01         ` Andrew Morton
2015-03-31 22:01         ` [rtc-linux] " Andrew Morton
2015-03-30  7:41   ` Tomasz Figa
2015-03-30  7:41     ` Tomasz Figa
2015-03-30  7:41     ` [rtc-linux] " 'Tomasz Figa' via rtc-linux
2015-03-31  9:44     ` Eddie Huang
2015-03-31  9:44       ` Eddie Huang
2015-03-31  9:44       ` [rtc-linux] " Eddie Huang
2015-03-31 11:01       ` Tomasz Figa
2015-03-31 11:01         ` Tomasz Figa
2015-03-31 11:01         ` Tomasz Figa
2015-03-31 11:01         ` [rtc-linux] " Tomasz Figa
2015-03-18  5:45 ` [PATCH v2 3/3] MAINTAINERS: add Mediatek " Eddie Huang
2015-03-18  5:45   ` Eddie Huang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1426657537-41414-3-git-send-email-eddie.huang@mediatek.com \
    --to=eddie.huang@mediatek.com \
    --cc=a.zummo@towertech.it \
    --cc=akpm@linux-foundation.org \
    --cc=davem@davemloft.net \
    --cc=gregkh@linuxfoundation.org \
    --cc=jg1.han@samsung.com \
    --cc=joe@perches.com \
    --cc=kernel@pengutronix.de \
    --cc=lee.jones@linaro.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mediatek@lists.infradead.org \
    --cc=matthias.bgg@gmail.com \
    --cc=mchehab@osg.samsung.com \
    --cc=rtc-linux@googlegroups.com \
    --cc=sameo@linux.intel.com \
    --cc=srv_heupstream@mediatek.com \
    --cc=tianping.fang@mediatek.com \
    --cc=tj@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.