1 /* 2 * HTC Shift touchscreen driver 3 * 4 * Copyright (C) 2008 Pau Oliva Fora <pof@eslack.org> 5 * 6 * This program is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 as published 8 * by the Free Software Foundation. 9 */ 10 11 #include <linux/errno.h> 12 #include <linux/kernel.h> 13 #include <linux/module.h> 14 #include <linux/input.h> 15 #include <linux/interrupt.h> 16 #include <linux/io.h> 17 #include <linux/init.h> 18 #include <linux/irq.h> 19 #include <linux/isa.h> 20 #include <linux/ioport.h> 21 #include <linux/dmi.h> 22 23 MODULE_AUTHOR("Pau Oliva Fora <pau@eslack.org>"); 24 MODULE_DESCRIPTION("HTC Shift touchscreen driver"); 25 MODULE_LICENSE("GPL"); 26 27 #define HTCPEN_PORT_IRQ_CLEAR 0x068 28 #define HTCPEN_PORT_INIT 0x06c 29 #define HTCPEN_PORT_INDEX 0x0250 30 #define HTCPEN_PORT_DATA 0x0251 31 #define HTCPEN_IRQ 3 32 33 #define DEVICE_ENABLE 0xa2 34 #define DEVICE_DISABLE 0xa3 35 36 #define X_INDEX 3 37 #define Y_INDEX 5 38 #define TOUCH_INDEX 0xb 39 #define LSB_XY_INDEX 0xc 40 #define X_AXIS_MAX 2040 41 #define Y_AXIS_MAX 2040 42 43 static int invert_x; 44 module_param(invert_x, bool, 0644); 45 MODULE_PARM_DESC(invert_x, "If set, X axis is inverted"); 46 static int invert_y; 47 module_param(invert_y, bool, 0644); 48 MODULE_PARM_DESC(invert_y, "If set, Y axis is inverted"); 49 50 static struct pnp_device_id pnp_ids[] = { 51 { .id = "PNP0cc0" }, 52 { .id = "" } 53 }; 54 MODULE_DEVICE_TABLE(pnp, pnp_ids); 55 56 static irqreturn_t htcpen_interrupt(int irq, void *handle) 57 { 58 struct input_dev *htcpen_dev = handle; 59 unsigned short x, y, xy; 60 61 /* 0 = press; 1 = release */ 62 outb_p(TOUCH_INDEX, HTCPEN_PORT_INDEX); 63 64 if (inb_p(HTCPEN_PORT_DATA)) { 65 input_report_key(htcpen_dev, BTN_TOUCH, 0); 66 } else { 67 outb_p(X_INDEX, HTCPEN_PORT_INDEX); 68 x = inb_p(HTCPEN_PORT_DATA); 69 70 outb_p(Y_INDEX, HTCPEN_PORT_INDEX); 71 y = inb_p(HTCPEN_PORT_DATA); 72 73 outb_p(LSB_XY_INDEX, HTCPEN_PORT_INDEX); 74 xy = inb_p(HTCPEN_PORT_DATA); 75 76 /* get high resolution value of X and Y using LSB */ 77 x = X_AXIS_MAX - ((x * 8) + ((xy >> 4) & 0xf)); 78 y = (y * 8) + (xy & 0xf); 79 if (invert_x) 80 x = X_AXIS_MAX - x; 81 if (invert_y) 82 y = Y_AXIS_MAX - y; 83 84 if (x != X_AXIS_MAX && x != 0) { 85 input_report_key(htcpen_dev, BTN_TOUCH, 1); 86 input_report_abs(htcpen_dev, ABS_X, x); 87 input_report_abs(htcpen_dev, ABS_Y, y); 88 } 89 } 90 91 input_sync(htcpen_dev); 92 93 inb_p(HTCPEN_PORT_IRQ_CLEAR); 94 95 return IRQ_HANDLED; 96 } 97 98 static int htcpen_open(struct input_dev *dev) 99 { 100 outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); 101 102 return 0; 103 } 104 105 static void htcpen_close(struct input_dev *dev) 106 { 107 outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); 108 synchronize_irq(HTCPEN_IRQ); 109 } 110 111 static int __devinit htcpen_isa_probe(struct device *dev, unsigned int id) 112 { 113 struct input_dev *htcpen_dev; 114 int err = -EBUSY; 115 116 if (!request_region(HTCPEN_PORT_IRQ_CLEAR, 1, "htcpen")) { 117 printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 118 HTCPEN_PORT_IRQ_CLEAR); 119 goto request_region1_failed; 120 } 121 122 if (!request_region(HTCPEN_PORT_INIT, 1, "htcpen")) { 123 printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 124 HTCPEN_PORT_INIT); 125 goto request_region2_failed; 126 } 127 128 if (!request_region(HTCPEN_PORT_INDEX, 2, "htcpen")) { 129 printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 130 HTCPEN_PORT_INDEX); 131 goto request_region3_failed; 132 } 133 134 htcpen_dev = input_allocate_device(); 135 if (!htcpen_dev) { 136 printk(KERN_ERR "htcpen: can't allocate device\n"); 137 err = -ENOMEM; 138 goto input_alloc_failed; 139 } 140 141 htcpen_dev->name = "HTC Shift EC TouchScreen"; 142 htcpen_dev->id.bustype = BUS_ISA; 143 144 htcpen_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); 145 htcpen_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); 146 input_set_abs_params(htcpen_dev, ABS_X, 0, X_AXIS_MAX, 0, 0); 147 input_set_abs_params(htcpen_dev, ABS_Y, 0, Y_AXIS_MAX, 0, 0); 148 149 htcpen_dev->open = htcpen_open; 150 htcpen_dev->close = htcpen_close; 151 152 err = request_irq(HTCPEN_IRQ, htcpen_interrupt, 0, "htcpen", 153 htcpen_dev); 154 if (err) { 155 printk(KERN_ERR "htcpen: irq busy\n"); 156 goto request_irq_failed; 157 } 158 159 inb_p(HTCPEN_PORT_IRQ_CLEAR); 160 161 err = input_register_device(htcpen_dev); 162 if (err) 163 goto input_register_failed; 164 165 dev_set_drvdata(dev, htcpen_dev); 166 167 return 0; 168 169 input_register_failed: 170 free_irq(HTCPEN_IRQ, htcpen_dev); 171 request_irq_failed: 172 input_free_device(htcpen_dev); 173 input_alloc_failed: 174 release_region(HTCPEN_PORT_INDEX, 2); 175 request_region3_failed: 176 release_region(HTCPEN_PORT_INIT, 1); 177 request_region2_failed: 178 release_region(HTCPEN_PORT_IRQ_CLEAR, 1); 179 request_region1_failed: 180 return err; 181 } 182 183 static int __devexit htcpen_isa_remove(struct device *dev, unsigned int id) 184 { 185 struct input_dev *htcpen_dev = dev_get_drvdata(dev); 186 187 input_unregister_device(htcpen_dev); 188 189 free_irq(HTCPEN_IRQ, htcpen_dev); 190 191 release_region(HTCPEN_PORT_INDEX, 2); 192 release_region(HTCPEN_PORT_INIT, 1); 193 release_region(HTCPEN_PORT_IRQ_CLEAR, 1); 194 195 dev_set_drvdata(dev, NULL); 196 197 return 0; 198 } 199 200 #ifdef CONFIG_PM 201 static int htcpen_isa_suspend(struct device *dev, unsigned int n, 202 pm_message_t state) 203 { 204 outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); 205 206 return 0; 207 } 208 209 static int htcpen_isa_resume(struct device *dev, unsigned int n) 210 { 211 outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); 212 213 return 0; 214 } 215 #endif 216 217 static struct isa_driver htcpen_isa_driver = { 218 .probe = htcpen_isa_probe, 219 .remove = __devexit_p(htcpen_isa_remove), 220 #ifdef CONFIG_PM 221 .suspend = htcpen_isa_suspend, 222 .resume = htcpen_isa_resume, 223 #endif 224 .driver = { 225 .owner = THIS_MODULE, 226 .name = "htcpen", 227 } 228 }; 229 230 static struct dmi_system_id __initdata htcshift_dmi_table[] = { 231 { 232 .ident = "Shift", 233 .matches = { 234 DMI_MATCH(DMI_SYS_VENDOR, "High Tech Computer Corp"), 235 DMI_MATCH(DMI_PRODUCT_NAME, "Shift"), 236 }, 237 }, 238 { } 239 }; 240 241 static int __init htcpen_isa_init(void) 242 { 243 if (!dmi_check_system(htcshift_dmi_table)) 244 return -ENODEV; 245 246 return isa_register_driver(&htcpen_isa_driver, 1); 247 } 248 249 static void __exit htcpen_isa_exit(void) 250 { 251 isa_unregister_driver(&htcpen_isa_driver); 252 } 253 254 module_init(htcpen_isa_init); 255 module_exit(htcpen_isa_exit); 256