Patchwork [v3,4/4] MAX8997/8966 RTC Driver Initial Release

login
register
mail settings
Submitter MyungJoo Ham
Date March 7, 2011, 10:27 a.m.
Message ID <1299493651-25832-5-git-send-email-myungjoo.ham@samsung.com>
Download mbox | patch
Permalink /patch/85712/
State New
Headers show

Comments

MyungJoo Ham - March 7, 2011, 10:27 a.m.
This patch adds support for RTC functionality for MAX8997/8966 chips.

The 16ms delay is somehow required by MAX8997 chips (at least for the
initial releases) and it can be enabled by .delay in platform_data.

Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 drivers/rtc/Kconfig         |   10 +
 drivers/rtc/Makefile        |    1 +
 drivers/rtc/rtc-max8997.c   |  445 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/max8997.h |    5 +-
 4 files changed, 460 insertions(+), 1 deletions(-)
 create mode 100644 drivers/rtc/rtc-max8997.c

Patch

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 4941cad..8dc93e8 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -196,6 +196,16 @@  config RTC_DRV_MAX8925
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-max8925.
 
+config RTC_DRV_MAX8997
+	tristate "Maxim MAX8997/8966"
+	depends on MFD_MAX8997
+	help
+	  If you say yes here you will get support for the
+	  RTC of Maxim MAX8997 and MAX8966 PMIC.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-max8997.
+
 config RTC_DRV_MAX8998
 	tristate "Maxim MAX8998"
 	depends on MFD_MAX8998
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2afdaf3..8b74d41 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -61,6 +61,7 @@  obj-$(CONFIG_RTC_DRV_M48T86)	+= rtc-m48t86.o
 obj-$(CONFIG_RTC_MXC)		+= rtc-mxc.o
 obj-$(CONFIG_RTC_DRV_MAX6900)	+= rtc-max6900.o
 obj-$(CONFIG_RTC_DRV_MAX8925)	+= rtc-max8925.o
+obj-$(CONFIG_RTC_DRV_MAX8997)	+= rtc-max8997.o
 obj-$(CONFIG_RTC_DRV_MAX8998)	+= rtc-max8998.o
 obj-$(CONFIG_RTC_DRV_MAX6902)	+= rtc-max6902.o
 obj-$(CONFIG_RTC_DRV_MC13XXX)	+= rtc-mc13xxx.o
