Commit | Line | Data |
---|---|---|
c1576497 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
afd8d0f9 DB |
2 | /* |
3 | * rtc-dm355evm.c - access battery-backed counter in MSP430 firmware | |
4 | * | |
5 | * Copyright (c) 2008 by David Brownell | |
afd8d0f9 DB |
6 | */ |
7 | #include <linux/kernel.h> | |
8 | #include <linux/init.h> | |
9 | #include <linux/rtc.h> | |
10 | #include <linux/platform_device.h> | |
11 | ||
0335a955 | 12 | #include <linux/mfd/dm355evm_msp.h> |
2113852b | 13 | #include <linux/module.h> |
afd8d0f9 DB |
14 | |
15 | ||
16 | /* | |
17 | * The MSP430 firmware on the DM355 EVM uses a watch crystal to feed | |
18 | * a 1 Hz counter. When a backup battery is supplied, that makes a | |
19 | * reasonable RTC for applications where alarms and non-NTP drift | |
20 | * compensation aren't important. | |
21 | * | |
22 | * The only real glitch is the inability to read or write all four | |
23 | * counter bytes atomically: the count may increment in the middle | |
24 | * of an operation, causing trouble when the LSB rolls over. | |
25 | * | |
26 | * This driver was tested with firmware revision A4. | |
27 | */ | |
28 | union evm_time { | |
29 | u8 bytes[4]; | |
30 | u32 value; | |
31 | }; | |
32 | ||
33 | static int dm355evm_rtc_read_time(struct device *dev, struct rtc_time *tm) | |
34 | { | |
35 | union evm_time time; | |
36 | int status; | |
37 | int tries = 0; | |
38 | ||
39 | do { | |
40 | /* | |
41 | * Read LSB(0) to MSB(3) bytes. Defend against the counter | |
42 | * rolling over by re-reading until the value is stable, | |
43 | * and assuming the four reads take at most a few seconds. | |
44 | */ | |
45 | status = dm355evm_msp_read(DM355EVM_MSP_RTC_0); | |
46 | if (status < 0) | |
47 | return status; | |
48 | if (tries && time.bytes[0] == status) | |
49 | break; | |
50 | time.bytes[0] = status; | |
51 | ||
52 | status = dm355evm_msp_read(DM355EVM_MSP_RTC_1); | |
53 | if (status < 0) | |
54 | return status; | |
55 | if (tries && time.bytes[1] == status) | |
56 | break; | |
57 | time.bytes[1] = status; | |
58 | ||
59 | status = dm355evm_msp_read(DM355EVM_MSP_RTC_2); | |
60 | if (status < 0) | |
61 | return status; | |
62 | if (tries && time.bytes[2] == status) | |
63 | break; | |
64 | time.bytes[2] = status; | |
65 | ||
66 | status = dm355evm_msp_read(DM355EVM_MSP_RTC_3); | |
67 | if (status < 0) | |
68 | return status; | |
69 | if (tries && time.bytes[3] == status) | |
70 | break; | |
71 | time.bytes[3] = status; | |
72 | ||
73 | } while (++tries < 5); | |
74 | ||
75 | dev_dbg(dev, "read timestamp %08x\n", time.value); | |
76 | ||
c5655adc | 77 | rtc_time64_to_tm(le32_to_cpu(time.value), tm); |
afd8d0f9 DB |
78 | return 0; |
79 | } | |
80 | ||
81 | static int dm355evm_rtc_set_time(struct device *dev, struct rtc_time *tm) | |
82 | { | |
83 | union evm_time time; | |
84 | unsigned long value; | |
85 | int status; | |
86 | ||
c5655adc | 87 | value = rtc_tm_to_time64(tm); |
afd8d0f9 DB |
88 | time.value = cpu_to_le32(value); |
89 | ||
90 | dev_dbg(dev, "write timestamp %08x\n", time.value); | |
91 | ||
92 | /* | |
93 | * REVISIT handle non-atomic writes ... maybe just retry until | |
94 | * byte[1] sticks (no rollover)? | |
95 | */ | |
96 | status = dm355evm_msp_write(time.bytes[0], DM355EVM_MSP_RTC_0); | |
97 | if (status < 0) | |
98 | return status; | |
99 | ||
100 | status = dm355evm_msp_write(time.bytes[1], DM355EVM_MSP_RTC_1); | |
101 | if (status < 0) | |
102 | return status; | |
103 | ||
104 | status = dm355evm_msp_write(time.bytes[2], DM355EVM_MSP_RTC_2); | |
105 | if (status < 0) | |
106 | return status; | |
107 | ||
108 | status = dm355evm_msp_write(time.bytes[3], DM355EVM_MSP_RTC_3); | |
109 | if (status < 0) | |
110 | return status; | |
111 | ||
112 | return 0; | |
113 | } | |
114 | ||
8bc57e7f | 115 | static const struct rtc_class_ops dm355evm_rtc_ops = { |
afd8d0f9 DB |
116 | .read_time = dm355evm_rtc_read_time, |
117 | .set_time = dm355evm_rtc_set_time, | |
118 | }; | |
119 | ||
120 | /*----------------------------------------------------------------------*/ | |
121 | ||
5a167f45 | 122 | static int dm355evm_rtc_probe(struct platform_device *pdev) |
afd8d0f9 DB |
123 | { |
124 | struct rtc_device *rtc; | |
125 | ||
c50fd69a AB |
126 | rtc = devm_rtc_allocate_device(&pdev->dev); |
127 | if (IS_ERR(rtc)) | |
afd8d0f9 | 128 | return PTR_ERR(rtc); |
c50fd69a | 129 | |
afd8d0f9 DB |
130 | platform_set_drvdata(pdev, rtc); |
131 | ||
c50fd69a | 132 | rtc->ops = &dm355evm_rtc_ops; |
4bc85a38 | 133 | rtc->range_max = U32_MAX; |
c50fd69a AB |
134 | |
135 | return rtc_register_device(rtc); | |
afd8d0f9 DB |
136 | } |
137 | ||
afd8d0f9 DB |
138 | /* |
139 | * I2C is used to talk to the MSP430, but this platform device is | |
140 | * exposed by an MFD driver that manages I2C communications. | |
141 | */ | |
142 | static struct platform_driver rtc_dm355evm_driver = { | |
143 | .probe = dm355evm_rtc_probe, | |
afd8d0f9 | 144 | .driver = { |
afd8d0f9 DB |
145 | .name = "rtc-dm355evm", |
146 | }, | |
147 | }; | |
148 | ||
0c4eae66 | 149 | module_platform_driver(rtc_dm355evm_driver); |
afd8d0f9 DB |
150 | |
151 | MODULE_LICENSE("GPL"); |