Hello, the following Patch (against 2.6 kernel.org tree, COMMIT_ID: f093182d313edde9b1f86dbdaf40ba4da2dbd0e7) adds support for the PCF8563 RTC on the PM82X Boards from Microsys. Signed-off-by: Heiko Schocher <hs at denx.de> --- drivers/i2c/chips/Kconfig | 9 + drivers/i2c/chips/Makefile | 1 drivers/i2c/chips/pcf8563.c | 549 +++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c-id.h | 1 4 files changed, 560 insertions(+), 0 deletions(-) diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index f9fae28..c470b0d 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -46,6 +46,15 @@ config SENSORS_PCF8574 This driver can also be built as a module. If so, the module will be called pcf8574. +config SENSORS_PCF8563 + tristate "Philips PCF8563 RTC Chip" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for Philips PCF8563 RTC chip. + + This driver can also be built as a module. If so, the module + will be called i2c-pcf8563. + config SENSORS_PCA9539 tristate "Philips PCA9539 16-bit I/O port" depends on I2C && EXPERIMENTAL diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index 46178b5..e37835a 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SENSORS_MAX6875) += max6875 obj-$(CONFIG_SENSORS_M41T00) += m41t00.o obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o +obj-$(CONFIG_SENSORS_PCF8563) += pcf8563.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_SENSORS_RTC8564) += rtc8564.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o diff --git a/drivers/i2c/chips/pcf8563.c b/drivers/i2c/chips/pcf8563.c new file mode 100644 index 0000000..8c4018b --- /dev/null +++ b/drivers/i2c/chips/pcf8563.c @@ -0,0 +1,549 @@ +/* + * linux/drivers/i2c/chips/pcf8563.c + * + * Copyright (C) 2002-2004 Stefan Eletzhofer + * + * based on linux/drivers/acron/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 system3's PHILIPS PCF 8563 chip + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/rtc.h> /* get the user-level API */ +#include <linux/init.h> +#include <linux/list.h> +#include <asm/time.h> +#include <asm/machdep.h> + +#include "rtc8564.h" + +#ifdef DEBUG +# define _DBG(x, fmt, args...) do{ if (debug>=x) printk(KERN_DEBUG"%s: " fmt "\n", __FUNCTION__, ##args); } while(0); +#else +# define _DBG(x, fmt, args...) do { } while(0); +#endif + +#define _DBGRTCTM(x, rtctm) if (debug>x) printk("%s: secs=%d, mins=%d, hours=%d, mday=%d, " \ + "mon=%d, year=%d, wday=%d\n", __FUNCTION__, \ + (rtctm).tm_sec, (rtctm).tm_min, (rtctm).tm_hour, (rtctm).tm_mday, \ + (rtctm).tm_mon, (rtctm).tm_year, (rtctm).tm_wday); + +struct pcf8563_data { + struct i2c_client client; + struct list_head list; + u16 ctrl; +}; + +/* + * Internal variables + */ +static LIST_HEAD(pcf8563_clients); + +static struct i2c_client *myclient = NULL; + +static inline u8 _pcf8563_ctrl1(struct i2c_client *client) +{ + struct pcf8563_data *data = i2c_get_clientdata(client); + return data->ctrl & 0xff; +} +static inline u8 _pcf8563_ctrl2(struct i2c_client *client) +{ + struct pcf8563_data *data = i2c_get_clientdata(client); + return (data->ctrl & 0xff00) >> 8; +} + +#define CTRL1(c) _pcf8563_ctrl1(c) +#define CTRL2(c) _pcf8563_ctrl2(c) + +#define BCD_TO_BIN(val) (((val)&15) + ((val)>>4)*10) +#define BIN_TO_BCD(val) ((((val)/10)<<4) + (val)%10) + +static int debug = 0; +module_param(debug, int, S_IRUGO | S_IWUSR); + +static struct i2c_driver pcf8563_driver; + +static int pcf8563_read_mem(struct i2c_client *client, struct mem *mem); +static int pcf8563_write_mem(struct i2c_client *client, struct mem *mem); +static unsigned long pcf8563_get_rtc_time(void); +static int pcf8563_set_rtc_time(unsigned long now); + +/* save/restore old machdep pointers */ +int (*save_set_rtc_time)(unsigned long); +unsigned long (*save_get_rtc_time)(void); + +static int pcf8563_read(struct i2c_client *client, unsigned char adr, + unsigned char *buf, unsigned char len) +{ + int ret = -EIO; + unsigned char addr[1] = { adr }; + struct i2c_msg msgs[2] = { + {client->addr, 0, 1, addr}, + {client->addr, I2C_M_RD, len, buf} + }; + + _DBG(1, "client=%p, adr=%d, buf=%p, len=%d", client, adr, buf, len); + + if (!buf) { + ret = -EINVAL; + goto done; + } + + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret == 2) { + ret = 0; + } + +done: + return ret; +} + +static int pcf8563_write(struct i2c_client *client, unsigned char adr, + unsigned char *data, unsigned char len) +{ + int ret = 0; + unsigned char _data[16]; + struct i2c_msg wr; + int i; + + if (!data || len > 15) { + ret = -EINVAL; + goto done; + } + + _DBG(1, "client=%p, adr=%d, buf=%p, len=%d", client, adr, data, len); + + _data[0] = adr; + for (i = 0; i < len; i++) { + _data[i + 1] = data[i]; + _DBG(5, "data[%d] = 0x%02x (%d)", i, data[i], data[i]); + } + + wr.addr = client->addr; + wr.flags = 0; + wr.len = len + 1; + wr.buf = _data; + + ret = i2c_transfer(client->adapter, &wr, 1); + if (ret == 1) { + ret = 0; + } + +done: + return ret; +} + +static void pcf8563_set_system_time (void) +{ + unsigned long now, flags; + extern time_t last_rtc_update; + extern seqlock_t xtime_lock; + + /* Switching kernel RTC pointers */ + _DBG (2,"RTC switching kernel pointers\n"); + + save_set_rtc_time = ppc_md.set_rtc_time; + save_get_rtc_time = ppc_md.get_rtc_time; + + ppc_md.set_rtc_time = pcf8563_set_rtc_time; + ppc_md.get_rtc_time = pcf8563_get_rtc_time; + + /* + * Set system time + * Code copied from arch/ppc/kernel/time.c + */ + write_seqlock_irqsave(&xtime_lock, flags); + + now = pcf8563_get_rtc_time(); + _DBG (2,"Set System Time from RTC Time: %lu\n", now); + xtime.tv_nsec = 0; + xtime.tv_sec = now; + + last_rtc_update = now - 658; + + time_adjust = 0; /* stop active adjtime() */ + time_status |= STA_UNSYNC; + time_state = TIME_ERROR; + time_maxerror = NTP_PHASE_LIMIT; + time_esterror = NTP_PHASE_LIMIT; + + write_sequnlock_irqrestore(&xtime_lock, flags); + +#if 0 + /* + * Check for low voltage + */ + if (rtc_rd (0x02) & 0x80) { + printk (KERN_CRIT + "PCF8563 RTC Low Voltage - date/time not reliable\n"); + } + + /* + * Reset any error conditions, alarms, etc. + */ + pcf8563_reset_rtc (); +#endif +} + +static int pcf8563_attach(struct i2c_adapter *adap, int addr, int kind) +{ + int ret; + struct i2c_client *new_client; + struct pcf8563_data *d; + unsigned char data[10]; + unsigned char ad[1] = { 0 }; + struct i2c_msg ctrl_wr[1] = { + {addr, 0, 2, data} + }; + struct i2c_msg ctrl_rd[2] = { + {addr, 0, 1, ad}, + {addr, I2C_M_RD, 2, data} + }; + + d = kmalloc(sizeof(struct pcf8563_data), GFP_KERNEL); + if (!d) { + ret = -ENOMEM; + goto done; + } + memset(d, 0, sizeof(struct pcf8563_data)); + INIT_LIST_HEAD(&d->list); + + new_client = &d->client; + + strlcpy(new_client->name, "RTC8563", I2C_NAME_SIZE); + i2c_set_clientdata(new_client, d); + new_client->flags = I2C_CLIENT_ALLOW_USE | I2C_DF_NOTIFY; + new_client->addr = addr; + new_client->adapter = adap; + new_client->driver = &pcf8563_driver; + + _DBG(1, "client=%p", new_client); + + /* init ctrl1 reg */ + data[0] = 0; + data[1] = 0; + ret = i2c_transfer(new_client->adapter, ctrl_wr, 1); + if (ret != 1) { + printk(KERN_INFO "pcf8563: cant init ctrl1\n"); + ret = -ENODEV; + goto done; + } + /* read back ctrl1 and ctrl2 */ + ret = i2c_transfer(new_client->adapter, ctrl_rd, 2); + if (ret != 2) { + printk(KERN_INFO "pcf8563: cant read ctrl\n"); + ret = -ENODEV; + goto done; + } + d->ctrl = data[0] | (data[1] << 8); + + _DBG(1, "RTC8564_REG_CTRL1=%02x, RTC8564_REG_CTRL2=%02x", + data[0], data[1]); + + ret = i2c_attach_client(new_client); + + /* Add client to local list */ + list_add(&d->list, &pcf8563_clients); + +done: + if (ret) { + kfree(d); + } else { + myclient = new_client; + pcf8563_set_system_time(); + } + return ret; +} + +static int pcf8563_probe(struct i2c_adapter *adap) +{ + /* + * Probing seems to confuse the RTC on PM826 and CPU86 and UC100. + * Not sure if it's true for other boards though - thus the + * conditional. + */ +#if defined(CONFIG_PM82X) + pcf8563_attach(adap, 0x51, 0); + return 0; +#else + return i2c_probe(adap, &addr_data, pcf8563_attach); +#endif +} + +static int pcf8563_detach(struct i2c_client *client) +{ + struct pcf8563_data *data = i2c_get_clientdata(client); + + i2c_detach_client(client); + kfree(i2c_get_clientdata(client)); + list_del(&data->list); + return 0; +} + +static int pcf8563_get_datetime(struct i2c_client *client, struct rtc_time *dt) +{ + int ret = -EIO; + unsigned char buf[15]; + + _DBG(1, "client=%p, dt=%p", client, dt); + + if (!dt) + return -EINVAL; + + memset(buf, 0, sizeof(buf)); + + ret = pcf8563_read(client, 0, buf, 15); + if (ret) + return ret; + + dt->tm_year = 1900 + BCD_TO_BIN(buf[RTC8564_REG_YEAR]); + if (buf[RTC8564_REG_MON_CENT] & 0x80) + dt->tm_year += 100; + dt->tm_mday = BCD_TO_BIN(buf[RTC8564_REG_DAY] & 0x3f); + dt->tm_wday = BCD_TO_BIN(buf[RTC8564_REG_WDAY] & 7); + dt->tm_mon = BCD_TO_BIN(buf[RTC8564_REG_MON_CENT] & 0x1f); + + dt->tm_sec = BCD_TO_BIN(buf[RTC8564_REG_SEC] & 0x7f); +/* dt->vl = (buf[RTC8564_REG_SEC] & 0x80) == 0x80; */ + dt->tm_min = BCD_TO_BIN(buf[RTC8564_REG_MIN] & 0x7f); + dt->tm_hour = BCD_TO_BIN(buf[RTC8564_REG_HR] & 0x3f); + + _DBGRTCTM(2, *dt); + return 0; +} + +static int +pcf8563_set_datetime(struct i2c_client *client, struct rtc_time *dt, int datetoo) +{ + int ret, len = 5; + unsigned char buf[15]; + unsigned char val; + + _DBG(1, "client=%p, dt=%p", client, dt); + + if (!dt) + return -EINVAL; + + _DBGRTCTM(2, *dt); + + buf[RTC8564_REG_CTRL1] = CTRL1(client) | RTC8564_CTRL1_STOP; + buf[RTC8564_REG_CTRL2] = CTRL2(client); + buf[RTC8564_REG_SEC] = BIN_TO_BCD(dt->tm_sec); + buf[RTC8564_REG_MIN] = BIN_TO_BCD(dt->tm_min); + buf[RTC8564_REG_HR] = BIN_TO_BCD(dt->tm_hour); + + if (datetoo) { + len += 5; + buf[RTC8564_REG_DAY] = BIN_TO_BCD(dt->tm_mday); + buf[RTC8564_REG_WDAY] = BIN_TO_BCD(dt->tm_wday); + buf[RTC8564_REG_MON_CENT] = BIN_TO_BCD(dt->tm_mon) & 0x1f; + if (dt->tm_year >= 2000) { + val = dt->tm_year - 2000; + buf[RTC8564_REG_MON_CENT] |= (1 << 7); + } else { + val = dt->tm_year - 1900; + } + buf[RTC8564_REG_YEAR] = BIN_TO_BCD(val); + } + + ret = pcf8563_write(client, 0, buf, len); + if (ret) { + _DBG(1, "error writing data! %d", ret); + } + + buf[RTC8564_REG_CTRL1] = CTRL1(client); + ret = pcf8563_write(client, 0, buf, 1); + if (ret) { + _DBG(1, "error writing data! %d", ret); + } + + return ret; +} + +static int pcf8563_get_ctrl(struct i2c_client *client, unsigned int *ctrl) +{ + struct pcf8563_data *data = i2c_get_clientdata(client); + + if (!ctrl) + return -1; + + *ctrl = data->ctrl; + return 0; +} + +static int pcf8563_set_ctrl(struct i2c_client *client, unsigned int *ctrl) +{ + struct pcf8563_data *data = i2c_get_clientdata(client); + unsigned char buf[2]; + + if (!ctrl) + return -1; + + buf[0] = *ctrl & 0xff; + buf[1] = (*ctrl & 0xff00) >> 8; + data->ctrl = *ctrl; + + return pcf8563_write(client, 0, buf, 2); +} + +static int pcf8563_read_mem(struct i2c_client *client, struct mem *mem) +{ + + if (!mem) + return -EINVAL; + + return pcf8563_read(client, mem->loc, mem->data, mem->nr); +} + +static int pcf8563_write_mem(struct i2c_client *client, struct mem *mem) +{ + + if (!mem) + return -EINVAL; + + return pcf8563_write(client, mem->loc, mem->data, mem->nr); +} + +static int +pcf8563_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + + _DBG(1, "cmd=%d", cmd); + + switch (cmd) { + case RTC_GETDATETIME: + return pcf8563_get_datetime(client, arg); + + case RTC_SETTIME: + return pcf8563_set_datetime(client, arg, 0); + + case RTC_SETDATETIME: + return pcf8563_set_datetime(client, arg, 1); + + case RTC_GETCTRL: + return pcf8563_get_ctrl(client, arg); + + case RTC_SETCTRL: + return pcf8563_set_ctrl(client, arg); + + case MEM_READ: + return pcf8563_read_mem(client, arg); + + case MEM_WRITE: + return pcf8563_write_mem(client, arg); + + default: + return -EINVAL; + } +} + +/* + * Public API for access to specific device. Useful for low-level + * RTC access from kernel code. + */ +int pcf8563_do_command(int bus, int cmd, void *arg) +{ + struct list_head *walk; + struct list_head *tmp; + struct pcf8563_data *data; + + list_for_each_safe(walk, tmp, &pcf8563_clients) { + data = list_entry(walk, struct pcf8563_data, list); + if (data->client.adapter->nr == bus) { + return pcf8563_command(&data->client, cmd, arg); + } + } + + return -ENODEV; +} + +extern spinlock_t rtc_lock; +/*************************************************************************** + * + * get RTC time: + */ +static unsigned long pcf8563_get_rtc_time(void) +{ + struct rtc_time tm; + int result; + + spin_lock(&rtc_lock); + result = pcf8563_do_command(0, RTC_GETDATETIME, &tm); + spin_unlock(&rtc_lock); + + if (result == 0) + result = mktime(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + + return result; +} + +/*************************************************************************** + * + * set RTC time: + */ +static int pcf8563_set_rtc_time(unsigned long now) +{ + struct rtc_time tm; + unsigned char century, year, mon, wday, mday, hour, min, sec; + + to_tm (now, &tm); + + _DBG (2, "Set RTC [dec] year=%d mon=%d day=%d hour=%d min=%d sec=%d\n", + tm.tm_year, tm.tm_mon, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + + century = (tm.tm_year >= 2000) ? 0x80 : 0; + year = BIN_TO_BCD (tm.tm_year % 100); + mon = BIN_TO_BCD (tm.tm_mon) | century; + wday = BIN_TO_BCD (tm.tm_wday); + mday = BIN_TO_BCD (tm.tm_mday); + hour = BIN_TO_BCD (tm.tm_hour); + min = BIN_TO_BCD (tm.tm_min); + sec = BIN_TO_BCD (tm.tm_sec); + + _DBG (2, "Set RTC [bcd] year=%X mon=%X day=%X " + "hour=%X min=%X sec=%X wday=%X\n", + year, mon, mday, hour, min, sec, wday); + + pcf8563_set_datetime(myclient, &tm, 1); + + return (0); +} + +static struct i2c_driver pcf8563_driver = { + .owner = THIS_MODULE, + .name = "PCF8563", + .id = I2C_DRIVERID_RTC8564, + .flags = I2C_DF_NOTIFY, + .attach_adapter = pcf8563_probe, + .detach_client = pcf8563_detach, + .command = pcf8563_command +}; + +static __init int pcf8563_init(void) +{ + return i2c_add_driver(&pcf8563_driver); +} + +static __exit void pcf8563_exit(void) +{ + ppc_md.set_rtc_time = save_set_rtc_time; + ppc_md.get_rtc_time = save_get_rtc_time; + + i2c_del_driver(&pcf8563_driver); +} + +MODULE_AUTHOR("Stefan Eletzhofer <Stefan.Eletzhofer at eletztrick.de>"); +MODULE_DESCRIPTION("EPSON RTC8563 Driver"); +MODULE_LICENSE("GPL"); + +module_init(pcf8563_init); +module_exit(pcf8563_exit); diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index 1ce4b54..b16c548 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -99,6 +99,7 @@ #define I2C_DRIVERID_MAX6900 63 /* MAX6900 real-time clock */ #define I2C_DRIVERID_SAA7114H 64 /* video decoder */ #define I2C_DRIVERID_DS1374 65 /* DS1374 real time clock */ +#define I2C_DRIVERID_PCF8563 66 /* real time clock */ #define I2C_DRIVERID_EXP0 0xF0 /* experimental use id's */ !-------------------------------------------------------------flip-