From 7966cc04ea47f251b847b3349393a4e7a4531c70 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Juha=20Yrj=C3=B6l=C3=A4?= Date: Tue, 27 Feb 2007 02:13:32 -0800 Subject: [PATCH] Add CBUS support MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit CBUS is the bus that Energy Management ASICs are connected to on some Nokia mobile devices. Added support for CBUS and two EM ASIC drivers. Also added board config structures for some of the Nokia 770 drivers. Signed-off-by: Juha Yrjölä --- arch/arm/Kconfig | 4 + drivers/Makefile | 2 +- drivers/cbus/Kconfig | 81 ++++ drivers/cbus/Makefile | 13 + drivers/cbus/cbus.c | 293 +++++++++++++ drivers/cbus/cbus.h | 36 ++ drivers/cbus/retu-pwrbutton.c | 119 ++++++ drivers/cbus/retu-rtc.c | 477 +++++++++++++++++++++ drivers/cbus/retu-user.c | 423 +++++++++++++++++++ drivers/cbus/retu-wdt.c | 198 +++++++++ drivers/cbus/retu.c | 454 ++++++++++++++++++++ drivers/cbus/retu.h | 73 ++++ drivers/cbus/tahvo-usb.c | 745 +++++++++++++++++++++++++++++++++ drivers/cbus/tahvo-user.c | 405 ++++++++++++++++++ drivers/cbus/tahvo.c | 441 +++++++++++++++++++ drivers/cbus/tahvo.h | 61 +++ drivers/cbus/user_retu_tahvo.h | 75 ++++ 17 files changed, 3899 insertions(+), 1 deletion(-) create mode 100644 drivers/cbus/Kconfig create mode 100644 drivers/cbus/Makefile create mode 100644 drivers/cbus/cbus.c create mode 100644 drivers/cbus/cbus.h create mode 100644 drivers/cbus/retu-pwrbutton.c create mode 100644 drivers/cbus/retu-rtc.c create mode 100644 drivers/cbus/retu-user.c create mode 100644 drivers/cbus/retu-wdt.c create mode 100644 drivers/cbus/retu.c create mode 100644 drivers/cbus/retu.h create mode 100644 drivers/cbus/tahvo-usb.c create mode 100644 drivers/cbus/tahvo-user.c create mode 100644 drivers/cbus/tahvo.c create mode 100644 drivers/cbus/tahvo.h create mode 100644 drivers/cbus/user_retu_tahvo.h diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index d75879d9dde..f86656a7d1f 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -987,6 +987,10 @@ source "drivers/mmc/Kconfig" source "drivers/rtc/Kconfig" +if ARCH_OMAP +source "drivers/cbus/Kconfig" +endif + endmenu source "fs/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 3a718f51350..41c2096ffea 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -27,7 +27,7 @@ obj-$(CONFIG_FB_INTEL) += video/intelfb/ obj-y += serial/ obj-$(CONFIG_PARPORT) += parport/ -obj-y += base/ block/ misc/ mfd/ net/ media/ +obj-y += base/ block/ misc/ mfd/ net/ media/ cbus/ obj-$(CONFIG_NUBUS) += nubus/ obj-$(CONFIG_ATM) += atm/ obj-y += macintosh/ diff --git a/drivers/cbus/Kconfig b/drivers/cbus/Kconfig new file mode 100644 index 00000000000..f1a5c63e8c9 --- /dev/null +++ b/drivers/cbus/Kconfig @@ -0,0 +1,81 @@ +# +# CBUS device configuration +# + +menu "CBUS support" + +config CBUS + depends on ARCH_OMAP + bool "CBUS support on OMAP" + ---help--- + CBUS is a proprietary serial protocol by Nokia. It is mainly + used for accessing Energy Management auxiliary chips. + + If you want CBUS support, you should say Y here. + +config CBUS_TAHVO + depends on CBUS + bool "Support for Tahvo" + ---help--- + Tahvo is a mixed signal ASIC with some system features + + If you want Tahvo support, you should say Y here. + +config CBUS_TAHVO_USER + depends on CBUS_TAHVO + bool "Support for Tahvo user space functions" + ---help--- + If you want support for Tahvo's user space read/write etc. functions, + you should say Y here. + +config CBUS_TAHVO_USB + depends on CBUS_TAHVO && USB + tristate "Support for Tahvo USB transceiver" + ---help--- + If you want Tahvo support for USB transceiver, say Y or M here. + +config CBUS_TAHVO_USB_HOST_BY_DEFAULT + depends on CBUS_TAHVO_USB && USB_OTG + boolean "Device in USB host mode by default" + ---help--- + Say Y here, if you want the device to enter USB host mode + by default on bootup. + +config CBUS_RETU + depends on CBUS + bool "Support for Retu" + ---help--- + Retu is a mixed signal ASIC with some system features + + If you want Retu support, you should say Y here. + +config CBUS_RETU_USER + depends on CBUS_RETU + bool "Support for Retu user space functions" + ---help--- + If you want support for Retu's user space read/write etc. functions, + you should say Y here. + +config CBUS_RETU_POWERBUTTON + depends on CBUS_RETU + bool "Support for Retu power button" + ---help--- + The power button on Nokia 770 is connected to the Retu ASIC. + + If you want support for the Retu power button, you should say Y here. + +config CBUS_RETU_RTC + depends on CBUS_RETU && SYSFS + tristate "Support for Retu pseudo-RTC" + ---help--- + Say Y here if you want support for the device that alleges to be an + RTC in Retu. This will expose a sysfs interface for it. + +config CBUS_RETU_WDT + depends on CBUS_RETU && SYSFS + tristate "Support for Retu watchdog timer" + ---help--- + Say Y here if you want support for the watchdog in Retu. This will + expose a sysfs interface to grok it. + +endmenu diff --git a/drivers/cbus/Makefile b/drivers/cbus/Makefile new file mode 100644 index 00000000000..6390139fb96 --- /dev/null +++ b/drivers/cbus/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for CBUS. +# + +obj-$(CONFIG_CBUS) += cbus.o +obj-$(CONFIG_CBUS_TAHVO) += tahvo.o +obj-$(CONFIG_CBUS_RETU) += retu.o +obj-$(CONFIG_CBUS_TAHVO_USB) += tahvo-usb.o +obj-$(CONFIG_CBUS_RETU_POWERBUTTON) += retu-pwrbutton.o +obj-$(CONFIG_CBUS_RETU_RTC) += retu-rtc.o +obj-$(CONFIG_CBUS_RETU_WDT) += retu-wdt.o +obj-$(CONFIG_CBUS_TAHVO_USER) += tahvo-user.o +obj-$(CONFIG_CBUS_RETU_USER) += retu-user.o diff --git a/drivers/cbus/cbus.c b/drivers/cbus/cbus.c new file mode 100644 index 00000000000..eff38be5088 --- /dev/null +++ b/drivers/cbus/cbus.c @@ -0,0 +1,293 @@ +/* + * drivers/cbus/cbus.c + * + * Support functions for CBUS serial protocol + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Juha Yrjölä , + * David Weinehall , and + * Mikko Ylinen + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "cbus.h" + +struct cbus_host *cbus_host = NULL; + +#ifdef CONFIG_ARCH_OMAP1 +/* We use our own MPUIO functions to get closer to 1MHz bus speed */ + +static inline void cbus_set_gpio_direction(u32 base, int mpuio, int is_input) +{ + u16 w; + + mpuio &= 0x0f; + w = __raw_readw(base + OMAP_MPUIO_IO_CNTL); + if (is_input) + w |= 1 << mpuio; + else + w &= ~(1 << mpuio); + __raw_writew(w, base + OMAP_MPUIO_IO_CNTL); + +} + +static inline void cbus_set_gpio_dataout(u32 base, int mpuio, int enable) +{ + u16 w; + + mpuio &= 0x0f; + w = __raw_readw(base + OMAP_MPUIO_OUTPUT); + if (enable) + w |= 1 << mpuio; + else + w &= ~(1 << mpuio); + __raw_writew(w, base + OMAP_MPUIO_OUTPUT); +} + +static inline int cbus_get_gpio_datain(u32 base, int mpuio) +{ + mpuio &= 0x0f; + + return (__raw_readw(base + OMAP_MPUIO_INPUT_LATCH) & (1 << mpuio)) != 0; +} + +static void cbus_send_bit(struct cbus_host *host, u32 base, int bit, + int set_to_input) +{ + cbus_set_gpio_dataout(base, host->dat_gpio, bit ? 1 : 0); + cbus_set_gpio_dataout(base, host->clk_gpio, 1); + + /* The data bit is read on the rising edge of CLK */ + if (set_to_input) + cbus_set_gpio_direction(base, host->dat_gpio, 1); + + cbus_set_gpio_dataout(base, host->clk_gpio, 0); +} + +static u8 cbus_receive_bit(struct cbus_host *host, u32 base) +{ + u8 ret; + + cbus_set_gpio_dataout(base, host->clk_gpio, 1); + ret = cbus_get_gpio_datain(base, host->dat_gpio); + cbus_set_gpio_dataout(base, host->clk_gpio, 0); + + return ret; +} + +#else + +#define cbus_set_gpio_direction(base, gpio, is_input) omap_set_gpio_direction(gpio, is_input) +#define cbus_set_gpio_dataout(base, gpio, enable) omap_set_gpio_dataout(gpio, enable) +#define cbus_get_gpio_datain(base, int, gpio) omap_get_gpio_datain(gpio) + +static void _cbus_send_bit(struct cbus_host *host, int bit, int set_to_input) +{ + omap_set_gpio_dataout(host->dat_gpio, bit ? 1 : 0); + omap_set_gpio_dataout(host->clk_gpio, 1); + + /* The data bit is read on the rising edge of CLK */ + if (set_to_input) + omap_set_gpio_direction(host->dat_gpio, 1); + + omap_set_gpio_dataout(host->clk_gpio, 0); +} + +static u8 _cbus_receive_bit(struct cbus_host *host) +{ + u8 ret; + + omap_set_gpio_dataout(host->clk_gpio, 1); + ret = omap_get_gpio_datain(host->dat_gpio); + omap_set_gpio_dataout(host->clk_gpio, 0); + + return ret; +} + +#define cbus_send_bit(host, base, bit, set_to_input) _cbus_send_bit(host, bit, set_to_input) +#define cbus_receive_bit(host, base) _cbus_receive_bit(host) + +#endif + +static int cbus_transfer(struct cbus_host *host, int dev, int reg, int data) +{ + int i; + int is_read = 0; + unsigned long flags; + u32 base; + +#ifdef CONFIG_ARCH_OMAP1 + base = (u32) io_p2v(OMAP_MPUIO_BASE); +#else + base = 0; +#endif + + if (data < 0) + is_read = 1; + + /* We don't want interrupts disturbing our transfer */ + spin_lock_irqsave(&host->lock, flags); + + /* Reset state and start of transfer, SEL stays down during transfer */ + cbus_set_gpio_dataout(base, host->sel_gpio, 0); + + /* Set the DAT pin to output */ + cbus_set_gpio_direction(base, host->dat_gpio, 0); + + /* Send the device address */ + for (i = 3; i > 0; i--) + cbus_send_bit(host, base, dev & (1 << (i - 1)), 0); + + /* Send the rw flag */ + cbus_send_bit(host, base, is_read, 0); + + /* Send the register address */ + for (i = 5; i > 0; i--) { + int set_to_input = 0; + + if (is_read && i == 1) + set_to_input = 1; + + cbus_send_bit(host, base, reg & (1 << (i - 1)), set_to_input); + } + + if (!is_read) { + for (i = 16; i > 0; i--) + cbus_send_bit(host, base, data & (1 << (i - 1)), 0); + } else { + cbus_set_gpio_dataout(base, host->clk_gpio, 1); + data = 0; + + for (i = 16; i > 0; i--) { + u8 bit = cbus_receive_bit(host, base); + + if (bit) + data |= 1 << (i - 1); + } + } + + /* Indicate end of transfer, SEL goes up until next transfer */ + cbus_set_gpio_dataout(base, host->sel_gpio, 1); + cbus_set_gpio_dataout(base, host->clk_gpio, 1); + cbus_set_gpio_dataout(base, host->clk_gpio, 0); + + spin_unlock_irqrestore(&host->lock, flags); + + return is_read ? data : 0; +} + +/* + * Read a given register from the device + */ +int cbus_read_reg(struct cbus_host *host, int dev, int reg) +{ + return cbus_host ? cbus_transfer(host, dev, reg, -1) : -ENODEV; +} + +/* + * Write to a given register of the device + */ +int cbus_write_reg(struct cbus_host *host, int dev, int reg, u16 val) +{ + return cbus_host ? cbus_transfer(host, dev, reg, (int)val) : -ENODEV; +} + +int __init cbus_bus_init(void) +{ + const struct omap_cbus_config * cbus_config; + struct cbus_host *chost; + int ret; + + chost = kmalloc(sizeof (*chost), GFP_KERNEL); + if (chost == NULL) + return -ENOMEM; + + memset(chost, 0, sizeof (*chost)); + + spin_lock_init(&chost->lock); + + cbus_config = omap_get_config(OMAP_TAG_CBUS, struct omap_cbus_config); + + if (cbus_config == NULL) { + printk(KERN_ERR "cbus: Unable to retrieve config data\n"); + return -ENODATA; + } + + chost->clk_gpio = cbus_config->clk_gpio; + chost->dat_gpio = cbus_config->dat_gpio; + chost->sel_gpio = cbus_config->sel_gpio; + +#ifdef CONFIG_ARCH_OMAP1 + if (!OMAP_GPIO_IS_MPUIO(chost->clk_gpio) || + !OMAP_GPIO_IS_MPUIO(chost->dat_gpio) || + !OMAP_GPIO_IS_MPUIO(chost->sel_gpio)) { + printk(KERN_ERR "cbus: Only MPUIO pins supported\n"); + ret = -ENODEV; + goto exit1; + } +#endif + + if ((ret = omap_request_gpio(chost->clk_gpio)) < 0) + goto exit1; + + if ((ret = omap_request_gpio(chost->dat_gpio)) < 0) + goto exit2; + + if ((ret = omap_request_gpio(chost->sel_gpio)) < 0) + goto exit3; + + omap_set_gpio_dataout(chost->clk_gpio, 0); + omap_set_gpio_dataout(chost->sel_gpio, 1); + + omap_set_gpio_direction(chost->clk_gpio, 0); + omap_set_gpio_direction(chost->dat_gpio, 1); + omap_set_gpio_direction(chost->sel_gpio, 0); + + omap_set_gpio_dataout(chost->clk_gpio, 1); + omap_set_gpio_dataout(chost->clk_gpio, 0); + + cbus_host = chost; + + return 0; +exit3: + omap_free_gpio(chost->dat_gpio); +exit2: + omap_free_gpio(chost->clk_gpio); +exit1: + kfree(chost); + return ret; +} + +subsys_initcall(cbus_bus_init); + +EXPORT_SYMBOL(cbus_host); +EXPORT_SYMBOL(cbus_read_reg); +EXPORT_SYMBOL(cbus_write_reg); + +MODULE_DESCRIPTION("CBUS serial protocol"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Juha Yrjölä, David Weinehall, and Mikko Ylinen"); diff --git a/drivers/cbus/cbus.h b/drivers/cbus/cbus.h new file mode 100644 index 00000000000..957224cf77b --- /dev/null +++ b/drivers/cbus/cbus.h @@ -0,0 +1,36 @@ +/* + * drivers/cbus/cbus.h + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Juha Yrjölä and + * David Weinehall + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __DRIVERS_CBUS_CBUS_H +#define __DRIVERS_CBUS_CBUS_H + +struct cbus_host { + int clk_gpio, dat_gpio, sel_gpio; + spinlock_t lock; +}; + +extern struct cbus_host *cbus_host; + +extern int cbus_read_reg(struct cbus_host *host, int dev, int reg); +extern int cbus_write_reg(struct cbus_host *host, int dev, int reg, u16 val); + +#endif /* __DRIVERS_CBUS_CBUS_H */ diff --git a/drivers/cbus/retu-pwrbutton.c b/drivers/cbus/retu-pwrbutton.c new file mode 100644 index 00000000000..ad37b6d051b --- /dev/null +++ b/drivers/cbus/retu-pwrbutton.c @@ -0,0 +1,119 @@ +/** + * drivers/cbus/retu-pwrbutton.c + * + * Driver for sending retu power button event to input-layer + * + * Copyright (C) 2004 Nokia Corporation + * + * Written by Ari Saastamoinen + * + * Contact Juha Yrjölä + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "retu.h" + +#define RETU_STATUS_PWRONX (1 << 5) + +#define PWRBTN_DELAY 20 +#define PWRBTN_UP 0 +#define PWRBTN_PRESSED 1 + +static int pwrbtn_state; +static struct input_dev *pwrbtn_dev; +static struct timer_list pwrbtn_timer; + +static void retubutton_timer_func(unsigned long arg) +{ + int state; + + if (retu_read_reg(RETU_REG_STATUS) & RETU_STATUS_PWRONX) + state = PWRBTN_UP; + else + state = PWRBTN_PRESSED; + + if (pwrbtn_state != state) { + input_report_key(pwrbtn_dev, KEY_POWER, state); + pwrbtn_state = state; + } +} + +/** + * Interrupt function is called whenever power button key is pressed + * or released. + */ +static void retubutton_irq(unsigned long arg) +{ + retu_ack_irq(RETU_INT_PWR); + mod_timer(&pwrbtn_timer, jiffies + msecs_to_jiffies(PWRBTN_DELAY)); +} + +/** + * Init function. + * Allocates interrupt for power button and registers itself to input layer. + */ +static int __init retubutton_init(void) +{ + int irq; + + printk(KERN_INFO "Retu power button driver initialized\n"); + irq = RETU_INT_PWR; + + init_timer(&pwrbtn_timer); + pwrbtn_timer.function = retubutton_timer_func; + + if (retu_request_irq(irq, &retubutton_irq, 0, "PwrOnX") < 0) { + printk(KERN_ERR "%s@%s: Cannot allocate irq\n", + __FUNCTION__, __FILE__); + return -EBUSY; + } + + pwrbtn_dev = input_allocate_device(); + if (!pwrbtn_dev) + return -ENOMEM; + + pwrbtn_dev->evbit[0] = BIT(EV_KEY); + pwrbtn_dev->keybit[LONG(KEY_POWER)] = BIT(KEY_POWER); + pwrbtn_dev->name = "retu-pwrbutton"; + + input_register_device(pwrbtn_dev); + + return 0; +} + +/** + * Cleanup function which is called when driver is unloaded + */ +static void __exit retubutton_exit(void) +{ + retu_free_irq(RETU_INT_PWR); + del_timer_sync(&pwrbtn_timer); + input_unregister_device(pwrbtn_dev); +} + +module_init(retubutton_init); +module_exit(retubutton_exit); + +MODULE_DESCRIPTION("Retu Power Button"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ari Saastamoinen"); diff --git a/drivers/cbus/retu-rtc.c b/drivers/cbus/retu-rtc.c new file mode 100644 index 00000000000..b36fd06323a --- /dev/null +++ b/drivers/cbus/retu-rtc.c @@ -0,0 +1,477 @@ +/** + * drivers/cbus/retu-rtc.c + * + * Support for Retu RTC + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Paul Mundt and + * Igor Stoppa + * + * The Retu RTC is essentially a partial read-only RTC that gives us Retu's + * idea of what time actually is. It's left as a userspace excercise to map + * this back to time in the real world and ensure that calibration settings + * are sane to compensate for any horrible drift (on account of not being able + * to set the clock to anything). + * + * Days are semi-writeable. Namely, Retu will only track 255 days for us + * consecutively, after which the counter is explicitly stuck at 255 until + * someone comes along and clears it with a write. In the event that no one + * comes along and clears it, we no longer have any idea what day it is. + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cbus.h" +#include "retu.h" + +static struct mutex retu_rtc_mutex; +static u16 retu_rtc_alarm_expired; +static u16 retu_rtc_reset_occurred; + +static DECLARE_COMPLETION(retu_rtc_exited); +static DECLARE_COMPLETION(retu_rtc_sync); + +static void retu_rtc_barrier(void); + +static void retu_rtc_device_release(struct device *dev) +{ + complete(&retu_rtc_exited); +} + +static ssize_t retu_rtc_time_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + u16 dsr, hmr, dsr2; + + mutex_lock(&retu_rtc_mutex); + + do { + u16 dummy; + + /* + * Not being in_interrupt() for a retu rtc IRQ, we need to + * read twice for consistency.. + */ + dummy = retu_read_reg(RETU_REG_RTCDSR); + dsr = retu_read_reg(RETU_REG_RTCDSR); + + dummy = retu_read_reg(RETU_REG_RTCHMR); + hmr = retu_read_reg(RETU_REG_RTCHMR); + + dummy = retu_read_reg(RETU_REG_RTCDSR); + dsr2 = retu_read_reg(RETU_REG_RTCDSR); + } while ((dsr != dsr2)); + + mutex_unlock(&retu_rtc_mutex); + + /* + * Format a 32-bit date-string for userspace + * + * days | hours | minutes | seconds + * + * 8 bits for each. + * + * This mostly sucks because days and seconds are tracked in RTCDSR + * while hours and minutes are tracked in RTCHMR. And yes, there + * really are no words that can describe an 8 bit day register (or + * rather, none that will be reprinted here). + */ + return sprintf(buf, "0x%08x\n", (((dsr >> 8) & 0xff) << 24) | + (((hmr >> 8) & 0x1f) << 16) | + ((hmr & 0x3f) << 8) | (dsr & 0x3f)); +} + +static ssize_t retu_rtc_time_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + mutex_lock(&retu_rtc_mutex); + /* + * Writing anything to the day counter forces it to 0 + * The seconds counter would be cleared by resetting the minutes counter, + * however this won't happen, since we are using the hh:mm counters as + * a set of free running counters and the day counter as a multiple + * overflow holder. + */ + + /* Reset day counter, but keep Temperature Shutdown state */ + retu_write_reg(RETU_REG_RTCDSR, + retu_read_reg(RETU_REG_RTCDSR) & (1 << 6)); + + mutex_unlock(&retu_rtc_mutex); + + return count; +} + +static DEVICE_ATTR(time, S_IRUGO | S_IWUSR, retu_rtc_time_show, + retu_rtc_time_store); + + +static ssize_t retu_rtc_reset_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + /* + * Returns the status of the rtc + * + * 0: no reset has occurred or the status has been cleared + * 1: a reset has occurred + * + * RTC needs to be reset only when both main battery + * _AND_ backup battery are discharged + */ + return sprintf(buf, "%u\n", retu_rtc_reset_occurred); +} + +static void retu_rtc_do_reset(void) +{ + u16 ccr1; + + ccr1 = retu_read_reg(RETU_REG_CC1); + /* RTC in reset */ + retu_write_reg(RETU_REG_CC1, ccr1 | 0x0001); + /* RTC in normal operating mode */ + retu_write_reg(RETU_REG_CC1, ccr1 & ~0x0001); + + retu_rtc_barrier(); + /* Disable alarm and RTC WD */ + retu_write_reg(RETU_REG_RTCHMAR, 0x7f3f); + /* Set Calibration register to default value */ + retu_write_reg(RETU_REG_RTCCALR, 0x00c0); + + retu_rtc_alarm_expired = 0; + retu_rtc_reset_occurred = 1; +} + +static ssize_t retu_rtc_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned choice; + + if(sscanf(buf, "%u", &choice) != 1) + return count; + mutex_lock(&retu_rtc_mutex); + if (choice == 0) + retu_rtc_reset_occurred = 0; + else if (choice == 1) + retu_rtc_do_reset(); + mutex_unlock(&retu_rtc_mutex); + return count; +} + +static DEVICE_ATTR(reset, S_IRUGO | S_IWUSR, retu_rtc_reset_show, + retu_rtc_reset_store); + +static ssize_t retu_rtc_alarm_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + u16 chmar; + ssize_t retval; + + mutex_lock(&retu_rtc_mutex); + /* + * Format a 16-bit date-string for userspace + * + * hours | minutes + * 8 bits for each. + */ + chmar = retu_read_reg(RETU_REG_RTCHMAR); + /* No shifting needed, only masking unrelated bits */ + retval = sprintf(buf, "0x%04x\n", chmar & 0x1f3f); + mutex_unlock(&retu_rtc_mutex); + + return retval; +} + +static ssize_t retu_rtc_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u16 chmar; + unsigned alrm; + unsigned hours; + unsigned minutes; + + mutex_lock(&retu_rtc_mutex); + + if(sscanf(buf, "%x", &alrm) != 1) + return count; + hours = (alrm >> 8) & 0x001f; + minutes = (alrm >> 0) & 0x003f; + if ((hours < 24 && minutes < 60) || (hours == 24 && minutes == 60)) { + /* + * OK, the time format for the alarm is valid (including the + * disabling values) + */ + /* Keeps the RTC watchdog status */ + chmar = retu_read_reg(RETU_REG_RTCHMAR) & 0x6000; + chmar |= alrm & 0x1f3f; /* Stores the requested alarm */ + retu_rtc_barrier(); + retu_write_reg(RETU_REG_RTCHMAR, chmar); + /* If the alarm is being disabled */ + if (hours == 24 && minutes == 60) { + /* disable the interrupt */ + retu_disable_irq(RETU_INT_RTCA); + retu_rtc_alarm_expired = 0; + } else + /* enable the interrupt */ + retu_enable_irq(RETU_INT_RTCA); + } + mutex_unlock(&retu_rtc_mutex); + + return count; +} + +static DEVICE_ATTR(alarm, S_IRUGO | S_IWUSR, retu_rtc_alarm_show, + retu_rtc_alarm_store); + +static ssize_t retu_rtc_alarm_expired_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + ssize_t retval; + + retval = sprintf(buf, "%u\n", retu_rtc_alarm_expired); + + return retval; +} + +static ssize_t retu_rtc_alarm_expired_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + retu_rtc_alarm_expired = 0; + + return count; +} + +static DEVICE_ATTR(alarm_expired, S_IRUGO | S_IWUSR, retu_rtc_alarm_expired_show, + retu_rtc_alarm_expired_store); + + +static ssize_t retu_rtc_cal_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + u16 rtccalr1; + + mutex_lock(&retu_rtc_mutex); + rtccalr1 = retu_read_reg(RETU_REG_RTCCALR); + mutex_unlock(&retu_rtc_mutex); + + /* + * Shows the status of the Calibration Register. + * + * Default, after power loss: 0x0000 + * Default, for R&D: 0x00C0 + * Default, for factory: 0x00?? + * + */ + return sprintf(buf, "0x%04x\n", rtccalr1 & 0x00ff); +} + +static ssize_t retu_rtc_cal_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned calibration_value; + + if (sscanf(buf, "%x", &calibration_value) != 1) + return count; + + mutex_lock(&retu_rtc_mutex); + retu_rtc_barrier(); + retu_write_reg(RETU_REG_RTCCALR, calibration_value & 0x00ff); + mutex_unlock(&retu_rtc_mutex); + + return count; +} + +static DEVICE_ATTR(cal, S_IRUGO | S_IWUSR, retu_rtc_cal_show, + retu_rtc_cal_store); + +static struct device_driver retu_rtc_driver; + +static void retu_rtca_disable(void) +{ + retu_disable_irq(RETU_INT_RTCA); + retu_rtc_alarm_expired = 1; + retu_rtc_barrier(); + retu_write_reg(RETU_REG_RTCHMAR, (24 << 8) | 60); +} + +static void retu_rtca_expired(struct work_struct *unused) +{ + retu_rtca_disable(); + sysfs_notify(&retu_rtc_driver.kobj, NULL, "alarm_expired"); +} + +DECLARE_WORK(retu_rtca_work, retu_rtca_expired); + +/* + * RTCHMR RTCHMAR RTCCAL must be accessed within 0.9 s since the seconds + * interrupt has been signaled in the IDR register + */ +static void retu_rtcs_interrupt(unsigned long unused) +{ + retu_ack_irq(RETU_INT_RTCS); + complete(&retu_rtc_sync); +} + +static void retu_rtca_interrupt(unsigned long unused) +{ + retu_ack_irq(RETU_INT_RTCA); + schedule_work(&retu_rtca_work); +} + +static int retu_rtc_init_irq(void) +{ + int ret; + + ret = retu_request_irq(RETU_INT_RTCS, retu_rtcs_interrupt, 0, "RTCS"); + if (ret != 0) + return ret; + /* + * We will take care of enabling and disabling the interrupt + * elsewhere, so leave it off by default.. + */ + retu_disable_irq(RETU_INT_RTCS); + + ret = retu_request_irq(RETU_INT_RTCA, retu_rtca_interrupt, 0, "RTCA"); + if (ret != 0) { + retu_free_irq(RETU_INT_RTCS); + return ret; + } + retu_disable_irq(RETU_INT_RTCA); + + return 0; +} + + +static int __devinit retu_rtc_probe(struct device *dev) +{ + int r; + + retu_rtc_alarm_expired = retu_read_reg(RETU_REG_IDR) & + (0x1 << RETU_INT_RTCA); + + if ((r = retu_rtc_init_irq()) != 0) + return r; + + mutex_init(&retu_rtc_mutex); + + /* If the calibration register is zero, we've probably lost + * power */ + if (retu_read_reg(RETU_REG_RTCCALR) & 0x00ff) + retu_rtc_reset_occurred = 0; + else + retu_rtc_do_reset(); + + if ((r = device_create_file(dev, &dev_attr_time)) != 0) + return r; + else if ((r = device_create_file(dev, &dev_attr_reset)) != 0) + goto err_unregister_time; + else if ((r = device_create_file(dev, &dev_attr_alarm)) != 0) + goto err_unregister_reset; + else if ((r = device_create_file(dev, &dev_attr_alarm_expired)) != 0) + goto err_unregister_alarm; + else if ((r = device_create_file(dev, &dev_attr_cal)) != 0) + goto err_unregister_alarm_expired; + else + return r; + +err_unregister_alarm_expired: + device_remove_file(dev, &dev_attr_alarm_expired); +err_unregister_alarm: + device_remove_file(dev, &dev_attr_alarm); +err_unregister_reset: + device_remove_file(dev, &dev_attr_reset); +err_unregister_time: + device_remove_file(dev, &dev_attr_time); + return r; +} + +static int __devexit retu_rtc_remove(struct device *dev) +{ + retu_disable_irq(RETU_INT_RTCS); + retu_free_irq(RETU_INT_RTCS); + retu_free_irq(RETU_INT_RTCA); + device_remove_file(dev, &dev_attr_cal); + device_remove_file(dev, &dev_attr_alarm_expired); + device_remove_file(dev, &dev_attr_alarm); + device_remove_file(dev, &dev_attr_reset); + device_remove_file(dev, &dev_attr_time); + return 0; +} + +static struct device_driver retu_rtc_driver = { + .name = "retu-rtc", + .bus = &platform_bus_type, + .probe = retu_rtc_probe, + .remove = __devexit_p(retu_rtc_remove), +}; + +static struct platform_device retu_rtc_device = { + .name = "retu-rtc", + .id = -1, + .dev = { + .release = retu_rtc_device_release, + }, +}; + +/* This function provides syncronization with the RTCS interrupt handler */ +static void retu_rtc_barrier(void) +{ + init_completion(&retu_rtc_sync); + retu_ack_irq(RETU_INT_RTCS); + retu_enable_irq(RETU_INT_RTCS); + wait_for_completion(&retu_rtc_sync); + retu_disable_irq(RETU_INT_RTCS); +} + +static int __init retu_rtc_init(void) +{ + int ret; + + init_completion(&retu_rtc_exited); + + if ((ret = driver_register(&retu_rtc_driver)) != 0) + return ret; + + if ((ret = platform_device_register(&retu_rtc_device)) != 0) + goto err_unregister_driver; + + return 0; + +err_unregister_driver: + driver_unregister(&retu_rtc_driver); + return ret; +} + +static void __exit retu_rtc_exit(void) +{ + platform_device_unregister(&retu_rtc_device); + driver_unregister(&retu_rtc_driver); + + wait_for_completion(&retu_rtc_exited); +} + +module_init(retu_rtc_init); +module_exit(retu_rtc_exit); + +MODULE_DESCRIPTION("Retu RTC"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Paul Mundt and Igor Stoppa"); diff --git a/drivers/cbus/retu-user.c b/drivers/cbus/retu-user.c new file mode 100644 index 00000000000..74a7d612e7e --- /dev/null +++ b/drivers/cbus/retu-user.c @@ -0,0 +1,423 @@ +/** + * drivers/cbus/retu-user.c + * + * Retu user space interface functions + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Mikko Ylinen + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "retu.h" + +#include "user_retu_tahvo.h" + +/* Maximum size of IRQ node buffer/pool */ +#define RETU_MAX_IRQ_BUF_LEN 16 + +#define PFX "retu-user: " + +/* Bitmap for marking the interrupt sources as having the handlers */ +static u32 retu_irq_bits; + +/* For allowing only one user process to subscribe to the retu interrupts */ +static struct file *retu_irq_subscr = NULL; + +/* For poll and IRQ passing */ +struct retu_irq { + u32 id; + struct list_head node; +}; + +static spinlock_t retu_irqs_lock; +static struct retu_irq *retu_irq_block; +static LIST_HEAD(retu_irqs); +static LIST_HEAD(retu_irqs_reserve); + +/* Wait queue - used when user wants to read the device */ +DECLARE_WAIT_QUEUE_HEAD(retu_user_waitqueue); + +/* Semaphore to protect irq subscription sequence */ +static struct mutex retu_mutex; + +/* This array specifies RETU register types (read/write/toggle) */ +static const u8 retu_access_bits[] = { + 1, + 4, + 3, + 3, + 1, + 3, + 3, + 0, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 3, + 0, + 0, + 0, + 0, + 1, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3 +}; + +/* + * The handler for all RETU interrupts. + * + * arg is the interrupt source in RETU. + */ +static void retu_user_irq_handler(unsigned long arg) +{ + struct retu_irq *irq; + + retu_ack_irq(arg); + + spin_lock(&retu_irqs_lock); + if (list_empty(&retu_irqs_reserve)) { + spin_unlock(&retu_irqs_lock); + return; + } + irq = list_entry((&retu_irqs_reserve)->next, struct retu_irq, node); + irq->id = arg; + list_move_tail(&irq->node, &retu_irqs); + spin_unlock(&retu_irqs_lock); + + /* wake up waiting thread */ + wake_up(&retu_user_waitqueue); +} + +/* + * This routine sets up the interrupt handler and marks an interrupt source + * in RETU as a candidate for signal delivery to the user process. + */ +static int retu_user_subscribe_to_irq(int id, struct file *filp) +{ + int ret; + + mutex_lock(&retu_mutex); + if ((retu_irq_subscr != NULL) && (retu_irq_subscr != filp)) { + mutex_unlock(&retu_mutex); + return -EBUSY; + } + /* Store the file pointer of the first user process registering IRQs */ + retu_irq_subscr = filp; + mutex_unlock(&retu_mutex); + + if (retu_irq_bits & (1 << id)) + return 0; + + ret = retu_request_irq(id, retu_user_irq_handler, id, ""); + if (ret < 0) + return ret; + + /* Mark that this interrupt has a handler */ + retu_irq_bits |= 1 << id; + + return 0; +} + +/* + * Unregisters all RETU interrupt handlers. + */ +static void retu_unreg_irq_handlers(void) +{ + int id; + + if (!retu_irq_bits) + return; + + for (id = 0; id < MAX_RETU_IRQ_HANDLERS; id++) + if (retu_irq_bits & (1 << id)) + retu_free_irq(id); + + retu_irq_bits = 0; +} + +/* + * Write to RETU register. + * Returns 0 upon success, a negative error value otherwise. + */ +static int retu_user_write_with_mask(u32 field, u16 value) +{ + u32 mask; + u32 reg; + u_short tmp; + unsigned long flags; + + mask = MASK(field); + reg = REG(field); + + /* Detect bad mask and reg */ + if (mask == 0 || reg > RETU_REG_MAX || + retu_access_bits[reg] == READ_ONLY) { + printk(KERN_ERR PFX "invalid arguments (reg=%#x, mask=%#x)\n", + reg, mask); + return -EINVAL; + } + + /* Justify value according to mask */ + while (!(mask & 1)) { + value = value << 1; + mask = mask >> 1; + } + + spin_lock_irqsave(&retu_lock, flags); + if (retu_access_bits[reg] == TOGGLE) { + /* No need to detect previous content of register */ + tmp = 0; + } else { + /* Read current value of register */ + tmp = retu_read_reg(reg); + } + + /* Generate new value */ + tmp = (tmp & ~MASK(field)) | (value & MASK(field)); + /* Write data to RETU */ + retu_write_reg(reg, tmp); + spin_unlock_irqrestore(&retu_lock, flags); + + return 0; +} + +/* + * Read RETU register. + */ +static u32 retu_user_read_with_mask(u32 field) +{ + u_short value; + u32 mask, reg; + + mask = MASK(field); + reg = REG(field); + + /* Detect bad mask and reg */ + if (mask == 0 || reg > RETU_REG_MAX) { + printk(KERN_ERR PFX "invalid arguments (reg=%#x, mask=%#x)\n", + reg, mask); + return -EINVAL; + } + + /* Read the register */ + value = retu_read_reg(reg) & mask; + + /* Right justify value */ + while (!(mask & 1)) { + value = value >> 1; + mask = mask >> 1; + } + + return value; +} + +/* + * Close device + */ +static int retu_close(struct inode *inode, struct file *filp) +{ + /* Unregister all interrupts that have been registered */ + if (retu_irq_subscr == filp) { + retu_unreg_irq_handlers(); + retu_irq_subscr = NULL; + } + + return 0; +} + +/* + * Device control (ioctl) + */ +static int retu_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct retu_tahvo_write_parms par; + int ret; + + switch (cmd) { + case URT_IOCT_IRQ_SUBSCR: + return retu_user_subscribe_to_irq(arg, filp); + case RETU_IOCH_READ: + return retu_user_read_with_mask(arg); + case RETU_IOCX_WRITE: + ret = copy_from_user(&par, (void __user *) arg, sizeof(par)); + if (ret) + printk(KERN_ERR "copy_from_user failed: %d\n", ret); + par.result = retu_user_write_with_mask(par.field, par.value); + ret = copy_to_user((void __user *) arg, &par, sizeof(par)); + if (ret) + printk(KERN_ERR "copy_to_user failed: %d\n", ret); + break; + case RETU_IOCH_ADC_READ: + return retu_read_adc(arg); + default: + return -ENOIOCTLCMD; + } + return 0; +} + +/* + * Read from device + */ +static ssize_t retu_read(struct file *filp, char *buf, size_t count, + loff_t * offp) +{ + struct retu_irq *irq; + + u32 nr, i; + + /* read not permitted if neither filp nor anyone has registered IRQs */ + if (retu_irq_subscr != filp) + return -EPERM; + + if ((count < sizeof(u32)) || ((count % sizeof(u32)) != 0)) + return -EINVAL; + + nr = count / sizeof(u32); + + for (i = 0; i < nr; i++) { + unsigned long flags; + u32 irq_id; + int ret; + + ret = wait_event_interruptible(retu_user_waitqueue, + !list_empty(&retu_irqs)); + if (ret < 0) + return ret; + + spin_lock_irqsave(&retu_irqs_lock, flags); + irq = list_entry((&retu_irqs)->next, struct retu_irq, node); + irq_id = irq->id; + list_move(&irq->node, &retu_irqs_reserve); + spin_unlock_irqrestore(&retu_irqs_lock, flags); + + ret = copy_to_user(buf + i * sizeof(irq_id), &irq_id, + sizeof(irq_id)); + if (ret) + printk(KERN_ERR "copy_to_user failed: %d\n", ret); + } + + return count; +} + +/* + * Poll method + */ +static unsigned retu_poll(struct file *filp, struct poll_table_struct *pt) +{ + if (!list_empty(&retu_irqs)) + return POLLIN; + + poll_wait(filp, &retu_user_waitqueue, pt); + + if (!list_empty(&retu_irqs)) + return POLLIN; + else + return 0; +} + +static struct file_operations retu_user_fileops = { + .owner = THIS_MODULE, + .ioctl = retu_ioctl, + .read = retu_read, + .release = retu_close, + .poll = retu_poll +}; + +static struct miscdevice retu_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "retu", + .fops = &retu_user_fileops +}; + +/* + * Initialization + * + * @return 0 if successful, error value otherwise. + */ +int retu_user_init(void) +{ + struct retu_irq *irq; + int res, i; + + irq = kmalloc(sizeof(*irq) * RETU_MAX_IRQ_BUF_LEN, GFP_KERNEL); + if (irq == NULL) { + printk(KERN_ERR PFX "kmalloc failed\n"); + return -ENOMEM; + } + memset(irq, 0, sizeof(*irq) * RETU_MAX_IRQ_BUF_LEN); + for (i = 0; i < RETU_MAX_IRQ_BUF_LEN; i++) + list_add(&irq[i].node, &retu_irqs_reserve); + + retu_irq_block = irq; + + spin_lock_init(&retu_irqs_lock); + mutex_init(&retu_mutex); + + /* Request a misc device */ + res = misc_register(&retu_device); + if (res < 0) { + printk(KERN_ERR PFX "unable to register misc device for %s\n", + retu_device.name); + kfree(irq); + return res; + } + + return 0; +} + +/* + * Cleanup. + */ +void retu_user_cleanup(void) +{ + /* Unregister our misc device */ + misc_deregister(&retu_device); + /* Unregister and disable all RETU interrupts used by this module */ + retu_unreg_irq_handlers(); + kfree(retu_irq_block); +} + +MODULE_DESCRIPTION("Retu ASIC user space functions"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mikko Ylinen"); diff --git a/drivers/cbus/retu-wdt.c b/drivers/cbus/retu-wdt.c new file mode 100644 index 00000000000..63194d86367 --- /dev/null +++ b/drivers/cbus/retu-wdt.c @@ -0,0 +1,198 @@ +/** + * drivers/cbus/retu-wdt.c + * + * Driver for Retu watchdog + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Amit Kucheria + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "cbus.h" +#include "retu.h" + +/* Watchdog timeout in seconds */ +#define RETU_WDT_MIN_TIMER 0 +#define RETU_WDT_DEFAULT_TIMER 32 +#define RETU_WDT_MAX_TIMER 63 + +static struct completion retu_wdt_completion; +static DECLARE_MUTEX(retu_wdt_mutex); /* Avoid simultaneous writes to watchdog register */ + +static unsigned int period_val = RETU_WDT_DEFAULT_TIMER; /* Current period of watchdog */ +static int counter_param = RETU_WDT_MAX_TIMER; + +static int retu_modify_counter(unsigned int new) +{ + int ret = 0; + + if (new < RETU_WDT_MIN_TIMER || new > RETU_WDT_MAX_TIMER) + return -EINVAL; + + down_interruptible(&retu_wdt_mutex); + + period_val = new; + retu_write_reg(RETU_REG_WATCHDOG, (u16)period_val); + + up(&retu_wdt_mutex); + return ret; +} + +static ssize_t retu_wdt_period_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + /* Show current max counter */ + return sprintf(buf, "%u\n", (u16)period_val); +} + +static ssize_t retu_wdt_period_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int new_period; + int ret; + + if (sscanf(buf, "%u", &new_period) != 1) { + printk(KERN_ALERT "retu_wdt_period_store: Invalid input\n"); + return -EINVAL; + } + + ret = retu_modify_counter(new_period); + if (ret < 0) + return ret; + + return strnlen(buf, count); +} + +static ssize_t retu_wdt_counter_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + u16 counter; + + /* Show current value in watchdog counter */ + counter = retu_read_reg(RETU_REG_WATCHDOG); + + /* Only the 5 LSB are important */ + return snprintf(buf, PAGE_SIZE, "%u\n", (counter & 0x3F)); +} + +static DEVICE_ATTR(period, S_IRUGO | S_IWUSR, retu_wdt_period_show, \ + retu_wdt_period_store); +static DEVICE_ATTR(counter, S_IRUGO, retu_wdt_counter_show, NULL); + +static int __devinit retu_wdt_probe(struct device *dev) +{ + int ret; + + ret = device_create_file(dev, &dev_attr_period); + if (ret) { + printk(KERN_ERR "retu_wdt_probe: Error creating sys device file: period\n"); + return ret; + } + + ret = device_create_file(dev, &dev_attr_counter); + if (ret) { + device_remove_file(dev, &dev_attr_period); + printk(KERN_ERR "retu_wdt_probe: Error creating sys device file: counter\n"); + } + + return ret; +} + +static int __devexit retu_wdt_remove(struct device *dev) +{ + device_remove_file(dev, &dev_attr_period); + device_remove_file(dev, &dev_attr_counter); + return 0; +} + +static void retu_wdt_device_release(struct device *dev) +{ + complete(&retu_wdt_completion); +} + +static struct platform_device retu_wdt_device = { + .name = "retu-watchdog", + .id = -1, + .dev = { + .release = retu_wdt_device_release, + }, +}; + +static struct device_driver retu_wdt_driver = { + .name = "retu-watchdog", + .bus = &platform_bus_type, + .probe = retu_wdt_probe, + .remove = __devexit_p(retu_wdt_remove), +}; + +static int __init retu_wdt_init(void) +{ + int ret; + + init_completion(&retu_wdt_completion); + + ret = driver_register(&retu_wdt_driver); + if (ret) + return ret; + + ret = platform_device_register(&retu_wdt_device); + if (ret) + goto exit1; + + /* passed as module parameter? */ + ret = retu_modify_counter(counter_param); + if (ret == -EINVAL) { + ret = retu_modify_counter(RETU_WDT_DEFAULT_TIMER); + printk(KERN_INFO + "retu_wdt_init: Intializing to default value\n"); + } + + printk(KERN_INFO "Retu watchdog driver initialized\n"); + return ret; + +exit1: + driver_unregister(&retu_wdt_driver); + wait_for_completion(&retu_wdt_completion); + + return ret; +} + +static void __exit retu_wdt_exit(void) +{ + platform_device_unregister(&retu_wdt_device); + driver_unregister(&retu_wdt_driver); + + wait_for_completion(&retu_wdt_completion); +} + +module_init(retu_wdt_init); +module_exit(retu_wdt_exit); +module_param(counter_param, int, 0); + +MODULE_DESCRIPTION("Retu WatchDog"); +MODULE_AUTHOR("Amit Kucheria"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/cbus/retu.c b/drivers/cbus/retu.c new file mode 100644 index 00000000000..417273a67a4 --- /dev/null +++ b/drivers/cbus/retu.c @@ -0,0 +1,454 @@ +/** + * drivers/cbus/retu.c + * + * Support functions for Retu ASIC + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Juha Yrjölä , + * David Weinehall , and + * Mikko Ylinen + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "cbus.h" +#include "retu.h" + +#define RETU_ID 0x01 +#define PFX "retu: " + +static int retu_initialized; +static int retu_irq_pin; +static int retu_is_vilma; + +static struct tasklet_struct retu_tasklet; +spinlock_t retu_lock = SPIN_LOCK_UNLOCKED; + +static struct completion device_release; + +struct retu_irq_handler_desc { + int (*func)(unsigned long); + unsigned long arg; + char name[8]; +}; + +static struct retu_irq_handler_desc retu_irq_handlers[MAX_RETU_IRQ_HANDLERS]; + +/** + * retu_read_reg - Read a value from a register in Retu + * @reg: the register to read from + * + * This function returns the contents of the specified register + */ +int retu_read_reg(int reg) +{ + BUG_ON(!retu_initialized); + return cbus_read_reg(cbus_host, RETU_ID, reg); +} + +/** + * retu_write_reg - Write a value to a register in Retu + * @reg: the register to write to + * @reg: the value to write to the register + * + * This function writes a value to the specified register + */ +void retu_write_reg(int reg, u16 val) +{ + BUG_ON(!retu_initialized); + cbus_write_reg(cbus_host, RETU_ID, reg, val); +} + +void retu_set_clear_reg_bits(int reg, u16 set, u16 clear) +{ + unsigned long flags; + u16 w; + + spin_lock_irqsave(&retu_lock, flags); + w = retu_read_reg(reg); + w &= ~clear; + w |= set; + retu_write_reg(reg, w); + spin_unlock_irqrestore(&retu_lock, flags); +} + +#define ADC_MAX_CHAN_NUMBER 13 + +int retu_read_adc(int channel) +{ + unsigned long flags; + int res; + + if (channel < 0 || channel > ADC_MAX_CHAN_NUMBER) + return -EINVAL; + + spin_lock_irqsave(&retu_lock, flags); + /* Select the channel and read result */ + retu_write_reg(RETU_REG_ADCR, channel << 10); + res = retu_read_reg(RETU_REG_ADCR) & 0x3ff; + /* Unlock retu */ + spin_unlock_irqrestore(&retu_lock, flags); + + return res; +} + + +static u16 retu_disable_bogus_irqs(u16 mask) +{ + int i; + + for (i = 0; i < MAX_RETU_IRQ_HANDLERS; i++) { + if (mask & (1 << i)) + continue; + if (retu_irq_handlers[i].func != NULL) + continue; + /* an IRQ was enabled but we don't have a handler for it */ + printk(KERN_INFO PFX "disabling bogus IRQ %d\n", i); + mask |= (1 << i); + } + return mask; +} + +/* + * Disable given RETU interrupt + */ +void retu_disable_irq(int id) +{ + unsigned long flags; + u16 mask; + + spin_lock_irqsave(&retu_lock, flags); + mask = retu_read_reg(RETU_REG_IMR); + mask |= 1 << id; + mask = retu_disable_bogus_irqs(mask); + retu_write_reg(RETU_REG_IMR, mask); + spin_unlock_irqrestore(&retu_lock, flags); +} + +/* + * Enable given RETU interrupt + */ +void retu_enable_irq(int id) +{ + unsigned long flags; + u16 mask; + + if (id == 3) { + printk("Enabling Retu IRQ %d\n", id); + dump_stack(); + } + spin_lock_irqsave(&retu_lock, flags); + mask = retu_read_reg(RETU_REG_IMR); + mask &= ~(1 << id); + mask = retu_disable_bogus_irqs(mask); + retu_write_reg(RETU_REG_IMR, mask); + spin_unlock_irqrestore(&retu_lock, flags); +} + +/* + * Acknowledge given RETU interrupt + */ +void retu_ack_irq(int id) +{ + retu_write_reg(RETU_REG_IDR, 1 << id); +} + +/* + * RETU interrupt handler. Only schedules the tasklet. + */ +static irqreturn_t retu_irq_handler(int irq, void *dev_id) +{ + tasklet_schedule(&retu_tasklet); + return IRQ_HANDLED; +} + +/* + * Tasklet handler + */ +static void retu_tasklet_handler(unsigned long data) +{ + struct retu_irq_handler_desc *hnd; + u16 id; + u16 im; + int i; + + for (;;) { + id = retu_read_reg(RETU_REG_IDR); + im = ~retu_read_reg(RETU_REG_IMR); + id &= im; + + if (!id) + break; + + for (i = 0; id != 0; i++, id >>= 1) { + if (!(id & 1)) + continue; + hnd = &retu_irq_handlers[i]; + if (hnd->func == NULL) { + /* Spurious retu interrupt - disable and ack it */ + printk(KERN_INFO "Spurious Retu interrupt " + "(id %d)\n", i); + retu_disable_irq(i); + retu_ack_irq(i); + continue; + } + hnd->func(hnd->arg); + /* + * Don't acknowledge the interrupt here + * It must be done explicitly + */ + } + } +} + +/* + * Register the handler for a given RETU interrupt source. + */ +int retu_request_irq(int id, void *irq_handler, unsigned long arg, char *name) +{ + struct retu_irq_handler_desc *hnd; + + if (irq_handler == NULL || id >= MAX_RETU_IRQ_HANDLERS || + name == NULL) { + printk(KERN_ERR PFX "Invalid arguments to %s\n", + __FUNCTION__); + return -EINVAL; + } + hnd = &retu_irq_handlers[id]; + if (hnd->func != NULL) { + printk(KERN_ERR PFX "IRQ %d already reserved\n", id); + return -EBUSY; + } + printk(KERN_INFO PFX "Registering interrupt %d for device %s\n", + id, name); + hnd->func = irq_handler; + hnd->arg = arg; + strlcpy(hnd->name, name, sizeof(hnd->name)); + + retu_ack_irq(id); + retu_enable_irq(id); + + return 0; +} + +/* + * Unregister the handler for a given RETU interrupt source. + */ +void retu_free_irq(int id) +{ + struct retu_irq_handler_desc *hnd; + + if (id >= MAX_RETU_IRQ_HANDLERS) { + printk(KERN_ERR PFX "Invalid argument to %s\n", + __FUNCTION__); + return; + } + hnd = &retu_irq_handlers[id]; + if (hnd->func == NULL) { + printk(KERN_ERR PFX "IRQ %d already freed\n", id); + return; + } + + retu_disable_irq(id); + hnd->func = NULL; +} + +/** + * retu_power_off - Shut down power to system + * + * This function puts the system in power off state + */ +static void retu_power_off(void) +{ + /* Ignore power button state */ + retu_write_reg(RETU_REG_CC1, retu_read_reg(RETU_REG_CC1) | 2); + /* Expire watchdog immediately */ + retu_write_reg(RETU_REG_WATCHDOG, 0); + /* Wait for poweroff*/ + for (;;); +} + +/** + * retu_probe - Probe for Retu ASIC + * @dev: the Retu device + * + * Probe for the Retu ASIC and allocate memory + * for its device-struct if found + */ +static int __devinit retu_probe(struct device *dev) +{ + const struct omap_em_asic_bb5_config * em_asic_config; + int rev, ret; + + /* Prepare tasklet */ + tasklet_init(&retu_tasklet, retu_tasklet_handler, 0); + + em_asic_config = omap_get_config(OMAP_TAG_EM_ASIC_BB5, + struct omap_em_asic_bb5_config); + if (em_asic_config == NULL) { + printk(KERN_ERR PFX "Unable to retrieve config data\n"); + return -ENODATA; + } + + retu_irq_pin = em_asic_config->retu_irq_gpio; + + if ((ret = omap_request_gpio(retu_irq_pin)) < 0) { + printk(KERN_ERR PFX "Unable to reserve IRQ GPIO\n"); + return ret; + } + + /* Set the pin as input */ + omap_set_gpio_direction(retu_irq_pin, 1); + + /* Rising edge triggers the IRQ */ + set_irq_type(OMAP_GPIO_IRQ(retu_irq_pin), IRQT_RISING); + + retu_initialized = 1; + + rev = retu_read_reg(RETU_REG_ASICR) & 0xff; + if (rev & (1 << 7)) + retu_is_vilma = 1; + + printk(KERN_INFO "%s v%d.%d found\n", retu_is_vilma ? "Vilma" : "Retu", + (rev >> 4) & 0x07, rev & 0x0f); + + /* Mask all RETU interrupts */ + retu_write_reg(RETU_REG_IMR, 0xffff); + + ret = request_irq(OMAP_GPIO_IRQ(retu_irq_pin), retu_irq_handler, 0, + "retu", 0); + if (ret < 0) { + printk(KERN_ERR PFX "Unable to register IRQ handler\n"); + omap_free_gpio(retu_irq_pin); + return ret; + } + set_irq_wake(OMAP_GPIO_IRQ(retu_irq_pin), 1); + + /* Register power off function */ + pm_power_off = retu_power_off; + +#ifdef CONFIG_CBUS_RETU_USER + /* Initialize user-space interface */ + if (retu_user_init() < 0) { + printk(KERN_ERR "Unable to initialize driver\n"); + free_irq(OMAP_GPIO_IRQ(retu_irq_pin), 0); + omap_free_gpio(retu_irq_pin); + return ret; + } +#endif + + return 0; +} + +static int retu_remove(struct device *dev) +{ +#ifdef CONFIG_CBUS_RETU_USER + retu_user_cleanup(); +#endif + /* Mask all RETU interrupts */ + retu_write_reg(RETU_REG_IMR, 0xffff); + free_irq(OMAP_GPIO_IRQ(retu_irq_pin), 0); + omap_free_gpio(retu_irq_pin); + tasklet_kill(&retu_tasklet); + + return 0; +} + +static void retu_device_release(struct device *dev) +{ + complete(&device_release); +} + +static struct device_driver retu_driver = { + .name = "retu", + .bus = &platform_bus_type, + .probe = retu_probe, + .remove = retu_remove, +}; + +static struct platform_device retu_device = { + .name = "retu", + .id = -1, + .dev = { + .release = retu_device_release, + } +}; + +/** + * retu_init - initialise Retu driver + * + * Initialise the Retu driver and return 0 if everything worked ok + */ +static int __init retu_init(void) +{ + int ret = 0; + + printk(KERN_INFO "Retu/Vilma driver initialising\n"); + + init_completion(&device_release); + + if ((ret = driver_register(&retu_driver)) < 0) + return ret; + + if ((ret = platform_device_register(&retu_device)) < 0) { + driver_unregister(&retu_driver); + return ret; + } + return 0; +} + +/* + * Cleanup + */ +static void __exit retu_exit(void) +{ + platform_device_unregister(&retu_device); + driver_unregister(&retu_driver); + wait_for_completion(&device_release); +} + +EXPORT_SYMBOL(retu_request_irq); +EXPORT_SYMBOL(retu_free_irq); +EXPORT_SYMBOL(retu_enable_irq); +EXPORT_SYMBOL(retu_disable_irq); +EXPORT_SYMBOL(retu_ack_irq); +EXPORT_SYMBOL(retu_read_reg); +EXPORT_SYMBOL(retu_write_reg); + +subsys_initcall(retu_init); +module_exit(retu_exit); + +MODULE_DESCRIPTION("Retu ASIC control"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Juha Yrjölä, David Weinehall, and Mikko Ylinen"); diff --git a/drivers/cbus/retu.h b/drivers/cbus/retu.h new file mode 100644 index 00000000000..02be6d4defc --- /dev/null +++ b/drivers/cbus/retu.h @@ -0,0 +1,73 @@ +/** + * drivers/cbus/retu.h + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Juha Yrjölä and + * David Weinehall + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __DRIVERS_CBUS_RETU_H +#define __DRIVERS_CBUS_RETU_H + +#include + +/* Registers */ +#define RETU_REG_ASICR 0x00 /* ASIC ID & revision */ +#define RETU_REG_IDR 0x01 /* Interrupt ID */ +#define RETU_REG_IMR 0x02 /* Interrupt mask */ +#define RETU_REG_RTCDSR 0x03 /* RTC seconds register */ +#define RETU_REG_RTCHMR 0x04 /* RTC hours and minutes register */ +#define RETU_REG_RTCHMAR 0x05 /* RTC hours and minutes alarm and time set register */ +#define RETU_REG_RTCCALR 0x06 /* RTC calibration register */ +#define RETU_REG_ADCR 0x08 /* ADC result */ +#define RETU_REG_CC1 0x0d /* Common control register 1 */ +#define RETU_REG_CC2 0x0e /* Common control register 2 */ +#define RETU_REG_CTRL_CLR 0x0f /* Regulator clear register */ +#define RETU_REG_CTRL_SET 0x10 /* Regulator set register */ +#define RETU_REG_STATUS 0x16 /* Status register */ +#define RETU_REG_WATCHDOG 0x17 /* Watchdog register */ +#define RETU_REG_MAX 0x1f + +/* Interrupt sources */ +#define RETU_INT_PWR 0 +#define RETU_INT_CHAR 1 +#define RETU_INT_RTCS 2 +#define RETU_INT_RTCM 3 +#define RETU_INT_RTCD 4 +#define RETU_INT_RTCA 5 +#define RETU_INT_ADCS 8 + +#define MAX_RETU_IRQ_HANDLERS 16 + +int retu_read_reg(int reg); +void retu_write_reg(int reg, u16 val); +void retu_set_clear_reg_bits(int reg, u16 set, u16 clear); +int retu_read_adc(int channel); +int retu_request_irq(int id, void *irq_handler, unsigned long arg, char *name); +void retu_free_irq(int id); +void retu_enable_irq(int id); +void retu_disable_irq(int id); +void retu_ack_irq(int id); + +#ifdef CONFIG_CBUS_RETU_USER +int retu_user_init(void); +void retu_user_cleanup(void); +#endif + +extern spinlock_t retu_lock; + +#endif /* __DRIVERS_CBUS_RETU_H */ diff --git a/drivers/cbus/tahvo-usb.c b/drivers/cbus/tahvo-usb.c new file mode 100644 index 00000000000..1c5a2a53d6e --- /dev/null +++ b/drivers/cbus/tahvo-usb.c @@ -0,0 +1,745 @@ +/** + * drivers/cbus/tahvo-usb.c + * + * Tahvo USB transeiver + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Parts copied from drivers/i2c/chips/isp1301_omap.c + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2004 David Brownell + * + * Written by Juha Yrjölä , + * Tony Lindgren , and + * Timo Teräs + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cbus.h" +#include "tahvo.h" + +#define DRIVER_NAME "tahvo-usb" + +#define USBR_SLAVE_CONTROL (1 << 8) +#define USBR_VPPVIO_SW (1 << 7) +#define USBR_SPEED (1 << 6) +#define USBR_REGOUT (1 << 5) +#define USBR_MASTER_SW2 (1 << 4) +#define USBR_MASTER_SW1 (1 << 3) +#define USBR_SLAVE_SW (1 << 2) +#define USBR_NSUSPEND (1 << 1) +#define USBR_SEMODE (1 << 0) + +/* bits in OTG_CTRL_REG */ + +/* Bits that are controlled by OMAP OTG and are read-only */ +#define OTG_CTRL_OMAP_MASK (OTG_PULLDOWN|OTG_PULLUP|OTG_DRV_VBUS|\ + OTG_PD_VBUS|OTG_PU_VBUS|OTG_PU_ID) +/* Bits that are controlled by transceiver */ +#define OTG_CTRL_XCVR_MASK (OTG_ASESSVLD|OTG_BSESSEND|\ + OTG_BSESSVLD|OTG_VBUSVLD|OTG_ID) +/* Bits that are controlled by system */ +#define OTG_CTRL_SYS_MASK (OTG_A_BUSREQ|OTG_A_SETB_HNPEN|OTG_B_BUSREQ|\ + OTG_B_HNPEN|OTG_BUSDROP) + +#if defined(CONFIG_USB_OHCI_HCD) && !defined(CONFIG_USB_OTG) +#error tahvo-otg.c does not work with OCHI yet! +#endif + +#define TAHVO_MODE_HOST 0 +#define TAHVO_MODE_PERIPHERAL 1 + +#ifdef CONFIG_USB_OTG +#define TAHVO_MODE(tu) (tu)->tahvo_mode +#elif defined(CONFIG_USB_GADGET_OMAP) +#define TAHVO_MODE(tu) TAHVO_MODE_PERIPHERAL +#else +#define TAHVO_MODE(tu) TAHVO_MODE_HOST +#endif + +struct tahvo_usb { + struct platform_device *pt_dev; + struct otg_transceiver otg; + int vbus_state; + struct work_struct irq_work; + struct mutex serialize; +#ifdef CONFIG_USB_OTG + int tahvo_mode; +#endif +}; +static struct platform_device tahvo_usb_device; + +/* + * --------------------------------------------------------------------------- + * OTG related functions + * + * These shoud be separated into omap-otg.c driver module, as they are used + * by various transceivers. These functions are needed in the UDC-only case + * as well. These functions are copied from GPL isp1301_omap.c + * --------------------------------------------------------------------------- + */ +static struct platform_device *tahvo_otg_dev; + +static irqreturn_t omap_otg_irq(int irq, void *arg) +{ + struct platform_device *otg_dev = (struct platform_device *) arg; + struct tahvo_usb *tu = (struct tahvo_usb *) otg_dev->dev.driver_data; + u16 otg_irq; + + otg_irq = OTG_IRQ_SRC_REG; + if (otg_irq & OPRT_CHG) { + OTG_IRQ_SRC_REG = OPRT_CHG; + } else if (otg_irq & B_SRP_TMROUT) { + OTG_IRQ_SRC_REG = B_SRP_TMROUT; + } else if (otg_irq & B_HNP_FAIL) { + OTG_IRQ_SRC_REG = B_HNP_FAIL; + } else if (otg_irq & A_SRP_DETECT) { + OTG_IRQ_SRC_REG = A_SRP_DETECT; + } else if (otg_irq & A_REQ_TMROUT) { + OTG_IRQ_SRC_REG = A_REQ_TMROUT; + } else if (otg_irq & A_VBUS_ERR) { + OTG_IRQ_SRC_REG = A_VBUS_ERR; + } else if (otg_irq & DRIVER_SWITCH) { + if ((!(OTG_CTRL_REG & OTG_DRIVER_SEL)) && + tu->otg.host && tu->otg.state == OTG_STATE_A_HOST) { + /* role is host */ + usb_bus_start_enum(tu->otg.host, + tu->otg.host->otg_port); + } + OTG_IRQ_SRC_REG = DRIVER_SWITCH; + } else + return IRQ_NONE; + + return IRQ_HANDLED; + +} + +static int omap_otg_init(void) +{ + +#ifdef CONFIG_USB_OTG + if (!tahvo_otg_dev) { + printk("tahvo-usb: no tahvo_otg_dev\n"); + return -ENODEV; + } +#endif + OTG_SYSCON_1_REG &= ~OTG_IDLE_EN; + udelay(100); + + /* some of these values are board-specific... */ + OTG_SYSCON_2_REG |= OTG_EN + /* for B-device: */ + | SRP_GPDATA /* 9msec Bdev D+ pulse */ + | SRP_GPDVBUS /* discharge after VBUS pulse */ + // | (3 << 24) /* 2msec VBUS pulse */ + /* for A-device: */ + | (0 << 20) /* 200ms nominal A_WAIT_VRISE timer */ + | SRP_DPW /* detect 167+ns SRP pulses */ + | SRP_DATA | SRP_VBUS; /* accept both kinds of SRP pulse */ + + OTG_IRQ_EN_REG = DRIVER_SWITCH | OPRT_CHG + | B_SRP_TMROUT | B_HNP_FAIL + | A_VBUS_ERR | A_SRP_DETECT | A_REQ_TMROUT; + OTG_SYSCON_2_REG |= OTG_EN; + + return 0; +} + +static int omap_otg_probe(struct device *dev) +{ + int ret; + + tahvo_otg_dev = to_platform_device(dev); + ret = omap_otg_init(); + if (ret != 0) { + printk(KERN_ERR "tahvo-usb: omap_otg_init failed\n"); + return ret; + } + + return request_irq(tahvo_otg_dev->resource[1].start, + omap_otg_irq, IRQF_DISABLED, DRIVER_NAME, + &tahvo_usb_device); +} + +static int omap_otg_remove(struct device *dev) +{ + free_irq(tahvo_otg_dev->resource[1].start, &tahvo_usb_device); + tahvo_otg_dev = NULL; + + return 0; +} + +struct device_driver omap_otg_driver = { + .name = "omap_otg", + .bus = &platform_bus_type, + .probe = omap_otg_probe, + .remove = omap_otg_remove, +}; + +/* + * --------------------------------------------------------------------------- + * Tahvo related functions + * These are Nokia proprietary code, except for the OTG register settings, + * which are copied from isp1301.c + * --------------------------------------------------------------------------- + */ +static ssize_t vbus_state_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct tahvo_usb *tu = (struct tahvo_usb*) device->driver_data; + return sprintf(buf, "%d\n", tu->vbus_state); +} +static DEVICE_ATTR(vbus_state, 0444, vbus_state_show, NULL); + +int vbus_active = 0; + +#if 0 + +static int host_suspend(struct tahvo_usb *tu) +{ + struct device *dev; + + if (!tu->otg.host) + return -ENODEV; + + /* Currently ASSUMES only the OTG port matters; + * other ports could be active... + */ + dev = tu->otg.host->controller; + return dev->driver->suspend(dev, PMSG_SUSPEND); +} + +static int host_resume(struct tahvo_usb *tu) +{ + struct device *dev; + + if (!tu->otg.host) + return -ENODEV; + + dev = tu->otg.host->controller; + return dev->driver->resume(dev); +} + +#else + +static int host_suspend(struct tahvo_usb *tu) +{ + return 0; +} + +static int host_resume(struct tahvo_usb *tu) +{ + return 0; +} + +#endif + +static void check_vbus_state(struct tahvo_usb *tu) +{ + int reg, prev_state; + + reg = tahvo_read_reg(TAHVO_REG_IDSR); + if (reg & 0x01) { + vbus_active = 1; + switch (tu->otg.state) { + case OTG_STATE_B_IDLE: + /* Enable the gadget driver */ + if (tu->otg.gadget) + usb_gadget_vbus_connect(tu->otg.gadget); + /* Set B-session valid and not B-sessio ended to indicate + * Vbus to be ok. */ + OTG_CTRL_REG = (OTG_CTRL_REG & ~OTG_BSESSEND) | OTG_BSESSVLD; + + tu->otg.state = OTG_STATE_B_PERIPHERAL; + break; + case OTG_STATE_A_IDLE: + /* Session is now valid assuming the USB hub is driving Vbus */ + tu->otg.state = OTG_STATE_A_HOST; + host_resume(tu); + break; + default: + break; + } + printk("USB cable connected\n"); + } else { + switch (tu->otg.state) { + case OTG_STATE_B_PERIPHERAL: + if (tu->otg.gadget) + usb_gadget_vbus_disconnect(tu->otg.gadget); + tu->otg.state = OTG_STATE_B_IDLE; + break; + case OTG_STATE_A_HOST: + tu->otg.state = OTG_STATE_A_IDLE; + break; + default: + break; + } + printk("USB cable disconnected\n"); + vbus_active = 0; + } + + prev_state = tu->vbus_state; + tu->vbus_state = reg & 0x01; + if (prev_state != tu->vbus_state) + sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state"); +} + +static void tahvo_usb_become_host(struct tahvo_usb *tu) +{ + u32 l; + + /* Clear system and transceiver controlled bits + * also mark the A-session is always valid */ + omap_otg_init(); + + l = OTG_CTRL_REG; + l &= ~(OTG_CTRL_XCVR_MASK|OTG_CTRL_SYS_MASK); + l |= OTG_ASESSVLD; + OTG_CTRL_REG = l; + + /* Power up the transceiver in USB host mode */ + tahvo_write_reg(TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND | + USBR_MASTER_SW2 | USBR_MASTER_SW1); + tu->otg.state = OTG_STATE_A_IDLE; + + check_vbus_state(tu); +} + +static void tahvo_usb_stop_host(struct tahvo_usb *tu) +{ + host_suspend(tu); + tu->otg.state = OTG_STATE_A_IDLE; +} + +static void tahvo_usb_become_peripheral(struct tahvo_usb *tu) +{ + /* Clear system and transceiver controlled bits + * and enable ID to mark peripheral mode and + * BSESSEND to mark no Vbus */ + omap_otg_init(); + OTG_CTRL_REG = (OTG_CTRL_REG & ~(OTG_CTRL_XCVR_MASK|OTG_CTRL_SYS_MASK|OTG_BSESSVLD)) + | OTG_ID | OTG_BSESSEND; + + /* Power up transceiver and set it in USB perhiperal mode */ + tahvo_write_reg(TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT | USBR_NSUSPEND | USBR_SLAVE_SW); + tu->otg.state = OTG_STATE_B_IDLE; + + check_vbus_state(tu); +} + +static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu) +{ + OTG_CTRL_REG = (OTG_CTRL_REG & ~OTG_BSESSVLD) | OTG_BSESSEND; + if (tu->otg.gadget) + usb_gadget_vbus_disconnect(tu->otg.gadget); + tu->otg.state = OTG_STATE_B_IDLE; + +} + +static void tahvo_usb_power_off(struct tahvo_usb *tu) +{ + u32 l; + int id; + + /* Disable gadget controller if any */ + if (tu->otg.gadget) + usb_gadget_vbus_disconnect(tu->otg.gadget); + + host_suspend(tu); + + /* Disable OTG and interrupts */ + if (TAHVO_MODE(tu) == TAHVO_MODE_PERIPHERAL) + id = OTG_ID; + else + id = 0; + l = OTG_CTRL_REG; + l &= ~(OTG_CTRL_XCVR_MASK | OTG_CTRL_SYS_MASK | OTG_BSESSVLD); + l |= id | OTG_BSESSEND; + OTG_CTRL_REG = l; + OTG_IRQ_EN_REG = 0; + + OTG_SYSCON_2_REG &= ~OTG_EN; + + OTG_SYSCON_1_REG |= OTG_IDLE_EN; + + /* Power off transceiver */ + tahvo_write_reg(TAHVO_REG_USBR, 0); + tu->otg.state = OTG_STATE_UNDEFINED; +} + + +static int tahvo_usb_set_power(struct otg_transceiver *dev, unsigned mA) +{ + struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, otg); + + dev_dbg(&tu->pt_dev->dev, "set_power %d mA\n", mA); + + if (dev->state == OTG_STATE_B_PERIPHERAL) { + /* REVISIT: Can Tahvo charge battery from VBUS? */ + } + return 0; +} + +static int tahvo_usb_set_suspend(struct otg_transceiver *dev, int suspend) +{ + struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, otg); + u16 w; + + dev_dbg(&tu->pt_dev->dev, "set_suspend\n"); + + w = tahvo_read_reg(TAHVO_REG_USBR); + if (suspend) + w &= ~USBR_NSUSPEND; + else + w |= USBR_NSUSPEND; + tahvo_write_reg(TAHVO_REG_USBR, w); + + return 0; +} + +static int tahvo_usb_start_srp(struct otg_transceiver *dev) +{ + struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, otg); + u32 otg_ctrl; + + dev_dbg(&tu->pt_dev->dev, "start_srp\n"); + + if (!dev || tu->otg.state != OTG_STATE_B_IDLE) + return -ENODEV; + + otg_ctrl = OTG_CTRL_REG; + if (!(otg_ctrl & OTG_BSESSEND)) + return -EINVAL; + + otg_ctrl |= OTG_B_BUSREQ; + otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_SYS_MASK; + OTG_CTRL_REG = otg_ctrl; + tu->otg.state = OTG_STATE_B_SRP_INIT; + + return 0; +} + +static int tahvo_usb_start_hnp(struct otg_transceiver *otg) +{ + struct tahvo_usb *tu = container_of(otg, struct tahvo_usb, otg); + + dev_dbg(&tu->pt_dev->dev, "start_hnp\n"); +#ifdef CONFIG_USB_OTG + /* REVISIT: Add this for OTG */ +#endif + return -EINVAL; +} + +static int tahvo_usb_set_host(struct otg_transceiver *otg, struct usb_bus *host) +{ + struct tahvo_usb *tu = container_of(otg, struct tahvo_usb, otg); + + dev_dbg(&tu->pt_dev->dev, "set_host %p\n", host); + + if (otg == NULL) + return -ENODEV; + +#if defined(CONFIG_USB_OTG) || !defined(CONFIG_USB_GADGET_OMAP) + + mutex_lock(&tu->serialize); + + if (host == NULL) { + if (TAHVO_MODE(tu) == TAHVO_MODE_HOST) + tahvo_usb_power_off(tu); + tu->otg.host = NULL; + mutex_unlock(&tu->serialize); + return 0; + } + + OTG_SYSCON_1_REG &= ~(OTG_IDLE_EN | HST_IDLE_EN | DEV_IDLE_EN); + + if (TAHVO_MODE(tu) == TAHVO_MODE_HOST) { + tu->otg.host = NULL; + tahvo_usb_become_host(tu); + } else + host_suspend(tu); + + tu->otg.host = host; + + mutex_unlock(&tu->serialize); +#else + /* No host mode configured, so do not allow host controlled to be set */ + return -EINVAL; +#endif + + return 0; +} + +static int tahvo_usb_set_peripheral(struct otg_transceiver *otg, struct usb_gadget *gadget) +{ + struct tahvo_usb *tu = container_of(otg, struct tahvo_usb, otg); + + dev_dbg(&tu->pt_dev->dev, "set_peripheral %p\n", gadget); + + if (!otg) + return -ENODEV; + +#if defined(CONFIG_USB_OTG) || defined(CONFIG_USB_GADGET_OMAP) + + mutex_lock(&tu->serialize); + + if (!gadget) { + if (TAHVO_MODE(tu) == TAHVO_MODE_PERIPHERAL) + tahvo_usb_power_off(tu); + tu->otg.gadget = NULL; + mutex_unlock(&tu->serialize); + return 0; + } + + tu->otg.gadget = gadget; + if (TAHVO_MODE(tu) == TAHVO_MODE_PERIPHERAL) + tahvo_usb_become_peripheral(tu); + + mutex_unlock(&tu->serialize); +#else + /* No gadget mode configured, so do not allow host controlled to be set */ + return -EINVAL; +#endif + + return 0; +} + +static void tahvo_usb_irq_work(struct work_struct *work) +{ + struct tahvo_usb *tu = container_of(work, struct tahvo_usb, irq_work); + + mutex_lock(&tu->serialize); + check_vbus_state(tu); + mutex_unlock(&tu->serialize); +} + +static void tahvo_usb_vbus_interrupt(unsigned long arg) +{ + struct tahvo_usb *tu = (struct tahvo_usb *) arg; + + tahvo_ack_irq(TAHVO_INT_VBUSON); + /* Seems we need this to acknowledge the interrupt */ + tahvo_read_reg(TAHVO_REG_IDSR); + schedule_work(&tu->irq_work); +} + +#ifdef CONFIG_USB_OTG +static ssize_t otg_mode_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct tahvo_usb *tu = (struct tahvo_usb*) device->driver_data; + switch (tu->tahvo_mode) { + case TAHVO_MODE_HOST: + return sprintf(buf, "host\n"); + case TAHVO_MODE_PERIPHERAL: + return sprintf(buf, "peripheral\n"); + } + return sprintf(buf, "unknown\n"); +} + +static ssize_t otg_mode_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tahvo_usb *tu = (struct tahvo_usb*) device->driver_data; + int r; + + r = strlen(buf); + mutex_lock(&tu->serialize); + if (strncmp(buf, "host", 4) == 0) { + if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL) + tahvo_usb_stop_peripheral(tu); + tu->tahvo_mode = TAHVO_MODE_HOST; + if (tu->otg.host) { + printk(KERN_INFO "Selected HOST mode: host controller present.\n"); + tahvo_usb_become_host(tu); + } else { + printk(KERN_INFO "Selected HOST mode: no host controller, powering off.\n"); + tahvo_usb_power_off(tu); + } + } else if (strncmp(buf, "peripheral", 10) == 0) { + if (tu->tahvo_mode == TAHVO_MODE_HOST) + tahvo_usb_stop_host(tu); + tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; + if (tu->otg.gadget) { + printk(KERN_INFO "Selected PERIPHERAL mode: gadget driver present.\n"); + tahvo_usb_become_peripheral(tu); + } else { + printk(KERN_INFO "Selected PERIPHERAL mode: no gadget driver, powering off.\n"); + tahvo_usb_power_off(tu); + } + } else + r = -EINVAL; + + mutex_unlock(&tu->serialize); + return r; +} + +static DEVICE_ATTR(otg_mode, 0644, otg_mode_show, otg_mode_store); +#endif + +static int tahvo_usb_probe(struct device *dev) +{ + struct tahvo_usb *tu; + int ret; + + dev_dbg(dev, "probe\n"); + + /* Create driver data */ + tu = kmalloc(sizeof(*tu), GFP_KERNEL); + if (!tu) + return -ENOMEM; + memset(tu, 0, sizeof(*tu)); + tu->pt_dev = container_of(dev, struct platform_device, dev); +#ifdef CONFIG_USB_OTG + /* Default mode */ +#ifdef CONFIG_CBUS_TAHVO_USB_HOST_BY_DEFAULT + tu->tahvo_mode = TAHVO_MODE_HOST; +#else + tu->tahvo_mode = TAHVO_MODE_PERIPHERAL; +#endif +#endif + + INIT_WORK(&tu->irq_work, tahvo_usb_irq_work); + mutex_init(&tu->serialize); + + /* Set initial state, so that we generate kevents only on + * state changes */ + tu->vbus_state = tahvo_read_reg(TAHVO_REG_IDSR) & 0x01; + + /* We cannot enable interrupt until omap_udc is initialized */ + ret = tahvo_request_irq(TAHVO_INT_VBUSON, tahvo_usb_vbus_interrupt, + (unsigned long) tu, "vbus_interrupt"); + if (ret != 0) { + kfree(tu); + printk(KERN_ERR "Could not register Tahvo interrupt for VBUS\n"); + return ret; + } + + /* Attributes */ + ret = device_create_file(dev, &dev_attr_vbus_state); +#ifdef CONFIG_USB_OTG + ret |= device_create_file(dev, &dev_attr_otg_mode); +#endif + if (ret) + printk(KERN_ERR "attribute creation failed: %d\n", ret); + + /* Create OTG interface */ + tahvo_usb_power_off(tu); + tu->otg.state = OTG_STATE_UNDEFINED; + tu->otg.label = DRIVER_NAME; + tu->otg.set_host = tahvo_usb_set_host; + tu->otg.set_peripheral = tahvo_usb_set_peripheral; + tu->otg.set_power = tahvo_usb_set_power; + tu->otg.set_suspend = tahvo_usb_set_suspend; + tu->otg.start_srp = tahvo_usb_start_srp; + tu->otg.start_hnp = tahvo_usb_start_hnp; + + ret = otg_set_transceiver(&tu->otg); + if (ret < 0) { + printk(KERN_ERR "Cannot register USB transceiver\n"); + kfree(tu); + tahvo_free_irq(TAHVO_INT_VBUSON); + return ret; + } + + dev->driver_data = tu; + + /* Act upon current vbus state once at startup. A vbus state irq may or + * may not be generated in addition to this. */ + schedule_work(&tu->irq_work); + return 0; +} + +static int tahvo_usb_remove(struct device *dev) +{ + dev_dbg(dev, "remove\n"); + + tahvo_free_irq(TAHVO_INT_VBUSON); + flush_scheduled_work(); + otg_set_transceiver(0); + device_remove_file(dev, &dev_attr_vbus_state); +#ifdef CONFIG_USB_OTG + device_remove_file(dev, &dev_attr_otg_mode); +#endif + return 0; +} + +static struct device_driver tahvo_usb_driver = { + .name = "tahvo-usb", + .bus = &platform_bus_type, + .probe = tahvo_usb_probe, + .remove = tahvo_usb_remove, +}; + +static struct platform_device tahvo_usb_device = { + .name = "tahvo-usb", + .id = -1, +}; + +static int __init tahvo_usb_init(void) +{ + int ret = 0; + + printk(KERN_INFO "Tahvo USB transceiver driver initializing\n"); + ret = driver_register(&tahvo_usb_driver); + if (ret) + return ret; + ret = platform_device_register(&tahvo_usb_device); + if (ret < 0) { + driver_unregister(&tahvo_usb_driver); + return ret; + } + ret = driver_register(&omap_otg_driver); + if (ret) { + platform_device_unregister(&tahvo_usb_device); + driver_unregister(&tahvo_usb_driver); + return ret; + } + return 0; +} + +subsys_initcall(tahvo_usb_init); + +static void __exit tahvo_usb_exit(void) +{ + driver_unregister(&omap_otg_driver); + platform_device_unregister(&tahvo_usb_device); + driver_unregister(&tahvo_usb_driver); +} +module_exit(tahvo_usb_exit); + +MODULE_DESCRIPTION("Tahvo USB OTG Transceiver Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs"); diff --git a/drivers/cbus/tahvo-user.c b/drivers/cbus/tahvo-user.c new file mode 100644 index 00000000000..873d6481573 --- /dev/null +++ b/drivers/cbus/tahvo-user.c @@ -0,0 +1,405 @@ +/** + * drivers/cbus/tahvo-user.c + * + * Tahvo user space interface functions + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Mikko Ylinen + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "tahvo.h" + +#include "user_retu_tahvo.h" + +/* Maximum size of IRQ node buffer/pool */ +#define TAHVO_MAX_IRQ_BUF_LEN 16 + +#define PFX "tahvo-user: " + +/* Bitmap for marking the interrupt sources as having the handlers */ +static u32 tahvo_irq_bits; + +/* For allowing only one user process to subscribe to the tahvo interrupts */ +static struct file *tahvo_irq_subscr = NULL; + +/* For poll and IRQ passing */ +struct tahvo_irq { + u32 id; + struct list_head node; +}; + +static spinlock_t tahvo_irqs_lock; +static struct tahvo_irq *tahvo_irq_block; +static LIST_HEAD(tahvo_irqs); +static LIST_HEAD(tahvo_irqs_reserve); + +/* Wait queue - used when user wants to read the device */ +DECLARE_WAIT_QUEUE_HEAD(tahvo_user_waitqueue); + +/* Semaphore to protect irq subscription sequence */ +static struct mutex tahvo_mutex; + +/* This array specifies TAHVO register types (read/write/toggle) */ +static const u8 tahvo_access_bits[] = { + 1, + 4, + 1, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 1 +}; + +/* + * The handler for all TAHVO interrupts. + * + * arg is the interrupt source in TAHVO. + */ +static void tahvo_user_irq_handler(unsigned long arg) +{ + struct tahvo_irq *irq; + + /* user has to re-enable the interrupt once ready + * for receiving them again */ + tahvo_disable_irq(arg); + tahvo_ack_irq(arg); + + spin_lock(&tahvo_irqs_lock); + if (list_empty(&tahvo_irqs_reserve)) { + spin_unlock(&tahvo_irqs_lock); + return; + } + irq = list_entry((&tahvo_irqs_reserve)->next, struct tahvo_irq, node); + irq->id = arg; + list_move_tail(&irq->node, &tahvo_irqs); + spin_unlock(&tahvo_irqs_lock); + + /* wake up waiting thread */ + wake_up(&tahvo_user_waitqueue); +} + +/* + * This routine sets up the interrupt handler and marks an interrupt source + * in TAHVO as a candidate for signal delivery to the user process. + */ +static int tahvo_user_subscribe_to_irq(int id, struct file *filp) +{ + int ret; + + mutex_lock(&tahvo_mutex); + if ((tahvo_irq_subscr != NULL) && (tahvo_irq_subscr != filp)) { + mutex_unlock(&tahvo_mutex); + return -EBUSY; + } + /* Store the file pointer of the first user process registering IRQs */ + tahvo_irq_subscr = filp; + mutex_unlock(&tahvo_mutex); + + if (tahvo_irq_bits & (1 << id)) + return 0; + + ret = tahvo_request_irq(id, tahvo_user_irq_handler, id, ""); + if (ret < 0) + return ret; + + /* Mark that this interrupt has a handler */ + tahvo_irq_bits |= 1 << id; + + return 0; +} + +/* + * Unregister all TAHVO interrupt handlers + */ +static void tahvo_unreg_irq_handlers(void) +{ + int id; + + if (!tahvo_irq_bits) + return; + + for (id = 0; id < MAX_TAHVO_IRQ_HANDLERS; id++) + if (tahvo_irq_bits & (1 << id)) + tahvo_free_irq(id); + + tahvo_irq_bits = 0; +} + +/* + * Write to TAHVO register. + * Returns 0 upon success, a negative error value otherwise. + */ +static int tahvo_user_write_with_mask(u32 field, u16 value) +{ + u32 mask; + u32 reg; + u_short tmp; + unsigned long flags; + + mask = MASK(field); + reg = REG(field); + + /* Detect bad mask and reg */ + if (mask == 0 || reg > TAHVO_REG_MAX || + tahvo_access_bits[reg] == READ_ONLY) { + printk(KERN_ERR PFX "invalid arguments (reg=%#x, mask=%#x)\n", + reg, mask); + return -EINVAL; + } + + /* Justify value according to mask */ + while (!(mask & 1)) { + value = value << 1; + mask = mask >> 1; + } + + spin_lock_irqsave(&tahvo_lock, flags); + if (tahvo_access_bits[reg] == TOGGLE) { + /* No need to detect previous content of register */ + tmp = 0; + } else { + /* Read current value of register */ + tmp = tahvo_read_reg(reg); + } + /* Generate a new value */ + tmp = (tmp & ~MASK(field)) | (value & MASK(field)); + /* Write data to TAHVO */ + tahvo_write_reg(reg, tmp); + spin_unlock_irqrestore(&tahvo_lock, flags); + + return 0; +} + +/* + * Read TAHVO register. + */ +static u32 tahvo_user_read_with_mask(u32 field) +{ + u_short value; + u32 mask, reg; + + mask = MASK(field); + reg = REG(field); + + /* Detect bad mask and reg */ + if (mask == 0 || reg > TAHVO_REG_MAX) { + printk(KERN_ERR PFX "invalid arguments (reg=%#x, mask=%#x)\n", + reg, mask); + return -EINVAL; + } + + /* Read the register */ + value = tahvo_read_reg(reg) & mask; + + /* Right justify value */ + while (!(mask & 1)) { + value = value >> 1; + mask = mask >> 1; + } + + return value; +} + +/* + * Close device + */ +static int tahvo_close(struct inode *inode, struct file *filp) +{ + /* Unregister all interrupts that have been registered */ + if (tahvo_irq_subscr == filp) { + tahvo_unreg_irq_handlers(); + tahvo_irq_subscr = NULL; + } + + return 0; +} + +/* + * Device control (ioctl) + */ +static int tahvo_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct retu_tahvo_write_parms par; + int ret; + + switch (cmd) { + case URT_IOCT_IRQ_SUBSCR: + return tahvo_user_subscribe_to_irq(arg, filp); + case TAHVO_IOCH_READ: + return tahvo_user_read_with_mask(arg); + case TAHVO_IOCX_WRITE: + ret = copy_from_user(&par, (void __user *) arg, sizeof(par)); + if (ret) + printk(KERN_ERR "copy_from_user failed: %d\n", ret); + par.result = tahvo_user_write_with_mask(par.field, par.value); + ret = copy_to_user((void __user *) arg, &par, sizeof(par)); + if (ret) + printk(KERN_ERR "copy_to_user failed: %d\n", ret); + break; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +/* + * Read from device + */ +static ssize_t tahvo_read(struct file *filp, char *buf, size_t count, + loff_t * offp) +{ + struct tahvo_irq *irq; + + u32 nr, i; + + /* read not permitted if neither filp nor anyone has registered IRQs */ + if (tahvo_irq_subscr != filp) + return -EPERM; + + if ((count < sizeof(u32)) || ((count % sizeof(u32)) != 0)) + return -EINVAL; + + nr = count / sizeof(u32); + + for (i = 0; i < nr; i++) { + unsigned long flags; + u32 irq_id; + int ret; + + ret = wait_event_interruptible(tahvo_user_waitqueue, + !list_empty(&tahvo_irqs)); + if (ret < 0) + return ret; + + spin_lock_irqsave(&tahvo_irqs_lock, flags); + irq = list_entry((&tahvo_irqs)->next, struct tahvo_irq, node); + irq_id = irq->id; + list_move(&irq->node, &tahvo_irqs_reserve); + spin_unlock_irqrestore(&tahvo_irqs_lock, flags); + + ret = copy_to_user(buf + i * sizeof(irq_id), &irq_id, + sizeof(irq_id)); + if (ret) + printk(KERN_ERR "copy_to_user failed: %d\n", ret); + } + + return count; +} + +/* + * Poll method + */ +static unsigned tahvo_poll(struct file *filp, struct poll_table_struct *pt) +{ + if (!list_empty(&tahvo_irqs)) + return POLLIN; + + poll_wait(filp, &tahvo_user_waitqueue, pt); + + if (!list_empty(&tahvo_irqs)) + return POLLIN; + else + return 0; +} + +static struct file_operations tahvo_user_fileops = { + .owner = THIS_MODULE, + .ioctl = tahvo_ioctl, + .read = tahvo_read, + .release = tahvo_close, + .poll = tahvo_poll +}; + +static struct miscdevice tahvo_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "tahvo", + .fops = &tahvo_user_fileops +}; + +/* + * Initialization + * + * @return 0 if successful, error value otherwise. + */ +int tahvo_user_init(void) +{ + struct tahvo_irq *irq; + int res, i; + + irq = kmalloc(sizeof(*irq) * TAHVO_MAX_IRQ_BUF_LEN, GFP_KERNEL); + if (irq == NULL) { + printk(KERN_ERR PFX "kmalloc failed\n"); + return -ENOMEM; + } + memset(irq, 0, sizeof(*irq) * TAHVO_MAX_IRQ_BUF_LEN); + for (i = 0; i < TAHVO_MAX_IRQ_BUF_LEN; i++) + list_add(&irq[i].node, &tahvo_irqs_reserve); + + tahvo_irq_block = irq; + + spin_lock_init(&tahvo_irqs_lock); + mutex_init(&tahvo_mutex); + + /* Request a misc device */ + res = misc_register(&tahvo_device); + if (res < 0) { + printk(KERN_ERR PFX "unable to register misc device for %s\n", + tahvo_device.name); + kfree(irq); + return res; + } + + return 0; +} + +/* + * Cleanup. + */ +void tahvo_user_cleanup(void) +{ + /* Unregister our misc device */ + misc_deregister(&tahvo_device); + /* Unregister and disable all TAHVO interrupts */ + tahvo_unreg_irq_handlers(); + kfree(tahvo_irq_block); +} + +MODULE_DESCRIPTION("Tahvo ASIC user space functions"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mikko Ylinen"); diff --git a/drivers/cbus/tahvo.c b/drivers/cbus/tahvo.c new file mode 100644 index 00000000000..edf7082c60a --- /dev/null +++ b/drivers/cbus/tahvo.c @@ -0,0 +1,441 @@ +/** + * drivers/cbus/tahvo.c + * + * Support functions for Tahvo ASIC + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Juha Yrjölä , + * David Weinehall , and + * Mikko Ylinen + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "cbus.h" +#include "tahvo.h" + +#define TAHVO_ID 0x02 +#define PFX "tahvo: " + +static int tahvo_initialized; +static int tahvo_irq_pin; +static int tahvo_is_betty; + +static struct tasklet_struct tahvo_tasklet; +spinlock_t tahvo_lock = SPIN_LOCK_UNLOCKED; + +static struct completion device_release; + +struct tahvo_irq_handler_desc { + int (*func)(unsigned long); + unsigned long arg; + char name[8]; +}; + +static struct tahvo_irq_handler_desc tahvo_irq_handlers[MAX_TAHVO_IRQ_HANDLERS]; + +/** + * tahvo_read_reg - Read a value from a register in Tahvo + * @reg: the register to read from + * + * This function returns the contents of the specified register + */ +int tahvo_read_reg(int reg) +{ + BUG_ON(!tahvo_initialized); + return cbus_read_reg(cbus_host, TAHVO_ID, reg); +} + +/** + * tahvo_write_reg - Write a value to a register in Tahvo + * @reg: the register to write to + * @reg: the value to write to the register + * + * This function writes a value to the specified register + */ +void tahvo_write_reg(int reg, u16 val) +{ + BUG_ON(!tahvo_initialized); + cbus_write_reg(cbus_host, TAHVO_ID, reg, val); +} + +/** + * tahvo_set_clear_reg_bits - set and clear register bits atomically + * @reg: the register to write to + * @bits: the bits to set + * + * This function sets and clears the specified Tahvo register bits atomically + */ +void tahvo_set_clear_reg_bits(int reg, u16 set, u16 clear) +{ + unsigned long flags; + u16 w; + + spin_lock_irqsave(&tahvo_lock, flags); + w = tahvo_read_reg(reg); + w &= ~clear; + w |= set; + tahvo_write_reg(reg, w); + spin_unlock_irqrestore(&tahvo_lock, flags); +} + +/* + * Disable given TAHVO interrupt + */ +void tahvo_disable_irq(int id) +{ + unsigned long flags; + u16 mask; + + spin_lock_irqsave(&tahvo_lock, flags); + mask = tahvo_read_reg(TAHVO_REG_IMR); + mask |= 1 << id; + tahvo_write_reg(TAHVO_REG_IMR, mask); + spin_unlock_irqrestore(&tahvo_lock, flags); +} + +/* + * Enable given TAHVO interrupt + */ +void tahvo_enable_irq(int id) +{ + unsigned long flags; + u16 mask; + + spin_lock_irqsave(&tahvo_lock, flags); + mask = tahvo_read_reg(TAHVO_REG_IMR); + mask &= ~(1 << id); + tahvo_write_reg(TAHVO_REG_IMR, mask); + spin_unlock_irqrestore(&tahvo_lock, flags); +} + +/* + * Acknowledge given TAHVO interrupt + */ +void tahvo_ack_irq(int id) +{ + tahvo_write_reg(TAHVO_REG_IDR, 1 << id); +} + +static int tahvo_7bit_backlight; + +int tahvo_get_backlight_level(void) +{ + int mask; + + if (tahvo_7bit_backlight) + mask = 0x7f; + else + mask = 0x0f; + return tahvo_read_reg(TAHVO_REG_LEDPWMR) & mask; +} + +int tahvo_get_max_backlight_level(void) +{ + if (tahvo_7bit_backlight) + return 0x7f; + else + return 0x0f; +} + +void tahvo_set_backlight_level(int level) +{ + int max_level; + + max_level = tahvo_get_max_backlight_level(); + if (level > max_level) + level = max_level; + tahvo_write_reg(TAHVO_REG_LEDPWMR, level); +} + +/* + * TAHVO interrupt handler. Only schedules the tasklet. + */ +static irqreturn_t tahvo_irq_handler(int irq, void *dev_id) +{ + tasklet_schedule(&tahvo_tasklet); + return IRQ_HANDLED; +} + +/* + * Tasklet handler + */ +static void tahvo_tasklet_handler(unsigned long data) +{ + struct tahvo_irq_handler_desc *hnd; + u16 id; + u16 im; + int i; + + for (;;) { + id = tahvo_read_reg(TAHVO_REG_IDR); + im = ~tahvo_read_reg(TAHVO_REG_IMR); + id &= im; + + if (!id) + break; + + for (i = 0; id != 0; i++, id >>= 1) { + if (!(id & 1)) + continue; + hnd = &tahvo_irq_handlers[i]; + if (hnd->func == NULL) { + /* Spurious tahvo interrupt - just ack it */ + printk(KERN_INFO "Spurious Tahvo interrupt " + "(id %d)\n", i); + tahvo_disable_irq(i); + tahvo_ack_irq(i); + continue; + } + hnd->func(hnd->arg); + /* + * Don't acknowledge the interrupt here + * It must be done explicitly + */ + } + } +} + +/* + * Register the handler for a given TAHVO interrupt source. + */ +int tahvo_request_irq(int id, void *irq_handler, unsigned long arg, char *name) +{ + struct tahvo_irq_handler_desc *hnd; + + if (irq_handler == NULL || id >= MAX_TAHVO_IRQ_HANDLERS || + name == NULL) { + printk(KERN_ERR PFX "Invalid arguments to %s\n", + __FUNCTION__); + return -EINVAL; + } + hnd = &tahvo_irq_handlers[id]; + if (hnd->func != NULL) { + printk(KERN_ERR PFX "IRQ %d already reserved\n", id); + return -EBUSY; + } + printk(KERN_INFO PFX "Registering interrupt %d for device %s\n", + id, name); + hnd->func = irq_handler; + hnd->arg = arg; + strlcpy(hnd->name, name, sizeof(hnd->name)); + + tahvo_ack_irq(id); + tahvo_enable_irq(id); + + return 0; +} + +/* + * Unregister the handler for a given TAHVO interrupt source. + */ +void tahvo_free_irq(int id) +{ + struct tahvo_irq_handler_desc *hnd; + + if (id >= MAX_TAHVO_IRQ_HANDLERS) { + printk(KERN_ERR PFX "Invalid argument to %s\n", + __FUNCTION__); + return; + } + hnd = &tahvo_irq_handlers[id]; + if (hnd->func == NULL) { + printk(KERN_ERR PFX "IRQ %d already freed\n", id); + return; + } + + tahvo_disable_irq(id); + hnd->func = NULL; +} + +/** + * tahvo_probe - Probe for Tahvo ASIC + * @dev: the Tahvo device + * + * Probe for the Tahvo ASIC and allocate memory + * for its device-struct if found + */ +static int __devinit tahvo_probe(struct device *dev) +{ + const struct omap_em_asic_bb5_config * em_asic_config; + int rev, id, ret; + + /* Prepare tasklet */ + tasklet_init(&tahvo_tasklet, tahvo_tasklet_handler, 0); + + em_asic_config = omap_get_config(OMAP_TAG_EM_ASIC_BB5, + struct omap_em_asic_bb5_config); + if (em_asic_config == NULL) { + printk(KERN_ERR PFX "Unable to retrieve config data\n"); + return -ENODATA; + } + + tahvo_initialized = 1; + + rev = tahvo_read_reg(TAHVO_REG_ASICR); + + id = (rev >> 8) & 0xff; + if (id == 0x03) { + if ((rev & 0xff) >= 0x50) + tahvo_7bit_backlight = 1; + } else if (id == 0x0b) { + tahvo_is_betty = 1; + tahvo_7bit_backlight = 1; + } else { + printk(KERN_ERR "Tahvo/Betty chip not found"); + return -ENODEV; + } + + printk(KERN_INFO "%s v%d.%d found\n", tahvo_is_betty ? "Betty" : "Tahvo", + (rev >> 4) & 0x0f, rev & 0x0f); + + tahvo_irq_pin = em_asic_config->tahvo_irq_gpio; + + if ((ret = omap_request_gpio(tahvo_irq_pin)) < 0) { + printk(KERN_ERR PFX "Unable to reserve IRQ GPIO\n"); + return ret; + } + + /* Set the pin as input */ + omap_set_gpio_direction(tahvo_irq_pin, 1); + + /* Rising edge triggers the IRQ */ + set_irq_type(OMAP_GPIO_IRQ(tahvo_irq_pin), IRQT_RISING); + + /* Mask all TAHVO interrupts */ + tahvo_write_reg(TAHVO_REG_IMR, 0xffff); + + ret = request_irq(OMAP_GPIO_IRQ(tahvo_irq_pin), tahvo_irq_handler, 0, + "tahvo", 0); + if (ret < 0) { + printk(KERN_ERR PFX "Unable to register IRQ handler\n"); + omap_free_gpio(tahvo_irq_pin); + return ret; + } +#ifdef CONFIG_CBUS_TAHVO_USER + /* Initialize user-space interface */ + if (tahvo_user_init() < 0) { + printk(KERN_ERR "Unable to initialize driver\n"); + free_irq(OMAP_GPIO_IRQ(tahvo_irq_pin), 0); + omap_free_gpio(tahvo_irq_pin); + return ret; + } +#endif + return 0; +} + +static int tahvo_remove(struct device *dev) +{ +#ifdef CONFIG_CBUS_TAHVO_USER + tahvo_user_cleanup(); +#endif + /* Mask all TAHVO interrupts */ + tahvo_write_reg(TAHVO_REG_IMR, 0xffff); + free_irq(OMAP_GPIO_IRQ(tahvo_irq_pin), 0); + omap_free_gpio(tahvo_irq_pin); + tasklet_kill(&tahvo_tasklet); + + return 0; +} + +static void tahvo_device_release(struct device *dev) +{ + complete(&device_release); +} + +static struct device_driver tahvo_driver = { + .name = "tahvo", + .bus = &platform_bus_type, + .probe = tahvo_probe, + .remove = tahvo_remove, +}; + +static struct platform_device tahvo_device = { + .name = "tahvo", + .id = -1, + .dev = { + .release = tahvo_device_release, + } +}; + +/** + * tahvo_init - initialise Tahvo driver + * + * Initialise the Tahvo driver and return 0 if everything worked ok + */ +static int __init tahvo_init(void) +{ + int ret = 0; + + printk(KERN_INFO "Tahvo/Betty driver initialising\n"); + + init_completion(&device_release); + + if ((ret = driver_register(&tahvo_driver)) < 0) + return ret; + + if ((ret = platform_device_register(&tahvo_device)) < 0) { + driver_unregister(&tahvo_driver); + return ret; + } + return 0; +} + +/* + * Cleanup + */ +static void __exit tahvo_exit(void) +{ + platform_device_unregister(&tahvo_device); + driver_unregister(&tahvo_driver); + wait_for_completion(&device_release); +} + +EXPORT_SYMBOL(tahvo_request_irq); +EXPORT_SYMBOL(tahvo_free_irq); +EXPORT_SYMBOL(tahvo_enable_irq); +EXPORT_SYMBOL(tahvo_disable_irq); +EXPORT_SYMBOL(tahvo_ack_irq); +EXPORT_SYMBOL(tahvo_read_reg); +EXPORT_SYMBOL(tahvo_write_reg); +EXPORT_SYMBOL(tahvo_get_backlight_level); +EXPORT_SYMBOL(tahvo_get_max_backlight_level); +EXPORT_SYMBOL(tahvo_set_backlight_level); + +subsys_initcall(tahvo_init); +module_exit(tahvo_exit); + +MODULE_DESCRIPTION("Tahvo ASIC control"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Juha Yrjölä, David Weinehall, and Mikko Ylinen"); diff --git a/drivers/cbus/tahvo.h b/drivers/cbus/tahvo.h new file mode 100644 index 00000000000..b7a8ee1758e --- /dev/null +++ b/drivers/cbus/tahvo.h @@ -0,0 +1,61 @@ +/* + * drivers/cbus/tahvo.h + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Juha Yrjölä and + * David Weinehall + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __DRIVERS_CBUS_TAHVO_H +#define __DRIVERS_CBUS_TAHVO_H + +#include + +/* Registers */ +#define TAHVO_REG_ASICR 0x00 /* ASIC ID & revision */ +#define TAHVO_REG_IDR 0x01 /* Interrupt ID */ +#define TAHVO_REG_IDSR 0x02 /* Interrupt status */ +#define TAHVO_REG_IMR 0x03 /* Interrupt mask */ +#define TAHVO_REG_LEDPWMR 0x05 /* LED PWM */ +#define TAHVO_REG_USBR 0x06 /* USB control */ +#define TAHVO_REG_MAX 0x0d + +/* Interrupt sources */ +#define TAHVO_INT_VBUSON 0 + +#define MAX_TAHVO_IRQ_HANDLERS 8 + +int tahvo_read_reg(int reg); +void tahvo_write_reg(int reg, u16 val); +void tahvo_set_clear_reg_bits(int reg, u16 set, u16 clear); +int tahvo_request_irq(int id, void *irq_handler, unsigned long arg, char *name); +void tahvo_free_irq(int id); +void tahvo_enable_irq(int id); +void tahvo_disable_irq(int id); +void tahvo_ack_irq(int id); +int tahvo_get_backlight_level(void); +int tahvo_get_max_backlight_level(void); +void tahvo_set_backlight_level(int level); + +#ifdef CONFIG_CBUS_TAHVO_USER +int tahvo_user_init(void); +void tahvo_user_cleanup(void); +#endif + +extern spinlock_t tahvo_lock; + +#endif /* __DRIVERS_CBUS_TAHVO_H */ diff --git a/drivers/cbus/user_retu_tahvo.h b/drivers/cbus/user_retu_tahvo.h new file mode 100644 index 00000000000..a5c219026c5 --- /dev/null +++ b/drivers/cbus/user_retu_tahvo.h @@ -0,0 +1,75 @@ +/** + * drivers/cbus/user_retu_tahvo.h + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Written by Mikko Ylinen + * + * Definitions and types used by both retu-user and tahvo-user. + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _USER_RETU_TAHVO_H +#define _USER_RETU_TAHVO_H + +/* Chip IDs */ +#define CHIP_RETU 1 +#define CHIP_TAHVO 2 + +/* Register access type bits */ +#define READ_ONLY 1 +#define WRITE_ONLY 2 +#define READ_WRITE 3 +#define TOGGLE 4 + +#define MASK(field) ((u16)(field & 0xFFFF)) +#define REG(field) ((u16)((field >> 16) & 0x3F)) + +/*** IOCTL definitions. These should be kept in sync with user space **********/ + +#define URT_IOC_MAGIC '`' + +/* + * IOCTL function naming conventions: + * ================================== + * 0 -- No argument and return value + * S -- Set through a pointer + * T -- Tell directly with the argument value + * G -- Reply by setting through a pointer + * Q -- response is on the return value + * X -- S and G atomically + * H -- T and Q atomically + */ + +/* General */ +#define URT_IOCT_IRQ_SUBSCR _IO(URT_IOC_MAGIC, 0) + +/* RETU */ +#define RETU_IOCH_READ _IO(URT_IOC_MAGIC, 1) +#define RETU_IOCX_WRITE _IO(URT_IOC_MAGIC, 2) +#define RETU_IOCH_ADC_READ _IO(URT_IOC_MAGIC, 3) + +/* TAHVO */ +#define TAHVO_IOCH_READ _IO(URT_IOC_MAGIC, 4) +#define TAHVO_IOCX_WRITE _IO(URT_IOC_MAGIC, 5) + +/* This structure is used for writing RETU/TAHVO registers */ +struct retu_tahvo_write_parms { + u32 field; + u16 value; + u8 result; +}; + +#endif -- 2.41.1