diff options
Diffstat (limited to 'drivers/rtc/rtc-pcf8563.c')
| -rw-r--r-- | drivers/rtc/rtc-pcf8563.c | 203 |
1 files changed, 84 insertions, 119 deletions
diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index 748a502a635..63b558c4819 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -17,6 +17,10 @@ #include <linux/i2c.h> #include <linux/bcd.h> #include <linux/rtc.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/err.h> #define DRV_VERSION "0.4.3" @@ -62,6 +66,7 @@ struct pcf8563 { * 1970...2069. */ int c_polarity; /* 0: MO_C=1 means 19xx, otherwise MO_C=1 means 20xx */ + int voltage_low; /* incicates if a low_voltage was detected */ }; /* @@ -74,8 +79,17 @@ static int pcf8563_get_datetime(struct i2c_client *client, struct rtc_time *tm) unsigned char buf[13] = { PCF8563_REG_ST1 }; struct i2c_msg msgs[] = { - { client->addr, 0, 1, buf }, /* setup read ptr */ - { client->addr, I2C_M_RD, 13, buf }, /* read status + date */ + {/* setup read ptr */ + .addr = client->addr, + .len = 1, + .buf = buf + }, + {/* read status + date */ + .addr = client->addr, + .flags = I2C_M_RD, + .len = 13, + .buf = buf + }, }; /* read registers */ @@ -84,9 +98,11 @@ static int pcf8563_get_datetime(struct i2c_client *client, struct rtc_time *tm) return -EIO; } - if (buf[PCF8563_REG_SC] & PCF8563_SC_LV) + if (buf[PCF8563_REG_SC] & PCF8563_SC_LV) { + pcf8563->voltage_low = 1; dev_info(&client->dev, "low voltage detected, date/time is not reliable.\n"); + } dev_dbg(&client->dev, "%s: raw data is st1=%02x, st2=%02x, sec=%02x, min=%02x, hr=%02x, " @@ -97,13 +113,13 @@ static int pcf8563_get_datetime(struct i2c_client *client, struct rtc_time *tm) buf[8]); - tm->tm_sec = BCD2BIN(buf[PCF8563_REG_SC] & 0x7F); - tm->tm_min = BCD2BIN(buf[PCF8563_REG_MN] & 0x7F); - tm->tm_hour = BCD2BIN(buf[PCF8563_REG_HR] & 0x3F); /* rtc hr 0-23 */ - tm->tm_mday = BCD2BIN(buf[PCF8563_REG_DM] & 0x3F); + tm->tm_sec = bcd2bin(buf[PCF8563_REG_SC] & 0x7F); + tm->tm_min = bcd2bin(buf[PCF8563_REG_MN] & 0x7F); + tm->tm_hour = bcd2bin(buf[PCF8563_REG_HR] & 0x3F); /* rtc hr 0-23 */ + tm->tm_mday = bcd2bin(buf[PCF8563_REG_DM] & 0x3F); tm->tm_wday = buf[PCF8563_REG_DW] & 0x07; - tm->tm_mon = BCD2BIN(buf[PCF8563_REG_MO] & 0x1F) - 1; /* rtc mn 1-12 */ - tm->tm_year = BCD2BIN(buf[PCF8563_REG_YR]); + tm->tm_mon = bcd2bin(buf[PCF8563_REG_MO] & 0x1F) - 1; /* rtc mn 1-12 */ + tm->tm_year = bcd2bin(buf[PCF8563_REG_YR]); if (tm->tm_year < 70) tm->tm_year += 100; /* assume we are in 1970...2069 */ /* detect the polarity heuristically. see note above. */ @@ -138,17 +154,17 @@ static int pcf8563_set_datetime(struct i2c_client *client, struct rtc_time *tm) tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday); /* hours, minutes and seconds */ - buf[PCF8563_REG_SC] = BIN2BCD(tm->tm_sec); - buf[PCF8563_REG_MN] = BIN2BCD(tm->tm_min); - buf[PCF8563_REG_HR] = BIN2BCD(tm->tm_hour); + buf[PCF8563_REG_SC] = bin2bcd(tm->tm_sec); + buf[PCF8563_REG_MN] = bin2bcd(tm->tm_min); + buf[PCF8563_REG_HR] = bin2bcd(tm->tm_hour); - buf[PCF8563_REG_DM] = BIN2BCD(tm->tm_mday); + buf[PCF8563_REG_DM] = bin2bcd(tm->tm_mday); /* month, 1 - 12 */ - buf[PCF8563_REG_MO] = BIN2BCD(tm->tm_mon + 1); + buf[PCF8563_REG_MO] = bin2bcd(tm->tm_mon + 1); /* year and century */ - buf[PCF8563_REG_YR] = BIN2BCD(tm->tm_year % 100); + buf[PCF8563_REG_YR] = bin2bcd(tm->tm_year % 100); if (pcf8563->c_polarity ? (tm->tm_year >= 100) : (tm->tm_year < 100)) buf[PCF8563_REG_MO] |= PCF8563_MO_C; @@ -166,70 +182,48 @@ static int pcf8563_set_datetime(struct i2c_client *client, struct rtc_time *tm) __func__, err, data[0], data[1]); return -EIO; } - }; + } return 0; } -struct pcf8563_limit -{ - unsigned char reg; - unsigned char mask; - unsigned char min; - unsigned char max; -}; - -static int pcf8563_validate_client(struct i2c_client *client) +#ifdef CONFIG_RTC_INTF_DEV +static int pcf8563_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) { - int i; - - static const struct pcf8563_limit pattern[] = { - /* register, mask, min, max */ - { PCF8563_REG_SC, 0x7F, 0, 59 }, - { PCF8563_REG_MN, 0x7F, 0, 59 }, - { PCF8563_REG_HR, 0x3F, 0, 23 }, - { PCF8563_REG_DM, 0x3F, 0, 31 }, - { PCF8563_REG_MO, 0x1F, 0, 12 }, - }; - - /* check limits (only registers with bcd values) */ - for (i = 0; i < ARRAY_SIZE(pattern); i++) { - int xfer; - unsigned char value; - unsigned char buf = pattern[i].reg; - - struct i2c_msg msgs[] = { - { client->addr, 0, 1, &buf }, - { client->addr, I2C_M_RD, 1, &buf }, - }; - - xfer = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); - - if (xfer != ARRAY_SIZE(msgs)) { - dev_err(&client->dev, - "%s: could not read register 0x%02X\n", - __func__, pattern[i].reg); - - return -EIO; - } - - value = BCD2BIN(buf & pattern[i].mask); - - if (value > pattern[i].max || - value < pattern[i].min) { - dev_dbg(&client->dev, - "%s: pattern=%d, reg=%x, mask=0x%02x, min=%d, " - "max=%d, value=%d, raw=0x%02X\n", - __func__, i, pattern[i].reg, pattern[i].mask, - pattern[i].min, pattern[i].max, - value, buf); - - return -ENODEV; - } + struct pcf8563 *pcf8563 = i2c_get_clientdata(to_i2c_client(dev)); + struct rtc_time tm; + + switch (cmd) { + case RTC_VL_READ: + if (pcf8563->voltage_low) + dev_info(dev, "low voltage detected, date/time is not reliable.\n"); + + if (copy_to_user((void __user *)arg, &pcf8563->voltage_low, + sizeof(int))) + return -EFAULT; + return 0; + case RTC_VL_CLR: + /* + * Clear the VL bit in the seconds register in case + * the time has not been set already (which would + * have cleared it). This does not really matter + * because of the cached voltage_low value but do it + * anyway for consistency. + */ + if (pcf8563_get_datetime(to_i2c_client(dev), &tm)) + pcf8563_set_datetime(to_i2c_client(dev), &tm); + + /* Clear the cached value. */ + pcf8563->voltage_low = 0; + + return 0; + default: + return -ENOIOCTLCMD; } - - return 0; } +#else +#define pcf8563_rtc_ioctl NULL +#endif static int pcf8563_rtc_read_time(struct device *dev, struct rtc_time *tm) { @@ -242,6 +236,7 @@ static int pcf8563_rtc_set_time(struct device *dev, struct rtc_time *tm) } static const struct rtc_class_ops pcf8563_rtc_ops = { + .ioctl = pcf8563_rtc_ioctl, .read_time = pcf8563_rtc_read_time, .set_time = pcf8563_rtc_set_time, }; @@ -251,53 +246,25 @@ static int pcf8563_probe(struct i2c_client *client, { struct pcf8563 *pcf8563; - int err = 0; - dev_dbg(&client->dev, "%s\n", __func__); if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -ENODEV; - pcf8563 = kzalloc(sizeof(struct pcf8563), GFP_KERNEL); + pcf8563 = devm_kzalloc(&client->dev, sizeof(struct pcf8563), + GFP_KERNEL); if (!pcf8563) return -ENOMEM; - /* Verify the chip is really an PCF8563 */ - if (pcf8563_validate_client(client) < 0) { - err = -ENODEV; - goto exit_kfree; - } - dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n"); - pcf8563->rtc = rtc_device_register(pcf8563_driver.driver.name, - &client->dev, &pcf8563_rtc_ops, THIS_MODULE); - - if (IS_ERR(pcf8563->rtc)) { - err = PTR_ERR(pcf8563->rtc); - goto exit_kfree; - } - i2c_set_clientdata(client, pcf8563); - return 0; - -exit_kfree: - kfree(pcf8563); - - return err; -} - -static int pcf8563_remove(struct i2c_client *client) -{ - struct pcf8563 *pcf8563 = i2c_get_clientdata(client); - - if (pcf8563->rtc) - rtc_device_unregister(pcf8563->rtc); - - kfree(pcf8563); + pcf8563->rtc = devm_rtc_device_register(&client->dev, + pcf8563_driver.driver.name, + &pcf8563_rtc_ops, THIS_MODULE); - return 0; + return PTR_ERR_OR_ZERO(pcf8563->rtc); } static const struct i2c_device_id pcf8563_id[] = { @@ -307,29 +274,27 @@ static const struct i2c_device_id pcf8563_id[] = { }; MODULE_DEVICE_TABLE(i2c, pcf8563_id); +#ifdef CONFIG_OF +static const struct of_device_id pcf8563_of_match[] = { + { .compatible = "nxp,pcf8563" }, + {} +}; +MODULE_DEVICE_TABLE(of, pcf8563_of_match); +#endif + static struct i2c_driver pcf8563_driver = { .driver = { .name = "rtc-pcf8563", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pcf8563_of_match), }, .probe = pcf8563_probe, - .remove = pcf8563_remove, .id_table = pcf8563_id, }; -static int __init pcf8563_init(void) -{ - return i2c_add_driver(&pcf8563_driver); -} - -static void __exit pcf8563_exit(void) -{ - i2c_del_driver(&pcf8563_driver); -} +module_i2c_driver(pcf8563_driver); MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); MODULE_DESCRIPTION("Philips PCF8563/Epson RTC8564 RTC driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); - -module_init(pcf8563_init); -module_exit(pcf8563_exit); |
