diff options
-rw-r--r-- | drivers/rtc/rtc-cmos.c | 129 |
1 files changed, 117 insertions, 12 deletions
diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index 29cf145..ab455dd 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -393,6 +393,80 @@ static const struct rtc_class_ops cmos_rtc_ops = { /*----------------------------------------------------------------*/ +/* + * All these chips have at least 64 bytes of address space, shared by + * RTC registers and NVRAM. Most of those bytes of NVRAM are used + * by boot firmware. Modern chips have 128 or 256 bytes. + */ + +#define NVRAM_OFFSET (RTC_REG_D + 1) + +static ssize_t +cmos_nvram_read(struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + int retval; + + if (unlikely(off >= attr->size)) + return 0; + if ((off + count) > attr->size) + count = attr->size - off; + + spin_lock_irq(&rtc_lock); + for (retval = 0, off += NVRAM_OFFSET; count--; retval++, off++) + *buf++ = CMOS_READ(off); + spin_unlock_irq(&rtc_lock); + + return retval; +} + +static ssize_t +cmos_nvram_write(struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct cmos_rtc *cmos; + int retval; + + cmos = dev_get_drvdata(container_of(kobj, struct device, kobj)); + if (unlikely(off >= attr->size)) + return -EFBIG; + if ((off + count) > attr->size) + count = attr->size - off; + + /* NOTE: on at least PCs and Ataris, the boot firmware uses a + * checksum on part of the NVRAM data. That's currently ignored + * here. If userspace is smart enough to know what fields of + * NVRAM to update, updating checksums is also part of its job. + */ + spin_lock_irq(&rtc_lock); + for (retval = 0, off += NVRAM_OFFSET; count--; retval++, off++) { + /* don't trash RTC registers */ + if (off == cmos->day_alrm + || off == cmos->mon_alrm + || off == cmos->century) + buf++; + else + CMOS_WRITE(*buf++, off); + } + spin_unlock_irq(&rtc_lock); + + return retval; +} + +static struct bin_attribute nvram = { + .attr = { + .name = "nvram", + .mode = S_IRUGO | S_IWUSR, + .owner = THIS_MODULE, + }, + + .read = cmos_nvram_read, + .write = cmos_nvram_write, + /* size gets set up later */ +}; + +/*----------------------------------------------------------------*/ + static struct cmos_rtc cmos_rtc; static irqreturn_t cmos_interrupt(int irq, void *p) @@ -412,11 +486,9 @@ static irqreturn_t cmos_interrupt(int irq, void *p) } #ifdef CONFIG_PNP -#define is_pnp() 1 #define INITSECTION #else -#define is_pnp() 0 #define INITSECTION __init #endif @@ -426,6 +498,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) struct cmos_rtc_board_info *info = dev->platform_data; int retval = 0; unsigned char rtc_control; + unsigned address_space; /* there can be only one ... */ if (cmos_rtc.dev) @@ -450,15 +523,36 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) cmos_rtc.irq = rtc_irq; cmos_rtc.iomem = ports; + /* Heuristic to deduce NVRAM size ... do what the legacy NVRAM + * driver did, but don't reject unknown configs. Old hardware + * won't address 128 bytes, and for now we ignore the way newer + * chips can address 256 bytes (using two more i/o ports). + */ +#if defined(CONFIG_ATARI) + address_space = 64; +#elif defined(__i386__) || defined(__x86_64__) || defined(__arm__) + address_space = 128; +#else +#warning Assuming 128 bytes of RTC+NVRAM address space, not 64 bytes. + address_space = 128; +#endif + /* For ACPI systems extension info comes from the FADT. On others, * board specific setup provides it as appropriate. Systems where * the alarm IRQ isn't automatically a wakeup IRQ (like ACPI, and * some almost-clones) can provide hooks to make that behave. + * + * Note that ACPI doesn't preclude putting these registers into + * "extended" areas of the chip, including some that we won't yet + * expect CMOS_READ and friends to handle. */ if (info) { - cmos_rtc.day_alrm = info->rtc_day_alarm; - cmos_rtc.mon_alrm = info->rtc_mon_alarm; - cmos_rtc.century = info->rtc_century; + if (info->rtc_day_alarm && info->rtc_day_alarm < 128) + cmos_rtc.day_alrm = info->rtc_day_alarm; + if (info->rtc_mon_alarm && info->rtc_mon_alarm < 128) + cmos_rtc.mon_alrm = info->rtc_mon_alarm; + if (info->rtc_century && info->rtc_century < 128) + cmos_rtc.century = info->rtc_century; if (info->wake_on && info->wake_off) { cmos_rtc.wake_on = info->wake_on; @@ -518,10 +612,13 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) goto cleanup1; } - /* REVISIT optionally make 50 or 114 bytes NVRAM available, - * like rtc-ds1553, rtc-ds1742 ... this will often include - * registers for century, and day/month alarm. - */ + /* export at least the first block of NVRAM */ + nvram.size = address_space - NVRAM_OFFSET; + retval = sysfs_create_bin_file(&dev->kobj, &nvram); + if (retval < 0) { + dev_dbg(dev, "can't create nvram file? %d\n", retval); + goto cleanup2; + } pr_info("%s: alarms up to one %s%s\n", cmos_rtc.rtc->dev.bus_id, @@ -536,6 +633,9 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) return 0; +cleanup2: + if (is_valid_irq(rtc_irq)) + free_irq(rtc_irq, cmos_rtc.rtc); cleanup1: cmos_rtc.dev = NULL; rtc_device_unregister(cmos_rtc.rtc); @@ -563,6 +663,8 @@ static void __exit cmos_do_remove(struct device *dev) cmos_do_shutdown(); + sysfs_remove_bin_file(&dev->kobj, &nvram); + if (is_valid_irq(cmos->irq)) free_irq(cmos->irq, cmos->rtc); @@ -659,9 +761,12 @@ static int cmos_resume(struct device *dev) /*----------------------------------------------------------------*/ -/* The "CMOS" RTC normally lives on the platform_bus. On ACPI systems, - * the device node will always be created as a PNPACPI device. Plus - * pre-ACPI PCs probably list it in the PNPBIOS tables. +/* On non-x86 systems, a "CMOS" RTC lives most naturally on platform_bus. + * ACPI systems always list these as PNPACPI devices, and pre-ACPI PCs + * probably list them in similar PNPBIOS tables; so PNP is more common. + * + * We don't use legacy "poke at the hardware" probing. Ancient PCs that + * predate even PNPBIOS should set up platform_bus devices. */ #ifdef CONFIG_PNP |