All of lore.kernel.org
 help / color / mirror / Atom feed
From: Trilok Soni <tsoni@codeaurora.org>
To: linux-kernel@vger.kernel.org
Cc: linux-input@vger.kernel.org, rtc-linux@googlegroups.com,
	linux-arm-msm@vger.kernel.org, Trilok Soni <tsoni@codeaurora.org>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>
Subject: [RFC v1 PATCH 2/6] input: pm8058_keypad: Qualcomm PMIC8058 keypad controller driver
Date: Wed, 10 Nov 2010 18:17:57 +0530	[thread overview]
Message-ID: <1289393281-4459-3-git-send-email-tsoni@codeaurora.org> (raw)
In-Reply-To: <1289393281-4459-1-git-send-email-tsoni@codeaurora.org>

Add Qualcomm PMIC8058 based keypad controller driver
supporting upto 18x8 matrix configuration.

Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Trilok Soni <tsoni@codeaurora.org>
---
 drivers/input/keyboard/Kconfig           |   11 +
 drivers/input/keyboard/Makefile          |    1 +
 drivers/input/keyboard/pmic8058-keypad.c |  769 ++++++++++++++++++++++++++++++
 include/linux/input/pmic8058-keypad.h    |   58 +++
 4 files changed, 839 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/pmic8058-keypad.c
 create mode 100644 include/linux/input/pmic8058-keypad.h

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index b8c51b9..d536acb 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -364,6 +364,17 @@ config KEYBOARD_PXA930_ROTARY
 	  To compile this driver as a module, choose M here: the
 	  module will be called pxa930_rotary.
 
+config KEYBOARD_PMIC8058
+	tristate "Qualcomm PMIC8058 keypad support"
+	depends on PMIC8058
+	help
+	  Say Y here if you want to enable the driver for the PMIC8058
+	  keypad provided as a reference design from Qualcomm. This is intended
+	  to support upto 18x8 matrix based keypad design.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called pmic8058-keypad.
+
 config KEYBOARD_SAMSUNG
 	tristate "Samsung keypad support"
 	depends on SAMSUNG_DEV_KEYPAD
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index a34452e..9ff8dbe 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_KEYBOARD_OMAP4)		+= omap4-keypad.o
 obj-$(CONFIG_KEYBOARD_OPENCORES)	+= opencores-kbd.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_PXA930_ROTARY)	+= pxa930_rotary.o
+obj-$(CONFIG_KEYBOARD_PMIC8058)		+= pmic8058-keypad.o
 obj-$(CONFIG_KEYBOARD_QT2160)		+= qt2160.o
 obj-$(CONFIG_KEYBOARD_SAMSUNG)		+= samsung-keypad.o
 obj-$(CONFIG_KEYBOARD_SH_KEYSC)		+= sh_keysc.o
