diff --git a/package/rtc-rv5c386a/Makefile b/package/rtc-rv5c386a/Makefile new file mode 100644 index 000000000..e0fde7834 --- /dev/null +++ b/package/rtc-rv5c386a/Makefile @@ -0,0 +1,42 @@ +# +# Copyright (C) 2006-2009 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=rtc-rv5c386a +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/rtc-rv5c386a + SUBMENU:=Other modules + DEPENDS:=@TARGET_brcm_2_4||TARGET_brcm47xx + TITLE:=Driver for RTC RV5C386A (used in WL-700gE and WL-HDD) + AUTOLOAD:=$(call AutoLoad,70,rtc) + FILES:=$(PKG_BUILD_DIR)/rtc.$(LINUX_KMOD_SUFFIX) +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +ifeq ($(BOARD),brcm-2.4) + BUILDFLAGS=-DBCMDRIVER -I$(LINUX_DIR)/arch/mips/bcm947xx/include -DLINUX_2_4 +endif + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + SUBDIRS="$(PKG_BUILD_DIR)" \ + EXTRA_CFLAGS="$(BUILDFLAGS)" \ + modules +endef + +$(eval $(call KernelPackage,rtc-rv5c386a)) diff --git a/package/rtc-rv5c386a/src/Makefile b/package/rtc-rv5c386a/src/Makefile new file mode 100644 index 000000000..eeb043077 --- /dev/null +++ b/package/rtc-rv5c386a/src/Makefile @@ -0,0 +1,18 @@ +# $Id$ +# +# Makefile for Real Time Clock driver for WL-HDD +# +# Copyright (C) 2007 Andreas Engel +# +# 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. +# + +obj-m := rtc.o + +ifeq ($(MAKING_MODULES),1) + +-include $(TOPDIR)/Rules.make +endif diff --git a/package/rtc-rv5c386a/src/gpio.h b/package/rtc-rv5c386a/src/gpio.h new file mode 100644 index 000000000..cd48637b2 --- /dev/null +++ b/package/rtc-rv5c386a/src/gpio.h @@ -0,0 +1,157 @@ +#ifndef __DIAG_GPIO_H +#define __DIAG_GPIO_H +#include + +#ifndef BCMDRIVER +#include + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25) +#include +#define ssb ssb_bcm47xx +#endif +extern struct ssb_bus ssb; + + +static inline u32 gpio_in(void) +{ + return ssb_gpio_in(&ssb, ~0); +} + +static inline u32 gpio_out(u32 mask, u32 value) +{ + return ssb_gpio_out(&ssb, mask, value); +} + +static inline u32 gpio_outen(u32 mask, u32 value) +{ + return ssb_gpio_outen(&ssb, mask, value); +} + +static inline u32 gpio_control(u32 mask, u32 value) +{ + return ssb_gpio_control(&ssb, mask, value); +} + +static inline u32 gpio_setintmask(u32 mask, u32 value) +{ + return ssb_gpio_intmask(&ssb, mask, value); +} + +static inline u32 gpio_intpolarity(u32 mask, u32 value) +{ + return ssb_gpio_polarity(&ssb, mask, value); +} + +static inline u32 __ssb_write32_masked(struct ssb_device *dev, u16 offset, + u32 mask, u32 value) +{ + value &= mask; + value |= ssb_read32(dev, offset) & ~mask; + ssb_write32(dev, offset, value); + return value; +} + +static void gpio_set_irqenable(int enabled, irqreturn_t (*handler)(int, void *)) +{ + int irq; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25) + irq = gpio_to_irq(0); + if (irq == -EINVAL) return; +#else + if (ssb.chipco.dev) + irq = ssb_mips_irq(ssb.chipco.dev) + 2; + else if (ssb.extif.dev) + irq = ssb_mips_irq(ssb.extif.dev) + 2; + else return; +#endif + + if (enabled) { + if (request_irq(irq, handler, IRQF_SHARED | IRQF_SAMPLE_RANDOM, "gpio", handler)) + return; + } else { + free_irq(irq, handler); + } + + if (ssb.chipco.dev) + __ssb_write32_masked(ssb.chipco.dev, SSB_CHIPCO_IRQMASK, SSB_CHIPCO_IRQ_GPIO, (enabled ? SSB_CHIPCO_IRQ_GPIO : 0)); +} + +#else + +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) +#include +#else +#include +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +#define sbh bcm947xx_sbh +#define sbh_lock bcm947xx_sbh_lock +#endif + +extern void *sbh; +extern spinlock_t sbh_lock; + +#define gpio_in() sb_gpioin(sbh) +#define gpio_out(mask, value) sb_gpioout(sbh, mask, ((value) & (mask)), GPIO_DRV_PRIORITY) +#define gpio_outen(mask, value) sb_gpioouten(sbh, mask, value, GPIO_DRV_PRIORITY) +#define gpio_control(mask, value) sb_gpiocontrol(sbh, mask, value, GPIO_DRV_PRIORITY) +#define gpio_setintmask(mask, value) sb_gpiointmask(sbh, mask, value, GPIO_DRV_PRIORITY) +#define gpio_intpolarity(mask, value) sb_gpiointpolarity(sbh, mask, value, GPIO_DRV_PRIORITY) + +static void gpio_set_irqenable(int enabled, irqreturn_t (*handler)(int, void *, struct pt_regs *)) +{ + unsigned int coreidx; + unsigned long flags; + chipcregs_t *cc; + int irq; + + spin_lock_irqsave(sbh_lock, flags); + coreidx = sb_coreidx(sbh); + + irq = sb_irq(sbh) + 2; + if (enabled) + request_irq(irq, handler, SA_SHIRQ | SA_SAMPLE_RANDOM, "gpio", handler); + else + free_irq(irq, handler); + + if ((cc = sb_setcore(sbh, SB_CC, 0))) { + int intmask; + + intmask = readl(&cc->intmask); + if (enabled) + intmask |= CI_GPIO; + else + intmask &= ~CI_GPIO; + writel(intmask, &cc->intmask); + } + sb_setcoreidx(sbh, coreidx); + spin_unlock_irqrestore(sbh_lock, flags); +} + +#endif /* BCMDRIVER */ + +#define EXTIF_ADDR 0x1f000000 +#define EXTIF_UART (EXTIF_ADDR + 0x00800000) + +#define GPIO_TYPE_NORMAL (0x0 << 24) +#define GPIO_TYPE_EXTIF (0x1 << 24) +#define GPIO_TYPE_MASK (0xf << 24) + +static inline void gpio_set_extif(int gpio, int value) +{ + volatile u8 *addr = (volatile u8 *) KSEG1ADDR(EXTIF_UART) + (gpio & ~GPIO_TYPE_MASK); + if (value) + *addr = 0xFF; + else + *addr; +} + +#endif /* __DIAG_GPIO_H */ diff --git a/package/rtc-rv5c386a/src/rtc.c b/package/rtc-rv5c386a/src/rtc.c new file mode 100644 index 000000000..34d0a83aa --- /dev/null +++ b/package/rtc-rv5c386a/src/rtc.c @@ -0,0 +1,593 @@ +/* + * Real Time Clock driver for WL-HDD + * + * Copyright (C) 2007 Andreas Engel + * + * Hacked together mostly by copying the relevant code parts from: + * drivers/i2c/i2c-bcm5365.c + * drivers/i2c/i2c-algo-bit.c + * drivers/char/rtc.c + * + * Note 1: + * This module uses the standard char device (10,135), while the Asus module + * rtcdrv.o uses (12,0). So, both can coexist which might be handy during + * development (but see the comment in rtc_open()). + * + * Note 2: + * You might need to set the clock once after loading the driver the first + * time because the driver switches the chip into 24h mode if it is running + * in 12h mode. + * + * Usage: + * For compatibility reasons with the original asus driver, the time can be + * read and set via the /dev/rtc device entry. The only accepted data format + * is "YYYY:MM:DD:W:HH:MM:SS\n". See OpenWrt wiki for a script which handles + * this format. + * + * In addition, this driver supports the standard ioctl() calls for setting + * and reading the hardware clock, so the ordinary hwclock utility can also + * be used. + * + * 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. + * + * TODO: + * - add a /proc/driver/rtc interface? + * - make the battery failure bit available through the /proc interface? + * + * $Id: rtc.c 7 2007-05-25 19:37:01Z ae $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gpio.h" + +#define RTC_IS_OPEN 0x01 /* Means /dev/rtc is in use. */ + +/* Can be changed via a module parameter. */ +static int rtc_debug = 0; + +static unsigned long rtc_status = 0; /* Bitmapped status byte. */ + +static spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED; + +/* These settings are platform dependents. */ +unsigned int sda_index = 0; +unsigned int scl_index = 0; + +#define I2C_READ_MASK 1 +#define I2C_WRITE_MASK 0 + +#define I2C_ACK 1 +#define I2C_NAK 0 + +#define RTC_EPOCH 1900 +#define RTC_I2C_ADDRESS (0x32 << 1) +#define RTC_24HOUR_MODE_MASK 0x20 +#define RTC_PM_MASK 0x20 +#define RTC_VDET_MASK 0x40 +#define RTC_Y2K_MASK 0x80 + +/* + * Delay in microseconds for generating the pulses on the I2C bus. We use + * a rather conservative setting here. See datasheet of the RTC chip. + */ +#define ADAP_DELAY 50 + +/* Avoid spurious compiler warnings. */ +#define UNUSED __attribute__((unused)) + +MODULE_AUTHOR("Andreas Engel"); +MODULE_LICENSE("GPL"); + +/* Test stolen from switch-adm.c. */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,52) +module_param(rtc_debug, int, 0); +#else +MODULE_PARM(rtc_debug, "i"); +#endif + +static inline void sdalo(void) +{ + gpio_direction_output(sda_index, 1); + udelay(ADAP_DELAY); +} + +static inline void sdahi(void) +{ + gpio_direction_input(sda_index); + udelay(ADAP_DELAY); +} + +static inline void scllo(void) +{ + gpio_direction_output(scl_index, 1); + udelay(ADAP_DELAY); +} + +static inline int getscl(void) +{ + return (gpio_get_value(scl_index)); +} + +static inline int getsda(void) +{ + return (gpio_get_value(sda_index)); +} + +/* + * We shouldn't simply set the SCL pin to high. Like SDA, the SCL line is + * bidirectional too. According to the I2C spec, the slave is allowed to + * pull down the SCL line to slow down the clock, so we need to check this. + * Generally, we'd need a timeout here, but in our case, we just check the + * line, assuming the RTC chip behaves well. + */ +static int sclhi(void) +{ + gpio_direction_input(scl_index); + udelay(ADAP_DELAY); + if (!getscl()) { + printk(KERN_ERR "SCL pin should be low\n"); + return -ETIMEDOUT; + } + return 0; +} + +static void i2c_start(void) +{ + sdalo(); + scllo(); +} + +static void i2c_stop(void) +{ + sdalo(); + sclhi(); + sdahi(); +} + +static int i2c_outb(int c) +{ + int i; + int ack; + + /* assert: scl is low */ + for (i = 7; i >= 0; i--) { + if (c & ( 1 << i )) { + sdahi(); + } else { + sdalo(); + } + if (sclhi() < 0) { /* timed out */ + sdahi(); /* we don't want to block the net */ + return -ETIMEDOUT; + }; + scllo(); + } + sdahi(); + if (sclhi() < 0) { + return -ETIMEDOUT; + }; + /* read ack: SDA should be pulled down by slave */ + ack = getsda() == 0; /* ack: sda is pulled low ->success. */ + scllo(); + + if (rtc_debug) + printk(KERN_DEBUG "i2c_outb(0x%02x) -> %s\n", + c, ack ? "ACK": "NAK"); + + return ack; /* return 1 if device acked */ + /* assert: scl is low (sda undef) */ +} + +static int i2c_inb(int ack) +{ + int i; + unsigned int indata = 0; + + /* assert: scl is low */ + + sdahi(); + for (i = 0; i < 8; i++) { + if (sclhi() < 0) { + return -ETIMEDOUT; + }; + indata *= 2; + if (getsda()) + indata |= 0x01; + scllo(); + } + if (ack) { + sdalo(); + } else { + sdahi(); + } + + if (sclhi() < 0) { + sdahi(); + return -ETIMEDOUT; + } + scllo(); + sdahi(); + + if (rtc_debug) + printk(KERN_DEBUG "i2c_inb() -> 0x%02x\n", indata); + + /* assert: scl is low */ + return indata & 0xff; +} + +static void i2c_init(void) +{ + /* no gpio_control for EXTIF */ + // gpio_control(sda_mask | scl_mask, 0); + + gpio_set_value(sda_index, 0); + gpio_set_value(scl_index, 0); + sdahi(); + sclhi(); +} + +static int rtc_open(UNUSED struct inode *inode, UNUSED struct file *filp) +{ + spin_lock_irq(&rtc_lock); + + if (rtc_status & RTC_IS_OPEN) { + spin_unlock_irq(&rtc_lock); + return -EBUSY; + } + + rtc_status |= RTC_IS_OPEN; + + /* + * The following call is only necessary if we use both this driver and + * the proprietary one from asus at the same time (which, b.t.w. only + * makes sense during development). Otherwise, each access via the asus + * driver will make access via this driver impossible. + */ + i2c_init(); + + spin_unlock_irq(&rtc_lock); + + return 0; +} + +static int rtc_release(UNUSED struct inode *inode, UNUSED struct file *filp) +{ + /* No need for locking here. */ + rtc_status &= ~RTC_IS_OPEN; + return 0; +} + +static int from_bcd(int bcdnum) +{ + int fac, num = 0; + + for (fac = 1; bcdnum; fac *= 10) { + num += (bcdnum % 16) * fac; + bcdnum /= 16; + } + + return num; +} + +static int to_bcd(int decnum) +{ + int fac, num = 0; + + for (fac = 1; decnum; fac *= 16) { + num += (decnum % 10) * fac; + decnum /= 10; + } + + return num; +} + +static void get_rtc_time(struct rtc_time *rtc_tm) +{ + int cr2; + + /* + * Read date and time from the RTC. We use read method (3). + */ + + i2c_start(); + i2c_outb(RTC_I2C_ADDRESS | I2C_READ_MASK); + cr2 = i2c_inb(I2C_ACK); + rtc_tm->tm_sec = i2c_inb(I2C_ACK); + rtc_tm->tm_min = i2c_inb(I2C_ACK); + rtc_tm->tm_hour = i2c_inb(I2C_ACK); + rtc_tm->tm_wday = i2c_inb(I2C_ACK); + rtc_tm->tm_mday = i2c_inb(I2C_ACK); + rtc_tm->tm_mon = i2c_inb(I2C_ACK); + rtc_tm->tm_year = i2c_inb(I2C_NAK); + i2c_stop(); + + if (cr2 & RTC_VDET_MASK) { + printk(KERN_WARNING "***RTC BATTERY FAILURE***\n"); + } + + /* Handle century bit */ + if (rtc_tm->tm_mon & RTC_Y2K_MASK) { + rtc_tm->tm_mon &= ~RTC_Y2K_MASK; + rtc_tm->tm_year += 0x100; + } + + rtc_tm->tm_sec = from_bcd(rtc_tm->tm_sec); + rtc_tm->tm_min = from_bcd(rtc_tm->tm_min); + rtc_tm->tm_hour = from_bcd(rtc_tm->tm_hour); + rtc_tm->tm_mday = from_bcd(rtc_tm->tm_mday); + rtc_tm->tm_mon = from_bcd(rtc_tm->tm_mon) - 1; + rtc_tm->tm_year = from_bcd(rtc_tm->tm_year); + + rtc_tm->tm_isdst = -1; /* DST not known */ +} + +static void set_rtc_time(struct rtc_time *rtc_tm) +{ + rtc_tm->tm_sec = to_bcd(rtc_tm->tm_sec); + rtc_tm->tm_min = to_bcd(rtc_tm->tm_min); + rtc_tm->tm_hour = to_bcd(rtc_tm->tm_hour); + rtc_tm->tm_mday = to_bcd(rtc_tm->tm_mday); + rtc_tm->tm_mon = to_bcd(rtc_tm->tm_mon + 1); + rtc_tm->tm_year = to_bcd(rtc_tm->tm_year); + + if (rtc_tm->tm_year >= 0x100) { + rtc_tm->tm_year -= 0x100; + rtc_tm->tm_mon |= RTC_Y2K_MASK; + } + + i2c_start(); + i2c_outb(RTC_I2C_ADDRESS | I2C_WRITE_MASK); + i2c_outb(0x00); /* set starting register to 0 (=seconds) */ + i2c_outb(rtc_tm->tm_sec); + i2c_outb(rtc_tm->tm_min); + i2c_outb(rtc_tm->tm_hour); + i2c_outb(rtc_tm->tm_wday); + i2c_outb(rtc_tm->tm_mday); + i2c_outb(rtc_tm->tm_mon); + i2c_outb(rtc_tm->tm_year); + i2c_stop(); +} + +static ssize_t rtc_write(UNUSED struct file *filp, const char *buf, + size_t count, loff_t *ppos) +{ + struct rtc_time rtc_tm; + char buffer[23]; + char *p; + + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + if (ppos != &filp->f_pos) + return -ESPIPE; + + /* + * For simplicity, the only acceptable format is: + * YYYY:MM:DD:W:HH:MM:SS\n + */ + + if (count != 22) + goto err_out; + + if (copy_from_user(buffer, buf, count)) + return -EFAULT; + + buffer[sizeof(buffer)-1] = '\0'; + + p = &buffer[0]; + + rtc_tm.tm_year = simple_strtoul(p, &p, 10); + if (*p++ != ':') goto err_out; + + rtc_tm.tm_mon = simple_strtoul(p, &p, 10) - 1; + if (*p++ != ':') goto err_out; + + rtc_tm.tm_mday = simple_strtoul(p, &p, 10); + if (*p++ != ':') goto err_out; + + rtc_tm.tm_wday = simple_strtoul(p, &p, 10); + if (*p++ != ':') goto err_out; + + rtc_tm.tm_hour = simple_strtoul(p, &p, 10); + if (*p++ != ':') goto err_out; + + rtc_tm.tm_min = simple_strtoul(p, &p, 10); + if (*p++ != ':') goto err_out; + + rtc_tm.tm_sec = simple_strtoul(p, &p, 10); + if (*p != '\n') goto err_out; + + rtc_tm.tm_year -= RTC_EPOCH; + + set_rtc_time(&rtc_tm); + + *ppos += count; + + return count; + + err_out: + printk(KERN_ERR "invalid format: use YYYY:MM:DD:W:HH:MM:SS\\n\n"); + return -EINVAL; +} + + +static ssize_t rtc_read(UNUSED struct file *filp, char *buf, size_t count, + loff_t *ppos) +{ + char wbuf[23]; + struct rtc_time tm; + ssize_t len; + + if (count == 0 || *ppos != 0) + return 0; + + get_rtc_time(&tm); + + len = sprintf(wbuf, "%04d:%02d:%02d:%d:%02d:%02d:%02d\n", + tm.tm_year + RTC_EPOCH, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_wday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec); + + if (len > (ssize_t)count) + len = count; + + if (copy_to_user(buf, wbuf, len)) + return -EFAULT; + + *ppos += len; + + return len; +} + +static int rtc_ioctl(UNUSED struct inode *inode, UNUSED struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct rtc_time rtc_tm; + + switch (cmd) { + case RTC_RD_TIME: + memset(&rtc_tm, 0, sizeof(struct rtc_time)); + get_rtc_time(&rtc_tm); + if (copy_to_user((void *)arg, &rtc_tm, sizeof(rtc_tm))) + return -EFAULT; + break; + + case RTC_SET_TIME: + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + if (copy_from_user(&rtc_tm, (struct rtc_time *)arg, + sizeof(struct rtc_time))) + return -EFAULT; + + set_rtc_time(&rtc_tm); + break; + + default: + return -ENOTTY; + } + + return 0; +} + +static struct file_operations rtc_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = rtc_read, + .write = rtc_write, + .ioctl = rtc_ioctl, + .open = rtc_open, + .release = rtc_release, +}; + +static struct miscdevice rtc_dev = { + .minor = RTC_MINOR, + .name = "rtc", + .fops = &rtc_fops, +}; + +/* Savagely ripped from diag.c. */ +extern char *nvram_get(char *str); +#define getvar(str) (nvram_get(str)?:"") +static inline int startswith (char *source, char *cmp) +{ return !strncmp(source,cmp,strlen(cmp)); } +static void platform_detect(void) +{ + char *buf; + + /* Based on "model_no". */ + if ((buf = nvram_get("model_no"))) { + if (startswith(buf,"WL700")) { /* WL700* */ + sda_index = 2; + scl_index = 5; + return; + } + } + + if (startswith(getvar("hardware_version"), "WL300-")) { + /* Either WL-300g or WL-HDD, do more extensive checks */ + if ((simple_strtoul(getvar("et0phyaddr"), NULL, 0) == 0) && + (simple_strtoul(getvar("et1phyaddr"), NULL, 0) == 1)) { + sda_index = 4; + scl_index = 5; + return; + } + } + /* not found */ +} + +static int __init rtc_init(void) +{ + int cr1; + + platform_detect(); + + if (sda_index == scl_index) { + printk(KERN_ERR "RTC-RV5C386A: unrecognized platform!\n"); + return -ENODEV; + } + + i2c_init(); + + /* + * Switch RTC to 24h mode + */ + i2c_start(); + i2c_outb(RTC_I2C_ADDRESS | I2C_WRITE_MASK); + i2c_outb(0xE4); /* start at address 0xE, transmission mode 4 */ + cr1 = i2c_inb(I2C_NAK); + i2c_stop(); + if ((cr1 & RTC_24HOUR_MODE_MASK) == 0) { + /* RTC is running in 12h mode */ + printk(KERN_INFO "rtc.o: switching to 24h mode\n"); + i2c_start(); + i2c_outb(RTC_I2C_ADDRESS | I2C_WRITE_MASK); + i2c_outb(0xE0); + i2c_outb(cr1 | RTC_24HOUR_MODE_MASK); + i2c_stop(); + } + + misc_register(&rtc_dev); + + printk(KERN_INFO "RV5C386A Real Time Clock Driver loaded\n"); + + return 0; +} + +static void __exit rtc_exit (void) +{ + misc_deregister(&rtc_dev); + printk(KERN_INFO "Successfully removed RTC RV5C386A driver\n"); +} + +module_init(rtc_init); +module_exit(rtc_exit); + +/* + * Local Variables: + * indent-tabs-mode:t + * c-basic-offset:8 + * End: + */