blob: 8d253ea2c563257c56c90f2caa6e2949f1c3b066 [file] [log] [blame]
/*
* 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 */