diff options
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 29 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 3 | ||||
-rw-r--r-- | drivers/rtc/rtc-cmos.c | 6 | ||||
-rw-r--r-- | drivers/rtc/rtc-ds3232.c | 326 | ||||
-rw-r--r-- | drivers/rtc/rtc-fm3130.c | 181 | ||||
-rw-r--r-- | drivers/rtc/rtc-imxdi.c | 519 | ||||
-rw-r--r-- | drivers/rtc/rtc-isl12022.c | 327 | ||||
-rw-r--r-- | drivers/rtc/rtc-m41t80.c | 4 | ||||
-rw-r--r-- | drivers/rtc/rtc-m48t59.c | 5 | ||||
-rw-r--r-- | drivers/rtc/rtc-m48t86.c | 2 | ||||
-rw-r--r-- | drivers/rtc/rtc-max6900.c | 2 | ||||
-rw-r--r-- | drivers/rtc/rtc-mxc.c | 6 | ||||
-rw-r--r-- | drivers/rtc/rtc-nuc900.c | 64 | ||||
-rw-r--r-- | drivers/rtc/rtc-pcf8563.c | 8 | ||||
-rw-r--r-- | drivers/rtc/rtc-pl031.c | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-pxa.c | 42 | ||||
-rw-r--r-- | drivers/rtc/rtc-rp5c01.c | 89 |
17 files changed, 1484 insertions, 130 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 9238c8f40f03..48ca7132cc05 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -166,6 +166,16 @@ config RTC_DRV_DS1672 This driver can also be built as a module. If so, the module will be called rtc-ds1672. +config RTC_DRV_DS3232 + tristate "Dallas/Maxim DS3232" + depends on RTC_CLASS && I2C + help + If you say yes here you get support for Dallas Semiconductor + DS3232 real-time clock chips. + + This driver can also be built as a module. If so, the module + will be called rtc-ds3232. + config RTC_DRV_MAX6900 tristate "Maxim MAX6900" help @@ -203,6 +213,15 @@ config RTC_DRV_ISL1208 This driver can also be built as a module. If so, the module will be called rtc-isl1208. +config RTC_DRV_ISL12022 + tristate "Intersil ISL12022" + help + If you say yes here you get support for the + Intersil ISL12022 RTC chip. + + This driver can also be built as a module. If so, the module + will be called rtc-isl12022. + config RTC_DRV_X1205 tristate "Xicor/Intersil X1205" help @@ -537,6 +556,16 @@ config RTC_DRV_MSM6242 This driver can also be built as a module. If so, the module will be called rtc-msm6242. +config RTC_DRV_IMXDI + tristate "Freescale IMX DryIce Real Time Clock" + depends on ARCH_MX25 + depends on RTC_CLASS + help + Support for Freescale IMX DryIce RTC + + This driver can also be built as a module, if so, the module + will be called "rtc-imxdi". + config RTC_MXC tristate "Freescale MXC Real Time Clock" depends on ARCH_MXC diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index fedf9bb36593..0f207b3b5833 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -41,12 +41,15 @@ obj-$(CONFIG_RTC_DRV_DS1511) += rtc-ds1511.o obj-$(CONFIG_RTC_DRV_DS1553) += rtc-ds1553.o obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o +obj-$(CONFIG_RTC_DRV_DS3232) += rtc-ds3232.o obj-$(CONFIG_RTC_DRV_DS3234) += rtc-ds3234.o obj-$(CONFIG_RTC_DRV_EFI) += rtc-efi.o obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o +obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o +obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index 11b8ea29d2b7..5856167a0c90 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -970,7 +970,6 @@ static inline int cmos_poweroff(struct device *dev) #include <linux/acpi.h> -#ifdef CONFIG_PM static u32 rtc_handler(void *context) { acpi_clear_event(ACPI_EVENT_RTC); @@ -999,11 +998,6 @@ static void rtc_wake_off(struct device *dev) { acpi_disable_event(ACPI_EVENT_RTC, 0); } -#else -#define rtc_wake_setup() do{}while(0) -#define rtc_wake_on NULL -#define rtc_wake_off NULL -#endif /* Every ACPI platform has a mc146818 compatible "cmos rtc". Here we find * its device node and pass extra config data. This helps its driver use diff --git a/drivers/rtc/rtc-ds3232.c b/drivers/rtc/rtc-ds3232.c new file mode 100644 index 000000000000..9daed8db83d3 --- /dev/null +++ b/drivers/rtc/rtc-ds3232.c @@ -0,0 +1,326 @@ +/* + * RTC client/driver for the Maxim/Dallas DS3232 Real-Time Clock over I2C + * + * Copyright (C) 2009-2010 Freescale Semiconductor. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +/* + * It would be more efficient to use i2c msgs/i2c_transfer directly but, as + * recommened in .../Documentation/i2c/writing-clients section + * "Sending and receiving", using SMBus level communication is preferred. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/rtc.h> +#include <linux/bcd.h> +#include <linux/workqueue.h> +#include <linux/slab.h> + +#define DS3232_REG_SECONDS 0x00 +#define DS3232_REG_MINUTES 0x01 +#define DS3232_REG_HOURS 0x02 +#define DS3232_REG_AMPM 0x02 +#define DS3232_REG_DAY 0x03 +#define DS3232_REG_DATE 0x04 +#define DS3232_REG_MONTH 0x05 +#define DS3232_REG_CENTURY 0x05 +#define DS3232_REG_YEAR 0x06 +#define DS3232_REG_ALARM1 0x07 /* Alarm 1 BASE */ +#define DS3232_REG_ALARM2 0x0B /* Alarm 2 BASE */ +#define DS3232_REG_CR 0x0E /* Control register */ +# define DS3232_REG_CR_nEOSC 0x80 +# define DS3232_REG_CR_INTCN 0x04 +# define DS3232_REG_CR_A2IE 0x02 +# define DS3232_REG_CR_A1IE 0x01 + +#define DS3232_REG_SR 0x0F /* control/status register */ +# define DS3232_REG_SR_OSF 0x80 +# define DS3232_REG_SR_BSY 0x04 +# define DS3232_REG_SR_A2F 0x02 +# define DS3232_REG_SR_A1F 0x01 + +struct ds3232 { + struct i2c_client *client; + struct rtc_device *rtc; + struct work_struct work; + + /* The mutex protects alarm operations, and prevents a race + * between the enable_irq() in the workqueue and the free_irq() + * in the remove function. + */ + struct mutex mutex; + int exiting; +}; + +static struct i2c_driver ds3232_driver; + +static int ds3232_check_rtc_status(struct i2c_client *client) +{ + int ret = 0; + int control, stat; + + stat = i2c_smbus_read_byte_data(client, DS3232_REG_SR); + if (stat < 0) + return stat; + + if (stat & DS3232_REG_SR_OSF) + dev_warn(&client->dev, + "oscillator discontinuity flagged, " + "time unreliable\n"); + + stat &= ~(DS3232_REG_SR_OSF | DS3232_REG_SR_A1F | DS3232_REG_SR_A2F); + + ret = i2c_smbus_write_byte_data(client, DS3232_REG_SR, stat); + if (ret < 0) + return ret; + + /* If the alarm is pending, clear it before requesting + * the interrupt, so an interrupt event isn't reported + * before everything is initialized. + */ + + control = i2c_smbus_read_byte_data(client, DS3232_REG_CR); + if (control < 0) + return control; + + control &= ~(DS3232_REG_CR_A1IE | DS3232_REG_CR_A2IE); + control |= DS3232_REG_CR_INTCN; + + return i2c_smbus_write_byte_data(client, DS3232_REG_CR, control); +} + +static int ds3232_read_time(struct device *dev, struct rtc_time *time) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret; + u8 buf[7]; + unsigned int year, month, day, hour, minute, second; + unsigned int week, twelve_hr, am_pm; + unsigned int century, add_century = 0; + + ret = i2c_smbus_read_i2c_block_data(client, DS3232_REG_SECONDS, 7, buf); + + if (ret < 0) + return ret; + if (ret < 7) + return -EIO; + + second = buf[0]; + minute = buf[1]; + hour = buf[2]; + week = buf[3]; + day = buf[4]; + month = buf[5]; + year = buf[6]; + + /* Extract additional information for AM/PM and century */ + + twelve_hr = hour & 0x40; + am_pm = hour & 0x20; + century = month & 0x80; + + /* Write to rtc_time structure */ + + time->tm_sec = bcd2bin(second); + time->tm_min = bcd2bin(minute); + if (twelve_hr) { + /* Convert to 24 hr */ + if (am_pm) + time->tm_hour = bcd2bin(hour & 0x1F) + 12; + else + time->tm_hour = bcd2bin(hour & 0x1F); + } else { + time->tm_hour = bcd2bin(hour); + } + + time->tm_wday = bcd2bin(week); + time->tm_mday = bcd2bin(day); + time->tm_mon = bcd2bin(month & 0x7F); + if (century) + add_century = 100; + + time->tm_year = bcd2bin(year) + add_century; + + return rtc_valid_tm(time); +} + +static int ds3232_set_time(struct device *dev, struct rtc_time *time) +{ + struct i2c_client *client = to_i2c_client(dev); + u8 buf[7]; + + /* Extract time from rtc_time and load into ds3232*/ + + buf[0] = bin2bcd(time->tm_sec); + buf[1] = bin2bcd(time->tm_min); + buf[2] = bin2bcd(time->tm_hour); + buf[3] = bin2bcd(time->tm_wday); /* Day of the week */ + buf[4] = bin2bcd(time->tm_mday); /* Date */ + buf[5] = bin2bcd(time->tm_mon); + if (time->tm_year >= 100) { + buf[5] |= 0x80; + buf[6] = bin2bcd(time->tm_year - 100); + } else { + buf[6] = bin2bcd(time->tm_year); + } + + return i2c_smbus_write_i2c_block_data(client, + DS3232_REG_SECONDS, 7, buf); +} + +static irqreturn_t ds3232_irq(int irq, void *dev_id) +{ + struct i2c_client *client = dev_id; + struct ds3232 *ds3232 = i2c_get_clientdata(client); + + disable_irq_nosync(irq); + schedule_work(&ds3232->work); + return IRQ_HANDLED; +} + +static void ds3232_work(struct work_struct *work) +{ + struct ds3232 *ds3232 = container_of(work, struct ds3232, work); + struct i2c_client *client = ds3232->client; + int stat, control; + + mutex_lock(&ds3232->mutex); + + stat = i2c_smbus_read_byte_data(client, DS3232_REG_SR); + if (stat < 0) + goto unlock; + + if (stat & DS3232_REG_SR_A1F) { + control = i2c_smbus_read_byte_data(client, DS3232_REG_CR); + if (control < 0) + goto out; + /* disable alarm1 interrupt */ + control &= ~(DS3232_REG_CR_A1IE); + i2c_smbus_write_byte_data(client, DS3232_REG_CR, control); + + /* clear the alarm pend flag */ + stat &= ~DS3232_REG_SR_A1F; + i2c_smbus_write_byte_data(client, DS3232_REG_SR, stat); + + rtc_update_irq(ds3232->rtc, 1, RTC_AF | RTC_IRQF); + } + +out: + if (!ds3232->exiting) + enable_irq(client->irq); +unlock: + mutex_unlock(&ds3232->mutex); +} + +static const struct rtc_class_ops ds3232_rtc_ops = { + .read_time = ds3232_read_time, + .set_time = ds3232_set_time, +}; + +static int __devinit ds3232_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ds3232 *ds3232; + int ret; + + ds3232 = kzalloc(sizeof(struct ds3232), GFP_KERNEL); + if (!ds3232) + return -ENOMEM; + + ds3232->client = client; + i2c_set_clientdata(client, ds3232); + + INIT_WORK(&ds3232->work, ds3232_work); + mutex_init(&ds3232->mutex); + + ret = ds3232_check_rtc_status(client); + if (ret) + goto out_free; + + ds3232->rtc = rtc_device_register(client->name, &client->dev, + &ds3232_rtc_ops, THIS_MODULE); + if (IS_ERR(ds3232->rtc)) { + ret = PTR_ERR(ds3232->rtc); + dev_err(&client->dev, "unable to register the class device\n"); + goto out_irq; + } + + if (client->irq >= 0) { + ret = request_irq(client->irq, ds3232_irq, 0, + "ds3232", client); + if (ret) { + dev_err(&client->dev, "unable to request IRQ\n"); + goto out_free; + } + } + + return 0; + +out_irq: + if (client->irq >= 0) + free_irq(client->irq, client); + +out_free: + i2c_set_clientdata(client, NULL); + kfree(ds3232); + return ret; +} + +static int __devexit ds3232_remove(struct i2c_client *client) +{ + struct ds3232 *ds3232 = i2c_get_clientdata(client); + + if (client->irq >= 0) { + mutex_lock(&ds3232->mutex); + ds3232->exiting = 1; + mutex_unlock(&ds3232->mutex); + + free_irq(client->irq, client); + flush_scheduled_work(); + } + + rtc_device_unregister(ds3232->rtc); + i2c_set_clientdata(client, NULL); + kfree(ds3232); + return 0; +} + +static const struct i2c_device_id ds3232_id[] = { + { "ds3232", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ds3232_id); + +static struct i2c_driver ds3232_driver = { + .driver = { + .name = "rtc-ds3232", + .owner = THIS_MODULE, + }, + .probe = ds3232_probe, + .remove = __devexit_p(ds3232_remove), + .id_table = ds3232_id, +}; + +static int __init ds3232_init(void) +{ + return i2c_add_driver(&ds3232_driver); +} + +static void __exit ds3232_exit(void) +{ + i2c_del_driver(&ds3232_driver); +} + +module_init(ds3232_init); +module_exit(ds3232_exit); + +MODULE_AUTHOR("Srikanth Srinivasan <srikanth.srinivasan@freescale.com>"); +MODULE_DESCRIPTION("Maxim/Dallas DS3232 RTC Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-fm3130.c b/drivers/rtc/rtc-fm3130.c index e4de8f37ae4a..4cf2e70c5078 100644 --- a/drivers/rtc/rtc-fm3130.c +++ b/drivers/rtc/rtc-fm3130.c @@ -52,8 +52,8 @@ struct fm3130 { struct i2c_msg msg[4]; struct i2c_client *client; struct rtc_device *rtc; + int alarm_valid; int data_valid; - int alarm; }; static const struct i2c_device_id fm3130_id[] = { { "fm3130", 0 }, @@ -87,11 +87,7 @@ static void fm3130_rtc_mode(struct device *dev, int mode) dev_dbg(dev, "invalid mode %d\n", mode); break; } - /* Checking for alarm */ - if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AF) { - fm3130->alarm = 1; - fm3130->regs[FM3130_RTC_CONTROL] &= ~FM3130_RTC_CONTROL_BIT_AF; - } + i2c_smbus_write_byte_data(fm3130->client, FM3130_RTC_CONTROL, fm3130->regs[FM3130_RTC_CONTROL]); } @@ -208,6 +204,17 @@ static int fm3130_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) struct fm3130 *fm3130 = dev_get_drvdata(dev); int tmp; struct rtc_time *tm = &alrm->time; + + if (!fm3130->alarm_valid) { + /* + * We have invalid alarm in RTC, probably due to battery faults + * or other problems. Return EIO for now, it will allow us to + * set alarm value later instead of error during probing which + * disables device + */ + return -EIO; + } + /* read the RTC alarm registers all at once */ tmp = i2c_transfer(to_i2c_adapter(fm3130->client->dev.parent), &fm3130->msg[2], 2); @@ -222,20 +229,31 @@ static int fm3130_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) fm3130->regs[FM3130_ALARM_DATE], fm3130->regs[FM3130_ALARM_MONTHS]); - tm->tm_sec = bcd2bin(fm3130->regs[FM3130_ALARM_SECONDS] & 0x7F); tm->tm_min = bcd2bin(fm3130->regs[FM3130_ALARM_MINUTES] & 0x7F); tm->tm_hour = bcd2bin(fm3130->regs[FM3130_ALARM_HOURS] & 0x3F); tm->tm_mday = bcd2bin(fm3130->regs[FM3130_ALARM_DATE] & 0x3F); tm->tm_mon = bcd2bin(fm3130->regs[FM3130_ALARM_MONTHS] & 0x1F); + if (tm->tm_mon > 0) tm->tm_mon -= 1; /* RTC is 1-12, tm_mon is 0-11 */ + dev_dbg(dev, "%s secs=%d, mins=%d, " "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", "read alarm", tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday); + /* check if alarm enabled */ + fm3130->regs[FM3130_RTC_CONTROL] = + i2c_smbus_read_byte_data(fm3130->client, FM3130_RTC_CONTROL); + + if ((fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AEN) && + (~fm3130->regs[FM3130_RTC_CONTROL] & + FM3130_RTC_CONTROL_BIT_CAL)) { + alrm->enabled = 1; + } + return 0; } @@ -251,25 +269,20 @@ static int fm3130_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) tm->tm_hour, tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday); - if (tm->tm_sec != -1) - fm3130->regs[FM3130_ALARM_SECONDS] = - bin2bcd(tm->tm_sec) | 0x80; + fm3130->regs[FM3130_ALARM_SECONDS] = + (tm->tm_sec != -1) ? bin2bcd(tm->tm_sec) : 0x80; - if (tm->tm_min != -1) - fm3130->regs[FM3130_ALARM_MINUTES] = - bin2bcd(tm->tm_min) | 0x80; + fm3130->regs[FM3130_ALARM_MINUTES] = + (tm->tm_min != -1) ? bin2bcd(tm->tm_min) : 0x80; - if (tm->tm_hour != -1) - fm3130->regs[FM3130_ALARM_HOURS] = - bin2bcd(tm->tm_hour) | 0x80; + fm3130->regs[FM3130_ALARM_HOURS] = + (tm->tm_hour != -1) ? bin2bcd(tm->tm_hour) : 0x80; - if (tm->tm_mday != -1) - fm3130->regs[FM3130_ALARM_DATE] = - bin2bcd(tm->tm_mday) | 0x80; + fm3130->regs[FM3130_ALARM_DATE] = + (tm->tm_mday != -1) ? bin2bcd(tm->tm_mday) : 0x80; - if (tm->tm_mon != -1) - fm3130->regs[FM3130_ALARM_MONTHS] = - bin2bcd(tm->tm_mon + 1) | 0x80; + fm3130->regs[FM3130_ALARM_MONTHS] = + (tm->tm_mon != -1) ? bin2bcd(tm->tm_mon + 1) : 0x80; dev_dbg(dev, "alarm write %02x %02x %02x %02x %02x\n", fm3130->regs[FM3130_ALARM_SECONDS], @@ -285,11 +298,8 @@ static int fm3130_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) } fm3130->regs[FM3130_RTC_CONTROL] = i2c_smbus_read_byte_data(fm3130->client, FM3130_RTC_CONTROL); - /* Checking for alarm */ - if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AF) { - fm3130->alarm = 1; - fm3130->regs[FM3130_RTC_CONTROL] &= ~FM3130_RTC_CONTROL_BIT_AF; - } + + /* enable or disable alarm */ if (alrm->enabled) { i2c_smbus_write_byte_data(fm3130->client, FM3130_RTC_CONTROL, (fm3130->regs[FM3130_RTC_CONTROL] & @@ -298,16 +308,55 @@ static int fm3130_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) } else { i2c_smbus_write_byte_data(fm3130->client, FM3130_RTC_CONTROL, fm3130->regs[FM3130_RTC_CONTROL] & - ~(FM3130_RTC_CONTROL_BIT_AEN)); + ~(FM3130_RTC_CONTROL_BIT_CAL) & + ~(FM3130_RTC_CONTROL_BIT_AEN)); } + + /* We assume here that data is valid once written */ + if (!fm3130->alarm_valid) + fm3130->alarm_valid = 1; + return 0; } +static int fm3130_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct fm3130 *fm3130 = dev_get_drvdata(dev); + int ret = 0; + + fm3130->regs[FM3130_RTC_CONTROL] = + i2c_smbus_read_byte_data(fm3130->client, FM3130_RTC_CONTROL); + + dev_dbg(dev, "alarm_irq_enable: enable=%d, FM3130_RTC_CONTROL=%02x\n", + enabled, fm3130->regs[FM3130_RTC_CONTROL]); + + switch (enabled) { + case 0: /* alarm off */ + ret = i2c_smbus_write_byte_data(fm3130->client, + FM3130_RTC_CONTROL, fm3130->regs[FM3130_RTC_CONTROL] & + ~(FM3130_RTC_CONTROL_BIT_CAL) & + ~(FM3130_RTC_CONTROL_BIT_AEN)); + break; + case 1: /* alarm on */ + ret = i2c_smbus_write_byte_data(fm3130->client, + FM3130_RTC_CONTROL, (fm3130->regs[FM3130_RTC_CONTROL] & + ~(FM3130_RTC_CONTROL_BIT_CAL)) | + FM3130_RTC_CONTROL_BIT_AEN); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + static const struct rtc_class_ops fm3130_rtc_ops = { .read_time = fm3130_get_time, .set_time = fm3130_set_time, .read_alarm = fm3130_read_alarm, .set_alarm = fm3130_set_alarm, + .alarm_irq_enable = fm3130_alarm_irq_enable, }; static struct i2c_driver fm3130_driver; @@ -356,6 +405,7 @@ static int __devinit fm3130_probe(struct i2c_client *client, fm3130->msg[3].len = FM3130_ALARM_REGS; fm3130->msg[3].buf = &fm3130->regs[FM3130_ALARM_SECONDS]; + fm3130->alarm_valid = 0; fm3130->data_valid = 0; tmp = i2c_transfer(adapter, fm3130->msg, 4); @@ -370,12 +420,6 @@ static int __devinit fm3130_probe(struct i2c_client *client, fm3130->regs[FM3130_CAL_CONTROL] = i2c_smbus_read_byte_data(client, FM3130_CAL_CONTROL); - /* Checking for alarm */ - if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AF) { - fm3130->alarm = 1; - fm3130->regs[FM3130_RTC_CONTROL] &= ~FM3130_RTC_CONTROL_BIT_AF; - } - /* Disabling calibration mode */ if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_CAL) { i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL, @@ -400,44 +444,79 @@ static int __devinit fm3130_probe(struct i2c_client *client, fm3130->regs[FM3130_CAL_CONTROL] & ~(FM3130_CAL_CONTROL_BIT_nOSCEN)); - /* oscillator fault? clear flag, and warn */ - if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_LB) + /* low battery? clear flag, and warn */ + if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_LB) { + i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL, + fm3130->regs[FM3130_RTC_CONTROL] & + ~(FM3130_RTC_CONTROL_BIT_LB)); dev_warn(&client->dev, "Low battery!\n"); + } - /* oscillator fault? clear flag, and warn */ + /* check if Power On Reset bit is set */ if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_POR) { i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL, fm3130->regs[FM3130_RTC_CONTROL] & ~FM3130_RTC_CONTROL_BIT_POR); - dev_warn(&client->dev, "SET TIME!\n"); + dev_dbg(&client->dev, "POR bit is set\n"); } /* ACS is controlled by alarm */ i2c_smbus_write_byte_data(client, FM3130_ALARM_WP_CONTROL, 0x80); - /* TODO */ - /* TODO need to sanity check alarm */ - tmp = fm3130->regs[FM3130_RTC_SECONDS]; - tmp = bcd2bin(tmp & 0x7f); - if (tmp > 60) - goto exit_bad; + /* alarm registers sanity check */ + tmp = bcd2bin(fm3130->regs[FM3130_RTC_SECONDS] & 0x7f); + if (tmp > 59) + goto bad_alarm; + tmp = bcd2bin(fm3130->regs[FM3130_RTC_MINUTES] & 0x7f); - if (tmp > 60) - goto exit_bad; + if (tmp > 59) + goto bad_alarm; + + tmp = bcd2bin(fm3130->regs[FM3130_RTC_HOURS] & 0x3f); + if (tmp > 23) + goto bad_alarm; tmp = bcd2bin(fm3130->regs[FM3130_RTC_DATE] & 0x3f); if (tmp == 0 || tmp > 31) - goto exit_bad; + goto bad_alarm; tmp = bcd2bin(fm3130->regs[FM3130_RTC_MONTHS] & 0x1f); if (tmp == 0 || tmp > 12) - goto exit_bad; + goto bad_alarm; - tmp = fm3130->regs[FM3130_RTC_HOURS]; + fm3130->alarm_valid = 1; + +bad_alarm: + + /* clock registers sanity chek */ + tmp = bcd2bin(fm3130->regs[FM3130_RTC_SECONDS] & 0x7f); + if (tmp > 59) + goto bad_clock; + + tmp = bcd2bin(fm3130->regs[FM3130_RTC_MINUTES] & 0x7f); + if (tmp > 59) + goto bad_clock; + + tmp = bcd2bin(fm3130->regs[FM3130_RTC_HOURS] & 0x3f); + if (tmp > 23) + goto bad_clock; + + tmp = bcd2bin(fm3130->regs[FM3130_RTC_DAY] & 0x7); + if (tmp == 0 || tmp > 7) + goto bad_clock; + + tmp = bcd2bin(fm3130->regs[FM3130_RTC_DATE] & 0x3f); + if (tmp == 0 || tmp > 31) + goto bad_clock; + + tmp = bcd2bin(fm3130->regs[FM3130_RTC_MONTHS] & 0x1f); + if (tmp == 0 || tmp > 12) + goto bad_clock; fm3130->data_valid = 1; -exit_bad: - if (!fm3130->data_valid) +bad_clock: + + if (!fm3130->data_valid || !fm3130->alarm_valid) dev_dbg(&client->dev, "%s: %02x %02x %02x %02x %02x %02x %02x %02x" "%02x %02x %02x %02x %02x %02x %02x\n", diff --git a/drivers/rtc/rtc-imxdi.c b/drivers/rtc/rtc-imxdi.c new file mode 100644 index 000000000000..2dd3c0163272 --- /dev/null +++ b/drivers/rtc/rtc-imxdi.c @@ -0,0 +1,519 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2010 Orex Computed Radiography + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* based on rtc-mc13892.c */ + +/* + * This driver uses the 47-bit 32 kHz counter in the Freescale DryIce block + * to implement a Linux RTC. Times and alarms are truncated to seconds. + * Since the RTC framework performs API locking via rtc->ops_lock the + * only simultaneous accesses we need to deal with is updating DryIce + * registers while servicing an alarm. + * + * Note that reading the DSR (DryIce Status Register) automatically clears + * the WCF (Write Complete Flag). All DryIce writes are synchronized to the + * LP (Low Power) domain and set the WCF upon completion. Writes to the + * DIER (DryIce Interrupt Enable Register) are the only exception. These + * occur at normal bus speeds and do not set WCF. Periodic interrupts are + * not supported by the hardware. + */ + +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/workqueue.h> + +/* DryIce Register Definitions */ + +#define DTCMR 0x00 /* Time Counter MSB Reg */ +#define DTCLR 0x04 /* Time Counter LSB Reg */ + +#define DCAMR 0x08 /* Clock Alarm MSB Reg */ +#define DCALR 0x0c /* Clock Alarm LSB Reg */ +#define DCAMR_UNSET 0xFFFFFFFF /* doomsday - 1 sec */ + +#define DCR 0x10 /* Control Reg */ +#define DCR_TCE (1 << 3) /* Time Counter Enable */ + +#define DSR 0x14 /* Status Reg */ +#define DSR_WBF (1 << 10) /* Write Busy Flag */ +#define DSR_WNF (1 << 9) /* Write Next Flag */ +#define DSR_WCF (1 << 8) /* Write Complete Flag */ +#define DSR_WEF (1 << 7) /* Write Error Flag */ +#define DSR_CAF (1 << 4) /* Clock Alarm Flag */ +#define DSR_NVF (1 << 1) /* Non-Valid Flag */ +#define DSR_SVF (1 << 0) /* Security Violation Flag */ + +#define DIER 0x18 /* Interrupt Enable Reg */ +#define DIER_WNIE (1 << 9) /* Write Next Interrupt Enable */ +#define DIER_WCIE (1 << 8) /* Write Complete Interrupt Enable */ +#define DIER_WEIE (1 << 7) /* Write Error Interrupt Enable */ +#define DIER_CAIE (1 << 4) /* Clock Alarm Interrupt Enable */ + +/** + * struct imxdi_dev - private imxdi rtc data + * @pdev: pionter to platform dev + * @rtc: pointer to rtc struct + * @ioaddr: IO registers pointer + * @irq: dryice normal interrupt + * @clk: input reference clock + * @dsr: copy of the DSR register + * @irq_lock: interrupt enable register (DIER) lock + * @write_wait: registers write complete queue + * @write_mutex: serialize registers write + * @work: schedule alarm work + */ +struct imxdi_dev { + struct platform_device *pdev; + struct rtc_device *rtc; + void __iomem *ioaddr; + int irq; + struct clk *clk; + u32 dsr; + spinlock_t irq_lock; + wait_queue_head_t write_wait; + struct mutex write_mutex; + struct work_struct work; +}; + +/* + * enable a dryice interrupt + */ +static void di_int_enable(struct imxdi_dev *imxdi, u32 intr) +{ + unsigned long flags; + + spin_lock_irqsave(&imxdi->irq_lock, flags); + __raw_writel(__raw_readl(imxdi->ioaddr + DIER) | intr, + imxdi->ioaddr + DIER); + spin_unlock_irqrestore(&imxdi->irq_lock, flags); +} + +/* + * disable a dryice interrupt + */ +static void di_int_disable(struct imxdi_dev *imxdi, u32 intr) +{ + unsigned long flags; + + spin_lock_irqsave(&imxdi->irq_lock, flags); + __raw_writel(__raw_readl(imxdi->ioaddr + DIER) & ~intr, + imxdi->ioaddr + DIER); + spin_unlock_irqrestore(&imxdi->irq_lock, flags); +} + +/* + * This function attempts to clear the dryice write-error flag. + * + * A dryice write error is similar to a bus fault and should not occur in + * normal operation. Clearing the flag requires another write, so the root + * cause of the problem may need to be fixed before the flag can be cleared. + */ +static void clear_write_error(struct imxdi_dev *imxdi) +{ + int cnt; + + dev_warn(&imxdi->pdev->dev, "WARNING: Register write error!\n"); + + /* clear the write error flag */ + __raw_writel(DSR_WEF, imxdi->ioaddr + DSR); + + /* wait for it to take effect */ + for (cnt = 0; cnt < 1000; cnt++) { + if ((__raw_readl(imxdi->ioaddr + DSR) & DSR_WEF) == 0) + return; + udelay(10); + } + dev_err(&imxdi->pdev->dev, + "ERROR: Cannot clear write-error flag!\n"); +} + +/* + * Write a dryice register and wait until it completes. + * + * This function uses interrupts to determine when the + * write has completed. + */ +static int di_write_wait(struct imxdi_dev *imxdi, u32 val, int reg) +{ + int ret; + int rc = 0; + + /* serialize register writes */ + mutex_lock(&imxdi->write_mutex); + + /* enable the write-complete interrupt */ + di_int_enable(imxdi, DIER_WCIE); + + imxdi->dsr = 0; + + /* do the register write */ + __raw_writel(val, imxdi->ioaddr + reg); + + /* wait for the write to finish */ + ret = wait_event_interruptible_timeout(imxdi->write_wait, + imxdi->dsr & (DSR_WCF | DSR_WEF), msecs_to_jiffies(1)); + if (ret < 0) { + rc = ret; + goto out; + } else if (ret == 0) { + dev_warn(&imxdi->pdev->dev, + "Write-wait timeout " + "val = 0x%08x reg = 0x%08x\n", val, reg); + } + + /* check for write error */ + if (imxdi->dsr & DSR_WEF) { + clear_write_error(imxdi); + rc = -EIO; + } + +out: + mutex_unlock(&imxdi->write_mutex); + + return rc; +} + +/* + * read the seconds portion of the current time from the dryice time counter + */ +static int dryice_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct imxdi_dev *imxdi = dev_get_drvdata(dev); + unsigned long now; + + now = __raw_readl(imxdi->ioaddr + DTCMR); + rtc_time_to_tm(now, tm); + + return 0; +} + +/* + * set the seconds portion of dryice time counter and clear the + * fractional part. + */ +static int dryice_rtc_set_mmss(struct device *dev, unsigned long secs) +{ + struct imxdi_dev *imxdi = dev_get_drvdata(dev); + int rc; + + /* zero the fractional part first */ + rc = di_write_wait(imxdi, 0, DTCLR); + if (rc == 0) + rc = di_write_wait(imxdi, secs, DTCMR); + + return rc; +} + +static int dryice_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct imxdi_dev *imxdi = dev_get_drvdata(dev); + + if (enabled) + di_int_enable(imxdi, DIER_CAIE); + else + di_int_disable(imxdi, DIER_CAIE); + + return 0; +} + +/* + * read the seconds portion of the alarm register. + * the fractional part of the alarm register is always zero. + */ +static int dryice_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct imxdi_dev *imxdi = dev_get_drvdata(dev); + u32 dcamr; + + dcamr = __raw_readl(imxdi->ioaddr + DCAMR); + rtc_time_to_tm(dcamr, &alarm->time); + + /* alarm is enabled if the interrupt is enabled */ + alarm->enabled = (__raw_readl(imxdi->ioaddr + DIER) & DIER_CAIE) != 0; + + /* don't allow the DSR read to mess up DSR_WCF */ + mutex_lock(&imxdi->write_mutex); + + /* alarm is pending if the alarm flag is set */ + alarm->pending = (__raw_readl(imxdi->ioaddr + DSR) & DSR_CAF) != 0; + + mutex_unlock(&imxdi->write_mutex); + + return 0; +} + +/* + * set the seconds portion of dryice alarm register + */ +static int dryice_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct imxdi_dev *imxdi = dev_get_drvdata(dev); + unsigned long now; + unsigned long alarm_time; + int rc; + + rc = rtc_tm_to_time(&alarm->time, &alarm_time); + if (rc) + return rc; + + /* don't allow setting alarm in the past */ + now = __raw_readl(imxdi->ioaddr + DTCMR); + if (alarm_time < now) + return -EINVAL; + + /* write the new alarm time */ + rc = di_write_wait(imxdi, (u32)alarm_time, DCAMR); + if (rc) + return rc; + + if (alarm->enabled) + di_int_enable(imxdi, DIER_CAIE); /* enable alarm intr */ + else + di_int_disable(imxdi, DIER_CAIE); /* disable alarm intr */ + + return 0; +} + +static struct rtc_class_ops dryice_rtc_ops = { + .read_time = dryice_rtc_read_time, + .set_mmss = dryice_rtc_set_mmss, + .alarm_irq_enable = dryice_rtc_alarm_irq_enable, + .read_alarm = dryice_rtc_read_alarm, + .set_alarm = dryice_rtc_set_alarm, +}; + +/* + * dryice "normal" interrupt handler + */ +static irqreturn_t dryice_norm_irq(int irq, void *dev_id) +{ + struct imxdi_dev *imxdi = dev_id; + u32 dsr, dier; + irqreturn_t rc = IRQ_NONE; + + dier = __raw_readl(imxdi->ioaddr + DIER); + + /* handle write complete and write error cases */ + if ((dier & DIER_WCIE)) { + /*If the write wait queue is empty then there is no pending + operations. It means the interrupt is for DryIce -Security. + IRQ must be returned as none.*/ + if (list_empty_careful(&imxdi->write_wait.task_list)) + return rc; + + /* DSR_WCF clears itself on DSR read */ + dsr = __raw_readl(imxdi->ioaddr + DSR); + if ((dsr & (DSR_WCF | DSR_WEF))) { + /* mask the interrupt */ + di_int_disable(imxdi, DIER_WCIE); + + /* save the dsr value for the wait queue */ + imxdi->dsr |= dsr; + + wake_up_interruptible(&imxdi->write_wait); + rc = IRQ_HANDLED; + } + } + + /* handle the alarm case */ + if ((dier & DIER_CAIE)) { + /* DSR_WCF clears itself on DSR read */ + dsr = __raw_readl(imxdi->ioaddr + DSR); + if (dsr & DSR_CAF) { + /* mask the interrupt */ + di_int_disable(imxdi, DIER_CAIE); + + /* finish alarm in user context */ + schedule_work(&imxdi->work); + rc = IRQ_HANDLED; + } + } + return rc; +} + +/* + * post the alarm event from user context so it can sleep + * on the write completion. + */ +static void dryice_work(struct work_struct *work) +{ + struct imxdi_dev *imxdi = container_of(work, + struct imxdi_dev, work); + + /* dismiss the interrupt (ignore error) */ + di_write_wait(imxdi, DSR_CAF, DSR); + + /* pass the alarm event to the rtc framework. */ + rtc_update_irq(imxdi->rtc, 1, RTC_AF | RTC_IRQF); +} + +/* + * probe for dryice rtc device + */ +static int dryice_rtc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct imxdi_dev *imxdi; + int rc; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + imxdi = devm_kzalloc(&pdev->dev, sizeof(*imxdi), GFP_KERNEL); + if (!imxdi) + return -ENOMEM; + + imxdi->pdev = pdev; + + if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), + pdev->name)) + return -EBUSY; + + imxdi->ioaddr = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (imxdi->ioaddr == NULL) + return -ENOMEM; + + imxdi->irq = platform_get_irq(pdev, 0); + if (imxdi->irq < 0) + return imxdi->irq; + + init_waitqueue_head(&imxdi->write_wait); + + INIT_WORK(&imxdi->work, dryice_work); + + mutex_init(&imxdi->write_mutex); + + imxdi->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(imxdi->clk)) + return PTR_ERR(imxdi->clk); + clk_enable(imxdi->clk); + + /* + * Initialize dryice hardware + */ + + /* mask all interrupts */ + __raw_writel(0, imxdi->ioaddr + DIER); + + rc = devm_request_irq(&pdev->dev, imxdi->irq, dryice_norm_irq, + IRQF_SHARED, pdev->name, imxdi); + if (rc) { + dev_warn(&pdev->dev, "interrupt not available.\n"); + goto err; + } + + /* put dryice into valid state */ + if (__raw_readl(imxdi->ioaddr + DSR) & DSR_NVF) { + rc = di_write_wait(imxdi, DSR_NVF | DSR_SVF, DSR); + if (rc) + goto err; + } + + /* initialize alarm */ + rc = di_write_wait(imxdi, DCAMR_UNSET, DCAMR); + if (rc) + goto err; + rc = di_write_wait(imxdi, 0, DCALR); + if (rc) + goto err; + + /* clear alarm flag */ + if (__raw_readl(imxdi->ioaddr + DSR) & DSR_CAF) { + rc = di_write_wait(imxdi, DSR_CAF, DSR); + if (rc) + goto err; + } + + /* the timer won't count if it has never been written to */ + if (__raw_readl(imxdi->ioaddr + DTCMR) == 0) { + rc = di_write_wait(imxdi, 0, DTCMR); + if (rc) + goto err; + } + + /* start keeping time */ + if (!(__raw_readl(imxdi->ioaddr + DCR) & DCR_TCE)) { + rc = di_write_wait(imxdi, + __raw_readl(imxdi->ioaddr + DCR) | DCR_TCE, + DCR); + if (rc) + goto err; + } + + platform_set_drvdata(pdev, imxdi); + imxdi->rtc = rtc_device_register(pdev->name, &pdev->dev, + &dryice_rtc_ops, THIS_MODULE); + if (IS_ERR(imxdi->rtc)) { + rc = PTR_ERR(imxdi->rtc); + goto err; + } + + return 0; + +err: + clk_disable(imxdi->clk); + clk_put(imxdi->clk); + + return rc; +} + +static int __devexit dryice_rtc_remove(struct platform_device *pdev) +{ + struct imxdi_dev *imxdi = platform_get_drvdata(pdev); + + flush_work(&imxdi->work); + + /* mask all interrupts */ + __raw_writel(0, imxdi->ioaddr + DIER); + + rtc_device_unregister(imxdi->rtc); + + clk_disable(imxdi->clk); + clk_put(imxdi->clk); + + return 0; +} + +static struct platform_driver dryice_rtc_driver = { + .driver = { + .name = "imxdi_rtc", + .owner = THIS_MODULE, + }, + .remove = __devexit_p(dryice_rtc_remove), +}; + +static int __init dryice_rtc_init(void) +{ + return platform_driver_probe(&dryice_rtc_driver, dryice_rtc_probe); +} + +static void __exit dryice_rtc_exit(void) +{ + platform_driver_unregister(&dryice_rtc_driver); +} + +module_init(dryice_rtc_init); +module_exit(dryice_rtc_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>"); +MODULE_DESCRIPTION("IMX DryIce Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-isl12022.c b/drivers/rtc/rtc-isl12022.c new file mode 100644 index 000000000000..ddbc797ea6cd --- /dev/null +++ b/drivers/rtc/rtc-isl12022.c @@ -0,0 +1,327 @@ +/* + * An I2C driver for the Intersil ISL 12022 + * + * Author: Roman Fietze <roman.fietze@telemotive.de> + * + * Based on the Philips PCF8563 RTC + * by Alessandro Zummo <a.zummo@towertech.it>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + */ + +#include <linux/i2c.h> +#include <linux/bcd.h> +#include <linux/rtc.h> +#include <linux/slab.h> + +#define DRV_VERSION "0.1" + +/* ISL register offsets */ +#define ISL12022_REG_SC 0x00 +#define ISL12022_REG_MN 0x01 +#define ISL12022_REG_HR 0x02 +#define ISL12022_REG_DT 0x03 +#define ISL12022_REG_MO 0x04 +#define ISL12022_REG_YR 0x05 +#define ISL12022_REG_DW 0x06 + +#define ISL12022_REG_SR 0x07 +#define ISL12022_REG_INT 0x08 + +/* ISL register bits */ +#define ISL12022_HR_MIL (1 << 7) /* military or 24 hour time */ + +#define ISL12022_SR_LBAT85 (1 << 2) +#define ISL12022_SR_LBAT75 (1 << 1) + +#define ISL12022_INT_WRTC (1 << 6) + + +static struct i2c_driver isl12022_driver; + +struct isl12022 { + struct rtc_device *rtc; + + bool write_enabled; /* true if write enable is set */ +}; + + +static int isl12022_read_regs(struct i2c_client *client, uint8_t reg, + uint8_t *data, size_t n) +{ + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = data + }, /* setup read ptr */ + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = n, + .buf = data + } + }; + + int ret; + + data[0] = reg; + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) { + dev_err(&client->dev, "%s: read error, ret=%d\n", + __func__, ret); + return -EIO; + } + + return 0; +} + + +static int isl12022_write_reg(struct i2c_client *client, + uint8_t reg, uint8_t val) +{ + uint8_t data[2] = { reg, val }; + int err; + + err = i2c_master_send(client, data, sizeof(data)); + if (err != sizeof(data)) { + dev_err(&client->dev, + "%s: err=%d addr=%02x, data=%02x\n", + __func__, err, data[0], data[1]); + return -EIO; + } + + return 0; +} + + +/* + * In the routines that deal directly with the isl12022 hardware, we use + * rtc_time -- month 0-11, hour 0-23, yr = calendar year-epoch. + */ +static int isl12022_get_datetime(struct i2c_client *client, struct rtc_time *tm) +{ + uint8_t buf[ISL12022_REG_INT + 1]; + int ret; + + ret = isl12022_read_regs(client, ISL12022_REG_SC, buf, sizeof(buf)); + if (ret) + return ret; + + if (buf[ISL12022_REG_SR] & (ISL12022_SR_LBAT85 | ISL12022_SR_LBAT75)) { + dev_warn(&client->dev, + "voltage dropped below %u%%, " + "date and time is not reliable.\n", + buf[ISL12022_REG_SR] & ISL12022_SR_LBAT85 ? 85 : 75); + } + + dev_dbg(&client->dev, + "%s: raw data is sec=%02x, min=%02x, hr=%02x, " + "mday=%02x, mon=%02x, year=%02x, wday=%02x, " + "sr=%02x, int=%02x", + __func__, + buf[ISL12022_REG_SC], + buf[ISL12022_REG_MN], + buf[ISL12022_REG_HR], + buf[ISL12022_REG_DT], + buf[ISL12022_REG_MO], + buf[ISL12022_REG_YR], + buf[ISL12022_REG_DW], + buf[ISL12022_REG_SR], + buf[ISL12022_REG_INT]); + + tm->tm_sec = bcd2bin(buf[ISL12022_REG_SC] & 0x7F); + tm->tm_min = bcd2bin(buf[ISL12022_REG_MN] & 0x7F); + tm->tm_hour = bcd2bin(buf[ISL12022_REG_HR] & 0x3F); + tm->tm_mday = bcd2bin(buf[ISL12022_REG_DT] & 0x3F); + tm->tm_wday = buf[ISL12022_REG_DW] & 0x07; + tm->tm_mon = bcd2bin(buf[ISL12022_REG_MO] & 0x1F) - 1; + tm->tm_year = bcd2bin(buf[ISL12022_REG_YR]) + 100; + + dev_dbg(&client->dev, "%s: secs=%d, mins=%d, hours=%d, " + "mday=%d, mon=%d, year=%d, wday=%d\n", + __func__, + tm->tm_sec, tm->tm_min, tm->tm_hour, + tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday); + + /* The clock can give out invalid datetime, but we cannot return + * -EINVAL otherwise hwclock will refuse to set the time on bootup. */ + if (rtc_valid_tm(tm) < 0) + dev_err(&client->dev, "retrieved date and time is invalid.\n"); + + return 0; +} + +static int isl12022_set_datetime(struct i2c_client *client, struct rtc_time *tm) +{ + struct isl12022 *isl12022 = i2c_get_clientdata(client); + size_t i; + int ret; + uint8_t buf[ISL12022_REG_DW + 1]; + + dev_dbg(&client->dev, "%s: secs=%d, mins=%d, hours=%d, " + "mday=%d, mon=%d, year=%d, wday=%d\n", + __func__, + tm->tm_sec, tm->tm_min, tm->tm_hour, + tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday); + + if (!isl12022->write_enabled) { + + ret = isl12022_read_regs(client, ISL12022_REG_INT, buf, 1); + if (ret) + return ret; + + /* Check if WRTC (write rtc enable) is set factory default is + * 0 (not set) */ + if (!(buf[0] & ISL12022_INT_WRTC)) { + dev_info(&client->dev, + "init write enable and 24 hour format\n"); + + /* Set the write enable bit. */ + ret = isl12022_write_reg(client, + ISL12022_REG_INT, + buf[0] | ISL12022_INT_WRTC); + if (ret) + return ret; + + /* Write to any RTC register to start RTC, we use the + * HR register, setting the MIL bit to use the 24 hour + * format. */ + ret = isl12022_read_regs(client, ISL12022_REG_HR, + buf, 1); + if (ret) + return ret; + + ret = isl12022_write_reg(client, + ISL12022_REG_HR, + buf[0] | ISL12022_HR_MIL); + if (ret) + return ret; + } + + isl12022->write_enabled = 1; + } + + /* hours, minutes and seconds */ + buf[ISL12022_REG_SC] = bin2bcd(tm->tm_sec); + buf[ISL12022_REG_MN] = bin2bcd(tm->tm_min); + buf[ISL12022_REG_HR] = bin2bcd(tm->tm_hour) | ISL12022_HR_MIL; + + buf[ISL12022_REG_DT] = bin2bcd(tm->tm_mday); + + /* month, 1 - 12 */ + buf[ISL12022_REG_MO] = bin2bcd(tm->tm_mon + 1); + + /* year and century */ + buf[ISL12022_REG_YR] = bin2bcd(tm->tm_year % 100); + + buf[ISL12022_REG_DW] = tm->tm_wday & 0x07; + + /* write register's data */ + for (i = 0; i < ARRAY_SIZE(buf); i++) { + ret = isl12022_write_reg(client, ISL12022_REG_SC + i, + buf[ISL12022_REG_SC + i]); + if (ret) + return -EIO; + }; + + return 0; +} + +static int isl12022_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + return isl12022_get_datetime(to_i2c_client(dev), tm); +} + +static int isl12022_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + return isl12022_set_datetime(to_i2c_client(dev), tm); +} + +static const struct rtc_class_ops isl12022_rtc_ops = { + .read_time = isl12022_rtc_read_time, + .set_time = isl12022_rtc_set_time, +}; + +static int isl12022_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isl12022 *isl12022; + + int ret = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + isl12022 = kzalloc(sizeof(struct isl12022), GFP_KERNEL); + if (!isl12022) + return -ENOMEM; + + dev_dbg(&client->dev, "chip found, driver version " DRV_VERSION "\n"); + + i2c_set_clientdata(client, isl12022); + + isl12022->rtc = rtc_device_register(isl12022_driver.driver.name, + &client->dev, + &isl12022_rtc_ops, + THIS_MODULE); + + if (IS_ERR(isl12022->rtc)) { + ret = PTR_ERR(isl12022->rtc); + goto exit_kfree; + } + + return 0; + +exit_kfree: + kfree(isl12022); + + return ret; +} + +static int isl12022_remove(struct i2c_client *client) +{ + struct isl12022 *isl12022 = i2c_get_clientdata(client); + + rtc_device_unregister(isl12022->rtc); + kfree(isl12022); + + return 0; +} + +static const struct i2c_device_id isl12022_id[] = { + { "isl12022", 0 }, + { "rtc8564", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, isl12022_id); + +static struct i2c_driver isl12022_driver = { + .driver = { + .name = "rtc-isl12022", + }, + .probe = isl12022_probe, + .remove = isl12022_remove, + .id_table = isl12022_id, +}; + +static int __init isl12022_init(void) +{ + return i2c_add_driver(&isl12022_driver); +} + +static void __exit isl12022_exit(void) +{ + i2c_del_driver(&isl12022_driver); +} + +module_init(isl12022_init); +module_exit(isl12022_exit); + +MODULE_AUTHOR("roman.fietze@telemotive.de"); +MODULE_DESCRIPTION("ISL 12022 RTC driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c index 6dc4e6241418..66377f3e28b8 100644 --- a/drivers/rtc/rtc-m41t80.c +++ b/drivers/rtc/rtc-m41t80.c @@ -121,7 +121,7 @@ static int m41t80_get_datetime(struct i2c_client *client, /* assume 20YY not 19YY, and ignore the Century Bit */ tm->tm_year = bcd2bin(buf[M41T80_REG_YEAR]) + 100; - return 0; + return rtc_valid_tm(tm); } /* Sets the given date and time to the real time clock. */ @@ -364,7 +364,7 @@ static int m41t80_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *t) t->time.tm_isdst = -1; t->enabled = !!(reg[M41T80_REG_ALARM_MON] & M41T80_ALMON_AFE); t->pending = !!(reg[M41T80_REG_FLAGS] & M41T80_FLAGS_AF); - return 0; + return rtc_valid_tm(t); } static struct rtc_class_ops m41t80_rtc_ops = { diff --git a/drivers/rtc/rtc-m48t59.c b/drivers/rtc/rtc-m48t59.c index be8359fdb65a..a99a0b554eb8 100644 --- a/drivers/rtc/rtc-m48t59.c +++ b/drivers/rtc/rtc-m48t59.c @@ -105,7 +105,7 @@ static int m48t59_rtc_read_time(struct device *dev, struct rtc_time *tm) dev_dbg(dev, "RTC read time %04d-%02d-%02d %02d/%02d/%02d\n", tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - return 0; + return rtc_valid_tm(tm); } static int m48t59_rtc_set_time(struct device *dev, struct rtc_time *tm) @@ -196,7 +196,7 @@ static int m48t59_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) dev_dbg(dev, "RTC read alarm time %04d-%02d-%02d %02d/%02d/%02d\n", tm->tm_year + 1900, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - return 0; + return rtc_valid_tm(tm); } /* @@ -506,7 +506,6 @@ out: free_irq(m48t59->irq, &pdev->dev); if (m48t59->ioaddr) iounmap(m48t59->ioaddr); - if (m48t59) kfree(m48t59); return ret; } diff --git a/drivers/rtc/rtc-m48t86.c b/drivers/rtc/rtc-m48t86.c index 7c045cffa9ff..f981287d582b 100644 --- a/drivers/rtc/rtc-m48t86.c +++ b/drivers/rtc/rtc-m48t86.c @@ -77,7 +77,7 @@ static int m48t86_rtc_read_time(struct device *dev, struct rtc_time *tm) if (ops->readbyte(M48T86_REG_HOUR) & 0x80) tm->tm_hour += 12; - return 0; + return rtc_valid_tm(tm); } static int m48t86_rtc_set_time(struct device *dev, struct rtc_time *tm) diff --git a/drivers/rtc/rtc-max6900.c b/drivers/rtc/rtc-max6900.c index a4f6665ab3c5..486142c2637a 100644 --- a/drivers/rtc/rtc-max6900.c +++ b/drivers/rtc/rtc-max6900.c @@ -159,7 +159,7 @@ static int max6900_i2c_read_time(struct i2c_client *client, struct rtc_time *tm) bcd2bin(regs[MAX6900_REG_CENTURY]) * 100 - 1900; tm->tm_wday = bcd2bin(regs[MAX6900_REG_DW]); - return 0; + return rtc_valid_tm(tm); } static int max6900_i2c_clear_write_protect(struct i2c_client *client) diff --git a/drivers/rtc/rtc-mxc.c b/drivers/rtc/rtc-mxc.c index 25ec921db07c..0b06c1e03fd5 100644 --- a/drivers/rtc/rtc-mxc.c +++ b/drivers/rtc/rtc-mxc.c @@ -83,12 +83,6 @@ struct rtc_plat_data { void __iomem *ioaddr; int irq; struct clk *clk; - unsigned int irqen; - int alrm_sec; - int alrm_min; - int alrm_hour; - int alrm_mday; - struct timespec mxc_rtc_delta; struct rtc_time g_rtc_alarm; }; diff --git a/drivers/rtc/rtc-nuc900.c b/drivers/rtc/rtc-nuc900.c index a351bd5d8176..62de66af0a68 100644 --- a/drivers/rtc/rtc-nuc900.c +++ b/drivers/rtc/rtc-nuc900.c @@ -85,25 +85,24 @@ static irqreturn_t nuc900_rtc_interrupt(int irq, void *_rtc) static int *check_rtc_access_enable(struct nuc900_rtc *nuc900_rtc) { - unsigned int i; + unsigned int timeout = 0x1000; __raw_writel(INIRRESET, nuc900_rtc->rtc_reg + REG_RTC_INIR); mdelay(10); __raw_writel(AERPOWERON, nuc900_rtc->rtc_reg + REG_RTC_AER); - for (i = 0; i < 1000; i++) { - if (__raw_readl(nuc900_rtc->rtc_reg + REG_RTC_AER) & AERRWENB) - return 0; - } + while (!(__raw_readl(nuc900_rtc->rtc_reg + REG_RTC_AER) & AERRWENB) + && timeout--) + mdelay(1); - if ((__raw_readl(nuc900_rtc->rtc_reg + REG_RTC_AER) & AERRWENB) == 0x0) - return ERR_PTR(-ENODEV); + if (!timeout) + return ERR_PTR(-EPERM); - return ERR_PTR(-EPERM); + return 0; } -static void nuc900_rtc_bcd2bin(unsigned int timereg, +static int nuc900_rtc_bcd2bin(unsigned int timereg, unsigned int calreg, struct rtc_time *tm) { tm->tm_mday = bcd2bin(calreg >> 0); @@ -114,15 +113,21 @@ static void nuc900_rtc_bcd2bin(unsigned int timereg, tm->tm_min = bcd2bin(timereg >> 8); tm->tm_hour = bcd2bin(timereg >> 16); - rtc_valid_tm(tm); + return rtc_valid_tm(tm); } -static void nuc900_rtc_bin2bcd(struct rtc_time *settm, +static void nuc900_rtc_bin2bcd(struct device *dev, struct rtc_time *settm, struct nuc900_bcd_time *gettm) { gettm->bcd_mday = bin2bcd(settm->tm_mday) << 0; gettm->bcd_mon = bin2bcd(settm->tm_mon) << 8; - gettm->bcd_year = bin2bcd(settm->tm_year - 100) << 16; + + if (settm->tm_year < 100) { + dev_warn(dev, "The year will be between 1970-1999, right?\n"); + gettm->bcd_year = bin2bcd(settm->tm_year) << 16; + } else { + gettm->bcd_year = bin2bcd(settm->tm_year - 100) << 16; + } gettm->bcd_sec = bin2bcd(settm->tm_sec) << 0; gettm->bcd_min = bin2bcd(settm->tm_min) << 8; @@ -165,9 +170,7 @@ static int nuc900_rtc_read_time(struct device *dev, struct rtc_time *tm) timeval = __raw_readl(rtc->rtc_reg + REG_RTC_TLR); clrval = __raw_readl(rtc->rtc_reg + REG_RTC_CLR); - nuc900_rtc_bcd2bin(timeval, clrval, tm); - - return 0; + return nuc900_rtc_bcd2bin(timeval, clrval, tm); } static int nuc900_rtc_set_time(struct device *dev, struct rtc_time *tm) @@ -177,7 +180,7 @@ static int nuc900_rtc_set_time(struct device *dev, struct rtc_time *tm) unsigned long val; int *err; - nuc900_rtc_bin2bcd(tm, &gettm); + nuc900_rtc_bin2bcd(dev, tm, &gettm); err = check_rtc_access_enable(rtc); if (IS_ERR(err)) @@ -200,9 +203,7 @@ static int nuc900_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) timeval = __raw_readl(rtc->rtc_reg + REG_RTC_TAR); carval = __raw_readl(rtc->rtc_reg + REG_RTC_CAR); - nuc900_rtc_bcd2bin(timeval, carval, &alrm->time); - - return 0; + return nuc900_rtc_bcd2bin(timeval, carval, &alrm->time); } static int nuc900_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) @@ -212,7 +213,7 @@ static int nuc900_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) unsigned long val; int *err; - nuc900_rtc_bin2bcd(&alrm->time, &tm); + nuc900_rtc_bin2bcd(dev, &alrm->time, &tm); err = check_rtc_access_enable(rtc); if (IS_ERR(err)) @@ -268,29 +269,30 @@ static int __devinit nuc900_rtc_probe(struct platform_device *pdev) goto fail2; } - nuc900_rtc->irq_num = platform_get_irq(pdev, 0); - if (request_irq(nuc900_rtc->irq_num, nuc900_rtc_interrupt, - IRQF_DISABLED, "nuc900rtc", nuc900_rtc)) { - dev_err(&pdev->dev, "NUC900 RTC request irq failed\n"); - err = -EBUSY; - goto fail3; - } + platform_set_drvdata(pdev, nuc900_rtc); nuc900_rtc->rtcdev = rtc_device_register(pdev->name, &pdev->dev, &nuc900_rtc_ops, THIS_MODULE); if (IS_ERR(nuc900_rtc->rtcdev)) { dev_err(&pdev->dev, "rtc device register faild\n"); err = PTR_ERR(nuc900_rtc->rtcdev); - goto fail4; + goto fail3; } - platform_set_drvdata(pdev, nuc900_rtc); __raw_writel(__raw_readl(nuc900_rtc->rtc_reg + REG_RTC_TSSR) | MODE24, nuc900_rtc->rtc_reg + REG_RTC_TSSR); + nuc900_rtc->irq_num = platform_get_irq(pdev, 0); + if (request_irq(nuc900_rtc->irq_num, nuc900_rtc_interrupt, + IRQF_DISABLED, "nuc900rtc", nuc900_rtc)) { + dev_err(&pdev->dev, "NUC900 RTC request irq failed\n"); + err = -EBUSY; + goto fail4; + } + return 0; -fail4: free_irq(nuc900_rtc->irq_num, nuc900_rtc); +fail4: rtc_device_unregister(nuc900_rtc->rtcdev); fail3: iounmap(nuc900_rtc->rtc_reg); fail2: release_mem_region(res->start, resource_size(res)); fail1: kfree(nuc900_rtc); @@ -302,8 +304,8 @@ static int __devexit nuc900_rtc_remove(struct platform_device *pdev) struct nuc900_rtc *nuc900_rtc = platform_get_drvdata(pdev); struct resource *res; - rtc_device_unregister(nuc900_rtc->rtcdev); free_irq(nuc900_rtc->irq_num, nuc900_rtc); + rtc_device_unregister(nuc900_rtc->rtcdev); iounmap(nuc900_rtc->rtc_reg); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index 1af42b4a6f59..b42c0c679266 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -172,14 +172,6 @@ static int pcf8563_set_datetime(struct i2c_client *client, struct rtc_time *tm) return 0; } -struct pcf8563_limit -{ - unsigned char reg; - unsigned char mask; - unsigned char min; - unsigned char max; -}; - static int pcf8563_rtc_read_time(struct device *dev, struct rtc_time *tm) { return pcf8563_get_datetime(to_i2c_client(dev), tm); diff --git a/drivers/rtc/rtc-pl031.c b/drivers/rtc/rtc-pl031.c index 71bbefc3544e..6c418fe7f288 100644 --- a/drivers/rtc/rtc-pl031.c +++ b/drivers/rtc/rtc-pl031.c @@ -23,7 +23,6 @@ #include <linux/io.h> #include <linux/bcd.h> #include <linux/delay.h> -#include <linux/version.h> #include <linux/slab.h> /* diff --git a/drivers/rtc/rtc-pxa.c b/drivers/rtc/rtc-pxa.c index e9c6fa035989..29e867a1aaa8 100644 --- a/drivers/rtc/rtc-pxa.c +++ b/drivers/rtc/rtc-pxa.c @@ -87,7 +87,6 @@ struct pxa_rtc { int irq_Alrm; struct rtc_device *rtc; spinlock_t lock; /* Protects this structure */ - struct rtc_time rtc_alarm; }; static u32 ryxr_calc(struct rtc_time *tm) @@ -236,32 +235,34 @@ static int pxa_periodic_irq_set_state(struct device *dev, int enabled) return 0; } -static int pxa_rtc_ioctl(struct device *dev, unsigned int cmd, - unsigned long arg) +static int pxa_alarm_irq_enable(struct device *dev, unsigned int enabled) { struct pxa_rtc *pxa_rtc = dev_get_drvdata(dev); - int ret = 0; spin_lock_irq(&pxa_rtc->lock); - switch (cmd) { - case RTC_AIE_OFF: - rtsr_clear_bits(pxa_rtc, RTSR_RDALE1); - break; - case RTC_AIE_ON: + + if (enabled) rtsr_set_bits(pxa_rtc, RTSR_RDALE1); - break; - case RTC_UIE_OFF: - rtsr_clear_bits(pxa_rtc, RTSR_HZE); - break; - case RTC_UIE_ON: + else + rtsr_clear_bits(pxa_rtc, RTSR_RDALE1); + + spin_unlock_irq(&pxa_rtc->lock); + return 0; +} + +static int pxa_update_irq_enable(struct device *dev, unsigned int enabled) +{ + struct pxa_rtc *pxa_rtc = dev_get_drvdata(dev); + + spin_lock_irq(&pxa_rtc->lock); + + if (enabled) rtsr_set_bits(pxa_rtc, RTSR_HZE); - break; - default: - ret = -ENOIOCTLCMD; - } + else + rtsr_clear_bits(pxa_rtc, RTSR_HZE); spin_unlock_irq(&pxa_rtc->lock); - return ret; + return 0; } static int pxa_rtc_read_time(struct device *dev, struct rtc_time *tm) @@ -340,11 +341,12 @@ static int pxa_rtc_proc(struct device *dev, struct seq_file *seq) static const struct rtc_class_ops pxa_rtc_ops = { .open = pxa_rtc_open, .release = pxa_rtc_release, - .ioctl = pxa_rtc_ioctl, .read_time = pxa_rtc_read_time, .set_time = pxa_rtc_set_time, .read_alarm = pxa_rtc_read_alarm, .set_alarm = pxa_rtc_set_alarm, + .alarm_irq_enable = pxa_alarm_irq_enable, + .update_irq_enable = pxa_update_irq_enable, .proc = pxa_rtc_proc, .irq_set_state = pxa_periodic_irq_set_state, .irq_set_freq = pxa_periodic_irq_set_freq, 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 { 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, @@ -92,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 + @@ -111,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); } @@ -119,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); @@ -139,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; } @@ -147,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; @@ -168,6 +240,15 @@ static int __init rp5c01_rtc_probe(struct platform_device *dev) goto out_free_priv; } + 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); + rtc = rtc_device_register("rtc-rp5c01", &dev->dev, &rp5c01_rtc_ops, THIS_MODULE); if (IS_ERR(rtc)) { @@ -177,8 +258,15 @@ static int __init rp5c01_rtc_probe(struct platform_device *dev) priv->rtc = rtc; platform_set_drvdata(dev, priv); + + error = sysfs_create_bin_file(&dev->dev.kobj, &priv->nvram_attr); + if (error) + goto out_unregister; + return 0; +out_unregister: + rtc_device_unregister(rtc); out_unmap: iounmap(priv->regs); out_free_priv: @@ -190,6 +278,7 @@ static int __exit rp5c01_rtc_remove(struct platform_device *dev) { struct rp5c01_priv *priv = platform_get_drvdata(dev); + sysfs_remove_bin_file(&dev->dev.kobj, &priv->nvram_attr); rtc_device_unregister(priv->rtc); iounmap(priv->regs); kfree(priv); |