diff options
Diffstat (limited to 'target/linux/omap24xx/patches-2.6.37/500-cbus.patch')
-rw-r--r-- | target/linux/omap24xx/patches-2.6.37/500-cbus.patch | 4657 |
1 files changed, 4657 insertions, 0 deletions
diff --git a/target/linux/omap24xx/patches-2.6.37/500-cbus.patch b/target/linux/omap24xx/patches-2.6.37/500-cbus.patch new file mode 100644 index 0000000..e218f95 --- /dev/null +++ b/target/linux/omap24xx/patches-2.6.37/500-cbus.patch @@ -0,0 +1,4657 @@ +--- + 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 | 424 ++++++++++++++++++++++ + drivers/cbus/retu-wdt.c | 387 ++++++++++++++++++++ + drivers/cbus/retu.c | 468 ++++++++++++++++++++++++ + drivers/cbus/retu.h | 77 ++++ + drivers/cbus/tahvo-usb.c | 788 +++++++++++++++++++++++++++++++++++++++++ + drivers/cbus/tahvo-user.c | 406 +++++++++++++++++++++ + drivers/cbus/tahvo.c | 443 +++++++++++++++++++++++ + drivers/cbus/tahvo.h | 61 +++ + drivers/cbus/user_retu_tahvo.h | 75 +++ + 18 files changed, 4533 insertions(+), 1 deletion(-) + +Index: linux-2.6.37-rc1/drivers/cbus/cbus.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/cbus.c 2010-11-05 17:04:49.000997852 +0100 +@@ -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"); ++ +Index: linux-2.6.37-rc1/drivers/cbus/cbus.h +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/cbus.h 2010-11-05 17:04:49.000997852 +0100 +@@ -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 */ +Index: linux-2.6.37-rc1/drivers/cbus/Kconfig +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/Kconfig 2010-11-05 17:04:49.001997921 +0100 +@@ -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 +Index: linux-2.6.37-rc1/drivers/cbus/Makefile +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/Makefile 2010-11-05 17:04:49.001997921 +0100 +@@ -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 +Index: linux-2.6.37-rc1/drivers/cbus/retu.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/retu.c 2010-11-05 17:04:49.001997921 +0100 +@@ -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"); +Index: linux-2.6.37-rc1/drivers/cbus/retu.h +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/retu.h 2010-11-05 17:04:49.001997921 +0100 +@@ -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 */ +Index: linux-2.6.37-rc1/drivers/cbus/retu-headset.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/retu-headset.c 2010-11-05 17:04:49.001997921 +0100 +@@ -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ä"); +Index: linux-2.6.37-rc1/drivers/cbus/retu-pwrbutton.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/retu-pwrbutton.c 2010-11-05 17:04:49.001997921 +0100 +@@ -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"); +Index: linux-2.6.37-rc1/drivers/cbus/retu-rtc.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/retu-rtc.c 2010-11-05 17:04:49.001997921 +0100 +@@ -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"); +Index: linux-2.6.37-rc1/drivers/cbus/retu-user.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/retu-user.c 2010-11-05 17:04:49.002997987 +0100 +@@ -0,0 +1,424 @@ ++/** ++ * 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 long retu_ioctl(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"); +Index: linux-2.6.37-rc1/drivers/cbus/retu-wdt.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/retu-wdt.c 2010-11-05 17:04:49.002997987 +0100 +@@ -0,0 +1,387 @@ ++/** ++ * 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 long retu_wdt_ioctl(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"); ++ +Index: linux-2.6.37-rc1/drivers/cbus/tahvo.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/tahvo.c 2010-11-05 17:04:49.002997987 +0100 +@@ -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"); +Index: linux-2.6.37-rc1/drivers/cbus/tahvo.h +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/tahvo.h 2010-11-05 17:04:49.002997987 +0100 +@@ -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 */ +Index: linux-2.6.37-rc1/drivers/cbus/tahvo-usb.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/tahvo-usb.c 2010-11-05 17:04:49.002997987 +0100 +@@ -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"); +Index: linux-2.6.37-rc1/drivers/cbus/tahvo-user.c +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/tahvo-user.c 2010-11-05 17:04:49.003998052 +0100 +@@ -0,0 +1,406 @@ ++/** ++ * 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 long tahvo_ioctl(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"); +Index: linux-2.6.37-rc1/drivers/cbus/user_retu_tahvo.h +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ linux-2.6.37-rc1/drivers/cbus/user_retu_tahvo.h 2010-11-05 17:04:49.003998052 +0100 +@@ -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 +Index: linux-2.6.37-rc1/drivers/Makefile +=================================================================== +--- linux-2.6.37-rc1.orig/drivers/Makefile 2010-11-01 12:54:12.000000000 +0100 ++++ linux-2.6.37-rc1/drivers/Makefile 2010-11-05 17:04:49.003998052 +0100 +@@ -73,7 +73,7 @@ + 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/ +Index: linux-2.6.37-rc1/arch/arm/Kconfig +=================================================================== +--- linux-2.6.37-rc1.orig/arch/arm/Kconfig 2010-11-01 12:54:12.000000000 +0100 ++++ linux-2.6.37-rc1/arch/arm/Kconfig 2010-11-05 17:04:49.003998052 +0100 +@@ -1850,6 +1850,10 @@ + + source "drivers/Kconfig" + ++if ARCH_OMAP ++source "drivers/cbus/Kconfig" ++endif ++ + source "fs/Kconfig" + + source "arch/arm/Kconfig.debug" |