2008-07-24 11:30:36 +07:00
|
|
|
/*
|
|
|
|
* rtc-ds1305.c -- driver for DS1305 and DS1306 SPI RTC chips
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 David Brownell
|
|
|
|
*
|
|
|
|
* 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/kernel.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/bcd.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2008-07-24 11:30:36 +07:00
|
|
|
#include <linux/rtc.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
|
|
|
|
#include <linux/spi/spi.h>
|
|
|
|
#include <linux/spi/ds1305.h>
|
2011-05-27 20:57:25 +07:00
|
|
|
#include <linux/module.h>
|
2008-07-24 11:30:36 +07:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Registers ... mask DS1305_WRITE into register address to write,
|
|
|
|
* otherwise you're reading it. All non-bitmask values are BCD.
|
|
|
|
*/
|
|
|
|
#define DS1305_WRITE 0x80
|
|
|
|
|
|
|
|
|
|
|
|
/* RTC date/time ... the main special cases are that we:
|
|
|
|
* - Need fancy "hours" encoding in 12hour mode
|
|
|
|
* - Don't rely on the "day-of-week" field (or tm_wday)
|
|
|
|
* - Are a 21st-century clock (2000 <= year < 2100)
|
|
|
|
*/
|
|
|
|
#define DS1305_RTC_LEN 7 /* bytes for RTC regs */
|
|
|
|
|
|
|
|
#define DS1305_SEC 0x00 /* register addresses */
|
|
|
|
#define DS1305_MIN 0x01
|
|
|
|
#define DS1305_HOUR 0x02
|
|
|
|
# define DS1305_HR_12 0x40 /* set == 12 hr mode */
|
|
|
|
# define DS1305_HR_PM 0x20 /* set == PM (12hr mode) */
|
|
|
|
#define DS1305_WDAY 0x03
|
|
|
|
#define DS1305_MDAY 0x04
|
|
|
|
#define DS1305_MON 0x05
|
|
|
|
#define DS1305_YEAR 0x06
|
|
|
|
|
|
|
|
|
|
|
|
/* The two alarms have only sec/min/hour/wday fields (ALM_LEN).
|
|
|
|
* DS1305_ALM_DISABLE disables a match field (some combos are bad).
|
|
|
|
*
|
|
|
|
* NOTE that since we don't use WDAY, we limit ourselves to alarms
|
|
|
|
* only one day into the future (vs potentially up to a week).
|
|
|
|
*
|
|
|
|
* NOTE ALSO that while we could generate once-a-second IRQs (UIE), we
|
|
|
|
* don't currently support them. We'd either need to do it only when
|
|
|
|
* no alarm is pending (not the standard model), or to use the second
|
|
|
|
* alarm (implying that this is a DS1305 not DS1306, *and* that either
|
|
|
|
* it's wired up a second IRQ we know, or that INTCN is set)
|
|
|
|
*/
|
|
|
|
#define DS1305_ALM_LEN 4 /* bytes for ALM regs */
|
|
|
|
#define DS1305_ALM_DISABLE 0x80
|
|
|
|
|
|
|
|
#define DS1305_ALM0(r) (0x07 + (r)) /* register addresses */
|
|
|
|
#define DS1305_ALM1(r) (0x0b + (r))
|
|
|
|
|
|
|
|
|
|
|
|
/* three control registers */
|
|
|
|
#define DS1305_CONTROL_LEN 3 /* bytes of control regs */
|
|
|
|
|
|
|
|
#define DS1305_CONTROL 0x0f /* register addresses */
|
|
|
|
# define DS1305_nEOSC 0x80 /* low enables oscillator */
|
|
|
|
# define DS1305_WP 0x40 /* write protect */
|
|
|
|
# define DS1305_INTCN 0x04 /* clear == only int0 used */
|
|
|
|
# define DS1306_1HZ 0x04 /* enable 1Hz output */
|
|
|
|
# define DS1305_AEI1 0x02 /* enable ALM1 IRQ */
|
|
|
|
# define DS1305_AEI0 0x01 /* enable ALM0 IRQ */
|
|
|
|
#define DS1305_STATUS 0x10
|
|
|
|
/* status has just AEIx bits, mirrored as IRQFx */
|
|
|
|
#define DS1305_TRICKLE 0x11
|
|
|
|
/* trickle bits are defined in <linux/spi/ds1305.h> */
|
|
|
|
|
|
|
|
/* a bunch of NVRAM */
|
|
|
|
#define DS1305_NVRAM_LEN 96 /* bytes of NVRAM */
|
|
|
|
|
|
|
|
#define DS1305_NVRAM 0x20 /* register addresses */
|
|
|
|
|
|
|
|
|
|
|
|
struct ds1305 {
|
|
|
|
struct spi_device *spi;
|
|
|
|
struct rtc_device *rtc;
|
|
|
|
|
|
|
|
struct work_struct work;
|
|
|
|
|
|
|
|
unsigned long flags;
|
|
|
|
#define FLAG_EXITING 0
|
|
|
|
|
|
|
|
bool hr12;
|
|
|
|
u8 ctrl[DS1305_CONTROL_LEN];
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Utilities ... tolerate 12-hour AM/PM notation in case of non-Linux
|
|
|
|
* software (like a bootloader) which may require it.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static unsigned bcd2hour(u8 bcd)
|
|
|
|
{
|
|
|
|
if (bcd & DS1305_HR_12) {
|
|
|
|
unsigned hour = 0;
|
|
|
|
|
|
|
|
bcd &= ~DS1305_HR_12;
|
|
|
|
if (bcd & DS1305_HR_PM) {
|
|
|
|
hour = 12;
|
|
|
|
bcd &= ~DS1305_HR_PM;
|
|
|
|
}
|
2008-10-19 10:28:41 +07:00
|
|
|
hour += bcd2bin(bcd);
|
2008-07-24 11:30:36 +07:00
|
|
|
return hour - 1;
|
|
|
|
}
|
2008-10-19 10:28:41 +07:00
|
|
|
return bcd2bin(bcd);
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static u8 hour2bcd(bool hr12, int hour)
|
|
|
|
{
|
|
|
|
if (hr12) {
|
|
|
|
hour++;
|
|
|
|
if (hour <= 12)
|
2008-10-19 10:28:41 +07:00
|
|
|
return DS1305_HR_12 | bin2bcd(hour);
|
2008-07-24 11:30:36 +07:00
|
|
|
hour -= 12;
|
2008-10-19 10:28:41 +07:00
|
|
|
return DS1305_HR_12 | DS1305_HR_PM | bin2bcd(hour);
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
2008-10-19 10:28:41 +07:00
|
|
|
return bin2bcd(hour);
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Interface to RTC framework
|
|
|
|
*/
|
|
|
|
|
2011-02-03 08:02:41 +07:00
|
|
|
static int ds1305_alarm_irq_enable(struct device *dev, unsigned int enabled)
|
2008-07-24 11:30:36 +07:00
|
|
|
{
|
|
|
|
struct ds1305 *ds1305 = dev_get_drvdata(dev);
|
|
|
|
u8 buf[2];
|
2011-02-03 08:02:41 +07:00
|
|
|
long err = -EINVAL;
|
2008-07-24 11:30:36 +07:00
|
|
|
|
|
|
|
buf[0] = DS1305_WRITE | DS1305_CONTROL;
|
|
|
|
buf[1] = ds1305->ctrl[0];
|
|
|
|
|
2011-02-03 08:02:41 +07:00
|
|
|
if (enabled) {
|
2008-07-24 11:30:36 +07:00
|
|
|
if (ds1305->ctrl[0] & DS1305_AEI0)
|
|
|
|
goto done;
|
|
|
|
buf[1] |= DS1305_AEI0;
|
2011-02-03 08:02:41 +07:00
|
|
|
} else {
|
|
|
|
if (!(buf[1] & DS1305_AEI0))
|
|
|
|
goto done;
|
|
|
|
buf[1] &= ~DS1305_AEI0;
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
2013-07-04 05:05:47 +07:00
|
|
|
err = spi_write_then_read(ds1305->spi, buf, sizeof(buf), NULL, 0);
|
2011-02-03 08:02:41 +07:00
|
|
|
if (err >= 0)
|
|
|
|
ds1305->ctrl[0] = buf[1];
|
2008-07-24 11:30:36 +07:00
|
|
|
done:
|
2011-02-03 08:02:41 +07:00
|
|
|
return err;
|
|
|
|
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get/set of date and time is pretty normal.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int ds1305_get_time(struct device *dev, struct rtc_time *time)
|
|
|
|
{
|
|
|
|
struct ds1305 *ds1305 = dev_get_drvdata(dev);
|
|
|
|
u8 addr = DS1305_SEC;
|
|
|
|
u8 buf[DS1305_RTC_LEN];
|
|
|
|
int status;
|
|
|
|
|
|
|
|
/* Use write-then-read to get all the date/time registers
|
|
|
|
* since dma from stack is nonportable
|
|
|
|
*/
|
2013-07-04 05:05:47 +07:00
|
|
|
status = spi_write_then_read(ds1305->spi, &addr, sizeof(addr),
|
|
|
|
buf, sizeof(buf));
|
2008-07-24 11:30:36 +07:00
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
dev_vdbg(dev, "%s: %02x %02x %02x, %02x %02x %02x %02x\n",
|
|
|
|
"read", buf[0], buf[1], buf[2], buf[3],
|
|
|
|
buf[4], buf[5], buf[6]);
|
|
|
|
|
|
|
|
/* Decode the registers */
|
2008-10-19 10:28:41 +07:00
|
|
|
time->tm_sec = bcd2bin(buf[DS1305_SEC]);
|
|
|
|
time->tm_min = bcd2bin(buf[DS1305_MIN]);
|
2008-07-24 11:30:36 +07:00
|
|
|
time->tm_hour = bcd2hour(buf[DS1305_HOUR]);
|
|
|
|
time->tm_wday = buf[DS1305_WDAY] - 1;
|
2008-10-19 10:28:41 +07:00
|
|
|
time->tm_mday = bcd2bin(buf[DS1305_MDAY]);
|
|
|
|
time->tm_mon = bcd2bin(buf[DS1305_MON]) - 1;
|
|
|
|
time->tm_year = bcd2bin(buf[DS1305_YEAR]) + 100;
|
2008-07-24 11:30:36 +07:00
|
|
|
|
|
|
|
dev_vdbg(dev, "%s secs=%d, mins=%d, "
|
|
|
|
"hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
|
|
|
|
"read", time->tm_sec, time->tm_min,
|
|
|
|
time->tm_hour, time->tm_mday,
|
|
|
|
time->tm_mon, time->tm_year, time->tm_wday);
|
|
|
|
|
|
|
|
/* Time may not be set */
|
|
|
|
return rtc_valid_tm(time);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ds1305_set_time(struct device *dev, struct rtc_time *time)
|
|
|
|
{
|
|
|
|
struct ds1305 *ds1305 = dev_get_drvdata(dev);
|
|
|
|
u8 buf[1 + DS1305_RTC_LEN];
|
|
|
|
u8 *bp = buf;
|
|
|
|
|
|
|
|
dev_vdbg(dev, "%s secs=%d, mins=%d, "
|
|
|
|
"hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
|
|
|
|
"write", time->tm_sec, time->tm_min,
|
|
|
|
time->tm_hour, time->tm_mday,
|
|
|
|
time->tm_mon, time->tm_year, time->tm_wday);
|
|
|
|
|
|
|
|
/* Write registers starting at the first time/date address. */
|
|
|
|
*bp++ = DS1305_WRITE | DS1305_SEC;
|
|
|
|
|
2008-10-19 10:28:41 +07:00
|
|
|
*bp++ = bin2bcd(time->tm_sec);
|
|
|
|
*bp++ = bin2bcd(time->tm_min);
|
2008-07-24 11:30:36 +07:00
|
|
|
*bp++ = hour2bcd(ds1305->hr12, time->tm_hour);
|
|
|
|
*bp++ = (time->tm_wday < 7) ? (time->tm_wday + 1) : 1;
|
2008-10-19 10:28:41 +07:00
|
|
|
*bp++ = bin2bcd(time->tm_mday);
|
|
|
|
*bp++ = bin2bcd(time->tm_mon + 1);
|
|
|
|
*bp++ = bin2bcd(time->tm_year - 100);
|
2008-07-24 11:30:36 +07:00
|
|
|
|
|
|
|
dev_dbg(dev, "%s: %02x %02x %02x, %02x %02x %02x %02x\n",
|
|
|
|
"write", buf[1], buf[2], buf[3],
|
|
|
|
buf[4], buf[5], buf[6], buf[7]);
|
|
|
|
|
|
|
|
/* use write-then-read since dma from stack is nonportable */
|
2013-07-04 05:05:47 +07:00
|
|
|
return spi_write_then_read(ds1305->spi, buf, sizeof(buf),
|
2008-07-24 11:30:36 +07:00
|
|
|
NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get/set of alarm is a bit funky:
|
|
|
|
*
|
|
|
|
* - First there's the inherent raciness of getting the (partitioned)
|
|
|
|
* status of an alarm that could trigger while we're reading parts
|
|
|
|
* of that status.
|
|
|
|
*
|
|
|
|
* - Second there's its limited range (we could increase it a bit by
|
|
|
|
* relying on WDAY), which means it will easily roll over.
|
|
|
|
*
|
|
|
|
* - Third there's the choice of two alarms and alarm signals.
|
|
|
|
* Here we use ALM0 and expect that nINT0 (open drain) is used;
|
|
|
|
* that's the only real option for DS1306 runtime alarms, and is
|
|
|
|
* natural on DS1305.
|
|
|
|
*
|
|
|
|
* - Fourth, there's also ALM1, and a second interrupt signal:
|
|
|
|
* + On DS1305 ALM1 uses nINT1 (when INTCN=1) else nINT0;
|
|
|
|
* + On DS1306 ALM1 only uses INT1 (an active high pulse)
|
|
|
|
* and it won't work when VCC1 is active.
|
|
|
|
*
|
|
|
|
* So to be most general, we should probably set both alarms to the
|
|
|
|
* same value, letting ALM1 be the wakeup event source on DS1306
|
|
|
|
* and handling several wiring options on DS1305.
|
|
|
|
*
|
|
|
|
* - Fifth, we support the polled mode (as well as possible; why not?)
|
|
|
|
* even when no interrupt line is wired to an IRQ.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Context: caller holds rtc->ops_lock (to protect ds1305->ctrl)
|
|
|
|
*/
|
|
|
|
static int ds1305_get_alarm(struct device *dev, struct rtc_wkalrm *alm)
|
|
|
|
{
|
|
|
|
struct ds1305 *ds1305 = dev_get_drvdata(dev);
|
|
|
|
struct spi_device *spi = ds1305->spi;
|
|
|
|
u8 addr;
|
|
|
|
int status;
|
|
|
|
u8 buf[DS1305_ALM_LEN];
|
|
|
|
|
|
|
|
/* Refresh control register cache BEFORE reading ALM0 registers,
|
|
|
|
* since reading alarm registers acks any pending IRQ. That
|
|
|
|
* makes returning "pending" status a bit of a lie, but that bit
|
|
|
|
* of EFI status is at best fragile anyway (given IRQ handlers).
|
|
|
|
*/
|
|
|
|
addr = DS1305_CONTROL;
|
2013-07-04 05:05:47 +07:00
|
|
|
status = spi_write_then_read(spi, &addr, sizeof(addr),
|
|
|
|
ds1305->ctrl, sizeof(ds1305->ctrl));
|
2008-07-24 11:30:36 +07:00
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
alm->enabled = !!(ds1305->ctrl[0] & DS1305_AEI0);
|
|
|
|
alm->pending = !!(ds1305->ctrl[1] & DS1305_AEI0);
|
|
|
|
|
|
|
|
/* get and check ALM0 registers */
|
|
|
|
addr = DS1305_ALM0(DS1305_SEC);
|
2013-07-04 05:05:47 +07:00
|
|
|
status = spi_write_then_read(spi, &addr, sizeof(addr),
|
|
|
|
buf, sizeof(buf));
|
2008-07-24 11:30:36 +07:00
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
dev_vdbg(dev, "%s: %02x %02x %02x %02x\n",
|
|
|
|
"alm0 read", buf[DS1305_SEC], buf[DS1305_MIN],
|
|
|
|
buf[DS1305_HOUR], buf[DS1305_WDAY]);
|
|
|
|
|
|
|
|
if ((DS1305_ALM_DISABLE & buf[DS1305_SEC])
|
|
|
|
|| (DS1305_ALM_DISABLE & buf[DS1305_MIN])
|
|
|
|
|| (DS1305_ALM_DISABLE & buf[DS1305_HOUR]))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
/* Stuff these values into alm->time and let RTC framework code
|
|
|
|
* fill in the rest ... and also handle rollover to tomorrow when
|
|
|
|
* that's needed.
|
|
|
|
*/
|
2008-10-19 10:28:41 +07:00
|
|
|
alm->time.tm_sec = bcd2bin(buf[DS1305_SEC]);
|
|
|
|
alm->time.tm_min = bcd2bin(buf[DS1305_MIN]);
|
2008-07-24 11:30:36 +07:00
|
|
|
alm->time.tm_hour = bcd2hour(buf[DS1305_HOUR]);
|
|
|
|
alm->time.tm_mday = -1;
|
|
|
|
alm->time.tm_mon = -1;
|
|
|
|
alm->time.tm_year = -1;
|
|
|
|
/* next three fields are unused by Linux */
|
|
|
|
alm->time.tm_wday = -1;
|
|
|
|
alm->time.tm_mday = -1;
|
|
|
|
alm->time.tm_isdst = -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Context: caller holds rtc->ops_lock (to protect ds1305->ctrl)
|
|
|
|
*/
|
|
|
|
static int ds1305_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
|
|
|
|
{
|
|
|
|
struct ds1305 *ds1305 = dev_get_drvdata(dev);
|
|
|
|
struct spi_device *spi = ds1305->spi;
|
|
|
|
unsigned long now, later;
|
|
|
|
struct rtc_time tm;
|
|
|
|
int status;
|
|
|
|
u8 buf[1 + DS1305_ALM_LEN];
|
|
|
|
|
|
|
|
/* convert desired alarm to time_t */
|
|
|
|
status = rtc_tm_to_time(&alm->time, &later);
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
/* Read current time as time_t */
|
|
|
|
status = ds1305_get_time(dev, &tm);
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
status = rtc_tm_to_time(&tm, &now);
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
/* make sure alarm fires within the next 24 hours */
|
|
|
|
if (later <= now)
|
|
|
|
return -EINVAL;
|
|
|
|
if ((later - now) > 24 * 60 * 60)
|
|
|
|
return -EDOM;
|
|
|
|
|
|
|
|
/* disable alarm if needed */
|
|
|
|
if (ds1305->ctrl[0] & DS1305_AEI0) {
|
|
|
|
ds1305->ctrl[0] &= ~DS1305_AEI0;
|
|
|
|
|
|
|
|
buf[0] = DS1305_WRITE | DS1305_CONTROL;
|
|
|
|
buf[1] = ds1305->ctrl[0];
|
|
|
|
status = spi_write_then_read(ds1305->spi, buf, 2, NULL, 0);
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* write alarm */
|
|
|
|
buf[0] = DS1305_WRITE | DS1305_ALM0(DS1305_SEC);
|
2008-10-19 10:28:41 +07:00
|
|
|
buf[1 + DS1305_SEC] = bin2bcd(alm->time.tm_sec);
|
|
|
|
buf[1 + DS1305_MIN] = bin2bcd(alm->time.tm_min);
|
2008-07-24 11:30:36 +07:00
|
|
|
buf[1 + DS1305_HOUR] = hour2bcd(ds1305->hr12, alm->time.tm_hour);
|
|
|
|
buf[1 + DS1305_WDAY] = DS1305_ALM_DISABLE;
|
|
|
|
|
|
|
|
dev_dbg(dev, "%s: %02x %02x %02x %02x\n",
|
|
|
|
"alm0 write", buf[1 + DS1305_SEC], buf[1 + DS1305_MIN],
|
|
|
|
buf[1 + DS1305_HOUR], buf[1 + DS1305_WDAY]);
|
|
|
|
|
2013-07-04 05:05:47 +07:00
|
|
|
status = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);
|
2008-07-24 11:30:36 +07:00
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
/* enable alarm if requested */
|
|
|
|
if (alm->enabled) {
|
|
|
|
ds1305->ctrl[0] |= DS1305_AEI0;
|
|
|
|
|
|
|
|
buf[0] = DS1305_WRITE | DS1305_CONTROL;
|
|
|
|
buf[1] = ds1305->ctrl[0];
|
|
|
|
status = spi_write_then_read(ds1305->spi, buf, 2, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
|
|
|
|
|
|
static int ds1305_proc(struct device *dev, struct seq_file *seq)
|
|
|
|
{
|
|
|
|
struct ds1305 *ds1305 = dev_get_drvdata(dev);
|
|
|
|
char *diodes = "no";
|
|
|
|
char *resistors = "";
|
|
|
|
|
|
|
|
/* ctrl[2] is treated as read-only; no locking needed */
|
|
|
|
if ((ds1305->ctrl[2] & 0xf0) == DS1305_TRICKLE_MAGIC) {
|
|
|
|
switch (ds1305->ctrl[2] & 0x0c) {
|
|
|
|
case DS1305_TRICKLE_DS2:
|
|
|
|
diodes = "2 diodes, ";
|
|
|
|
break;
|
|
|
|
case DS1305_TRICKLE_DS1:
|
|
|
|
diodes = "1 diode, ";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
switch (ds1305->ctrl[2] & 0x03) {
|
|
|
|
case DS1305_TRICKLE_2K:
|
|
|
|
resistors = "2k Ohm";
|
|
|
|
break;
|
|
|
|
case DS1305_TRICKLE_4K:
|
|
|
|
resistors = "4k Ohm";
|
|
|
|
break;
|
|
|
|
case DS1305_TRICKLE_8K:
|
|
|
|
resistors = "8k Ohm";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
diodes = "no";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
2015-04-16 06:17:51 +07:00
|
|
|
seq_printf(seq, "trickle_charge\t: %s%s\n", diodes, resistors);
|
|
|
|
|
|
|
|
return 0;
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
#define ds1305_proc NULL
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct rtc_class_ops ds1305_ops = {
|
|
|
|
.read_time = ds1305_get_time,
|
|
|
|
.set_time = ds1305_set_time,
|
|
|
|
.read_alarm = ds1305_get_alarm,
|
|
|
|
.set_alarm = ds1305_set_alarm,
|
|
|
|
.proc = ds1305_proc,
|
2011-02-03 08:02:41 +07:00
|
|
|
.alarm_irq_enable = ds1305_alarm_irq_enable,
|
2008-07-24 11:30:36 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static void ds1305_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct ds1305 *ds1305 = container_of(work, struct ds1305, work);
|
|
|
|
struct mutex *lock = &ds1305->rtc->ops_lock;
|
|
|
|
struct spi_device *spi = ds1305->spi;
|
|
|
|
u8 buf[3];
|
|
|
|
int status;
|
|
|
|
|
|
|
|
/* lock to protect ds1305->ctrl */
|
|
|
|
mutex_lock(lock);
|
|
|
|
|
|
|
|
/* Disable the IRQ, and clear its status ... for now, we "know"
|
|
|
|
* that if more than one alarm is active, they're in sync.
|
|
|
|
* Note that reading ALM data registers also clears IRQ status.
|
|
|
|
*/
|
|
|
|
ds1305->ctrl[0] &= ~(DS1305_AEI1 | DS1305_AEI0);
|
|
|
|
ds1305->ctrl[1] = 0;
|
|
|
|
|
|
|
|
buf[0] = DS1305_WRITE | DS1305_CONTROL;
|
|
|
|
buf[1] = ds1305->ctrl[0];
|
|
|
|
buf[2] = 0;
|
|
|
|
|
2013-07-04 05:05:47 +07:00
|
|
|
status = spi_write_then_read(spi, buf, sizeof(buf),
|
2008-07-24 11:30:36 +07:00
|
|
|
NULL, 0);
|
|
|
|
if (status < 0)
|
|
|
|
dev_dbg(&spi->dev, "clear irq --> %d\n", status);
|
|
|
|
|
|
|
|
mutex_unlock(lock);
|
|
|
|
|
|
|
|
if (!test_bit(FLAG_EXITING, &ds1305->flags))
|
|
|
|
enable_irq(spi->irq);
|
|
|
|
|
|
|
|
rtc_update_irq(ds1305->rtc, 1, RTC_AF | RTC_IRQF);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This "real" IRQ handler hands off to a workqueue mostly to allow
|
|
|
|
* mutex locking for ds1305->ctrl ... unlike I2C, we could issue async
|
|
|
|
* I/O requests in IRQ context (to clear the IRQ status).
|
|
|
|
*/
|
|
|
|
static irqreturn_t ds1305_irq(int irq, void *p)
|
|
|
|
{
|
|
|
|
struct ds1305 *ds1305 = p;
|
|
|
|
|
|
|
|
disable_irq(irq);
|
|
|
|
schedule_work(&ds1305->work);
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Interface for NVRAM
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void msg_init(struct spi_message *m, struct spi_transfer *x,
|
|
|
|
u8 *addr, size_t count, char *tx, char *rx)
|
|
|
|
{
|
|
|
|
spi_message_init(m);
|
|
|
|
memset(x, 0, 2 * sizeof(*x));
|
|
|
|
|
|
|
|
x->tx_buf = addr;
|
|
|
|
x->len = 1;
|
|
|
|
spi_message_add_tail(x, m);
|
|
|
|
|
|
|
|
x++;
|
|
|
|
|
|
|
|
x->tx_buf = tx;
|
|
|
|
x->rx_buf = rx;
|
|
|
|
x->len = count;
|
|
|
|
spi_message_add_tail(x, m);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
2010-05-13 08:28:57 +07:00
|
|
|
ds1305_nvram_read(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr,
|
2008-07-24 11:30:36 +07:00
|
|
|
char *buf, loff_t off, size_t count)
|
|
|
|
{
|
|
|
|
struct spi_device *spi;
|
|
|
|
u8 addr;
|
|
|
|
struct spi_message m;
|
|
|
|
struct spi_transfer x[2];
|
|
|
|
int status;
|
|
|
|
|
|
|
|
spi = container_of(kobj, struct spi_device, dev.kobj);
|
|
|
|
|
|
|
|
if (unlikely(off >= DS1305_NVRAM_LEN))
|
|
|
|
return 0;
|
|
|
|
if (count >= DS1305_NVRAM_LEN)
|
|
|
|
count = DS1305_NVRAM_LEN;
|
|
|
|
if ((off + count) > DS1305_NVRAM_LEN)
|
|
|
|
count = DS1305_NVRAM_LEN - off;
|
|
|
|
if (unlikely(!count))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
addr = DS1305_NVRAM + off;
|
|
|
|
msg_init(&m, x, &addr, count, NULL, buf);
|
|
|
|
|
|
|
|
status = spi_sync(spi, &m);
|
|
|
|
if (status < 0)
|
|
|
|
dev_err(&spi->dev, "nvram %s error %d\n", "read", status);
|
|
|
|
return (status < 0) ? status : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
2010-05-13 08:28:57 +07:00
|
|
|
ds1305_nvram_write(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr,
|
2008-07-24 11:30:36 +07:00
|
|
|
char *buf, loff_t off, size_t count)
|
|
|
|
{
|
|
|
|
struct spi_device *spi;
|
|
|
|
u8 addr;
|
|
|
|
struct spi_message m;
|
|
|
|
struct spi_transfer x[2];
|
|
|
|
int status;
|
|
|
|
|
|
|
|
spi = container_of(kobj, struct spi_device, dev.kobj);
|
|
|
|
|
|
|
|
if (unlikely(off >= DS1305_NVRAM_LEN))
|
|
|
|
return -EFBIG;
|
|
|
|
if (count >= DS1305_NVRAM_LEN)
|
|
|
|
count = DS1305_NVRAM_LEN;
|
|
|
|
if ((off + count) > DS1305_NVRAM_LEN)
|
|
|
|
count = DS1305_NVRAM_LEN - off;
|
|
|
|
if (unlikely(!count))
|
|
|
|
return count;
|
|
|
|
|
|
|
|
addr = (DS1305_WRITE | DS1305_NVRAM) + off;
|
|
|
|
msg_init(&m, x, &addr, count, buf, NULL);
|
|
|
|
|
|
|
|
status = spi_sync(spi, &m);
|
|
|
|
if (status < 0)
|
|
|
|
dev_err(&spi->dev, "nvram %s error %d\n", "write", status);
|
|
|
|
return (status < 0) ? status : count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct bin_attribute nvram = {
|
|
|
|
.attr.name = "nvram",
|
|
|
|
.attr.mode = S_IRUGO | S_IWUSR,
|
|
|
|
.read = ds1305_nvram_read,
|
|
|
|
.write = ds1305_nvram_write,
|
|
|
|
.size = DS1305_NVRAM_LEN,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Interface to SPI stack
|
|
|
|
*/
|
|
|
|
|
2012-12-22 04:09:38 +07:00
|
|
|
static int ds1305_probe(struct spi_device *spi)
|
2008-07-24 11:30:36 +07:00
|
|
|
{
|
|
|
|
struct ds1305 *ds1305;
|
|
|
|
int status;
|
|
|
|
u8 addr, value;
|
2013-11-13 06:10:40 +07:00
|
|
|
struct ds1305_platform_data *pdata = dev_get_platdata(&spi->dev);
|
2008-07-24 11:30:36 +07:00
|
|
|
bool write_ctrl = false;
|
|
|
|
|
|
|
|
/* Sanity check board setup data. This may be hooked up
|
|
|
|
* in 3wire mode, but we don't care. Note that unless
|
|
|
|
* there's an inverter in place, this needs SPI_CS_HIGH!
|
|
|
|
*/
|
|
|
|
if ((spi->bits_per_word && spi->bits_per_word != 8)
|
|
|
|
|| (spi->max_speed_hz > 2000000)
|
|
|
|
|| !(spi->mode & SPI_CPHA))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* set up driver data */
|
2013-04-30 06:20:29 +07:00
|
|
|
ds1305 = devm_kzalloc(&spi->dev, sizeof(*ds1305), GFP_KERNEL);
|
2008-07-24 11:30:36 +07:00
|
|
|
if (!ds1305)
|
|
|
|
return -ENOMEM;
|
|
|
|
ds1305->spi = spi;
|
|
|
|
spi_set_drvdata(spi, ds1305);
|
|
|
|
|
|
|
|
/* read and cache control registers */
|
|
|
|
addr = DS1305_CONTROL;
|
2013-07-04 05:05:47 +07:00
|
|
|
status = spi_write_then_read(spi, &addr, sizeof(addr),
|
|
|
|
ds1305->ctrl, sizeof(ds1305->ctrl));
|
2008-07-24 11:30:36 +07:00
|
|
|
if (status < 0) {
|
|
|
|
dev_dbg(&spi->dev, "can't %s, %d\n",
|
|
|
|
"read", status);
|
2013-04-30 06:20:29 +07:00
|
|
|
return status;
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
2013-02-22 07:44:22 +07:00
|
|
|
dev_dbg(&spi->dev, "ctrl %s: %3ph\n", "read", ds1305->ctrl);
|
2008-07-24 11:30:36 +07:00
|
|
|
|
|
|
|
/* Sanity check register values ... partially compensating for the
|
|
|
|
* fact that SPI has no device handshake. A pullup on MISO would
|
|
|
|
* make these tests fail; but not all systems will have one. If
|
|
|
|
* some register is neither 0x00 nor 0xff, a chip is likely there.
|
|
|
|
*/
|
|
|
|
if ((ds1305->ctrl[0] & 0x38) != 0 || (ds1305->ctrl[1] & 0xfc) != 0) {
|
|
|
|
dev_dbg(&spi->dev, "RTC chip is not present\n");
|
2013-04-30 06:20:29 +07:00
|
|
|
return -ENODEV;
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
if (ds1305->ctrl[2] == 0)
|
|
|
|
dev_dbg(&spi->dev, "chip may not be present\n");
|
|
|
|
|
|
|
|
/* enable writes if needed ... if we were paranoid it would
|
|
|
|
* make sense to enable them only when absolutely necessary.
|
|
|
|
*/
|
|
|
|
if (ds1305->ctrl[0] & DS1305_WP) {
|
|
|
|
u8 buf[2];
|
|
|
|
|
|
|
|
ds1305->ctrl[0] &= ~DS1305_WP;
|
|
|
|
|
|
|
|
buf[0] = DS1305_WRITE | DS1305_CONTROL;
|
|
|
|
buf[1] = ds1305->ctrl[0];
|
2013-07-04 05:05:47 +07:00
|
|
|
status = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);
|
2008-07-24 11:30:36 +07:00
|
|
|
|
|
|
|
dev_dbg(&spi->dev, "clear WP --> %d\n", status);
|
|
|
|
if (status < 0)
|
2013-04-30 06:20:29 +07:00
|
|
|
return status;
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* on DS1305, maybe start oscillator; like most low power
|
|
|
|
* oscillators, it may take a second to stabilize
|
|
|
|
*/
|
|
|
|
if (ds1305->ctrl[0] & DS1305_nEOSC) {
|
|
|
|
ds1305->ctrl[0] &= ~DS1305_nEOSC;
|
|
|
|
write_ctrl = true;
|
|
|
|
dev_warn(&spi->dev, "SET TIME!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ack any pending IRQs */
|
|
|
|
if (ds1305->ctrl[1]) {
|
|
|
|
ds1305->ctrl[1] = 0;
|
|
|
|
write_ctrl = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* this may need one-time (re)init */
|
|
|
|
if (pdata) {
|
|
|
|
/* maybe enable trickle charge */
|
|
|
|
if (((ds1305->ctrl[2] & 0xf0) != DS1305_TRICKLE_MAGIC)) {
|
|
|
|
ds1305->ctrl[2] = DS1305_TRICKLE_MAGIC
|
|
|
|
| pdata->trickle;
|
|
|
|
write_ctrl = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* on DS1306, configure 1 Hz signal */
|
|
|
|
if (pdata->is_ds1306) {
|
|
|
|
if (pdata->en_1hz) {
|
|
|
|
if (!(ds1305->ctrl[0] & DS1306_1HZ)) {
|
|
|
|
ds1305->ctrl[0] |= DS1306_1HZ;
|
|
|
|
write_ctrl = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (ds1305->ctrl[0] & DS1306_1HZ) {
|
|
|
|
ds1305->ctrl[0] &= ~DS1306_1HZ;
|
|
|
|
write_ctrl = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (write_ctrl) {
|
|
|
|
u8 buf[4];
|
|
|
|
|
|
|
|
buf[0] = DS1305_WRITE | DS1305_CONTROL;
|
|
|
|
buf[1] = ds1305->ctrl[0];
|
|
|
|
buf[2] = ds1305->ctrl[1];
|
|
|
|
buf[3] = ds1305->ctrl[2];
|
2013-07-04 05:05:47 +07:00
|
|
|
status = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);
|
2008-07-24 11:30:36 +07:00
|
|
|
if (status < 0) {
|
|
|
|
dev_dbg(&spi->dev, "can't %s, %d\n",
|
|
|
|
"write", status);
|
2013-04-30 06:20:29 +07:00
|
|
|
return status;
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
2013-02-22 07:44:22 +07:00
|
|
|
dev_dbg(&spi->dev, "ctrl %s: %3ph\n", "write", ds1305->ctrl);
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* see if non-Linux software set up AM/PM mode */
|
|
|
|
addr = DS1305_HOUR;
|
2013-07-04 05:05:47 +07:00
|
|
|
status = spi_write_then_read(spi, &addr, sizeof(addr),
|
|
|
|
&value, sizeof(value));
|
2008-07-24 11:30:36 +07:00
|
|
|
if (status < 0) {
|
|
|
|
dev_dbg(&spi->dev, "read HOUR --> %d\n", status);
|
2013-04-30 06:20:29 +07:00
|
|
|
return status;
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
ds1305->hr12 = (DS1305_HR_12 & value) != 0;
|
|
|
|
if (ds1305->hr12)
|
|
|
|
dev_dbg(&spi->dev, "AM/PM\n");
|
|
|
|
|
|
|
|
/* register RTC ... from here on, ds1305->ctrl needs locking */
|
2013-04-30 06:20:29 +07:00
|
|
|
ds1305->rtc = devm_rtc_device_register(&spi->dev, "ds1305",
|
2008-07-24 11:30:36 +07:00
|
|
|
&ds1305_ops, THIS_MODULE);
|
2009-12-16 07:45:53 +07:00
|
|
|
if (IS_ERR(ds1305->rtc)) {
|
|
|
|
status = PTR_ERR(ds1305->rtc);
|
2008-07-24 11:30:36 +07:00
|
|
|
dev_dbg(&spi->dev, "register rtc --> %d\n", status);
|
2013-04-30 06:20:29 +07:00
|
|
|
return status;
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Maybe set up alarm IRQ; be ready to handle it triggering right
|
|
|
|
* away. NOTE that we don't share this. The signal is active low,
|
|
|
|
* and we can't ack it before a SPI message delay. We temporarily
|
|
|
|
* disable the IRQ until it's acked, which lets us work with more
|
|
|
|
* IRQ trigger modes (not all IRQ controllers can do falling edge).
|
|
|
|
*/
|
|
|
|
if (spi->irq) {
|
|
|
|
INIT_WORK(&ds1305->work, ds1305_work);
|
2013-04-30 06:20:29 +07:00
|
|
|
status = devm_request_irq(&spi->dev, spi->irq, ds1305_irq,
|
2009-12-16 07:45:53 +07:00
|
|
|
0, dev_name(&ds1305->rtc->dev), ds1305);
|
2008-07-24 11:30:36 +07:00
|
|
|
if (status < 0) {
|
2014-04-04 04:49:36 +07:00
|
|
|
dev_err(&spi->dev, "request_irq %d --> %d\n",
|
2008-07-24 11:30:36 +07:00
|
|
|
spi->irq, status);
|
2014-04-04 04:49:36 +07:00
|
|
|
} else {
|
|
|
|
device_set_wakeup_capable(&spi->dev, 1);
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* export NVRAM */
|
|
|
|
status = sysfs_create_bin_file(&spi->dev.kobj, &nvram);
|
|
|
|
if (status < 0) {
|
2014-04-04 04:49:36 +07:00
|
|
|
dev_err(&spi->dev, "register nvram --> %d\n", status);
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-22 04:09:38 +07:00
|
|
|
static int ds1305_remove(struct spi_device *spi)
|
2008-07-24 11:30:36 +07:00
|
|
|
{
|
2009-12-16 07:45:53 +07:00
|
|
|
struct ds1305 *ds1305 = spi_get_drvdata(spi);
|
2008-07-24 11:30:36 +07:00
|
|
|
|
|
|
|
sysfs_remove_bin_file(&spi->dev.kobj, &nvram);
|
|
|
|
|
|
|
|
/* carefully shut down irq and workqueue, if present */
|
|
|
|
if (spi->irq) {
|
|
|
|
set_bit(FLAG_EXITING, &ds1305->flags);
|
2013-04-30 06:20:29 +07:00
|
|
|
devm_free_irq(&spi->dev, spi->irq, ds1305);
|
2010-12-24 22:00:17 +07:00
|
|
|
cancel_work_sync(&ds1305->work);
|
2008-07-24 11:30:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct spi_driver ds1305_driver = {
|
|
|
|
.driver.name = "rtc-ds1305",
|
|
|
|
.driver.owner = THIS_MODULE,
|
|
|
|
.probe = ds1305_probe,
|
2012-12-22 04:09:38 +07:00
|
|
|
.remove = ds1305_remove,
|
2008-07-24 11:30:36 +07:00
|
|
|
/* REVISIT add suspend/resume */
|
|
|
|
};
|
|
|
|
|
2012-03-24 05:02:30 +07:00
|
|
|
module_spi_driver(ds1305_driver);
|
2008-07-24 11:30:36 +07:00
|
|
|
|
|
|
|
MODULE_DESCRIPTION("RTC driver for DS1305 and DS1306 chips");
|
|
|
|
MODULE_LICENSE("GPL");
|
2009-09-23 06:46:08 +07:00
|
|
|
MODULE_ALIAS("spi:rtc-ds1305");
|