/** * drivers/cbus/retu.c * * Support functions for Retu ASIC * * Copyright (C) 2004, 2005 Nokia Corporation * * Written by Juha Yrjölä , * David Weinehall , and * Mikko Ylinen * * This file is subject to the terms and conditions of the GNU General * Public License. See the file "COPYING" in the main directory of this * archive for more details. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cbus.h" #include "retu.h" #define RETU_ID 0x01 #define PFX "retu: " static int retu_initialized; static int retu_irq_pin; static int retu_is_vilma; static struct tasklet_struct retu_tasklet; spinlock_t retu_lock = SPIN_LOCK_UNLOCKED; static struct completion device_release; struct retu_irq_handler_desc { int (*func)(unsigned long); unsigned long arg; char name[8]; }; static struct retu_irq_handler_desc retu_irq_handlers[MAX_RETU_IRQ_HANDLERS]; /** * retu_read_reg - Read a value from a register in Retu * @reg: the register to read from * * This function returns the contents of the specified register */ int retu_read_reg(int reg) { BUG_ON(!retu_initialized); return cbus_read_reg(cbus_host, RETU_ID, reg); } /** * retu_write_reg - Write a value to a register in Retu * @reg: the register to write to * @reg: the value to write to the register * * This function writes a value to the specified register */ void retu_write_reg(int reg, u16 val) { BUG_ON(!retu_initialized); cbus_write_reg(cbus_host, RETU_ID, reg, val); } void retu_set_clear_reg_bits(int reg, u16 set, u16 clear) { unsigned long flags; u16 w; spin_lock_irqsave(&retu_lock, flags); w = retu_read_reg(reg); w &= ~clear; w |= set; retu_write_reg(reg, w); spin_unlock_irqrestore(&retu_lock, flags); } #define ADC_MAX_CHAN_NUMBER 13 int retu_read_adc(int channel) { unsigned long flags; int res; if (channel < 0 || channel > ADC_MAX_CHAN_NUMBER) return -EINVAL; spin_lock_irqsave(&retu_lock, flags); /* Select the channel and read result */ retu_write_reg(RETU_REG_ADCR, channel << 10); res = retu_read_reg(RETU_REG_ADCR) & 0x3ff; /* Unlock retu */ spin_unlock_irqrestore(&retu_lock, flags); return res; } static u16 retu_disable_bogus_irqs(u16 mask) { int i; for (i = 0; i < MAX_RETU_IRQ_HANDLERS; i++) { if (mask & (1 << i)) continue; if (retu_irq_handlers[i].func != NULL) continue; /* an IRQ was enabled but we don't have a handler for it */ printk(KERN_INFO PFX "disabling bogus IRQ %d\n", i); mask |= (1 << i); } return mask; } /* * Disable given RETU interrupt */ void retu_disable_irq(int id) { unsigned long flags; u16 mask; spin_lock_irqsave(&retu_lock, flags); mask = retu_read_reg(RETU_REG_IMR); mask |= 1 << id; mask = retu_disable_bogus_irqs(mask); retu_write_reg(RETU_REG_IMR, mask); spin_unlock_irqrestore(&retu_lock, flags); } /* * Enable given RETU interrupt */ void retu_enable_irq(int id) { unsigned long flags; u16 mask; if (id == 3) { printk("Enabling Retu IRQ %d\n", id); dump_stack(); } spin_lock_irqsave(&retu_lock, flags); mask = retu_read_reg(RETU_REG_IMR); mask &= ~(1 << id); mask = retu_disable_bogus_irqs(mask); retu_write_reg(RETU_REG_IMR, mask); spin_unlock_irqrestore(&retu_lock, flags); } /* * Acknowledge given RETU interrupt */ void retu_ack_irq(int id) { retu_write_reg(RETU_REG_IDR, 1 << id); } /* * RETU interrupt handler. Only schedules the tasklet. */ static irqreturn_t retu_irq_handler(int irq, void *dev_id, struct pt_regs *regs) { tasklet_schedule(&retu_tasklet); return IRQ_HANDLED; } /* * Tasklet handler */ static void retu_tasklet_handler(unsigned long data) { struct retu_irq_handler_desc *hnd; u16 id; u16 im; int i; for (;;) { id = retu_read_reg(RETU_REG_IDR); im = ~retu_read_reg(RETU_REG_IMR); id &= im; if (!id) break; for (i = 0; id != 0; i++, id >>= 1) { if (!(id & 1)) continue; hnd = &retu_irq_handlers[i]; if (hnd->func == NULL) { /* Spurious retu interrupt - disable and ack it */ printk(KERN_INFO "Spurious Retu interrupt " "(id %d)\n", i); retu_disable_irq(i); retu_ack_irq(i); continue; } hnd->func(hnd->arg); /* * Don't acknowledge the interrupt here * It must be done explicitly */ } } } /* * Register the handler for a given RETU interrupt source. */ int retu_request_irq(int id, void *irq_handler, unsigned long arg, char *name) { struct retu_irq_handler_desc *hnd; if (irq_handler == NULL || id >= MAX_RETU_IRQ_HANDLERS || name == NULL) { printk(KERN_ERR PFX "Invalid arguments to %s\n", __FUNCTION__); return -EINVAL; } hnd = &retu_irq_handlers[id]; if (hnd->func != NULL) { printk(KERN_ERR PFX "IRQ %d already reserved\n", id); return -EBUSY; } printk(KERN_INFO PFX "Registering interrupt %d for device %s\n", id, name); hnd->func = irq_handler; hnd->arg = arg; strlcpy(hnd->name, name, sizeof(hnd->name)); retu_ack_irq(id); retu_enable_irq(id); return 0; } /* * Unregister the handler for a given RETU interrupt source. */ void retu_free_irq(int id) { struct retu_irq_handler_desc *hnd; if (id >= MAX_RETU_IRQ_HANDLERS) { printk(KERN_ERR PFX "Invalid argument to %s\n", __FUNCTION__); return; } hnd = &retu_irq_handlers[id]; if (hnd->func == NULL) { printk(KERN_ERR PFX "IRQ %d already freed\n", id); return; } retu_disable_irq(id); hnd->func = NULL; } /** * retu_power_off - Shut down power to system * * This function puts the system in power off state */ static void retu_power_off(void) { /* Ignore power button state */ retu_write_reg(RETU_REG_CC1, retu_read_reg(RETU_REG_CC1) | 2); /* Expire watchdog immediately */ retu_write_reg(RETU_REG_WATCHDOG, 0); /* Wait for poweroff*/ for (;;); } /** * retu_probe - Probe for Retu ASIC * @dev: the Retu device * * Probe for the Retu ASIC and allocate memory * for its device-struct if found */ static int __devinit retu_probe(struct device *dev) { const struct omap_em_asic_bb5_config * em_asic_config; int rev, ret; /* Prepare tasklet */ tasklet_init(&retu_tasklet, retu_tasklet_handler, 0); em_asic_config = omap_get_config(OMAP_TAG_EM_ASIC_BB5, struct omap_em_asic_bb5_config); if (em_asic_config == NULL) { printk(KERN_ERR PFX "Unable to retrieve config data\n"); return -ENODATA; } retu_irq_pin = em_asic_config->retu_irq_gpio; if ((ret = omap_request_gpio(retu_irq_pin)) < 0) { printk(KERN_ERR PFX "Unable to reserve IRQ GPIO\n"); return ret; } /* Set the pin as input */ omap_set_gpio_direction(retu_irq_pin, 1); /* Rising edge triggers the IRQ */ set_irq_type(OMAP_GPIO_IRQ(retu_irq_pin), IRQT_RISING); retu_initialized = 1; rev = retu_read_reg(RETU_REG_ASICR) & 0xff; if (rev & (1 << 7)) retu_is_vilma = 1; printk(KERN_INFO "%s v%d.%d found\n", retu_is_vilma ? "Vilma" : "Retu", (rev >> 4) & 0x07, rev & 0x0f); /* Mask all RETU interrupts */ retu_write_reg(RETU_REG_IMR, 0xffff); ret = request_irq(OMAP_GPIO_IRQ(retu_irq_pin), retu_irq_handler, 0, "retu", 0); if (ret < 0) { printk(KERN_ERR PFX "Unable to register IRQ handler\n"); omap_free_gpio(retu_irq_pin); return ret; } set_irq_wake(OMAP_GPIO_IRQ(retu_irq_pin), 1); /* Register power off function */ pm_power_off = retu_power_off; #ifdef CONFIG_CBUS_RETU_USER /* Initialize user-space interface */ if (retu_user_init() < 0) { printk(KERN_ERR "Unable to initialize driver\n"); free_irq(OMAP_GPIO_IRQ(retu_irq_pin), 0); omap_free_gpio(retu_irq_pin); return ret; } #endif return 0; } static int retu_remove(struct device *dev) { #ifdef CONFIG_CBUS_RETU_USER retu_user_cleanup(); #endif /* Mask all RETU interrupts */ retu_write_reg(RETU_REG_IMR, 0xffff); free_irq(OMAP_GPIO_IRQ(retu_irq_pin), 0); omap_free_gpio(retu_irq_pin); tasklet_kill(&retu_tasklet); return 0; } static void retu_device_release(struct device *dev) { complete(&device_release); } static struct device_driver retu_driver = { .name = "retu", .bus = &platform_bus_type, .probe = retu_probe, .remove = retu_remove, }; static struct platform_device retu_device = { .name = "retu", .id = -1, .dev = { .release = retu_device_release, } }; /** * retu_init - initialise Retu driver * * Initialise the Retu driver and return 0 if everything worked ok */ static int __init retu_init(void) { int ret = 0; printk(KERN_INFO "Retu/Vilma driver initialising\n"); init_completion(&device_release); if ((ret = driver_register(&retu_driver)) < 0) return ret; if ((ret = platform_device_register(&retu_device)) < 0) { driver_unregister(&retu_driver); return ret; } return 0; } /* * Cleanup */ static void __exit retu_exit(void) { platform_device_unregister(&retu_device); driver_unregister(&retu_driver); wait_for_completion(&device_release); } EXPORT_SYMBOL(retu_request_irq); EXPORT_SYMBOL(retu_free_irq); EXPORT_SYMBOL(retu_enable_irq); EXPORT_SYMBOL(retu_disable_irq); EXPORT_SYMBOL(retu_ack_irq); EXPORT_SYMBOL(retu_read_reg); EXPORT_SYMBOL(retu_write_reg); subsys_initcall(retu_init); module_exit(retu_exit); MODULE_DESCRIPTION("Retu ASIC control"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Juha Yrjölä, David Weinehall, and Mikko Ylinen");