summaryrefslogblamecommitdiff
path: root/drivers/rtc/rtc-ds1305.c
blob: 129add77065d66d355e4711d057b081fa7471a07 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                                                                       
                       




                             
                         

































































































                                                                             
                                     

                                
                            






                                       
                                                            
                           
                                                                   
         
                             







                                                                          
                                                                            


                                                       
                                      



                                               
                      


                                                  



                                            
         
                                                                          

                                         
     

                   

 














                                                                     

                                                                      







                                                                  

                                                

                                                   


                                                        

























                                                                      

                                      

                                                              


                                             





                                                                     
                                                                 















































                                                                        

                                                              







                                                         

                                                              















                                                                         

                                                    























































                                                                           

                                                        






                                                                         
                                                                     





























































                                                                           




                                           
                                                    























                                                                          
                                                           








                                                                 
                                                          








































                                                                          

                                                          




























                                                                          

                                                           






























                                                                           










                                                                          
                                               

                                                

                                                    
                                                                             











                                                                
                                                                      






                                              

                                                              


                                                    
                              

         
                                                                    







                                                                             
                               













                                                                    
                                                                             


                                                                
                                      
















































                                                                         
                                                                             


                                                            
                                      

                 
                                                                             



                                                         

                                                              

                                                                 
                              






                                                                       
                                                                   
                                                  

                                              
                                                                    
                              
         








                                                                           
                                                                          
                                                                        
                                 
                                                                     
                                                          

                                                                





                                                               
                                                                      


                 

 
                                                
 
                                                     





                                                               
                                                           
                                                

         






                                          
                                        


                                        
                                 


                                                             
                               
/*
 * 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 <linux/slab.h>
#include <linux/rtc.h>
#include <linux/workqueue.h>

#include <linux/spi/spi.h>
#include <linux/spi/ds1305.h>
#include <linux/module.h>


/*
 * 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;
		}
		hour += bcd2bin(bcd);
		return hour - 1;
	}
	return bcd2bin(bcd);
}

static u8 hour2bcd(bool hr12, int hour)
{
	if (hr12) {
		hour++;
		if (hour <= 12)
			return DS1305_HR_12 | bin2bcd(hour);
		hour -= 12;
		return DS1305_HR_12 | DS1305_HR_PM | bin2bcd(hour);
	}
	return bin2bcd(hour);
}

/*----------------------------------------------------------------------*/

/*
 * Interface to RTC framework
 */

static int ds1305_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
	struct ds1305	*ds1305 = dev_get_drvdata(dev);
	u8		buf[2];
	long		err = -EINVAL;

	buf[0] = DS1305_WRITE | DS1305_CONTROL;
	buf[1] = ds1305->ctrl[0];

	if (enabled) {
		if (ds1305->ctrl[0] & DS1305_AEI0)
			goto done;
		buf[1] |= DS1305_AEI0;
	} else {
		if (!(buf[1] & DS1305_AEI0))
			goto done;
		buf[1] &= ~DS1305_AEI0;
	}
	err = spi_write_then_read(ds1305->spi, buf, sizeof(buf), NULL, 0);
	if (err >= 0)
		ds1305->ctrl[0] = buf[1];
done:
	return err;

}


/*
 * 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
	 */
	status = spi_write_then_read(ds1305->spi, &addr, sizeof(addr),
			buf, sizeof(buf));
	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 */
	time->tm_sec = bcd2bin(buf[DS1305_SEC]);
	time->tm_min = bcd2bin(buf[DS1305_MIN]);
	time->tm_hour = bcd2hour(buf[DS1305_HOUR]);
	time->tm_wday = buf[DS1305_WDAY] - 1;
	time->tm_mday = bcd2bin(buf[DS1305_MDAY]);
	time->tm_mon = bcd2bin(buf[DS1305_MON]) - 1;
	time->tm_year = bcd2bin(buf[DS1305_YEAR]) + 100;

	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;

	*bp++ = bin2bcd(time->tm_sec);
	*bp++ = bin2bcd(time->tm_min);
	*bp++ = hour2bcd(ds1305->hr12, time->tm_hour);
	*bp++ = (time->tm_wday < 7) ? (time->tm_wday + 1) : 1;
	*bp++ = bin2bcd(time->tm_mday);
	*bp++ = bin2bcd(time->tm_mon + 1);
	*bp++ = bin2bcd(time->tm_year - 100);

	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 */
	return spi_write_then_read(ds1305->spi, buf, sizeof(buf),
			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;
	status = spi_write_then_read(spi, &addr, sizeof(addr),
			ds1305->ctrl, sizeof(ds1305->ctrl));
	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);
	status = spi_write_then_read(spi, &addr, sizeof(addr),
			buf, sizeof(buf));
	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.
	 */
	alm->time.tm_sec = bcd2bin(buf[DS1305_SEC]);
	alm->time.tm_min = bcd2bin(buf[DS1305_MIN]);
	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);
	buf[1 + DS1305_SEC] = bin2bcd(alm->time.tm_sec);
	buf[1 + DS1305_MIN] = bin2bcd(alm->time.tm_min);
	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]);

	status = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);
	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:
	return seq_printf(seq,
			"trickle_charge\t: %s%s\n",
			diodes, resistors);
}

