lkml.org 
[lkml]   [2012]   [Aug]   [10]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH] rtc: add MAX8907 RTC driver
    Date
    From: Stephen Warren <swarren@nvidia.com>

    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 <tcherry@nvidia.com>
    * Prashant Gaikwad <pgaikwad@nvidia.com>
    * Joseph Yoon <tyoon@nvidia.com>

    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 <swarren@nvidia.com>
    ---
    drivers/rtc/Kconfig | 10 ++
    drivers/rtc/Makefile | 1 +
    drivers/rtc/rtc-max8907.c | 245 +++++++++++++++++++++++++++++++++++++++++++++
    3 files changed, 256 insertions(+), 0 deletions(-)
    create mode 100644 drivers/rtc/rtc-max8907.c

    diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
    index fabc99a..c84f960 100644
    --- a/drivers/rtc/Kconfig
    +++ b/drivers/rtc/Kconfig
    @@ -203,6 +203,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 0d5b2b6..a0b4fbe 100644
    --- a/drivers/rtc/Makefile
    +++ b/drivers/rtc/Makefile
    @@ -64,6 +64,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 0000000..4880374
    --- /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 <linux/bcd.h>
    +#include <linux/i2c.h>
    +#include <linux/mfd/max8907.h>
    +#include <linux/module.h>
    +#include <linux/platform_device.h>
    +#include <linux/regmap.h>
    +#include <linux/rtc.h>
    +#include <linux/slab.h>
    +
    +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");
    +
    --
    1.7.0.4


    \
     
     \ /
      Last update: 2012-08-11 01:42    [W:0.034 / U:0.324 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site