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 34 MODULE_AUTHOR("Hans Ulli Kroll <ulli.kroll@googlemail.com>"); 35 MODULE_DESCRIPTION("RTC driver for Gemini SoC"); 36 MODULE_LICENSE("GPL"); 37 MODULE_ALIAS("platform:" DRV_NAME); 38 39 struct 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 47 enum 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 59 static 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 73 static 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 93 static 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 114 static const struct rtc_class_ops ftrtc010_rtc_ops = { 115 .read_time = ftrtc010_rtc_read_time, 116 .set_time = ftrtc010_rtc_set_time, 117 }; 118 119 static 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 191 static 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 203 static const struct of_device_id ftrtc010_rtc_dt_match[] = { 204 { .compatible = "cortina,gemini-rtc" }, 205 { .compatible = "faraday,ftrtc010" }, 206 { } 207 }; 208 MODULE_DEVICE_TABLE(of, ftrtc010_rtc_dt_match); 209 210 static 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 219 module_platform_driver_probe(ftrtc010_rtc_driver, ftrtc010_rtc_probe); 220