X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=drivers%2Frtc%2Frtc-m41t80.c;h=a3e0880b38fb2cdb0116b4168664566fdab84ab4;hb=48948a3e237ff47823d414704aeb8604a4c61ad0;hp=4875a444421670a286b2ed49064da6faa5501d36;hpb=caaff562e0ba44a7991ee8322fa4a6891d939757;p=linux-2.6 diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c index 4875a44442..a3e0880b38 100644 --- a/drivers/rtc/rtc-m41t80.c +++ b/drivers/rtc/rtc-m41t80.c @@ -15,11 +15,19 @@ #include #include +#include #include #include #include #include #include +#ifdef CONFIG_RTC_DRV_M41T80_WDT +#include +#include +#include +#include +#include +#endif #define M41T80_REG_SSEC 0 #define M41T80_REG_SEC 1 @@ -53,48 +61,21 @@ #define DRV_VERSION "0.05" -struct m41t80_chip_info { - const char *name; - u8 features; -}; - -static const struct m41t80_chip_info m41t80_chip_info_tbl[] = { - { - .name = "m41t80", - .features = 0, - }, - { - .name = "m41t81", - .features = M41T80_FEATURE_HT, - }, - { - .name = "m41t81s", - .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, - }, - { - .name = "m41t82", - .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, - }, - { - .name = "m41t83", - .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, - }, - { - .name = "m41st84", - .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, - }, - { - .name = "m41st85", - .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, - }, - { - .name = "m41st87", - .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, - }, +static const struct i2c_device_id m41t80_id[] = { + { "m41t80", 0 }, + { "m41t81", M41T80_FEATURE_HT }, + { "m41t81s", M41T80_FEATURE_HT | M41T80_FEATURE_BL }, + { "m41t82", M41T80_FEATURE_HT | M41T80_FEATURE_BL }, + { "m41t83", M41T80_FEATURE_HT | M41T80_FEATURE_BL }, + { "m41st84", M41T80_FEATURE_HT | M41T80_FEATURE_BL }, + { "m41st85", M41T80_FEATURE_HT | M41T80_FEATURE_BL }, + { "m41st87", M41T80_FEATURE_HT | M41T80_FEATURE_BL }, + { } }; +MODULE_DEVICE_TABLE(i2c, m41t80_id); struct m41t80_data { - const struct m41t80_chip_info *chip; + u8 features; struct rtc_device *rtc; }; @@ -201,7 +182,7 @@ static int m41t80_rtc_proc(struct device *dev, struct seq_file *seq) struct m41t80_data *clientdata = i2c_get_clientdata(client); u8 reg; - if (clientdata->chip->features & M41T80_FEATURE_BL) { + if (clientdata->features & M41T80_FEATURE_BL) { reg = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS); seq_printf(seq, "battery\t\t: %s\n", (reg & M41T80_FLAGS_BATT_LOW) ? "exhausted" : "ok"); @@ -480,6 +461,268 @@ static int m41t80_sysfs_register(struct device *dev) } #endif +#ifdef CONFIG_RTC_DRV_M41T80_WDT +/* + ***************************************************************************** + * + * Watchdog Driver + * + ***************************************************************************** + */ +static struct i2c_client *save_client; + +/* Default margin */ +#define WD_TIMO 60 /* 1..31 seconds */ + +static int wdt_margin = WD_TIMO; +module_param(wdt_margin, int, 0); +MODULE_PARM_DESC(wdt_margin, "Watchdog timeout in seconds (default 60s)"); + +static unsigned long wdt_is_open; +static int boot_flag; + +/** + * wdt_ping: + * + * Reload counter one with the watchdog timeout. We don't bother reloading + * the cascade counter. + */ +static void wdt_ping(void) +{ + unsigned char i2c_data[2]; + struct i2c_msg msgs1[1] = { + { + .addr = save_client->addr, + .flags = 0, + .len = 2, + .buf = i2c_data, + }, + }; + i2c_data[0] = 0x09; /* watchdog register */ + + if (wdt_margin > 31) + i2c_data[1] = (wdt_margin & 0xFC) | 0x83; /* resolution = 4s */ + else + /* + * WDS = 1 (0x80), mulitplier = WD_TIMO, resolution = 1s (0x02) + */ + i2c_data[1] = wdt_margin<<2 | 0x82; + + i2c_transfer(save_client->adapter, msgs1, 1); +} + +/** + * wdt_disable: + * + * disables watchdog. + */ +static void wdt_disable(void) +{ + unsigned char i2c_data[2], i2c_buf[0x10]; + struct i2c_msg msgs0[2] = { + { + .addr = save_client->addr, + .flags = 0, + .len = 1, + .buf = i2c_data, + }, + { + .addr = save_client->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = i2c_buf, + }, + }; + struct i2c_msg msgs1[1] = { + { + .addr = save_client->addr, + .flags = 0, + .len = 2, + .buf = i2c_data, + }, + }; + + i2c_data[0] = 0x09; + i2c_transfer(save_client->adapter, msgs0, 2); + + i2c_data[0] = 0x09; + i2c_data[1] = 0x00; + i2c_transfer(save_client->adapter, msgs1, 1); +} + +/** + * wdt_write: + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we we don't define content meaning. + */ +static ssize_t wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* Can't seek (pwrite) on this device + if (ppos != &file->f_pos) + return -ESPIPE; + */ + if (count) { + wdt_ping(); + return 1; + } + return 0; +} + +static ssize_t wdt_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +/** + * wdt_ioctl: + * @inode: inode of the device + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. We only actually usefully support + * querying capabilities and current status. + */ +static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_margin, rv; + static struct watchdog_info ident = { + .options = WDIOF_POWERUNDER | WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT, + .firmware_version = 1, + .identity = "M41T80 WTD" + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user((struct watchdog_info __user *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(boot_flag, (int __user *)arg); + case WDIOC_KEEPALIVE: + wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, (int __user *)arg)) + return -EFAULT; + /* Arbitrary, can't find the card's limits */ + if (new_margin < 1 || new_margin > 124) + return -EINVAL; + wdt_margin = new_margin; + wdt_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(wdt_margin, (int __user *)arg); + + case WDIOC_SETOPTIONS: + if (copy_from_user(&rv, (int __user *)arg, sizeof(int))) + return -EFAULT; + + if (rv & WDIOS_DISABLECARD) { + printk(KERN_INFO + "rtc-m41t80: disable watchdog\n"); + wdt_disable(); + } + + if (rv & WDIOS_ENABLECARD) { + printk(KERN_INFO + "rtc-m41t80: enable watchdog\n"); + wdt_ping(); + } + + return -EINVAL; + } + return -ENOTTY; +} + +/** + * wdt_open: + * @inode: inode of device + * @file: file handle to device + * + */ +static int wdt_open(struct inode *inode, struct file *file) +{ + if (MINOR(inode->i_rdev) == WATCHDOG_MINOR) { + if (test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + /* + * Activate + */ + wdt_is_open = 1; + return 0; + } + return -ENODEV; +} + +/** + * wdt_close: + * @inode: inode to board + * @file: file handle to board + * + */ +static int wdt_release(struct inode *inode, struct file *file) +{ + if (MINOR(inode->i_rdev) == WATCHDOG_MINOR) + clear_bit(0, &wdt_is_open); + return 0; +} + +/** + * notify_sys: + * @this: our notifier block + * @code: the event being reported + * @unused: unused + * + * Our notifier is called on system shutdowns. We want to turn the card + * off at reboot otherwise the machine will reboot again during memory + * test or worse yet during the following fsck. This would suck, in fact + * trust me - if it happens it does suck. + */ +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + /* Disable Watchdog */ + wdt_disable(); + return NOTIFY_DONE; +} + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .read = wdt_read, + .ioctl = wdt_ioctl, + .write = wdt_write, + .open = wdt_open, + .release = wdt_release, +}; + +static struct miscdevice wdt_dev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * The WDT card needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; +#endif /* CONFIG_RTC_DRV_M41T80_WDT */ + /* ***************************************************************************** * @@ -487,12 +730,12 @@ static int m41t80_sysfs_register(struct device *dev) * ***************************************************************************** */ -static int m41t80_probe(struct i2c_client *client) +static int m41t80_probe(struct i2c_client *client, + const struct i2c_device_id *id) { - int i, rc = 0; + int rc = 0; struct rtc_device *rtc = NULL; struct rtc_time tm; - const struct m41t80_chip_info *chip; struct m41t80_data *clientdata = NULL; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C @@ -504,19 +747,6 @@ static int m41t80_probe(struct i2c_client *client) dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n"); - chip = NULL; - for (i = 0; i < ARRAY_SIZE(m41t80_chip_info_tbl); i++) { - if (!strcmp(m41t80_chip_info_tbl[i].name, client->name)) { - chip = &m41t80_chip_info_tbl[i]; - break; - } - } - if (!chip) { - dev_err(&client->dev, "%s is not supported\n", client->name); - rc = -ENODEV; - goto exit; - } - clientdata = kzalloc(sizeof(*clientdata), GFP_KERNEL); if (!clientdata) { rc = -ENOMEM; @@ -532,7 +762,7 @@ static int m41t80_probe(struct i2c_client *client) } clientdata->rtc = rtc; - clientdata->chip = chip; + clientdata->features = id->driver_data; i2c_set_clientdata(client, clientdata); /* Make sure HT (Halt Update) bit is cleared */ @@ -541,7 +771,7 @@ static int m41t80_probe(struct i2c_client *client) goto ht_err; if (rc & M41T80_ALHOUR_HT) { - if (chip->features & M41T80_FEATURE_HT) { + if (clientdata->features & M41T80_FEATURE_HT) { m41t80_get_datetime(client, &tm); dev_info(&client->dev, "HT bit was set!\n"); dev_info(&client->dev, @@ -572,6 +802,19 @@ static int m41t80_probe(struct i2c_client *client) if (rc) goto exit; +#ifdef CONFIG_RTC_DRV_M41T80_WDT + if (clientdata->features & M41T80_FEATURE_HT) { + save_client = client; + rc = misc_register(&wdt_dev); + if (rc) + goto exit; + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + misc_deregister(&wdt_dev); + goto exit; + } + } +#endif return 0; st_err: @@ -595,6 +838,12 @@ static int m41t80_remove(struct i2c_client *client) struct m41t80_data *clientdata = i2c_get_clientdata(client); struct rtc_device *rtc = clientdata->rtc; +#ifdef CONFIG_RTC_DRV_M41T80_WDT + if (clientdata->features & M41T80_FEATURE_HT) { + misc_deregister(&wdt_dev); + unregister_reboot_notifier(&wdt_notifier); + } +#endif if (rtc) rtc_device_unregister(rtc); kfree(clientdata); @@ -604,10 +853,11 @@ static int m41t80_remove(struct i2c_client *client) static struct i2c_driver m41t80_driver = { .driver = { - .name = "m41t80", + .name = "rtc-m41t80", }, .probe = m41t80_probe, .remove = m41t80_remove, + .id_table = m41t80_id, }; static int __init m41t80_rtc_init(void)