#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,
	.alarm_irq_enable = ds1305_alarm_irq_enable,
};

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;

	status = spi_write_then_read(spi, buf, sizeof(buf),
			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
ds1305_nvram_read(struct file *filp, struct kobject *kobj,
		struct bin_attribute *attr,
		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
ds1305_nvram_write(struct file *filp, struct kobject *kobj,
		struct bin_attribute *attr,
		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
 */

static int ds1305_probe(struct spi_device *spi)
{
	struct ds1305			*ds1305;
	int				status;
	u8				addr, value;
	struct ds1305_platform_data	*pdata = dev_get_platdata(&spi->dev);
	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 */
	ds1305 = devm_kzalloc(&spi->dev, sizeof(*ds1305), GFP_KERNEL);
	if (!ds1305)
		return -ENOMEM;
	ds1305->spi = spi;
	spi_set_drvdata(spi, ds1305);

	/* read and cache control registers */
	addr = DS1305_CONTROL;
	status = spi_write_then_read(spi, &addr, sizeof(addr),
			ds1305->ctrl, sizeof(ds1305->ctrl));
	if (status < 0) {
		dev_dbg(&spi->dev, "can't %s, %d\n",
				"read", status);
		return status;
	}

	dev_dbg(&spi->dev, "ctrl %s: %3ph\n", "read", ds1305->ctrl);

	/* 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");
		return -ENODEV;
	}
	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];
		status = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);

		dev_dbg(&spi->dev, "clear WP --> %d\n", status);
		if (status < 0)
			return status;
	}

	/* 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];
		status = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);
		if (status < 0) {
			dev_dbg(&spi->dev, "can't %s, %d\n",
					"write", status);
			return status;
		}

		dev_dbg(&spi->dev, "ctrl %s: %3ph\n", "write", ds1305->ctrl);
	}

	/* see if non-Linux software set up AM/PM mode */
	addr = DS1305_HOUR;
	status = spi_write_then_read(spi, &addr, sizeof(addr),
				&value, sizeof(value));
	if (status < 0) {
		dev_dbg(&spi->dev, "read HOUR --> %d\n", status);
		return status;
	}

	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 */
	ds1305->rtc = devm_rtc_device_register(&spi->dev, "ds1305",
			&ds1305_ops, THIS_MODULE);
	if (IS_ERR(ds1305->rtc)) {
		status = PTR_ERR(ds1305->rtc);
		dev_dbg(&spi->dev, "register rtc --> %d\n", status);
		return status;
	}

	/* 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);
		status = devm_request_irq(&spi->dev, spi->irq, ds1305_irq,
				0, dev_name(&ds1305->rtc->dev), ds1305);
		if (status < 0) {
			dev_err(&spi->dev, "request_irq %d --> %d\n",
					spi->irq, status);
		} else {
			device_set_wakeup_capable(&spi->dev, 1);
		}
	}

	/* export NVRAM */
	status = sysfs_create_bin_file(&spi->dev.kobj, &nvram);
	if (status < 0) {
		dev_err(&spi->dev, "register nvram --> %d\n", status);
	}

	return 0;
}

static int ds1305_remove(struct spi_device *spi)
{
	struct ds1305 *ds1305 = spi_get_drvdata(spi);

	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);
		devm_free_irq(&spi->dev, spi->irq, ds1305);
		cancel_work_sync(&ds1305->work);
	}

	return 0;
}

static struct spi_driver ds1305_driver = {
	.driver.name	= "rtc-ds1305",
	.driver.owner	= THIS_MODULE,
	.probe		= ds1305_probe,
	.remove		= ds1305_remove,
	/* REVISIT add suspend/resume */
};

module_spi_driver(ds1305_driver);

MODULE_DESCRIPTION("RTC driver for DS1305 and DS1306 chips");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:rtc-ds1305");