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); |
