public inbox for linux-omap@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 10:04 [PATCH 0/5] n810 drivers Felipe Balbi
@ 2008-04-09 10:04 ` Felipe Balbi
  0 siblings, 0 replies; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 10:04 UTC (permalink / raw)
  To: linux-omap; +Cc: Tony Lindgren, Daniel Stone, Felipe Balbi

From: Daniel Stone <daniel.stone@nokia.com>

Introduce lm8323 keypad driver.

Signed-off-by: Daniel Stone <daniel.stone@nokia.com

Updated to build with recent linux-omap and new-style
i2c driver.

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/mach-omap2/board-n800.c |   77 ++++
 arch/arm/mach-omap2/board-n810.c |    2 +
 drivers/input/keyboard/Kconfig   |    7 +
 drivers/input/keyboard/Makefile  |    1 +
 drivers/input/keyboard/lm8323.c  |  921 ++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/lm8323.h       |   45 ++
 6 files changed, 1053 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/lm8323.c
 create mode 100644 include/linux/i2c/lm8323.h

diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
index 758e2c1..367e518 100644
--- a/arch/arm/mach-omap2/board-n800.c
+++ b/arch/arm/mach-omap2/board-n800.c
@@ -23,6 +23,7 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
 #include <asm/mach/arch.h>
@@ -48,6 +49,76 @@
 #define N800_DAV_IRQ_GPIO		103
 #define N800_TSC2301_RESET_GPIO		118
 
+#ifdef CONFIG_MACH_NOKIA_N810
+static s16 rx44_keymap[LM8323_KEYMAP_SIZE] = {
+	[0x01] = KEY_Q,
+	[0x02] = KEY_K,
+	[0x03] = KEY_O,
+	[0x04] = KEY_P,
+	[0x05] = KEY_BACKSPACE,
+	[0x06] = KEY_A,
+	[0x07] = KEY_S,
+	[0x08] = KEY_D,
+	[0x09] = KEY_F,
+	[0x0a] = KEY_G,
+	[0x0b] = KEY_H,
+	[0x0c] = KEY_J,
+
+	[0x11] = KEY_W,
+	[0x12] = KEY_F4,
+	[0x13] = KEY_L,
+	[0x14] = KEY_APOSTROPHE,
+	[0x16] = KEY_Z,
+	[0x17] = KEY_X,
+	[0x18] = KEY_C,
+	[0x19] = KEY_V,
+	[0x1a] = KEY_B,
+	[0x1b] = KEY_N,
+	[0x1c] = KEY_LEFTSHIFT, /* Actually, this is both shift keys */
+	[0x1f] = KEY_F7,
+
+	[0x21] = KEY_E,
+	[0x22] = KEY_SEMICOLON,
+	[0x23] = KEY_MINUS,
+	[0x24] = KEY_EQUAL,
+	[0x2b] = KEY_FN,
+	[0x2c] = KEY_M,
+	[0x2f] = KEY_F8,
+
+	[0x31] = KEY_R,
+	[0x32] = KEY_RIGHTCTRL,
+	[0x34] = KEY_SPACE,
+	[0x35] = KEY_COMMA,
+	[0x37] = KEY_UP,
+	[0x3c] = KEY_COMPOSE,
+	[0x3f] = KEY_F6,
+
+	[0x41] = KEY_T,
+	[0x44] = KEY_DOT,
+	[0x46] = KEY_RIGHT,
+	[0x4f] = KEY_F5,
+	[0x51] = KEY_Y,
+	[0x53] = KEY_DOWN,
+	[0x55] = KEY_ENTER,
+	[0x5f] = KEY_ESC,
+
+	[0x61] = KEY_U,
+	[0x64] = KEY_LEFT,
+
+	[0x71] = KEY_I,
+	[0x75] = KEY_KPENTER,
+};
+
+static struct lm8323_platform_data lm8323_pdata = {
+	.repeat = 0, /* Repeat is handled in userspace for now. */
+	.keymap = rx44_keymap,
+
+	.name = "Internal keyboard",
+	.pwm1_name = "keyboard",
+	.pwm2_name = "cover",
+};
+#endif
+
 void __init nokia_n800_init_irq(void)
 {
 	omap2_init_common_hw();
@@ -502,6 +573,12 @@ static struct i2c_board_info __initdata n800_i2c_board_info_2[] = {
 		I2C_BOARD_INFO("tea5761", 0x10),
 	},
 #endif
+	{
+		I2C_BOARD_INFO("lm8323", 0x45),
+		.type		= "lm8323",
+		.irq		= OMAP_GPIO_IRQ(109),
+		.platform_data	= &lm8323_pdata,
+	},
 };
 
 void __init nokia_n800_common_init(void)
diff --git a/arch/arm/mach-omap2/board-n810.c b/arch/arm/mach-omap2/board-n810.c
index c4f4dd5..fb0e61f 100644
--- a/arch/arm/mach-omap2/board-n810.c
+++ b/arch/arm/mach-omap2/board-n810.c
@@ -10,6 +10,8 @@
  */
 
 #include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 1c22930..a8be2dc 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -285,6 +285,13 @@ config KEYBOARD_TSC2301
 	help
 	  Say Y here for if you are using the keypad features of TSC2301.
 
+config KEYBOARD_LM8323
+	bool "LM8323 keypad chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for the National Semiconductor
+	  LM8323 keypad controller.
+
 config KEYBOARD_PXA27x
 	tristate "PXA27x/PXA3xx keypad support"
 	depends on PXA27x || PXA3xx
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index bc0bbc1..ec447cd 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
 obj-$(CONFIG_KEYBOARD_TSC2301)		+= tsc2301_kp.o
+obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= omap-twl4030keypad.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_AAED2000)		+= aaed2000_kbd.o
diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c
new file mode 100644
index 0000000..f43451d
--- /dev/null
+++ b/drivers/input/keyboard/lm8323.c
@@ -0,0 +1,921 @@
+/*
+ * drivers/i2c/chips/lm8323.c
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Written by Daniel Stone <daniel.stone@nokia.com>
+ *            Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 2 of the License only).
+ *
+ * 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/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/i2c/lm8323.h>
+
+#include <asm/mach-types.h>
+#include <asm/mach/irq.h>
+
+#ifdef VERBOSE
+#define debug dev_dbg
+#else
+#define debug(...)
+#endif
+
+/* Commands to send to the chip. */
+#define LM8323_CMD_READ_ID		0x80 /* Read chip ID. */
+#define LM8323_CMD_WRITE_CFG		0x81 /* Set configuration item. */
+#define LM8323_CMD_READ_INT		0x82 /* Get interrupt status. */
+#define LM8323_CMD_RESET		0x83 /* Reset, same as external one */
+#define LM8323_CMD_WRITE_PORT_SEL	0x85 /* Set GPIO in/out. */
+#define LM8323_CMD_WRITE_PORT_STATE	0x86 /* Set GPIO pullup. */
+#define LM8323_CMD_READ_PORT_SEL	0x87 /* Get GPIO in/out. */
+#define LM8323_CMD_READ_PORT_STATE	0x88 /* Get GPIO pullup. */
+#define LM8323_CMD_READ_FIFO		0x89 /* Read byte from FIFO. */
+#define LM8323_CMD_RPT_READ_FIFO	0x8a /* Read FIFO (no increment). */
+#define LM8323_CMD_SET_ACTIVE		0x8b /* Set active time. */
+#define LM8323_CMD_READ_ERR		0x8c /* Get error status. */
+#define LM8323_CMD_READ_ROTATOR		0x8e /* Read rotator status. */
+#define LM8323_CMD_SET_DEBOUNCE		0x8f /* Set debouncing time. */
+#define LM8323_CMD_SET_KEY_SIZE		0x90 /* Set keypad size. */
+#define LM8323_CMD_READ_KEY_SIZE	0x91 /* Get keypad size. */
+#define LM8323_CMD_READ_CFG		0x92 /* Get configuration item. */
+#define LM8323_CMD_WRITE_CLOCK		0x93 /* Set clock config. */
+#define LM8323_CMD_READ_CLOCK		0x94 /* Get clock config. */
+#define LM8323_CMD_PWM_WRITE		0x95 /* Write PWM script. */
+#define LM8323_CMD_START_PWM		0x96 /* Start PWM engine. */
+#define LM8323_CMD_STOP_PWM		0x97 /* Stop PWM engine. */
+
+/* Interrupt status. */
+#define INT_KEYPAD			0x01 /* Key event. */
+#define INT_ROTATOR			0x02 /* Rotator event. */
+#define INT_ERROR			0x08 /* Error: use CMD_READ_ERR. */
+#define INT_NOINIT			0x10 /* Lost configuration. */
+#define INT_PWM1			0x20 /* PWM1 stopped. */
+#define INT_PWM2			0x40 /* PWM2 stopped. */
+#define INT_PWM3			0x80 /* PWM3 stopped. */
+
+/* Errors (signalled by INT_ERROR, read with CMD_READ_ERR). */
+#define ERR_BADPAR			0x01 /* Bad parameter. */
+#define ERR_CMDUNK			0x02 /* Unknown command. */
+#define ERR_KEYOVR			0x04 /* Too many keys pressed. */
+#define ERR_FIFOOVER			0x40 /* FIFO overflow. */
+
+/* Configuration keys (CMD_{WRITE,READ}_CFG). */
+#define CFG_MUX1SEL			0x01 /* Select MUX1_OUT input. */
+#define CFG_MUX1EN			0x02 /* Enable MUX1_OUT. */
+#define CFG_MUX2SEL			0x04 /* Select MUX2_OUT input. */
+#define CFG_MUX2EN			0x08 /* Enable MUX2_OUT. */
+#define CFG_PSIZE			0x20 /* Package size (must be 0). */
+#define CFG_ROTEN			0x40 /* Enable rotator. */
+
+/* Clock settings (CMD_{WRITE,READ}_CLOCK). */
+#define CLK_RCPWM_INTERNAL		0x00
+#define CLK_RCPWM_EXTERNAL		0x03
+#define CLK_SLOWCLKEN			0x08 /* Enable 32.768kHz clock. */
+#define CLK_SLOWCLKOUT			0x40 /* Enable slow pulse output. */
+
+/* The possible addresses corresponding to CONFIG1 and CONFIG2 pin wirings. */
+#define LM8323_I2C_ADDR00		(0x84 >> 1)	/* 1000 010x */
+#define LM8323_I2C_ADDR01		(0x86 >> 1)	/* 1000 011x */
+#define LM8323_I2C_ADDR10		(0x88 >> 1)	/* 1000 100x */
+#define LM8323_I2C_ADDR11		(0x8A >> 1)	/* 1000 101x */
+
+/* Key event fifo length */
+#define LM8323_FIFO_LEN			15
+
+/* Commands for PWM engine; feed in with PWM_WRITE. */
+/* Load ramp counter from duty cycle field (range 0 - 0xff). */
+#define PWM_SET(v)			(0x4000 | ((v) & 0xff))
+/* Go to start of script. */
+#define PWM_GOTOSTART			0x0000
+/*
+ * Stop engine (generates interrupt).  If reset is 1, clear the program
+ * counter, else leave it.
+ */
+#define PWM_END(reset)			(0xc000 | (!!(reset) << 11))
+/*
+ * Ramp.  If s is 1, divide clock by 512, else divide clock by 16.
+ * Take t clock scales (up to 63) per step, for n steps (up to 126).
+ * If u is set, ramp up, else ramp down.
+ */
+#define PWM_RAMP(s, t, n, u)		((!!(s) << 14) | ((t) & 0x3f) << 8 | \
+					 ((n) & 0x7f) | ((u) ? 0 : 0x80))
+/*
+ * Loop (i.e. jump back to pos) for a given number of iterations (up to 63).
+ * If cnt is zero, execute until PWM_END is encountered.
+ */
+#define PWM_LOOP(cnt, pos)		(0xa000 | (((cnt) & 0x3f) << 7) | \
+					 ((pos) & 0x3f))
+/*
+ * Wait for trigger.  Argument is a mask of channels, shifted by the channel
+ * number, e.g. 0xa for channels 3 and 1.  Note that channels are numbered
+ * from 1, not 0.
+ */
+#define PWM_WAIT_TRIG(chans)		(0xe000 | (((chans) & 0x7) << 6))
+/* Send trigger.  Argument is same as PWM_WAIT_TRIG. */
+#define PWM_SEND_TRIG(chans)		(0xe000 | ((chans) & 0x7))
+
+#define DRIVER_NAME  "lm8323"
+
+static unsigned short normal_i2c[] =
+{
+	LM8323_I2C_ADDR00, LM8323_I2C_ADDR01,
+	LM8323_I2C_ADDR10, LM8323_I2C_ADDR11,
+	I2C_CLIENT_END
+};
+
+I2C_CLIENT_INSMOD;
+
+struct lm8323_pwm {
+	int			id;
+	int			enabled;
+	int			fade_time;
+	int			brightness;
+	int			desired_brightness;
+	struct work_struct	work;
+	struct led_classdev	cdev;
+};
+
+struct lm8323_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	struct work_struct	work;
+	struct input_dev	*idev;
+	int			irq;
+	unsigned		kp_enabled : 1;
+	unsigned		pm_suspend : 1;
+	unsigned		keys_down;
+	char			phys[32];
+	s16			keymap[LM8323_KEYMAP_SIZE];
+	int			size_x;
+	int			size_y;
+	int			debounce_time;
+	int			active_time;
+	struct lm8323_pwm	pwm1;
+	struct lm8323_pwm	pwm2;
+	struct lm8323_pwm	pwm3;
+};
+
+#define client_to_lm8323(c)	container_of(c, struct lm8323_chip, client)
+#define dev_to_lm8323(d)	container_of(d, struct lm8323_chip, client->dev)
+#define work_to_lm8323(w)	container_of(w, struct lm8323_chip, work)
+#define cdev_to_pwm(c)		container_of(c, struct lm8323_pwm, cdev)
+#define work_to_pwm(w)		container_of(w, struct lm8323_pwm, work)
+
+static struct lm8323_chip *pwm_to_lm8323(struct lm8323_pwm *pwm)
+{
+	switch (pwm->id) {
+	case 1:
+		return container_of(pwm, struct lm8323_chip, pwm1);
+	case 2:
+		return container_of(pwm, struct lm8323_chip, pwm2);
+	case 3:
+		return container_of(pwm, struct lm8323_chip, pwm3);
+	default:
+		return NULL;
+	}
+}
+
+static struct lm8323_platform_data *lm8323_pdata = NULL;
+
+
+#define LM8323_MAX_DATA 8
+
+/*
+ * To write, we just access the chip's address in write mode, and dump the
+ * command and data out on the bus.  The command byte and data are taken as
+ * sequential u8s out of varargs, to a maximum of LM8323_MAX_DATA.
+ */
+static int lm8323_write(struct lm8323_chip *lm, int len, ...)
+{
+	int ret, i;
+	va_list ap;
+	u8 data[LM8323_MAX_DATA];
+
+	va_start(ap, len);
+
+	if (unlikely(len > LM8323_MAX_DATA)) {
+		dev_err(&lm->client->dev, "tried to send %d bytes\n", len);
+		va_end(ap);
+		return 0;
+	}
+
+	for (i = 0; i < len; i++)
+		data[i] = va_arg(ap, int);
+
+	va_end(ap);
+
+	/*
+	 * If the host is asleep while we send the data, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "sent %d bytes of %d total\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * To read, we first send the command byte to the chip and end the transaction,
+ * then access the chip in read mode, at which point it will send the data.
+ */
+static int lm8323_read(struct lm8323_chip *lm, u8 cmd, u8 *buf, int len)
+{
+	int ret;
+
+	/*
+	 * If the host is asleep while we send the byte, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret != 1)) {
+		dev_err(&lm->client->dev, "sending read cmd 0x%02x failed\n",
+			cmd);
+		return 0;
+	}
+
+	ret = i2c_master_recv(lm->client, buf, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "wanted %d bytes, got %d\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * Set the chip active time (idle time before it enters halt).
+ */
+static void lm8323_set_active_time(struct lm8323_chip *lm, int time)
+{
+	lm8323_write(lm, 2, LM8323_CMD_SET_ACTIVE, time >> 2);
+}
+
+/*
+ * The signals are AT-style: the low 7 bits are the keycode, and the top
+ * bit indicates the state (1 for down, 0 for up).
+ */
+static inline u8 lm8323_whichkey(u8 event)
+{
+	return event & 0x7f;
+}
+
+static inline int lm8323_ispress(u8 event)
+{
+	return (event & 0x80) ? 1 : 0;
+}
+
+static void process_keys(struct lm8323_chip *lm)
+{
+	u8 event;
+	u8 key_fifo[LM8323_FIFO_LEN + 1];
+	int old_keys_down = lm->keys_down;
+	int ret;
+	int i = 0;
+
+	/*
+	 * Read all key events from the FIFO at once. Next READ_FIFO clears the
+	 * FIFO even if we didn't read all events previously.
+	 */
+	ret = lm8323_read(lm, LM8323_CMD_READ_FIFO, key_fifo, LM8323_FIFO_LEN);
+
+	if (ret < 0) {
+		dev_err(&lm->client->dev, "Failed reading fifo \n");
+		return;
+	}
+	key_fifo[ret] = 0;
+
+	while ((event = key_fifo[i])) {
+		u8 key = lm8323_whichkey(event);
+		int isdown = lm8323_ispress(event);
+		s16 keycode = lm->keymap[key];
+
+		if (likely(keycode > 0)) {
+			debug(&lm->client->dev, "key 0x%02x %s\n", key,
+			      isdown ? "down" : "up");
+			if (likely(lm->kp_enabled)) {
+				input_report_key(lm->idev, keycode, isdown);
+				input_sync(lm->idev);
+			}
+			if (isdown)
+				lm->keys_down++;
+			else
+				lm->keys_down--;
+		} else {
+			dev_err(&lm->client->dev, "keycode 0x%02x not mapped "
+				"to any key\n", key);
+		}
+		i++;
+	}
+
+	/*
+	 * Errata: We need to ensure that the chip never enters halt mode
+	 * during a keypress, so set active time to 0.  When it's released,
+	 * we can enter halt again, so set the active time back to normal.
+	 */
+	if (!old_keys_down && lm->keys_down)
+		lm8323_set_active_time(lm, 0);
+	if (old_keys_down && !lm->keys_down)
+		lm8323_set_active_time(lm, lm->active_time);
+}
+
+static void lm8323_process_error(struct lm8323_chip *lm)
+{
+	u8 error;
+
+	if (lm8323_read(lm, LM8323_CMD_READ_ERR, &error, 1) == 1) {
+		if (error & ERR_FIFOOVER)
+			debug(&lm->client->dev, "fifo overflow!\n");
+		if (error & ERR_KEYOVR)
+			debug(&lm->client->dev, "more than two keys pressed\n");
+		if (error & ERR_CMDUNK)
+			debug(&lm->client->dev, "unknown command submitted\n");
+		if (error & ERR_BADPAR)
+			debug(&lm->client->dev, "bad command parameter\n");
+	}
+}
+
+static void lm8323_reset(struct lm8323_chip *lm)
+{
+	/* The docs say we must pass 0xAA as the data byte. */
+	lm8323_write(lm, 2, LM8323_CMD_RESET, 0xAA);
+}
+
+static int lm8323_configure(struct lm8323_chip *lm)
+{
+	int keysize = (lm->size_x << 4) | lm->size_y;
+	int clock = (CLK_SLOWCLKEN | CLK_RCPWM_EXTERNAL);
+	int debounce = lm->debounce_time >> 2;
+	int active = lm->active_time >> 2;
+
+	/*
+	 * Active time must be greater than the debounce time: if it's
+	 * a close-run thing, give ourselves a 12ms buffer.
+	 */
+	if (debounce >= active)
+		active = debounce + 3;
+
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CFG, 0);
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CLOCK, clock);
+	lm8323_write(lm, 2, LM8323_CMD_SET_KEY_SIZE, keysize);
+	lm8323_set_active_time(lm, lm->active_time);
+	lm8323_write(lm, 2, LM8323_CMD_SET_DEBOUNCE, debounce);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_STATE, 0xff, 0xff);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_SEL, 0, 0);
+
+	/*
+	 * Not much we can do about errors at this point, so just hope
+	 * for the best.
+	 */
+
+	return 0;
+}
+
+/*
+ * Bottom half: handle the interrupt by posting key events, or dealing with
+ * errors appropriately.
+ */
+static void lm8323_work(struct work_struct *work)
+{
+	struct lm8323_chip *lm = work_to_lm8323(work);
+	u8 ints;
+
+	mutex_lock(&lm->lock);
+
+	while ((lm8323_read(lm, LM8323_CMD_READ_INT, &ints, 1) == 1) && ints) {
+		if (likely(ints & INT_KEYPAD)) {
+			process_keys(lm);
+		}
+		if (ints & INT_ROTATOR) {
+			/* We don't currently support the rotator. */
+			debug(&lm->client->dev, "rotator fired\n");
+		}
+		if (ints & INT_ERROR) {
+			debug(&lm->client->dev, "error!\n");
+			lm8323_process_error(lm);
+		}
+		if (ints & INT_NOINIT) {
+			dev_err(&lm->client->dev, "chip lost config; "
+						  "reinitialising\n");
+			lm8323_configure(lm);
+		}
+		if (ints & INT_PWM1) {
+			debug(&lm->client->dev, "pwm1 engine completed\n");
+		}
+		if (ints & INT_PWM2) {
+			debug(&lm->client->dev, "pwm2 engine completed\n");
+		}
+		if (ints & INT_PWM3) {
+			debug(&lm->client->dev, "pwm3 engine completed\n");
+		}
+	}
+
+	mutex_unlock(&lm->lock);
+}
+
+/*
+ * We cannot use I2C in interrupt context, so we just schedule work.
+ */
+static irqreturn_t lm8323_irq(int irq, void *data)
+{
+	struct lm8323_chip *lm = data;
+
+	schedule_work(&lm->work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Read the chip ID.
+ */
+static int lm8323_read_id(struct lm8323_chip *lm, u8 *buf)
+{
+	int bytes;
+
+	bytes = lm8323_read(lm, LM8323_CMD_READ_ID, buf, 2);
+	if (unlikely(bytes != 2))
+		return -EIO;
+
+	return 0;
+}
+
+static void lm8323_write_pwm_one(struct lm8323_pwm *pwm, int pos, u16 cmd)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	lm8323_write(lm, 4, LM8323_CMD_PWM_WRITE, (pos << 2) | pwm->id,
+		     (cmd & 0xff00) >> 8, cmd & 0x00ff);
+}
+
+/*
+ * Write a script into a given PWM engine, concluding with PWM_END.
+ * If 'keepalive' is specified, the engine will be kept running
+ * indefinitely.
+ */
+static void lm8323_write_pwm(struct lm8323_pwm *pwm, int keepalive,
+			     int len, ...)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+	int i, cmd;
+	va_list ap;
+
+	/*
+	 * If there are any scripts running at the moment, terminate them
+	 * and make sure the duty cycle is as if it finished.
+	 */
+	lm8323_write(lm, 2, LM8323_CMD_STOP_PWM, pwm->id);
+
+	va_start(ap, len);
+	for (i = 0; i < len; i++) {
+		cmd = va_arg(ap, int);
+		lm8323_write_pwm_one(pwm, i, cmd);
+	}
+	va_end(ap);
+
+	/* Wait for a trigger from any channel. This keeps the engine alive. */
+	if (keepalive)
+		lm8323_write_pwm_one(pwm, i++, PWM_WAIT_TRIG(0xe));
+	else
+		lm8323_write_pwm_one(pwm, i++, PWM_END(1));
+
+	lm8323_write(lm, 2, LM8323_CMD_START_PWM, pwm->id);
+}
+
+static void lm8323_pwm_work(struct work_struct *work)
+{
+	struct lm8323_pwm *pwm = work_to_pwm(work);
+	int div, perstep, steps, hz, direction, keepalive;
+
+	/* Do nothing if we're already at the requested level. */
+	if (pwm->desired_brightness == pwm->brightness)
+		return;
+
+	keepalive = (pwm->desired_brightness > 0);
+	direction = (pwm->desired_brightness > pwm->brightness);
+	steps = abs(pwm->desired_brightness - pwm->brightness);
+
+	/*
+	 * Convert time (in ms) into a divisor (512 or 16 on a refclk of
+	 * 32768Hz), and number of ticks per step.
+	 */
+	if ((pwm->fade_time / steps) > (32768 / 512))
+		div = 512;
+	else
+		div = 16;
+
+	hz = 32768 / div;
+	if (pwm->fade_time < ((steps * 1000) / hz))
+		perstep = 1;
+	else
+		perstep = (hz * pwm->fade_time) / (steps * 1000);
+
+	if (perstep == 0)
+		perstep = 1;
+	else if (perstep > 63)
+		perstep = 63;
+
+	if (steps > 252) {
+		lm8323_write_pwm(pwm, keepalive, 3,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 252,
+					  direction));
+	} else if (steps > 126) {
+		lm8323_write_pwm(pwm, keepalive, 2,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 126,
+					  direction));
+	} else {
+		lm8323_write_pwm(pwm, keepalive, 1,
+				 PWM_RAMP((div == 512), perstep, steps,
+					  direction));
+	}
+
+	pwm->brightness = pwm->desired_brightness;
+}
+
+static void lm8323_pwm_set_brightness(struct led_classdev *led_cdev,
+				      enum led_brightness brightness)
+{
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	pwm->desired_brightness = brightness;
+
+	if (in_interrupt()) {
+		schedule_work(&pwm->work);
+	} else {
+		/*
+		 * Schedule PWM work as usual unless we are going into suspend
+		 */
+		mutex_lock(&lm->lock);
+		if (likely(!lm->pm_suspend))
+			schedule_work(&pwm->work);
+		else
+			lm8323_pwm_work(&pwm->work);
+		mutex_unlock(&lm->lock);
+	}
+}
+
+static ssize_t lm8323_pwm_show_time(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+
+	return sprintf(buf, "%d\n", pwm->fade_time);
+}
+
+static ssize_t lm8323_pwm_store_time(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	char *endptr = NULL;
+	int time;
+
+	time = simple_strtoul(buf, &endptr, 10);
+	/* Numbers only, please. */
+	if (endptr && *endptr != '\n' && *(endptr + 1) != '\0')
+		return -EINVAL;
+
+	pwm->fade_time = time;
+
+	return strlen(buf);
+}
+static DEVICE_ATTR(time, 0644, lm8323_pwm_show_time, lm8323_pwm_store_time);
+
+static int init_pwm(struct lm8323_chip *lm, int id, struct device *dev,
+		    const char *name)
+{
+	struct lm8323_pwm *pwm = NULL;
+
+	BUG_ON(id > 3);
+
+	switch (id) {
+	case 1:
+		pwm = &lm->pwm1;
+		break;
+	case 2:
+		pwm = &lm->pwm2;
+		break;
+	case 3:
+		pwm = &lm->pwm3;
+		break;
+	}
+
+	pwm->id = id;
+	pwm->fade_time = 0;
+	pwm->brightness = 0;
+	pwm->desired_brightness = 0;
+	if (name) {
+		pwm->cdev.name = name;
+		pwm->cdev.brightness_set = lm8323_pwm_set_brightness;
+		if (led_classdev_register(dev, &pwm->cdev) < 0) {
+			dev_err(dev, "couldn't register PWM %d\n", id);
+			return -1;
+		}
+		if (device_create_file(pwm->cdev.dev,
+					     &dev_attr_time) < 0) {
+			dev_err(dev, "couldn't register time attribute\n");
+			led_classdev_unregister(&pwm->cdev);
+			return -1;
+		}
+		INIT_WORK(&pwm->work, lm8323_pwm_work);
+		pwm->enabled = 1;
+	} else {
+		pwm->enabled = 0;
+	}
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver;
+
+static ssize_t lm8323_show_disable(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", !lm->kp_enabled);
+}
+
+static ssize_t lm8323_set_disable(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+	char *endp;
+	int i;
+
+	i = simple_strtoul(buf, &endp, 10);
+
+	mutex_lock(&lm->lock);
+	lm->kp_enabled = !i;
+	mutex_unlock(&lm->lock);
+
+	return count;
+}
+static DEVICE_ATTR(disable_kp, 0644, lm8323_show_disable, lm8323_set_disable);
+
+static int lm8323_probe(struct i2c_client *client)
+{
+	struct input_dev *idev;
+	struct lm8323_chip *lm;
+	int i, err = 0;
+	unsigned long tmo;
+	u8 data[2];
+
+	lm = kzalloc(sizeof *lm, GFP_KERNEL);
+	if (!lm)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, lm);
+	lm->client = client;
+	lm8323_pdata = client->dev.platform_data;
+	if (!lm8323_pdata)
+		return -EINVAL; /* ? */
+
+	lm->size_x = lm8323_pdata->size_x;
+	if (lm->size_x == 0) {
+		lm->size_x = 8;
+	} else if (lm->size_x > 8) {
+		dev_err(&client->dev, "invalid x size %d specified\n", lm->size_x);
+		lm->size_x = 8;
+	}
+
+	lm->size_y = lm8323_pdata->size_y;
+	if (lm->size_y == 0) {
+		lm->size_y = 12;
+	} else if (lm->size_y > 12) {
+		dev_err(&client->dev, "invalid y size %d specified\n", lm->size_y);
+		lm->size_x = 12;
+	}
+
+	debug(&c->dev, "Keypad size: %d x %d\n", lm->size_x, lm->size_y);
+
+	lm->debounce_time = lm8323_pdata->debounce_time;
+	if (lm->debounce_time == 0) /* Default. */
+		lm->debounce_time = 12;
+	else if (lm->debounce_time == -1) /* Disable debounce. */
+		lm->debounce_time = 0;
+
+	lm->active_time = lm8323_pdata->active_time;
+	if (lm->active_time == 0) /* Default. */
+		lm->active_time = 500;
+	else if (lm->active_time == -1) /* Disable sleep. */
+		lm->active_time = 0;
+
+	lm8323_reset(lm);
+
+	/* Nothing's set up to service the IRQ yet, so just spin for max.
+	 * 100ms until we can configure. */
+	tmo = jiffies + msecs_to_jiffies(100);
+	while (lm8323_read(lm, LM8323_CMD_READ_INT, data, 1) == 1) {
+		if (data[0] & INT_NOINIT)
+			break;
+
+		if (time_after(jiffies, tmo)) {
+			dev_err(&client->dev, "timeout waiting for initialisation\n");
+			break;
+		}
+
+		msleep(1);
+	}
+	lm8323_configure(lm);
+
+	/* If a true probe check the device */
+	if (lm8323_read_id(lm, data) != 0) {
+		dev_err(&client->dev, "device not found\n");
+		err = -ENODEV;
+		goto fail2;
+	}
+
+	if (init_pwm(lm, 1, &client->dev, lm8323_pdata->pwm1_name) < 0)
+		goto fail3;
+	if (init_pwm(lm, 2, &client->dev, lm8323_pdata->pwm2_name) < 0)
+		goto fail4;
+	if (init_pwm(lm, 3, &client->dev, lm8323_pdata->pwm3_name) < 0)
+		goto fail5;
+
+	lm->irq = lm8323_pdata->irq_gpio;
+	debug(&c->dev, "IRQ: %d\n", lm->irq);
+
+	mutex_init(&lm->lock);
+	INIT_WORK(&lm->work, lm8323_work);
+
+	err = request_irq(client->irq, lm8323_irq,
+			  IRQF_TRIGGER_FALLING | IRQF_DISABLED |
+			  IRQF_SAMPLE_RANDOM, DRIVER_NAME, lm);
+	if (err) {
+		dev_err(&client->dev, "could not get IRQ %d\n", lm->irq);
+		goto fail6;
+	}
+
+	set_irq_wake(lm->irq, 1);
+
+	lm->kp_enabled = 1;
+	err = device_create_file(&client->dev, &dev_attr_disable_kp);
+	if (err < 0)
+		goto fail7;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		err = -ENOMEM;
+		goto fail8;
+	}
+
+	if (lm8323_pdata->name)
+		idev->name = lm8323_pdata->name;
+	else
+		idev->name = "LM8323 keypad";
+	snprintf(lm->phys, sizeof(lm->phys), "%s/input-kp", client->dev.bus_id);
+	idev->phys = lm->phys;
+
+	lm->keys_down = 0;
+	idev->evbit[0] = BIT(EV_KEY);
+	for (i = 0; i < LM8323_KEYMAP_SIZE; i++) {
+		if (lm8323_pdata->keymap[i] > 0)
+			set_bit(lm8323_pdata->keymap[i], idev->keybit);
+
+		lm->keymap[i] = lm8323_pdata->keymap[i];
+	}
+
+	if (lm8323_pdata->repeat)
+		set_bit(EV_REP, idev->evbit);
+
+	lm->idev = idev;
+	if (input_register_device(idev)) {
+		dev_dbg(&client->dev, "error registering input device\n");
+		goto fail8;
+	}
+
+	return 0;
+
+fail8:
+	device_remove_file(&client->dev, &dev_attr_disable_kp);
+fail7:
+	free_irq(lm->irq, lm);
+fail6:
+	if (lm->pwm3.enabled)
+		led_classdev_unregister(&lm->pwm3.cdev);
+fail5:
+	if (lm->pwm2.enabled)
+		led_classdev_unregister(&lm->pwm2.cdev);
+fail4:
+	if (lm->pwm1.enabled)
+		led_classdev_unregister(&lm->pwm1.cdev);
+fail3:
+fail2:
+	kfree(lm);
+	return err;
+}
+
+static int lm8323_remove(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	free_irq(lm->irq, lm);
+	device_remove_file(&lm->client->dev, &dev_attr_disable_kp);
+
+	return 0;
+}
+
+/*
+ * We don't need to explicitly suspend the chip, as it already switches off
+ * when there's no activity.
+ */
+static int lm8323_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	set_irq_wake(lm->irq, 0);
+	disable_irq(lm->irq);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 1;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_suspend(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_suspend(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_suspend(&lm->pwm3.cdev);
+
+	return 0;
+}
+
+static int lm8323_resume(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 0;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_resume(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_resume(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_resume(&lm->pwm3.cdev);
+
+	enable_irq(lm->irq);
+	set_irq_wake(lm->irq, 1);
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver = {
+	.driver = {
+		.name	 = DRIVER_NAME,
+	},
+	.probe		= lm8323_probe,
+	.remove		= __exit_p(lm8323_remove),
+	.suspend	= lm8323_suspend,
+	.resume		= lm8323_resume,
+};
+
+static int __init lm8323_init(void)
+{
+	return i2c_add_driver(&lm8323_i2c_driver);
+}
+
+static void __exit lm8323_exit(void)
+{
+	i2c_del_driver(&lm8323_i2c_driver);
+}
+
+MODULE_AUTHOR("Daniel Stone");
+MODULE_DESCRIPTION("LM8323 keypad driver");
+MODULE_LICENSE("GPL");
+
+module_init(lm8323_init);
+module_exit(lm8323_exit);
diff --git a/include/linux/i2c/lm8323.h b/include/linux/i2c/lm8323.h
new file mode 100644
index 0000000..2f0c213
--- /dev/null
+++ b/include/linux/i2c/lm8323.h
@@ -0,0 +1,45 @@
+/*
+ * include/lm8323.h
+ *
+ * Configuration for LM8323 keypad driver.
+ */
+
+#ifndef __LINUX_LM8323_H
+#define __LINUX_LM8323_H
+
+#include <linux/types.h>
+
+/*
+ * Largest keycode that the chip can send, plus one,
+ * so keys can be mapped directly at the index of the
+ * LM8323 keycode instead of subtracting one.
+ */
+#define LM8323_KEYMAP_SIZE (0x7f + 1)
+
+/*
+ * Keymap is an array indexed with chip keycode, the array entry being
+ * the evdev (not AT) keycode.
+ */
+typedef s16 lm8323_km_t[LM8323_KEYMAP_SIZE];
+
+struct lm8323_platform_data {
+	u16 irq_gpio;
+
+	int debounce_time; /* Time to watch for key bouncing, in ms. */
+	int active_time; /* Idle time until sleep, in ms. */
+
+	int size_x;
+	int size_y;
+	int repeat : 1;
+	const s16 *keymap;
+
+	char *pwm1_name; /* Device name for PWM1. */
+	char *pwm2_name; /* Device name for PWM2. */
+	char *pwm3_name; /* Device name for PWM3. */
+
+	char *name; /* Device name. */
+};
+
+void __init lm8323_set_platform_data(struct lm8323_platform_data *pdata);
+
+#endif /* __LINUX_LM8323_H */
-- 
1.5.5.rc3


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 10:09 [PATCH 0/5] resend n810 drivers Felipe Balbi
@ 2008-04-09 10:09 ` Felipe Balbi
  2008-04-09 10:54   ` Eduardo Valentin
  0 siblings, 1 reply; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 10:09 UTC (permalink / raw)
  To: linux-omap; +Cc: Tony Lindgren, Daniel Stone, Felipe Balbi

From: Daniel Stone <daniel.stone@nokia.com>

Introduce lm8323 keypad driver.

Signed-off-by: Daniel Stone <daniel.stone@nokia.com

Updated to build with recent linux-omap and new-style
i2c driver.

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/mach-omap2/board-n800.c |   77 ++++
 arch/arm/mach-omap2/board-n810.c |    2 +
 drivers/input/keyboard/Kconfig   |    7 +
 drivers/input/keyboard/Makefile  |    1 +
 drivers/input/keyboard/lm8323.c  |  921 ++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/lm8323.h       |   45 ++
 6 files changed, 1053 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/lm8323.c
 create mode 100644 include/linux/i2c/lm8323.h

diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
index 758e2c1..367e518 100644
--- a/arch/arm/mach-omap2/board-n800.c
+++ b/arch/arm/mach-omap2/board-n800.c
@@ -23,6 +23,7 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
 #include <asm/mach/arch.h>
@@ -48,6 +49,76 @@
 #define N800_DAV_IRQ_GPIO		103
 #define N800_TSC2301_RESET_GPIO		118
 
+#ifdef CONFIG_MACH_NOKIA_N810
+static s16 rx44_keymap[LM8323_KEYMAP_SIZE] = {
+	[0x01] = KEY_Q,
+	[0x02] = KEY_K,
+	[0x03] = KEY_O,
+	[0x04] = KEY_P,
+	[0x05] = KEY_BACKSPACE,
+	[0x06] = KEY_A,
+	[0x07] = KEY_S,
+	[0x08] = KEY_D,
+	[0x09] = KEY_F,
+	[0x0a] = KEY_G,
+	[0x0b] = KEY_H,
+	[0x0c] = KEY_J,
+
+	[0x11] = KEY_W,
+	[0x12] = KEY_F4,
+	[0x13] = KEY_L,
+	[0x14] = KEY_APOSTROPHE,
+	[0x16] = KEY_Z,
+	[0x17] = KEY_X,
+	[0x18] = KEY_C,
+	[0x19] = KEY_V,
+	[0x1a] = KEY_B,
+	[0x1b] = KEY_N,
+	[0x1c] = KEY_LEFTSHIFT, /* Actually, this is both shift keys */
+	[0x1f] = KEY_F7,
+
+	[0x21] = KEY_E,
+	[0x22] = KEY_SEMICOLON,
+	[0x23] = KEY_MINUS,
+	[0x24] = KEY_EQUAL,
+	[0x2b] = KEY_FN,
+	[0x2c] = KEY_M,
+	[0x2f] = KEY_F8,
+
+	[0x31] = KEY_R,
+	[0x32] = KEY_RIGHTCTRL,
+	[0x34] = KEY_SPACE,
+	[0x35] = KEY_COMMA,
+	[0x37] = KEY_UP,
+	[0x3c] = KEY_COMPOSE,
+	[0x3f] = KEY_F6,
+
+	[0x41] = KEY_T,
+	[0x44] = KEY_DOT,
+	[0x46] = KEY_RIGHT,
+	[0x4f] = KEY_F5,
+	[0x51] = KEY_Y,
+	[0x53] = KEY_DOWN,
+	[0x55] = KEY_ENTER,
+	[0x5f] = KEY_ESC,
+
+	[0x61] = KEY_U,
+	[0x64] = KEY_LEFT,
+
+	[0x71] = KEY_I,
+	[0x75] = KEY_KPENTER,
+};
+
+static struct lm8323_platform_data lm8323_pdata = {
+	.repeat = 0, /* Repeat is handled in userspace for now. */
+	.keymap = rx44_keymap,
+
+	.name = "Internal keyboard",
+	.pwm1_name = "keyboard",
+	.pwm2_name = "cover",
+};
+#endif
+
 void __init nokia_n800_init_irq(void)
 {
 	omap2_init_common_hw();
@@ -502,6 +573,12 @@ static struct i2c_board_info __initdata n800_i2c_board_info_2[] = {
 		I2C_BOARD_INFO("tea5761", 0x10),
 	},
 #endif
+	{
+		I2C_BOARD_INFO("lm8323", 0x45),
+		.type		= "lm8323",
+		.irq		= OMAP_GPIO_IRQ(109),
+		.platform_data	= &lm8323_pdata,
+	},
 };
 
 void __init nokia_n800_common_init(void)
diff --git a/arch/arm/mach-omap2/board-n810.c b/arch/arm/mach-omap2/board-n810.c
index c4f4dd5..fb0e61f 100644
--- a/arch/arm/mach-omap2/board-n810.c
+++ b/arch/arm/mach-omap2/board-n810.c
@@ -10,6 +10,8 @@
  */
 
 #include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 1c22930..137f7e4 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -285,6 +285,13 @@ config KEYBOARD_TSC2301
 	help
 	  Say Y here for if you are using the keypad features of TSC2301.
 
+config KEYBOARD_LM8323
+	tristate "LM8323 keypad chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for the National Semiconductor
+	  LM8323 keypad controller.
+
 config KEYBOARD_PXA27x
 	tristate "PXA27x/PXA3xx keypad support"
 	depends on PXA27x || PXA3xx
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index bc0bbc1..ec447cd 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
 obj-$(CONFIG_KEYBOARD_TSC2301)		+= tsc2301_kp.o
+obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= omap-twl4030keypad.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_AAED2000)		+= aaed2000_kbd.o
diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c
new file mode 100644
index 0000000..f43451d
--- /dev/null
+++ b/drivers/input/keyboard/lm8323.c
@@ -0,0 +1,921 @@
+/*
+ * drivers/i2c/chips/lm8323.c
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Written by Daniel Stone <daniel.stone@nokia.com>
+ *            Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 2 of the License only).
+ *
+ * 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/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/i2c/lm8323.h>
+
+#include <asm/mach-types.h>
+#include <asm/mach/irq.h>
+
+#ifdef VERBOSE
+#define debug dev_dbg
+#else
+#define debug(...)
+#endif
+
+/* Commands to send to the chip. */
+#define LM8323_CMD_READ_ID		0x80 /* Read chip ID. */
+#define LM8323_CMD_WRITE_CFG		0x81 /* Set configuration item. */
+#define LM8323_CMD_READ_INT		0x82 /* Get interrupt status. */
+#define LM8323_CMD_RESET		0x83 /* Reset, same as external one */
+#define LM8323_CMD_WRITE_PORT_SEL	0x85 /* Set GPIO in/out. */
+#define LM8323_CMD_WRITE_PORT_STATE	0x86 /* Set GPIO pullup. */
+#define LM8323_CMD_READ_PORT_SEL	0x87 /* Get GPIO in/out. */
+#define LM8323_CMD_READ_PORT_STATE	0x88 /* Get GPIO pullup. */
+#define LM8323_CMD_READ_FIFO		0x89 /* Read byte from FIFO. */
+#define LM8323_CMD_RPT_READ_FIFO	0x8a /* Read FIFO (no increment). */
+#define LM8323_CMD_SET_ACTIVE		0x8b /* Set active time. */
+#define LM8323_CMD_READ_ERR		0x8c /* Get error status. */
+#define LM8323_CMD_READ_ROTATOR		0x8e /* Read rotator status. */
+#define LM8323_CMD_SET_DEBOUNCE		0x8f /* Set debouncing time. */
+#define LM8323_CMD_SET_KEY_SIZE		0x90 /* Set keypad size. */
+#define LM8323_CMD_READ_KEY_SIZE	0x91 /* Get keypad size. */
+#define LM8323_CMD_READ_CFG		0x92 /* Get configuration item. */
+#define LM8323_CMD_WRITE_CLOCK		0x93 /* Set clock config. */
+#define LM8323_CMD_READ_CLOCK		0x94 /* Get clock config. */
+#define LM8323_CMD_PWM_WRITE		0x95 /* Write PWM script. */
+#define LM8323_CMD_START_PWM		0x96 /* Start PWM engine. */
+#define LM8323_CMD_STOP_PWM		0x97 /* Stop PWM engine. */
+
+/* Interrupt status. */
+#define INT_KEYPAD			0x01 /* Key event. */
+#define INT_ROTATOR			0x02 /* Rotator event. */
+#define INT_ERROR			0x08 /* Error: use CMD_READ_ERR. */
+#define INT_NOINIT			0x10 /* Lost configuration. */
+#define INT_PWM1			0x20 /* PWM1 stopped. */
+#define INT_PWM2			0x40 /* PWM2 stopped. */
+#define INT_PWM3			0x80 /* PWM3 stopped. */
+
+/* Errors (signalled by INT_ERROR, read with CMD_READ_ERR). */
+#define ERR_BADPAR			0x01 /* Bad parameter. */
+#define ERR_CMDUNK			0x02 /* Unknown command. */
+#define ERR_KEYOVR			0x04 /* Too many keys pressed. */
+#define ERR_FIFOOVER			0x40 /* FIFO overflow. */
+
+/* Configuration keys (CMD_{WRITE,READ}_CFG). */
+#define CFG_MUX1SEL			0x01 /* Select MUX1_OUT input. */
+#define CFG_MUX1EN			0x02 /* Enable MUX1_OUT. */
+#define CFG_MUX2SEL			0x04 /* Select MUX2_OUT input. */
+#define CFG_MUX2EN			0x08 /* Enable MUX2_OUT. */
+#define CFG_PSIZE			0x20 /* Package size (must be 0). */
+#define CFG_ROTEN			0x40 /* Enable rotator. */
+
+/* Clock settings (CMD_{WRITE,READ}_CLOCK). */
+#define CLK_RCPWM_INTERNAL		0x00
+#define CLK_RCPWM_EXTERNAL		0x03
+#define CLK_SLOWCLKEN			0x08 /* Enable 32.768kHz clock. */
+#define CLK_SLOWCLKOUT			0x40 /* Enable slow pulse output. */
+
+/* The possible addresses corresponding to CONFIG1 and CONFIG2 pin wirings. */
+#define LM8323_I2C_ADDR00		(0x84 >> 1)	/* 1000 010x */
+#define LM8323_I2C_ADDR01		(0x86 >> 1)	/* 1000 011x */
+#define LM8323_I2C_ADDR10		(0x88 >> 1)	/* 1000 100x */
+#define LM8323_I2C_ADDR11		(0x8A >> 1)	/* 1000 101x */
+
+/* Key event fifo length */
+#define LM8323_FIFO_LEN			15
+
+/* Commands for PWM engine; feed in with PWM_WRITE. */
+/* Load ramp counter from duty cycle field (range 0 - 0xff). */
+#define PWM_SET(v)			(0x4000 | ((v) & 0xff))
+/* Go to start of script. */
+#define PWM_GOTOSTART			0x0000
+/*
+ * Stop engine (generates interrupt).  If reset is 1, clear the program
+ * counter, else leave it.
+ */
+#define PWM_END(reset)			(0xc000 | (!!(reset) << 11))
+/*
+ * Ramp.  If s is 1, divide clock by 512, else divide clock by 16.
+ * Take t clock scales (up to 63) per step, for n steps (up to 126).
+ * If u is set, ramp up, else ramp down.
+ */
+#define PWM_RAMP(s, t, n, u)		((!!(s) << 14) | ((t) & 0x3f) << 8 | \
+					 ((n) & 0x7f) | ((u) ? 0 : 0x80))
+/*
+ * Loop (i.e. jump back to pos) for a given number of iterations (up to 63).
+ * If cnt is zero, execute until PWM_END is encountered.
+ */
+#define PWM_LOOP(cnt, pos)		(0xa000 | (((cnt) & 0x3f) << 7) | \
+					 ((pos) & 0x3f))
+/*
+ * Wait for trigger.  Argument is a mask of channels, shifted by the channel
+ * number, e.g. 0xa for channels 3 and 1.  Note that channels are numbered
+ * from 1, not 0.
+ */
+#define PWM_WAIT_TRIG(chans)		(0xe000 | (((chans) & 0x7) << 6))
+/* Send trigger.  Argument is same as PWM_WAIT_TRIG. */
+#define PWM_SEND_TRIG(chans)		(0xe000 | ((chans) & 0x7))
+
+#define DRIVER_NAME  "lm8323"
+
+static unsigned short normal_i2c[] =
+{
+	LM8323_I2C_ADDR00, LM8323_I2C_ADDR01,
+	LM8323_I2C_ADDR10, LM8323_I2C_ADDR11,
+	I2C_CLIENT_END
+};
+
+I2C_CLIENT_INSMOD;
+
+struct lm8323_pwm {
+	int			id;
+	int			enabled;
+	int			fade_time;
+	int			brightness;
+	int			desired_brightness;
+	struct work_struct	work;
+	struct led_classdev	cdev;
+};
+
+struct lm8323_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	struct work_struct	work;
+	struct input_dev	*idev;
+	int			irq;
+	unsigned		kp_enabled : 1;
+	unsigned		pm_suspend : 1;
+	unsigned		keys_down;
+	char			phys[32];
+	s16			keymap[LM8323_KEYMAP_SIZE];
+	int			size_x;
+	int			size_y;
+	int			debounce_time;
+	int			active_time;
+	struct lm8323_pwm	pwm1;
+	struct lm8323_pwm	pwm2;
+	struct lm8323_pwm	pwm3;
+};
+
+#define client_to_lm8323(c)	container_of(c, struct lm8323_chip, client)
+#define dev_to_lm8323(d)	container_of(d, struct lm8323_chip, client->dev)
+#define work_to_lm8323(w)	container_of(w, struct lm8323_chip, work)
+#define cdev_to_pwm(c)		container_of(c, struct lm8323_pwm, cdev)
+#define work_to_pwm(w)		container_of(w, struct lm8323_pwm, work)
+
+static struct lm8323_chip *pwm_to_lm8323(struct lm8323_pwm *pwm)
+{
+	switch (pwm->id) {
+	case 1:
+		return container_of(pwm, struct lm8323_chip, pwm1);
+	case 2:
+		return container_of(pwm, struct lm8323_chip, pwm2);
+	case 3:
+		return container_of(pwm, struct lm8323_chip, pwm3);
+	default:
+		return NULL;
+	}
+}
+
+static struct lm8323_platform_data *lm8323_pdata = NULL;
+
+
+#define LM8323_MAX_DATA 8
+
+/*
+ * To write, we just access the chip's address in write mode, and dump the
+ * command and data out on the bus.  The command byte and data are taken as
+ * sequential u8s out of varargs, to a maximum of LM8323_MAX_DATA.
+ */
+static int lm8323_write(struct lm8323_chip *lm, int len, ...)
+{
+	int ret, i;
+	va_list ap;
+	u8 data[LM8323_MAX_DATA];
+
+	va_start(ap, len);
+
+	if (unlikely(len > LM8323_MAX_DATA)) {
+		dev_err(&lm->client->dev, "tried to send %d bytes\n", len);
+		va_end(ap);
+		return 0;
+	}
+
+	for (i = 0; i < len; i++)
+		data[i] = va_arg(ap, int);
+
+	va_end(ap);
+
+	/*
+	 * If the host is asleep while we send the data, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "sent %d bytes of %d total\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * To read, we first send the command byte to the chip and end the transaction,
+ * then access the chip in read mode, at which point it will send the data.
+ */
+static int lm8323_read(struct lm8323_chip *lm, u8 cmd, u8 *buf, int len)
+{
+	int ret;
+
+	/*
+	 * If the host is asleep while we send the byte, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret != 1)) {
+		dev_err(&lm->client->dev, "sending read cmd 0x%02x failed\n",
+			cmd);
+		return 0;
+	}
+
+	ret = i2c_master_recv(lm->client, buf, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "wanted %d bytes, got %d\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * Set the chip active time (idle time before it enters halt).
+ */
+static void lm8323_set_active_time(struct lm8323_chip *lm, int time)
+{
+	lm8323_write(lm, 2, LM8323_CMD_SET_ACTIVE, time >> 2);
+}
+
+/*
+ * The signals are AT-style: the low 7 bits are the keycode, and the top
+ * bit indicates the state (1 for down, 0 for up).
+ */
+static inline u8 lm8323_whichkey(u8 event)
+{
+	return event & 0x7f;
+}
+
+static inline int lm8323_ispress(u8 event)
+{
+	return (event & 0x80) ? 1 : 0;
+}
+
+static void process_keys(struct lm8323_chip *lm)
+{
+	u8 event;
+	u8 key_fifo[LM8323_FIFO_LEN + 1];
+	int old_keys_down = lm->keys_down;
+	int ret;
+	int i = 0;
+
+	/*
+	 * Read all key events from the FIFO at once. Next READ_FIFO clears the
+	 * FIFO even if we didn't read all events previously.
+	 */
+	ret = lm8323_read(lm, LM8323_CMD_READ_FIFO, key_fifo, LM8323_FIFO_LEN);
+
+	if (ret < 0) {
+		dev_err(&lm->client->dev, "Failed reading fifo \n");
+		return;
+	}
+	key_fifo[ret] = 0;
+
+	while ((event = key_fifo[i])) {
+		u8 key = lm8323_whichkey(event);
+		int isdown = lm8323_ispress(event);
+		s16 keycode = lm->keymap[key];
+
+		if (likely(keycode > 0)) {
+			debug(&lm->client->dev, "key 0x%02x %s\n", key,
+			      isdown ? "down" : "up");
+			if (likely(lm->kp_enabled)) {
+				input_report_key(lm->idev, keycode, isdown);
+				input_sync(lm->idev);
+			}
+			if (isdown)
+				lm->keys_down++;
+			else
+				lm->keys_down--;
+		} else {
+			dev_err(&lm->client->dev, "keycode 0x%02x not mapped "
+				"to any key\n", key);
+		}
+		i++;
+	}
+
+	/*
+	 * Errata: We need to ensure that the chip never enters halt mode
+	 * during a keypress, so set active time to 0.  When it's released,
+	 * we can enter halt again, so set the active time back to normal.
+	 */
+	if (!old_keys_down && lm->keys_down)
+		lm8323_set_active_time(lm, 0);
+	if (old_keys_down && !lm->keys_down)
+		lm8323_set_active_time(lm, lm->active_time);
+}
+
+static void lm8323_process_error(struct lm8323_chip *lm)
+{
+	u8 error;
+
+	if (lm8323_read(lm, LM8323_CMD_READ_ERR, &error, 1) == 1) {
+		if (error & ERR_FIFOOVER)
+			debug(&lm->client->dev, "fifo overflow!\n");
+		if (error & ERR_KEYOVR)
+			debug(&lm->client->dev, "more than two keys pressed\n");
+		if (error & ERR_CMDUNK)
+			debug(&lm->client->dev, "unknown command submitted\n");
+		if (error & ERR_BADPAR)
+			debug(&lm->client->dev, "bad command parameter\n");
+	}
+}
+
+static void lm8323_reset(struct lm8323_chip *lm)
+{
+	/* The docs say we must pass 0xAA as the data byte. */
+	lm8323_write(lm, 2, LM8323_CMD_RESET, 0xAA);
+}
+
+static int lm8323_configure(struct lm8323_chip *lm)
+{
+	int keysize = (lm->size_x << 4) | lm->size_y;
+	int clock = (CLK_SLOWCLKEN | CLK_RCPWM_EXTERNAL);
+	int debounce = lm->debounce_time >> 2;
+	int active = lm->active_time >> 2;
+
+	/*
+	 * Active time must be greater than the debounce time: if it's
+	 * a close-run thing, give ourselves a 12ms buffer.
+	 */
+	if (debounce >= active)
+		active = debounce + 3;
+
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CFG, 0);
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CLOCK, clock);
+	lm8323_write(lm, 2, LM8323_CMD_SET_KEY_SIZE, keysize);
+	lm8323_set_active_time(lm, lm->active_time);
+	lm8323_write(lm, 2, LM8323_CMD_SET_DEBOUNCE, debounce);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_STATE, 0xff, 0xff);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_SEL, 0, 0);
+
+	/*
+	 * Not much we can do about errors at this point, so just hope
+	 * for the best.
+	 */
+
+	return 0;
+}
+
+/*
+ * Bottom half: handle the interrupt by posting key events, or dealing with
+ * errors appropriately.
+ */
+static void lm8323_work(struct work_struct *work)
+{
+	struct lm8323_chip *lm = work_to_lm8323(work);
+	u8 ints;
+
+	mutex_lock(&lm->lock);
+
+	while ((lm8323_read(lm, LM8323_CMD_READ_INT, &ints, 1) == 1) && ints) {
+		if (likely(ints & INT_KEYPAD)) {
+			process_keys(lm);
+		}
+		if (ints & INT_ROTATOR) {
+			/* We don't currently support the rotator. */
+			debug(&lm->client->dev, "rotator fired\n");
+		}
+		if (ints & INT_ERROR) {
+			debug(&lm->client->dev, "error!\n");
+			lm8323_process_error(lm);
+		}
+		if (ints & INT_NOINIT) {
+			dev_err(&lm->client->dev, "chip lost config; "
+						  "reinitialising\n");
+			lm8323_configure(lm);
+		}
+		if (ints & INT_PWM1) {
+			debug(&lm->client->dev, "pwm1 engine completed\n");
+		}
+		if (ints & INT_PWM2) {
+			debug(&lm->client->dev, "pwm2 engine completed\n");
+		}
+		if (ints & INT_PWM3) {
+			debug(&lm->client->dev, "pwm3 engine completed\n");
+		}
+	}
+
+	mutex_unlock(&lm->lock);
+}
+
+/*
+ * We cannot use I2C in interrupt context, so we just schedule work.
+ */
+static irqreturn_t lm8323_irq(int irq, void *data)
+{
+	struct lm8323_chip *lm = data;
+
+	schedule_work(&lm->work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Read the chip ID.
+ */
+static int lm8323_read_id(struct lm8323_chip *lm, u8 *buf)
+{
+	int bytes;
+
+	bytes = lm8323_read(lm, LM8323_CMD_READ_ID, buf, 2);
+	if (unlikely(bytes != 2))
+		return -EIO;
+
+	return 0;
+}
+
+static void lm8323_write_pwm_one(struct lm8323_pwm *pwm, int pos, u16 cmd)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	lm8323_write(lm, 4, LM8323_CMD_PWM_WRITE, (pos << 2) | pwm->id,
+		     (cmd & 0xff00) >> 8, cmd & 0x00ff);
+}
+
+/*
+ * Write a script into a given PWM engine, concluding with PWM_END.
+ * If 'keepalive' is specified, the engine will be kept running
+ * indefinitely.
+ */
+static void lm8323_write_pwm(struct lm8323_pwm *pwm, int keepalive,
+			     int len, ...)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+	int i, cmd;
+	va_list ap;
+
+	/*
+	 * If there are any scripts running at the moment, terminate them
+	 * and make sure the duty cycle is as if it finished.
+	 */
+	lm8323_write(lm, 2, LM8323_CMD_STOP_PWM, pwm->id);
+
+	va_start(ap, len);
+	for (i = 0; i < len; i++) {
+		cmd = va_arg(ap, int);
+		lm8323_write_pwm_one(pwm, i, cmd);
+	}
+	va_end(ap);
+
+	/* Wait for a trigger from any channel. This keeps the engine alive. */
+	if (keepalive)
+		lm8323_write_pwm_one(pwm, i++, PWM_WAIT_TRIG(0xe));
+	else
+		lm8323_write_pwm_one(pwm, i++, PWM_END(1));
+
+	lm8323_write(lm, 2, LM8323_CMD_START_PWM, pwm->id);
+}
+
+static void lm8323_pwm_work(struct work_struct *work)
+{
+	struct lm8323_pwm *pwm = work_to_pwm(work);
+	int div, perstep, steps, hz, direction, keepalive;
+
+	/* Do nothing if we're already at the requested level. */
+	if (pwm->desired_brightness == pwm->brightness)
+		return;
+
+	keepalive = (pwm->desired_brightness > 0);
+	direction = (pwm->desired_brightness > pwm->brightness);
+	steps = abs(pwm->desired_brightness - pwm->brightness);
+
+	/*
+	 * Convert time (in ms) into a divisor (512 or 16 on a refclk of
+	 * 32768Hz), and number of ticks per step.
+	 */
+	if ((pwm->fade_time / steps) > (32768 / 512))
+		div = 512;
+	else
+		div = 16;
+
+	hz = 32768 / div;
+	if (pwm->fade_time < ((steps * 1000) / hz))
+		perstep = 1;
+	else
+		perstep = (hz * pwm->fade_time) / (steps * 1000);
+
+	if (perstep == 0)
+		perstep = 1;
+	else if (perstep > 63)
+		perstep = 63;
+
+	if (steps > 252) {
+		lm8323_write_pwm(pwm, keepalive, 3,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 252,
+					  direction));
+	} else if (steps > 126) {
+		lm8323_write_pwm(pwm, keepalive, 2,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 126,
+					  direction));
+	} else {
+		lm8323_write_pwm(pwm, keepalive, 1,
+				 PWM_RAMP((div == 512), perstep, steps,
+					  direction));
+	}
+
+	pwm->brightness = pwm->desired_brightness;
+}
+
+static void lm8323_pwm_set_brightness(struct led_classdev *led_cdev,
+				      enum led_brightness brightness)
+{
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	pwm->desired_brightness = brightness;
+
+	if (in_interrupt()) {
+		schedule_work(&pwm->work);
+	} else {
+		/*
+		 * Schedule PWM work as usual unless we are going into suspend
+		 */
+		mutex_lock(&lm->lock);
+		if (likely(!lm->pm_suspend))
+			schedule_work(&pwm->work);
+		else
+			lm8323_pwm_work(&pwm->work);
+		mutex_unlock(&lm->lock);
+	}
+}
+
+static ssize_t lm8323_pwm_show_time(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+
+	return sprintf(buf, "%d\n", pwm->fade_time);
+}
+
+static ssize_t lm8323_pwm_store_time(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	char *endptr = NULL;
+	int time;
+
+	time = simple_strtoul(buf, &endptr, 10);
+	/* Numbers only, please. */
+	if (endptr && *endptr != '\n' && *(endptr + 1) != '\0')
+		return -EINVAL;
+
+	pwm->fade_time = time;
+
+	return strlen(buf);
+}
+static DEVICE_ATTR(time, 0644, lm8323_pwm_show_time, lm8323_pwm_store_time);
+
+static int init_pwm(struct lm8323_chip *lm, int id, struct device *dev,
+		    const char *name)
+{
+	struct lm8323_pwm *pwm = NULL;
+
+	BUG_ON(id > 3);
+
+	switch (id) {
+	case 1:
+		pwm = &lm->pwm1;
+		break;
+	case 2:
+		pwm = &lm->pwm2;
+		break;
+	case 3:
+		pwm = &lm->pwm3;
+		break;
+	}
+
+	pwm->id = id;
+	pwm->fade_time = 0;
+	pwm->brightness = 0;
+	pwm->desired_brightness = 0;
+	if (name) {
+		pwm->cdev.name = name;
+		pwm->cdev.brightness_set = lm8323_pwm_set_brightness;
+		if (led_classdev_register(dev, &pwm->cdev) < 0) {
+			dev_err(dev, "couldn't register PWM %d\n", id);
+			return -1;
+		}
+		if (device_create_file(pwm->cdev.dev,
+					     &dev_attr_time) < 0) {
+			dev_err(dev, "couldn't register time attribute\n");
+			led_classdev_unregister(&pwm->cdev);
+			return -1;
+		}
+		INIT_WORK(&pwm->work, lm8323_pwm_work);
+		pwm->enabled = 1;
+	} else {
+		pwm->enabled = 0;
+	}
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver;
+
+static ssize_t lm8323_show_disable(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", !lm->kp_enabled);
+}
+
+static ssize_t lm8323_set_disable(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+	char *endp;
+	int i;
+
+	i = simple_strtoul(buf, &endp, 10);
+
+	mutex_lock(&lm->lock);
+	lm->kp_enabled = !i;
+	mutex_unlock(&lm->lock);
+
+	return count;
+}
+static DEVICE_ATTR(disable_kp, 0644, lm8323_show_disable, lm8323_set_disable);
+
+static int lm8323_probe(struct i2c_client *client)
+{
+	struct input_dev *idev;
+	struct lm8323_chip *lm;
+	int i, err = 0;
+	unsigned long tmo;
+	u8 data[2];
+
+	lm = kzalloc(sizeof *lm, GFP_KERNEL);
+	if (!lm)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, lm);
+	lm->client = client;
+	lm8323_pdata = client->dev.platform_data;
+	if (!lm8323_pdata)
+		return -EINVAL; /* ? */
+
+	lm->size_x = lm8323_pdata->size_x;
+	if (lm->size_x == 0) {
+		lm->size_x = 8;
+	} else if (lm->size_x > 8) {
+		dev_err(&client->dev, "invalid x size %d specified\n", lm->size_x);
+		lm->size_x = 8;
+	}
+
+	lm->size_y = lm8323_pdata->size_y;
+	if (lm->size_y == 0) {
+		lm->size_y = 12;
+	} else if (lm->size_y > 12) {
+		dev_err(&client->dev, "invalid y size %d specified\n", lm->size_y);
+		lm->size_x = 12;
+	}
+
+	debug(&c->dev, "Keypad size: %d x %d\n", lm->size_x, lm->size_y);
+
+	lm->debounce_time = lm8323_pdata->debounce_time;
+	if (lm->debounce_time == 0) /* Default. */
+		lm->debounce_time = 12;
+	else if (lm->debounce_time == -1) /* Disable debounce. */
+		lm->debounce_time = 0;
+
+	lm->active_time = lm8323_pdata->active_time;
+	if (lm->active_time == 0) /* Default. */
+		lm->active_time = 500;
+	else if (lm->active_time == -1) /* Disable sleep. */
+		lm->active_time = 0;
+
+	lm8323_reset(lm);
+
+	/* Nothing's set up to service the IRQ yet, so just spin for max.
+	 * 100ms until we can configure. */
+	tmo = jiffies + msecs_to_jiffies(100);
+	while (lm8323_read(lm, LM8323_CMD_READ_INT, data, 1) == 1) {
+		if (data[0] & INT_NOINIT)
+			break;
+
+		if (time_after(jiffies, tmo)) {
+			dev_err(&client->dev, "timeout waiting for initialisation\n");
+			break;
+		}
+
+		msleep(1);
+	}
+	lm8323_configure(lm);
+
+	/* If a true probe check the device */
+	if (lm8323_read_id(lm, data) != 0) {
+		dev_err(&client->dev, "device not found\n");
+		err = -ENODEV;
+		goto fail2;
+	}
+
+	if (init_pwm(lm, 1, &client->dev, lm8323_pdata->pwm1_name) < 0)
+		goto fail3;
+	if (init_pwm(lm, 2, &client->dev, lm8323_pdata->pwm2_name) < 0)
+		goto fail4;
+	if (init_pwm(lm, 3, &client->dev, lm8323_pdata->pwm3_name) < 0)
+		goto fail5;
+
+	lm->irq = lm8323_pdata->irq_gpio;
+	debug(&c->dev, "IRQ: %d\n", lm->irq);
+
+	mutex_init(&lm->lock);
+	INIT_WORK(&lm->work, lm8323_work);
+
+	err = request_irq(client->irq, lm8323_irq,
+			  IRQF_TRIGGER_FALLING | IRQF_DISABLED |
+			  IRQF_SAMPLE_RANDOM, DRIVER_NAME, lm);
+	if (err) {
+		dev_err(&client->dev, "could not get IRQ %d\n", lm->irq);
+		goto fail6;
+	}
+
+	set_irq_wake(lm->irq, 1);
+
+	lm->kp_enabled = 1;
+	err = device_create_file(&client->dev, &dev_attr_disable_kp);
+	if (err < 0)
+		goto fail7;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		err = -ENOMEM;
+		goto fail8;
+	}
+
+	if (lm8323_pdata->name)
+		idev->name = lm8323_pdata->name;
+	else
+		idev->name = "LM8323 keypad";
+	snprintf(lm->phys, sizeof(lm->phys), "%s/input-kp", client->dev.bus_id);
+	idev->phys = lm->phys;
+
+	lm->keys_down = 0;
+	idev->evbit[0] = BIT(EV_KEY);
+	for (i = 0; i < LM8323_KEYMAP_SIZE; i++) {
+		if (lm8323_pdata->keymap[i] > 0)
+			set_bit(lm8323_pdata->keymap[i], idev->keybit);
+
+		lm->keymap[i] = lm8323_pdata->keymap[i];
+	}
+
+	if (lm8323_pdata->repeat)
+		set_bit(EV_REP, idev->evbit);
+
+	lm->idev = idev;
+	if (input_register_device(idev)) {
+		dev_dbg(&client->dev, "error registering input device\n");
+		goto fail8;
+	}
+
+	return 0;
+
+fail8:
+	device_remove_file(&client->dev, &dev_attr_disable_kp);
+fail7:
+	free_irq(lm->irq, lm);
+fail6:
+	if (lm->pwm3.enabled)
+		led_classdev_unregister(&lm->pwm3.cdev);
+fail5:
+	if (lm->pwm2.enabled)
+		led_classdev_unregister(&lm->pwm2.cdev);
+fail4:
+	if (lm->pwm1.enabled)
+		led_classdev_unregister(&lm->pwm1.cdev);
+fail3:
+fail2:
+	kfree(lm);
+	return err;
+}
+
+static int lm8323_remove(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	free_irq(lm->irq, lm);
+	device_remove_file(&lm->client->dev, &dev_attr_disable_kp);
+
+	return 0;
+}
+
+/*
+ * We don't need to explicitly suspend the chip, as it already switches off
+ * when there's no activity.
+ */
+static int lm8323_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	set_irq_wake(lm->irq, 0);
+	disable_irq(lm->irq);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 1;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_suspend(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_suspend(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_suspend(&lm->pwm3.cdev);
+
+	return 0;
+}
+
+static int lm8323_resume(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 0;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_resume(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_resume(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_resume(&lm->pwm3.cdev);
+
+	enable_irq(lm->irq);
+	set_irq_wake(lm->irq, 1);
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver = {
+	.driver = {
+		.name	 = DRIVER_NAME,
+	},
+	.probe		= lm8323_probe,
+	.remove		= __exit_p(lm8323_remove),
+	.suspend	= lm8323_suspend,
+	.resume		= lm8323_resume,
+};
+
+static int __init lm8323_init(void)
+{
+	return i2c_add_driver(&lm8323_i2c_driver);
+}
+
+static void __exit lm8323_exit(void)
+{
+	i2c_del_driver(&lm8323_i2c_driver);
+}
+
+MODULE_AUTHOR("Daniel Stone");
+MODULE_DESCRIPTION("LM8323 keypad driver");
+MODULE_LICENSE("GPL");
+
+module_init(lm8323_init);
+module_exit(lm8323_exit);
diff --git a/include/linux/i2c/lm8323.h b/include/linux/i2c/lm8323.h
new file mode 100644
index 0000000..2f0c213
--- /dev/null
+++ b/include/linux/i2c/lm8323.h
@@ -0,0 +1,45 @@
+/*
+ * include/lm8323.h
+ *
+ * Configuration for LM8323 keypad driver.
+ */
+
+#ifndef __LINUX_LM8323_H
+#define __LINUX_LM8323_H
+
+#include <linux/types.h>
+
+/*
+ * Largest keycode that the chip can send, plus one,
+ * so keys can be mapped directly at the index of the
+ * LM8323 keycode instead of subtracting one.
+ */
+#define LM8323_KEYMAP_SIZE (0x7f + 1)
+
+/*
+ * Keymap is an array indexed with chip keycode, the array entry being
+ * the evdev (not AT) keycode.
+ */
+typedef s16 lm8323_km_t[LM8323_KEYMAP_SIZE];
+
+struct lm8323_platform_data {
+	u16 irq_gpio;
+
+	int debounce_time; /* Time to watch for key bouncing, in ms. */
+	int active_time; /* Idle time until sleep, in ms. */
+
+	int size_x;
+	int size_y;
+	int repeat : 1;
+	const s16 *keymap;
+
+	char *pwm1_name; /* Device name for PWM1. */
+	char *pwm2_name; /* Device name for PWM2. */
+	char *pwm3_name; /* Device name for PWM3. */
+
+	char *name; /* Device name. */
+};
+
+void __init lm8323_set_platform_data(struct lm8323_platform_data *pdata);
+
+#endif /* __LINUX_LM8323_H */
-- 
1.5.5.rc3


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 10:09 ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
@ 2008-04-09 10:54   ` Eduardo Valentin
  2008-04-09 11:02     ` Daniel Stone
  0 siblings, 1 reply; 23+ messages in thread
From: Eduardo Valentin @ 2008-04-09 10:54 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Tony Lindgren, Daniel Stone

Hi Balbi and Daniel,

This code has some style problems as well. A simple execution of
checkpatch will produce:
ERROR: do not initialise statics to 0 or NULL
#417: FILE: drivers/input/keyboard/lm8323.c:197:
+static struct lm8323_platform_data *lm8323_pdata = NULL;

WARNING: braces {} are not necessary for single statement blocks
#629: FILE: drivers/input/keyboard/lm8323.c:409:
+               if (likely(ints & INT_KEYPAD)) {
+                       process_keys(lm);
+               }

WARNING: braces {} are not necessary for single statement blocks
#645: FILE: drivers/input/keyboard/lm8323.c:425:
+               if (ints & INT_PWM1) {
+                       debug(&lm->client->dev, "pwm1 engine completed\n");
+               }

WARNING: braces {} are not necessary for single statement blocks
#648: FILE: drivers/input/keyboard/lm8323.c:428:
+               if (ints & INT_PWM2) {
+                       debug(&lm->client->dev, "pwm2 engine completed\n");
+               }

WARNING: braces {} are not necessary for single statement blocks
#651: FILE: drivers/input/keyboard/lm8323.c:431:
+               if (ints & INT_PWM3) {
+                       debug(&lm->client->dev, "pwm3 engine completed\n");
+               }

WARNING: consider using strict_strtoul in preference to simple_strtoul
#823: FILE: drivers/input/keyboard/lm8323.c:603:
+       time = simple_strtoul(buf, &endptr, 10);

WARNING: consider using strict_strtoul in preference to simple_strtoul
#897: FILE: drivers/input/keyboard/lm8323.c:677:
+       i = simple_strtoul(buf, &endp, 10);

WARNING: line over 80 characters
#929: FILE: drivers/input/keyboard/lm8323.c:709:
+               dev_err(&client->dev, "invalid x size %d specified\n",
lm->size_x);

WARNING: line over 80 characters
#937: FILE: drivers/input/keyboard/lm8323.c:717:
+               dev_err(&client->dev, "invalid y size %d specified\n",
lm->size_y);

WARNING: line over 80 characters
#965: FILE: drivers/input/keyboard/lm8323.c:745:
+                       dev_err(&client->dev, "timeout waiting for
initialisation\n");

WARNING: do not add new typedefs
#1170: FILE: include/linux/i2c/lm8323.h:23:
+typedef s16 lm8323_km_t[LM8323_KEYMAP_SIZE];

total: 1 errors, 10 warnings, 1089 lines checked

daniel.diff has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.


I'd suggest using some #defines for lots for magic numbers in this code.

On Wed, Apr 9, 2008 at 1:09 PM, Felipe Balbi <felipe.balbi@nokia.com> wrote:
>
> From: Daniel Stone <daniel.stone@nokia.com>
>
>  Introduce lm8323 keypad driver.
>
>  Signed-off-by: Daniel Stone <daniel.stone@nokia.com
>
>  Updated to build with recent linux-omap and new-style
>  i2c driver.
>
>  Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
>  ---
>   arch/arm/mach-omap2/board-n800.c |   77 ++++
>   arch/arm/mach-omap2/board-n810.c |    2 +
>   drivers/input/keyboard/Kconfig   |    7 +
>   drivers/input/keyboard/Makefile  |    1 +
>   drivers/input/keyboard/lm8323.c  |  921 ++++++++++++++++++++++++++++++++++++++
>   include/linux/i2c/lm8323.h       |   45 ++
>   6 files changed, 1053 insertions(+), 0 deletions(-)
>   create mode 100644 drivers/input/keyboard/lm8323.c
>   create mode 100644 include/linux/i2c/lm8323.h
>
>  diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
>  index 758e2c1..367e518 100644
>  --- a/arch/arm/mach-omap2/board-n800.c
>  +++ b/arch/arm/mach-omap2/board-n800.c
>  @@ -23,6 +23,7 @@
>   #include <linux/interrupt.h>
>   #include <linux/irq.h>
>   #include <linux/i2c.h>
>  +#include <linux/i2c/lm8323.h>
>   #include <asm/hardware.h>
>   #include <asm/mach-types.h>
>   #include <asm/mach/arch.h>
>  @@ -48,6 +49,76 @@
>   #define N800_DAV_IRQ_GPIO              103
>   #define N800_TSC2301_RESET_GPIO                118
>
>  +#ifdef CONFIG_MACH_NOKIA_N810
>  +static s16 rx44_keymap[LM8323_KEYMAP_SIZE] = {
>  +       [0x01] = KEY_Q,
>  +       [0x02] = KEY_K,
>  +       [0x03] = KEY_O,
>  +       [0x04] = KEY_P,
>  +       [0x05] = KEY_BACKSPACE,
>  +       [0x06] = KEY_A,
>  +       [0x07] = KEY_S,
>  +       [0x08] = KEY_D,
>  +       [0x09] = KEY_F,
>  +       [0x0a] = KEY_G,
>  +       [0x0b] = KEY_H,
>  +       [0x0c] = KEY_J,
>  +
>  +       [0x11] = KEY_W,
>  +       [0x12] = KEY_F4,
>  +       [0x13] = KEY_L,
>  +       [0x14] = KEY_APOSTROPHE,
>  +       [0x16] = KEY_Z,
>  +       [0x17] = KEY_X,
>  +       [0x18] = KEY_C,
>  +       [0x19] = KEY_V,
>  +       [0x1a] = KEY_B,
>  +       [0x1b] = KEY_N,
>  +       [0x1c] = KEY_LEFTSHIFT, /* Actually, this is both shift keys */
>  +       [0x1f] = KEY_F7,
>  +
>  +       [0x21] = KEY_E,
>  +       [0x22] = KEY_SEMICOLON,
>  +       [0x23] = KEY_MINUS,
>  +       [0x24] = KEY_EQUAL,
>  +       [0x2b] = KEY_FN,
>  +       [0x2c] = KEY_M,
>  +       [0x2f] = KEY_F8,
>  +
>  +       [0x31] = KEY_R,
>  +       [0x32] = KEY_RIGHTCTRL,
>  +       [0x34] = KEY_SPACE,
>  +       [0x35] = KEY_COMMA,
>  +       [0x37] = KEY_UP,
>  +       [0x3c] = KEY_COMPOSE,
>  +       [0x3f] = KEY_F6,
>  +
>  +       [0x41] = KEY_T,
>  +       [0x44] = KEY_DOT,
>  +       [0x46] = KEY_RIGHT,
>  +       [0x4f] = KEY_F5,
>  +       [0x51] = KEY_Y,
>  +       [0x53] = KEY_DOWN,
>  +       [0x55] = KEY_ENTER,
>  +       [0x5f] = KEY_ESC,
>  +
>  +       [0x61] = KEY_U,
>  +       [0x64] = KEY_LEFT,
>  +
>  +       [0x71] = KEY_I,
>  +       [0x75] = KEY_KPENTER,
>  +};
>  +
>  +static struct lm8323_platform_data lm8323_pdata = {
>  +       .repeat = 0, /* Repeat is handled in userspace for now. */
>  +       .keymap = rx44_keymap,
>  +
>  +       .name = "Internal keyboard",
>  +       .pwm1_name = "keyboard",
>  +       .pwm2_name = "cover",
>  +};
>  +#endif
>  +
>   void __init nokia_n800_init_irq(void)
>   {
>         omap2_init_common_hw();
>  @@ -502,6 +573,12 @@ static struct i2c_board_info __initdata n800_i2c_board_info_2[] = {
>                 I2C_BOARD_INFO("tea5761", 0x10),
>         },
>   #endif
>  +       {
>  +               I2C_BOARD_INFO("lm8323", 0x45),
>  +               .type           = "lm8323",
>  +               .irq            = OMAP_GPIO_IRQ(109),
>  +               .platform_data  = &lm8323_pdata,
>  +       },
>   };
>
>   void __init nokia_n800_common_init(void)
>  diff --git a/arch/arm/mach-omap2/board-n810.c b/arch/arm/mach-omap2/board-n810.c
>  index c4f4dd5..fb0e61f 100644
>  --- a/arch/arm/mach-omap2/board-n810.c
>  +++ b/arch/arm/mach-omap2/board-n810.c
>  @@ -10,6 +10,8 @@
>   */
>
>   #include <linux/init.h>
>  +#include <linux/i2c.h>
>  +#include <linux/i2c/lm8323.h>
>
>   #include <asm/hardware.h>
>   #include <asm/mach-types.h>
>  diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
>  index 1c22930..137f7e4 100644
>
> --- a/drivers/input/keyboard/Kconfig
>  +++ b/drivers/input/keyboard/Kconfig
>  @@ -285,6 +285,13 @@ config KEYBOARD_TSC2301
>         help
>           Say Y here for if you are using the keypad features of TSC2301.
>
>  +config KEYBOARD_LM8323
>  +       tristate "LM8323 keypad chip"
>
>
> +       depends on I2C
>  +       help
>  +         If you say yes here you get support for the National Semiconductor
>  +         LM8323 keypad controller.
>  +
>   config KEYBOARD_PXA27x
>         tristate "PXA27x/PXA3xx keypad support"
>         depends on PXA27x || PXA3xx
>  diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
>  index bc0bbc1..ec447cd 100644
>  --- a/drivers/input/keyboard/Makefile
>  +++ b/drivers/input/keyboard/Makefile
>  @@ -21,6 +21,7 @@ obj-$(CONFIG_KEYBOARD_HIL_OLD)                += hilkbd.o
>   obj-$(CONFIG_KEYBOARD_OMAP)            += omap-keypad.o
>   obj-$(CONFIG_OMAP_PS2)                 += innovator_ps2.o
>   obj-$(CONFIG_KEYBOARD_TSC2301)         += tsc2301_kp.o
>  +obj-$(CONFIG_KEYBOARD_LM8323)          += lm8323.o
>   obj-$(CONFIG_KEYBOARD_TWL4030)         += omap-twl4030keypad.o
>   obj-$(CONFIG_KEYBOARD_PXA27x)          += pxa27x_keypad.o
>   obj-$(CONFIG_KEYBOARD_AAED2000)                += aaed2000_kbd.o
>  diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c
>  new file mode 100644
>  index 0000000..f43451d
>  --- /dev/null
>  +++ b/drivers/input/keyboard/lm8323.c
>  @@ -0,0 +1,921 @@
>  +/*
>  + * drivers/i2c/chips/lm8323.c
>  + *
>  + * Copyright (C) 2007 Nokia Corporation
>  + *
>  + * Written by Daniel Stone <daniel.stone@nokia.com>
>  + *            Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
>  + *
>  + * This program is free software; you can redistribute it and/or modify
>  + * it under the terms of the GNU General Public License as published by
>  + * the Free Software Foundation (version 2 of the License only).
>  + *
>  + * 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/module.h>
>  +#include <linux/i2c.h>
>  +#include <linux/interrupt.h>
>  +#include <linux/sched.h>
>  +#include <linux/mutex.h>
>  +#include <linux/delay.h>
>  +#include <linux/input.h>
>  +#include <linux/leds.h>
>  +#include <linux/i2c/lm8323.h>
>  +
>  +#include <asm/mach-types.h>
>  +#include <asm/mach/irq.h>
>  +
>  +#ifdef VERBOSE
>  +#define debug dev_dbg
>  +#else
>  +#define debug(...)
>  +#endif
>  +
>  +/* Commands to send to the chip. */
>  +#define LM8323_CMD_READ_ID             0x80 /* Read chip ID. */
>  +#define LM8323_CMD_WRITE_CFG           0x81 /* Set configuration item. */
>  +#define LM8323_CMD_READ_INT            0x82 /* Get interrupt status. */
>  +#define LM8323_CMD_RESET               0x83 /* Reset, same as external one */
>  +#define LM8323_CMD_WRITE_PORT_SEL      0x85 /* Set GPIO in/out. */
>  +#define LM8323_CMD_WRITE_PORT_STATE    0x86 /* Set GPIO pullup. */
>  +#define LM8323_CMD_READ_PORT_SEL       0x87 /* Get GPIO in/out. */
>  +#define LM8323_CMD_READ_PORT_STATE     0x88 /* Get GPIO pullup. */
>  +#define LM8323_CMD_READ_FIFO           0x89 /* Read byte from FIFO. */
>  +#define LM8323_CMD_RPT_READ_FIFO       0x8a /* Read FIFO (no increment). */
>  +#define LM8323_CMD_SET_ACTIVE          0x8b /* Set active time. */
>  +#define LM8323_CMD_READ_ERR            0x8c /* Get error status. */
>  +#define LM8323_CMD_READ_ROTATOR                0x8e /* Read rotator status. */
>  +#define LM8323_CMD_SET_DEBOUNCE                0x8f /* Set debouncing time. */
>  +#define LM8323_CMD_SET_KEY_SIZE                0x90 /* Set keypad size. */
>  +#define LM8323_CMD_READ_KEY_SIZE       0x91 /* Get keypad size. */
>  +#define LM8323_CMD_READ_CFG            0x92 /* Get configuration item. */
>  +#define LM8323_CMD_WRITE_CLOCK         0x93 /* Set clock config. */
>  +#define LM8323_CMD_READ_CLOCK          0x94 /* Get clock config. */
>  +#define LM8323_CMD_PWM_WRITE           0x95 /* Write PWM script. */
>  +#define LM8323_CMD_START_PWM           0x96 /* Start PWM engine. */
>  +#define LM8323_CMD_STOP_PWM            0x97 /* Stop PWM engine. */
>  +
>  +/* Interrupt status. */
>  +#define INT_KEYPAD                     0x01 /* Key event. */
>  +#define INT_ROTATOR                    0x02 /* Rotator event. */
>  +#define INT_ERROR                      0x08 /* Error: use CMD_READ_ERR. */
>  +#define INT_NOINIT                     0x10 /* Lost configuration. */
>  +#define INT_PWM1                       0x20 /* PWM1 stopped. */
>  +#define INT_PWM2                       0x40 /* PWM2 stopped. */
>  +#define INT_PWM3                       0x80 /* PWM3 stopped. */
>  +
>  +/* Errors (signalled by INT_ERROR, read with CMD_READ_ERR). */
>  +#define ERR_BADPAR                     0x01 /* Bad parameter. */
>  +#define ERR_CMDUNK                     0x02 /* Unknown command. */
>  +#define ERR_KEYOVR                     0x04 /* Too many keys pressed. */
>  +#define ERR_FIFOOVER                   0x40 /* FIFO overflow. */
>  +
>  +/* Configuration keys (CMD_{WRITE,READ}_CFG). */
>  +#define CFG_MUX1SEL                    0x01 /* Select MUX1_OUT input. */
>  +#define CFG_MUX1EN                     0x02 /* Enable MUX1_OUT. */
>  +#define CFG_MUX2SEL                    0x04 /* Select MUX2_OUT input. */
>  +#define CFG_MUX2EN                     0x08 /* Enable MUX2_OUT. */
>  +#define CFG_PSIZE                      0x20 /* Package size (must be 0). */
>  +#define CFG_ROTEN                      0x40 /* Enable rotator. */
>  +
>  +/* Clock settings (CMD_{WRITE,READ}_CLOCK). */
>  +#define CLK_RCPWM_INTERNAL             0x00
>  +#define CLK_RCPWM_EXTERNAL             0x03
>  +#define CLK_SLOWCLKEN                  0x08 /* Enable 32.768kHz clock. */
>  +#define CLK_SLOWCLKOUT                 0x40 /* Enable slow pulse output. */
>  +
>  +/* The possible addresses corresponding to CONFIG1 and CONFIG2 pin wirings. */
>  +#define LM8323_I2C_ADDR00              (0x84 >> 1)     /* 1000 010x */
>  +#define LM8323_I2C_ADDR01              (0x86 >> 1)     /* 1000 011x */
>  +#define LM8323_I2C_ADDR10              (0x88 >> 1)     /* 1000 100x */
>  +#define LM8323_I2C_ADDR11              (0x8A >> 1)     /* 1000 101x */
>  +
>  +/* Key event fifo length */
>  +#define LM8323_FIFO_LEN                        15
>  +
>  +/* Commands for PWM engine; feed in with PWM_WRITE. */
>  +/* Load ramp counter from duty cycle field (range 0 - 0xff). */
>  +#define PWM_SET(v)                     (0x4000 | ((v) & 0xff))
>  +/* Go to start of script. */
>  +#define PWM_GOTOSTART                  0x0000
>  +/*
>  + * Stop engine (generates interrupt).  If reset is 1, clear the program
>  + * counter, else leave it.
>  + */
>  +#define PWM_END(reset)                 (0xc000 | (!!(reset) << 11))
>  +/*
>  + * Ramp.  If s is 1, divide clock by 512, else divide clock by 16.
>  + * Take t clock scales (up to 63) per step, for n steps (up to 126).
>  + * If u is set, ramp up, else ramp down.
>  + */
>  +#define PWM_RAMP(s, t, n, u)           ((!!(s) << 14) | ((t) & 0x3f) << 8 | \
>  +                                        ((n) & 0x7f) | ((u) ? 0 : 0x80))
>  +/*
>  + * Loop (i.e. jump back to pos) for a given number of iterations (up to 63).
>  + * If cnt is zero, execute until PWM_END is encountered.
>  + */
>  +#define PWM_LOOP(cnt, pos)             (0xa000 | (((cnt) & 0x3f) << 7) | \
>  +                                        ((pos) & 0x3f))
>  +/*
>  + * Wait for trigger.  Argument is a mask of channels, shifted by the channel
>  + * number, e.g. 0xa for channels 3 and 1.  Note that channels are numbered
>  + * from 1, not 0.
>  + */
>  +#define PWM_WAIT_TRIG(chans)           (0xe000 | (((chans) & 0x7) << 6))
>  +/* Send trigger.  Argument is same as PWM_WAIT_TRIG. */
>  +#define PWM_SEND_TRIG(chans)           (0xe000 | ((chans) & 0x7))
>  +
>  +#define DRIVER_NAME  "lm8323"
>  +
>  +static unsigned short normal_i2c[] =
>  +{
>  +       LM8323_I2C_ADDR00, LM8323_I2C_ADDR01,
>  +       LM8323_I2C_ADDR10, LM8323_I2C_ADDR11,
>  +       I2C_CLIENT_END
>  +};
>  +
>  +I2C_CLIENT_INSMOD;
>  +
>  +struct lm8323_pwm {
>  +       int                     id;
>  +       int                     enabled;
>  +       int                     fade_time;
>  +       int                     brightness;
>  +       int                     desired_brightness;
>  +       struct work_struct      work;
>  +       struct led_classdev     cdev;
>  +};
>  +
>  +struct lm8323_chip {
>  +       struct mutex            lock;
>  +       struct i2c_client       *client;
>  +       struct work_struct      work;
>  +       struct input_dev        *idev;
>  +       int                     irq;
>  +       unsigned                kp_enabled : 1;
>  +       unsigned                pm_suspend : 1;
>  +       unsigned                keys_down;
>  +       char                    phys[32];
>  +       s16                     keymap[LM8323_KEYMAP_SIZE];
>  +       int                     size_x;
>  +       int                     size_y;
>  +       int                     debounce_time;
>  +       int                     active_time;
>  +       struct lm8323_pwm       pwm1;
>  +       struct lm8323_pwm       pwm2;
>  +       struct lm8323_pwm       pwm3;
>  +};
>  +
>  +#define client_to_lm8323(c)    container_of(c, struct lm8323_chip, client)
>  +#define dev_to_lm8323(d)       container_of(d, struct lm8323_chip, client->dev)
>  +#define work_to_lm8323(w)      container_of(w, struct lm8323_chip, work)
>  +#define cdev_to_pwm(c)         container_of(c, struct lm8323_pwm, cdev)
>  +#define work_to_pwm(w)         container_of(w, struct lm8323_pwm, work)
>  +
>  +static struct lm8323_chip *pwm_to_lm8323(struct lm8323_pwm *pwm)
>  +{
>  +       switch (pwm->id) {
>  +       case 1:
>  +               return container_of(pwm, struct lm8323_chip, pwm1);
>  +       case 2:
>  +               return container_of(pwm, struct lm8323_chip, pwm2);
>  +       case 3:
>  +               return container_of(pwm, struct lm8323_chip, pwm3);
>  +       default:
>  +               return NULL;
>  +       }
>  +}
>  +
>  +static struct lm8323_platform_data *lm8323_pdata = NULL;
>  +
>  +
>  +#define LM8323_MAX_DATA 8
>  +
>  +/*
>  + * To write, we just access the chip's address in write mode, and dump the
>  + * command and data out on the bus.  The command byte and data are taken as
>  + * sequential u8s out of varargs, to a maximum of LM8323_MAX_DATA.
>  + */
>  +static int lm8323_write(struct lm8323_chip *lm, int len, ...)
>  +{
>  +       int ret, i;
>  +       va_list ap;
>  +       u8 data[LM8323_MAX_DATA];
>  +
>  +       va_start(ap, len);
>  +
>  +       if (unlikely(len > LM8323_MAX_DATA)) {
>  +               dev_err(&lm->client->dev, "tried to send %d bytes\n", len);
>  +               va_end(ap);
>  +               return 0;
>  +       }
>  +
>  +       for (i = 0; i < len; i++)
>  +               data[i] = va_arg(ap, int);
>  +
>  +       va_end(ap);
>  +
>  +       /*
>  +        * If the host is asleep while we send the data, we can get a NACK
>  +        * back while it wakes up, so try again, once.
>  +        */
>  +       ret = i2c_master_send(lm->client, data, len);
>  +       if (unlikely(ret == -EREMOTEIO))
>  +               ret = i2c_master_send(lm->client, data, len);
>  +       if (unlikely(ret != len))
>  +               dev_err(&lm->client->dev, "sent %d bytes of %d total\n",
>  +                       len, ret);
>  +
>  +       return ret;
>  +}
>  +
>  +/*
>  + * To read, we first send the command byte to the chip and end the transaction,
>  + * then access the chip in read mode, at which point it will send the data.
>  + */
>  +static int lm8323_read(struct lm8323_chip *lm, u8 cmd, u8 *buf, int len)
>  +{
>  +       int ret;
>  +
>  +       /*
>  +        * If the host is asleep while we send the byte, we can get a NACK
>  +        * back while it wakes up, so try again, once.
>  +        */
>  +       ret = i2c_master_send(lm->client, &cmd, 1);
>  +       if (unlikely(ret == -EREMOTEIO))
>  +               ret = i2c_master_send(lm->client, &cmd, 1);
>  +       if (unlikely(ret != 1)) {
>  +               dev_err(&lm->client->dev, "sending read cmd 0x%02x failed\n",
>  +                       cmd);
>  +               return 0;
>  +       }
>  +
>  +       ret = i2c_master_recv(lm->client, buf, len);
>  +       if (unlikely(ret != len))
>  +               dev_err(&lm->client->dev, "wanted %d bytes, got %d\n",
>  +                       len, ret);
>  +
>  +       return ret;
>  +}
>  +
>  +/*
>  + * Set the chip active time (idle time before it enters halt).
>  + */
>  +static void lm8323_set_active_time(struct lm8323_chip *lm, int time)
>  +{
>  +       lm8323_write(lm, 2, LM8323_CMD_SET_ACTIVE, time >> 2);
>  +}
>  +
>  +/*
>  + * The signals are AT-style: the low 7 bits are the keycode, and the top
>  + * bit indicates the state (1 for down, 0 for up).
>  + */
>  +static inline u8 lm8323_whichkey(u8 event)
>  +{
>  +       return event & 0x7f;
>  +}
>  +
>  +static inline int lm8323_ispress(u8 event)
>  +{
>  +       return (event & 0x80) ? 1 : 0;
>  +}
>  +
>  +static void process_keys(struct lm8323_chip *lm)
>  +{
>  +       u8 event;
>  +       u8 key_fifo[LM8323_FIFO_LEN + 1];
>  +       int old_keys_down = lm->keys_down;
>  +       int ret;
>  +       int i = 0;
>  +
>  +       /*
>  +        * Read all key events from the FIFO at once. Next READ_FIFO clears the
>  +        * FIFO even if we didn't read all events previously.
>  +        */
>  +       ret = lm8323_read(lm, LM8323_CMD_READ_FIFO, key_fifo, LM8323_FIFO_LEN);
>  +
>  +       if (ret < 0) {
>  +               dev_err(&lm->client->dev, "Failed reading fifo \n");
>  +               return;
>  +       }
>  +       key_fifo[ret] = 0;
>  +
>  +       while ((event = key_fifo[i])) {
>  +               u8 key = lm8323_whichkey(event);
>  +               int isdown = lm8323_ispress(event);
>  +               s16 keycode = lm->keymap[key];
>  +
>  +               if (likely(keycode > 0)) {
>  +                       debug(&lm->client->dev, "key 0x%02x %s\n", key,
>  +                             isdown ? "down" : "up");
>  +                       if (likely(lm->kp_enabled)) {
>  +                               input_report_key(lm->idev, keycode, isdown);
>  +                               input_sync(lm->idev);
>  +                       }
>  +                       if (isdown)
>  +                               lm->keys_down++;
>  +                       else
>  +                               lm->keys_down--;
>  +               } else {
>  +                       dev_err(&lm->client->dev, "keycode 0x%02x not mapped "
>  +                               "to any key\n", key);
>  +               }
>  +               i++;
>  +       }
>  +
>  +       /*
>  +        * Errata: We need to ensure that the chip never enters halt mode
>  +        * during a keypress, so set active time to 0.  When it's released,
>  +        * we can enter halt again, so set the active time back to normal.
>  +        */
>  +       if (!old_keys_down && lm->keys_down)
>  +               lm8323_set_active_time(lm, 0);
>  +       if (old_keys_down && !lm->keys_down)
>  +               lm8323_set_active_time(lm, lm->active_time);
>  +}
>  +
>  +static void lm8323_process_error(struct lm8323_chip *lm)
>  +{
>  +       u8 error;
>  +
>  +       if (lm8323_read(lm, LM8323_CMD_READ_ERR, &error, 1) == 1) {
>  +               if (error & ERR_FIFOOVER)
>  +                       debug(&lm->client->dev, "fifo overflow!\n");
>  +               if (error & ERR_KEYOVR)
>  +                       debug(&lm->client->dev, "more than two keys pressed\n");
>  +               if (error & ERR_CMDUNK)
>  +                       debug(&lm->client->dev, "unknown command submitted\n");
>  +               if (error & ERR_BADPAR)
>  +                       debug(&lm->client->dev, "bad command parameter\n");
>  +       }
>  +}
>  +
>  +static void lm8323_reset(struct lm8323_chip *lm)
>  +{
>  +       /* The docs say we must pass 0xAA as the data byte. */
>  +       lm8323_write(lm, 2, LM8323_CMD_RESET, 0xAA);
>  +}
>  +
>  +static int lm8323_configure(struct lm8323_chip *lm)
>  +{
>  +       int keysize = (lm->size_x << 4) | lm->size_y;
>  +       int clock = (CLK_SLOWCLKEN | CLK_RCPWM_EXTERNAL);
>  +       int debounce = lm->debounce_time >> 2;
>  +       int active = lm->active_time >> 2;
>  +
>  +       /*
>  +        * Active time must be greater than the debounce time: if it's
>  +        * a close-run thing, give ourselves a 12ms buffer.
>  +        */
>  +       if (debounce >= active)
>  +               active = debounce + 3;
>  +
>  +       lm8323_write(lm, 2, LM8323_CMD_WRITE_CFG, 0);
>  +       lm8323_write(lm, 2, LM8323_CMD_WRITE_CLOCK, clock);
>  +       lm8323_write(lm, 2, LM8323_CMD_SET_KEY_SIZE, keysize);
>  +       lm8323_set_active_time(lm, lm->active_time);
>  +       lm8323_write(lm, 2, LM8323_CMD_SET_DEBOUNCE, debounce);
>  +       lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_STATE, 0xff, 0xff);
>  +       lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_SEL, 0, 0);
>  +
>  +       /*
>  +        * Not much we can do about errors at this point, so just hope
>  +        * for the best.
>  +        */
>  +
>  +       return 0;
>  +}
>  +
>  +/*
>  + * Bottom half: handle the interrupt by posting key events, or dealing with
>  + * errors appropriately.
>  + */
>  +static void lm8323_work(struct work_struct *work)
>  +{
>  +       struct lm8323_chip *lm = work_to_lm8323(work);
>  +       u8 ints;
>  +
>  +       mutex_lock(&lm->lock);
>  +
>  +       while ((lm8323_read(lm, LM8323_CMD_READ_INT, &ints, 1) == 1) && ints) {
>  +               if (likely(ints & INT_KEYPAD)) {
>  +                       process_keys(lm);
>  +               }
>  +               if (ints & INT_ROTATOR) {
>  +                       /* We don't currently support the rotator. */
>  +                       debug(&lm->client->dev, "rotator fired\n");
>  +               }
>  +               if (ints & INT_ERROR) {
>  +                       debug(&lm->client->dev, "error!\n");
>  +                       lm8323_process_error(lm);
>  +               }
>  +               if (ints & INT_NOINIT) {
>  +                       dev_err(&lm->client->dev, "chip lost config; "
>  +                                                 "reinitialising\n");
>  +                       lm8323_configure(lm);
>  +               }
>  +               if (ints & INT_PWM1) {
>  +                       debug(&lm->client->dev, "pwm1 engine completed\n");
>  +               }
>  +               if (ints & INT_PWM2) {
>  +                       debug(&lm->client->dev, "pwm2 engine completed\n");
>  +               }
>  +               if (ints & INT_PWM3) {
>  +                       debug(&lm->client->dev, "pwm3 engine completed\n");
>  +               }
>  +       }
>  +
>  +       mutex_unlock(&lm->lock);
>  +}
>  +
>  +/*
>  + * We cannot use I2C in interrupt context, so we just schedule work.
>  + */
>  +static irqreturn_t lm8323_irq(int irq, void *data)
>  +{
>  +       struct lm8323_chip *lm = data;
>  +
>  +       schedule_work(&lm->work);
>  +
>  +       return IRQ_HANDLED;
>  +}
>  +
>  +/*
>  + * Read the chip ID.
>  + */
>  +static int lm8323_read_id(struct lm8323_chip *lm, u8 *buf)
>  +{
>  +       int bytes;
>  +
>  +       bytes = lm8323_read(lm, LM8323_CMD_READ_ID, buf, 2);
>  +       if (unlikely(bytes != 2))
>  +               return -EIO;
>  +
>  +       return 0;
>  +}
>  +
>  +static void lm8323_write_pwm_one(struct lm8323_pwm *pwm, int pos, u16 cmd)
>  +{
>  +       struct lm8323_chip *lm = pwm_to_lm8323(pwm);
>  +
>  +       lm8323_write(lm, 4, LM8323_CMD_PWM_WRITE, (pos << 2) | pwm->id,
>  +                    (cmd & 0xff00) >> 8, cmd & 0x00ff);
>  +}
>  +
>  +/*
>  + * Write a script into a given PWM engine, concluding with PWM_END.
>  + * If 'keepalive' is specified, the engine will be kept running
>  + * indefinitely.
>  + */
>  +static void lm8323_write_pwm(struct lm8323_pwm *pwm, int keepalive,
>  +                            int len, ...)
>  +{
>  +       struct lm8323_chip *lm = pwm_to_lm8323(pwm);
>  +       int i, cmd;
>  +       va_list ap;
>  +
>  +       /*
>  +        * If there are any scripts running at the moment, terminate them
>  +        * and make sure the duty cycle is as if it finished.
>  +        */
>  +       lm8323_write(lm, 2, LM8323_CMD_STOP_PWM, pwm->id);
>  +
>  +       va_start(ap, len);
>  +       for (i = 0; i < len; i++) {
>  +               cmd = va_arg(ap, int);
>  +               lm8323_write_pwm_one(pwm, i, cmd);
>  +       }
>  +       va_end(ap);
>  +
>  +       /* Wait for a trigger from any channel. This keeps the engine alive. */
>  +       if (keepalive)
>  +               lm8323_write_pwm_one(pwm, i++, PWM_WAIT_TRIG(0xe));
>  +       else
>  +               lm8323_write_pwm_one(pwm, i++, PWM_END(1));
>  +
>  +       lm8323_write(lm, 2, LM8323_CMD_START_PWM, pwm->id);
>  +}
>  +
>  +static void lm8323_pwm_work(struct work_struct *work)
>  +{
>  +       struct lm8323_pwm *pwm = work_to_pwm(work);
>  +       int div, perstep, steps, hz, direction, keepalive;
>  +
>  +       /* Do nothing if we're already at the requested level. */
>  +       if (pwm->desired_brightness == pwm->brightness)
>  +               return;
>  +
>  +       keepalive = (pwm->desired_brightness > 0);
>  +       direction = (pwm->desired_brightness > pwm->brightness);
>  +       steps = abs(pwm->desired_brightness - pwm->brightness);
>  +
>  +       /*
>  +        * Convert time (in ms) into a divisor (512 or 16 on a refclk of
>  +        * 32768Hz), and number of ticks per step.
>  +        */
>  +       if ((pwm->fade_time / steps) > (32768 / 512))
>  +               div = 512;
>  +       else
>  +               div = 16;
>  +
>  +       hz = 32768 / div;
>  +       if (pwm->fade_time < ((steps * 1000) / hz))
>  +               perstep = 1;
>  +       else
>  +               perstep = (hz * pwm->fade_time) / (steps * 1000);
>  +
>  +       if (perstep == 0)
>  +               perstep = 1;
>  +       else if (perstep > 63)
>  +               perstep = 63;
>  +
>  +       if (steps > 252) {
>  +               lm8323_write_pwm(pwm, keepalive, 3,
>  +                                PWM_RAMP((div == 512), perstep, 126,
>  +                                         direction),
>  +                                PWM_RAMP((div == 512), perstep, 126,
>  +                                         direction),
>  +                                PWM_RAMP((div == 512), perstep, steps - 252,
>  +                                         direction));
>  +       } else if (steps > 126) {
>  +               lm8323_write_pwm(pwm, keepalive, 2,
>  +                                PWM_RAMP((div == 512), perstep, 126,
>  +                                         direction),
>  +                                PWM_RAMP((div == 512), perstep, steps - 126,
>  +                                         direction));
>  +       } else {
>  +               lm8323_write_pwm(pwm, keepalive, 1,
>  +                                PWM_RAMP((div == 512), perstep, steps,
>  +                                         direction));
>  +       }
>  +
>  +       pwm->brightness = pwm->desired_brightness;
>  +}
>  +
>  +static void lm8323_pwm_set_brightness(struct led_classdev *led_cdev,
>  +                                     enum led_brightness brightness)
>  +{
>  +       struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
>  +       struct lm8323_chip *lm = pwm_to_lm8323(pwm);
>  +
>  +       pwm->desired_brightness = brightness;
>  +
>  +       if (in_interrupt()) {
>  +               schedule_work(&pwm->work);
>  +       } else {
>  +               /*
>  +                * Schedule PWM work as usual unless we are going into suspend
>  +                */
>  +               mutex_lock(&lm->lock);
>  +               if (likely(!lm->pm_suspend))
>  +                       schedule_work(&pwm->work);
>  +               else
>  +                       lm8323_pwm_work(&pwm->work);
>  +               mutex_unlock(&lm->lock);
>  +       }
>  +}
>  +
>  +static ssize_t lm8323_pwm_show_time(struct device *dev,
>  +               struct device_attribute *attr, char *buf)
>  +{
>  +       struct led_classdev *led_cdev = dev_get_drvdata(dev);
>  +       struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
>  +
>  +       return sprintf(buf, "%d\n", pwm->fade_time);
>  +}
>  +
>  +static ssize_t lm8323_pwm_store_time(struct device *dev,
>  +               struct device_attribute *attr, const char *buf, size_t len)
>  +{
>  +       struct led_classdev *led_cdev = dev_get_drvdata(dev);
>  +       struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
>  +       char *endptr = NULL;
>  +       int time;
>  +
>  +       time = simple_strtoul(buf, &endptr, 10);
>  +       /* Numbers only, please. */
>  +       if (endptr && *endptr != '\n' && *(endptr + 1) != '\0')
>  +               return -EINVAL;
>  +
>  +       pwm->fade_time = time;
>  +
>  +       return strlen(buf);
>  +}
>  +static DEVICE_ATTR(time, 0644, lm8323_pwm_show_time, lm8323_pwm_store_time);
>  +
>  +static int init_pwm(struct lm8323_chip *lm, int id, struct device *dev,
>  +                   const char *name)
>  +{
>  +       struct lm8323_pwm *pwm = NULL;
>  +
>  +       BUG_ON(id > 3);
>  +
>  +       switch (id) {
>  +       case 1:
>  +               pwm = &lm->pwm1;
>  +               break;
>  +       case 2:
>  +               pwm = &lm->pwm2;
>  +               break;
>  +       case 3:
>  +               pwm = &lm->pwm3;
>  +               break;
>  +       }
>  +
>  +       pwm->id = id;
>  +       pwm->fade_time = 0;
>  +       pwm->brightness = 0;
>  +       pwm->desired_brightness = 0;
>  +       if (name) {
>  +               pwm->cdev.name = name;
>  +               pwm->cdev.brightness_set = lm8323_pwm_set_brightness;
>  +               if (led_classdev_register(dev, &pwm->cdev) < 0) {
>  +                       dev_err(dev, "couldn't register PWM %d\n", id);
>  +                       return -1;
>  +               }
>  +               if (device_create_file(pwm->cdev.dev,
>  +                                            &dev_attr_time) < 0) {
>  +                       dev_err(dev, "couldn't register time attribute\n");
>  +                       led_classdev_unregister(&pwm->cdev);
>  +                       return -1;
>  +               }
>  +               INIT_WORK(&pwm->work, lm8323_pwm_work);
>  +               pwm->enabled = 1;
>  +       } else {
>  +               pwm->enabled = 0;
>  +       }
>  +
>  +       return 0;
>  +}
>  +
>  +static struct i2c_driver lm8323_i2c_driver;
>  +
>  +static ssize_t lm8323_show_disable(struct device *dev,
>  +                                  struct device_attribute *attr, char *buf)
>  +{
>  +       struct lm8323_chip *lm = dev_get_drvdata(dev);
>  +
>  +       return sprintf(buf, "%u\n", !lm->kp_enabled);
>  +}
>  +
>  +static ssize_t lm8323_set_disable(struct device *dev,
>  +                                 struct device_attribute *attr,
>  +                                 const char *buf, size_t count)
>  +{
>  +       struct lm8323_chip *lm = dev_get_drvdata(dev);
>  +       char *endp;
>  +       int i;
>  +
>  +       i = simple_strtoul(buf, &endp, 10);
>  +
>  +       mutex_lock(&lm->lock);
>  +       lm->kp_enabled = !i;
>  +       mutex_unlock(&lm->lock);
>  +
>  +       return count;
>  +}
>  +static DEVICE_ATTR(disable_kp, 0644, lm8323_show_disable, lm8323_set_disable);
>  +
>  +static int lm8323_probe(struct i2c_client *client)
>  +{
>  +       struct input_dev *idev;
>  +       struct lm8323_chip *lm;
>  +       int i, err = 0;
>  +       unsigned long tmo;
>  +       u8 data[2];
>  +
>  +       lm = kzalloc(sizeof *lm, GFP_KERNEL);
>  +       if (!lm)
>  +               return -ENOMEM;
>  +
>  +       i2c_set_clientdata(client, lm);
>  +       lm->client = client;
>  +       lm8323_pdata = client->dev.platform_data;
>  +       if (!lm8323_pdata)
>  +               return -EINVAL; /* ? */
>  +
>  +       lm->size_x = lm8323_pdata->size_x;
>  +       if (lm->size_x == 0) {
>  +               lm->size_x = 8;
>  +       } else if (lm->size_x > 8) {
>  +               dev_err(&client->dev, "invalid x size %d specified\n", lm->size_x);
>  +               lm->size_x = 8;
>  +       }
>  +
>  +       lm->size_y = lm8323_pdata->size_y;
>  +       if (lm->size_y == 0) {
>  +               lm->size_y = 12;
>  +       } else if (lm->size_y > 12) {
>  +               dev_err(&client->dev, "invalid y size %d specified\n", lm->size_y);
>  +               lm->size_x = 12;
>  +       }
>  +
>  +       debug(&c->dev, "Keypad size: %d x %d\n", lm->size_x, lm->size_y);
>  +
>  +       lm->debounce_time = lm8323_pdata->debounce_time;
>  +       if (lm->debounce_time == 0) /* Default. */
>  +               lm->debounce_time = 12;
>  +       else if (lm->debounce_time == -1) /* Disable debounce. */
>  +               lm->debounce_time = 0;
>  +
>  +       lm->active_time = lm8323_pdata->active_time;
>  +       if (lm->active_time == 0) /* Default. */
>  +               lm->active_time = 500;
>  +       else if (lm->active_time == -1) /* Disable sleep. */
>  +               lm->active_time = 0;
>  +
>  +       lm8323_reset(lm);
>  +
>  +       /* Nothing's set up to service the IRQ yet, so just spin for max.
>  +        * 100ms until we can configure. */
>  +       tmo = jiffies + msecs_to_jiffies(100);
>  +       while (lm8323_read(lm, LM8323_CMD_READ_INT, data, 1) == 1) {
>  +               if (data[0] & INT_NOINIT)
>  +                       break;
>  +
>  +               if (time_after(jiffies, tmo)) {
>  +                       dev_err(&client->dev, "timeout waiting for initialisation\n");
>  +                       break;
>  +               }
>  +
>  +               msleep(1);
>  +       }
>  +       lm8323_configure(lm);
>  +
>  +       /* If a true probe check the device */
>  +       if (lm8323_read_id(lm, data) != 0) {
>  +               dev_err(&client->dev, "device not found\n");
>  +               err = -ENODEV;
>  +               goto fail2;
>  +       }
>  +
>  +       if (init_pwm(lm, 1, &client->dev, lm8323_pdata->pwm1_name) < 0)
>  +               goto fail3;
>  +       if (init_pwm(lm, 2, &client->dev, lm8323_pdata->pwm2_name) < 0)
>  +               goto fail4;
>  +       if (init_pwm(lm, 3, &client->dev, lm8323_pdata->pwm3_name) < 0)
>  +               goto fail5;
>  +
>  +       lm->irq = lm8323_pdata->irq_gpio;
>  +       debug(&c->dev, "IRQ: %d\n", lm->irq);
>  +
>  +       mutex_init(&lm->lock);
>  +       INIT_WORK(&lm->work, lm8323_work);
>  +
>  +       err = request_irq(client->irq, lm8323_irq,
>  +                         IRQF_TRIGGER_FALLING | IRQF_DISABLED |
>  +                         IRQF_SAMPLE_RANDOM, DRIVER_NAME, lm);
>  +       if (err) {
>  +               dev_err(&client->dev, "could not get IRQ %d\n", lm->irq);
>  +               goto fail6;
>  +       }
>  +
>  +       set_irq_wake(lm->irq, 1);
>  +
>  +       lm->kp_enabled = 1;
>  +       err = device_create_file(&client->dev, &dev_attr_disable_kp);
>  +       if (err < 0)
>  +               goto fail7;
>  +
>  +       idev = input_allocate_device();
>  +       if (idev == NULL) {
>  +               err = -ENOMEM;
>  +               goto fail8;
>  +       }
>  +
>  +       if (lm8323_pdata->name)
>  +               idev->name = lm8323_pdata->name;
>  +       else
>  +               idev->name = "LM8323 keypad";
>  +       snprintf(lm->phys, sizeof(lm->phys), "%s/input-kp", client->dev.bus_id);
>  +       idev->phys = lm->phys;
>  +
>  +       lm->keys_down = 0;
>  +       idev->evbit[0] = BIT(EV_KEY);
>  +       for (i = 0; i < LM8323_KEYMAP_SIZE; i++) {
>  +               if (lm8323_pdata->keymap[i] > 0)
>  +                       set_bit(lm8323_pdata->keymap[i], idev->keybit);
>  +
>  +               lm->keymap[i] = lm8323_pdata->keymap[i];
>  +       }
>  +
>  +       if (lm8323_pdata->repeat)
>  +               set_bit(EV_REP, idev->evbit);
>  +
>  +       lm->idev = idev;
>  +       if (input_register_device(idev)) {
>  +               dev_dbg(&client->dev, "error registering input device\n");
>  +               goto fail8;
>  +       }
>  +
>  +       return 0;
>  +
>  +fail8:
>  +       device_remove_file(&client->dev, &dev_attr_disable_kp);
>  +fail7:
>  +       free_irq(lm->irq, lm);
>  +fail6:
>  +       if (lm->pwm3.enabled)
>  +               led_classdev_unregister(&lm->pwm3.cdev);
>  +fail5:
>  +       if (lm->pwm2.enabled)
>  +               led_classdev_unregister(&lm->pwm2.cdev);
>  +fail4:
>  +       if (lm->pwm1.enabled)
>  +               led_classdev_unregister(&lm->pwm1.cdev);
>  +fail3:
>  +fail2:
>  +       kfree(lm);
>  +       return err;
>  +}
>  +
>  +static int lm8323_remove(struct i2c_client *client)
>  +{
>  +       struct lm8323_chip *lm = i2c_get_clientdata(client);
>  +
>  +       free_irq(lm->irq, lm);
>  +       device_remove_file(&lm->client->dev, &dev_attr_disable_kp);
>  +
>  +       return 0;
>  +}
>  +
>  +/*
>  + * We don't need to explicitly suspend the chip, as it already switches off
>  + * when there's no activity.
>  + */
>  +static int lm8323_suspend(struct i2c_client *client, pm_message_t mesg)
>  +{
>  +       struct lm8323_chip *lm = i2c_get_clientdata(client);
>  +
>  +       set_irq_wake(lm->irq, 0);
>  +       disable_irq(lm->irq);
>  +
>  +       mutex_lock(&lm->lock);
>  +       lm->pm_suspend = 1;
>  +       mutex_unlock(&lm->lock);
>  +
>  +       if (lm->pwm1.enabled)
>  +               led_classdev_suspend(&lm->pwm1.cdev);
>  +       if (lm->pwm2.enabled)
>  +               led_classdev_suspend(&lm->pwm2.cdev);
>  +       if (lm->pwm3.enabled)
>  +               led_classdev_suspend(&lm->pwm3.cdev);
>  +
>  +       return 0;
>  +}
>  +
>  +static int lm8323_resume(struct i2c_client *client)
>  +{
>  +       struct lm8323_chip *lm = i2c_get_clientdata(client);
>  +
>  +       mutex_lock(&lm->lock);
>  +       lm->pm_suspend = 0;
>  +       mutex_unlock(&lm->lock);
>  +
>  +       if (lm->pwm1.enabled)
>  +               led_classdev_resume(&lm->pwm1.cdev);
>  +       if (lm->pwm2.enabled)
>  +               led_classdev_resume(&lm->pwm2.cdev);
>  +       if (lm->pwm3.enabled)
>  +               led_classdev_resume(&lm->pwm3.cdev);
>  +
>  +       enable_irq(lm->irq);
>  +       set_irq_wake(lm->irq, 1);
>  +
>  +       return 0;
>  +}
>  +
>  +static struct i2c_driver lm8323_i2c_driver = {
>  +       .driver = {
>  +               .name    = DRIVER_NAME,
>  +       },
>  +       .probe          = lm8323_probe,
>  +       .remove         = __exit_p(lm8323_remove),
>  +       .suspend        = lm8323_suspend,
>  +       .resume         = lm8323_resume,
>  +};
>  +
>  +static int __init lm8323_init(void)
>  +{
>  +       return i2c_add_driver(&lm8323_i2c_driver);
>  +}
>  +
>  +static void __exit lm8323_exit(void)
>  +{
>  +       i2c_del_driver(&lm8323_i2c_driver);
>  +}
>  +
>  +MODULE_AUTHOR("Daniel Stone");
>  +MODULE_DESCRIPTION("LM8323 keypad driver");
>  +MODULE_LICENSE("GPL");
>  +
>  +module_init(lm8323_init);
>  +module_exit(lm8323_exit);
>  diff --git a/include/linux/i2c/lm8323.h b/include/linux/i2c/lm8323.h
>  new file mode 100644
>  index 0000000..2f0c213
>  --- /dev/null
>  +++ b/include/linux/i2c/lm8323.h
>  @@ -0,0 +1,45 @@
>  +/*
>  + * include/lm8323.h
>  + *
>  + * Configuration for LM8323 keypad driver.
>  + */
>  +
>  +#ifndef __LINUX_LM8323_H
>  +#define __LINUX_LM8323_H
>  +
>  +#include <linux/types.h>
>  +
>  +/*
>  + * Largest keycode that the chip can send, plus one,
>  + * so keys can be mapped directly at the index of the
>  + * LM8323 keycode instead of subtracting one.
>  + */
>  +#define LM8323_KEYMAP_SIZE (0x7f + 1)
>  +
>  +/*
>  + * Keymap is an array indexed with chip keycode, the array entry being
>  + * the evdev (not AT) keycode.
>  + */
>  +typedef s16 lm8323_km_t[LM8323_KEYMAP_SIZE];
>  +
>  +struct lm8323_platform_data {
>  +       u16 irq_gpio;
>  +
>  +       int debounce_time; /* Time to watch for key bouncing, in ms. */
>  +       int active_time; /* Idle time until sleep, in ms. */
>  +
>  +       int size_x;
>  +       int size_y;
>  +       int repeat : 1;
>  +       const s16 *keymap;
>  +
>  +       char *pwm1_name; /* Device name for PWM1. */
>  +       char *pwm2_name; /* Device name for PWM2. */
>  +       char *pwm3_name; /* Device name for PWM3. */
>  +
>  +       char *name; /* Device name. */
>  +};
>  +
>  +void __init lm8323_set_platform_data(struct lm8323_platform_data *pdata);
>  +
>  +#endif /* __LINUX_LM8323_H */
>  --
>  1.5.5.rc3
>
>  --
>  To unsubscribe from this list: send the line "unsubscribe linux-omap" in
>  the body of a message to majordomo@vger.kernel.org
>  More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

Cheers,

-- 
Eduardo Bezerra Valentin

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 10:54   ` Eduardo Valentin
@ 2008-04-09 11:02     ` Daniel Stone
  2008-04-09 11:33       ` Eduardo Valentin
  0 siblings, 1 reply; 23+ messages in thread
From: Daniel Stone @ 2008-04-09 11:02 UTC (permalink / raw)
  To: ext Eduardo Valentin; +Cc: Felipe Balbi, linux-omap, Tony Lindgren

[-- Attachment #1: Type: text/plain, Size: 1014 bytes --]

Hi,

On Wed, Apr 09, 2008 at 01:54:16PM +0300, ext Eduardo Valentin wrote:
> ERROR: do not initialise statics to 0 or NULL
> WARNING: braces {} are not necessary for single statement blocks

Yeah, these should be fixed.

> WARNING: consider using strict_strtoul in preference to simple_strtoul
> WARNING: consider using strict_strtoul in preference to simple_strtoul

I don't even know what strict_strtoul is, but okay.

> WARNING: line over 80 characters
> WARNING: do not add new typedefs

Sure.

> daniel.diff has style problems, please review.  If any of these errors
> are false positives report them to the maintainer, see
> CHECKPATCH in MAINTAINERS.
> 
> 
> I'd suggest using some #defines for lots for magic numbers in this code.

'lots'? The only ones I can see are the magic constant needed for reset
(used in one place and commented), and the default/max values for
debounce/active time, and max x/y, both of which are again only used in
a single place.

Cheers,
Daniel

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 11:02     ` Daniel Stone
@ 2008-04-09 11:33       ` Eduardo Valentin
  0 siblings, 0 replies; 23+ messages in thread
From: Eduardo Valentin @ 2008-04-09 11:33 UTC (permalink / raw)
  To: Daniel Stone; +Cc: Felipe Balbi, linux-omap, Tony Lindgren

Hi Daniel,

On Wed, Apr 9, 2008 at 2:02 PM, Daniel Stone <daniel.stone@nokia.com> wrote:
> Hi,
>
>
>  On Wed, Apr 09, 2008 at 01:54:16PM +0300, ext Eduardo Valentin wrote:
>  > ERROR: do not initialise statics to 0 or NULL
>
> > WARNING: braces {} are not necessary for single statement blocks
>
>  Yeah, these should be fixed.
>
>
>  > WARNING: consider using strict_strtoul in preference to simple_strtoul
>  > WARNING: consider using strict_strtoul in preference to simple_strtoul
>
>  I don't even know what strict_strtoul is, but okay.
>
>
>  > WARNING: line over 80 characters
>
> > WARNING: do not add new typedefs
>
>  Sure.
>
>
>  > daniel.diff has style problems, please review.  If any of these errors
>  > are false positives report them to the maintainer, see
>  > CHECKPATCH in MAINTAINERS.
>  >
>  >
>  > I'd suggest using some #defines for lots for magic numbers in this code.
>
>  'lots'? The only ones I can see are the magic constant needed for reset
>  (used in one place and commented), and the default/max values for
>  debounce/active time, and max x/y, both of which are again only used in
>  a single place.

That's true. But, IMHO they still make the code difficult to read.

>
>  Cheers,
>  Daniel
>
> -----BEGIN PGP SIGNATURE-----
>  Version: GnuPG v1.4.6 (GNU/Linux)
>
>  iD8DBQFH/KJg68xJuWtYYdURAlUQAKCGvDHvm1DvmmIavnztgedxQj8pfACfVaEJ
>  GMOOUOVCzTEm7ULxwz5PE/Q=
>  =U9pC
>  -----END PGP SIGNATURE-----
>
>



-- 
Eduardo Bezerra Valentin

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [PATCH 0/5] n810 drivers, take #3
@ 2008-04-09 12:03 Felipe Balbi
  2008-04-09 12:04 ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
  2008-04-14 18:03 ` [PATCH 0/5] n810 drivers, take #3 Tony Lindgren
  0 siblings, 2 replies; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 12:03 UTC (permalink / raw)
  To: linux-omap; +Cc: Tony Lindgren, Eduardo Valentin, Felipe Balbi

Now it passes checkpatch.pl:

$ scripts/checkpatch.pl linux-omap-09042008/*
linux-omap-09042008/0001-I2C-LM8323-Introduce-lm8323-keypad-driver.diff has no obvious style problems and is ready for submission.
total: 0 errors, 0 warnings, 767 lines checked

linux-omap-09042008/0002-I2C-TSL2563-Add-support-for-Taos-tsl2563-ambient-li.diff has no obvious style problems and is ready for submission.
total: 0 errors, 0 warnings, 918 lines checked

linux-omap-09042008/0003-INPUT-TOUCHSCREEN-Introduce-tsc2005-driver.diff has no obvious style problems and is ready for submission.
total: 0 errors, 0 warnings, 607 lines checked

linux-omap-09042008/0004-I2C-LP5521-Introduce-lp5521-LED-driver.diff has no obvious style problems and is ready for submission.
total: 0 errors, 0 warnings, 284 lines checked

linux-omap-09042008/0005-ARM-N800-Update-n800-defconfig.diff has no obvious style problems and is ready for submission.

Documentation follows again

---


The following patches are updates from n810 tree to
be able to build on top of current linux-omap.

I'm addind here the keypad driver, touchscreen driver,
ambient light sensor driver and LED driver.

Please give it a good review and comments are welcome. I
might have some design issues due to n800 and n810 be using
the same board files, but as of my tests everything works fine.

In case of the LED and ambient light sensor we're gonna
have to test through sysfs.

Here's some notes about both:

- ambient light sensor:
        # cat /sys/class/i2c-adapter/i2c-2/2-0029/lux
        this will show the light intensity, cover the sensor and it'll change.

- LED driver:
        # echo ff:00:00 > /sys/class/i2c-adapter/i2c-2/2-0032/color
        this will make the led bright red.

        # echo load > /sys/class/i2c-adapter/i2c-2/2-0032/mode
        # echo 40000a7f0aff0000 > /sys/class/i2c-adapter/i2c-2/2-0032/load
        # echo run > /sys/class/i2c-adapter/i2c-2/2-0032/mode
        this will make the LED bright following the pattern in the second echo.

        The pattern format is here:
        40aabbccbbcc0000
        .aa - set pwm [00..ff]
        .bb - step time increase brightness [01..3f]short step or [41..7f] long step
        .cc - brightness increment/decrement steps [00..7f] increment or [80..ff] decrement
        .0000 - goto start

        note: you can put as much 'bbcc' patterns as you want, i mean:
        the following pattern still valid:

        # echo 40000a7f0aff137f13ff0c7f0cff0000 > load

That's all, the last patch just updates n800_defconfig to enable these new drivers.

Daniel Stone (1):
  I2C: LM8323: Introduce lm8323 keypad driver

Felipe Balbi (1):
  ARM: N800: Update n800 defconfig

Lauri Leukkunen (1):
  INPUT: TOUCHSCREEN: Introduce tsc2005 driver

Mathias Nyman (2):
  I2C: TSL2563: Add support for Taos tsl2563 ambient light sensor
  I2C: LP5521: Introduce lp5521 LED driver

 arch/arm/configs/n800_defconfig     |  189 +++++++-
 arch/arm/mach-omap2/board-n800.c    |  180 +++++++-
 arch/arm/mach-omap2/board-n810.c    |    2 +
 drivers/i2c/chips/Kconfig           |   17 +
 drivers/i2c/chips/Makefile          |    3 +-
 drivers/i2c/chips/lp5521.c          |  577 ++++++++++++++++++++++
 drivers/i2c/chips/tsl2563.c         |  731 ++++++++++++++++++++++++++++
 drivers/input/keyboard/Kconfig      |    7 +
 drivers/input/keyboard/Makefile     |    1 +
 drivers/input/keyboard/lm8323.c     |  920 +++++++++++++++++++++++++++++++++++
 drivers/input/touchscreen/Kconfig   |    5 +
 drivers/input/touchscreen/Makefile  |    1 +
 drivers/input/touchscreen/tsc2005.c |  739 ++++++++++++++++++++++++++++
 include/linux/i2c/lm8323.h          |   39 ++
 include/linux/spi/tsc2005.h         |   29 ++
 15 files changed, 3428 insertions(+), 12 deletions(-)
 create mode 100644 drivers/i2c/chips/lp5521.c
 create mode 100644 drivers/i2c/chips/tsl2563.c
 create mode 100644 drivers/input/keyboard/lm8323.c
 create mode 100644 drivers/input/touchscreen/tsc2005.c
 create mode 100644 include/linux/i2c/lm8323.h
 create mode 100644 include/linux/spi/tsc2005.h


^ permalink raw reply	[flat|nested] 23+ messages in thread

* [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 12:03 [PATCH 0/5] n810 drivers, take #3 Felipe Balbi
@ 2008-04-09 12:04 ` Felipe Balbi
  2008-04-09 12:04   ` [PATCH 2/5] I2C: TSL2563: Add support for Taos tsl2563 ambient light sensor Felipe Balbi
  2008-04-09 12:11   ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
  2008-04-14 18:03 ` [PATCH 0/5] n810 drivers, take #3 Tony Lindgren
  1 sibling, 2 replies; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 12:04 UTC (permalink / raw)
  To: linux-omap; +Cc: Tony Lindgren, Eduardo Valentin, Daniel Stone, Felipe Balbi

From: Daniel Stone <daniel.stone@nokia.com>

Introduce lm8323 keypad driver.

Signed-off-by: Daniel Stone <daniel.stone@nokia.com

Updated to build with recent linux-omap and new-style
i2c driver.

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/mach-omap2/board-n800.c |   77 ++++
 arch/arm/mach-omap2/board-n810.c |    2 +
 drivers/input/keyboard/Kconfig   |    7 +
 drivers/input/keyboard/Makefile  |    1 +
 drivers/input/keyboard/lm8323.c  |  920 ++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/lm8323.h       |   39 ++
 6 files changed, 1046 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/lm8323.c
 create mode 100644 include/linux/i2c/lm8323.h

diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
index 758e2c1..367e518 100644
--- a/arch/arm/mach-omap2/board-n800.c
+++ b/arch/arm/mach-omap2/board-n800.c
@@ -23,6 +23,7 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
 #include <asm/mach/arch.h>
@@ -48,6 +49,76 @@
 #define N800_DAV_IRQ_GPIO		103
 #define N800_TSC2301_RESET_GPIO		118
 
+#ifdef CONFIG_MACH_NOKIA_N810
+static s16 rx44_keymap[LM8323_KEYMAP_SIZE] = {
+	[0x01] = KEY_Q,
+	[0x02] = KEY_K,
+	[0x03] = KEY_O,
+	[0x04] = KEY_P,
+	[0x05] = KEY_BACKSPACE,
+	[0x06] = KEY_A,
+	[0x07] = KEY_S,
+	[0x08] = KEY_D,
+	[0x09] = KEY_F,
+	[0x0a] = KEY_G,
+	[0x0b] = KEY_H,
+	[0x0c] = KEY_J,
+
+	[0x11] = KEY_W,
+	[0x12] = KEY_F4,
+	[0x13] = KEY_L,
+	[0x14] = KEY_APOSTROPHE,
+	[0x16] = KEY_Z,
+	[0x17] = KEY_X,
+	[0x18] = KEY_C,
+	[0x19] = KEY_V,
+	[0x1a] = KEY_B,
+	[0x1b] = KEY_N,
+	[0x1c] = KEY_LEFTSHIFT, /* Actually, this is both shift keys */
+	[0x1f] = KEY_F7,
+
+	[0x21] = KEY_E,
+	[0x22] = KEY_SEMICOLON,
+	[0x23] = KEY_MINUS,
+	[0x24] = KEY_EQUAL,
+	[0x2b] = KEY_FN,
+	[0x2c] = KEY_M,
+	[0x2f] = KEY_F8,
+
+	[0x31] = KEY_R,
+	[0x32] = KEY_RIGHTCTRL,
+	[0x34] = KEY_SPACE,
+	[0x35] = KEY_COMMA,
+	[0x37] = KEY_UP,
+	[0x3c] = KEY_COMPOSE,
+	[0x3f] = KEY_F6,
+
+	[0x41] = KEY_T,
+	[0x44] = KEY_DOT,
+	[0x46] = KEY_RIGHT,
+	[0x4f] = KEY_F5,
+	[0x51] = KEY_Y,
+	[0x53] = KEY_DOWN,
+	[0x55] = KEY_ENTER,
+	[0x5f] = KEY_ESC,
+
+	[0x61] = KEY_U,
+	[0x64] = KEY_LEFT,
+
+	[0x71] = KEY_I,
+	[0x75] = KEY_KPENTER,
+};
+
+static struct lm8323_platform_data lm8323_pdata = {
+	.repeat = 0, /* Repeat is handled in userspace for now. */
+	.keymap = rx44_keymap,
+
+	.name = "Internal keyboard",
+	.pwm1_name = "keyboard",
+	.pwm2_name = "cover",
+};
+#endif
+
 void __init nokia_n800_init_irq(void)
 {
 	omap2_init_common_hw();
@@ -502,6 +573,12 @@ static struct i2c_board_info __initdata n800_i2c_board_info_2[] = {
 		I2C_BOARD_INFO("tea5761", 0x10),
 	},
 #endif
+	{
+		I2C_BOARD_INFO("lm8323", 0x45),
+		.type		= "lm8323",
+		.irq		= OMAP_GPIO_IRQ(109),
+		.platform_data	= &lm8323_pdata,
+	},
 };
 
 void __init nokia_n800_common_init(void)
diff --git a/arch/arm/mach-omap2/board-n810.c b/arch/arm/mach-omap2/board-n810.c
index c4f4dd5..fb0e61f 100644
--- a/arch/arm/mach-omap2/board-n810.c
+++ b/arch/arm/mach-omap2/board-n810.c
@@ -10,6 +10,8 @@
  */
 
 #include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 1c22930..137f7e4 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -285,6 +285,13 @@ config KEYBOARD_TSC2301
 	help
 	  Say Y here for if you are using the keypad features of TSC2301.
 
+config KEYBOARD_LM8323
+	tristate "LM8323 keypad chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for the National Semiconductor
+	  LM8323 keypad controller.
+
 config KEYBOARD_PXA27x
 	tristate "PXA27x/PXA3xx keypad support"
 	depends on PXA27x || PXA3xx
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index bc0bbc1..ec447cd 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
 obj-$(CONFIG_KEYBOARD_TSC2301)		+= tsc2301_kp.o
+obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= omap-twl4030keypad.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_AAED2000)		+= aaed2000_kbd.o
diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c
new file mode 100644
index 0000000..169dc3d
--- /dev/null
+++ b/drivers/input/keyboard/lm8323.c
@@ -0,0 +1,920 @@
+/*
+ * drivers/i2c/chips/lm8323.c
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Written by Daniel Stone <daniel.stone@nokia.com>
+ *            Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 2 of the License only).
+ *
+ * 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/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/i2c/lm8323.h>
+
+#include <asm/mach-types.h>
+#include <asm/mach/irq.h>
+
+#ifdef VERBOSE
+#define debug dev_dbg
+#else
+#define debug(...)
+#endif
+
+/* Commands to send to the chip. */
+#define LM8323_CMD_READ_ID		0x80 /* Read chip ID. */
+#define LM8323_CMD_WRITE_CFG		0x81 /* Set configuration item. */
+#define LM8323_CMD_READ_INT		0x82 /* Get interrupt status. */
+#define LM8323_CMD_RESET		0x83 /* Reset, same as external one */
+#define LM8323_CMD_WRITE_PORT_SEL	0x85 /* Set GPIO in/out. */
+#define LM8323_CMD_WRITE_PORT_STATE	0x86 /* Set GPIO pullup. */
+#define LM8323_CMD_READ_PORT_SEL	0x87 /* Get GPIO in/out. */
+#define LM8323_CMD_READ_PORT_STATE	0x88 /* Get GPIO pullup. */
+#define LM8323_CMD_READ_FIFO		0x89 /* Read byte from FIFO. */
+#define LM8323_CMD_RPT_READ_FIFO	0x8a /* Read FIFO (no increment). */
+#define LM8323_CMD_SET_ACTIVE		0x8b /* Set active time. */
+#define LM8323_CMD_READ_ERR		0x8c /* Get error status. */
+#define LM8323_CMD_READ_ROTATOR		0x8e /* Read rotator status. */
+#define LM8323_CMD_SET_DEBOUNCE		0x8f /* Set debouncing time. */
+#define LM8323_CMD_SET_KEY_SIZE		0x90 /* Set keypad size. */
+#define LM8323_CMD_READ_KEY_SIZE	0x91 /* Get keypad size. */
+#define LM8323_CMD_READ_CFG		0x92 /* Get configuration item. */
+#define LM8323_CMD_WRITE_CLOCK		0x93 /* Set clock config. */
+#define LM8323_CMD_READ_CLOCK		0x94 /* Get clock config. */
+#define LM8323_CMD_PWM_WRITE		0x95 /* Write PWM script. */
+#define LM8323_CMD_START_PWM		0x96 /* Start PWM engine. */
+#define LM8323_CMD_STOP_PWM		0x97 /* Stop PWM engine. */
+
+/* Interrupt status. */
+#define INT_KEYPAD			0x01 /* Key event. */
+#define INT_ROTATOR			0x02 /* Rotator event. */
+#define INT_ERROR			0x08 /* Error: use CMD_READ_ERR. */
+#define INT_NOINIT			0x10 /* Lost configuration. */
+#define INT_PWM1			0x20 /* PWM1 stopped. */
+#define INT_PWM2			0x40 /* PWM2 stopped. */
+#define INT_PWM3			0x80 /* PWM3 stopped. */
+
+/* Errors (signalled by INT_ERROR, read with CMD_READ_ERR). */
+#define ERR_BADPAR			0x01 /* Bad parameter. */
+#define ERR_CMDUNK			0x02 /* Unknown command. */
+#define ERR_KEYOVR			0x04 /* Too many keys pressed. */
+#define ERR_FIFOOVER			0x40 /* FIFO overflow. */
+
+/* Configuration keys (CMD_{WRITE,READ}_CFG). */
+#define CFG_MUX1SEL			0x01 /* Select MUX1_OUT input. */
+#define CFG_MUX1EN			0x02 /* Enable MUX1_OUT. */
+#define CFG_MUX2SEL			0x04 /* Select MUX2_OUT input. */
+#define CFG_MUX2EN			0x08 /* Enable MUX2_OUT. */
+#define CFG_PSIZE			0x20 /* Package size (must be 0). */
+#define CFG_ROTEN			0x40 /* Enable rotator. */
+
+/* Clock settings (CMD_{WRITE,READ}_CLOCK). */
+#define CLK_RCPWM_INTERNAL		0x00
+#define CLK_RCPWM_EXTERNAL		0x03
+#define CLK_SLOWCLKEN			0x08 /* Enable 32.768kHz clock. */
+#define CLK_SLOWCLKOUT			0x40 /* Enable slow pulse output. */
+
+/* The possible addresses corresponding to CONFIG1 and CONFIG2 pin wirings. */
+#define LM8323_I2C_ADDR00		(0x84 >> 1)	/* 1000 010x */
+#define LM8323_I2C_ADDR01		(0x86 >> 1)	/* 1000 011x */
+#define LM8323_I2C_ADDR10		(0x88 >> 1)	/* 1000 100x */
+#define LM8323_I2C_ADDR11		(0x8A >> 1)	/* 1000 101x */
+
+/* Key event fifo length */
+#define LM8323_FIFO_LEN			15
+
+/* Commands for PWM engine; feed in with PWM_WRITE. */
+/* Load ramp counter from duty cycle field (range 0 - 0xff). */
+#define PWM_SET(v)			(0x4000 | ((v) & 0xff))
+/* Go to start of script. */
+#define PWM_GOTOSTART			0x0000
+/*
+ * Stop engine (generates interrupt).  If reset is 1, clear the program
+ * counter, else leave it.
+ */
+#define PWM_END(reset)			(0xc000 | (!!(reset) << 11))
+/*
+ * Ramp.  If s is 1, divide clock by 512, else divide clock by 16.
+ * Take t clock scales (up to 63) per step, for n steps (up to 126).
+ * If u is set, ramp up, else ramp down.
+ */
+#define PWM_RAMP(s, t, n, u)		((!!(s) << 14) | ((t) & 0x3f) << 8 | \
+					 ((n) & 0x7f) | ((u) ? 0 : 0x80))
+/*
+ * Loop (i.e. jump back to pos) for a given number of iterations (up to 63).
+ * If cnt is zero, execute until PWM_END is encountered.
+ */
+#define PWM_LOOP(cnt, pos)		(0xa000 | (((cnt) & 0x3f) << 7) | \
+					 ((pos) & 0x3f))
+/*
+ * Wait for trigger.  Argument is a mask of channels, shifted by the channel
+ * number, e.g. 0xa for channels 3 and 1.  Note that channels are numbered
+ * from 1, not 0.
+ */
+#define PWM_WAIT_TRIG(chans)		(0xe000 | (((chans) & 0x7) << 6))
+/* Send trigger.  Argument is same as PWM_WAIT_TRIG. */
+#define PWM_SEND_TRIG(chans)		(0xe000 | ((chans) & 0x7))
+
+#define DRIVER_NAME  "lm8323"
+
+static unsigned short normal_i2c[] =
+{
+	LM8323_I2C_ADDR00, LM8323_I2C_ADDR01,
+	LM8323_I2C_ADDR10, LM8323_I2C_ADDR11,
+	I2C_CLIENT_END
+};
+
+I2C_CLIENT_INSMOD;
+
+struct lm8323_pwm {
+	int			id;
+	int			enabled;
+	int			fade_time;
+	int			brightness;
+	int			desired_brightness;
+	struct work_struct	work;
+	struct led_classdev	cdev;
+};
+
+struct lm8323_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	struct work_struct	work;
+	struct input_dev	*idev;
+	int			irq;
+	unsigned		kp_enabled : 1;
+	unsigned		pm_suspend : 1;
+	unsigned		keys_down;
+	char			phys[32];
+	s16			keymap[LM8323_KEYMAP_SIZE];
+	int			size_x;
+	int			size_y;
+	int			debounce_time;
+	int			active_time;
+	struct lm8323_pwm	pwm1;
+	struct lm8323_pwm	pwm2;
+	struct lm8323_pwm	pwm3;
+};
+
+#define client_to_lm8323(c)	container_of(c, struct lm8323_chip, client)
+#define dev_to_lm8323(d)	container_of(d, struct lm8323_chip, client->dev)
+#define work_to_lm8323(w)	container_of(w, struct lm8323_chip, work)
+#define cdev_to_pwm(c)		container_of(c, struct lm8323_pwm, cdev)
+#define work_to_pwm(w)		container_of(w, struct lm8323_pwm, work)
+
+static struct lm8323_chip *pwm_to_lm8323(struct lm8323_pwm *pwm)
+{
+	switch (pwm->id) {
+	case 1:
+		return container_of(pwm, struct lm8323_chip, pwm1);
+	case 2:
+		return container_of(pwm, struct lm8323_chip, pwm2);
+	case 3:
+		return container_of(pwm, struct lm8323_chip, pwm3);
+	default:
+		return NULL;
+	}
+}
+
+static struct lm8323_platform_data *lm8323_pdata;
+
+
+#define LM8323_MAX_DATA 8
+
+/*
+ * To write, we just access the chip's address in write mode, and dump the
+ * command and data out on the bus.  The command byte and data are taken as
+ * sequential u8s out of varargs, to a maximum of LM8323_MAX_DATA.
+ */
+static int lm8323_write(struct lm8323_chip *lm, int len, ...)
+{
+	int ret, i;
+	va_list ap;
+	u8 data[LM8323_MAX_DATA];
+
+	va_start(ap, len);
+
+	if (unlikely(len > LM8323_MAX_DATA)) {
+		dev_err(&lm->client->dev, "tried to send %d bytes\n", len);
+		va_end(ap);
+		return 0;
+	}
+
+	for (i = 0; i < len; i++)
+		data[i] = va_arg(ap, int);
+
+	va_end(ap);
+
+	/*
+	 * If the host is asleep while we send the data, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "sent %d bytes of %d total\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * To read, we first send the command byte to the chip and end the transaction,
+ * then access the chip in read mode, at which point it will send the data.
+ */
+static int lm8323_read(struct lm8323_chip *lm, u8 cmd, u8 *buf, int len)
+{
+	int ret;
+
+	/*
+	 * If the host is asleep while we send the byte, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret != 1)) {
+		dev_err(&lm->client->dev, "sending read cmd 0x%02x failed\n",
+			cmd);
+		return 0;
+	}
+
+	ret = i2c_master_recv(lm->client, buf, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "wanted %d bytes, got %d\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * Set the chip active time (idle time before it enters halt).
+ */
+static void lm8323_set_active_time(struct lm8323_chip *lm, int time)
+{
+	lm8323_write(lm, 2, LM8323_CMD_SET_ACTIVE, time >> 2);
+}
+
+/*
+ * The signals are AT-style: the low 7 bits are the keycode, and the top
+ * bit indicates the state (1 for down, 0 for up).
+ */
+static inline u8 lm8323_whichkey(u8 event)
+{
+	return event & 0x7f;
+}
+
+static inline int lm8323_ispress(u8 event)
+{
+	return (event & 0x80) ? 1 : 0;
+}
+
+static void process_keys(struct lm8323_chip *lm)
+{
+	u8 event;
+	u8 key_fifo[LM8323_FIFO_LEN + 1];
+	int old_keys_down = lm->keys_down;
+	int ret;
+	int i = 0;
+
+	/*
+	 * Read all key events from the FIFO at once. Next READ_FIFO clears the
+	 * FIFO even if we didn't read all events previously.
+	 */
+	ret = lm8323_read(lm, LM8323_CMD_READ_FIFO, key_fifo, LM8323_FIFO_LEN);
+
+	if (ret < 0) {
+		dev_err(&lm->client->dev, "Failed reading fifo \n");
+		return;
+	}
+	key_fifo[ret] = 0;
+
+	while ((event = key_fifo[i])) {
+		u8 key = lm8323_whichkey(event);
+		int isdown = lm8323_ispress(event);
+		s16 keycode = lm->keymap[key];
+
+		if (likely(keycode > 0)) {
+			debug(&lm->client->dev, "key 0x%02x %s\n", key,
+			      isdown ? "down" : "up");
+			if (likely(lm->kp_enabled)) {
+				input_report_key(lm->idev, keycode, isdown);
+				input_sync(lm->idev);
+			}
+			if (isdown)
+				lm->keys_down++;
+			else
+				lm->keys_down--;
+		} else {
+			dev_err(&lm->client->dev, "keycode 0x%02x not mapped "
+				"to any key\n", key);
+		}
+		i++;
+	}
+
+	/*
+	 * Errata: We need to ensure that the chip never enters halt mode
+	 * during a keypress, so set active time to 0.  When it's released,
+	 * we can enter halt again, so set the active time back to normal.
+	 */
+	if (!old_keys_down && lm->keys_down)
+		lm8323_set_active_time(lm, 0);
+	if (old_keys_down && !lm->keys_down)
+		lm8323_set_active_time(lm, lm->active_time);
+}
+
+static void lm8323_process_error(struct lm8323_chip *lm)
+{
+	u8 error;
+
+	if (lm8323_read(lm, LM8323_CMD_READ_ERR, &error, 1) == 1) {
+		if (error & ERR_FIFOOVER)
+			debug(&lm->client->dev, "fifo overflow!\n");
+		if (error & ERR_KEYOVR)
+			debug(&lm->client->dev, "more than two keys pressed\n");
+		if (error & ERR_CMDUNK)
+			debug(&lm->client->dev, "unknown command submitted\n");
+		if (error & ERR_BADPAR)
+			debug(&lm->client->dev, "bad command parameter\n");
+	}
+}
+
+static void lm8323_reset(struct lm8323_chip *lm)
+{
+	/* The docs say we must pass 0xAA as the data byte. */
+	lm8323_write(lm, 2, LM8323_CMD_RESET, 0xAA);
+}
+
+static int lm8323_configure(struct lm8323_chip *lm)
+{
+	int keysize = (lm->size_x << 4) | lm->size_y;
+	int clock = (CLK_SLOWCLKEN | CLK_RCPWM_EXTERNAL);
+	int debounce = lm->debounce_time >> 2;
+	int active = lm->active_time >> 2;
+
+	/*
+	 * Active time must be greater than the debounce time: if it's
+	 * a close-run thing, give ourselves a 12ms buffer.
+	 */
+	if (debounce >= active)
+		active = debounce + 3;
+
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CFG, 0);
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CLOCK, clock);
+	lm8323_write(lm, 2, LM8323_CMD_SET_KEY_SIZE, keysize);
+	lm8323_set_active_time(lm, lm->active_time);
+	lm8323_write(lm, 2, LM8323_CMD_SET_DEBOUNCE, debounce);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_STATE, 0xff, 0xff);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_SEL, 0, 0);
+
+	/*
+	 * Not much we can do about errors at this point, so just hope
+	 * for the best.
+	 */
+
+	return 0;
+}
+
+/*
+ * Bottom half: handle the interrupt by posting key events, or dealing with
+ * errors appropriately.
+ */
+static void lm8323_work(struct work_struct *work)
+{
+	struct lm8323_chip *lm = work_to_lm8323(work);
+	u8 ints;
+
+	mutex_lock(&lm->lock);
+
+	while ((lm8323_read(lm, LM8323_CMD_READ_INT, &ints, 1) == 1) && ints) {
+		if (likely(ints & INT_KEYPAD))
+			process_keys(lm);
+		if (ints & INT_ROTATOR) {
+			/* We don't currently support the rotator. */
+			debug(&lm->client->dev, "rotator fired\n");
+		}
+		if (ints & INT_ERROR) {
+			debug(&lm->client->dev, "error!\n");
+			lm8323_process_error(lm);
+		}
+		if (ints & INT_NOINIT) {
+			dev_err(&lm->client->dev, "chip lost config; "
+						  "reinitialising\n");
+			lm8323_configure(lm);
+		}
+		if (ints & INT_PWM1)
+			debug(&lm->client->dev, "pwm1 engine completed\n");
+		if (ints & INT_PWM2)
+			debug(&lm->client->dev, "pwm2 engine completed\n");
+		if (ints & INT_PWM3)
+			debug(&lm->client->dev, "pwm3 engine completed\n");
+	}
+
+	mutex_unlock(&lm->lock);
+}
+
+/*
+ * We cannot use I2C in interrupt context, so we just schedule work.
+ */
+static irqreturn_t lm8323_irq(int irq, void *data)
+{
+	struct lm8323_chip *lm = data;
+
+	schedule_work(&lm->work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Read the chip ID.
+ */
+static int lm8323_read_id(struct lm8323_chip *lm, u8 *buf)
+{
+	int bytes;
+
+	bytes = lm8323_read(lm, LM8323_CMD_READ_ID, buf, 2);
+	if (unlikely(bytes != 2))
+		return -EIO;
+
+	return 0;
+}
+
+static void lm8323_write_pwm_one(struct lm8323_pwm *pwm, int pos, u16 cmd)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	lm8323_write(lm, 4, LM8323_CMD_PWM_WRITE, (pos << 2) | pwm->id,
+		     (cmd & 0xff00) >> 8, cmd & 0x00ff);
+}
+
+/*
+ * Write a script into a given PWM engine, concluding with PWM_END.
+ * If 'keepalive' is specified, the engine will be kept running
+ * indefinitely.
+ */
+static void lm8323_write_pwm(struct lm8323_pwm *pwm, int keepalive,
+			     int len, ...)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+	int i, cmd;
+	va_list ap;
+
+	/*
+	 * If there are any scripts running at the moment, terminate them
+	 * and make sure the duty cycle is as if it finished.
+	 */
+	lm8323_write(lm, 2, LM8323_CMD_STOP_PWM, pwm->id);
+
+	va_start(ap, len);
+	for (i = 0; i < len; i++) {
+		cmd = va_arg(ap, int);
+		lm8323_write_pwm_one(pwm, i, cmd);
+	}
+	va_end(ap);
+
+	/* Wait for a trigger from any channel. This keeps the engine alive. */
+	if (keepalive)
+		lm8323_write_pwm_one(pwm, i++, PWM_WAIT_TRIG(0xe));
+	else
+		lm8323_write_pwm_one(pwm, i++, PWM_END(1));
+
+	lm8323_write(lm, 2, LM8323_CMD_START_PWM, pwm->id);
+}
+
+static void lm8323_pwm_work(struct work_struct *work)
+{
+	struct lm8323_pwm *pwm = work_to_pwm(work);
+	int div, perstep, steps, hz, direction, keepalive;
+
+	/* Do nothing if we're already at the requested level. */
+	if (pwm->desired_brightness == pwm->brightness)
+		return;
+
+	keepalive = (pwm->desired_brightness > 0);
+	direction = (pwm->desired_brightness > pwm->brightness);
+	steps = abs(pwm->desired_brightness - pwm->brightness);
+
+	/*
+	 * Convert time (in ms) into a divisor (512 or 16 on a refclk of
+	 * 32768Hz), and number of ticks per step.
+	 */
+	if ((pwm->fade_time / steps) > (32768 / 512))
+		div = 512;
+	else
+		div = 16;
+
+	hz = 32768 / div;
+	if (pwm->fade_time < ((steps * 1000) / hz))
+		perstep = 1;
+	else
+		perstep = (hz * pwm->fade_time) / (steps * 1000);
+
+	if (perstep == 0)
+		perstep = 1;
+	else if (perstep > 63)
+		perstep = 63;
+
+	if (steps > 252) {
+		lm8323_write_pwm(pwm, keepalive, 3,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 252,
+					  direction));
+	} else if (steps > 126) {
+		lm8323_write_pwm(pwm, keepalive, 2,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 126,
+					  direction));
+	} else {
+		lm8323_write_pwm(pwm, keepalive, 1,
+				 PWM_RAMP((div == 512), perstep, steps,
+					  direction));
+	}
+
+	pwm->brightness = pwm->desired_brightness;
+}
+
+static void lm8323_pwm_set_brightness(struct led_classdev *led_cdev,
+				      enum led_brightness brightness)
+{
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	pwm->desired_brightness = brightness;
+
+	if (in_interrupt()) {
+		schedule_work(&pwm->work);
+	} else {
+		/*
+		 * Schedule PWM work as usual unless we are going into suspend
+		 */
+		mutex_lock(&lm->lock);
+		if (likely(!lm->pm_suspend))
+			schedule_work(&pwm->work);
+		else
+			lm8323_pwm_work(&pwm->work);
+		mutex_unlock(&lm->lock);
+	}
+}
+
+static ssize_t lm8323_pwm_show_time(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+
+	return sprintf(buf, "%d\n", pwm->fade_time);
+}
+
+static ssize_t lm8323_pwm_store_time(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	unsigned long res;
+	int time;
+
+	time = strict_strtoul(buf, 10, &res);
+	/* Numbers only, please. */
+	if (buf && *buf != '\n' && *(buf + 1) != '\0')
+		return -EINVAL;
+
+	pwm->fade_time = time;
+
+	return strlen(buf);
+}
+static DEVICE_ATTR(time, 0644, lm8323_pwm_show_time, lm8323_pwm_store_time);
+
+static int init_pwm(struct lm8323_chip *lm, int id, struct device *dev,
+		    const char *name)
+{
+	struct lm8323_pwm *pwm = NULL;
+
+	BUG_ON(id > 3);
+
+	switch (id) {
+	case 1:
+		pwm = &lm->pwm1;
+		break;
+	case 2:
+		pwm = &lm->pwm2;
+		break;
+	case 3:
+		pwm = &lm->pwm3;
+		break;
+	}
+
+	pwm->id = id;
+	pwm->fade_time = 0;
+	pwm->brightness = 0;
+	pwm->desired_brightness = 0;
+	if (name) {
+		pwm->cdev.name = name;
+		pwm->cdev.brightness_set = lm8323_pwm_set_brightness;
+		if (led_classdev_register(dev, &pwm->cdev) < 0) {
+			dev_err(dev, "couldn't register PWM %d\n", id);
+			return -1;
+		}
+		if (device_create_file(pwm->cdev.dev,
+					     &dev_attr_time) < 0) {
+			dev_err(dev, "couldn't register time attribute\n");
+			led_classdev_unregister(&pwm->cdev);
+			return -1;
+		}
+		INIT_WORK(&pwm->work, lm8323_pwm_work);
+		pwm->enabled = 1;
+	} else {
+		pwm->enabled = 0;
+	}
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver;
+
+static ssize_t lm8323_show_disable(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", !lm->kp_enabled);
+}
+
+static ssize_t lm8323_set_disable(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+	unsigned long res;
+	int i;
+
+	i = strict_strtoul(buf, 10, &res);
+
+	mutex_lock(&lm->lock);
+	lm->kp_enabled = !i;
+	mutex_unlock(&lm->lock);
+
+	return count;
+}
+static DEVICE_ATTR(disable_kp, 0644, lm8323_show_disable, lm8323_set_disable);
+
+static int lm8323_probe(struct i2c_client *client)
+{
+	struct input_dev *idev;
+	struct lm8323_chip *lm;
+	int i, err = 0;
+	unsigned long tmo;
+	u8 data[2];
+
+	lm = kzalloc(sizeof *lm, GFP_KERNEL);
+	if (!lm)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, lm);
+	lm->client = client;
+	lm8323_pdata = client->dev.platform_data;
+	if (!lm8323_pdata)
+		return -EINVAL; /* ? */
+
+	lm->size_x = lm8323_pdata->size_x;
+	if (lm->size_x == 0) {
+		lm->size_x = 8;
+	} else if (lm->size_x > 8) {
+		dev_err(&client->dev, "invalid x size %d specified\n",
+				lm->size_x);
+		lm->size_x = 8;
+	}
+
+	lm->size_y = lm8323_pdata->size_y;
+	if (lm->size_y == 0) {
+		lm->size_y = 12;
+	} else if (lm->size_y > 12) {
+		dev_err(&client->dev, "invalid y size %d specified\n",
+				lm->size_y);
+		lm->size_x = 12;
+	}
+
+	debug(&c->dev, "Keypad size: %d x %d\n", lm->size_x, lm->size_y);
+
+	lm->debounce_time = lm8323_pdata->debounce_time;
+	if (lm->debounce_time == 0) /* Default. */
+		lm->debounce_time = 12;
+	else if (lm->debounce_time == -1) /* Disable debounce. */
+		lm->debounce_time = 0;
+
+	lm->active_time = lm8323_pdata->active_time;
+	if (lm->active_time == 0) /* Default. */
+		lm->active_time = 500;
+	else if (lm->active_time == -1) /* Disable sleep. */
+		lm->active_time = 0;
+
+	lm8323_reset(lm);
+
+	/* Nothing's set up to service the IRQ yet, so just spin for max.
+	 * 100ms until we can configure. */
+	tmo = jiffies + msecs_to_jiffies(100);
+	while (lm8323_read(lm, LM8323_CMD_READ_INT, data, 1) == 1) {
+		if (data[0] & INT_NOINIT)
+			break;
+
+		if (time_after(jiffies, tmo)) {
+			dev_err(&client->dev,
+					"timeout waiting for initialisation\n");
+			break;
+		}
+
+		msleep(1);
+	}
+	lm8323_configure(lm);
+
+	/* If a true probe check the device */
+	if (lm8323_read_id(lm, data) != 0) {
+		dev_err(&client->dev, "device not found\n");
+		err = -ENODEV;
+		goto fail2;
+	}
+
+	if (init_pwm(lm, 1, &client->dev, lm8323_pdata->pwm1_name) < 0)
+		goto fail3;
+	if (init_pwm(lm, 2, &client->dev, lm8323_pdata->pwm2_name) < 0)
+		goto fail4;
+	if (init_pwm(lm, 3, &client->dev, lm8323_pdata->pwm3_name) < 0)
+		goto fail5;
+
+	lm->irq = lm8323_pdata->irq_gpio;
+	debug(&c->dev, "IRQ: %d\n", lm->irq);
+
+	mutex_init(&lm->lock);
+	INIT_WORK(&lm->work, lm8323_work);
+
+	err = request_irq(client->irq, lm8323_irq,
+			  IRQF_TRIGGER_FALLING | IRQF_DISABLED |
+			  IRQF_SAMPLE_RANDOM, DRIVER_NAME, lm);
+	if (err) {
+		dev_err(&client->dev, "could not get IRQ %d\n", lm->irq);
+		goto fail6;
+	}
+
+	set_irq_wake(lm->irq, 1);
+
+	lm->kp_enabled = 1;
+	err = device_create_file(&client->dev, &dev_attr_disable_kp);
+	if (err < 0)
+		goto fail7;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		err = -ENOMEM;
+		goto fail8;
+	}
+
+	if (lm8323_pdata->name)
+		idev->name = lm8323_pdata->name;
+	else
+		idev->name = "LM8323 keypad";
+	snprintf(lm->phys, sizeof(lm->phys), "%s/input-kp", client->dev.bus_id);
+	idev->phys = lm->phys;
+
+	lm->keys_down = 0;
+	idev->evbit[0] = BIT(EV_KEY);
+	for (i = 0; i < LM8323_KEYMAP_SIZE; i++) {
+		if (lm8323_pdata->keymap[i] > 0)
+			set_bit(lm8323_pdata->keymap[i], idev->keybit);
+
+		lm->keymap[i] = lm8323_pdata->keymap[i];
+	}
+
+	if (lm8323_pdata->repeat)
+		set_bit(EV_REP, idev->evbit);
+
+	lm->idev = idev;
+	if (input_register_device(idev)) {
+		dev_dbg(&client->dev, "error registering input device\n");
+		goto fail8;
+	}
+
+	return 0;
+
+fail8:
+	device_remove_file(&client->dev, &dev_attr_disable_kp);
+fail7:
+	free_irq(lm->irq, lm);
+fail6:
+	if (lm->pwm3.enabled)
+		led_classdev_unregister(&lm->pwm3.cdev);
+fail5:
+	if (lm->pwm2.enabled)
+		led_classdev_unregister(&lm->pwm2.cdev);
+fail4:
+	if (lm->pwm1.enabled)
+		led_classdev_unregister(&lm->pwm1.cdev);
+fail3:
+fail2:
+	kfree(lm);
+	return err;
+}
+
+static int lm8323_remove(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	free_irq(lm->irq, lm);
+	device_remove_file(&lm->client->dev, &dev_attr_disable_kp);
+
+	return 0;
+}
+
+/*
+ * We don't need to explicitly suspend the chip, as it already switches off
+ * when there's no activity.
+ */
+static int lm8323_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	set_irq_wake(lm->irq, 0);
+	disable_irq(lm->irq);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 1;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_suspend(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_suspend(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_suspend(&lm->pwm3.cdev);
+
+	return 0;
+}
+
+static int lm8323_resume(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 0;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_resume(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_resume(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_resume(&lm->pwm3.cdev);
+
+	enable_irq(lm->irq);
+	set_irq_wake(lm->irq, 1);
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver = {
+	.driver = {
+		.name	 = DRIVER_NAME,
+	},
+	.probe		= lm8323_probe,
+	.remove		= __exit_p(lm8323_remove),
+	.suspend	= lm8323_suspend,
+	.resume		= lm8323_resume,
+};
+
+static int __init lm8323_init(void)
+{
+	return i2c_add_driver(&lm8323_i2c_driver);
+}
+
+static void __exit lm8323_exit(void)
+{
+	i2c_del_driver(&lm8323_i2c_driver);
+}
+
+MODULE_AUTHOR("Daniel Stone");
+MODULE_DESCRIPTION("LM8323 keypad driver");
+MODULE_LICENSE("GPL");
+
+module_init(lm8323_init);
+module_exit(lm8323_exit);
diff --git a/include/linux/i2c/lm8323.h b/include/linux/i2c/lm8323.h
new file mode 100644
index 0000000..5cb09ab
--- /dev/null
+++ b/include/linux/i2c/lm8323.h
@@ -0,0 +1,39 @@
+/*
+ * include/lm8323.h
+ *
+ * Configuration for LM8323 keypad driver.
+ */
+
+#ifndef __LINUX_LM8323_H
+#define __LINUX_LM8323_H
+
+#include <linux/types.h>
+
+/*
+ * Largest keycode that the chip can send, plus one,
+ * so keys can be mapped directly at the index of the
+ * LM8323 keycode instead of subtracting one.
+ */
+#define LM8323_KEYMAP_SIZE (0x7f + 1)
+
+struct lm8323_platform_data {
+	u16 irq_gpio;
+
+	int debounce_time; /* Time to watch for key bouncing, in ms. */
+	int active_time; /* Idle time until sleep, in ms. */
+
+	int size_x;
+	int size_y;
+	int repeat : 1;
+	const s16 *keymap;
+
+	char *pwm1_name; /* Device name for PWM1. */
+	char *pwm2_name; /* Device name for PWM2. */
+	char *pwm3_name; /* Device name for PWM3. */
+
+	char *name; /* Device name. */
+};
+
+void __init lm8323_set_platform_data(struct lm8323_platform_data *pdata);
+
+#endif /* __LINUX_LM8323_H */
-- 
1.5.5.rc3


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 2/5] I2C: TSL2563: Add support for Taos tsl2563 ambient light sensor
  2008-04-09 12:04 ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
@ 2008-04-09 12:04   ` Felipe Balbi
  2008-04-09 12:04     ` [PATCH 3/5] INPUT: TOUCHSCREEN: Introduce tsc2005 driver Felipe Balbi
  2008-04-09 12:11   ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
  1 sibling, 1 reply; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 12:04 UTC (permalink / raw)
  To: linux-omap; +Cc: Tony Lindgren, Eduardo Valentin, Mathias Nyman, Felipe Balbi

From: Mathias Nyman <mathias.nyman@nokia.com>

Add support for Taos tsl2563 ambient light sensor.

Signed-off-by: Mathias Nyman <mathias.nyman@nokia.com>

Updated to build with current linux-omap, new-style
i2c driver and cleaned up checkpatch.pl issues.

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/mach-omap2/board-n800.c |    4 +
 drivers/i2c/chips/Kconfig        |   10 +
 drivers/i2c/chips/Makefile       |    2 +-
 drivers/i2c/chips/tsl2563.c      |  731 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 746 insertions(+), 1 deletions(-)
 create mode 100644 drivers/i2c/chips/tsl2563.c

diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
index 367e518..7a41245 100644
--- a/arch/arm/mach-omap2/board-n800.c
+++ b/arch/arm/mach-omap2/board-n800.c
@@ -579,6 +579,10 @@ static struct i2c_board_info __initdata n800_i2c_board_info_2[] = {
 		.irq		= OMAP_GPIO_IRQ(109),
 		.platform_data	= &lm8323_pdata,
 	},
+	{
+		I2C_BOARD_INFO("tsl2563", 0x29),
+		.type		= "tsl2563",
+	},
 };
 
 void __init nokia_n800_common_init(void)
diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index 0d566fa..dc4f140 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -202,6 +202,16 @@ config SENSORS_TSL2550
 	  This driver can also be built as a module.  If so, the module
 	  will be called tsl2550.
 
+config SENSORS_TSL2563
+       tristate "Taos TSL2563 ambient light sensor"
+       depends on I2C && HWMON
+       help
+         If you say yes here you get support for the Taos TSL2563
+         ambient light sensor.
+
+         This driver can also be built as a module.  If so, the module
+         will be called tsl2563.
+
 config MENELAUS
 	bool "TWL92330/Menelaus PM chip"
 	depends on I2C=y && ARCH_OMAP24XX
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index 7c47fc4..e20fc10 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_SENSORS_TLV320AIC23) += tlv320aic23.o
 obj-$(CONFIG_GPIOEXPANDER_OMAP)	+= gpio_expander_omap.o
 obj-$(CONFIG_MENELAUS)		+= menelaus.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
+obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
 obj-$(CONFIG_TWL4030_CORE)	+= twl4030-core.o twl4030-pwrirq.o
 obj-$(CONFIG_TWL4030_GPIO)	+= twl4030-gpio.o
 obj-$(CONFIG_TWL4030_USB)	+= twl4030-usb.o
@@ -33,4 +34,3 @@ obj-$(CONFIG_RTC_X1205_I2C)	+= x1205.o
 ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
 EXTRA_CFLAGS += -DDEBUG
 endif
-
diff --git a/drivers/i2c/chips/tsl2563.c b/drivers/i2c/chips/tsl2563.c
new file mode 100644
index 0000000..cf2e313
--- /dev/null
+++ b/drivers/i2c/chips/tsl2563.c
@@ -0,0 +1,731 @@
+/*
+ * drivers/i2c/chips/tsl2563.c
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Written by Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
+ * Contact: Mathias Nyman <mathias.nyman@nokia.com>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/hwmon.h>
+#include <linux/err.h>
+#include <asm/arch/board.h>
+
+#define DRIVER_NAME  "tsl2563"
+
+/* Use this many bits for fraction part. */
+#define ADC_FRAC_BITS		(14)
+
+/* Given number of 1/10000's in ADC_FRAC_BITS precision. */
+#define FRAC10K(f)		(((f) * (1L << (ADC_FRAC_BITS))) / (10000))
+
+/* Bits used for fraction in calibration coefficients.*/
+#define CALIB_FRAC_BITS		(10)
+/* 0.5 in CALIB_FRAC_BITS precision */
+#define CALIB_FRAC_HALF		(1 << (CALIB_FRAC_BITS - 1))
+/* Make a fraction from a number n that was multiplied with b. */
+#define CALIB_FRAC(n, b)	(((n) << CALIB_FRAC_BITS) / (b))
+/* Decimal 10^(digits in sysfs presentation) */
+#define CALIB_BASE_SYSFS	(1000)
+
+#define TSL2563_CMD		(0x80)
+#define TSL2563_CLEARINT	(0x40)
+
+#define TSL2563_REG_CTRL	(0x00)
+#define TSL2563_REG_TIMING	(0x01)
+#define TSL2563_REG_LOWLOW	(0x02) /* data0 low threshold, 2 bytes */
+#define TSL2563_REG_LOWHIGH	(0x03)
+#define TSL2563_REG_HIGHLOW	(0x04) /* data0 high threshold, 2 bytes */
+#define TSL2563_REG_HIGHHIGH	(0x05)
+#define TSL2563_REG_INT		(0x06)
+#define TSL2563_REG_ID		(0x0a)
+#define TSL2563_REG_DATA0LOW	(0x0c) /* broadband sensor value, 2 bytes */
+#define TSL2563_REG_DATA0HIGH	(0x0d)
+#define TSL2563_REG_DATA1LOW	(0x0e) /* infrared sensor value, 2 bytes */
+#define TSL2563_REG_DATA1HIGH	(0x0f)
+
+#define TSL2563_CMD_POWER_ON	(0x03)
+#define TSL2563_CMD_POWER_OFF	(0x00)
+#define TSL2563_CTRL_POWER_MASK	(0x03)
+
+#define TSL2563_TIMING_13MS	(0x00)
+#define TSL2563_TIMING_100MS	(0x01)
+#define TSL2563_TIMING_400MS	(0x02)
+#define TSL2563_TIMING_MASK	(0x03)
+#define TSL2563_TIMING_GAIN16	(0x10)
+#define TSL2563_TIMING_GAIN1	(0x00)
+
+#define TSL2563_INT_DISBLED	(0x00)
+#define TSL2563_INT_LEVEL	(0x10)
+#define TSL2563_INT_PERSIST(n)	((n) & 0x0F)
+
+struct tsl2563_gainlevel_coeff {
+	u8 gaintime;
+	u16 min;
+	u16 max;
+};
+
+static struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = {
+	{
+		.gaintime	= TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16,
+		.min		= 0,
+		.max		= 65534,
+	}, {
+		.gaintime	= TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1,
+		.min		= 2048,
+		.max		= 65534,
+	}, {
+		.gaintime	= TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1,
+		.min		= 4095,
+		.max		= 37177,
+	}, {
+		.gaintime	= TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1,
+		.min		= 3000,
+		.max		= 65535,
+	},
+};
+
+struct tsl2563_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	struct device		*hwmon_dev;
+
+	/* Remember state for suspend and resume functions */
+	pm_message_t		state;
+
+	struct tsl2563_gainlevel_coeff *gainlevel;
+
+	/* Thresholds are in lux */
+	u16			low_thres;
+	u16			high_thres;
+	u8			intr;
+
+	/* Calibration coefficients */
+	u32			calib0;
+	u32			calib1;
+
+	/* Cache current values, to be returned while suspended */
+	u32			data0;
+	u32			data1;
+};
+
+static int tsl2563_write(struct i2c_client *client, u8 reg, u8 value)
+{
+	int ret;
+	u8 buf[2];
+
+	buf[0] = TSL2563_CMD | reg;
+	buf[1] = value;
+
+	ret = i2c_master_send(client, buf, sizeof(buf));
+	return (ret == sizeof(buf)) ? 0 : ret;
+}
+
+static int tsl2563_read(struct i2c_client *client, u8 reg, void *buf, int len)
+{
+	int ret;
+	u8 cmd = TSL2563_CMD | reg;
+
+	ret = i2c_master_send(client, &cmd, sizeof(cmd));
+	if (ret != sizeof(cmd))
+		return ret;
+
+	return i2c_master_recv(client, buf, len);
+}
+
+static int tsl2563_set_power(struct tsl2563_chip *chip, int on)
+{
+	struct i2c_client *client = chip->client;
+	u8 cmd;
+
+	cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF;
+	return tsl2563_write(client, TSL2563_REG_CTRL, cmd);
+}
+
+/*
+ * Return value is 0 for off, 1 for on, or a negative error
+ * code if reading failed.
+ */
+static int tsl2563_get_power(struct tsl2563_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+	u8 val;
+
+	ret = tsl2563_read(client, TSL2563_REG_CTRL, &val, sizeof(val));
+	if (ret != sizeof(val))
+		return ret;
+
+	return (val & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON;
+}
+
+static int tsl2563_configure(struct tsl2563_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+
+	ret = tsl2563_write(client, TSL2563_REG_TIMING,
+			chip->gainlevel->gaintime);
+	if (ret)
+		goto out;
+
+	ret = tsl2563_write(client, TSL2563_REG_INT, chip->intr);
+
+out:
+	return ret;
+}
+
+static int tsl2563_detect(struct tsl2563_chip *chip)
+{
+	int ret;
+
+	ret = tsl2563_set_power(chip, 1);
+	if (ret)
+		return ret;
+
+	ret = tsl2563_get_power(chip);
+	if (ret < 0)
+		return ret;
+
+	return ret ? 0 : -ENODEV;
+}
+
+static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+
+	ret = tsl2563_read(client, TSL2563_REG_ID, id, sizeof(*id));
+	if (ret != sizeof(*id))
+		return ret;
+
+	return 0;
+}
+
+/*
+ * "Normalized" ADC value is one obtained with 400ms of integration time and
+ * 16x gain. This function returns the number of bits of shift needed to
+ * convert between normalized values and HW values obtained using given
+ * timing and gain settings.
+ */
+static int adc_shiftbits(u8 timing)
+{
+	int shift = 0;
+
+	switch (timing & TSL2563_TIMING_MASK) {
+	case TSL2563_TIMING_13MS:
+		shift += 5;
+		break;
+	case TSL2563_TIMING_100MS:
+		shift += 2;
+		break;
+	case TSL2563_TIMING_400MS:
+		/* no-op */
+		break;
+	}
+
+	if (!(timing & TSL2563_TIMING_GAIN16))
+		shift += 4;
+
+	return shift;
+}
+
+/* Convert a HW ADC value to normalized scale. */
+static u32 normalize_adc(u16 adc, u8 timing)
+{
+	return adc << adc_shiftbits(timing);
+}
+
+static void tsl2563_wait_adc(struct tsl2563_chip *chip)
+{
+	unsigned int delay;
+
+	switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) {
+	case TSL2563_TIMING_13MS:
+		delay = 14;
+		break;
+	case TSL2563_TIMING_100MS:
+		delay = 101;
+		break;
+	default:
+		delay = 402;
+	}
+	/*
+	 * TODO: Make sure that we wait at least required delay but why we
+	 * have to extend it one tick more?
+	 */
+	schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2);
+}
+
+static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc)
+{
+	struct i2c_client *client = chip->client;
+
+	if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) {
+
+		(adc > chip->gainlevel->max) ?
+			chip->gainlevel++ : chip->gainlevel--;
+
+		tsl2563_write(client, TSL2563_REG_TIMING,
+			      chip->gainlevel->gaintime);
+
+		tsl2563_wait_adc(chip);
+		tsl2563_wait_adc(chip);
+
+		return 1;
+	} else
+		return 0;
+}
+
+static int tsl2563_get_adc(struct tsl2563_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	u8 buf0[2], buf1[2];
+	u16 adc0, adc1;
+	int retry = 1;
+	int ret = 0;
+
+	if (chip->state.event != PM_EVENT_ON)
+		goto out;
+
+	while (retry) {
+		ret = tsl2563_read(client,
+				   TSL2563_REG_DATA0LOW | TSL2563_CLEARINT,
+				   buf0, sizeof(buf0));
+		if (ret != sizeof(buf0))
+			goto out;
+
+		ret = tsl2563_read(client, TSL2563_REG_DATA1LOW,
+				   buf1, sizeof(buf1));
+		if (ret != sizeof(buf1))
+			goto out;
+
+		adc0 = (buf0[1] << 8) + buf0[0];
+		adc1 = (buf1[1] << 8) + buf1[0];
+
+		retry = tsl2563_adjust_gainlevel(chip, adc0);
+	}
+
+	chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime);
+	chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime);
+
+	ret = 0;
+out:
+	return ret;
+}
+
+static inline int calib_to_sysfs(u32 calib)
+{
+	return (int) (((calib * CALIB_BASE_SYSFS) +
+		       CALIB_FRAC_HALF) >> CALIB_FRAC_BITS);
+}
+
+static inline u32 calib_from_sysfs(int value)
+{
+	return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS;
+}
+
+/*
+ * Conversions between lux and ADC values.
+ *
+ * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are
+ * appropriate constants. Different constants are needed for different
+ * kinds of light, determined by the ratio adc1/adc0 (basically the ratio
+ * of the intensities in infrared and visible wavelengths). lux_table below
+ * lists the upper threshold of the adc1/adc0 ratio and the corresponding
+ * constants.
+ */
+
+struct tsl2563_lux_coeff {
+	unsigned long ch_ratio;
+	unsigned long ch0_coeff;
+	unsigned long ch1_coeff;
+};
+
+static const struct tsl2563_lux_coeff lux_table[] = {
+	{
+		.ch_ratio	= FRAC10K(1300),
+		.ch0_coeff	= FRAC10K(315),
+		.ch1_coeff	= FRAC10K(262),
+	}, {
+		.ch_ratio	= FRAC10K(2600),
+		.ch0_coeff	= FRAC10K(337),
+		.ch1_coeff	= FRAC10K(430),
+	}, {
+		.ch_ratio	= FRAC10K(3900),
+		.ch0_coeff	= FRAC10K(363),
+		.ch1_coeff	= FRAC10K(529),
+	}, {
+		.ch_ratio	= FRAC10K(5200),
+		.ch0_coeff	= FRAC10K(392),
+		.ch1_coeff	= FRAC10K(605),
+	}, {
+		.ch_ratio	= FRAC10K(6500),
+		.ch0_coeff	= FRAC10K(229),
+		.ch1_coeff	= FRAC10K(291),
+	}, {
+		.ch_ratio	= FRAC10K(8000),
+		.ch0_coeff	= FRAC10K(157),
+		.ch1_coeff	= FRAC10K(180),
+	}, {
+		.ch_ratio	= FRAC10K(13000),
+		.ch0_coeff	= FRAC10K(34),
+		.ch1_coeff	= FRAC10K(26),
+	}, {
+		.ch_ratio	= ULONG_MAX,
+		.ch0_coeff	= 0,
+		.ch1_coeff	= 0,
+	},
+};
+
+/*
+ * Convert normalized, scaled ADC values to lux.
+ */
+static unsigned int adc_to_lux(u32 adc0, u32 adc1)
+{
+	const struct tsl2563_lux_coeff *lp = lux_table;
+	unsigned long ratio, lux, ch0 = adc0, ch1 = adc1;
+
+	ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX;
+
+	while (lp->ch_ratio < ratio)
+		lp++;
+
+	lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff;
+
+	return (unsigned int) (lux >> ADC_FRAC_BITS);
+}
+
+/*--------------------------------------------------------------*/
+/*                      Sysfs interface                         */
+/*--------------------------------------------------------------*/
+
+static ssize_t tsl2563_adc0_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_get_adc(chip);
+	if (ret)
+		return ret;
+
+	ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->data0);
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static ssize_t tsl2563_adc1_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_get_adc(chip);
+	if (ret)
+		return ret;
+
+	ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->data1);
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+/* Apply calibration coefficient to ADC count. */
+static u32 calib_adc(u32 adc, u32 calib)
+{
+	unsigned long scaled = adc;
+
+	scaled *= calib;
+	scaled >>= CALIB_FRAC_BITS;
+
+	return (u32) scaled;
+}
+
+static ssize_t tsl2563_lux_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	u32 calib0, calib1;
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_get_adc(chip);
+	if (ret)
+		goto out;
+
+	calib0 = calib_adc(chip->data0, chip->calib0);
+	calib1 = calib_adc(chip->data1, chip->calib1);
+
+	ret = snprintf(buf, PAGE_SIZE, "%d\n", adc_to_lux(calib0, calib1));
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static ssize_t format_calib(char *buf, int len, u32 calib)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n", calib_to_sysfs(calib));
+}
+
+static ssize_t tsl2563_calib0_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+	ret = format_calib(buf, PAGE_SIZE, chip->calib0);
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static ssize_t tsl2563_calib1_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+	ret = format_calib(buf, PAGE_SIZE, chip->calib1);
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static int do_calib_store(struct device *dev, const char *buf, size_t len,
+			  int ch)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int value;
+	u32 calib;
+
+	if (1 != sscanf(buf, "%d", &value))
+		return -EINVAL;
+
+	calib = calib_from_sysfs(value);
+
+	if (ch)
+		chip->calib1 = calib;
+	else
+		chip->calib0 = calib;
+
+	return len;
+}
+
+static ssize_t tsl2563_calib0_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t len)
+{
+	return do_calib_store(dev, buf, len, 0);
+}
+
+static ssize_t tsl2563_calib1_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t len)
+{
+	return do_calib_store(dev, buf, len, 1);
+}
+
+static DEVICE_ATTR(adc0, S_IRUGO, tsl2563_adc0_show, NULL);
+static DEVICE_ATTR(adc1, S_IRUGO, tsl2563_adc1_show, NULL);
+static DEVICE_ATTR(lux, S_IRUGO, tsl2563_lux_show, NULL);
+static DEVICE_ATTR(calib0, S_IRUGO | S_IWUSR,
+		   tsl2563_calib0_show, tsl2563_calib0_store);
+static DEVICE_ATTR(calib1, S_IRUGO | S_IWUSR,
+		   tsl2563_calib1_show, tsl2563_calib1_store);
+
+static struct attribute *tsl2563_attributes[] = {
+	&dev_attr_adc0.attr,
+	&dev_attr_adc1.attr,
+	&dev_attr_lux.attr,
+	&dev_attr_calib0.attr,
+	&dev_attr_calib1.attr,
+	NULL
+};
+
+static const struct attribute_group tsl2563_group = {
+	.attrs = tsl2563_attributes,
+};
+
+static int tsl2563_register_sysfs(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+
+	return sysfs_create_group(&dev->kobj, &tsl2563_group);
+}
+
+static void tsl2563_unregister_sysfs(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+
+	sysfs_remove_group(&dev->kobj, &tsl2563_group);
+}
+
+/*--------------------------------------------------------------*/
+/*                      Probe, Attach, Remove                   */
+/*--------------------------------------------------------------*/
+static struct i2c_driver tsl2563_i2c_driver;
+
+static int tsl2563_probe(struct i2c_client *client)
+{
+	struct tsl2563_chip *chip;
+	int err = 0;
+	u8 id;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, chip);
+	chip->client = client;
+
+	err = tsl2563_detect(chip);
+	if (err) {
+		dev_err(&client->dev, "device not found, error %d \n", -err);
+		goto fail1;
+	}
+
+	err = tsl2563_read_id(chip, &id);
+	if (err)
+		goto fail1;
+
+	mutex_init(&chip->lock);
+
+	/* Default values used until userspace says otherwise */
+	chip->low_thres = 0x0;
+	chip->high_thres = 0xffff;
+	chip->gainlevel = tsl2563_gainlevel_table;
+	chip->intr = TSL2563_INT_PERSIST(4);
+	chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS);
+	chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS);
+
+	dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f);
+
+	err = tsl2563_configure(chip);
+	if (err)
+		goto fail1;
+
+	chip->hwmon_dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(chip->hwmon_dev))
+		goto fail1;
+
+	err = tsl2563_register_sysfs(client);
+	if (err) {
+		dev_err(&client->dev, "sysfs registration failed, %d\n", err);
+		goto fail2;
+	}
+
+	return 0;
+fail2:
+	hwmon_device_unregister(chip->hwmon_dev);
+fail1:
+	kfree(chip);
+	return err;
+}
+
+static int tsl2563_remove(struct i2c_client *client)
+{
+	struct tsl2563_chip *chip = i2c_get_clientdata(client);
+
+	tsl2563_unregister_sysfs(client);
+	hwmon_device_unregister(chip->hwmon_dev);
+
+	kfree(chip);
+	return 0;
+}
+
+static int tsl2563_suspend(struct i2c_client *client, pm_message_t state)
+{
+	struct tsl2563_chip *chip = i2c_get_clientdata(client);
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_set_power(chip, 0);
+	if (ret)
+		goto out;
+
+	chip->state = state;
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static int tsl2563_resume(struct i2c_client *client)
+{
+	struct tsl2563_chip *chip = i2c_get_clientdata(client);
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_set_power(chip, 1);
+	if (ret)
+		goto out;
+
+	ret = tsl2563_configure(chip);
+	if (ret)
+		goto out;
+
+	chip->state.event = PM_EVENT_ON;
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static struct i2c_driver tsl2563_i2c_driver = {
+	.driver = {
+		.name	 = DRIVER_NAME,
+	},
+	.suspend	= tsl2563_suspend,
+	.resume		= tsl2563_resume,
+	.probe		= tsl2563_probe,
+	.remove		= __exit_p(tsl2563_remove),
+};
+
+static int __init tsl2563_init(void)
+{
+	return i2c_add_driver(&tsl2563_i2c_driver);
+}
+
+static void __exit tsl2563_exit(void)
+{
+	i2c_del_driver(&tsl2563_i2c_driver);
+}
+
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_DESCRIPTION("tsl2563 light sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(tsl2563_init);
+module_exit(tsl2563_exit);
-- 
1.5.5.rc3


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 3/5] INPUT: TOUCHSCREEN: Introduce tsc2005 driver
  2008-04-09 12:04   ` [PATCH 2/5] I2C: TSL2563: Add support for Taos tsl2563 ambient light sensor Felipe Balbi
@ 2008-04-09 12:04     ` Felipe Balbi
  2008-04-09 12:04       ` [PATCH 4/5] I2C: LP5521: Introduce lp5521 LED driver Felipe Balbi
  0 siblings, 1 reply; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 12:04 UTC (permalink / raw)
  To: linux-omap; +Cc: Tony Lindgren, Eduardo Valentin, Lauri Leukkunen, Felipe Balbi

From: Lauri Leukkunen <lauri.leukkunen@nokia.com>

Introduce n810's tsc2005 driver

Signed-off-by: Lauri Leukkunen <lauri.leukkunen@nokia.com>

Updated to build with current linux-omap.

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/mach-omap2/board-n800.c    |   95 +++++-
 drivers/input/touchscreen/Kconfig   |    5 +
 drivers/input/touchscreen/Makefile  |    1 +
 drivers/input/touchscreen/tsc2005.c |  739 +++++++++++++++++++++++++++++++++++
 include/linux/spi/tsc2005.h         |   29 ++
 5 files changed, 865 insertions(+), 4 deletions(-)
 create mode 100644 drivers/input/touchscreen/tsc2005.c
 create mode 100644 include/linux/spi/tsc2005.h

diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
index 7a41245..9ed432f 100644
--- a/arch/arm/mach-omap2/board-n800.c
+++ b/arch/arm/mach-omap2/board-n800.c
@@ -18,6 +18,7 @@
 #include <linux/platform_device.h>
 #include <linux/spi/spi.h>
 #include <linux/spi/tsc2301.h>
+#include <linux/spi/tsc2005.h>
 #include <linux/input.h>
 #include <linux/delay.h>
 #include <linux/interrupt.h>
@@ -371,21 +372,34 @@ static struct omap2_mcspi_device_config cx3110x_mcspi_config = {
 	.single_channel = 1,
 };
 
+#ifdef CONFIG_TOUCHSCREEN_TSC2005
+static struct tsc2005_platform_data tsc2005_config = {
+	.reset_gpio = 94,
+	.dav_gpio = 106
+};
+
+static struct omap2_mcspi_device_config tsc2005_mcspi_config = {
+	.turbo_mode	= 0,
+	.single_channel = 1,
+};
+#endif
+
 static struct spi_board_info n800_spi_board_info[] __initdata = {
-	[0] = {
+	{
 		.modalias	= "lcd_mipid",
 		.bus_num	= 1,
 		.chip_select	= 1,
 		.max_speed_hz	= 4000000,
 		.controller_data= &mipid_mcspi_config,
 		.platform_data	= &n800_mipid_platform_data,
-	}, [1] = {
+	}, {
 		.modalias	= "cx3110x",
 		.bus_num	= 2,
 		.chip_select	= 0,
 		.max_speed_hz   = 48000000,
 		.controller_data= &cx3110x_mcspi_config,
-	}, [2] = {
+	},
+	{
 		.modalias	= "tsc2301",
 		.bus_num	= 1,
 		.chip_select	= 0,
@@ -395,6 +409,73 @@ static struct spi_board_info n800_spi_board_info[] __initdata = {
 	},
 };
 
+static struct spi_board_info n810_spi_board_info[] __initdata = {
+	{
+		.modalias	 = "lcd_mipid",
+		.bus_num	 = 1,
+		.chip_select	 = 1,
+		.max_speed_hz	 = 4000000,
+		.controller_data = &mipid_mcspi_config,
+		.platform_data	 = &n800_mipid_platform_data,
+	},
+	{
+		.modalias	 = "cx3110x",
+		.bus_num	 = 2,
+		.chip_select	 = 0,
+		.max_speed_hz    = 48000000,
+		.controller_data = &cx3110x_mcspi_config,
+	},
+	{
+		.modalias	 = "tsc2005",
+		.bus_num	 = 1,
+		.chip_select	 = 0,
+		.max_speed_hz    = 6000000,
+		.controller_data = &tsc2005_mcspi_config,
+		.platform_data   = &tsc2005_config,
+	},
+};
+
+static void __init tsc2005_set_config(void)
+{
+	const struct omap_lcd_config *conf;
+
+	conf = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config);
+	if (conf != NULL) {
+#ifdef CONFIG_TOUCHSCREEN_TSC2005
+		if (strcmp(conf->panel_name, "lph8923") == 0) {
+			tsc2005_config.ts_x_plate_ohm = 180;
+			tsc2005_config.ts_hw_avg = 0;
+			tsc2005_config.ts_ignore_last = 0;
+			tsc2005_config.ts_touch_pressure = 1500;
+			tsc2005_config.ts_stab_time = 100;
+			tsc2005_config.ts_pressure_max = 2048;
+			tsc2005_config.ts_pressure_fudge = 2;
+			tsc2005_config.ts_x_max = 4096;
+			tsc2005_config.ts_x_fudge = 4;
+			tsc2005_config.ts_y_max = 4096;
+			tsc2005_config.ts_y_fudge = 7;
+		} else if (strcmp(conf->panel_name, "ls041y3") == 0) {
+			tsc2005_config.ts_x_plate_ohm = 280;
+			tsc2005_config.ts_hw_avg = 0;
+			tsc2005_config.ts_ignore_last = 0;
+			tsc2005_config.ts_touch_pressure = 1500;
+			tsc2005_config.ts_stab_time = 1000;
+			tsc2005_config.ts_pressure_max = 2048;
+			tsc2005_config.ts_pressure_fudge = 2;
+			tsc2005_config.ts_x_max = 4096;
+			tsc2005_config.ts_x_fudge = 4;
+			tsc2005_config.ts_y_max = 4096;
+			tsc2005_config.ts_y_fudge = 7;
+		} else {
+			printk(KERN_ERR "Unknown panel type, set default "
+			       "touchscreen configuration\n");
+			tsc2005_config.ts_x_plate_ohm = 200;
+			tsc2005_config.ts_stab_time = 100;
+		}
+#endif
+	}
+}
+
 #if defined(CONFIG_CBUS_RETU) && defined(CONFIG_LEDS_OMAP_PWM)
 
 void retu_keypad_led_set_power(struct omap_pwm_led_platform_data *self,
@@ -595,8 +676,14 @@ void __init nokia_n800_common_init(void)
 	n800_dsp_init();
 	n800_usb_init();
 	n800_cam_init();
-	spi_register_board_info(n800_spi_board_info,
+	if (machine_is_nokia_n800())
+		spi_register_board_info(n800_spi_board_info,
 				ARRAY_SIZE(n800_spi_board_info));
+	if (machine_is_nokia_n810()) {
+		tsc2005_set_config();
+		spi_register_board_info(n810_spi_board_info,
+				ARRAY_SIZE(n810_spi_board_info));
+	}
 	omap_serial_init();
 	omap_register_i2c_bus(1, 400, n800_i2c_board_info_1,
 			      ARRAY_SIZE(n800_i2c_board_info_1));
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index f3c45b6..1542e16 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -185,6 +185,11 @@ config TOUCHSCREEN_UCB1400
 	  To compile this driver as a module, choose M here: the
 	  module will be called ucb1400_ts.
 
+config TOUCHSCREEN_TSC2005
+	tristate "TSC2005 touchscreen support"
+	help
+	  Say Y here for if you are using the touchscreen features of TSC2301.
+
 config TOUCHSCREEN_TSC2102
 	tristate "TSC 2102 based touchscreens"
 	depends on SPI_MASTER
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index bdd2571..51d3a23 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_TOUCHSCREEN_PENMOUNT)	+= penmount.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
 obj-$(CONFIG_TOUCHSCREEN_UCB1400)	+= ucb1400_ts.o
+obj-$(CONFIG_TOUCHSCREEN_TSC2005)	+= tsc2005.o
 obj-$(CONFIG_TOUCHSCREEN_TSC2102)	+= tsc2102_ts.o
 obj-$(CONFIG_TOUCHSCREEN_OMAP)	+= omap/
 obj-$(CONFIG_TOUCHSCREEN_TSC210X)	+= tsc210x_ts.o
diff --git a/drivers/input/touchscreen/tsc2005.c b/drivers/input/touchscreen/tsc2005.c
new file mode 100644
index 0000000..035d209
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2005.c
@@ -0,0 +1,739 @@
+/*
+ * TSC2005 touchscreen driver
+ *
+ * Copyright (C) 2006-2008 Nokia Corporation
+ *
+ * Author: Lauri Leukkunen <lauri.leukkunen@nokia.com>
+ * based on TSC2301 driver by Klaus K. Pedersen <klaus.k.pedersen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+
+#ifdef CONFIG_ARCH_OMAP
+#include <asm/arch/gpio.h>
+#endif
+
+#include <linux/spi/tsc2005.h>
+
+/**
+ * The touchscreen interface operates as follows:
+ *
+ * Initialize:
+ *    Request access to GPIO103 (DAV)
+ *    tsc2005_dav_irq_handler will trigger when DAV line goes down
+ *
+ *  1) Pen is pressed against touchscreeen
+ *  2) TSC2005 performs AD conversion
+ *  3) After the conversion is done TSC2005 drives DAV line down
+ *  4) GPIO IRQ is received and tsc2005_dav_irq_handler is called
+ *  5) tsc2005_ts_irq_handler queues up an spi transfer to fetch
+ *     the x, y, z1, z2 values
+ *  6) tsc2005_ts_rx() reports coordinates to input layer and
+ *     sets up tsc2005_ts_timer() to be called after TSC2005_TS_SCAN_TIME
+ *  7)  When the penup_timer expires, there have not been DAV interrupts
+ *     during the last 20ms which means the pen has been lifted.
+ */
+
+#define TSC2005_HZ	(14000000)
+
+#define TSC2005_CMD	(0x80)
+#define TSC2005_REG	(0x00)
+
+#define TSC2005_CMD_STOP	(1)
+#define TSC2005_CMD_10BIT	(0 << 2)
+#define TSC2005_CMD_12BIT	(1 << 2)
+
+#define TSC2005_CMD_SCAN_XYZZ	(0 << 3)
+#define TSC2005_CMD_SCAN_XY	(1 << 3)
+#define TSC2005_CMD_SCAN_X	(2 << 3)
+#define TSC2005_CMD_SCAN_Y	(3 << 3)
+#define TSC2005_CMD_SCAN_ZZ	(4 << 3)
+#define TSC2005_CMD_AUX_SINGLE	(5 << 3)
+#define TSC2005_CMD_TEMP1	(6 << 3)
+#define TSC2005_CMD_TEMP2	(7 << 3)
+#define TSC2005_CMD_AUX_CONT	(8 << 3)
+#define TSC2005_CMD_TEST_X_CONN	(9 << 3)
+#define TSC2005_CMD_TEST_Y_CONN	(10 << 3)
+/* command 11 reserved */
+#define TSC2005_CMD_TEST_SHORT	(12 << 3)
+#define TSC2005_CMD_DRIVE_XX	(13 << 3)
+#define TSC2005_CMD_DRIVE_YY	(14 << 3)
+#define TSC2005_CMD_DRIVE_YX	(15 << 3)
+
+#define TSC2005_REG_X		(0 << 3)
+#define TSC2005_REG_Y		(1 << 3)
+#define TSC2005_REG_Z1		(2 << 3)
+#define TSC2005_REG_Z2		(3 << 3)
+#define TSC2005_REG_AUX		(4 << 3)
+#define TSC2005_REG_TEMP1	(5 << 3)
+#define TSC2005_REG_TEMP2	(6 << 3)
+#define TSC2005_REG_STATUS	(7 << 3)
+#define TSC2005_REG_AUX_HIGH	(8 << 3)
+#define TSC2005_REG_AUX_LOW	(9 << 3)
+#define TSC2005_REG_TEMP_HIGH	(10 << 3)
+#define TSC2005_REG_TEMP_LOW	(11 << 3)
+#define TSC2005_REG_CFR0	(12 << 3)
+#define TSC2005_REG_CFR1	(13 << 3)
+#define TSC2005_REG_CFR2	(14 << 3)
+#define TSC2005_REG_FUNCTION	(15 << 3)
+
+#define TSC2005_REG_PND0	(1 << 1)
+#define TSC2005_REG_READ	(0x01)
+#define TSC2005_REG_WRITE	(0x00)
+
+
+#define TSC2005_CFR0_LONGSAMPLING	(1)
+#define TSC2005_CFR0_DETECTINWAIT	(1 << 1)
+#define TSC2005_CFR0_SENSETIME_32US	(0)
+#define TSC2005_CFR0_SENSETIME_96US	(1 << 2)
+#define TSC2005_CFR0_SENSETIME_544US	(1 << 3)
+#define TSC2005_CFR0_SENSETIME_2080US	(1 << 4)
+#define TSC2005_CFR0_SENSETIME_2656US	(0x001C)
+#define TSC2005_CFR0_PRECHARGE_20US	(0x0000)
+#define TSC2005_CFR0_PRECHARGE_84US	(0x0020)
+#define TSC2005_CFR0_PRECHARGE_276US	(0x0040)
+#define TSC2005_CFR0_PRECHARGE_1044US	(0x0080)
+#define TSC2005_CFR0_PRECHARGE_1364US	(0x00E0)
+#define TSC2005_CFR0_STABTIME_0US	(0x0000)
+#define TSC2005_CFR0_STABTIME_100US	(0x0100)
+#define TSC2005_CFR0_STABTIME_500US	(0x0200)
+#define TSC2005_CFR0_STABTIME_1MS	(0x0300)
+#define TSC2005_CFR0_STABTIME_5MS	(0x0400)
+#define TSC2005_CFR0_STABTIME_100MS	(0x0700)
+#define TSC2005_CFR0_CLOCK_4MHZ		(0x0000)
+#define TSC2005_CFR0_CLOCK_2MHZ		(0x0800)
+#define TSC2005_CFR0_CLOCK_1MHZ		(0x1000)
+#define TSC2005_CFR0_RESOLUTION12	(0x2000)
+#define TSC2005_CFR0_STATUS		(0x4000)
+#define TSC2005_CFR0_PENMODE		(0x8000)
+
+#define TSC2005_CFR0_INITVALUE	(TSC2005_CFR0_STABTIME_1MS  |	\
+				 TSC2005_CFR0_CLOCK_1MHZ    |	\
+				 TSC2005_CFR0_RESOLUTION12  |	\
+				 TSC2005_CFR0_PRECHARGE_276US | \
+				 TSC2005_CFR0_PENMODE)
+
+#define TSC2005_CFR1_BATCHDELAY_0MS	(0x0000)
+#define TSC2005_CFR1_BATCHDELAY_1MS	(0x0001)
+#define TSC2005_CFR1_BATCHDELAY_2MS	(0x0002)
+#define TSC2005_CFR1_BATCHDELAY_4MS	(0x0003)
+#define TSC2005_CFR1_BATCHDELAY_10MS	(0x0004)
+#define TSC2005_CFR1_BATCHDELAY_20MS	(0x0005)
+#define TSC2005_CFR1_BATCHDELAY_40MS	(0x0006)
+#define TSC2005_CFR1_BATCHDELAY_100MS	(0x0007)
+
+#define TSC2005_CFR1_INITVALUE	(TSC2005_CFR1_BATCHDELAY_2MS)
+
+#define TSC2005_CFR2_MAVE_TEMP	(0x0001)
+#define TSC2005_CFR2_MAVE_AUX	(0x0002)
+#define TSC2005_CFR2_MAVE_Z	(0x0004)
+#define TSC2005_CFR2_MAVE_Y	(0x0008)
+#define TSC2005_CFR2_MAVE_X	(0x0010)
+#define TSC2005_CFR2_AVG_1	(0x0000)
+#define TSC2005_CFR2_AVG_3	(0x0400)
+#define TSC2005_CFR2_AVG_7	(0x0800)
+#define TSC2005_CFR2_MEDIUM_1	(0x0000)
+#define TSC2005_CFR2_MEDIUM_3	(0x1000)
+#define TSC2005_CFR2_MEDIUM_7	(0x2000)
+#define TSC2005_CFR2_MEDIUM_15	(0x3000)
+
+#define TSC2005_CFR2_IRQ_DAV	(0x4000)
+#define TSC2005_CFR2_IRQ_PEN	(0x8000)
+#define TSC2005_CFR2_IRQ_PENDAV	(0x0000)
+
+#define TSC2005_CFR2_INITVALUE	(TSC2005_CFR2_IRQ_DAV   |	\
+				 TSC2005_CFR2_MAVE_X    |	\
+				 TSC2005_CFR2_MAVE_Y    |	\
+				 TSC2005_CFR2_MAVE_Z    |	\
+				 TSC2005_CFR2_MEDIUM_15 |	\
+				 TSC2005_CFR2_AVG_7)
+
+#define MAX_12BIT					((1 << 12) - 1)
+#define TS_SAMPLES					4
+#define TS_RECT_SIZE					8
+#define TSC2005_TS_PENUP_TIME				20
+
+static const u32 tsc2005_read_reg[] = {
+	(TSC2005_REG | TSC2005_REG_X | TSC2005_REG_READ) << 16,
+	(TSC2005_REG | TSC2005_REG_Y | TSC2005_REG_READ) << 16,
+	(TSC2005_REG | TSC2005_REG_Z1 | TSC2005_REG_READ) << 16,
+	(TSC2005_REG | TSC2005_REG_Z2 | TSC2005_REG_READ) << 16,
+};
+#define NUM_READ_REGS	(sizeof(tsc2005_read_reg)/sizeof(tsc2005_read_reg[0]))
+
+struct tsc2005 {
+	struct spi_device	*spi;
+
+	struct input_dev	*idev;
+	char			phys[32];
+	struct timer_list	penup_timer;
+	spinlock_t		lock;
+	struct mutex		mutex;
+
+	struct spi_message	read_msg;
+	struct spi_transfer	read_xfer[NUM_READ_REGS];
+	u32                     data[NUM_READ_REGS];
+
+	/* previous x,y,z */
+	int			x;
+	int			y;
+	int			p;
+	/* average accumulators for each component */
+	int			sample_cnt;
+	int			avg_x;
+	int			avg_y;
+	int			avg_z1;
+	int			avg_z2;
+	/* configuration */
+	int			x_plate_ohm;
+	int			hw_avg_max;
+	int			stab_time;
+	int			p_max;
+	int			touch_pressure;
+	int			irq;
+	s16			dav_gpio;
+	/* status */
+	u8			sample_sent;
+	u8			pen_down;
+	u8			disabled;
+	u8			disable_depth;
+	u8			spi_active;
+};
+
+static void tsc2005_cmd(struct tsc2005 *ts, u8 cmd)
+{
+	u16 data = TSC2005_CMD | TSC2005_CMD_12BIT | cmd;
+	struct spi_message msg;
+	struct spi_transfer xfer = { 0 };
+
+	spi_message_init(&msg);
+	msg.spi = ts->spi;
+	xfer.tx_buf = &data;
+	xfer.rx_buf = NULL;
+	xfer.len = 2;
+	xfer.bits_per_word = 8;
+
+	spi_message_add_tail(&xfer, &msg);
+	spi_sync(ts->spi, &msg);
+}
+
+static void tsc2005_write(struct tsc2005 *ts, u8 reg, u16 value)
+{
+	u32 tx;
+	struct spi_message msg;
+	struct spi_transfer xfer = { 0 };
+
+	tx = (TSC2005_REG | reg | TSC2005_REG_PND0 |
+	       TSC2005_REG_WRITE) << (2 * 8);
+	tx |= value;
+
+	spi_message_init(&msg);
+	msg.spi = ts->spi;
+	xfer.tx_buf = &tx;
+	xfer.rx_buf = NULL;
+	xfer.len = 4;
+	xfer.bits_per_word = 3 * 8;
+
+	spi_message_add_tail(&xfer, &msg);
+	spi_sync(ts->spi, &msg);
+}
+
+static void tsc2005_ts_update_pen_state(struct tsc2005 *ts,
+					int x, int y, int pressure)
+{
+	if (pressure) {
+		input_report_abs(ts->idev, ABS_X, x);
+		input_report_abs(ts->idev, ABS_Y, y);
+		input_report_abs(ts->idev, ABS_PRESSURE, pressure);
+		if (!ts->pen_down) {
+			input_report_key(ts->idev, BTN_TOUCH, 1);
+			ts->pen_down = 1;
+		}
+	} else {
+		input_report_abs(ts->idev, ABS_PRESSURE, 0);
+		if (ts->pen_down)
+			input_report_key(ts->idev, BTN_TOUCH, 0);
+
+		ts->pen_down = 0;
+	}
+
+	input_sync(ts->idev);
+}
+
+/*
+ * This function is called by the SPI framework after the coordinates
+ * have been read from TSC2005
+ */
+static void tsc2005_ts_rx(void *arg)
+{
+	struct tsc2005 *ts = arg;
+	unsigned long flags;
+	int inside_rect, pressure_limit;
+	int x, y, z1, z2, pressure;
+
+	spin_lock_irqsave(&ts->lock, flags);
+
+	x = ts->data[0];
+	y = ts->data[1];
+	z1 = ts->data[2];
+	z2 = ts->data[3];
+
+	/* validate pressure and position */
+	if (x > MAX_12BIT || y > MAX_12BIT)
+		goto out;
+
+	/* skip coords if the pressure-components are out of range */
+	if (z1 < 100 || z2 > 4000)
+		goto out;
+
+	/* don't run average on the "pen down" event */
+	if (ts->sample_sent) {
+		ts->avg_x += x;
+		ts->avg_y += y;
+		ts->avg_z1 += z1;
+		ts->avg_z2 += z2;
+
+		if (++ts->sample_cnt < TS_SAMPLES)
+			goto out;
+
+		x = ts->avg_x / TS_SAMPLES;
+		y = ts->avg_y / TS_SAMPLES;
+		z1 = ts->avg_z1 / TS_SAMPLES;
+		z2 = ts->avg_z2 / TS_SAMPLES;
+	}
+
+	ts->sample_cnt = 0;
+	ts->avg_x = 0;
+	ts->avg_y = 0;
+	ts->avg_z1 = 0;
+	ts->avg_z2 = 0;
+
+	if (z1) {
+		pressure = x * (z2 - z1) / z1;
+		pressure = pressure * ts->x_plate_ohm / 4096;
+	} else
+		goto out;
+
+	pressure_limit = ts->sample_sent? ts->p_max: ts->touch_pressure;
+	if (pressure > pressure_limit)
+		goto out;
+
+	/* discard the event if it still is within the previous rect - unless
+	 * if the pressure is harder, but then use previous x,y position */
+	inside_rect = (ts->sample_sent &&
+		x > (int)ts->x - TS_RECT_SIZE &&
+		x < (int)ts->x + TS_RECT_SIZE &&
+		y > (int)ts->y - TS_RECT_SIZE &&
+		y < (int)ts->y + TS_RECT_SIZE);
+	if (inside_rect)
+		x = ts->x, y = ts->y;
+
+	if (!inside_rect || pressure < ts->p) {
+		tsc2005_ts_update_pen_state(ts, x, y, pressure);
+		ts->sample_sent = 1;
+		ts->x = x;
+		ts->y = y;
+		ts->p = pressure;
+	}
+out:
+	ts->spi_active = 0;
+	spin_unlock_irqrestore(&ts->lock, flags);
+
+	/* kick pen up timer - to make sure it expires again(!) */
+	if (ts->sample_sent)
+		mod_timer(&ts->penup_timer,
+			  jiffies + msecs_to_jiffies(TSC2005_TS_PENUP_TIME));
+}
+
+static void tsc2005_ts_penup_timer_handler(unsigned long data)
+{
+	struct tsc2005 *ts = (struct tsc2005 *)data;
+
+	if (ts->sample_sent) {
+		tsc2005_ts_update_pen_state(ts, 0, 0, 0);
+		ts->sample_sent = 0;
+	}
+}
+
+/*
+ * This interrupt is called when pen is down and coordinates are
+ * available. That is indicated by a falling edge on DEV line.
+ */
+static irqreturn_t tsc2005_ts_irq_handler(int irq, void *dev_id)
+{
+	struct tsc2005 *ts = dev_id;
+	int r;
+
+	if (ts->spi_active)
+		return IRQ_HANDLED;
+
+	ts->spi_active = 1;
+	r = spi_async(ts->spi, &ts->read_msg);
+	if (r)
+		dev_err(&ts->spi->dev, "ts: spi_async() failed");
+
+	/* kick pen up timer */
+	mod_timer(&ts->penup_timer,
+		  jiffies + msecs_to_jiffies(TSC2005_TS_PENUP_TIME));
+
+	return IRQ_HANDLED;
+}
+
+static void tsc2005_ts_setup_spi_xfer(struct tsc2005 *ts)
+{
+	struct spi_message *m = &ts->read_msg;
+	struct spi_transfer *x = &ts->read_xfer[0];
+	int i;
+
+	spi_message_init(m);
+	m->spi = ts->spi;
+
+	for (i = 0; i < NUM_READ_REGS; i++, x++) {
+		x->tx_buf = &tsc2005_read_reg[i];
+		x->rx_buf = &ts->data[i];
+		x->len = 4;
+		x->bits_per_word = 24;
+		x->cs_change = i < (NUM_READ_REGS - 1);
+		spi_message_add_tail(x, m);
+	}
+
+	m->complete = tsc2005_ts_rx;
+	m->context = ts;
+}
+
+static ssize_t tsc2005_ts_pen_down_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct tsc2005 *tsc = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", tsc->pen_down);
+}
+
+static DEVICE_ATTR(pen_down, S_IRUGO, tsc2005_ts_pen_down_show, NULL);
+
+static int tsc2005_configure(struct tsc2005 *tsc, int flags)
+{
+	tsc2005_write(tsc, TSC2005_REG_CFR0, TSC2005_CFR0_INITVALUE);
+	tsc2005_write(tsc, TSC2005_REG_CFR1, TSC2005_CFR1_INITVALUE);
+	tsc2005_write(tsc, TSC2005_REG_CFR2, TSC2005_CFR2_INITVALUE);
+	tsc2005_cmd(tsc, flags);
+
+	return 0;
+}
+
+static void tsc2005_start_scan(struct tsc2005 *tsc)
+{
+	tsc2005_configure(tsc, TSC2005_CMD_SCAN_XYZZ);
+}
+
+static void tsc2005_stop_scan(struct tsc2005 *tsc)
+{
+	tsc2005_cmd(tsc, TSC2005_CMD_STOP);
+}
+
+/* Must be called with mutex held */
+static void tsc2005_disable(struct tsc2005 *ts)
+{
+	if (ts->disable_depth++ != 0)
+		return;
+
+	disable_irq(ts->irq);
+
+	/* wait until penup timer expire normally */
+	do {
+		msleep(4);
+	} while (ts->sample_sent);
+
+	tsc2005_stop_scan(ts);
+}
+
+static void tsc2005_enable(struct tsc2005 *ts)
+{
+	if (--ts->disable_depth != 0)
+		return;
+
+	enable_irq(ts->irq);
+
+	tsc2005_start_scan(ts);
+}
+
+static ssize_t tsc2005_disable_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct tsc2005 *ts = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", ts->disabled);
+}
+
+static ssize_t tsc2005_disable_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct tsc2005		*tsc = dev_get_drvdata(dev);
+	unsigned long res;
+	int i;
+
+	i = strict_strtoul(buf, 10, &res);
+	i = i ? 1 : 0;
+
+	mutex_lock(&tsc->mutex);
+	if (i == tsc->disabled)
+		goto out;
+	tsc->disabled = i;
+
+	if (i)
+		tsc2005_disable(tsc);
+	else
+		tsc2005_enable(tsc);
+out:
+	mutex_unlock(&tsc->mutex);
+	return count;
+}
+
+static DEVICE_ATTR(disable_ts, 0664, tsc2005_disable_show,
+		   tsc2005_disable_store);
+
+
+static int __devinit tsc2005_ts_init(struct tsc2005 *ts,
+				     struct tsc2005_platform_data *pdata)
+{
+	struct input_dev *idev;
+	int dav_gpio, r;
+	int x_max, y_max;
+	int x_fudge, y_fudge, p_fudge;
+
+	if (pdata->dav_gpio < 0) {
+		dev_err(&ts->spi->dev, "need DAV GPIO");
+		return -EINVAL;
+	}
+	dav_gpio = pdata->dav_gpio;
+	ts->dav_gpio = dav_gpio;
+	dev_dbg(&ts->spi->dev, "TSC2005: DAV GPIO = %d\n", dav_gpio);
+
+#ifdef CONFIG_ARCH_OMAP
+	r = omap_request_gpio(dav_gpio);
+	if (r < 0) {
+		dev_err(&ts->spi->dev, "unable to get DAV GPIO");
+		goto err1;
+	}
+	omap_set_gpio_direction(dav_gpio, 1);
+	ts->irq = OMAP_GPIO_IRQ(dav_gpio);
+	dev_dbg(&ts->spi->dev, "TSC2005: DAV IRQ = %d\n", ts->irq);
+#endif
+	init_timer(&ts->penup_timer);
+	setup_timer(&ts->penup_timer, tsc2005_ts_penup_timer_handler,
+			(unsigned long)ts);
+
+	spin_lock_init(&ts->lock);
+	mutex_init(&ts->mutex);
+
+	ts->x_plate_ohm		= pdata->ts_x_plate_ohm ? : 280;
+	ts->hw_avg_max		= pdata->ts_hw_avg;
+	ts->stab_time		= pdata->ts_stab_time;
+	x_max			= pdata->ts_x_max ? : 4096;
+	x_fudge			= pdata->ts_x_fudge ? : 4;
+	y_max			= pdata->ts_y_max ? : 4096;
+	y_fudge			= pdata->ts_y_fudge ? : 8;
+	ts->p_max		= pdata->ts_pressure_max ? : MAX_12BIT;
+	ts->touch_pressure	= pdata->ts_touch_pressure ? : ts->p_max;
+	p_fudge			= pdata->ts_pressure_fudge ? : 2;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		r = -ENOMEM;
+		goto err2;
+	}
+
+	/*
+	 * TODO: should be "TSC2005 touchscreen", but X has hardcoded these
+	 * strings and doesn't accept TSC2005 yet...
+	 */
+	idev->name = "TSC2301 touchscreen";
+	snprintf(ts->phys, sizeof(ts->phys), "%s/input-ts",
+		 ts->spi->dev.bus_id);
+	idev->phys = ts->phys;
+
+	idev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);
+	idev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);
+	ts->idev = idev;
+
+	tsc2005_ts_setup_spi_xfer(ts);
+
+	input_set_abs_params(idev, ABS_X, 0, x_max, x_fudge, 0);
+	input_set_abs_params(idev, ABS_Y, 0, y_max, y_fudge, 0);
+	input_set_abs_params(idev, ABS_PRESSURE, 0, ts->p_max, p_fudge, 0);
+
+	tsc2005_start_scan(ts);
+
+	r = request_irq(ts->irq, tsc2005_ts_irq_handler,
+			IRQF_TRIGGER_FALLING | IRQF_DISABLED |
+			IRQF_SAMPLE_RANDOM, "tsc2005", ts);
+	if (r < 0) {
+		dev_err(&ts->spi->dev, "unable to get DAV IRQ");
+		goto err3;
+	}
+
+	set_irq_wake(ts->irq, 1);
+
+	r = input_register_device(idev);
+	if (r < 0) {
+		dev_err(&ts->spi->dev, "can't register touchscreen device\n");
+		goto err4;
+	}
+
+	/* We can tolerate these failing */
+	if (device_create_file(&ts->spi->dev, &dev_attr_pen_down));
+	if (device_create_file(&ts->spi->dev, &dev_attr_disable_ts));
+
+	return 0;
+err4:
+	free_irq(ts->irq, ts);
+err3:
+	tsc2005_stop_scan(ts);
+	input_free_device(idev);
+err2:
+#ifdef CONFIG_ARCH_OMAP
+	omap_free_gpio(dav_gpio);
+#endif
+err1:
+	return r;
+}
+
+static int __devinit tsc2005_probe(struct spi_device *spi)
+{
+	struct tsc2005			*tsc;
+	struct tsc2005_platform_data	*pdata = spi->dev.platform_data;
+	int r;
+
+	if (!pdata) {
+		dev_dbg(&spi->dev, "no platform data?\n");
+		return -ENODEV;
+	}
+
+	tsc = kzalloc(sizeof(*tsc), GFP_KERNEL);
+	if (tsc == NULL)
+		return -ENOMEM;
+
+	dev_set_drvdata(&spi->dev, tsc);
+	tsc->spi = spi;
+	spi->dev.power.power_state = PMSG_ON;
+
+	spi->mode = SPI_MODE_0;
+	spi->bits_per_word = 8;
+	/* The max speed might've been defined by the board-specific
+	 * struct */
+	if (!spi->max_speed_hz)
+		spi->max_speed_hz = TSC2005_HZ;
+
+	spi_setup(spi);
+
+	r = tsc2005_ts_init(tsc, pdata);
+	if (r)
+		goto err1;
+
+	return 0;
+
+err1:
+	kfree(tsc);
+	return r;
+}
+
+static int __devexit tsc2005_remove(struct spi_device *spi)
+{
+	struct tsc2005 *ts = dev_get_drvdata(&spi->dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ts->lock, flags);
+	tsc2005_disable(ts);
+	spin_unlock_irqrestore(&ts->lock, flags);
+
+	device_remove_file(&ts->spi->dev, &dev_attr_disable_ts);
+	device_remove_file(&ts->spi->dev, &dev_attr_pen_down);
+
+	free_irq(ts->irq, ts);
+	input_unregister_device(ts->idev);
+
+#ifdef CONFIG_ARCH_OMAP
+	omap_free_gpio(ts->dav_gpio);
+#endif
+	kfree(ts);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int tsc2005_suspend(struct spi_device *spi, pm_message_t mesg)
+{
+	struct tsc2005 *ts = dev_get_drvdata(&spi->dev);
+
+	mutex_lock(&ts->mutex);
+	tsc2005_disable(ts);
+	mutex_unlock(&ts->mutex);
+
+	return 0;
+}
+
+static int tsc2005_resume(struct spi_device *spi)
+{
+	struct tsc2005 *ts = dev_get_drvdata(&spi->dev);
+
+	mutex_lock(&ts->mutex);
+	tsc2005_enable(ts);
+	mutex_unlock(&ts->mutex);
+
+	return 0;
+}
+#endif
+
+static struct spi_driver tsc2005_driver = {
+	.driver = {
+		.name = "tsc2005",
+		.bus = &spi_bus_type,
+		.owner = THIS_MODULE,
+	},
+#ifdef CONFIG_PM
+	.suspend = tsc2005_suspend,
+	.resume = tsc2005_resume,
+#endif
+	.probe = tsc2005_probe,
+	.remove = __devexit_p(tsc2005_remove),
+};
+
+static int __init tsc2005_init(void)
+{
+	printk(KERN_INFO "TSC2005 driver initializing\n");
+
+	return spi_register_driver(&tsc2005_driver);
+}
+module_init(tsc2005_init);
+
+static void __exit tsc2005_exit(void)
+{
+	spi_unregister_driver(&tsc2005_driver);
+}
+module_exit(tsc2005_exit);
+
+MODULE_AUTHOR("Lauri Leukkunen <lauri.leukkunen@nokia.com>");
+MODULE_LICENSE("GPL");
+
diff --git a/include/linux/spi/tsc2005.h b/include/linux/spi/tsc2005.h
new file mode 100644
index 0000000..dbc01f7
--- /dev/null
+++ b/include/linux/spi/tsc2005.h
@@ -0,0 +1,29 @@
+#ifndef _LINUX_SPI_TSC2005_H
+#define _LINUX_SPI_TSC2005_H
+
+#include <linux/types.h>
+
+struct tsc2005_platform_data {
+	s16	reset_gpio;
+	s16	dav_gpio;
+	s16	pen_int_gpio;
+	u16	ts_x_plate_ohm;
+	u32	ts_stab_time;	/* voltage settling time */
+	u8	ts_hw_avg;	/* HW assiseted averaging. Can be
+				   0, 4, 8, 16 samples per reading */
+	u32	ts_touch_pressure;	/* Pressure limit until we report a
+					   touch event. After that we switch
+					   to ts_max_pressure. */
+	u32	ts_pressure_max;/* Samples with bigger pressure value will
+				   be ignored, since the corresponding X, Y
+				   values are unreliable */
+	u32	ts_pressure_fudge;
+	u32	ts_x_max;
+	u32	ts_x_fudge;
+	u32	ts_y_max;
+	u32	ts_y_fudge;
+
+	unsigned ts_ignore_last : 1;
+};
+
+#endif
-- 
1.5.5.rc3


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 4/5] I2C: LP5521: Introduce lp5521 LED driver
  2008-04-09 12:04     ` [PATCH 3/5] INPUT: TOUCHSCREEN: Introduce tsc2005 driver Felipe Balbi
@ 2008-04-09 12:04       ` Felipe Balbi
  2008-04-09 12:04         ` [PATCH 5/5] ARM: N800: Update n800 defconfig Felipe Balbi
  0 siblings, 1 reply; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 12:04 UTC (permalink / raw)
  To: linux-omap; +Cc: Tony Lindgren, Eduardo Valentin, Mathias Nyman, Felipe Balbi

From: Mathias Nyman <mathias.nyman@nokia.com>

Introduce n810's lp5521 LED driver.

Signed-off-by: Mathias Nyman <mathias.nyman@nokia.com

Updated to new style i2c driver and to build on current
linux-omap head.

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/mach-omap2/board-n800.c |    4 +
 drivers/i2c/chips/Kconfig        |    7 +
 drivers/i2c/chips/Makefile       |    1 +
 drivers/i2c/chips/lp5521.c       |  577 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 589 insertions(+), 0 deletions(-)
 create mode 100644 drivers/i2c/chips/lp5521.c

diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
index 9ed432f..8b5956b 100644
--- a/arch/arm/mach-omap2/board-n800.c
+++ b/arch/arm/mach-omap2/board-n800.c
@@ -664,6 +664,10 @@ static struct i2c_board_info __initdata n800_i2c_board_info_2[] = {
 		I2C_BOARD_INFO("tsl2563", 0x29),
 		.type		= "tsl2563",
 	},
+	{
+		I2C_BOARD_INFO("lp5521", 0x32),
+		.type		= "lp5521",
+	},
 };
 
 void __init nokia_n800_common_init(void)
diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index dc4f140..c4000b5 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -212,6 +212,13 @@ config SENSORS_TSL2563
          This driver can also be built as a module.  If so, the module
          will be called tsl2563.
 
+config LP5521
+	tristate "LP5521 LED driver chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for the National Semiconductor
+	  LP5521 LED driver.
+
 config MENELAUS
 	bool "TWL92330/Menelaus PM chip"
 	depends on I2C=y && ARCH_OMAP24XX
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index e20fc10..1f81ebd 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_TWL4030_POWEROFF)	+= twl4030-poweroff.o
 obj-$(CONFIG_TWL4030_PWRBUTTON)	+= twl4030-pwrbutton.o
 obj-$(CONFIG_TWL4030_MADC)	+= twl4030-madc.o
 obj-$(CONFIG_RTC_X1205_I2C)	+= x1205.o
+obj-$(CONFIG_LP5521)		+= lp5521.o
 
 ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
 EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/i2c/chips/lp5521.c b/drivers/i2c/chips/lp5521.c
new file mode 100644
index 0000000..d1803a9
--- /dev/null
+++ b/drivers/i2c/chips/lp5521.c
@@ -0,0 +1,577 @@
+/*
+ * drivers/i2c/chips/lp5521.c
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Written by Mathias Nyman <mathias.nyman@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <asm/arch/gpio.h>
+
+#define LP5521_DRIVER_NAME		"lp5521"
+
+#ifdef LED_CONNECTED_WRONG
+#define LP5521_REG_R_PWM		0x04
+#define LP5521_REG_B_PWM		0x02
+#else
+#define LP5521_REG_R_PWM		0x02
+#define LP5521_REG_B_PWM		0x04
+#endif
+#define LP5521_REG_ENABLE		0x00
+#define LP5521_REG_OP_MODE		0x01
+#define LP5521_REG_G_PWM		0x03
+#define LP5521_REG_R_CNTRL		0x05
+#define LP5521_REG_G_CNTRL		0x06
+#define LP5521_REG_B_CNTRL		0x07
+#define LP5521_REG_MISC			0x08
+#define LP5521_REG_R_CHANNEL_PC		0x09
+#define LP5521_REG_G_CHANNEL_PC		0x0a
+#define LP5521_REG_B_CHANNEL_PC		0x0b
+#define LP5521_REG_STATUS		0x0c
+#define LP5521_REG_RESET		0x0d
+#define LP5521_REG_GPO			0x0e
+#define LP5521_REG_R_PROG_MEM		0x10
+#define LP5521_REG_G_PROG_MEM		0x30
+#define LP5521_REG_B_PROG_MEM		0x50
+
+#define LP5521_MODE_LOAD		"load"
+#define LP5521_MODE_RUN			"run"
+#define LP5521_MODE_DIRECT_CONTROL	"direct"
+
+#define LP5521_CURRENT_1m5		0x0f
+#define LP5521_CURRENT_3m1		0x1f
+#define LP5521_CURRENT_4m7		0x2f
+#define LP5521_CURRENT_6m3		0x3f
+#define LP5521_CURRENT_7m9		0x4f
+#define LP5521_CURRENT_9m5		0x5f
+#define LP5521_CURRENT_11m1		0x6f
+#define LP5521_CURRENT_12m7		0x7f
+#define LP5521_CURRENT_14m3		0x8f
+#define LP5521_CURRENT_15m9		0x9f
+#define LP5521_CURRENT_17m5		0xaf
+#define LP5521_CURRENT_19m1		0xbf
+#define LP5521_CURRENT_20m7		0xcf
+#define LP5521_CURRENT_22m3		0xdf
+#define LP5521_CURRENT_23m9		0xef
+#define LP5521_CURRENT_25m5		0xff
+
+#define LP5521_PROGRAM_LENGTH		32	/* in bytes */
+
+struct lp5521_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	char			*mode;
+	int			red;
+	int			green;
+	int			blue;
+};
+
+static int lp5521_set_mode(struct lp5521_chip *chip, char *mode);
+
+static int lp5521_write(struct i2c_client *client, u8 reg, u8 value)
+{
+	return i2c_smbus_write_byte_data(client, reg, value);
+}
+
+static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf)
+{
+	s32 ret = i2c_smbus_read_byte_data(client, reg);
+
+	if (ret < 0)
+		return -EIO;
+
+	*buf = ret;
+	return 0;
+}
+
+static int lp5521_configure(struct i2c_client *client)
+{
+	int ret = 0;
+
+	/* Enable chip and set light to logarithmic mode*/
+	ret |= lp5521_write(client, LP5521_REG_ENABLE, 0xc0);
+
+	/* setting all color pwms to direct control mode */
+	ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3f);
+
+	/* setting current to 4.7 mA for all channels */
+	ret |= lp5521_write(client, LP5521_REG_R_CNTRL, LP5521_CURRENT_4m7);
+	ret |= lp5521_write(client, LP5521_REG_G_CNTRL, LP5521_CURRENT_4m7);
+	ret |= lp5521_write(client, LP5521_REG_B_CNTRL, LP5521_CURRENT_4m7);
+
+	/* Enable auto-powersave, set charge pump to auto, red to battery */
+	ret |= lp5521_write(client, LP5521_REG_MISC, 0x3c);
+
+	/* initialize all channels pwm to zero */
+	ret |= lp5521_write(client, LP5521_REG_R_PWM, 0);
+	ret |= lp5521_write(client, LP5521_REG_G_PWM, 0);
+	ret |= lp5521_write(client, LP5521_REG_B_PWM, 0);
+
+	/* Not much can be done about errors at this point */
+	return ret;
+}
+
+static int lp5521_load_program(struct lp5521_chip *chip, u8 *pattern)
+{
+	struct i2c_client *client = chip->client;
+	int ret = 0;
+
+	/* Enter load program mode for all led channels */
+	ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15); /* 0001 0101 */
+	if (ret)
+		return ret;
+
+	if (chip->red)
+		ret |= i2c_smbus_write_i2c_block_data(client,
+						      LP5521_REG_R_PROG_MEM,
+						      LP5521_PROGRAM_LENGTH,
+						      pattern);
+	if (chip->green)
+		ret |= i2c_smbus_write_i2c_block_data(client,
+						      LP5521_REG_G_PROG_MEM,
+						      LP5521_PROGRAM_LENGTH,
+						      pattern);
+	if (chip->blue)
+		ret |= i2c_smbus_write_i2c_block_data(client,
+						      LP5521_REG_B_PROG_MEM,
+						      LP5521_PROGRAM_LENGTH,
+						      pattern);
+
+	return ret;
+}
+
+static int lp5521_run_program(struct lp5521_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+	u8 mask = 0xc0;
+	u8 exec_state = 0;
+	u8 enable_reg;
+
+	ret = lp5521_read(client, LP5521_REG_ENABLE, &enable_reg);
+	if (ret)
+		goto fail;
+
+	enable_reg &= mask;
+
+	/* set all active channels exec state to countinous run*/
+	exec_state |= (chip->red   << 5);
+	exec_state |= (chip->green << 3);
+	exec_state |= (chip->blue  << 1);
+
+	enable_reg |= exec_state;
+
+	ret |= lp5521_write(client, LP5521_REG_ENABLE, enable_reg);
+
+	/* set op-mode to run for active channels, disabled for others */
+	ret |= lp5521_write(client, LP5521_REG_OP_MODE, exec_state);
+
+fail:
+	return ret;
+}
+
+/*--------------------------------------------------------------*/
+/*			Sysfs interface				*/
+/*--------------------------------------------------------------*/
+
+static ssize_t show_active_channels(struct device *dev,
+			    struct device_attribute *attr,
+			    char *buf)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+	char channels[4];
+	int pos = 0;
+
+#ifdef LED_CONNECTED_WRONG
+	if (chip->blue)
+		pos += sprintf(channels + pos, "r");
+	if (chip->green)
+		pos += sprintf(channels + pos, "g");
+	if (chip->red)
+		pos += sprintf(channels + pos, "b");
+
+#else
+	if (chip->red)
+		pos += sprintf(channels + pos, "r");
+	if (chip->green)
+		pos += sprintf(channels + pos, "g");
+	if (chip->blue)
+		pos += sprintf(channels + pos, "b");
+#endif
+
+	channels[pos] = '\0';
+
+	return sprintf(buf, "%s\n", channels);
+}
+
+static ssize_t store_active_channels(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+
+	chip->red = 0;
+	chip->green = 0;
+	chip->blue = 0;
+
+#ifdef LED_CONNECTED_WRONG
+	if (strchr(buf, 'r') != NULL)
+		chip->blue = 1;
+	if (strchr(buf, 'b') != NULL)
+		chip->red = 1;
+#else
+	if (strchr(buf, 'r') != NULL)
+		chip->red = 1;
+	if (strchr(buf, 'b') != NULL)
+		chip->blue = 1;
+#endif
+	if (strchr(buf, 'g') != NULL)
+		chip->green = 1;
+
+	return len;
+}
+
+static ssize_t show_color(struct device *dev,
+			    struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 r, g, b;
+
+	ret |= lp5521_read(client, LP5521_REG_R_PWM, &r);
+	ret |= lp5521_read(client, LP5521_REG_G_PWM, &g);
+	ret |= lp5521_read(client, LP5521_REG_B_PWM, &b);
+
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%.2x:%.2x:%.2x\n", r, g, b);
+}
+
+static ssize_t store_color(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct lp5521_chip *chip = i2c_get_clientdata(client);
+	int ret;
+	unsigned r, g, b;
+
+
+	ret = sscanf(buf, "%2x:%2x:%2x", &r, &g, &b);
+	if (ret != 3)
+		return  -EINVAL;
+
+	mutex_lock(&chip->lock);
+
+	ret = lp5521_write(client, LP5521_REG_R_PWM, (u8)r);
+	ret = lp5521_write(client, LP5521_REG_G_PWM, (u8)g);
+	ret = lp5521_write(client, LP5521_REG_B_PWM, (u8)b);
+
+	mutex_unlock(&chip->lock);
+
+	return len;
+}
+
+static ssize_t store_load(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+	int  ret, nrchars, offset = 0, i = 0;
+	char c[3];
+	unsigned cmd;
+	u8 pattern[LP5521_PROGRAM_LENGTH] = {0};
+
+	while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) {
+
+		/* separate sscanfs because length is working only for %s */
+		ret = sscanf(buf + offset, "%2s%n ", c, &nrchars);
+		ret = sscanf(c, "%2x", &cmd);
+		if (ret != 1)
+			goto fail;
+		pattern[i] = (u8)cmd;
+
+		offset += nrchars;
+		i++;
+	}
+
+	/* pattern commands are always two bytes long */
+	if (i % 2)
+		goto fail;
+
+	mutex_lock(&chip->lock);
+
+	ret = lp5521_load_program(chip, pattern);
+	mutex_unlock(&chip->lock);
+
+	if (ret) {
+		dev_err(dev, "lp5521 failed loading pattern\n");
+		return ret;
+	}
+
+	return len;
+fail:
+	dev_err(dev, "lp5521 wrong pattern format\n");
+	return -EINVAL;
+}
+
+static ssize_t show_mode(struct device *dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%s\n", chip->mode);
+}
+
+static ssize_t store_mode(struct device *dev,
+			  struct device_attribute *attr,
+			  const char *buf, size_t len)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+
+	mutex_lock(&chip->lock);
+
+	if (!strncmp(buf, "run", 3))
+		lp5521_set_mode(chip, LP5521_MODE_RUN);
+	else if (!strncmp(buf, "load", 4))
+		lp5521_set_mode(chip, LP5521_MODE_LOAD);
+	else if (!strncmp(buf, "direct", 6))
+		lp5521_set_mode(chip, LP5521_MODE_DIRECT_CONTROL);
+
+	mutex_unlock(&chip->lock);
+
+	return len;
+}
+
+static ssize_t show_current(struct device *dev,
+			    struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 r_curr, g_curr, b_curr;
+
+	ret |= lp5521_read(client, LP5521_REG_R_CNTRL, &r_curr);
+	ret |= lp5521_read(client, LP5521_REG_G_CNTRL, &g_curr);
+	ret |= lp5521_read(client, LP5521_REG_B_CNTRL, &b_curr);
+
+	if (ret)
+		return ret;
+
+	r_curr = r_curr >> 4;
+	g_curr = g_curr >> 4;
+	b_curr = b_curr >> 4;
+
+	if (r_curr == g_curr && g_curr == b_curr)
+		return sprintf(buf, "%x\n", r_curr);
+	else
+		return sprintf(buf, "%x %x %x\n", r_curr, g_curr, b_curr);
+}
+
+static ssize_t store_current(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+	struct i2c_client *client = chip->client;
+	int ret;
+	unsigned curr;
+
+	ret = sscanf(buf, "%1x", &curr);
+	if (ret != 1)
+		return  -EINVAL;
+
+	/* current level is determined by the 4 upper bits, rest is ones */
+	curr = (curr << 4) | 0x0f;
+
+	mutex_lock(&chip->lock);
+
+	ret |= lp5521_write(client, LP5521_REG_R_CNTRL, (u8)curr);
+	ret |= lp5521_write(client, LP5521_REG_G_CNTRL, (u8)curr);
+	ret |= lp5521_write(client, LP5521_REG_B_CNTRL, (u8)curr);
+
+	mutex_unlock(&chip->lock);
+
+	return len;
+}
+
+static DEVICE_ATTR(color, S_IRUGO | S_IWUGO, show_color, store_color);
+static DEVICE_ATTR(load, S_IWUGO, NULL, store_load);
+static DEVICE_ATTR(mode, S_IRUGO | S_IWUGO, show_mode, store_mode);
+static DEVICE_ATTR(active_channels, S_IRUGO | S_IWUGO,
+		   show_active_channels, store_active_channels);
+static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current);
+
+static int lp5521_register_sysfs(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	int ret;
+
+	ret = device_create_file(dev, &dev_attr_color);
+	if (ret)
+		goto fail1;
+	ret = device_create_file(dev, &dev_attr_load);
+	if (ret)
+		goto fail2;
+	ret = device_create_file(dev, &dev_attr_active_channels);
+	if (ret)
+		goto fail3;
+	ret = device_create_file(dev, &dev_attr_mode);
+	if (ret)
+		goto fail4;
+	ret = device_create_file(dev, &dev_attr_led_current);
+	if (ret)
+		goto fail5;
+	return 0;
+
+fail5:
+	device_remove_file(dev, &dev_attr_mode);
+fail4:
+	device_remove_file(dev, &dev_attr_active_channels);
+fail3:
+	device_remove_file(dev, &dev_attr_load);
+fail2:
+	device_remove_file(dev, &dev_attr_color);
+fail1:
+	return ret;
+}
+
+static void lp5521_unregister_sysfs(struct i2c_client *client)
+{
+	struct lp5521_chip *chip = i2c_get_clientdata(client);
+	struct device *dev = &client->dev;
+
+	device_remove_file(dev, &dev_attr_led_current);
+	device_remove_file(dev, &dev_attr_mode);
+	device_remove_file(dev, &dev_attr_active_channels);
+	device_remove_file(dev, &dev_attr_color);
+
+	if (!strcmp(chip->mode, LP5521_MODE_LOAD))
+		device_remove_file(dev, &dev_attr_load);
+}
+
+/*--------------------------------------------------------------*/
+/*			Set chip operating mode			*/
+/*--------------------------------------------------------------*/
+
+static int lp5521_set_mode(struct lp5521_chip *chip, char *mode)
+{
+	struct i2c_client *client = chip->client ;
+	int ret = 0;
+
+	/* if in that mode already do nothing, except for run */
+	if (!strcmp(mode, chip->mode) && strcmp(mode, LP5521_MODE_RUN))
+		return 0;
+
+	if (!strcmp(mode, LP5521_MODE_RUN))
+		ret = lp5521_run_program(chip);
+
+	if (!strcmp(mode, LP5521_MODE_LOAD))
+		ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15);
+
+	if (!strcmp(mode, LP5521_MODE_DIRECT_CONTROL))
+		ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3F);
+
+	chip->mode = mode;
+
+	return ret;
+}
+
+/*--------------------------------------------------------------*/
+/*			Probe, Attach, Remove			*/
+/*--------------------------------------------------------------*/
+static struct i2c_driver lp5521_driver;
+
+static int lp5521_probe(struct i2c_client *client)
+{
+	struct lp5521_chip *chip;
+	int ret = 0;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->client	= client;
+	strncpy(client->name, LP5521_DRIVER_NAME, I2C_NAME_SIZE);
+	i2c_set_clientdata(client, chip);
+
+	mutex_init(&chip->lock);
+
+	ret = lp5521_configure(client);
+	if (ret < 0) {
+		dev_err(&client->dev, "lp5521 error configuring chip \n");
+		goto fail1;
+	}
+
+	/* Set default values */
+	chip->mode	= LP5521_MODE_DIRECT_CONTROL;
+	chip->red	= 1;
+	chip->green	= 1;
+	chip->blue	= 1;
+
+	ret = lp5521_register_sysfs(client);
+	if (ret)
+		dev_err(&client->dev, "lp5521 registering sysfs failed \n");
+
+	return ret;
+
+fail1:
+	kfree(chip);
+	return ret;
+}
+
+static int lp5521_remove(struct i2c_client *client)
+{
+	struct lp5521_chip *chip = i2c_get_clientdata(client);
+
+	lp5521_unregister_sysfs(client);
+	kfree(chip);
+
+	return 0;
+}
+
+static struct i2c_driver lp5521_driver = {
+	.driver = {
+		.name	= LP5521_DRIVER_NAME,
+	},
+	.probe		= lp5521_probe,
+	.remove		= __exit_p(lp5521_remove),
+};
+
+static int __init lp5521_init(void)
+{
+	return i2c_add_driver(&lp5521_driver);
+}
+
+static void __exit lp5521_exit(void)
+{
+	i2c_del_driver(&lp5521_driver);
+}
+
+MODULE_AUTHOR("Mathias Nyman <mathias.nyman@nokia.com>");
+MODULE_DESCRIPTION("lp5521 LED driver");
+MODULE_LICENSE("GPL");
+
+module_init(lp5521_init);
+module_exit(lp5521_exit);
-- 
1.5.5.rc3


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* [PATCH 5/5] ARM: N800: Update n800 defconfig
  2008-04-09 12:04       ` [PATCH 4/5] I2C: LP5521: Introduce lp5521 LED driver Felipe Balbi
@ 2008-04-09 12:04         ` Felipe Balbi
  0 siblings, 0 replies; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 12:04 UTC (permalink / raw)
  To: linux-omap; +Cc: Tony Lindgren, Eduardo Valentin, Felipe Balbi

Enables recent drivers and usb.

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/configs/n800_defconfig |  189 +++++++++++++++++++++++++++++++++++++--
 1 files changed, 182 insertions(+), 7 deletions(-)

diff --git a/arch/arm/configs/n800_defconfig b/arch/arm/configs/n800_defconfig
index ea687ff..a8e133c 100644
--- a/arch/arm/configs/n800_defconfig
+++ b/arch/arm/configs/n800_defconfig
@@ -1,7 +1,7 @@
 #
 # Automatically generated make config: don't edit
-# Linux kernel version: 2.6.25-rc6-omap1
-# Fri Mar 28 10:53:25 2008
+# Linux kernel version: 2.6.25-rc8-omap1
+# Tue Apr  8 15:34:27 2008
 #
 CONFIG_ARM=y
 CONFIG_SYS_SUPPORTS_APM_EMULATION=y
@@ -465,8 +465,13 @@ CONFIG_BT_HIDP=y
 #
 # Bluetooth device drivers
 #
+# CONFIG_BT_HCIUSB is not set
+# CONFIG_BT_HCIBTUSB is not set
 # CONFIG_BT_HCIBTSDIO is not set
 # CONFIG_BT_HCIUART is not set
+# CONFIG_BT_HCIBCM203X is not set
+# CONFIG_BT_HCIBPA10X is not set
+# CONFIG_BT_HCIBFUSB is not set
 # CONFIG_BT_HCIBRF6150 is not set
 # CONFIG_BT_HCIH4P is not set
 # CONFIG_BT_HCIVHCI is not set
@@ -578,6 +583,7 @@ CONFIG_BLK_DEV=y
 CONFIG_BLK_DEV_LOOP=y
 # CONFIG_BLK_DEV_CRYPTOLOOP is not set
 # CONFIG_BLK_DEV_NBD is not set
+# CONFIG_BLK_DEV_UB is not set
 CONFIG_BLK_DEV_RAM=y
 CONFIG_BLK_DEV_RAM_COUNT=16
 CONFIG_BLK_DEV_RAM_SIZE=4096
@@ -651,6 +657,15 @@ CONFIG_NETDEV_10000=y
 #
 # CONFIG_WLAN_PRE80211 is not set
 # CONFIG_WLAN_80211 is not set
+
+#
+# USB Network Adapters
+#
+# CONFIG_USB_CATC is not set
+# CONFIG_USB_KAWETH is not set
+# CONFIG_USB_PEGASUS is not set
+# CONFIG_USB_RTL8150 is not set
+# CONFIG_USB_USBNET is not set
 # CONFIG_WAN is not set
 CONFIG_PPP=y
 # CONFIG_PPP_MULTILINK is not set
@@ -698,7 +713,7 @@ CONFIG_INPUT_KEYBOARD=y
 # CONFIG_KEYBOARD_NEWTON is not set
 # CONFIG_KEYBOARD_STOWAWAY is not set
 # CONFIG_KEYBOARD_OMAP is not set
-CONFIG_KEYBOARD_TSC2301=y
+CONFIG_KEYBOARD_LM8323=y
 # CONFIG_KEYBOARD_GPIO is not set
 # CONFIG_INPUT_MOUSE is not set
 # CONFIG_INPUT_JOYSTICK is not set
@@ -714,9 +729,10 @@ CONFIG_INPUT_TOUCHSCREEN=y
 # CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set
 # CONFIG_TOUCHSCREEN_TOUCHWIN is not set
 # CONFIG_TOUCHSCREEN_UCB1400 is not set
+CONFIG_TOUCHSCREEN_TSC2005=y
 # CONFIG_TOUCHSCREEN_TSC2102 is not set
 # CONFIG_TOUCHSCREEN_TSC210X is not set
-CONFIG_TOUCHSCREEN_TSC2301=y
+# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set
 # CONFIG_INPUT_MISC is not set
 
 #
@@ -784,6 +800,7 @@ CONFIG_I2C_OMAP=y
 # CONFIG_I2C_SIMTEC is not set
 # CONFIG_I2C_TAOS_EVM is not set
 # CONFIG_I2C_STUB is not set
+# CONFIG_I2C_TINY_USB is not set
 
 #
 # Miscellaneous I2C Chip support
@@ -800,6 +817,8 @@ CONFIG_I2C_OMAP=y
 # CONFIG_TWL4030_CORE is not set
 # CONFIG_SENSORS_MAX6875 is not set
 # CONFIG_SENSORS_TSL2550 is not set
+CONFIG_SENSORS_TSL2563=y
+CONFIG_LP5521=y
 CONFIG_MENELAUS=y
 # CONFIG_I2C_DEBUG_CORE is not set
 # CONFIG_I2C_DEBUG_ALGO is not set
@@ -826,8 +845,7 @@ CONFIG_SPI_OMAP24XX=y
 # CONFIG_SPI_TSC2101 is not set
 # CONFIG_SPI_TSC2102 is not set
 # CONFIG_SPI_TSC210X is not set
-CONFIG_SPI_TSC2301=y
-CONFIG_SPI_TSC2301_AUDIO=y
+# CONFIG_SPI_TSC2301 is not set
 # CONFIG_SPI_SPIDEV is not set
 # CONFIG_SPI_TLE62X0 is not set
 CONFIG_HAVE_GPIO_LIB=y
@@ -911,6 +929,11 @@ CONFIG_WATCHDOG_NOWAYOUT=y
 CONFIG_OMAP_WATCHDOG=y
 
 #
+# USB-based Watchdog Cards
+#
+# CONFIG_USBPCWATCHDOG is not set
+
+#
 # Sonics Silicon Backplane
 #
 CONFIG_SSB_POSSIBLE=y
@@ -939,8 +962,19 @@ CONFIG_VIDEO_TCM825X=y
 # CONFIG_VIDEO_SAA5249 is not set
 # CONFIG_VIDEO_OMAP_CAMERA is not set
 CONFIG_VIDEO_OMAP2=y
+CONFIG_V4L_USB_DRIVERS=y
+# CONFIG_VIDEO_PVRUSB2 is not set
+# CONFIG_VIDEO_EM28XX is not set
+# CONFIG_VIDEO_USBVISION is not set
+# CONFIG_USB_ET61X251 is not set
+# CONFIG_USB_SN9C102 is not set
+# CONFIG_USB_ZC0301 is not set
+# CONFIG_USB_ZR364XX is not set
+# CONFIG_USB_STKWEBCAM is not set
 CONFIG_RADIO_ADAPTERS=y
 CONFIG_RADIO_TEA5761=y
+# CONFIG_USB_DSBR is not set
+# CONFIG_USB_SI470X is not set
 # CONFIG_DVB_CORE is not set
 CONFIG_VIDEOBUF_GEN=y
 CONFIG_VIDEOBUF_DMA_SG=y
@@ -1038,6 +1072,12 @@ CONFIG_SND_OMAP24XX_EAC=y
 #
 
 #
+# USB devices
+#
+# CONFIG_SND_USB_AUDIO is not set
+# CONFIG_SND_USB_CAIAQ is not set
+
+#
 # System on Chip audio support
 #
 # CONFIG_SND_SOC is not set
@@ -1058,7 +1098,140 @@ CONFIG_HID_SUPPORT=y
 CONFIG_HID=y
 # CONFIG_HID_DEBUG is not set
 # CONFIG_HIDRAW is not set
-# CONFIG_USB_SUPPORT is not set
+
+#
+# USB Input Devices
+#
+CONFIG_USB_HID=y
+# CONFIG_USB_HIDINPUT_POWERBOOK is not set
+# CONFIG_HID_FF is not set
+# CONFIG_USB_HIDDEV is not set
+CONFIG_USB_SUPPORT=y
+CONFIG_USB_ARCH_HAS_HCD=y
+CONFIG_USB_ARCH_HAS_OHCI=y
+# CONFIG_USB_ARCH_HAS_EHCI is not set
+CONFIG_USB=y
+CONFIG_USB_DEBUG=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+
+#
+# Miscellaneous USB options
+#
+CONFIG_USB_DEVICEFS=y
+CONFIG_USB_DEVICE_CLASS=y
+# CONFIG_USB_DYNAMIC_MINORS is not set
+CONFIG_USB_SUSPEND=y
+# CONFIG_USB_PERSIST is not set
+CONFIG_USB_OTG=y
+# CONFIG_USB_OTG_WHITELIST is not set
+# CONFIG_USB_OTG_BLACKLIST_HUB is not set
+
+#
+# USB Host Controller Drivers
+#
+# CONFIG_USB_ISP116X_HCD is not set
+# CONFIG_USB_OHCI_HCD is not set
+# CONFIG_USB_SL811_HCD is not set
+# CONFIG_USB_R8A66597_HCD is not set
+CONFIG_USB_MUSB_HDRC=y
+CONFIG_USB_TUSB6010=y
+# CONFIG_USB_MUSB_HOST is not set
+# CONFIG_USB_MUSB_PERIPHERAL is not set
+CONFIG_USB_MUSB_OTG=y
+CONFIG_USB_GADGET_MUSB_HDRC=y
+CONFIG_USB_MUSB_HDRC_HCD=y
+# CONFIG_MUSB_PIO_ONLY is not set
+# CONFIG_USB_INVENTRA_DMA is not set
+# CONFIG_USB_TI_CPPI_DMA is not set
+CONFIG_USB_TUSB_OMAP_DMA=y
+CONFIG_USB_MUSB_LOGLEVEL=1
+
+#
+# USB Device Class drivers
+#
+# CONFIG_USB_ACM is not set
+# CONFIG_USB_PRINTER is not set
+
+#
+# NOTE: USB_STORAGE enables SCSI, and 'SCSI disk support'
+#
+
+#
+# may also be needed; see USB_STORAGE Help for more information
+#
+CONFIG_USB_STORAGE=y
+# CONFIG_USB_STORAGE_DEBUG is not set
+# CONFIG_USB_STORAGE_DATAFAB is not set
+# CONFIG_USB_STORAGE_FREECOM is not set
+# CONFIG_USB_STORAGE_ISD200 is not set
+# CONFIG_USB_STORAGE_DPCM is not set
+# CONFIG_USB_STORAGE_USBAT is not set
+# CONFIG_USB_STORAGE_SDDR09 is not set
+# CONFIG_USB_STORAGE_SDDR55 is not set
+# CONFIG_USB_STORAGE_JUMPSHOT is not set
+# CONFIG_USB_STORAGE_ALAUDA is not set
+# CONFIG_USB_STORAGE_KARMA is not set
+CONFIG_USB_LIBUSUAL=y
+
+#
+# USB Imaging devices
+#
+# CONFIG_USB_MDC800 is not set
+# CONFIG_USB_MICROTEK is not set
+CONFIG_USB_MON=y
+
+#
+# USB port drivers
+#
+# CONFIG_USB_SERIAL is not set
+
+#
+# USB Miscellaneous drivers
+#
+# CONFIG_USB_EMI62 is not set
+# CONFIG_USB_EMI26 is not set
+# CONFIG_USB_ADUTUX is not set
+# CONFIG_USB_AUERSWALD is not set
+# CONFIG_USB_RIO500 is not set
+# CONFIG_USB_LEGOTOWER is not set
+# CONFIG_USB_LCD is not set
+# CONFIG_USB_BERRY_CHARGE is not set
+# CONFIG_USB_LED is not set
+# CONFIG_USB_CYPRESS_CY7C63 is not set
+# CONFIG_USB_CYTHERM is not set
+# CONFIG_USB_PHIDGET is not set
+# CONFIG_USB_IDMOUSE is not set
+# CONFIG_USB_FTDI_ELAN is not set
+# CONFIG_USB_APPLEDISPLAY is not set
+# CONFIG_USB_LD is not set
+# CONFIG_USB_TRANCEVIBRATOR is not set
+# CONFIG_USB_IOWARRIOR is not set
+CONFIG_USB_TEST=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_GADGET_DEBUG=y
+CONFIG_USB_GADGET_DEBUG_FILES=y
+CONFIG_USB_GADGET_SELECTED=y
+# CONFIG_USB_GADGET_AMD5536UDC is not set
+# CONFIG_USB_GADGET_ATMEL_USBA is not set
+# CONFIG_USB_GADGET_FSL_USB2 is not set
+# CONFIG_USB_GADGET_NET2280 is not set
+# CONFIG_USB_GADGET_PXA2XX is not set
+# CONFIG_USB_GADGET_M66592 is not set
+# CONFIG_USB_GADGET_GOKU is not set
+# CONFIG_USB_GADGET_LH7A40X is not set
+# CONFIG_USB_GADGET_OMAP is not set
+# CONFIG_USB_GADGET_S3C2410 is not set
+# CONFIG_USB_GADGET_AT91 is not set
+# CONFIG_USB_GADGET_DUMMY_HCD is not set
+CONFIG_USB_GADGET_DUALSPEED=y
+# CONFIG_USB_ZERO is not set
+CONFIG_USB_ETH=y
+CONFIG_USB_ETH_RNDIS=y
+# CONFIG_USB_GADGETFS is not set
+# CONFIG_USB_FILE_STORAGE is not set
+# CONFIG_USB_G_SERIAL is not set
+# CONFIG_USB_MIDI_GADGET is not set
+# CONFIG_USB_G_PRINTER is not set
 CONFIG_MMC=y
 # CONFIG_MMC_DEBUG is not set
 # CONFIG_MMC_UNSAFE_RESUME is not set
@@ -1101,6 +1274,7 @@ CONFIG_RTC_LIB=y
 CONFIG_CBUS=y
 CONFIG_CBUS_TAHVO=y
 CONFIG_CBUS_TAHVO_USER=y
+# CONFIG_CBUS_TAHVO_USB is not set
 CONFIG_CBUS_RETU=y
 CONFIG_CBUS_RETU_USER=y
 CONFIG_CBUS_RETU_POWERBUTTON=y
@@ -1306,6 +1480,7 @@ CONFIG_DEBUG_ERRORS=y
 CONFIG_SECURITY=y
 # CONFIG_SECURITY_NETWORK is not set
 # CONFIG_SECURITY_CAPABILITIES is not set
+# CONFIG_SECURITY_ROOTPLUG is not set
 # CONFIG_SECURITY_LOWMEM is not set
 CONFIG_SECURITY_DEFAULT_MMAP_MIN_ADDR=0
 CONFIG_CRYPTO=y
-- 
1.5.5.rc3


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 12:04 ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
  2008-04-09 12:04   ` [PATCH 2/5] I2C: TSL2563: Add support for Taos tsl2563 ambient light sensor Felipe Balbi
@ 2008-04-09 12:11   ` Felipe Balbi
  2008-04-09 17:26     ` andrzej zaborowski
  1 sibling, 1 reply; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 12:11 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Tony Lindgren, Eduardo Valentin, Daniel Stone

On Wed, Apr 09, 2008 at 03:04:00PM +0300, Felipe Balbi wrote:
> +static unsigned short normal_i2c[] =
> +{
> +	LM8323_I2C_ADDR00, LM8323_I2C_ADDR01,
> +	LM8323_I2C_ADDR10, LM8323_I2C_ADDR11,
> +	I2C_CLIENT_END
> +};
> +
> +I2C_CLIENT_INSMOD;

This was garbage, already remove in this version below.


>From 3bab182ebe0f922773e62bf6915a8bef1222cec0 Mon Sep 17 00:00:00 2001
From: Daniel Stone <daniel.stone@nokia.com>
Date: Mon, 7 Apr 2008 17:07:37 +0300
Subject: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver

Introduce lm8323 keypad driver.

Signed-off-by: Daniel Stone <daniel.stone@nokia.com

Updated to build with recent linux-omap and new-style
i2c driver.

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/mach-omap2/board-n800.c |   77 ++++
 arch/arm/mach-omap2/board-n810.c |    2 +
 drivers/input/keyboard/Kconfig   |    7 +
 drivers/input/keyboard/Makefile  |    1 +
 drivers/input/keyboard/lm8323.c  |  911 ++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/lm8323.h       |   39 ++
 6 files changed, 1037 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/lm8323.c
 create mode 100644 include/linux/i2c/lm8323.h

diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
index 758e2c1..367e518 100644
--- a/arch/arm/mach-omap2/board-n800.c
+++ b/arch/arm/mach-omap2/board-n800.c
@@ -23,6 +23,7 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
 #include <asm/mach/arch.h>
@@ -48,6 +49,76 @@
 #define N800_DAV_IRQ_GPIO		103
 #define N800_TSC2301_RESET_GPIO		118
 
+#ifdef CONFIG_MACH_NOKIA_N810
+static s16 rx44_keymap[LM8323_KEYMAP_SIZE] = {
+	[0x01] = KEY_Q,
+	[0x02] = KEY_K,
+	[0x03] = KEY_O,
+	[0x04] = KEY_P,
+	[0x05] = KEY_BACKSPACE,
+	[0x06] = KEY_A,
+	[0x07] = KEY_S,
+	[0x08] = KEY_D,
+	[0x09] = KEY_F,
+	[0x0a] = KEY_G,
+	[0x0b] = KEY_H,
+	[0x0c] = KEY_J,
+
+	[0x11] = KEY_W,
+	[0x12] = KEY_F4,
+	[0x13] = KEY_L,
+	[0x14] = KEY_APOSTROPHE,
+	[0x16] = KEY_Z,
+	[0x17] = KEY_X,
+	[0x18] = KEY_C,
+	[0x19] = KEY_V,
+	[0x1a] = KEY_B,
+	[0x1b] = KEY_N,
+	[0x1c] = KEY_LEFTSHIFT, /* Actually, this is both shift keys */
+	[0x1f] = KEY_F7,
+
+	[0x21] = KEY_E,
+	[0x22] = KEY_SEMICOLON,
+	[0x23] = KEY_MINUS,
+	[0x24] = KEY_EQUAL,
+	[0x2b] = KEY_FN,
+	[0x2c] = KEY_M,
+	[0x2f] = KEY_F8,
+
+	[0x31] = KEY_R,
+	[0x32] = KEY_RIGHTCTRL,
+	[0x34] = KEY_SPACE,
+	[0x35] = KEY_COMMA,
+	[0x37] = KEY_UP,
+	[0x3c] = KEY_COMPOSE,
+	[0x3f] = KEY_F6,
+
+	[0x41] = KEY_T,
+	[0x44] = KEY_DOT,
+	[0x46] = KEY_RIGHT,
+	[0x4f] = KEY_F5,
+	[0x51] = KEY_Y,
+	[0x53] = KEY_DOWN,
+	[0x55] = KEY_ENTER,
+	[0x5f] = KEY_ESC,
+
+	[0x61] = KEY_U,
+	[0x64] = KEY_LEFT,
+
+	[0x71] = KEY_I,
+	[0x75] = KEY_KPENTER,
+};
+
+static struct lm8323_platform_data lm8323_pdata = {
+	.repeat = 0, /* Repeat is handled in userspace for now. */
+	.keymap = rx44_keymap,
+
+	.name = "Internal keyboard",
+	.pwm1_name = "keyboard",
+	.pwm2_name = "cover",
+};
+#endif
+
 void __init nokia_n800_init_irq(void)
 {
 	omap2_init_common_hw();
@@ -502,6 +573,12 @@ static struct i2c_board_info __initdata n800_i2c_board_info_2[] = {
 		I2C_BOARD_INFO("tea5761", 0x10),
 	},
 #endif
+	{
+		I2C_BOARD_INFO("lm8323", 0x45),
+		.type		= "lm8323",
+		.irq		= OMAP_GPIO_IRQ(109),
+		.platform_data	= &lm8323_pdata,
+	},
 };
 
 void __init nokia_n800_common_init(void)
diff --git a/arch/arm/mach-omap2/board-n810.c b/arch/arm/mach-omap2/board-n810.c
index c4f4dd5..fb0e61f 100644
--- a/arch/arm/mach-omap2/board-n810.c
+++ b/arch/arm/mach-omap2/board-n810.c
@@ -10,6 +10,8 @@
  */
 
 #include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 1c22930..137f7e4 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -285,6 +285,13 @@ config KEYBOARD_TSC2301
 	help
 	  Say Y here for if you are using the keypad features of TSC2301.
 
+config KEYBOARD_LM8323
+	tristate "LM8323 keypad chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for the National Semiconductor
+	  LM8323 keypad controller.
+
 config KEYBOARD_PXA27x
 	tristate "PXA27x/PXA3xx keypad support"
 	depends on PXA27x || PXA3xx
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index bc0bbc1..ec447cd 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
 obj-$(CONFIG_KEYBOARD_TSC2301)		+= tsc2301_kp.o
+obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= omap-twl4030keypad.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_AAED2000)		+= aaed2000_kbd.o
diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c
new file mode 100644
index 0000000..ce64ef2
--- /dev/null
+++ b/drivers/input/keyboard/lm8323.c
@@ -0,0 +1,911 @@
+/*
+ * drivers/i2c/chips/lm8323.c
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Written by Daniel Stone <daniel.stone@nokia.com>
+ *            Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 2 of the License only).
+ *
+ * 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/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/i2c/lm8323.h>
+
+#include <asm/mach-types.h>
+#include <asm/mach/irq.h>
+
+#ifdef VERBOSE
+#define debug dev_dbg
+#else
+#define debug(...)
+#endif
+
+/* Commands to send to the chip. */
+#define LM8323_CMD_READ_ID		0x80 /* Read chip ID. */
+#define LM8323_CMD_WRITE_CFG		0x81 /* Set configuration item. */
+#define LM8323_CMD_READ_INT		0x82 /* Get interrupt status. */
+#define LM8323_CMD_RESET		0x83 /* Reset, same as external one */
+#define LM8323_CMD_WRITE_PORT_SEL	0x85 /* Set GPIO in/out. */
+#define LM8323_CMD_WRITE_PORT_STATE	0x86 /* Set GPIO pullup. */
+#define LM8323_CMD_READ_PORT_SEL	0x87 /* Get GPIO in/out. */
+#define LM8323_CMD_READ_PORT_STATE	0x88 /* Get GPIO pullup. */
+#define LM8323_CMD_READ_FIFO		0x89 /* Read byte from FIFO. */
+#define LM8323_CMD_RPT_READ_FIFO	0x8a /* Read FIFO (no increment). */
+#define LM8323_CMD_SET_ACTIVE		0x8b /* Set active time. */
+#define LM8323_CMD_READ_ERR		0x8c /* Get error status. */
+#define LM8323_CMD_READ_ROTATOR		0x8e /* Read rotator status. */
+#define LM8323_CMD_SET_DEBOUNCE		0x8f /* Set debouncing time. */
+#define LM8323_CMD_SET_KEY_SIZE		0x90 /* Set keypad size. */
+#define LM8323_CMD_READ_KEY_SIZE	0x91 /* Get keypad size. */
+#define LM8323_CMD_READ_CFG		0x92 /* Get configuration item. */
+#define LM8323_CMD_WRITE_CLOCK		0x93 /* Set clock config. */
+#define LM8323_CMD_READ_CLOCK		0x94 /* Get clock config. */
+#define LM8323_CMD_PWM_WRITE		0x95 /* Write PWM script. */
+#define LM8323_CMD_START_PWM		0x96 /* Start PWM engine. */
+#define LM8323_CMD_STOP_PWM		0x97 /* Stop PWM engine. */
+
+/* Interrupt status. */
+#define INT_KEYPAD			0x01 /* Key event. */
+#define INT_ROTATOR			0x02 /* Rotator event. */
+#define INT_ERROR			0x08 /* Error: use CMD_READ_ERR. */
+#define INT_NOINIT			0x10 /* Lost configuration. */
+#define INT_PWM1			0x20 /* PWM1 stopped. */
+#define INT_PWM2			0x40 /* PWM2 stopped. */
+#define INT_PWM3			0x80 /* PWM3 stopped. */
+
+/* Errors (signalled by INT_ERROR, read with CMD_READ_ERR). */
+#define ERR_BADPAR			0x01 /* Bad parameter. */
+#define ERR_CMDUNK			0x02 /* Unknown command. */
+#define ERR_KEYOVR			0x04 /* Too many keys pressed. */
+#define ERR_FIFOOVER			0x40 /* FIFO overflow. */
+
+/* Configuration keys (CMD_{WRITE,READ}_CFG). */
+#define CFG_MUX1SEL			0x01 /* Select MUX1_OUT input. */
+#define CFG_MUX1EN			0x02 /* Enable MUX1_OUT. */
+#define CFG_MUX2SEL			0x04 /* Select MUX2_OUT input. */
+#define CFG_MUX2EN			0x08 /* Enable MUX2_OUT. */
+#define CFG_PSIZE			0x20 /* Package size (must be 0). */
+#define CFG_ROTEN			0x40 /* Enable rotator. */
+
+/* Clock settings (CMD_{WRITE,READ}_CLOCK). */
+#define CLK_RCPWM_INTERNAL		0x00
+#define CLK_RCPWM_EXTERNAL		0x03
+#define CLK_SLOWCLKEN			0x08 /* Enable 32.768kHz clock. */
+#define CLK_SLOWCLKOUT			0x40 /* Enable slow pulse output. */
+
+/* The possible addresses corresponding to CONFIG1 and CONFIG2 pin wirings. */
+#define LM8323_I2C_ADDR00		(0x84 >> 1)	/* 1000 010x */
+#define LM8323_I2C_ADDR01		(0x86 >> 1)	/* 1000 011x */
+#define LM8323_I2C_ADDR10		(0x88 >> 1)	/* 1000 100x */
+#define LM8323_I2C_ADDR11		(0x8A >> 1)	/* 1000 101x */
+
+/* Key event fifo length */
+#define LM8323_FIFO_LEN			15
+
+/* Commands for PWM engine; feed in with PWM_WRITE. */
+/* Load ramp counter from duty cycle field (range 0 - 0xff). */
+#define PWM_SET(v)			(0x4000 | ((v) & 0xff))
+/* Go to start of script. */
+#define PWM_GOTOSTART			0x0000
+/*
+ * Stop engine (generates interrupt).  If reset is 1, clear the program
+ * counter, else leave it.
+ */
+#define PWM_END(reset)			(0xc000 | (!!(reset) << 11))
+/*
+ * Ramp.  If s is 1, divide clock by 512, else divide clock by 16.
+ * Take t clock scales (up to 63) per step, for n steps (up to 126).
+ * If u is set, ramp up, else ramp down.
+ */
+#define PWM_RAMP(s, t, n, u)		((!!(s) << 14) | ((t) & 0x3f) << 8 | \
+					 ((n) & 0x7f) | ((u) ? 0 : 0x80))
+/*
+ * Loop (i.e. jump back to pos) for a given number of iterations (up to 63).
+ * If cnt is zero, execute until PWM_END is encountered.
+ */
+#define PWM_LOOP(cnt, pos)		(0xa000 | (((cnt) & 0x3f) << 7) | \
+					 ((pos) & 0x3f))
+/*
+ * Wait for trigger.  Argument is a mask of channels, shifted by the channel
+ * number, e.g. 0xa for channels 3 and 1.  Note that channels are numbered
+ * from 1, not 0.
+ */
+#define PWM_WAIT_TRIG(chans)		(0xe000 | (((chans) & 0x7) << 6))
+/* Send trigger.  Argument is same as PWM_WAIT_TRIG. */
+#define PWM_SEND_TRIG(chans)		(0xe000 | ((chans) & 0x7))
+
+#define DRIVER_NAME  "lm8323"
+
+struct lm8323_pwm {
+	int			id;
+	int			enabled;
+	int			fade_time;
+	int			brightness;
+	int			desired_brightness;
+	struct work_struct	work;
+	struct led_classdev	cdev;
+};
+
+struct lm8323_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	struct work_struct	work;
+	struct input_dev	*idev;
+	int			irq;
+	unsigned		kp_enabled : 1;
+	unsigned		pm_suspend : 1;
+	unsigned		keys_down;
+	char			phys[32];
+	s16			keymap[LM8323_KEYMAP_SIZE];
+	int			size_x;
+	int			size_y;
+	int			debounce_time;
+	int			active_time;
+	struct lm8323_pwm	pwm1;
+	struct lm8323_pwm	pwm2;
+	struct lm8323_pwm	pwm3;
+};
+
+#define client_to_lm8323(c)	container_of(c, struct lm8323_chip, client)
+#define dev_to_lm8323(d)	container_of(d, struct lm8323_chip, client->dev)
+#define work_to_lm8323(w)	container_of(w, struct lm8323_chip, work)
+#define cdev_to_pwm(c)		container_of(c, struct lm8323_pwm, cdev)
+#define work_to_pwm(w)		container_of(w, struct lm8323_pwm, work)
+
+static struct lm8323_chip *pwm_to_lm8323(struct lm8323_pwm *pwm)
+{
+	switch (pwm->id) {
+	case 1:
+		return container_of(pwm, struct lm8323_chip, pwm1);
+	case 2:
+		return container_of(pwm, struct lm8323_chip, pwm2);
+	case 3:
+		return container_of(pwm, struct lm8323_chip, pwm3);
+	default:
+		return NULL;
+	}
+}
+
+static struct lm8323_platform_data *lm8323_pdata;
+
+
+#define LM8323_MAX_DATA 8
+
+/*
+ * To write, we just access the chip's address in write mode, and dump the
+ * command and data out on the bus.  The command byte and data are taken as
+ * sequential u8s out of varargs, to a maximum of LM8323_MAX_DATA.
+ */
+static int lm8323_write(struct lm8323_chip *lm, int len, ...)
+{
+	int ret, i;
+	va_list ap;
+	u8 data[LM8323_MAX_DATA];
+
+	va_start(ap, len);
+
+	if (unlikely(len > LM8323_MAX_DATA)) {
+		dev_err(&lm->client->dev, "tried to send %d bytes\n", len);
+		va_end(ap);
+		return 0;
+	}
+
+	for (i = 0; i < len; i++)
+		data[i] = va_arg(ap, int);
+
+	va_end(ap);
+
+	/*
+	 * If the host is asleep while we send the data, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "sent %d bytes of %d total\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * To read, we first send the command byte to the chip and end the transaction,
+ * then access the chip in read mode, at which point it will send the data.
+ */
+static int lm8323_read(struct lm8323_chip *lm, u8 cmd, u8 *buf, int len)
+{
+	int ret;
+
+	/*
+	 * If the host is asleep while we send the byte, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret != 1)) {
+		dev_err(&lm->client->dev, "sending read cmd 0x%02x failed\n",
+			cmd);
+		return 0;
+	}
+
+	ret = i2c_master_recv(lm->client, buf, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "wanted %d bytes, got %d\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * Set the chip active time (idle time before it enters halt).
+ */
+static void lm8323_set_active_time(struct lm8323_chip *lm, int time)
+{
+	lm8323_write(lm, 2, LM8323_CMD_SET_ACTIVE, time >> 2);
+}
+
+/*
+ * The signals are AT-style: the low 7 bits are the keycode, and the top
+ * bit indicates the state (1 for down, 0 for up).
+ */
+static inline u8 lm8323_whichkey(u8 event)
+{
+	return event & 0x7f;
+}
+
+static inline int lm8323_ispress(u8 event)
+{
+	return (event & 0x80) ? 1 : 0;
+}
+
+static void process_keys(struct lm8323_chip *lm)
+{
+	u8 event;
+	u8 key_fifo[LM8323_FIFO_LEN + 1];
+	int old_keys_down = lm->keys_down;
+	int ret;
+	int i = 0;
+
+	/*
+	 * Read all key events from the FIFO at once. Next READ_FIFO clears the
+	 * FIFO even if we didn't read all events previously.
+	 */
+	ret = lm8323_read(lm, LM8323_CMD_READ_FIFO, key_fifo, LM8323_FIFO_LEN);
+
+	if (ret < 0) {
+		dev_err(&lm->client->dev, "Failed reading fifo \n");
+		return;
+	}
+	key_fifo[ret] = 0;
+
+	while ((event = key_fifo[i])) {
+		u8 key = lm8323_whichkey(event);
+		int isdown = lm8323_ispress(event);
+		s16 keycode = lm->keymap[key];
+
+		if (likely(keycode > 0)) {
+			debug(&lm->client->dev, "key 0x%02x %s\n", key,
+			      isdown ? "down" : "up");
+			if (likely(lm->kp_enabled)) {
+				input_report_key(lm->idev, keycode, isdown);
+				input_sync(lm->idev);
+			}
+			if (isdown)
+				lm->keys_down++;
+			else
+				lm->keys_down--;
+		} else {
+			dev_err(&lm->client->dev, "keycode 0x%02x not mapped "
+				"to any key\n", key);
+		}
+		i++;
+	}
+
+	/*
+	 * Errata: We need to ensure that the chip never enters halt mode
+	 * during a keypress, so set active time to 0.  When it's released,
+	 * we can enter halt again, so set the active time back to normal.
+	 */
+	if (!old_keys_down && lm->keys_down)
+		lm8323_set_active_time(lm, 0);
+	if (old_keys_down && !lm->keys_down)
+		lm8323_set_active_time(lm, lm->active_time);
+}
+
+static void lm8323_process_error(struct lm8323_chip *lm)
+{
+	u8 error;
+
+	if (lm8323_read(lm, LM8323_CMD_READ_ERR, &error, 1) == 1) {
+		if (error & ERR_FIFOOVER)
+			debug(&lm->client->dev, "fifo overflow!\n");
+		if (error & ERR_KEYOVR)
+			debug(&lm->client->dev, "more than two keys pressed\n");
+		if (error & ERR_CMDUNK)
+			debug(&lm->client->dev, "unknown command submitted\n");
+		if (error & ERR_BADPAR)
+			debug(&lm->client->dev, "bad command parameter\n");
+	}
+}
+
+static void lm8323_reset(struct lm8323_chip *lm)
+{
+	/* The docs say we must pass 0xAA as the data byte. */
+	lm8323_write(lm, 2, LM8323_CMD_RESET, 0xAA);
+}
+
+static int lm8323_configure(struct lm8323_chip *lm)
+{
+	int keysize = (lm->size_x << 4) | lm->size_y;
+	int clock = (CLK_SLOWCLKEN | CLK_RCPWM_EXTERNAL);
+	int debounce = lm->debounce_time >> 2;
+	int active = lm->active_time >> 2;
+
+	/*
+	 * Active time must be greater than the debounce time: if it's
+	 * a close-run thing, give ourselves a 12ms buffer.
+	 */
+	if (debounce >= active)
+		active = debounce + 3;
+
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CFG, 0);
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CLOCK, clock);
+	lm8323_write(lm, 2, LM8323_CMD_SET_KEY_SIZE, keysize);
+	lm8323_set_active_time(lm, lm->active_time);
+	lm8323_write(lm, 2, LM8323_CMD_SET_DEBOUNCE, debounce);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_STATE, 0xff, 0xff);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_SEL, 0, 0);
+
+	/*
+	 * Not much we can do about errors at this point, so just hope
+	 * for the best.
+	 */
+
+	return 0;
+}
+
+/*
+ * Bottom half: handle the interrupt by posting key events, or dealing with
+ * errors appropriately.
+ */
+static void lm8323_work(struct work_struct *work)
+{
+	struct lm8323_chip *lm = work_to_lm8323(work);
+	u8 ints;
+
+	mutex_lock(&lm->lock);
+
+	while ((lm8323_read(lm, LM8323_CMD_READ_INT, &ints, 1) == 1) && ints) {
+		if (likely(ints & INT_KEYPAD))
+			process_keys(lm);
+		if (ints & INT_ROTATOR) {
+			/* We don't currently support the rotator. */
+			debug(&lm->client->dev, "rotator fired\n");
+		}
+		if (ints & INT_ERROR) {
+			debug(&lm->client->dev, "error!\n");
+			lm8323_process_error(lm);
+		}
+		if (ints & INT_NOINIT) {
+			dev_err(&lm->client->dev, "chip lost config; "
+						  "reinitialising\n");
+			lm8323_configure(lm);
+		}
+		if (ints & INT_PWM1)
+			debug(&lm->client->dev, "pwm1 engine completed\n");
+		if (ints & INT_PWM2)
+			debug(&lm->client->dev, "pwm2 engine completed\n");
+		if (ints & INT_PWM3)
+			debug(&lm->client->dev, "pwm3 engine completed\n");
+	}
+
+	mutex_unlock(&lm->lock);
+}
+
+/*
+ * We cannot use I2C in interrupt context, so we just schedule work.
+ */
+static irqreturn_t lm8323_irq(int irq, void *data)
+{
+	struct lm8323_chip *lm = data;
+
+	schedule_work(&lm->work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Read the chip ID.
+ */
+static int lm8323_read_id(struct lm8323_chip *lm, u8 *buf)
+{
+	int bytes;
+
+	bytes = lm8323_read(lm, LM8323_CMD_READ_ID, buf, 2);
+	if (unlikely(bytes != 2))
+		return -EIO;
+
+	return 0;
+}
+
+static void lm8323_write_pwm_one(struct lm8323_pwm *pwm, int pos, u16 cmd)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	lm8323_write(lm, 4, LM8323_CMD_PWM_WRITE, (pos << 2) | pwm->id,
+		     (cmd & 0xff00) >> 8, cmd & 0x00ff);
+}
+
+/*
+ * Write a script into a given PWM engine, concluding with PWM_END.
+ * If 'keepalive' is specified, the engine will be kept running
+ * indefinitely.
+ */
+static void lm8323_write_pwm(struct lm8323_pwm *pwm, int keepalive,
+			     int len, ...)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+	int i, cmd;
+	va_list ap;
+
+	/*
+	 * If there are any scripts running at the moment, terminate them
+	 * and make sure the duty cycle is as if it finished.
+	 */
+	lm8323_write(lm, 2, LM8323_CMD_STOP_PWM, pwm->id);
+
+	va_start(ap, len);
+	for (i = 0; i < len; i++) {
+		cmd = va_arg(ap, int);
+		lm8323_write_pwm_one(pwm, i, cmd);
+	}
+	va_end(ap);
+
+	/* Wait for a trigger from any channel. This keeps the engine alive. */
+	if (keepalive)
+		lm8323_write_pwm_one(pwm, i++, PWM_WAIT_TRIG(0xe));
+	else
+		lm8323_write_pwm_one(pwm, i++, PWM_END(1));
+
+	lm8323_write(lm, 2, LM8323_CMD_START_PWM, pwm->id);
+}
+
+static void lm8323_pwm_work(struct work_struct *work)
+{
+	struct lm8323_pwm *pwm = work_to_pwm(work);
+	int div, perstep, steps, hz, direction, keepalive;
+
+	/* Do nothing if we're already at the requested level. */
+	if (pwm->desired_brightness == pwm->brightness)
+		return;
+
+	keepalive = (pwm->desired_brightness > 0);
+	direction = (pwm->desired_brightness > pwm->brightness);
+	steps = abs(pwm->desired_brightness - pwm->brightness);
+
+	/*
+	 * Convert time (in ms) into a divisor (512 or 16 on a refclk of
+	 * 32768Hz), and number of ticks per step.
+	 */
+	if ((pwm->fade_time / steps) > (32768 / 512))
+		div = 512;
+	else
+		div = 16;
+
+	hz = 32768 / div;
+	if (pwm->fade_time < ((steps * 1000) / hz))
+		perstep = 1;
+	else
+		perstep = (hz * pwm->fade_time) / (steps * 1000);
+
+	if (perstep == 0)
+		perstep = 1;
+	else if (perstep > 63)
+		perstep = 63;
+
+	if (steps > 252) {
+		lm8323_write_pwm(pwm, keepalive, 3,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 252,
+					  direction));
+	} else if (steps > 126) {
+		lm8323_write_pwm(pwm, keepalive, 2,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 126,
+					  direction));
+	} else {
+		lm8323_write_pwm(pwm, keepalive, 1,
+				 PWM_RAMP((div == 512), perstep, steps,
+					  direction));
+	}
+
+	pwm->brightness = pwm->desired_brightness;
+}
+
+static void lm8323_pwm_set_brightness(struct led_classdev *led_cdev,
+				      enum led_brightness brightness)
+{
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	pwm->desired_brightness = brightness;
+
+	if (in_interrupt()) {
+		schedule_work(&pwm->work);
+	} else {
+		/*
+		 * Schedule PWM work as usual unless we are going into suspend
+		 */
+		mutex_lock(&lm->lock);
+		if (likely(!lm->pm_suspend))
+			schedule_work(&pwm->work);
+		else
+			lm8323_pwm_work(&pwm->work);
+		mutex_unlock(&lm->lock);
+	}
+}
+
+static ssize_t lm8323_pwm_show_time(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+
+	return sprintf(buf, "%d\n", pwm->fade_time);
+}
+
+static ssize_t lm8323_pwm_store_time(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	unsigned long res;
+	int time;
+
+	time = strict_strtoul(buf, 10, &res);
+	/* Numbers only, please. */
+	if (buf && *buf != '\n' && *(buf + 1) != '\0')
+		return -EINVAL;
+
+	pwm->fade_time = time;
+
+	return strlen(buf);
+}
+static DEVICE_ATTR(time, 0644, lm8323_pwm_show_time, lm8323_pwm_store_time);
+
+static int init_pwm(struct lm8323_chip *lm, int id, struct device *dev,
+		    const char *name)
+{
+	struct lm8323_pwm *pwm = NULL;
+
+	BUG_ON(id > 3);
+
+	switch (id) {
+	case 1:
+		pwm = &lm->pwm1;
+		break;
+	case 2:
+		pwm = &lm->pwm2;
+		break;
+	case 3:
+		pwm = &lm->pwm3;
+		break;
+	}
+
+	pwm->id = id;
+	pwm->fade_time = 0;
+	pwm->brightness = 0;
+	pwm->desired_brightness = 0;
+	if (name) {
+		pwm->cdev.name = name;
+		pwm->cdev.brightness_set = lm8323_pwm_set_brightness;
+		if (led_classdev_register(dev, &pwm->cdev) < 0) {
+			dev_err(dev, "couldn't register PWM %d\n", id);
+			return -1;
+		}
+		if (device_create_file(pwm->cdev.dev,
+					     &dev_attr_time) < 0) {
+			dev_err(dev, "couldn't register time attribute\n");
+			led_classdev_unregister(&pwm->cdev);
+			return -1;
+		}
+		INIT_WORK(&pwm->work, lm8323_pwm_work);
+		pwm->enabled = 1;
+	} else {
+		pwm->enabled = 0;
+	}
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver;
+
+static ssize_t lm8323_show_disable(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", !lm->kp_enabled);
+}
+
+static ssize_t lm8323_set_disable(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+	unsigned long res;
+	int i;
+
+	i = strict_strtoul(buf, 10, &res);
+
+	mutex_lock(&lm->lock);
+	lm->kp_enabled = !i;
+	mutex_unlock(&lm->lock);
+
+	return count;
+}
+static DEVICE_ATTR(disable_kp, 0644, lm8323_show_disable, lm8323_set_disable);
+
+static int lm8323_probe(struct i2c_client *client)
+{
+	struct input_dev *idev;
+	struct lm8323_chip *lm;
+	int i, err = 0;
+	unsigned long tmo;
+	u8 data[2];
+
+	lm = kzalloc(sizeof *lm, GFP_KERNEL);
+	if (!lm)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, lm);
+	lm->client = client;
+	lm8323_pdata = client->dev.platform_data;
+	if (!lm8323_pdata)
+		return -EINVAL; /* ? */
+
+	lm->size_x = lm8323_pdata->size_x;
+	if (lm->size_x == 0) {
+		lm->size_x = 8;
+	} else if (lm->size_x > 8) {
+		dev_err(&client->dev, "invalid x size %d specified\n",
+				lm->size_x);
+		lm->size_x = 8;
+	}
+
+	lm->size_y = lm8323_pdata->size_y;
+	if (lm->size_y == 0) {
+		lm->size_y = 12;
+	} else if (lm->size_y > 12) {
+		dev_err(&client->dev, "invalid y size %d specified\n",
+				lm->size_y);
+		lm->size_x = 12;
+	}
+
+	debug(&c->dev, "Keypad size: %d x %d\n", lm->size_x, lm->size_y);
+
+	lm->debounce_time = lm8323_pdata->debounce_time;
+	if (lm->debounce_time == 0) /* Default. */
+		lm->debounce_time = 12;
+	else if (lm->debounce_time == -1) /* Disable debounce. */
+		lm->debounce_time = 0;
+
+	lm->active_time = lm8323_pdata->active_time;
+	if (lm->active_time == 0) /* Default. */
+		lm->active_time = 500;
+	else if (lm->active_time == -1) /* Disable sleep. */
+		lm->active_time = 0;
+
+	lm8323_reset(lm);
+
+	/* Nothing's set up to service the IRQ yet, so just spin for max.
+	 * 100ms until we can configure. */
+	tmo = jiffies + msecs_to_jiffies(100);
+	while (lm8323_read(lm, LM8323_CMD_READ_INT, data, 1) == 1) {
+		if (data[0] & INT_NOINIT)
+			break;
+
+		if (time_after(jiffies, tmo)) {
+			dev_err(&client->dev,
+					"timeout waiting for initialisation\n");
+			break;
+		}
+
+		msleep(1);
+	}
+	lm8323_configure(lm);
+
+	/* If a true probe check the device */
+	if (lm8323_read_id(lm, data) != 0) {
+		dev_err(&client->dev, "device not found\n");
+		err = -ENODEV;
+		goto fail2;
+	}
+
+	if (init_pwm(lm, 1, &client->dev, lm8323_pdata->pwm1_name) < 0)
+		goto fail3;
+	if (init_pwm(lm, 2, &client->dev, lm8323_pdata->pwm2_name) < 0)
+		goto fail4;
+	if (init_pwm(lm, 3, &client->dev, lm8323_pdata->pwm3_name) < 0)
+		goto fail5;
+
+	lm->irq = lm8323_pdata->irq_gpio;
+	debug(&c->dev, "IRQ: %d\n", lm->irq);
+
+	mutex_init(&lm->lock);
+	INIT_WORK(&lm->work, lm8323_work);
+
+	err = request_irq(client->irq, lm8323_irq,
+			  IRQF_TRIGGER_FALLING | IRQF_DISABLED |
+			  IRQF_SAMPLE_RANDOM, DRIVER_NAME, lm);
+	if (err) {
+		dev_err(&client->dev, "could not get IRQ %d\n", lm->irq);
+		goto fail6;
+	}
+
+	set_irq_wake(lm->irq, 1);
+
+	lm->kp_enabled = 1;
+	err = device_create_file(&client->dev, &dev_attr_disable_kp);
+	if (err < 0)
+		goto fail7;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		err = -ENOMEM;
+		goto fail8;
+	}
+
+	if (lm8323_pdata->name)
+		idev->name = lm8323_pdata->name;
+	else
+		idev->name = "LM8323 keypad";
+	snprintf(lm->phys, sizeof(lm->phys), "%s/input-kp", client->dev.bus_id);
+	idev->phys = lm->phys;
+
+	lm->keys_down = 0;
+	idev->evbit[0] = BIT(EV_KEY);
+	for (i = 0; i < LM8323_KEYMAP_SIZE; i++) {
+		if (lm8323_pdata->keymap[i] > 0)
+			set_bit(lm8323_pdata->keymap[i], idev->keybit);
+
+		lm->keymap[i] = lm8323_pdata->keymap[i];
+	}
+
+	if (lm8323_pdata->repeat)
+		set_bit(EV_REP, idev->evbit);
+
+	lm->idev = idev;
+	if (input_register_device(idev)) {
+		dev_dbg(&client->dev, "error registering input device\n");
+		goto fail8;
+	}
+
+	return 0;
+
+fail8:
+	device_remove_file(&client->dev, &dev_attr_disable_kp);
+fail7:
+	free_irq(lm->irq, lm);
+fail6:
+	if (lm->pwm3.enabled)
+		led_classdev_unregister(&lm->pwm3.cdev);
+fail5:
+	if (lm->pwm2.enabled)
+		led_classdev_unregister(&lm->pwm2.cdev);
+fail4:
+	if (lm->pwm1.enabled)
+		led_classdev_unregister(&lm->pwm1.cdev);
+fail3:
+fail2:
+	kfree(lm);
+	return err;
+}
+
+static int lm8323_remove(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	free_irq(lm->irq, lm);
+	device_remove_file(&lm->client->dev, &dev_attr_disable_kp);
+
+	return 0;
+}
+
+/*
+ * We don't need to explicitly suspend the chip, as it already switches off
+ * when there's no activity.
+ */
+static int lm8323_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	set_irq_wake(lm->irq, 0);
+	disable_irq(lm->irq);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 1;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_suspend(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_suspend(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_suspend(&lm->pwm3.cdev);
+
+	return 0;
+}
+
+static int lm8323_resume(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 0;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_resume(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_resume(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_resume(&lm->pwm3.cdev);
+
+	enable_irq(lm->irq);
+	set_irq_wake(lm->irq, 1);
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver = {
+	.driver = {
+		.name	 = DRIVER_NAME,
+	},
+	.probe		= lm8323_probe,
+	.remove		= __exit_p(lm8323_remove),
+	.suspend	= lm8323_suspend,
+	.resume		= lm8323_resume,
+};
+
+static int __init lm8323_init(void)
+{
+	return i2c_add_driver(&lm8323_i2c_driver);
+}
+
+static void __exit lm8323_exit(void)
+{
+	i2c_del_driver(&lm8323_i2c_driver);
+}
+
+MODULE_AUTHOR("Daniel Stone");
+MODULE_DESCRIPTION("LM8323 keypad driver");
+MODULE_LICENSE("GPL");
+
+module_init(lm8323_init);
+module_exit(lm8323_exit);
diff --git a/include/linux/i2c/lm8323.h b/include/linux/i2c/lm8323.h
new file mode 100644
index 0000000..5cb09ab
--- /dev/null
+++ b/include/linux/i2c/lm8323.h
@@ -0,0 +1,39 @@
+/*
+ * include/lm8323.h
+ *
+ * Configuration for LM8323 keypad driver.
+ */
+
+#ifndef __LINUX_LM8323_H
+#define __LINUX_LM8323_H
+
+#include <linux/types.h>
+
+/*
+ * Largest keycode that the chip can send, plus one,
+ * so keys can be mapped directly at the index of the
+ * LM8323 keycode instead of subtracting one.
+ */
+#define LM8323_KEYMAP_SIZE (0x7f + 1)
+
+struct lm8323_platform_data {
+	u16 irq_gpio;
+
+	int debounce_time; /* Time to watch for key bouncing, in ms. */
+	int active_time; /* Idle time until sleep, in ms. */
+
+	int size_x;
+	int size_y;
+	int repeat : 1;
+	const s16 *keymap;
+
+	char *pwm1_name; /* Device name for PWM1. */
+	char *pwm2_name; /* Device name for PWM2. */
+	char *pwm3_name; /* Device name for PWM3. */
+
+	char *name; /* Device name. */
+};
+
+void __init lm8323_set_platform_data(struct lm8323_platform_data *pdata);
+
+#endif /* __LINUX_LM8323_H */
-- 
1.5.5.rc3



-- 
	- Balbi

^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 12:11   ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
@ 2008-04-09 17:26     ` andrzej zaborowski
  2008-04-09 18:05       ` Felipe Balbi
  0 siblings, 1 reply; 23+ messages in thread
From: andrzej zaborowski @ 2008-04-09 17:26 UTC (permalink / raw)
  To: felipe.balbi; +Cc: linux-omap, Tony Lindgren, Eduardo Valentin, Daniel Stone

Hi,

On 09/04/2008, Felipe Balbi <felipe.balbi@nokia.com> wrote:
> On Wed, Apr 09, 2008 at 03:04:00PM +0300, Felipe Balbi wrote:
>  > +static unsigned short normal_i2c[] =
>  > +{
>  > +     LM8323_I2C_ADDR00, LM8323_I2C_ADDR01,
>  > +     LM8323_I2C_ADDR10, LM8323_I2C_ADDR11,
>  > +     I2C_CLIENT_END
>  > +};
>  > +
>  > +I2C_CLIENT_INSMOD;
>
>
> This was garbage, already remove in this version below.
>
>
>  From 3bab182ebe0f922773e62bf6915a8bef1222cec0 Mon Sep 17 00:00:00 2001
>
> From: Daniel Stone <daniel.stone@nokia.com>
>
> Date: Mon, 7 Apr 2008 17:07:37 +0300
>  Subject: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
>
>
>  Introduce lm8323 keypad driver.
>
>  Signed-off-by: Daniel Stone <daniel.stone@nokia.com
>
>  Updated to build with recent linux-omap and new-style
>  i2c driver.
>
>  Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
>  ---
>   arch/arm/mach-omap2/board-n800.c |   77 ++++
>   arch/arm/mach-omap2/board-n810.c |    2 +
>   drivers/input/keyboard/Kconfig   |    7 +
>   drivers/input/keyboard/Makefile  |    1 +
>
>  drivers/input/keyboard/lm8323.c  |  911 ++++++++++++++++++++++++++++++++++++++
>
>  include/linux/i2c/lm8323.h       |   39 ++
>
>  6 files changed, 1037 insertions(+), 0 deletions(-)
>
>  create mode 100644 drivers/input/keyboard/lm8323.c
>   create mode 100644 include/linux/i2c/lm8323.h
>
>  diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
>  index 758e2c1..367e518 100644
>  --- a/arch/arm/mach-omap2/board-n800.c
>  +++ b/arch/arm/mach-omap2/board-n800.c
>  @@ -23,6 +23,7 @@
>   #include <linux/interrupt.h>
>   #include <linux/irq.h>
>   #include <linux/i2c.h>
>  +#include <linux/i2c/lm8323.h>
>   #include <asm/hardware.h>
>   #include <asm/mach-types.h>
>   #include <asm/mach/arch.h>
>  @@ -48,6 +49,76 @@
>   #define N800_DAV_IRQ_GPIO              103
>   #define N800_TSC2301_RESET_GPIO                118
>
>  +#ifdef CONFIG_MACH_NOKIA_N810
>  +static s16 rx44_keymap[LM8323_KEYMAP_SIZE] = {
>  +       [0x01] = KEY_Q,
>  +       [0x02] = KEY_K,
>  +       [0x03] = KEY_O,
>  +       [0x04] = KEY_P,
>  +       [0x05] = KEY_BACKSPACE,
>  +       [0x06] = KEY_A,
>  +       [0x07] = KEY_S,
>  +       [0x08] = KEY_D,
>  +       [0x09] = KEY_F,
>  +       [0x0a] = KEY_G,
>  +       [0x0b] = KEY_H,
>  +       [0x0c] = KEY_J,
>  +
>  +       [0x11] = KEY_W,
>  +       [0x12] = KEY_F4,
>  +       [0x13] = KEY_L,
>  +       [0x14] = KEY_APOSTROPHE,
>  +       [0x16] = KEY_Z,
>  +       [0x17] = KEY_X,
>  +       [0x18] = KEY_C,
>  +       [0x19] = KEY_V,
>  +       [0x1a] = KEY_B,
>  +       [0x1b] = KEY_N,
>  +       [0x1c] = KEY_LEFTSHIFT, /* Actually, this is both shift keys */
>  +       [0x1f] = KEY_F7,
>  +
>  +       [0x21] = KEY_E,
>  +       [0x22] = KEY_SEMICOLON,
>  +       [0x23] = KEY_MINUS,
>  +       [0x24] = KEY_EQUAL,
>  +       [0x2b] = KEY_FN,
>  +       [0x2c] = KEY_M,
>  +       [0x2f] = KEY_F8,
>  +
>  +       [0x31] = KEY_R,
>  +       [0x32] = KEY_RIGHTCTRL,
>  +       [0x34] = KEY_SPACE,
>  +       [0x35] = KEY_COMMA,
>  +       [0x37] = KEY_UP,
>  +       [0x3c] = KEY_COMPOSE,
>  +       [0x3f] = KEY_F6,
>  +
>  +       [0x41] = KEY_T,
>  +       [0x44] = KEY_DOT,
>  +       [0x46] = KEY_RIGHT,
>  +       [0x4f] = KEY_F5,
>  +       [0x51] = KEY_Y,
>  +       [0x53] = KEY_DOWN,
>  +       [0x55] = KEY_ENTER,
>  +       [0x5f] = KEY_ESC,
>  +
>  +       [0x61] = KEY_U,
>  +       [0x64] = KEY_LEFT,
>  +
>  +       [0x71] = KEY_I,
>  +       [0x75] = KEY_KPENTER,
>  +};
>  +
>  +static struct lm8323_platform_data lm8323_pdata = {
>  +       .repeat = 0, /* Repeat is handled in userspace for now. */
>  +       .keymap = rx44_keymap,
>  +
>  +       .name = "Internal keyboard",
>  +       .pwm1_name = "keyboard",
>  +       .pwm2_name = "cover",
>  +};
>  +#endif
>  +
>   void __init nokia_n800_init_irq(void)
>   {
>         omap2_init_common_hw();
>  @@ -502,6 +573,12 @@ static struct i2c_board_info __initdata n800_i2c_board_info_2[] = {
>                 I2C_BOARD_INFO("tea5761", 0x10),
>         },
>   #endif
>  +       {
>  +               I2C_BOARD_INFO("lm8323", 0x45),
>  +               .type           = "lm8323",
>  +               .irq            = OMAP_GPIO_IRQ(109),
>  +               .platform_data  = &lm8323_pdata,
>  +       },
>   };
>
>   void __init nokia_n800_common_init(void)
>  diff --git a/arch/arm/mach-omap2/board-n810.c b/arch/arm/mach-omap2/board-n810.c
>  index c4f4dd5..fb0e61f 100644
>  --- a/arch/arm/mach-omap2/board-n810.c
>  +++ b/arch/arm/mach-omap2/board-n810.c
>  @@ -10,6 +10,8 @@
>   */
>
>   #include <linux/init.h>
>  +#include <linux/i2c.h>
>  +#include <linux/i2c/lm8323.h>
>
>   #include <asm/hardware.h>
>   #include <asm/mach-types.h>
>  diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
>  index 1c22930..137f7e4 100644
>  --- a/drivers/input/keyboard/Kconfig
>  +++ b/drivers/input/keyboard/Kconfig
>  @@ -285,6 +285,13 @@ config KEYBOARD_TSC2301
>         help
>           Say Y here for if you are using the keypad features of TSC2301.
>
>  +config KEYBOARD_LM8323
>  +       tristate "LM8323 keypad chip"
>  +       depends on I2C
>  +       help
>  +         If you say yes here you get support for the National Semiconductor
>  +         LM8323 keypad controller.
>  +
>   config KEYBOARD_PXA27x
>         tristate "PXA27x/PXA3xx keypad support"
>         depends on PXA27x || PXA3xx
>  diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
>  index bc0bbc1..ec447cd 100644
>  --- a/drivers/input/keyboard/Makefile
>  +++ b/drivers/input/keyboard/Makefile
>  @@ -21,6 +21,7 @@ obj-$(CONFIG_KEYBOARD_HIL_OLD)                += hilkbd.o
>   obj-$(CONFIG_KEYBOARD_OMAP)            += omap-keypad.o
>   obj-$(CONFIG_OMAP_PS2)                 += innovator_ps2.o
>   obj-$(CONFIG_KEYBOARD_TSC2301)         += tsc2301_kp.o
>  +obj-$(CONFIG_KEYBOARD_LM8323)          += lm8323.o
>   obj-$(CONFIG_KEYBOARD_TWL4030)         += omap-twl4030keypad.o
>   obj-$(CONFIG_KEYBOARD_PXA27x)          += pxa27x_keypad.o
>   obj-$(CONFIG_KEYBOARD_AAED2000)                += aaed2000_kbd.o
>  diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c
>  new file mode 100644
>
> index 0000000..ce64ef2
>
> --- /dev/null
>  +++ b/drivers/input/keyboard/lm8323.c
>
> @@ -0,0 +1,911 @@
>
> +/*
>  + * drivers/i2c/chips/lm8323.c
>  + *
>  + * Copyright (C) 2007 Nokia Corporation
>  + *
>  + * Written by Daniel Stone <daniel.stone@nokia.com>
>  + *            Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
>  + *
>  + * This program is free software; you can redistribute it and/or modify
>  + * it under the terms of the GNU General Public License as published by
>  + * the Free Software Foundation (version 2 of the License only).
>  + *
>  + * 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/module.h>
>  +#include <linux/i2c.h>
>  +#include <linux/interrupt.h>
>  +#include <linux/sched.h>
>  +#include <linux/mutex.h>
>  +#include <linux/delay.h>
>  +#include <linux/input.h>
>  +#include <linux/leds.h>
>  +#include <linux/i2c/lm8323.h>
>  +
>  +#include <asm/mach-types.h>
>  +#include <asm/mach/irq.h>
>  +
>  +#ifdef VERBOSE
>  +#define debug dev_dbg
>  +#else
>  +#define debug(...)
>  +#endif
>  +
>  +/* Commands to send to the chip. */
>  +#define LM8323_CMD_READ_ID             0x80 /* Read chip ID. */
>  +#define LM8323_CMD_WRITE_CFG           0x81 /* Set configuration item. */
>  +#define LM8323_CMD_READ_INT            0x82 /* Get interrupt status. */
>  +#define LM8323_CMD_RESET               0x83 /* Reset, same as external one */
>  +#define LM8323_CMD_WRITE_PORT_SEL      0x85 /* Set GPIO in/out. */
>  +#define LM8323_CMD_WRITE_PORT_STATE    0x86 /* Set GPIO pullup. */
>  +#define LM8323_CMD_READ_PORT_SEL       0x87 /* Get GPIO in/out. */
>  +#define LM8323_CMD_READ_PORT_STATE     0x88 /* Get GPIO pullup. */
>  +#define LM8323_CMD_READ_FIFO           0x89 /* Read byte from FIFO. */
>  +#define LM8323_CMD_RPT_READ_FIFO       0x8a /* Read FIFO (no increment). */
>  +#define LM8323_CMD_SET_ACTIVE          0x8b /* Set active time. */
>  +#define LM8323_CMD_READ_ERR            0x8c /* Get error status. */
>  +#define LM8323_CMD_READ_ROTATOR                0x8e /* Read rotator status. */
>  +#define LM8323_CMD_SET_DEBOUNCE                0x8f /* Set debouncing time. */
>  +#define LM8323_CMD_SET_KEY_SIZE                0x90 /* Set keypad size. */
>  +#define LM8323_CMD_READ_KEY_SIZE       0x91 /* Get keypad size. */
>  +#define LM8323_CMD_READ_CFG            0x92 /* Get configuration item. */
>  +#define LM8323_CMD_WRITE_CLOCK         0x93 /* Set clock config. */
>  +#define LM8323_CMD_READ_CLOCK          0x94 /* Get clock config. */
>  +#define LM8323_CMD_PWM_WRITE           0x95 /* Write PWM script. */
>  +#define LM8323_CMD_START_PWM           0x96 /* Start PWM engine. */
>  +#define LM8323_CMD_STOP_PWM            0x97 /* Stop PWM engine. */
>  +
>  +/* Interrupt status. */
>  +#define INT_KEYPAD                     0x01 /* Key event. */
>  +#define INT_ROTATOR                    0x02 /* Rotator event. */
>  +#define INT_ERROR                      0x08 /* Error: use CMD_READ_ERR. */
>  +#define INT_NOINIT                     0x10 /* Lost configuration. */
>  +#define INT_PWM1                       0x20 /* PWM1 stopped. */
>  +#define INT_PWM2                       0x40 /* PWM2 stopped. */
>  +#define INT_PWM3                       0x80 /* PWM3 stopped. */
>  +
>  +/* Errors (signalled by INT_ERROR, read with CMD_READ_ERR). */
>  +#define ERR_BADPAR                     0x01 /* Bad parameter. */
>  +#define ERR_CMDUNK                     0x02 /* Unknown command. */
>  +#define ERR_KEYOVR                     0x04 /* Too many keys pressed. */
>  +#define ERR_FIFOOVER                   0x40 /* FIFO overflow. */
>  +
>  +/* Configuration keys (CMD_{WRITE,READ}_CFG). */
>  +#define CFG_MUX1SEL                    0x01 /* Select MUX1_OUT input. */
>  +#define CFG_MUX1EN                     0x02 /* Enable MUX1_OUT. */
>  +#define CFG_MUX2SEL                    0x04 /* Select MUX2_OUT input. */
>  +#define CFG_MUX2EN                     0x08 /* Enable MUX2_OUT. */
>  +#define CFG_PSIZE                      0x20 /* Package size (must be 0). */
>  +#define CFG_ROTEN                      0x40 /* Enable rotator. */
>  +
>  +/* Clock settings (CMD_{WRITE,READ}_CLOCK). */
>  +#define CLK_RCPWM_INTERNAL             0x00
>  +#define CLK_RCPWM_EXTERNAL             0x03
>  +#define CLK_SLOWCLKEN                  0x08 /* Enable 32.768kHz clock. */
>  +#define CLK_SLOWCLKOUT                 0x40 /* Enable slow pulse output. */
>  +
>  +/* The possible addresses corresponding to CONFIG1 and CONFIG2 pin wirings. */
>  +#define LM8323_I2C_ADDR00              (0x84 >> 1)     /* 1000 010x */
>  +#define LM8323_I2C_ADDR01              (0x86 >> 1)     /* 1000 011x */
>  +#define LM8323_I2C_ADDR10              (0x88 >> 1)     /* 1000 100x */
>  +#define LM8323_I2C_ADDR11              (0x8A >> 1)     /* 1000 101x */
>  +
>  +/* Key event fifo length */
>  +#define LM8323_FIFO_LEN                        15
>  +
>  +/* Commands for PWM engine; feed in with PWM_WRITE. */
>  +/* Load ramp counter from duty cycle field (range 0 - 0xff). */
>  +#define PWM_SET(v)                     (0x4000 | ((v) & 0xff))
>  +/* Go to start of script. */
>  +#define PWM_GOTOSTART                  0x0000
>  +/*
>  + * Stop engine (generates interrupt).  If reset is 1, clear the program
>  + * counter, else leave it.
>  + */
>  +#define PWM_END(reset)                 (0xc000 | (!!(reset) << 11))
>  +/*
>  + * Ramp.  If s is 1, divide clock by 512, else divide clock by 16.
>  + * Take t clock scales (up to 63) per step, for n steps (up to 126).
>  + * If u is set, ramp up, else ramp down.
>  + */
>  +#define PWM_RAMP(s, t, n, u)           ((!!(s) << 14) | ((t) & 0x3f) << 8 | \
>  +                                        ((n) & 0x7f) | ((u) ? 0 : 0x80))
>  +/*
>  + * Loop (i.e. jump back to pos) for a given number of iterations (up to 63).
>  + * If cnt is zero, execute until PWM_END is encountered.
>  + */
>  +#define PWM_LOOP(cnt, pos)             (0xa000 | (((cnt) & 0x3f) << 7) | \
>  +                                        ((pos) & 0x3f))
>  +/*
>  + * Wait for trigger.  Argument is a mask of channels, shifted by the channel
>  + * number, e.g. 0xa for channels 3 and 1.  Note that channels are numbered
>  + * from 1, not 0.
>  + */
>  +#define PWM_WAIT_TRIG(chans)           (0xe000 | (((chans) & 0x7) << 6))
>  +/* Send trigger.  Argument is same as PWM_WAIT_TRIG. */
>  +#define PWM_SEND_TRIG(chans)           (0xe000 | ((chans) & 0x7))
>  +
>  +#define DRIVER_NAME  "lm8323"
>  +
>
> +struct lm8323_pwm {
>  +       int                     id;
>  +       int                     enabled;
>  +       int                     fade_time;
>  +       int                     brightness;
>  +       int                     desired_brightness;
>  +       struct work_struct      work;
>  +       struct led_classdev     cdev;
>  +};
>  +
>  +struct lm8323_chip {
>  +       struct mutex            lock;
>  +       struct i2c_client       *client;
>  +       struct work_struct      work;
>  +       struct input_dev        *idev;
>  +       int                     irq;
>  +       unsigned                kp_enabled : 1;
>  +       unsigned                pm_suspend : 1;
>  +       unsigned                keys_down;
>  +       char                    phys[32];
>  +       s16                     keymap[LM8323_KEYMAP_SIZE];
>  +       int                     size_x;
>  +       int                     size_y;
>  +       int                     debounce_time;
>  +       int                     active_time;
>  +       struct lm8323_pwm       pwm1;
>  +       struct lm8323_pwm       pwm2;
>  +       struct lm8323_pwm       pwm3;
>  +};
>  +
>  +#define client_to_lm8323(c)    container_of(c, struct lm8323_chip, client)
>  +#define dev_to_lm8323(d)       container_of(d, struct lm8323_chip, client->dev)
>  +#define work_to_lm8323(w)      container_of(w, struct lm8323_chip, work)
>  +#define cdev_to_pwm(c)         container_of(c, struct lm8323_pwm, cdev)
>  +#define work_to_pwm(w)         container_of(w, struct lm8323_pwm, work)
>  +
>  +static struct lm8323_chip *pwm_to_lm8323(struct lm8323_pwm *pwm)
>  +{
>  +       switch (pwm->id) {
>  +       case 1:
>  +               return container_of(pwm, struct lm8323_chip, pwm1);
>  +       case 2:
>  +               return container_of(pwm, struct lm8323_chip, pwm2);
>  +       case 3:
>  +               return container_of(pwm, struct lm8323_chip, pwm3);
>  +       default:
>  +               return NULL;
>  +       }
>  +}
>  +
>  +static struct lm8323_platform_data *lm8323_pdata;
>  +
>  +
>  +#define LM8323_MAX_DATA 8
>  +
>  +/*
>  + * To write, we just access the chip's address in write mode, and dump the
>  + * command and data out on the bus.  The command byte and data are taken as
>  + * sequential u8s out of varargs, to a maximum of LM8323_MAX_DATA.
>  + */
>  +static int lm8323_write(struct lm8323_chip *lm, int len, ...)
>  +{
>  +       int ret, i;
>  +       va_list ap;
>  +       u8 data[LM8323_MAX_DATA];
>  +
>  +       va_start(ap, len);
>  +
>  +       if (unlikely(len > LM8323_MAX_DATA)) {
>  +               dev_err(&lm->client->dev, "tried to send %d bytes\n", len);
>  +               va_end(ap);
>  +               return 0;
>  +       }
>  +
>  +       for (i = 0; i < len; i++)
>  +               data[i] = va_arg(ap, int);
>  +
>  +       va_end(ap);
>  +
>  +       /*
>  +        * If the host is asleep while we send the data, we can get a NACK
>  +        * back while it wakes up, so try again, once.
>  +        */
>  +       ret = i2c_master_send(lm->client, data, len);
>  +       if (unlikely(ret == -EREMOTEIO))
>  +               ret = i2c_master_send(lm->client, data, len);
>  +       if (unlikely(ret != len))
>  +               dev_err(&lm->client->dev, "sent %d bytes of %d total\n",
>  +                       len, ret);
>  +
>  +       return ret;
>  +}
>  +
>  +/*
>  + * To read, we first send the command byte to the chip and end the transaction,
>  + * then access the chip in read mode, at which point it will send the data.
>  + */
>  +static int lm8323_read(struct lm8323_chip *lm, u8 cmd, u8 *buf, int len)
>  +{
>  +       int ret;
>  +
>  +       /*
>  +        * If the host is asleep while we send the byte, we can get a NACK
>  +        * back while it wakes up, so try again, once.
>  +        */
>  +       ret = i2c_master_send(lm->client, &cmd, 1);
>  +       if (unlikely(ret == -EREMOTEIO))
>  +               ret = i2c_master_send(lm->client, &cmd, 1);
>  +       if (unlikely(ret != 1)) {
>  +               dev_err(&lm->client->dev, "sending read cmd 0x%02x failed\n",
>  +                       cmd);
>  +               return 0;
>  +       }
>  +
>  +       ret = i2c_master_recv(lm->client, buf, len);
>  +       if (unlikely(ret != len))
>  +               dev_err(&lm->client->dev, "wanted %d bytes, got %d\n",
>  +                       len, ret);
>  +
>  +       return ret;
>  +}
>  +
>  +/*
>  + * Set the chip active time (idle time before it enters halt).
>  + */
>  +static void lm8323_set_active_time(struct lm8323_chip *lm, int time)
>  +{
>  +       lm8323_write(lm, 2, LM8323_CMD_SET_ACTIVE, time >> 2);
>  +}
>  +
>  +/*
>  + * The signals are AT-style: the low 7 bits are the keycode, and the top
>  + * bit indicates the state (1 for down, 0 for up).
>  + */
>  +static inline u8 lm8323_whichkey(u8 event)
>  +{
>  +       return event & 0x7f;
>  +}
>  +
>  +static inline int lm8323_ispress(u8 event)
>  +{
>  +       return (event & 0x80) ? 1 : 0;
>  +}
>  +
>  +static void process_keys(struct lm8323_chip *lm)
>  +{
>  +       u8 event;
>  +       u8 key_fifo[LM8323_FIFO_LEN + 1];
>  +       int old_keys_down = lm->keys_down;
>  +       int ret;
>  +       int i = 0;
>  +
>  +       /*
>  +        * Read all key events from the FIFO at once. Next READ_FIFO clears the
>  +        * FIFO even if we didn't read all events previously.
>  +        */
>  +       ret = lm8323_read(lm, LM8323_CMD_READ_FIFO, key_fifo, LM8323_FIFO_LEN);
>  +
>  +       if (ret < 0) {
>  +               dev_err(&lm->client->dev, "Failed reading fifo \n");
>  +               return;
>  +       }
>  +       key_fifo[ret] = 0;
>  +
>  +       while ((event = key_fifo[i])) {
>  +               u8 key = lm8323_whichkey(event);
>  +               int isdown = lm8323_ispress(event);
>  +               s16 keycode = lm->keymap[key];
>  +
>  +               if (likely(keycode > 0)) {
>  +                       debug(&lm->client->dev, "key 0x%02x %s\n", key,
>  +                             isdown ? "down" : "up");
>  +                       if (likely(lm->kp_enabled)) {
>  +                               input_report_key(lm->idev, keycode, isdown);
>  +                               input_sync(lm->idev);
>  +                       }
>  +                       if (isdown)
>  +                               lm->keys_down++;
>  +                       else
>  +                               lm->keys_down--;
>  +               } else {
>  +                       dev_err(&lm->client->dev, "keycode 0x%02x not mapped "
>  +                               "to any key\n", key);
>  +               }
>  +               i++;
>  +       }
>  +
>  +       /*
>  +        * Errata: We need to ensure that the chip never enters halt mode
>  +        * during a keypress, so set active time to 0.  When it's released,
>  +        * we can enter halt again, so set the active time back to normal.
>  +        */
>  +       if (!old_keys_down && lm->keys_down)
>  +               lm8323_set_active_time(lm, 0);
>  +       if (old_keys_down && !lm->keys_down)
>  +               lm8323_set_active_time(lm, lm->active_time);
>  +}
>  +
>  +static void lm8323_process_error(struct lm8323_chip *lm)
>  +{
>  +       u8 error;
>  +
>  +       if (lm8323_read(lm, LM8323_CMD_READ_ERR, &error, 1) == 1) {
>  +               if (error & ERR_FIFOOVER)
>  +                       debug(&lm->client->dev, "fifo overflow!\n");
>  +               if (error & ERR_KEYOVR)
>  +                       debug(&lm->client->dev, "more than two keys pressed\n");
>  +               if (error & ERR_CMDUNK)
>  +                       debug(&lm->client->dev, "unknown command submitted\n");
>  +               if (error & ERR_BADPAR)
>  +                       debug(&lm->client->dev, "bad command parameter\n");
>  +       }
>  +}
>  +
>  +static void lm8323_reset(struct lm8323_chip *lm)
>  +{
>  +       /* The docs say we must pass 0xAA as the data byte. */
>  +       lm8323_write(lm, 2, LM8323_CMD_RESET, 0xAA);
>  +}
>  +
>  +static int lm8323_configure(struct lm8323_chip *lm)
>  +{
>  +       int keysize = (lm->size_x << 4) | lm->size_y;
>  +       int clock = (CLK_SLOWCLKEN | CLK_RCPWM_EXTERNAL);
>  +       int debounce = lm->debounce_time >> 2;
>  +       int active = lm->active_time >> 2;
>  +
>  +       /*
>  +        * Active time must be greater than the debounce time: if it's
>  +        * a close-run thing, give ourselves a 12ms buffer.
>  +        */
>  +       if (debounce >= active)
>  +               active = debounce + 3;
>  +
>  +       lm8323_write(lm, 2, LM8323_CMD_WRITE_CFG, 0);
>  +       lm8323_write(lm, 2, LM8323_CMD_WRITE_CLOCK, clock);
>  +       lm8323_write(lm, 2, LM8323_CMD_SET_KEY_SIZE, keysize);
>  +       lm8323_set_active_time(lm, lm->active_time);
>  +       lm8323_write(lm, 2, LM8323_CMD_SET_DEBOUNCE, debounce);
>  +       lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_STATE, 0xff, 0xff);
>  +       lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_SEL, 0, 0);
>  +
>  +       /*
>  +        * Not much we can do about errors at this point, so just hope
>  +        * for the best.
>  +        */
>  +
>  +       return 0;
>  +}
>  +
>  +/*
>  + * Bottom half: handle the interrupt by posting key events, or dealing with
>  + * errors appropriately.
>  + */
>  +static void lm8323_work(struct work_struct *work)
>  +{
>  +       struct lm8323_chip *lm = work_to_lm8323(work);
>  +       u8 ints;
>  +
>  +       mutex_lock(&lm->lock);
>  +
>  +       while ((lm8323_read(lm, LM8323_CMD_READ_INT, &ints, 1) == 1) && ints) {
>  +               if (likely(ints & INT_KEYPAD))
>  +                       process_keys(lm);
>  +               if (ints & INT_ROTATOR) {
>  +                       /* We don't currently support the rotator. */
>  +                       debug(&lm->client->dev, "rotator fired\n");
>  +               }
>  +               if (ints & INT_ERROR) {
>  +                       debug(&lm->client->dev, "error!\n");
>  +                       lm8323_process_error(lm);
>  +               }
>  +               if (ints & INT_NOINIT) {
>  +                       dev_err(&lm->client->dev, "chip lost config; "
>  +                                                 "reinitialising\n");
>  +                       lm8323_configure(lm);
>  +               }
>  +               if (ints & INT_PWM1)
>  +                       debug(&lm->client->dev, "pwm1 engine completed\n");
>  +               if (ints & INT_PWM2)
>  +                       debug(&lm->client->dev, "pwm2 engine completed\n");
>  +               if (ints & INT_PWM3)
>  +                       debug(&lm->client->dev, "pwm3 engine completed\n");
>  +       }
>  +
>  +       mutex_unlock(&lm->lock);
>  +}
>  +
>  +/*
>  + * We cannot use I2C in interrupt context, so we just schedule work.
>  + */
>  +static irqreturn_t lm8323_irq(int irq, void *data)
>  +{
>  +       struct lm8323_chip *lm = data;
>  +
>  +       schedule_work(&lm->work);
>  +
>  +       return IRQ_HANDLED;
>  +}
>  +
>  +/*
>  + * Read the chip ID.
>  + */
>  +static int lm8323_read_id(struct lm8323_chip *lm, u8 *buf)
>  +{
>  +       int bytes;
>  +
>  +       bytes = lm8323_read(lm, LM8323_CMD_READ_ID, buf, 2);
>  +       if (unlikely(bytes != 2))
>  +               return -EIO;
>  +
>  +       return 0;
>  +}
>  +
>  +static void lm8323_write_pwm_one(struct lm8323_pwm *pwm, int pos, u16 cmd)
>  +{
>  +       struct lm8323_chip *lm = pwm_to_lm8323(pwm);
>  +
>  +       lm8323_write(lm, 4, LM8323_CMD_PWM_WRITE, (pos << 2) | pwm->id,
>  +                    (cmd & 0xff00) >> 8, cmd & 0x00ff);
>  +}
>  +
>  +/*
>  + * Write a script into a given PWM engine, concluding with PWM_END.
>  + * If 'keepalive' is specified, the engine will be kept running
>  + * indefinitely.
>  + */
>  +static void lm8323_write_pwm(struct lm8323_pwm *pwm, int keepalive,
>  +                            int len, ...)
>  +{
>  +       struct lm8323_chip *lm = pwm_to_lm8323(pwm);
>  +       int i, cmd;
>  +       va_list ap;
>  +
>  +       /*
>  +        * If there are any scripts running at the moment, terminate them
>  +        * and make sure the duty cycle is as if it finished.
>  +        */
>  +       lm8323_write(lm, 2, LM8323_CMD_STOP_PWM, pwm->id);
>  +
>  +       va_start(ap, len);
>  +       for (i = 0; i < len; i++) {
>  +               cmd = va_arg(ap, int);
>  +               lm8323_write_pwm_one(pwm, i, cmd);
>  +       }
>  +       va_end(ap);
>  +
>  +       /* Wait for a trigger from any channel. This keeps the engine alive. */
>  +       if (keepalive)
>  +               lm8323_write_pwm_one(pwm, i++, PWM_WAIT_TRIG(0xe));
>  +       else
>  +               lm8323_write_pwm_one(pwm, i++, PWM_END(1));
>  +
>  +       lm8323_write(lm, 2, LM8323_CMD_START_PWM, pwm->id);
>  +}
>  +
>  +static void lm8323_pwm_work(struct work_struct *work)
>  +{
>  +       struct lm8323_pwm *pwm = work_to_pwm(work);
>  +       int div, perstep, steps, hz, direction, keepalive;
>  +
>  +       /* Do nothing if we're already at the requested level. */
>  +       if (pwm->desired_brightness == pwm->brightness)
>  +               return;
>  +
>  +       keepalive = (pwm->desired_brightness > 0);
>  +       direction = (pwm->desired_brightness > pwm->brightness);
>  +       steps = abs(pwm->desired_brightness - pwm->brightness);
>  +
>  +       /*
>  +        * Convert time (in ms) into a divisor (512 or 16 on a refclk of
>  +        * 32768Hz), and number of ticks per step.
>  +        */
>  +       if ((pwm->fade_time / steps) > (32768 / 512))
>  +               div = 512;
>  +       else
>  +               div = 16;
>  +
>  +       hz = 32768 / div;
>  +       if (pwm->fade_time < ((steps * 1000) / hz))
>  +               perstep = 1;
>  +       else
>  +               perstep = (hz * pwm->fade_time) / (steps * 1000);
>  +
>  +       if (perstep == 0)
>  +               perstep = 1;
>  +       else if (perstep > 63)
>  +               perstep = 63;
>  +
>  +       if (steps > 252) {
>  +               lm8323_write_pwm(pwm, keepalive, 3,
>  +                                PWM_RAMP((div == 512), perstep, 126,
>  +                                         direction),
>  +                                PWM_RAMP((div == 512), perstep, 126,
>  +                                         direction),
>  +                                PWM_RAMP((div == 512), perstep, steps - 252,
>  +                                         direction));
>  +       } else if (steps > 126) {
>  +               lm8323_write_pwm(pwm, keepalive, 2,
>  +                                PWM_RAMP((div == 512), perstep, 126,
>  +                                         direction),
>  +                                PWM_RAMP((div == 512), perstep, steps - 126,
>  +                                         direction));
>  +       } else {
>  +               lm8323_write_pwm(pwm, keepalive, 1,
>  +                                PWM_RAMP((div == 512), perstep, steps,
>  +                                         direction));
>  +       }
>  +
>  +       pwm->brightness = pwm->desired_brightness;
>  +}
>  +
>  +static void lm8323_pwm_set_brightness(struct led_classdev *led_cdev,
>  +                                     enum led_brightness brightness)
>  +{
>  +       struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
>  +       struct lm8323_chip *lm = pwm_to_lm8323(pwm);
>  +
>  +       pwm->desired_brightness = brightness;
>  +
>  +       if (in_interrupt()) {
>  +               schedule_work(&pwm->work);
>  +       } else {
>  +               /*
>  +                * Schedule PWM work as usual unless we are going into suspend
>  +                */
>  +               mutex_lock(&lm->lock);
>  +               if (likely(!lm->pm_suspend))
>  +                       schedule_work(&pwm->work);
>  +               else
>  +                       lm8323_pwm_work(&pwm->work);
>  +               mutex_unlock(&lm->lock);
>  +       }
>  +}
>  +
>  +static ssize_t lm8323_pwm_show_time(struct device *dev,
>  +               struct device_attribute *attr, char *buf)
>  +{
>  +       struct led_classdev *led_cdev = dev_get_drvdata(dev);
>  +       struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
>  +
>  +       return sprintf(buf, "%d\n", pwm->fade_time);
>  +}
>  +
>  +static ssize_t lm8323_pwm_store_time(struct device *dev,
>  +               struct device_attribute *attr, const char *buf, size_t len)
>  +{
>  +       struct led_classdev *led_cdev = dev_get_drvdata(dev);
>  +       struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
>  +       unsigned long res;
>  +       int time;
>  +
>  +       time = strict_strtoul(buf, 10, &res);
>  +       /* Numbers only, please. */
>  +       if (buf && *buf != '\n' && *(buf + 1) != '\0')
>  +               return -EINVAL;

The condition doesn't look correct, for example it rejects "55\n\0"
but accepts NULL.

>  +
>  +       pwm->fade_time = time;
>  +
>  +       return strlen(buf);
>  +}
>  +static DEVICE_ATTR(time, 0644, lm8323_pwm_show_time, lm8323_pwm_store_time);
>  +
>  +static int init_pwm(struct lm8323_chip *lm, int id, struct device *dev,
>  +                   const char *name)
>  +{
>  +       struct lm8323_pwm *pwm = NULL;
>  +
>  +       BUG_ON(id > 3);
>  +
>  +       switch (id) {
>  +       case 1:
>  +               pwm = &lm->pwm1;
>  +               break;
>  +       case 2:
>  +               pwm = &lm->pwm2;
>  +               break;
>  +       case 3:
>  +               pwm = &lm->pwm3;
>  +               break;
>  +       }
>  +
>  +       pwm->id = id;
>  +       pwm->fade_time = 0;
>  +       pwm->brightness = 0;
>  +       pwm->desired_brightness = 0;
>  +       if (name) {
>  +               pwm->cdev.name = name;
>  +               pwm->cdev.brightness_set = lm8323_pwm_set_brightness;
>  +               if (led_classdev_register(dev, &pwm->cdev) < 0) {
>  +                       dev_err(dev, "couldn't register PWM %d\n", id);
>  +                       return -1;
>  +               }
>  +               if (device_create_file(pwm->cdev.dev,
>  +                                            &dev_attr_time) < 0) {
>  +                       dev_err(dev, "couldn't register time attribute\n");
>  +                       led_classdev_unregister(&pwm->cdev);
>  +                       return -1;
>  +               }
>  +               INIT_WORK(&pwm->work, lm8323_pwm_work);
>  +               pwm->enabled = 1;
>  +       } else {
>  +               pwm->enabled = 0;
>  +       }
>  +
>  +       return 0;
>  +}
>  +
>  +static struct i2c_driver lm8323_i2c_driver;
>  +
>  +static ssize_t lm8323_show_disable(struct device *dev,
>  +                                  struct device_attribute *attr, char *buf)
>  +{
>  +       struct lm8323_chip *lm = dev_get_drvdata(dev);
>  +
>  +       return sprintf(buf, "%u\n", !lm->kp_enabled);
>  +}
>  +
>  +static ssize_t lm8323_set_disable(struct device *dev,
>  +                                 struct device_attribute *attr,
>  +                                 const char *buf, size_t count)
>  +{
>  +       struct lm8323_chip *lm = dev_get_drvdata(dev);
>  +       unsigned long res;
>  +       int i;
>  +
>  +       i = strict_strtoul(buf, 10, &res);
>  +
>  +       mutex_lock(&lm->lock);
>  +       lm->kp_enabled = !i;
>  +       mutex_unlock(&lm->lock);
>  +
>  +       return count;
>  +}
>  +static DEVICE_ATTR(disable_kp, 0644, lm8323_show_disable, lm8323_set_disable);
>  +
>  +static int lm8323_probe(struct i2c_client *client)
>  +{
>  +       struct input_dev *idev;
>  +       struct lm8323_chip *lm;
>  +       int i, err = 0;
>  +       unsigned long tmo;
>  +       u8 data[2];
>  +
>  +       lm = kzalloc(sizeof *lm, GFP_KERNEL);
>  +       if (!lm)
>  +               return -ENOMEM;
>  +
>  +       i2c_set_clientdata(client, lm);
>  +       lm->client = client;
>  +       lm8323_pdata = client->dev.platform_data;
>  +       if (!lm8323_pdata)
>  +               return -EINVAL; /* ? */

Here lm remains allocated and is lost.  Not that it bothers me, but
probably not what was intended.

>  +
>  +       lm->size_x = lm8323_pdata->size_x;
>  +       if (lm->size_x == 0) {
>  +               lm->size_x = 8;
>  +       } else if (lm->size_x > 8) {
>  +               dev_err(&client->dev, "invalid x size %d specified\n",
>  +                               lm->size_x);
>  +               lm->size_x = 8;
>  +       }
>  +
>  +       lm->size_y = lm8323_pdata->size_y;
>  +       if (lm->size_y == 0) {
>  +               lm->size_y = 12;
>  +       } else if (lm->size_y > 12) {
>  +               dev_err(&client->dev, "invalid y size %d specified\n",
>  +                               lm->size_y);
>  +               lm->size_x = 12;
>  +       }
>  +
>  +       debug(&c->dev, "Keypad size: %d x %d\n", lm->size_x, lm->size_y);
>  +
>  +       lm->debounce_time = lm8323_pdata->debounce_time;
>  +       if (lm->debounce_time == 0) /* Default. */
>  +               lm->debounce_time = 12;
>  +       else if (lm->debounce_time == -1) /* Disable debounce. */
>  +               lm->debounce_time = 0;
>  +
>  +       lm->active_time = lm8323_pdata->active_time;
>  +       if (lm->active_time == 0) /* Default. */
>  +               lm->active_time = 500;
>  +       else if (lm->active_time == -1) /* Disable sleep. */
>  +               lm->active_time = 0;
>  +
>  +       lm8323_reset(lm);
>  +
>  +       /* Nothing's set up to service the IRQ yet, so just spin for max.
>  +        * 100ms until we can configure. */
>  +       tmo = jiffies + msecs_to_jiffies(100);
>  +       while (lm8323_read(lm, LM8323_CMD_READ_INT, data, 1) == 1) {
>  +               if (data[0] & INT_NOINIT)
>  +                       break;
>  +
>  +               if (time_after(jiffies, tmo)) {
>  +                       dev_err(&client->dev,
>  +                                       "timeout waiting for initialisation\n");
>  +                       break;
>  +               }
>  +
>  +               msleep(1);
>  +       }
>  +       lm8323_configure(lm);
>  +
>  +       /* If a true probe check the device */
>  +       if (lm8323_read_id(lm, data) != 0) {
>  +               dev_err(&client->dev, "device not found\n");
>  +               err = -ENODEV;
>  +               goto fail2;
>  +       }
>  +
>  +       if (init_pwm(lm, 1, &client->dev, lm8323_pdata->pwm1_name) < 0)
>  +               goto fail3;
>  +       if (init_pwm(lm, 2, &client->dev, lm8323_pdata->pwm2_name) < 0)
>  +               goto fail4;
>  +       if (init_pwm(lm, 3, &client->dev, lm8323_pdata->pwm3_name) < 0)
>  +               goto fail5;
>  +
>  +       lm->irq = lm8323_pdata->irq_gpio;
>  +       debug(&c->dev, "IRQ: %d\n", lm->irq);
>  +
>  +       mutex_init(&lm->lock);
>  +       INIT_WORK(&lm->work, lm8323_work);
>  +
>  +       err = request_irq(client->irq, lm8323_irq,
>  +                         IRQF_TRIGGER_FALLING | IRQF_DISABLED |
>  +                         IRQF_SAMPLE_RANDOM, DRIVER_NAME, lm);
>  +       if (err) {
>  +               dev_err(&client->dev, "could not get IRQ %d\n", lm->irq);
>  +               goto fail6;
>  +       }
>  +
>  +       set_irq_wake(lm->irq, 1);
>  +
>  +       lm->kp_enabled = 1;
>  +       err = device_create_file(&client->dev, &dev_attr_disable_kp);
>  +       if (err < 0)
>  +               goto fail7;
>  +
>  +       idev = input_allocate_device();
>  +       if (idev == NULL) {
>  +               err = -ENOMEM;
>  +               goto fail8;
>  +       }
>  +
>  +       if (lm8323_pdata->name)
>  +               idev->name = lm8323_pdata->name;
>  +       else
>  +               idev->name = "LM8323 keypad";
>  +       snprintf(lm->phys, sizeof(lm->phys), "%s/input-kp", client->dev.bus_id);
>  +       idev->phys = lm->phys;
>  +
>  +       lm->keys_down = 0;
>  +       idev->evbit[0] = BIT(EV_KEY);
>  +       for (i = 0; i < LM8323_KEYMAP_SIZE; i++) {
>  +               if (lm8323_pdata->keymap[i] > 0)
>  +                       set_bit(lm8323_pdata->keymap[i], idev->keybit);
>  +
>  +               lm->keymap[i] = lm8323_pdata->keymap[i];
>  +       }
>  +
>  +       if (lm8323_pdata->repeat)
>  +               set_bit(EV_REP, idev->evbit);
>  +
>  +       lm->idev = idev;
>  +       if (input_register_device(idev)) {
>  +               dev_dbg(&client->dev, "error registering input device\n");
>  +               goto fail8;
>  +       }
>  +
>  +       return 0;
>  +
>  +fail8:
>  +       device_remove_file(&client->dev, &dev_attr_disable_kp);
>  +fail7:
>  +       free_irq(lm->irq, lm);
>  +fail6:
>  +       if (lm->pwm3.enabled)
>  +               led_classdev_unregister(&lm->pwm3.cdev);
>  +fail5:
>  +       if (lm->pwm2.enabled)
>  +               led_classdev_unregister(&lm->pwm2.cdev);
>  +fail4:
>  +       if (lm->pwm1.enabled)
>  +               led_classdev_unregister(&lm->pwm1.cdev);
>  +fail3:
>  +fail2:
>  +       kfree(lm);
>  +       return err;
>  +}
...

Question not really related to this patchset, but in the TSC2005
driver some averaging and limit checks are done to the ADC values.
Shouldn't that be done in userspace instead?  (The ADS784x and TSC2301
drivers do the same evidently.)

Regards
-- 
Please do not print this email unless absolutely necessary. Spread
environmental awareness.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 17:26     ` andrzej zaborowski
@ 2008-04-09 18:05       ` Felipe Balbi
  2008-04-09 18:28         ` Lauri Leukkunen
                           ` (2 more replies)
  0 siblings, 3 replies; 23+ messages in thread
From: Felipe Balbi @ 2008-04-09 18:05 UTC (permalink / raw)
  To: andrzej zaborowski
  Cc: felipe.balbi, linux-omap, Tony Lindgren, Eduardo Valentin,
	Daniel Stone

On Wed, Apr 09, 2008 at 07:26:31PM +0200, andrzej zaborowski wrote:

[...]


> >  +       time = strict_strtoul(buf, 10, &res);
> >  +       /* Numbers only, please. */
> >  +       if (buf && *buf != '\n' && *(buf + 1) != '\0')
> >  +               return -EINVAL;
> 
> The condition doesn't look correct, for example it rejects "55\n\0"
> but accepts NULL.

Well, this is not my driver and i just moved to strict_stroul cuz
checkpatch asked me to. I'll take a closer look tomorrow but if you can
come up with a better solution for this, I'll ack.

btw, good catch, i didn't had too much time to work on this but still
the driver is working fine.

> >  +       i2c_set_clientdata(client, lm);
> >  +       lm->client = client;
> >  +       lm8323_pdata = client->dev.platform_data;
> >  +       if (!lm8323_pdata)
> >  +               return -EINVAL; /* ? */
> 
> Here lm remains allocated and is lost.  Not that it bothers me, but
> probably not what was intended.

You mean the error handling?

if (!lm8323_pdata) {
	kfree(lm);
	return -EINVAL;
}

fixing tomorrow.

> >  +fail8:
> >  +       device_remove_file(&client->dev, &dev_attr_disable_kp);
> >  +fail7:
> >  +       free_irq(lm->irq, lm);
> >  +fail6:
> >  +       if (lm->pwm3.enabled)
> >  +               led_classdev_unregister(&lm->pwm3.cdev);
> >  +fail5:
> >  +       if (lm->pwm2.enabled)
> >  +               led_classdev_unregister(&lm->pwm2.cdev);
> >  +fail4:
> >  +       if (lm->pwm1.enabled)
> >  +               led_classdev_unregister(&lm->pwm1.cdev);
> >  +fail3:
> >  +fail2:
> >  +       kfree(lm);
> >  +       return err;
> >  +}
> ...
> 
> Question not really related to this patchset, but in the TSC2005
> driver some averaging and limit checks are done to the ADC values.
> Shouldn't that be done in userspace instead?  (The ADS784x and TSC2301
> drivers do the same evidently.)

Now that's not really up to me, but if we move it now it probably won't
provide the feature n810 needs. I mean, n810 is designed on top of the
features provided by driver, the application probably expects the driver
to do the averaging and limit checks so at least for tsc2005 I think
it's not a good idea to change due to application requirements.

-- 
Best Regards,

Felipe Balbi
me@felipebalbi.com
http://blog.felipebalbi.com

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 18:05       ` Felipe Balbi
@ 2008-04-09 18:28         ` Lauri Leukkunen
  2008-04-09 23:58           ` andrzej zaborowski
  2008-04-09 20:35         ` Daniel Stone
  2008-04-10 13:18         ` andrzej zaborowski
  2 siblings, 1 reply; 23+ messages in thread
From: Lauri Leukkunen @ 2008-04-09 18:28 UTC (permalink / raw)
  To: ext Felipe Balbi
  Cc: andrzej zaborowski, felipe.balbi, linux-omap, Tony Lindgren,
	Eduardo Valentin, Daniel Stone

On 09/04/08 21:05 +0300, ext Felipe Balbi wrote:
> On Wed, Apr 09, 2008 at 07:26:31PM +0200, andrzej zaborowski wrote:
> > Question not really related to this patchset, but in the TSC2005
> > driver some averaging and limit checks are done to the ADC values.
> > Shouldn't that be done in userspace instead?  (The ADS784x and TSC2301
> > drivers do the same evidently.)
> 
> Now that's not really up to me, but if we move it now it probably won't
> provide the feature n810 needs. I mean, n810 is designed on top of the
> features provided by driver, the application probably expects the driver
> to do the averaging and limit checks so at least for tsc2005 I think
> it's not a good idea to change due to application requirements.

User-space is too late for this filtering, we would end up feeding ~1k samples
per second there, and that would simply clog the system pretty badly, can't
do much buffering either as that degrades interactive responsiveness of the
UI. In my opinion kernel should provide "correct" data to user-space, not
some pseudo-random interference from the LCD. Micro-kernel people will of
course disagree.

/lauri


^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 18:05       ` Felipe Balbi
  2008-04-09 18:28         ` Lauri Leukkunen
@ 2008-04-09 20:35         ` Daniel Stone
  2008-04-10 13:18         ` andrzej zaborowski
  2 siblings, 0 replies; 23+ messages in thread
From: Daniel Stone @ 2008-04-09 20:35 UTC (permalink / raw)
  To: ext Felipe Balbi
  Cc: andrzej zaborowski, felipe.balbi, linux-omap, Tony Lindgren,
	Eduardo Valentin

[-- Attachment #1: Type: text/plain, Size: 961 bytes --]

On Wed, Apr 09, 2008 at 09:05:58PM +0300, ext Felipe Balbi wrote:
> On Wed, Apr 09, 2008 at 07:26:31PM +0200, andrzej zaborowski wrote:
> > Question not really related to this patchset, but in the TSC2005
> > driver some averaging and limit checks are done to the ADC values.
> > Shouldn't that be done in userspace instead?  (The ADS784x and TSC2301
> > drivers do the same evidently.)
> 
> Now that's not really up to me, but if we move it now it probably won't
> provide the feature n810 needs. I mean, n810 is designed on top of the
> features provided by driver, the application probably expects the driver
> to do the averaging and limit checks so at least for tsc2005 I think
> it's not a good idea to change due to application requirements.

It's specific to the device (and device _build_): the actual
touchscreen, and how it's working in the device, so a combination of LCD
driver and board files sounds best to me.

Cheers,
Daniel

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 18:28         ` Lauri Leukkunen
@ 2008-04-09 23:58           ` andrzej zaborowski
  2008-04-10  0:12             ` Daniel Stone
  0 siblings, 1 reply; 23+ messages in thread
From: andrzej zaborowski @ 2008-04-09 23:58 UTC (permalink / raw)
  To: Lauri Leukkunen
  Cc: ext Felipe Balbi, felipe.balbi, linux-omap, Tony Lindgren,
	Eduardo Valentin, Daniel Stone

Hi,

On 09/04/2008, Lauri Leukkunen <lauri.leukkunen@nokia.com> wrote:
> On 09/04/08 21:05 +0300, ext Felipe Balbi wrote:
>  > On Wed, Apr 09, 2008 at 07:26:31PM +0200, andrzej zaborowski wrote:
>
> > > Question not really related to this patchset, but in the TSC2005
>  > > driver some averaging and limit checks are done to the ADC values.
>  > > Shouldn't that be done in userspace instead?  (The ADS784x and TSC2301
>  > > drivers do the same evidently.)
>  >
>  > Now that's not really up to me, but if we move it now it probably won't
>  > provide the feature n810 needs. I mean, n810 is designed on top of the
>  > features provided by driver, the application probably expects the driver
>  > to do the averaging and limit checks so at least for tsc2005 I think
>  > it's not a good idea to change due to application requirements.
>
>
> User-space is too late for this filtering, we would end up feeding ~1k samples
>  per second there, and that would simply clog the system pretty badly, can't
>  do much buffering either as that degrades interactive responsiveness of the
>  UI.

That's true, that'd send to userspace the amount of data multiplied by
the number of samples over which the averaging is done.  I don't know
how significant amount that would be.

> In my opinion kernel should provide "correct" data to user-space, not
>  some pseudo-random interference from the LCD.

I think this is discutible.  There's a number of userspace libraries
written to talk tightly to the kernel and make that data more
"correct", one of them is tslib.  Distros that come with tslib often
have a set of device-specific config files for tslib which load tslib
plugins for things like averaging, smoothing, checking bounds on the
values.

One of the arguments for doing that in userspace is that of avoiding
every touchscreen driver redoing the same averaging and/or limit
checking code.  I agree thought that it may be better to sacrifice
that for performance.

Cheers

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 23:58           ` andrzej zaborowski
@ 2008-04-10  0:12             ` Daniel Stone
  2008-04-10  1:12               ` andrzej zaborowski
  0 siblings, 1 reply; 23+ messages in thread
From: Daniel Stone @ 2008-04-10  0:12 UTC (permalink / raw)
  To: ext andrzej zaborowski
  Cc: Lauri Leukkunen, ext Felipe Balbi, felipe.balbi, linux-omap,
	Tony Lindgren, Eduardo Valentin

[-- Attachment #1: Type: text/plain, Size: 1406 bytes --]

On Thu, Apr 10, 2008 at 01:58:51AM +0200, ext andrzej zaborowski wrote:
> On 09/04/2008, Lauri Leukkunen <lauri.leukkunen@nokia.com> wrote:
> > In my opinion kernel should provide "correct" data to user-space, not
> >  some pseudo-random interference from the LCD.
> 
> I think this is discutible.  There's a number of userspace libraries
> written to talk tightly to the kernel and make that data more
> "correct", one of them is tslib.  Distros that come with tslib often
> have a set of device-specific config files for tslib which load tslib
> plugins for things like averaging, smoothing, checking bounds on the
> values.
> 
> One of the arguments for doing that in userspace is that of avoiding
> every touchscreen driver redoing the same averaging and/or limit
> checking code.  I agree thought that it may be better to sacrifice
> that for performance.

If everyone's doing the same thing, move it to drivers/input/, not to
userspace.  Why should we be forced to have _two_ drivers and
essentially maintain a stable kernel/userspace ABI between them? Surely
it's better to only have _one_ hardware-specific driver, rather than
half in the kernel, and half hidden behind an abstraction layer that is
meant to let you just deal with input events without knowing stupid
details about the hardware?

I don't see any coherent argument for it being in tslib.

Cheers,
Daniel

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-10  0:12             ` Daniel Stone
@ 2008-04-10  1:12               ` andrzej zaborowski
  2008-04-14 18:02                 ` Tony Lindgren
  0 siblings, 1 reply; 23+ messages in thread
From: andrzej zaborowski @ 2008-04-10  1:12 UTC (permalink / raw)
  To: Daniel Stone
  Cc: Lauri Leukkunen, ext Felipe Balbi, felipe.balbi, linux-omap,
	Tony Lindgren, Eduardo Valentin

On 10/04/2008, Daniel Stone <daniel.stone@nokia.com> wrote:
> On Thu, Apr 10, 2008 at 01:58:51AM +0200, ext andrzej zaborowski wrote:
>  > On 09/04/2008, Lauri Leukkunen <lauri.leukkunen@nokia.com> wrote:
>
> > > In my opinion kernel should provide "correct" data to user-space, not
>  > >  some pseudo-random interference from the LCD.
>  >
>  > I think this is discutible.  There's a number of userspace libraries
>  > written to talk tightly to the kernel and make that data more
>  > "correct", one of them is tslib.  Distros that come with tslib often
>  > have a set of device-specific config files for tslib which load tslib
>  > plugins for things like averaging, smoothing, checking bounds on the
>  > values.
>  >
>  > One of the arguments for doing that in userspace is that of avoiding
>  > every touchscreen driver redoing the same averaging and/or limit
>  > checking code.  I agree thought that it may be better to sacrifice
>  > that for performance.
>
>
> If everyone's doing the same thing, move it to drivers/input/, not to
>  userspace.  Why should we be forced to have _two_ drivers and
>  essentially maintain a stable kernel/userspace ABI between them? Surely
>  it's better to only have _one_ hardware-specific driver, rather than
>  half in the kernel, and half hidden behind an abstraction layer that is
>  meant to let you just deal with input events without knowing stupid
>  details about the hardware?

For the ease of reconfiguration for one thing, tslib is quite
configurable with the plugins loaded by a config file.  The ABI you
talk about is the same evdev ABI which is already stable.

Averaging doesn't just cancel the noise from LCD, just lessens it but
there may be better things to do with it and the userspace already
knows how to deal with that. So it expects the kernel driver to be
more like a ADC driver.

Of course doing it in drivers/input/ as configured by board files,
would also work if things were designed that way.

Cheers

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-09 18:05       ` Felipe Balbi
  2008-04-09 18:28         ` Lauri Leukkunen
  2008-04-09 20:35         ` Daniel Stone
@ 2008-04-10 13:18         ` andrzej zaborowski
  2008-04-10 14:34           ` Felipe Balbi
  2 siblings, 1 reply; 23+ messages in thread
From: andrzej zaborowski @ 2008-04-10 13:18 UTC (permalink / raw)
  To: me; +Cc: felipe.balbi, linux-omap, Tony Lindgren, Eduardo Valentin,
	Daniel Stone

On 09/04/2008, Felipe Balbi <me@felipebalbi.com> wrote:
> On Wed, Apr 09, 2008 at 07:26:31PM +0200, andrzej zaborowski wrote:
>  [...]
>
>  > >  +       time = strict_strtoul(buf, 10, &res);
>  > >  +       /* Numbers only, please. */
>  > >  +       if (buf && *buf != '\n' && *(buf + 1) != '\0')
>  > >  +               return -EINVAL;
>  >
>  > The condition doesn't look correct, for example it rejects "55\n\0"
>  > but accepts NULL.
>
>
> Well, this is not my driver and i just moved to strict_stroul cuz
>  checkpatch asked me to. I'll take a closer look tomorrow but if you can
>  come up with a better solution for this, I'll ack.

Turns out the result is returned in the buffer in third parameter, and
the checking is already "strict" (rejects trailing non-numbers), so
this should look something like:

ret = strict_strtoul(buf, 0, &time);
if (ret)
        return ret;

>
>  btw, good catch, i didn't had too much time to work on this but still
>  the driver is working fine.
>
>
>  > >  +       i2c_set_clientdata(client, lm);
>  > >  +       lm->client = client;
>  > >  +       lm8323_pdata = client->dev.platform_data;
>  > >  +       if (!lm8323_pdata)
>  > >  +               return -EINVAL; /* ? */
>  >
>  > Here lm remains allocated and is lost.  Not that it bothers me, but
>  > probably not what was intended.
>
>
> You mean the error handling?

Yep.

>
>  if (!lm8323_pdata) {
>         kfree(lm);
>         return -EINVAL;
>  }
>
>  fixing tomorrow.

Thanks
-- 
Please do not print this email unless absolutely necessary. Spread
environmental awareness.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-10 13:18         ` andrzej zaborowski
@ 2008-04-10 14:34           ` Felipe Balbi
  0 siblings, 0 replies; 23+ messages in thread
From: Felipe Balbi @ 2008-04-10 14:34 UTC (permalink / raw)
  To: linux-omap; +Cc: andrzej zaborowski, Daniel Stone, Felipe Balbi

From: Daniel Stone <daniel.stone@nokia.com>

Introduce lm8323 keypad driver.

Signed-off-by: Daniel Stone <daniel.stone@nokia.com

Updated to build with recent linux-omap and new-style
i2c driver.

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/mach-omap2/board-n800.c |   77 ++++
 arch/arm/mach-omap2/board-n810.c |    2 +
 drivers/input/keyboard/Kconfig   |    7 +
 drivers/input/keyboard/Makefile  |    1 +
 drivers/input/keyboard/lm8323.c  |  911 ++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/lm8323.h       |   39 ++
 6 files changed, 1037 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/lm8323.c
 create mode 100644 include/linux/i2c/lm8323.h

diff --git a/arch/arm/mach-omap2/board-n800.c b/arch/arm/mach-omap2/board-n800.c
index 758e2c1..367e518 100644
--- a/arch/arm/mach-omap2/board-n800.c
+++ b/arch/arm/mach-omap2/board-n800.c
@@ -23,6 +23,7 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
 #include <asm/mach/arch.h>
@@ -48,6 +49,76 @@
 #define N800_DAV_IRQ_GPIO		103
 #define N800_TSC2301_RESET_GPIO		118
 
+#ifdef CONFIG_MACH_NOKIA_N810
+static s16 rx44_keymap[LM8323_KEYMAP_SIZE] = {
+	[0x01] = KEY_Q,
+	[0x02] = KEY_K,
+	[0x03] = KEY_O,
+	[0x04] = KEY_P,
+	[0x05] = KEY_BACKSPACE,
+	[0x06] = KEY_A,
+	[0x07] = KEY_S,
+	[0x08] = KEY_D,
+	[0x09] = KEY_F,
+	[0x0a] = KEY_G,
+	[0x0b] = KEY_H,
+	[0x0c] = KEY_J,
+
+	[0x11] = KEY_W,
+	[0x12] = KEY_F4,
+	[0x13] = KEY_L,
+	[0x14] = KEY_APOSTROPHE,
+	[0x16] = KEY_Z,
+	[0x17] = KEY_X,
+	[0x18] = KEY_C,
+	[0x19] = KEY_V,
+	[0x1a] = KEY_B,
+	[0x1b] = KEY_N,
+	[0x1c] = KEY_LEFTSHIFT, /* Actually, this is both shift keys */
+	[0x1f] = KEY_F7,
+
+	[0x21] = KEY_E,
+	[0x22] = KEY_SEMICOLON,
+	[0x23] = KEY_MINUS,
+	[0x24] = KEY_EQUAL,
+	[0x2b] = KEY_FN,
+	[0x2c] = KEY_M,
+	[0x2f] = KEY_F8,
+
+	[0x31] = KEY_R,
+	[0x32] = KEY_RIGHTCTRL,
+	[0x34] = KEY_SPACE,
+	[0x35] = KEY_COMMA,
+	[0x37] = KEY_UP,
+	[0x3c] = KEY_COMPOSE,
+	[0x3f] = KEY_F6,
+
+	[0x41] = KEY_T,
+	[0x44] = KEY_DOT,
+	[0x46] = KEY_RIGHT,
+	[0x4f] = KEY_F5,
+	[0x51] = KEY_Y,
+	[0x53] = KEY_DOWN,
+	[0x55] = KEY_ENTER,
+	[0x5f] = KEY_ESC,
+
+	[0x61] = KEY_U,
+	[0x64] = KEY_LEFT,
+
+	[0x71] = KEY_I,
+	[0x75] = KEY_KPENTER,
+};
+
+static struct lm8323_platform_data lm8323_pdata = {
+	.repeat = 0, /* Repeat is handled in userspace for now. */
+	.keymap = rx44_keymap,
+
+	.name = "Internal keyboard",
+	.pwm1_name = "keyboard",
+	.pwm2_name = "cover",
+};
+#endif
+
 void __init nokia_n800_init_irq(void)
 {
 	omap2_init_common_hw();
@@ -502,6 +573,12 @@ static struct i2c_board_info __initdata n800_i2c_board_info_2[] = {
 		I2C_BOARD_INFO("tea5761", 0x10),
 	},
 #endif
+	{
+		I2C_BOARD_INFO("lm8323", 0x45),
+		.type		= "lm8323",
+		.irq		= OMAP_GPIO_IRQ(109),
+		.platform_data	= &lm8323_pdata,
+	},
 };
 
 void __init nokia_n800_common_init(void)
diff --git a/arch/arm/mach-omap2/board-n810.c b/arch/arm/mach-omap2/board-n810.c
index c4f4dd5..fb0e61f 100644
--- a/arch/arm/mach-omap2/board-n810.c
+++ b/arch/arm/mach-omap2/board-n810.c
@@ -10,6 +10,8 @@
  */
 
 #include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/i2c/lm8323.h>
 
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 1c22930..137f7e4 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -285,6 +285,13 @@ config KEYBOARD_TSC2301
 	help
 	  Say Y here for if you are using the keypad features of TSC2301.
 
+config KEYBOARD_LM8323
+	tristate "LM8323 keypad chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for the National Semiconductor
+	  LM8323 keypad controller.
+
 config KEYBOARD_PXA27x
 	tristate "PXA27x/PXA3xx keypad support"
 	depends on PXA27x || PXA3xx
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index bc0bbc1..ec447cd 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
 obj-$(CONFIG_KEYBOARD_TSC2301)		+= tsc2301_kp.o
+obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= omap-twl4030keypad.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_AAED2000)		+= aaed2000_kbd.o
diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c
new file mode 100644
index 0000000..3040622
--- /dev/null
+++ b/drivers/input/keyboard/lm8323.c
@@ -0,0 +1,911 @@
+/*
+ * drivers/i2c/chips/lm8323.c
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Written by Daniel Stone <daniel.stone@nokia.com>
+ *            Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 2 of the License only).
+ *
+ * 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/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/i2c/lm8323.h>
+
+#include <asm/mach-types.h>
+#include <asm/mach/irq.h>
+
+#ifdef VERBOSE
+#define debug dev_dbg
+#else
+#define debug(...)
+#endif
+
+/* Commands to send to the chip. */
+#define LM8323_CMD_READ_ID		0x80 /* Read chip ID. */
+#define LM8323_CMD_WRITE_CFG		0x81 /* Set configuration item. */
+#define LM8323_CMD_READ_INT		0x82 /* Get interrupt status. */
+#define LM8323_CMD_RESET		0x83 /* Reset, same as external one */
+#define LM8323_CMD_WRITE_PORT_SEL	0x85 /* Set GPIO in/out. */
+#define LM8323_CMD_WRITE_PORT_STATE	0x86 /* Set GPIO pullup. */
+#define LM8323_CMD_READ_PORT_SEL	0x87 /* Get GPIO in/out. */
+#define LM8323_CMD_READ_PORT_STATE	0x88 /* Get GPIO pullup. */
+#define LM8323_CMD_READ_FIFO		0x89 /* Read byte from FIFO. */
+#define LM8323_CMD_RPT_READ_FIFO	0x8a /* Read FIFO (no increment). */
+#define LM8323_CMD_SET_ACTIVE		0x8b /* Set active time. */
+#define LM8323_CMD_READ_ERR		0x8c /* Get error status. */
+#define LM8323_CMD_READ_ROTATOR		0x8e /* Read rotator status. */
+#define LM8323_CMD_SET_DEBOUNCE		0x8f /* Set debouncing time. */
+#define LM8323_CMD_SET_KEY_SIZE		0x90 /* Set keypad size. */
+#define LM8323_CMD_READ_KEY_SIZE	0x91 /* Get keypad size. */
+#define LM8323_CMD_READ_CFG		0x92 /* Get configuration item. */
+#define LM8323_CMD_WRITE_CLOCK		0x93 /* Set clock config. */
+#define LM8323_CMD_READ_CLOCK		0x94 /* Get clock config. */
+#define LM8323_CMD_PWM_WRITE		0x95 /* Write PWM script. */
+#define LM8323_CMD_START_PWM		0x96 /* Start PWM engine. */
+#define LM8323_CMD_STOP_PWM		0x97 /* Stop PWM engine. */
+
+/* Interrupt status. */
+#define INT_KEYPAD			0x01 /* Key event. */
+#define INT_ROTATOR			0x02 /* Rotator event. */
+#define INT_ERROR			0x08 /* Error: use CMD_READ_ERR. */
+#define INT_NOINIT			0x10 /* Lost configuration. */
+#define INT_PWM1			0x20 /* PWM1 stopped. */
+#define INT_PWM2			0x40 /* PWM2 stopped. */
+#define INT_PWM3			0x80 /* PWM3 stopped. */
+
+/* Errors (signalled by INT_ERROR, read with CMD_READ_ERR). */
+#define ERR_BADPAR			0x01 /* Bad parameter. */
+#define ERR_CMDUNK			0x02 /* Unknown command. */
+#define ERR_KEYOVR			0x04 /* Too many keys pressed. */
+#define ERR_FIFOOVER			0x40 /* FIFO overflow. */
+
+/* Configuration keys (CMD_{WRITE,READ}_CFG). */
+#define CFG_MUX1SEL			0x01 /* Select MUX1_OUT input. */
+#define CFG_MUX1EN			0x02 /* Enable MUX1_OUT. */
+#define CFG_MUX2SEL			0x04 /* Select MUX2_OUT input. */
+#define CFG_MUX2EN			0x08 /* Enable MUX2_OUT. */
+#define CFG_PSIZE			0x20 /* Package size (must be 0). */
+#define CFG_ROTEN			0x40 /* Enable rotator. */
+
+/* Clock settings (CMD_{WRITE,READ}_CLOCK). */
+#define CLK_RCPWM_INTERNAL		0x00
+#define CLK_RCPWM_EXTERNAL		0x03
+#define CLK_SLOWCLKEN			0x08 /* Enable 32.768kHz clock. */
+#define CLK_SLOWCLKOUT			0x40 /* Enable slow pulse output. */
+
+/* The possible addresses corresponding to CONFIG1 and CONFIG2 pin wirings. */
+#define LM8323_I2C_ADDR00		(0x84 >> 1)	/* 1000 010x */
+#define LM8323_I2C_ADDR01		(0x86 >> 1)	/* 1000 011x */
+#define LM8323_I2C_ADDR10		(0x88 >> 1)	/* 1000 100x */
+#define LM8323_I2C_ADDR11		(0x8A >> 1)	/* 1000 101x */
+
+/* Key event fifo length */
+#define LM8323_FIFO_LEN			15
+
+/* Commands for PWM engine; feed in with PWM_WRITE. */
+/* Load ramp counter from duty cycle field (range 0 - 0xff). */
+#define PWM_SET(v)			(0x4000 | ((v) & 0xff))
+/* Go to start of script. */
+#define PWM_GOTOSTART			0x0000
+/*
+ * Stop engine (generates interrupt).  If reset is 1, clear the program
+ * counter, else leave it.
+ */
+#define PWM_END(reset)			(0xc000 | (!!(reset) << 11))
+/*
+ * Ramp.  If s is 1, divide clock by 512, else divide clock by 16.
+ * Take t clock scales (up to 63) per step, for n steps (up to 126).
+ * If u is set, ramp up, else ramp down.
+ */
+#define PWM_RAMP(s, t, n, u)		((!!(s) << 14) | ((t) & 0x3f) << 8 | \
+					 ((n) & 0x7f) | ((u) ? 0 : 0x80))
+/*
+ * Loop (i.e. jump back to pos) for a given number of iterations (up to 63).
+ * If cnt is zero, execute until PWM_END is encountered.
+ */
+#define PWM_LOOP(cnt, pos)		(0xa000 | (((cnt) & 0x3f) << 7) | \
+					 ((pos) & 0x3f))
+/*
+ * Wait for trigger.  Argument is a mask of channels, shifted by the channel
+ * number, e.g. 0xa for channels 3 and 1.  Note that channels are numbered
+ * from 1, not 0.
+ */
+#define PWM_WAIT_TRIG(chans)		(0xe000 | (((chans) & 0x7) << 6))
+/* Send trigger.  Argument is same as PWM_WAIT_TRIG. */
+#define PWM_SEND_TRIG(chans)		(0xe000 | ((chans) & 0x7))
+
+#define DRIVER_NAME  "lm8323"
+
+struct lm8323_pwm {
+	int			id;
+	int			enabled;
+	int			fade_time;
+	int			brightness;
+	int			desired_brightness;
+	struct work_struct	work;
+	struct led_classdev	cdev;
+};
+
+struct lm8323_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	struct work_struct	work;
+	struct input_dev	*idev;
+	int			irq;
+	unsigned		kp_enabled : 1;
+	unsigned		pm_suspend : 1;
+	unsigned		keys_down;
+	char			phys[32];
+	s16			keymap[LM8323_KEYMAP_SIZE];
+	int			size_x;
+	int			size_y;
+	int			debounce_time;
+	int			active_time;
+	struct lm8323_pwm	pwm1;
+	struct lm8323_pwm	pwm2;
+	struct lm8323_pwm	pwm3;
+};
+
+#define client_to_lm8323(c)	container_of(c, struct lm8323_chip, client)
+#define dev_to_lm8323(d)	container_of(d, struct lm8323_chip, client->dev)
+#define work_to_lm8323(w)	container_of(w, struct lm8323_chip, work)
+#define cdev_to_pwm(c)		container_of(c, struct lm8323_pwm, cdev)
+#define work_to_pwm(w)		container_of(w, struct lm8323_pwm, work)
+
+static struct lm8323_chip *pwm_to_lm8323(struct lm8323_pwm *pwm)
+{
+	switch (pwm->id) {
+	case 1:
+		return container_of(pwm, struct lm8323_chip, pwm1);
+	case 2:
+		return container_of(pwm, struct lm8323_chip, pwm2);
+	case 3:
+		return container_of(pwm, struct lm8323_chip, pwm3);
+	default:
+		return NULL;
+	}
+}
+
+static struct lm8323_platform_data *lm8323_pdata;
+
+
+#define LM8323_MAX_DATA 8
+
+/*
+ * To write, we just access the chip's address in write mode, and dump the
+ * command and data out on the bus.  The command byte and data are taken as
+ * sequential u8s out of varargs, to a maximum of LM8323_MAX_DATA.
+ */
+static int lm8323_write(struct lm8323_chip *lm, int len, ...)
+{
+	int ret, i;
+	va_list ap;
+	u8 data[LM8323_MAX_DATA];
+
+	va_start(ap, len);
+
+	if (unlikely(len > LM8323_MAX_DATA)) {
+		dev_err(&lm->client->dev, "tried to send %d bytes\n", len);
+		va_end(ap);
+		return 0;
+	}
+
+	for (i = 0; i < len; i++)
+		data[i] = va_arg(ap, int);
+
+	va_end(ap);
+
+	/*
+	 * If the host is asleep while we send the data, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "sent %d bytes of %d total\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * To read, we first send the command byte to the chip and end the transaction,
+ * then access the chip in read mode, at which point it will send the data.
+ */
+static int lm8323_read(struct lm8323_chip *lm, u8 cmd, u8 *buf, int len)
+{
+	int ret;
+
+	/*
+	 * If the host is asleep while we send the byte, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret != 1)) {
+		dev_err(&lm->client->dev, "sending read cmd 0x%02x failed\n",
+			cmd);
+		return 0;
+	}
+
+	ret = i2c_master_recv(lm->client, buf, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "wanted %d bytes, got %d\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * Set the chip active time (idle time before it enters halt).
+ */
+static void lm8323_set_active_time(struct lm8323_chip *lm, int time)
+{
+	lm8323_write(lm, 2, LM8323_CMD_SET_ACTIVE, time >> 2);
+}
+
+/*
+ * The signals are AT-style: the low 7 bits are the keycode, and the top
+ * bit indicates the state (1 for down, 0 for up).
+ */
+static inline u8 lm8323_whichkey(u8 event)
+{
+	return event & 0x7f;
+}
+
+static inline int lm8323_ispress(u8 event)
+{
+	return (event & 0x80) ? 1 : 0;
+}
+
+static void process_keys(struct lm8323_chip *lm)
+{
+	u8 event;
+	u8 key_fifo[LM8323_FIFO_LEN + 1];
+	int old_keys_down = lm->keys_down;
+	int ret;
+	int i = 0;
+
+	/*
+	 * Read all key events from the FIFO at once. Next READ_FIFO clears the
+	 * FIFO even if we didn't read all events previously.
+	 */
+	ret = lm8323_read(lm, LM8323_CMD_READ_FIFO, key_fifo, LM8323_FIFO_LEN);
+
+	if (ret < 0) {
+		dev_err(&lm->client->dev, "Failed reading fifo \n");
+		return;
+	}
+	key_fifo[ret] = 0;
+
+	while ((event = key_fifo[i])) {
+		u8 key = lm8323_whichkey(event);
+		int isdown = lm8323_ispress(event);
+		s16 keycode = lm->keymap[key];
+
+		if (likely(keycode > 0)) {
+			debug(&lm->client->dev, "key 0x%02x %s\n", key,
+			      isdown ? "down" : "up");
+			if (likely(lm->kp_enabled)) {
+				input_report_key(lm->idev, keycode, isdown);
+				input_sync(lm->idev);
+			}
+			if (isdown)
+				lm->keys_down++;
+			else
+				lm->keys_down--;
+		} else {
+			dev_err(&lm->client->dev, "keycode 0x%02x not mapped "
+				"to any key\n", key);
+		}
+		i++;
+	}
+
+	/*
+	 * Errata: We need to ensure that the chip never enters halt mode
+	 * during a keypress, so set active time to 0.  When it's released,
+	 * we can enter halt again, so set the active time back to normal.
+	 */
+	if (!old_keys_down && lm->keys_down)
+		lm8323_set_active_time(lm, 0);
+	if (old_keys_down && !lm->keys_down)
+		lm8323_set_active_time(lm, lm->active_time);
+}
+
+static void lm8323_process_error(struct lm8323_chip *lm)
+{
+	u8 error;
+
+	if (lm8323_read(lm, LM8323_CMD_READ_ERR, &error, 1) == 1) {
+		if (error & ERR_FIFOOVER)
+			debug(&lm->client->dev, "fifo overflow!\n");
+		if (error & ERR_KEYOVR)
+			debug(&lm->client->dev, "more than two keys pressed\n");
+		if (error & ERR_CMDUNK)
+			debug(&lm->client->dev, "unknown command submitted\n");
+		if (error & ERR_BADPAR)
+			debug(&lm->client->dev, "bad command parameter\n");
+	}
+}
+
+static void lm8323_reset(struct lm8323_chip *lm)
+{
+	/* The docs say we must pass 0xAA as the data byte. */
+	lm8323_write(lm, 2, LM8323_CMD_RESET, 0xAA);
+}
+
+static int lm8323_configure(struct lm8323_chip *lm)
+{
+	int keysize = (lm->size_x << 4) | lm->size_y;
+	int clock = (CLK_SLOWCLKEN | CLK_RCPWM_EXTERNAL);
+	int debounce = lm->debounce_time >> 2;
+	int active = lm->active_time >> 2;
+
+	/*
+	 * Active time must be greater than the debounce time: if it's
+	 * a close-run thing, give ourselves a 12ms buffer.
+	 */
+	if (debounce >= active)
+		active = debounce + 3;
+
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CFG, 0);
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CLOCK, clock);
+	lm8323_write(lm, 2, LM8323_CMD_SET_KEY_SIZE, keysize);
+	lm8323_set_active_time(lm, lm->active_time);
+	lm8323_write(lm, 2, LM8323_CMD_SET_DEBOUNCE, debounce);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_STATE, 0xff, 0xff);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_SEL, 0, 0);
+
+	/*
+	 * Not much we can do about errors at this point, so just hope
+	 * for the best.
+	 */
+
+	return 0;
+}
+
+/*
+ * Bottom half: handle the interrupt by posting key events, or dealing with
+ * errors appropriately.
+ */
+static void lm8323_work(struct work_struct *work)
+{
+	struct lm8323_chip *lm = work_to_lm8323(work);
+	u8 ints;
+
+	mutex_lock(&lm->lock);
+
+	while ((lm8323_read(lm, LM8323_CMD_READ_INT, &ints, 1) == 1) && ints) {
+		if (likely(ints & INT_KEYPAD))
+			process_keys(lm);
+		if (ints & INT_ROTATOR) {
+			/* We don't currently support the rotator. */
+			debug(&lm->client->dev, "rotator fired\n");
+		}
+		if (ints & INT_ERROR) {
+			debug(&lm->client->dev, "error!\n");
+			lm8323_process_error(lm);
+		}
+		if (ints & INT_NOINIT) {
+			dev_err(&lm->client->dev, "chip lost config; "
+						  "reinitialising\n");
+			lm8323_configure(lm);
+		}
+		if (ints & INT_PWM1)
+			debug(&lm->client->dev, "pwm1 engine completed\n");
+		if (ints & INT_PWM2)
+			debug(&lm->client->dev, "pwm2 engine completed\n");
+		if (ints & INT_PWM3)
+			debug(&lm->client->dev, "pwm3 engine completed\n");
+	}
+
+	mutex_unlock(&lm->lock);
+}
+
+/*
+ * We cannot use I2C in interrupt context, so we just schedule work.
+ */
+static irqreturn_t lm8323_irq(int irq, void *data)
+{
+	struct lm8323_chip *lm = data;
+
+	schedule_work(&lm->work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Read the chip ID.
+ */
+static int lm8323_read_id(struct lm8323_chip *lm, u8 *buf)
+{
+	int bytes;
+
+	bytes = lm8323_read(lm, LM8323_CMD_READ_ID, buf, 2);
+	if (unlikely(bytes != 2))
+		return -EIO;
+
+	return 0;
+}
+
+static void lm8323_write_pwm_one(struct lm8323_pwm *pwm, int pos, u16 cmd)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	lm8323_write(lm, 4, LM8323_CMD_PWM_WRITE, (pos << 2) | pwm->id,
+		     (cmd & 0xff00) >> 8, cmd & 0x00ff);
+}
+
+/*
+ * Write a script into a given PWM engine, concluding with PWM_END.
+ * If 'keepalive' is specified, the engine will be kept running
+ * indefinitely.
+ */
+static void lm8323_write_pwm(struct lm8323_pwm *pwm, int keepalive,
+			     int len, ...)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+	int i, cmd;
+	va_list ap;
+
+	/*
+	 * If there are any scripts running at the moment, terminate them
+	 * and make sure the duty cycle is as if it finished.
+	 */
+	lm8323_write(lm, 2, LM8323_CMD_STOP_PWM, pwm->id);
+
+	va_start(ap, len);
+	for (i = 0; i < len; i++) {
+		cmd = va_arg(ap, int);
+		lm8323_write_pwm_one(pwm, i, cmd);
+	}
+	va_end(ap);
+
+	/* Wait for a trigger from any channel. This keeps the engine alive. */
+	if (keepalive)
+		lm8323_write_pwm_one(pwm, i++, PWM_WAIT_TRIG(0xe));
+	else
+		lm8323_write_pwm_one(pwm, i++, PWM_END(1));
+
+	lm8323_write(lm, 2, LM8323_CMD_START_PWM, pwm->id);
+}
+
+static void lm8323_pwm_work(struct work_struct *work)
+{
+	struct lm8323_pwm *pwm = work_to_pwm(work);
+	int div, perstep, steps, hz, direction, keepalive;
+
+	/* Do nothing if we're already at the requested level. */
+	if (pwm->desired_brightness == pwm->brightness)
+		return;
+
+	keepalive = (pwm->desired_brightness > 0);
+	direction = (pwm->desired_brightness > pwm->brightness);
+	steps = abs(pwm->desired_brightness - pwm->brightness);
+
+	/*
+	 * Convert time (in ms) into a divisor (512 or 16 on a refclk of
+	 * 32768Hz), and number of ticks per step.
+	 */
+	if ((pwm->fade_time / steps) > (32768 / 512))
+		div = 512;
+	else
+		div = 16;
+
+	hz = 32768 / div;
+	if (pwm->fade_time < ((steps * 1000) / hz))
+		perstep = 1;
+	else
+		perstep = (hz * pwm->fade_time) / (steps * 1000);
+
+	if (perstep == 0)
+		perstep = 1;
+	else if (perstep > 63)
+		perstep = 63;
+
+	if (steps > 252) {
+		lm8323_write_pwm(pwm, keepalive, 3,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 252,
+					  direction));
+	} else if (steps > 126) {
+		lm8323_write_pwm(pwm, keepalive, 2,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 126,
+					  direction));
+	} else {
+		lm8323_write_pwm(pwm, keepalive, 1,
+				 PWM_RAMP((div == 512), perstep, steps,
+					  direction));
+	}
+
+	pwm->brightness = pwm->desired_brightness;
+}
+
+static void lm8323_pwm_set_brightness(struct led_classdev *led_cdev,
+				      enum led_brightness brightness)
+{
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	pwm->desired_brightness = brightness;
+
+	if (in_interrupt()) {
+		schedule_work(&pwm->work);
+	} else {
+		/*
+		 * Schedule PWM work as usual unless we are going into suspend
+		 */
+		mutex_lock(&lm->lock);
+		if (likely(!lm->pm_suspend))
+			schedule_work(&pwm->work);
+		else
+			lm8323_pwm_work(&pwm->work);
+		mutex_unlock(&lm->lock);
+	}
+}
+
+static ssize_t lm8323_pwm_show_time(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+
+	return sprintf(buf, "%d\n", pwm->fade_time);
+}
+
+static ssize_t lm8323_pwm_store_time(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	int ret;
+	int time;
+
+	ret = strict_strtoul(buf, 10, &time);
+	/* Numbers only, please. */
+	if (ret)
+		return -EINVAL;
+
+	pwm->fade_time = time;
+
+	return strlen(buf);
+}
+static DEVICE_ATTR(time, 0644, lm8323_pwm_show_time, lm8323_pwm_store_time);
+
+static int init_pwm(struct lm8323_chip *lm, int id, struct device *dev,
+		    const char *name)
+{
+	struct lm8323_pwm *pwm = NULL;
+
+	BUG_ON(id > 3);
+
+	switch (id) {
+	case 1:
+		pwm = &lm->pwm1;
+		break;
+	case 2:
+		pwm = &lm->pwm2;
+		break;
+	case 3:
+		pwm = &lm->pwm3;
+		break;
+	}
+
+	pwm->id = id;
+	pwm->fade_time = 0;
+	pwm->brightness = 0;
+	pwm->desired_brightness = 0;
+	if (name) {
+		pwm->cdev.name = name;
+		pwm->cdev.brightness_set = lm8323_pwm_set_brightness;
+		if (led_classdev_register(dev, &pwm->cdev) < 0) {
+			dev_err(dev, "couldn't register PWM %d\n", id);
+			return -1;
+		}
+		if (device_create_file(pwm->cdev.dev,
+					     &dev_attr_time) < 0) {
+			dev_err(dev, "couldn't register time attribute\n");
+			led_classdev_unregister(&pwm->cdev);
+			return -1;
+		}
+		INIT_WORK(&pwm->work, lm8323_pwm_work);
+		pwm->enabled = 1;
+	} else {
+		pwm->enabled = 0;
+	}
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver;
+
+static ssize_t lm8323_show_disable(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", !lm->kp_enabled);
+}
+
+static ssize_t lm8323_set_disable(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+	int ret;
+	int i;
+
+	i = strict_strtoul(buf, 10, &ret);
+
+	mutex_lock(&lm->lock);
+	lm->kp_enabled = !i;
+	mutex_unlock(&lm->lock);
+
+	return count;
+}
+static DEVICE_ATTR(disable_kp, 0644, lm8323_show_disable, lm8323_set_disable);
+
+static int lm8323_probe(struct i2c_client *client)
+{
+	struct input_dev *idev;
+	struct lm8323_chip *lm;
+	int i, err = 0;
+	unsigned long tmo;
+	u8 data[2];
+
+	lm = kzalloc(sizeof *lm, GFP_KERNEL);
+	if (!lm)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, lm);
+	lm->client = client;
+	lm8323_pdata = client->dev.platform_data;
+	if (!lm8323_pdata)
+		return -EINVAL; /* ? */
+
+	lm->size_x = lm8323_pdata->size_x;
+	if (lm->size_x == 0) {
+		lm->size_x = 8;
+	} else if (lm->size_x > 8) {
+		dev_err(&client->dev, "invalid x size %d specified\n",
+				lm->size_x);
+		lm->size_x = 8;
+	}
+
+	lm->size_y = lm8323_pdata->size_y;
+	if (lm->size_y == 0) {
+		lm->size_y = 12;
+	} else if (lm->size_y > 12) {
+		dev_err(&client->dev, "invalid y size %d specified\n",
+				lm->size_y);
+		lm->size_x = 12;
+	}
+
+	debug(&c->dev, "Keypad size: %d x %d\n", lm->size_x, lm->size_y);
+
+	lm->debounce_time = lm8323_pdata->debounce_time;
+	if (lm->debounce_time == 0) /* Default. */
+		lm->debounce_time = 12;
+	else if (lm->debounce_time == -1) /* Disable debounce. */
+		lm->debounce_time = 0;
+
+	lm->active_time = lm8323_pdata->active_time;
+	if (lm->active_time == 0) /* Default. */
+		lm->active_time = 500;
+	else if (lm->active_time == -1) /* Disable sleep. */
+		lm->active_time = 0;
+
+	lm8323_reset(lm);
+
+	/* Nothing's set up to service the IRQ yet, so just spin for max.
+	 * 100ms until we can configure. */
+	tmo = jiffies + msecs_to_jiffies(100);
+	while (lm8323_read(lm, LM8323_CMD_READ_INT, data, 1) == 1) {
+		if (data[0] & INT_NOINIT)
+			break;
+
+		if (time_after(jiffies, tmo)) {
+			dev_err(&client->dev,
+					"timeout waiting for initialisation\n");
+			break;
+		}
+
+		msleep(1);
+	}
+	lm8323_configure(lm);
+
+	/* If a true probe check the device */
+	if (lm8323_read_id(lm, data) != 0) {
+		dev_err(&client->dev, "device not found\n");
+		err = -ENODEV;
+		goto fail2;
+	}
+
+	if (init_pwm(lm, 1, &client->dev, lm8323_pdata->pwm1_name) < 0)
+		goto fail3;
+	if (init_pwm(lm, 2, &client->dev, lm8323_pdata->pwm2_name) < 0)
+		goto fail4;
+	if (init_pwm(lm, 3, &client->dev, lm8323_pdata->pwm3_name) < 0)
+		goto fail5;
+
+	lm->irq = lm8323_pdata->irq_gpio;
+	debug(&c->dev, "IRQ: %d\n", lm->irq);
+
+	mutex_init(&lm->lock);
+	INIT_WORK(&lm->work, lm8323_work);
+
+	err = request_irq(client->irq, lm8323_irq,
+			  IRQF_TRIGGER_FALLING | IRQF_DISABLED |
+			  IRQF_SAMPLE_RANDOM, DRIVER_NAME, lm);
+	if (err) {
+		dev_err(&client->dev, "could not get IRQ %d\n", lm->irq);
+		goto fail6;
+	}
+
+	set_irq_wake(lm->irq, 1);
+
+	lm->kp_enabled = 1;
+	err = device_create_file(&client->dev, &dev_attr_disable_kp);
+	if (err < 0)
+		goto fail7;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		err = -ENOMEM;
+		goto fail8;
+	}
+
+	if (lm8323_pdata->name)
+		idev->name = lm8323_pdata->name;
+	else
+		idev->name = "LM8323 keypad";
+	snprintf(lm->phys, sizeof(lm->phys), "%s/input-kp", client->dev.bus_id);
+	idev->phys = lm->phys;
+
+	lm->keys_down = 0;
+	idev->evbit[0] = BIT(EV_KEY);
+	for (i = 0; i < LM8323_KEYMAP_SIZE; i++) {
+		if (lm8323_pdata->keymap[i] > 0)
+			set_bit(lm8323_pdata->keymap[i], idev->keybit);
+
+		lm->keymap[i] = lm8323_pdata->keymap[i];
+	}
+
+	if (lm8323_pdata->repeat)
+		set_bit(EV_REP, idev->evbit);
+
+	lm->idev = idev;
+	if (input_register_device(idev)) {
+		dev_dbg(&client->dev, "error registering input device\n");
+		goto fail8;
+	}
+
+	return 0;
+
+fail8:
+	device_remove_file(&client->dev, &dev_attr_disable_kp);
+fail7:
+	free_irq(lm->irq, lm);
+fail6:
+	if (lm->pwm3.enabled)
+		led_classdev_unregister(&lm->pwm3.cdev);
+fail5:
+	if (lm->pwm2.enabled)
+		led_classdev_unregister(&lm->pwm2.cdev);
+fail4:
+	if (lm->pwm1.enabled)
+		led_classdev_unregister(&lm->pwm1.cdev);
+fail3:
+fail2:
+	kfree(lm);
+	return err;
+}
+
+static int lm8323_remove(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	free_irq(lm->irq, lm);
+	device_remove_file(&lm->client->dev, &dev_attr_disable_kp);
+
+	return 0;
+}
+
+/*
+ * We don't need to explicitly suspend the chip, as it already switches off
+ * when there's no activity.
+ */
+static int lm8323_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	set_irq_wake(lm->irq, 0);
+	disable_irq(lm->irq);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 1;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_suspend(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_suspend(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_suspend(&lm->pwm3.cdev);
+
+	return 0;
+}
+
+static int lm8323_resume(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 0;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_resume(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_resume(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_resume(&lm->pwm3.cdev);
+
+	enable_irq(lm->irq);
+	set_irq_wake(lm->irq, 1);
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver = {
+	.driver = {
+		.name	 = DRIVER_NAME,
+	},
+	.probe		= lm8323_probe,
+	.remove		= __exit_p(lm8323_remove),
+	.suspend	= lm8323_suspend,
+	.resume		= lm8323_resume,
+};
+
+static int __init lm8323_init(void)
+{
+	return i2c_add_driver(&lm8323_i2c_driver);
+}
+
+static void __exit lm8323_exit(void)
+{
+	i2c_del_driver(&lm8323_i2c_driver);
+}
+
+MODULE_AUTHOR("Daniel Stone");
+MODULE_DESCRIPTION("LM8323 keypad driver");
+MODULE_LICENSE("GPL");
+
+module_init(lm8323_init);
+module_exit(lm8323_exit);
diff --git a/include/linux/i2c/lm8323.h b/include/linux/i2c/lm8323.h
new file mode 100644
index 0000000..5cb09ab
--- /dev/null
+++ b/include/linux/i2c/lm8323.h
@@ -0,0 +1,39 @@
+/*
+ * include/lm8323.h
+ *
+ * Configuration for LM8323 keypad driver.
+ */
+
+#ifndef __LINUX_LM8323_H
+#define __LINUX_LM8323_H
+
+#include <linux/types.h>
+
+/*
+ * Largest keycode that the chip can send, plus one,
+ * so keys can be mapped directly at the index of the
+ * LM8323 keycode instead of subtracting one.
+ */
+#define LM8323_KEYMAP_SIZE (0x7f + 1)
+
+struct lm8323_platform_data {
+	u16 irq_gpio;
+
+	int debounce_time; /* Time to watch for key bouncing, in ms. */
+	int active_time; /* Idle time until sleep, in ms. */
+
+	int size_x;
+	int size_y;
+	int repeat : 1;
+	const s16 *keymap;
+
+	char *pwm1_name; /* Device name for PWM1. */
+	char *pwm2_name; /* Device name for PWM2. */
+	char *pwm3_name; /* Device name for PWM3. */
+
+	char *name; /* Device name. */
+};
+
+void __init lm8323_set_platform_data(struct lm8323_platform_data *pdata);
+
+#endif /* __LINUX_LM8323_H */
-- 
1.5.5.23.g2a5fe


^ permalink raw reply related	[flat|nested] 23+ messages in thread

* Re: [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver
  2008-04-10  1:12               ` andrzej zaborowski
@ 2008-04-14 18:02                 ` Tony Lindgren
  0 siblings, 0 replies; 23+ messages in thread
From: Tony Lindgren @ 2008-04-14 18:02 UTC (permalink / raw)
  To: andrzej zaborowski
  Cc: Daniel Stone, Lauri Leukkunen, ext Felipe Balbi, felipe.balbi,
	linux-omap, Eduardo Valentin

* andrzej zaborowski <balrogg@gmail.com> [080409 18:12]:
> On 10/04/2008, Daniel Stone <daniel.stone@nokia.com> wrote:
> > On Thu, Apr 10, 2008 at 01:58:51AM +0200, ext andrzej zaborowski wrote:
> >  > On 09/04/2008, Lauri Leukkunen <lauri.leukkunen@nokia.com> wrote:
> >
> > > > In my opinion kernel should provide "correct" data to user-space, not
> >  > >  some pseudo-random interference from the LCD.
> >  >
> >  > I think this is discutible.  There's a number of userspace libraries
> >  > written to talk tightly to the kernel and make that data more
> >  > "correct", one of them is tslib.  Distros that come with tslib often
> >  > have a set of device-specific config files for tslib which load tslib
> >  > plugins for things like averaging, smoothing, checking bounds on the
> >  > values.
> >  >
> >  > One of the arguments for doing that in userspace is that of avoiding
> >  > every touchscreen driver redoing the same averaging and/or limit
> >  > checking code.  I agree thought that it may be better to sacrifice
> >  > that for performance.
> >
> >
> > If everyone's doing the same thing, move it to drivers/input/, not to
> >  userspace.  Why should we be forced to have _two_ drivers and
> >  essentially maintain a stable kernel/userspace ABI between them? Surely
> >  it's better to only have _one_ hardware-specific driver, rather than
> >  half in the kernel, and half hidden behind an abstraction layer that is
> >  meant to let you just deal with input events without knowing stupid
> >  details about the hardware?
> 
> For the ease of reconfiguration for one thing, tslib is quite
> configurable with the plugins loaded by a config file.  The ABI you
> talk about is the same evdev ABI which is already stable.
> 
> Averaging doesn't just cancel the noise from LCD, just lessens it but
> there may be better things to do with it and the userspace already
> knows how to deal with that. So it expects the kernel driver to be
> more like a ADC driver.
> 
> Of course doing it in drivers/input/ as configured by board files,
> would also work if things were designed that way.

Where to do the filtering should be discussed with the input people.

Meanwhile, I'll push this patch to linux-omap tree.

Tony

^ permalink raw reply	[flat|nested] 23+ messages in thread

* Re: [PATCH 0/5] n810 drivers, take #3
  2008-04-09 12:03 [PATCH 0/5] n810 drivers, take #3 Felipe Balbi
  2008-04-09 12:04 ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
@ 2008-04-14 18:03 ` Tony Lindgren
  1 sibling, 0 replies; 23+ messages in thread
From: Tony Lindgren @ 2008-04-14 18:03 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Eduardo Valentin

* Felipe Balbi <felipe.balbi@nokia.com> [080409 05:05]:
> 
> The following patches are updates from n810 tree to
> be able to build on top of current linux-omap.
>
> I'm addind here the keypad driver, touchscreen driver,
> ambient light sensor driver and LED driver.

I'll push the whole series today.

Tony

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2008-04-14 18:03 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-04-09 12:03 [PATCH 0/5] n810 drivers, take #3 Felipe Balbi
2008-04-09 12:04 ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
2008-04-09 12:04   ` [PATCH 2/5] I2C: TSL2563: Add support for Taos tsl2563 ambient light sensor Felipe Balbi
2008-04-09 12:04     ` [PATCH 3/5] INPUT: TOUCHSCREEN: Introduce tsc2005 driver Felipe Balbi
2008-04-09 12:04       ` [PATCH 4/5] I2C: LP5521: Introduce lp5521 LED driver Felipe Balbi
2008-04-09 12:04         ` [PATCH 5/5] ARM: N800: Update n800 defconfig Felipe Balbi
2008-04-09 12:11   ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
2008-04-09 17:26     ` andrzej zaborowski
2008-04-09 18:05       ` Felipe Balbi
2008-04-09 18:28         ` Lauri Leukkunen
2008-04-09 23:58           ` andrzej zaborowski
2008-04-10  0:12             ` Daniel Stone
2008-04-10  1:12               ` andrzej zaborowski
2008-04-14 18:02                 ` Tony Lindgren
2008-04-09 20:35         ` Daniel Stone
2008-04-10 13:18         ` andrzej zaborowski
2008-04-10 14:34           ` Felipe Balbi
2008-04-14 18:03 ` [PATCH 0/5] n810 drivers, take #3 Tony Lindgren
  -- strict thread matches above, loose matches on Subject: below --
2008-04-09 10:09 [PATCH 0/5] resend n810 drivers Felipe Balbi
2008-04-09 10:09 ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi
2008-04-09 10:54   ` Eduardo Valentin
2008-04-09 11:02     ` Daniel Stone
2008-04-09 11:33       ` Eduardo Valentin
2008-04-09 10:04 [PATCH 0/5] n810 drivers Felipe Balbi
2008-04-09 10:04 ` [PATCH 1/5] I2C: LM8323: Introduce lm8323 keypad driver Felipe Balbi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox