diff options
Diffstat (limited to 'drivers/rtc/rtc-rp5c01.c')
| -rw-r--r-- | drivers/rtc/rtc-rp5c01.c | 134 |
1 files changed, 99 insertions, 35 deletions
diff --git a/drivers/rtc/rtc-rp5c01.c b/drivers/rtc/rtc-rp5c01.c index e1313feb060..89d07367926 100644 --- a/drivers/rtc/rtc-rp5c01.c +++ b/drivers/rtc/rtc-rp5c01.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/rtc.h> +#include <linux/slab.h> enum { @@ -62,6 +63,8 @@ enum { struct rp5c01_priv { u32 __iomem *regs; struct rtc_device *rtc; + spinlock_t lock; /* against concurrent RTC/NVRAM access */ + struct bin_attribute nvram_attr; }; static inline unsigned int rp5c01_read(struct rp5c01_priv *priv, @@ -73,7 +76,7 @@ static inline unsigned int rp5c01_read(struct rp5c01_priv *priv, static inline void rp5c01_write(struct rp5c01_priv *priv, unsigned int val, unsigned int reg) { - return __raw_writel(val, &priv->regs[reg]); + __raw_writel(val, &priv->regs[reg]); } static void rp5c01_lock(struct rp5c01_priv *priv) @@ -91,6 +94,7 @@ static int rp5c01_read_time(struct device *dev, struct rtc_time *tm) { struct rp5c01_priv *priv = dev_get_drvdata(dev); + spin_lock_irq(&priv->lock); rp5c01_lock(priv); tm->tm_sec = rp5c01_read(priv, RP5C01_10_SECOND) * 10 + @@ -110,6 +114,7 @@ static int rp5c01_read_time(struct device *dev, struct rtc_time *tm) tm->tm_year += 100; rp5c01_unlock(priv); + spin_unlock_irq(&priv->lock); return rtc_valid_tm(tm); } @@ -118,6 +123,7 @@ static int rp5c01_set_time(struct device *dev, struct rtc_time *tm) { struct rp5c01_priv *priv = dev_get_drvdata(dev); + spin_lock_irq(&priv->lock); rp5c01_lock(priv); rp5c01_write(priv, tm->tm_sec / 10, RP5C01_10_SECOND); @@ -138,6 +144,7 @@ static int rp5c01_set_time(struct device *dev, struct rtc_time *tm) rp5c01_write(priv, tm->tm_year % 10, RP5C01_1_YEAR); rp5c01_unlock(priv); + spin_unlock_irq(&priv->lock); return 0; } @@ -146,6 +153,72 @@ static const struct rtc_class_ops rp5c01_rtc_ops = { .set_time = rp5c01_set_time, }; + +/* + * The NVRAM is organized as 2 blocks of 13 nibbles of 4 bits. + * We provide access to them like AmigaOS does: the high nibble of each 8-bit + * byte is stored in BLOCK10, the low nibble in BLOCK11. + */ + +static ssize_t rp5c01_nvram_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t size) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct rp5c01_priv *priv = dev_get_drvdata(dev); + ssize_t count; + + spin_lock_irq(&priv->lock); + + for (count = 0; size > 0 && pos < RP5C01_MODE; count++, size--) { + u8 data; + + rp5c01_write(priv, + RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK10, + RP5C01_MODE); + data = rp5c01_read(priv, pos) << 4; + rp5c01_write(priv, + RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK11, + RP5C01_MODE); + data |= rp5c01_read(priv, pos++); + rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01, + RP5C01_MODE); + *buf++ = data; + } + + spin_unlock_irq(&priv->lock); + return count; +} + +static ssize_t rp5c01_nvram_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t size) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct rp5c01_priv *priv = dev_get_drvdata(dev); + ssize_t count; + + spin_lock_irq(&priv->lock); + + for (count = 0; size > 0 && pos < RP5C01_MODE; count++, size--) { + u8 data = *buf++; + + rp5c01_write(priv, + RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK10, + RP5C01_MODE); + rp5c01_write(priv, data >> 4, pos); + rp5c01_write(priv, + RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK11, + RP5C01_MODE); + rp5c01_write(priv, data & 0xf, pos++); + rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01, + RP5C01_MODE); + } + + spin_unlock_irq(&priv->lock); + return count; +} + static int __init rp5c01_rtc_probe(struct platform_device *dev) { struct resource *res; @@ -157,41 +230,43 @@ static int __init rp5c01_rtc_probe(struct platform_device *dev) if (!res) return -ENODEV; - priv = kzalloc(sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - priv->regs = ioremap(res->start, resource_size(res)); - if (!priv->regs) { - error = -ENOMEM; - goto out_free_priv; - } + priv->regs = devm_ioremap(&dev->dev, res->start, resource_size(res)); + if (!priv->regs) + return -ENOMEM; - rtc = rtc_device_register("rtc-rp5c01", &dev->dev, &rp5c01_rtc_ops, - THIS_MODULE); - if (IS_ERR(rtc)) { - error = PTR_ERR(rtc); - goto out_unmap; - } + sysfs_bin_attr_init(&priv->nvram_attr); + priv->nvram_attr.attr.name = "nvram"; + priv->nvram_attr.attr.mode = S_IRUGO | S_IWUSR; + priv->nvram_attr.read = rp5c01_nvram_read; + priv->nvram_attr.write = rp5c01_nvram_write; + priv->nvram_attr.size = RP5C01_MODE; + + spin_lock_init(&priv->lock); - priv->rtc = rtc; platform_set_drvdata(dev, priv); - return 0; -out_unmap: - iounmap(priv->regs); -out_free_priv: - kfree(priv); - return error; + rtc = devm_rtc_device_register(&dev->dev, "rtc-rp5c01", &rp5c01_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + priv->rtc = rtc; + + error = sysfs_create_bin_file(&dev->dev.kobj, &priv->nvram_attr); + if (error) + return error; + + return 0; } static int __exit rp5c01_rtc_remove(struct platform_device *dev) { struct rp5c01_priv *priv = platform_get_drvdata(dev); - rtc_device_unregister(priv->rtc); - iounmap(priv->regs); - kfree(priv); + sysfs_remove_bin_file(&dev->dev.kobj, &priv->nvram_attr); return 0; } @@ -203,18 +278,7 @@ static struct platform_driver rp5c01_rtc_driver = { .remove = __exit_p(rp5c01_rtc_remove), }; -static int __init rp5c01_rtc_init(void) -{ - return platform_driver_probe(&rp5c01_rtc_driver, rp5c01_rtc_probe); -} - -static void __exit rp5c01_rtc_fini(void) -{ - platform_driver_unregister(&rp5c01_rtc_driver); -} - -module_init(rp5c01_rtc_init); -module_exit(rp5c01_rtc_fini); +module_platform_driver_probe(rp5c01_rtc_driver, rp5c01_rtc_probe); MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>"); MODULE_LICENSE("GPL"); |
