/* * Copyright (C) 2004-2009 STMicroelectronics * * Author Angelo Castello * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, wrssc to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Jul 2009 : Ported for kernel 2.6.30. Removed any deprecated istances. * angelo.castello@st.com */ #include #include #include #include #include /* General debugging */ #undef M41ST85Y_DEBUG #ifdef M41ST85Y_DEBUG #define DPRINTK(fmt, args...) printk("%s: " fmt, __FUNCTION__ , ## args) #else #define DPRINTK(fmt, args...) #endif #define M41ST85Y_NAME "m41st85y" #define M41ST85Y_NREGMAP 0x14 /* no of RTC's registers */ #define M41ST85Y_ADDR 0x68 /* MY41ST85Y slave address */ #define M41ST85Y_ISOPEN 0x01 /* means /dev/rtc is in use */ #define M41ST85Y_RD 0x01 /* read flag for a i2c transfer */ #define M41ST85Y_WR 0x00 /* write flag for a i2c transfer */ #define M41ST85Y_INVALID 0xff /* invalid value */ #define M41ST85Y_IRQ_LEVEL 0x01 /* default value. 1=High, 0=Low */ #define M41ST85Y_SQW_LEVEL 0x00 /* default value. 1=High, 0=Low */ #define M41ST85Y_NOBUS 0x03 /* number of I2C busses */ /* * Addressing compliante to SPI PIO address mechanism * Address = [7:Not used:7][6:PIO-Port:3][2:PIO-Pin:0] * Ex: PIO0[7] = 0x07, PIO2[5] = 0x15 */ #define m41st85y_get_pioport(address) ((address >> 0x03) & 0x0f) #define m41st85y_get_piopin(address) (address & 0x07) /* Addresses to scan: none. This chip cannot be detected. */ static unsigned short normal_i2c[] = { M41ST85Y_ADDR, I2C_CLIENT_END }; /* Insmod parameters */ I2C_CLIENT_INSMOD; /* private data */ struct m41st85y_s { struct rtc_device *rtc; struct i2c_adapter *adapter; unsigned long status; unsigned long epoch; /* default linux epoch 1900 */ struct stpio_pin *irqpio; /* PIO used as RTC-IRQ line */ struct stpio_pin *sqwpio; /* PIO used as RTC-SWQ line */ unsigned int cmd; } m41st85y; static __u8 rbuf[M41ST85Y_NREGMAP]; static __u8 wbuf[M41ST85Y_NREGMAP]; static __u32 busid = M41ST85Y_NOBUS; static __u32 irqpio = CONFIG_RTC_DRV_M41ST85Y_IRQPIO /* Ex: 0x07 */ , sqwpio = CONFIG_RTC_DRV_M41ST85Y_SQWPIO /* Ex: 0x0F */ ; static int m41st85y_transfer(struct m41st85y_s *instance, __u8 * buf, __u8 len, __u8 oper, __u8 at_addr) { struct i2c_msg msg[2]; __u8 n_msg; int err = 0; if (oper == M41ST85Y_WR) { /* perform write request */ msg[0].addr = M41ST85Y_ADDR; msg[0].flags = oper; msg[0].len = len; msg[0].buf = buf; n_msg = 1; } else { /* perform read request */ rbuf[0] = at_addr; msg[0].addr = M41ST85Y_ADDR; msg[0].flags = M41ST85Y_WR; msg[0].len = 1; msg[0].buf = rbuf; msg[1].addr = M41ST85Y_ADDR; msg[1].flags = M41ST85Y_RD; msg[1].len = len; msg[1].buf = buf; n_msg = 2; } if ((err = i2c_transfer(instance->adapter, msg, n_msg)) != n_msg) (oper == M41ST85Y_WR) ? printk(KERN_ERR "m41st85y: I2C write failed, err %d\n", err) : printk(KERN_ERR "m41st85y: I2C read failed, err %d\n", err); return err; } static int m41st85y_power_up(void) { __u8 RegsMap[M41ST85Y_NREGMAP]; int err = 0; while (1) { m41st85y_transfer(&m41st85y, rbuf, 1, M41ST85Y_RD, 0x0F); if ((rbuf[0] & 0x40) == 0x00) break; printk(KERN_INFO "m41st85y: There was an alarm during the back-up mode AF 0x%x\n", rbuf[0]); } if ((err = m41st85y_transfer(&m41st85y, RegsMap + 1, M41ST85Y_NREGMAP - 1, M41ST85Y_RD, 0x01)) >= 0) { RegsMap[0x00] = 0x01; /* address offset */ RegsMap[0x01] &= ~0x80; /* ST bit, wake up the oscillator */ RegsMap[0x08] = 0x80; /* IRQ/FT/OUT line is driven low */ RegsMap[0x0A] &= ~0x40; /* Swq disable */ RegsMap[0x0C] &= ~0x40; /* Update the TIMEKEEPER registers */ RegsMap[0x13] &= 0x00; /* Default Square wave output is 1Hz */ /* also irq_freq should be setting up 1Hz at init fase */ if ((err = m41st85y_transfer(&m41st85y, RegsMap, M41ST85Y_NREGMAP, M41ST85Y_WR, M41ST85Y_INVALID)) >= 0) { /* waiting RTC hardware restart */ ssleep(1); return 0; } } return err; } void m41st85y_handler(struct stpio_pin *pin, void *dev) { struct m41st85y_s *instance = dev; __u8 skip = 0, events = 0; stpio_disable_irq(pin); if ((instance->cmd == RTC_PIE_ON) || (instance->cmd == RTC_UIE_ON)) { if (stpio_get_pin(pin) == M41ST85Y_IRQ_LEVEL) { skip = 1; stpio_enable_irq(pin, M41ST85Y_IRQ_LEVEL); } else stpio_enable_irq(pin, !M41ST85Y_IRQ_LEVEL); } if (!skip) { events |= RTC_IRQF; rtc_update_irq(instance->rtc, 1, events); } } static int m41st85y_read_time(struct device *dev, struct rtc_time *time_read) { if (time_read == NULL) return -EIO; memset(time_read, 0, sizeof(struct rtc_time)); if (m41st85y_transfer(&m41st85y, rbuf, 9, M41ST85Y_RD, 0x00) >= 0) { time_read->tm_sec = bcd2bin(rbuf[1] & 0x7f); time_read->tm_min = bcd2bin(rbuf[2] & 0x7f); time_read->tm_hour = bcd2bin(rbuf[3] & 0x3f); time_read->tm_wday = bcd2bin(rbuf[4] & 0x07); time_read->tm_mday = bcd2bin(rbuf[5] & 0x3f); time_read->tm_mon = bcd2bin(rbuf[6] & 0x1f); time_read->tm_year = bcd2bin(rbuf[7]); return 0; } return -EIO; } static int m41st85y_alarmset(struct device *dev, unsigned int ioctl_cmd, struct rtc_time *ltime) { /* to be sure that incoming ioctl request can be managed by this */ if ((ioctl_cmd != RTC_UIE_ON) && (ioctl_cmd != RTC_ALM_SET)) return -1; if (ioctl_cmd == RTC_UIE_ON) { m41st85y_read_time(dev, ltime); /* alarm update */ wbuf[0] = 0x0A; wbuf[1] = bin2bcd(ltime->tm_mon); wbuf[2] = 0xC0 | bin2bcd(ltime->tm_mday); wbuf[3] = 0x80 | bin2bcd(ltime->tm_hour); wbuf[4] = 0x80 | bin2bcd(ltime->tm_min); wbuf[5] = 0x80 | bin2bcd(ltime->tm_sec + 1); if (m41st85y_transfer(&m41st85y, wbuf, 6, M41ST85Y_WR, M41ST85Y_INVALID) >= 0) { wbuf[0] = 0x0A; wbuf[1] = (wbuf[1] | 0x80); DPRINTK("enable AFE writing %#x\n", wbuf[1]); if (m41st85y_transfer(&m41st85y, wbuf, 2, M41ST85Y_WR, M41ST85Y_INVALID) >= 0) return 0; } } else { if (m41st85y_transfer(&m41st85y, rbuf, 6, M41ST85Y_RD, 0x0A) >= 0) { /* alarm update */ wbuf[0] = 0x0A; wbuf[1] = (rbuf[0] & 0xE0) | bin2bcd(ltime->tm_mon); wbuf[2] = (rbuf[1] & 0xC0) | bin2bcd(ltime->tm_mday); wbuf[3] = (rbuf[2] & 0xC0) | bin2bcd(ltime->tm_hour); wbuf[4] = (rbuf[3] & 0x80) | bin2bcd(ltime->tm_min); wbuf[5] = (rbuf[4] & 0x80) | bin2bcd(ltime->tm_sec); DPRINTK("writing alarm date\n"); if (m41st85y_transfer(&m41st85y, wbuf, 6, M41ST85Y_WR, M41ST85Y_INVALID) >= 0) return 0; } } return -EIO; } static int m41st85y_open(struct device *dev) { /* locked at top level until the device will be release */ if (m41st85y.status & M41ST85Y_ISOPEN) { return -EBUSY; } m41st85y.status |= M41ST85Y_ISOPEN; return 0; } static void m41st85y_release(struct device *dev) { m41st85y.status &= ~M41ST85Y_ISOPEN; /* unlocked at top level before to be use it again */ } static int m41st85y_set_time(struct device *dev, struct rtc_time *time_to_write) { int err; /* this is already part of mutex area performed at top level */ if ((err = m41st85y_transfer(&m41st85y, rbuf, 8, M41ST85Y_RD, 0x00)) >= 0) { /* time update */ wbuf[0] = 0x00; wbuf[1] = 0x00; wbuf[2] = (rbuf[1] & 0x80) | bin2bcd(time_to_write->tm_sec); wbuf[3] = (rbuf[2] & 0x80) | bin2bcd(time_to_write->tm_min); wbuf[4] = (rbuf[3] & 0xC0) | bin2bcd(time_to_write->tm_hour); memcpy(&wbuf[5], &rbuf[4], sizeof(char)); wbuf[6] = (rbuf[5] & 0xC0) | bin2bcd(time_to_write->tm_mday); wbuf[7] = (rbuf[6] & 0xE0) | bin2bcd(time_to_write->tm_mon); wbuf[8] = bin2bcd((time_to_write->tm_year - m41st85y.epoch)); err = m41st85y_transfer(&m41st85y, wbuf, 9, M41ST85Y_WR, M41ST85Y_INVALID); } return err; } static int m41st85y_read_alarm(struct device *dev, struct rtc_wkalrm *alarm_read) { int err = 0; if (alarm_read != NULL) { if ((err = m41st85y_transfer(&m41st85y, rbuf, 6, M41ST85Y_RD, 0x0A)) >= 0) { alarm_read->time.tm_mon = bcd2bin(rbuf[0] & 0x1f); alarm_read->time.tm_mday = bcd2bin(rbuf[1] & 0x3f); alarm_read->time.tm_hour = bcd2bin(rbuf[2] & 0x3f); alarm_read->time.tm_min = bcd2bin(rbuf[3] & 0x7f); alarm_read->time.tm_sec = bcd2bin(rbuf[4] & 0x7f); } } else err = -EIO; return err; } static int m41st85y_set_alarm(struct device *dev, struct rtc_wkalrm *alarm_to_write) { return m41st85y_alarmset(dev, RTC_ALM_SET, &alarm_to_write->time); } static int m41st85y_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) { struct rtc_time ltime; int err = 0; memset(<ime, 0, sizeof(struct rtc_time)); m41st85y.cmd = cmd; switch (cmd) { case RTC_UIE_OFF: /* Mask ints from RTC updates. */ case RTC_AIE_OFF: /* Mask alarm int. enab. bit */ case RTC_AIE_ON: /* Allow alarm interrupts. */ { /* reading AFE bits */ if (m41st85y_transfer(&m41st85y, rbuf, 1, M41ST85Y_RD, 0x0A) >= 0) { char n_data = 2; wbuf[0] = 0x0A; if (cmd == RTC_AIE_ON) { stpio_enable_irq(m41st85y.irqpio, M41ST85Y_IRQ_LEVEL); wbuf[1] = (rbuf[0] | 0x80); } else { stpio_disable_irq(m41st85y.irqpio); m41st85y.cmd = 0; /* disable status */ n_data = 6; wbuf[1] = (rbuf[0] & ~0xA0); /* disabling AFE flag bit */ wbuf[2] = wbuf[3] = wbuf[4] = wbuf[5] = 0x00; /* disabling RPT5-RPT1 */ } DPRINTK("writing AFE %#x\n", wbuf[1]); if (m41st85y_transfer(&m41st85y, wbuf, n_data, M41ST85Y_WR, M41ST85Y_INVALID) >= 0) { return 0; } } return -EIO; } case RTC_PIE_OFF: /* Mask periodic int. enab. bit */ case RTC_PIE_ON: /* Allow periodic ints */ { if (m41st85y_transfer(&m41st85y, rbuf, 1, M41ST85Y_RD, 0x0A) >= 0) { wbuf[0] = 0x0A; if (cmd == RTC_PIE_OFF) { stpio_disable_irq(m41st85y.sqwpio); wbuf[1] = (rbuf[0] & ~0x40); } else { stpio_enable_irq(m41st85y.sqwpio, M41ST85Y_SQW_LEVEL); wbuf[1] = (rbuf[0] | 0x40); } DPRINTK("writing on SWQE %#x\n", wbuf[1]); if (m41st85y_transfer(&m41st85y, wbuf, 2, M41ST85Y_WR, M41ST85Y_INVALID) >= 0) return 0; } return -EIO; } case RTC_UIE_ON: /* Allow ints for RTC updates. (one per second) */ { stpio_enable_irq(m41st85y.irqpio, M41ST85Y_IRQ_LEVEL); return m41st85y_alarmset(dev, cmd, <ime); } case RTC_ALM_READ: /* Read the present alarm time */ { struct rtc_wkalrm alarm_read; if ((err = m41st85y_read_alarm(NULL, &alarm_read)) >= 0) return copy_to_user((void __user *)arg, &alarm_read.time, sizeof alarm_read.time) ? -EFAULT : 0; return err; } case RTC_ALM_SET: /* Store a time into the alarm */ { if (copy_from_user (<ime, (struct rtc_time __user *)arg, sizeof ltime)) return -EFAULT; return m41st85y_alarmset(dev, cmd, <ime); } case RTC_RD_TIME: /* Read the time/date from RTC */ { m41st85y_read_time(dev, <ime); return copy_to_user((void __user *)arg, <ime, sizeof ltime) ? -EFAULT : 0; } case RTC_SET_TIME: /* Set the RTC */ { if (copy_from_user (<ime, (struct rtc_time __user *)arg, sizeof ltime)) return -EFAULT; return m41st85y_set_time(dev, <ime); } case RTC_IRQP_READ: /* Read the periodic IRQ rate. */ { return put_user(m41st85y.rtc->irq_freq, (unsigned long __user *)arg); } case RTC_IRQP_SET: /* Set periodic IRQ rate. */ { wbuf[0] = 0x13; switch (arg) { case 0: wbuf[1] = 0x00; break; case 1: wbuf[1] = 0xF0; break; case 2: wbuf[1] = 0xE0; break; case 4: wbuf[1] = 0xD0; break; case 8: wbuf[1] = 0xC0; break; case 16: wbuf[1] = 0xB0; break; case 32: wbuf[1] = 0xA0; break; case 64: wbuf[1] = 0x90; break; case 128: wbuf[1] = 0x80; break; case 256: wbuf[1] = 0x70; break; case 512: wbuf[1] = 0x60; break; case 1024: wbuf[1] = 0x50; break; case 2048: wbuf[1] = 0x40; break; case 4096: wbuf[1] = 0x30; break; case 8192: wbuf[1] = 0x20; break; default: return -ENOTSUPP; } if ((err = m41st85y_transfer(&m41st85y, wbuf, 2, M41ST85Y_WR, M41ST85Y_INVALID)) >= 0) { m41st85y.rtc->irq_freq = arg; return 0; } return err; } case RTC_EPOCH_READ: /* Read the epoch. */ { return put_user(m41st85y.epoch, (unsigned long __user *)arg); } case RTC_EPOCH_SET: /* Set the epoch. */ { copy_from_user(&m41st85y.epoch, (void *)arg, sizeof(long)); return 0; } default: return -ENOTTY; } } static int m41st85y_read_callback(struct device *dev, int data) { if (data & RTC_IRQF) { if ((m41st85y.cmd == RTC_AIE_ON) || (m41st85y.cmd == RTC_UIE_ON)) { while (1) { m41st85y_transfer(&m41st85y, rbuf, 1, M41ST85Y_RD, 0x0F); DPRINTK("AF 0x%x\n", rbuf[0]); if ((rbuf[0] & 0x40) == 0x00) break; } } } return data; } static int m41st85y_irq_set_state(struct device *dev, int enabled) { if (enabled) return m41st85y_ioctl(dev, RTC_PIE_ON, 0); else return m41st85y_ioctl(dev, RTC_PIE_OFF, 0); } static int m41st85y_irq_set_freq(struct device *dev, int freq) { return m41st85y_ioctl(dev, RTC_IRQP_SET, freq); } static struct rtc_class_ops m41st85y_rtc_ops = { .open = m41st85y_open, .release = m41st85y_release, .ioctl = m41st85y_ioctl, .read_time = m41st85y_read_time, .set_time = m41st85y_set_time, .read_alarm = m41st85y_read_alarm, .set_alarm = m41st85y_set_alarm, .irq_set_state = m41st85y_irq_set_state, .irq_set_freq = m41st85y_irq_set_freq, .read_callback = m41st85y_read_callback, }; static int m41st85y_probe(struct i2c_client *client, const struct i2c_device_id *id) { if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { printk(KERN_ERR "m41st85y: functionality I2C unsupported\n"); return -ENODEV; } if (m41st85y_power_up() >= 0) { m41st85y.rtc = rtc_device_register(M41ST85Y_NAME, &client->dev, &m41st85y_rtc_ops, THIS_MODULE); if (IS_ERR(m41st85y.rtc)) { printk(KERN_ERR "m41st85y: RTC dev register failure.\n"); return PTR_ERR(m41st85y.rtc); } m41st85y.adapter = client->adapter; m41st85y.cmd = 0; /* none */ m41st85y.epoch = 1900; /* default value on Linux */ m41st85y.rtc->irq_freq = 1; /* default square ware 1Hz */ printk(KERN_INFO "m41st85y: IRQ line plugged on PIO%d[%d]\n", m41st85y_get_pioport(irqpio), m41st85y_get_piopin(irqpio)); printk(KERN_INFO "m41st85y: SQW line plugged on PIO%d[%d]\n", m41st85y_get_pioport(sqwpio), m41st85y_get_piopin(sqwpio)); if ((m41st85y.irqpio = stpio_request_pin(m41st85y_get_pioport(irqpio), m41st85y_get_piopin(irqpio), M41ST85Y_NAME, STPIO_BIDIR_Z1)) != NULL) { if ((m41st85y.sqwpio = stpio_request_pin(m41st85y_get_pioport(sqwpio), m41st85y_get_piopin(sqwpio), M41ST85Y_NAME, STPIO_IN)) != NULL) { stpio_flagged_request_irq(m41st85y.irqpio, M41ST85Y_IRQ_LEVEL, m41st85y_handler, (void *)&m41st85y, 0); stpio_flagged_request_irq(m41st85y.sqwpio, M41ST85Y_SQW_LEVEL, m41st85y_handler, (void *)&m41st85y, 0); i2c_set_clientdata(client, &m41st85y); printk(KERN_INFO "m41st85y: STMicroelectronics M41ST85Y RTC Driver registered\n"); return 0; } else stpio_free_pin(m41st85y.irqpio); } printk(KERN_ERR "m41st85y: STMicroelectronics M41ST85Y RTC Driver unregistered\n"); } return -EIO; } static int m41st85y_remove(struct i2c_client *client) { stpio_free_irq(m41st85y.irqpio); stpio_free_irq(m41st85y.sqwpio); rtc_device_unregister(m41st85y.rtc); return 0; } static struct i2c_device_id m41st85y_id[] = { {M41ST85Y_NAME, 0}, {} }; static struct i2c_driver m41st85y_driver = { .driver.name = M41ST85Y_NAME, .probe = m41st85y_probe, .remove = m41st85y_remove, .id_table = m41st85y_id }; static __init int m41st85y_init(void) { return i2c_add_driver(&m41st85y_driver); } static __exit void m41st85y_exit(void) { i2c_del_driver(&m41st85y_driver); } EXPORT_SYMBOL(rtc_register); EXPORT_SYMBOL(rtc_unregister); EXPORT_SYMBOL(rtc_control); int rtc_register(rtc_task_t * task) { if (task == NULL || task->func == NULL) return -EINVAL; spin_lock_irq(&m41st85y.rtc->irq_lock); if (m41st85y.status & M41ST85Y_ISOPEN) { spin_unlock_irq(&m41st85y.rtc->irq_lock); return -EBUSY; } spin_lock(&m41st85y.rtc->irq_task_lock); if (m41st85y.rtc->irq_task) { spin_unlock(&m41st85y.rtc->irq_task_lock); spin_unlock_irq(&m41st85y.rtc->irq_lock); return -EBUSY; } m41st85y.status |= M41ST85Y_ISOPEN; m41st85y.rtc->irq_task = task; spin_unlock(&m41st85y.rtc->irq_task_lock); spin_unlock_irq(&m41st85y.rtc->irq_lock); return 0; } int rtc_control(rtc_task_t * task, unsigned int cmd, unsigned long arg) { spin_lock_irq(&m41st85y.rtc->irq_task_lock); if (m41st85y.rtc->irq_task != task) { spin_unlock_irq(&m41st85y.rtc->irq_task_lock); return -ENXIO; } spin_unlock_irq(&m41st85y.rtc->irq_task_lock); return m41st85y_ioctl(NULL, cmd, arg); } int rtc_unregister(rtc_task_t * task) { spin_lock_irq(&m41st85y.rtc->irq_lock); spin_lock(&m41st85y.rtc->irq_task_lock); if (m41st85y.rtc->irq_task != task) { spin_unlock(&m41st85y.rtc->irq_task_lock); spin_unlock_irq(&m41st85y.rtc->irq_lock); return -ENXIO; } m41st85y.rtc->irq_task = NULL; /* diasbilng the RTC's AIE, UIE and PIE control */ if (m41st85y_transfer(&m41st85y, rbuf, 1, M41ST85Y_RD, 0x0A) >= 0) { wbuf[0] = 0x0A; wbuf[1] = rbuf[0] & ~0xC0; if (m41st85y_transfer(&m41st85y, wbuf, 2, M41ST85Y_WR, M41ST85Y_INVALID) >= 0) { m41st85y.status &= ~M41ST85Y_ISOPEN; spin_unlock(&m41st85y.rtc->irq_task_lock); spin_unlock_irq(&m41st85y.rtc->irq_lock); return 0; } } spin_unlock(&m41st85y.rtc->irq_task_lock); spin_unlock_irq(&m41st85y.rtc->irq_lock); return -EIO; } module_param(busid, uint, 0644); module_param(irqpio, uint, 0644); module_param(sqwpio, uint, 0644); module_init(m41st85y_init); module_exit(m41st85y_exit); MODULE_AUTHOR("angelo castello "); MODULE_PARM_DESC(busid, "I2C bus ID"); MODULE_PARM_DESC(irqpio, "PIO port/pin for RTC-IRQ line"); MODULE_PARM_DESC(busid, "PIO port/pin for RTC-SWQ line"); MODULE_DESCRIPTION("External RTC upon I2C"); MODULE_LICENSE("GPL");