2009-09-18 13:39:38 +08:00
|
|
|
/*
|
|
|
|
* File: drivers/input/keyboard/adp5588_keys.c
|
2010-01-19 16:28:44 +08:00
|
|
|
* Description: keypad driver for ADP5588 and ADP5587
|
|
|
|
* I2C QWERTY Keypad and IO Expander
|
2009-09-18 13:39:38 +08:00
|
|
|
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
|
|
|
*
|
2010-11-03 02:33:05 +08:00
|
|
|
* Copyright (C) 2008-2010 Analog Devices Inc.
|
2009-09-18 13:39:38 +08:00
|
|
|
* Licensed under the GPL-2 or later.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/irq.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/pm.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <linux/i2c.h>
|
2010-07-26 16:01:11 +08:00
|
|
|
#include <linux/gpio.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2009-09-18 13:39:38 +08:00
|
|
|
|
|
|
|
#include <linux/i2c/adp5588.h>
|
|
|
|
|
|
|
|
/* Key Event Register xy */
|
|
|
|
#define KEY_EV_PRESSED (1 << 7)
|
|
|
|
#define KEY_EV_MASK (0x7F)
|
|
|
|
|
|
|
|
#define KP_SEL(x) (0xFFFF >> (16 - x)) /* 2^x-1 */
|
|
|
|
|
|
|
|
#define KEYP_MAX_EVENT 10
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Early pre 4.0 Silicon required to delay readout by at least 25ms,
|
|
|
|
* since the Event Counter Register updated 25ms after the interrupt
|
|
|
|
* asserted.
|
|
|
|
*/
|
|
|
|
#define WA_DELAYED_READOUT_REVID(rev) ((rev) < 4)
|
|
|
|
|
|
|
|
struct adp5588_kpad {
|
|
|
|
struct i2c_client *client;
|
|
|
|
struct input_dev *input;
|
|
|
|
struct delayed_work work;
|
|
|
|
unsigned long delay;
|
|
|
|
unsigned short keycode[ADP5588_KEYMAPSIZE];
|
2010-06-25 10:10:40 +08:00
|
|
|
const struct adp5588_gpi_map *gpimap;
|
|
|
|
unsigned short gpimapsize;
|
2010-07-26 16:01:11 +08:00
|
|
|
#ifdef CONFIG_GPIOLIB
|
2010-11-03 02:33:05 +08:00
|
|
|
unsigned char gpiomap[ADP5588_MAXGPIO];
|
2010-07-26 16:01:11 +08:00
|
|
|
bool export_gpio;
|
|
|
|
struct gpio_chip gc;
|
|
|
|
struct mutex gpio_lock; /* Protect cached dir, dat_out */
|
|
|
|
u8 dat_out[3];
|
|
|
|
u8 dir[3];
|
|
|
|
#endif
|
2009-09-18 13:39:38 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int adp5588_read(struct i2c_client *client, u8 reg)
|
|
|
|
{
|
|
|
|
int ret = i2c_smbus_read_byte_data(client, reg);
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(&client->dev, "Read Error\n");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5588_write(struct i2c_client *client, u8 reg, u8 val)
|
|
|
|
{
|
|
|
|
return i2c_smbus_write_byte_data(client, reg, val);
|
|
|
|
}
|
|
|
|
|
2010-07-26 16:01:11 +08:00
|
|
|
#ifdef CONFIG_GPIOLIB
|
|
|
|
static int adp5588_gpio_get_value(struct gpio_chip *chip, unsigned off)
|
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad = container_of(chip, struct adp5588_kpad, gc);
|
2010-11-03 02:33:05 +08:00
|
|
|
unsigned int bank = ADP5588_BANK(kpad->gpiomap[off]);
|
|
|
|
unsigned int bit = ADP5588_BIT(kpad->gpiomap[off]);
|
2010-07-26 16:01:11 +08:00
|
|
|
|
|
|
|
return !!(adp5588_read(kpad->client, GPIO_DAT_STAT1 + bank) & bit);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void adp5588_gpio_set_value(struct gpio_chip *chip,
|
|
|
|
unsigned off, int val)
|
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad = container_of(chip, struct adp5588_kpad, gc);
|
2010-11-03 02:33:05 +08:00
|
|
|
unsigned int bank = ADP5588_BANK(kpad->gpiomap[off]);
|
|
|
|
unsigned int bit = ADP5588_BIT(kpad->gpiomap[off]);
|
2010-07-26 16:01:11 +08:00
|
|
|
|
|
|
|
mutex_lock(&kpad->gpio_lock);
|
|
|
|
|
|
|
|
if (val)
|
|
|
|
kpad->dat_out[bank] |= bit;
|
|
|
|
else
|
|
|
|
kpad->dat_out[bank] &= ~bit;
|
|
|
|
|
|
|
|
adp5588_write(kpad->client, GPIO_DAT_OUT1 + bank,
|
|
|
|
kpad->dat_out[bank]);
|
|
|
|
|
|
|
|
mutex_unlock(&kpad->gpio_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5588_gpio_direction_input(struct gpio_chip *chip, unsigned off)
|
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad = container_of(chip, struct adp5588_kpad, gc);
|
2010-11-03 02:33:05 +08:00
|
|
|
unsigned int bank = ADP5588_BANK(kpad->gpiomap[off]);
|
|
|
|
unsigned int bit = ADP5588_BIT(kpad->gpiomap[off]);
|
2010-07-26 16:01:11 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
mutex_lock(&kpad->gpio_lock);
|
|
|
|
|
|
|
|
kpad->dir[bank] &= ~bit;
|
|
|
|
ret = adp5588_write(kpad->client, GPIO_DIR1 + bank, kpad->dir[bank]);
|
|
|
|
|
|
|
|
mutex_unlock(&kpad->gpio_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5588_gpio_direction_output(struct gpio_chip *chip,
|
|
|
|
unsigned off, int val)
|
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad = container_of(chip, struct adp5588_kpad, gc);
|
2010-11-03 02:33:05 +08:00
|
|
|
unsigned int bank = ADP5588_BANK(kpad->gpiomap[off]);
|
|
|
|
unsigned int bit = ADP5588_BIT(kpad->gpiomap[off]);
|
2010-07-26 16:01:11 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
mutex_lock(&kpad->gpio_lock);
|
|
|
|
|
|
|
|
kpad->dir[bank] |= bit;
|
|
|
|
|
|
|
|
if (val)
|
|
|
|
kpad->dat_out[bank] |= bit;
|
|
|
|
else
|
|
|
|
kpad->dat_out[bank] &= ~bit;
|
|
|
|
|
|
|
|
ret = adp5588_write(kpad->client, GPIO_DAT_OUT1 + bank,
|
|
|
|
kpad->dat_out[bank]);
|
|
|
|
ret |= adp5588_write(kpad->client, GPIO_DIR1 + bank,
|
|
|
|
kpad->dir[bank]);
|
|
|
|
|
|
|
|
mutex_unlock(&kpad->gpio_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-11-24 13:38:25 +08:00
|
|
|
static int adp5588_build_gpiomap(struct adp5588_kpad *kpad,
|
2010-08-03 09:33:26 +08:00
|
|
|
const struct adp5588_kpad_platform_data *pdata)
|
2010-07-26 16:01:11 +08:00
|
|
|
{
|
2010-11-03 02:33:05 +08:00
|
|
|
bool pin_used[ADP5588_MAXGPIO];
|
2010-08-03 09:33:26 +08:00
|
|
|
int n_unused = 0;
|
|
|
|
int i;
|
2010-07-26 16:01:11 +08:00
|
|
|
|
2010-08-03 09:33:26 +08:00
|
|
|
memset(pin_used, 0, sizeof(pin_used));
|
2010-07-26 16:01:11 +08:00
|
|
|
|
2010-08-03 09:33:26 +08:00
|
|
|
for (i = 0; i < pdata->rows; i++)
|
|
|
|
pin_used[i] = true;
|
2010-07-26 16:01:11 +08:00
|
|
|
|
2010-08-03 09:33:26 +08:00
|
|
|
for (i = 0; i < pdata->cols; i++)
|
|
|
|
pin_used[i + GPI_PIN_COL_BASE - GPI_PIN_BASE] = true;
|
2010-07-26 16:01:11 +08:00
|
|
|
|
2010-08-03 09:33:26 +08:00
|
|
|
for (i = 0; i < kpad->gpimapsize; i++)
|
|
|
|
pin_used[kpad->gpimap[i].pin - GPI_PIN_BASE] = true;
|
2010-07-26 16:01:11 +08:00
|
|
|
|
2010-11-03 02:33:05 +08:00
|
|
|
for (i = 0; i < ADP5588_MAXGPIO; i++)
|
2010-08-03 09:33:26 +08:00
|
|
|
if (!pin_used[i])
|
|
|
|
kpad->gpiomap[n_unused++] = i;
|
2010-07-26 16:01:11 +08:00
|
|
|
|
2010-08-03 09:33:26 +08:00
|
|
|
return n_unused;
|
|
|
|
}
|
2010-07-26 16:01:11 +08:00
|
|
|
|
2012-11-24 13:38:25 +08:00
|
|
|
static int adp5588_gpio_add(struct adp5588_kpad *kpad)
|
2010-08-03 09:33:26 +08:00
|
|
|
{
|
|
|
|
struct device *dev = &kpad->client->dev;
|
|
|
|
const struct adp5588_kpad_platform_data *pdata = dev->platform_data;
|
|
|
|
const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data;
|
|
|
|
int i, error;
|
|
|
|
|
|
|
|
if (!gpio_data)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
kpad->gc.ngpio = adp5588_build_gpiomap(kpad, pdata);
|
|
|
|
if (kpad->gc.ngpio == 0) {
|
2010-07-26 16:01:11 +08:00
|
|
|
dev_info(dev, "No unused gpios left to export\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-08-03 09:33:26 +08:00
|
|
|
kpad->export_gpio = true;
|
|
|
|
|
2010-07-26 16:01:11 +08:00
|
|
|
kpad->gc.direction_input = adp5588_gpio_direction_input;
|
|
|
|
kpad->gc.direction_output = adp5588_gpio_direction_output;
|
|
|
|
kpad->gc.get = adp5588_gpio_get_value;
|
|
|
|
kpad->gc.set = adp5588_gpio_set_value;
|
|
|
|
kpad->gc.can_sleep = 1;
|
|
|
|
|
|
|
|
kpad->gc.base = gpio_data->gpio_start;
|
|
|
|
kpad->gc.label = kpad->client->name;
|
|
|
|
kpad->gc.owner = THIS_MODULE;
|
2012-05-11 13:32:00 +08:00
|
|
|
kpad->gc.names = gpio_data->names;
|
2010-07-26 16:01:11 +08:00
|
|
|
|
|
|
|
mutex_init(&kpad->gpio_lock);
|
|
|
|
|
|
|
|
error = gpiochip_add(&kpad->gc);
|
|
|
|
if (error) {
|
|
|
|
dev_err(dev, "gpiochip_add failed, err: %d\n", error);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2010-11-03 02:33:05 +08:00
|
|
|
for (i = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) {
|
2010-07-26 16:01:11 +08:00
|
|
|
kpad->dat_out[i] = adp5588_read(kpad->client,
|
|
|
|
GPIO_DAT_OUT1 + i);
|
|
|
|
kpad->dir[i] = adp5588_read(kpad->client, GPIO_DIR1 + i);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gpio_data->setup) {
|
|
|
|
error = gpio_data->setup(kpad->client,
|
|
|
|
kpad->gc.base, kpad->gc.ngpio,
|
|
|
|
gpio_data->context);
|
|
|
|
if (error)
|
|
|
|
dev_warn(dev, "setup failed, %d\n", error);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-08-03 09:33:26 +08:00
|
|
|
static void __devexit adp5588_gpio_remove(struct adp5588_kpad *kpad)
|
2010-07-26 16:01:11 +08:00
|
|
|
{
|
2010-08-03 09:33:26 +08:00
|
|
|
struct device *dev = &kpad->client->dev;
|
2010-07-26 16:01:11 +08:00
|
|
|
const struct adp5588_kpad_platform_data *pdata = dev->platform_data;
|
|
|
|
const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
if (!kpad->export_gpio)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (gpio_data->teardown) {
|
|
|
|
error = gpio_data->teardown(kpad->client,
|
|
|
|
kpad->gc.base, kpad->gc.ngpio,
|
|
|
|
gpio_data->context);
|
|
|
|
if (error)
|
|
|
|
dev_warn(dev, "teardown failed %d\n", error);
|
|
|
|
}
|
|
|
|
|
|
|
|
error = gpiochip_remove(&kpad->gc);
|
|
|
|
if (error)
|
|
|
|
dev_warn(dev, "gpiochip_remove failed %d\n", error);
|
|
|
|
}
|
|
|
|
#else
|
2010-08-03 09:33:26 +08:00
|
|
|
static inline int adp5588_gpio_add(struct adp5588_kpad *kpad)
|
2010-07-26 16:01:11 +08:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-08-03 09:33:26 +08:00
|
|
|
static inline void adp5588_gpio_remove(struct adp5588_kpad *kpad)
|
2010-07-26 16:01:11 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2010-06-25 10:10:40 +08:00
|
|
|
static void adp5588_report_events(struct adp5588_kpad *kpad, int ev_cnt)
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
for (i = 0; i < ev_cnt; i++) {
|
|
|
|
int key = adp5588_read(kpad->client, Key_EVENTA + i);
|
|
|
|
int key_val = key & KEY_EV_MASK;
|
|
|
|
|
|
|
|
if (key_val >= GPI_PIN_BASE && key_val <= GPI_PIN_END) {
|
|
|
|
for (j = 0; j < kpad->gpimapsize; j++) {
|
|
|
|
if (key_val == kpad->gpimap[j].pin) {
|
|
|
|
input_report_switch(kpad->input,
|
|
|
|
kpad->gpimap[j].sw_evt,
|
|
|
|
key & KEY_EV_PRESSED);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
input_report_key(kpad->input,
|
|
|
|
kpad->keycode[key_val - 1],
|
|
|
|
key & KEY_EV_PRESSED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-18 13:39:38 +08:00
|
|
|
static void adp5588_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad = container_of(work,
|
|
|
|
struct adp5588_kpad, work.work);
|
|
|
|
struct i2c_client *client = kpad->client;
|
2010-06-25 10:10:40 +08:00
|
|
|
int status, ev_cnt;
|
2009-09-18 13:39:38 +08:00
|
|
|
|
|
|
|
status = adp5588_read(client, INT_STAT);
|
|
|
|
|
2010-11-03 02:33:05 +08:00
|
|
|
if (status & ADP5588_OVR_FLOW_INT) /* Unlikely and should never happen */
|
2009-09-18 13:39:38 +08:00
|
|
|
dev_err(&client->dev, "Event Overflow Error\n");
|
|
|
|
|
2010-11-03 02:33:05 +08:00
|
|
|
if (status & ADP5588_KE_INT) {
|
|
|
|
ev_cnt = adp5588_read(client, KEY_LCK_EC_STAT) & ADP5588_KEC;
|
2009-09-18 13:39:38 +08:00
|
|
|
if (ev_cnt) {
|
2010-06-25 10:10:40 +08:00
|
|
|
adp5588_report_events(kpad, ev_cnt);
|
2009-09-18 13:39:38 +08:00
|
|
|
input_sync(kpad->input);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
adp5588_write(client, INT_STAT, status); /* Status is W1C */
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t adp5588_irq(int irq, void *handle)
|
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad = handle;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* use keventd context to read the event fifo registers
|
|
|
|
* Schedule readout at least 25ms after notification for
|
|
|
|
* REVID < 4
|
|
|
|
*/
|
|
|
|
|
|
|
|
schedule_delayed_work(&kpad->work, kpad->delay);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2012-11-24 13:38:25 +08:00
|
|
|
static int adp5588_setup(struct i2c_client *client)
|
2009-09-18 13:39:38 +08:00
|
|
|
{
|
2010-07-26 16:01:11 +08:00
|
|
|
const struct adp5588_kpad_platform_data *pdata = client->dev.platform_data;
|
|
|
|
const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data;
|
2009-09-18 13:39:38 +08:00
|
|
|
int i, ret;
|
2010-06-25 10:10:40 +08:00
|
|
|
unsigned char evt_mode1 = 0, evt_mode2 = 0, evt_mode3 = 0;
|
2009-09-18 13:39:38 +08:00
|
|
|
|
|
|
|
ret = adp5588_write(client, KP_GPIO1, KP_SEL(pdata->rows));
|
|
|
|
ret |= adp5588_write(client, KP_GPIO2, KP_SEL(pdata->cols) & 0xFF);
|
|
|
|
ret |= adp5588_write(client, KP_GPIO3, KP_SEL(pdata->cols) >> 8);
|
|
|
|
|
|
|
|
if (pdata->en_keylock) {
|
|
|
|
ret |= adp5588_write(client, UNLOCK1, pdata->unlock_key1);
|
|
|
|
ret |= adp5588_write(client, UNLOCK2, pdata->unlock_key2);
|
2010-11-03 02:33:05 +08:00
|
|
|
ret |= adp5588_write(client, KEY_LCK_EC_STAT, ADP5588_K_LCK_EN);
|
2009-09-18 13:39:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < KEYP_MAX_EVENT; i++)
|
|
|
|
ret |= adp5588_read(client, Key_EVENTA);
|
|
|
|
|
2010-06-25 10:10:40 +08:00
|
|
|
for (i = 0; i < pdata->gpimapsize; i++) {
|
|
|
|
unsigned short pin = pdata->gpimap[i].pin;
|
|
|
|
|
|
|
|
if (pin <= GPI_PIN_ROW_END) {
|
|
|
|
evt_mode1 |= (1 << (pin - GPI_PIN_ROW_BASE));
|
|
|
|
} else {
|
|
|
|
evt_mode2 |= ((1 << (pin - GPI_PIN_COL_BASE)) & 0xFF);
|
|
|
|
evt_mode3 |= ((1 << (pin - GPI_PIN_COL_BASE)) >> 8);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pdata->gpimapsize) {
|
|
|
|
ret |= adp5588_write(client, GPI_EM1, evt_mode1);
|
|
|
|
ret |= adp5588_write(client, GPI_EM2, evt_mode2);
|
|
|
|
ret |= adp5588_write(client, GPI_EM3, evt_mode3);
|
|
|
|
}
|
|
|
|
|
2010-07-26 16:01:11 +08:00
|
|
|
if (gpio_data) {
|
2010-11-03 02:33:05 +08:00
|
|
|
for (i = 0; i <= ADP5588_BANK(ADP5588_MAXGPIO); i++) {
|
2010-07-26 16:01:11 +08:00
|
|
|
int pull_mask = gpio_data->pullup_dis_mask;
|
|
|
|
|
|
|
|
ret |= adp5588_write(client, GPIO_PULL1 + i,
|
|
|
|
(pull_mask >> (8 * i)) & 0xFF);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-03 02:33:05 +08:00
|
|
|
ret |= adp5588_write(client, INT_STAT,
|
|
|
|
ADP5588_CMP2_INT | ADP5588_CMP1_INT |
|
|
|
|
ADP5588_OVR_FLOW_INT | ADP5588_K_LCK_INT |
|
|
|
|
ADP5588_GPI_INT | ADP5588_KE_INT); /* Status is W1C */
|
2009-09-18 13:39:38 +08:00
|
|
|
|
2010-11-03 02:33:05 +08:00
|
|
|
ret |= adp5588_write(client, CFG, ADP5588_INT_CFG |
|
|
|
|
ADP5588_OVR_FLOW_IEN |
|
|
|
|
ADP5588_KE_IEN);
|
2009-09-18 13:39:38 +08:00
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(&client->dev, "Write Error\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-11-24 13:38:25 +08:00
|
|
|
static void adp5588_report_switch_state(struct adp5588_kpad *kpad)
|
2010-06-25 10:10:40 +08:00
|
|
|
{
|
|
|
|
int gpi_stat1 = adp5588_read(kpad->client, GPIO_DAT_STAT1);
|
|
|
|
int gpi_stat2 = adp5588_read(kpad->client, GPIO_DAT_STAT2);
|
|
|
|
int gpi_stat3 = adp5588_read(kpad->client, GPIO_DAT_STAT3);
|
|
|
|
int gpi_stat_tmp, pin_loc;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < kpad->gpimapsize; i++) {
|
|
|
|
unsigned short pin = kpad->gpimap[i].pin;
|
|
|
|
|
|
|
|
if (pin <= GPI_PIN_ROW_END) {
|
|
|
|
gpi_stat_tmp = gpi_stat1;
|
|
|
|
pin_loc = pin - GPI_PIN_ROW_BASE;
|
|
|
|
} else if ((pin - GPI_PIN_COL_BASE) < 8) {
|
|
|
|
gpi_stat_tmp = gpi_stat2;
|
|
|
|
pin_loc = pin - GPI_PIN_COL_BASE;
|
|
|
|
} else {
|
|
|
|
gpi_stat_tmp = gpi_stat3;
|
|
|
|
pin_loc = pin - GPI_PIN_COL_BASE - 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gpi_stat_tmp < 0) {
|
|
|
|
dev_err(&kpad->client->dev,
|
|
|
|
"Can't read GPIO_DAT_STAT switch %d default to OFF\n",
|
|
|
|
pin);
|
|
|
|
gpi_stat_tmp = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
input_report_switch(kpad->input,
|
|
|
|
kpad->gpimap[i].sw_evt,
|
|
|
|
!(gpi_stat_tmp & (1 << pin_loc)));
|
|
|
|
}
|
|
|
|
|
|
|
|
input_sync(kpad->input);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-11-24 13:38:25 +08:00
|
|
|
static int adp5588_probe(struct i2c_client *client,
|
|
|
|
const struct i2c_device_id *id)
|
2009-09-18 13:39:38 +08:00
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad;
|
2010-07-26 16:01:11 +08:00
|
|
|
const struct adp5588_kpad_platform_data *pdata = client->dev.platform_data;
|
2009-09-18 13:39:38 +08:00
|
|
|
struct input_dev *input;
|
|
|
|
unsigned int revid;
|
|
|
|
int ret, i;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
if (!i2c_check_functionality(client->adapter,
|
|
|
|
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
|
|
|
dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pdata) {
|
|
|
|
dev_err(&client->dev, "no platform data?\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pdata->rows || !pdata->cols || !pdata->keymap) {
|
|
|
|
dev_err(&client->dev, "no rows, cols or keymap from pdata\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pdata->keymapsize != ADP5588_KEYMAPSIZE) {
|
|
|
|
dev_err(&client->dev, "invalid keymapsize\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2010-06-25 10:10:40 +08:00
|
|
|
if (!pdata->gpimap && pdata->gpimapsize) {
|
|
|
|
dev_err(&client->dev, "invalid gpimap from pdata\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pdata->gpimapsize > ADP5588_GPIMAPSIZE_MAX) {
|
|
|
|
dev_err(&client->dev, "invalid gpimapsize\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < pdata->gpimapsize; i++) {
|
|
|
|
unsigned short pin = pdata->gpimap[i].pin;
|
|
|
|
|
|
|
|
if (pin < GPI_PIN_BASE || pin > GPI_PIN_END) {
|
|
|
|
dev_err(&client->dev, "invalid gpi pin data\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pin <= GPI_PIN_ROW_END) {
|
|
|
|
if (pin - GPI_PIN_ROW_BASE + 1 <= pdata->rows) {
|
|
|
|
dev_err(&client->dev, "invalid gpi row data\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (pin - GPI_PIN_COL_BASE + 1 <= pdata->cols) {
|
|
|
|
dev_err(&client->dev, "invalid gpi col data\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-09-18 13:39:38 +08:00
|
|
|
if (!client->irq) {
|
|
|
|
dev_err(&client->dev, "no IRQ?\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
kpad = kzalloc(sizeof(*kpad), GFP_KERNEL);
|
|
|
|
input = input_allocate_device();
|
|
|
|
if (!kpad || !input) {
|
|
|
|
error = -ENOMEM;
|
|
|
|
goto err_free_mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
kpad->client = client;
|
|
|
|
kpad->input = input;
|
|
|
|
INIT_DELAYED_WORK(&kpad->work, adp5588_work);
|
|
|
|
|
|
|
|
ret = adp5588_read(client, DEV_ID);
|
|
|
|
if (ret < 0) {
|
|
|
|
error = ret;
|
|
|
|
goto err_free_mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
revid = (u8) ret & ADP5588_DEVICE_ID_MASK;
|
|
|
|
if (WA_DELAYED_READOUT_REVID(revid))
|
|
|
|
kpad->delay = msecs_to_jiffies(30);
|
|
|
|
|
|
|
|
input->name = client->name;
|
|
|
|
input->phys = "adp5588-keys/input0";
|
|
|
|
input->dev.parent = &client->dev;
|
|
|
|
|
|
|
|
input_set_drvdata(input, kpad);
|
|
|
|
|
|
|
|
input->id.bustype = BUS_I2C;
|
|
|
|
input->id.vendor = 0x0001;
|
|
|
|
input->id.product = 0x0001;
|
|
|
|
input->id.version = revid;
|
|
|
|
|
|
|
|
input->keycodesize = sizeof(kpad->keycode[0]);
|
|
|
|
input->keycodemax = pdata->keymapsize;
|
|
|
|
input->keycode = kpad->keycode;
|
|
|
|
|
|
|
|
memcpy(kpad->keycode, pdata->keymap,
|
|
|
|
pdata->keymapsize * input->keycodesize);
|
|
|
|
|
2010-06-25 10:10:40 +08:00
|
|
|
kpad->gpimap = pdata->gpimap;
|
|
|
|
kpad->gpimapsize = pdata->gpimapsize;
|
|
|
|
|
2009-09-18 13:39:38 +08:00
|
|
|
/* setup input device */
|
|
|
|
__set_bit(EV_KEY, input->evbit);
|
|
|
|
|
|
|
|
if (pdata->repeat)
|
|
|
|
__set_bit(EV_REP, input->evbit);
|
|
|
|
|
|
|
|
for (i = 0; i < input->keycodemax; i++)
|
|
|
|
__set_bit(kpad->keycode[i] & KEY_MAX, input->keybit);
|
|
|
|
__clear_bit(KEY_RESERVED, input->keybit);
|
|
|
|
|
2010-06-25 10:10:40 +08:00
|
|
|
if (kpad->gpimapsize)
|
|
|
|
__set_bit(EV_SW, input->evbit);
|
|
|
|
for (i = 0; i < kpad->gpimapsize; i++)
|
|
|
|
__set_bit(kpad->gpimap[i].sw_evt, input->swbit);
|
|
|
|
|
2009-09-18 13:39:38 +08:00
|
|
|
error = input_register_device(input);
|
|
|
|
if (error) {
|
|
|
|
dev_err(&client->dev, "unable to register input device\n");
|
|
|
|
goto err_free_mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
error = request_irq(client->irq, adp5588_irq,
|
2011-09-08 05:04:16 +08:00
|
|
|
IRQF_TRIGGER_FALLING,
|
2009-09-18 13:39:38 +08:00
|
|
|
client->dev.driver->name, kpad);
|
|
|
|
if (error) {
|
|
|
|
dev_err(&client->dev, "irq %d busy?\n", client->irq);
|
|
|
|
goto err_unreg_dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
error = adp5588_setup(client);
|
|
|
|
if (error)
|
|
|
|
goto err_free_irq;
|
|
|
|
|
2010-06-25 10:10:40 +08:00
|
|
|
if (kpad->gpimapsize)
|
|
|
|
adp5588_report_switch_state(kpad);
|
|
|
|
|
2010-08-03 09:33:26 +08:00
|
|
|
error = adp5588_gpio_add(kpad);
|
2010-07-26 16:01:11 +08:00
|
|
|
if (error)
|
|
|
|
goto err_free_irq;
|
|
|
|
|
2009-09-18 13:39:38 +08:00
|
|
|
device_init_wakeup(&client->dev, 1);
|
|
|
|
i2c_set_clientdata(client, kpad);
|
|
|
|
|
|
|
|
dev_info(&client->dev, "Rev.%d keypad, irq %d\n", revid, client->irq);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_free_irq:
|
|
|
|
free_irq(client->irq, kpad);
|
|
|
|
err_unreg_dev:
|
|
|
|
input_unregister_device(input);
|
|
|
|
input = NULL;
|
|
|
|
err_free_mem:
|
|
|
|
input_free_device(input);
|
|
|
|
kfree(kpad);
|
|
|
|
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __devexit adp5588_remove(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad = i2c_get_clientdata(client);
|
|
|
|
|
|
|
|
adp5588_write(client, CFG, 0);
|
|
|
|
free_irq(client->irq, kpad);
|
|
|
|
cancel_delayed_work_sync(&kpad->work);
|
|
|
|
input_unregister_device(kpad->input);
|
2010-08-03 09:33:26 +08:00
|
|
|
adp5588_gpio_remove(kpad);
|
2009-09-18 13:39:38 +08:00
|
|
|
kfree(kpad);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
static int adp5588_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad = dev_get_drvdata(dev);
|
|
|
|
struct i2c_client *client = kpad->client;
|
|
|
|
|
|
|
|
disable_irq(client->irq);
|
|
|
|
cancel_delayed_work_sync(&kpad->work);
|
|
|
|
|
|
|
|
if (device_may_wakeup(&client->dev))
|
|
|
|
enable_irq_wake(client->irq);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5588_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct adp5588_kpad *kpad = dev_get_drvdata(dev);
|
|
|
|
struct i2c_client *client = kpad->client;
|
|
|
|
|
|
|
|
if (device_may_wakeup(&client->dev))
|
|
|
|
disable_irq_wake(client->irq);
|
|
|
|
|
|
|
|
enable_irq(client->irq);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-12-15 10:00:08 +08:00
|
|
|
static const struct dev_pm_ops adp5588_dev_pm_ops = {
|
2009-09-18 13:39:38 +08:00
|
|
|
.suspend = adp5588_suspend,
|
|
|
|
.resume = adp5588_resume,
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct i2c_device_id adp5588_id[] = {
|
2010-10-19 08:46:03 +08:00
|
|
|
{ "adp5588-keys", 0 },
|
2010-01-19 16:28:44 +08:00
|
|
|
{ "adp5587-keys", 0 },
|
2009-09-18 13:39:38 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, adp5588_id);
|
|
|
|
|
|
|
|
static struct i2c_driver adp5588_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = KBUILD_MODNAME,
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
.pm = &adp5588_dev_pm_ops,
|
|
|
|
#endif
|
|
|
|
},
|
|
|
|
.probe = adp5588_probe,
|
2012-11-24 13:27:39 +08:00
|
|
|
.remove = adp5588_remove,
|
2009-09-18 13:39:38 +08:00
|
|
|
.id_table = adp5588_id,
|
|
|
|
};
|
|
|
|
|
2012-03-17 14:05:41 +08:00
|
|
|
module_i2c_driver(adp5588_driver);
|
2009-09-18 13:39:38 +08:00
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
|
2010-01-19 16:28:44 +08:00
|
|
|
MODULE_DESCRIPTION("ADP5588/87 Keypad driver");
|