162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * HTC Shift touchscreen driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2008 Pau Oliva Fora <pof@eslack.org> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/errno.h> 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/input.h> 1262306a36Sopenharmony_ci#include <linux/interrupt.h> 1362306a36Sopenharmony_ci#include <linux/io.h> 1462306a36Sopenharmony_ci#include <linux/init.h> 1562306a36Sopenharmony_ci#include <linux/irq.h> 1662306a36Sopenharmony_ci#include <linux/isa.h> 1762306a36Sopenharmony_ci#include <linux/ioport.h> 1862306a36Sopenharmony_ci#include <linux/dmi.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ciMODULE_AUTHOR("Pau Oliva Fora <pau@eslack.org>"); 2162306a36Sopenharmony_ciMODULE_DESCRIPTION("HTC Shift touchscreen driver"); 2262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define HTCPEN_PORT_IRQ_CLEAR 0x068 2562306a36Sopenharmony_ci#define HTCPEN_PORT_INIT 0x06c 2662306a36Sopenharmony_ci#define HTCPEN_PORT_INDEX 0x0250 2762306a36Sopenharmony_ci#define HTCPEN_PORT_DATA 0x0251 2862306a36Sopenharmony_ci#define HTCPEN_IRQ 3 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define DEVICE_ENABLE 0xa2 3162306a36Sopenharmony_ci#define DEVICE_DISABLE 0xa3 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define X_INDEX 3 3462306a36Sopenharmony_ci#define Y_INDEX 5 3562306a36Sopenharmony_ci#define TOUCH_INDEX 0xb 3662306a36Sopenharmony_ci#define LSB_XY_INDEX 0xc 3762306a36Sopenharmony_ci#define X_AXIS_MAX 2040 3862306a36Sopenharmony_ci#define Y_AXIS_MAX 2040 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic bool invert_x; 4162306a36Sopenharmony_cimodule_param(invert_x, bool, 0644); 4262306a36Sopenharmony_ciMODULE_PARM_DESC(invert_x, "If set, X axis is inverted"); 4362306a36Sopenharmony_cistatic bool invert_y; 4462306a36Sopenharmony_cimodule_param(invert_y, bool, 0644); 4562306a36Sopenharmony_ciMODULE_PARM_DESC(invert_y, "If set, Y axis is inverted"); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic irqreturn_t htcpen_interrupt(int irq, void *handle) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci struct input_dev *htcpen_dev = handle; 5062306a36Sopenharmony_ci unsigned short x, y, xy; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci /* 0 = press; 1 = release */ 5362306a36Sopenharmony_ci outb_p(TOUCH_INDEX, HTCPEN_PORT_INDEX); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci if (inb_p(HTCPEN_PORT_DATA)) { 5662306a36Sopenharmony_ci input_report_key(htcpen_dev, BTN_TOUCH, 0); 5762306a36Sopenharmony_ci } else { 5862306a36Sopenharmony_ci outb_p(X_INDEX, HTCPEN_PORT_INDEX); 5962306a36Sopenharmony_ci x = inb_p(HTCPEN_PORT_DATA); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci outb_p(Y_INDEX, HTCPEN_PORT_INDEX); 6262306a36Sopenharmony_ci y = inb_p(HTCPEN_PORT_DATA); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci outb_p(LSB_XY_INDEX, HTCPEN_PORT_INDEX); 6562306a36Sopenharmony_ci xy = inb_p(HTCPEN_PORT_DATA); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci /* get high resolution value of X and Y using LSB */ 6862306a36Sopenharmony_ci x = X_AXIS_MAX - ((x * 8) + ((xy >> 4) & 0xf)); 6962306a36Sopenharmony_ci y = (y * 8) + (xy & 0xf); 7062306a36Sopenharmony_ci if (invert_x) 7162306a36Sopenharmony_ci x = X_AXIS_MAX - x; 7262306a36Sopenharmony_ci if (invert_y) 7362306a36Sopenharmony_ci y = Y_AXIS_MAX - y; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci if (x != X_AXIS_MAX && x != 0) { 7662306a36Sopenharmony_ci input_report_key(htcpen_dev, BTN_TOUCH, 1); 7762306a36Sopenharmony_ci input_report_abs(htcpen_dev, ABS_X, x); 7862306a36Sopenharmony_ci input_report_abs(htcpen_dev, ABS_Y, y); 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci input_sync(htcpen_dev); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci inb_p(HTCPEN_PORT_IRQ_CLEAR); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci return IRQ_HANDLED; 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int htcpen_open(struct input_dev *dev) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic void htcpen_close(struct input_dev *dev) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); 9962306a36Sopenharmony_ci synchronize_irq(HTCPEN_IRQ); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic int htcpen_isa_probe(struct device *dev, unsigned int id) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct input_dev *htcpen_dev; 10562306a36Sopenharmony_ci int err = -EBUSY; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci if (!request_region(HTCPEN_PORT_IRQ_CLEAR, 1, "htcpen")) { 10862306a36Sopenharmony_ci printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 10962306a36Sopenharmony_ci HTCPEN_PORT_IRQ_CLEAR); 11062306a36Sopenharmony_ci goto request_region1_failed; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci if (!request_region(HTCPEN_PORT_INIT, 1, "htcpen")) { 11462306a36Sopenharmony_ci printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 11562306a36Sopenharmony_ci HTCPEN_PORT_INIT); 11662306a36Sopenharmony_ci goto request_region2_failed; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (!request_region(HTCPEN_PORT_INDEX, 2, "htcpen")) { 12062306a36Sopenharmony_ci printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", 12162306a36Sopenharmony_ci HTCPEN_PORT_INDEX); 12262306a36Sopenharmony_ci goto request_region3_failed; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci htcpen_dev = input_allocate_device(); 12662306a36Sopenharmony_ci if (!htcpen_dev) { 12762306a36Sopenharmony_ci printk(KERN_ERR "htcpen: can't allocate device\n"); 12862306a36Sopenharmony_ci err = -ENOMEM; 12962306a36Sopenharmony_ci goto input_alloc_failed; 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci htcpen_dev->name = "HTC Shift EC TouchScreen"; 13362306a36Sopenharmony_ci htcpen_dev->id.bustype = BUS_ISA; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci htcpen_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); 13662306a36Sopenharmony_ci htcpen_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); 13762306a36Sopenharmony_ci input_set_abs_params(htcpen_dev, ABS_X, 0, X_AXIS_MAX, 0, 0); 13862306a36Sopenharmony_ci input_set_abs_params(htcpen_dev, ABS_Y, 0, Y_AXIS_MAX, 0, 0); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci htcpen_dev->open = htcpen_open; 14162306a36Sopenharmony_ci htcpen_dev->close = htcpen_close; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci err = request_irq(HTCPEN_IRQ, htcpen_interrupt, 0, "htcpen", 14462306a36Sopenharmony_ci htcpen_dev); 14562306a36Sopenharmony_ci if (err) { 14662306a36Sopenharmony_ci printk(KERN_ERR "htcpen: irq busy\n"); 14762306a36Sopenharmony_ci goto request_irq_failed; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci inb_p(HTCPEN_PORT_IRQ_CLEAR); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci err = input_register_device(htcpen_dev); 15362306a36Sopenharmony_ci if (err) 15462306a36Sopenharmony_ci goto input_register_failed; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci dev_set_drvdata(dev, htcpen_dev); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return 0; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci input_register_failed: 16162306a36Sopenharmony_ci free_irq(HTCPEN_IRQ, htcpen_dev); 16262306a36Sopenharmony_ci request_irq_failed: 16362306a36Sopenharmony_ci input_free_device(htcpen_dev); 16462306a36Sopenharmony_ci input_alloc_failed: 16562306a36Sopenharmony_ci release_region(HTCPEN_PORT_INDEX, 2); 16662306a36Sopenharmony_ci request_region3_failed: 16762306a36Sopenharmony_ci release_region(HTCPEN_PORT_INIT, 1); 16862306a36Sopenharmony_ci request_region2_failed: 16962306a36Sopenharmony_ci release_region(HTCPEN_PORT_IRQ_CLEAR, 1); 17062306a36Sopenharmony_ci request_region1_failed: 17162306a36Sopenharmony_ci return err; 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic void htcpen_isa_remove(struct device *dev, unsigned int id) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct input_dev *htcpen_dev = dev_get_drvdata(dev); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci input_unregister_device(htcpen_dev); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci free_irq(HTCPEN_IRQ, htcpen_dev); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci release_region(HTCPEN_PORT_INDEX, 2); 18362306a36Sopenharmony_ci release_region(HTCPEN_PORT_INIT, 1); 18462306a36Sopenharmony_ci release_region(HTCPEN_PORT_IRQ_CLEAR, 1); 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci#ifdef CONFIG_PM 18862306a36Sopenharmony_cistatic int htcpen_isa_suspend(struct device *dev, unsigned int n, 18962306a36Sopenharmony_ci pm_message_t state) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci return 0; 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic int htcpen_isa_resume(struct device *dev, unsigned int n) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci return 0; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci#endif 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic struct isa_driver htcpen_isa_driver = { 20562306a36Sopenharmony_ci .probe = htcpen_isa_probe, 20662306a36Sopenharmony_ci .remove = htcpen_isa_remove, 20762306a36Sopenharmony_ci#ifdef CONFIG_PM 20862306a36Sopenharmony_ci .suspend = htcpen_isa_suspend, 20962306a36Sopenharmony_ci .resume = htcpen_isa_resume, 21062306a36Sopenharmony_ci#endif 21162306a36Sopenharmony_ci .driver = { 21262306a36Sopenharmony_ci .owner = THIS_MODULE, 21362306a36Sopenharmony_ci .name = "htcpen", 21462306a36Sopenharmony_ci } 21562306a36Sopenharmony_ci}; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic const struct dmi_system_id htcshift_dmi_table[] __initconst = { 21862306a36Sopenharmony_ci { 21962306a36Sopenharmony_ci .ident = "Shift", 22062306a36Sopenharmony_ci .matches = { 22162306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "High Tech Computer Corp"), 22262306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "Shift"), 22362306a36Sopenharmony_ci }, 22462306a36Sopenharmony_ci }, 22562306a36Sopenharmony_ci { } 22662306a36Sopenharmony_ci}; 22762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(dmi, htcshift_dmi_table); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_cistatic int __init htcpen_isa_init(void) 23062306a36Sopenharmony_ci{ 23162306a36Sopenharmony_ci if (!dmi_check_system(htcshift_dmi_table)) 23262306a36Sopenharmony_ci return -ENODEV; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci return isa_register_driver(&htcpen_isa_driver, 1); 23562306a36Sopenharmony_ci} 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_cistatic void __exit htcpen_isa_exit(void) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci isa_unregister_driver(&htcpen_isa_driver); 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cimodule_init(htcpen_isa_init); 24362306a36Sopenharmony_cimodule_exit(htcpen_isa_exit); 244