summaryrefslogtreecommitdiff
path: root/target/linux/omap24xx/patches-2.6.36/500-cbus.patch
diff options
context:
space:
mode:
authormb <mb@3c298f89-4303-0410-b956-a3cf2f4a3e73>2010-09-15 17:45:10 +0000
committermb <mb@3c298f89-4303-0410-b956-a3cf2f4a3e73>2010-09-15 17:45:10 +0000
commit52869e93433f6e2e158f8224dc03c5c11b7d4ddb (patch)
treebaf5f1f05b4d4b3edfd52f50781aa47947faaaf8 /target/linux/omap24xx/patches-2.6.36/500-cbus.patch
parent2e7a68301991a5ce6b5bfb78330c45361adc5ec8 (diff)
omap24xx: Add 2.6.36 support
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@23072 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/omap24xx/patches-2.6.36/500-cbus.patch')
-rw-r--r--target/linux/omap24xx/patches-2.6.36/500-cbus.patch4624
1 files changed, 4624 insertions, 0 deletions
diff --git a/target/linux/omap24xx/patches-2.6.36/500-cbus.patch b/target/linux/omap24xx/patches-2.6.36/500-cbus.patch
new file mode 100644
index 0000000000..8c9ed55d7d
--- /dev/null
+++ b/target/linux/omap24xx/patches-2.6.36/500-cbus.patch
@@ -0,0 +1,4624 @@
+---
+ arch/arm/Kconfig | 4
+ drivers/Makefile | 2
+ drivers/cbus/Kconfig | 89 ++++
+ drivers/cbus/Makefile | 14
+ drivers/cbus/cbus.c | 309 ++++++++++++++++
+ drivers/cbus/cbus.h | 36 +
+ drivers/cbus/retu-headset.c | 356 ++++++++++++++++++
+ drivers/cbus/retu-pwrbutton.c | 118 ++++++
+ drivers/cbus/retu-rtc.c | 477 ++++++++++++++++++++++++
+ drivers/cbus/retu-user.c | 425 ++++++++++++++++++++++
+ drivers/cbus/retu-wdt.c | 388 ++++++++++++++++++++
+ drivers/cbus/retu.c | 468 ++++++++++++++++++++++++
+ drivers/cbus/retu.h | 77 ++++
+ drivers/cbus/tahvo-usb.c | 788 +++++++++++++++++++++++++++++++++++++++++
+ drivers/cbus/tahvo-user.c | 407 +++++++++++++++++++++
+ drivers/cbus/tahvo.c | 443 +++++++++++++++++++++++
+ drivers/cbus/tahvo.h | 61 +++
+ drivers/cbus/user_retu_tahvo.h | 75 +++
+ 18 files changed, 4536 insertions(+), 1 deletion(-)
+
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/cbus.c
+@@ -0,0 +1,309 @@
++/*
++ * drivers/cbus/cbus.c
++ *
++ * Support functions for CBUS serial protocol
++ *
++ * Copyright (C) 2004, 2005 Nokia Corporation
++ *
++ * Written by Juha Yrjölä <juha.yrjola@nokia.com>,
++ * David Weinehall <david.weinehall@nokia.com>, and
++ * Mikko Ylinen <mikko.k.ylinen@nokia.com>
++ *
++ * 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 <linux/device.h>
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/delay.h>
++#include <linux/spinlock.h>
++#include <linux/gpio.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++
++#include <asm/io.h>
++#include <asm/mach-types.h>
++
++#include <plat/board.h>
++#include <plat/cbus.h>
++
++#include "cbus.h"
++
++struct cbus_host *cbus_host = NULL;
++EXPORT_SYMBOL(cbus_host);
++
++#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;
++}
++
++#define cbus_output(base, gpio, val) cbus_set_gpio_direction(base, gpio, 0)
++
++#else
++
++#define cbus_output(base, gpio, val) gpio_direction_output(gpio, val)
++#define cbus_set_gpio_dataout(base, gpio, enable) gpio_set_value(gpio, enable)
++#define cbus_get_gpio_datain(base, int, gpio) gpio_get_value(gpio)
++
++static void _cbus_send_bit(struct cbus_host *host, int bit, int set_to_input)
++{
++ gpio_set_value(host->dat_gpio, bit ? 1 : 0);
++ gpio_set_value(host->clk_gpio, 1);
++
++ /* The data bit is read on the rising edge of CLK */
++ if (set_to_input)
++ gpio_direction_input(host->dat_gpio);
++
++ gpio_set_value(host->clk_gpio, 0);
++}
++
++static u8 _cbus_receive_bit(struct cbus_host *host)
++{
++ u8 ret;
++
++ gpio_set_value(host->clk_gpio, 1);
++ ret = gpio_get_value(host->dat_gpio);
++ gpio_set_value(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 = OMAP1_IO_ADDRESS(OMAP1_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_output(base, host->dat_gpio, 1);
++
++ /* 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;
++}
++EXPORT_SYMBOL(cbus_read_reg);
++
++/*
++ * 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;
++}
++EXPORT_SYMBOL(cbus_write_reg);
++
++static int __init cbus_bus_probe(struct platform_device *pdev)
++{
++ struct cbus_host *chost;
++ struct cbus_host_platform_data *pdata = pdev->dev.platform_data;
++ int ret;
++
++ chost = kzalloc(sizeof (*chost), GFP_KERNEL);
++ if (chost == NULL)
++ return -ENOMEM;
++
++ spin_lock_init(&chost->lock);
++
++ chost->clk_gpio = pdata->clk_gpio;
++ chost->dat_gpio = pdata->dat_gpio;
++ chost->sel_gpio = pdata->sel_gpio;
++
++ if ((ret = gpio_request(chost->clk_gpio, "CBUS clk")) < 0)
++ goto exit1;
++
++ if ((ret = gpio_request(chost->dat_gpio, "CBUS data")) < 0)
++ goto exit2;
++
++ if ((ret = gpio_request(chost->sel_gpio, "CBUS sel")) < 0)
++ goto exit3;
++
++ gpio_direction_output(chost->clk_gpio, 0);
++ gpio_direction_input(chost->dat_gpio);
++ gpio_direction_output(chost->sel_gpio, 1);
++
++ gpio_set_value(chost->clk_gpio, 1);
++ gpio_set_value(chost->clk_gpio, 0);
++
++ platform_set_drvdata(pdev, chost);
++
++ cbus_host = chost;
++
++ return 0;
++exit3:
++ gpio_free(chost->dat_gpio);
++exit2:
++ gpio_free(chost->clk_gpio);
++exit1:
++ kfree(chost);
++
++ return ret;
++}
++
++static void __exit cbus_bus_remove(struct platform_device *pdev)
++{
++ struct cbus_host *chost = platform_get_drvdata(pdev);
++
++ gpio_free(chost->dat_gpio);
++ gpio_free(chost->clk_gpio);
++ kfree(chost);
++}
++
++static struct platform_driver cbus_driver = {
++ .remove = __exit_p(cbus_bus_remove),
++ .driver = {
++ .name = "cbus",
++ },
++};
++
++static int __init cbus_bus_init(void)
++{
++ return platform_driver_probe(&cbus_driver, cbus_bus_probe);
++}
++
++subsys_initcall(cbus_bus_init);
++
++static void __exit cbus_bus_exit(void)
++{
++ platform_driver_unregister(&cbus_driver);
++}
++module_exit(cbus_bus_exit);
++
++MODULE_DESCRIPTION("CBUS serial protocol");
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Juha Yrjölä");
++MODULE_AUTHOR("David Weinehall");
++MODULE_AUTHOR("Mikko Ylinen");
++
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/cbus.h
+@@ -0,0 +1,36 @@
++/*
++ * drivers/cbus/cbus.h
++ *
++ * Copyright (C) 2004, 2005 Nokia Corporation
++ *
++ * Written by Juha Yrjölä <juha.yrjola@nokia.com> and
++ * David Weinehall <david.weinehall@nokia.com>
++ *
++ * 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 */
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/Kconfig
+@@ -0,0 +1,89 @@
++#
++# 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 && WATCHDOG
++ 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.
++
++config CBUS_RETU_HEADSET
++ depends on CBUS_RETU && SYSFS
++ tristate "Support for headset detection with Retu/Vilma"
++ ---help---
++ Say Y here if you want support detecting a headset that's connected
++ to Retu/Vilma. Detection state and events are exposed through
++ sysfs.
++
++endmenu
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/Makefile
+@@ -0,0 +1,14 @@
++#
++# 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
++obj-$(CONFIG_CBUS_RETU_HEADSET) += retu-headset.o
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/retu.c
+@@ -0,0 +1,468 @@
++/**
++ * drivers/cbus/retu.c
++ *
++ * Support functions for Retu ASIC
++ *
++ * Copyright (C) 2004, 2005 Nokia Corporation
++ *
++ * Written by Juha Yrjölä <juha.yrjola@nokia.com>,
++ * David Weinehall <david.weinehall@nokia.com>, and
++ * Mikko Ylinen <mikko.k.ylinen@nokia.com>
++ *
++ * 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 <linux/module.h>
++#include <linux/init.h>
++
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/device.h>
++#include <linux/miscdevice.h>
++#include <linux/poll.h>
++#include <linux/fs.h>
++#include <linux/irq.h>
++#include <linux/interrupt.h>
++#include <linux/platform_device.h>
++#include <linux/gpio.h>
++
++#include <asm/uaccess.h>
++#include <asm/mach-types.h>
++
++#include <plat/mux.h>
++#include <plat/board.h>
++
++#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);
++
++ if ((channel == 8) && retu_is_vilma) {
++ int scr = retu_read_reg(RETU_REG_ADCSCR);
++ int ch = (retu_read_reg(RETU_REG_ADCR) >> 10) & 0xf;
++ if (((scr & 0xff) != 0) && (ch != 8))
++ retu_write_reg (RETU_REG_ADCSCR, (scr & ~0xff));
++ }
++
++ /* Select the channel and read result */
++ retu_write_reg(RETU_REG_ADCR, channel << 10);
++ res = retu_read_reg(RETU_REG_ADCR) & 0x3ff;
++
++ if (retu_is_vilma)
++ retu_write_reg(RETU_REG_ADCR, (1 << 13));
++
++ /* 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)
++{
++ int rev, ret;
++
++ /* Prepare tasklet */
++ tasklet_init(&retu_tasklet, retu_tasklet_handler, 0);
++
++ /* REVISIT: Pass these from board-*.c files in platform_data */
++ if (machine_is_nokia770()) {
++ retu_irq_pin = 62;
++ } else if (machine_is_nokia_n800() || machine_is_nokia_n810() ||
++ machine_is_nokia_n810_wimax()) {
++ retu_irq_pin = 108;
++ } else {
++ printk(KERN_ERR "cbus: Unsupported board for tahvo\n");
++ return -ENODEV;
++ }
++
++ if ((ret = gpio_request(retu_irq_pin, "RETU irq")) < 0) {
++ printk(KERN_ERR PFX "Unable to reserve IRQ GPIO\n");
++ return ret;
++ }
++
++ /* Set the pin as input */
++ gpio_direction_input(retu_irq_pin);
++
++ /* Rising edge triggers the IRQ */
++ set_irq_type(gpio_to_irq(retu_irq_pin), IRQ_TYPE_EDGE_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(gpio_to_irq(retu_irq_pin), retu_irq_handler, 0,
++ "retu", 0);
++ if (ret < 0) {
++ printk(KERN_ERR PFX "Unable to register IRQ handler\n");
++ gpio_free(retu_irq_pin);
++ return ret;
++ }
++ set_irq_wake(gpio_to_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(gpio_to_irq(retu_irq_pin), 0);
++ gpio_free(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(gpio_to_irq(retu_irq_pin), 0);
++ gpio_free(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");
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/retu.h
+@@ -0,0 +1,77 @@
++/**
++ * drivers/cbus/retu.h
++ *
++ * Copyright (C) 2004, 2005 Nokia Corporation
++ *
++ * Written by Juha Yrjölä <juha.yrjola@nokia.com> and
++ * David Weinehall <david.weinehall@nokia.com>
++ *
++ * 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 <linux/types.h>
++
++/* 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_ADCSCR 0x09 /* ADC sample ctrl */
++#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_AUDTXR 0x18 /* Audio Codec Tx 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_HOOK 6
++#define RETU_INT_HEAD 7
++#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 */
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/retu-headset.c
+@@ -0,0 +1,356 @@
++/**
++ * Retu/Vilma headset detection
++ *
++ * Copyright (C) 2006 Nokia Corporation
++ *
++ * Written by 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 <linux/module.h>
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/delay.h>
++#include <linux/input.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++
++#include "retu.h"
++
++#define RETU_ADC_CHANNEL_HOOKDET 0x05
++
++#define RETU_HEADSET_KEY KEY_PHONE
++
++struct retu_headset {
++ spinlock_t lock;
++ struct mutex mutex;
++ struct platform_device *pdev;
++ struct input_dev *idev;
++ unsigned bias_enabled;
++ unsigned detection_enabled;
++ unsigned pressed;
++ struct timer_list enable_timer;
++ struct timer_list detect_timer;
++};
++
++static void retu_headset_set_bias(int enable)
++{
++ if (enable) {
++ retu_set_clear_reg_bits(RETU_REG_AUDTXR,
++ (1 << 0) | (1 << 1), 0);
++ msleep(2);
++ retu_set_clear_reg_bits(RETU_REG_AUDTXR, 1 << 3, 0);
++ } else {
++ retu_set_clear_reg_bits(RETU_REG_AUDTXR, 0,
++ (1 << 0) | (1 << 1) | (1 << 3));
++ }
++}
++
++static void retu_headset_enable(struct retu_headset *hs)
++{
++ mutex_lock(&hs->mutex);
++ if (!hs->bias_enabled) {
++ hs->bias_enabled = 1;
++ retu_headset_set_bias(1);
++ }
++ mutex_unlock(&hs->mutex);
++}
++
++static void retu_headset_disable(struct retu_headset *hs)
++{
++ mutex_lock(&hs->mutex);
++ if (hs->bias_enabled) {
++ hs->bias_enabled = 0;
++ retu_headset_set_bias(0);
++ }
++ mutex_unlock(&hs->mutex);
++}
++
++static void retu_headset_det_enable(struct retu_headset *hs)
++{
++ mutex_lock(&hs->mutex);
++ if (!hs->detection_enabled) {
++ hs->detection_enabled = 1;
++ retu_set_clear_reg_bits(RETU_REG_CC1, (1 << 10) | (1 << 8), 0);
++ retu_enable_irq(RETU_INT_HOOK);
++ }
++ mutex_unlock(&hs->mutex);
++}
++
++static void retu_headset_det_disable(struct retu_headset *hs)
++{
++ unsigned long flags;
++
++ mutex_lock(&hs->mutex);
++ if (hs->detection_enabled) {
++ hs->detection_enabled = 0;
++ retu_disable_irq(RETU_INT_HOOK);
++ del_timer_sync(&hs->enable_timer);
++ del_timer_sync(&hs->detect_timer);
++ spin_lock_irqsave(&hs->lock, flags);
++ if (hs->pressed)
++ input_report_key(hs->idev, RETU_HEADSET_KEY, 0);
++ spin_unlock_irqrestore(&hs->lock, flags);
++ retu_set_clear_reg_bits(RETU_REG_CC1, 0, (1 << 10) | (1 << 8));
++ }
++ mutex_unlock(&hs->mutex);
++}
++
++static ssize_t retu_headset_hookdet_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ int val;
++
++ val = retu_read_adc(RETU_ADC_CHANNEL_HOOKDET);
++ return sprintf(buf, "%d\n", val);
++}
++
++static DEVICE_ATTR(hookdet, S_IRUGO, retu_headset_hookdet_show, NULL);
++
++static ssize_t retu_headset_enable_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ struct retu_headset *hs = dev_get_drvdata(dev);
++
++ return sprintf(buf, "%u\n", hs->bias_enabled);
++}
++
++static ssize_t retu_headset_enable_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ struct retu_headset *hs = dev_get_drvdata(dev);
++ int enable;
++
++ if (sscanf(buf, "%u", &enable) != 1)
++ return -EINVAL;
++ if (enable)
++ retu_headset_enable(hs);
++ else
++ retu_headset_disable(hs);
++ return count;
++}
++
++static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
++ retu_headset_enable_show, retu_headset_enable_store);
++
++static ssize_t retu_headset_enable_det_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ struct retu_headset *hs = dev_get_drvdata(dev);
++
++ return sprintf(buf, "%u\n", hs->detection_enabled);
++}
++
++static ssize_t retu_headset_enable_det_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ struct retu_headset *hs = dev_get_drvdata(dev);
++ int enable;
++
++ if (sscanf(buf, "%u", &enable) != 1)
++ return -EINVAL;
++ if (enable)
++ retu_headset_det_enable(hs);
++ else
++ retu_headset_det_disable(hs);
++ return count;
++}
++
++static DEVICE_ATTR(enable_det, S_IRUGO | S_IWUSR | S_IWGRP,
++ retu_headset_enable_det_show,
++ retu_headset_enable_det_store);
++
++static void retu_headset_hook_interrupt(unsigned long arg)
++{
++ struct retu_headset *hs = (struct retu_headset *) arg;
++ unsigned long flags;
++
++ retu_ack_irq(RETU_INT_HOOK);
++ spin_lock_irqsave(&hs->lock, flags);
++ if (!hs->pressed) {
++ /* Headset button was just pressed down. */
++ hs->pressed = 1;
++ input_report_key(hs->idev, RETU_HEADSET_KEY, 1);
++ }
++ spin_unlock_irqrestore(&hs->lock, flags);
++ retu_set_clear_reg_bits(RETU_REG_CC1, 0, (1 << 10) | (1 << 8));
++ mod_timer(&hs->enable_timer, jiffies + msecs_to_jiffies(50));
++}
++
++static void retu_headset_enable_timer(unsigned long arg)
++{
++ struct retu_headset *hs = (struct retu_headset *) arg;
++
++ retu_set_clear_reg_bits(RETU_REG_CC1, (1 << 10) | (1 << 8), 0);
++ mod_timer(&hs->detect_timer, jiffies + msecs_to_jiffies(350));
++}
++
++static void retu_headset_detect_timer(unsigned long arg)
++{
++ struct retu_headset *hs = (struct retu_headset *) arg;
++ unsigned long flags;
++
++ spin_lock_irqsave(&hs->lock, flags);
++ if (hs->pressed) {
++ hs->pressed = 0;
++ input_report_key(hs->idev, RETU_HEADSET_KEY, 0);
++ }
++ spin_unlock_irqrestore(&hs->lock, flags);
++}
++
++static int __init retu_headset_probe(struct platform_device *pdev)
++{
++ struct retu_headset *hs;
++ int r;
++
++ hs = kzalloc(sizeof(*hs), GFP_KERNEL);
++ if (hs == NULL)
++ return -ENOMEM;
++
++ hs->pdev = pdev;
++
++ hs->idev = input_allocate_device();
++ if (hs->idev == NULL) {
++ r = -ENOMEM;
++ goto err1;
++ }
++ hs->idev->name = "retu-headset";
++ hs->idev->dev.parent = &pdev->dev;
++ set_bit(EV_KEY, hs->idev->evbit);
++ set_bit(RETU_HEADSET_KEY, hs->idev->keybit);
++ r = input_register_device(hs->idev);
++ if (r < 0)
++ goto err2;
++
++ r = device_create_file(&pdev->dev, &dev_attr_hookdet);
++ if (r < 0)
++ goto err3;
++ r = device_create_file(&pdev->dev, &dev_attr_enable);
++ if (r < 0)
++ goto err4;
++ r = device_create_file(&pdev->dev, &dev_attr_enable_det);
++ if (r < 0)
++ goto err5;
++ platform_set_drvdata(pdev, hs);
++
++ spin_lock_init(&hs->lock);
++ mutex_init(&hs->mutex);
++ setup_timer(&hs->enable_timer, retu_headset_enable_timer,
++ (unsigned long) hs);
++ setup_timer(&hs->detect_timer, retu_headset_detect_timer,
++ (unsigned long) hs);
++
++ r = retu_request_irq(RETU_INT_HOOK, retu_headset_hook_interrupt,
++ (unsigned long) hs, "hookdet");
++ if (r != 0) {
++ dev_err(&pdev->dev, "hookdet IRQ not available\n");
++ goto err6;
++ }
++ retu_disable_irq(RETU_INT_HOOK);
++ return 0;
++err6:
++ device_remove_file(&pdev->dev, &dev_attr_enable_det);
++err5:
++ device_remove_file(&pdev->dev, &dev_attr_enable);
++err4:
++ device_remove_file(&pdev->dev, &dev_attr_hookdet);
++err3:
++ input_unregister_device(hs->idev);
++err2:
++ input_free_device(hs->idev);
++err1:
++ kfree(hs);
++ return r;
++}
++
++static int retu_headset_remove(struct platform_device *pdev)
++{
++ struct retu_headset *hs = platform_get_drvdata(pdev);
++
++ device_remove_file(&pdev->dev, &dev_attr_hookdet);
++ device_remove_file(&pdev->dev, &dev_attr_enable);
++ device_remove_file(&pdev->dev, &dev_attr_enable_det);
++ retu_headset_disable(hs);
++ retu_headset_det_disable(hs);
++ retu_free_irq(RETU_INT_HOOK);
++ input_unregister_device(hs->idev);
++ input_free_device(hs->idev);
++ return 0;
++}
++
++static int retu_headset_suspend(struct platform_device *pdev,
++ pm_message_t mesg)
++{
++ struct retu_headset *hs = platform_get_drvdata(pdev);
++
++ mutex_lock(&hs->mutex);
++ if (hs->bias_enabled)
++ retu_headset_set_bias(0);
++ mutex_unlock(&hs->mutex);
++
++ return 0;
++}
++
++static int retu_headset_resume(struct platform_device *pdev)
++{
++ struct retu_headset *hs = platform_get_drvdata(pdev);
++
++ mutex_lock(&hs->mutex);
++ if (hs->bias_enabled)
++ retu_headset_set_bias(1);
++ mutex_unlock(&hs->mutex);
++
++ return 0;
++}
++
++static struct platform_driver retu_headset_driver = {
++ .probe = retu_headset_probe,
++ .remove = retu_headset_remove,
++ .suspend = retu_headset_suspend,
++ .resume = retu_headset_resume,
++ .driver = {
++ .name = "retu-headset",
++ },
++};
++
++static int __init retu_headset_init(void)
++{
++ int r;
++
++ printk(KERN_INFO "Retu/Vilma headset driver initializing\n");
++
++ r = platform_driver_register(&retu_headset_driver);
++ if (r < 0)
++ return r;
++
++ return 0;
++}
++
++static void __exit retu_headset_exit(void)
++{
++ platform_driver_unregister(&retu_headset_driver);
++}
++
++module_init(retu_headset_init);
++module_exit(retu_headset_exit);
++
++MODULE_DESCRIPTION("Retu/Vilma headset detection");
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Juha Yrjölä");
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/retu-pwrbutton.c
+@@ -0,0 +1,118 @@
++/**
++ * drivers/cbus/retu-pwrbutton.c
++ *
++ * Driver for sending retu power button event to input-layer
++ *
++ * Copyright (C) 2004 Nokia Corporation
++ *
++ * Written by Ari Saastamoinen <ari.saastamoinen@elektrobit.com>
++ *
++ * Contact Juha Yrjölä <juha.yrjola@nokia.com>
++ *
++ * 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 <linux/module.h>
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/input.h>
++#include <linux/timer.h>
++#include <linux/jiffies.h>
++#include <linux/bitops.h>
++
++#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_MASK(EV_KEY);
++ pwrbtn_dev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
++ pwrbtn_dev->name = "retu-pwrbutton";
++
++ return input_register_device(pwrbtn_dev);
++}
++
++/**
++ * 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");
+--- /dev/null
++++ linux-2.6.36-rc4/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 <paul.mundt@nokia.com> and
++ * Igor Stoppa <igor.stoppa@nokia.com>
++ *
++ * 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 <linux/device.h>
++#include <linux/init.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/completion.h>
++#include <linux/platform_device.h>
++#include <linux/mutex.h>
++#include <linux/workqueue.h>
++
++#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 platform_device retu_rtc_device;
++
++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_device.dev.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_all(&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");
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/retu-user.c
+@@ -0,0 +1,425 @@
++/**
++ * drivers/cbus/retu-user.c
++ *
++ * Retu user space interface functions
++ *
++ * Copyright (C) 2004, 2005 Nokia Corporation
++ *
++ * Written by Mikko Ylinen <mikko.k.ylinen@nokia.com>
++ *
++ * 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 <linux/types.h>
++#include <linux/kernel.h>
++#include <linux/interrupt.h>
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/fs.h>
++#include <linux/miscdevice.h>
++#include <linux/poll.h>
++#include <linux/list.h>
++#include <linux/spinlock.h>
++#include <linux/sched.h>
++#include <linux/mutex.h>
++#include <linux/slab.h>
++
++#include <asm/uaccess.h>
++
++#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,
++ .unlocked_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");
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/retu-wdt.c
+@@ -0,0 +1,388 @@
++/**
++ * drivers/cbus/retu-wdt.c
++ *
++ * Driver for Retu watchdog
++ *
++ * Copyright (C) 2004, 2005 Nokia Corporation
++ *
++ * Written by Amit Kucheria <amit.kucheria@nokia.com>
++ *
++ * 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 <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/device.h>
++#include <linux/init.h>
++#include <linux/fs.h>
++#include <linux/io.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++
++#include <linux/completion.h>
++#include <linux/errno.h>
++#include <linux/moduleparam.h>
++#include <linux/platform_device.h>
++#include <linux/miscdevice.h>
++#include <linux/watchdog.h>
++
++#include <asm/uaccess.h>
++
++#include <plat/prcm.h>
++
++#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 DEFINE_MUTEX(retu_wdt_mutex);
++
++/* Current period of watchdog */
++static unsigned int period_val = RETU_WDT_DEFAULT_TIMER;
++static int counter_param = RETU_WDT_MAX_TIMER;
++
++struct retu_wdt_dev {
++ struct device *dev;
++ int users;
++ struct miscdevice retu_wdt_miscdev;
++ struct timer_list ping_timer;
++};
++
++static struct retu_wdt_dev *retu_wdt;
++
++static void retu_wdt_set_ping_timer(unsigned long enable);
++
++static int _retu_modify_counter(unsigned int new)
++{
++ retu_write_reg(RETU_REG_WATCHDOG, (u16)new);
++
++ return 0;
++}
++
++static int retu_modify_counter(unsigned int new)
++{
++ if (new < RETU_WDT_MIN_TIMER || new > RETU_WDT_MAX_TIMER)
++ return -EINVAL;
++
++ mutex_lock(&retu_wdt_mutex);
++ period_val = new;
++ _retu_modify_counter(period_val);
++ mutex_unlock(&retu_wdt_mutex);
++
++ return 0;
++}
++
++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);
++}
++
++/*
++ * Note: This inteface is non-standard and likely to disappear!
++ * Use /dev/watchdog instead, that's the standard.
++ */
++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;
++
++#ifdef CONFIG_WATCHDOG_NOWAYOUT
++ retu_wdt_set_ping_timer(0);
++#endif
++
++ 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);
++
++/*----------------------------------------------------------------------------*/
++
++/*
++ * Since retu watchdog cannot be disabled in hardware, we must kick it
++ * with a timer until userspace watchdog software takes over. Do this
++ * unless /dev/watchdog is open or CONFIG_WATCHDOG_NOWAYOUT is set.
++ */
++static void retu_wdt_set_ping_timer(unsigned long enable)
++{
++ _retu_modify_counter(RETU_WDT_MAX_TIMER);
++ if (enable)
++ mod_timer(&retu_wdt->ping_timer,
++ jiffies + RETU_WDT_DEFAULT_TIMER * HZ);
++ else
++ del_timer_sync(&retu_wdt->ping_timer);
++}
++
++static int retu_wdt_open(struct inode *inode, struct file *file)
++{
++ if (test_and_set_bit(1, (unsigned long *)&(retu_wdt->users)))
++ return -EBUSY;
++
++ file->private_data = (void *)retu_wdt;
++ retu_wdt_set_ping_timer(0);
++
++ return nonseekable_open(inode, file);
++}
++
++static int retu_wdt_release(struct inode *inode, struct file *file)
++{
++ struct retu_wdt_dev *wdev = file->private_data;
++
++#ifndef CONFIG_WATCHDOG_NOWAYOUT
++ retu_wdt_set_ping_timer(1);
++#endif
++ wdev->users = 0;
++
++ return 0;
++}
++
++static ssize_t retu_wdt_write(struct file *file, const char __user *data,
++ size_t len, loff_t *ppos)
++{
++ if (len)
++ retu_modify_counter(RETU_WDT_MAX_TIMER);
++
++ return len;
++}
++
++static int retu_wdt_ioctl(struct inode *inode, struct file *file,
++ unsigned int cmd, unsigned long arg)
++{
++ int new_margin;
++
++ static struct watchdog_info ident = {
++ .identity = "Retu Watchdog",
++ .options = WDIOF_SETTIMEOUT,
++ .firmware_version = 0,
++ };
++
++ switch (cmd) {
++ default:
++ return -ENOTTY;
++ case WDIOC_GETSUPPORT:
++ return copy_to_user((struct watchdog_info __user *)arg, &ident,
++ sizeof(ident));
++ case WDIOC_GETSTATUS:
++ return put_user(0, (int __user *)arg);
++ case WDIOC_GETBOOTSTATUS:
++ if (cpu_is_omap16xx())
++ return put_user(omap_readw(ARM_SYSST),
++ (int __user *)arg);
++ if (cpu_is_omap24xx())
++ return put_user(omap_prcm_get_reset_sources(),
++ (int __user *)arg);
++ case WDIOC_KEEPALIVE:
++ retu_modify_counter(RETU_WDT_MAX_TIMER);
++ break;
++ case WDIOC_SETTIMEOUT:
++ if (get_user(new_margin, (int __user *)arg))
++ return -EFAULT;
++ retu_modify_counter(new_margin);
++ /* Fall through */
++ case WDIOC_GETTIMEOUT:
++ return put_user(period_val, (int __user *)arg);
++ }
++
++ return 0;
++}
++
++/* Start kicking retu watchdog until user space starts doing the kicking */
++static int __init retu_wdt_ping(void)
++{
++
++#ifdef CONFIG_WATCHDOG_NOWAYOUT
++ retu_modify_counter(RETU_WDT_MAX_TIMER);
++#else
++ retu_wdt_set_ping_timer(1);
++#endif
++
++ return 0;
++}
++late_initcall(retu_wdt_ping);
++
++static const struct file_operations retu_wdt_fops = {
++ .owner = THIS_MODULE,
++ .write = retu_wdt_write,
++ .unlocked_ioctl = retu_wdt_ioctl,
++ .open = retu_wdt_open,
++ .release = retu_wdt_release,
++};
++
++/*----------------------------------------------------------------------------*/
++
++static int __devinit retu_wdt_probe(struct device *dev)
++{
++ struct retu_wdt_dev *wdev;
++ int ret;
++
++ wdev = kzalloc(sizeof(struct retu_wdt_dev), GFP_KERNEL);
++ if (!wdev)
++ return -ENOMEM;
++
++ wdev->users = 0;
++
++ ret = device_create_file(dev, &dev_attr_period);
++ if (ret) {
++ printk(KERN_ERR "retu_wdt_probe: Error creating "
++ "sys device file: period\n");
++ goto free1;
++ }
++
++ ret = device_create_file(dev, &dev_attr_counter);
++ if (ret) {
++ printk(KERN_ERR "retu_wdt_probe: Error creating "
++ "sys device file: counter\n");
++ goto free2;
++ }
++
++ dev_set_drvdata(dev, wdev);
++ retu_wdt = wdev;
++ wdev->retu_wdt_miscdev.parent = dev;
++ wdev->retu_wdt_miscdev.minor = WATCHDOG_MINOR;
++ wdev->retu_wdt_miscdev.name = "watchdog";
++ wdev->retu_wdt_miscdev.fops = &retu_wdt_fops;
++
++ ret = misc_register(&(wdev->retu_wdt_miscdev));
++ if (ret)
++ goto free3;
++
++ setup_timer(&wdev->ping_timer, retu_wdt_set_ping_timer, 1);
++
++ /* Kick the watchdog for kernel booting to finish */
++ retu_modify_counter(RETU_WDT_MAX_TIMER);
++
++ return 0;
++
++free3:
++ device_remove_file(dev, &dev_attr_counter);
++
++free2:
++ device_remove_file(dev, &dev_attr_period);
++free1:
++ kfree(wdev);
++
++ return ret;
++}
++
++static int __devexit retu_wdt_remove(struct device *dev)
++{
++ struct retu_wdt_dev *wdev;
++
++ wdev = dev_get_drvdata(dev);
++ misc_deregister(&(wdev->retu_wdt_miscdev));
++ device_remove_file(dev, &dev_attr_period);
++ device_remove_file(dev, &dev_attr_counter);
++ kfree(wdev);
++
++ 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");
++
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/tahvo.c
+@@ -0,0 +1,443 @@
++/**
++ * drivers/cbus/tahvo.c
++ *
++ * Support functions for Tahvo ASIC
++ *
++ * Copyright (C) 2004, 2005 Nokia Corporation
++ *
++ * Written by Juha Yrjölä <juha.yrjola@nokia.com>,
++ * David Weinehall <david.weinehall@nokia.com>, and
++ * Mikko Ylinen <mikko.k.ylinen@nokia.com>
++ *
++ * 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 <linux/module.h>
++#include <linux/init.h>
++
++#include <linux/kernel.h>
++#include <linux/errno.h>
++#include <linux/device.h>
++#include <linux/miscdevice.h>
++#include <linux/poll.h>
++#include <linux/fs.h>
++#include <linux/irq.h>
++#include <linux/interrupt.h>
++#include <linux/platform_device.h>
++#include <linux/gpio.h>
++
++#include <asm/uaccess.h>
++#include <asm/mach-types.h>
++
++#include <plat/mux.h>
++#include <plat/board.h>
++
++#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)
++{
++ int rev, id, ret;
++
++ /* Prepare tasklet */
++ tasklet_init(&tahvo_tasklet, tahvo_tasklet_handler, 0);
++
++ 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);
++
++ /* REVISIT: Pass these from board-*.c files in platform_data */
++ if (machine_is_nokia770()) {
++ tahvo_irq_pin = 40;
++ } else if (machine_is_nokia_n800() || machine_is_nokia_n810() ||
++ machine_is_nokia_n810_wimax()) {
++ tahvo_irq_pin = 111;
++ } else {
++ printk(KERN_ERR "cbus: Unsupported board for tahvo\n");
++ return -ENODEV;
++ }
++
++ if ((ret = gpio_request(tahvo_irq_pin, "TAHVO irq")) < 0) {
++ printk(KERN_ERR PFX "Unable to reserve IRQ GPIO\n");
++ return ret;
++ }
++
++ /* Set the pin as input */
++ gpio_direction_input(tahvo_irq_pin);
++
++ /* Rising edge triggers the IRQ */
++ set_irq_type(gpio_to_irq(tahvo_irq_pin), IRQ_TYPE_EDGE_RISING);
++
++ /* Mask all TAHVO interrupts */
++ tahvo_write_reg(TAHVO_REG_IMR, 0xffff);
++
++ ret = request_irq(gpio_to_irq(tahvo_irq_pin), tahvo_irq_handler, 0,
++ "tahvo", 0);
++ if (ret < 0) {
++ printk(KERN_ERR PFX "Unable to register IRQ handler\n");
++ gpio_free(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(gpio_to_irq(tahvo_irq_pin), 0);
++ gpio_free(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(gpio_to_irq(tahvo_irq_pin), 0);
++ gpio_free(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");
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/tahvo.h
+@@ -0,0 +1,61 @@
++/*
++ * drivers/cbus/tahvo.h
++ *
++ * Copyright (C) 2004, 2005 Nokia Corporation
++ *
++ * Written by Juha Yrjölä <juha.yrjola@nokia.com> and
++ * David Weinehall <david.weinehall@nokia.com>
++ *
++ * 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 <linux/types.h>
++
++/* 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 */
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/tahvo-usb.c
+@@ -0,0 +1,788 @@
++/**
++ * 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ä <juha.yrjola@nokia.com>,
++ * Tony Lindgren <tony@atomide.com>, and
++ * Timo Teräs <timo.teras@nokia.com>
++ *
++ * 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 <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/slab.h>
++#include <linux/io.h>
++#include <linux/interrupt.h>
++#include <linux/platform_device.h>
++#include <linux/usb/ch9.h>
++#include <linux/usb/gadget.h>
++#include <linux/usb.h>
++#include <linux/usb/otg.h>
++#include <linux/i2c.h>
++#include <linux/workqueue.h>
++#include <linux/kobject.h>
++#include <linux/clk.h>
++#include <linux/mutex.h>
++
++#include <asm/irq.h>
++#include <plat/usb.h>
++
++#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 */
++
++/* 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 = arg;
++ struct tahvo_usb *tu = platform_get_drvdata(otg_dev);
++ u16 otg_irq;
++
++ otg_irq = omap_readw(OTG_IRQ_SRC);
++ if (otg_irq & OPRT_CHG) {
++ omap_writew(OPRT_CHG, OTG_IRQ_SRC);
++ } else if (otg_irq & B_SRP_TMROUT) {
++ omap_writew(B_SRP_TMROUT, OTG_IRQ_SRC);
++ } else if (otg_irq & B_HNP_FAIL) {
++ omap_writew(B_HNP_FAIL, OTG_IRQ_SRC);
++ } else if (otg_irq & A_SRP_DETECT) {
++ omap_writew(A_SRP_DETECT, OTG_IRQ_SRC);
++ } else if (otg_irq & A_REQ_TMROUT) {
++ omap_writew(A_REQ_TMROUT, OTG_IRQ_SRC);
++ } else if (otg_irq & A_VBUS_ERR) {
++ omap_writew(A_VBUS_ERR, OTG_IRQ_SRC);
++ } else if (otg_irq & DRIVER_SWITCH) {
++ if ((!(omap_readl(OTG_CTRL) & 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);
++ }
++ omap_writew(DRIVER_SWITCH, OTG_IRQ_SRC);
++ } else
++ return IRQ_NONE;
++
++ return IRQ_HANDLED;
++
++}
++
++static int tahvo_omap_otg_init(void)
++{
++ u32 l;
++
++#ifdef CONFIG_USB_OTG
++ if (!tahvo_otg_dev) {
++ printk("tahvo-usb: no tahvo_otg_dev\n");
++ return -ENODEV;
++ }
++#endif
++
++ l = omap_readl(OTG_SYSCON_1);
++ l &= ~OTG_IDLE_EN;
++ omap_writel(l, OTG_SYSCON_1);
++ udelay(100);
++
++ /* some of these values are board-specific... */
++ l = omap_readl(OTG_SYSCON_2);
++ l |= 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 */
++ omap_writel(l, OTG_SYSCON_2);
++
++ omap_writew(DRIVER_SWITCH | OPRT_CHG
++ | B_SRP_TMROUT | B_HNP_FAIL
++ | A_VBUS_ERR | A_SRP_DETECT | A_REQ_TMROUT,
++ OTG_IRQ_EN);
++ l = omap_readl(OTG_SYSCON_2);
++ l |= OTG_EN;
++ omap_writel(l, OTG_SYSCON_2);
++
++ return 0;
++}
++
++static int omap_otg_probe(struct platform_device *pdev)
++{
++ int ret, err;
++
++ tahvo_otg_dev = pdev;
++ ret = tahvo_omap_otg_init();
++ if (ret != 0) {
++ printk(KERN_ERR "tahvo-usb: omap_otg_init failed\n");
++ return ret;
++ }
++
++ err = request_irq(tahvo_otg_dev->resource[1].start,
++ omap_otg_irq, IRQF_DISABLED, DRIVER_NAME,
++ &tahvo_usb_device);
++
++ return err;
++}
++
++static int omap_otg_remove(struct platform_device *pdev)
++{
++ free_irq(tahvo_otg_dev->resource[1].start, &tahvo_usb_device);
++ tahvo_otg_dev = NULL;
++
++ return 0;
++}
++
++static struct platform_driver omap_otg_driver = {
++ .probe = omap_otg_probe,
++ .remove = omap_otg_remove,
++ .driver = {
++ .name = "omap_otg",
++ }
++};
++
++/*
++ * ---------------------------------------------------------------------------
++ * 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 platform_device *pdev = to_platform_device(device);
++ struct tahvo_usb *tu = platform_get_drvdata(pdev);
++ 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) {
++ u32 l;
++
++ 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. */
++ l = omap_readl(OTG_CTRL);
++ l &= ~OTG_BSESSEND;
++ l |= OTG_BSESSVLD;
++ omap_writel(l, OTG_CTRL);
++
++ 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 */
++ tahvo_omap_otg_init();
++
++ l = omap_readl(OTG_CTRL);
++ l &= ~(OTG_CTRL_XCVR_MASK | OTG_CTRL_SYS_MASK);
++ l |= OTG_ASESSVLD;
++ omap_writel(l, OTG_CTRL);
++
++ /* 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)
++{
++ u32 l;
++
++ /* Clear system and transceiver controlled bits
++ * and enable ID to mark peripheral mode and
++ * BSESSEND to mark no Vbus */
++ tahvo_omap_otg_init();
++ l = omap_readl(OTG_CTRL);
++ l &= ~(OTG_CTRL_XCVR_MASK | OTG_CTRL_SYS_MASK | OTG_BSESSVLD);
++ l |= OTG_ID | OTG_BSESSEND;
++ omap_writel(l, OTG_CTRL);
++
++ /* 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)
++{
++ u32 l;
++
++ l = omap_readl(OTG_CTRL);
++ l &= ~OTG_BSESSVLD;
++ l |= OTG_BSESSEND;
++ omap_writel(l, OTG_CTRL);
++
++ 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 = omap_readl(OTG_CTRL);
++ l &= ~(OTG_CTRL_XCVR_MASK | OTG_CTRL_SYS_MASK | OTG_BSESSVLD);
++ l |= id | OTG_BSESSEND;
++ omap_writel(l, OTG_CTRL);
++ omap_writew(0, OTG_IRQ_EN);
++
++ l = omap_readl(OTG_SYSCON_2);
++ l &= ~OTG_EN;
++ omap_writel(l, OTG_SYSCON_2);
++
++ l = omap_readl(OTG_SYSCON_1);
++ l |= OTG_IDLE_EN;
++ omap_writel(l, OTG_SYSCON_1);
++
++ /* 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 = omap_readl(OTG_CTRL);
++ if (!(otg_ctrl & OTG_BSESSEND))
++ return -EINVAL;
++
++ otg_ctrl |= OTG_B_BUSREQ;
++ otg_ctrl &= ~OTG_A_BUSREQ & OTG_CTRL_SYS_MASK;
++ omap_writel(otg_ctrl, 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);
++ u32 l;
++
++ 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;
++ }
++
++ l = omap_readl(OTG_SYSCON_1);
++ l &= ~(OTG_IDLE_EN | HST_IDLE_EN | DEV_IDLE_EN);
++ omap_writel(l, OTG_SYSCON_1);
++
++ 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 platform_device *pdev = to_platform_device(device);
++ struct tahvo_usb *tu = platform_get_drvdata(pdev);
++
++ 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 platform_device *pdev = to_platform_device(device);
++ struct tahvo_usb *tu = platform_get_drvdata(pdev);
++ 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 platform_device *pdev)
++{
++ struct tahvo_usb *tu;
++ int ret;
++
++ dev_dbg(&pdev->dev, "probe\n");
++
++ /* Create driver data */
++ tu = kmalloc(sizeof(*tu), GFP_KERNEL);
++ if (!tu)
++ return -ENOMEM;
++ memset(tu, 0, sizeof(*tu));
++ tu->pt_dev = pdev;
++#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(&pdev->dev, &dev_attr_vbus_state);
++#ifdef CONFIG_USB_OTG
++ ret |= device_create_file(&pdev->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;
++ }
++
++ platform_set_drvdata(pdev, 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 platform_device *pdev)
++{
++ dev_dbg(&pdev->dev, "remove\n");
++
++ tahvo_free_irq(TAHVO_INT_VBUSON);
++ flush_scheduled_work();
++ otg_set_transceiver(0);
++ device_remove_file(&pdev->dev, &dev_attr_vbus_state);
++#ifdef CONFIG_USB_OTG
++ device_remove_file(&pdev->dev, &dev_attr_otg_mode);
++#endif
++ return 0;
++}
++
++static struct platform_driver tahvo_usb_driver = {
++ .probe = tahvo_usb_probe,
++ .remove = tahvo_usb_remove,
++ .driver = {
++ .name = "tahvo-usb",
++ }
++};
++
++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 = platform_driver_register(&tahvo_usb_driver);
++ if (ret)
++ return ret;
++ ret = platform_driver_register(&omap_otg_driver);
++ if (ret) {
++ platform_driver_unregister(&tahvo_usb_driver);
++ return ret;
++ }
++
++ ret = platform_device_register(&tahvo_usb_device);
++ if (ret) {
++ platform_driver_unregister(&omap_otg_driver);
++ platform_driver_unregister(&tahvo_usb_driver);
++ return ret;
++ }
++
++ return 0;
++}
++subsys_initcall(tahvo_usb_init);
++
++static void __exit tahvo_usb_exit(void)
++{
++ platform_device_unregister(&tahvo_usb_device);
++ platform_driver_unregister(&omap_otg_driver);
++ platform_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");
+--- /dev/null
++++ linux-2.6.36-rc4/drivers/cbus/tahvo-user.c
+@@ -0,0 +1,407 @@
++/**
++ * drivers/cbus/tahvo-user.c
++ *
++ * Tahvo user space interface functions
++ *
++ * Copyright (C) 2004, 2005 Nokia Corporation
++ *
++ * Written by Mikko Ylinen <mikko.k.ylinen@nokia.com>
++ *
++ * 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 <linux/types.h>
++#include <linux/kernel.h>
++#include <linux/interrupt.h>
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/fs.h>
++#include <linux/miscdevice.h>
++#include <linux/poll.h>
++#include <linux/list.h>
++#include <linux/spinlock.h>
++#include <linux/sched.h>
++#include <linux/mutex.h>
++#include <linux/slab.h>
++
++#include <asm/uaccess.h>
++
++#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,
++ .unlocked_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");
+--- /dev/null
++++ linux-2.6.36-rc4/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 <mikko.k.ylinen@nokia.com>
++ *
++ * 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
+--- linux-2.6.36-rc4.orig/drivers/Makefile
++++ linux-2.6.36-rc4/drivers/Makefile
+@@ -74,7 +74,7 @@ obj-$(CONFIG_GAMEPORT) += input/gamepor
+ obj-$(CONFIG_INPUT) += input/
+ obj-$(CONFIG_I2O) += message/
+ obj-$(CONFIG_RTC_LIB) += rtc/
+-obj-y += i2c/ media/
++obj-y += i2c/ media/ cbus/
+ obj-$(CONFIG_PPS) += pps/
+ obj-$(CONFIG_W1) += w1/
+ obj-$(CONFIG_POWER_SUPPLY) += power/
+--- linux-2.6.36-rc4.orig/arch/arm/Kconfig
++++ linux-2.6.36-rc4/arch/arm/Kconfig
+@@ -1760,6 +1760,10 @@ source "net/Kconfig"
+
+ source "drivers/Kconfig"
+
++if ARCH_OMAP
++source "drivers/cbus/Kconfig"
++endif
++
+ source "fs/Kconfig"
+
+ source "arch/arm/Kconfig.debug"