diff options
Diffstat (limited to 'drivers/mfd/cpcap-irq.c')
| -rw-r--r-- | drivers/mfd/cpcap-irq.c | 809 |
1 files changed, 809 insertions, 0 deletions
diff --git a/drivers/mfd/cpcap-irq.c b/drivers/mfd/cpcap-irq.c new file mode 100644 index 00000000000..8d253ea2c56 --- /dev/null +++ b/drivers/mfd/cpcap-irq.c @@ -0,0 +1,809 @@ +/* + * Copyright (C) 2009 - 2010, Motorola, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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/gpio.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/wakelock.h> + +#include <linux/spi/cpcap.h> +#include <linux/spi/spi.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#ifdef CONFIG_PM_DEEPSLEEP +#include <linux/suspend.h> +#endif + +#define LONG_KEYPRESS_DURATION 4 /* in seconds */ + +#define NUM_INT_REGS 5 +#define NUM_INTS_PER_REG 16 + +#define CPCAP_INT1_VALID_BITS 0xFFFB +#define CPCAP_INT2_VALID_BITS 0xFFFF +#define CPCAP_INT3_VALID_BITS 0xFFFF +#define CPCAP_INT4_VALID_BITS 0x03FF +#define CPCAP_INT5_VALID_BITS 0xFFFF + +struct cpcap_event_handler { + void (*func)(enum cpcap_irqs, void *); + void *data; +}; + +struct cpcap_irq_info { + uint8_t registered; + uint8_t enabled; + uint32_t count; +}; + +struct cpcap_irqdata { + struct mutex lock; + struct work_struct work; + struct workqueue_struct *workqueue; + struct cpcap_device *cpcap; + struct cpcap_event_handler event_handler[CPCAP_IRQ__NUM]; + struct cpcap_irq_info irq_info[CPCAP_IRQ__NUM]; + struct wake_lock wake_lock; +}; + +#define EVENT_MASK(event) (1 << ((event) % NUM_INTS_PER_REG)) + +enum pwrkey_states { + PWRKEY_RELEASE, /* Power key released state. */ + PWRKEY_PRESS, /* Power key pressed state. */ + PWRKEY_UNKNOWN, /* Unknown power key state. */ +}; + +static irqreturn_t event_isr(int irq, void *data) +{ + struct cpcap_irqdata *irq_data = data; + disable_irq_nosync(irq); + wake_lock(&irq_data->wake_lock); + queue_work(irq_data->workqueue, &irq_data->work); + + return IRQ_HANDLED; +} + +static unsigned short get_int_reg(enum cpcap_irqs event) +{ + unsigned short ret; + + if ((event) >= CPCAP_IRQ_INT5_INDEX) + ret = CPCAP_REG_MI1; + else if ((event) >= CPCAP_IRQ_INT4_INDEX) + ret = CPCAP_REG_INT4; + else if ((event) >= CPCAP_IRQ_INT3_INDEX) + ret = CPCAP_REG_INT3; + else if ((event) >= CPCAP_IRQ_INT2_INDEX) + ret = CPCAP_REG_INT2; + else + ret = CPCAP_REG_INT1; + + return ret; +} + +static unsigned short get_mask_reg(enum cpcap_irqs event) +{ + unsigned short ret; + + if (event >= CPCAP_IRQ_INT5_INDEX) + ret = CPCAP_REG_MIM1; + else if (event >= CPCAP_IRQ_INT4_INDEX) + ret = CPCAP_REG_INTM4; + else if (event >= CPCAP_IRQ_INT3_INDEX) + ret = CPCAP_REG_INTM3; + else if (event >= CPCAP_IRQ_INT2_INDEX) + ret = CPCAP_REG_INTM2; + else + ret = CPCAP_REG_INTM1; + + return ret; +} + +static unsigned short get_sense_reg(enum cpcap_irqs event) +{ + unsigned short ret; + + if (event >= CPCAP_IRQ_INT5_INDEX) + ret = CPCAP_REG_MI2; + else if (event >= CPCAP_IRQ_INT4_INDEX) + ret = CPCAP_REG_INTS4; + else if (event >= CPCAP_IRQ_INT3_INDEX) + ret = CPCAP_REG_INTS3; + else if (event >= CPCAP_IRQ_INT2_INDEX) + ret = CPCAP_REG_INTS2; + else + ret = CPCAP_REG_INTS1; + + return ret; +} + +void cpcap_irq_mask_all(struct cpcap_device *cpcap) +{ + int i; + + static const struct { + unsigned short mask_reg; + unsigned short valid; + } int_reg[NUM_INT_REGS] = { + {CPCAP_REG_INTM1, CPCAP_INT1_VALID_BITS}, + {CPCAP_REG_INTM2, CPCAP_INT2_VALID_BITS}, + {CPCAP_REG_INTM3, CPCAP_INT3_VALID_BITS}, + {CPCAP_REG_INTM4, CPCAP_INT4_VALID_BITS}, + {CPCAP_REG_MIM1, CPCAP_INT5_VALID_BITS} + }; + + for (i = 0; i < NUM_INT_REGS; i++) { + cpcap_regacc_write(cpcap, int_reg[i].mask_reg, + int_reg[i].valid, + int_reg[i].valid); + } +} + +struct pwrkey_data { + struct cpcap_device *cpcap; + enum pwrkey_states state; + struct wake_lock wake_lock; + struct delayed_work pwrkey_work; + int power_double_pressed; +#ifdef CONFIG_PM_DEEPSLEEP + struct hrtimer longPress_timer; + int expired; +#endif + +}; + +#ifdef CONFIG_PM_DBG_DRV +static struct cpcap_irq_pm_dbg { + unsigned short en_ints[NUM_INT_REGS]; + unsigned char suspend; + unsigned char wakeup; +} pm_dbg_info; +#endif /* CONFIG_PM_DBG_DRV */ + +#ifdef CONFIG_PM_DEEPSLEEP + +static enum hrtimer_restart longPress_timer_callback(struct hrtimer *timer) +{ + struct pwrkey_data *pwrkey_data = + container_of(timer, struct pwrkey_data, longPress_timer); + struct cpcap_device *cpcap = pwrkey_data->cpcap; + enum pwrkey_states new_state = PWRKEY_PRESS; + + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + + /* long timer expired without being cancelled so send long press + keydown event */ + pwrkey_data->expired = 1; + cpcap_broadcast_key_event(cpcap, KEY_SENDFILE, new_state); + pwrkey_data->state = new_state; + + return HRTIMER_NORESTART; + +} +#endif + +static void pwrkey_work_func(struct work_struct *work) +{ + struct pwrkey_data *pwrkey_data = + container_of(work, struct pwrkey_data, pwrkey_work.work); + struct cpcap_device *cpcap = pwrkey_data->cpcap; + + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + if (pwrkey_data->state == PWRKEY_RELEASE) { + /* keyup was detected before keydown was sent, so send + keydown first */ + cpcap_broadcast_key_event(cpcap, KEY_END, PWRKEY_PRESS); + } + + /* Send detected state (keyup/keydown) */ + cpcap_broadcast_key_event(cpcap, KEY_END, pwrkey_data->state); +} + +static void pwrkey_handler(enum cpcap_irqs irq, void *data) +{ + struct pwrkey_data *pwrkey_data = data; + enum pwrkey_states new_state, last_state = pwrkey_data->state; + struct cpcap_device *cpcap = pwrkey_data->cpcap; + + new_state = (enum pwrkey_states) cpcap_irq_sense(cpcap, irq, 0); + + /* First do long keypress detection */ + if (new_state == PWRKEY_RELEASE) { +#ifdef CONFIG_PM_DEEPSLEEP + /* Got a keyup so cancel 2 second timer */ + hrtimer_cancel(&pwrkey_data->longPress_timer); + /* If longpress keydown was previously sent, then send the + long press keyup */ + if (pwrkey_data->expired == 1) { + pwrkey_data->expired = 0; +#endif + cpcap_broadcast_key_event(cpcap, + KEY_SENDFILE, new_state); + pwrkey_data->state = new_state; +#ifdef CONFIG_PM_DEEPSLEEP + } +#endif + } else if (new_state == PWRKEY_PRESS) { +#ifdef CONFIG_PM_DEEPSLEEP + /* Got a keydown so start long keypress timer */ + pwrkey_data->expired = 0; + hrtimer_start(&pwrkey_data->longPress_timer, + ktime_set(LONG_KEYPRESS_DURATION, 0), + HRTIMER_MODE_REL); +#endif + wake_lock_timeout(&pwrkey_data->wake_lock, + (LONG_KEYPRESS_DURATION*HZ)+5); + } + + /* Now do normal powerkey detection (in addition to long press) */ + if ((new_state < PWRKEY_UNKNOWN) && (new_state != last_state)) { + if (new_state == PWRKEY_PRESS) { + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + if (delayed_work_pending(&pwrkey_data->pwrkey_work)) { + /* If 600ms delayed work exists and we got a + keydown, then a doublepress has occured */ + cancel_delayed_work_sync(&pwrkey_data-> \ + pwrkey_work); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + cpcap_broadcast_key_event(cpcap, + KEY_POWER_DOUBLE, new_state); + pwrkey_data->power_double_pressed = 1; + } else { + /* If no delayed work was pending and we got a + keydown, then start 600ms delayed work */ + wake_lock(&pwrkey_data->wake_lock); + schedule_delayed_work(&pwrkey_data->pwrkey_work, + msecs_to_jiffies(600)); + } + } else { + /* Got a keyup. If we previously sent a doublepress + keydown, then send a doublepress keyup now */ + if (pwrkey_data->power_double_pressed) { + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + cpcap_broadcast_key_event(cpcap, + KEY_POWER_DOUBLE, new_state); + pwrkey_data->power_double_pressed = 0; + /* If the 600ms delayed work is done and we got a keyup, + then send the keyup now */ + } else if (!delayed_work_pending(&pwrkey_data-> \ + pwrkey_work)) { + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + cpcap_broadcast_key_event(cpcap, KEY_END, + new_state); + } + /* If we got a keyup while 600ms delayed work is still + pending, then do nothing now and let the delayed + work handler handle this */ + } + pwrkey_data->state = new_state; + } + cpcap_irq_unmask(cpcap, CPCAP_IRQ_ON); +} + +static int pwrkey_init(struct cpcap_device *cpcap) +{ + struct pwrkey_data *data = kmalloc(sizeof(struct pwrkey_data), + GFP_KERNEL); + int retval; + + if (!data) + return -ENOMEM; + data->cpcap = cpcap; + data->state = PWRKEY_RELEASE; + data->power_double_pressed = 0; + retval = cpcap_irq_register(cpcap, CPCAP_IRQ_ON, pwrkey_handler, data); + if (retval) + kfree(data); + wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "pwrkey"); +#ifdef CONFIG_PM_DEEPSLEEP + + hrtimer_init(&(data->longPress_timer), + CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + + (data->longPress_timer).function = longPress_timer_callback; +#endif + INIT_DELAYED_WORK(&data->pwrkey_work, pwrkey_work_func); + + return retval; +} + +static void pwrkey_remove(struct cpcap_device *cpcap) +{ + struct pwrkey_data *data; + + cpcap_irq_get_data(cpcap, CPCAP_IRQ_ON, (void **)&data); + if (!data) + return; + cancel_delayed_work_sync(&data->pwrkey_work); + cpcap_irq_free(cpcap, CPCAP_IRQ_ON); + wake_lock_destroy(&data->wake_lock); + kfree(data); +} + +static int int_read_and_clear(struct cpcap_device *cpcap, + unsigned short status_reg, + unsigned short mask_reg, + unsigned short valid_mask, + unsigned short *en) +{ + unsigned short ireg_val, mreg_val; + int ret; + ret = cpcap_regacc_read(cpcap, status_reg, &ireg_val); + if (ret) + return ret; + ret = cpcap_regacc_read(cpcap, mask_reg, &mreg_val); + if (ret) + return ret; + *en |= ireg_val & ~mreg_val; + *en &= valid_mask; + ret = cpcap_regacc_write(cpcap, mask_reg, *en, *en); + if (ret) + return ret; + ret = cpcap_regacc_write(cpcap, status_reg, *en, *en); + if (ret) + return ret; + return 0; +} + + +static void irq_work_func(struct work_struct *work) +{ + int retval = 0; + unsigned short en_ints[NUM_INT_REGS]; + int i; + struct cpcap_irqdata *data; + struct cpcap_device *cpcap; + struct spi_device *spi; + struct cpcap_platform_data *pdata; + unsigned int irq_gpio; + + static const struct { + unsigned short status_reg; + unsigned short mask_reg; + unsigned short valid; + } int_reg[NUM_INT_REGS] = { + {CPCAP_REG_INT1, CPCAP_REG_INTM1, CPCAP_INT1_VALID_BITS}, + {CPCAP_REG_INT2, CPCAP_REG_INTM2, CPCAP_INT2_VALID_BITS}, + {CPCAP_REG_INT3, CPCAP_REG_INTM3, CPCAP_INT3_VALID_BITS}, + {CPCAP_REG_INT4, CPCAP_REG_INTM4, CPCAP_INT4_VALID_BITS}, + {CPCAP_REG_MI1, CPCAP_REG_MIM1, CPCAP_INT5_VALID_BITS} + }; + + for (i = 0; i < NUM_INT_REGS; ++i) + en_ints[i] = 0; + + data = container_of(work, struct cpcap_irqdata, work); + cpcap = data->cpcap; + spi = cpcap->spi; + pdata = (struct cpcap_platform_data *) spi->controller_data; + irq_gpio = pdata->irq_gpio; + + while (gpio_get_value(irq_gpio)) { + for (i = 0; i < NUM_INT_REGS; ++i) { + retval = int_read_and_clear(cpcap, + int_reg[i].status_reg, + int_reg[i].mask_reg, + int_reg[i].valid, + &en_ints[i]); + if (retval < 0) { + dev_err(&cpcap->spi->dev, + "Error reading interrupts\n"); + break; + } + } + } + enable_irq(spi->irq); + +#ifdef CONFIG_PM_DBG_DRV + if ((pm_dbg_info.suspend != 0) && (pm_dbg_info.wakeup == 0)) { + for (i = 0; i < NUM_INT_REGS; ++i) + pm_dbg_info.en_ints[i] = en_ints[i]; + pm_dbg_info.wakeup = 1; + } +#endif /* CONFIG_PM_DBG_DRV */ + + /* lock protects event handlers and data */ + mutex_lock(&data->lock); + for (i = 0; i < NUM_INT_REGS; ++i) { + unsigned char index; + + while (en_ints[i] > 0) { + struct cpcap_event_handler *event_handler; + + /* find the first set bit */ + index = (unsigned char)(ffs(en_ints[i]) - 1); + if (index >= CPCAP_IRQ__NUM) + goto error; + /* clear the bit */ + en_ints[i] &= ~(1 << index); + /* find the event that occurred */ + index += CPCAP_IRQ__START + (i * NUM_INTS_PER_REG); + if (index >= CPCAP_IRQ__NUM) + goto error; + event_handler = &data->event_handler[index]; + + if (event_handler->func) + event_handler->func(index, event_handler->data); + + data->irq_info[index].count++; + + } + } +error: + mutex_unlock(&data->lock); + wake_unlock(&data->wake_lock); +} + +#ifdef CONFIG_DEBUG_FS +static int cpcap_dbg_irq_show(struct seq_file *s, void *data) +{ + static const char *irq_name[] = { + [CPCAP_IRQ_HSCLK] = "HSCLK", + [CPCAP_IRQ_PRIMAC] = "PRIMAC", + [CPCAP_IRQ_SECMAC] = "SECMAC", + [CPCAP_IRQ_LOWBPL] = "LOWBPL", + [CPCAP_IRQ_SEC2PRI] = "SEC2PRI", + [CPCAP_IRQ_LOWBPH] = "LOWBPH", + [CPCAP_IRQ_EOL] = "EOL", + [CPCAP_IRQ_TS] = "TS", + [CPCAP_IRQ_ADCDONE] = "ADCDONE", + [CPCAP_IRQ_HS] = "HS", + [CPCAP_IRQ_MB2] = "MB2", + [CPCAP_IRQ_VBUSOV] = "VBUSOV", + [CPCAP_IRQ_RVRS_CHRG] = "RVRS_CHRG", + [CPCAP_IRQ_CHRG_DET] = "CHRG_DET", + [CPCAP_IRQ_IDFLOAT] = "IDFLOAT", + [CPCAP_IRQ_IDGND] = "IDGND", + + [CPCAP_IRQ_SE1] = "SE1", + [CPCAP_IRQ_SESSEND] = "SESSEND", + [CPCAP_IRQ_SESSVLD] = "SESSVLD", + [CPCAP_IRQ_VBUSVLD] = "VBUSVLD", + [CPCAP_IRQ_CHRG_CURR1] = "CHRG_CURR1", + [CPCAP_IRQ_CHRG_CURR2] = "CHRG_CURR2", + [CPCAP_IRQ_RVRS_MODE] = "RVRS_MODE", + [CPCAP_IRQ_ON] = "ON", + [CPCAP_IRQ_ON2] = "ON2", + [CPCAP_IRQ_CLK] = "CLK", + [CPCAP_IRQ_1HZ] = "1HZ", + [CPCAP_IRQ_PTT] = "PTT", + [CPCAP_IRQ_SE0CONN] = "SE0CONN", + [CPCAP_IRQ_CHRG_SE1B] = "CHRG_SE1B", + [CPCAP_IRQ_UART_ECHO_OVERRUN] = "UART_ECHO_OVERRUN", + [CPCAP_IRQ_EXTMEMHD] = "EXTMEMHD", + + [CPCAP_IRQ_WARM] = "WARM", + [CPCAP_IRQ_SYSRSTR] = "SYSRSTR", + [CPCAP_IRQ_SOFTRST] = "SOFTRST", + [CPCAP_IRQ_DIEPWRDWN] = "DIEPWRDWN", + [CPCAP_IRQ_DIETEMPH] = "DIETEMPH", + [CPCAP_IRQ_PC] = "PC", + [CPCAP_IRQ_OFLOWSW] = "OFLOWSW", + [CPCAP_IRQ_TODA] = "TODA", + [CPCAP_IRQ_OPT_SEL_DTCH] = "OPT_SEL_DTCH", + [CPCAP_IRQ_OPT_SEL_STATE] = "OPT_SEL_STATE", + [CPCAP_IRQ_ONEWIRE1] = "ONEWIRE1", + [CPCAP_IRQ_ONEWIRE2] = "ONEWIRE2", + [CPCAP_IRQ_ONEWIRE3] = "ONEWIRE3", + [CPCAP_IRQ_UCRESET] = "UCRESET", + [CPCAP_IRQ_PWRGOOD] = "PWRGOOD", + [CPCAP_IRQ_USBDPLLCLK] = "USBDPLLCLK", + + [CPCAP_IRQ_DPI] = "DPI", + [CPCAP_IRQ_DMI] = "DMI", + [CPCAP_IRQ_UCBUSY] = "UCBUSY", + [CPCAP_IRQ_GCAI_CURR1] = "GCAI_CURR1", + [CPCAP_IRQ_GCAI_CURR2] = "GCAI_CURR2", + [CPCAP_IRQ_SB_MAX_RETRANSMIT_ERR] = "SB_MAX_RETRANSMIT_ERR", + [CPCAP_IRQ_BATTDETB] = "BATTDETB", + [CPCAP_IRQ_PRIHALT] = "PRIHALT", + [CPCAP_IRQ_SECHALT] = "SECHALT", + [CPCAP_IRQ_CC_CAL] = "CC_CAL", + + [CPCAP_IRQ_UC_PRIROMR] = "UC_PRIROMR", + [CPCAP_IRQ_UC_PRIRAMW] = "UC_PRIRAMW", + [CPCAP_IRQ_UC_PRIRAMR] = "UC_PRIRAMR", + [CPCAP_IRQ_UC_USEROFF] = "UC_USEROFF", + [CPCAP_IRQ_UC_PRIMACRO_4] = "UC_PRIMACRO_4", + [CPCAP_IRQ_UC_PRIMACRO_5] = "UC_PRIMACRO_5", + [CPCAP_IRQ_UC_PRIMACRO_6] = "UC_PRIMACRO_6", + [CPCAP_IRQ_UC_PRIMACRO_7] = "UC_PRIMACRO_7", + [CPCAP_IRQ_UC_PRIMACRO_8] = "UC_PRIMACRO_8", + [CPCAP_IRQ_UC_PRIMACRO_9] = "UC_PRIMACRO_9", + [CPCAP_IRQ_UC_PRIMACRO_10] = "UC_PRIMACRO_10", + [CPCAP_IRQ_UC_PRIMACRO_11] = "UC_PRIMACRO_11", + [CPCAP_IRQ_UC_PRIMACRO_12] = "UC_PRIMACRO_12", + [CPCAP_IRQ_UC_PRIMACRO_13] = "UC_PRIMACRO_13", + [CPCAP_IRQ_UC_PRIMACRO_14] = "UC_PRIMACRO_14", + [CPCAP_IRQ_UC_PRIMACRO_15] = "UC_PRIMACRO_15", + }; + unsigned int i; + struct cpcap_irqdata *irqdata = s->private; + + seq_printf(s, "%21s%9s%12s%10s\n", + "CPCAP IRQ", "Enabled", "Registered", "Count"); + + for (i = 0; i < CPCAP_IRQ__NUM; i++) { + if ((i <= CPCAP_IRQ_CC_CAL) || (i >= CPCAP_IRQ_UC_PRIROMR)) { + seq_printf(s, "%21s%9d%12d%10d\n", + irq_name[i], + irqdata->irq_info[i].enabled, + irqdata->irq_info[i].registered, + irqdata->irq_info[i].count); + } + } + return 0; +} + +static int cpcap_dbg_irq_open(struct inode *inode, struct file *file) +{ + return single_open(file, cpcap_dbg_irq_show, inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = cpcap_dbg_irq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +int cpcap_irq_init(struct cpcap_device *cpcap) +{ + int retval; + struct spi_device *spi = cpcap->spi; + struct cpcap_irqdata *data; + + data = kzalloc(sizeof(struct cpcap_irqdata), GFP_KERNEL); + if (!data) + return -ENOMEM; + + cpcap_irq_mask_all(cpcap); + + data->workqueue = create_workqueue("cpcap_irq"); + INIT_WORK(&data->work, irq_work_func); + mutex_init(&data->lock); + wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "cpcap-irq"); + data->cpcap = cpcap; + + retval = request_irq(spi->irq, event_isr, IRQF_DISABLED | + IRQF_TRIGGER_RISING, "cpcap-irq", data); + if (retval) { + printk(KERN_ERR "cpcap_irq: Failed requesting irq.\n"); + goto error; + } + + enable_irq_wake(spi->irq); + + cpcap->irqdata = data; + retval = pwrkey_init(cpcap); + if (retval) { + printk(KERN_ERR "cpcap_irq: Failed initializing pwrkey.\n"); + goto error; + } +#ifdef CONFIG_DEBUG_FS + (void)debugfs_create_file("cpcap-irq", S_IRUGO, NULL, data, + &debug_fops); +#endif + return 0; + +error: + free_irq(spi->irq, data); + kfree(data); + printk(KERN_ERR "cpcap_irq: Error registering cpcap irq.\n"); + return retval; +} + +void cpcap_irq_shutdown(struct cpcap_device *cpcap) +{ + struct spi_device *spi = cpcap->spi; + struct cpcap_irqdata *data = cpcap->irqdata; + + pwrkey_remove(cpcap); + cancel_work_sync(&data->work); + destroy_workqueue(data->workqueue); + free_irq(spi->irq, data); + kfree(data); +} + +int cpcap_irq_register(struct cpcap_device *cpcap, + enum cpcap_irqs irq, + void (*cb_func) (enum cpcap_irqs, void *), + void *data) +{ + struct cpcap_irqdata *irqdata = cpcap->irqdata; + int retval = 0; + + if ((irq >= CPCAP_IRQ__NUM) || (!cb_func)) + return -EINVAL; + + mutex_lock(&irqdata->lock); + + if (irqdata->event_handler[irq].func == NULL) { + irqdata->irq_info[irq].registered = 1; + cpcap_irq_unmask(cpcap, irq); + irqdata->event_handler[irq].func = cb_func; + irqdata->event_handler[irq].data = data; + } else + retval = -EPERM; + + mutex_unlock(&irqdata->lock); + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_register); + +int cpcap_irq_free(struct cpcap_device *cpcap, enum cpcap_irqs irq) +{ + struct cpcap_irqdata *data = cpcap->irqdata; + int retval; + + if (irq >= CPCAP_IRQ__NUM) + return -EINVAL; + + mutex_lock(&data->lock); + retval = cpcap_irq_mask(cpcap, irq); + data->event_handler[irq].func = NULL; + data->event_handler[irq].data = NULL; + data->irq_info[irq].registered = 0; + mutex_unlock(&data->lock); + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_free); + +int cpcap_irq_get_data(struct cpcap_device *cpcap, + enum cpcap_irqs irq, + void **data) +{ + struct cpcap_irqdata *irqdata = cpcap->irqdata; + + if (irq >= CPCAP_IRQ__NUM) + return -EINVAL; + + mutex_lock(&irqdata->lock); + *data = irqdata->event_handler[irq].data; + mutex_unlock(&irqdata->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(cpcap_irq_get_data); + +int cpcap_irq_clear(struct cpcap_device *cpcap, + enum cpcap_irqs irq) +{ + int retval = -EINVAL; + + if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) { + retval = cpcap_regacc_write(cpcap, + get_int_reg(irq), + EVENT_MASK(irq), + EVENT_MASK(irq)); + } + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_clear); + +int cpcap_irq_mask(struct cpcap_device *cpcap, + enum cpcap_irqs irq) +{ + struct cpcap_irqdata *data = cpcap->irqdata; + int retval = -EINVAL; + + if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) { + data->irq_info[irq].enabled = 0; + retval = cpcap_regacc_write(cpcap, + get_mask_reg(irq), + EVENT_MASK(irq), + EVENT_MASK(irq)); + } + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_mask); + +int cpcap_irq_unmask(struct cpcap_device *cpcap, + enum cpcap_irqs irq) +{ + struct cpcap_irqdata *data = cpcap->irqdata; + int retval = -EINVAL; + + if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) { + data->irq_info[irq].enabled = 1; + retval = cpcap_regacc_write(cpcap, + get_mask_reg(irq), + 0, + EVENT_MASK(irq)); + } + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_unmask); + +int cpcap_irq_mask_get(struct cpcap_device *cpcap, + enum cpcap_irqs irq) +{ + struct cpcap_irqdata *data = cpcap->irqdata; + int retval = -EINVAL; + + if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) + return data->irq_info[irq].enabled; + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_mask_get); + +int cpcap_irq_sense(struct cpcap_device *cpcap, + enum cpcap_irqs irq, + unsigned char clear) +{ + unsigned short val; + int retval; + + if (irq >= CPCAP_IRQ__NUM) + return -EINVAL; + + retval = cpcap_regacc_read(cpcap, get_sense_reg(irq), &val); + if (retval) + return retval; + + if (clear) + retval = cpcap_irq_clear(cpcap, irq); + if (retval) + return retval; + + return ((val & EVENT_MASK(irq)) != 0) ? 1 : 0; +} +EXPORT_SYMBOL_GPL(cpcap_irq_sense); + +#ifdef CONFIG_PM_DBG_DRV +void cpcap_irq_pm_dbg_suspend(void) +{ + pm_dbg_info.suspend = 1; + pm_dbg_info.wakeup = 0; +} + +void cpcap_irq_pm_dbg_resume(void) +{ + pm_dbg_info.suspend = 0; + if (pm_dbg_info.wakeup != 0) { + printk(KERN_INFO "PM_DBG WAKEUP CPCAP IRQ = 0x%x.0x%x.0%x.0x%x.0x%x\n", + pm_dbg_info.en_ints[0], + pm_dbg_info.en_ints[1], + pm_dbg_info.en_ints[2], + pm_dbg_info.en_ints[3], + pm_dbg_info.en_ints[4]); + } +} +#endif /* CONFIG_PM_DBG_DRV */ |