rtc-rs5c348.c 7.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 * A SPI driver for the Ricoh RS5C348 RTC
 *
 * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
 *
 * 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.
 *
 * The board specific init code should provide characteristics of this
 * device:
 *     Mode 1 (High-Active, Shift-Then-Sample), High Avtive CS
 */

#include <linux/bcd.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/rtc.h>
#include <linux/workqueue.h>
#include <linux/spi/spi.h>

26
#define DRV_VERSION "0.2"
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

#define RS5C348_REG_SECS	0
#define RS5C348_REG_MINS	1
#define RS5C348_REG_HOURS	2
#define RS5C348_REG_WDAY	3
#define RS5C348_REG_DAY	4
#define RS5C348_REG_MONTH	5
#define RS5C348_REG_YEAR	6
#define RS5C348_REG_CTL1	14
#define RS5C348_REG_CTL2	15

#define RS5C348_SECS_MASK	0x7f
#define RS5C348_MINS_MASK	0x7f
#define RS5C348_HOURS_MASK	0x3f
#define RS5C348_WDAY_MASK	0x03
#define RS5C348_DAY_MASK	0x3f
#define RS5C348_MONTH_MASK	0x1f

#define RS5C348_BIT_PM	0x20	/* REG_HOURS */
#define RS5C348_BIT_Y2K	0x80	/* REG_MONTH */
#define RS5C348_BIT_24H	0x20	/* REG_CTL1 */
#define RS5C348_BIT_XSTP	0x10	/* REG_CTL2 */
#define RS5C348_BIT_VDET	0x40	/* REG_CTL2 */

#define RS5C348_CMD_W(addr)	(((addr) << 4) | 0x08)	/* single write */
#define RS5C348_CMD_R(addr)	(((addr) << 4) | 0x0c)	/* single read */
#define RS5C348_CMD_MW(addr)	(((addr) << 4) | 0x00)	/* burst write */
#define RS5C348_CMD_MR(addr)	(((addr) << 4) | 0x04)	/* burst read */

struct rs5c348_plat_data {
	struct rtc_device *rtc;
	int rtc_24h;
};

static int
rs5c348_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
	struct spi_device *spi = to_spi_device(dev);
	struct rs5c348_plat_data *pdata = spi->dev.platform_data;
	u8 txbuf[5+7], *txp;
	int ret;

	/* Transfer 5 bytes before writing SEC.  This gives 31us for carry. */
	txp = txbuf;
	txbuf[0] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
	txbuf[1] = 0;	/* dummy */
	txbuf[2] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
	txbuf[3] = 0;	/* dummy */
	txbuf[4] = RS5C348_CMD_MW(RS5C348_REG_SECS); /* cmd, sec, ... */
	txp = &txbuf[5];
A
Adrian Bunk 已提交
77 78
	txp[RS5C348_REG_SECS] = bin2bcd(tm->tm_sec);
	txp[RS5C348_REG_MINS] = bin2bcd(tm->tm_min);
79
	if (pdata->rtc_24h) {
A
Adrian Bunk 已提交
80
		txp[RS5C348_REG_HOURS] = bin2bcd(tm->tm_hour);
81 82
	} else {
		/* hour 0 is AM12, noon is PM12 */
A
Adrian Bunk 已提交
83
		txp[RS5C348_REG_HOURS] = bin2bcd((tm->tm_hour + 11) % 12 + 1) |
84 85
			(tm->tm_hour >= 12 ? RS5C348_BIT_PM : 0);
	}
A
Adrian Bunk 已提交
86 87 88
	txp[RS5C348_REG_WDAY] = bin2bcd(tm->tm_wday);
	txp[RS5C348_REG_DAY] = bin2bcd(tm->tm_mday);
	txp[RS5C348_REG_MONTH] = bin2bcd(tm->tm_mon + 1) |
89
		(tm->tm_year >= 100 ? RS5C348_BIT_Y2K : 0);
A
Adrian Bunk 已提交
90
	txp[RS5C348_REG_YEAR] = bin2bcd(tm->tm_year % 100);
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
	/* write in one transfer to avoid data inconsistency */
	ret = spi_write_then_read(spi, txbuf, sizeof(txbuf), NULL, 0);
	udelay(62);	/* Tcsr 62us */
	return ret;
}

static int
rs5c348_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
	struct spi_device *spi = to_spi_device(dev);
	struct rs5c348_plat_data *pdata = spi->dev.platform_data;
	u8 txbuf[5], rxbuf[7];
	int ret;

	/* Transfer 5 byte befores reading SEC.  This gives 31us for carry. */
	txbuf[0] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
	txbuf[1] = 0;	/* dummy */
	txbuf[2] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
	txbuf[3] = 0;	/* dummy */
	txbuf[4] = RS5C348_CMD_MR(RS5C348_REG_SECS); /* cmd, sec, ... */

	/* read in one transfer to avoid data inconsistency */
	ret = spi_write_then_read(spi, txbuf, sizeof(txbuf),
				  rxbuf, sizeof(rxbuf));
	udelay(62);	/* Tcsr 62us */
	if (ret < 0)
		return ret;