diff --git a/drivers/input/keyboard/pmic8058-keypad.c b/drivers/input/keyboard/pmic8058-keypad.c
new file mode 100644
index 0000000..77ddda6
--- /dev/null
+++ b/drivers/input/keyboard/pmic8058-keypad.c
@@ -0,0 +1,769 @@
+/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and
+ * only 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/bitops.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/delay.h>
+
+#include <linux/input/pmic8058-keypad.h>
+
+#define PM8058_MAX_ROWS		18
+#define PM8058_MAX_COLS		8
+#define PM8058_ROW_SHIFT	3
+#define PM8058_MATRIX_MAX_SIZE	(PM8058_MAX_ROWS * PM8058_MAX_COLS)
+
+#define PM8058_MIN_ROWS		5
+#define PM8058_MIN_COLS		5
+
+#define MAX_SCAN_DELAY		128
+#define MIN_SCAN_DELAY		1
+
+/* in nanoseconds */
+#define MAX_ROW_HOLD_DELAY	122000
+#define MIN_ROW_HOLD_DELAY	30500
+
+#define MAX_DEBOUNCE_TIME	20
+#define MIN_DEBOUNCE_TIME	5
+
+#define KEYP_CTRL			0x148
+
+#define KEYP_CTRL_EVNTS			BIT(0)
+#define KEYP_CTRL_EVNTS_MASK		0x3
+
+#define KEYP_CTRL_SCAN_COLS_SHIFT	5
+#define KEYP_CTRL_SCAN_COLS_MIN		5
+#define KEYP_CTRL_SCAN_COLS_BITS	0x3
+
+#define KEYP_CTRL_SCAN_ROWS_SHIFT	2
+#define KEYP_CTRL_SCAN_ROWS_MIN		5
+#define KEYP_CTRL_SCAN_ROWS_BITS	0x7
+
+#define KEYP_CTRL_KEYP_EN		BIT(7)
+
+#define KEYP_SCAN			0x149
+
+#define KEYP_SCAN_READ_STATE		BIT(0)
+#define KEYP_SCAN_DBOUNCE_SHIFT		1
+#define KEYP_SCAN_PAUSE_SHIFT		3
+#define KEYP_SCAN_ROW_HOLD_SHIFT	6
+
+#define KEYP_TEST			0x14A
+
+#define KEYP_TEST_CLEAR_RECENT_SCAN	BIT(6)
+#define KEYP_TEST_CLEAR_OLD_SCAN	BIT(5)
+#define KEYP_TEST_READ_RESET		BIT(4)
+#define KEYP_TEST_DTEST_EN		BIT(3)
+#define KEYP_TEST_ABORT_READ		BIT(0)
+
+#define KEYP_TEST_DBG_SELECT_SHIFT	1
+
+/* bits of these registers represent
+ * '0' for key press
+ * '1' for key release
+ */
+#define KEYP_RECENT_DATA		0x14B
+#define KEYP_OLD_DATA			0x14C
+
+#define KEYP_CLOCK_FREQ			32768
+
+/**
+ * struct pmic8058_kp - internal keypad data structure
+ * @pdata - keypad platform data pointer
+ * @input - input device pointer for keypad
+ * @key_sense_irq - key press/release irq number
+ * @key_stuck_irq - key stuck notification irq number
+ * @keycodes - array to hold the key codes
+ * @dev - parent device pointer
+ * @keystate - present key press/release state
+ * @stuckstate - present state when key stuck irq
+ * @pm_chip - parent MFD core device
+ * @ctrl_reg - control register value
+ */
+struct pmic8058_kp {
+	const struct pmic8058_keypad_data *pdata;
+	struct input_dev *input;
+	int key_sense_irq;
+	int key_stuck_irq;
+
+	unsigned short *keycodes;
+
+	struct device *dev;
+	u16 keystate[PM8058_MAX_ROWS];
+	u16 stuckstate[PM8058_MAX_ROWS];
+
+	struct pm8058_chip	*pm_chip;
+
+	u8			ctrl_reg;
+};
+
+static int pmic8058_kp_write_u8(struct pmic8058_kp *kp,
+				 u8 data, u16 reg)
+{
+	int rc;
+
+	rc = pm8058_write(kp->pm_chip, reg, &data, 1);
+	if (rc < 0)
+		dev_warn(kp->dev, "Error writing pmic8058: %X - ret %X\n",
+				reg, rc);
+	return rc;
+}
+
+static int pmic8058_kp_read(struct pmic8058_kp *kp,
+				 u8 *data, u16 reg, unsigned num_bytes)
+{
+	int rc;
+
+	rc = pm8058_read(kp->pm_chip, reg, data, num_bytes);
+	if (rc < 0)
+		dev_warn(kp->dev, "Error reading pmic8058: %X - ret %X\n",
+				reg, rc);
+
+	return rc;
+}
+
+static int pmic8058_kp_read_u8(struct pmic8058_kp *kp,
+				 u8 *data, u16 reg)
+{
+	int rc;
+
+	rc = pmic8058_kp_read(kp, data, reg, 1);
+	if (rc < 0)
+		dev_warn(kp->dev, "Error reading pmic8058: %X - ret %X\n",
+				reg, rc);
+	return rc;
+}
+
+static u8 pmic8058_col_state(struct pmic8058_kp *kp, u8 col)
+{
+	/* all keys pressed on that particular row? */
+	if (col == 0x00)
+		return 1 << kp->pdata->num_cols;
+	else
+		return col & ((1 << kp->pdata->num_cols) - 1);
+}
+
+/*
+ * Synchronous read protocol for RevB0 onwards:
+ *
+ * 1. Write '1' to ReadState bit in KEYP_SCAN register
+ * 2. Wait 2*32KHz clocks, so that HW can successfully enter read mode
+ *    synchronously
+ * 3. Read rows in old array first if events are more than one
+ * 4. Read rows in recent array
+ * 5. Wait 4*32KHz clocks
+ * 6. Write '0' to ReadState bit of KEYP_SCAN register so that hw can
+ *    synchronously exit read mode.
+ */
+static int pmic8058_chk_sync_read(struct pmic8058_kp *kp)
+{
+	int rc;
+	u8 scan_val;
+
+	rc = pmic8058_kp_read_u8(kp, &scan_val, KEYP_SCAN);
+	if (rc < 0)
+		return rc;
+
+	scan_val |= 0x1;
+
+	rc = pmic8058_kp_write_u8(kp, scan_val, KEYP_SCAN);
+	if (rc < 0)
+		return rc;
+
+	/* 2 * 32KHz clocks */
+	udelay((2 * DIV_ROUND_UP(USEC_PER_SEC, KEYP_CLOCK_FREQ)) + 1);
+
+	return rc;
+}
+
+static int pmic8058_kp_read_data(struct pmic8058_kp *kp, u16 *state,
+					u16 data_reg, int read_rows)
+{
+	int rc, row;
+	u8 new_data[PM8058_MAX_ROWS];
+
+	rc = pmic8058_kp_read(kp, new_data, data_reg, read_rows);
+
+	if (!rc) {
+		for (row = 0; row < kp->pdata->num_rows; row++) {
+			dev_dbg(kp->dev, "new_data[%d] = %d\n", row,
+						new_data[row]);
+			state[row] = pmic8058_col_state(kp, new_data[row]);
+		}
+	}
+
+	return rc;
+}
+
+static int pmic8058_kp_read_matrix(struct pmic8058_kp *kp, u16 *new_state,
+					 u16 *old_state)
+{
+	int rc, read_rows;
+	u8 scan_val;
+
+	read_rows = kp->pdata->num_rows;
+
+	pmic8058_chk_sync_read(kp);
+
+	if (old_state) {
+		rc = pmic8058_kp_read_data(kp, old_state, KEYP_OLD_DATA,
+						read_rows);
+		if (rc < 0)
+			return rc;
+	}
+
+	rc = pmic8058_kp_read_data(kp, new_state, KEYP_RECENT_DATA,
+					 read_rows);
+	if (rc < 0)
+		return rc;
+
+	/* 4 * 32KHz clocks */
+	udelay((4 * DIV_ROUND_UP(USEC_PER_SEC, KEYP_CLOCK_FREQ)) + 1);
+
+	rc = pmic8058_kp_read_u8(kp, &scan_val, KEYP_SCAN);
+	if (rc < 0)
+		return rc;
+	scan_val &= 0xFE;
+	rc = pmic8058_kp_write_u8(kp, scan_val, KEYP_SCAN);
+	if (rc < 0)
+		return rc;
+
+	return rc;
+}
+
+static void __pmic8058_kp_scan_matrix(struct pmic8058_kp *kp, u16 *new_state,
+					 u16 *old_state)
+{
+	int row, col, code;
+
+	for (row = 0; row < kp->pdata->num_rows; row++) {
+		int bits_changed = new_state[row] ^ old_state[row];
+
+		if (!bits_changed)
+			continue;
+
+		for (col = 0; col < kp->pdata->num_cols; col++) {
+			if (!(bits_changed & (1 << col)))
+				continue;
+
+			dev_dbg(kp->dev, "key [%d:%d] %s\n", row, col,
+					!(new_state[row] & (1 << col)) ?
+					"pressed" : "released");
+
+			code = MATRIX_SCAN_CODE(row, col, PM8058_ROW_SHIFT);
+			input_event(kp->input, EV_MSC, MSC_SCAN, code);
+			input_report_key(kp->input,
+					kp->keycodes[code],
+					!(new_state[row] & (1 << col)));
+
+			input_sync(kp->input);
+		}
+	}
+}
+
+static int pmic8058_detect_ghost_keys(struct pmic8058_kp *kp, u16 *new_state)
+{
+	int row, found_first = -1;
+	u16 check, row_state;
+
+	check = 0;
+	for (row = 0; row < kp->pdata->num_rows; row++) {
+		row_state = (~new_state[row]) &
+				 ((1 << kp->pdata->num_cols) - 1);
+
+		if (hweight16(row_state) > 1) {
+			if (found_first == -1)
+				found_first = row;
+			if (check & row_state) {
+				dev_dbg(kp->dev, "detected ghost key on row[%d]"
+					 " and row[%d]\n", found_first, row);
+				return 1;
+			}
+		}
+		check |= row_state;
+	}
+	return 0;
+}
+
+static int pmic8058_kp_scan_matrix(struct pmic8058_kp *kp, unsigned int events)
+{
+	u16 new_state[PM8058_MAX_ROWS];
+	u16 old_state[PM8058_MAX_ROWS];
+	int rc;
+
+	switch (events) {
+	case 0x1:
+		rc = pmic8058_kp_read_matrix(kp, new_state, NULL);
+		if (rc < 0)
+			return rc;
+
+		/* detecting ghost key is not an error */
+		if (pmic8058_detect_ghost_keys(kp, new_state))
+			return 0;
+		__pmic8058_kp_scan_matrix(kp, new_state, kp->keystate);
+		memcpy(kp->keystate, new_state, sizeof(new_state));
+	break;
+	case 0x3: /* two events - eventcounter is gray-coded */
+		rc = pmic8058_kp_read_matrix(kp, new_state, old_state);
+		if (rc < 0)
+			return rc;
+
+		__pmic8058_kp_scan_matrix(kp, old_state, kp->keystate);
+		__pmic8058_kp_scan_matrix(kp, new_state, old_state);
+		memcpy(kp->keystate, new_state, sizeof(new_state));
+	break;
+	case 0x2:
+		dev_dbg(kp->dev, "Some key events were lost\n");
+		rc = pmic8058_kp_read_matrix(kp, new_state, old_state);
+		if (rc < 0)
+			return rc;
+		__pmic8058_kp_scan_matrix(kp, old_state, kp->keystate);
+		__pmic8058_kp_scan_matrix(kp, new_state, old_state);
+		memcpy(kp->keystate, new_state, sizeof(new_state));
+	break;
+	default:
+		rc = -EINVAL;
+	}
+	return rc;
+}
+
+/*
+ * NOTE: We are reading recent and old data registers blindly
+ * whenever key-stuck interrupt happens, because events counter doesn't
+ * get updated when this interrupt happens due to key stuck doesn't get
+ * considered as key state change.
+ *
+ * We are not using old data register contents after they are being read
+ * because it might report the key which was pressed before the key being stuck
+ * as stuck key because it's pressed status is stored in the old data
+ * register.
+ */
+static irqreturn_t pmic8058_kp_stuck_irq(int irq, void *data)
+{
+	u16 new_state[PM8058_MAX_ROWS];
+	u16 old_state[PM8058_MAX_ROWS];
+	int rc;
+	struct pmic8058_kp *kp = data;
+
+	rc = pmic8058_kp_read_matrix(kp, new_state, old_state);
+	if (rc < 0) {
+		dev_err(kp->dev, "failed to read keypad matrix\n");
+		return IRQ_HANDLED;
+	}
+
+	__pmic8058_kp_scan_matrix(kp, new_state, kp->stuckstate);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t pmic8058_kp_irq(int irq, void *data)
+{
+	struct pmic8058_kp *kp = data;
+	u8 ctrl_val, events;
+	int rc;
+
+	rc = pmic8058_kp_read(kp, &ctrl_val, KEYP_CTRL, 1);
+	if (rc < 0) {
+		dev_err(kp->dev, "failed to read keyp_ctrl register\n");
+		return IRQ_HANDLED;
+	}
+
+	events = ctrl_val & KEYP_CTRL_EVNTS_MASK;
+
+	rc = pmic8058_kp_scan_matrix(kp, events);
+	if (rc < 0)
+		dev_err(kp->dev, "failed to scan matrix\n");
+
+	return IRQ_HANDLED;
+}
+
+static int pmic8058_kpd_init(struct pmic8058_kp *kp)
+{
+	int bits, rc, cycles;
+	u8 scan_val = 0, ctrl_val = 0;
+	static u8 row_bits[] = {
+		0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7,
+	};
+
+	/* Find column bits */
+	if (kp->pdata->num_cols < KEYP_CTRL_SCAN_COLS_MIN)
+		bits = 0;
+	else
+		bits = kp->pdata->num_cols - KEYP_CTRL_SCAN_COLS_MIN;
+	ctrl_val = (bits & KEYP_CTRL_SCAN_COLS_BITS) <<
+		KEYP_CTRL_SCAN_COLS_SHIFT;
+
+	/* Find row bits */
+	if (kp->pdata->num_rows < KEYP_CTRL_SCAN_ROWS_MIN)
+		bits = 0;
+	else
+		bits = row_bits[kp->pdata->num_rows - KEYP_CTRL_SCAN_ROWS_MIN];
+
+	ctrl_val |= (bits << KEYP_CTRL_SCAN_ROWS_SHIFT);
+
+	rc = pmic8058_kp_write_u8(kp, ctrl_val, KEYP_CTRL);
+	if (rc < 0)
+		return rc;
+
+	bits = (kp->pdata->debounce_ms / 5) - 1;
+
+	scan_val |= (bits << KEYP_SCAN_DBOUNCE_SHIFT);
+
+	bits = fls(kp->pdata->scan_delay_ms) - 1;
+	scan_val |= (bits << KEYP_SCAN_PAUSE_SHIFT);
+
+	/* Row hold time is a multiple of 32KHz cycles. */
+	cycles = (kp->pdata->row_hold_ns * KEYP_CLOCK_FREQ) / NSEC_PER_SEC;
+
+	scan_val |= (cycles << KEYP_SCAN_ROW_HOLD_SHIFT);
+
+	rc = pmic8058_kp_write_u8(kp, scan_val, KEYP_SCAN);
+
+	return rc;
+}
+
+static int pmic8058_kp_enable(struct pmic8058_kp *kp)
+{
+	int rc;
+
+	kp->ctrl_reg |= KEYP_CTRL_KEYP_EN;
+
+	rc = pmic8058_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL);
+	if (rc < 0)
+		return rc;
+
+	enable_irq(kp->key_sense_irq);
+	enable_irq(kp->key_stuck_irq);
+
+	return rc;
+}
+
+static int pmic8058_kp_disable(struct pmic8058_kp *kp)
+{
+	int rc;
+
+	kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN;
+
+	rc = pmic8058_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL);
+	if (rc < 0)
+		return rc;
+
+	disable_irq(kp->key_sense_irq);
+	disable_irq(kp->key_stuck_irq);
+
+	return rc;
+}
+
+static int pmic8058_kp_open(struct input_dev *dev)
+{
+	struct pmic8058_kp *kp = input_get_drvdata(dev);
+
+	return pmic8058_kp_enable(kp);
+}
+
+static void pmic8058_kp_close(struct input_dev *dev)
+{
+	struct pmic8058_kp *kp = input_get_drvdata(dev);
+
+	pmic8058_kp_disable(kp);
+}
+
+/*
+ * keypad controller should be initialized in the following sequence
+ * only, otherwise it might get into FSM stuck state.
+ *
+ * - Initialize keypad control parameters, like no. of rows, columns,
+ *   timing values etc.,
+ * - configure rows and column gpios pull up/down.
+ * - set irq edge type.
+ * - enable the keypad controller.
+ */
+static int __devinit pmic8058_kp_probe(struct platform_device *pdev)
+{
+	struct pmic8058_keypad_data *pdata = pdev->dev.platform_data;
+	const struct matrix_keymap_data *keymap_data;
+	struct pmic8058_kp *kp;
+	int rc;
+	unsigned short *keycodes;
+	u8 ctrl_val;
+	struct pm8058_chip	*pm_chip;
+
+	pm_chip = platform_get_drvdata(pdev);
+	if (pm_chip == NULL) {
+		dev_err(&pdev->dev, "no parent data passed in\n");
+		return -EFAULT;
+	}
+
+	/* Check PMIC8058 version. A0 version is not supported */
+	if (pm8058_rev(pm_chip) == PM_8058_REV_1p0) {
+		dev_err(&pdev->dev, "PMIC8058 1.0 version is not supported\n");
+		return -ENODEV;
+	}
+
+	if (!pdata || !pdata->num_cols || !pdata->num_rows ||
+		pdata->num_cols > PM8058_MAX_COLS ||
+		pdata->num_rows > PM8058_MAX_ROWS ||
+		pdata->num_cols < PM8058_MIN_COLS ||
+		pdata->num_rows < PM8058_MIN_ROWS) {
+		dev_err(&pdev->dev, "invalid platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdata->rows_gpio_start < 0 || pdata->cols_gpio_start < 0) {
+		dev_err(&pdev->dev, "invalid gpio_start platform data\n");
+		return -EINVAL;
+	}
+
+	if (!pdata->scan_delay_ms || pdata->scan_delay_ms > MAX_SCAN_DELAY
+		|| pdata->scan_delay_ms < MIN_SCAN_DELAY ||
+		!is_power_of_2(pdata->scan_delay_ms)) {
+		dev_err(&pdev->dev, "invalid keypad scan time supplied\n");
+		return -EINVAL;
+	}
+
+	if (!pdata->row_hold_ns || pdata->row_hold_ns > MAX_ROW_HOLD_DELAY
+		|| pdata->row_hold_ns < MIN_ROW_HOLD_DELAY ||
+		((pdata->row_hold_ns % MIN_ROW_HOLD_DELAY) != 0)) {
+		dev_err(&pdev->dev, "invalid keypad row hold time supplied\n");
+		return -EINVAL;
+	}
+
+	if (!pdata->debounce_ms
+		|| ((pdata->debounce_ms % 5) != 0)
+		|| pdata->debounce_ms > MAX_DEBOUNCE_TIME
+		|| pdata->debounce_ms < MIN_DEBOUNCE_TIME) {
+		dev_err(&pdev->dev, "invalid debounce time supplied\n");
+		return -EINVAL;
+	}
+
+	keymap_data = pdata->keymap_data;
+	if (!keymap_data) {
+		dev_err(&pdev->dev, "no keymap data supplied\n");
+		return -EINVAL;
+	}
+
+	kp = kzalloc(sizeof(*kp), GFP_KERNEL);
+	if (!kp)
+		return -ENOMEM;
+
+	keycodes = kzalloc(PM8058_MATRIX_MAX_SIZE * sizeof(*keycodes),
+				 GFP_KERNEL);
+	if (!keycodes) {
+		rc = -ENOMEM;
+		goto err_alloc_mem;
+	}
+
+	platform_set_drvdata(pdev, kp);
+
+	kp->pdata	= pdata;
+	kp->dev		= &pdev->dev;
+	kp->keycodes	= keycodes;
+	kp->pm_chip	= pm_chip;
+
+	kp->input = input_allocate_device();
+	if (!kp->input) {
+		dev_err(&pdev->dev, "unable to allocate input device\n");
+		rc = -ENOMEM;
+		goto err_alloc_device;
+	}
+
+	kp->key_sense_irq = platform_get_irq(pdev, 0);
+	if (kp->key_sense_irq < 0) {
+		dev_err(&pdev->dev, "unable to get keypad sense irq\n");
+		rc = -ENXIO;
+		goto err_get_irq;
+	}
+
+	kp->key_stuck_irq = platform_get_irq(pdev, 1);
+	if (kp->key_stuck_irq < 0) {
+		dev_err(&pdev->dev, "unable to get keypad stuck irq\n");
+		rc = -ENXIO;
+		goto err_get_irq;
+	}
+
+	if (pdata->input_name)
+		kp->input->name = pdata->input_name;
+	else
+		kp->input->name = "PMIC8058 keypad";
+
+	if (pdata->input_phys_device)
+		kp->input->phys = pdata->input_phys_device;
+	else
+		kp->input->phys = "pmic8058_keypad/input0";
+
+	kp->input->dev.parent	= &pdev->dev;
+
+	kp->input->id.bustype	= BUS_I2C;
+	kp->input->id.version	= 0x0001;
+	kp->input->id.product	= 0x0001;
+	kp->input->id.vendor	= 0x0001;
+
+	kp->input->evbit[0]	= BIT_MASK(EV_KEY);
+
+	if (pdata->rep)
+		__set_bit(EV_REP, kp->input->evbit);
+
+	kp->input->keycode	= keycodes;
+	kp->input->keycodemax	= PM8058_MATRIX_MAX_SIZE;
+	kp->input->keycodesize	= sizeof(*keycodes);
+	kp->input->open		= pmic8058_kp_open;
+	kp->input->close	= pmic8058_kp_close;
+
+	matrix_keypad_build_keymap(keymap_data, PM8058_ROW_SHIFT,
+					kp->input->keycode, kp->input->keybit);
+
+	input_set_capability(kp->input, EV_MSC, MSC_SCAN);
+	input_set_drvdata(kp->input, kp);
+
+	/* initialize keypad state */
+	memset(kp->keystate, 0xff, sizeof(kp->keystate));
+	memset(kp->stuckstate, 0xff, sizeof(kp->stuckstate));
+
+	rc = pmic8058_kpd_init(kp);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "unable to initialize keypad controller\n");
+		goto err_kpd_init;
+	}
+
+	rc = request_any_context_irq(kp->key_sense_irq, pmic8058_kp_irq,
+				 IRQF_TRIGGER_RISING, "pmic-keypad", kp);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "failed to request keypad sense irq\n");
+		goto err_req_sense_irq;
+	}
+	/* kp->open will enable the irq */
+	disable_irq(kp->key_sense_irq);
+
+	rc = request_any_context_irq(kp->key_stuck_irq, pmic8058_kp_stuck_irq,
+				 IRQF_TRIGGER_RISING, "pmic-keypad-stuck", kp);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "failed to request keypad stuck irq\n");
+		goto err_req_stuck_irq;
+	}
+	/* kp->open will enable the irq */
+	disable_irq(kp->key_stuck_irq);
+
+	rc = pmic8058_kp_read_u8(kp, &ctrl_val, KEYP_CTRL);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "failed to read KEYP_CTRL register\n");
+		goto err_pmic_reg_read;
+	}
+
+	kp->ctrl_reg = ctrl_val;
+
+	rc = input_register_device(kp->input);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "unable to register keypad input device\n");
+		goto err_reg_input_dev;
+	}
+
+	device_init_wakeup(&pdev->dev, pdata->wakeup);
+
+	return 0;
+
+err_reg_input_dev:
+err_pmic_reg_read:
+	free_irq(kp->key_stuck_irq, NULL);
+err_req_stuck_irq:
+	free_irq(kp->key_sense_irq, NULL);
+err_req_sense_irq:
+err_kpd_init:
+err_get_irq:
+	input_free_device(kp->input);
+err_alloc_device:
+	kfree(keycodes);
+err_alloc_mem:
+	kfree(kp);
+	return rc;
+}
+
+static int __devexit pmic8058_kp_remove(struct platform_device *pdev)
+{
+	struct pmic8058_kp *kp = platform_get_drvdata(pdev);
+
+	device_init_wakeup(&pdev->dev, 0);
+	free_irq(kp->key_stuck_irq, NULL);
+	free_irq(kp->key_sense_irq, NULL);
+	input_unregister_device(kp->input);
+	kfree(kp->input->keycode);
+	kfree(kp);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int pmic8058_kp_suspend(struct device *dev)
+{
+	struct pmic8058_kp *kp = dev_get_drvdata(dev);
+
+	if (device_may_wakeup(dev))
+		enable_irq_wake(kp->key_sense_irq);
+
+	return 0;
+}
+
+static int pmic8058_kp_resume(struct device *dev)
+{
+	struct pmic8058_kp *kp = dev_get_drvdata(dev);
+
+	if (device_may_wakeup(dev))
+		disable_irq_wake(kp->key_sense_irq);
+
+	return 0;
+}
+
+static const struct dev_pm_ops pm8058_kp_pm_ops = {
+	.suspend	= pmic8058_kp_suspend,
+	.resume		= pmic8058_kp_resume,
+};
+#endif
+
+static struct platform_driver pmic8058_kp_driver = {
+	.probe		= pmic8058_kp_probe,
+	.remove		= __devexit_p(pmic8058_kp_remove),
+	.driver		= {
+		.name = "pm8058-keypad",
+		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm = &pm8058_kp_pm_ops,
+#endif
+	},
+};
+
+static int __init pmic8058_kp_init(void)
+{
+	return platform_driver_register(&pmic8058_kp_driver);
+}
+module_init(pmic8058_kp_init);
+
+static void __exit pmic8058_kp_exit(void)
+{
+	platform_driver_unregister(&pmic8058_kp_driver);
+}
+module_exit(pmic8058_kp_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8058 keypad driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:pmic8058_keypad");
+MODULE_AUTHOR("Trilok Soni <tsoni@codeaurora.org>");
diff --git a/include/linux/input/pmic8058-keypad.h b/include/linux/input/pmic8058-keypad.h
new file mode 100644
index 0000000..901f6cc
--- /dev/null
+++ b/include/linux/input/pmic8058-keypad.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and
+ * only 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __PMIC8058_KEYPAD_H__
+#define __PMIC8058_KEYPAD_H__
+
+#include <linux/input/matrix_keypad.h>
+
+/**
+ * struct pmic8058_keypad_data - platform data for keypad
+ * @keymap_data - matrix keymap data
+ * @input_name - input device name
+ * @input_phys_device - input device name
+ * @num_cols - number of columns of keypad
+ * @num_rows - number of row of keypad
+ * @rows_gpio_start - starting PMIC gpio number for rows
+ * @cols_gpio_start - starting PMIC gpio number for cols
+ * @debounce_ms - debounce period in milliseconds
+ * @scan_delay_ms - scan delay in milliseconds
+ * @row_hold_ns - row hold period in nanoseconds
+ * @wakeup - configure keypad as wakeup
+ * @rep - enable or disable key repeat bit
+ */
+struct pmic8058_keypad_data {
+	const struct matrix_keymap_data *keymap_data;
+
+	const char *input_name;
+	const char *input_phys_device;
+
+	unsigned int num_cols;
+	unsigned int num_rows;
+
+	unsigned int rows_gpio_start;
+	unsigned int cols_gpio_start;
+
+	unsigned int debounce_ms;
+	unsigned int scan_delay_ms;
+	unsigned int row_hold_ns;
+
+	bool wakeup;
+	bool rep;
+};
+
+#endif /*__PMIC8058_KEYPAD_H__ */
-- 
1.7.0.2


  parent reply	other threads:[~2010-11-10 12:48 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-11-10 12:47 [RFC v1 PATCH 0/6] Qualcomm PMIC8058 sub-device drivers Trilok Soni
2010-11-10 12:47 ` [RFC v1 PATCH 1/6] matrix_keypad: Increase the max limit of rows and columns Trilok Soni
2010-11-10 18:33   ` Eric Miao
2010-11-10 18:33     ` Eric Miao
2010-11-10 23:30     ` Dmitry Torokhov
2010-11-10 23:30       ` Dmitry Torokhov
2010-11-17 17:54       ` Dmitry Torokhov
2010-11-10 12:47 ` Trilok Soni [this message]
2010-11-16  3:49   ` [RFC v1 PATCH 2/6] input: pm8058_keypad: Qualcomm PMIC8058 keypad controller driver Trilok Soni
2010-11-17 18:02   ` Dmitry Torokhov
2010-12-06 18:14   ` Dmitry Torokhov
2010-12-07  9:22     ` Trilok Soni
2010-12-07  9:30       ` Dmitry Torokhov
2010-12-07  9:32         ` Trilok Soni
2011-01-06 11:56     ` [rtc-linux] " Anirudh Ghayal
2010-11-10 12:47 ` [RFC v1 PATCH 3/6] led: pmic8058: Add PMIC8058 leds driver Trilok Soni
2010-11-10 20:45   ` Lars-Peter Clausen
2010-11-10 23:28     ` Dmitry Torokhov
2010-11-10 23:50       ` Lars-Peter Clausen
2010-11-11 12:15     ` Trilok Soni
2010-12-06 13:44       ` Trilok Soni
2010-12-07 15:11         ` Lars-Peter Clausen
2010-12-07 15:47           ` Trilok Soni
2010-11-10 12:47 ` [RFC v1 PATCH 4/6] input: pmic8058_pwrkey: Add support for power key Trilok Soni
2010-11-11  7:21   ` Dmitry Torokhov
2010-11-11 12:00     ` Trilok Soni
2010-11-12  0:57       ` Dmitry Torokhov
2010-11-12  8:56         ` Trilok Soni
2010-11-12 19:58           ` Dmitry Torokhov
2010-11-10 12:48 ` [RFC v1 PATCH 5/6] input: pmic8058-othc: Add support for PM8058 based OTHC Trilok Soni
2010-11-16  3:08   ` Trilok Soni
2010-11-16  5:36   ` Datta, Shubhrajyoti
2010-11-16  6:36     ` Trilok Soni
2010-12-01  5:34       ` Anirudh Ghayal
2010-12-07 10:04   ` Dmitry Torokhov
2010-12-07 12:10     ` Anirudh Ghayal
2010-11-10 12:48 ` [RFC v1 PATCH 6/6] drivers: rtc: Add support for Qualcomm PMIC8058 RTC Trilok Soni
2010-11-12  8:53   ` Trilok Soni
2010-11-12 12:53   ` Lars-Peter Clausen
2010-11-12 15:17     ` [rtc-linux] " Anirudh Ghayal

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1289393281-4459-3-git-send-email-tsoni@codeaurora.org \
    --to=tsoni@codeaurora.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=rtc-linux@googlegroups.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.