diff options
Diffstat (limited to 'drivers/rtc/rtc-rp5c01.c')
-rw-r--r-- | drivers/rtc/rtc-rp5c01.c | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-rp5c01.c b/drivers/rtc/rtc-rp5c01.c index a95f733bb15a..36eb66184461 100644 --- a/drivers/rtc/rtc-rp5c01.c +++ b/drivers/rtc/rtc-rp5c01.c | |||
@@ -63,6 +63,8 @@ enum { | |||
63 | struct rp5c01_priv { | 63 | struct rp5c01_priv { |
64 | u32 __iomem *regs; | 64 | u32 __iomem *regs; |
65 | struct rtc_device *rtc; | 65 | struct rtc_device *rtc; |
66 | spinlock_t lock; /* against concurrent RTC/NVRAM access */ | ||
67 | struct bin_attribute nvram_attr; | ||
66 | }; | 68 | }; |
67 | 69 | ||
68 | static inline unsigned int rp5c01_read(struct rp5c01_priv *priv, | 70 | static inline unsigned int rp5c01_read(struct rp5c01_priv *priv, |
@@ -92,6 +94,7 @@ static int rp5c01_read_time(struct device *dev, struct rtc_time *tm) | |||
92 | { | 94 | { |
93 | struct rp5c01_priv *priv = dev_get_drvdata(dev); | 95 | struct rp5c01_priv *priv = dev_get_drvdata(dev); |
94 | 96 | ||
97 | spin_lock_irq(&priv->lock); | ||
95 | rp5c01_lock(priv); | 98 | rp5c01_lock(priv); |
96 | 99 | ||
97 | tm->tm_sec = rp5c01_read(priv, RP5C01_10_SECOND) * 10 + | 100 | tm->tm_sec = rp5c01_read(priv, RP5C01_10_SECOND) * 10 + |
@@ -111,6 +114,7 @@ static int rp5c01_read_time(struct device *dev, struct rtc_time *tm) | |||
111 | tm->tm_year += 100; | 114 | tm->tm_year += 100; |
112 | 115 | ||
113 | rp5c01_unlock(priv); | 116 | rp5c01_unlock(priv); |
117 | spin_unlock_irq(&priv->lock); | ||
114 | 118 | ||
115 | return rtc_valid_tm(tm); | 119 | return rtc_valid_tm(tm); |
116 | } | 120 | } |
@@ -119,6 +123,7 @@ static int rp5c01_set_time(struct device *dev, struct rtc_time *tm) | |||
119 | { | 123 | { |
120 | struct rp5c01_priv *priv = dev_get_drvdata(dev); | 124 | struct rp5c01_priv *priv = dev_get_drvdata(dev); |
121 | 125 | ||
126 | spin_lock_irq(&priv->lock); | ||
122 | rp5c01_lock(priv); | 127 | rp5c01_lock(priv); |
123 | 128 | ||
124 | rp5c01_write(priv, tm->tm_sec / 10, RP5C01_10_SECOND); | 129 | rp5c01_write(priv, tm->tm_sec / 10, RP5C01_10_SECOND); |
@@ -139,6 +144,7 @@ static int rp5c01_set_time(struct device *dev, struct rtc_time *tm) | |||
139 | rp5c01_write(priv, tm->tm_year % 10, RP5C01_1_YEAR); | 144 | rp5c01_write(priv, tm->tm_year % 10, RP5C01_1_YEAR); |
140 | 145 | ||
141 | rp5c01_unlock(priv); | 146 | rp5c01_unlock(priv); |
147 | spin_unlock_irq(&priv->lock); | ||
142 | return 0; | 148 | return 0; |
143 | } | 149 | } |
144 | 150 | ||
@@ -147,6 +153,72 @@ static const struct rtc_class_ops rp5c01_rtc_ops = { | |||
147 | .set_time = rp5c01_set_time, | 153 | .set_time = rp5c01_set_time, |
148 | }; | 154 | }; |
149 | 155 | ||
156 | |||
157 | /* | ||
158 | * The NVRAM is organized as 2 blocks of 13 nibbles of 4 bits. | ||
159 | * We provide access to them like AmigaOS does: the high nibble of each 8-bit | ||
160 | * byte is stored in BLOCK10, the low nibble in BLOCK11. | ||
161 | */ | ||
162 | |||
163 | static ssize_t rp5c01_nvram_read(struct file *filp, struct kobject *kobj, | ||
164 | struct bin_attribute *bin_attr, | ||
165 | char *buf, loff_t pos, size_t size) | ||
166 | { | ||
167 | struct device *dev = container_of(kobj, struct device, kobj); | ||
168 | struct rp5c01_priv *priv = dev_get_drvdata(dev); | ||
169 | ssize_t count; | ||
170 | |||
171 | spin_lock_irq(&priv->lock); | ||
172 | |||
173 | for (count = 0; size > 0 && pos < RP5C01_MODE; count++, size--) { | ||
174 | u8 data; | ||
175 | |||
176 | rp5c01_write(priv, | ||
177 | RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK10, | ||
178 | RP5C01_MODE); | ||
179 | data = rp5c01_read(priv, pos) << 4; | ||
180 | rp5c01_write(priv, | ||
181 | RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK11, | ||
182 | RP5C01_MODE); | ||
183 | data |= rp5c01_read(priv, pos++); | ||
184 | rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01, | ||
185 | RP5C01_MODE); | ||
186 | *buf++ = data; | ||
187 | } | ||
188 | |||
189 | spin_unlock_irq(&priv->lock); | ||
190 | return count; | ||
191 | } | ||
192 | |||
193 | static ssize_t rp5c01_nvram_write(struct file *filp, struct kobject *kobj, | ||
194 | struct bin_attribute *bin_attr, | ||
195 | char *buf, loff_t pos, size_t size) | ||
196 | { | ||
197 | struct device *dev = container_of(kobj, struct device, kobj); | ||
198 | struct rp5c01_priv *priv = dev_get_drvdata(dev); | ||
199 | ssize_t count; | ||
200 | |||
201 | spin_lock_irq(&priv->lock); | ||
202 | |||
203 | for (count = 0; size > 0 && pos < RP5C01_MODE; count++, size--) { | ||
204 | u8 data = *buf++; | ||
205 | |||
206 | rp5c01_write(priv, | ||
207 | RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK10, | ||
208 | RP5C01_MODE); | ||
209 | rp5c01_write(priv, data >> 4, pos); | ||
210 | rp5c01_write(priv, | ||
211 | RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK11, | ||
212 | RP5C01_MODE); | ||
213 | rp5c01_write(priv, data & 0xf, pos++); | ||
214 | rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01, | ||
215 | RP5C01_MODE); | ||
216 | } | ||
217 | |||
218 | spin_unlock_irq(&priv->lock); | ||
219 | return count; | ||
220 | } | ||
221 | |||
150 | static int __init rp5c01_rtc_probe(struct platform_device *dev) | 222 | static int __init rp5c01_rtc_probe(struct platform_device *dev) |
151 | { | 223 | { |
152 | struct resource *res; | 224 | struct resource *res; |
@@ -168,6 +240,15 @@ static int __init rp5c01_rtc_probe(struct platform_device *dev) | |||
168 | goto out_free_priv; | 240 | goto out_free_priv; |
169 | } | 241 | } |
170 | 242 | ||
243 | sysfs_bin_attr_init(&priv->nvram_attr); | ||
244 | priv->nvram_attr.attr.name = "nvram"; | ||
245 | priv->nvram_attr.attr.mode = S_IRUGO | S_IWUSR; | ||
246 | priv->nvram_attr.read = rp5c01_nvram_read; | ||
247 | priv->nvram_attr.write = rp5c01_nvram_write; | ||
248 | priv->nvram_attr.size = RP5C01_MODE; | ||
249 | |||
250 | spin_lock_init(&priv->lock); | ||
251 | |||
171 | rtc = rtc_device_register("rtc-rp5c01", &dev->dev, &rp5c01_rtc_ops, | 252 | rtc = rtc_device_register("rtc-rp5c01", &dev->dev, &rp5c01_rtc_ops, |
172 | THIS_MODULE); | 253 | THIS_MODULE); |
173 | if (IS_ERR(rtc)) { | 254 | if (IS_ERR(rtc)) { |
@@ -177,8 +258,15 @@ static int __init rp5c01_rtc_probe(struct platform_device *dev) | |||
177 | 258 | ||
178 | priv->rtc = rtc; | 259 | priv->rtc = rtc; |
179 | platform_set_drvdata(dev, priv); | 260 | platform_set_drvdata(dev, priv); |
261 | |||
262 | error = sysfs_create_bin_file(&dev->dev.kobj, &priv->nvram_attr); | ||
263 | if (error) | ||
264 | goto out_unregister; | ||
265 | |||
180 | return 0; | 266 | return 0; |
181 | 267 | ||
268 | out_unregister: | ||
269 | rtc_device_unregister(rtc); | ||
182 | out_unmap: | 270 | out_unmap: |
183 | iounmap(priv->regs); | 271 | iounmap(priv->regs); |
184 | out_free_priv: | 272 | out_free_priv: |
@@ -190,6 +278,7 @@ static int __exit rp5c01_rtc_remove(struct platform_device *dev) | |||
190 | { | 278 | { |
191 | struct rp5c01_priv *priv = platform_get_drvdata(dev); | 279 | struct rp5c01_priv *priv = platform_get_drvdata(dev); |
192 | 280 | ||
281 | sysfs_remove_bin_file(&dev->dev.kobj, &priv->nvram_attr); | ||
193 | rtc_device_unregister(priv->rtc); | 282 | rtc_device_unregister(priv->rtc); |
194 | iounmap(priv->regs); | 283 | iounmap(priv->regs); |
195 | kfree(priv); | 284 | kfree(priv); |