rtc-rs5c372.c 7.0 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 26 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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
/*
 * An I2C driver for the Ricoh RS5C372 RTC
 *
 * Copyright (C) 2005 Pavel Mironchik <pmironchik@optifacio.net>
 * Copyright (C) 2006 Tower Technologies
 *
 * 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/i2c.h>
#include <linux/rtc.h>
#include <linux/bcd.h>

#define DRV_VERSION "0.2"

/* Addresses to scan */
static unsigned short normal_i2c[] = { /* 0x32,*/ I2C_CLIENT_END };

/* Insmod parameters */
I2C_CLIENT_INSMOD;

#define RS5C372_REG_SECS	0
#define RS5C372_REG_MINS	1
#define RS5C372_REG_HOURS	2
#define RS5C372_REG_WDAY	3
#define RS5C372_REG_DAY		4
#define RS5C372_REG_MONTH	5
#define RS5C372_REG_YEAR	6
#define RS5C372_REG_TRIM	7

#define RS5C372_TRIM_XSL	0x80
#define RS5C372_TRIM_MASK	0x7F

#define RS5C372_REG_BASE	0

static int rs5c372_attach(struct i2c_adapter *adapter);
static int rs5c372_detach(struct i2c_client *client);
static int rs5c372_probe(struct i2c_adapter *adapter, int address, int kind);

static struct i2c_driver rs5c372_driver = {
	.driver		= {
		.name	= "rs5c372",
	},
	.attach_adapter	= &rs5c372_attach,
	.detach_client	= &rs5c372_detach,
};

static int rs5c372_get_datetime(struct i2c_client *client, struct rtc_time *tm)
{
	unsigned char buf[7] = { RS5C372_REG_BASE };

	/* this implements the 1st reading method, according
	 * to the datasheet. buf[0] is initialized with
	 * address ptr and transmission format register.
	 */
	struct i2c_msg msgs[] = {
		{ client->addr, 0, 1, buf },
		{ client->addr, I2C_M_RD, 7, buf },
	};

	if ((i2c_transfer(client->adapter, msgs, 2)) != 2) {
		dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
		return -EIO;
	}

	tm->tm_sec = BCD2BIN(buf[RS5C372_REG_SECS] & 0x7f);
	tm->tm_min = BCD2BIN(buf[RS5C372_REG_MINS] & 0x7f);
	tm->tm_hour = BCD2BIN(buf[RS5C372_REG_HOURS] & 0x3f);
	tm->tm_wday = BCD2BIN(buf[RS5C372_REG_WDAY] & 0x07);
	tm->tm_mday = BCD2BIN(buf[RS5C372_REG_DAY] & 0x3f);

	/* tm->tm_mon is zero-based */
	tm->tm_mon = BCD2BIN(buf[RS5C372_REG_MONTH] & 0x1f) - 1;

	/* year is 1900 + tm->tm_year */
	tm->tm_year = BCD2BIN(buf[RS5C372_REG_YEAR]) + 100;

	dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
		"mday=%d, mon=%d, year=%d, wday=%d\n",
		__FUNCTION__,
		tm->tm_sec, tm->tm_min, tm->tm_hour,
		tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

	return 0;
}

static int rs5c372_set_datetime(struct i2c_client *client, struct rtc_time *tm)
{
	unsigned char buf[8] = { RS5C372_REG_BASE };

	dev_dbg(&client->dev,
J
Jeff Garzik 已提交
94
		"%s: secs=%d, mins=%d, hours=%d "
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
		"mday=%d, mon=%d, year=%d, wday=%d\n",
		__FUNCTION__, tm->tm_sec, tm->tm_min, tm->tm_hour,
		tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);

	buf[1] = BIN2BCD(tm->tm_sec);
	buf[2] = BIN2BCD(tm->tm_min);
	buf[3] = BIN2BCD(tm->tm_hour);
	buf[4] = BIN2BCD(tm->tm_wday);
	buf[5] = BIN2BCD(tm->tm_mday);
	buf[6] = BIN2BCD(tm->tm_mon + 1);
	buf[7] = BIN2BCD(tm->tm_year - 100);

	if ((i2c_master_send(client, buf, 8)) != 8) {
		dev_err(&client->dev, "%s: write error\n", __FUNCTION__);
		return -EIO;
	}

	return 0;
}

static int rs5c372_get_trim(struct i2c_client *client, int *osc, int *trim)
{
	unsigned char buf = RS5C372_REG_TRIM;

	struct i2c_msg msgs[] = {
		{ client->addr, 0, 1, &buf },
		{ client->addr, I2C_M_RD, 1, &buf },
	};

	if ((i2c_transfer(client->adapter, msgs, 2)) != 2) {
		dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
		return -EIO;
	}

	if (osc)
		*osc = (buf & RS5C372_TRIM_XSL) ? 32000 : 32768;

132
	if (trim) {
133
		*trim = buf & RS5C372_TRIM_MASK;
134 135
		dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, *trim);
	}
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153

	return 0;
}

