summaryrefslogtreecommitdiffstats
path: root/drivers/input/keyboard/adp5588-keys.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/keyboard/adp5588-keys.c')
-rw-r--r--drivers/input/keyboard/adp5588-keys.c351
1 files changed, 342 insertions, 9 deletions
diff --git a/drivers/input/keyboard/adp5588-keys.c b/drivers/input/keyboard/adp5588-keys.c
index 744600eff222..d6918cb966c0 100644
--- a/drivers/input/keyboard/adp5588-keys.c
+++ b/drivers/input/keyboard/adp5588-keys.c
@@ -19,6 +19,7 @@
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/i2c.h>
+#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/i2c/adp5588.h>
@@ -54,6 +55,10 @@
#define KEYP_MAX_EVENT 10
+#define MAXGPIO 18
+#define ADP_BANK(offs) ((offs) >> 3)
+#define ADP_BIT(offs) (1u << ((offs) & 0x7))
+
/*
* Early pre 4.0 Silicon required to delay readout by at least 25ms,
* since the Event Counter Register updated 25ms after the interrupt
@@ -67,6 +72,16 @@ struct adp5588_kpad {
struct delayed_work work;
unsigned long delay;
unsigned short keycode[ADP5588_KEYMAPSIZE];
+ const struct adp5588_gpi_map *gpimap;
+ unsigned short gpimapsize;
+#ifdef CONFIG_GPIOLIB
+ unsigned char gpiomap[MAXGPIO];
+ bool export_gpio;
+ struct gpio_chip gc;
+ struct mutex gpio_lock; /* Protect cached dir, dat_out */
+ u8 dat_out[3];
+ u8 dir[3];
+#endif
};
static int adp5588_read(struct i2c_client *client, u8 reg)
@@ -84,12 +99,222 @@ static int adp5588_write(struct i2c_client *client, u8 reg, u8 val)
return i2c_smbus_write_byte_data(client, reg, val);
}
+#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);
+ unsigned int bank = ADP_BANK(kpad->gpiomap[off]);
+ unsigned int bit = ADP_BIT(kpad->gpiomap[off]);
+
+ 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);
+ unsigned int bank = ADP_BANK(kpad->gpiomap[off]);
+ unsigned int bit = ADP_BIT(kpad->gpiomap[off]);
+
+ 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);
+ unsigned int bank = ADP_BANK(kpad->gpiomap[off]);
+ unsigned int bit = ADP_BIT(kpad->gpiomap[off]);
+ 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);
+ unsigned int bank = ADP_BANK(kpad->gpiomap[off]);
+ unsigned int bit = ADP_BIT(kpad->gpiomap[off]);
+ 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;
+}
+
+static int __devinit adp5588_build_gpiomap(struct adp5588_kpad *kpad,
+ const struct adp5588_kpad_platform_data *pdata)
+{
+ bool pin_used[MAXGPIO];
+ int n_unused = 0;
+ int i;
+
+ memset(pin_used, 0, sizeof(pin_used));
+
+ for (i = 0; i < pdata->rows; i++)
+ pin_used[i] = true;
+
+ for (i = 0; i < pdata->cols; i++)
+ pin_used[i + GPI_PIN_COL_BASE - GPI_PIN_BASE] = true;
+
+ for (i = 0; i < kpad->gpimapsize; i++)
+ pin_used[kpad->gpimap[i].pin - GPI_PIN_BASE] = true;
+
+ for (i = 0; i < MAXGPIO; i++)
+ if (!pin_used[i])
+ kpad->gpiomap[n_unused++] = i;
+
+ return n_unused;
+}
+
+static int __devinit adp5588_gpio_add(struct adp5588_kpad *kpad)
+{
+ 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) {
+ dev_info(dev, "No unused gpios left to export\n");
+ return 0;
+ }
+
+ kpad->export_gpio = true;
+
+ 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;
+
+ mutex_init(&kpad->gpio_lock);
+
+ error = gpiochip_add(&kpad->gc);
+ if (error) {
+ dev_err(dev, "gpiochip_add failed, err: %d\n", error);
+ return error;
+ }
+
+ for (i = 0; i <= ADP_BANK(MAXGPIO); i++) {
+ 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;
+}
+
+static void __devexit adp5588_gpio_remove(struct adp5588_kpad *kpad)
+{
+ 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 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
+static inline int adp5588_gpio_add(struct adp5588_kpad *kpad)
+{
+ return 0;
+}
+
+static inline void adp5588_gpio_remove(struct adp5588_kpad *kpad)
+{
+}
+#endif
+
+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);
+ }
+ }
+}
+
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;
- int i, key, status, ev_cnt;
+ int status, ev_cnt;
status = adp5588_read(client, INT_STAT);
@@ -99,12 +324,7 @@ static void adp5588_work(struct work_struct *work)
if (status & KE_INT) {
ev_cnt = adp5588_read(client, KEY_LCK_EC_STAT) & KEC;
if (ev_cnt) {
- for (i = 0; i < ev_cnt; i++) {
- key = adp5588_read(client, Key_EVENTA + i);
- input_report_key(kpad->input,
- kpad->keycode[(key & KEY_EV_MASK) - 1],
- key & KEY_EV_PRESSED);
- }
+ adp5588_report_events(kpad, ev_cnt);
input_sync(kpad->input);
}
}
@@ -128,8 +348,10 @@ static irqreturn_t adp5588_irq(int irq, void *handle)
static int __devinit adp5588_setup(struct i2c_client *client)
{
- struct adp5588_kpad_platform_data *pdata = client->dev.platform_data;
+ const struct adp5588_kpad_platform_data *pdata = client->dev.platform_data;
+ const struct adp5588_gpio_platform_data *gpio_data = pdata->gpio_data;
int i, ret;
+ unsigned char evt_mode1 = 0, evt_mode2 = 0, evt_mode3 = 0;
ret = adp5588_write(client, KP_GPIO1, KP_SEL(pdata->rows));
ret |= adp5588_write(client, KP_GPIO2, KP_SEL(pdata->cols) & 0xFF);
@@ -144,6 +366,32 @@ static int __devinit adp5588_setup(struct i2c_client *client)
for (i = 0; i < KEYP_MAX_EVENT; i++)
ret |= adp5588_read(client, Key_EVENTA);
+ 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);
+ }
+
+ if (gpio_data) {
+ for (i = 0; i <= ADP_BANK(MAXGPIO); i++) {
+ int pull_mask = gpio_data->pullup_dis_mask;
+
+ ret |= adp5588_write(client, GPIO_PULL1 + i,
+ (pull_mask >> (8 * i)) & 0xFF);
+ }
+ }
+
ret |= adp5588_write(client, INT_STAT, CMP2_INT | CMP1_INT |
OVR_FLOW_INT | K_LCK_INT |
GPI_INT | KE_INT); /* Status is W1C */
@@ -158,11 +406,49 @@ static int __devinit adp5588_setup(struct i2c_client *client)
return 0;
}
+static void __devinit adp5588_report_switch_state(struct adp5588_kpad *kpad)
+{
+ 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);
+}
+
+
static int __devinit adp5588_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct adp5588_kpad *kpad;
- struct adp5588_kpad_platform_data *pdata = client->dev.platform_data;
+ const struct adp5588_kpad_platform_data *pdata = client->dev.platform_data;
struct input_dev *input;
unsigned int revid;
int ret, i;
@@ -189,6 +475,37 @@ static int __devinit adp5588_probe(struct i2c_client *client,
return -EINVAL;
}
+ 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;
+ }
+ }
+ }
+
if (!client->irq) {
dev_err(&client->dev, "no IRQ?\n");
return -EINVAL;
@@ -233,6 +550,9 @@ static int __devinit adp5588_probe(struct i2c_client *client,
memcpy(kpad->keycode, pdata->keymap,
pdata->keymapsize * input->keycodesize);
+ kpad->gpimap = pdata->gpimap;
+ kpad->gpimapsize = pdata->gpimapsize;
+
/* setup input device */
__set_bit(EV_KEY, input->evbit);
@@ -243,6 +563,11 @@ static int __devinit adp5588_probe(struct i2c_client *client,
__set_bit(kpad->keycode[i] & KEY_MAX, input->keybit);
__clear_bit(KEY_RESERVED, input->keybit);
+ 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);
+
error = input_register_device(input);
if (error) {
dev_err(&client->dev, "unable to register input device\n");
@@ -261,6 +586,13 @@ static int __devinit adp5588_probe(struct i2c_client *client,
if (error)
goto err_free_irq;
+ if (kpad->gpimapsize)
+ adp5588_report_switch_state(kpad);
+
+ error = adp5588_gpio_add(kpad);
+ if (error)
+ goto err_free_irq;
+
device_init_wakeup(&client->dev, 1);
i2c_set_clientdata(client, kpad);
@@ -287,6 +619,7 @@ static int __devexit adp5588_remove(struct i2c_client *client)
free_irq(client->irq, kpad);
cancel_delayed_work_sync(&kpad->work);
input_unregister_device(kpad->input);
+ adp5588_gpio_remove(kpad);
kfree(kpad);
return 0;
OpenPOWER on IntegriCloud