Commit 362600fe60fd18a25b4de8ec544b9e24e77e1484
Committed by
Linus Torvalds
1 parent
9be05b57bd
Exists in
master
and in
4 other branches
[PATCH] Add v3020 RTC support
This patch adds support for the v3020 RTC from EM Microelectronic. The v3020 RTC is designed to be connected on a bus using only one data bit. Since any data bit may be used, it is necessary to specify this to the driver by passing a struct v3020_platform_data pointer (see include/linux/rtc-v3020.h) to the driver. Part of the following code comes from the kernel patchs produced by Compulab for their products. The original file (available here: http://raph.people.8d.com/misc/emv3020.c) was released under the terms of the GPL license. [akpm@osdl.org: cleanups] Signed-off-by: Raphael Assenat <raph@raphnet.net> Cc: Alessandro Zummo <a.zummo@towertech.it> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Showing 4 changed files with 310 additions and 0 deletions Side-by-side Diff
drivers/rtc/Kconfig
... | ... | @@ -227,5 +227,15 @@ |
227 | 227 | This driver can also be built as a module. If so, the module |
228 | 228 | will be called rtc-max6902. |
229 | 229 | |
230 | +config RTC_DRV_V3020 | |
231 | + tristate "EM Microelectronic V3020" | |
232 | + depends on RTC_CLASS | |
233 | + help | |
234 | + If you say yes here you will get support for the | |
235 | + EM Microelectronic v3020 RTC chip. | |
236 | + | |
237 | + This driver can also be built as a module. If so, the module | |
238 | + will be called rtc-v3020. | |
239 | + | |
230 | 240 | endmenu |
drivers/rtc/Makefile
drivers/rtc/rtc-v3020.c
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> | |
29 | + | |
30 | +#include <asm/io.h> | |
31 | + | |
32 | +#undef DEBUG | |
33 | + | |
34 | +struct v3020 { | |
35 | + void __iomem *ioaddress; | |
36 | + int leftshift; | |
37 | + struct rtc_device *rtc; | |
38 | +}; | |
39 | + | |
40 | +static void v3020_set_reg(struct v3020 *chip, unsigned char address, | |
41 | + unsigned char data) | |
42 | +{ | |
43 | + int i; | |
44 | + unsigned char tmp; | |
45 | + | |
46 | + tmp = address; | |
47 | + for (i = 0; i < 4; i++) { | |
48 | + writel((tmp & 1) << chip->leftshift, chip->ioaddress); | |
49 | + tmp >>= 1; | |
50 | + } | |
51 | + | |
52 | + /* Commands dont have data */ | |
53 | + if (!V3020_IS_COMMAND(address)) { | |
54 | + for (i = 0; i < 8; i++) { | |
55 | + writel((data & 1) << chip->leftshift, chip->ioaddress); | |
56 | + data >>= 1; | |
57 | + } | |
58 | + } | |
59 | +} | |
60 | + | |
61 | +static unsigned char v3020_get_reg(struct v3020 *chip, unsigned char address) | |
62 | +{ | |
63 | + unsigned int data=0; | |
64 | + int i; | |
65 | + | |
66 | + for (i = 0; i < 4; i++) { | |
67 | + writel((address & 1) << chip->leftshift, chip->ioaddress); | |
68 | + address >>= 1; | |
69 | + } | |
70 | + | |
71 | + for (i = 0; i < 8; i++) { | |
72 | + data >>= 1; | |
73 | + if (readl(chip->ioaddress) & (1 << chip->leftshift)) | |
74 | + data |= 0x80; | |
75 | + } | |
76 | + | |
77 | + return data; | |
78 | +} | |
79 | + | |
80 | +static int v3020_read_time(struct device *dev, struct rtc_time *dt) | |
81 | +{ | |
82 | + struct v3020 *chip = dev_get_drvdata(dev); | |
83 | + int tmp; | |
84 | + | |
85 | + /* Copy the current time to ram... */ | |
86 | + v3020_set_reg(chip, V3020_CMD_CLOCK2RAM, 0); | |
87 | + | |
88 | + /* ...and then read constant values. */ | |
89 | + tmp = v3020_get_reg(chip, V3020_SECONDS); | |
90 | + dt->tm_sec = BCD2BIN(tmp); | |
91 | + tmp = v3020_get_reg(chip, V3020_MINUTES); | |
92 | + dt->tm_min = BCD2BIN(tmp); | |
93 | + tmp = v3020_get_reg(chip, V3020_HOURS); | |
94 | + dt->tm_hour = BCD2BIN(tmp); | |
95 | + tmp = v3020_get_reg(chip, V3020_MONTH_DAY); | |
96 | + dt->tm_mday = BCD2BIN(tmp); | |
97 | + tmp = v3020_get_reg(chip, V3020_MONTH); | |
98 | + dt->tm_mon = BCD2BIN(tmp); | |
99 | + tmp = v3020_get_reg(chip, V3020_WEEK_DAY); | |
100 | + dt->tm_wday = BCD2BIN(tmp); | |
101 | + tmp = v3020_get_reg(chip, V3020_YEAR); | |
102 | + dt->tm_year = BCD2BIN(tmp)+100; | |
103 | + | |
104 | +#ifdef DEBUG | |
105 | + printk("\n%s : Read RTC values\n",__FUNCTION__); | |
106 | + printk("tm_hour: %i\n",dt->tm_hour); | |
107 | + printk("tm_min : %i\n",dt->tm_min); | |
108 | + printk("tm_sec : %i\n",dt->tm_sec); | |
109 | + printk("tm_year: %i\n",dt->tm_year); | |
110 | + printk("tm_mon : %i\n",dt->tm_mon); | |
111 | + printk("tm_mday: %i\n",dt->tm_mday); | |
112 | + printk("tm_wday: %i\n",dt->tm_wday); | |
113 | +#endif | |
114 | + | |
115 | + return 0; | |
116 | +} | |
117 | + | |
118 | + | |
119 | +static int v3020_set_time(struct device *dev, struct rtc_time *dt) | |
120 | +{ | |
121 | + struct v3020 *chip = dev_get_drvdata(dev); | |
122 | + | |
123 | +#ifdef DEBUG | |
124 | + printk("\n%s : Setting RTC values\n",__FUNCTION__); | |
125 | + printk("tm_sec : %i\n",dt->tm_sec); | |
126 | + printk("tm_min : %i\n",dt->tm_min); | |
127 | + printk("tm_hour: %i\n",dt->tm_hour); | |
128 | + printk("tm_mday: %i\n",dt->tm_mday); | |
129 | + printk("tm_wday: %i\n",dt->tm_wday); | |
130 | + printk("tm_year: %i\n",dt->tm_year); | |
131 | +#endif | |
132 | + | |
133 | + /* Write all the values to ram... */ | |
134 | + v3020_set_reg(chip, V3020_SECONDS, BIN2BCD(dt->tm_sec)); | |
135 | + v3020_set_reg(chip, V3020_MINUTES, BIN2BCD(dt->tm_min)); | |
136 | + v3020_set_reg(chip, V3020_HOURS, BIN2BCD(dt->tm_hour)); | |
137 | + v3020_set_reg(chip, V3020_MONTH_DAY, BIN2BCD(dt->tm_mday)); | |
138 | + v3020_set_reg(chip, V3020_MONTH, BIN2BCD(dt->tm_mon)); | |
139 | + v3020_set_reg(chip, V3020_WEEK_DAY, BIN2BCD(dt->tm_wday)); | |
140 | + v3020_set_reg(chip, V3020_YEAR, BIN2BCD(dt->tm_year % 100)); | |
141 | + | |
142 | + /* ...and set the clock. */ | |
143 | + v3020_set_reg(chip, V3020_CMD_RAM2CLOCK, 0); | |
144 | + | |
145 | + /* Compulab used this delay here. I dont know why, | |
146 | + * the datasheet does not specify a delay. */ | |
147 | + /*mdelay(5);*/ | |
148 | + | |
149 | + return 0; | |
150 | +} | |
151 | + | |
152 | +static struct rtc_class_ops v3020_rtc_ops = { | |
153 | + .read_time = v3020_read_time, | |
154 | + .set_time = v3020_set_time, | |
155 | +}; | |
156 | + | |
157 | +static int rtc_probe(struct platform_device *pdev) | |
158 | +{ | |
159 | + struct v3020_platform_data *pdata = pdev->dev.platform_data; | |
160 | + struct v3020 *chip; | |
161 | + struct rtc_device *rtc; | |
162 | + int retval = -EBUSY; | |
163 | + int i; | |
164 | + int temp; | |
165 | + | |
166 | + if (pdev->num_resources != 1) | |
167 | + return -EBUSY; | |
168 | + | |
169 | + if (pdev->resource[0].flags != IORESOURCE_MEM) | |
170 | + return -EBUSY; | |
171 | + | |
172 | + if (pdev == NULL) | |
173 | + return -EBUSY; | |
174 | + | |
175 | + chip = kzalloc(sizeof *chip, GFP_KERNEL); | |
176 | + if (!chip) | |
177 | + return -ENOMEM; | |
178 | + | |
179 | + chip->leftshift = pdata->leftshift; | |
180 | + chip->ioaddress = ioremap(pdev->resource[0].start, 1); | |
181 | + if (chip->ioaddress == NULL) | |
182 | + goto err_chip; | |
183 | + | |
184 | + /* Make sure the v3020 expects a communication cycle | |
185 | + * by reading 8 times */ | |
186 | + for (i = 0; i < 8; i++) | |
187 | + temp = readl(chip->ioaddress); | |
188 | + | |
189 | + /* Test chip by doing a write/read sequence | |
190 | + * to the chip ram */ | |
191 | + v3020_set_reg(chip, V3020_SECONDS, 0x33); | |
192 | + if(v3020_get_reg(chip, V3020_SECONDS) != 0x33) { | |
193 | + retval = -ENODEV; | |
194 | + goto err_io; | |
195 | + } | |
196 | + | |
197 | + /* Make sure frequency measurment mode, test modes, and lock | |
198 | + * are all disabled */ | |
199 | + v3020_set_reg(chip, V3020_STATUS_0, 0x0); | |
200 | + | |
201 | + dev_info(&pdev->dev, "Chip available at physical address 0x%p," | |
202 | + "data connected to D%d\n", | |
203 | + (void*)pdev->resource[0].start, | |
204 | + chip->leftshift); | |
205 | + | |
206 | + platform_set_drvdata(pdev, chip); | |
207 | + | |
208 | + rtc = rtc_device_register("v3020", | |
209 | + &pdev->dev, &v3020_rtc_ops, THIS_MODULE); | |
210 | + if (IS_ERR(rtc)) { | |
211 | + retval = PTR_ERR(rtc); | |
212 | + goto err_io; | |
213 | + } | |
214 | + chip->rtc = rtc; | |
215 | + | |
216 | + return 0; | |
217 | + | |
218 | +err_io: | |
219 | + iounmap(chip->ioaddress); | |
220 | +err_chip: | |
221 | + kfree(chip); | |
222 | + | |
223 | + return retval; | |
224 | +} | |
225 | + | |
226 | +static int rtc_remove(struct platform_device *dev) | |
227 | +{ | |
228 | + struct v3020 *chip = platform_get_drvdata(dev); | |
229 | + struct rtc_device *rtc = chip->rtc; | |
230 | + | |
231 | + if (rtc) | |
232 | + rtc_device_unregister(rtc); | |
233 | + | |
234 | + iounmap(chip->ioaddress); | |
235 | + kfree(chip); | |
236 | + | |
237 | + return 0; | |
238 | +} | |
239 | + | |
240 | +static struct platform_driver rtc_device_driver = { | |
241 | + .probe = rtc_probe, | |
242 | + .remove = rtc_remove, | |
243 | + .driver = { | |
244 | + .name = "v3020", | |
245 | + .owner = THIS_MODULE, | |
246 | + }, | |
247 | +}; | |
248 | + | |
249 | +static __init int v3020_init(void) | |
250 | +{ | |
251 | + return platform_driver_register(&rtc_device_driver); | |
252 | +} | |
253 | + | |
254 | +static __exit void v3020_exit(void) | |
255 | +{ | |
256 | + platform_driver_unregister(&rtc_device_driver); | |
257 | +} | |
258 | + | |
259 | +module_init(v3020_init); | |
260 | +module_exit(v3020_exit); | |
261 | + | |
262 | +MODULE_DESCRIPTION("V3020 RTC"); | |
263 | +MODULE_AUTHOR("Raphael Assenat"); | |
264 | +MODULE_LICENSE("GPL"); |
include/linux/rtc-v3020.h
1 | +/* | |
2 | + * v3020.h - Registers definition and platform data structure for the v3020 RTC. | |
3 | + * | |
4 | + * This file is subject to the terms and conditions of the GNU General Public | |
5 | + * License. See the file "COPYING" in the main directory of this archive | |
6 | + * for more details. | |
7 | + * | |
8 | + * Copyright (C) 2006, 8D Technologies inc. | |
9 | + */ | |
10 | +#ifndef __LINUX_V3020_H | |
11 | +#define __LINUX_V3020_H | |
12 | + | |
13 | +/* The v3020 has only one data pin but which one | |
14 | + * is used depends on the board. */ | |
15 | +struct v3020_platform_data { | |
16 | + int leftshift; /* (1<<(leftshift)) & readl() */ | |
17 | +}; | |
18 | + | |
19 | +#define V3020_STATUS_0 0x00 | |
20 | +#define V3020_STATUS_1 0x01 | |
21 | +#define V3020_SECONDS 0x02 | |
22 | +#define V3020_MINUTES 0x03 | |
23 | +#define V3020_HOURS 0x04 | |
24 | +#define V3020_MONTH_DAY 0x05 | |
25 | +#define V3020_MONTH 0x06 | |
26 | +#define V3020_YEAR 0x07 | |
27 | +#define V3020_WEEK_DAY 0x08 | |
28 | +#define V3020_WEEK 0x09 | |
29 | + | |
30 | +#define V3020_IS_COMMAND(val) ((val)>=0x0E) | |
31 | + | |
32 | +#define V3020_CMD_RAM2CLOCK 0x0E | |
33 | +#define V3020_CMD_CLOCK2RAM 0x0F | |
34 | + | |
35 | +#endif /* __LINUX_V3020_H */ |