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 u32 days, hour, min, sec, offset; 77 timeu64_t 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_time64_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 u32 sec, min, hour, day, offset; 96 timeu64_t time; 97 98 time = rtc_tm_to_time64(tm); 99 100 sec = readl(rtc->rtc_base + FTRTC010_RTC_SECOND); 101 min = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE); 102 hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR); 103 day = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); 104 105 offset = time - (day * 86400 + hour * 3600 + min * 60 + sec); 106 107 writel(offset, rtc->rtc_base + FTRTC010_RTC_RECORD); 108 writel(0x01, rtc->rtc_base + FTRTC010_RTC_CR); 109 110 return 0; 111 } 112 113 static const struct rtc_class_ops ftrtc010_rtc_ops = { 114 .read_time = ftrtc010_rtc_read_time, 115 .set_time = ftrtc010_rtc_set_time, 116 }; 117 118 static int ftrtc010_rtc_probe(struct platform_device *pdev) 119 { 120 u32 days, hour, min, sec; 121 struct ftrtc010_rtc *rtc; 122 struct device *dev = &pdev->dev; 123 struct resource *res; 124 int ret; 125 126 rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); 127 if (unlikely(!rtc)) 128 return -ENOMEM; 129 platform_set_drvdata(pdev, rtc); 130 131 rtc->pclk = devm_clk_get(dev, "PCLK"); 132 if (IS_ERR(rtc->pclk)) { 133 dev_err(dev, "could not get PCLK\n"); 134 } else { 135 ret = clk_prepare_enable(rtc->pclk); 136 if (ret) { 137 dev_err(dev, "failed to enable PCLK\n"); 138 return ret; 139 } 140 } 141 rtc->extclk = devm_clk_get(dev, "EXTCLK"); 142 if (IS_ERR(rtc->extclk)) { 143 dev_err(dev, "could not get EXTCLK\n"); 144 } else { 145 ret = clk_prepare_enable(rtc->extclk); 146 if (ret) { 147 dev_err(dev, "failed to enable EXTCLK\n"); 148 return ret; 149 } 150 } 151 152 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 153 if (!res) 154 return -ENODEV; 155 156 rtc->rtc_irq = res->start; 157 158 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 159 if (!res) 160 return -ENODEV; 161 162 rtc->rtc_base = devm_ioremap(dev, res->start, 163 resource_size(res)); 164 if (!rtc->rtc_base) 165 return -ENOMEM; 166 167 rtc->rtc_dev = devm_rtc_allocate_device(dev); 168 if (IS_ERR(rtc->rtc_dev)) 169 return PTR_ERR(rtc->rtc_dev); 170 171 rtc->rtc_dev->ops = &ftrtc010_rtc_ops; 172 173 sec = readl(rtc->rtc_base + FTRTC010_RTC_SECOND); 174 min = readl(rtc->rtc_base + FTRTC010_RTC_MINUTE); 175 hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR); 176 days = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); 177 178 rtc->rtc_dev->range_min = (u64)days * 86400 + hour * 3600 + 179 min * 60 + sec; 180 rtc->rtc_dev->range_max = U32_MAX + rtc->rtc_dev->range_min; 181 182 ret = devm_request_irq(dev, rtc->rtc_irq, ftrtc010_rtc_interrupt, 183 IRQF_SHARED, pdev->name, dev); 184 if (unlikely(ret)) 185 return ret; 186 187 return rtc_register_device(rtc->rtc_dev); 188 } 189 190 static int ftrtc010_rtc_remove(struct platform_device *pdev) 191 { 192 struct ftrtc010_rtc *rtc = platform_get_drvdata(pdev); 193 194 if (!IS_ERR(rtc->extclk)) 195 clk_disable_unprepare(rtc->extclk); 196 if (!IS_ERR(rtc->pclk)) 197 clk_disable_unprepare(rtc->pclk); 198 199 return 0; 200 } 201 202 static const struct of_device_id ftrtc010_rtc_dt_match[] = { 203 { .compatible = "cortina,gemini-rtc" }, 204 { .compatible = "faraday,ftrtc010" }, 205 { } 206 }; 207 MODULE_DEVICE_TABLE(of, ftrtc010_rtc_dt_match); 208 209 static struct platform_driver ftrtc010_rtc_driver = { 210 .driver = { 211 .name = DRV_NAME, 212 .of_match_table = ftrtc010_rtc_dt_match, 213 }, 214 .probe = ftrtc010_rtc_probe, 215 .remove = ftrtc010_rtc_remove, 216 }; 217 218 module_platform_driver_probe(ftrtc010_rtc_driver, ftrtc010_rtc_probe); 219