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