| /* |
| * 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 */ |