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/clk.h> 30 31 #define DRV_NAME "rtc-ftrtc010" 32 33 MODULE_AUTHOR("Hans Ulli Kroll <ulli.kroll@googlemail.com>"); 34 MODULE_DESCRIPTION("RTC driver for Gemini SoC"); 35 MODULE_LICENSE("GPL"); 36 MODULE_ALIAS("platform:" DRV_NAME); 37 38 struct ftrtc010_rtc { 39 struct rtc_device *rtc_dev; 40 void __iomem *rtc_base; 41 int rtc_irq; 42 struct clk *pclk; 43 struct clk *extclk; 44 }; 45 46 enum ftrtc010_rtc_offsets { 47 FTRTC010_RTC_SECOND = 0x00, 48 FTRTC010_RTC_MINUTE = 0x04, 49 FTRTC010_RTC_HOUR = 0x08, 50 FTRTC010_RTC_DAYS = 0x0C, 51 FTRTC010_RTC_ALARM_SECOND = 0x10, 52 FTRTC010_RTC_ALARM_MINUTE = 0x14, 53 FTRTC010_RTC_ALARM_HOUR = 0x18, 54 FTRTC010_RTC_RECORD = 0x1C, 55 FTRTC010_RTC_CR = 0x20, 56 }; 57 58 static irqreturn_t ftrtc010_rtc_interrupt(int irq, void *dev) 59 { 60 return IRQ_HANDLED; 61 } 62 63 /* 64 * Looks like the RTC in the Gemini SoC is (totaly) broken 65 * We can't read/write directly the time from RTC registers. 66 * We must do some "offset" calculation to get the real time 67 * 68 * This FIX works pretty fine and Stormlinksemi aka Cortina-Networks does 69 * the same thing, without the rtc-lib.c calls. 70 */ 71 72 static int ftrtc010_rtc_read_time(struct device *dev, struct rtc_time *tm) 73 { 74 struct ftrtc010_rtc *rtc = dev_get_drvdata(dev); 75 76 unsigned int days, hour, min, sec; 77 unsigned long offset, time; 78 79 sec = readl(rtc->rtc_base + FTRTC010_RTC_SECOND); 80 min = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE); 81 hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR); 82 days = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); 83 offset = readl(rtc->rtc_base + FTRTC010_RTC_RECORD); 84 85 time = offset + days * 86400 + hour * 3600 + min * 60 + sec; 86 87 rtc_time_to_tm(time, tm); 88 89 return 0; 90 } 91 92 static int ftrtc010_rtc_set_time(struct device *dev, struct rtc_time *tm) 93 { 94 struct ftrtc010_rtc *rtc = dev_get_drvdata(dev); 95 unsigned int sec, min, hour, day; 96 unsigned long offset, time; 97 98 if (tm->tm_year >= 2148) /* EPOCH Year + 179 */ 99 return -EINVAL; 100 101 rtc_tm_to_time(tm, &time); 102 103 sec = readl(rtc->rtc_base + FTRTC010_RTC_SECOND); 104 min = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE); 105 hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR); 106 day = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); 107 108 offset = time - (day * 86400 + hour * 3600 + min * 60 + sec); 109 110 writel(offset, rtc->rtc_base + FTRTC010_RTC_RECORD); 111 writel(0x01, rtc->rtc_base + FTRTC010_RTC_CR); 112 113 return 0; 114 } 115 116 static const struct rtc_class_ops ftrtc010_rtc_ops = { 117 .read_time = ftrtc010_rtc_read_time, 118 .set_time = ftrtc010_rtc_set_time, 119 }; 120 121 static int ftrtc010_rtc_probe(struct platform_device *pdev) 122 { 123 struct ftrtc010_rtc *rtc; 124 struct device *dev = &pdev->dev; 125 struct resource *res; 126 int ret; 127 128 rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); 129 if (unlikely(!rtc)) 130 return -ENOMEM; 131 platform_set_drvdata(pdev, rtc); 132 133 rtc->pclk = devm_clk_get(dev, "PCLK"); 134 if (IS_ERR(rtc->pclk)) { 135 dev_err(dev, "could not get PCLK\n"); 136 } else { 137 ret = clk_prepare_enable(rtc->pclk); 138 if (ret) { 139 dev_err(dev, "failed to enable PCLK\n"); 140 return ret; 141 } 142 } 143 rtc->extclk = devm_clk_get(dev, "EXTCLK"); 144 if (IS_ERR(rtc->extclk)) { 145 dev_err(dev, "could not get EXTCLK\n"); 146 } else { 147 ret = clk_prepare_enable(rtc->extclk); 148 if (ret) { 149 dev_err(dev, "failed to enable EXTCLK\n"); 150 return ret; 151 } 152 } 153 154 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 155 if (!res) 156 return -ENODEV; 157 158 rtc->rtc_irq = res->start; 159 160 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 161 if (!res) 162 return -ENODEV; 163 164 rtc->rtc_base = devm_ioremap(dev, res->start, 165 resource_size(res)); 166 if (!rtc->rtc_base) 167 return -ENOMEM; 168 169 ret = devm_request_irq(dev, rtc->rtc_irq, ftrtc010_rtc_interrupt, 170 IRQF_SHARED, pdev->name, dev); 171 if (unlikely(ret)) 172 return ret; 173 174 rtc->rtc_dev = rtc_device_register(pdev->name, dev, 175 &ftrtc010_rtc_ops, THIS_MODULE); 176 return PTR_ERR_OR_ZERO(rtc->rtc_dev); 177 } 178 179 static int ftrtc010_rtc_remove(struct platform_device *pdev) 180 { 181 struct ftrtc010_rtc *rtc = platform_get_drvdata(pdev); 182 183 if (!IS_ERR(rtc->extclk)) 184 clk_disable_unprepare(rtc->extclk); 185 if (!IS_ERR(rtc->pclk)) 186 clk_disable_unprepare(rtc->pclk); 187 rtc_device_unregister(rtc->rtc_dev); 188 189 return 0; 190 } 191 192 static const struct of_device_id ftrtc010_rtc_dt_match[] = { 193 { .compatible = "cortina,gemini-rtc" }, 194 { .compatible = "faraday,ftrtc010" }, 195 { } 196 }; 197 MODULE_DEVICE_TABLE(of, ftrtc010_rtc_dt_match); 198 199 static struct platform_driver ftrtc010_rtc_driver = { 200 .driver = { 201 .name = DRV_NAME, 202 .of_match_table = ftrtc010_rtc_dt_match, 203 }, 204 .probe = ftrtc010_rtc_probe, 205 .remove = ftrtc010_rtc_remove, 206 }; 207 208 module_platform_driver_probe(ftrtc010_rtc_driver, ftrtc010_rtc_probe); 209