A
Adrian Bunk 已提交
119 120 121
	tm->tm_sec = bcd2bin(rxbuf[RS5C348_REG_SECS] & RS5C348_SECS_MASK);
	tm->tm_min = bcd2bin(rxbuf[RS5C348_REG_MINS] & RS5C348_MINS_MASK);
	tm->tm_hour = bcd2bin(rxbuf[RS5C348_REG_HOURS] & RS5C348_HOURS_MASK);
122 123 124 125 126
	if (!pdata->rtc_24h) {
		tm->tm_hour %= 12;
		if (rxbuf[RS5C348_REG_HOURS] & RS5C348_BIT_PM)
			tm->tm_hour += 12;
	}
A
Adrian Bunk 已提交
127 128
	tm->tm_wday = bcd2bin(rxbuf[RS5C348_REG_WDAY] & RS5C348_WDAY_MASK);
	tm->tm_mday = bcd2bin(rxbuf[RS5C348_REG_DAY] & RS5C348_DAY_MASK);
129
	tm->tm_mon =
A
Adrian Bunk 已提交
130
		bcd2bin(rxbuf[RS5C348_REG_MONTH] & RS5C348_MONTH_MASK) - 1;
131
	/* year is 1900 + tm->tm_year */
A
Adrian Bunk 已提交
132
	tm->tm_year = bcd2bin(rxbuf[RS5C348_REG_YEAR]) +
133 134 135 136 137 138 139 140 141 142
		((rxbuf[RS5C348_REG_MONTH] & RS5C348_BIT_Y2K) ? 100 : 0);

	if (rtc_valid_tm(tm) < 0) {
		dev_err(&spi->dev, "retrieved date/time is not valid.\n");
		rtc_time_to_tm(0, tm);
	}

	return 0;
}

143
static const struct rtc_class_ops rs5c348_rtc_ops = {
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
	.read_time	= rs5c348_rtc_read_time,
	.set_time	= rs5c348_rtc_set_time,
};

static struct spi_driver rs5c348_driver;

static int __devinit rs5c348_probe(struct spi_device *spi)
{
	int ret;
	struct rtc_device *rtc;
	struct rs5c348_plat_data *pdata;

	pdata = kzalloc(sizeof(struct rs5c348_plat_data), GFP_KERNEL);
	if (!pdata)
		return -ENOMEM;
	spi->dev.platform_data = pdata;

	/* Check D7 of SECOND register */
	ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_SECS));
	if (ret < 0 || (ret & 0x80)) {
		dev_err(&spi->dev, "not found.\n");
		goto kfree_exit;
	}

	dev_info(&spi->dev, "chip found, driver version " DRV_VERSION "\n");
	dev_info(&spi->dev, "spiclk %u KHz.\n",
		 (spi->max_speed_hz + 500) / 1000);

	/* turn RTC on if it was not on */
	ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_CTL2));
	if (ret < 0)
		goto kfree_exit;
	if (ret & (RS5C348_BIT_XSTP | RS5C348_BIT_VDET)) {
		u8 buf[2];
178
		struct rtc_time tm;
179 180
		if (ret & RS5C348_BIT_VDET)
			dev_warn(&spi->dev, "voltage-low detected.\n");
181 182 183 184 185 186
		if (ret & RS5C348_BIT_XSTP)
			dev_warn(&spi->dev, "oscillator-stop detected.\n");
		rtc_time_to_tm(0, &tm);	/* 1970/1/1 */
		ret = rs5c348_rtc_set_time(&spi->dev, &tm);
		if (ret < 0)
			goto kfree_exit;
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
		buf[0] = RS5C348_CMD_W(RS5C348_REG_CTL2);
		buf[1] = 0;
		ret = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);
		if (ret < 0)
			goto kfree_exit;
	}

	ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_CTL1));
	if (ret < 0)
		goto kfree_exit;
	if (ret & RS5C348_BIT_24H)
		pdata->rtc_24h = 1;

	rtc = rtc_device_register(rs5c348_driver.driver.name, &spi->dev,
				  &rs5c348_rtc_ops, THIS_MODULE);

	if (IS_ERR(rtc)) {
		ret = PTR_ERR(rtc);
		goto kfree_exit;
	}

	pdata->rtc = rtc;

	return 0;
 kfree_exit:
	kfree(pdata);
	return ret;
}

static int __devexit rs5c348_remove(struct spi_device *spi)
{
	struct rs5c348_plat_data *pdata = spi->dev.platform_data;
	struct rtc_device *rtc = pdata->rtc;

	if (rtc)
		rtc_device_unregister(rtc);
	kfree(pdata);
	return 0;
}

static struct spi_driver rs5c348_driver = {
	.driver = {
229
		.name	= "rtc-rs5c348",
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
		.bus	= &spi_bus_type,
		.owner	= THIS_MODULE,
	},
	.probe	= rs5c348_probe,
	.remove	= __devexit_p(rs5c348_remove),
};

static __init int rs5c348_init(void)
{
	return spi_register_driver(&rs5c348_driver);
}

static __exit void rs5c348_exit(void)
{
	spi_unregister_driver(&rs5c348_driver);
}

module_init(rs5c348_init);
module_exit(rs5c348_exit);

MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
MODULE_DESCRIPTION("Ricoh RS5C348 RTC driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);
254
MODULE_ALIAS("spi:rtc-rs5c348");