linux/drivers/rtc/rtc-mv.c
<<
>>
Prefs
   1/*
   2 * Driver for the RTC in Marvell SoCs.
   3 *
   4 * This file is licensed under the terms of the GNU General Public
   5 * License version 2.  This program is licensed "as is" without any
   6 * warranty of any kind, whether express or implied.
   7 */
   8
   9#include <linux/init.h>
  10#include <linux/kernel.h>
  11#include <linux/rtc.h>
  12#include <linux/bcd.h>
  13#include <linux/io.h>
  14#include <linux/platform_device.h>
  15#include <linux/delay.h>
  16
  17
  18#define RTC_TIME_REG_OFFS       0
  19#define RTC_SECONDS_OFFS        0
  20#define RTC_MINUTES_OFFS        8
  21#define RTC_HOURS_OFFS          16
  22#define RTC_WDAY_OFFS           24
  23#define RTC_HOURS_12H_MODE              (1 << 22) /* 12 hours mode */
  24
  25#define RTC_DATE_REG_OFFS       4
  26#define RTC_MDAY_OFFS           0
  27#define RTC_MONTH_OFFS          8
  28#define RTC_YEAR_OFFS           16
  29
  30
  31struct rtc_plat_data {
  32        struct rtc_device *rtc;
  33        void __iomem *ioaddr;
  34};
  35
  36static int mv_rtc_set_time(struct device *dev, struct rtc_time *tm)
  37{
  38        struct rtc_plat_data *pdata = dev_get_drvdata(dev);
  39        void __iomem *ioaddr = pdata->ioaddr;
  40        u32     rtc_reg;
  41
  42        rtc_reg = (bin2bcd(tm->tm_sec) << RTC_SECONDS_OFFS) |
  43                (bin2bcd(tm->tm_min) << RTC_MINUTES_OFFS) |
  44                (bin2bcd(tm->tm_hour) << RTC_HOURS_OFFS) |
  45                (bin2bcd(tm->tm_wday) << RTC_WDAY_OFFS);
  46        writel(rtc_reg, ioaddr + RTC_TIME_REG_OFFS);
  47
  48        rtc_reg = (bin2bcd(tm->tm_mday) << RTC_MDAY_OFFS) |
  49                (bin2bcd(tm->tm_mon + 1) << RTC_MONTH_OFFS) |
  50                (bin2bcd(tm->tm_year % 100) << RTC_YEAR_OFFS);
  51        writel(rtc_reg, ioaddr + RTC_DATE_REG_OFFS);
  52
  53        return 0;
  54}
  55
  56static int mv_rtc_read_time(struct device *dev, struct rtc_time *tm)
  57{
  58        struct rtc_plat_data *pdata = dev_get_drvdata(dev);
  59        void __iomem *ioaddr = pdata->ioaddr;
  60        u32     rtc_time, rtc_date;
  61        unsigned int year, month, day, hour, minute, second, wday;
  62
  63        rtc_time = readl(ioaddr + RTC_TIME_REG_OFFS);
  64        rtc_date = readl(ioaddr + RTC_DATE_REG_OFFS);
  65
  66        second = rtc_time & 0x7f;
  67        minute = (rtc_time >> RTC_MINUTES_OFFS) & 0x7f;
  68        hour = (rtc_time >> RTC_HOURS_OFFS) & 0x3f; /* assume 24 hours mode */
  69        wday = (rtc_time >> RTC_WDAY_OFFS) & 0x7;
  70
  71        day = rtc_date & 0x3f;
  72        month = (rtc_date >> RTC_MONTH_OFFS) & 0x3f;
  73        year = (rtc_date >> RTC_YEAR_OFFS) & 0xff;
  74
  75        tm->tm_sec = bcd2bin(second);
  76        tm->tm_min = bcd2bin(minute);
  77        tm->tm_hour = bcd2bin(hour);
  78        tm->tm_mday = bcd2bin(day);
  79        tm->tm_wday = bcd2bin(wday);
  80        tm->tm_mon = bcd2bin(month) - 1;
  81        /* hw counts from year 2000, but tm_year is relative to 1900 */
  82        tm->tm_year = bcd2bin(year) + 100;
  83
  84        return rtc_valid_tm(tm);
  85}
  86
  87static const struct rtc_class_ops mv_rtc_ops = {
  88        .read_time      = mv_rtc_read_time,
  89        .set_time       = mv_rtc_set_time,
  90};
  91
  92static int __init mv_rtc_probe(struct platform_device *pdev)
  93{
  94        struct resource *res;
  95        struct rtc_plat_data *pdata;
  96        resource_size_t size;
  97        u32 rtc_time;
  98
  99        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 100        if (!res)
 101                return -ENODEV;
 102
 103        pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
 104        if (!pdata)
 105                return -ENOMEM;
 106
 107        size = resource_size(res);
 108        if (!devm_request_mem_region(&pdev->dev, res->start, size,
 109                                     pdev->name))
 110                return -EBUSY;
 111
 112        pdata->ioaddr = devm_ioremap(&pdev->dev, res->start, size);
 113        if (!pdata->ioaddr)
 114                return -ENOMEM;
 115
 116        /* make sure the 24 hours mode is enabled */
 117        rtc_time = readl(pdata->ioaddr + RTC_TIME_REG_OFFS);
 118        if (rtc_time & RTC_HOURS_12H_MODE) {
 119                dev_err(&pdev->dev, "24 Hours mode not supported.\n");
 120                return -EINVAL;
 121        }
 122
 123        /* make sure it is actually functional */
 124        if (rtc_time == 0x01000000) {
 125                ssleep(1);
 126                rtc_time = readl(pdata->ioaddr + RTC_TIME_REG_OFFS);
 127                if (rtc_time == 0x01000000) {
 128                        dev_err(&pdev->dev, "internal RTC not ticking\n");
 129                        return -ENODEV;
 130                }
 131        }
 132
 133        platform_set_drvdata(pdev, pdata);
 134        pdata->rtc = rtc_device_register(pdev->name, &pdev->dev,
 135                                         &mv_rtc_ops, THIS_MODULE);
 136        if (IS_ERR(pdata->rtc))
 137                return PTR_ERR(pdata->rtc);
 138
 139        return 0;
 140}
 141
 142static int __exit mv_rtc_remove(struct platform_device *pdev)
 143{
 144        struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
 145
 146        rtc_device_unregister(pdata->rtc);
 147        return 0;
 148}
 149
 150static struct platform_driver mv_rtc_driver = {
 151        .remove         = __exit_p(mv_rtc_remove),
 152        .driver         = {
 153                .name   = "rtc-mv",
 154                .owner  = THIS_MODULE,
 155        },
 156};
 157
 158static __init int mv_init(void)
 159{
 160        return platform_driver_probe(&mv_rtc_driver, mv_rtc_probe);
 161}
 162
 163static __exit void mv_exit(void)
 164{
 165        platform_driver_unregister(&mv_rtc_driver);
 166}
 167
 168module_init(mv_init);
 169module_exit(mv_exit);
 170
 171MODULE_AUTHOR("Saeed Bishara <saeed@marvell.com>");
 172MODULE_DESCRIPTION("Marvell RTC driver");
 173MODULE_LICENSE("GPL");
 174MODULE_ALIAS("platform:rtc-mv");
 175