diff --git a/drivers/rtc/rtc-max8997.c b/drivers/rtc/rtc-max8997.c
new file mode 100644
index 0000000..96e8df7
--- /dev/null
+++ b/drivers/rtc/rtc-max8997.c
@@ -0,0 +1,445 @@ 
+/*
+ * rtc-max8997.c - RTC driver for Maxim 8966 and 8997
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <linux/bcd.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+struct rtc_data {
+	struct mutex		lock;
+	struct i2c_client	*i2c;
+	struct max8997_dev	*iodev;
+
+	struct device		*dev;
+	struct rtc_device	*rtc_dev;
+
+	int irq;
+	bool delay;
+};
+
+enum {
+	RTC_SEC = 0,
+	RTC_MIN,
+	RTC_HOUR,
+	RTC_WEEKDAY,
+	RTC_MONTH,
+	RTC_YEAR,
+	RTC_MONTHDAY,
+	RTC_DATA_SIZE,
+};
+
+static inline int wday_8997_to_kernel(u8 shifted)
+{
+	int counter = -1;
+	while (shifted) {
+		shifted >>= 1;
+		counter++;
+	}
+	return counter;
+}
+
+static void __maybe_unused dump(struct rtc_data *rtc)
+{
+	int i;
+	u8 buf[48];
+
+	for (i = 0; i < 48; i++)
+		max8997_read_reg(rtc->i2c, i, &buf[i]);
+	print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 1,
+			buf, sizeof(buf), false);
+}
+
+static inline u8 wday_kernel_to_8997(u8 wday)
+{
+	return 1 << wday;
+}
+
+static void data_to_tm(u8 *data, struct rtc_time *tm)
+{
+	/* Binary Mode (not BCD) */
+	tm->tm_sec = bcd2bin(data[RTC_SEC] & 0x7f);
+	tm->tm_min = bcd2bin(data[RTC_MIN] & 0x7f);
+	tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x3f); /* 24 hour mode */
+	tm->tm_wday = wday_8997_to_kernel(data[RTC_WEEKDAY] & 0x7f);
+	tm->tm_mon = bcd2bin(data[RTC_MONTH] & 0x1f);
+	tm->tm_year = bcd2bin(data[RTC_YEAR] & 0x7f) + 100;
+	tm->tm_mday = bcd2bin(data[RTC_MONTHDAY] & 0x3f);
+	tm->tm_yday = 0;
+	tm->tm_isdst = 0;
+}
+
+static int tm_to_data(struct rtc_time *tm, u8 *data)
+{
+	data[RTC_SEC] = bin2bcd(tm->tm_sec) & 0x7f;
+	data[RTC_MIN] = bin2bcd(tm->tm_min) & 0x7f;
+	data[RTC_HOUR] = bin2bcd(tm->tm_hour) & 0x3f;
+	data[RTC_WEEKDAY] = wday_kernel_to_8997(tm->tm_wday) & 0x7f;
+	data[RTC_MONTH] = bin2bcd(tm->tm_mon) & 0x1f;
+	data[RTC_YEAR] = bin2bcd((tm->tm_year > 100) ? (tm->tm_year - 100) : 0)
+			& 0x7f;
+	data[RTC_MONTHDAY] = bin2bcd(tm->tm_mday) & 0x3f;
+
+	/* MAX8997 RTC cannot deal with years prior to 2000 */
+	if (tm->tm_year < 100) {
+		pr_err("MAX8997 RTC cannot handle the year %d. "
+				"Assume it's 2000.\n", 1900 + tm->tm_year);
+		return -EINVAL;
+	} else {
+		return 0;
+	}
+}
+
+static int max8997_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct rtc_data *rtc = dev_get_drvdata(dev);
+	u8 data[RTC_DATA_SIZE];
+	int ret;
+
+	mutex_lock(&rtc->lock);
+	ret = max8997_bulk_read(rtc->i2c, MAX8997_RTC_SEC, RTC_DATA_SIZE, data);
+	mutex_unlock(&rtc->lock);
+
+	if (ret < 0)
+		return ret;
+
+	data_to_tm(data, tm);
+
+	return rtc_valid_tm(tm);
+}
+
+static int max8997_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct rtc_data *rtc = dev_get_drvdata(dev);
+	u8 data[RTC_DATA_SIZE];
+	int ret;
+
+	ret = tm_to_data(tm, data);
+	if (ret)
+		return ret;
+
+	mutex_lock(&rtc->lock);
+
+	max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, 0x03);
+
+	ret = max8997_bulk_write(rtc->i2c, MAX8997_RTC_SEC,
+				RTC_DATA_SIZE, data);
+
+	if (rtc->delay)
+		msleep(20);
+
+	mutex_unlock(&rtc->lock);
+
+	return ret;
+}
+
+static int max8997_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct rtc_data *rtc = dev_get_drvdata(dev);
+	u8 data[RTC_DATA_SIZE];
+	u8 val;
+	int i;
+	int ret;
+
+	mutex_lock(&rtc->lock);
+	ret = max8997_bulk_read(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+				RTC_DATA_SIZE, data);
+
+	if (ret < 0)
+		goto exit;
+
+	data_to_tm(data, &alrm->time);
+
+	alrm->enabled = 0;
+	for (i = 0; i < RTC_DATA_SIZE; i++)
+		if (data[i] & (1 << 7)) {
+			alrm->enabled = 1;
+			break;
+		}
+
+	alrm->pending = 0;
+	ret = max8997_read_reg(rtc->iodev->i2c, MAX8997_REG_STATUS1, &val);
+	if (val & (1 << 4)) /* "RTCA1" */
+		alrm->pending = 1;
+exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static int max8997_rtc_start_alarm(struct rtc_data *rtc)
+{
+	u8 data[RTC_DATA_SIZE];
+	int ret;
+
+	/* Should be locked before entering here */
+	if (!mutex_is_locked(&rtc->lock))
+		dev_warn(rtc->dev, "%s should have mutex locked.\n", __func__);
+
+	ret = max8997_bulk_read(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+				RTC_DATA_SIZE, data);
+	if (rtc < 0)
+		goto exit;
+
+	data[RTC_SEC] |= (1 << 7);
+	data[RTC_MIN] |= (1 << 7);
+	data[RTC_HOUR] |= (1 << 7);
+	data[RTC_WEEKDAY] &= ~(1 << 7);
+	if (data[RTC_MONTH] & 0x1f)
+		data[RTC_MONTH] |= (1 << 7);
+	if (data[RTC_YEAR] & 0x7f)
+		data[RTC_YEAR] |= (1 << 7);
+	if (data[RTC_MONTHDAY] & 0x3f)
+		data[RTC_MONTHDAY] |= (1 << 7);
+
+	max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, 0x03);
+	ret = max8997_bulk_write(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+				RTC_DATA_SIZE, data);
+	if (ret < 0)
+		goto exit;
+	if (rtc->delay)
+		msleep(20);
+
+exit:
+	return ret;
+}
+
+static int max8997_rtc_stop_alarm(struct rtc_data *rtc)
+{
+	u8 data[RTC_DATA_SIZE];
+	int ret;
+	int i;
+
+	/* Should be locked before entering here */
+	if (!mutex_is_locked(&rtc->lock))
+		dev_warn(rtc->dev, "%s should have mutex locked.\n", __func__);
+
+	ret = max8997_bulk_read(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+				RTC_DATA_SIZE, data);
+	if (rtc < 0)
+		goto exit;
+
+	for (i = 0; i < RTC_DATA_SIZE; i++)
+		data[i] &= ~(1 << 7);
+
+	max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, 0x03);
+	ret = max8997_bulk_write(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+				RTC_DATA_SIZE, data);
+	if (ret < 0)
+		goto exit;
+	if (rtc->delay)
+		msleep(20);
+
+exit:
+	return ret;
+}
+
+static int max8997_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct rtc_data *rtc = dev_get_drvdata(dev);
+	u8 data[RTC_DATA_SIZE];
+	int ret;
+
+	ret = tm_to_data(&alrm->time, data);
+	if (ret)
+		return ret;
+
+	mutex_lock(&rtc->lock);
+	ret = max8997_rtc_stop_alarm(rtc);
+	if (ret < 0)
+		goto exit;
+
+	if (alrm->enabled) {
+		data[RTC_SEC] |= (1 << 7);
+		data[RTC_MIN] |= (1 << 7);
+		data[RTC_HOUR] |= (1 << 7);
+		data[RTC_WEEKDAY] &= ~(1 << 7);
+		if (data[RTC_MONTH] & 0x1f)
+			data[RTC_MONTH] |= (1 << 7);
+		if (data[RTC_YEAR] & 0x7f)
+			data[RTC_YEAR] |= (1 << 7);
+		if (data[RTC_MONTHDAY] & 0x3f)
+			data[RTC_MONTHDAY] |= (1 << 7);
+	}
+
+	max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, 0x03);
+
+	ret = max8997_bulk_write(rtc->i2c, MAX8997_RTC_ALARM1_SEC,
+				RTC_DATA_SIZE, data);
+	if (ret < 0)
+		goto exit;
+	if (rtc->delay)
+		msleep(20);
+
+exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static int max8997_rtc_alarm_irq_enable(struct device *dev,
+					unsigned int enabled)
+{
+	struct rtc_data *rtc = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&rtc->lock);
+	if (enabled)
+		ret = max8997_rtc_start_alarm(rtc);
+	else
+		ret = max8997_rtc_stop_alarm(rtc);
+	mutex_unlock(&rtc->lock);
+
+	return ret;
+}
+
+static irqreturn_t max8997_rtc_alarm_irq(int irq, void *data)
+{
+	struct rtc_data *rtc = data;
+
+	rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+	return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops max8997_rtc_ops = {
+	.read_time		= max8997_rtc_read_time,
+	.set_time		= max8997_rtc_set_time,
+	.read_alarm		= max8997_rtc_read_alarm,
+	.set_alarm		= max8997_rtc_set_alarm,
+	.alarm_irq_enable	= max8997_rtc_alarm_irq_enable,
+};
+
+static __devinit int rtc_probe(struct platform_device *pdev)
+{
+	struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+	struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev);
+	struct rtc_data *rtc;
+	int ret = 0;
+	u8 val;
+
+	if (!pdata) {
+		dev_err(pdev->dev.parent, "No platform init data supplied.\n");
+		return -ENODEV;
+	}
+
+	rtc = kzalloc(sizeof(struct rtc_data), GFP_KERNEL);
+	if (!rtc)
+		return -ENOMEM;
+
+	mutex_init(&rtc->lock);
+	mutex_lock(&rtc->lock);
+	rtc->i2c = iodev->rtc;
+	rtc->dev = &pdev->dev;
+	rtc->iodev = iodev;
+	rtc->irq = iodev->irq_base + MAX8997_PMICIRQ_RTCA1;
+
+	if (pdata->delay)
+		rtc->delay = true;
+
+	platform_set_drvdata(pdev, rtc);
+
+	/* Allow update setting */
+	val = 0x03;
+	max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRLMASK, val);
+	/* 24 hour | BCD */
+	val = (1 << 1) | (1 << 0);
+	max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRL, val);
+	/* Allow RTC Update */
+	val = 0x03;
+	max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, val);
+
+	if (pdata->wakeup)
+		device_init_wakeup(&pdev->dev, 1);
+
+	rtc->rtc_dev = rtc_device_register("max8997-rtc", &pdev->dev,
+			&max8997_rtc_ops, THIS_MODULE);
+	if (IS_ERR_OR_NULL(rtc->rtc_dev)) {
+		ret = PTR_ERR(rtc->rtc_dev);
+
+		dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+		if (ret == 0)
+			ret = -EINVAL;
+		goto err;
+	}
+
+	ret = request_threaded_irq(rtc->irq, NULL, max8997_rtc_alarm_irq, 0,
+			"max8997-rtc-alarm1", rtc);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to request alarm IRQ %d (%d)\n",
+				rtc->irq, ret);
+		goto err;
+	}
+
+	goto exit;
+err:
+	kfree(rtc);
+exit:
+	mutex_unlock(&rtc->lock);
+	return ret;
+}
+
+static __devexit int rtc_remove(struct platform_device *pdev)
+{
+	struct rtc_data *rtc = platform_get_drvdata(pdev);
+
+	rtc_device_unregister(rtc->rtc_dev);
+	free_irq(rtc->irq, rtc);
+	kfree(rtc);
+
+	return 0;
+}
+
+static const struct platform_device_id rtc_id[] = {
+	{ "max8997-rtc", 0 },
+	{ },
+};
+
+static struct platform_driver max8997_rtc_driver = {
+	.driver = {
+		.name = "max8997-rtc",
+		.owner = THIS_MODULE,
+	},
+	.probe = rtc_probe,
+	.remove = __devexit_p(rtc_remove),
+	.id_table = rtc_id,
+};
+
+static int __init max8997_rtc_init(void)
+{
+	return platform_driver_register(&max8997_rtc_driver);
+}
+subsys_initcall(max8997_rtc_init);
+
+static void __exit max8997_rtc_cleanup(void)
+{
+	platform_driver_unregister(&max8997_rtc_driver);
+}
+module_exit(max8997_rtc_cleanup);
+
+MODULE_DESCRIPTION("MAXIM 8997/8966 RTC Driver");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:max8997-rtc");
diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h
index 60931d0..83f39b7 100644
--- a/include/linux/mfd/max8997.h
+++ b/include/linux/mfd/max8997.h
@@ -109,7 +109,10 @@  struct max8997_platform_data {
 
 	/* MUIC: Not implemented */
 	/* HAPTIC: Not implemented */
-	/* RTC: Not implemented */
+
+	/* ---- RTC ---- */
+	bool delay; /* The MAX8997 RTC write delay is requred. */
+
 	/* Flash: Not implemented */
 	/* Charger control: Not implemented */
 };