/* * Hisilicon Hi6421 RTC driver * * Copyright (C) 2013 Hisilicon Ltd. * Copyright (C) 2013 Linaro Ltd. * * Author: Haojian Zhuang * * 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 #include #include #include #include #include #include #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