From 8c3167d8e0673a06e2a98980734e251dea56cee3 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 21 Sep 2012 11:02:17 +1000 Subject: [PATCH] rtc: add MAX8907 RTC driver The MAX8907 is an I2C-based power-management IC containing voltage regulators, a reset controller, a real-time clock, and a touch-screen controller. The driver is based on an original by or fixed by: * Tom Cherry * Prashant Gaikwad * Joseph Yoon During upstreaming, I (swarren): * Converted to regmap. * Fixed handling of RTC_HOUR register containing 12. * Fixed handling of RTC_WEEKDAY register. * General cleanup. Signed-off-by: Stephen Warren Cc: Tom Cherry Cc: Prashant Gaikwad Cc: Joseph Yoon Cc: Alessandro Zummo Signed-off-by: Andrew Morton --- drivers/rtc/Kconfig | 10 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-max8907.c | 245 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) create mode 100644 drivers/rtc/rtc-max8907.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 6309df4e2654..499fb9984abe 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -205,6 +205,16 @@ config RTC_DRV_MAX6900 This driver can also be built as a module. If so, the module will be called rtc-max6900. +config RTC_DRV_MAX8907 + tristate "Maxim MAX8907" + depends on MFD_MAX8907 + help + If you say yes here you will get support for the + RTC of Maxim MAX8907 PMIC. + + This driver can also be built as a module. If so, the module + will be called rtc-max8907. + config RTC_DRV_MAX8925 tristate "Maxim MAX8925" depends on MFD_MAX8925 diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 5c8f106237da..d9526b549f87 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o obj-$(CONFIG_RTC_DRV_MXC) += rtc-mxc.o obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o +obj-$(CONFIG_RTC_DRV_MAX8907) += rtc-max8907.o obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o diff --git a/drivers/rtc/rtc-max8907.c b/drivers/rtc/rtc-max8907.c new file mode 100644 index 000000000000..488037412923 --- /dev/null +++ b/drivers/rtc/rtc-max8907.c @@ -0,0 +1,245 @@ +/* + * RTC driver for Maxim MAX8907 + * + * Copyright (c) 2011-2012, NVIDIA Corporation. + * + * Based on drivers/rtc/rtc-max8925.c, + * Copyright (C) 2009-2010 Marvell International Ltd. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + RTC_SEC = 0, + RTC_MIN, + RTC_HOUR, + RTC_WEEKDAY, + RTC_DATE, + RTC_MONTH, + RTC_YEAR1, + RTC_YEAR2, +}; + +#define TIME_NUM 8 +#define ALARM_1SEC (1 << 7) +#define HOUR_12 (1 << 7) +#define HOUR_AM_PM (1 << 5) +#define ALARM0_IRQ (1 << 3) +#define ALARM1_IRQ (1 << 2) +#define ALARM0_STATUS (1 << 2) +#define ALARM1_STATUS (1 << 1) + +struct max8907_rtc { + struct max8907 *max8907; + struct regmap *regmap; + struct rtc_device *rtc_dev; + int irq; +}; + +static irqreturn_t max8907_irq_handler(int irq, void *data) +{ + struct max8907_rtc *rtc = data; + + regmap_update_bits(rtc->regmap, MAX8907_REG_ALARM0_CNTL, 0x7f, 0); + + rtc_update_irq(rtc->rtc_dev, 1, RTC_IRQF | RTC_AF); + + return IRQ_HANDLED; +} + +static void regs_to_tm(u8 *regs, struct rtc_time *tm) +{ + tm->tm_year = bcd2bin(regs[RTC_YEAR2]) * 100 + + bcd2bin(regs[RTC_YEAR1]) - 1900; + tm->tm_mon = bcd2bin(regs[RTC_MONTH] & 0x1f) - 1; + tm->tm_mday = bcd2bin(regs[RTC_DATE] & 0x3f); + tm->tm_wday = (regs[RTC_WEEKDAY] & 0x07) - 1; + if (regs[RTC_HOUR] & HOUR_12) { + tm->tm_hour = bcd2bin(regs[RTC_HOUR] & 0x01f); + if (tm->tm_hour == 12) + tm->tm_hour = 0; + if (regs[RTC_HOUR] & HOUR_AM_PM) + tm->tm_hour += 12; + } else { + tm->tm_hour = bcd2bin(regs[RTC_HOUR] & 0x03f); + } + tm->tm_min = bcd2bin(regs[RTC_MIN] & 0x7f); + tm->tm_sec = bcd2bin(regs[RTC_SEC] & 0x7f); +} + +static void tm_to_regs(struct rtc_time *tm, u8 *regs) +{ + u8 high, low; + + high = (tm->tm_year + 1900) / 100; + low = tm->tm_year % 100; + regs[RTC_YEAR2] = bin2bcd(high); + regs[RTC_YEAR1] = bin2bcd(low); + regs[RTC_MONTH] = bin2bcd(tm->tm_mon + 1); + regs[RTC_DATE] = bin2bcd(tm->tm_mday); + regs[RTC_WEEKDAY] = tm->tm_wday + 1; + regs[RTC_HOUR] = bin2bcd(tm->tm_hour); + regs[RTC_MIN] = bin2bcd(tm->tm_min); + regs[RTC_SEC] = bin2bcd(tm->tm_sec); +} + +static int max8907_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct max8907_rtc *rtc = dev_get_drvdata(dev); + u8 regs[TIME_NUM]; + int ret; + + ret = regmap_bulk_read(rtc->regmap, MAX8907_REG_RTC_SEC, regs, + TIME_NUM); + if (ret < 0) + return ret; + + regs_to_tm(regs, tm); + + return 0; +} + +static int max8907_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct max8907_rtc *rtc = dev_get_drvdata(dev); + u8 regs[TIME_NUM]; + + tm_to_regs(tm, regs); + + return regmap_bulk_write(rtc->regmap, MAX8907_REG_RTC_SEC, regs, + TIME_NUM); +} + +static int max8907_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max8907_rtc *rtc = dev_get_drvdata(dev); + u8 regs[TIME_NUM]; + unsigned int val; + int ret; + + ret = regmap_bulk_read(rtc->regmap, MAX8907_REG_ALARM0_SEC, regs, + TIME_NUM); + if (ret < 0) + return ret; + + regs_to_tm(regs, &alrm->time); + + ret = regmap_read(rtc->regmap, MAX8907_REG_ALARM0_CNTL, &val); + if (ret < 0) + return ret; + + alrm->enabled = !!(val & 0x7f); + + return 0; +} + +static int max8907_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max8907_rtc *rtc = dev_get_drvdata(dev); + u8 regs[TIME_NUM]; + int ret; + + tm_to_regs(&alrm->time, regs); + + /* Disable alarm while we update the target time */ + ret = regmap_update_bits(rtc->regmap, MAX8907_REG_ALARM0_CNTL, 0x7f, 0); + if (ret < 0) + return ret; + + ret = regmap_bulk_write(rtc->regmap, MAX8907_REG_ALARM0_SEC, regs, + TIME_NUM); + if (ret < 0) + return ret; + + if (alrm->enabled) + ret = regmap_update_bits(rtc->regmap, MAX8907_REG_ALARM0_CNTL, + 0x7f, 0x7f); + + return ret; +} + +static const struct rtc_class_ops max8907_rtc_ops = { + .read_time = max8907_rtc_read_time, + .set_time = max8907_rtc_set_time, + .read_alarm = max8907_rtc_read_alarm, + .set_alarm = max8907_rtc_set_alarm, +}; + +static int __devinit max8907_rtc_probe(struct platform_device *pdev) +{ + struct max8907 *max8907 = dev_get_drvdata(pdev->dev.parent); + struct max8907_rtc *rtc; + int ret; + + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + platform_set_drvdata(pdev, rtc); + + rtc->max8907 = max8907; + rtc->regmap = max8907->regmap_rtc; + + rtc->rtc_dev = rtc_device_register("max8907-rtc", &pdev->dev, + &max8907_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc->rtc_dev)) { + ret = PTR_ERR(rtc->rtc_dev); + dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret); + return ret; + } + + rtc->irq = regmap_irq_get_virq(max8907->irqc_rtc, + MAX8907_IRQ_RTC_ALARM0); + if (rtc->irq < 0) { + ret = rtc->irq; + goto err_unregister; + } + + ret = request_threaded_irq(rtc->irq, NULL, max8907_irq_handler, + IRQF_ONESHOT, "max8907-alarm0", rtc); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request IRQ%d: %d\n", + rtc->irq, ret); + goto err_unregister; + } + + return 0; + +err_unregister: + rtc_device_unregister(rtc->rtc_dev); + return ret; +} + +static int __devexit max8907_rtc_remove(struct platform_device *pdev) +{ + struct max8907_rtc *rtc = platform_get_drvdata(pdev); + + free_irq(rtc->irq, rtc); + rtc_device_unregister(rtc->rtc_dev); + + return 0; +} + +static struct platform_driver max8907_rtc_driver = { + .driver = { + .name = "max8907-rtc", + .owner = THIS_MODULE, + }, + .probe = max8907_rtc_probe, + .remove = __devexit_p(max8907_rtc_remove), +}; +module_platform_driver(max8907_rtc_driver); + +MODULE_DESCRIPTION("Maxim MAX8907 RTC driver"); +MODULE_LICENSE("GPL v2"); + -- 2.39.5