hid-3m-pct.c 8.2 KB
Newer Older
1 2 3
/*
 *  HID driver for 3M PCT multitouch panels
 *
4
 *  Copyright (c) 2009-2010 Stephane Chatty <chatty@enac.fr>
H
Henrik Rydberg 已提交
5 6
 *  Copyright (c) 2010      Henrik Rydberg <rydberg@euromail.se>
 *  Copyright (c) 2010      Canonical, Ltd.
7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 */

/*
 * 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 <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
20
#include <linux/slab.h>
21 22 23 24 25 26 27 28
#include <linux/usb.h>

MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>");
MODULE_DESCRIPTION("3M PCT multitouch panels");
MODULE_LICENSE("GPL");

#include "hid-ids.h"

29
#define MAX_SLOTS		60
H
Henrik Rydberg 已提交
30 31 32 33 34 35
#define MAX_TRKID		USHRT_MAX
#define MAX_EVENTS		360

/* estimated signal-to-noise ratios */
#define SN_MOVE			2048
#define SN_WIDTH		128
36

37
struct mmm_finger {
38
	__s32 x, y, w, h;
H
Henrik Rydberg 已提交
39 40
	__u16 id;
	bool prev_touch;
41 42 43 44
	bool touch, valid;
};

struct mmm_data {
45
	struct mmm_finger f[MAX_SLOTS];
H
Henrik Rydberg 已提交
46
	__u16 id;
47
	__u8 curid;
48
	__u8 nexp, nreal;
49 50 51 52 53 54 55
	bool touch, valid;
};

static int mmm_input_mapping(struct hid_device *hdev, struct hid_input *hi,
		struct hid_field *field, struct hid_usage *usage,
		unsigned long **bit, int *max)
{
H
Henrik Rydberg 已提交
56 57 58 59
	int f1 = field->logical_minimum;
	int f2 = field->logical_maximum;
	int df = f2 - f1;

60 61 62 63 64 65 66 67 68 69
	switch (usage->hid & HID_USAGE_PAGE) {

	case HID_UP_BUTTON:
		return -1;

	case HID_UP_GENDESK:
		switch (usage->hid) {
		case HID_GD_X:
			hid_map_usage(hi, usage, bit, max,
					EV_ABS, ABS_MT_POSITION_X);
H
Henrik Rydberg 已提交
70 71
			input_set_abs_params(hi->input, ABS_MT_POSITION_X,
					     f1, f2, df / SN_MOVE, 0);
72 73
			/* touchscreen emulation */
			input_set_abs_params(hi->input, ABS_X,
H
Henrik Rydberg 已提交
74
					     f1, f2, df / SN_MOVE, 0);
75 76 77 78
			return 1;
		case HID_GD_Y:
			hid_map_usage(hi, usage, bit, max,
					EV_ABS, ABS_MT_POSITION_Y);
H
Henrik Rydberg 已提交
79 80
			input_set_abs_params(hi->input, ABS_MT_POSITION_Y,
					     f1, f2, df / SN_MOVE, 0);
81 82
			/* touchscreen emulation */
			input_set_abs_params(hi->input, ABS_Y,
H
Henrik Rydberg 已提交
83
					     f1, f2, df / SN_MOVE, 0);
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
			return 1;
		}
		return 0;

	case HID_UP_DIGITIZER:
		switch (usage->hid) {
		/* we do not want to map these: no input-oriented meaning */
		case 0x14:
		case 0x23:
		case HID_DG_INPUTMODE:
		case HID_DG_DEVICEINDEX:
		case HID_DG_CONTACTCOUNT:
		case HID_DG_CONTACTMAX:
		case HID_DG_INRANGE:
		case HID_DG_CONFIDENCE:
			return -1;
		case HID_DG_TIPSWITCH:
			/* touchscreen emulation */
			hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH);
H
Henrik Rydberg 已提交
103
			input_set_capability(hi->input, EV_KEY, BTN_TOUCH);
104
			return 1;
105 106 107
		case HID_DG_WIDTH:
			hid_map_usage(hi, usage, bit, max,
					EV_ABS, ABS_MT_TOUCH_MAJOR);
H
Henrik Rydberg 已提交
108 109
			input_set_abs_params(hi->input, ABS_MT_TOUCH_MAJOR,
					     f1, f2, df / SN_WIDTH, 0);
110 111 112 113
			return 1;
		case HID_DG_HEIGHT:
			hid_map_usage(hi, usage, bit, max,
					EV_ABS, ABS_MT_TOUCH_MINOR);
H
Henrik Rydberg 已提交
114 115
			input_set_abs_params(hi->input, ABS_MT_TOUCH_MINOR,
					     f1, f2, df / SN_WIDTH, 0);
116
			input_set_abs_params(hi->input, ABS_MT_ORIENTATION,
117
					0, 1, 0, 0);
118
			return 1;
119
		case HID_DG_CONTACTID:
120
			field->logical_maximum = MAX_TRKID;
121 122
			hid_map_usage(hi, usage, bit, max,
					EV_ABS, ABS_MT_TRACKING_ID);
H
Henrik Rydberg 已提交
123 124 125 126 127
			input_set_abs_params(hi->input, ABS_MT_TRACKING_ID,
					     0, MAX_TRKID, 0, 0);
			if (!hi->input->mt)
				input_mt_create_slots(hi->input, MAX_SLOTS);
			input_set_events_per_packet(hi->input, MAX_EVENTS);
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
			return 1;
		}
		/* let hid-input decide for the others */
		return 0;

	case 0xff000000:
		/* we do not want to map these: no input-oriented meaning */
		return -1;
	}

	return 0;
}

