* [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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ 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; 18+ 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] 18+ messages in thread
end of thread, other threads:[~2008-04-14 18:03 UTC | newest] Thread overview: 18+ 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
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox