18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * HTC Shift touchscreen driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2008 Pau Oliva Fora <pof@eslack.org> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/errno.h> 98c2ecf20Sopenharmony_ci#include <linux/kernel.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/input.h> 128c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 138c2ecf20Sopenharmony_ci#include <linux/io.h> 148c2ecf20Sopenharmony_ci#include <linux/init.h> 158c2ecf20Sopenharmony_ci#include <linux/irq.h> 168c2ecf20Sopenharmony_ci#include <linux/isa.h> 178c2ecf20Sopenharmony_ci#include <linux/ioport.h> 188c2ecf20Sopenharmony_ci#include <linux/dmi.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pau Oliva Fora <pau@eslack.org>"); 218c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HTC Shift touchscreen driver"); 228c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define HTCPEN_PORT_IRQ_CLEAR 0x068 258c2ecf20Sopenharmony_ci#define HTCPEN_PORT_INIT 0x06c 268c2ecf20Sopenharmony_ci#define HTCPEN_PORT_INDEX 0x0250 278c2ecf20Sopenharmony_ci#define HTCPEN_PORT_DATA 0x0251 288c2ecf20Sopenharmony_ci#define HTCPEN_IRQ 3 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define DEVICE_ENABLE 0xa2 318c2ecf20Sopenharmony_ci#define DEVICE_DISABLE 0xa3 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define X_INDEX 3 348c2ecf20Sopenharmony_ci#define Y_INDEX 5 358c2ecf20Sopenharmony_ci#define TOUCH_INDEX 0xb 368c2ecf20Sopenharmony_ci#define LSB_XY_INDEX 0xc 378c2ecf20Sopenharmony_ci#define X_AXIS_MAX 2040 388c2ecf20Sopenharmony_ci#define Y_AXIS_MAX 2040 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic bool invert_x; 418c2ecf20Sopenharmony_cimodule_param(invert_x, bool, 0644); 428c2ecf20Sopenharmony_ciMODULE_PARM_DESC(invert_x, "If set, X axis is inverted"); 438c2ecf20Sopenharmony_cistatic bool invert_y; 448c2ecf20Sopenharmony_cimodule_param(invert_y, bool, 0644); 458c2ecf20Sopenharmony_ciMODULE_PARM_DESC(invert_y, "If set, Y axis is inverted"); 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic irqreturn_t htcpen_interrupt(int irq, void *handle) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci struct input_dev *htcpen_dev = handle; 508c2ecf20Sopenharmony_ci unsigned short x, y, xy; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci /* 0 = press; 1 = release */ 538c2ecf20Sopenharmony_ci outb_p(TOUCH_INDEX, HTCPEN_PORT_INDEX); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci if (inb_p(HTCPEN_PORT_DATA)) { 568c2ecf20Sopenharmony_ci input_report_key(htcpen_dev, BTN_TOUCH, 0); 578c2ecf20Sopenharmony_ci } else { 588c2ecf20Sopenharmony_ci outb_p(X_INDEX, HTCPEN_PORT_INDEX); 598c2ecf20Sopenharmony_ci x = inb_p(HTCPEN_PORT_DATA); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci outb_p(Y_INDEX, HTCPEN_PORT_INDEX); 628c2ecf20Sopenharmony_ci y = inb_p(HTCPEN_PORT_DATA); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci outb_p(LSB_XY_INDEX, HTCPEN_PORT_INDEX); 658c2ecf20Sopenharmony_ci xy = inb_p(HTCPEN_PORT_DATA); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci /* get high resolution value of X and Y using LSB */ 688c2ecf20Sopenharmony_ci x = X_AXIS_MAX - ((x * 8) + ((xy >> 4) & 0xf)); 698c2ecf20Sopenharmony_ci y = (y * 8) + (xy & 0xf); 708c2ecf20Sopenharmony_ci if (invert_x) 718c2ecf20Sopenharmony_ci x = X_AXIS_MAX - x; 728c2ecf20Sopenharmony_ci if (invert_y) 738c2ecf20Sopenharmony_ci y = Y_AXIS_MAX - y; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (x != X_AXIS_MAX && x != 0) { 768c2ecf20Sopenharmony_ci input_report_key(htcpen_dev, BTN_TOUCH, 1); 778c2ecf20Sopenharmony_ci input_report_abs(htcpen_dev, ABS_X, x); 788c2ecf20Sopenharmony_ci input_report_abs(htcpen_dev, ABS_Y, y); 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci } 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci input_sync(htcpen_dev); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci inb_p(HTCPEN_PORT_IRQ_CLEAR); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci return IRQ_HANDLED; 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic int htcpen_open(struct input_dev *dev) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci return 0; 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic void htcpen_close(struct input_dev *dev) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); 998c2ecf20Sopenharmony_ci synchronize_irq(HTCPEN_IRQ); 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic int htcpen_isa_probe(struct device *dev, unsigned int id) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci struct input_dev *htcpen_dev; 1058c2ecf20Sopenharmony_ci int err = -EBUSY; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci if (!request_region(HTCPEN_PORT_IRQ_CLEAR, 1, "htcpen")) { 1088c2ecf20Sopenharmony_ci printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 1098c2ecf20Sopenharmony_ci HTCPEN_PORT_IRQ_CLEAR); 1108c2ecf20Sopenharmony_ci goto request_region1_failed; 1118c2ecf20Sopenharmony_ci } 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci if (!request_region(HTCPEN_PORT_INIT, 1, "htcpen")) { 1148c2ecf20Sopenharmony_ci printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 1158c2ecf20Sopenharmony_ci HTCPEN_PORT_INIT); 1168c2ecf20Sopenharmony_ci goto request_region2_failed; 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci if (!request_region(HTCPEN_PORT_INDEX, 2, "htcpen")) { 1208c2ecf20Sopenharmony_ci printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 1218c2ecf20Sopenharmony_ci HTCPEN_PORT_INDEX); 1228c2ecf20Sopenharmony_ci goto request_region3_failed; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci htcpen_dev = input_allocate_device(); 1268c2ecf20Sopenharmony_ci if (!htcpen_dev) { 1278c2ecf20Sopenharmony_ci printk(KERN_ERR "htcpen: can't allocate device\n"); 1288c2ecf20Sopenharmony_ci err = -ENOMEM; 1298c2ecf20Sopenharmony_ci goto input_alloc_failed; 1308c2ecf20Sopenharmony_ci } 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci htcpen_dev->name = "HTC Shift EC TouchScreen"; 1338c2ecf20Sopenharmony_ci htcpen_dev->id.bustype = BUS_ISA; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci htcpen_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); 1368c2ecf20Sopenharmony_ci htcpen_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); 1378c2ecf20Sopenharmony_ci input_set_abs_params(htcpen_dev, ABS_X, 0, X_AXIS_MAX, 0, 0); 1388c2ecf20Sopenharmony_ci input_set_abs_params(htcpen_dev, ABS_Y, 0, Y_AXIS_MAX, 0, 0); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci htcpen_dev->open = htcpen_open; 1418c2ecf20Sopenharmony_ci htcpen_dev->close = htcpen_close; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci err = request_irq(HTCPEN_IRQ, htcpen_interrupt, 0, "htcpen", 1448c2ecf20Sopenharmony_ci htcpen_dev); 1458c2ecf20Sopenharmony_ci if (err) { 1468c2ecf20Sopenharmony_ci printk(KERN_ERR "htcpen: irq busy\n"); 1478c2ecf20Sopenharmony_ci goto request_irq_failed; 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci inb_p(HTCPEN_PORT_IRQ_CLEAR); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci err = input_register_device(htcpen_dev); 1538c2ecf20Sopenharmony_ci if (err) 1548c2ecf20Sopenharmony_ci goto input_register_failed; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci dev_set_drvdata(dev, htcpen_dev); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci return 0; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci input_register_failed: 1618c2ecf20Sopenharmony_ci free_irq(HTCPEN_IRQ, htcpen_dev); 1628c2ecf20Sopenharmony_ci request_irq_failed: 1638c2ecf20Sopenharmony_ci input_free_device(htcpen_dev); 1648c2ecf20Sopenharmony_ci input_alloc_failed: 1658c2ecf20Sopenharmony_ci release_region(HTCPEN_PORT_INDEX, 2); 1668c2ecf20Sopenharmony_ci request_region3_failed: 1678c2ecf20Sopenharmony_ci release_region(HTCPEN_PORT_INIT, 1); 1688c2ecf20Sopenharmony_ci request_region2_failed: 1698c2ecf20Sopenharmony_ci release_region(HTCPEN_PORT_IRQ_CLEAR, 1); 1708c2ecf20Sopenharmony_ci request_region1_failed: 1718c2ecf20Sopenharmony_ci return err; 1728c2ecf20Sopenharmony_ci} 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic int htcpen_isa_remove(struct device *dev, unsigned int id) 1758c2ecf20Sopenharmony_ci{ 1768c2ecf20Sopenharmony_ci struct input_dev *htcpen_dev = dev_get_drvdata(dev); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci input_unregister_device(htcpen_dev); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci free_irq(HTCPEN_IRQ, htcpen_dev); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci release_region(HTCPEN_PORT_INDEX, 2); 1838c2ecf20Sopenharmony_ci release_region(HTCPEN_PORT_INIT, 1); 1848c2ecf20Sopenharmony_ci release_region(HTCPEN_PORT_IRQ_CLEAR, 1); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci return 0; 1878c2ecf20Sopenharmony_ci} 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 1908c2ecf20Sopenharmony_cistatic int htcpen_isa_suspend(struct device *dev, unsigned int n, 1918c2ecf20Sopenharmony_ci pm_message_t state) 1928c2ecf20Sopenharmony_ci{ 1938c2ecf20Sopenharmony_ci outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci return 0; 1968c2ecf20Sopenharmony_ci} 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic int htcpen_isa_resume(struct device *dev, unsigned int n) 1998c2ecf20Sopenharmony_ci{ 2008c2ecf20Sopenharmony_ci outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci return 0; 2038c2ecf20Sopenharmony_ci} 2048c2ecf20Sopenharmony_ci#endif 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic struct isa_driver htcpen_isa_driver = { 2078c2ecf20Sopenharmony_ci .probe = htcpen_isa_probe, 2088c2ecf20Sopenharmony_ci .remove = htcpen_isa_remove, 2098c2ecf20Sopenharmony_ci#ifdef CONFIG_PM 2108c2ecf20Sopenharmony_ci .suspend = htcpen_isa_suspend, 2118c2ecf20Sopenharmony_ci .resume = htcpen_isa_resume, 2128c2ecf20Sopenharmony_ci#endif 2138c2ecf20Sopenharmony_ci .driver = { 2148c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2158c2ecf20Sopenharmony_ci .name = "htcpen", 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci}; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic const struct dmi_system_id htcshift_dmi_table[] __initconst = { 2208c2ecf20Sopenharmony_ci { 2218c2ecf20Sopenharmony_ci .ident = "Shift", 2228c2ecf20Sopenharmony_ci .matches = { 2238c2ecf20Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "High Tech Computer Corp"), 2248c2ecf20Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "Shift"), 2258c2ecf20Sopenharmony_ci }, 2268c2ecf20Sopenharmony_ci }, 2278c2ecf20Sopenharmony_ci { } 2288c2ecf20Sopenharmony_ci}; 2298c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(dmi, htcshift_dmi_table); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_cistatic int __init htcpen_isa_init(void) 2328c2ecf20Sopenharmony_ci{ 2338c2ecf20Sopenharmony_ci if (!dmi_check_system(htcshift_dmi_table)) 2348c2ecf20Sopenharmony_ci return -ENODEV; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci return isa_register_driver(&htcpen_isa_driver, 1); 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cistatic void __exit htcpen_isa_exit(void) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci isa_unregister_driver(&htcpen_isa_driver); 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cimodule_init(htcpen_isa_init); 2458c2ecf20Sopenharmony_cimodule_exit(htcpen_isa_exit); 246