static int mmm_input_mapped(struct hid_device *hdev, struct hid_input *hi,
		struct hid_field *field, struct hid_usage *usage,
		unsigned long **bit, int *max)
{
H
Henrik Rydberg 已提交
145
	/* tell hid-input to skip setup of these event types */
146
	if (usage->type == EV_KEY || usage->type == EV_ABS)
H
Henrik Rydberg 已提交
147 148
		set_bit(usage->type, hi->input->evbit);
	return -1;
149 150 151 152 153 154 155 156 157 158
}

/*
 * this function is called when a whole packet has been received and processed,
 * so that it can decide what to send to the input layer.
 */
static void mmm_filter_event(struct mmm_data *md, struct input_dev *input)
{
	struct mmm_finger *oldest = 0;
	int i;
159
	for (i = 0; i < MAX_SLOTS; ++i) {
160 161 162
		struct mmm_finger *f = &md->f[i];
		if (!f->valid) {
			/* this finger is just placeholder data, ignore */
H
Henrik Rydberg 已提交
163 164 165 166
			continue;
		}
		input_mt_slot(input, i);
		if (f->touch) {
167
			/* this finger is on the screen */
168
			int wide = (f->w > f->h);
H
Henrik Rydberg 已提交
169 170 171
			if (!f->prev_touch)
				f->id = md->id++;
			input_event(input, EV_ABS, ABS_MT_TRACKING_ID, f->id);
172 173
			input_event(input, EV_ABS, ABS_MT_POSITION_X, f->x);
			input_event(input, EV_ABS, ABS_MT_POSITION_Y, f->y);
174 175 176 177 178
			input_event(input, EV_ABS, ABS_MT_ORIENTATION, wide);
			input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR,
						wide ? f->w : f->h);
			input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR,
						wide ? f->h : f->w);
179 180
			/* touchscreen emulation: pick the oldest contact */
			if (!oldest || ((f->id - oldest->id) & (SHRT_MAX + 1)))
181 182 183
				oldest = f;
		} else {
			/* this finger took off the screen */
H
Henrik Rydberg 已提交
184
			input_event(input, EV_ABS, ABS_MT_TRACKING_ID, -1);
185
		}
H
Henrik Rydberg 已提交
186
		f->prev_touch = f->touch;
