diff options
Diffstat (limited to 'drivers/rtc/rtc-ds1374.c')
| -rw-r--r-- | drivers/rtc/rtc-ds1374.c | 209 |
1 files changed, 178 insertions, 31 deletions
diff --git a/drivers/rtc/rtc-ds1374.c b/drivers/rtc/rtc-ds1374.c index 9e6e14fb53d7..c2359eb86bc9 100644 --- a/drivers/rtc/rtc-ds1374.c +++ b/drivers/rtc/rtc-ds1374.c @@ -1,22 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * RTC client/driver for the Maxim/Dallas DS1374 Real-Time Clock over I2C * * Based on code by Randy Vinson <rvinson@mvista.com>, * which was based on the m41t00.c by Mark Greer <mgreer@mvista.com>. * + * Copyright (C) 2014 Rose Technology * Copyright (C) 2006-2007 Freescale Semiconductor - * - * 2005 (c) MontaVista Software, Inc. This file is licensed under - * the terms of the GNU General Public License version 2. This program - * is licensed "as is" without any warranty of any kind, whether express - * or implied. + * Copyright (c) 2005 MontaVista Software, Inc. */ /* * It would be more efficient to use i2c msgs/i2c_transfer directly but, as - * recommened in .../Documentation/i2c/writing-clients section + * recommended in .../Documentation/i2c/writing-clients.rst section * "Sending and receiving", using SMBus level communication is preferred. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/module.h> #include <linux/interrupt.h> @@ -26,6 +26,13 @@ #include <linux/workqueue.h> #include <linux/slab.h> #include <linux/pm.h> +#ifdef CONFIG_RTC_DRV_DS1374_WDT +#include <linux/fs.h> +#include <linux/ioctl.h> +#include <linux/miscdevice.h> +#include <linux/reboot.h> +#include <linux/watchdog.h> +#endif #define DS1374_REG_TOD0 0x00 /* Time of Day */ #define DS1374_REG_TOD1 0x01 @@ -36,6 +43,7 @@ #define DS1374_REG_WDALM2 0x06 #define DS1374_REG_CR 0x07 /* Control */ #define DS1374_REG_CR_AIE 0x01 /* Alarm Int. Enable */ +#define DS1374_REG_CR_WDSTR 0x08 /* 1=INT, 0=RST */ #define DS1374_REG_CR_WDALM 0x20 /* 1=Watchdog, 0=Alarm */ #define DS1374_REG_CR_WACE 0x40 /* WD/Alarm counter enable */ #define DS1374_REG_SR 0x08 /* Status */ @@ -44,16 +52,26 @@ #define DS1374_REG_TCR 0x09 /* Trickle Charge */ static const struct i2c_device_id ds1374_id[] = { - { "ds1374", 0 }, + { "ds1374" }, { } }; MODULE_DEVICE_TABLE(i2c, ds1374_id); +#ifdef CONFIG_OF +static const struct of_device_id ds1374_of_match[] = { + { .compatible = "dallas,ds1374" }, + { } +}; +MODULE_DEVICE_TABLE(of, ds1374_of_match); +#endif + struct ds1374 { struct i2c_client *client; struct rtc_device *rtc; struct work_struct work; - +#ifdef CONFIG_RTC_DRV_DS1374_WDT + struct watchdog_device wdt; +#endif /* The mutex protects alarm operations, and prevents a race * between the enable_irq() in the workqueue and the free_irq() * in the remove function. @@ -71,10 +89,8 @@ static int ds1374_read_rtc(struct i2c_client *client, u32 *time, int ret; int i; - if (nbytes > 4) { - WARN_ON(1); + if (WARN_ON(nbytes > 4)) return -EINVAL; - } ret = i2c_smbus_read_i2c_block_data(client, reg, nbytes, buf); @@ -148,7 +164,7 @@ static int ds1374_read_time(struct device *dev, struct rtc_time *time) ret = ds1374_read_rtc(client, &itime, DS1374_REG_TOD0, 4); if (!ret) - rtc_time_to_tm(itime, time); + rtc_time64_to_tm(itime, time); return ret; } @@ -156,12 +172,12 @@ static int ds1374_read_time(struct device *dev, struct rtc_time *time) static int ds1374_set_time(struct device *dev, struct rtc_time *time) { struct i2c_client *client = to_i2c_client(dev); - unsigned long itime; + unsigned long itime = rtc_tm_to_time64(time); - rtc_tm_to_time(time, &itime); return ds1374_write_rtc(client, itime, DS1374_REG_TOD0, 4); } +#ifndef CONFIG_RTC_DRV_DS1374_WDT /* The ds1374 has a decrementer for an alarm, rather than a comparator. * If the time of day is changed, then the alarm will need to be * reset. @@ -195,7 +211,7 @@ static int ds1374_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) if (ret) goto out; - rtc_time_to_tm(now + cur_alarm, &alarm->time); + rtc_time64_to_tm(now + cur_alarm, &alarm->time); alarm->enabled = !!(cr & DS1374_REG_CR_WACE); alarm->pending = !!(sr & DS1374_REG_SR_AF); @@ -220,8 +236,8 @@ static int ds1374_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) if (ret < 0) return ret; - rtc_tm_to_time(&alarm->time, &new_alarm); - rtc_tm_to_time(&now, &itime); + new_alarm = rtc_tm_to_time64(&alarm->time); + itime = rtc_tm_to_time64(&now); /* This can happen due to races, in addition to dates that are * truly in the past. To avoid requiring the caller to check for @@ -263,6 +279,7 @@ out: mutex_unlock(&ds1374->mutex); return ret; } +#endif static irqreturn_t ds1374_irq(int irq, void *dev_id) { @@ -307,6 +324,7 @@ unlock: mutex_unlock(&ds1374->mutex); } +#ifndef CONFIG_RTC_DRV_DS1374_WDT static int ds1374_alarm_irq_enable(struct device *dev, unsigned int enabled) { struct i2c_client *client = to_i2c_client(dev); @@ -331,17 +349,125 @@ out: mutex_unlock(&ds1374->mutex); return ret; } +#endif static const struct rtc_class_ops ds1374_rtc_ops = { .read_time = ds1374_read_time, .set_time = ds1374_set_time, +#ifndef CONFIG_RTC_DRV_DS1374_WDT .read_alarm = ds1374_read_alarm, .set_alarm = ds1374_set_alarm, .alarm_irq_enable = ds1374_alarm_irq_enable, +#endif +}; + +#ifdef CONFIG_RTC_DRV_DS1374_WDT +/* + ***************************************************************************** + * + * Watchdog Driver + * + ***************************************************************************** + */ +/* Default margin */ +#define TIMER_MARGIN_DEFAULT 32 +#define TIMER_MARGIN_MIN 1 +#define TIMER_MARGIN_MAX 4095 /* 24-bit value */ + +static int wdt_margin; +module_param(wdt_margin, int, 0); +MODULE_PARM_DESC(wdt_margin, "Watchdog timeout in seconds (default 32s)"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default =" + __MODULE_STRING(WATCHDOG_NOWAYOUT)")"); + +static const struct watchdog_info ds1374_wdt_info = { + .identity = "DS1374 Watchdog", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, }; -static int ds1374_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int ds1374_wdt_settimeout(struct watchdog_device *wdt, unsigned int timeout) +{ + struct ds1374 *ds1374 = watchdog_get_drvdata(wdt); + struct i2c_client *client = ds1374->client; + int ret, cr; + + wdt->timeout = timeout; + + cr = i2c_smbus_read_byte_data(client, DS1374_REG_CR); + if (cr < 0) + return cr; + + /* Disable any existing watchdog/alarm before setting the new one */ + cr &= ~DS1374_REG_CR_WACE; + + ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); + if (ret < 0) + return ret; + + /* Set new watchdog time */ + timeout = timeout * 4096; + ret = ds1374_write_rtc(client, timeout, DS1374_REG_WDALM0, 3); + if (ret) + return ret; + + /* Enable watchdog timer */ + cr |= DS1374_REG_CR_WACE | DS1374_REG_CR_WDALM; + cr &= ~DS1374_REG_CR_WDSTR;/* for RST PIN */ + cr &= ~DS1374_REG_CR_AIE; + + ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); + if (ret < 0) + return ret; + + return 0; +} + +/* + * Reload the watchdog timer. (ie, pat the watchdog) + */ +static int ds1374_wdt_start(struct watchdog_device *wdt) +{ + struct ds1374 *ds1374 = watchdog_get_drvdata(wdt); + u32 val; + + return ds1374_read_rtc(ds1374->client, &val, DS1374_REG_WDALM0, 3); +} + +static int ds1374_wdt_stop(struct watchdog_device *wdt) +{ + struct ds1374 *ds1374 = watchdog_get_drvdata(wdt); + struct i2c_client *client = ds1374->client; + int cr; + + cr = i2c_smbus_read_byte_data(client, DS1374_REG_CR); + if (cr < 0) + return cr; + + /* Disable watchdog timer */ + cr &= ~DS1374_REG_CR_WACE; + + return i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); +} + +static const struct watchdog_ops ds1374_wdt_ops = { + .owner = THIS_MODULE, + .start = ds1374_wdt_start, + .stop = ds1374_wdt_stop, + .set_timeout = ds1374_wdt_settimeout, +}; +#endif /*CONFIG_RTC_DRV_DS1374_WDT*/ +/* + ***************************************************************************** + * + * Driver Interface + * + ***************************************************************************** + */ +static int ds1374_probe(struct i2c_client *client) { struct ds1374 *ds1374; int ret; @@ -350,6 +476,10 @@ static int ds1374_probe(struct i2c_client *client, if (!ds1374) return -ENOMEM; + ds1374->rtc = devm_rtc_allocate_device(&client->dev); + if (IS_ERR(ds1374->rtc)) + return PTR_ERR(ds1374->rtc); + ds1374->client = client; i2c_set_clientdata(client, ds1374); @@ -371,17 +501,36 @@ static int ds1374_probe(struct i2c_client *client, device_set_wakeup_capable(&client->dev, 1); } - ds1374->rtc = devm_rtc_device_register(&client->dev, client->name, - &ds1374_rtc_ops, THIS_MODULE); - if (IS_ERR(ds1374->rtc)) { - dev_err(&client->dev, "unable to register the class device\n"); - return PTR_ERR(ds1374->rtc); - } + ds1374->rtc->ops = &ds1374_rtc_ops; + ds1374->rtc->range_max = U32_MAX; + + ret = devm_rtc_register_device(ds1374->rtc); + if (ret) + return ret; + +#ifdef CONFIG_RTC_DRV_DS1374_WDT + ds1374->wdt.info = &ds1374_wdt_info; + ds1374->wdt.ops = &ds1374_wdt_ops; + ds1374->wdt.timeout = TIMER_MARGIN_DEFAULT; + ds1374->wdt.min_timeout = TIMER_MARGIN_MIN; + ds1374->wdt.max_timeout = TIMER_MARGIN_MAX; + + watchdog_init_timeout(&ds1374->wdt, wdt_margin, &client->dev); + watchdog_set_nowayout(&ds1374->wdt, nowayout); + watchdog_stop_on_reboot(&ds1374->wdt); + watchdog_stop_on_unregister(&ds1374->wdt); + watchdog_set_drvdata(&ds1374->wdt, ds1374); + ds1374_wdt_settimeout(&ds1374->wdt, ds1374->wdt.timeout); + + ret = devm_watchdog_register_device(&client->dev, &ds1374->wdt); + if (ret) + return ret; +#endif return 0; } -static int ds1374_remove(struct i2c_client *client) +static void ds1374_remove(struct i2c_client *client) { struct ds1374 *ds1374 = i2c_get_clientdata(client); @@ -393,8 +542,6 @@ static int ds1374_remove(struct i2c_client *client) devm_free_irq(&client->dev, client->irq, client); cancel_work_sync(&ds1374->work); } - - return 0; } #ifdef CONFIG_PM_SLEEP @@ -402,7 +549,7 @@ static int ds1374_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); - if (client->irq >= 0 && device_may_wakeup(&client->dev)) + if (client->irq > 0 && device_may_wakeup(&client->dev)) enable_irq_wake(client->irq); return 0; } @@ -411,7 +558,7 @@ static int ds1374_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); - if (client->irq >= 0 && device_may_wakeup(&client->dev)) + if (client->irq > 0 && device_may_wakeup(&client->dev)) disable_irq_wake(client->irq); return 0; } @@ -422,7 +569,7 @@ static SIMPLE_DEV_PM_OPS(ds1374_pm, ds1374_suspend, ds1374_resume); static struct i2c_driver ds1374_driver = { .driver = { .name = "rtc-ds1374", - .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ds1374_of_match), .pm = &ds1374_pm, }, .probe = ds1374_probe, |
