linux/drivers/rtc/rtc-ftrtc010.c
<<
>>
Prefs
   1/*
   2 *  Faraday Technology FTRTC010 driver
   3 *
   4 *  Copyright (C) 2009 Janos Laube <janos.dev@gmail.com>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License as published by
   8 * the Free Software Foundation; either version 2 of the License, or
   9 * (at your option) any later version.
  10 *
  11 * This program is distributed in the hope that it will be useful,
  12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 * GNU General Public License for more details.
  15 *
  16 * Original code for older kernel 2.6.15 are from Stormlinksemi
  17 * first update from Janos Laube for > 2.6.29 kernels
  18 *
  19 * checkpatch fixes and usage of rtc-lib code
  20 * Hans Ulli Kroll <ulli.kroll@googlemail.com>
  21 */
  22
  23#include <linux/rtc.h>
  24#include <linux/io.h>
  25#include <linux/slab.h>
  26#include <linux/platform_device.h>
  27#include <linux/kernel.h>
  28#include <linux/module.h>
  29#include <linux/mod_devicetable.h>
  30#include <linux/clk.h>
  31
  32#define DRV_NAME        "rtc-ftrtc010"
  33
  34MODULE_AUTHOR("Hans Ulli Kroll <ulli.kroll@googlemail.com>");
  35MODULE_DESCRIPTION("RTC driver for Gemini SoC");
  36MODULE_LICENSE("GPL");
  37MODULE_ALIAS("platform:" DRV_NAME);
  38
  39struct ftrtc010_rtc {
  40        struct rtc_device       *rtc_dev;
  41        void __iomem            *rtc_base;
  42        int                     rtc_irq;
  43        struct clk              *pclk;
  44        struct clk              *extclk;
  45};
  46
  47enum ftrtc010_rtc_offsets {
  48        FTRTC010_RTC_SECOND             = 0x00,
  49        FTRTC010_RTC_MINUTE             = 0x04,
  50        FTRTC010_RTC_HOUR               = 0x08,
  51        FTRTC010_RTC_DAYS               = 0x0C,
  52        FTRTC010_RTC_ALARM_SECOND       = 0x10,
  53        FTRTC010_RTC_ALARM_MINUTE       = 0x14,
  54        FTRTC010_RTC_ALARM_HOUR         = 0x18,
  55        FTRTC010_RTC_RECORD             = 0x1C,
  56        FTRTC010_RTC_CR                 = 0x20,
  57};
  58
  59static irqreturn_t ftrtc010_rtc_interrupt(int irq, void *dev)
  60{
  61        return IRQ_HANDLED;
  62}
  63
  64/*
  65 * Looks like the RTC in the Gemini SoC is (totaly) broken
  66 * We can't read/write directly the time from RTC registers.
  67 * We must do some "offset" calculation to get the real time
  68 *
  69 * This FIX works pretty fine and Stormlinksemi aka Cortina-Networks does
  70 * the same thing, without the rtc-lib.c calls.
  71 */
  72
  73static int ftrtc010_rtc_read_time(struct device *dev, struct rtc_time *tm)
  74{
  75        struct ftrtc010_rtc *rtc = dev_get_drvdata(dev);
  76
  77        u32 days, hour, min, sec, offset;
  78        timeu64_t time;
  79
  80        sec  = readl(rtc->rtc_base + FTRTC010_RTC_SECOND);
  81        min  = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE);
  82        hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR);
  83        days = readl(rtc->rtc_base + FTRTC010_RTC_DAYS);
  84        offset = readl(rtc->rtc_base + FTRTC010_RTC_RECORD);
  85
  86        time = offset + days * 86400 + hour * 3600 + min * 60 + sec;
  87
  88        rtc_time64_to_tm(time, tm);
  89
  90        return 0;
  91}
  92
  93static int ftrtc010_rtc_set_time(struct device *dev, struct rtc_time *tm)
  94{
  95        struct ftrtc010_rtc *rtc = dev_get_drvdata(dev);
  96        u32 sec, min, hour, day, offset;
  97        timeu64_t time;
  98
  99        time = rtc_tm_to_time64(tm);
 100
 101        sec = readl(rtc->rtc_base + FTRTC010_RTC_SECOND);
 102        min = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE);
 103        hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR);
 104        day = readl(rtc->rtc_base + FTRTC010_RTC_DAYS);
 105
 106        offset = time - (day * 86400 + hour * 3600 + min * 60 + sec);
 107
 108        writel(offset, rtc->rtc_base + FTRTC010_RTC_RECORD);
 109        writel(0x01, rtc->rtc_base + FTRTC010_RTC_CR);
 110
 111        return 0;
 112}
 113
 114static const struct rtc_class_ops ftrtc010_rtc_ops = {
 115        .read_time     = ftrtc010_rtc_read_time,
 116        .set_time      = ftrtc010_rtc_set_time,
 117};
 118
 119static int ftrtc010_rtc_probe(struct platform_device *pdev)
 120{
 121        u32 days, hour, min, sec;
 122        struct ftrtc010_rtc *rtc;
 123        struct device *dev = &pdev->dev;
 124        struct resource *res;
 125        int ret;
 126
 127        rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
 128        if (unlikely(!rtc))
 129                return -ENOMEM;
 130        platform_set_drvdata(pdev, rtc);
 131
 132        rtc->pclk = devm_clk_get(dev, "PCLK");
 133        if (IS_ERR(rtc->pclk)) {
 134                dev_err(dev, "could not get PCLK\n");
 135        } else {
 136                ret = clk_prepare_enable(rtc->pclk);
 137                if (ret) {
 138                        dev_err(dev, "failed to enable PCLK\n");
 139                        return ret;
 140                }
 141        }
 142        rtc->extclk = devm_clk_get(dev, "EXTCLK");
 143        if (IS_ERR(rtc->extclk)) {
 144                dev_err(dev, "could not get EXTCLK\n");
 145        } else {
 146                ret = clk_prepare_enable(rtc->extclk);
 147                if (ret) {
 148                        dev_err(dev, "failed to enable EXTCLK\n");
 149                        return ret;
 150                }
 151        }
 152
 153        res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 154        if (!res)
 155                return -ENODEV;
 156
 157        rtc->rtc_irq = res->start;
 158
 159        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 160        if (!res)
 161                return -ENODEV;
 162
 163        rtc->rtc_base = devm_ioremap(dev, res->start,
 164                                     resource_size(res));
 165        if (!rtc->rtc_base)
 166                return -ENOMEM;
 167
 168        rtc->rtc_dev = devm_rtc_allocate_device(dev);
 169        if (IS_ERR(rtc->rtc_dev))
 170                return PTR_ERR(rtc->rtc_dev);
 171
 172        rtc->rtc_dev->ops = &ftrtc010_rtc_ops;
 173
 174        sec  = readl(rtc->rtc_base + FTRTC010_RTC_SECOND);
 175        min  = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE);
 176        hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR);
 177        days = readl(rtc->rtc_base + FTRTC010_RTC_DAYS);
 178
 179        rtc->rtc_dev->range_min = (u64)days * 86400 + hour * 3600 +
 180                                  min * 60 + sec;
 181        rtc->rtc_dev->range_max = U32_MAX + rtc->rtc_dev->range_min;
 182
 183        ret = devm_request_irq(dev, rtc->rtc_irq, ftrtc010_rtc_interrupt,
 184                               IRQF_SHARED, pdev->name, dev);
 185        if (unlikely(ret))
 186                return ret;
 187
 188        return rtc_register_device(rtc->rtc_dev);
 189}
 190
 191static int ftrtc010_rtc_remove(struct platform_device *pdev)
 192{
 193        struct ftrtc010_rtc *rtc = platform_get_drvdata(pdev);
 194
 195        if (!IS_ERR(rtc->extclk))
 196                clk_disable_unprepare(rtc->extclk);
 197        if (!IS_ERR(rtc->pclk))
 198                clk_disable_unprepare(rtc->pclk);
 199
 200        return 0;
 201}
 202
 203static const struct of_device_id ftrtc010_rtc_dt_match[] = {
 204        { .compatible = "cortina,gemini-rtc" },
 205        { .compatible = "faraday,ftrtc010" },
 206        { }
 207};
 208MODULE_DEVICE_TABLE(of, ftrtc010_rtc_dt_match);
 209
 210static struct platform_driver ftrtc010_rtc_driver = {
 211        .driver         = {
 212                .name   = DRV_NAME,
 213                .of_match_table = ftrtc010_rtc_dt_match,
 214        },
 215        .probe          = ftrtc010_rtc_probe,
 216        .remove         = ftrtc010_rtc_remove,
 217};
 218
 219module_platform_driver_probe(ftrtc010_rtc_driver, ftrtc010_rtc_probe);
 220