static int rs5c372_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
	return rs5c372_get_datetime(to_i2c_client(dev), tm);
}

static int rs5c372_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
	return rs5c372_set_datetime(to_i2c_client(dev), tm);
}

static int rs5c372_rtc_proc(struct device *dev, struct seq_file *seq)
{
	int err, osc, trim;

154 155
	err = rs5c372_get_trim(to_i2c_client(dev), &osc, &trim);
	if (err == 0) {
156 157 158 159 160 161 162
		seq_printf(seq, "%d.%03d KHz\n", osc / 1000, osc % 1000);
		seq_printf(seq, "trim\t: %d\n", trim);
	}

	return 0;
}

163
static const struct rtc_class_ops rs5c372_rtc_ops = {
164 165 166 167 168 169 170 171
	.proc		= rs5c372_rtc_proc,
	.read_time	= rs5c372_rtc_read_time,
	.set_time	= rs5c372_rtc_set_time,
};

static ssize_t rs5c372_sysfs_show_trim(struct device *dev,
				struct device_attribute *attr, char *buf)
{
172
	int err, trim;
173

174 175 176
	err = rs5c372_get_trim(to_i2c_client(dev), NULL, &trim);
	if (err)
		return err;
177

178
	return sprintf(buf, "0x%2x\n", trim);
179 180 181 182 183 184
}
static DEVICE_ATTR(trim, S_IRUGO, rs5c372_sysfs_show_trim, NULL);

static ssize_t rs5c372_sysfs_show_osc(struct device *dev,
				struct device_attribute *attr, char *buf)
{
185
	int err, osc;
186

187 188 189
	err = rs5c372_get_trim(to_i2c_client(dev), &osc, NULL);
	if (err)
		return err;
190

191
	return sprintf(buf, "%d.%03d KHz\n", osc / 1000, osc % 1000);
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 229 230 231 232 233 234 235 236 237 238 239 240
}
static DEVICE_ATTR(osc, S_IRUGO, rs5c372_sysfs_show_osc, NULL);

static int rs5c372_attach(struct i2c_adapter *adapter)
{
	return i2c_probe(adapter, &addr_data, rs5c372_probe);
}

static int rs5c372_probe(struct i2c_adapter *adapter, int address, int kind)
{
	int err = 0;
	struct i2c_client *client;
	struct rtc_device *rtc;

	dev_dbg(&adapter->dev, "%s\n", __FUNCTION__);

	if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
		err = -ENODEV;
		goto exit;
	}

	if (!(client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL))) {
		err = -ENOMEM;
		goto exit;
	}

	/* I2C client */
	client->addr = address;
	client->driver = &rs5c372_driver;
	client->adapter = adapter;

	strlcpy(client->name, rs5c372_driver.driver.name, I2C_NAME_SIZE);

	/* Inform the i2c layer */
	if ((err = i2c_attach_client(client)))
		goto exit_kfree;

	dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");

	rtc = rtc_device_register(rs5c372_driver.driver.name, &client->dev,
				&rs5c372_rtc_ops, THIS_MODULE);

	if (IS_ERR(rtc)) {
		err = PTR_ERR(rtc);
		goto exit_detach;
	}

	i2c_set_clientdata(client, rtc);

J
Jeff Garzik 已提交
241 242 243 244
	err = device_create_file(&client->dev, &dev_attr_trim);
	if (err) goto exit_devreg;
	err = device_create_file(&client->dev, &dev_attr_osc);
	if (err) goto exit_trim;
245 246 247

	return 0;

J
Jeff Garzik 已提交
248 249 250 251 252 253
exit_trim:
	device_remove_file(&client->dev, &dev_attr_trim);

exit_devreg:
	rtc_device_unregister(rtc);

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
exit_detach:
	i2c_detach_client(client);

exit_kfree:
	kfree(client);

exit:
	return err;
}

static int rs5c372_detach(struct i2c_client *client)
{
	int err;
	struct rtc_device *rtc = i2c_get_clientdata(client);

	if (rtc)
		rtc_device_unregister(rtc);

	if ((err = i2c_detach_client(client)))
		return err;

	kfree(client);

	return 0;
}

static __init int rs5c372_init(void)
{
	return i2c_add_driver(&rs5c372_driver);
}

static __exit void rs5c372_exit(void)
{
	i2c_del_driver(&rs5c372_driver);
}

module_init(rs5c372_init);
module_exit(rs5c372_exit);

MODULE_AUTHOR(
		"Pavel Mironchik <pmironchik@optifacio.net>, "
		"Alessandro Zummo <a.zummo@towertech.it>");
MODULE_DESCRIPTION("Ricoh RS5C372 RTC driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);