Message ID | 1300239551-21285-3-git-send-email-myungjoo.ham@samsung.com |
---|---|
State | Accepted |
Headers | show |
Hi MyungJoo, On Wed, Mar 16, 2011 at 10:39:11AM +0900, MyungJoo Ham wrote: > 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. I'll wait for Alessandro's ACK before pushing this one. Cheers, Samuel.
Hi MyungJoo, Some comments inline. On Wed, Mar 16, 2011 at 7:09 AM, MyungJoo Ham <myungjoo.ham@samsung.com>wrote: > 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 | 476 > +++++++++++++++++++++++++++++++++++++++++++ > include/linux/mfd/max8997.h | 5 +- > 4 files changed, 491 insertions(+), 1 deletions(-) > create mode 100644 drivers/rtc/rtc-max8997.c > > 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..b1a4b66 > --- /dev/null > +++ b/drivers/rtc/rtc-max8997.c > @@ -0,0 +1,476 @@ > +/* > + * 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> Do you really need the above include? +#include <linux/slab.h> > +#include <linux/platform_device.h> > +#include <linux/rtc.h> > +#include <linux/mfd/max8997.h> > +#include <linux/mfd/max8997-private.h> > + > +/* Allow RTC Update */ > +#define MAX8997_RTC_UPDATE1_ENABLE_UPDATE (1 << 0) > +/* > + * Set Interrupt Mode (about how the interrupt is cleared.) > + * Set the bit in order to let inetrrupt be cleared with register read > + * Unset the bit in order to let it be cleared by writing 0. > + */ > +#define MAX8997_RTC_UPDATE1_INT_READ_CLEAR (1 << 1) > + > +/* Mode between BCD(1) and Binary(0) */ > +#define MAX8997_RTC_CTRL_BCD (1 << 0) > +/* Mode between 24hrs(1) and 12hrs(0) */ > +#define MAX8997_RTC_CTRL_Mode24_12 (1 << 1) > + > +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) - 1; > + 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 + 1) & 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); > + > + /* Allow RTC Update */ > + max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, > + MAX8997_RTC_UPDATE1_ENABLE_UPDATE | > + MAX8997_RTC_UPDATE1_INT_READ_CLEAR); > + > + 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); > Please explain the need the locks here and in the other ops ? If this is for the I2C writes, I see that core driver already has mutexs for read & writes. If this is for RTC ops they are already serialized by the RTC core. Please remove them if not required. > + 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); > + > + /* Allow RTC Update */ > + max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, > + MAX8997_RTC_UPDATE1_ENABLE_UPDATE | > + MAX8997_RTC_UPDATE1_INT_READ_CLEAR); > + > + 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); > + > + /* Allow RTC Update */ > + max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, > + MAX8997_RTC_UPDATE1_ENABLE_UPDATE | > + MAX8997_RTC_UPDATE1_INT_READ_CLEAR); > + > + 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); > + } > + > + /* Allow RTC Update */ > + max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, > + MAX8997_RTC_UPDATE1_ENABLE_UPDATE | > + MAX8997_RTC_UPDATE1_INT_READ_CLEAR); > please handle the return error. > + > + 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); const struct ? > + 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); > + > + val = MAX8997_RTC_CTRL_BCD | > + MAX8997_RTC_CTRL_Mode24_12; > + max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRLMASK, val); > please handle the error return for all i2c read/write calls. > + > + val = MAX8997_RTC_CTRL_BCD | > + MAX8997_RTC_CTRL_Mode24_12; > + max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRL, val); > + > + /* Allow RTC Update */ > + val = MAX8997_RTC_UPDATE1_ENABLE_UPDATE | > + MAX8997_RTC_UPDATE1_INT_READ_CLEAR; > + 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); > Please unregister the rtc device in this error path. > + 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 */ > }; > -- > 1.7.1 > > Thx, ~Anirudh > -- > You received this message because you are subscribed to "rtc-linux". > Membership options at http://groups.google.com/group/rtc-linux . > Please read http://groups.google.com/group/rtc-linux/web/checklist > before submitting a driver.
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..b1a4b66 --- /dev/null +++ b/drivers/rtc/rtc-max8997.c @@ -0,0 +1,476 @@ +/* + * 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> + +/* Allow RTC Update */ +#define MAX8997_RTC_UPDATE1_ENABLE_UPDATE (1 << 0) +/* + * Set Interrupt Mode (about how the interrupt is cleared.) + * Set the bit in order to let inetrrupt be cleared with register read + * Unset the bit in order to let it be cleared by writing 0. + */ +#define MAX8997_RTC_UPDATE1_INT_READ_CLEAR (1 << 1) + +/* Mode between BCD(1) and Binary(0) */ +#define MAX8997_RTC_CTRL_BCD (1 << 0) +/* Mode between 24hrs(1) and 12hrs(0) */ +#define MAX8997_RTC_CTRL_Mode24_12 (1 << 1) + +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) - 1; + 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 + 1) & 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); + + /* Allow RTC Update */ + max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, + MAX8997_RTC_UPDATE1_ENABLE_UPDATE | + MAX8997_RTC_UPDATE1_INT_READ_CLEAR); + + 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); + + /* Allow RTC Update */ + max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, + MAX8997_RTC_UPDATE1_ENABLE_UPDATE | + MAX8997_RTC_UPDATE1_INT_READ_CLEAR); + + 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); + + /* Allow RTC Update */ + max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, + MAX8997_RTC_UPDATE1_ENABLE_UPDATE | + MAX8997_RTC_UPDATE1_INT_READ_CLEAR); + + 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); + } + + /* Allow RTC Update */ + max8997_write_reg(rtc->i2c, MAX8997_RTC_UPDATE1, + MAX8997_RTC_UPDATE1_ENABLE_UPDATE | + MAX8997_RTC_UPDATE1_INT_READ_CLEAR); + + 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); + + val = MAX8997_RTC_CTRL_BCD | + MAX8997_RTC_CTRL_Mode24_12; + max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRLMASK, val); + + val = MAX8997_RTC_CTRL_BCD | + MAX8997_RTC_CTRL_Mode24_12; + max8997_write_reg(rtc->i2c, MAX8997_RTC_CTRL, val); + + /* Allow RTC Update */ + val = MAX8997_RTC_UPDATE1_ENABLE_UPDATE | + MAX8997_RTC_UPDATE1_INT_READ_CLEAR; + 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 */ };