summaryrefslogtreecommitdiff
path: root/drivers/input
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2008-04-25 12:38:14 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2008-04-25 12:38:14 -0700
commitce1d5b23a8d1e19866ab82bdec0dc41fde5273d8 (patch)
tree028c80655ee8853ebf607d435bc3d6ab223aef1f /drivers/input
parentad5e1b0f5d913d2c8bddfba81049cc07228da1a6 (diff)
parent308f0a5898033691d050374a949bbfe173987a16 (diff)
downloadlinux-3.10-ce1d5b23a8d1e19866ab82bdec0dc41fde5273d8.tar.gz
linux-3.10-ce1d5b23a8d1e19866ab82bdec0dc41fde5273d8.tar.bz2
linux-3.10-ce1d5b23a8d1e19866ab82bdec0dc41fde5273d8.zip
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: (40 commits) Input: wacom - add support for Cintiq 20WSX Input: ucb1400_ts - IRQ probe fix Input: at32psif - update MODULE_AUTHOR with new email Input: mac_hid - add lockdep annotation to emumousebtn Input: i8042 - fix incorrect usage of strncpy and strncat Input: bf54x-keys - add infrastructure for keypad wakeups Input: add MODULE_ALIAS() to hotpluggable platform modules Input: drivers/char/keyboard.c - use time_after Input: fix ordering in joystick Makefile Input: wm97xx-core - support use as a wakeup source Input: wm97xx-core - use IRQF_SAMPLE_RANDOM Input: wm97xx-core - only schedule interrupt handler if not already scheduled Input: add Zhen Hua driver Input: aiptek - add support for Genius G-PEN 560 tablet Input: wacom - implement suspend and autosuspend Input: xpad - set proper buffer length for outgoing requests Input: omap-keypad - fix build warning Input: gpio_keys - irq handling cleanup Input: add PS/2 serio driver for AVR32 devices Input: put ledstate in the keyboard notifier ...
Diffstat (limited to 'drivers/input')
-rw-r--r--drivers/input/input-polldev.c6
-rw-r--r--drivers/input/joystick/Kconfig12
-rw-r--r--drivers/input/joystick/Makefile3
-rw-r--r--drivers/input/joystick/xpad.c213
-rw-r--r--drivers/input/joystick/zhenhua.c243
-rw-r--r--drivers/input/keyboard/aaed2000_kbd.c4
-rw-r--r--drivers/input/keyboard/bf54x-keys.c37
-rw-r--r--drivers/input/keyboard/corgikbd.c2
-rw-r--r--drivers/input/keyboard/gpio_keys.c5
-rw-r--r--drivers/input/keyboard/jornada680_kbd.c2
-rw-r--r--drivers/input/keyboard/jornada720_kbd.c4
-rw-r--r--drivers/input/keyboard/locomokbd.c73
-rw-r--r--drivers/input/keyboard/omap-keypad.c9
-rw-r--r--drivers/input/keyboard/pxa27x_keypad.c4
-rw-r--r--drivers/input/keyboard/spitzkbd.c1
-rw-r--r--drivers/input/keyboard/tosakbd.c23
-rw-r--r--drivers/input/misc/cobalt_btns.c3
-rw-r--r--drivers/input/mouse/gpio_mouse.c4
-rw-r--r--drivers/input/serio/Kconfig10
-rw-r--r--drivers/input/serio/Makefile1
-rw-r--r--drivers/input/serio/at32psif.c375
-rw-r--r--drivers/input/serio/i8042-x86ia64io.h12
-rw-r--r--drivers/input/serio/rpckbd.c2
-rw-r--r--drivers/input/tablet/Kconfig10
-rw-r--r--drivers/input/tablet/aiptek.c2
-rw-r--r--drivers/input/tablet/wacom.h3
-rw-r--r--drivers/input/tablet/wacom_sys.c76
-rw-r--r--drivers/input/tablet/wacom_wac.c2
-rw-r--r--drivers/input/touchscreen/Kconfig53
-rw-r--r--drivers/input/touchscreen/Makefile7
-rw-r--r--drivers/input/touchscreen/ads7846.c22
-rw-r--r--drivers/input/touchscreen/corgi_ts.c2
-rw-r--r--drivers/input/touchscreen/jornada720_ts.c4
-rw-r--r--drivers/input/touchscreen/mainstone-wm97xx.c302
-rw-r--r--drivers/input/touchscreen/ucb1400_ts.c4
-rw-r--r--drivers/input/touchscreen/usbtouchscreen.c31
-rw-r--r--drivers/input/touchscreen/wm9705.c353
-rw-r--r--drivers/input/touchscreen/wm9712.c462
-rw-r--r--drivers/input/touchscreen/wm9713.c460
-rw-r--r--drivers/input/touchscreen/wm97xx-core.c829
40 files changed, 3559 insertions, 111 deletions
diff --git a/drivers/input/input-polldev.c b/drivers/input/input-polldev.c
index 490918a5d19..0d3ce7a50fb 100644
--- a/drivers/input/input-polldev.c
+++ b/drivers/input/input-polldev.c
@@ -73,7 +73,7 @@ static void input_polled_device_work(struct work_struct *work)
static int input_open_polled_device(struct input_dev *input)
{
- struct input_polled_dev *dev = input->private;
+ struct input_polled_dev *dev = input_get_drvdata(input);
int error;
error = input_polldev_start_workqueue();
@@ -91,7 +91,7 @@ static int input_open_polled_device(struct input_dev *input)
static void input_close_polled_device(struct input_dev *input)
{
- struct input_polled_dev *dev = input->private;
+ struct input_polled_dev *dev = input_get_drvdata(input);
cancel_delayed_work_sync(&dev->work);
input_polldev_stop_workqueue();
@@ -151,10 +151,10 @@ int input_register_polled_device(struct input_polled_dev *dev)
{
struct input_dev *input = dev->input;
+ input_set_drvdata(input, dev);
INIT_DELAYED_WORK(&dev->work, input_polled_device_work);
if (!dev->poll_interval)
dev->poll_interval = 500;
- input->private = dev;
input->open = input_open_polled_device;
input->close = input_close_polled_device;
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 7c662ee594a..be5c14a5a0a 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -193,6 +193,18 @@ config JOYSTICK_TWIDJOY
To compile this driver as a module, choose M here: the
module will be called twidjoy.
+config JOYSTICK_ZHENHUA
+ tristate "5-byte Zhenhua RC transmitter"
+ select SERIO
+ help
+ Say Y here if you have a Zhen Hua PPM-4CH transmitter which is
+ supplied with a ready to fly micro electric indoor helicopters
+ such as EasyCopter, Lama, MiniCopter, DragonFly or Jabo and want
+ to use it via serial cable as a joystick.
+
+ To compile this driver as a module, choose M here: the
+ module will be called zhenhua.
+
config JOYSTICK_DB9
tristate "Multisystem, Sega Genesis, Saturn joysticks and gamepads"
depends on PARPORT
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index e855abb0cc5..fdbf8c4c287 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_JOYSTICK_GF2K) += gf2k.o
obj-$(CONFIG_JOYSTICK_GRIP) += grip.o
obj-$(CONFIG_JOYSTICK_GRIP_MP) += grip_mp.o
obj-$(CONFIG_JOYSTICK_GUILLEMOT) += guillemot.o
+obj-$(CONFIG_JOYSTICK_IFORCE) += iforce/
obj-$(CONFIG_JOYSTICK_INTERACT) += interact.o
obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o
obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o
@@ -27,5 +28,5 @@ obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o
obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o
obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o
obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o
+obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o
-obj-$(CONFIG_JOYSTICK_IFORCE) += iforce/
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index 0380597249b..52ddb04644a 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -1,5 +1,5 @@
/*
- * X-Box gamepad - v0.0.6
+ * X-Box gamepad driver
*
* Copyright (c) 2002 Marko Friedemann <mfr@bmx-chemnitz.de>
* 2004 Oliver Schwartz <Oliver.Schwartz@gmx.de>,
@@ -68,6 +68,8 @@
* - dance pads will map D-PAD to buttons, not axes
* - pass the module paramater 'dpad_to_buttons' to force
* the D-PAD to map to buttons if your pad is not detected
+ *
+ * Later changes can be tracked in SCM.
*/
#include <linux/kernel.h>
@@ -77,7 +79,6 @@
#include <linux/module.h>
#include <linux/usb/input.h>
-#define DRIVER_VERSION "v0.0.6"
#define DRIVER_AUTHOR "Marko Friedemann <mfr@bmx-chemnitz.de>"
#define DRIVER_DESC "X-Box pad driver"
@@ -87,10 +88,12 @@
but we map them to axes when possible to simplify things */
#define MAP_DPAD_TO_BUTTONS 0
#define MAP_DPAD_TO_AXES 1
-#define MAP_DPAD_UNKNOWN -1
+#define MAP_DPAD_UNKNOWN 2
#define XTYPE_XBOX 0
#define XTYPE_XBOX360 1
+#define XTYPE_XBOX360W 2
+#define XTYPE_UNKNOWN 3
static int dpad_to_buttons;
module_param(dpad_to_buttons, bool, S_IRUGO);
@@ -107,8 +110,10 @@ static const struct xpad_device {
{ 0x045e, 0x0289, "Microsoft X-Box pad v2 (US)", MAP_DPAD_TO_AXES, XTYPE_XBOX },
{ 0x045e, 0x0285, "Microsoft X-Box pad (Japan)", MAP_DPAD_TO_AXES, XTYPE_XBOX },
{ 0x045e, 0x0287, "Microsoft Xbox Controller S", MAP_DPAD_TO_AXES, XTYPE_XBOX },
+ { 0x045e, 0x0719, "Xbox 360 Wireless Receiver", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W },
{ 0x0c12, 0x8809, "RedOctane Xbox Dance Pad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
{ 0x044f, 0x0f07, "Thrustmaster, Inc. Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX },
+ { 0x046d, 0xc242, "Logitech Chillstream Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX360 },
{ 0x046d, 0xca84, "Logitech Xbox Cordless Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX },
{ 0x046d, 0xca88, "Logitech Compact Controller for Xbox", MAP_DPAD_TO_AXES, XTYPE_XBOX },
{ 0x05fd, 0x1007, "Mad Catz Controller (unverified)", MAP_DPAD_TO_AXES, XTYPE_XBOX },
@@ -135,18 +140,26 @@ static const struct xpad_device {
{ 0x0f30, 0x8888, "BigBen XBMiniPad Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX },
{ 0x102c, 0xff0c, "Joytech Wireless Advanced Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX },
{ 0x12ab, 0x8809, "Xbox DDR dancepad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
+ { 0x1430, 0x4748, "RedOctane Guitar Hero X-plorer", MAP_DPAD_TO_AXES, XTYPE_XBOX360 },
{ 0x1430, 0x8888, "TX6500+ Dance Pad (first generation)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
{ 0x045e, 0x028e, "Microsoft X-Box 360 pad", MAP_DPAD_TO_AXES, XTYPE_XBOX360 },
{ 0xffff, 0xffff, "Chinese-made Xbox Controller", MAP_DPAD_TO_AXES, XTYPE_XBOX },
- { 0x0000, 0x0000, "Generic X-Box pad", MAP_DPAD_UNKNOWN, XTYPE_XBOX }
+ { 0x0000, 0x0000, "Generic X-Box pad", MAP_DPAD_UNKNOWN, XTYPE_UNKNOWN }
};
-static const signed short xpad_btn[] = {
- BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, /* "analog" buttons */
+/* buttons shared with xbox and xbox360 */
+static const signed short xpad_common_btn[] = {
+ BTN_A, BTN_B, BTN_X, BTN_Y, /* "analog" buttons */
BTN_START, BTN_BACK, BTN_THUMBL, BTN_THUMBR, /* start/back/sticks */
-1 /* terminating entry */
};
+/* original xbox controllers only */
+static const signed short xpad_btn[] = {
+ BTN_C, BTN_Z, /* "analog" buttons */
+ -1 /* terminating entry */
+};
+
/* only used if MAP_DPAD_TO_BUTTONS */
static const signed short xpad_btn_pad[] = {
BTN_LEFT, BTN_RIGHT, /* d-pad left, right */
@@ -173,12 +186,27 @@ static const signed short xpad_abs_pad[] = {
-1 /* terminating entry */
};
-/* Xbox 360 has a vendor-specific (sub)class, so we cannot match it with only
- * USB_INTERFACE_INFO, more to that this device has 4 InterfaceProtocols,
- * but we need only one of them. */
+/* Xbox 360 has a vendor-specific class, so we cannot match it with only
+ * USB_INTERFACE_INFO (also specifically refused by USB subsystem), so we
+ * match against vendor id as well. Wired Xbox 360 devices have protocol 1,
+ * wireless controllers have protocol 129. */
+#define XPAD_XBOX360_VENDOR_PROTOCOL(vend,pr) \
+ .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \
+ .idVendor = (vend), \
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \
+ .bInterfaceSubClass = 93, \
+ .bInterfaceProtocol = (pr)
+#define XPAD_XBOX360_VENDOR(vend) \
+ { XPAD_XBOX360_VENDOR_PROTOCOL(vend,1) }, \
+ { XPAD_XBOX360_VENDOR_PROTOCOL(vend,129) }
+
static struct usb_device_id xpad_table [] = {
{ USB_INTERFACE_INFO('X', 'B', 0) }, /* X-Box USB-IF not approved class */
- { USB_DEVICE_INTERFACE_PROTOCOL(0x045e, 0x028e, 1) }, /* X-Box 360 controller */
+ XPAD_XBOX360_VENDOR(0x045e), /* Microsoft X-Box 360 controllers */
+ XPAD_XBOX360_VENDOR(0x046d), /* Logitech X-Box 360 style controllers */
+ XPAD_XBOX360_VENDOR(0x0738), /* Mad Catz X-Box 360 controllers */
+ XPAD_XBOX360_VENDOR(0x0e6f), /* 0x0e6f X-Box 360 controllers */
+ XPAD_XBOX360_VENDOR(0x1430), /* RedOctane X-Box 360 controllers */
{ }
};
@@ -188,10 +216,15 @@ struct usb_xpad {
struct input_dev *dev; /* input device interface */
struct usb_device *udev; /* usb device */
+ int pad_present;
+
struct urb *irq_in; /* urb for interrupt in report */
unsigned char *idata; /* input data */
dma_addr_t idata_dma;
+ struct urb *bulk_out;
+ unsigned char *bdata;
+
#if defined(CONFIG_JOYSTICK_XPAD_FF) || defined(CONFIG_JOYSTICK_XPAD_LEDS)
struct urb *irq_out; /* urb for interrupt out report */
unsigned char *odata; /* output data */
@@ -227,13 +260,13 @@ static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *d
input_report_abs(dev, ABS_X,
(__s16) le16_to_cpup((__le16 *)(data + 12)));
input_report_abs(dev, ABS_Y,
- (__s16) le16_to_cpup((__le16 *)(data + 14)));
+ ~(__s16) le16_to_cpup((__le16 *)(data + 14)));
/* right stick */
input_report_abs(dev, ABS_RX,
(__s16) le16_to_cpup((__le16 *)(data + 16)));
input_report_abs(dev, ABS_RY,
- (__s16) le16_to_cpup((__le16 *)(data + 18)));
+ ~(__s16) le16_to_cpup((__le16 *)(data + 18)));
/* triggers left/right */
input_report_abs(dev, ABS_Z, data[10]);
@@ -321,13 +354,13 @@ static void xpad360_process_packet(struct usb_xpad *xpad,
input_report_abs(dev, ABS_X,
(__s16) le16_to_cpup((__le16 *)(data + 6)));
input_report_abs(dev, ABS_Y,
- (__s16) le16_to_cpup((__le16 *)(data + 8)));
+ ~(__s16) le16_to_cpup((__le16 *)(data + 8)));
/* right stick */
input_report_abs(dev, ABS_RX,
(__s16) le16_to_cpup((__le16 *)(data + 10)));
input_report_abs(dev, ABS_RY,
- (__s16) le16_to_cpup((__le16 *)(data + 12)));
+ ~(__s16) le16_to_cpup((__le16 *)(data + 12)));
/* triggers left/right */
input_report_abs(dev, ABS_Z, data[4]);
@@ -336,6 +369,39 @@ static void xpad360_process_packet(struct usb_xpad *xpad,
input_sync(dev);
}
+/*
+ * xpad360w_process_packet
+ *
+ * Completes a request by converting the data into events for the
+ * input subsystem. It is version for xbox 360 wireless controller.
+ *
+ * Byte.Bit
+ * 00.1 - Status change: The controller or headset has connected/disconnected
+ * Bits 01.7 and 01.6 are valid
+ * 01.7 - Controller present
+ * 01.6 - Headset present
+ * 01.1 - Pad state (Bytes 4+) valid
+ *
+ */
+
+static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data)
+{
+ /* Presence change */
+ if (data[0] & 0x08) {
+ if (data[1] & 0x80) {
+ xpad->pad_present = 1;
+ usb_submit_urb(xpad->bulk_out, GFP_ATOMIC);
+ } else
+ xpad->pad_present = 0;
+ }
+
+ /* Valid pad data */
+ if (!(data[1] & 0x1))
+ return;
+
+ xpad360_process_packet(xpad, cmd, &data[4]);
+}
+
static void xpad_irq_in(struct urb *urb)
{
struct usb_xpad *xpad = urb->context;
@@ -358,10 +424,16 @@ static void xpad_irq_in(struct urb *urb)
goto exit;
}
- if (xpad->xtype == XTYPE_XBOX360)
+ switch (xpad->xtype) {
+ case XTYPE_XBOX360:
xpad360_process_packet(xpad, 0, xpad->idata);
- else
+ break;
+ case XTYPE_XBOX360W:
+ xpad360w_process_packet(xpad, 0, xpad->idata);
+ break;
+ default:
xpad_process_packet(xpad, 0, xpad->idata);
+ }
exit:
retval = usb_submit_urb (urb, GFP_ATOMIC);
@@ -399,6 +471,23 @@ exit:
__FUNCTION__, retval);
}
+static void xpad_bulk_out(struct urb *urb)
+{
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status);
+ break;
+ default:
+ dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status);
+ }
+}
+
static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad)
{
struct usb_endpoint_descriptor *ep_irq_out;
@@ -408,7 +497,7 @@ static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad)
return 0;
xpad->odata = usb_buffer_alloc(xpad->udev, XPAD_PKT_LEN,
- GFP_ATOMIC, &xpad->odata_dma );
+ GFP_KERNEL, &xpad->odata_dma);
if (!xpad->odata)
goto fail1;
@@ -469,6 +558,7 @@ static int xpad_play_effect(struct input_dev *dev, void *data,
xpad->odata[5] = 0x00;
xpad->odata[6] = 0x00;
xpad->odata[7] = 0x00;
+ xpad->irq_out->transfer_buffer_length = 8;
usb_submit_urb(xpad->irq_out, GFP_KERNEL);
}
@@ -477,6 +567,9 @@ static int xpad_play_effect(struct input_dev *dev, void *data,
static int xpad_init_ff(struct usb_xpad *xpad)
{
+ if (xpad->xtype != XTYPE_XBOX360)
+ return 0;
+
input_set_capability(xpad->dev, EV_FF, FF_RUMBLE);
return input_ff_create_memless(xpad->dev, NULL, xpad_play_effect);
@@ -502,6 +595,7 @@ static void xpad_send_led_command(struct usb_xpad *xpad, int command)
xpad->odata[0] = 0x01;
xpad->odata[1] = 0x03;
xpad->odata[2] = command;
+ xpad->irq_out->transfer_buffer_length = 3;
usb_submit_urb(xpad->irq_out, GFP_KERNEL);
mutex_unlock(&xpad->odata_mutex);
}
@@ -574,6 +668,10 @@ static int xpad_open(struct input_dev *dev)
{
struct usb_xpad *xpad = input_get_drvdata(dev);
+ /* URB was submitted in probe */
+ if(xpad->xtype == XTYPE_XBOX360W)
+ return 0;
+
xpad->irq_in->dev = xpad->udev;
if (usb_submit_urb(xpad->irq_in, GFP_KERNEL))
return -EIO;
@@ -585,7 +683,8 @@ static void xpad_close(struct input_dev *dev)
{
struct usb_xpad *xpad = input_get_drvdata(dev);
- usb_kill_urb(xpad->irq_in);
+ if(xpad->xtype != XTYPE_XBOX360W)
+ usb_kill_urb(xpad->irq_in);
xpad_stop_output(xpad);
}
@@ -632,7 +731,7 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
goto fail1;
xpad->idata = usb_buffer_alloc(udev, XPAD_PKT_LEN,
- GFP_ATOMIC, &xpad->idata_dma);
+ GFP_KERNEL, &xpad->idata_dma);
if (!xpad->idata)
goto fail1;
@@ -644,7 +743,16 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
xpad->dpad_mapping = xpad_device[i].dpad_mapping;
xpad->xtype = xpad_device[i].xtype;
if (xpad->dpad_mapping == MAP_DPAD_UNKNOWN)
- xpad->dpad_mapping = dpad_to_buttons;
+ xpad->dpad_mapping = !dpad_to_buttons;
+ if (xpad->xtype == XTYPE_UNKNOWN) {
+ if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) {
+ if (intf->cur_altsetting->desc.bInterfaceProtocol == 129)
+ xpad->xtype = XTYPE_XBOX360W;
+ else
+ xpad->xtype = XTYPE_XBOX360;
+ } else
+ xpad->xtype = XTYPE_XBOX;
+ }
xpad->dev = input_dev;
usb_make_path(udev, xpad->phys, sizeof(xpad->phys));
strlcat(xpad->phys, "/input0", sizeof(xpad->phys));
@@ -662,11 +770,14 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
/* set up buttons */
- for (i = 0; xpad_btn[i] >= 0; i++)
- set_bit(xpad_btn[i], input_dev->keybit);
- if (xpad->xtype == XTYPE_XBOX360)
+ for (i = 0; xpad_common_btn[i] >= 0; i++)
+ set_bit(xpad_common_btn[i], input_dev->keybit);
+ if ((xpad->xtype == XTYPE_XBOX360) || (xpad->xtype == XTYPE_XBOX360W))
for (i = 0; xpad360_btn[i] >= 0; i++)
set_bit(xpad360_btn[i], input_dev->keybit);
+ else
+ for (i = 0; xpad_btn[i] >= 0; i++)
+ set_bit(xpad_btn[i], input_dev->keybit);
if (xpad->dpad_mapping == MAP_DPAD_TO_BUTTONS)
for (i = 0; xpad_btn_pad[i] >= 0; i++)
set_bit(xpad_btn_pad[i], input_dev->keybit);
@@ -703,8 +814,57 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
goto fail4;
usb_set_intfdata(intf, xpad);
+
+ /*
+ * Submit the int URB immediatly rather than waiting for open
+ * because we get status messages from the device whether
+ * or not any controllers are attached. In fact, it's
+ * exactly the message that a controller has arrived that
+ * we're waiting for.
+ */
+ if (xpad->xtype == XTYPE_XBOX360W) {
+ xpad->irq_in->dev = xpad->udev;
+ error = usb_submit_urb(xpad->irq_in, GFP_KERNEL);
+ if (error)
+ goto fail4;
+
+ /*
+ * Setup the message to set the LEDs on the
+ * controller when it shows up
+ */
+ xpad->bulk_out = usb_alloc_urb(0, GFP_KERNEL);
+ if(!xpad->bulk_out)
+ goto fail5;
+
+ xpad->bdata = kzalloc(XPAD_PKT_LEN, GFP_KERNEL);
+ if(!xpad->bdata)
+ goto fail6;
+
+ xpad->bdata[2] = 0x08;
+ switch (intf->cur_altsetting->desc.bInterfaceNumber) {
+ case 0:
+ xpad->bdata[3] = 0x42;
+ break;
+ case 2:
+ xpad->bdata[3] = 0x43;
+ break;
+ case 4:
+ xpad->bdata[3] = 0x44;
+ break;
+ case 6:
+ xpad->bdata[3] = 0x45;
+ }
+
+ ep_irq_in = &intf->cur_altsetting->endpoint[1].desc;
+ usb_fill_bulk_urb(xpad->bulk_out, udev,
+ usb_sndbulkpipe(udev, ep_irq_in->bEndpointAddress),
+ xpad->bdata, XPAD_PKT_LEN, xpad_bulk_out, xpad);
+ }
+
return 0;
+ fail6: usb_free_urb(xpad->bulk_out);
+ fail5: usb_kill_urb(xpad->irq_in);
fail4: usb_free_urb(xpad->irq_in);
fail3: xpad_deinit_output(xpad);
fail2: usb_buffer_free(udev, XPAD_PKT_LEN, xpad->idata, xpad->idata_dma);
@@ -723,6 +883,11 @@ static void xpad_disconnect(struct usb_interface *intf)
xpad_led_disconnect(xpad);
input_unregister_device(xpad->dev);
xpad_deinit_output(xpad);
+ if (xpad->xtype == XTYPE_XBOX360W) {
+ usb_kill_urb(xpad->bulk_out);
+ usb_free_urb(xpad->bulk_out);
+ usb_kill_urb(xpad->irq_in);
+ }
usb_free_urb(xpad->irq_in);
usb_buffer_free(xpad->udev, XPAD_PKT_LEN,
xpad->idata, xpad->idata_dma);
@@ -741,7 +906,7 @@ static int __init usb_xpad_init(void)
{
int result = usb_register(&xpad_driver);
if (result == 0)
- info(DRIVER_DESC ":" DRIVER_VERSION);
+ info(DRIVER_DESC);
return result;
}
diff --git a/drivers/input/joystick/zhenhua.c b/drivers/input/joystick/zhenhua.c
new file mode 100644
index 00000000000..b5853125c89
--- /dev/null
+++ b/drivers/input/joystick/zhenhua.c
@@ -0,0 +1,243 @@
+/*
+ * derived from "twidjoy.c"
+ *
+ * Copyright (c) 2008 Martin Kebert
+ * Copyright (c) 2001 Arndt Schoenewald
+ * Copyright (c) 2000-2001 Vojtech Pavlik
+ * Copyright (c) 2000 Mark Fletcher
+ *
+ */
+
+/*
+ * Driver to use 4CH RC transmitter using Zhen Hua 5-byte protocol (Walkera Lama,
+ * EasyCopter etc.) as a joystick under Linux.
+ *
+ * RC transmitters using Zhen Hua 5-byte protocol are cheap four channels
+ * transmitters for control a RC planes or RC helicopters with possibility to
+ * connect on a serial port.
+ * Data coming from transmitter is in this order:
+ * 1. byte = synchronisation byte
+ * 2. byte = X axis
+ * 3. byte = Y axis
+ * 4. byte = RZ axis
+ * 5. byte = Z axis
+ * (and this is repeated)
+ *
+ * For questions or feedback regarding this driver module please contact:
+ * Martin Kebert <gkmarty@gmail.com> - but I am not a C-programmer nor kernel
+ * coder :-(
+ */
+
+/*
+ * 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/slab.h>
+#include <linux/input.h>
+#include <linux/serio.h>
+#include <linux/init.h>
+
+#define DRIVER_DESC "RC transmitter with 5-byte Zhen Hua protocol joystick driver"
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/*
+ * Constants.
+ */
+
+#define ZHENHUA_MAX_LENGTH 5
+
+/*
+ * Zhen Hua data.
+ */
+
+struct zhenhua {
+ struct input_dev *dev;
+ int idx;
+ unsigned char data[ZHENHUA_MAX_LENGTH];
+ char phys[32];
+};
+
+
+/* bits in all incoming bytes needs to be "reversed" */
+static int zhenhua_bitreverse(int x)
+{
+ x = ((x & 0xaa) >> 1) | ((x & 0x55) << 1);
+ x = ((x & 0xcc) >> 2) | ((x & 0x33) << 2);
+ x = ((x & 0xf0) >> 4) | ((x & 0x0f) << 4);
+ return x;
+}
+
+/*
+ * zhenhua_process_packet() decodes packets the driver receives from the
+ * RC transmitter. It updates the data accordingly.
+ */
+
+static void zhenhua_process_packet(struct zhenhua *zhenhua)
+{
+ struct input_dev *dev = zhenhua->dev;
+ unsigned char *data = zhenhua->data;
+
+ input_report_abs(dev, ABS_Y, data[1]);
+ input_report_abs(dev, ABS_X, data[2]);
+ input_report_abs(dev, ABS_RZ, data[3]);
+ input_report_abs(dev, ABS_Z, data[4]);
+
+ input_sync(dev);
+}
+
+/*
+ * zhenhua_interrupt() is called by the low level driver when characters
+ * are ready for us. We then buffer them for further processing, or call the
+ * packet processing routine.
+ */
+
+static irqreturn_t zhenhua_interrupt(struct serio *serio, unsigned char data, unsigned int flags)
+{
+ struct zhenhua *zhenhua = serio_get_drvdata(serio);
+
+ /* All Zhen Hua packets are 5 bytes. The fact that the first byte
+ * is allways 0xf7 and all others are in range 0x32 - 0xc8 (50-200)
+ * can be used to check and regain sync. */
+
+ if (data == 0xef)
+ zhenhua->idx = 0; /* this byte starts a new packet */
+ else if (zhenhua->idx == 0)
+ return IRQ_HANDLED; /* wrong MSB -- ignore this byte */
+
+ if (zhenhua->idx < ZHENHUA_MAX_LENGTH)
+ zhenhua->data[zhenhua->idx++] = zhenhua_bitreverse(data);
+
+ if (zhenhua->idx == ZHENHUA_MAX_LENGTH) {
+ zhenhua_process_packet(zhenhua);
+ zhenhua->idx = 0;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * zhenhua_disconnect() is the opposite of zhenhua_connect()
+ */
+
+static void zhenhua_disconnect(struct serio *serio)
+{
+ struct zhenhua *zhenhua = serio_get_drvdata(serio);
+
+ serio_close(serio);
+ serio_set_drvdata(serio, NULL);
+ input_unregister_device(zhenhua->dev);
+ kfree(zhenhua);
+}
+
+/*
+ * zhenhua_connect() is the routine that is called when someone adds a
+ * new serio device. It looks for the Twiddler, and if found, registers
+ * it as an input device.
+ */
+
+static int zhenhua_connect(struct serio *serio, struct serio_driver *drv)
+{
+ struct zhenhua *zhenhua;
+ struct input_dev *input_dev;
+ int err = -ENOMEM;
+
+ zhenhua = kzalloc(sizeof(struct zhenhua), GFP_KERNEL);
+ input_dev = input_allocate_device();
+ if (!zhenhua || !input_dev)
+ goto fail1;
+
+ zhenhua->dev = input_dev;
+ snprintf(zhenhua->phys, sizeof(zhenhua->phys), "%s/input0", serio->phys);
+
+ input_dev->name = "Zhen Hua 5-byte device";
+ input_dev->phys = zhenhua->phys;
+ input_dev->id.bustype = BUS_RS232;
+ input_dev->id.vendor = SERIO_ZHENHUA;
+ input_dev->id.product = 0x0001;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &serio->dev;
+
+ input_dev->evbit[0] = BIT(EV_ABS);
+ input_set_abs_params(input_dev, ABS_X, 50, 200, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 50, 200, 0, 0);
+ input_set_abs_params(input_dev, ABS_Z, 50, 200, 0, 0);
+ input_set_abs_params(input_dev, ABS_RZ, 50, 200, 0, 0);
+
+ serio_set_drvdata(serio, zhenhua);
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto fail2;
+
+ err = input_register_device(zhenhua->dev);
+ if (err)
+ goto fail3;
+
+ return 0;
+
+ fail3: serio_close(serio);
+ fail2: serio_set_drvdata(serio, NULL);
+ fail1: input_free_device(input_dev);
+ kfree(zhenhua);
+ return err;
+}
+
+/*
+ * The serio driver structure.
+ */
+
+static struct serio_device_id zhenhua_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_ZHENHUA,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, zhenhua_serio_ids);
+
+static struct serio_driver zhenhua_drv = {
+ .driver = {
+ .name = "zhenhua",
+ },
+ .description = DRIVER_DESC,
+ .id_table = zhenhua_serio_ids,
+ .interrupt = zhenhua_interrupt,
+ .connect = zhenhua_connect,
+ .disconnect = zhenhua_disconnect,
+};
+
+/*
+ * The functions for inserting/removing us as a module.
+ */
+
+static int __init zhenhua_init(void)
+{
+ return serio_register_driver(&zhenhua_drv);
+}
+
+static void __exit zhenhua_exit(void)
+{
+ serio_unregister_driver(&zhenhua_drv);
+}
+
+module_init(zhenhua_init);
+module_exit(zhenhua_exit);
diff --git a/drivers/input/keyboard/aaed2000_kbd.c b/drivers/input/keyboard/aaed2000_kbd.c
index 72abc196ce6..a293e8b3f50 100644
--- a/drivers/input/keyboard/aaed2000_kbd.c
+++ b/drivers/input/keyboard/aaed2000_kbd.c
@@ -156,11 +156,15 @@ static int __devexit aaedkbd_remove(struct platform_device *pdev)
return 0;
}
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:aaed2000-keyboard");
+
static struct platform_driver aaedkbd_driver = {
.probe = aaedkbd_probe,
.remove = __devexit_p(aaedkbd_remove),
.driver = {
.name = "aaed2000-keyboard",
+ .owner = THIS_MODULE,
},
};
diff --git a/drivers/input/keyboard/bf54x-keys.c b/drivers/input/keyboard/bf54x-keys.c
index 05e3494cf8b..54ed8e2e1c0 100644
--- a/drivers/input/keyboard/bf54x-keys.c
+++ b/drivers/input/keyboard/bf54x-keys.c
@@ -312,6 +312,8 @@ static int __devinit bfin_kpad_probe(struct platform_device *pdev)
bfin_write_KPAD_CTL(bfin_read_KPAD_CTL() | KPAD_EN);
+ device_init_wakeup(&pdev->dev, 1);
+
printk(KERN_ERR DRV_NAME
": Blackfin BF54x Keypad registered IRQ %d\n", bf54x_kpad->irq);
@@ -354,12 +356,40 @@ static int __devexit bfin_kpad_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM
+static int bfin_kpad_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev);
+
+ if (device_may_wakeup(&pdev->dev))
+ enable_irq_wake(bf54x_kpad->irq);
+
+ return 0;
+}
+
+static int bfin_kpad_resume(struct platform_device *pdev)
+{
+ struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev);
+
+ if (device_may_wakeup(&pdev->dev))
+ disable_irq_wake(bf54x_kpad->irq);
+
+ return 0;
+}
+#else
+# define bfin_kpad_suspend NULL
+# define bfin_kpad_resume NULL
+#endif
+
struct platform_driver bfin_kpad_device_driver = {
- .probe = bfin_kpad_probe,
- .remove = __devexit_p(bfin_kpad_remove),
.driver = {
.name = DRV_NAME,
- }
+ .owner = THIS_MODULE,
+ },
+ .probe = bfin_kpad_probe,
+ .remove = __devexit_p(bfin_kpad_remove),
+ .suspend = bfin_kpad_suspend,
+ .resume = bfin_kpad_resume,
};
static int __init bfin_kpad_init(void)
@@ -378,3 +408,4 @@ module_exit(bfin_kpad_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
MODULE_DESCRIPTION("Keypad driver for BF54x Processors");
+MODULE_ALIAS("platform:bf54x-keys");
diff --git a/drivers/input/keyboard/corgikbd.c b/drivers/input/keyboard/corgikbd.c
index 5d6cc7f1dc9..29fbec6218b 100644
--- a/drivers/input/keyboard/corgikbd.c
+++ b/drivers/input/keyboard/corgikbd.c
@@ -393,6 +393,7 @@ static struct platform_driver corgikbd_driver = {
.resume = corgikbd_resume,
.driver = {
.name = "corgi-keyboard",
+ .owner = THIS_MODULE,
},
};
@@ -412,3 +413,4 @@ module_exit(corgikbd_exit);
MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>");
MODULE_DESCRIPTION("Corgi Keyboard Driver");
MODULE_LICENSE("GPLv2");
+MODULE_ALIAS("platform:corgi-keyboard");
diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
index 6a9ca4bdcb7..bbd00c3fe98 100644
--- a/drivers/input/keyboard/gpio_keys.c
+++ b/drivers/input/keyboard/gpio_keys.c
@@ -43,10 +43,11 @@ static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
input_event(input, type, button->code, !!state);
input_sync(input);
+ return IRQ_HANDLED;
}
}
- return IRQ_HANDLED;
+ return IRQ_NONE;
}
static int __devinit gpio_keys_probe(struct platform_device *pdev)
@@ -213,6 +214,7 @@ struct platform_driver gpio_keys_device_driver = {
.resume = gpio_keys_resume,
.driver = {
.name = "gpio-keys",
+ .owner = THIS_MODULE,
}
};
@@ -232,3 +234,4 @@ module_exit(gpio_keys_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Phil Blundell <pb@handhelds.org>");
MODULE_DESCRIPTION("Keyboard driver for CPU GPIOs");
+MODULE_ALIAS("platform:gpio-keys");
diff --git a/drivers/input/keyboard/jornada680_kbd.c b/drivers/input/keyboard/jornada680_kbd.c
index a23633a2e1b..9387da343f9 100644
--- a/drivers/input/keyboard/jornada680_kbd.c
+++ b/drivers/input/keyboard/jornada680_kbd.c
@@ -254,6 +254,7 @@ static int __devexit jornada680kbd_remove(struct platform_device *pdev)
static struct platform_driver jornada680kbd_driver = {
.driver = {
.name = "jornada680_kbd",
+ .owner = THIS_MODULE,
},
.probe = jornada680kbd_probe,
.remove = __devexit_p(jornada680kbd_remove),
@@ -275,3 +276,4 @@ module_exit(jornada680kbd_exit);
MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>");
MODULE_DESCRIPTION("HP Jornada 620/660/680/690 Keyboard Driver");
MODULE_LICENSE("GPLv2");
+MODULE_ALIAS("platform:jornada680_kbd");
diff --git a/drivers/input/keyboard/jornada720_kbd.c b/drivers/input/keyboard/jornada720_kbd.c
index 986f93cfc6b..a1164a0c773 100644
--- a/drivers/input/keyboard/jornada720_kbd.c
+++ b/drivers/input/keyboard/jornada720_kbd.c
@@ -162,9 +162,13 @@ static int __devexit jornada720_kbd_remove(struct platform_device *pdev)
return 0;
}
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:jornada720_kbd");
+
static struct platform_driver jornada720_kbd_driver = {
.driver = {
.name = "jornada720_kbd",
+ .owner = THIS_MODULE,
},
.probe = jornada720_kbd_probe,
.remove = __devexit_p(jornada720_kbd_remove),
diff --git a/drivers/input/keyboard/locomokbd.c b/drivers/input/keyboard/locomokbd.c
index 5a0ca18d675..9caed30f3bb 100644
--- a/drivers/input/keyboard/locomokbd.c
+++ b/drivers/input/keyboard/locomokbd.c
@@ -1,14 +1,12 @@
/*
- * Copyright (c) 2005 John Lenz
+ * LoCoMo keyboard driver for Linux-based ARM PDAs:
+ * - SHARP Zaurus Collie (SL-5500)
+ * - SHARP Zaurus Poodle (SL-5600)
*
+ * Copyright (c) 2005 John Lenz
* Based on from xtkbd.c
- */
-
-/*
- * LoCoMo keyboard driver for Linux/ARM
- */
-
-/*
+ *
+ *
* 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
@@ -47,7 +45,8 @@ MODULE_LICENSE("GPL");
#define KEY_CONTACT KEY_F18
#define KEY_CENTER KEY_F15
-static unsigned char locomokbd_keycode[LOCOMOKBD_NUMKEYS] = {
+static const unsigned char
+locomokbd_keycode[LOCOMOKBD_NUMKEYS] __devinitconst = {
0, KEY_ESC, KEY_ACTIVITY, 0, 0, 0, 0, 0, 0, 0, /* 0 - 9 */
0, 0, 0, 0, 0, 0, 0, KEY_MENU, KEY_HOME, KEY_CONTACT, /* 10 - 19 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 29 */
@@ -67,22 +66,21 @@ static unsigned char locomokbd_keycode[LOCOMOKBD_NUMKEYS] = {
#define KB_COLS 8
#define KB_ROWMASK(r) (1 << (r))
#define SCANCODE(c,r) ( ((c)<<4) + (r) + 1 )
-#define NR_SCANCODES 128
#define KB_DELAY 8
#define SCAN_INTERVAL (HZ/10)
-#define LOCOMOKBD_PRESSED 1
struct locomokbd {
unsigned char keycode[LOCOMOKBD_NUMKEYS];
struct input_dev *input;
char phys[32];
- struct locomo_dev *ldev;
unsigned long base;
spinlock_t lock;
struct timer_list timer;
+ unsigned long suspend_jiffies;
+ unsigned int count_cancel;
};
/* helper functions for reading the keyboard matrix */
@@ -128,7 +126,7 @@ static inline void locomokbd_reset_col(unsigned long membase, int col)
/* Scan the hardware keyboard and push any changes up through the input layer */
static void locomokbd_scankeyboard(struct locomokbd *locomokbd)
{
- unsigned int row, col, rowd, scancode;
+ unsigned int row, col, rowd;
unsigned long flags;
unsigned int num_pressed;
unsigned long membase = locomokbd->base;
@@ -145,13 +143,33 @@ static void locomokbd_scankeyboard(struct locomokbd *locomokbd)
rowd = ~locomo_readl(membase + LOCOMO_KIB);
for (row = 0; row < KB_ROWS; row++) {
+ unsigned int scancode, pressed, key;
+
scancode = SCANCODE(col, row);
- if (rowd & KB_ROWMASK(row)) {
- num_pressed += 1;
- input_report_key(locomokbd->input, locomokbd->keycode[scancode], 1);
- } else {
- input_report_key(locomokbd->input, locomokbd->keycode[scancode], 0);
- }
+ pressed = rowd & KB_ROWMASK(row);
+ key = locomokbd->keycode[scancode];
+
+ input_report_key(locomokbd->input, key, pressed);
+ if (likely(!pressed))
+ continue;
+
+ num_pressed++;
+
+ /* The "Cancel/ESC" key is labeled "On/Off" on
+ * Collie and Poodle and should suspend the device
+ * if it was pressed for more than a second. */
+ if (unlikely(key == KEY_ESC)) {
+ if (!time_after(jiffies,
+ locomokbd->suspend_jiffies + HZ))
+ continue;
+ if (locomokbd->count_cancel++
+ != (HZ/SCAN_INTERVAL + 1))
+ continue;
+ input_event(locomokbd->input, EV_PWR,
+ KEY_SUSPEND, 1);
+ locomokbd->suspend_jiffies = jiffies;
+ } else
+ locomokbd->count_cancel = 0;
}
locomokbd_reset_col(membase, col);
}
@@ -162,6 +180,8 @@ static void locomokbd_scankeyboard(struct locomokbd *locomokbd)
/* if any keys are pressed, enable the timer */
if (num_pressed)
mod_timer(&locomokbd->timer, jiffies + SCAN_INTERVAL);
+ else
+ locomokbd->count_cancel = 0;
spin_unlock_irqrestore(&locomokbd->lock, flags);
}
@@ -186,10 +206,11 @@ static irqreturn_t locomokbd_interrupt(int irq, void *dev_id)
static void locomokbd_timer_callback(unsigned long data)
{
struct locomokbd *locomokbd = (struct locomokbd *) data;
+
locomokbd_scankeyboard(locomokbd);
}
-static int locomokbd_probe(struct locomo_dev *dev)
+static int __devinit locomokbd_probe(struct locomo_dev *dev)
{
struct locomokbd *locomokbd;
struct input_dev *input_dev;
@@ -211,7 +232,6 @@ static int locomokbd_probe(struct locomo_dev *dev)
goto err_free_mem;
}
- locomokbd->ldev = dev;
locomo_set_drvdata(dev, locomokbd);
locomokbd->base = (unsigned long) dev->mapbase;
@@ -222,6 +242,8 @@ static int locomokbd_probe(struct locomo_dev *dev)
locomokbd->timer.function = locomokbd_timer_callback;
locomokbd->timer.data = (unsigned long) locomokbd;
+ locomokbd->suspend_jiffies = jiffies;
+
locomokbd->input = input_dev;
strcpy(locomokbd->phys, "locomokbd/input0");
@@ -233,9 +255,10 @@ static int locomokbd_probe(struct locomo_dev *dev)
input_dev->id.version = 0x0100;
input_dev->dev.parent = &dev->dev;
- input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) |
+ BIT_MASK(EV_PWR);
input_dev->keycode = locomokbd->keycode;
- input_dev->keycodesize = sizeof(unsigned char);
+ input_dev->keycodesize = sizeof(locomokbd_keycode[0]);
input_dev->keycodemax = ARRAY_SIZE(locomokbd_keycode);
memcpy(locomokbd->keycode, locomokbd_keycode, sizeof(locomokbd->keycode));
@@ -268,7 +291,7 @@ static int locomokbd_probe(struct locomo_dev *dev)
return err;
}
-static int locomokbd_remove(struct locomo_dev *dev)
+static int __devexit locomokbd_remove(struct locomo_dev *dev)
{
struct locomokbd *locomokbd = locomo_get_drvdata(dev);
@@ -292,7 +315,7 @@ static struct locomo_driver keyboard_driver = {
},
.devid = LOCOMO_DEVID_KEYBOARD,
.probe = locomokbd_probe,
- .remove = locomokbd_remove,
+ .remove = __devexit_p(locomokbd_remove),
};
static int __init locomokbd_init(void)
diff --git a/drivers/input/keyboard/omap-keypad.c b/drivers/input/keyboard/omap-keypad.c
index babc913d549..10afd206806 100644
--- a/drivers/input/keyboard/omap-keypad.c
+++ b/drivers/input/keyboard/omap-keypad.c
@@ -352,6 +352,9 @@ static int __init omap_kp_probe(struct platform_device *pdev)
}
omap_set_gpio_direction(row_gpios[row_idx], 1);
}
+ } else {
+ col_idx = 0;
+ row_idx = 0;
}
setup_timer(&omap_kp->timer, omap_kp_timer, (unsigned long)omap_kp);
@@ -415,10 +418,10 @@ err4:
err3:
device_remove_file(&pdev->dev, &dev_attr_enable);
err2:
- for (i = row_idx-1; i >=0; i--)
+ for (i = row_idx - 1; i >=0; i--)
omap_free_gpio(row_gpios[i]);
err1:
- for (i = col_idx-1; i >=0; i--)
+ for (i = col_idx - 1; i >=0; i--)
omap_free_gpio(col_gpios[i]);
kfree(omap_kp);
@@ -464,6 +467,7 @@ static struct platform_driver omap_kp_driver = {
.resume = omap_kp_resume,
.driver = {
.name = "omap-keypad",
+ .owner = THIS_MODULE,
},
};
@@ -484,3 +488,4 @@ module_exit(omap_kp_exit);
MODULE_AUTHOR("Timo Teräs");
MODULE_DESCRIPTION("OMAP Keypad Driver");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:omap-keypad");
diff --git a/drivers/input/keyboard/pxa27x_keypad.c b/drivers/input/keyboard/pxa27x_keypad.c
index 4e651c11c1d..3dea0c5077a 100644
--- a/drivers/input/keyboard/pxa27x_keypad.c
+++ b/drivers/input/keyboard/pxa27x_keypad.c
@@ -545,6 +545,9 @@ static int __devexit pxa27x_keypad_remove(struct platform_device *pdev)
return 0;
}
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:pxa27x-keypad");
+
static struct platform_driver pxa27x_keypad_driver = {
.probe = pxa27x_keypad_probe,
.remove = __devexit_p(pxa27x_keypad_remove),
@@ -552,6 +555,7 @@ static struct platform_driver pxa27x_keypad_driver = {
.resume = pxa27x_keypad_resume,
.driver = {
.name = "pxa27x-keypad",
+ .owner = THIS_MODULE,
},
};
diff --git a/drivers/input/keyboard/spitzkbd.c b/drivers/input/keyboard/spitzkbd.c
index 0be74bfc58f..61e401bc910 100644
--- a/drivers/input/keyboard/spitzkbd.c
+++ b/drivers/input/keyboard/spitzkbd.c
@@ -495,3 +495,4 @@ module_exit(spitzkbd_exit);
MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>");
MODULE_DESCRIPTION("Spitz Keyboard Driver");
MODULE_LICENSE("GPLv2");
+MODULE_ALIAS("platform:spitz-keyboard");
diff --git a/drivers/input/keyboard/tosakbd.c b/drivers/input/keyboard/tosakbd.c
index 3884d1e3f07..94e444b4ee1 100644
--- a/drivers/input/keyboard/tosakbd.c
+++ b/drivers/input/keyboard/tosakbd.c
@@ -52,7 +52,7 @@ KEY_X, KEY_F, KEY_SPACE, KEY_APOSTROPHE, TOSA_KEY_MAIL, KEY_LEFT, KEY_DOWN, KEY_
struct tosakbd {
unsigned int keycode[ARRAY_SIZE(tosakbd_keycode)];
struct input_dev *input;
-
+ int suspended;
spinlock_t lock; /* protect kbd scanning */
struct timer_list timer;
};
@@ -133,6 +133,9 @@ static void tosakbd_scankeyboard(struct platform_device *dev)
spin_lock_irqsave(&tosakbd->lock, flags);
+ if (tosakbd->suspended)
+ goto out;
+
for (col = 0; col < TOSA_KEY_STROBE_NUM; col++) {
/*
* Discharge the output driver capacitatance
@@ -174,6 +177,7 @@ static void tosakbd_scankeyboard(struct platform_device *dev)
if (num_pressed)
mod_timer(&tosakbd->timer, jiffies + SCAN_INTERVAL);
+ out:
spin_unlock_irqrestore(&tosakbd->lock, flags);
}
@@ -200,6 +204,7 @@ static irqreturn_t tosakbd_interrupt(int irq, void *__dev)
static void tosakbd_timer_callback(unsigned long __dev)
{
struct platform_device *dev = (struct platform_device *)__dev;
+
tosakbd_scankeyboard(dev);
}
@@ -207,6 +212,13 @@ static void tosakbd_timer_callback(unsigned long __dev)
static int tosakbd_suspend(struct platform_device *dev, pm_message_t state)
{
struct tosakbd *tosakbd = platform_get_drvdata(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&tosakbd->lock, flags);
+ PGSR1 = (PGSR1 & ~TOSA_GPIO_LOW_STROBE_BIT);
+ PGSR2 = (PGSR2 & ~TOSA_GPIO_HIGH_STROBE_BIT);
+ tosakbd->suspended = 1;
+ spin_unlock_irqrestore(&tosakbd->lock, flags);
del_timer_sync(&tosakbd->timer);
@@ -215,6 +227,9 @@ static int tosakbd_suspend(struct platform_device *dev, pm_message_t state)
static int tosakbd_resume(struct platform_device *dev)
{
+ struct tosakbd *tosakbd = platform_get_drvdata(dev);
+
+ tosakbd->suspended = 0;
tosakbd_scankeyboard(dev);
return 0;
@@ -365,8 +380,8 @@ fail:
return error;
}
-static int __devexit tosakbd_remove(struct platform_device *dev) {
-
+static int __devexit tosakbd_remove(struct platform_device *dev)
+{
int i;
struct tosakbd *tosakbd = platform_get_drvdata(dev);
@@ -394,6 +409,7 @@ static struct platform_driver tosakbd_driver = {
.resume = tosakbd_resume,
.driver = {
.name = "tosa-keyboard",
+ .owner = THIS_MODULE,
},
};
@@ -413,3 +429,4 @@ module_exit(tosakbd_exit);
MODULE_AUTHOR("Dirk Opfer <Dirk@Opfer-Online.de>");
MODULE_DESCRIPTION("Tosa Keyboard Driver");
MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:tosa-keyboard");
diff --git a/drivers/input/misc/cobalt_btns.c b/drivers/input/misc/cobalt_btns.c
index 5511ef006a6..6a1f48b76e3 100644
--- a/drivers/input/misc/cobalt_btns.c
+++ b/drivers/input/misc/cobalt_btns.c
@@ -148,6 +148,9 @@ static int __devexit cobalt_buttons_remove(struct platform_device *pdev)
return 0;
}
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:Cobalt buttons");
+
static struct platform_driver cobalt_buttons_driver = {
.probe = cobalt_buttons_probe,
.remove = __devexit_p(cobalt_buttons_remove),
diff --git a/drivers/input/mouse/gpio_mouse.c b/drivers/input/mouse/gpio_mouse.c
index 0936d6ba015..33929018487 100644
--- a/drivers/input/mouse/gpio_mouse.c
+++ b/drivers/input/mouse/gpio_mouse.c
@@ -171,10 +171,14 @@ static int __devexit gpio_mouse_remove(struct platform_device *pdev)
return 0;
}
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:gpio_mouse");
+
struct platform_driver gpio_mouse_device_driver = {
.remove = __devexit_p(gpio_mouse_remove),
.driver = {
.name = "gpio_mouse",
+ .owner = THIS_MODULE,
}
};
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index b88569e21d6..ec4b6610f73 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -88,6 +88,16 @@ config SERIO_RPCKBD
To compile this driver as a module, choose M here: the
module will be called rpckbd.
+config SERIO_AT32PSIF
+ tristate "AVR32 PSIF PS/2 keyboard and mouse controller"
+ depends on AVR32
+ help
+ Say Y here if you want to use the PSIF peripheral on AVR32 devices
+ and connect a PS/2 keyboard and/or mouse to it.
+
+ To compile this driver as a module, choose M here: the module will
+ be called at32psif.
+
config SERIO_AMBAKMI
tristate "AMBA KMI keyboard controller"
depends on ARM_AMBA
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 4155197867a..38b886887cb 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_SERIO_CT82C710) += ct82c710.o
obj-$(CONFIG_SERIO_RPCKBD) += rpckbd.o
obj-$(CONFIG_SERIO_SA1111) += sa1111ps2.o
obj-$(CONFIG_SERIO_AMBAKMI) += ambakmi.o
+obj-$(CONFIG_SERIO_AT32PSIF) += at32psif.o
obj-$(CONFIG_SERIO_Q40KBD) += q40kbd.o
obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o
obj-$(CONFIG_HP_SDC) += hp_sdc.o
diff --git a/drivers/input/serio/at32psif.c b/drivers/input/serio/at32psif.c
new file mode 100644
index 00000000000..41fda8c67b1
--- /dev/null
+++ b/drivers/input/serio/at32psif.c
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2007 Atmel Corporation
+ *
+ * Driver for the AT32AP700X PS/2 controller (PSIF).
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/serio.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+
+/* PSIF register offsets */
+#define PSIF_CR 0x00
+#define PSIF_RHR 0x04
+#define PSIF_THR 0x08
+#define PSIF_SR 0x10
+#define PSIF_IER 0x14
+#define PSIF_IDR 0x18
+#define PSIF_IMR 0x1c
+#define PSIF_PSR 0x24
+
+/* Bitfields in control register. */
+#define PSIF_CR_RXDIS_OFFSET 1
+#define PSIF_CR_RXDIS_SIZE 1
+#define PSIF_CR_RXEN_OFFSET 0
+#define PSIF_CR_RXEN_SIZE 1
+#define PSIF_CR_SWRST_OFFSET 15
+#define PSIF_CR_SWRST_SIZE 1
+#define PSIF_CR_TXDIS_OFFSET 9
+#define PSIF_CR_TXDIS_SIZE 1
+#define PSIF_CR_TXEN_OFFSET 8
+#define PSIF_CR_TXEN_SIZE 1
+
+/* Bitfields in interrupt disable, enable, mask and status register. */
+#define PSIF_NACK_OFFSET 8
+#define PSIF_NACK_SIZE 1
+#define PSIF_OVRUN_OFFSET 5
+#define PSIF_OVRUN_SIZE 1
+#define PSIF_PARITY_OFFSET 9
+#define PSIF_PARITY_SIZE 1
+#define PSIF_RXRDY_OFFSET 4
+#define PSIF_RXRDY_SIZE 1
+#define PSIF_TXEMPTY_OFFSET 1
+#define PSIF_TXEMPTY_SIZE 1
+#define PSIF_TXRDY_OFFSET 0
+#define PSIF_TXRDY_SIZE 1
+
+/* Bitfields in prescale register. */
+#define PSIF_PSR_PRSCV_OFFSET 0
+#define PSIF_PSR_PRSCV_SIZE 12
+
+/* Bitfields in receive hold register. */
+#define PSIF_RHR_RXDATA_OFFSET 0
+#define PSIF_RHR_RXDATA_SIZE 8
+
+/* Bitfields in transmit hold register. */
+#define PSIF_THR_TXDATA_OFFSET 0
+#define PSIF_THR_TXDATA_SIZE 8
+
+/* Bit manipulation macros */
+#define PSIF_BIT(name) \
+ (1 << PSIF_##name##_OFFSET)
+
+#define PSIF_BF(name, value) \
+ (((value) & ((1 << PSIF_##name##_SIZE) - 1)) \
+ << PSIF_##name##_OFFSET)
+
+#define PSIF_BFEXT(name, value) \
+ (((value) >> PSIF_##name##_OFFSET) \
+ & ((1 << PSIF_##name##_SIZE) - 1))
+
+#define PSIF_BFINS(name, value, old) \
+ (((old) & ~(((1 << PSIF_##name##_SIZE) - 1) \
+ << PSIF_##name##_OFFSET)) \
+ | PSIF_BF(name, value))
+
+/* Register access macros */
+#define psif_readl(port, reg) \
+ __raw_readl((port)->regs + PSIF_##reg)
+
+#define psif_writel(port, reg, value) \
+ __raw_writel((value), (port)->regs + PSIF_##reg)
+
+struct psif {
+ struct platform_device *pdev;
+ struct clk *pclk;
+ struct serio *io;
+ void __iomem *regs;
+ unsigned int irq;
+ unsigned int open;
+ /* Prevent concurrent writes to PSIF THR. */
+ spinlock_t lock;
+};
+
+static irqreturn_t psif_interrupt(int irq, void *_ptr)
+{
+ struct psif *psif = _ptr;
+ int retval = IRQ_NONE;
+ unsigned int io_flags = 0;
+ unsigned long status;
+
+ status = psif_readl(psif, SR);
+
+ if (status & PSIF_BIT(RXRDY)) {
+ unsigned char val = (unsigned char) psif_readl(psif, RHR);
+
+ if (status & PSIF_BIT(PARITY))
+ io_flags |= SERIO_PARITY;
+ if (status & PSIF_BIT(OVRUN))
+ dev_err(&psif->pdev->dev, "overrun read error\n");
+
+ serio_interrupt(psif->io, val, io_flags);
+
+ retval = IRQ_HANDLED;
+ }
+
+ return retval;
+}
+
+static int psif_write(struct serio *io, unsigned char val)
+{
+ struct psif *psif = io->port_data;
+ unsigned long flags;
+ int timeout = 10;
+ int retval = 0;
+
+ spin_lock_irqsave(&psif->lock, flags);
+
+ while (!(psif_readl(psif, SR) & PSIF_BIT(TXEMPTY)) && timeout--)
+ msleep(10);
+
+ if (timeout >= 0) {
+ psif_writel(psif, THR, val);
+ } else {
+ dev_dbg(&psif->pdev->dev, "timeout writing to THR\n");
+ retval = -EBUSY;
+ }
+
+ spin_unlock_irqrestore(&psif->lock, flags);
+
+ return retval;
+}
+
+static int psif_open(struct serio *io)
+{
+ struct psif *psif = io->port_data;
+ int retval;
+
+ retval = clk_enable(psif->pclk);
+ if (retval)
+ goto out;
+
+ psif_writel(psif, CR, PSIF_BIT(CR_TXEN) | PSIF_BIT(CR_RXEN));
+ psif_writel(psif, IER, PSIF_BIT(RXRDY));
+
+ psif->open = 1;
+out:
+ return retval;
+}
+
+static void psif_close(struct serio *io)
+{
+ struct psif *psif = io->port_data;
+
+ psif->open = 0;
+
+ psif_writel(psif, IDR, ~0UL);
+ psif_writel(psif, CR, PSIF_BIT(CR_TXDIS) | PSIF_BIT(CR_RXDIS));
+
+ clk_disable(psif->pclk);
+}
+
+static void psif_set_prescaler(struct psif *psif)
+{
+ unsigned long prscv;
+ unsigned long rate = clk_get_rate(psif->pclk);
+
+ /* PRSCV = Pulse length (100 us) * PSIF module frequency. */
+ prscv = 100 * (rate / 1000000UL);
+
+ if (prscv > ((1<<PSIF_PSR_PRSCV_SIZE) - 1)) {
+ prscv = (1<<PSIF_PSR_PRSCV_SIZE) - 1;
+ dev_dbg(&psif->pdev->dev, "pclk too fast, "
+ "prescaler set to max\n");
+ }
+
+ clk_enable(psif->pclk);
+ psif_writel(psif, PSR, prscv);
+ clk_disable(psif->pclk);
+}
+
+static int __init psif_probe(struct platform_device *pdev)
+{
+ struct resource *regs;
+ struct psif *psif;
+ struct serio *io;
+ struct clk *pclk;
+ int irq;
+ int ret;
+
+ psif = kzalloc(sizeof(struct psif), GFP_KERNEL);
+ if (!psif) {
+ dev_dbg(&pdev->dev, "out of memory\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+ psif->pdev = pdev;
+
+ io = kzalloc(sizeof(struct serio), GFP_KERNEL);
+ if (!io) {
+ dev_dbg(&pdev->dev, "out of memory\n");
+ ret = -ENOMEM;
+ goto out_free_psif;
+ }
+ psif->io = io;
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!regs) {
+ dev_dbg(&pdev->dev, "no mmio resources defined\n");
+ ret = -ENOMEM;
+ goto out_free_io;
+ }
+
+ psif->regs = ioremap(regs->start, regs->end - regs->start + 1);
+ if (!psif->regs) {
+ ret = -ENOMEM;
+ dev_dbg(&pdev->dev, "could not map I/O memory\n");
+ goto out_free_io;
+ }
+
+ pclk = clk_get(&pdev->dev, "pclk");
+ if (IS_ERR(pclk)) {
+ dev_dbg(&pdev->dev, "could not get peripheral clock\n");
+ ret = PTR_ERR(pclk);
+ goto out_iounmap;
+ }
+ psif->pclk = pclk;
+
+ /* Reset the PSIF to enter at a known state. */
+ ret = clk_enable(pclk);
+ if (ret) {
+ dev_dbg(&pdev->dev, "could not enable pclk\n");
+ goto out_put_clk;
+ }
+ psif_writel(psif, CR, PSIF_BIT(CR_SWRST));
+ clk_disable(pclk);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_dbg(&pdev->dev, "could not get irq\n");
+ ret = -ENXIO;
+ goto out_put_clk;
+ }
+ ret = request_irq(irq, psif_interrupt, IRQF_SHARED, "at32psif", psif);
+ if (ret) {
+ dev_dbg(&pdev->dev, "could not request irq %d\n", irq);
+ goto out_put_clk;
+ }
+ psif->irq = irq;
+
+ io->id.type = SERIO_8042;
+ io->write = psif_write;
+ io->open = psif_open;
+ io->close = psif_close;
+ snprintf(io->name, sizeof(io->name), "AVR32 PS/2 port%d", pdev->id);
+ snprintf(io->phys, sizeof(io->phys), "at32psif/serio%d", pdev->id);
+ io->port_data = psif;
+ io->dev.parent = &pdev->dev;
+
+ psif_set_prescaler(psif);
+
+ spin_lock_init(&psif->lock);
+ serio_register_port(psif->io);
+ platform_set_drvdata(pdev, psif);
+
+ dev_info(&pdev->dev, "Atmel AVR32 PSIF PS/2 driver on 0x%08x irq %d\n",
+ (int)psif->regs, psif->irq);
+
+ return 0;
+
+out_put_clk:
+ clk_put(psif->pclk);
+out_iounmap:
+ iounmap(psif->regs);
+out_free_io:
+ kfree(io);
+out_free_psif:
+ kfree(psif);
+out:
+ return ret;
+}
+
+static int __exit psif_remove(struct platform_device *pdev)
+{
+ struct psif *psif = platform_get_drvdata(pdev);
+
+ psif_writel(psif, IDR, ~0UL);
+ psif_writel(psif, CR, PSIF_BIT(CR_TXDIS) | PSIF_BIT(CR_RXDIS));
+
+ serio_unregister_port(psif->io);
+ iounmap(psif->regs);
+ free_irq(psif->irq, psif);
+ clk_put(psif->pclk);
+ kfree(psif);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int psif_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct psif *psif = platform_get_drvdata(pdev);
+
+ if (psif->open) {
+ psif_writel(psif, CR, PSIF_BIT(CR_RXDIS) | PSIF_BIT(CR_TXDIS));
+ clk_disable(psif->pclk);
+ }
+
+ return 0;
+}
+
+static int psif_resume(struct platform_device *pdev)
+{
+ struct psif *psif = platform_get_drvdata(pdev);
+
+ if (psif->open) {
+ clk_enable(psif->pclk);
+ psif_set_prescaler(psif);
+ psif_writel(psif, CR, PSIF_BIT(CR_RXEN) | PSIF_BIT(CR_TXEN));
+ }
+
+ return 0;
+}
+#else
+#define psif_suspend NULL
+#define psif_resume NULL
+#endif
+
+static struct platform_driver psif_driver = {
+ .remove = __exit_p(psif_remove),
+ .driver = {
+ .name = "atmel_psif",
+ },
+ .suspend = psif_suspend,
+ .resume = psif_resume,
+};
+
+static int __init psif_init(void)
+{
+ return platform_driver_probe(&psif_driver, psif_probe);
+}
+
+static void __exit psif_exit(void)
+{
+ platform_driver_unregister(&psif_driver);
+}
+
+module_init(psif_init);
+module_exit(psif_exit);
+
+MODULE_AUTHOR("Hans-Christian Egtvedt <hans-christian.egtvedt@atmel.com>");
+MODULE_DESCRIPTION("Atmel AVR32 PSIF PS/2 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h
index 60931aceb82..5ece9f56bab 100644
--- a/drivers/input/serio/i8042-x86ia64io.h
+++ b/drivers/input/serio/i8042-x86ia64io.h
@@ -370,10 +370,10 @@ static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *
if (pnp_irq_valid(dev,0))
i8042_pnp_kbd_irq = pnp_irq(dev, 0);
- strncpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name));
+ strlcpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name));
if (strlen(pnp_dev_name(dev))) {
- strncat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name));
- strncat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name));
+ strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name));
+ strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name));
}
i8042_pnp_kbd_devices++;
@@ -391,10 +391,10 @@ static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *
if (pnp_irq_valid(dev, 0))
i8042_pnp_aux_irq = pnp_irq(dev, 0);
- strncpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name));
+ strlcpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name));
if (strlen(pnp_dev_name(dev))) {
- strncat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name));
- strncat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name));
+ strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name));
+ strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name));
}
i8042_pnp_aux_devices++;
diff --git a/drivers/input/serio/rpckbd.c b/drivers/input/serio/rpckbd.c
index 49f84315cb3..34c59d9c620 100644
--- a/drivers/input/serio/rpckbd.c
+++ b/drivers/input/serio/rpckbd.c
@@ -45,6 +45,7 @@
MODULE_AUTHOR("Vojtech Pavlik, Russell King");
MODULE_DESCRIPTION("Acorn RiscPC PS/2 keyboard controller driver");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kart");
static int rpckbd_write(struct serio *port, unsigned char val)
{
@@ -140,6 +141,7 @@ static struct platform_driver rpckbd_driver = {
.remove = __devexit_p(rpckbd_remove),
.driver = {
.name = "kart",
+ .owner = THIS_MODULE,
},
};
diff --git a/drivers/input/tablet/Kconfig b/drivers/input/tablet/Kconfig
index d371c0bdc0b..effb49ea24a 100644
--- a/drivers/input/tablet/Kconfig
+++ b/drivers/input/tablet/Kconfig
@@ -25,14 +25,14 @@ config TABLET_USB_ACECAD
module will be called acecad.
config TABLET_USB_AIPTEK
- tristate "Aiptek 6000U/8000U tablet support (USB)"
+ tristate "Aiptek 6000U/8000U and Genius G_PEN tablet support (USB)"
depends on USB_ARCH_HAS_HCD
select USB
help
- Say Y here if you want to use the USB version of the Aiptek 6000U
- or Aiptek 8000U tablet. Make sure to say Y to "Mouse support"
- (CONFIG_INPUT_MOUSEDEV) and/or "Event interface support"
- (CONFIG_INPUT_EVDEV) as well.
+ Say Y here if you want to use the USB version of the Aiptek 6000U,
+ Aiptek 8000U or Genius G-PEN 560 tablet. Make sure to say Y to
+ "Mouse support" (CONFIG_INPUT_MOUSEDEV) and/or "Event interface
+ support" (CONFIG_INPUT_EVDEV) as well.
To compile this driver as a module, choose M here: the
module will be called aiptek.
diff --git a/drivers/input/tablet/aiptek.c b/drivers/input/tablet/aiptek.c
index 94683f58c9e..1d759f6f807 100644
--- a/drivers/input/tablet/aiptek.c
+++ b/drivers/input/tablet/aiptek.c
@@ -184,6 +184,7 @@
*/
#define USB_VENDOR_ID_AIPTEK 0x08ca
+#define USB_VENDOR_ID_KYE 0x0458
#define USB_REQ_GET_REPORT 0x01
#define USB_REQ_SET_REPORT 0x09
@@ -832,6 +833,7 @@ static const struct usb_device_id aiptek_ids[] = {
{USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x22)},
{USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x23)},
{USB_DEVICE(USB_VENDOR_ID_AIPTEK, 0x24)},
+ {USB_DEVICE(USB_VENDOR_ID_KYE, 0x5003)},
{}
};
diff --git a/drivers/input/tablet/wacom.h b/drivers/input/tablet/wacom.h
index acf9830698c..706619d06f7 100644
--- a/drivers/input/tablet/wacom.h
+++ b/drivers/input/tablet/wacom.h
@@ -101,8 +101,11 @@ struct wacom {
dma_addr_t data_dma;
struct input_dev *dev;
struct usb_device *usbdev;
+ struct usb_interface *intf;
struct urb *irq;
struct wacom_wac * wacom_wac;
+ struct mutex lock;
+ int open:1;
char phys[32];
};
diff --git a/drivers/input/tablet/wacom_sys.c b/drivers/input/tablet/wacom_sys.c
index 41caaef8e2d..71cc0c14079 100644
--- a/drivers/input/tablet/wacom_sys.c
+++ b/drivers/input/tablet/wacom_sys.c
@@ -70,6 +70,7 @@ static void wacom_sys_irq(struct urb *urb)
input_sync(get_input_dev(&wcombo));
exit:
+ usb_mark_last_busy(wacom->usbdev);
retval = usb_submit_urb (urb, GFP_ATOMIC);
if (retval)
err ("%s - usb_submit_urb failed with result %d",
@@ -124,10 +125,25 @@ static int wacom_open(struct input_dev *dev)
{
struct wacom *wacom = input_get_drvdata(dev);
+ mutex_lock(&wacom->lock);
+
wacom->irq->dev = wacom->usbdev;
- if (usb_submit_urb(wacom->irq, GFP_KERNEL))
+
+ if (usb_autopm_get_interface(wacom->intf) < 0) {
+ mutex_unlock(&wacom->lock);
return -EIO;
+ }
+
+ if (usb_submit_urb(wacom->irq, GFP_KERNEL)) {
+ usb_autopm_put_interface(wacom->intf);
+ mutex_unlock(&wacom->lock);
+ return -EIO;
+ }
+
+ wacom->open = 1;
+ wacom->intf->needs_remote_wakeup = 1;
+ mutex_unlock(&wacom->lock);
return 0;
}
@@ -135,7 +151,11 @@ static void wacom_close(struct input_dev *dev)
{
struct wacom *wacom = input_get_drvdata(dev);
+ mutex_lock(&wacom->lock);
usb_kill_urb(wacom->irq);
+ wacom->open = 0;
+ wacom->intf->needs_remote_wakeup = 0;
+ mutex_unlock(&wacom->lock);
}
void input_dev_mo(struct input_dev *input_dev, struct wacom_wac *wacom_wac)
@@ -243,6 +263,8 @@ static int wacom_probe(struct usb_interface *intf, const struct usb_device_id *i
wacom->usbdev = dev;
wacom->dev = input_dev;
+ wacom->intf = intf;
+ mutex_init(&wacom->lock);
usb_make_path(dev, wacom->phys, sizeof(wacom->phys));
strlcat(wacom->phys, "/input0", sizeof(wacom->phys));
@@ -304,23 +326,57 @@ static int wacom_probe(struct usb_interface *intf, const struct usb_device_id *i
static void wacom_disconnect(struct usb_interface *intf)
{
- struct wacom *wacom = usb_get_intfdata (intf);
+ struct wacom *wacom = usb_get_intfdata(intf);
usb_set_intfdata(intf, NULL);
- if (wacom) {
- usb_kill_urb(wacom->irq);
- input_unregister_device(wacom->dev);
- usb_free_urb(wacom->irq);
- usb_buffer_free(interface_to_usbdev(intf), 10, wacom->wacom_wac->data, wacom->data_dma);
- kfree(wacom->wacom_wac);
- kfree(wacom);
- }
+
+ usb_kill_urb(wacom->irq);
+ input_unregister_device(wacom->dev);
+ usb_free_urb(wacom->irq);
+ usb_buffer_free(interface_to_usbdev(intf), 10, wacom->wacom_wac->data, wacom->data_dma);
+ kfree(wacom->wacom_wac);
+ kfree(wacom);
+}
+
+static int wacom_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct wacom *wacom = usb_get_intfdata(intf);
+
+ mutex_lock(&wacom->lock);
+ usb_kill_urb(wacom->irq);
+ mutex_unlock(&wacom->lock);
+
+ return 0;
+}
+
+static int wacom_resume(struct usb_interface *intf)
+{
+ struct wacom *wacom = usb_get_intfdata(intf);
+ int rv;
+
+ mutex_lock(&wacom->lock);
+ if (wacom->open)
+ rv = usb_submit_urb(wacom->irq, GFP_NOIO);
+ else
+ rv = 0;
+ mutex_unlock(&wacom->lock);
+
+ return rv;
+}
+
+static int wacom_reset_resume(struct usb_interface *intf)
+{
+ return wacom_resume(intf);
}
static struct usb_driver wacom_driver = {
.name = "wacom",
.probe = wacom_probe,
.disconnect = wacom_disconnect,
+ .suspend = wacom_suspend,
+ .resume = wacom_resume,
+ .reset_resume = wacom_reset_resume,
+ .supports_autosuspend = 1,
};
static int __init wacom_init(void)
diff --git a/drivers/input/tablet/wacom_wac.c b/drivers/input/tablet/wacom_wac.c
index ffe33842143..192513e1f04 100644
--- a/drivers/input/tablet/wacom_wac.c
+++ b/drivers/input/tablet/wacom_wac.c
@@ -649,6 +649,7 @@ static struct wacom_features wacom_features[] = {
{ "Wacom Intuos3 6x11", 10, 54204, 31750, 1023, 63, INTUOS3 },
{ "Wacom Intuos3 4x6", 10, 31496, 19685, 1023, 63, INTUOS3S },
{ "Wacom Cintiq 21UX", 10, 87200, 65600, 1023, 63, CINTIQ },
+ { "Wacom Cintiq 20WSX", 10, 86680, 54180, 1023, 63, WACOM_BEE },
{ "Wacom Cintiq 12WX", 10, 53020, 33440, 1023, 63, WACOM_BEE },
{ "Wacom Intuos2 6x8", 10, 20320, 16240, 1023, 31, INTUOS },
{ }
@@ -702,6 +703,7 @@ static struct usb_device_id wacom_ids[] = {
{ USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB5) },
{ USB_DEVICE(USB_VENDOR_ID_WACOM, 0xB7) },
{ USB_DEVICE(USB_VENDOR_ID_WACOM, 0x3F) },
+ { USB_DEVICE(USB_VENDOR_ID_WACOM, 0xC5) },
{ USB_DEVICE(USB_VENDOR_ID_WACOM, 0xC6) },
{ USB_DEVICE(USB_VENDOR_ID_WACOM, 0x47) },
{ }
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 90e8e92dfe4..565ec711c2e 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -185,6 +185,59 @@ config TOUCHSCREEN_UCB1400
To compile this driver as a module, choose M here: the
module will be called ucb1400_ts.
+config TOUCHSCREEN_WM97XX
+ tristate "Support for WM97xx AC97 touchscreen controllers"
+ depends on AC97_BUS
+ help
+ Say Y here if you have a Wolfson Microelectronics WM97xx
+ touchscreen connected to your system. Note that this option
+ only enables core driver, you will also need to select
+ support for appropriate chip below.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wm97xx-ts.
+
+config TOUCHSCREEN_WM9705
+ bool "WM9705 Touchscreen interface support"
+ depends on TOUCHSCREEN_WM97XX
+ help
+ Say Y here if you have a Wolfson Microelectronics WM9705
+ touchscreen controller connected to your system.
+
+ If unsure, say N.
+
+config TOUCHSCREEN_WM9712
+ bool "WM9712 Touchscreen interface support"
+ depends on TOUCHSCREEN_WM97XX
+ help
+ Say Y here if you have a Wolfson Microelectronics WM9712
+ touchscreen controller connected to your system.
+
+ If unsure, say N.
+
+config TOUCHSCREEN_WM9713
+ bool "WM9713 Touchscreen interface support"
+ depends on TOUCHSCREEN_WM97XX
+ help
+ Say Y here if you have a Wolfson Microelectronics WM9713 touchscreen
+ controller connected to your system.
+
+ If unsure, say N.
+
+config TOUCHSCREEN_WM97XX_MAINSTONE
+ tristate "WM97xx Mainstone accelerated touch"
+ depends on TOUCHSCREEN_WM97XX && ARCH_PXA
+ help
+ Say Y here for support for streaming mode with WM97xx touchscreens
+ on Mainstone systems.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mainstone-wm97xx.
+
config TOUCHSCREEN_USB_COMPOSITE
tristate "USB Touchscreen Driver"
depends on USB_ARCH_HAS_HCD
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 35d4097df35..3c096d75651 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -4,6 +4,8 @@
# Each configuration option enables a list of files.
+wm97xx-ts-y := wm97xx-core.o
+
obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o
obj-$(CONFIG_TOUCHSCREEN_BITSY) += h3600_ts_input.o
obj-$(CONFIG_TOUCHSCREEN_CORGI) += corgi_ts.o
@@ -19,3 +21,8 @@ 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_WM97XX) += wm97xx-ts.o
+wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o
+wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o
+wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o
+obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c
index 39573b91c8d..907a45fe9d4 100644
--- a/drivers/input/touchscreen/ads7846.c
+++ b/drivers/input/touchscreen/ads7846.c
@@ -80,6 +80,7 @@ struct ads7846 {
#endif
u16 model;
+ u16 vref_mv;
u16 vref_delay_usecs;
u16 x_plate_ohms;
u16 pressure_max;
@@ -177,9 +178,6 @@ struct ads7846 {
* The range is GND..vREF. The ads7843 and ads7835 must use external vREF;
* ads7846 lets that pin be unconnected, to use internal vREF.
*/
-static unsigned vREF_mV;
-module_param(vREF_mV, uint, 0);
-MODULE_PARM_DESC(vREF_mV, "external vREF voltage, in milliVolts");
struct ser_req {
u8 ref_on;
@@ -206,7 +204,6 @@ static int ads7846_read12_ser(struct device *dev, unsigned command)
struct ads7846 *ts = dev_get_drvdata(dev);
struct ser_req *req = kzalloc(sizeof *req, GFP_KERNEL);
int status;
- int uninitialized_var(sample);
int use_internal;
if (!req)
@@ -263,13 +260,13 @@ static int ads7846_read12_ser(struct device *dev, unsigned command)
if (status == 0) {
/* on-wire is a must-ignore bit, a BE12 value, then padding */
- sample = be16_to_cpu(req->sample);
- sample = sample >> 3;
- sample &= 0x0fff;
+ status = be16_to_cpu(req->sample);
+ status = status >> 3;
+ status &= 0x0fff;
}
kfree(req);
- return status ? status : sample;
+ return status;
}
#if defined(CONFIG_HWMON) || defined(CONFIG_HWMON_MODULE)
@@ -310,7 +307,7 @@ static inline unsigned vaux_adjust(struct ads7846 *ts, ssize_t v)
unsigned retval = v;
/* external resistors may scale vAUX into 0..vREF */
- retval *= vREF_mV;
+ retval *= ts->vref_mv;
retval = retval >> 12;
return retval;
}
@@ -368,14 +365,14 @@ static int ads784x_hwmon_register(struct spi_device *spi, struct ads7846 *ts)
/* hwmon sensors need a reference voltage */
switch (ts->model) {
case 7846:
- if (!vREF_mV) {
+ if (!ts->vref_mv) {
dev_dbg(&spi->dev, "assuming 2.5V internal vREF\n");
- vREF_mV = 2500;
+ ts->vref_mv = 2500;
}
break;
case 7845:
case 7843:
- if (!vREF_mV) {
+ if (!ts->vref_mv) {
dev_warn(&spi->dev,
"external vREF for ADS%d not specified\n",
ts->model);
@@ -868,6 +865,7 @@ static int __devinit ads7846_probe(struct spi_device *spi)
ts->spi = spi;
ts->input = input_dev;
+ ts->vref_mv = pdata->vref_mv;
hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
ts->timer.function = ads7846_timer;
diff --git a/drivers/input/touchscreen/corgi_ts.c b/drivers/input/touchscreen/corgi_ts.c
index a22576779ac..4e9d8eece2e 100644
--- a/drivers/input/touchscreen/corgi_ts.c
+++ b/drivers/input/touchscreen/corgi_ts.c
@@ -362,6 +362,7 @@ static struct platform_driver corgits_driver = {
.resume = corgits_resume,
.driver = {
.name = "corgi-ts",
+ .owner = THIS_MODULE,
},
};
@@ -381,3 +382,4 @@ module_exit(corgits_exit);
MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>");
MODULE_DESCRIPTION("Corgi TouchScreen Driver");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:corgi-ts");
diff --git a/drivers/input/touchscreen/jornada720_ts.c b/drivers/input/touchscreen/jornada720_ts.c
index 42a1c9a1940..742242111bf 100644
--- a/drivers/input/touchscreen/jornada720_ts.c
+++ b/drivers/input/touchscreen/jornada720_ts.c
@@ -160,11 +160,15 @@ static int __devexit jornada720_ts_remove(struct platform_device *pdev)
return 0;
}
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:jornada_ts");
+
static struct platform_driver jornada720_ts_driver = {
.probe = jornada720_ts_probe,
.remove = __devexit_p(jornada720_ts_remove),
.driver = {
.name = "jornada_ts",
+ .owner = THIS_MODULE,
},
};
diff --git a/drivers/input/touchscreen/mainstone-wm97xx.c b/drivers/input/touchscreen/mainstone-wm97xx.c
new file mode 100644
index 00000000000..a79f029b91c
--- /dev/null
+++ b/drivers/input/touchscreen/mainstone-wm97xx.c
@@ -0,0 +1,302 @@
+/*
+ * mainstone-wm97xx.c -- Mainstone Continuous Touch screen driver for
+ * Wolfson WM97xx AC97 Codecs.
+ *
+ * Copyright 2004, 2007 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ *
+ * 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.
+ *
+ * Notes:
+ * This is a wm97xx extended touch driver to capture touch
+ * data in a continuous manner on the Intel XScale archictecture
+ *
+ * Features:
+ * - codecs supported:- WM9705, WM9712, WM9713
+ * - processors supported:- Intel XScale PXA25x, PXA26x, PXA27x
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/wm97xx.h>
+#include <linux/io.h>
+#include <asm/arch/pxa-regs.h>
+
+#define VERSION "0.13"
+
+struct continuous {
+ u16 id; /* codec id */
+ u8 code; /* continuous code */
+ u8 reads; /* number of coord reads per read cycle */
+ u32 speed; /* number of coords per second */
+};
+
+#define WM_READS(sp) ((sp / HZ) + 1)
+
+static const struct continuous cinfo[] = {
+ {WM9705_ID2, 0, WM_READS(94), 94},
+ {WM9705_ID2, 1, WM_READS(188), 188},
+ {WM9705_ID2, 2, WM_READS(375), 375},
+ {WM9705_ID2, 3, WM_READS(750), 750},
+ {WM9712_ID2, 0, WM_READS(94), 94},
+ {WM9712_ID2, 1, WM_READS(188), 188},
+ {WM9712_ID2, 2, WM_READS(375), 375},
+ {WM9712_ID2, 3, WM_READS(750), 750},
+ {WM9713_ID2, 0, WM_READS(94), 94},
+ {WM9713_ID2, 1, WM_READS(120), 120},
+ {WM9713_ID2, 2, WM_READS(154), 154},
+ {WM9713_ID2, 3, WM_READS(188), 188},
+};
+
+/* continuous speed index */
+static int sp_idx;
+static u16 last, tries;
+
+/*
+ * Pen sampling frequency (Hz) in continuous mode.
+ */
+static int cont_rate = 200;
+module_param(cont_rate, int, 0);
+MODULE_PARM_DESC(cont_rate, "Sampling rate in continuous mode (Hz)");
+
+/*
+ * Pen down detection.
+ *
+ * This driver can either poll or use an interrupt to indicate a pen down
+ * event. If the irq request fails then it will fall back to polling mode.
+ */
+static int pen_int;
+module_param(pen_int, int, 0);
+MODULE_PARM_DESC(pen_int, "Pen down detection (1 = interrupt, 0 = polling)");
+
+/*
+ * Pressure readback.
+ *
+ * Set to 1 to read back pen down pressure
+ */
+static int pressure;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Pressure readback (1 = pressure, 0 = no pressure)");
+
+/*
+ * AC97 touch data slot.
+ *
+ * Touch screen readback data ac97 slot
+ */
+static int ac97_touch_slot = 5;
+module_param(ac97_touch_slot, int, 0);
+MODULE_PARM_DESC(ac97_touch_slot, "Touch screen data slot AC97 number");
+
+
+/* flush AC97 slot 5 FIFO on pxa machines */
+#ifdef CONFIG_PXA27x
+static void wm97xx_acc_pen_up(struct wm97xx *wm)
+{
+ schedule_timeout_uninterruptible(1);
+
+ while (MISR & (1 << 2))
+ MODR;
+}
+#else
+static void wm97xx_acc_pen_up(struct wm97xx *wm)
+{
+ int count = 16;
+ schedule_timeout_uninterruptible(1);
+
+ while (count < 16) {
+ MODR;
+ count--;
+ }
+}
+#endif
+
+static int wm97xx_acc_pen_down(struct wm97xx *wm)
+{
+ u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES;
+ int reads = 0;
+
+ /* When the AC97 queue has been drained we need to allow time
+ * to buffer up samples otherwise we end up spinning polling
+ * for samples. The controller can't have a suitably low
+ * threashold set to use the notifications it gives.
+ */
+ schedule_timeout_uninterruptible(1);
+
+ if (tries > 5) {
+ tries = 0;
+ return RC_PENUP;
+ }
+
+ x = MODR;
+ if (x == last) {
+ tries++;
+ return RC_AGAIN;
+ }
+ last = x;
+ do {
+ if (reads)
+ x = MODR;
+ y = MODR;
+ if (pressure)
+ p = MODR;
+
+ /* are samples valid */
+ if ((x & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_X ||
+ (y & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_Y ||
+ (p & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_PRES)
+ goto up;
+
+ /* coordinate is good */
+ tries = 0;
+ input_report_abs(wm->input_dev, ABS_X, x & 0xfff);
+ input_report_abs(wm->input_dev, ABS_Y, y & 0xfff);
+ input_report_abs(wm->input_dev, ABS_PRESSURE, p & 0xfff);
+ input_sync(wm->input_dev);
+ reads++;
+ } while (reads < cinfo[sp_idx].reads);
+up:
+ return RC_PENDOWN | RC_AGAIN;
+}
+
+static int wm97xx_acc_startup(struct wm97xx *wm)
+{
+ int idx = 0;
+
+ /* check we have a codec */
+ if (wm->ac97 == NULL)
+ return -ENODEV;
+
+ /* Go you big red fire engine */
+ for (idx = 0; idx < ARRAY_SIZE(cinfo); idx++) {
+ if (wm->id != cinfo[idx].id)
+ continue;
+ sp_idx = idx;
+ if (cont_rate <= cinfo[idx].speed)
+ break;
+ }
+ wm->acc_rate = cinfo[sp_idx].code;
+ wm->acc_slot = ac97_touch_slot;
+ dev_info(wm->dev,
+ "mainstone accelerated touchscreen driver, %d samples/sec\n",
+ cinfo[sp_idx].speed);
+
+ /* codec specific irq config */
+ if (pen_int) {
+ switch (wm->id) {
+ case WM9705_ID2:
+ wm->pen_irq = IRQ_GPIO(4);
+ set_irq_type(IRQ_GPIO(4), IRQT_BOTHEDGE);
+ break;
+ case WM9712_ID2:
+ case WM9713_ID2:
+ /* enable pen down interrupt */
+ /* use PEN_DOWN GPIO 13 to assert IRQ on GPIO line 2 */
+ wm->pen_irq = MAINSTONE_AC97_IRQ;
+ wm97xx_config_gpio(wm, WM97XX_GPIO_13, WM97XX_GPIO_IN,
+ WM97XX_GPIO_POL_HIGH,
+ WM97XX_GPIO_STICKY,
+ WM97XX_GPIO_WAKE);
+ wm97xx_config_gpio(wm, WM97XX_GPIO_2, WM97XX_GPIO_OUT,
+ WM97XX_GPIO_POL_HIGH,
+ WM97XX_GPIO_NOTSTICKY,
+ WM97XX_GPIO_NOWAKE);
+ break;
+ default:
+ dev_err(wm->dev,
+ "pen down irq not supported on this device\n");
+ pen_int = 0;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void wm97xx_acc_shutdown(struct wm97xx *wm)
+{
+ /* codec specific deconfig */
+ if (pen_int) {
+ switch (wm->id & 0xffff) {
+ case WM9705_ID2:
+ wm->pen_irq = 0;
+ break;
+ case WM9712_ID2:
+ case WM9713_ID2:
+ /* disable interrupt */
+ wm->pen_irq = 0;
+ break;
+ }
+ }
+}
+
+static void wm97xx_irq_enable(struct wm97xx *wm, int enable)
+{
+ if (enable)
+ enable_irq(wm->pen_irq);
+ else
+ disable_irq(wm->pen_irq);
+}
+
+static struct wm97xx_mach_ops mainstone_mach_ops = {
+ .acc_enabled = 1,
+ .acc_pen_up = wm97xx_acc_pen_up,
+ .acc_pen_down = wm97xx_acc_pen_down,
+ .acc_startup = wm97xx_acc_startup,
+ .acc_shutdown = wm97xx_acc_shutdown,
+ .irq_enable = wm97xx_irq_enable,
+ .irq_gpio = WM97XX_GPIO_2,
+};
+
+static int mainstone_wm97xx_probe(struct platform_device *pdev)
+{
+ struct wm97xx *wm = platform_get_drvdata(pdev);
+
+ return wm97xx_register_mach_ops(wm, &mainstone_mach_ops);
+}
+
+static int mainstone_wm97xx_remove(struct platform_device *pdev)
+{
+ struct wm97xx *wm = platform_get_drvdata(pdev);
+
+ wm97xx_unregister_mach_ops(wm);
+ return 0;
+}
+
+static struct platform_driver mainstone_wm97xx_driver = {
+ .probe = mainstone_wm97xx_probe,
+ .remove = mainstone_wm97xx_remove,
+ .driver = {
+ .name = "wm97xx-touch",
+ },
+};
+
+static int __init mainstone_wm97xx_init(void)
+{
+ return platform_driver_register(&mainstone_wm97xx_driver);
+}
+
+static void __exit mainstone_wm97xx_exit(void)
+{
+ platform_driver_unregister(&mainstone_wm97xx_driver);
+}
+
+module_init(mainstone_wm97xx_init);
+module_exit(mainstone_wm97xx_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <liam.girdwood@wolfsonmicro.com>");
+MODULE_DESCRIPTION("wm97xx continuous touch driver for mainstone");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/ucb1400_ts.c b/drivers/input/touchscreen/ucb1400_ts.c
index 607f9933aa1..bce018e45bc 100644
--- a/drivers/input/touchscreen/ucb1400_ts.c
+++ b/drivers/input/touchscreen/ucb1400_ts.c
@@ -427,10 +427,6 @@ static int ucb1400_detect_irq(struct ucb1400 *ucb)
unsigned long mask, timeout;
mask = probe_irq_on();
- if (!mask) {
- probe_irq_off(mask);
- return -EBUSY;
- }
/* Enable the ADC interrupt. */
ucb1400_reg_write(ucb, UCB_IE_RIS, UCB_IE_ADC);
diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c
index 63f9664a066..3a0a8ca5707 100644
--- a/drivers/input/touchscreen/usbtouchscreen.c
+++ b/drivers/input/touchscreen/usbtouchscreen.c
@@ -396,9 +396,12 @@ static int gunze_read_data(struct usbtouch_usb *dev, unsigned char *pkt)
static int dmc_tsc10_init(struct usbtouch_usb *usbtouch)
{
struct usb_device *dev = usbtouch->udev;
- int ret;
- unsigned char buf[2];
+ int ret = -ENOMEM;
+ unsigned char *buf;
+ buf = kmalloc(2, GFP_KERNEL);
+ if (!buf)
+ goto err_nobuf;
/* reset */
buf[0] = buf[1] = 0xFF;
ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0),
@@ -406,9 +409,11 @@ static int dmc_tsc10_init(struct usbtouch_usb *usbtouch)
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
0, 0, buf, 2, USB_CTRL_SET_TIMEOUT);
if (ret < 0)
- return ret;
- if (buf[0] != 0x06 || buf[1] != 0x00)
- return -ENODEV;
+ goto err_out;
+ if (buf[0] != 0x06 || buf[1] != 0x00) {
+ ret = -ENODEV;
+ goto err_out;
+ }
/* set coordinate output rate */
buf[0] = buf[1] = 0xFF;
@@ -417,20 +422,22 @@ static int dmc_tsc10_init(struct usbtouch_usb *usbtouch)
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
TSC10_RATE_150, 0, buf, 2, USB_CTRL_SET_TIMEOUT);
if (ret < 0)
- return ret;
+ goto err_out;
if ((buf[0] != 0x06 || buf[1] != 0x00) &&
- (buf[0] != 0x15 || buf[1] != 0x01))
- return -ENODEV;
+ (buf[0] != 0x15 || buf[1] != 0x01)) {
+ ret = -ENODEV;
+ goto err_out;
+ }
/* start sending data */
ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0),
TSC10_CMD_DATA1,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
- if (ret < 0)
- return ret;
-
- return 0;
+err_out:
+ kfree(buf);
+err_nobuf:
+ return ret;
}
diff --git a/drivers/input/touchscreen/wm9705.c b/drivers/input/touchscreen/wm9705.c
new file mode 100644
index 00000000000..978e1a13ffc
--- /dev/null
+++ b/drivers/input/touchscreen/wm9705.c
@@ -0,0 +1,353 @@
+/*
+ * wm9705.c -- Codec driver for Wolfson WM9705 AC97 Codec.
+ *
+ * Copyright 2003, 2004, 2005, 2006, 2007 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ * Russell King <rmk@arm.linux.org.uk>
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/wm97xx.h>
+
+#define TS_NAME "wm97xx"
+#define WM9705_VERSION "1.00"
+#define DEFAULT_PRESSURE 0xb0c0
+
+/*
+ * Module parameters
+ */
+
+/*
+ * Set current used for pressure measurement.
+ *
+ * Set pil = 2 to use 400uA
+ * pil = 1 to use 200uA and
+ * pil = 0 to disable pressure measurement.
+ *
+ * This is used to increase the range of values returned by the adc
+ * when measureing touchpanel pressure.
+ */
+static int pil;
+module_param(pil, int, 0);
+MODULE_PARM_DESC(pil, "Set current used for pressure measurement.");
+
+/*
+ * Set threshold for pressure measurement.
+ *
+ * Pen down pressure below threshold is ignored.
+ */
+static int pressure = DEFAULT_PRESSURE & 0xfff;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement.");
+
+/*
+ * Set adc sample delay.
+ *
+ * For accurate touchpanel measurements, some settling time may be
+ * required between the switch matrix applying a voltage across the
+ * touchpanel plate and the ADC sampling the signal.
+ *
+ * This delay can be set by setting delay = n, where n is the array
+ * position of the delay in the array delay_table below.
+ * Long delays > 1ms are supported for completeness, but are not
+ * recommended.
+ */
+static int delay = 4;
+module_param(delay, int, 0);
+MODULE_PARM_DESC(delay, "Set adc sample delay.");
+
+/*
+ * Pen detect comparator threshold.
+ *
+ * 0 to Vmid in 15 steps, 0 = use zero power comparator with Vmid threshold
+ * i.e. 1 = Vmid/15 threshold
+ * 15 = Vmid/1 threshold
+ *
+ * Adjust this value if you are having problems with pen detect not
+ * detecting any down events.
+ */
+static int pdd = 8;
+module_param(pdd, int, 0);
+MODULE_PARM_DESC(pdd, "Set pen detect comparator threshold");
+
+/*
+ * Set adc mask function.
+ *
+ * Sources of glitch noise, such as signals driving an LCD display, may feed
+ * through to the touch screen plates and affect measurement accuracy. In
+ * order to minimise this, a signal may be applied to the MASK pin to delay or
+ * synchronise the sampling.
+ *
+ * 0 = No delay or sync
+ * 1 = High on pin stops conversions
+ * 2 = Edge triggered, edge on pin delays conversion by delay param (above)
+ * 3 = Edge triggered, edge on pin starts conversion after delay param
+ */
+static int mask;
+module_param(mask, int, 0);
+MODULE_PARM_DESC(mask, "Set adc mask function.");
+
+/*
+ * ADC sample delay times in uS
+ */
+static const int delay_table[] = {
+ 21, /* 1 AC97 Link frames */
+ 42, /* 2 */
+ 84, /* 4 */
+ 167, /* 8 */
+ 333, /* 16 */
+ 667, /* 32 */
+ 1000, /* 48 */
+ 1333, /* 64 */
+ 2000, /* 96 */
+ 2667, /* 128 */
+ 3333, /* 160 */
+ 4000, /* 192 */
+ 4667, /* 224 */
+ 5333, /* 256 */
+ 6000, /* 288 */
+ 0 /* No delay, switch matrix always on */
+};
+
+/*
+ * Delay after issuing a POLL command.
+ *
+ * The delay is 3 AC97 link frames + the touchpanel settling delay
+ */
+static inline void poll_delay(int d)
+{
+ udelay(3 * AC97_LINK_FRAME + delay_table[d]);
+}
+
+/*
+ * set up the physical settings of the WM9705
+ */
+static void wm9705_phy_init(struct wm97xx *wm)
+{
+ u16 dig1 = 0, dig2 = WM97XX_RPR;
+
+ /*
+ * mute VIDEO and AUX as they share X and Y touchscreen
+ * inputs on the WM9705
+ */
+ wm97xx_reg_write(wm, AC97_AUX, 0x8000);
+ wm97xx_reg_write(wm, AC97_VIDEO, 0x8000);
+
+ /* touchpanel pressure current*/
+ if (pil == 2) {
+ dig2 |= WM9705_PIL;
+ dev_dbg(wm->dev,
+ "setting pressure measurement current to 400uA.");
+ } else if (pil)
+ dev_dbg(wm->dev,
+ "setting pressure measurement current to 200uA.");
+ if (!pil)
+ pressure = 0;
+
+ /* polling mode sample settling delay */
+ if (delay != 4) {
+ if (delay < 0 || delay > 15) {
+ dev_dbg(wm->dev, "supplied delay out of range.");
+ delay = 4;
+ }
+ }
+ dig1 &= 0xff0f;
+ dig1 |= WM97XX_DELAY(delay);
+ dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.",
+ delay_table[delay]);
+
+ /* WM9705 pdd */
+ dig2 |= (pdd & 0x000f);
+ dev_dbg(wm->dev, "setting pdd to Vmid/%d", 1 - (pdd & 0x000f));
+
+ /* mask */
+ dig2 |= ((mask & 0x3) << 4);
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2);
+}
+
+static void wm9705_dig_enable(struct wm97xx *wm, int enable)
+{
+ if (enable) {
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2,
+ wm->dig[2] | WM97XX_PRP_DET_DIG);
+ wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */
+ } else
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2,
+ wm->dig[2] & ~WM97XX_PRP_DET_DIG);
+}
+
+static void wm9705_aux_prepare(struct wm97xx *wm)
+{
+ memcpy(wm->dig_save, wm->dig, sizeof(wm->dig));
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG);
+}
+
+static void wm9705_dig_restore(struct wm97xx *wm)
+{
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]);
+}
+
+static inline int is_pden(struct wm97xx *wm)
+{
+ return wm->dig[2] & WM9705_PDEN;
+}
+
+/*
+ * Read a sample from the WM9705 adc in polling mode.
+ */
+static int wm9705_poll_sample(struct wm97xx *wm, int adcsel, int *sample)
+{
+ int timeout = 5 * delay;
+
+ if (!wm->pen_probably_down) {
+ u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(data & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ if (adcsel & 0x8000)
+ adcsel = ((adcsel & 0x7fff) + 3) << 12;
+
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(adcsel);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1,
+ adcsel | WM97XX_POLL | WM97XX_DELAY(delay));
+
+ /* wait 3 AC97 time slots + delay for conversion */
+ poll_delay(delay);
+
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL)
+ && timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout == 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout");
+ return RC_PENUP;
+ }
+
+ *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(adcsel);
+
+ /* check we have correct sample */
+ if ((*sample & WM97XX_ADCSEL_MASK) != adcsel) {
+ dev_dbg(wm->dev, "adc wrong sample, read %x got %x", adcsel,
+ *sample & WM97XX_ADCSEL_MASK);
+ return RC_PENUP;
+ }
+
+ if (!(*sample & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+
+ return RC_VALID;
+}
+
+/*
+ * Sample the WM9705 touchscreen in polling mode
+ */
+static int wm9705_poll_touch(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ int rc;
+
+ rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_X, &data->x);
+ if (rc != RC_VALID)
+ return rc;
+ rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_Y, &data->y);
+ if (rc != RC_VALID)
+ return rc;
+ if (pil) {
+ rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_PRES, &data->p);
+ if (rc != RC_VALID)
+ return rc;
+ } else
+ data->p = DEFAULT_PRESSURE;
+
+ return RC_VALID;
+}
+
+/*
+ * Enable WM9705 continuous mode, i.e. touch data is streamed across
+ * an AC97 slot
+ */
+static int wm9705_acc_enable(struct wm97xx *wm, int enable)
+{
+ u16 dig1, dig2;
+ int ret = 0;
+
+ dig1 = wm->dig[1];
+ dig2 = wm->dig[2];
+
+ if (enable) {
+ /* continous mode */
+ if (wm->mach_ops->acc_startup &&
+ (ret = wm->mach_ops->acc_startup(wm)) < 0)
+ return ret;
+ dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK |
+ WM97XX_DELAY_MASK | WM97XX_SLT_MASK);
+ dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN |
+ WM97XX_DELAY(delay) |
+ WM97XX_SLT(wm->acc_slot) |
+ WM97XX_RATE(wm->acc_rate);
+ if (pil)
+ dig1 |= WM97XX_ADCSEL_PRES;
+ dig2 |= WM9705_PDEN;
+ } else {
+ dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN);
+ dig2 &= ~WM9705_PDEN;
+ if (wm->mach_ops->acc_shutdown)
+ wm->mach_ops->acc_shutdown(wm);
+ }
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2);
+
+ return ret;
+}
+
+struct wm97xx_codec_drv wm9705_codec = {
+ .id = WM9705_ID2,
+ .name = "wm9705",
+ .poll_sample = wm9705_poll_sample,
+ .poll_touch = wm9705_poll_touch,
+ .acc_enable = wm9705_acc_enable,
+ .phy_init = wm9705_phy_init,
+ .dig_enable = wm9705_dig_enable,
+ .dig_restore = wm9705_dig_restore,
+ .aux_prepare = wm9705_aux_prepare,
+};
+EXPORT_SYMBOL_GPL(wm9705_codec);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <liam.girdwood@wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM9705 Touch Screen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/wm9712.c b/drivers/input/touchscreen/wm9712.c
new file mode 100644
index 00000000000..0b6e4cfa6a2
--- /dev/null
+++ b/drivers/input/touchscreen/wm9712.c
@@ -0,0 +1,462 @@
+/*
+ * wm9712.c -- Codec driver for Wolfson WM9712 AC97 Codecs.
+ *
+ * Copyright 2003, 2004, 2005, 2006, 2007 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ * Russell King <rmk@arm.linux.org.uk>
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/wm97xx.h>
+
+#define TS_NAME "wm97xx"
+#define WM9712_VERSION "1.00"
+#define DEFAULT_PRESSURE 0xb0c0
+
+/*
+ * Module parameters
+ */
+
+/*
+ * Set internal pull up for pen detect.
+ *
+ * Pull up is in the range 1.02k (least sensitive) to 64k (most sensitive)
+ * i.e. pull up resistance = 64k Ohms / rpu.
+ *
+ * Adjust this value if you are having problems with pen detect not
+ * detecting any down event.
+ */
+static int rpu = 8;
+module_param(rpu, int, 0);
+MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect.");
+
+/*
+ * Set current used for pressure measurement.
+ *
+ * Set pil = 2 to use 400uA
+ * pil = 1 to use 200uA and
+ * pil = 0 to disable pressure measurement.
+ *
+ * This is used to increase the range of values returned by the adc
+ * when measureing touchpanel pressure.
+ */
+static int pil;
+module_param(pil, int, 0);
+MODULE_PARM_DESC(pil, "Set current used for pressure measurement.");
+
+/*
+ * Set threshold for pressure measurement.
+ *
+ * Pen down pressure below threshold is ignored.
+ */
+static int pressure = DEFAULT_PRESSURE & 0xfff;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement.");
+
+/*
+ * Set adc sample delay.
+ *
+ * For accurate touchpanel measurements, some settling time may be
+ * required between the switch matrix applying a voltage across the
+ * touchpanel plate and the ADC sampling the signal.
+ *
+ * This delay can be set by setting delay = n, where n is the array
+ * position of the delay in the array delay_table below.
+ * Long delays > 1ms are supported for completeness, but are not
+ * recommended.
+ */
+static int delay = 3;
+module_param(delay, int, 0);
+MODULE_PARM_DESC(delay, "Set adc sample delay.");
+
+/*
+ * Set five_wire = 1 to use a 5 wire touchscreen.
+ *
+ * NOTE: Five wire mode does not allow for readback of pressure.
+ */
+static int five_wire;
+module_param(five_wire, int, 0);
+MODULE_PARM_DESC(five_wire, "Set to '1' to use 5-wire touchscreen.");
+
+/*
+ * Set adc mask function.
+ *
+ * Sources of glitch noise, such as signals driving an LCD display, may feed
+ * through to the touch screen plates and affect measurement accuracy. In
+ * order to minimise this, a signal may be applied to the MASK pin to delay or
+ * synchronise the sampling.
+ *
+ * 0 = No delay or sync
+ * 1 = High on pin stops conversions
+ * 2 = Edge triggered, edge on pin delays conversion by delay param (above)
+ * 3 = Edge triggered, edge on pin starts conversion after delay param
+ */
+static int mask;
+module_param(mask, int, 0);
+MODULE_PARM_DESC(mask, "Set adc mask function.");
+
+/*
+ * Coordinate Polling Enable.
+ *
+ * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together
+ * for every poll.
+ */
+static int coord;
+module_param(coord, int, 0);
+MODULE_PARM_DESC(coord, "Polling coordinate mode");
+
+/*
+ * ADC sample delay times in uS
+ */
+static const int delay_table[] = {
+ 21, /* 1 AC97 Link frames */
+ 42, /* 2 */
+ 84, /* 4 */
+ 167, /* 8 */
+ 333, /* 16 */
+ 667, /* 32 */
+ 1000, /* 48 */
+ 1333, /* 64 */
+ 2000, /* 96 */
+ 2667, /* 128 */
+ 3333, /* 160 */
+ 4000, /* 192 */
+ 4667, /* 224 */
+ 5333, /* 256 */
+ 6000, /* 288 */
+ 0 /* No delay, switch matrix always on */
+};
+
+/*
+ * Delay after issuing a POLL command.
+ *
+ * The delay is 3 AC97 link frames + the touchpanel settling delay
+ */
+static inline void poll_delay(int d)
+{
+ udelay(3 * AC97_LINK_FRAME + delay_table[d]);
+}
+
+/*
+ * set up the physical settings of the WM9712
+ */
+static void wm9712_phy_init(struct wm97xx *wm)
+{
+ u16 dig1 = 0;
+ u16 dig2 = WM97XX_RPR | WM9712_RPU(1);
+
+ /* WM9712 rpu */
+ if (rpu) {
+ dig2 &= 0xffc0;
+ dig2 |= WM9712_RPU(rpu);
+ dev_dbg(wm->dev, "setting pen detect pull-up to %d Ohms",
+ 64000 / rpu);
+ }
+
+ /* touchpanel pressure current*/
+ if (pil == 2) {
+ dig2 |= WM9712_PIL;
+ dev_dbg(wm->dev,
+ "setting pressure measurement current to 400uA.");
+ } else if (pil)
+ dev_dbg(wm->dev,
+ "setting pressure measurement current to 200uA.");
+ if (!pil)
+ pressure = 0;
+
+ /* WM9712 five wire */
+ if (five_wire) {
+ dig2 |= WM9712_45W;
+ dev_dbg(wm->dev, "setting 5-wire touchscreen mode.");
+ }
+
+ /* polling mode sample settling delay */
+ if (delay < 0 || delay > 15) {
+ dev_dbg(wm->dev, "supplied delay out of range.");
+ delay = 4;
+ }
+ dig1 &= 0xff0f;
+ dig1 |= WM97XX_DELAY(delay);
+ dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.",
+ delay_table[delay]);
+
+ /* mask */
+ dig2 |= ((mask & 0x3) << 6);
+ if (mask) {
+ u16 reg;
+ /* Set GPIO4 as Mask Pin*/
+ reg = wm97xx_reg_read(wm, AC97_MISC_AFE);
+ wm97xx_reg_write(wm, AC97_MISC_AFE, reg | WM97XX_GPIO_4);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_CFG);
+ wm97xx_reg_write(wm, AC97_GPIO_CFG, reg | WM97XX_GPIO_4);
+ }
+
+ /* wait - coord mode */
+ if (coord)
+ dig2 |= WM9712_WAIT;
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2);
+}
+
+static void wm9712_dig_enable(struct wm97xx *wm, int enable)
+{
+ u16 dig2 = wm->dig[2];
+
+ if (enable) {
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2,
+ dig2 | WM97XX_PRP_DET_DIG);
+ wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */
+ } else
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2,
+ dig2 & ~WM97XX_PRP_DET_DIG);
+}
+
+static void wm9712_aux_prepare(struct wm97xx *wm)
+{
+ memcpy(wm->dig_save, wm->dig, sizeof(wm->dig));
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG);
+}
+
+static void wm9712_dig_restore(struct wm97xx *wm)
+{
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]);
+}
+
+static inline int is_pden(struct wm97xx *wm)
+{
+ return wm->dig[2] & WM9712_PDEN;
+}
+
+/*
+ * Read a sample from the WM9712 adc in polling mode.
+ */
+static int wm9712_poll_sample(struct wm97xx *wm, int adcsel, int *sample)
+{
+ int timeout = 5 * delay;
+
+ if (!wm->pen_probably_down) {
+ u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(data & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ if (adcsel & 0x8000)
+ adcsel = ((adcsel & 0x7fff) + 3) << 12;
+
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(adcsel);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1,
+ adcsel | WM97XX_POLL | WM97XX_DELAY(delay));
+
+ /* wait 3 AC97 time slots + delay for conversion */
+ poll_delay(delay);
+
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL)
+ && timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout <= 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout");
+ return RC_PENUP;
+ }
+
+ *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(adcsel);
+
+ /* check we have correct sample */
+ if ((*sample & WM97XX_ADCSEL_MASK) != adcsel) {
+ dev_dbg(wm->dev, "adc wrong sample, read %x got %x", adcsel,
+ *sample & WM97XX_ADCSEL_MASK);
+ return RC_PENUP;
+ }
+
+ if (!(*sample & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+
+ return RC_VALID;
+}
+
+/*
+ * Read a coord from the WM9712 adc in polling mode.
+ */
+static int wm9712_poll_coord(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ int timeout = 5 * delay;
+
+ if (!wm->pen_probably_down) {
+ u16 data_rd = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(data_rd & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y);
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1,
+ WM97XX_COO | WM97XX_POLL | WM97XX_DELAY(delay));
+
+ /* wait 3 AC97 time slots + delay for conversion and read x */
+ poll_delay(delay);
+ data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL)
+ && timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout <= 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout");
+ return RC_PENUP;
+ }
+
+ /* read back y data */
+ data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (pil)
+ data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ else
+ data->p = DEFAULT_PRESSURE;
+
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y);
+
+ /* check we have correct sample */
+ if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y))
+ goto err;
+ if (pil && !(data->p & WM97XX_ADCSEL_PRES))
+ goto err;
+
+ if (!(data->x & WM97XX_PEN_DOWN) || !(data->y & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+ return RC_VALID;
+err:
+ return 0;
+}
+
+/*
+ * Sample the WM9712 touchscreen in polling mode
+ */
+static int wm9712_poll_touch(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ int rc;
+
+ if (coord) {
+ rc = wm9712_poll_coord(wm, data);
+ if (rc != RC_VALID)
+ return rc;
+ } else {
+ rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_X, &data->x);
+ if (rc != RC_VALID)
+ return rc;
+
+ rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_Y, &data->y);
+ if (rc != RC_VALID)
+ return rc;
+
+ if (pil && !five_wire) {
+ rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_PRES,
+ &data->p);
+ if (rc != RC_VALID)
+ return rc;
+ } else
+ data->p = DEFAULT_PRESSURE;
+ }
+ return RC_VALID;
+}
+
+/*
+ * Enable WM9712 continuous mode, i.e. touch data is streamed across
+ * an AC97 slot
+ */
+static int wm9712_acc_enable(struct wm97xx *wm, int enable)
+{
+ u16 dig1, dig2;
+ int ret = 0;
+
+ dig1 = wm->dig[1];
+ dig2 = wm->dig[2];
+
+ if (enable) {
+ /* continous mode */
+ if (wm->mach_ops->acc_startup) {
+ ret = wm->mach_ops->acc_startup(wm);
+ if (ret < 0)
+ return ret;
+ }
+ dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK |
+ WM97XX_DELAY_MASK | WM97XX_SLT_MASK);
+ dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN |
+ WM97XX_DELAY(delay) |
+ WM97XX_SLT(wm->acc_slot) |
+ WM97XX_RATE(wm->acc_rate);
+ if (pil)
+ dig1 |= WM97XX_ADCSEL_PRES;
+ dig2 |= WM9712_PDEN;
+ } else {
+ dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN);
+ dig2 &= ~WM9712_PDEN;
+ if (wm->mach_ops->acc_shutdown)
+ wm->mach_ops->acc_shutdown(wm);
+ }
+
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1);
+ wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2);
+
+ return 0;
+}
+
+struct wm97xx_codec_drv wm9712_codec = {
+ .id = WM9712_ID2,
+ .name = "wm9712",
+ .poll_sample = wm9712_poll_sample,
+ .poll_touch = wm9712_poll_touch,
+ .acc_enable = wm9712_acc_enable,
+ .phy_init = wm9712_phy_init,
+ .dig_enable = wm9712_dig_enable,
+ .dig_restore = wm9712_dig_restore,
+ .aux_prepare = wm9712_aux_prepare,
+};
+EXPORT_SYMBOL_GPL(wm9712_codec);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <liam.girdwood@wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM9712 Touch Screen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/wm9713.c b/drivers/input/touchscreen/wm9713.c
new file mode 100644
index 00000000000..01278bd7e65
--- /dev/null
+++ b/drivers/input/touchscreen/wm9713.c
@@ -0,0 +1,460 @@
+/*
+ * wm9713.c -- Codec touch driver for Wolfson WM9713 AC97 Codec.
+ *
+ * Copyright 2003, 2004, 2005, 2006, 2007, 2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ * Russell King <rmk@arm.linux.org.uk>
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/wm97xx.h>
+
+#define TS_NAME "wm97xx"
+#define WM9713_VERSION "1.00"
+#define DEFAULT_PRESSURE 0xb0c0
+
+/*
+ * Module parameters
+ */
+
+/*
+ * Set internal pull up for pen detect.
+ *
+ * Pull up is in the range 1.02k (least sensitive) to 64k (most sensitive)
+ * i.e. pull up resistance = 64k Ohms / rpu.
+ *
+ * Adjust this value if you are having problems with pen detect not
+ * detecting any down event.
+ */
+static int rpu = 8;
+module_param(rpu, int, 0);
+MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect.");
+
+/*
+ * Set current used for pressure measurement.
+ *
+ * Set pil = 2 to use 400uA
+ * pil = 1 to use 200uA and
+ * pil = 0 to disable pressure measurement.
+ *
+ * This is used to increase the range of values returned by the adc
+ * when measureing touchpanel pressure.
+ */
+static int pil;
+module_param(pil, int, 0);
+MODULE_PARM_DESC(pil, "Set current used for pressure measurement.");
+
+/*
+ * Set threshold for pressure measurement.
+ *
+ * Pen down pressure below threshold is ignored.
+ */
+static int pressure = DEFAULT_PRESSURE & 0xfff;
+module_param(pressure, int, 0);
+MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement.");
+
+/*
+ * Set adc sample delay.
+ *
+ * For accurate touchpanel measurements, some settling time may be
+ * required between the switch matrix applying a voltage across the
+ * touchpanel plate and the ADC sampling the signal.
+ *
+ * This delay can be set by setting delay = n, where n is the array
+ * position of the delay in the array delay_table below.
+ * Long delays > 1ms are supported for completeness, but are not
+ * recommended.
+ */
+static int delay = 4;
+module_param(delay, int, 0);
+MODULE_PARM_DESC(delay, "Set adc sample delay.");
+
+/*
+ * Set adc mask function.
+ *
+ * Sources of glitch noise, such as signals driving an LCD display, may feed
+ * through to the touch screen plates and affect measurement accuracy. In
+ * order to minimise this, a signal may be applied to the MASK pin to delay or
+ * synchronise the sampling.
+ *
+ * 0 = No delay or sync
+ * 1 = High on pin stops conversions
+ * 2 = Edge triggered, edge on pin delays conversion by delay param (above)
+ * 3 = Edge triggered, edge on pin starts conversion after delay param
+ */
+static int mask;
+module_param(mask, int, 0);
+MODULE_PARM_DESC(mask, "Set adc mask function.");
+
+/*
+ * Coordinate Polling Enable.
+ *
+ * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together
+ * for every poll.
+ */
+static int coord;
+module_param(coord, int, 0);
+MODULE_PARM_DESC(coord, "Polling coordinate mode");
+
+/*
+ * ADC sample delay times in uS
+ */
+static const int delay_table[] = {
+ 21, /* 1 AC97 Link frames */
+ 42, /* 2 */
+ 84, /* 4 */
+ 167, /* 8 */
+ 333, /* 16 */
+ 667, /* 32 */
+ 1000, /* 48 */
+ 1333, /* 64 */
+ 2000, /* 96 */
+ 2667, /* 128 */
+ 3333, /* 160 */
+ 4000, /* 192 */
+ 4667, /* 224 */
+ 5333, /* 256 */
+ 6000, /* 288 */
+ 0 /* No delay, switch matrix always on */
+};
+
+/*
+ * Delay after issuing a POLL command.
+ *
+ * The delay is 3 AC97 link frames + the touchpanel settling delay
+ */
+static inline void poll_delay(int d)
+{
+ udelay(3 * AC97_LINK_FRAME + delay_table[d]);
+}
+
+/*
+ * set up the physical settings of the WM9713
+ */
+static void wm9713_phy_init(struct wm97xx *wm)
+{
+ u16 dig1 = 0, dig2, dig3;
+
+ /* default values */
+ dig2 = WM97XX_DELAY(4) | WM97XX_SLT(5);
+ dig3 = WM9712_RPU(1);
+
+ /* rpu */
+ if (rpu) {
+ dig3 &= 0xffc0;
+ dig3 |= WM9712_RPU(rpu);
+ dev_info(wm->dev, "setting pen detect pull-up to %d Ohms\n",
+ 64000 / rpu);
+ }
+
+ /* touchpanel pressure */
+ if (pil == 2) {
+ dig3 |= WM9712_PIL;
+ dev_info(wm->dev,
+ "setting pressure measurement current to 400uA.");
+ } else if (pil)
+ dev_info(wm->dev,
+ "setting pressure measurement current to 200uA.");
+ if (!pil)
+ pressure = 0;
+
+ /* sample settling delay */
+ if (delay < 0 || delay > 15) {
+ dev_info(wm->dev, "supplied delay out of range.");
+ delay = 4;
+ dev_info(wm->dev, "setting adc sample delay to %d u Secs.",
+ delay_table[delay]);
+ }
+ dig2 &= 0xff0f;
+ dig2 |= WM97XX_DELAY(delay);
+
+ /* mask */
+ dig3 |= ((mask & 0x3) << 4);
+ if (coord)
+ dig3 |= WM9713_WAIT;
+
+ wm->misc = wm97xx_reg_read(wm, 0x5a);
+
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3);
+ wm97xx_reg_write(wm, AC97_GPIO_STICKY, 0x0);
+}
+
+static void wm9713_dig_enable(struct wm97xx *wm, int enable)
+{
+ u16 val;
+
+ if (enable) {
+ val = wm97xx_reg_read(wm, AC97_EXTENDED_MID);
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, val & 0x7fff);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] |
+ WM97XX_PRP_DET_DIG);
+ wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */
+ } else {
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] &
+ ~WM97XX_PRP_DET_DIG);
+ val = wm97xx_reg_read(wm, AC97_EXTENDED_MID);
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, val | 0x8000);
+ }
+}
+
+static void wm9713_dig_restore(struct wm97xx *wm)
+{
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig_save[0]);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig_save[1]);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig_save[2]);
+}
+
+static void wm9713_aux_prepare(struct wm97xx *wm)
+{
+ memcpy(wm->dig_save, wm->dig, sizeof(wm->dig));
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, 0);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, 0);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, WM97XX_PRP_DET_DIG);
+}
+
+static inline int is_pden(struct wm97xx *wm)
+{
+ return wm->dig[2] & WM9713_PDEN;
+}
+
+/*
+ * Read a sample from the WM9713 adc in polling mode.
+ */
+static int wm9713_poll_sample(struct wm97xx *wm, int adcsel, int *sample)
+{
+ u16 dig1;
+ int timeout = 5 * delay;
+
+ if (!wm->pen_probably_down) {
+ u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(data & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ if (adcsel & 0x8000)
+ adcsel = 1 << ((adcsel & 0x7fff) + 3);
+
+ dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1);
+ dig1 &= ~WM9713_ADCSEL_MASK;
+
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(adcsel);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1 | adcsel | WM9713_POLL);
+
+ /* wait 3 AC97 time slots + delay for conversion */
+ poll_delay(delay);
+
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL) &&
+ timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout <= 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout");
+ return RC_PENUP;
+ }
+
+ *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(adcsel);
+
+ /* check we have correct sample */
+ if ((*sample & WM97XX_ADCSRC_MASK) != ffs(adcsel >> 1) << 12) {
+ dev_dbg(wm->dev, "adc wrong sample, read %x got %x", adcsel,
+ *sample & WM97XX_ADCSRC_MASK);
+ return RC_PENUP;
+ }
+
+ if (!(*sample & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+
+ return RC_VALID;
+}
+
+/*
+ * Read a coordinate from the WM9713 adc in polling mode.
+ */
+static int wm9713_poll_coord(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ u16 dig1;
+ int timeout = 5 * delay;
+
+ if (!wm->pen_probably_down) {
+ u16 val = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (!(val & WM97XX_PEN_DOWN))
+ return RC_PENUP;
+ wm->pen_probably_down = 1;
+ }
+
+ /* set up digitiser */
+ dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1);
+ dig1 &= ~WM9713_ADCSEL_MASK;
+ if (pil)
+ dig1 |= WM9713_ADCSEL_PRES;
+
+ if (wm->mach_ops && wm->mach_ops->pre_sample)
+ wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1,
+ dig1 | WM9713_POLL | WM9713_COO);
+
+ /* wait 3 AC97 time slots + delay for conversion */
+ poll_delay(delay);
+ data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ /* wait for POLL to go low */
+ while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL)
+ && timeout) {
+ udelay(AC97_LINK_FRAME);
+ timeout--;
+ }
+
+ if (timeout <= 0) {
+ /* If PDEN is set, we can get a timeout when pen goes up */
+ if (is_pden(wm))
+ wm->pen_probably_down = 0;
+ else
+ dev_dbg(wm->dev, "adc sample timeout");
+ return RC_PENUP;
+ }
+
+ /* read back data */
+ data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ if (pil)
+ data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD);
+ else
+ data->p = DEFAULT_PRESSURE;
+
+ if (wm->mach_ops && wm->mach_ops->post_sample)
+ wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y);
+
+ /* check we have correct sample */
+ if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y))
+ goto err;
+ if (pil && !(data->p & WM97XX_ADCSEL_PRES))
+ goto err;
+
+ if (!(data->x & WM97XX_PEN_DOWN) || !(data->y & WM97XX_PEN_DOWN)) {
+ wm->pen_probably_down = 0;
+ return RC_PENUP;
+ }
+ return RC_VALID;
+err:
+ return 0;
+}
+
+/*
+ * Sample the WM9713 touchscreen in polling mode
+ */
+static int wm9713_poll_touch(struct wm97xx *wm, struct wm97xx_data *data)
+{
+ int rc;
+
+ if (coord) {
+ rc = wm9713_poll_coord(wm, data);
+ if (rc != RC_VALID)
+ return rc;
+ } else {
+ rc = wm9713_poll_sample(wm, WM9713_ADCSEL_X, &data->x);
+ if (rc != RC_VALID)
+ return rc;
+ rc = wm9713_poll_sample(wm, WM9713_ADCSEL_Y, &data->y);
+ if (rc != RC_VALID)
+ return rc;
+ if (pil) {
+ rc = wm9713_poll_sample(wm, WM9713_ADCSEL_PRES,
+ &data->p);
+ if (rc != RC_VALID)
+ return rc;
+ } else
+ data->p = DEFAULT_PRESSURE;
+ }
+ return RC_VALID;
+}
+
+/*
+ * Enable WM9713 continuous mode, i.e. touch data is streamed across
+ * an AC97 slot
+ */
+static int wm9713_acc_enable(struct wm97xx *wm, int enable)
+{
+ u16 dig1, dig2, dig3;
+ int ret = 0;
+
+ dig1 = wm->dig[0];
+ dig2 = wm->dig[1];
+ dig3 = wm->dig[2];
+
+ if (enable) {
+ /* continous mode */
+ if (wm->mach_ops->acc_startup &&
+ (ret = wm->mach_ops->acc_startup(wm)) < 0)
+ return ret;
+
+ dig1 &= ~WM9713_ADCSEL_MASK;
+ dig1 |= WM9713_CTC | WM9713_COO | WM9713_ADCSEL_X |
+ WM9713_ADCSEL_Y;
+ if (pil)
+ dig1 |= WM9713_ADCSEL_PRES;
+ dig2 &= ~(WM97XX_DELAY_MASK | WM97XX_SLT_MASK |
+ WM97XX_CM_RATE_MASK);
+ dig2 |= WM97XX_SLEN | WM97XX_DELAY(delay) |
+ WM97XX_SLT(wm->acc_slot) | WM97XX_RATE(wm->acc_rate);
+ dig3 |= WM9713_PDEN;
+ } else {
+ dig1 &= ~(WM9713_CTC | WM9713_COO);
+ dig2 &= ~WM97XX_SLEN;
+ dig3 &= ~WM9713_PDEN;
+ if (wm->mach_ops->acc_shutdown)
+ wm->mach_ops->acc_shutdown(wm);
+ }
+
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3);
+
+ return ret;
+}
+
+struct wm97xx_codec_drv wm9713_codec = {
+ .id = WM9713_ID2,
+ .name = "wm9713",
+ .poll_sample = wm9713_poll_sample,
+ .poll_touch = wm9713_poll_touch,
+ .acc_enable = wm9713_acc_enable,
+ .phy_init = wm9713_phy_init,
+ .dig_enable = wm9713_dig_enable,
+ .dig_restore = wm9713_dig_restore,
+ .aux_prepare = wm9713_aux_prepare,
+};
+EXPORT_SYMBOL_GPL(wm9713_codec);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <liam.girdwood@wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM9713 Touch Screen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/wm97xx-core.c b/drivers/input/touchscreen/wm97xx-core.c
new file mode 100644
index 00000000000..e9c7ea46b6e
--- /dev/null
+++ b/drivers/input/touchscreen/wm97xx-core.c
@@ -0,0 +1,829 @@
+/*
+ * wm97xx-core.c -- Touch screen driver core for Wolfson WM9705, WM9712
+ * and WM9713 AC97 Codecs.
+ *
+ * Copyright 2003, 2004, 2005, 2006, 2007, 2008 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ * Parts Copyright : Ian Molton <spyro@f2s.com>
+ * Andrew Zabolotny <zap@homelink.ru>
+ * Russell King <rmk@arm.linux.org.uk>
+ *
+ * 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.
+ *
+ * Notes:
+ *
+ * Features:
+ * - supports WM9705, WM9712, WM9713
+ * - polling mode
+ * - continuous mode (arch-dependent)
+ * - adjustable rpu/dpp settings
+ * - adjustable pressure current
+ * - adjustable sample settle delay
+ * - 4 and 5 wire touchscreens (5 wire is WM9712 only)
+ * - pen down detection
+ * - battery monitor
+ * - sample AUX adcs
+ * - power management
+ * - codec GPIO
+ * - codec event notification
+ * Todo
+ * - Support for async sampling control for noisy LCDs.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/proc_fs.h>
+#include <linux/pm.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/workqueue.h>
+#include <linux/wm97xx.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#define TS_NAME "wm97xx"
+#define WM_CORE_VERSION "1.00"
+#define DEFAULT_PRESSURE 0xb0c0
+
+
+/*
+ * Touchscreen absolute values
+ *
+ * These parameters are used to help the input layer discard out of
+ * range readings and reduce jitter etc.
+ *
+ * o min, max:- indicate the min and max values your touch screen returns
+ * o fuzz:- use a higher number to reduce jitter
+ *
+ * The default values correspond to Mainstone II in QVGA mode
+ *
+ * Please read
+ * Documentation/input/input-programming.txt for more details.
+ */
+
+static int abs_x[3] = {350, 3900, 5};
+module_param_array(abs_x, int, NULL, 0);
+MODULE_PARM_DESC(abs_x, "Touchscreen absolute X min, max, fuzz");
+
+static int abs_y[3] = {320, 3750, 40};
+module_param_array(abs_y, int, NULL, 0);
+MODULE_PARM_DESC(abs_y, "Touchscreen absolute Y min, max, fuzz");
+
+static int abs_p[3] = {0, 150, 4};
+module_param_array(abs_p, int, NULL, 0);
+MODULE_PARM_DESC(abs_p, "Touchscreen absolute Pressure min, max, fuzz");
+
+/*
+ * wm97xx IO access, all IO locking done by AC97 layer
+ */
+int wm97xx_reg_read(struct wm97xx *wm, u16 reg)
+{
+ if (wm->ac97)
+ return wm->ac97->bus->ops->read(wm->ac97, reg);
+ else
+ return -1;
+}
+EXPORT_SYMBOL_GPL(wm97xx_reg_read);
+
+void wm97xx_reg_write(struct wm97xx *wm, u16 reg, u16 val)
+{
+ /* cache digitiser registers */
+ if (reg >= AC97_WM9713_DIG1 && reg <= AC97_WM9713_DIG3)
+ wm->dig[(reg - AC97_WM9713_DIG1) >> 1] = val;
+
+ /* cache gpio regs */
+ if (reg >= AC97_GPIO_CFG && reg <= AC97_MISC_AFE)
+ wm->gpio[(reg - AC97_GPIO_CFG) >> 1] = val;
+
+ /* wm9713 irq reg */
+ if (reg == 0x5a)
+ wm->misc = val;
+
+ if (wm->ac97)
+ wm->ac97->bus->ops->write(wm->ac97, reg, val);
+}
+EXPORT_SYMBOL_GPL(wm97xx_reg_write);
+
+/**
+ * wm97xx_read_aux_adc - Read the aux adc.
+ * @wm: wm97xx device.
+ * @adcsel: codec ADC to be read
+ *
+ * Reads the selected AUX ADC.
+ */
+
+int wm97xx_read_aux_adc(struct wm97xx *wm, u16 adcsel)
+{
+ int power_adc = 0, auxval;
+ u16 power = 0;
+
+ /* get codec */
+ mutex_lock(&wm->codec_mutex);
+
+ /* When the touchscreen is not in use, we may have to power up
+ * the AUX ADC before we can use sample the AUX inputs->
+ */
+ if (wm->id == WM9713_ID2 &&
+ (power = wm97xx_reg_read(wm, AC97_EXTENDED_MID)) & 0x8000) {
+ power_adc = 1;
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, power & 0x7fff);
+ }
+
+ /* Prepare the codec for AUX reading */
+ wm->codec->aux_prepare(wm);
+
+ /* Turn polling mode on to read AUX ADC */
+ wm->pen_probably_down = 1;
+ wm->codec->poll_sample(wm, adcsel, &auxval);
+
+ if (power_adc)
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, power | 0x8000);
+
+ wm->codec->dig_restore(wm);
+
+ wm->pen_probably_down = 0;
+
+ mutex_unlock(&wm->codec_mutex);
+ return auxval & 0xfff;
+}
+EXPORT_SYMBOL_GPL(wm97xx_read_aux_adc);
+
+/**
+ * wm97xx_get_gpio - Get the status of a codec GPIO.
+ * @wm: wm97xx device.
+ * @gpio: gpio
+ *
+ * Get the status of a codec GPIO pin
+ */
+
+enum wm97xx_gpio_status wm97xx_get_gpio(struct wm97xx *wm, u32 gpio)
+{
+ u16 status;
+ enum wm97xx_gpio_status ret;
+
+ mutex_lock(&wm->codec_mutex);
+ status = wm97xx_reg_read(wm, AC97_GPIO_STATUS);
+
+ if (status & gpio)
+ ret = WM97XX_GPIO_HIGH;
+ else
+ ret = WM97XX_GPIO_LOW;
+
+ mutex_unlock(&wm->codec_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm97xx_get_gpio);
+
+/**
+ * wm97xx_set_gpio - Set the status of a codec GPIO.
+ * @wm: wm97xx device.
+ * @gpio: gpio
+ *
+ *
+ * Set the status of a codec GPIO pin
+ */
+
+void wm97xx_set_gpio(struct wm97xx *wm, u32 gpio,
+ enum wm97xx_gpio_status status)
+{
+ u16 reg;
+
+ mutex_lock(&wm->codec_mutex);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_STATUS);
+
+ if (status & WM97XX_GPIO_HIGH)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ if (wm->id == WM9712_ID2)
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg << 1);
+ else
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg);
+ mutex_unlock(&wm->codec_mutex);
+}
+EXPORT_SYMBOL_GPL(wm97xx_set_gpio);
+
+/*
+ * Codec GPIO pin configuration, this sets pin direction, polarity,
+ * stickyness and wake up.
+ */
+void wm97xx_config_gpio(struct wm97xx *wm, u32 gpio, enum wm97xx_gpio_dir dir,
+ enum wm97xx_gpio_pol pol, enum wm97xx_gpio_sticky sticky,
+ enum wm97xx_gpio_wake wake)
+{
+ u16 reg;
+
+ mutex_lock(&wm->codec_mutex);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_POLARITY);
+
+ if (pol == WM97XX_GPIO_POL_HIGH)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ wm97xx_reg_write(wm, AC97_GPIO_POLARITY, reg);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_STICKY);
+
+ if (sticky == WM97XX_GPIO_STICKY)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ wm97xx_reg_write(wm, AC97_GPIO_STICKY, reg);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP);
+
+ if (wake == WM97XX_GPIO_WAKE)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, reg);
+ reg = wm97xx_reg_read(wm, AC97_GPIO_CFG);
+
+ if (dir == WM97XX_GPIO_IN)
+ reg |= gpio;
+ else
+ reg &= ~gpio;
+
+ wm97xx_reg_write(wm, AC97_GPIO_CFG, reg);
+ mutex_unlock(&wm->codec_mutex);
+}
+EXPORT_SYMBOL_GPL(wm97xx_config_gpio);
+
+/*
+ * Configure the WM97XX_PRP value to use while system is suspended.
+ * If a value other than 0 is set then WM97xx pen detection will be
+ * left enabled in the configured mode while the system is in suspend,
+ * the device has users and suspend has not been disabled via the
+ * wakeup sysfs entries.
+ *
+ * @wm: WM97xx device to configure
+ * @mode: WM97XX_PRP value to configure while suspended
+ */
+void wm97xx_set_suspend_mode(struct wm97xx *wm, u16 mode)
+{
+ wm->suspend_mode = mode;
+ device_init_wakeup(&wm->input_dev->dev, mode != 0);
+}
+EXPORT_SYMBOL_GPL(wm97xx_set_suspend_mode);
+
+/*
+ * Handle a pen down interrupt.
+ */
+static void wm97xx_pen_irq_worker(struct work_struct *work)
+{
+ struct wm97xx *wm = container_of(work, struct wm97xx, pen_event_work);
+ int pen_was_down = wm->pen_is_down;
+
+ /* do we need to enable the touch panel reader */
+ if (wm->id == WM9705_ID2) {
+ if (wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD) &
+ WM97XX_PEN_DOWN)
+ wm->pen_is_down = 1;
+ else
+ wm->pen_is_down = 0;
+ } else {
+ u16 status, pol;
+ mutex_lock(&wm->codec_mutex);
+ status = wm97xx_reg_read(wm, AC97_GPIO_STATUS);
+ pol = wm97xx_reg_read(wm, AC97_GPIO_POLARITY);
+
+ if (WM97XX_GPIO_13 & pol & status) {
+ wm->pen_is_down = 1;
+ wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol &
+ ~WM97XX_GPIO_13);
+ } else {
+ wm->pen_is_down = 0;
+ wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol |
+ WM97XX_GPIO_13);
+ }
+
+ if (wm->id == WM9712_ID2)
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, (status &
+ ~WM97XX_GPIO_13) << 1);
+ else
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, status &
+ ~WM97XX_GPIO_13);
+ mutex_unlock(&wm->codec_mutex);
+ }
+
+ /* If the system is not using continuous mode or it provides a
+ * pen down operation then we need to schedule polls while the
+ * pen is down. Otherwise the machine driver is responsible
+ * for scheduling reads.
+ */
+ if (!wm->mach_ops->acc_enabled || wm->mach_ops->acc_pen_down) {
+ if (wm->pen_is_down && !pen_was_down) {
+ /* Data is not availiable immediately on pen down */
+ queue_delayed_work(wm->ts_workq, &wm->ts_reader, 1);
+ }
+
+ /* Let ts_reader report the pen up for debounce. */
+ if (!wm->pen_is_down && pen_was_down)
+ wm->pen_is_down = 1;
+ }
+
+ if (!wm->pen_is_down && wm->mach_ops->acc_enabled)
+ wm->mach_ops->acc_pen_up(wm);
+
+ wm->mach_ops->irq_enable(wm, 1);
+}
+
+/*
+ * Codec PENDOWN irq handler
+ *
+ * We have to disable the codec interrupt in the handler because it
+ * can take upto 1ms to clear the interrupt source. We schedule a task
+ * in a work queue to do the actual interaction with the chip. The
+ * interrupt is then enabled again in the slow handler when the source
+ * has been cleared.
+ */
+static irqreturn_t wm97xx_pen_interrupt(int irq, void *dev_id)
+{
+ struct wm97xx *wm = dev_id;
+
+ if (!work_pending(&wm->pen_event_work)) {
+ wm->mach_ops->irq_enable(wm, 0);
+ queue_work(wm->ts_workq, &wm->pen_event_work);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * initialise pen IRQ handler and workqueue
+ */
+static int wm97xx_init_pen_irq(struct wm97xx *wm)
+{
+ u16 reg;
+
+ /* If an interrupt is supplied an IRQ enable operation must also be
+ * provided. */
+ BUG_ON(!wm->mach_ops->irq_enable);
+
+ if (request_irq(wm->pen_irq, wm97xx_pen_interrupt,
+ IRQF_SHARED | IRQF_SAMPLE_RANDOM,
+ "wm97xx-pen", wm)) {
+ dev_err(wm->dev,
+ "Failed to register pen down interrupt, polling");
+ wm->pen_irq = 0;
+ return -EINVAL;
+ }
+
+ /* Configure GPIO as interrupt source on WM971x */
+ if (wm->id != WM9705_ID2) {
+ BUG_ON(!wm->mach_ops->irq_gpio);
+ reg = wm97xx_reg_read(wm, AC97_MISC_AFE);
+ wm97xx_reg_write(wm, AC97_MISC_AFE,
+ reg & ~(wm->mach_ops->irq_gpio));
+ reg = wm97xx_reg_read(wm, 0x5a);
+ wm97xx_reg_write(wm, 0x5a, reg & ~0x0001);
+ }
+
+ return 0;
+}
+
+static int wm97xx_read_samples(struct wm97xx *wm)
+{
+ struct wm97xx_data data;
+ int rc;
+
+ mutex_lock(&wm->codec_mutex);
+
+ if (wm->mach_ops && wm->mach_ops->acc_enabled)
+ rc = wm->mach_ops->acc_pen_down(wm);
+ else
+ rc = wm->codec->poll_touch(wm, &data);
+
+ if (rc & RC_PENUP) {
+ if (wm->pen_is_down) {
+ wm->pen_is_down = 0;
+ dev_dbg(wm->dev, "pen up\n");
+ input_report_abs(wm->input_dev, ABS_PRESSURE, 0);
+ input_sync(wm->input_dev);
+ } else if (!(rc & RC_AGAIN)) {
+ /* We need high frequency updates only while
+ * pen is down, the user never will be able to
+ * touch screen faster than a few times per
+ * second... On the other hand, when the user
+ * is actively working with the touchscreen we
+ * don't want to lose the quick response. So we
+ * will slowly increase sleep time after the
+ * pen is up and quicky restore it to ~one task
+ * switch when pen is down again.
+ */
+ if (wm->ts_reader_interval < HZ / 10)
+ wm->ts_reader_interval++;
+ }
+
+ } else if (rc & RC_VALID) {
+ dev_dbg(wm->dev,
+ "pen down: x=%x:%d, y=%x:%d, pressure=%x:%d\n",
+ data.x >> 12, data.x & 0xfff, data.y >> 12,
+ data.y & 0xfff, data.p >> 12, data.p & 0xfff);
+ input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff);
+ input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff);
+ input_report_abs(wm->input_dev, ABS_PRESSURE, data.p & 0xfff);
+ input_sync(wm->input_dev);
+ wm->pen_is_down = 1;
+ wm->ts_reader_interval = wm->ts_reader_min_interval;
+ } else if (rc & RC_PENDOWN) {
+ dev_dbg(wm->dev, "pen down\n");
+ wm->pen_is_down = 1;
+ wm->ts_reader_interval = wm->ts_reader_min_interval;
+ }
+
+ mutex_unlock(&wm->codec_mutex);
+ return rc;
+}
+
+/*
+* The touchscreen sample reader.
+*/
+static void wm97xx_ts_reader(struct work_struct *work)
+{
+ int rc;
+ struct wm97xx *wm = container_of(work, struct wm97xx, ts_reader.work);
+
+ BUG_ON(!wm->codec);
+
+ do {
+ rc = wm97xx_read_samples(wm);
+ } while (rc & RC_AGAIN);
+
+ if (wm->pen_is_down || !wm->pen_irq)
+ queue_delayed_work(wm->ts_workq, &wm->ts_reader,
+ wm->ts_reader_interval);
+}
+
+/**
+ * wm97xx_ts_input_open - Open the touch screen input device.
+ * @idev: Input device to be opened.
+ *
+ * Called by the input sub system to open a wm97xx touchscreen device.
+ * Starts the touchscreen thread and touch digitiser.
+ */
+static int wm97xx_ts_input_open(struct input_dev *idev)
+{
+ struct wm97xx *wm = input_get_drvdata(idev);
+
+ wm->ts_workq = create_singlethread_workqueue("kwm97xx");
+ if (wm->ts_workq == NULL) {
+ dev_err(wm->dev,
+ "Failed to create workqueue\n");
+ return -EINVAL;
+ }
+
+ /* start digitiser */
+ if (wm->mach_ops && wm->mach_ops->acc_enabled)
+ wm->codec->acc_enable(wm, 1);
+ wm->codec->dig_enable(wm, 1);
+
+ INIT_DELAYED_WORK(&wm->ts_reader, wm97xx_ts_reader);
+ INIT_WORK(&wm->pen_event_work, wm97xx_pen_irq_worker);
+
+ wm->ts_reader_min_interval = HZ >= 100 ? HZ / 100 : 1;
+ if (wm->ts_reader_min_interval < 1)
+ wm->ts_reader_min_interval = 1;
+ wm->ts_reader_interval = wm->ts_reader_min_interval;
+
+ wm->pen_is_down = 0;
+ if (wm->pen_irq)
+ wm97xx_init_pen_irq(wm);
+ else
+ dev_err(wm->dev, "No IRQ specified\n");
+
+ /* If we either don't have an interrupt for pen down events or
+ * failed to acquire it then we need to poll.
+ */
+ if (wm->pen_irq == 0)
+ queue_delayed_work(wm->ts_workq, &wm->ts_reader,
+ wm->ts_reader_interval);
+
+ return 0;
+}
+
+/**
+ * wm97xx_ts_input_close - Close the touch screen input device.
+ * @idev: Input device to be closed.
+ *
+ * Called by the input sub system to close a wm97xx touchscreen
+ * device. Kills the touchscreen thread and stops the touch
+ * digitiser.
+ */
+
+static void wm97xx_ts_input_close(struct input_dev *idev)
+{
+ struct wm97xx *wm = input_get_drvdata(idev);
+ u16 reg;
+
+ if (wm->pen_irq) {
+ /* Return the interrupt to GPIO usage (disabling it) */
+ if (wm->id != WM9705_ID2) {
+ BUG_ON(!wm->mach_ops->irq_gpio);
+ reg = wm97xx_reg_read(wm, AC97_MISC_AFE);
+ wm97xx_reg_write(wm, AC97_MISC_AFE,
+ reg | wm->mach_ops->irq_gpio);
+ }
+
+ free_irq(wm->pen_irq, wm);
+ }
+
+ wm->pen_is_down = 0;
+
+ /* Balance out interrupt disables/enables */
+ if (cancel_work_sync(&wm->pen_event_work))
+ wm->mach_ops->irq_enable(wm, 1);
+
+ /* ts_reader rearms itself so we need to explicitly stop it
+ * before we destroy the workqueue.
+ */
+ cancel_delayed_work_sync(&wm->ts_reader);
+
+ destroy_workqueue(wm->ts_workq);
+
+ /* stop digitiser */
+ wm->codec->dig_enable(wm, 0);
+ if (wm->mach_ops && wm->mach_ops->acc_enabled)
+ wm->codec->acc_enable(wm, 0);
+}
+
+static int wm97xx_probe(struct device *dev)
+{
+ struct wm97xx *wm;
+ int ret = 0, id = 0;
+
+ wm = kzalloc(sizeof(struct wm97xx), GFP_KERNEL);
+ if (!wm)
+ return -ENOMEM;
+ mutex_init(&wm->codec_mutex);
+
+ wm->dev = dev;
+ dev->driver_data = wm;
+ wm->ac97 = to_ac97_t(dev);
+
+ /* check that we have a supported codec */
+ id = wm97xx_reg_read(wm, AC97_VENDOR_ID1);
+ if (id != WM97XX_ID1) {
+ dev_err(dev, "Device with vendor %04x is not a wm97xx\n", id);
+ ret = -ENODEV;
+ goto alloc_err;
+ }
+
+ wm->id = wm97xx_reg_read(wm, AC97_VENDOR_ID2);
+
+ dev_info(wm->dev, "detected a wm97%02x codec\n", wm->id & 0xff);
+
+ switch (wm->id & 0xff) {
+#ifdef CONFIG_TOUCHSCREEN_WM9705
+ case 0x05:
+ wm->codec = &wm9705_codec;
+ break;
+#endif
+#ifdef CONFIG_TOUCHSCREEN_WM9712
+ case 0x12:
+ wm->codec = &wm9712_codec;
+ break;
+#endif
+#ifdef CONFIG_TOUCHSCREEN_WM9713
+ case 0x13:
+ wm->codec = &wm9713_codec;
+ break;
+#endif
+ default:
+ dev_err(wm->dev, "Support for wm97%02x not compiled in.\n",
+ wm->id & 0xff);
+ ret = -ENODEV;
+ goto alloc_err;
+ }
+
+ wm->input_dev = input_allocate_device();
+ if (wm->input_dev == NULL) {
+ ret = -ENOMEM;
+ goto alloc_err;
+ }
+
+ /* set up touch configuration */
+ wm->input_dev->name = "wm97xx touchscreen";
+ wm->input_dev->open = wm97xx_ts_input_open;
+ wm->input_dev->close = wm97xx_ts_input_close;
+ set_bit(EV_ABS, wm->input_dev->evbit);
+ set_bit(ABS_X, wm->input_dev->absbit);
+ set_bit(ABS_Y, wm->input_dev->absbit);
+ set_bit(ABS_PRESSURE, wm->input_dev->absbit);
+ input_set_abs_params(wm->input_dev, ABS_X, abs_x[0], abs_x[1],
+ abs_x[2], 0);
+ input_set_abs_params(wm->input_dev, ABS_Y, abs_y[0], abs_y[1],
+ abs_y[2], 0);
+ input_set_abs_params(wm->input_dev, ABS_PRESSURE, abs_p[0], abs_p[1],
+ abs_p[2], 0);
+ input_set_drvdata(wm->input_dev, wm);
+ wm->input_dev->dev.parent = dev;
+ ret = input_register_device(wm->input_dev);
+ if (ret < 0)
+ goto dev_alloc_err;
+
+ /* set up physical characteristics */
+ wm->codec->phy_init(wm);
+
+ /* load gpio cache */
+ wm->gpio[0] = wm97xx_reg_read(wm, AC97_GPIO_CFG);
+ wm->gpio[1] = wm97xx_reg_read(wm, AC97_GPIO_POLARITY);
+ wm->gpio[2] = wm97xx_reg_read(wm, AC97_GPIO_STICKY);
+ wm->gpio[3] = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP);
+ wm->gpio[4] = wm97xx_reg_read(wm, AC97_GPIO_STATUS);
+ wm->gpio[5] = wm97xx_reg_read(wm, AC97_MISC_AFE);
+
+ /* register our battery device */
+ wm->battery_dev = platform_device_alloc("wm97xx-battery", -1);
+ if (!wm->battery_dev) {
+ ret = -ENOMEM;
+ goto batt_err;
+ }
+ platform_set_drvdata(wm->battery_dev, wm);
+ wm->battery_dev->dev.parent = dev;
+ ret = platform_device_add(wm->battery_dev);
+ if (ret < 0)
+ goto batt_reg_err;
+
+ /* register our extended touch device (for machine specific
+ * extensions) */
+ wm->touch_dev = platform_device_alloc("wm97xx-touch", -1);
+ if (!wm->touch_dev) {
+ ret = -ENOMEM;
+ goto touch_err;
+ }
+ platform_set_drvdata(wm->touch_dev, wm);
+ wm->touch_dev->dev.parent = dev;
+ ret = platform_device_add(wm->touch_dev);
+ if (ret < 0)
+ goto touch_reg_err;
+
+ return ret;
+
+ touch_reg_err:
+ platform_device_put(wm->touch_dev);
+ touch_err:
+ platform_device_unregister(wm->battery_dev);
+ wm->battery_dev = NULL;
+ batt_reg_err:
+ platform_device_put(wm->battery_dev);
+ batt_err:
+ input_unregister_device(wm->input_dev);
+ wm->input_dev = NULL;
+ dev_alloc_err:
+ input_free_device(wm->input_dev);
+ alloc_err:
+ kfree(wm);
+
+ return ret;
+}
+
+static int wm97xx_remove(struct device *dev)
+{
+ struct wm97xx *wm = dev_get_drvdata(dev);
+
+ platform_device_unregister(wm->battery_dev);
+ platform_device_unregister(wm->touch_dev);
+ input_unregister_device(wm->input_dev);
+ kfree(wm);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int wm97xx_suspend(struct device *dev, pm_message_t state)
+{
+ struct wm97xx *wm = dev_get_drvdata(dev);
+ u16 reg;
+ int suspend_mode;
+
+ if (device_may_wakeup(&wm->input_dev->dev))
+ suspend_mode = wm->suspend_mode;
+ else
+ suspend_mode = 0;
+
+ if (wm->input_dev->users)
+ cancel_delayed_work_sync(&wm->ts_reader);
+
+ /* Power down the digitiser (bypassing the cache for resume) */
+ reg = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER2);
+ reg &= ~WM97XX_PRP_DET_DIG;
+ if (wm->input_dev->users)
+ reg |= suspend_mode;
+ wm->ac97->bus->ops->write(wm->ac97, AC97_WM97XX_DIGITISER2, reg);
+
+ /* WM9713 has an additional power bit - turn it off if there
+ * are no users or if suspend mode is zero. */
+ if (wm->id == WM9713_ID2 &&
+ (!wm->input_dev->users || !suspend_mode)) {
+ reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) | 0x8000;
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg);
+ }
+
+ return 0;
+}
+
+static int wm97xx_resume(struct device *dev)
+{
+ struct wm97xx *wm = dev_get_drvdata(dev);
+
+ /* restore digitiser and gpios */
+ if (wm->id == WM9713_ID2) {
+ wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig[0]);
+ wm97xx_reg_write(wm, 0x5a, wm->misc);
+ if (wm->input_dev->users) {
+ u16 reg;
+ reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) & 0x7fff;
+ wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg);
+ }
+ }
+
+ wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig[1]);
+ wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2]);
+
+ wm97xx_reg_write(wm, AC97_GPIO_CFG, wm->gpio[0]);
+ wm97xx_reg_write(wm, AC97_GPIO_POLARITY, wm->gpio[1]);
+ wm97xx_reg_write(wm, AC97_GPIO_STICKY, wm->gpio[2]);
+ wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, wm->gpio[3]);
+ wm97xx_reg_write(wm, AC97_GPIO_STATUS, wm->gpio[4]);
+ wm97xx_reg_write(wm, AC97_MISC_AFE, wm->gpio[5]);
+
+ if (wm->input_dev->users && !wm->pen_irq) {
+ wm->ts_reader_interval = wm->ts_reader_min_interval;
+ queue_delayed_work(wm->ts_workq, &wm->ts_reader,
+ wm->ts_reader_interval);
+ }
+
+ return 0;
+}
+
+#else
+#define wm97xx_suspend NULL
+#define wm97xx_resume NULL
+#endif
+
+/*
+ * Machine specific operations
+ */
+int wm97xx_register_mach_ops(struct wm97xx *wm,
+ struct wm97xx_mach_ops *mach_ops)
+{
+ mutex_lock(&wm->codec_mutex);
+ if (wm->mach_ops) {
+ mutex_unlock(&wm->codec_mutex);
+ return -EINVAL;
+ }
+ wm->mach_ops = mach_ops;
+ mutex_unlock(&wm->codec_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm97xx_register_mach_ops);
+
+void wm97xx_unregister_mach_ops(struct wm97xx *wm)
+{
+ mutex_lock(&wm->codec_mutex);
+ wm->mach_ops = NULL;
+ mutex_unlock(&wm->codec_mutex);
+}
+EXPORT_SYMBOL_GPL(wm97xx_unregister_mach_ops);
+
+static struct device_driver wm97xx_driver = {
+ .name = "ac97",
+ .bus = &ac97_bus_type,
+ .owner = THIS_MODULE,
+ .probe = wm97xx_probe,
+ .remove = wm97xx_remove,
+ .suspend = wm97xx_suspend,
+ .resume = wm97xx_resume,
+};
+
+static int __init wm97xx_init(void)
+{
+ return driver_register(&wm97xx_driver);
+}
+
+static void __exit wm97xx_exit(void)
+{
+ driver_unregister(&wm97xx_driver);
+}
+
+module_init(wm97xx_init);
+module_exit(wm97xx_exit);
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood <liam.girdwood@wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM97xx Core - Touch Screen / AUX ADC / GPIO Driver");
+MODULE_LICENSE("GPL");