From patchwork Mon Feb 7 16:28:43 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anton Chikin X-Patchwork-Id: 538341 X-Patchwork-Delegate: jikos@jikos.cz Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id p17GT3xs027014 for ; Mon, 7 Feb 2011 16:29:04 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752692Ab1BGQ3C (ORCPT ); Mon, 7 Feb 2011 11:29:02 -0500 Received: from mail-fx0-f46.google.com ([209.85.161.46]:45496 "EHLO mail-fx0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751962Ab1BGQ3A (ORCPT ); Mon, 7 Feb 2011 11:29:00 -0500 Received: by fxm20 with SMTP id 20so5068280fxm.19 for ; Mon, 07 Feb 2011 08:28:58 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:from:to:cc:subject:date:message-id:x-mailer; bh=8Wp2r4XlvKA4w6ig4cCfgKoYN8oIVMRTTOdTIHCh8wE=; b=k1UmljEf2zb4eYPbNIH6x3z8t3hbyxR5RR9ixRuHwyob0jvz/eQj9QORs43L4tGj30 +95TJFxgJZeXgJ4Up6ae/d3Ydq0eqwnZNWYCYMRwXIy/ZGgJlBciiJxOrj4nviza0x+I H0fSNUnZiC/NDJQ0scP2KR1/lQKYgEcmIbtQM= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer; b=UMNmXlT+uK/7ricxGS7qt3MF4YYJVdI+nWFsekzVL9pc5v70i11GCAcCcd71xU9LME cdxYa0aor+ZPspHQ18InK55sILTClnA7kuIJ1hSDQgMseue1skfxkBVyrNV7soP7NsXD SWYm0FudpvGh54x0bazOsVjWVjZfgOByaCvwo= Received: by 10.223.93.140 with SMTP id v12mr10464340fam.96.1297096138662; Mon, 07 Feb 2011 08:28:58 -0800 (PST) Received: from localhost.localdomain ([195.110.62.193]) by mx.google.com with ESMTPS id z1sm1270619fau.45.2011.02.07.08.28.55 (version=TLSv1/SSLv3 cipher=RC4-MD5); Mon, 07 Feb 2011 08:28:56 -0800 (PST) From: Anton Chikin To: jkosina@suse.cz, rydberg@euromail.se, linux-input@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Anton Chikin , Anton Chikin Subject: [PATCH] HID: added new driver for Panasonic Elite Panaboard UB-T780 and UB-T880 Date: Mon, 7 Feb 2011 08:28:43 -0800 Message-Id: <1297096123-4623-1-git-send-email-anton.chikin@dataart.com> X-Mailer: git-send-email 1.7.1 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Mon, 07 Feb 2011 16:29:04 +0000 (UTC) diff --git a/drivers/hid/hid-ubt880.c b/drivers/hid/hid-ubt880.c new file mode 100644 index 0000000..4f5e3de --- /dev/null +++ b/drivers/hid/hid-ubt880.c @@ -0,0 +1,1193 @@ +/* + * HID driver for Cando dual-touch panels + * + * Copyright (c) 2010 Stephane Chatty + * + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include +#include +#include +#include +#include +#include +#include "hid-ids.h" +#include "hid-ubt880.h" + +#define UBT780_DEBUG + +#ifdef UBT780_DEBUG +#define UBT_DUMMY_DEBUG if (ubt_debug) printk(KERN_DEBUG "ubt880: %s:%s line %i\n", __FILE__, __func__ , __LINE__); +#else +#define UBT_DUMMY_DEBUG +#endif + +static int ubt_major; +static struct cdev ubt_cdev; +static struct class *ubt_class; +static struct ubt_chrdev *ubt_table[UBT780_MAX_DEVICES]; +static DEFINE_MUTEX(minors_lock); +static int ubt_debug = 0; +module_param_named(debug_enabled, ubt_debug, int, 0600); +MODULE_PARM_DESC(debug_enabled, "Toggle huge and ugly UBT debugging messages."); + +static int isCalibrated = 0; + +static int mode = 0; + +static int LTx = -1; +static int LTy = -1; +static int LBx = -1; +static int LBy = -1; +static int RTx = -1; +static int RTy = -1; +static int RBx = -1; +static int RBy = -1; + +static int LTX = -1; +static int LTY = -1; +static int LBX = -1; +static int LBY = -1; +static int RTX = -1; +static int RTY = -1; +static int RBX = -1; +static int RBY = -1; + +static int Xoff_A = -1; +static int Xmag_A = -1; +static int Xmag_B = -1; +static int Yoff_A = -1; +static int Ymag_A = -1; +static int Ymag_B = -1; + +module_param(mode, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(mode, "1 - digitizer mode, 0 - mouse mode"); + +module_param(isCalibrated, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(isCalibrated, "Calibration data is set to driver."); +/*---------------------------------------------------------------------*/ + +module_param(LTx, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(LTx, "point x of left-up corner, screen coordinate"); + +module_param(LTy, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(LTy, "point y of left-up corner, screen coordinate"); + +module_param(LBx, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(LBx, "point x of left-bot corner, screen coordinate"); + +module_param(LBy, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(LBy, "point y of left-bot corner, screen coordinate"); + +module_param(RTx, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(RTx, "point x of rigth-up corner, screen coordinate"); + +module_param(RTy, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(RTy, "point y of rigth-up corner, screen coordinate"); + +module_param(RBx, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(RBx, "point x of rigth-bot corner, screen coordinate"); + +module_param(RBy, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(RBy, "point y of rigth-bot corner, screen coordinate"); + +/*---------------------------------------------------------------------*/ + +module_param(LTX, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(LTX, "point x of left-up corner, board coordinate"); + +module_param(LTY, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(LTY, "point y of left-up corner, board coordinate"); + +module_param(LBX, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(LBX, "point x of left-bot corner, board coordinate"); + +module_param(LBY, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(LBY, "point y of left-bot corner, board coordinate"); + +module_param(RTX, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(RTX, "point x of rigth-up corner, board coordinate"); + +module_param(RTY, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(RTY, "point y of rigth-up corner, board coordinate"); + +module_param(RBX, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(RBX, "point x of rigth-bot corner, board coordinate"); + +module_param(RBY, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(RBY, "point y of rigth-bot corner, board coordinate"); +/*-----------------------------------------------------------------*/ +module_param(Xoff_A, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(Xoff_A, "point x of rigth-up corner, board coordinate"); + +module_param(Xmag_A, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(Xmag_A, "point y of rigth-up corner, board coordinate"); + +module_param(Xmag_B, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(Xmag_B, "point x of rigth-bot corner, board coordinate"); + +module_param(Ymag_A, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(Ymag_A, "point y of rigth-bot corner, board coordinate"); + +module_param(Ymag_B, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(Ymag_B, "point y of rigth-bot corner, board coordinate"); + +module_param(Yoff_A, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +MODULE_PARM_DESC(Yoff_A, "point x of rigth-up corner, board coordinate"); + +/*Character device forward declarations*/ +static int ubt880_open_io(struct inode *node, struct file *file); +static int ubt880_release_io(struct inode *node, struct file *file); +static long ubt880_ioctl(struct file *iofile, unsigned int command, unsigned long data); +static int ubt_chrdev_connect(struct hid_device *, struct ubt880_data *); +static void ubt_chrdev_disconnect(struct hid_device *hid); +#ifdef UBT780_DEBUG +static void send_test_packet(struct hid_device *hdev); +static void fake_calibration(struct hid_device *hdev, unsigned short *coords); +#endif +static struct input_dev *ubt880_get_input(struct hid_device *hdev); +static int ubt880_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size); +/*We are using function from usbhid module*/ +extern void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir); +static void set_calib_to_device(struct ubt880_data *drvdata); + +/*switch_mode : Our device has two modes: mouse mode in which it is acting like mouse, producing + * absolute coordinates [0 - 4095] + * The second mode is digitizer mode, which produce two raw clock measurments from ultrasound + * recievers in the top-left corner of the board. This mode more flexible and also reports batterey + * status of digitizer pen and penID. + */ +static int ubt880_switch_mode(struct hid_device *hid, unsigned char mode) +{ + int rc = 0; + + struct hid_report *report = NULL; + struct hid_report *report_cur = NULL; + __s32 *val = NULL; + /** Packet forming */ + + UBT_DUMMY_DEBUG + + list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list) + { + if (hid->report_enum[HID_FEATURE_REPORT].numbered) { + report_cur = report; + } + } + + if (report_cur == NULL) { + if (ubt_debug) + printk(KERN_DEBUG "ubt880 : switch mode : report_cur = 0"); + return rc; + } + + if (ubt_debug) + printk(KERN_DEBUG "ubt880: switch mode: reportid = %d", report_cur->id); + val = report_cur->field[0]->value; + + switch (mode) { + case MODE_MOUSE: { + mode = 0x0; + break; + } + case MODE_SINGLETOUCH: { + mode = 0x01; + break; + } + case MODE_DGTZR: { + mode = 0x02; + break; + } + default: { + rc = -EIO; + return rc; + } + } + val[0] = mode; + UBT_DUMMY_DEBUG + if (ubt_debug) + printk(KERN_DEBUG "ubt880 :switch_mode: usbhid_submit_report drv=%p p=%p mode=%d", hid, report_cur, mode); + /*Send report to device*/ + + usbhid_submit_report(hid, report_cur, USB_DIR_OUT); + UBT_DUMMY_DEBUG + return rc; +} + +static int ubt780_switch_mode(struct hid_device *hid, unsigned char mode) +{ + int rc = 0; + + struct hid_report *report = NULL; + struct hid_report *report_cur = NULL; + __s32 *val = NULL; + /** Packet forming */ + UBT_DUMMY_DEBUG + + list_for_each_entry(report, + &hid->report_enum[HID_OUTPUT_REPORT].report_list, + list) + { + if (hid->report_enum[HID_OUTPUT_REPORT].numbered) + report_cur = report; + } + + switch (mode) { + case MODE_MOUSE: { + mode = 0x00; + break; + } + case MODE_DGTZR: { + mode = 0x01; + break; + } + default: { + rc = -EIO; + return rc; + } + } + val = report_cur->field[0]->value; + val[0] = 0x7e; + val[1] = 0x04; + val[2] = 0x4d; + val[3] = mode; + val[4] = 0x00; + val[5] = 0x0a; + val[6] = 0x00; + val[7] = 0x00; + + UBT_DUMMY_DEBUG + + if (report_cur == NULL) { + if (ubt_debug) + printk(KERN_DEBUG "ubt780 : switch mode : report_cur = 0"); + return rc; + } + + if (ubt_debug) + printk(KERN_DEBUG "ubt780 :switch_mode:\ + usbhid_submit_report\ + drv=%p p=%p mode=%d", + hid, report_cur, mode); + /*Send report to device*/ + + usbhid_submit_report(hid, report_cur, USB_DIR_OUT); + UBT_DUMMY_DEBUG + return rc; +} + +/* PenToInt : converts ultrasound clock measurements to screen coordinates.*/ +/*Apply calibration to ultrasound clock measurements*/ +int xold = 0, yold = 0; +bool ubt_pen_to_int(int *pLeft, int *pRight) +{ + int left2, right2 , n, w_n, sqr, xx, yy; + bool bRet = false; + static int w = 1164; + + left2 = (*pLeft)*(*pLeft); + right2 = (*pRight)*(*pRight); + UBT_DUMMY_DEBUG + if (left2 == 0 && right2 == 0) { + *pLeft = (int)xold; + *pRight = (int)yold; + if (ubt_debug) + printk(KERN_DEBUG + "ubt780_XY old values: %d,%d", xold, yold); + return true; + } + + n = (right2 - left2) / (2*w); + w_n = w-n; + + sqr = (2*left2 - (w_n * w_n)); + + if (sqr < 0) + return bRet; + + xx = (w_n + int_sqrt(sqr)) / 2; + yy = xx + n; + + if (xx < 0 || yy < 0) + return bRet; + + *pLeft = (int)xx; + *pRight = (int)yy; + bRet = true; + if (ubt_debug) + printk(KERN_DEBUG "ubt780_XY: %d,%d", xx, yy); + + return bRet; +} + +static bool ubt_calibrate(int inX, int inY, int *outX, int *outY, struct ubt_calib *calib) +{ + int Y_LTY, X_LTX, Xoff, Xmag, Yoff, Ymag; + unsigned int x, y; + xold = inX; + yold = inY; + + Y_LTY = inY - calib->LTY; + X_LTX = inX - calib->LTX; + + Xoff = Y_LTY * calib->Xoff_A + calib->LTX * 10000; + Xmag = (Y_LTY * calib->Xmag_A) / 10000; + Xmag += calib->Xmag_B; + Yoff = X_LTX * calib->Yoff_A + calib->LTY * 10000; + Ymag = X_LTX * calib->Ymag_A / 10000; + Ymag += calib->Ymag_B; + + if (!Xmag) + Xmag = 1; + x = (inX * 10000 - Xoff) / Xmag; + x += calib->LTx; + + if (!Ymag) + Ymag = 1; + y = (inY * 10000 - Yoff) / Ymag; + y += calib->LTy; + + if (x < 0) + x = 0; + if (x > UBT880_MAX_AXIS_X) + x = UBT880_MAX_AXIS_X; + if (y < 0) + y = 0; + if (y > UBT880_MAX_AXIS_Y) + y = UBT880_MAX_AXIS_Y; + + if (ubt_debug) + printk(KERN_DEBUG "%d - %d", x, y); + *outX = x; + *outY = y; + return true; +} + +/* +input_mapping : Set appropriate bits for input device according to current field, usage and usage page. +For info about valid usage pages and usages see include/linux/hid.h +For more info on input see Documentation/input/input.txt and Documentation/input/input-programming.txt +and of course include/linux/input.h +What to return? +return < 0 - field was ignored +return > 0 - field was mapped +return == 0 - let hid driver map this field for you +*/ +static int ubt880_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + /*Just ignore all fields*/ + return -1; +} +/* + * input_mapped : called from hidinput_configure_usage after input is mapped. + * Never called if you have returned negative value from input_mapping. + * Return values: + * return < 0 - ignore field + * return >= 0 - do further processing + */ +/*static int ubt880_input_mapped(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + return -1; +}*/ + + +/* event : Called on every single report field. + * Return : + * return == 0 - do generic input hidinput and hiddev processing. See hid_process_event in hid-core.c. + * return > 0 - finish processing. + */ +static int ubt880_event(struct hid_device *hid, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + /*Just prevent further processing by hid*/ + return 1; +} +static void ubt880_set_input(struct input_dev *input) +{ + /* Basics */ + /*We have a key*/ + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_LEFT, input->keybit); + __set_bit(BTN_RIGHT, input->keybit); + /*two absolute axis*/ + __set_bit(EV_ABS, input->evbit); + __set_bit(ABS_X, input->absbit); + __set_bit(ABS_Y, input->absbit); + /*two absolute MT axis and tracking IDs*/ + input_set_abs_params(input, ABS_MT_POSITION_X, 0, UBT880_MAX_AXIS_X, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, UBT880_MAX_AXIS_Y, 0, 0); + input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, 3, 0, 0); + input_set_abs_params(input, ABS_X, 0, UBT880_MAX_AXIS_X, 0, 0); + input_set_abs_params(input, ABS_Y, 0, UBT880_MAX_AXIS_Y, 0, 0); +} + +static void ubt780_set_input(struct input_dev *input) +{ + input->evbit[0] |= BIT(EV_KEY) | BIT(EV_ABS); + input->absbit[0] |= BIT(ABS_X) | BIT(ABS_Y); + set_bit(BTN_LEFT, input->keybit); + set_bit(BTN_RIGHT, input->keybit); + + input->absmax[ABS_X] = UBT780_MAX_AXIS_X; + input->absmax[ABS_Y] = UBT780_MAX_AXIS_Y; + input->absmin[ABS_X] = 0; + input->absmin[ABS_Y] = 0; +} + +int ubt_set_device(struct ubt880_data *data, __u32 devid) +{ + int ret; + data->calib.calibrated = 0; + data->ubt_packet.report = 0; + set_calib_to_device(data); + switch (devid) { + case USB_DEVICE_ID_PANABOARD_UBT780: { + data->switch_mode = ubt780_switch_mode; + data->set_input = ubt780_set_input; + break; + } + case USB_DEVICE_ID_PANABOARD_UBT880: { + data->switch_mode = ubt880_switch_mode; + data->set_input = ubt880_set_input; + break; + } + default: { + ret = -1; + } + } + return 0; +} +static int ubt880_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + struct ubt880_data *drvdata; + struct hid_input *hidinput = NULL; + struct input_dev *input; + + UBT_DUMMY_DEBUG + + drvdata = kmalloc(sizeof(struct ubt880_data), GFP_KERNEL); + if (!drvdata) { + dev_err(&hdev->dev, "cannot allocate UB-T880 data\n"); + return -ENOMEM; + } + + hid_set_drvdata(hdev, drvdata); + + ret = hid_parse(hdev); + if (!ret) { + if (ubt_debug) + printk(KERN_DEBUG "Trying hid_hw_start"); + /*We need HIDINPUT dev only. We have our own char device instead of hidraw*/ + ret = hid_hw_start(hdev, HID_CONNECT_HIDINPUT); + if (ubt_debug) + printk(KERN_DEBUG "hid_hw_start() returned : %d", ret); + } + + if (ret) { + kfree(drvdata); + goto skip_input; + } + ubt_chrdev_connect(hdev, drvdata); + ubt_set_device(drvdata, hdev->product); + + drvdata->switch_mode(hdev, MODE_MOUSE); + drvdata->current_mode = MODE_MOUSE; + + UBT_DUMMY_DEBUG + hidinput = list_entry(hdev->inputs.next, struct hid_input, list); + if (hidinput == NULL) { + if (ubt_debug) + printk(KERN_DEBUG "ubt880: probe : Failed to get valid hidinput"); + goto skip_input; + } + UBT_DUMMY_DEBUG + input = hidinput->input; + + UBT_DUMMY_DEBUG + drvdata->set_input(input); +skip_input: + return ret; +} +/* Our own char device for ioctls */ +/*---------------------------------------------------------------------------------------------*/ + + +static const struct file_operations ubt880_fops = { + .owner = THIS_MODULE, + .open = ubt880_open_io, + .release = ubt880_release_io, + .unlocked_ioctl = ubt880_ioctl, +}; + +static int ubt_chrdev_init(void) +{ + int result; + dev_t dev_id; + + result = alloc_chrdev_region(&dev_id, UBT780_FIRST_MINOR, + UBT780_MAX_DEVICES, "ubt_chr"); + + ubt_major = MAJOR(dev_id); + + if (result < 0) { + printk(KERN_WARNING "ubt: can't get major number\n"); + result = 0; + goto out; + } + + ubt_class = class_create(THIS_MODULE, "ubt_chr"); + if (IS_ERR(ubt_class)) { + result = PTR_ERR(ubt_class); + unregister_chrdev(ubt_major, "ubt_chr"); + goto out; + } + + cdev_init(&ubt_cdev, &ubt880_fops); + cdev_add(&ubt_cdev, dev_id, UBT780_MAX_DEVICES); +out: + return result; +} + +static void ubt_chrdev_cleanup(void) +{ + dev_t dev_id = MKDEV(ubt_major, 0); + cdev_del(&ubt_cdev); + class_destroy(ubt_class); + unregister_chrdev_region(dev_id, UBT780_MAX_DEVICES); +} +static int ubt_chrdev_connect(struct hid_device *hid, struct ubt880_data *drvdata) +{ + int minor, result; + struct ubt_chrdev *dev; + + dev = kzalloc(sizeof(struct ubt_chrdev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + result = -EINVAL; + + mutex_lock(&minors_lock); + + for (minor = 0; minor < UBT780_MAX_DEVICES; minor++) { + if (ubt_table[minor]) + continue; + ubt_table[minor] = dev; + result = 0; + break; + } + UBT_DUMMY_DEBUG + printk(KERN_DEBUG "ubt_chrdev_connect : result = %X", result); + + if (result) { + mutex_unlock(&minors_lock); + kfree(dev); + goto out; + } + printk(KERN_DEBUG "ubt : chrdev_connect : ubt_majo=%d, minor=%d", ubt_major, minor); + dev->dev = device_create(ubt_class, &hid->dev, MKDEV(ubt_major, minor), + NULL, "%s%d", "ubt_", minor); + + if (IS_ERR(dev->dev)) { + ubt_table[minor] = NULL; + mutex_unlock(&minors_lock); + result = PTR_ERR(dev->dev); + kfree(dev); + drvdata->chrdev = NULL; + goto out; + } + + UBT_DUMMY_DEBUG + printk(KERN_DEBUG "ubt_chrdev_connect : result = %X", result); + + mutex_unlock(&minors_lock); + + dev->hid = hid; + drvdata->chrdev = dev; + dev->minor = minor; + + dev->exist = 1; + +out: + printk(KERN_DEBUG "ubt_chrdev_connect : result = %X", result); + return result; + +} + +static void ubt_chrdev_disconnect(struct hid_device *hid) +{ + + struct ubt880_data *ubt880_drv = hid_get_drvdata(hid); + struct ubt_chrdev *ubt880 = ubt880_drv->chrdev; + + if (!ubt880) + return; + + ubt880->exist = 0; + + mutex_lock(&minors_lock); + ubt_table[ubt880->minor] = NULL; + mutex_unlock(&minors_lock); + + device_destroy(ubt_class, MKDEV(ubt_major, ubt880->minor)); + + if (ubt880->open) { + hid->ll_driver->close(hid); + } else { + kfree(ubt880); + } +} + + +static int ubt880_open_io(struct inode *node, struct file *file) +{ + unsigned int minor = iminor(node); + struct ubt_chrdev *device; + int error = 0; + + mutex_lock(&minors_lock); + UBT_DUMMY_DEBUG + device = ubt_table[minor]; + + if (!device) { + printk(KERN_EMERG "ubt880 device with minor %d doesn't exist\n", + minor); + error = -ENODEV; + goto exit_unlock; + } + + if (!device->open) { + device->open++; + /* power on device */ + if (device->hid->ll_driver->power) { + error = device->hid->ll_driver->power(device->hid, + PM_HINT_FULLON); + if (error < 0) { + --device->open; + goto exit_unlock; + } + } + error = device->hid->ll_driver->open(device->hid); + if (error < 0) { + if (device->hid->ll_driver->power) + device->hid->ll_driver->power(device->hid, + PM_HINT_NORMAL); + --device->open; + goto exit_unlock; + } + } else { + device->open++; + /*error = -EBUSY;*/ + } + +exit_unlock: + mutex_unlock(&minors_lock); + return error; +} + +static int ubt880_release_io(struct inode *node, struct file *file) +{ + unsigned int minor = iminor(node); + struct ubt_chrdev *dev; + int ret = 0; + UBT_DUMMY_DEBUG + mutex_lock(&minors_lock); + if (!ubt_table[minor]) { + ret = -ENODEV; + goto unlock; + } + + dev = ubt_table[minor]; + if (dev->open) { + dev->open--; + if (dev->open) + goto unlock; + if (dev->hid->ll_driver->power) + dev->hid->ll_driver->power(dev->hid, PM_HINT_NORMAL); + dev->hid->ll_driver->close(dev->hid); + } + + ret = 0; +unlock: + mutex_unlock(&minors_lock); + + return ret; +} +static void set_calib_to_driver(struct ubt_calib *calib) +{ + + if (!calib) + return; + LTx = calib->LTx; + LTy = calib->LTy; + LBx = calib->LBx; + LBy = calib->LBy; + RTx = calib->RTx; + RTy = calib->RTy; + RBx = calib->RBx; + RBy = calib->RBy; + + LTX = calib->LTX; + LTY = calib->LTY; + LBX = calib->LBX; + LBY = calib->LBY; + RTX = calib->RTX; + RTY = calib->RTY; + RBX = calib->RBX; + RBY = calib->RBY; + + Xoff_A = calib->Xoff_A; + Xmag_A = calib->Xmag_A; + Xmag_B = calib->Xmag_B; + Yoff_A = calib->Yoff_A; + Ymag_A = calib->Ymag_A; + Ymag_B = calib->Ymag_B; + + isCalibrated = 1; +} + + +static void set_calib_to_device(struct ubt880_data *drvdata) +{ + + if (!drvdata) + return; + if (!isCalibrated) + return; + drvdata->calib.LTx = LTx; + drvdata->calib.LTy = LTy; + drvdata->calib.LBx = LBx; + drvdata->calib.LBy = LBy; + drvdata->calib.RTx = RTx; + drvdata->calib.RTy = RTy; + drvdata->calib.RBx = RBx; + drvdata->calib.RBy = RBy; + + drvdata->calib.LTX = LTX; + drvdata->calib.LTY = LTY; + drvdata->calib.LBX = LBX; + drvdata->calib.LBY = LBY; + drvdata->calib.RTX = RTX; + drvdata->calib.RTY = RTY; + drvdata->calib.RBX = RBX; + drvdata->calib.RBY = RBY; + + drvdata->calib.Xoff_A = Xoff_A; + drvdata->calib.Xmag_A = Xmag_A; + drvdata->calib.Xmag_B = Xmag_B; + drvdata->calib.Yoff_A = Yoff_A; + drvdata->calib.Ymag_A = Ymag_A; + drvdata->calib.Ymag_B = Ymag_B; + + drvdata->calib.calibrated = 1; +} + +static long ubt880_ioctl(struct file *iofile, unsigned int command, unsigned long data) +{ + + int retval = 0; + struct inode *inode = iofile->f_path.dentry->d_inode; + unsigned int minor = iminor(inode); + struct ubt_chrdev *dev; + struct ubt880_data *drvdata; + mutex_lock(&minors_lock); + dev = ubt_table[minor]; + if (!dev) { + retval = -ENODEV; + goto out; + } + drvdata = hid_get_drvdata(dev->hid); + switch (command) { + case GET_BATTERY_STATUS: + { + int *in = (int *) data; + *in = 0; + retval = 0; + break; + } + case GET_LAST_PACKET_OLD: + case GET_LAST_PACKET: + { + struct ubt880_dgtzr *packsrc = &drvdata->ubt_packet; + struct ubt880_dgtzr *packdst = (struct ubt880_dgtzr *) data; + + if (packsrc->report != 0x00) { + memcpy((void *)packdst, (void *)packsrc, sizeof(struct ubt880_dgtzr)); + packsrc->report = 0; + if (ubt_debug) + printk(KERN_DEBUG "ubt880_ioctl_io : GET_LAST_PACKET2"); + } + + retval = 0; + break; + } + case GET_MODE: + { + int *in = (int *) data; + printk(KERN_DEBUG "ubt880_ioctl_io : GET_MODE"); + *in = drvdata->current_mode; + retval = 0; + break; + } + case SET_MODE: + { + int *dt = (int *)data; + printk(KERN_DEBUG "ubt_ioctl_io : SET_MODE"); + drvdata->ubt_packet.report = 0; + + if (drvdata->switch_mode(dev->hid, *dt) != 0) { + retval = -EIO; + if (ubt_debug) + printk(KERN_DEBUG "ubt_ioctl_io : SET_MODE error"); + } else { + drvdata->current_mode = *dt; + if (ubt_debug) + printk(KERN_DEBUG "ubt_ioctl_io : SET_MODE ok, mode = %d", drvdata->current_mode); + retval = 0; + } + break; + } + case SET_CALIB: { + struct ubt_calib *calib = (struct ubt_calib *) data; + + if (ubt_debug) + printk(KERN_DEBUG "ubt880_ioctl_io : SET_CALIB"); + + set_calib_to_driver(calib); + set_calib_to_device(drvdata); + + retval = 0; + break; + } + case GET_DEVICE_ID: { + int *in = (int *) data; + if (ubt_debug) + printk(KERN_DEBUG "ubt_ioctl_io : get device id"); + *in = dev->hid->product; + retval = 0; + break; + } +#ifdef UBT780_DEBUG + case TEST_MT_PACKET: { + + send_test_packet(dev->hid); + retval = 0; + break; + } + case TEST_CALIBRATION: { + + unsigned short *coord = (unsigned short *)data; + fake_calibration(dev->hid, coord); + retval = 0; + break; + } +#endif + default: + { + retval = -EIO; + if (ubt_debug) + printk(KERN_DEBUG "ubt880_ioctl_io : unknown command %d", command); + break; + } + } +out: + mutex_unlock(&minors_lock); + return retval; +} +/*-----------------------------------------------------------------------------------------------*/ +static void ubt_report_input(struct input_dev *input, unsigned int x, unsigned int y, int btn_left, int btn_right) +{ + /* To prevent situation when new coords arrive before unpess */ + /* and a line is drawn istead of 2 dots */ + input_report_key(input, BTN_LEFT, btn_left); + input_report_key(input, BTN_RIGHT, btn_right); + /*if (btn)*/ + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + + input_sync(input); +} +/* Debug output of raw MT packet from device */ +static void ubt880_print_multitouch(struct ubt_mt_contact *pack, int size) +{ + int i; + printk(KERN_DEBUG "-------------------------------------"); + for (i = 0; i < size; i++) { + printk(KERN_DEBUG "-----------%d------------", i); + printk(KERN_DEBUG "ubt880: mt packet: flags : inrange %X | tipswitch %X", pack[i].flags & 0x02, pack[i].flags & 0x01); + printk(KERN_DEBUG "ubt880: mt packet: id | %d", pack[i].id); + printk(KERN_DEBUG "ubt880: mt packet: X | %d", pack[i].x); + printk(KERN_DEBUG "ubt880: mt packet: Y | %d", pack[i].y); + printk(KERN_DEBUG "-------------------------"); + } + printk(KERN_DEBUG "ubt880: mt packet: count: %d", size); +} +/* Report MT to the input subsystem*/ +static void ubt880_report_contact(struct input_dev *input, struct ubt_mt_contact *contact) +{ + if (!contact || !input) + return; + input_report_abs(input, ABS_MT_TRACKING_ID, contact->id); + input_report_abs(input, ABS_MT_POSITION_X, contact->x); + input_report_abs(input, ABS_MT_POSITION_Y, contact->y); + input_mt_sync(input); +} +static void ubt880_report_mt(struct input_dev *input, struct ubt_mt_contact *pack, int size, struct ubt_calib *calib) +{ + int i = 0; + /* Report MT contacts */ + if (size < 1) + return; + if (!input || !pack || !calib) + return; + + for (i = 0; i < size; i++) { + /* If Tip Switch bit is set */ + if (pack[i].flags & 0x01) { + if (calib->calibrated) { + ubt_calibrate(pack[i].x, + pack[i].y, + (int *)&pack[i].x, + (int *)&pack[i].y, + calib); + } + ubt880_report_contact(input, &pack[i]); + } + } + input_sync(input); + /* Emulate single-touch device. Only first contact is used. */ + /* Note that is already calibrated */ + ubt_report_input(input, pack[0].x, pack[0].y, pack[0].flags & 0x01, 0); +} + +#ifdef UBT780_DEBUG +static void fake_calibration(struct hid_device *hdev, unsigned short *coord) +{ + char pack[6]; + unsigned short *pack_coord = (unsigned short *)&pack[2]; + pack[0] = 0x01; + pack[1] = coord[0]; + pack_coord[0] = coord[1]; + pack_coord[1] = coord[2]; + ubt880_raw_event(hdev, 0, (u8 *)&pack, 6); +} +static void send_test_packet(struct hid_device *hdev) +{ + char testdata[20]; + struct ubt_mt_contact *contacts; + struct ubt_calib calib; + + calib.calibrated = 0; + + testdata[0] = 0x03; + contacts = (struct ubt_mt_contact *)&testdata[1]; + contacts[0].flags = 0x01; + contacts[0].id = 0x01; + contacts[0].x = 205; + contacts[0].y = 1023; + + contacts[1].flags = 0x01; + contacts[1].id = 0x02; + contacts[1].x = 1283; + contacts[1].y = 1048; + + contacts[2].flags = 0x01; + contacts[2].id = 0x03; + contacts[2].x = 508; + contacts[2].y = 2473; + + testdata[19] = 3; + + ubt880_raw_event(hdev, 0, (u8 *)&testdata, 20); +} +#endif + +static struct input_dev *ubt880_get_input(struct hid_device *hdev) +{ + struct hid_input *hidinput = list_entry(hdev->inputs.next, struct hid_input, list); + struct input_dev *input; + + if (!hidinput) + return 0; + input = hidinput->input; + return input; +} +static int ubt880_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +{ + struct ubt880_data *driver_data = hid_get_drvdata(hdev); + struct ubt880_dgtzr *pack = &driver_data->ubt_packet; + + struct input_dev *input = ubt880_get_input(hdev); + if (!input) + return -1; + + if (!driver_data) + return -1; + /*Save the packet for userspace processing*/ + /*Mouse mode packet*/ + if (ubt_debug) + printk(KERN_DEBUG "ubt880:raw_event: report = %X", data[0]); + switch (data[0]) { + case 0x01: { + int X, Y; + memcpy((void *)pack, (void *)data, size); + if (ubt_debug) + printk(KERN_DEBUG "ubt880_mode: Mouse"); + + if (driver_data->current_mode == MODE_UKN) + driver_data->current_mode = MODE_MOUSE; + + if (ubt_debug) + printk(KERN_DEBUG "ubt880: mouse packet: X=%d,Y=%d,Button=%d", + pack->data[0], pack->data[1], pack->command); + X = pack->data[0]; + Y = pack->data[1]; + if (driver_data->calib.calibrated) { + ubt_calibrate(X, Y, &X, &Y, &driver_data->calib); + if (ubt_debug) + printk(KERN_DEBUG "ubt880: mouse packet calibrated: X=%d,Y=%d", + X, Y); + } + + ubt_report_input(input, X, Y, pack->command & 0x01, pack->command & 0x02); + /*Switch to digitizer*/ + if (driver_data->current_mode != MODE_MOUSE) + ubt880_switch_mode(hdev, MODE_MOUSE); + break; + } + case 0x02: { + struct ubt780_dgtzr *pack = (struct ubt780_dgtzr *)data; + if (ubt_debug) + printk(KERN_DEBUG "ubt780_mode: DIG"); + if (driver_data->current_mode == MODE_UKN) + driver_data->current_mode = MODE_DGTZR; + + if (driver_data->current_mode == MODE_MOUSE) { + pack->report = 0; + if (ubt_debug) + printk(KERN_DEBUG "ubt780_current_mode: MOUSE"); + /*Switch to mouse*/ + driver_data->switch_mode(hdev, MODE_MOUSE); + } else if (driver_data->current_mode == MODE_DGTZR) { + unsigned short *pCoord = (unsigned short *)(&pack->data[3]); + struct ubt_calib *calib = &driver_data->calib; + int X = (int) pCoord[0]; + int Y = (int) pCoord[1]; + int leftBtn = pack->data[2] >> 5 & 0x01; + int rightBtn = pack->data[2] >> 4 & 0x01; + + ubt_pen_to_int(&X, &Y); + + driver_data->ubt_packet.report = 0x02; + driver_data->ubt_packet.command = pack->data[2]; + driver_data->ubt_packet.data[0] = X; + driver_data->ubt_packet.data[1] = Y; + + if (driver_data->calib.calibrated) { + if (ubt_calibrate(X, Y, &X, &Y, calib)) { + if (ubt_debug) + printk(KERN_DEBUG "ubt780_mode:\ + Calibrated;\ + x=%d y=%d,left_btn=%d\n", + X, Y, leftBtn); + ubt_report_input(input, X, Y, leftBtn, rightBtn); + } + } else { + ubt_report_input(input, X, Y, leftBtn, rightBtn); + if (ubt_debug) + printk(KERN_DEBUG "ubt780_mode:\ + Not calibrated;\ + x=%d y=%d,left_btn=%d\n", + X, Y, leftBtn); + } + } + break; + } + case 0x03: { + struct ubt_mt_packet *packet = (struct ubt_mt_packet *)data; + if (ubt_debug) { + printk(KERN_DEBUG "ubt880_mode: Multitouch"); + printk(KERN_DEBUG "ubt880_driver_mode : %d", driver_data->current_mode); + } + if (driver_data->current_mode == MODE_UKN) + driver_data->current_mode = MODE_DGTZR; + + if (ubt_debug) + ubt880_print_multitouch((struct ubt_mt_contact *)&packet->data[1], packet->data[19]); + + if (driver_data->current_mode == MODE_MOUSE) { + driver_data->switch_mode(hdev, MODE_MOUSE); + } else if (driver_data->current_mode == MODE_DGTZR) { + ubt880_report_mt(input, (struct ubt_mt_contact *)&packet->data[1], packet->data[19], &driver_data->calib); + } + break; + } + default: + if (ubt_debug) + printk(KERN_DEBUG "ubt880: raw_report: Unknown report %X", pack->report); + } + + + UBT_DUMMY_DEBUG + return 0; +} +static void ubt880_remove(struct hid_device *hdev) +{ + UBT_DUMMY_DEBUG + ubt_chrdev_disconnect(hdev); + hid_hw_stop(hdev); + kfree(hid_get_drvdata(hdev)); + hid_set_drvdata(hdev, NULL); +} + +static struct hid_device_id ubt880_devices[] = { + { HID_USB_DEVICE(0x04da, 0x104d) }, + { HID_USB_DEVICE(0x04da, 0x1044) }, + { } +}; +MODULE_DEVICE_TABLE(hid, ubt880_devices); + +static struct hid_driver ubt880_driver = { + .name = "ubt880", + .id_table = ubt880_devices, + .probe = ubt880_probe, + .remove = ubt880_remove, + .input_mapping = ubt880_input_mapping, + .raw_event = ubt880_raw_event, + .event = ubt880_event, +}; + +static int __init ubt880_init(void) +{ + int retval = ubt_chrdev_init(); + retval = hid_register_driver(&ubt880_driver); + UBT_DUMMY_DEBUG + if (ubt_debug) + printk(KERN_DEBUG "ubt880 : init : chrdev_init : retval = %X", retval); + return retval; +} + +static void __exit ubt880_exit(void) +{ + UBT_DUMMY_DEBUG + hid_unregister_driver(&ubt880_driver); + ubt_chrdev_cleanup(); +} + +module_init(ubt880_init); +module_exit(ubt880_exit); + +MODULE_AUTHOR("Anton Chikin "); +MODULE_DESCRIPTION("Panasonic UB-T780 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ubt880.h b/drivers/hid/hid-ubt880.h new file mode 100644 index 0000000..e1451f1 --- /dev/null +++ b/drivers/hid/hid-ubt880.h @@ -0,0 +1,157 @@ +/* + * USB HID driver for Panasonic elite Panaboard UTB780 + * Copyright (c) 2008 Igor Shakirov, Victor Grenke + + * Information. + * It's driver for supporting Panaboard elite USB device. + * The driver is redesigned using usb hid core driver: + * Copyright (c) 1999 Andreas Gal + * Copyright (c) 2000-2005 Vojtech Pavlik + * Copyright (c) 2005 Michael Haboustak for Concept2, Inc + * Copyright (c) 2010 Panasonic + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ +#include +#ifndef UBT780CTRL +#define UBT780CTRL + +/*IOCTRL codes*/ +/** Battary status IOCTL defines */ +#define GET_BATTERY_STATUS 0x01 /*get battery status, return BATTERY_STATUS_UKN, BATTERY_STATUS_FINE, BATTERY_STATUS_WEAK*/ +#define GET_LAST_PACKET_OLD 0x02 /*get last packet (ubt780_dgtzr). It is necessary for calibration*/ + +/** Modes of work IOCTL */ +#define GET_MODE 0x10 /*get current mode, return MODE_MOUSE, MODE_DGTZR*/ +#define SET_MODE 0x11 /*switch to mouse mode, MODE_MOUSE, MODE_DGTZR*/ +#define SET_CALIB 0x20 /*set calibration mode*/ +#define GET_LAST_PACKET 0x21 /*get last packet (ubt780_dgtzr). It is necessary for calibration*/ +#define GET_DEVICE_ID 0x22 /*get device id to recognize the device version*/ +#define TEST_MT_PACKET 0x23 +#define TEST_CALIBRATION 0x24 +/*return values for ioctrl*/ +/** Battary status defines */ +#define BATTERY_STATUS_FINE 0x00 /*battery is fine*/ +#define BATTERY_STATUS_WEAK 0x01 /*battery is weak*/ +#define BATTERY_STATUS_UKN 0x02 /*unknown status. Status will be known after the first pen touch*/ + +/** Mode status defines */ +#define MODE_MOUSE 0x10 /*current mode is mouse*/ +#define MODE_DGTZR 0x11 /*current mode is digitizer*/ +#define MODE_UKN 0x12 /*current mode is unknown. It will be known after the first pen touch*/ +#define MODE_SINGLETOUCH 0x13 + +/*Char device declarations*/ +#define UBT780_MAX_DEVICES 0x05 +#define UBT780_FIRST_MINOR 0 + +#define UBT780_MAX_AXIS_X 4095 +#define UBT780_MAX_AXIS_Y 4095 +#define UBT880_MAX_AXIS_X 32767 +#define UBT880_MAX_AXIS_Y 32767 +#define NUM_CONTACTS 0x03 +/** Digitizer mode stucture: contains packet data */ +struct ubt880_dgtzr { + /** Report contains the type of packet: 0 - mouse, 1 - digitize */ + unsigned char report; + /** Command is coming from device */ + unsigned char command; + /** Packet size */ + unsigned short data[2]; +}; + +struct ubt780_dgtzr { + /** Report contains the type of packet: 0 - mouse, 1 - digitize */ + unsigned char report; + /** Command is coming from device */ + unsigned char command; + /** Packet size */ + unsigned char number; + /** Data part of packet */ + unsigned char data[17]; +}; +/** Calibration stucture: contains calibration data */ +struct ubt_calib { + /** Screen coordinates: Left Top X */ + int LTx; + /** Screen coordinates: Left Top Y */ + int LTy; + /** Screen coordinates: Left Bottom X */ + int LBx; + /** Screen coordinates: Left Bottom Y */ + int LBy; + /** Screen coordinates: Right Top X */ + int RTx; + /** Screen coordinates: Right Top Y */ + int RTy; + /** Screen coordinates: Right Bottom X */ + int RBx; + /** Screen coordinates: Right Bottom Y */ + int RBy; + + /** Board coordinates: Left Top X */ + int LTX; + /** Board coordinates: Left Top Y */ + int LTY; + /** Board coordinates: Left Bottom X */ + int LBX; + /** Board coordinates: Left Bottom Y */ + int LBY; + /** Board coordinates: Right Top X */ + int RTX; + /** Board coordinates: Right Top Y */ + int RTY; + /** Board coordinates: Right Bottom X */ + int RBX; + /** Board coordinates: Right Bottom Y */ + int RBY; + + /** Calculated values */ + int Xoff_A; + int Xmag_A; + int Xmag_B; + int Yoff_A; + int Ymag_A; + int Ymag_B; + int calibrated; +}; +/*Character device data structure*/ +struct ubt_chrdev { + unsigned int minor; + int exist; + int open; + struct hid_device *hid; + struct device *dev; +}; +/*Driver data structure*/ +struct ubt780_data { + struct ubt_calib calib; + struct ubt780_dgtzr ubt_packet; + struct ubt_chrdev *chrdev; + unsigned char current_mode; +}; + +struct ubt880_data { + struct ubt_calib calib; + struct ubt880_dgtzr ubt_packet; + struct ubt_chrdev *chrdev; + unsigned char current_mode; + int (*switch_mode) (struct hid_device *hid, unsigned char mode); + void (*set_input) (struct input_dev *input); +}; +/*ubt880 multitouch structures*/ +struct ubt_mt_contact { + unsigned char flags; + unsigned char id; + unsigned short x; + unsigned short y; +}; +struct ubt_mt_packet { + unsigned char data[20]; +}; +#endif /* UBT780CTRL */