15a18c343SPau Oliva Fora /* 25a18c343SPau Oliva Fora * HTC Shift touchscreen driver 35a18c343SPau Oliva Fora * 45a18c343SPau Oliva Fora * Copyright (C) 2008 Pau Oliva Fora <pof@eslack.org> 55a18c343SPau Oliva Fora * 65a18c343SPau Oliva Fora * This program is free software; you can redistribute it and/or modify it 75a18c343SPau Oliva Fora * under the terms of the GNU General Public License version 2 as published 85a18c343SPau Oliva Fora * by the Free Software Foundation. 95a18c343SPau Oliva Fora */ 105a18c343SPau Oliva Fora 115a18c343SPau Oliva Fora #include <linux/errno.h> 125a18c343SPau Oliva Fora #include <linux/kernel.h> 135a18c343SPau Oliva Fora #include <linux/module.h> 145a18c343SPau Oliva Fora #include <linux/input.h> 155a18c343SPau Oliva Fora #include <linux/interrupt.h> 165a18c343SPau Oliva Fora #include <linux/io.h> 175a18c343SPau Oliva Fora #include <linux/init.h> 185a18c343SPau Oliva Fora #include <linux/irq.h> 195a18c343SPau Oliva Fora #include <linux/isa.h> 205a18c343SPau Oliva Fora #include <linux/ioport.h> 215a18c343SPau Oliva Fora #include <linux/dmi.h> 225a18c343SPau Oliva Fora 235a18c343SPau Oliva Fora MODULE_AUTHOR("Pau Oliva Fora <pau@eslack.org>"); 245a18c343SPau Oliva Fora MODULE_DESCRIPTION("HTC Shift touchscreen driver"); 255a18c343SPau Oliva Fora MODULE_LICENSE("GPL"); 265a18c343SPau Oliva Fora 275a18c343SPau Oliva Fora #define HTCPEN_PORT_IRQ_CLEAR 0x068 285a18c343SPau Oliva Fora #define HTCPEN_PORT_INIT 0x06c 295a18c343SPau Oliva Fora #define HTCPEN_PORT_INDEX 0x0250 305a18c343SPau Oliva Fora #define HTCPEN_PORT_DATA 0x0251 315a18c343SPau Oliva Fora #define HTCPEN_IRQ 3 325a18c343SPau Oliva Fora 335a18c343SPau Oliva Fora #define DEVICE_ENABLE 0xa2 345a18c343SPau Oliva Fora #define DEVICE_DISABLE 0xa3 355a18c343SPau Oliva Fora 365a18c343SPau Oliva Fora #define X_INDEX 3 375a18c343SPau Oliva Fora #define Y_INDEX 5 385a18c343SPau Oliva Fora #define TOUCH_INDEX 0xb 395a18c343SPau Oliva Fora #define LSB_XY_INDEX 0xc 405a18c343SPau Oliva Fora #define X_AXIS_MAX 2040 415a18c343SPau Oliva Fora #define Y_AXIS_MAX 2040 425a18c343SPau Oliva Fora 4390ab5ee9SRusty Russell static bool invert_x; 445a18c343SPau Oliva Fora module_param(invert_x, bool, 0644); 455a18c343SPau Oliva Fora MODULE_PARM_DESC(invert_x, "If set, X axis is inverted"); 4690ab5ee9SRusty Russell static bool invert_y; 475a18c343SPau Oliva Fora module_param(invert_y, bool, 0644); 485a18c343SPau Oliva Fora MODULE_PARM_DESC(invert_y, "If set, Y axis is inverted"); 495a18c343SPau Oliva Fora 505a18c343SPau Oliva Fora static irqreturn_t htcpen_interrupt(int irq, void *handle) 515a18c343SPau Oliva Fora { 525a18c343SPau Oliva Fora struct input_dev *htcpen_dev = handle; 535a18c343SPau Oliva Fora unsigned short x, y, xy; 545a18c343SPau Oliva Fora 555a18c343SPau Oliva Fora /* 0 = press; 1 = release */ 565a18c343SPau Oliva Fora outb_p(TOUCH_INDEX, HTCPEN_PORT_INDEX); 575a18c343SPau Oliva Fora 585a18c343SPau Oliva Fora if (inb_p(HTCPEN_PORT_DATA)) { 595a18c343SPau Oliva Fora input_report_key(htcpen_dev, BTN_TOUCH, 0); 605a18c343SPau Oliva Fora } else { 615a18c343SPau Oliva Fora outb_p(X_INDEX, HTCPEN_PORT_INDEX); 625a18c343SPau Oliva Fora x = inb_p(HTCPEN_PORT_DATA); 635a18c343SPau Oliva Fora 645a18c343SPau Oliva Fora outb_p(Y_INDEX, HTCPEN_PORT_INDEX); 655a18c343SPau Oliva Fora y = inb_p(HTCPEN_PORT_DATA); 665a18c343SPau Oliva Fora 675a18c343SPau Oliva Fora outb_p(LSB_XY_INDEX, HTCPEN_PORT_INDEX); 685a18c343SPau Oliva Fora xy = inb_p(HTCPEN_PORT_DATA); 695a18c343SPau Oliva Fora 705a18c343SPau Oliva Fora /* get high resolution value of X and Y using LSB */ 715a18c343SPau Oliva Fora x = X_AXIS_MAX - ((x * 8) + ((xy >> 4) & 0xf)); 725a18c343SPau Oliva Fora y = (y * 8) + (xy & 0xf); 735a18c343SPau Oliva Fora if (invert_x) 745a18c343SPau Oliva Fora x = X_AXIS_MAX - x; 755a18c343SPau Oliva Fora if (invert_y) 765a18c343SPau Oliva Fora y = Y_AXIS_MAX - y; 775a18c343SPau Oliva Fora 785a18c343SPau Oliva Fora if (x != X_AXIS_MAX && x != 0) { 795a18c343SPau Oliva Fora input_report_key(htcpen_dev, BTN_TOUCH, 1); 805a18c343SPau Oliva Fora input_report_abs(htcpen_dev, ABS_X, x); 815a18c343SPau Oliva Fora input_report_abs(htcpen_dev, ABS_Y, y); 825a18c343SPau Oliva Fora } 835a18c343SPau Oliva Fora } 845a18c343SPau Oliva Fora 855a18c343SPau Oliva Fora input_sync(htcpen_dev); 865a18c343SPau Oliva Fora 875a18c343SPau Oliva Fora inb_p(HTCPEN_PORT_IRQ_CLEAR); 885a18c343SPau Oliva Fora 895a18c343SPau Oliva Fora return IRQ_HANDLED; 905a18c343SPau Oliva Fora } 915a18c343SPau Oliva Fora 925a18c343SPau Oliva Fora static int htcpen_open(struct input_dev *dev) 935a18c343SPau Oliva Fora { 945a18c343SPau Oliva Fora outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); 955a18c343SPau Oliva Fora 965a18c343SPau Oliva Fora return 0; 975a18c343SPau Oliva Fora } 985a18c343SPau Oliva Fora 995a18c343SPau Oliva Fora static void htcpen_close(struct input_dev *dev) 1005a18c343SPau Oliva Fora { 1015a18c343SPau Oliva Fora outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); 1025a18c343SPau Oliva Fora synchronize_irq(HTCPEN_IRQ); 1035a18c343SPau Oliva Fora } 1045a18c343SPau Oliva Fora 1055298cc4cSBill Pemberton static int htcpen_isa_probe(struct device *dev, unsigned int id) 1065a18c343SPau Oliva Fora { 1075a18c343SPau Oliva Fora struct input_dev *htcpen_dev; 1085a18c343SPau Oliva Fora int err = -EBUSY; 1095a18c343SPau Oliva Fora 1105a18c343SPau Oliva Fora if (!request_region(HTCPEN_PORT_IRQ_CLEAR, 1, "htcpen")) { 1115a18c343SPau Oliva Fora printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 1125a18c343SPau Oliva Fora HTCPEN_PORT_IRQ_CLEAR); 1135a18c343SPau Oliva Fora goto request_region1_failed; 1145a18c343SPau Oliva Fora } 1155a18c343SPau Oliva Fora 1165a18c343SPau Oliva Fora if (!request_region(HTCPEN_PORT_INIT, 1, "htcpen")) { 1175a18c343SPau Oliva Fora printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 1185a18c343SPau Oliva Fora HTCPEN_PORT_INIT); 1195a18c343SPau Oliva Fora goto request_region2_failed; 1205a18c343SPau Oliva Fora } 1215a18c343SPau Oliva Fora 1225a18c343SPau Oliva Fora if (!request_region(HTCPEN_PORT_INDEX, 2, "htcpen")) { 1235a18c343SPau Oliva Fora printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 1245a18c343SPau Oliva Fora HTCPEN_PORT_INDEX); 1255a18c343SPau Oliva Fora goto request_region3_failed; 1265a18c343SPau Oliva Fora } 1275a18c343SPau Oliva Fora 1285a18c343SPau Oliva Fora htcpen_dev = input_allocate_device(); 1295a18c343SPau Oliva Fora if (!htcpen_dev) { 1305a18c343SPau Oliva Fora printk(KERN_ERR "htcpen: can't allocate device\n"); 1315a18c343SPau Oliva Fora err = -ENOMEM; 1325a18c343SPau Oliva Fora goto input_alloc_failed; 1335a18c343SPau Oliva Fora } 1345a18c343SPau Oliva Fora 1355a18c343SPau Oliva Fora htcpen_dev->name = "HTC Shift EC TouchScreen"; 1365a18c343SPau Oliva Fora htcpen_dev->id.bustype = BUS_ISA; 1375a18c343SPau Oliva Fora 1385a18c343SPau Oliva Fora htcpen_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); 1395a18c343SPau Oliva Fora htcpen_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); 1405a18c343SPau Oliva Fora input_set_abs_params(htcpen_dev, ABS_X, 0, X_AXIS_MAX, 0, 0); 1415a18c343SPau Oliva Fora input_set_abs_params(htcpen_dev, ABS_Y, 0, Y_AXIS_MAX, 0, 0); 1425a18c343SPau Oliva Fora 1435a18c343SPau Oliva Fora htcpen_dev->open = htcpen_open; 1445a18c343SPau Oliva Fora htcpen_dev->close = htcpen_close; 1455a18c343SPau Oliva Fora 1465a18c343SPau Oliva Fora err = request_irq(HTCPEN_IRQ, htcpen_interrupt, 0, "htcpen", 1475a18c343SPau Oliva Fora htcpen_dev); 1485a18c343SPau Oliva Fora if (err) { 1495a18c343SPau Oliva Fora printk(KERN_ERR "htcpen: irq busy\n"); 1505a18c343SPau Oliva Fora goto request_irq_failed; 1515a18c343SPau Oliva Fora } 1525a18c343SPau Oliva Fora 1535a18c343SPau Oliva Fora inb_p(HTCPEN_PORT_IRQ_CLEAR); 1545a18c343SPau Oliva Fora 1555a18c343SPau Oliva Fora err = input_register_device(htcpen_dev); 1565a18c343SPau Oliva Fora if (err) 1575a18c343SPau Oliva Fora goto input_register_failed; 1585a18c343SPau Oliva Fora 1595a18c343SPau Oliva Fora dev_set_drvdata(dev, htcpen_dev); 1605a18c343SPau Oliva Fora 1615a18c343SPau Oliva Fora return 0; 1625a18c343SPau Oliva Fora 1635a18c343SPau Oliva Fora input_register_failed: 1645a18c343SPau Oliva Fora free_irq(HTCPEN_IRQ, htcpen_dev); 1655a18c343SPau Oliva Fora request_irq_failed: 1665a18c343SPau Oliva Fora input_free_device(htcpen_dev); 1675a18c343SPau Oliva Fora input_alloc_failed: 1685a18c343SPau Oliva Fora release_region(HTCPEN_PORT_INDEX, 2); 1695a18c343SPau Oliva Fora request_region3_failed: 1705a18c343SPau Oliva Fora release_region(HTCPEN_PORT_INIT, 1); 1715a18c343SPau Oliva Fora request_region2_failed: 1725a18c343SPau Oliva Fora release_region(HTCPEN_PORT_IRQ_CLEAR, 1); 1735a18c343SPau Oliva Fora request_region1_failed: 1745a18c343SPau Oliva Fora return err; 1755a18c343SPau Oliva Fora } 1765a18c343SPau Oliva Fora 177e2619cf7SBill Pemberton static int htcpen_isa_remove(struct device *dev, unsigned int id) 1785a18c343SPau Oliva Fora { 1795a18c343SPau Oliva Fora struct input_dev *htcpen_dev = dev_get_drvdata(dev); 1805a18c343SPau Oliva Fora 1815a18c343SPau Oliva Fora input_unregister_device(htcpen_dev); 1825a18c343SPau Oliva Fora 1835a18c343SPau Oliva Fora free_irq(HTCPEN_IRQ, htcpen_dev); 1845a18c343SPau Oliva Fora 1855a18c343SPau Oliva Fora release_region(HTCPEN_PORT_INDEX, 2); 1865a18c343SPau Oliva Fora release_region(HTCPEN_PORT_INIT, 1); 1875a18c343SPau Oliva Fora release_region(HTCPEN_PORT_IRQ_CLEAR, 1); 1885a18c343SPau Oliva Fora 1895a18c343SPau Oliva Fora return 0; 1905a18c343SPau Oliva Fora } 1915a18c343SPau Oliva Fora 1925a18c343SPau Oliva Fora #ifdef CONFIG_PM 1935a18c343SPau Oliva Fora static int htcpen_isa_suspend(struct device *dev, unsigned int n, 1945a18c343SPau Oliva Fora pm_message_t state) 1955a18c343SPau Oliva Fora { 1965a18c343SPau Oliva Fora outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); 1975a18c343SPau Oliva Fora 1985a18c343SPau Oliva Fora return 0; 1995a18c343SPau Oliva Fora } 2005a18c343SPau Oliva Fora 2015a18c343SPau Oliva Fora static int htcpen_isa_resume(struct device *dev, unsigned int n) 2025a18c343SPau Oliva Fora { 2035a18c343SPau Oliva Fora outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); 2045a18c343SPau Oliva Fora 2055a18c343SPau Oliva Fora return 0; 2065a18c343SPau Oliva Fora } 2075a18c343SPau Oliva Fora #endif 2085a18c343SPau Oliva Fora 2095a18c343SPau Oliva Fora static struct isa_driver htcpen_isa_driver = { 2105a18c343SPau Oliva Fora .probe = htcpen_isa_probe, 2111cb0aa88SBill Pemberton .remove = htcpen_isa_remove, 2125a18c343SPau Oliva Fora #ifdef CONFIG_PM 2135a18c343SPau Oliva Fora .suspend = htcpen_isa_suspend, 2145a18c343SPau Oliva Fora .resume = htcpen_isa_resume, 2155a18c343SPau Oliva Fora #endif 2165a18c343SPau Oliva Fora .driver = { 2175a18c343SPau Oliva Fora .owner = THIS_MODULE, 2185a18c343SPau Oliva Fora .name = "htcpen", 2195a18c343SPau Oliva Fora } 2205a18c343SPau Oliva Fora }; 2215a18c343SPau Oliva Fora 222*6faadbbbSChristoph Hellwig static const struct dmi_system_id htcshift_dmi_table[] __initconst = { 2235a18c343SPau Oliva Fora { 2245a18c343SPau Oliva Fora .ident = "Shift", 2255a18c343SPau Oliva Fora .matches = { 2265a18c343SPau Oliva Fora DMI_MATCH(DMI_SYS_VENDOR, "High Tech Computer Corp"), 2275a18c343SPau Oliva Fora DMI_MATCH(DMI_PRODUCT_NAME, "Shift"), 2285a18c343SPau Oliva Fora }, 2295a18c343SPau Oliva Fora }, 2305a18c343SPau Oliva Fora { } 2315a18c343SPau Oliva Fora }; 232e23ed600SDmitry Torokhov MODULE_DEVICE_TABLE(dmi, htcshift_dmi_table); 2335a18c343SPau Oliva Fora 2345a18c343SPau Oliva Fora static int __init htcpen_isa_init(void) 2355a18c343SPau Oliva Fora { 2365a18c343SPau Oliva Fora if (!dmi_check_system(htcshift_dmi_table)) 2375a18c343SPau Oliva Fora return -ENODEV; 2385a18c343SPau Oliva Fora 2395a18c343SPau Oliva Fora return isa_register_driver(&htcpen_isa_driver, 1); 2405a18c343SPau Oliva Fora } 2415a18c343SPau Oliva Fora 2425a18c343SPau Oliva Fora static void __exit htcpen_isa_exit(void) 2435a18c343SPau Oliva Fora { 2445a18c343SPau Oliva Fora isa_unregister_driver(&htcpen_isa_driver); 2455a18c343SPau Oliva Fora } 2465a18c343SPau Oliva Fora 2475a18c343SPau Oliva Fora module_init(htcpen_isa_init); 2485a18c343SPau Oliva Fora module_exit(htcpen_isa_exit); 249