Commit | Line | Data |
---|---|---|
362600fe RA |
1 | /* drivers/rtc/rtc-v3020.c |
2 | * | |
3 | * Copyright (C) 2006 8D Technologies inc. | |
4 | * Copyright (C) 2004 Compulab Ltd. | |
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 version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | * Driver for the V3020 RTC | |
11 | * | |
12 | * Changelog: | |
13 | * | |
14 | * 10-May-2006: Raphael Assenat <raph@8d.com> | |
15 | * - Converted to platform driver | |
16 | * - Use the generic rtc class | |
17 | * | |
18 | * ??-???-2004: Someone at Compulab | |
19 | * - Initial driver creation. | |
20 | * | |
21 | */ | |
22 | #include <linux/platform_device.h> | |
23 | #include <linux/module.h> | |
24 | #include <linux/init.h> | |
25 | #include <linux/rtc.h> | |
26 | #include <linux/types.h> | |
27 | #include <linux/bcd.h> | |
28 | #include <linux/rtc-v3020.h> | |
f3d79b20 | 29 | #include <linux/delay.h> |
362600fe RA |
30 | |
31 | #include <asm/io.h> | |
32 | ||
33 | #undef DEBUG | |
34 | ||
35 | struct v3020 { | |
36 | void __iomem *ioaddress; | |
37 | int leftshift; | |
38 | struct rtc_device *rtc; | |
39 | }; | |
40 | ||
41 | static void v3020_set_reg(struct v3020 *chip, unsigned char address, | |
42 | unsigned char data) | |
43 | { | |
44 | int i; | |
45 | unsigned char tmp; | |
46 | ||
47 | tmp = address; | |
48 | for (i = 0; i < 4; i++) { | |
49 | writel((tmp & 1) << chip->leftshift, chip->ioaddress); | |
50 | tmp >>= 1; | |
f3d79b20 | 51 | udelay(1); |
362600fe RA |
52 | } |
53 | ||
54 | /* Commands dont have data */ | |
55 | if (!V3020_IS_COMMAND(address)) { | |
56 | for (i = 0; i < 8; i++) { | |
57 | writel((data & 1) << chip->leftshift, chip->ioaddress); | |
58 | data >>= 1; | |
f3d79b20 | 59 | udelay(1); |
362600fe RA |
60 | } |
61 | } | |
62 | } | |
63 | ||
64 | static unsigned char v3020_get_reg(struct v3020 *chip, unsigned char address) | |
65 | { | |
66 | unsigned int data=0; | |
67 | int i; | |
68 | ||
69 | for (i = 0; i < 4; i++) { | |
70 | writel((address & 1) << chip->leftshift, chip->ioaddress); | |
71 | address >>= 1; | |
f3d79b20 | 72 | udelay(1); |
362600fe RA |
73 | } |
74 | ||
75 | for (i = 0; i < 8; i++) { | |
76 | data >>= 1; | |
77 | if (readl(chip->ioaddress) & (1 << chip->leftshift)) | |
78 | data |= 0x80; | |
f3d79b20 | 79 | udelay(1); |
362600fe RA |
80 | } |
81 | ||
82 | return data; | |
83 | } | |
84 | ||
85 | static int v3020_read_time(struct device *dev, struct rtc_time *dt) | |
86 | { | |
87 | struct v3020 *chip = dev_get_drvdata(dev); | |
88 | int tmp; | |
89 | ||
90 | /* Copy the current time to ram... */ | |
91 | v3020_set_reg(chip, V3020_CMD_CLOCK2RAM, 0); | |
92 | ||
93 | /* ...and then read constant values. */ | |
94 | tmp = v3020_get_reg(chip, V3020_SECONDS); | |
95 | dt->tm_sec = BCD2BIN(tmp); | |
96 | tmp = v3020_get_reg(chip, V3020_MINUTES); | |
97 | dt->tm_min = BCD2BIN(tmp); | |
98 | tmp = v3020_get_reg(chip, V3020_HOURS); | |
99 | dt->tm_hour = BCD2BIN(tmp); | |
100 | tmp = v3020_get_reg(chip, V3020_MONTH_DAY); | |
101 | dt->tm_mday = BCD2BIN(tmp); | |
102 | tmp = v3020_get_reg(chip, V3020_MONTH); | |
f3d79b20 | 103 | dt->tm_mon = BCD2BIN(tmp) - 1; |
362600fe RA |
104 | tmp = v3020_get_reg(chip, V3020_WEEK_DAY); |
105 | dt->tm_wday = BCD2BIN(tmp); | |
106 | tmp = v3020_get_reg(chip, V3020_YEAR); | |
107 | dt->tm_year = BCD2BIN(tmp)+100; | |
108 | ||
109 | #ifdef DEBUG | |
2a4e2b87 | 110 | printk("\n%s : Read RTC values\n",__func__); |
362600fe RA |
111 | printk("tm_hour: %i\n",dt->tm_hour); |
112 | printk("tm_min : %i\n",dt->tm_min); | |
113 | printk("tm_sec : %i\n",dt->tm_sec); | |
114 | printk("tm_year: %i\n",dt->tm_year); | |
115 | printk("tm_mon : %i\n",dt->tm_mon); | |
116 | printk("tm_mday: %i\n",dt->tm_mday); | |
117 | printk("tm_wday: %i\n",dt->tm_wday); | |
118 | #endif | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
123 | ||
124 | static int v3020_set_time(struct device *dev, struct rtc_time *dt) | |
125 | { | |
126 | struct v3020 *chip = dev_get_drvdata(dev); | |
127 | ||
128 | #ifdef DEBUG | |
2a4e2b87 | 129 | printk("\n%s : Setting RTC values\n",__func__); |
362600fe RA |
130 | printk("tm_sec : %i\n",dt->tm_sec); |
131 | printk("tm_min : %i\n",dt->tm_min); | |
132 | printk("tm_hour: %i\n",dt->tm_hour); | |
133 | printk("tm_mday: %i\n",dt->tm_mday); | |
134 | printk("tm_wday: %i\n",dt->tm_wday); | |
135 | printk("tm_year: %i\n",dt->tm_year); | |
136 | #endif | |
137 | ||
138 | /* Write all the values to ram... */ | |
139 | v3020_set_reg(chip, V3020_SECONDS, BIN2BCD(dt->tm_sec)); | |
140 | v3020_set_reg(chip, V3020_MINUTES, BIN2BCD(dt->tm_min)); | |
141 | v3020_set_reg(chip, V3020_HOURS, BIN2BCD(dt->tm_hour)); | |
142 | v3020_set_reg(chip, V3020_MONTH_DAY, BIN2BCD(dt->tm_mday)); | |
f3d79b20 | 143 | v3020_set_reg(chip, V3020_MONTH, BIN2BCD(dt->tm_mon + 1)); |
362600fe RA |
144 | v3020_set_reg(chip, V3020_WEEK_DAY, BIN2BCD(dt->tm_wday)); |
145 | v3020_set_reg(chip, V3020_YEAR, BIN2BCD(dt->tm_year % 100)); | |
146 | ||
147 | /* ...and set the clock. */ | |
148 | v3020_set_reg(chip, V3020_CMD_RAM2CLOCK, 0); | |
149 | ||
150 | /* Compulab used this delay here. I dont know why, | |
151 | * the datasheet does not specify a delay. */ | |
152 | /*mdelay(5);*/ | |
153 | ||
154 | return 0; | |
155 | } | |
156 | ||
ff8371ac | 157 | static const struct rtc_class_ops v3020_rtc_ops = { |
362600fe RA |
158 | .read_time = v3020_read_time, |
159 | .set_time = v3020_set_time, | |
160 | }; | |
161 | ||
162 | static int rtc_probe(struct platform_device *pdev) | |
163 | { | |
164 | struct v3020_platform_data *pdata = pdev->dev.platform_data; | |
165 | struct v3020 *chip; | |
166 | struct rtc_device *rtc; | |
167 | int retval = -EBUSY; | |
168 | int i; | |
169 | int temp; | |
170 | ||
171 | if (pdev->num_resources != 1) | |
172 | return -EBUSY; | |
173 | ||
174 | if (pdev->resource[0].flags != IORESOURCE_MEM) | |
175 | return -EBUSY; | |
176 | ||
362600fe RA |
177 | chip = kzalloc(sizeof *chip, GFP_KERNEL); |
178 | if (!chip) | |
179 | return -ENOMEM; | |
180 | ||
181 | chip->leftshift = pdata->leftshift; | |
182 | chip->ioaddress = ioremap(pdev->resource[0].start, 1); | |
183 | if (chip->ioaddress == NULL) | |
184 | goto err_chip; | |
185 | ||
186 | /* Make sure the v3020 expects a communication cycle | |
187 | * by reading 8 times */ | |
188 | for (i = 0; i < 8; i++) | |
189 | temp = readl(chip->ioaddress); | |
190 | ||
191 | /* Test chip by doing a write/read sequence | |
192 | * to the chip ram */ | |
193 | v3020_set_reg(chip, V3020_SECONDS, 0x33); | |
194 | if(v3020_get_reg(chip, V3020_SECONDS) != 0x33) { | |
195 | retval = -ENODEV; | |
196 | goto err_io; | |
197 | } | |
198 | ||
199 | /* Make sure frequency measurment mode, test modes, and lock | |
200 | * are all disabled */ | |
201 | v3020_set_reg(chip, V3020_STATUS_0, 0x0); | |
202 | ||
6a15f46c | 203 | dev_info(&pdev->dev, "Chip available at physical address 0x%llx," |
362600fe | 204 | "data connected to D%d\n", |
6a15f46c | 205 | (unsigned long long)pdev->resource[0].start, |
362600fe RA |
206 | chip->leftshift); |
207 | ||
208 | platform_set_drvdata(pdev, chip); | |
209 | ||
210 | rtc = rtc_device_register("v3020", | |
211 | &pdev->dev, &v3020_rtc_ops, THIS_MODULE); | |
212 | if (IS_ERR(rtc)) { | |
213 | retval = PTR_ERR(rtc); | |
214 | goto err_io; | |
215 | } | |
216 | chip->rtc = rtc; | |
217 | ||
218 | return 0; | |
219 | ||
220 | err_io: | |
221 | iounmap(chip->ioaddress); | |
222 | err_chip: | |
223 | kfree(chip); | |
224 | ||
225 | return retval; | |
226 | } | |
227 | ||
228 | static int rtc_remove(struct platform_device *dev) | |
229 | { | |
230 | struct v3020 *chip = platform_get_drvdata(dev); | |
231 | struct rtc_device *rtc = chip->rtc; | |
232 | ||
233 | if (rtc) | |
234 | rtc_device_unregister(rtc); | |
235 | ||
236 | iounmap(chip->ioaddress); | |
237 | kfree(chip); | |
238 | ||
239 | return 0; | |
240 | } | |
241 | ||
242 | static struct platform_driver rtc_device_driver = { | |
243 | .probe = rtc_probe, | |
244 | .remove = rtc_remove, | |
245 | .driver = { | |
246 | .name = "v3020", | |
247 | .owner = THIS_MODULE, | |
248 | }, | |
249 | }; | |
250 | ||
251 | static __init int v3020_init(void) | |
252 | { | |
253 | return platform_driver_register(&rtc_device_driver); | |
254 | } | |
255 | ||
256 | static __exit void v3020_exit(void) | |
257 | { | |
258 | platform_driver_unregister(&rtc_device_driver); | |
259 | } | |
260 | ||
261 | module_init(v3020_init); | |
262 | module_exit(v3020_exit); | |
263 | ||
264 | MODULE_DESCRIPTION("V3020 RTC"); | |
265 | MODULE_AUTHOR("Raphael Assenat"); | |
266 | MODULE_LICENSE("GPL"); | |
ad28a07b | 267 | MODULE_ALIAS("platform:v3020"); |