1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
|
/*
* ST M48T86 / Dallas DS12887 RTC driver
* Copyright (c) 2006 Tower Technologies
*
* Author: Alessandro Zummo <a.zummo@towertech.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This drivers only supports the clock running in BCD and 24H mode.
* If it will be ever adapted to binary and 12H mode, care must be taken
* to not introduce bugs.
*/
#include <linux/module.h>
#include <linux/rtc.h>
#include <linux/platform_device.h>
#include <linux/platform_data/rtc-m48t86.h>
#include <linux/bcd.h>
#include <linux/io.h>
#define M48T86_SEC 0x00
#define M48T86_SECALRM 0x01
#define M48T86_MIN 0x02
#define M48T86_MINALRM 0x03
#define M48T86_HOUR 0x04
#define M48T86_HOURALRM 0x05
#define M48T86_DOW 0x06 /* 1 = sunday */
#define M48T86_DOM 0x07
#define M48T86_MONTH 0x08 /* 1 - 12 */
#define M48T86_YEAR 0x09 /* 0 - 99 */
#define M48T86_A 0x0a
#define M48T86_B 0x0b
#define M48T86_B_SET BIT(7)
#define M48T86_B_DM BIT(2)
#define M48T86_B_H24 BIT(1)
#define M48T86_C 0x0c
#define M48T86_D 0x0d
#define M48T86_D_VRT BIT(7)
struct m48t86_rtc_info {
void __iomem *index_reg;
void __iomem *data_reg;
struct rtc_device *rtc;
struct m48t86_ops *ops;
};
static unsigned char m48t86_readb(struct device *dev, unsigned long addr)
{
struct m48t86_rtc_info *info = dev_get_drvdata(dev);
unsigned char value;
if (info->ops) {
value = info->ops->readbyte(addr);
} else {
writeb(addr, info->index_reg);
value = readb(info->data_reg);
}
return value;
}
static void m48t86_writeb(struct device *dev,
unsigned char value, unsigned long addr)
{
struct m48t86_rtc_info *info = dev_get_drvdata(dev);
if (info->ops) {
info->ops->writebyte(value, addr);
} else {
writeb(addr, info->index_reg);
writeb(value, info->data_reg);
}
}
static int m48t86_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
unsigned char reg;
reg = m48t86_readb(dev, M48T86_B);
if (reg & M48T86_B_DM) {
/* data (binary) mode */
tm->tm_sec = m48t86_readb(dev, M48T86_SEC);
tm->tm_min = m48t86_readb(dev, M48T86_MIN);
tm->tm_hour = m48t86_readb(dev, M48T86_HOUR) & 0x3f;
tm->tm_mday = m48t86_readb(dev, M48T86_DOM);
/* tm_mon is 0-11 */
tm->tm_mon = m48t86_readb(dev, M48T86_MONTH) - 1;
tm->tm_year = m48t86_readb(dev, M48T86_YEAR) + 100;
tm->tm_wday = m48t86_readb(dev, M48T86_DOW);
} else {
/* bcd mode */
tm->tm_sec = bcd2bin(m48t86_readb(dev, M48T86_SEC));
tm->tm_min = bcd2bin(m48t86_readb(dev, M48T86_MIN));
tm->tm_hour = bcd2bin(m48t86_readb(dev, M48T86_HOUR) &
0x3f);
tm->tm_mday = bcd2bin(m48t86_readb(dev, M48T86_DOM));
/* tm_mon is 0-11 */
tm->tm_mon = bcd2bin(m48t86_readb(dev, M48T86_MONTH)) - 1;
tm->tm_year = bcd2bin(m48t86_readb(dev, M48T86_YEAR)) + 100;
tm->tm_wday = bcd2bin(m48t86_readb(dev, M48T86_DOW));
}
/* correct the hour if the clock is in 12h mode */
if (!(reg & M48T86_B_H24))
if (m48t86_readb(dev, M48T86_HOUR) & 0x80)
tm->tm_hour += 12;
return rtc_valid_tm(tm);
}
static int m48t86_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
unsigned char reg;
reg = m48t86_readb(dev, M48T86_B);
/* update flag and 24h mode */
reg |= M48T86_B_SET | M48T86_B_H24;
m48t86_writeb(dev, reg, M48T86_B);
if (reg & M48T86_B_DM) {
/* data (binary) mode */
m48t86_writeb(dev, tm->tm_sec, M48T86_SEC);
m48t86_writeb(dev, tm->tm_min, M48T86_MIN);
m48t86_writeb(dev, tm->tm_hour, M48T86_HOUR);
m48t86_writeb(dev, tm->tm_mday, M48T86_DOM);
m48t86_writeb(dev, tm->tm_mon + 1, M48T86_MONTH);
m48t86_writeb(dev, tm->tm_year % 100, M48T86_YEAR);
m48t86_writeb(dev, tm->tm_wday, M48T86_DOW);
} else {
/* bcd mode */
m48t86_writeb(dev, bin2bcd(tm->tm_sec), M48T86_SEC);
m48t86_writeb(dev, bin2bcd(tm->tm_min), M48T86_MIN);
m48t86_writeb(dev, bin2bcd(tm->tm_hour), M48T86_HOUR);
m48t86_writeb(dev, bin2bcd(tm->tm_mday), M48T86_DOM);
m48t86_writeb(dev, bin2bcd(tm->tm_mon + 1), M48T86_MONTH);
m48t86_writeb(dev, bin2bcd(tm->tm_year % 100), M48T86_YEAR);
m48t86_writeb(dev, bin2bcd(tm->tm_wday), M48T86_DOW);
}
/* update ended */
reg &= ~M48T86_B_SET;
m48t86_writeb(dev, reg, M48T86_B);
return 0;
}
static int m48t86_rtc_proc(struct device *dev, struct seq_file *seq)
{
unsigned char reg;
reg = m48t86_readb(dev, M48T86_B);
seq_printf(seq, "mode\t\t: %s\n",
(reg & M48T86_B_DM) ? "binary" : "bcd");
reg = m48t86_readb(dev, M48T86_D);
seq_printf(seq, "battery\t\t: %s\n",
(reg & M48T86_D_VRT) ? "ok" : "exhausted");
return 0;
}
static const struct rtc_class_ops m48t86_rtc_ops = {
.read_time = m48t86_rtc_read_time,
.set_time = m48t86_rtc_set_time,
.proc = m48t86_rtc_proc,
};
static int m48t86_rtc_probe(struct platform_device *pdev)
{
struct m48t86_rtc_info *info;
unsigned char reg;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->ops = dev_get_platdata(&pdev->dev);
if (!info->ops) {
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
info->index_reg = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(info->index_reg))
return PTR_ERR(info->index_reg);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (!res)
return -ENODEV;
info->data_reg = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(info->data_reg))
return PTR_ERR(info->data_reg);
}
dev_set_drvdata(&pdev->dev, info);
info->rtc = devm_rtc_device_register(&pdev->dev, "m48t86",
&m48t86_rtc_ops, THIS_MODULE);
if (IS_ERR(info->rtc))
return PTR_ERR(info->rtc);
/* read battery status */
reg = m48t86_readb(&pdev->dev, M48T86_D);
dev_info(&pdev->dev, "battery %s\n",
(reg & M48T86_D_VRT) ? "ok" : "exhausted");
return 0;
}
static struct platform_driver m48t86_rtc_platform_driver = {
.driver = {
.name = "rtc-m48t86",
},
.probe = m48t86_rtc_probe,
};
module_platform_driver(m48t86_rtc_platform_driver);
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
MODULE_DESCRIPTION("M48T86 RTC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:rtc-m48t86");
|