187 188 189 190 191
		f->valid = 0;
	}

	/* touchscreen emulation */
	if (oldest) {
192
		input_event(input, EV_KEY, BTN_TOUCH, 1);
193 194
		input_event(input, EV_ABS, ABS_X, oldest->x);
		input_event(input, EV_ABS, ABS_Y, oldest->y);
195
	} else {
196 197
		input_event(input, EV_KEY, BTN_TOUCH, 0);
	}
198
	input_sync(input);
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
}

/*
 * this function is called upon all reports
 * so that we can accumulate contact point information,
 * and call input_mt_sync after each point.
 */
static int mmm_event(struct hid_device *hid, struct hid_field *field,
				struct hid_usage *usage, __s32 value)
{
	struct mmm_data *md = hid_get_drvdata(hid);
	/*
	 * strangely, this function can be called before
	 * field->hidinput is initialized!
	 */
	if (hid->claimed & HID_CLAIMED_INPUT) {
		struct input_dev *input = field->hidinput->input;
		switch (usage->hid) {
		case HID_DG_TIPSWITCH:
			md->touch = value;
			break;
		case HID_DG_CONFIDENCE:
			md->valid = value;
			break;
223 224 225 226 227 228 229 230
		case HID_DG_WIDTH:
			if (md->valid)
				md->f[md->curid].w = value;
			break;
		case HID_DG_HEIGHT:
			if (md->valid)
				md->f[md->curid].h = value;
			break;
231
		case HID_DG_CONTACTID:
232
			value = clamp_val(value, 0, MAX_SLOTS - 1);
233 234 235 236
			if (md->valid) {
				md->curid = value;
				md->f[value].touch = md->touch;
				md->f[value].valid = 1;
237
				md->nreal++;
238 239 240 241 242 243 244 245 246 247 248
			}
			break;
		case HID_GD_X:
			if (md->valid)
				md->f[md->curid].x = value;
			break;
		case HID_GD_Y:
			if (md->valid)
				md->f[md->curid].y = value;
			break;
		case HID_DG_CONTACTCOUNT:
249 250 251 252 253 254
			if (value)
				md->nexp = value;
			if (md->nreal >= md->nexp) {
				mmm_filter_event(md, input);
				md->nreal = 0;
			}
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
			break;
		}
	}

	/* we have handled the hidinput part, now remains hiddev */
	if (hid->claimed & HID_CLAIMED_HIDDEV && hid->hiddev_hid_event)
		hid->hiddev_hid_event(hid, field, usage, value);

	return 1;
}

static int mmm_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
	int ret;
	struct mmm_data *md;

271 272
	hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC;

273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
	md = kzalloc(sizeof(struct mmm_data), GFP_KERNEL);
	if (!md) {
		dev_err(&hdev->dev, "cannot allocate 3M data\n");
		return -ENOMEM;
	}
	hid_set_drvdata(hdev, md);

	ret = hid_parse(hdev);
	if (!ret)
		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);

	if (ret)
		kfree(md);
	return ret;
}

static void mmm_remove(struct hid_device *hdev)
{
	hid_hw_stop(hdev);
	kfree(hid_get_drvdata(hdev));
	hid_set_drvdata(hdev, NULL);
}

static const struct hid_device_id mmm_devices[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M1968) },
298
	{ HID_USB_DEVICE(USB_VENDOR_ID_3M, USB_DEVICE_ID_3M2256) },
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
	{ }
};
MODULE_DEVICE_TABLE(hid, mmm_devices);

static const struct hid_usage_id mmm_grabbed_usages[] = {
	{ HID_ANY_ID, HID_ANY_ID, HID_ANY_ID },
	{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1}
};

static struct hid_driver mmm_driver = {
	.name = "3m-pct",
	.id_table = mmm_devices,
	.probe = mmm_probe,
	.remove = mmm_remove,
	.input_mapping = mmm_input_mapping,
	.input_mapped = mmm_input_mapped,
	.usage_table = mmm_grabbed_usages,
	.event = mmm_event,
};

static int __init mmm_init(void)
{
	return hid_register_driver(&mmm_driver);
}

static void __exit mmm_exit(void)
{
	hid_unregister_driver(&mmm_driver);
}

module_init(mmm_init);
module_exit(mmm_exit);