blob: 50ec3911861b38578a11abdbdf5baf4319d26fa9 [file] [log] [blame]
/*
* Hisilicon Hi6421 RTC driver
*
* Copyright (C) 2013 Hisilicon Ltd.
* Copyright (C) 2013 Linaro Ltd.
*
* Author: Haojian Zhuang <haojian.zhuang@linaro.org>
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of_device.h>
#include <linux/rtc.h>
#include <linux/mfd/hi6421-pmic.h>
#define REG_IRQ1 0x01
#define REG_IRQM1 0x04
#define REG_RTCDR0 0x58
#define REG_RTCDR1 0x59
#define REG_RTCDR2 0x5a
#define REG_RTCDR3 0x5b
#define REG_RTCMR0 0x5c
#define REG_RTCMR1 0x5d
#define REG_RTCMR2 0x5e
#define REG_RTCMR3 0x5f
#define REG_RTCLR0 0x60
#define REG_RTCLR1 0x61
#define REG_RTCLR2 0x62
#define REG_RTCLR3 0x63
#define REG_RTCCTRL 0x64
#define REG_SOFT_RST 0x86
#define ALARM_ON (1 << HI6421_IRQ_ALARM)
struct hi6421_rtc_info {
struct rtc_device *rtc;
struct hi6421_pmic *pmic;
int irq;
};
static irqreturn_t hi6421_rtc_handler(int irq, void *data)
{
struct hi6421_rtc_info *info = (struct hi6421_rtc_info *)data;
/* clear alarm status */
hi6421_pmic_rmw(info->pmic, REG_IRQ1, ALARM_ON, ALARM_ON);
rtc_update_irq(info->rtc, 1, RTC_AF);
return IRQ_HANDLED;
}
/* read 4 8-bit registers & covert it into a 32-bit data */
static unsigned int hi6421_read_bulk(struct hi6421_pmic *pmic,
unsigned int addr)
{
unsigned int data, sum = 0;
int i;
for (i = 0; i < 4; i++) {
data = hi6421_pmic_read(pmic, addr + i);
sum |= (data & 0xff) << (i * 8);
}
return sum;
}
/* write a 32-bit data into 4 8-bit registers */
static void hi6421_write_bulk(struct hi6421_pmic *pmic, unsigned int addr,
unsigned int data)
{
unsigned int value;
int i;
for (i = 0; i < 4; i++) {
value = (data >> (i * 8)) & 0xff;
hi6421_pmic_write(pmic, addr + i, value);
}
}
static int hi6421_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct hi6421_rtc_info *info = dev_get_drvdata(dev);
unsigned long ticks;
ticks = hi6421_read_bulk(info->pmic, REG_RTCDR0);
rtc_time_to_tm(ticks, tm);
return 0;
}
static int hi6421_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
struct hi6421_rtc_info *info = dev_get_drvdata(dev);
unsigned long ticks;
if ((tm->tm_year < 70) || (tm->tm_year > 138)) {
dev_dbg(dev, "Set time %d out of range. "
"Please set time between 1970 to 2038.\n",
1900 + tm->tm_year);
return -EINVAL;
}
rtc_tm_to_time(tm, &ticks);
hi6421_write_bulk(info->pmic, REG_RTCLR0, ticks);
return 0;
}
static int hi6421_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct hi6421_rtc_info *info = dev_get_drvdata(dev);
unsigned long ticks, data;
ticks = hi6421_read_bulk(info->pmic, REG_RTCMR0);
rtc_time_to_tm(ticks, &alrm->time);
data = hi6421_pmic_read(info->pmic, REG_IRQ1);
alrm->pending = (data & ALARM_ON) ? 1 : 0;
data = hi6421_pmic_read(info->pmic, REG_IRQM1);
alrm->enabled = (data & ALARM_ON) ? 0 : 1;
return 0;
}
/*
* Calculate the next alarm time given the requested alarm time mask
* and the current time.
*/
static void rtc_next_alarm_time(struct rtc_time *next, struct rtc_time *now,
struct rtc_time *alrm)
{
unsigned long next_time;
unsigned long now_time;
next->tm_year = now->tm_year;
next->tm_mon = now->tm_mon;
next->tm_mday = now->tm_mday;
next->tm_hour = alrm->tm_hour;
next->tm_min = alrm->tm_min;
next->tm_sec = alrm->tm_sec;
rtc_tm_to_time(now, &now_time);
rtc_tm_to_time(next, &next_time);
if (next_time < now_time) {
/* Advance one day */
next_time += 60 * 60 * 24;
rtc_time_to_tm(next_time, next);
}
}
static int hi6421_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct hi6421_rtc_info *info = dev_get_drvdata(dev);
struct rtc_time now_tm, alarm_tm;
unsigned long ticks;
/* load 32-bit read-only counter */
ticks = hi6421_read_bulk(info->pmic, REG_RTCDR0);
rtc_time_to_tm(ticks, &now_tm);
dev_dbg(dev, "%s, now time : %lu\n", __func__, ticks);
rtc_next_alarm_time(&alarm_tm, &now_tm, &alrm->time);
/* get new ticks for alarm in 24 hours */
rtc_tm_to_time(&alarm_tm, &ticks);
dev_dbg(dev, "%s, alarm time: %lu\n", __func__, ticks);
if (alrm->enabled)
hi6421_write_bulk(info->pmic, REG_RTCMR0, ticks);
return 0;
}
static int hi6421_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
struct hi6421_rtc_info *info = dev_get_drvdata(dev);
unsigned int data = 0;
if (!enabled)
data = ALARM_ON;
hi6421_pmic_rmw(info->pmic, REG_IRQM1, ALARM_ON, data);
return 0;
}
static const struct rtc_class_ops hi6421_rtc_ops = {
.read_time = hi6421_rtc_read_time,
.set_time = hi6421_rtc_set_time,
.read_alarm = hi6421_rtc_read_alarm,
.set_alarm = hi6421_rtc_set_alarm,
.alarm_irq_enable = hi6421_rtc_alarm_irq_enable,
};
static int hi6421_rtc_probe(struct platform_device *pdev)
{
struct hi6421_rtc_info *info;
int ret;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info) {
dev_err(&pdev->dev, "failed to allocate memory\n");
return -ENOMEM;
}
info->irq = platform_get_irq(pdev, 0);
if (info->irq < 0)
return -ENOENT;
info->pmic = dev_get_drvdata(pdev->dev.parent);
platform_set_drvdata(pdev, info);
/* enable RTC device */
hi6421_pmic_write(info->pmic, REG_RTCCTRL, 1);
info->rtc = devm_rtc_device_register(&pdev->dev, pdev->name,
&hi6421_rtc_ops, THIS_MODULE);
if (IS_ERR(info->rtc))
return PTR_ERR(info->rtc);
ret = devm_request_irq(&pdev->dev, info->irq, hi6421_rtc_handler,
IRQF_DISABLED, "alarm", info);
if (ret < 0)
return ret;
return 0;
}
static int hi6421_rtc_remove(struct platform_device *pdev)
{
return 0;
}
static struct of_device_id hi6421_rtc_of_match[] = {
{ .compatible = "hisilicon,hi6421-rtc" },
{ }
};
MODULE_DEVICE_TABLE(of, hi6421_rtc_of_match);
static struct platform_driver hi6421_rtc_driver = {
.driver = {
.name = "hi6421-rtc",
.of_match_table = of_match_ptr(hi6421_rtc_of_match),
},
.probe = hi6421_rtc_probe,
.remove = hi6421_rtc_remove,
};
module_platform_driver(hi6421_rtc_driver);
MODULE_LICENSE("GPLv2");
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@linaro.org");
MODULE_ALIAS("platform:hi6421-rtc");