|  | /* | 
|  | * RTC driver for Maxim MAX8998 | 
|  | * | 
|  | * Copyright (C) 2010 Samsung Electronics Co.Ltd | 
|  | * Author: Minkyu Kang <[email protected]> | 
|  | * Author: Joonyoung Shim <[email protected]> | 
|  | * | 
|  | *  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. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/bcd.h> | 
|  | #include <linux/irqdomain.h> | 
|  | #include <linux/rtc.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/mfd/max8998.h> | 
|  | #include <linux/mfd/max8998-private.h> | 
|  | #include <linux/delay.h> | 
|  |  | 
|  | #define MAX8998_RTC_SEC			0x00 | 
|  | #define MAX8998_RTC_MIN			0x01 | 
|  | #define MAX8998_RTC_HOUR		0x02 | 
|  | #define MAX8998_RTC_WEEKDAY		0x03 | 
|  | #define MAX8998_RTC_DATE		0x04 | 
|  | #define MAX8998_RTC_MONTH		0x05 | 
|  | #define MAX8998_RTC_YEAR1		0x06 | 
|  | #define MAX8998_RTC_YEAR2		0x07 | 
|  | #define MAX8998_ALARM0_SEC		0x08 | 
|  | #define MAX8998_ALARM0_MIN		0x09 | 
|  | #define MAX8998_ALARM0_HOUR		0x0a | 
|  | #define MAX8998_ALARM0_WEEKDAY		0x0b | 
|  | #define MAX8998_ALARM0_DATE		0x0c | 
|  | #define MAX8998_ALARM0_MONTH		0x0d | 
|  | #define MAX8998_ALARM0_YEAR1		0x0e | 
|  | #define MAX8998_ALARM0_YEAR2		0x0f | 
|  | #define MAX8998_ALARM1_SEC		0x10 | 
|  | #define MAX8998_ALARM1_MIN		0x11 | 
|  | #define MAX8998_ALARM1_HOUR		0x12 | 
|  | #define MAX8998_ALARM1_WEEKDAY		0x13 | 
|  | #define MAX8998_ALARM1_DATE		0x14 | 
|  | #define MAX8998_ALARM1_MONTH		0x15 | 
|  | #define MAX8998_ALARM1_YEAR1		0x16 | 
|  | #define MAX8998_ALARM1_YEAR2		0x17 | 
|  | #define MAX8998_ALARM0_CONF		0x18 | 
|  | #define MAX8998_ALARM1_CONF		0x19 | 
|  | #define MAX8998_RTC_STATUS		0x1a | 
|  | #define MAX8998_WTSR_SMPL_CNTL		0x1b | 
|  | #define MAX8998_TEST			0x1f | 
|  |  | 
|  | #define HOUR_12				(1 << 7) | 
|  | #define HOUR_PM				(1 << 5) | 
|  | #define ALARM0_STATUS			(1 << 1) | 
|  | #define ALARM1_STATUS			(1 << 2) | 
|  |  | 
|  | enum { | 
|  | RTC_SEC = 0, | 
|  | RTC_MIN, | 
|  | RTC_HOUR, | 
|  | RTC_WEEKDAY, | 
|  | RTC_DATE, | 
|  | RTC_MONTH, | 
|  | RTC_YEAR1, | 
|  | RTC_YEAR2, | 
|  | }; | 
|  |  | 
|  | struct max8998_rtc_info { | 
|  | struct device		*dev; | 
|  | struct max8998_dev	*max8998; | 
|  | struct i2c_client	*rtc; | 
|  | struct rtc_device	*rtc_dev; | 
|  | int irq; | 
|  | bool lp3974_bug_workaround; | 
|  | }; | 
|  |  | 
|  | static void max8998_data_to_tm(u8 *data, struct rtc_time *tm) | 
|  | { | 
|  | tm->tm_sec = bcd2bin(data[RTC_SEC]); | 
|  | tm->tm_min = bcd2bin(data[RTC_MIN]); | 
|  | if (data[RTC_HOUR] & HOUR_12) { | 
|  | tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x1f); | 
|  | if (data[RTC_HOUR] & HOUR_PM) | 
|  | tm->tm_hour += 12; | 
|  | } else | 
|  | tm->tm_hour = bcd2bin(data[RTC_HOUR] & 0x3f); | 
|  |  | 
|  | tm->tm_wday = data[RTC_WEEKDAY] & 0x07; | 
|  | tm->tm_mday = bcd2bin(data[RTC_DATE]); | 
|  | tm->tm_mon = bcd2bin(data[RTC_MONTH]); | 
|  | tm->tm_year = bcd2bin(data[RTC_YEAR1]) + bcd2bin(data[RTC_YEAR2]) * 100; | 
|  | tm->tm_year -= 1900; | 
|  | } | 
|  |  | 
|  | static void max8998_tm_to_data(struct rtc_time *tm, u8 *data) | 
|  | { | 
|  | data[RTC_SEC] = bin2bcd(tm->tm_sec); | 
|  | data[RTC_MIN] = bin2bcd(tm->tm_min); | 
|  | data[RTC_HOUR] = bin2bcd(tm->tm_hour); | 
|  | data[RTC_WEEKDAY] = tm->tm_wday; | 
|  | data[RTC_DATE] = bin2bcd(tm->tm_mday); | 
|  | data[RTC_MONTH] = bin2bcd(tm->tm_mon); | 
|  | data[RTC_YEAR1] = bin2bcd(tm->tm_year % 100); | 
|  | data[RTC_YEAR2] = bin2bcd((tm->tm_year + 1900) / 100); | 
|  | } | 
|  |  | 
|  | static int max8998_rtc_read_time(struct device *dev, struct rtc_time *tm) | 
|  | { | 
|  | struct max8998_rtc_info *info = dev_get_drvdata(dev); | 
|  | u8 data[8]; | 
|  | int ret; | 
|  |  | 
|  | ret = max8998_bulk_read(info->rtc, MAX8998_RTC_SEC, 8, data); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | max8998_data_to_tm(data, tm); | 
|  |  | 
|  | return rtc_valid_tm(tm); | 
|  | } | 
|  |  | 
|  | static int max8998_rtc_set_time(struct device *dev, struct rtc_time *tm) | 
|  | { | 
|  | struct max8998_rtc_info *info = dev_get_drvdata(dev); | 
|  | u8 data[8]; | 
|  | int ret; | 
|  |  | 
|  | max8998_tm_to_data(tm, data); | 
|  |  | 
|  | ret = max8998_bulk_write(info->rtc, MAX8998_RTC_SEC, 8, data); | 
|  |  | 
|  | if (info->lp3974_bug_workaround) | 
|  | msleep(2000); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int max8998_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) | 
|  | { | 
|  | struct max8998_rtc_info *info = dev_get_drvdata(dev); | 
|  | u8 data[8]; | 
|  | u8 val; | 
|  | int ret; | 
|  |  | 
|  | ret = max8998_bulk_read(info->rtc, MAX8998_ALARM0_SEC, 8, data); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | max8998_data_to_tm(data, &alrm->time); | 
|  |  | 
|  | ret = max8998_read_reg(info->rtc, MAX8998_ALARM0_CONF, &val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | alrm->enabled = !!val; | 
|  |  | 
|  | ret = max8998_read_reg(info->rtc, MAX8998_RTC_STATUS, &val); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | if (val & ALARM0_STATUS) | 
|  | alrm->pending = 1; | 
|  | else | 
|  | alrm->pending = 0; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int max8998_rtc_stop_alarm(struct max8998_rtc_info *info) | 
|  | { | 
|  | int ret = max8998_write_reg(info->rtc, MAX8998_ALARM0_CONF, 0); | 
|  |  | 
|  | if (info->lp3974_bug_workaround) | 
|  | msleep(2000); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int max8998_rtc_start_alarm(struct max8998_rtc_info *info) | 
|  | { | 
|  | int ret; | 
|  | u8 alarm0_conf = 0x77; | 
|  |  | 
|  | /* LP3974 with delay bug chips has rtc alarm bugs with "MONTH" field */ | 
|  | if (info->lp3974_bug_workaround) | 
|  | alarm0_conf = 0x57; | 
|  |  | 
|  | ret = max8998_write_reg(info->rtc, MAX8998_ALARM0_CONF, alarm0_conf); | 
|  |  | 
|  | if (info->lp3974_bug_workaround) | 
|  | msleep(2000); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int max8998_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) | 
|  | { | 
|  | struct max8998_rtc_info *info = dev_get_drvdata(dev); | 
|  | u8 data[8]; | 
|  | int ret; | 
|  |  | 
|  | max8998_tm_to_data(&alrm->time, data); | 
|  |  | 
|  | ret = max8998_rtc_stop_alarm(info); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | ret = max8998_bulk_write(info->rtc, MAX8998_ALARM0_SEC, 8, data); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | if (info->lp3974_bug_workaround) | 
|  | msleep(2000); | 
|  |  | 
|  | if (alrm->enabled) | 
|  | ret = max8998_rtc_start_alarm(info); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int max8998_rtc_alarm_irq_enable(struct device *dev, | 
|  | unsigned int enabled) | 
|  | { | 
|  | struct max8998_rtc_info *info = dev_get_drvdata(dev); | 
|  |  | 
|  | if (enabled) | 
|  | return max8998_rtc_start_alarm(info); | 
|  | else | 
|  | return max8998_rtc_stop_alarm(info); | 
|  | } | 
|  |  | 
|  | static irqreturn_t max8998_rtc_alarm_irq(int irq, void *data) | 
|  | { | 
|  | struct max8998_rtc_info *info = data; | 
|  |  | 
|  | rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF); | 
|  |  | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static const struct rtc_class_ops max8998_rtc_ops = { | 
|  | .read_time = max8998_rtc_read_time, | 
|  | .set_time = max8998_rtc_set_time, | 
|  | .read_alarm = max8998_rtc_read_alarm, | 
|  | .set_alarm = max8998_rtc_set_alarm, | 
|  | .alarm_irq_enable = max8998_rtc_alarm_irq_enable, | 
|  | }; | 
|  |  | 
|  | static int max8998_rtc_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct max8998_dev *max8998 = dev_get_drvdata(pdev->dev.parent); | 
|  | struct max8998_platform_data *pdata = max8998->pdata; | 
|  | struct max8998_rtc_info *info; | 
|  | int ret; | 
|  |  | 
|  | info = devm_kzalloc(&pdev->dev, sizeof(struct max8998_rtc_info), | 
|  | GFP_KERNEL); | 
|  | if (!info) | 
|  | return -ENOMEM; | 
|  |  | 
|  | info->dev = &pdev->dev; | 
|  | info->max8998 = max8998; | 
|  | info->rtc = max8998->rtc; | 
|  |  | 
|  | platform_set_drvdata(pdev, info); | 
|  |  | 
|  | info->rtc_dev = devm_rtc_device_register(&pdev->dev, "max8998-rtc", | 
|  | &max8998_rtc_ops, THIS_MODULE); | 
|  |  | 
|  | if (IS_ERR(info->rtc_dev)) { | 
|  | ret = PTR_ERR(info->rtc_dev); | 
|  | dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (!max8998->irq_domain) | 
|  | goto no_irq; | 
|  |  | 
|  | info->irq = irq_create_mapping(max8998->irq_domain, MAX8998_IRQ_ALARM0); | 
|  | if (!info->irq) { | 
|  | dev_warn(&pdev->dev, "Failed to map alarm IRQ\n"); | 
|  | goto no_irq; | 
|  | } | 
|  |  | 
|  | ret = devm_request_threaded_irq(&pdev->dev, info->irq, NULL, | 
|  | max8998_rtc_alarm_irq, 0, "rtc-alarm0", info); | 
|  |  | 
|  | if (ret < 0) | 
|  | dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n", | 
|  | info->irq, ret); | 
|  |  | 
|  | no_irq: | 
|  | dev_info(&pdev->dev, "RTC CHIP NAME: %s\n", pdev->id_entry->name); | 
|  | if (pdata && pdata->rtc_delay) { | 
|  | info->lp3974_bug_workaround = true; | 
|  | dev_warn(&pdev->dev, "LP3974 with RTC REGERR option." | 
|  | " RTC updates will be extremely slow.\n"); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct platform_device_id max8998_rtc_id[] = { | 
|  | { "max8998-rtc", TYPE_MAX8998 }, | 
|  | { "lp3974-rtc", TYPE_LP3974 }, | 
|  | { } | 
|  | }; | 
|  |  | 
|  | static struct platform_driver max8998_rtc_driver = { | 
|  | .driver		= { | 
|  | .name	= "max8998-rtc", | 
|  | .owner	= THIS_MODULE, | 
|  | }, | 
|  | .probe		= max8998_rtc_probe, | 
|  | .id_table	= max8998_rtc_id, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(max8998_rtc_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Minkyu Kang <[email protected]>"); | 
|  | MODULE_AUTHOR("Joonyoung Shim <[email protected]>"); | 
|  | MODULE_DESCRIPTION("Maxim MAX8998 RTC driver"); | 
|  | MODULE_LICENSE("GPL"); |