/* * linux/drivers/i2c/chips/ds1337.c * * Copyright (C) 2005 James Chapman * * based on linux/drivers/acorn/char/pcf8583.c * Copyright (C) 2000 Russell King * * 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. * * Driver for Dallas Semiconductor DS1337 real time clock chip */ #include #include #include #include #include #include #include #include /* get the user-level API */ #include #include /* Device registers */ #define DS1337_REG_HOUR 2 #define DS1337_REG_DAY 3 #define DS1337_REG_DATE 4 #define DS1337_REG_MONTH 5 #define DS1337_REG_CONTROL 14 #define DS1337_REG_STATUS 15 /* FIXME - how do we export these interface constants? */ #define DS1337_GET_DATE 0 #define DS1337_SET_DATE 1 /* * Functions declaration */ static unsigned short normal_i2c[] = { 0x68, I2C_CLIENT_END }; static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END }; SENSORS_INSMOD_1(ds1337); static int ds1337_attach_adapter(struct i2c_adapter *adapter); static int ds1337_detect(struct i2c_adapter *adapter, int address, int kind); static void ds1337_init_client(struct i2c_client *client); static int ds1337_detach_client(struct i2c_client *client); static int ds1337_command(struct i2c_client *client, unsigned int cmd, void *arg); /* * Driver data (common to all clients) */ static struct i2c_driver ds1337_driver = { .owner = THIS_MODULE, .name = "ds1337", .flags = I2C_DF_NOTIFY, .attach_adapter = ds1337_attach_adapter, .detach_client = ds1337_detach_client, .command = ds1337_command, }; /* * Client data (each client gets its own) */ struct ds1337_data { struct i2c_client client; struct list_head list; int id; }; /* * Internal variables */ static int ds1337_id; static LIST_HEAD(ds1337_clients); static inline int ds1337_read(struct i2c_client *client, u8 reg, u8 *value) { s32 tmp = i2c_smbus_read_byte_data(client, reg); if (tmp < 0) return -EIO; *value = tmp; return 0; } /* * Chip access functions */ static int ds1337_get_datetime(struct i2c_client *client, struct rtc_time *dt) { int result; u8 buf[7]; u8 val; struct i2c_msg msg[2]; u8 offs = 0; if (!dt) { dev_dbg(&client->dev, "%s: EINVAL: dt=NULL\n", __FUNCTION__); return -EINVAL; } msg[0].addr = client->addr; msg[0].flags = 0; msg[0].len = 1; msg[0].buf = &offs; msg[1].addr = client->addr; msg[1].flags = I2C_M_RD; msg[1].len = sizeof(buf); msg[1].buf = &buf[0]; result = i2c_transfer(client->adapter, msg, 2); dev_dbg(&client->dev, "%s: [%d] %02x %02x %02x %02x %02x %02x %02x\n", __FUNCTION__, result, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); if (result >= 0) { dt->tm_sec = BCD2BIN(buf[0]); dt->tm_min = BCD2BIN(buf[1]); val = buf[2] & 0x3f; dt->tm_hour = BCD2BIN(val); dt->tm_wday = BCD2BIN(buf[3]) - 1; dt->tm_mday = BCD2BIN(buf[4]); val = buf[5] & 0x7f; dt->tm_mon = BCD2BIN(val) - 1; dt->tm_year = BCD2BIN(buf[6]); if (buf[5] & 0x80) dt->tm_year += 100; dev_dbg(&client->dev, "%s: secs=%d, mins=%d, " "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", __FUNCTION__, dt->tm_sec, dt->tm_min, dt->tm_hour, dt->tm_mday, dt->tm_mon, dt->tm_year, dt->tm_wday); } else { dev_err(&client->dev, "error reading data! %d\n", result); result = -EIO; } return result; } static int ds1337_set_datetime(struct i2c_client *client, struct rtc_time *dt) { int result; u8 buf[8]; u8 val; struct i2c_msg msg[1]; if (!dt) { dev_dbg(&client->dev, "%s: EINVAL: dt=NULL\n", __FUNCTION__); return -EINVAL; } dev_dbg(&client->dev, "%s: secs=%d, mins=%d, hours=%d, " "mday=%d, mon=%d, year=%d, wday=%d\n", __FUNCTION__, dt->tm_sec, dt->tm_min, dt->tm_hour, dt->tm_mday, dt->tm_mon, dt->tm_year, dt->tm_wday); buf[0] = 0; /* reg offset */ buf[1] = BIN2BCD(dt->tm_sec); buf[2] = BIN2BCD(dt->tm_min); buf[3] = BIN2BCD(dt->tm_hour) | (1 << 6); buf[4] = BIN2BCD(dt->tm_wday) + 1; buf[5] = BIN2BCD(dt->tm_mday); buf[6] = BIN2BCD(dt->tm_mon) + 1; val = dt->tm_year; if (val >= 100) { val -= 100; buf[6] |= (1 << 7); } buf[7] = BIN2BCD(val); msg[0].addr = client->addr; msg[0].flags = 0; msg[0].len = sizeof(buf); msg[0].buf = &buf[0]; result = i2c_transfer(client->adapter, msg, 1); if (result < 0) { dev_err(&client->dev, "error writing data! %d\n", result); result = -EIO; } else { result = 0; } return result; } static int ds1337_command(struct i2c_client *client, unsigned int cmd, void *arg) { dev_dbg(&client->dev, "%s: cmd=%d\n", __FUNCTION__, cmd); switch (cmd) { case DS1337_GET_DATE: return ds1337_get_datetime(client, arg); case DS1337_SET_DATE: return ds1337_set_datetime(client, arg); default: return -EINVAL; } } /* * Public API for access to specific device. Useful for low-level * RTC access from kernel code. */ int ds1337_do_command(int id, int cmd, void *arg) { struct list_head *walk; struct list_head *tmp; struct ds1337_data *data; list_for_each_safe(walk, tmp, &ds1337_clients) { data = list_entry(walk, struct ds1337_data, list); if (data->id == id) return ds1337_command(&data->client, cmd, arg); } return -ENODEV; } static int ds1337_attach_adapter(struct i2c_adapter *adapter) { return i2c_detect(adapter, &addr_data, ds1337_detect); } /* * The following function does more than just detection. If detection * succeeds, it also registers the new chip. */ static int ds1337_detect(struct i2c_adapter *adapter, int address, int kind) { struct i2c_client *new_client; struct ds1337_data *data; int err = 0; const char *name = ""; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_I2C)) goto exit; if (!(data = kmalloc(sizeof(struct ds1337_data), GFP_KERNEL))) { err = -ENOMEM; goto exit; } memset(data, 0, sizeof(struct ds1337_data)); INIT_LIST_HEAD(&data->list); /* The common I2C client data is placed right before the * DS1337-specific data. */ new_client = &data->client; i2c_set_clientdata(new_client, data); new_client->addr = address; new_client->adapter = adapter; new_client->driver = &ds1337_driver; new_client->flags = 0; /* * Now we do the remaining detection. A negative kind means that * the driver was loaded with no force parameter (default), so we * must both detect and identify the chip. A zero kind means that * the driver was loaded with the force parameter, the detection * step shall be skipped. A positive kind means that the driver * was loaded with the force parameter and a given kind of chip is * requested, so both the detection and the identification steps * are skipped. * * For detection, we read registers that are most likely to cause * detection failure, i.e. those that have more bits with fixed * or reserved values. */ /* Default to an DS1337 if forced */ if (kind == 0) kind = ds1337; if (kind < 0) { /* detection and identification */ u8 data; /* Check that status register bits 6-2 are zero */ if ((ds1337_read(new_client, DS1337_REG_STATUS, &data) < 0) || (data & 0x7c)) goto exit_free; /* Check for a valid day register value */ if ((ds1337_read(new_client, DS1337_REG_DAY, &data) < 0) || (data == 0) || (data & 0xf8)) goto exit_free; /* Check for a valid date register value */ if ((ds1337_read(new_client, DS1337_REG_DATE, &data) < 0) || (data == 0) || (data & 0xc0) || ((data & 0x0f) > 9) || (data >= 0x32)) goto exit_free; /* Check for a valid month register value */ if ((ds1337_read(new_client, DS1337_REG_MONTH, &data) < 0) || (data == 0) || (data & 0x60) || ((data & 0x0f) > 9) || ((data >= 0x13) && (data <= 0x19))) goto exit_free; /* Check that control register bits 6-5 are zero */ if ((ds1337_read(new_client, DS1337_REG_CONTROL, &data) < 0) || (data & 0x60)) goto exit_free; kind = ds1337; } if (kind == ds1337) name = "ds1337"; /* We can fill in the remaining client fields */ strlcpy(new_client->name, name, I2C_NAME_SIZE); /* Tell the I2C layer a new client has arrived */ if ((err = i2c_attach_client(new_client))) goto exit_free; /* Initialize the DS1337 chip */ ds1337_init_client(new_client); /* Add client to local list */ data->id = ds1337_id++; list_add(&data->list, &ds1337_clients); return 0; exit_free: kfree(data); exit: return err; } static void ds1337_init_client(struct i2c_client *client) { s32 val; /* Ensure that device is set in 24-hour mode */ val = i2c_smbus_read_byte_data(client, DS1337_REG_HOUR); if ((val >= 0) && (val & (1 << 6)) == 0) i2c_smbus_write_byte_data(client, DS1337_REG_HOUR, val | (1 << 6)); } static int ds1337_detach_client(struct i2c_client *client) { int err; struct ds1337_data *data = i2c_get_clientdata(client); if ((err = i2c_detach_client(client))) { dev_err(&client->dev, "Client deregistration failed, " "client not detached.\n"); return err; } list_del(&data->list); kfree(data); return 0; } static int __init ds1337_init(void) { return i2c_add_driver(&ds1337_driver); } static void __exit ds1337_exit(void) { i2c_del_driver(&ds1337_driver); } MODULE_AUTHOR("James Chapman "); MODULE_DESCRIPTION("DS1337 RTC driver"); MODULE_LICENSE("GPL"); module_init(ds1337_init); module_exit(ds1337_exit);