device/generic/x86
修訂 | 4721c4db1245134babbeac866bfe5d002e63d9ca (tree) |
---|---|
時間 | 2014-06-04 10:24:41 |
作者 | twisteroid ambassador <twisteroid.ambassador@gmai...> |
Commiter | Chih-Wei Huang |
android_x86: eliminate device/ibm/thinkpad
The code is moved from device/ibm/thinkpad.
@@ -0,0 +1,21 @@ | ||
1 | +# Copyright 2012 The Android-x86 Open Source Project | |
2 | + | |
3 | +LOCAL_PATH := $(call my-dir) | |
4 | +include $(CLEAR_VARS) | |
5 | + | |
6 | +LOCAL_SRC_FILES:= tablet-mode.c | |
7 | + | |
8 | +LOCAL_C_INCLUDES := $(KERNEL_HEADERS) | |
9 | +LOCAL_SHARED_LIBRARIES := liblog | |
10 | + | |
11 | +LOCAL_CFLAGS := -O2 -Wall | |
12 | + | |
13 | +ifeq ($(TARGET_ARCH),x86) | |
14 | +LOCAL_CFLAGS += -Ulinux | |
15 | +endif | |
16 | + | |
17 | +LOCAL_MODULE := tablet-mode | |
18 | +LOCAL_MODULE_TAGS := optional | |
19 | +#LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) | |
20 | + | |
21 | +include $(BUILD_EXECUTABLE) |
@@ -0,0 +1,16 @@ | ||
1 | +CC=gcc | |
2 | +CFLAGS=-c -Wall -O2 | |
3 | +SOURCES=tablet-mode.c | |
4 | +OBJECTS=$(SOURCES:.c=.o) | |
5 | +EXECUTABLE=tablet-mode | |
6 | + | |
7 | +all: $(SOURCES) $(EXECUTABLE) | |
8 | + | |
9 | +$(EXECUTABLE): $(OBJECTS) | |
10 | + $(CC) $(LDFLAGS) $(OBJECTS) -o $@ | |
11 | + | |
12 | +.c.o: | |
13 | + $(CC) $(CFLAGS) $< -o $@ | |
14 | + | |
15 | +clean: | |
16 | + rm $(OBJECTS) $(EXECUTABLE) |
@@ -0,0 +1,125 @@ | ||
1 | +/** | |
2 | + * Convert SW_TABLET_MODE events to SW_LID events for Android | |
3 | + * | |
4 | + * Copyright 2012 The Android-x86 Open Source Project | |
5 | + * | |
6 | + * Author: Stefan Seidel <android@stefanseidel.info> | |
7 | + * | |
8 | + * Licensed under GPLv2 or later | |
9 | + * | |
10 | + **/ | |
11 | + | |
12 | +#define LOG_TAG "tablet-mode" | |
13 | + | |
14 | +#include <sys/stat.h> | |
15 | +#include <poll.h> | |
16 | +#include <fcntl.h> | |
17 | +#include <errno.h> | |
18 | +#include <dirent.h> | |
19 | +#include <cutils/log.h> | |
20 | +#include <linux/input.h> | |
21 | +#include <linux/uinput.h> | |
22 | + | |
23 | +/* we must use this kernel-compatible implementation */ | |
24 | +#define BITS_PER_LONG (sizeof(long) * 8) | |
25 | +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) | |
26 | +#define OFF(x) ((x)%BITS_PER_LONG) | |
27 | +#define BIT(x) (1UL<<OFF(x)) | |
28 | +#define LONG(x) ((x)/BITS_PER_LONG) | |
29 | +#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) | |
30 | + | |
31 | +int openfd(void) | |
32 | +{ | |
33 | + int fd; | |
34 | + const char *dirname = "/dev/input"; | |
35 | + DIR *dir; | |
36 | + if ((dir = opendir(dirname))) { | |
37 | + struct dirent *de; | |
38 | + unsigned long caps[NBITS(SW_TABLET_MODE+1)]; | |
39 | + while ((de = readdir(dir))) { | |
40 | + if (de->d_name[0] != 'e') // eventX | |
41 | + continue; | |
42 | + char name[PATH_MAX]; | |
43 | + snprintf(name, PATH_MAX, "%s/%s", dirname, de->d_name); | |
44 | + fd = open(name, O_RDONLY); | |
45 | + if (fd < 0) { | |
46 | + ALOGE("could not open %s, %s", name, strerror(errno)); | |
47 | + continue; | |
48 | + } | |
49 | + memset(caps, 0, sizeof(caps)); | |
50 | + if (ioctl(fd, EVIOCGBIT(EV_SW, sizeof(caps)), caps) < 1) { | |
51 | + ALOGE("could not get device caps for %s, %s\n", name, strerror(errno)); | |
52 | + continue; | |
53 | + } | |
54 | + if (test_bit(SW_TABLET_MODE, caps)) { | |
55 | + ALOGI("open %s(%s) ok", de->d_name, name); | |
56 | + return fd; | |
57 | + } | |
58 | + close(fd); | |
59 | + } | |
60 | + closedir(dir); | |
61 | + } | |
62 | + return -1; | |
63 | +} | |
64 | + | |
65 | +void send_switch(int ufd, int state) { | |
66 | + struct input_event nev; | |
67 | + ALOGI("Tablet Mode Switch to %d\n", state); | |
68 | + memset(&nev, 0, sizeof(struct input_event)); | |
69 | + nev.type = EV_SW; | |
70 | + nev.code = SW_LID; | |
71 | + nev.value = !!state; | |
72 | + write(ufd, &nev, sizeof(struct input_event)); | |
73 | + nev.type = EV_SYN; | |
74 | + nev.code = SYN_REPORT; | |
75 | + nev.value = 0; | |
76 | + write(ufd, &nev, sizeof(struct input_event)); | |
77 | +} | |
78 | + | |
79 | +int main(void) | |
80 | +{ | |
81 | + int ifd = openfd(); | |
82 | + if (ifd < 0) { | |
83 | + ALOGE("could not find any tablet mode switch, exiting."); | |
84 | + return -1; | |
85 | + } | |
86 | + | |
87 | + sleep(10); //wait some time or otherwise EventHub might not pick up our events correctly!? | |
88 | + | |
89 | + int ufd = open("/dev/uinput", O_WRONLY | O_NDELAY); | |
90 | + if (ufd >= 0) { | |
91 | + struct uinput_user_dev ud; | |
92 | + memset(&ud, 0, sizeof(struct uinput_user_dev)); | |
93 | + strcpy(ud.name, "Android Tablet Lid Switch"); | |
94 | + write(ufd, &ud, sizeof(struct uinput_user_dev)); | |
95 | + ioctl(ufd, UI_SET_EVBIT, EV_SW); | |
96 | + ioctl(ufd, UI_SET_SWBIT, SW_LID); | |
97 | + ioctl(ufd, UI_DEV_CREATE, 0); | |
98 | + } else { | |
99 | + ALOGE("could not open uinput device: %s", strerror(errno)); | |
100 | + return -1; | |
101 | + } | |
102 | + | |
103 | + // send initial switch state | |
104 | + unsigned long sw_state[NBITS(SW_TABLET_MODE+1)]; | |
105 | + memset(sw_state, 0, sizeof(sw_state)); | |
106 | + if (ioctl(ifd, EVIOCGSW(sizeof(sw_state)), sw_state) >= 0) { | |
107 | + send_switch(ufd, test_bit(SW_TABLET_MODE, sw_state) ? 1 : 0); | |
108 | + } | |
109 | + | |
110 | + // read events and pass them on modified | |
111 | + while (1) { | |
112 | + struct input_event iev; | |
113 | + size_t res = read(ifd, &iev, sizeof(struct input_event)); | |
114 | + if (res < sizeof(struct input_event)) { | |
115 | + ALOGW("insufficient input data(%d)? fd=%d", res, ifd); | |
116 | + continue; | |
117 | + } | |
118 | + //LOGV("type=%d scancode=%d value=%d from fd=%d", iev.type, iev.code, iev.value, ifd); | |
119 | + if (iev.type == EV_SW && iev.code == SW_TABLET_MODE) { | |
120 | + send_switch(ufd, iev.value); | |
121 | + } | |
122 | + } | |
123 | + | |
124 | + return 0; | |
125 | +} |
@@ -0,0 +1,15 @@ | ||
1 | +# | |
2 | +# Copyright (C) 2009-2011 The Android-x86 Open Source Project | |
3 | +# | |
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +# you may not use this file except in compliance with the License. | |
6 | +# You may obtain a copy of the License at | |
7 | +# | |
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +# | |
10 | + | |
11 | +LOCAL_PATH := $(my-dir) | |
12 | +include $(CLEAR_VARS) | |
13 | + | |
14 | +LOCAL_MODULE := tp_smapi | |
15 | +EXTRA_KERNEL_MODULE_PATH_$(LOCAL_MODULE) := $(LOCAL_PATH) |
@@ -0,0 +1 @@ | ||
1 | +obj-m += thinkpad_ec.o tp_smapi.o hdaps.o |
@@ -0,0 +1,891 @@ | ||
1 | +/* | |
2 | + * drivers/platform/x86/hdaps.c - driver for IBM's Hard Drive Active Protection System | |
3 | + * | |
4 | + * Copyright (C) 2005 Robert Love <rml@novell.com> | |
5 | + * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com> | |
6 | + * | |
7 | + * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads | |
8 | + * starting with the R40, T41, and X40. It provides a basic two-axis | |
9 | + * accelerometer and other data, such as the device's temperature. | |
10 | + * | |
11 | + * This driver is based on the document by Mark A. Smith available at | |
12 | + * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial | |
13 | + * and error. | |
14 | + * | |
15 | + * This program is free software; you can redistribute it and/or modify it | |
16 | + * under the terms of the GNU General Public License v2 as published by the | |
17 | + * Free Software Foundation. | |
18 | + * | |
19 | + * This program is distributed in the hope that it will be useful, but WITHOUT | |
20 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
21 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
22 | + * more details. | |
23 | + * | |
24 | + * You should have received a copy of the GNU General Public License along with | |
25 | + * this program; if not, write to the Free Software Foundation, Inc., | |
26 | + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA | |
27 | + */ | |
28 | + | |
29 | +#include <linux/delay.h> | |
30 | +#include <linux/platform_device.h> | |
31 | +#include <linux/input.h> | |
32 | +#include <linux/kernel.h> | |
33 | +#include <linux/module.h> | |
34 | +#include <linux/timer.h> | |
35 | +#include <linux/dmi.h> | |
36 | +#include <linux/jiffies.h> | |
37 | +#include "thinkpad_ec.h" | |
38 | +#include <linux/pci_ids.h> | |
39 | +#include <linux/version.h> | |
40 | + | |
41 | +/* Embedded controller accelerometer read command and its result: */ | |
42 | +static const struct thinkpad_ec_row ec_accel_args = | |
43 | + { .mask = 0x0001, .val = {0x11} }; | |
44 | +#define EC_ACCEL_IDX_READOUTS 0x1 /* readouts included in this read */ | |
45 | + /* First readout, if READOUTS>=1: */ | |
46 | +#define EC_ACCEL_IDX_YPOS1 0x2 /* y-axis position word */ | |
47 | +#define EC_ACCEL_IDX_XPOS1 0x4 /* x-axis position word */ | |
48 | +#define EC_ACCEL_IDX_TEMP1 0x6 /* device temperature in Celsius */ | |
49 | + /* Second readout, if READOUTS>=2: */ | |
50 | +#define EC_ACCEL_IDX_XPOS2 0x7 /* y-axis position word */ | |
51 | +#define EC_ACCEL_IDX_YPOS2 0x9 /* x-axis position word */ | |
52 | +#define EC_ACCEL_IDX_TEMP2 0xb /* device temperature in Celsius */ | |
53 | +#define EC_ACCEL_IDX_QUEUED 0xc /* Number of queued readouts left */ | |
54 | +#define EC_ACCEL_IDX_KMACT 0xd /* keyboard or mouse activity */ | |
55 | +#define EC_ACCEL_IDX_RETVAL 0xf /* command return value, good=0x00 */ | |
56 | + | |
57 | +#define KEYBD_MASK 0x20 /* set if keyboard activity */ | |
58 | +#define MOUSE_MASK 0x40 /* set if mouse activity */ | |
59 | + | |
60 | +#define READ_TIMEOUT_MSECS 100 /* wait this long for device read */ | |
61 | +#define RETRY_MSECS 3 /* retry delay */ | |
62 | + | |
63 | +#define HDAPS_INPUT_FUZZ 4 /* input event threshold */ | |
64 | +#define HDAPS_INPUT_FLAT 4 | |
65 | +#define KMACT_REMEMBER_PERIOD (HZ/10) /* keyboard/mouse persistance */ | |
66 | + | |
67 | +/* Input IDs */ | |
68 | +#define HDAPS_INPUT_VENDOR PCI_VENDOR_ID_IBM | |
69 | +#define HDAPS_INPUT_PRODUCT 0x5054 /* "TP", shared with thinkpad_acpi */ | |
70 | +#define HDAPS_INPUT_JS_VERSION 0x6801 /* Joystick emulation input device */ | |
71 | +#define HDAPS_INPUT_RAW_VERSION 0x4801 /* Raw accelerometer input device */ | |
72 | + | |
73 | +/* Axis orientation. */ | |
74 | +/* The unnatural bit-representation of inversions is for backward | |
75 | + * compatibility with the"invert=1" module parameter. */ | |
76 | +#define HDAPS_ORIENT_INVERT_XY 0x01 /* Invert both X and Y axes. */ | |
77 | +#define HDAPS_ORIENT_INVERT_X 0x02 /* Invert the X axis (uninvert if | |
78 | + * already inverted by INVERT_XY). */ | |
79 | +#define HDAPS_ORIENT_SWAP 0x04 /* Swap the axes. The swap occurs | |
80 | + * before inverting X or Y. */ | |
81 | +#define HDAPS_ORIENT_MAX 0x07 | |
82 | +#define HDAPS_ORIENT_UNDEFINED 0xFF /* Placeholder during initialization */ | |
83 | +#define HDAPS_ORIENT_INVERT_Y (HDAPS_ORIENT_INVERT_XY | HDAPS_ORIENT_INVERT_X) | |
84 | + | |
85 | +static struct timer_list hdaps_timer; | |
86 | +static struct platform_device *pdev; | |
87 | +static struct input_dev *hdaps_idev; /* joystick-like device with fuzz */ | |
88 | +static struct input_dev *hdaps_idev_raw; /* raw hdaps sensor readouts */ | |
89 | +static unsigned int hdaps_invert = HDAPS_ORIENT_UNDEFINED; | |
90 | +static int needs_calibration; | |
91 | + | |
92 | +/* Configuration: */ | |
93 | +static int sampling_rate = 50; /* Sampling rate */ | |
94 | +static int oversampling_ratio = 5; /* Ratio between our sampling rate and | |
95 | + * EC accelerometer sampling rate */ | |
96 | +static int running_avg_filter_order = 2; /* EC running average filter order */ | |
97 | + | |
98 | +/* Latest state readout: */ | |
99 | +static int pos_x, pos_y; /* position */ | |
100 | +static int temperature; /* temperature */ | |
101 | +static int stale_readout = 1; /* last read invalid */ | |
102 | +static int rest_x, rest_y; /* calibrated rest position */ | |
103 | + | |
104 | +/* Last time we saw keyboard and mouse activity: */ | |
105 | +static u64 last_keyboard_jiffies = INITIAL_JIFFIES; | |
106 | +static u64 last_mouse_jiffies = INITIAL_JIFFIES; | |
107 | +static u64 last_update_jiffies = INITIAL_JIFFIES; | |
108 | + | |
109 | +/* input device use count */ | |
110 | +static int hdaps_users; | |
111 | +static DEFINE_MUTEX(hdaps_users_mtx); | |
112 | + | |
113 | +/* Some models require an axis transformation to the standard representation */ | |
114 | +static void transform_axes(int *x, int *y) | |
115 | +{ | |
116 | + if (hdaps_invert & HDAPS_ORIENT_SWAP) { | |
117 | + int z; | |
118 | + z = *x; | |
119 | + *x = *y; | |
120 | + *y = z; | |
121 | + } | |
122 | + if (hdaps_invert & HDAPS_ORIENT_INVERT_XY) { | |
123 | + *x = -*x; | |
124 | + *y = -*y; | |
125 | + } | |
126 | + if (hdaps_invert & HDAPS_ORIENT_INVERT_X) | |
127 | + *x = -*x; | |
128 | +} | |
129 | + | |
130 | +/** | |
131 | + * __hdaps_update - query current state, with locks already acquired | |
132 | + * @fast: if nonzero, do one quick attempt without retries. | |
133 | + * | |
134 | + * Query current accelerometer state and update global state variables. | |
135 | + * Also prefetches the next query. Caller must hold controller lock. | |
136 | + */ | |
137 | +static int __hdaps_update(int fast) | |
138 | +{ | |
139 | + /* Read data: */ | |
140 | + struct thinkpad_ec_row data; | |
141 | + int ret; | |
142 | + | |
143 | + data.mask = (1 << EC_ACCEL_IDX_READOUTS) | (1 << EC_ACCEL_IDX_KMACT) | | |
144 | + (3 << EC_ACCEL_IDX_YPOS1) | (3 << EC_ACCEL_IDX_XPOS1) | | |
145 | + (1 << EC_ACCEL_IDX_TEMP1) | (1 << EC_ACCEL_IDX_RETVAL); | |
146 | + if (fast) | |
147 | + ret = thinkpad_ec_try_read_row(&ec_accel_args, &data); | |
148 | + else | |
149 | + ret = thinkpad_ec_read_row(&ec_accel_args, &data); | |
150 | + thinkpad_ec_prefetch_row(&ec_accel_args); /* Prefetch even if error */ | |
151 | + if (ret) | |
152 | + return ret; | |
153 | + | |
154 | + /* Check status: */ | |
155 | + if (data.val[EC_ACCEL_IDX_RETVAL] != 0x00) { | |
156 | + printk(KERN_WARNING "hdaps: read RETVAL=0x%02x\n", | |
157 | + data.val[EC_ACCEL_IDX_RETVAL]); | |
158 | + return -EIO; | |
159 | + } | |
160 | + | |
161 | + if (data.val[EC_ACCEL_IDX_READOUTS] < 1) | |
162 | + return -EBUSY; /* no pending readout, try again later */ | |
163 | + | |
164 | + /* Parse position data: */ | |
165 | + pos_x = *(s16 *)(data.val+EC_ACCEL_IDX_XPOS1); | |
166 | + pos_y = *(s16 *)(data.val+EC_ACCEL_IDX_YPOS1); | |
167 | + transform_axes(&pos_x, &pos_y); | |
168 | + | |
169 | + /* Keyboard and mouse activity status is cleared as soon as it's read, | |
170 | + * so applications will eat each other's events. Thus we remember any | |
171 | + * event for KMACT_REMEMBER_PERIOD jiffies. | |
172 | + */ | |
173 | + if (data.val[EC_ACCEL_IDX_KMACT] & KEYBD_MASK) | |
174 | + last_keyboard_jiffies = get_jiffies_64(); | |
175 | + if (data.val[EC_ACCEL_IDX_KMACT] & MOUSE_MASK) | |
176 | + last_mouse_jiffies = get_jiffies_64(); | |
177 | + | |
178 | + temperature = data.val[EC_ACCEL_IDX_TEMP1]; | |
179 | + | |
180 | + last_update_jiffies = get_jiffies_64(); | |
181 | + stale_readout = 0; | |
182 | + if (needs_calibration) { | |
183 | + rest_x = pos_x; | |
184 | + rest_y = pos_y; | |
185 | + needs_calibration = 0; | |
186 | + } | |
187 | + | |
188 | + return 0; | |
189 | +} | |
190 | + | |
191 | +/** | |
192 | + * hdaps_update - acquire locks and query current state | |
193 | + * | |
194 | + * Query current accelerometer state and update global state variables. | |
195 | + * Also prefetches the next query. | |
196 | + * Retries until timeout if the accelerometer is not in ready status (common). | |
197 | + * Does its own locking. | |
198 | + */ | |
199 | +static int hdaps_update(void) | |
200 | +{ | |
201 | + u64 age = get_jiffies_64() - last_update_jiffies; | |
202 | + int total, ret; | |
203 | + | |
204 | + if (!stale_readout && age < (9*HZ)/(10*sampling_rate)) | |
205 | + return 0; /* already updated recently */ | |
206 | + for (total = 0; total < READ_TIMEOUT_MSECS; total += RETRY_MSECS) { | |
207 | + ret = thinkpad_ec_lock(); | |
208 | + if (ret) | |
209 | + return ret; | |
210 | + ret = __hdaps_update(0); | |
211 | + thinkpad_ec_unlock(); | |
212 | + | |
213 | + if (!ret) | |
214 | + return 0; | |
215 | + if (ret != -EBUSY) | |
216 | + break; | |
217 | + msleep(RETRY_MSECS); | |
218 | + } | |
219 | + return ret; | |
220 | +} | |
221 | + | |
222 | +/** | |
223 | + * hdaps_set_power - enable or disable power to the accelerometer. | |
224 | + * Returns zero on success and negative error code on failure. Can sleep. | |
225 | + */ | |
226 | +static int hdaps_set_power(int on) | |
227 | +{ | |
228 | + struct thinkpad_ec_row args = | |
229 | + { .mask = 0x0003, .val = {0x14, on?0x01:0x00} }; | |
230 | + struct thinkpad_ec_row data = { .mask = 0x8000 }; | |
231 | + int ret = thinkpad_ec_read_row(&args, &data); | |
232 | + if (ret) | |
233 | + return ret; | |
234 | + if (data.val[0xF] != 0x00) | |
235 | + return -EIO; | |
236 | + return 0; | |
237 | +} | |
238 | + | |
239 | +/** | |
240 | + * hdaps_set_ec_config - set accelerometer parameters. | |
241 | + * @ec_rate: embedded controller sampling rate | |
242 | + * @order: embedded controller running average filter order | |
243 | + * (Normally we have @ec_rate = sampling_rate * oversampling_ratio.) | |
244 | + * Returns zero on success and negative error code on failure. Can sleep. | |
245 | + */ | |
246 | +static int hdaps_set_ec_config(int ec_rate, int order) | |
247 | +{ | |
248 | + struct thinkpad_ec_row args = { .mask = 0x000F, | |
249 | + .val = {0x10, (u8)ec_rate, (u8)(ec_rate>>8), order} }; | |
250 | + struct thinkpad_ec_row data = { .mask = 0x8000 }; | |
251 | + int ret = thinkpad_ec_read_row(&args, &data); | |
252 | + printk(KERN_DEBUG "hdaps: setting ec_rate=%d, filter_order=%d\n", | |
253 | + ec_rate, order); | |
254 | + if (ret) | |
255 | + return ret; | |
256 | + if (data.val[0xF] == 0x03) { | |
257 | + printk(KERN_WARNING "hdaps: config param out of range\n"); | |
258 | + return -EINVAL; | |
259 | + } | |
260 | + if (data.val[0xF] == 0x06) { | |
261 | + printk(KERN_WARNING "hdaps: config change already pending\n"); | |
262 | + return -EBUSY; | |
263 | + } | |
264 | + if (data.val[0xF] != 0x00) { | |
265 | + printk(KERN_WARNING "hdaps: config change error, ret=%d\n", | |
266 | + data.val[0xF]); | |
267 | + return -EIO; | |
268 | + } | |
269 | + return 0; | |
270 | +} | |
271 | + | |
272 | +/** | |
273 | + * hdaps_get_ec_config - get accelerometer parameters. | |
274 | + * @ec_rate: embedded controller sampling rate | |
275 | + * @order: embedded controller running average filter order | |
276 | + * Returns zero on success and negative error code on failure. Can sleep. | |
277 | + */ | |
278 | +static int hdaps_get_ec_config(int *ec_rate, int *order) | |
279 | +{ | |
280 | + const struct thinkpad_ec_row args = | |
281 | + { .mask = 0x0003, .val = {0x17, 0x82} }; | |
282 | + struct thinkpad_ec_row data = { .mask = 0x801F }; | |
283 | + int ret = thinkpad_ec_read_row(&args, &data); | |
284 | + if (ret) | |
285 | + return ret; | |
286 | + if (data.val[0xF] != 0x00) | |
287 | + return -EIO; | |
288 | + if (!(data.val[0x1] & 0x01)) | |
289 | + return -ENXIO; /* accelerometer polling not enabled */ | |
290 | + if (data.val[0x1] & 0x02) | |
291 | + return -EBUSY; /* config change in progress, retry later */ | |
292 | + *ec_rate = data.val[0x2] | ((int)(data.val[0x3]) << 8); | |
293 | + *order = data.val[0x4]; | |
294 | + return 0; | |
295 | +} | |
296 | + | |
297 | +/** | |
298 | + * hdaps_get_ec_mode - get EC accelerometer mode | |
299 | + * Returns zero on success and negative error code on failure. Can sleep. | |
300 | + */ | |
301 | +static int hdaps_get_ec_mode(u8 *mode) | |
302 | +{ | |
303 | + const struct thinkpad_ec_row args = | |
304 | + { .mask = 0x0001, .val = {0x13} }; | |
305 | + struct thinkpad_ec_row data = { .mask = 0x8002 }; | |
306 | + int ret = thinkpad_ec_read_row(&args, &data); | |
307 | + if (ret) | |
308 | + return ret; | |
309 | + if (data.val[0xF] != 0x00) { | |
310 | + printk(KERN_WARNING | |
311 | + "accelerometer not implemented (0x%02x)\n", | |
312 | + data.val[0xF]); | |
313 | + return -EIO; | |
314 | + } | |
315 | + *mode = data.val[0x1]; | |
316 | + return 0; | |
317 | +} | |
318 | + | |
319 | +/** | |
320 | + * hdaps_check_ec - checks something about the EC. | |
321 | + * Follows the clean-room spec for HDAPS; we don't know what it means. | |
322 | + * Returns zero on success and negative error code on failure. Can sleep. | |
323 | + */ | |
324 | +static int hdaps_check_ec(void) | |
325 | +{ | |
326 | + const struct thinkpad_ec_row args = | |
327 | + { .mask = 0x0003, .val = {0x17, 0x81} }; | |
328 | + struct thinkpad_ec_row data = { .mask = 0x800E }; | |
329 | + int ret = thinkpad_ec_read_row(&args, &data); | |
330 | + if (ret) | |
331 | + return ret; | |
332 | + if (!((data.val[0x1] == 0x00 && data.val[0x2] == 0x60) || /* cleanroom spec */ | |
333 | + (data.val[0x1] == 0x01 && data.val[0x2] == 0x00)) || /* seen on T61 */ | |
334 | + data.val[0x3] != 0x00 || data.val[0xF] != 0x00) { | |
335 | + printk(KERN_WARNING | |
336 | + "hdaps_check_ec: bad response (0x%x,0x%x,0x%x,0x%x)\n", | |
337 | + data.val[0x1], data.val[0x2], | |
338 | + data.val[0x3], data.val[0xF]); | |
339 | + return -EIO; | |
340 | + } | |
341 | + return 0; | |
342 | +} | |
343 | + | |
344 | +/** | |
345 | + * hdaps_device_init - initialize the accelerometer. | |
346 | + * | |
347 | + * Call several embedded controller functions to test and initialize the | |
348 | + * accelerometer. | |
349 | + * Returns zero on success and negative error code on failure. Can sleep. | |
350 | + */ | |
351 | +#define FAILED_INIT(msg) printk(KERN_ERR "hdaps init failed at: %s\n", msg) | |
352 | +static int hdaps_device_init(void) | |
353 | +{ | |
354 | + int ret; | |
355 | + u8 mode; | |
356 | + | |
357 | + ret = thinkpad_ec_lock(); | |
358 | + if (ret) | |
359 | + return ret; | |
360 | + | |
361 | + if (hdaps_get_ec_mode(&mode)) | |
362 | + { FAILED_INIT("hdaps_get_ec_mode failed"); goto bad; } | |
363 | + | |
364 | + printk(KERN_DEBUG "hdaps: initial mode latch is 0x%02x\n", mode); | |
365 | + if (mode == 0x00) | |
366 | + { FAILED_INIT("accelerometer not available"); goto bad; } | |
367 | + | |
368 | + if (hdaps_check_ec()) | |
369 | + { FAILED_INIT("hdaps_check_ec failed"); goto bad; } | |
370 | + | |
371 | + if (hdaps_set_power(1)) | |
372 | + { FAILED_INIT("hdaps_set_power failed"); goto bad; } | |
373 | + | |
374 | + if (hdaps_set_ec_config(sampling_rate*oversampling_ratio, | |
375 | + running_avg_filter_order)) | |
376 | + { FAILED_INIT("hdaps_set_ec_config failed"); goto bad; } | |
377 | + | |
378 | + thinkpad_ec_invalidate(); | |
379 | + udelay(200); | |
380 | + | |
381 | + /* Just prefetch instead of reading, to avoid ~1sec delay on load */ | |
382 | + ret = thinkpad_ec_prefetch_row(&ec_accel_args); | |
383 | + if (ret) | |
384 | + { FAILED_INIT("initial prefetch failed"); goto bad; } | |
385 | + goto good; | |
386 | +bad: | |
387 | + thinkpad_ec_invalidate(); | |
388 | + ret = -ENXIO; | |
389 | +good: | |
390 | + stale_readout = 1; | |
391 | + thinkpad_ec_unlock(); | |
392 | + return ret; | |
393 | +} | |
394 | + | |
395 | +/** | |
396 | + * hdaps_device_shutdown - power off the accelerometer | |
397 | + * Returns nonzero on failure. Can sleep. | |
398 | + */ | |
399 | +static int hdaps_device_shutdown(void) | |
400 | +{ | |
401 | + int ret; | |
402 | + ret = hdaps_set_power(0); | |
403 | + if (ret) { | |
404 | + printk(KERN_WARNING "hdaps: cannot power off\n"); | |
405 | + return ret; | |
406 | + } | |
407 | + ret = hdaps_set_ec_config(0, 1); | |
408 | + if (ret) | |
409 | + printk(KERN_WARNING "hdaps: cannot stop EC sampling\n"); | |
410 | + return ret; | |
411 | +} | |
412 | + | |
413 | +/* Device model stuff */ | |
414 | + | |
415 | +static int hdaps_probe(struct platform_device *dev) | |
416 | +{ | |
417 | + int ret; | |
418 | + | |
419 | + ret = hdaps_device_init(); | |
420 | + if (ret) | |
421 | + return ret; | |
422 | + | |
423 | + printk(KERN_INFO "hdaps: device successfully initialized.\n"); | |
424 | + return 0; | |
425 | +} | |
426 | + | |
427 | +static int hdaps_suspend(struct platform_device *dev, pm_message_t state) | |
428 | +{ | |
429 | + /* Don't do hdaps polls until resume re-initializes the sensor. */ | |
430 | + del_timer_sync(&hdaps_timer); | |
431 | + hdaps_device_shutdown(); /* ignore errors, effect is negligible */ | |
432 | + return 0; | |
433 | +} | |
434 | + | |
435 | +static int hdaps_resume(struct platform_device *dev) | |
436 | +{ | |
437 | + int ret = hdaps_device_init(); | |
438 | + if (ret) | |
439 | + return ret; | |
440 | + | |
441 | + mutex_lock(&hdaps_users_mtx); | |
442 | + if (hdaps_users) | |
443 | + mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); | |
444 | + mutex_unlock(&hdaps_users_mtx); | |
445 | + return 0; | |
446 | +} | |
447 | + | |
448 | +static struct platform_driver hdaps_driver = { | |
449 | + .probe = hdaps_probe, | |
450 | + .suspend = hdaps_suspend, | |
451 | + .resume = hdaps_resume, | |
452 | + .driver = { | |
453 | + .name = "hdaps", | |
454 | + .owner = THIS_MODULE, | |
455 | + }, | |
456 | +}; | |
457 | + | |
458 | +/** | |
459 | + * hdaps_calibrate - set our "resting" values. | |
460 | + * Does its own locking. | |
461 | + */ | |
462 | +static void hdaps_calibrate(void) | |
463 | +{ | |
464 | + needs_calibration = 1; | |
465 | + hdaps_update(); | |
466 | + /* If that fails, the mousedev poll will take care of things later. */ | |
467 | +} | |
468 | + | |
469 | +/* Timer handler for updating the input device. Runs in softirq context, | |
470 | + * so avoid lenghty or blocking operations. | |
471 | + */ | |
472 | +static void hdaps_mousedev_poll(unsigned long unused) | |
473 | +{ | |
474 | + int ret; | |
475 | + | |
476 | + stale_readout = 1; | |
477 | + | |
478 | + /* Cannot sleep. Try nonblockingly. If we fail, try again later. */ | |
479 | + if (thinkpad_ec_try_lock()) | |
480 | + goto keep_active; | |
481 | + | |
482 | + ret = __hdaps_update(1); /* fast update, we're in softirq context */ | |
483 | + thinkpad_ec_unlock(); | |
484 | + /* Any of "successful", "not yet ready" and "not prefetched"? */ | |
485 | + if (ret != 0 && ret != -EBUSY && ret != -ENODATA) { | |
486 | + printk(KERN_ERR | |
487 | + "hdaps: poll failed, disabling updates\n"); | |
488 | + return; | |
489 | + } | |
490 | + | |
491 | +keep_active: | |
492 | + /* Even if we failed now, pos_x,y may have been updated earlier: */ | |
493 | + input_report_abs(hdaps_idev, ABS_X, pos_x - rest_x); | |
494 | + input_report_abs(hdaps_idev, ABS_Y, pos_y - rest_y); | |
495 | + input_sync(hdaps_idev); | |
496 | + input_report_abs(hdaps_idev_raw, ABS_X, pos_x); | |
497 | + input_report_abs(hdaps_idev_raw, ABS_Y, pos_y); | |
498 | + input_sync(hdaps_idev_raw); | |
499 | + mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); | |
500 | +} | |
501 | + | |
502 | + | |
503 | +/* Sysfs Files */ | |
504 | + | |
505 | +static ssize_t hdaps_position_show(struct device *dev, | |
506 | + struct device_attribute *attr, char *buf) | |
507 | +{ | |
508 | + int ret = hdaps_update(); | |
509 | + if (ret) | |
510 | + return ret; | |
511 | + return sprintf(buf, "(%d,%d)\n", pos_x, pos_y); | |
512 | +} | |
513 | + | |
514 | +static ssize_t hdaps_temp1_show(struct device *dev, | |
515 | + struct device_attribute *attr, char *buf) | |
516 | +{ | |
517 | + int ret = hdaps_update(); | |
518 | + if (ret) | |
519 | + return ret; | |
520 | + return sprintf(buf, "%d\n", temperature); | |
521 | +} | |
522 | + | |
523 | +static ssize_t hdaps_keyboard_activity_show(struct device *dev, | |
524 | + struct device_attribute *attr, | |
525 | + char *buf) | |
526 | +{ | |
527 | + int ret = hdaps_update(); | |
528 | + if (ret) | |
529 | + return ret; | |
530 | + return sprintf(buf, "%u\n", | |
531 | + get_jiffies_64() < last_keyboard_jiffies + KMACT_REMEMBER_PERIOD); | |
532 | +} | |
533 | + | |
534 | +static ssize_t hdaps_mouse_activity_show(struct device *dev, | |
535 | + struct device_attribute *attr, | |
536 | + char *buf) | |
537 | +{ | |
538 | + int ret = hdaps_update(); | |
539 | + if (ret) | |
540 | + return ret; | |
541 | + return sprintf(buf, "%u\n", | |
542 | + get_jiffies_64() < last_mouse_jiffies + KMACT_REMEMBER_PERIOD); | |
543 | +} | |
544 | + | |
545 | +static ssize_t hdaps_calibrate_show(struct device *dev, | |
546 | + struct device_attribute *attr, char *buf) | |
547 | +{ | |
548 | + return sprintf(buf, "(%d,%d)\n", rest_x, rest_y); | |
549 | +} | |
550 | + | |
551 | +static ssize_t hdaps_calibrate_store(struct device *dev, | |
552 | + struct device_attribute *attr, | |
553 | + const char *buf, size_t count) | |
554 | +{ | |
555 | + hdaps_calibrate(); | |
556 | + return count; | |
557 | +} | |
558 | + | |
559 | +static ssize_t hdaps_invert_show(struct device *dev, | |
560 | + struct device_attribute *attr, char *buf) | |
561 | +{ | |
562 | + return sprintf(buf, "%u\n", hdaps_invert); | |
563 | +} | |
564 | + | |
565 | +static ssize_t hdaps_invert_store(struct device *dev, | |
566 | + struct device_attribute *attr, | |
567 | + const char *buf, size_t count) | |
568 | +{ | |
569 | + int invert; | |
570 | + | |
571 | + if (sscanf(buf, "%d", &invert) != 1 || | |
572 | + invert < 0 || invert > HDAPS_ORIENT_MAX) | |
573 | + return -EINVAL; | |
574 | + | |
575 | + hdaps_invert = invert; | |
576 | + hdaps_calibrate(); | |
577 | + | |
578 | + return count; | |
579 | +} | |
580 | + | |
581 | +static ssize_t hdaps_sampling_rate_show( | |
582 | + struct device *dev, struct device_attribute *attr, char *buf) | |
583 | +{ | |
584 | + return sprintf(buf, "%d\n", sampling_rate); | |
585 | +} | |
586 | + | |
587 | +static ssize_t hdaps_sampling_rate_store( | |
588 | + struct device *dev, struct device_attribute *attr, | |
589 | + const char *buf, size_t count) | |
590 | +{ | |
591 | + int rate, ret; | |
592 | + if (sscanf(buf, "%d", &rate) != 1 || rate > HZ || rate <= 0) { | |
593 | + printk(KERN_WARNING | |
594 | + "must have 0<input_sampling_rate<=HZ=%d\n", HZ); | |
595 | + return -EINVAL; | |
596 | + } | |
597 | + ret = hdaps_set_ec_config(rate*oversampling_ratio, | |
598 | + running_avg_filter_order); | |
599 | + if (ret) | |
600 | + return ret; | |
601 | + sampling_rate = rate; | |
602 | + return count; | |
603 | +} | |
604 | + | |
605 | +static ssize_t hdaps_oversampling_ratio_show( | |
606 | + struct device *dev, struct device_attribute *attr, char *buf) | |
607 | +{ | |
608 | + int ec_rate, order; | |
609 | + int ret = hdaps_get_ec_config(&ec_rate, &order); | |
610 | + if (ret) | |
611 | + return ret; | |
612 | + return sprintf(buf, "%u\n", ec_rate / sampling_rate); | |
613 | +} | |
614 | + | |
615 | +static ssize_t hdaps_oversampling_ratio_store( | |
616 | + struct device *dev, struct device_attribute *attr, | |
617 | + const char *buf, size_t count) | |
618 | +{ | |
619 | + int ratio, ret; | |
620 | + if (sscanf(buf, "%d", &ratio) != 1 || ratio < 1) | |
621 | + return -EINVAL; | |
622 | + ret = hdaps_set_ec_config(sampling_rate*ratio, | |
623 | + running_avg_filter_order); | |
624 | + if (ret) | |
625 | + return ret; | |
626 | + oversampling_ratio = ratio; | |
627 | + return count; | |
628 | +} | |
629 | + | |
630 | +static ssize_t hdaps_running_avg_filter_order_show( | |
631 | + struct device *dev, struct device_attribute *attr, char *buf) | |
632 | +{ | |
633 | + int rate, order; | |
634 | + int ret = hdaps_get_ec_config(&rate, &order); | |
635 | + if (ret) | |
636 | + return ret; | |
637 | + return sprintf(buf, "%u\n", order); | |
638 | +} | |
639 | + | |
640 | +static ssize_t hdaps_running_avg_filter_order_store( | |
641 | + struct device *dev, struct device_attribute *attr, | |
642 | + const char *buf, size_t count) | |
643 | +{ | |
644 | + int order, ret; | |
645 | + if (sscanf(buf, "%d", &order) != 1) | |
646 | + return -EINVAL; | |
647 | + ret = hdaps_set_ec_config(sampling_rate*oversampling_ratio, order); | |
648 | + if (ret) | |
649 | + return ret; | |
650 | + running_avg_filter_order = order; | |
651 | + return count; | |
652 | +} | |
653 | + | |
654 | +static int hdaps_mousedev_open(struct input_dev *dev) | |
655 | +{ | |
656 | + if (!try_module_get(THIS_MODULE)) | |
657 | + return -ENODEV; | |
658 | + | |
659 | + mutex_lock(&hdaps_users_mtx); | |
660 | + if (hdaps_users++ == 0) /* first input user */ | |
661 | + mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); | |
662 | + mutex_unlock(&hdaps_users_mtx); | |
663 | + return 0; | |
664 | +} | |
665 | + | |
666 | +static void hdaps_mousedev_close(struct input_dev *dev) | |
667 | +{ | |
668 | + mutex_lock(&hdaps_users_mtx); | |
669 | + if (--hdaps_users == 0) /* no input users left */ | |
670 | + del_timer_sync(&hdaps_timer); | |
671 | + mutex_unlock(&hdaps_users_mtx); | |
672 | + | |
673 | + module_put(THIS_MODULE); | |
674 | +} | |
675 | + | |
676 | +static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL); | |
677 | +static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL); | |
678 | + /* "temp1" instead of "temperature" is hwmon convention */ | |
679 | +static DEVICE_ATTR(keyboard_activity, 0444, | |
680 | + hdaps_keyboard_activity_show, NULL); | |
681 | +static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL); | |
682 | +static DEVICE_ATTR(calibrate, 0644, | |
683 | + hdaps_calibrate_show, hdaps_calibrate_store); | |
684 | +static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store); | |
685 | +static DEVICE_ATTR(sampling_rate, 0644, | |
686 | + hdaps_sampling_rate_show, hdaps_sampling_rate_store); | |
687 | +static DEVICE_ATTR(oversampling_ratio, 0644, | |
688 | + hdaps_oversampling_ratio_show, | |
689 | + hdaps_oversampling_ratio_store); | |
690 | +static DEVICE_ATTR(running_avg_filter_order, 0644, | |
691 | + hdaps_running_avg_filter_order_show, | |
692 | + hdaps_running_avg_filter_order_store); | |
693 | + | |
694 | +static struct attribute *hdaps_attributes[] = { | |
695 | + &dev_attr_position.attr, | |
696 | + &dev_attr_temp1.attr, | |
697 | + &dev_attr_keyboard_activity.attr, | |
698 | + &dev_attr_mouse_activity.attr, | |
699 | + &dev_attr_calibrate.attr, | |
700 | + &dev_attr_invert.attr, | |
701 | + &dev_attr_sampling_rate.attr, | |
702 | + &dev_attr_oversampling_ratio.attr, | |
703 | + &dev_attr_running_avg_filter_order.attr, | |
704 | + NULL, | |
705 | +}; | |
706 | + | |
707 | +static struct attribute_group hdaps_attribute_group = { | |
708 | + .attrs = hdaps_attributes, | |
709 | +}; | |
710 | + | |
711 | + | |
712 | +/* Module stuff */ | |
713 | + | |
714 | +/* hdaps_dmi_match_invert - found an inverted match. */ | |
715 | +static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id) | |
716 | +{ | |
717 | + unsigned int orient = (kernel_ulong_t) id->driver_data; | |
718 | + hdaps_invert = orient; | |
719 | + printk(KERN_INFO "hdaps: %s detected, setting orientation %u\n", | |
720 | + id->ident, orient); | |
721 | + return 1; /* stop enumeration */ | |
722 | +} | |
723 | + | |
724 | +#define HDAPS_DMI_MATCH_INVERT(vendor, model, orient) { \ | |
725 | + .ident = vendor " " model, \ | |
726 | + .callback = hdaps_dmi_match_invert, \ | |
727 | + .driver_data = (void *)(orient), \ | |
728 | + .matches = { \ | |
729 | + DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ | |
730 | + DMI_MATCH(DMI_PRODUCT_VERSION, model) \ | |
731 | + } \ | |
732 | +} | |
733 | + | |
734 | +/* List of models with abnormal axis configuration. | |
735 | + Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match | |
736 | + "ThinkPad T42p", and enumeration stops after first match, | |
737 | + so the order of the entries matters. */ | |
738 | +struct dmi_system_id __initdata hdaps_whitelist[] = { | |
739 | + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_ORIENT_INVERT_XY), | |
740 | + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY), | |
741 | + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_ORIENT_INVERT_XY), | |
742 | + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_ORIENT_INVERT_XY), | |
743 | + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X40", HDAPS_ORIENT_INVERT_Y), | |
744 | + HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_ORIENT_INVERT_Y), | |
745 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY), | |
746 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_ORIENT_INVERT_XY), | |
747 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_ORIENT_INVERT_XY), | |
748 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_ORIENT_INVERT_XY), | |
749 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60 Tablet", HDAPS_ORIENT_INVERT_Y), | |
750 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60s", HDAPS_ORIENT_INVERT_Y), | |
751 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X), | |
752 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X), | |
753 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400s", HDAPS_ORIENT_INVERT_X), | |
754 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_ORIENT_INVERT_XY), | |
755 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410s", HDAPS_ORIENT_SWAP), | |
756 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410", HDAPS_ORIENT_INVERT_XY), | |
757 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T500", HDAPS_ORIENT_INVERT_XY), | |
758 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad W51O", HDAPS_ORIENT_MAX), | |
759 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X200", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X | HDAPS_ORIENT_INVERT_Y), | |
760 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201 Tablet", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY), | |
761 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201s", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY), | |
762 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X), | |
763 | + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X220", HDAPS_ORIENT_SWAP), | |
764 | + { .ident = NULL } | |
765 | +}; | |
766 | + | |
767 | +static int __init hdaps_init(void) | |
768 | +{ | |
769 | + int ret; | |
770 | + | |
771 | + /* Determine axis orientation orientation */ | |
772 | + if (hdaps_invert == HDAPS_ORIENT_UNDEFINED) /* set by module param? */ | |
773 | + if (dmi_check_system(hdaps_whitelist) < 1) /* in whitelist? */ | |
774 | + hdaps_invert = 0; /* default */ | |
775 | + | |
776 | + /* Init timer before platform_driver_register, in case of suspend */ | |
777 | + init_timer(&hdaps_timer); | |
778 | + hdaps_timer.function = hdaps_mousedev_poll; | |
779 | + ret = platform_driver_register(&hdaps_driver); | |
780 | + if (ret) | |
781 | + goto out; | |
782 | + | |
783 | + pdev = platform_device_register_simple("hdaps", -1, NULL, 0); | |
784 | + if (IS_ERR(pdev)) { | |
785 | + ret = PTR_ERR(pdev); | |
786 | + goto out_driver; | |
787 | + } | |
788 | + | |
789 | + ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group); | |
790 | + if (ret) | |
791 | + goto out_device; | |
792 | + | |
793 | + hdaps_idev = input_allocate_device(); | |
794 | + if (!hdaps_idev) { | |
795 | + ret = -ENOMEM; | |
796 | + goto out_group; | |
797 | + } | |
798 | + | |
799 | + hdaps_idev_raw = input_allocate_device(); | |
800 | + if (!hdaps_idev_raw) { | |
801 | + ret = -ENOMEM; | |
802 | + goto out_idev_first; | |
803 | + } | |
804 | + | |
805 | + /* calibration for the input device (deferred to avoid delay) */ | |
806 | + needs_calibration = 1; | |
807 | + | |
808 | + /* initialize the joystick-like fuzzed input device */ | |
809 | + hdaps_idev->name = "hdaps"; | |
810 | + hdaps_idev->phys = "hdaps/input0"; | |
811 | + hdaps_idev->id.bustype = BUS_HOST; | |
812 | + hdaps_idev->id.vendor = HDAPS_INPUT_VENDOR; | |
813 | + hdaps_idev->id.product = HDAPS_INPUT_PRODUCT; | |
814 | + hdaps_idev->id.version = HDAPS_INPUT_JS_VERSION; | |
815 | +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) | |
816 | + hdaps_idev->cdev.dev = &pdev->dev; | |
817 | +#endif | |
818 | + hdaps_idev->evbit[0] = BIT(EV_ABS); | |
819 | + hdaps_idev->open = hdaps_mousedev_open; | |
820 | + hdaps_idev->close = hdaps_mousedev_close; | |
821 | + input_set_abs_params(hdaps_idev, ABS_X, | |
822 | + -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); | |
823 | + input_set_abs_params(hdaps_idev, ABS_Y, | |
824 | + -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); | |
825 | + | |
826 | + ret = input_register_device(hdaps_idev); | |
827 | + if (ret) | |
828 | + goto out_idev; | |
829 | + | |
830 | + /* initialize the raw data input device */ | |
831 | + hdaps_idev_raw->name = "raw_hdaps_data"; | |
832 | + hdaps_idev_raw->phys = "hdaps/input1"; | |
833 | + hdaps_idev_raw->id.bustype = BUS_HOST; | |
834 | + hdaps_idev_raw->id.vendor = HDAPS_INPUT_VENDOR; | |
835 | + hdaps_idev_raw->id.product = HDAPS_INPUT_PRODUCT; | |
836 | + hdaps_idev_raw->id.version = HDAPS_INPUT_RAW_VERSION; | |
837 | +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) | |
838 | + hdaps_idev_raw->cdev.dev = &pdev->dev; | |
839 | +#endif | |
840 | + hdaps_idev_raw->evbit[0] = BIT(EV_ABS); | |
841 | + hdaps_idev_raw->open = hdaps_mousedev_open; | |
842 | + hdaps_idev_raw->close = hdaps_mousedev_close; | |
843 | + input_set_abs_params(hdaps_idev_raw, ABS_X, -32768, 32767, 0, 0); | |
844 | + input_set_abs_params(hdaps_idev_raw, ABS_Y, -32768, 32767, 0, 0); | |
845 | + | |
846 | + ret = input_register_device(hdaps_idev_raw); | |
847 | + if (ret) | |
848 | + goto out_idev_reg_first; | |
849 | + | |
850 | + printk(KERN_INFO "hdaps: driver successfully loaded.\n"); | |
851 | + return 0; | |
852 | + | |
853 | +out_idev_reg_first: | |
854 | + input_unregister_device(hdaps_idev); | |
855 | +out_idev: | |
856 | + input_free_device(hdaps_idev_raw); | |
857 | +out_idev_first: | |
858 | + input_free_device(hdaps_idev); | |
859 | +out_group: | |
860 | + sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); | |
861 | +out_device: | |
862 | + platform_device_unregister(pdev); | |
863 | +out_driver: | |
864 | + platform_driver_unregister(&hdaps_driver); | |
865 | + hdaps_device_shutdown(); | |
866 | +out: | |
867 | + printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret); | |
868 | + return ret; | |
869 | +} | |
870 | + | |
871 | +static void __exit hdaps_exit(void) | |
872 | +{ | |
873 | + input_unregister_device(hdaps_idev_raw); | |
874 | + input_unregister_device(hdaps_idev); | |
875 | + hdaps_device_shutdown(); /* ignore errors, effect is negligible */ | |
876 | + sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); | |
877 | + platform_device_unregister(pdev); | |
878 | + platform_driver_unregister(&hdaps_driver); | |
879 | + | |
880 | + printk(KERN_INFO "hdaps: driver unloaded.\n"); | |
881 | +} | |
882 | + | |
883 | +module_init(hdaps_init); | |
884 | +module_exit(hdaps_exit); | |
885 | + | |
886 | +module_param_named(invert, hdaps_invert, uint, 0); | |
887 | +MODULE_PARM_DESC(invert, "axis orientation code"); | |
888 | + | |
889 | +MODULE_AUTHOR("Robert Love"); | |
890 | +MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver"); | |
891 | +MODULE_LICENSE("GPL v2"); |
@@ -0,0 +1,513 @@ | ||
1 | +/* | |
2 | + * thinkpad_ec.c - ThinkPad embedded controller LPC3 functions | |
3 | + * | |
4 | + * The embedded controller on ThinkPad laptops has a non-standard interface, | |
5 | + * where LPC channel 3 of the H8S EC chip is hooked up to IO ports | |
6 | + * 0x1600-0x161F and implements (a special case of) the H8S LPC protocol. | |
7 | + * The EC LPC interface provides various system management services (currently | |
8 | + * known: battery information and accelerometer readouts). This driver | |
9 | + * provides access and mutual exclusion for the EC interface. | |
10 | +* | |
11 | + * The LPC protocol and terminology are documented here: | |
12 | + * "H8S/2104B Group Hardware Manual", | |
13 | + * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf | |
14 | + * | |
15 | + * Copyright (C) 2006-2007 Shem Multinymous <multinymous@gmail.com> | |
16 | + * | |
17 | + * This program is free software; you can redistribute it and/or modify | |
18 | + * it under the terms of the GNU General Public License as published by | |
19 | + * the Free Software Foundation; either version 2 of the License, or | |
20 | + * (at your option) any later version. | |
21 | + * | |
22 | + * This program is distributed in the hope that it will be useful, | |
23 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
24 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
25 | + * GNU General Public License for more details. | |
26 | + * | |
27 | + * You should have received a copy of the GNU General Public License | |
28 | + * along with this program; if not, write to the Free Software | |
29 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
30 | + */ | |
31 | + | |
32 | +#include <linux/kernel.h> | |
33 | +#include <linux/module.h> | |
34 | +#include <linux/dmi.h> | |
35 | +#include <linux/ioport.h> | |
36 | +#include <linux/delay.h> | |
37 | +#include "thinkpad_ec.h" | |
38 | +#include <linux/jiffies.h> | |
39 | +#include <asm/io.h> | |
40 | + | |
41 | +#include <linux/version.h> | |
42 | +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26) | |
43 | + #include <asm/semaphore.h> | |
44 | +#else | |
45 | + #include <linux/semaphore.h> | |
46 | +#endif | |
47 | + | |
48 | +#define TP_VERSION "0.41" | |
49 | + | |
50 | +MODULE_AUTHOR("Shem Multinymous"); | |
51 | +MODULE_DESCRIPTION("ThinkPad embedded controller hardware access"); | |
52 | +MODULE_VERSION(TP_VERSION); | |
53 | +MODULE_LICENSE("GPL"); | |
54 | + | |
55 | +/* IO ports used by embedded controller LPC channel 3: */ | |
56 | +#define TPC_BASE_PORT 0x1600 | |
57 | +#define TPC_NUM_PORTS 0x20 | |
58 | +#define TPC_STR3_PORT 0x1604 /* Reads H8S EC register STR3 */ | |
59 | +#define TPC_TWR0_PORT 0x1610 /* Mapped to H8S EC register TWR0MW/SW */ | |
60 | +#define TPC_TWR15_PORT 0x161F /* Mapped to H8S EC register TWR15. */ | |
61 | + /* (and port TPC_TWR0_PORT+i is mapped to H8S reg TWRi for 0<i<16) */ | |
62 | + | |
63 | +/* H8S STR3 status flags (see "H8S/2104B Group Hardware Manual" p.549) */ | |
64 | +#define H8S_STR3_IBF3B 0x80 /* Bidi. Data Register Input Buffer Full */ | |
65 | +#define H8S_STR3_OBF3B 0x40 /* Bidi. Data Register Output Buffer Full */ | |
66 | +#define H8S_STR3_MWMF 0x20 /* Master Write Mode Flag */ | |
67 | +#define H8S_STR3_SWMF 0x10 /* Slave Write Mode Flag */ | |
68 | +#define H8S_STR3_MASK 0xF0 /* All bits we care about in STR3 */ | |
69 | + | |
70 | +/* Timeouts and retries */ | |
71 | +#define TPC_READ_RETRIES 150 | |
72 | +#define TPC_READ_NDELAY 500 | |
73 | +#define TPC_REQUEST_RETRIES 1000 | |
74 | +#define TPC_REQUEST_NDELAY 10 | |
75 | +#define TPC_PREFETCH_TIMEOUT (HZ/10) /* invalidate prefetch after 0.1sec */ | |
76 | + | |
77 | +/* A few macros for printk()ing: */ | |
78 | +#define MSG_FMT(fmt, args...) \ | |
79 | + "thinkpad_ec: %s: " fmt "\n", __func__, ## args | |
80 | +#define REQ_FMT(msg, code) \ | |
81 | + MSG_FMT("%s: (0x%02x:0x%02x)->0x%02x", \ | |
82 | + msg, args->val[0x0], args->val[0xF], code) | |
83 | + | |
84 | +/* State of request prefetching: */ | |
85 | +static u8 prefetch_arg0, prefetch_argF; /* Args of last prefetch */ | |
86 | +static u64 prefetch_jiffies; /* time of prefetch, or: */ | |
87 | +#define TPC_PREFETCH_NONE INITIAL_JIFFIES /* No prefetch */ | |
88 | +#define TPC_PREFETCH_JUNK (INITIAL_JIFFIES+1) /* Ignore prefetch */ | |
89 | + | |
90 | +/* Locking: */ | |
91 | +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) | |
92 | +static DECLARE_MUTEX(thinkpad_ec_mutex); | |
93 | +#else | |
94 | +static DEFINE_SEMAPHORE(thinkpad_ec_mutex); | |
95 | +#endif | |
96 | + | |
97 | +/* Kludge in case the ACPI DSDT reserves the ports we need. */ | |
98 | +static int force_io; /* Willing to do IO to ports we couldn't reserve? */ | |
99 | +static int reserved_io; /* Successfully reserved the ports? */ | |
100 | +module_param_named(force_io, force_io, bool, 0600); | |
101 | +MODULE_PARM_DESC(force_io, "Force IO even if region already reserved (0=off, 1=on)"); | |
102 | + | |
103 | +/** | |
104 | + * thinkpad_ec_lock - get lock on the ThinkPad EC | |
105 | + * | |
106 | + * Get exclusive lock for accesing the ThinkPad embedded controller LPC3 | |
107 | + * interface. Returns 0 iff lock acquired. | |
108 | + */ | |
109 | +int thinkpad_ec_lock(void) | |
110 | +{ | |
111 | + int ret; | |
112 | + ret = down_interruptible(&thinkpad_ec_mutex); | |
113 | + return ret; | |
114 | +} | |
115 | +EXPORT_SYMBOL_GPL(thinkpad_ec_lock); | |
116 | + | |
117 | +/** | |
118 | + * thinkpad_ec_try_lock - try getting lock on the ThinkPad EC | |
119 | + * | |
120 | + * Try getting an exclusive lock for accesing the ThinkPad embedded | |
121 | + * controller LPC3. Returns immediately if lock is not available; neither | |
122 | + * blocks nor sleeps. Returns 0 iff lock acquired . | |
123 | + */ | |
124 | +int thinkpad_ec_try_lock(void) | |
125 | +{ | |
126 | + return down_trylock(&thinkpad_ec_mutex); | |
127 | +} | |
128 | +EXPORT_SYMBOL_GPL(thinkpad_ec_try_lock); | |
129 | + | |
130 | +/** | |
131 | + * thinkpad_ec_unlock - release lock on ThinkPad EC | |
132 | + * | |
133 | + * Release a previously acquired exclusive lock on the ThinkPad ebmedded | |
134 | + * controller LPC3 interface. | |
135 | + */ | |
136 | +void thinkpad_ec_unlock(void) | |
137 | +{ | |
138 | + up(&thinkpad_ec_mutex); | |
139 | +} | |
140 | +EXPORT_SYMBOL_GPL(thinkpad_ec_unlock); | |
141 | + | |
142 | +/** | |
143 | + * thinkpad_ec_request_row - tell embedded controller to prepare a row | |
144 | + * @args Input register arguments | |
145 | + * | |
146 | + * Requests a data row by writing to H8S LPC registers TRW0 through TWR15 (or | |
147 | + * a subset thereof) following the protocol prescribed by the "H8S/2104B Group | |
148 | + * Hardware Manual". Does sanity checks via status register STR3. | |
149 | + */ | |
150 | +static int thinkpad_ec_request_row(const struct thinkpad_ec_row *args) | |
151 | +{ | |
152 | + u8 str3; | |
153 | + int i; | |
154 | + | |
155 | + /* EC protocol requires write to TWR0 (function code): */ | |
156 | + if (!(args->mask & 0x0001)) { | |
157 | + printk(KERN_ERR MSG_FMT("bad args->mask=0x%02x", args->mask)); | |
158 | + return -EINVAL; | |
159 | + } | |
160 | + | |
161 | + /* Check initial STR3 status: */ | |
162 | + str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; | |
163 | + if (str3 & H8S_STR3_OBF3B) { /* data already pending */ | |
164 | + inb(TPC_TWR15_PORT); /* marks end of previous transaction */ | |
165 | + if (prefetch_jiffies == TPC_PREFETCH_NONE) | |
166 | + printk(KERN_WARNING REQ_FMT( | |
167 | + "EC has result from unrequested transaction", | |
168 | + str3)); | |
169 | + return -EBUSY; /* EC will be ready in a few usecs */ | |
170 | + } else if (str3 == H8S_STR3_SWMF) { /* busy with previous request */ | |
171 | + if (prefetch_jiffies == TPC_PREFETCH_NONE) | |
172 | + printk(KERN_WARNING REQ_FMT( | |
173 | + "EC is busy with unrequested transaction", | |
174 | + str3)); | |
175 | + return -EBUSY; /* data will be pending in a few usecs */ | |
176 | + } else if (str3 != 0x00) { /* unexpected status? */ | |
177 | + printk(KERN_WARNING REQ_FMT("unexpected initial STR3", str3)); | |
178 | + return -EIO; | |
179 | + } | |
180 | + | |
181 | + /* Send TWR0MW: */ | |
182 | + outb(args->val[0], TPC_TWR0_PORT); | |
183 | + str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; | |
184 | + if (str3 != H8S_STR3_MWMF) { /* not accepted? */ | |
185 | + printk(KERN_WARNING REQ_FMT("arg0 rejected", str3)); | |
186 | + return -EIO; | |
187 | + } | |
188 | + | |
189 | + /* Send TWR1 through TWR14: */ | |
190 | + for (i = 1; i < TP_CONTROLLER_ROW_LEN-1; i++) | |
191 | + if ((args->mask>>i)&1) | |
192 | + outb(args->val[i], TPC_TWR0_PORT+i); | |
193 | + | |
194 | + /* Send TWR15 (default to 0x01). This marks end of command. */ | |
195 | + outb((args->mask & 0x8000) ? args->val[0xF] : 0x01, TPC_TWR15_PORT); | |
196 | + | |
197 | + /* Wait until EC starts writing its reply (~60ns on average). | |
198 | + * Releasing locks before this happens may cause an EC hang | |
199 | + * due to firmware bug! | |
200 | + */ | |
201 | + for (i = 0; i < TPC_REQUEST_RETRIES; i++) { | |
202 | + str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; | |
203 | + if (str3 & H8S_STR3_SWMF) /* EC started replying */ | |
204 | + return 0; | |
205 | + else if (!(str3 & ~(H8S_STR3_IBF3B|H8S_STR3_MWMF))) | |
206 | + /* Normal progress (the EC hasn't seen the request | |
207 | + * yet, or is processing it). Wait it out. */ | |
208 | + ndelay(TPC_REQUEST_NDELAY); | |
209 | + else { /* weird EC status */ | |
210 | + printk(KERN_WARNING | |
211 | + REQ_FMT("bad end STR3", str3)); | |
212 | + return -EIO; | |
213 | + } | |
214 | + } | |
215 | + printk(KERN_WARNING REQ_FMT("EC is mysteriously silent", str3)); | |
216 | + return -EIO; | |
217 | +} | |
218 | + | |
219 | +/** | |
220 | + * thinkpad_ec_read_data - read pre-requested row-data from EC | |
221 | + * @args Input register arguments of pre-requested rows | |
222 | + * @data Output register values | |
223 | + * | |
224 | + * Reads current row data from the controller, assuming it's already | |
225 | + * requested. Follows the H8S spec for register access and status checks. | |
226 | + */ | |
227 | +static int thinkpad_ec_read_data(const struct thinkpad_ec_row *args, | |
228 | + struct thinkpad_ec_row *data) | |
229 | +{ | |
230 | + int i; | |
231 | + u8 str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; | |
232 | + /* Once we make a request, STR3 assumes the sequence of values listed | |
233 | + * in the following 'if' as it reads the request and writes its data. | |
234 | + * It takes about a few dozen nanosecs total, with very high variance. | |
235 | + */ | |
236 | + if (str3 == (H8S_STR3_IBF3B|H8S_STR3_MWMF) || | |
237 | + str3 == 0x00 || /* the 0x00 is indistinguishable from idle EC! */ | |
238 | + str3 == H8S_STR3_SWMF) | |
239 | + return -EBUSY; /* not ready yet */ | |
240 | + /* Finally, the EC signals output buffer full: */ | |
241 | + if (str3 != (H8S_STR3_OBF3B|H8S_STR3_SWMF)) { | |
242 | + printk(KERN_WARNING | |
243 | + REQ_FMT("bad initial STR3", str3)); | |
244 | + return -EIO; | |
245 | + } | |
246 | + | |
247 | + /* Read first byte (signals start of read transactions): */ | |
248 | + data->val[0] = inb(TPC_TWR0_PORT); | |
249 | + /* Optionally read 14 more bytes: */ | |
250 | + for (i = 1; i < TP_CONTROLLER_ROW_LEN-1; i++) | |
251 | + if ((data->mask >> i)&1) | |
252 | + data->val[i] = inb(TPC_TWR0_PORT+i); | |
253 | + /* Read last byte from 0x161F (signals end of read transaction): */ | |
254 | + data->val[0xF] = inb(TPC_TWR15_PORT); | |
255 | + | |
256 | + /* Readout still pending? */ | |
257 | + str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; | |
258 | + if (str3 & H8S_STR3_OBF3B) | |
259 | + printk(KERN_WARNING | |
260 | + REQ_FMT("OBF3B=1 after read", str3)); | |
261 | + /* If port 0x161F returns 0x80 too often, the EC may lock up. Warn: */ | |
262 | + if (data->val[0xF] == 0x80) | |
263 | + printk(KERN_WARNING | |
264 | + REQ_FMT("0x161F reports error", data->val[0xF])); | |
265 | + return 0; | |
266 | +} | |
267 | + | |
268 | +/** | |
269 | + * thinkpad_ec_is_row_fetched - is the given row currently prefetched? | |
270 | + * | |
271 | + * To keep things simple we compare only the first and last args; | |
272 | + * this suffices for all known cases. | |
273 | + */ | |
274 | +static int thinkpad_ec_is_row_fetched(const struct thinkpad_ec_row *args) | |
275 | +{ | |
276 | + return (prefetch_jiffies != TPC_PREFETCH_NONE) && | |
277 | + (prefetch_jiffies != TPC_PREFETCH_JUNK) && | |
278 | + (prefetch_arg0 == args->val[0]) && | |
279 | + (prefetch_argF == args->val[0xF]) && | |
280 | + (get_jiffies_64() < prefetch_jiffies + TPC_PREFETCH_TIMEOUT); | |
281 | +} | |
282 | + | |
283 | +/** | |
284 | + * thinkpad_ec_read_row - request and read data from ThinkPad EC | |
285 | + * @args Input register arguments | |
286 | + * @data Output register values | |
287 | + * | |
288 | + * Read a data row from the ThinkPad embedded controller LPC3 interface. | |
289 | + * Does fetching and retrying if needed. The row is specified by an | |
290 | + * array of 16 bytes, some of which may be undefined (but the first is | |
291 | + * mandatory). These bytes are given in @args->val[], where @args->val[i] is | |
292 | + * used iff (@args->mask>>i)&1). The resulting row data is stored in | |
293 | + * @data->val[], but is only guaranteed to be valid for indices corresponding | |
294 | + * to set bit in @data->mask. That is, if @data->mask&(1<<i)==0 then | |
295 | + * @data->val[i] is undefined. | |
296 | + * | |
297 | + * Returns -EBUSY on transient error and -EIO on abnormal condition. | |
298 | + * Caller must hold controller lock. | |
299 | + */ | |
300 | +int thinkpad_ec_read_row(const struct thinkpad_ec_row *args, | |
301 | + struct thinkpad_ec_row *data) | |
302 | +{ | |
303 | + int retries, ret; | |
304 | + | |
305 | + if (thinkpad_ec_is_row_fetched(args)) | |
306 | + goto read_row; /* already requested */ | |
307 | + | |
308 | + /* Request the row */ | |
309 | + for (retries = 0; retries < TPC_READ_RETRIES; ++retries) { | |
310 | + ret = thinkpad_ec_request_row(args); | |
311 | + if (!ret) | |
312 | + goto read_row; | |
313 | + if (ret != -EBUSY) | |
314 | + break; | |
315 | + ndelay(TPC_READ_NDELAY); | |
316 | + } | |
317 | + printk(KERN_ERR REQ_FMT("failed requesting row", ret)); | |
318 | + goto out; | |
319 | + | |
320 | +read_row: | |
321 | + /* Read the row's data */ | |
322 | + for (retries = 0; retries < TPC_READ_RETRIES; ++retries) { | |
323 | + ret = thinkpad_ec_read_data(args, data); | |
324 | + if (!ret) | |
325 | + goto out; | |
326 | + if (ret != -EBUSY) | |
327 | + break; | |
328 | + ndelay(TPC_READ_NDELAY); | |
329 | + } | |
330 | + | |
331 | + printk(KERN_ERR REQ_FMT("failed waiting for data", ret)); | |
332 | + | |
333 | +out: | |
334 | + prefetch_jiffies = TPC_PREFETCH_JUNK; | |
335 | + return ret; | |
336 | +} | |
337 | +EXPORT_SYMBOL_GPL(thinkpad_ec_read_row); | |
338 | + | |
339 | +/** | |
340 | + * thinkpad_ec_try_read_row - try reading prefetched data from ThinkPad EC | |
341 | + * @args Input register arguments | |
342 | + * @data Output register values | |
343 | + * | |
344 | + * Try reading a data row from the ThinkPad embedded controller LPC3 | |
345 | + * interface, if this raw was recently prefetched using | |
346 | + * thinkpad_ec_prefetch_row(). Does not fetch, retry or block. | |
347 | + * The parameters have the same meaning as in thinkpad_ec_read_row(). | |
348 | + * | |
349 | + * Returns -EBUSY is data not ready and -ENODATA if row not prefetched. | |
350 | + * Caller must hold controller lock. | |
351 | + */ | |
352 | +int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args, | |
353 | + struct thinkpad_ec_row *data) | |
354 | +{ | |
355 | + int ret; | |
356 | + if (!thinkpad_ec_is_row_fetched(args)) { | |
357 | + ret = -ENODATA; | |
358 | + } else { | |
359 | + ret = thinkpad_ec_read_data(args, data); | |
360 | + if (!ret) | |
361 | + prefetch_jiffies = TPC_PREFETCH_NONE; /* eaten up */ | |
362 | + } | |
363 | + return ret; | |
364 | +} | |
365 | +EXPORT_SYMBOL_GPL(thinkpad_ec_try_read_row); | |
366 | + | |
367 | +/** | |
368 | + * thinkpad_ec_prefetch_row - prefetch data from ThinkPad EC | |
369 | + * @args Input register arguments | |
370 | + * | |
371 | + * Prefetch a data row from the ThinkPad embedded controller LCP3 | |
372 | + * interface. A subsequent call to thinkpad_ec_read_row() with the | |
373 | + * same arguments will be faster, and a subsequent call to | |
374 | + * thinkpad_ec_try_read_row() stands a good chance of succeeding if | |
375 | + * done neither too soon nor too late. See | |
376 | + * thinkpad_ec_read_row() for the meaning of @args. | |
377 | + * | |
378 | + * Returns -EBUSY on transient error and -EIO on abnormal condition. | |
379 | + * Caller must hold controller lock. | |
380 | + */ | |
381 | +int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args) | |
382 | +{ | |
383 | + int ret; | |
384 | + ret = thinkpad_ec_request_row(args); | |
385 | + if (ret) { | |
386 | + prefetch_jiffies = TPC_PREFETCH_JUNK; | |
387 | + } else { | |
388 | + prefetch_jiffies = get_jiffies_64(); | |
389 | + prefetch_arg0 = args->val[0x0]; | |
390 | + prefetch_argF = args->val[0xF]; | |
391 | + } | |
392 | + return ret; | |
393 | +} | |
394 | +EXPORT_SYMBOL_GPL(thinkpad_ec_prefetch_row); | |
395 | + | |
396 | +/** | |
397 | + * thinkpad_ec_invalidate - invalidate prefetched ThinkPad EC data | |
398 | + * | |
399 | + * Invalidate the data prefetched via thinkpad_ec_prefetch_row() from the | |
400 | + * ThinkPad embedded controller LPC3 interface. | |
401 | + * Must be called before unlocking by any code that accesses the controller | |
402 | + * ports directly. | |
403 | + */ | |
404 | +void thinkpad_ec_invalidate(void) | |
405 | +{ | |
406 | + prefetch_jiffies = TPC_PREFETCH_JUNK; | |
407 | +} | |
408 | +EXPORT_SYMBOL_GPL(thinkpad_ec_invalidate); | |
409 | + | |
410 | + | |
411 | +/*** Checking for EC hardware ***/ | |
412 | + | |
413 | +/** | |
414 | + * thinkpad_ec_test - verify the EC is present and follows protocol | |
415 | + * | |
416 | + * Ensure the EC LPC3 channel really works on this machine by making | |
417 | + * an EC request and seeing if the EC follows the documented H8S protocol. | |
418 | + * The requested row just reads battery status, so it should be harmless to | |
419 | + * access it (on a correct EC). | |
420 | + * This test writes to IO ports, so execute only after checking DMI. | |
421 | + */ | |
422 | +static int __init thinkpad_ec_test(void) | |
423 | +{ | |
424 | + int ret; | |
425 | + const struct thinkpad_ec_row args = /* battery 0 basic status */ | |
426 | + { .mask = 0x8001, .val = {0x01,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x00} }; | |
427 | + struct thinkpad_ec_row data = { .mask = 0x0000 }; | |
428 | + ret = thinkpad_ec_lock(); | |
429 | + if (ret) | |
430 | + return ret; | |
431 | + ret = thinkpad_ec_read_row(&args, &data); | |
432 | + thinkpad_ec_unlock(); | |
433 | + return ret; | |
434 | +} | |
435 | + | |
436 | +/* Search all DMI device names of a given type for a substring */ | |
437 | +static int __init dmi_find_substring(int type, const char *substr) | |
438 | +{ | |
439 | + const struct dmi_device *dev = NULL; | |
440 | + while ((dev = dmi_find_device(type, NULL, dev))) { | |
441 | + if (strstr(dev->name, substr)) | |
442 | + return 1; | |
443 | + } | |
444 | + return 0; | |
445 | +} | |
446 | + | |
447 | +#define TP_DMI_MATCH(vendor,model) { \ | |
448 | + .ident = vendor " " model, \ | |
449 | + .matches = { \ | |
450 | + DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ | |
451 | + DMI_MATCH(DMI_PRODUCT_VERSION, model) \ | |
452 | + } \ | |
453 | +} | |
454 | + | |
455 | +/* Check DMI for existence of ThinkPad embedded controller */ | |
456 | +static int __init check_dmi_for_ec(void) | |
457 | +{ | |
458 | + /* A few old models that have a good EC but don't report it in DMI */ | |
459 | + struct dmi_system_id tp_whitelist[] = { | |
460 | + TP_DMI_MATCH("IBM", "ThinkPad A30"), | |
461 | + TP_DMI_MATCH("IBM", "ThinkPad T23"), | |
462 | + TP_DMI_MATCH("IBM", "ThinkPad X24"), | |
463 | + TP_DMI_MATCH("LENOVO", "ThinkPad"), | |
464 | + { .ident = NULL } | |
465 | + }; | |
466 | + return dmi_find_substring(DMI_DEV_TYPE_OEM_STRING, | |
467 | + "IBM ThinkPad Embedded Controller") || | |
468 | + dmi_check_system(tp_whitelist); | |
469 | +} | |
470 | + | |
471 | +/*** Init and cleanup ***/ | |
472 | + | |
473 | +static int __init thinkpad_ec_init(void) | |
474 | +{ | |
475 | + if (!check_dmi_for_ec()) { | |
476 | + printk(KERN_WARNING | |
477 | + "thinkpad_ec: no ThinkPad embedded controller!\n"); | |
478 | + return -ENODEV; | |
479 | + } | |
480 | + | |
481 | + if (request_region(TPC_BASE_PORT, TPC_NUM_PORTS, "thinkpad_ec")) { | |
482 | + reserved_io = 1; | |
483 | + } else { | |
484 | + printk(KERN_ERR "thinkpad_ec: cannot claim IO ports %#x-%#x... ", | |
485 | + TPC_BASE_PORT, | |
486 | + TPC_BASE_PORT + TPC_NUM_PORTS - 1); | |
487 | + if (force_io) { | |
488 | + printk("forcing use of unreserved IO ports.\n"); | |
489 | + } else { | |
490 | + printk("consider using force_io=1.\n"); | |
491 | + return -ENXIO; | |
492 | + } | |
493 | + } | |
494 | + prefetch_jiffies = TPC_PREFETCH_JUNK; | |
495 | + if (thinkpad_ec_test()) { | |
496 | + printk(KERN_ERR "thinkpad_ec: initial ec test failed\n"); | |
497 | + if (reserved_io) | |
498 | + release_region(TPC_BASE_PORT, TPC_NUM_PORTS); | |
499 | + return -ENXIO; | |
500 | + } | |
501 | + printk(KERN_INFO "thinkpad_ec: thinkpad_ec " TP_VERSION " loaded.\n"); | |
502 | + return 0; | |
503 | +} | |
504 | + | |
505 | +static void __exit thinkpad_ec_exit(void) | |
506 | +{ | |
507 | + if (reserved_io) | |
508 | + release_region(TPC_BASE_PORT, TPC_NUM_PORTS); | |
509 | + printk(KERN_INFO "thinkpad_ec: unloaded.\n"); | |
510 | +} | |
511 | + | |
512 | +module_init(thinkpad_ec_init); | |
513 | +module_exit(thinkpad_ec_exit); |
@@ -0,0 +1,47 @@ | ||
1 | +/* | |
2 | + * thinkpad_ec.h - interface to ThinkPad embedded controller LPC3 functions | |
3 | + * | |
4 | + * Copyright (C) 2005 Shem Multinymous <multinymous@gmail.com> | |
5 | + * | |
6 | + * This program is free software; you can redistribute it and/or modify | |
7 | + * it under the terms of the GNU General Public License as published by | |
8 | + * the Free Software Foundation; either version 2 of the License, or | |
9 | + * (at your option) any later version. | |
10 | + * | |
11 | + * This program is distributed in the hope that it will be useful, | |
12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | + * GNU General Public License for more details. | |
15 | + * | |
16 | + * You should have received a copy of the GNU General Public License | |
17 | + * along with this program; if not, write to the Free Software | |
18 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | + */ | |
20 | + | |
21 | +#ifndef _THINKPAD_EC_H | |
22 | +#define _THINKPAD_EC_H | |
23 | + | |
24 | +#ifdef __KERNEL__ | |
25 | + | |
26 | +#define TP_CONTROLLER_ROW_LEN 16 | |
27 | + | |
28 | +/* EC transactions input and output (possibly partial) vectors of 16 bytes. */ | |
29 | +struct thinkpad_ec_row { | |
30 | + u16 mask; /* bitmap of which entries of val[] are meaningful */ | |
31 | + u8 val[TP_CONTROLLER_ROW_LEN]; | |
32 | +}; | |
33 | + | |
34 | +extern int __must_check thinkpad_ec_lock(void); | |
35 | +extern int __must_check thinkpad_ec_try_lock(void); | |
36 | +extern void thinkpad_ec_unlock(void); | |
37 | + | |
38 | +extern int thinkpad_ec_read_row(const struct thinkpad_ec_row *args, | |
39 | + struct thinkpad_ec_row *data); | |
40 | +extern int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args, | |
41 | + struct thinkpad_ec_row *mask); | |
42 | +extern int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args); | |
43 | +extern void thinkpad_ec_invalidate(void); | |
44 | + | |
45 | + | |
46 | +#endif /* __KERNEL */ | |
47 | +#endif /* _THINKPAD_EC_H */ |
@@ -0,0 +1,1493 @@ | ||
1 | +/* | |
2 | + * tp_smapi.c - ThinkPad SMAPI support | |
3 | + * | |
4 | + * This driver exposes some features of the System Management Application | |
5 | + * Program Interface (SMAPI) BIOS found on ThinkPad laptops. It works on | |
6 | + * models in which the SMAPI BIOS runs in SMM and is invoked by writing | |
7 | + * to the APM control port 0xB2. | |
8 | + * It also exposes battery status information, obtained from the ThinkPad | |
9 | + * embedded controller (via the thinkpad_ec module). | |
10 | + * Ancient ThinkPad models use a different interface, supported by the | |
11 | + * "thinkpad" module from "tpctl". | |
12 | + * | |
13 | + * Many of the battery status values obtained from the EC simply mirror | |
14 | + * values provided by the battery's Smart Battery System (SBS) interface, so | |
15 | + * their meaning is defined by the Smart Battery Data Specification (see | |
16 | + * http://sbs-forum.org/specs/sbdat110.pdf). References to this SBS spec | |
17 | + * are given in the code where relevant. | |
18 | + * | |
19 | + * Copyright (C) 2006 Shem Multinymous <multinymous@gmail.com>. | |
20 | + * SMAPI access code based on the mwave driver by Mike Sullivan. | |
21 | + * | |
22 | + * This program is free software; you can redistribute it and/or modify | |
23 | + * it under the terms of the GNU General Public License as published by | |
24 | + * the Free Software Foundation; either version 2 of the License, or | |
25 | + * (at your option) any later version. | |
26 | + * | |
27 | + * This program is distributed in the hope that it will be useful, | |
28 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
29 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
30 | + * GNU General Public License for more details. | |
31 | + * | |
32 | + * You should have received a copy of the GNU General Public License | |
33 | + * along with this program; if not, write to the Free Software | |
34 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
35 | + */ | |
36 | + | |
37 | +#include <linux/kernel.h> | |
38 | +#include <linux/module.h> | |
39 | +#include <linux/init.h> | |
40 | +#include <linux/types.h> | |
41 | +#include <linux/proc_fs.h> | |
42 | +#include <linux/mc146818rtc.h> /* CMOS defines */ | |
43 | +#include <linux/delay.h> | |
44 | +#include <linux/version.h> | |
45 | +#include "thinkpad_ec.h" | |
46 | +#include <linux/platform_device.h> | |
47 | +#include <asm/uaccess.h> | |
48 | +#include <asm/io.h> | |
49 | + | |
50 | +#define TP_VERSION "0.41" | |
51 | +#define TP_DESC "ThinkPad SMAPI Support" | |
52 | +#define TP_DIR "smapi" | |
53 | + | |
54 | +MODULE_AUTHOR("Shem Multinymous"); | |
55 | +MODULE_DESCRIPTION(TP_DESC); | |
56 | +MODULE_VERSION(TP_VERSION); | |
57 | +MODULE_LICENSE("GPL"); | |
58 | + | |
59 | +static struct platform_device *pdev; | |
60 | + | |
61 | +static int tp_debug; | |
62 | +module_param_named(debug, tp_debug, int, 0600); | |
63 | +MODULE_PARM_DESC(debug, "Debug level (0=off, 1=on)"); | |
64 | + | |
65 | +/* A few macros for printk()ing: */ | |
66 | +#define TPRINTK(level, fmt, args...) \ | |
67 | + dev_printk(level, &(pdev->dev), "%s: " fmt "\n", __func__, ## args) | |
68 | +#define DPRINTK(fmt, args...) \ | |
69 | + do { if (tp_debug) TPRINTK(KERN_DEBUG, fmt, ## args); } while (0) | |
70 | + | |
71 | +/********************************************************************* | |
72 | + * SMAPI interface | |
73 | + */ | |
74 | + | |
75 | +/* SMAPI functions (register BX when making the SMM call). */ | |
76 | +#define SMAPI_GET_INHIBIT_CHARGE 0x2114 | |
77 | +#define SMAPI_SET_INHIBIT_CHARGE 0x2115 | |
78 | +#define SMAPI_GET_THRESH_START 0x2116 | |
79 | +#define SMAPI_SET_THRESH_START 0x2117 | |
80 | +#define SMAPI_GET_FORCE_DISCHARGE 0x2118 | |
81 | +#define SMAPI_SET_FORCE_DISCHARGE 0x2119 | |
82 | +#define SMAPI_GET_THRESH_STOP 0x211a | |
83 | +#define SMAPI_SET_THRESH_STOP 0x211b | |
84 | + | |
85 | +/* SMAPI error codes (see ThinkPad 770 Technical Reference Manual p.83 at | |
86 | + http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD */ | |
87 | +#define SMAPI_RETCODE_EOF 0xff | |
88 | +static struct { u8 rc; char *msg; int ret; } smapi_retcode[] = | |
89 | +{ | |
90 | + {0x00, "OK", 0}, | |
91 | + {0x53, "SMAPI function is not available", -ENXIO}, | |
92 | + {0x81, "Invalid parameter", -EINVAL}, | |
93 | + {0x86, "Function is not supported by SMAPI BIOS", -EOPNOTSUPP}, | |
94 | + {0x90, "System error", -EIO}, | |
95 | + {0x91, "System is invalid", -EIO}, | |
96 | + {0x92, "System is busy, -EBUSY"}, | |
97 | + {0xa0, "Device error (disk read error)", -EIO}, | |
98 | + {0xa1, "Device is busy", -EBUSY}, | |
99 | + {0xa2, "Device is not attached", -ENXIO}, | |
100 | + {0xa3, "Device is disbled", -EIO}, | |
101 | + {0xa4, "Request parameter is out of range", -EINVAL}, | |
102 | + {0xa5, "Request parameter is not accepted", -EINVAL}, | |
103 | + {0xa6, "Transient error", -EBUSY}, /* ? */ | |
104 | + {SMAPI_RETCODE_EOF, "Unknown error code", -EIO} | |
105 | +}; | |
106 | + | |
107 | + | |
108 | +#define SMAPI_MAX_RETRIES 10 | |
109 | +#define SMAPI_PORT2 0x4F /* fixed port, meaning unclear */ | |
110 | +static unsigned short smapi_port; /* APM control port, normally 0xB2 */ | |
111 | + | |
112 | +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) | |
113 | +static DECLARE_MUTEX(smapi_mutex); | |
114 | +#else | |
115 | +static DEFINE_SEMAPHORE(smapi_mutex); | |
116 | +#endif | |
117 | + | |
118 | +/** | |
119 | + * find_smapi_port - read SMAPI port from NVRAM | |
120 | + */ | |
121 | +static int __init find_smapi_port(void) | |
122 | +{ | |
123 | + u16 smapi_id = 0; | |
124 | + unsigned short port = 0; | |
125 | + unsigned long flags; | |
126 | + | |
127 | + spin_lock_irqsave(&rtc_lock, flags); | |
128 | + smapi_id = CMOS_READ(0x7C); | |
129 | + smapi_id |= (CMOS_READ(0x7D) << 8); | |
130 | + spin_unlock_irqrestore(&rtc_lock, flags); | |
131 | + | |
132 | + if (smapi_id != 0x5349) { | |
133 | + printk(KERN_ERR "SMAPI not supported (ID=0x%x)\n", smapi_id); | |
134 | + return -ENXIO; | |
135 | + } | |
136 | + spin_lock_irqsave(&rtc_lock, flags); | |
137 | + port = CMOS_READ(0x7E); | |
138 | + port |= (CMOS_READ(0x7F) << 8); | |
139 | + spin_unlock_irqrestore(&rtc_lock, flags); | |
140 | + if (port == 0) { | |
141 | + printk(KERN_ERR "unable to read SMAPI port number\n"); | |
142 | + return -ENXIO; | |
143 | + } | |
144 | + return port; | |
145 | +} | |
146 | + | |
147 | +/** | |
148 | + * smapi_request - make a SMAPI call | |
149 | + * @inEBX, @inECX, @inEDI, @inESI: input registers | |
150 | + * @outEBX, @outECX, @outEDX, @outEDI, @outESI: outputs registers | |
151 | + * @msg: textual error message | |
152 | + * Invokes the SMAPI SMBIOS with the given input and outpu args. | |
153 | + * All outputs are optional (can be %NULL). | |
154 | + * Returns 0 when successful, and a negative errno constant | |
155 | + * (see smapi_retcode above) upon failure. | |
156 | + */ | |
157 | +static int smapi_request(u32 inEBX, u32 inECX, | |
158 | + u32 inEDI, u32 inESI, | |
159 | + u32 *outEBX, u32 *outECX, u32 *outEDX, | |
160 | + u32 *outEDI, u32 *outESI, const char **msg) | |
161 | +{ | |
162 | + int ret = 0; | |
163 | + int i; | |
164 | + int retries; | |
165 | + u8 rc; | |
166 | + /* Must use local vars for output regs, due to reg pressure. */ | |
167 | + u32 tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI; | |
168 | + | |
169 | + for (retries = 0; retries < SMAPI_MAX_RETRIES; ++retries) { | |
170 | + DPRINTK("req_in: BX=%x CX=%x DI=%x SI=%x", | |
171 | + inEBX, inECX, inEDI, inESI); | |
172 | + | |
173 | + /* SMAPI's SMBIOS call and thinkpad_ec end up using use | |
174 | + * different interfaces to the same chip, so play it safe. */ | |
175 | + ret = thinkpad_ec_lock(); | |
176 | + if (ret) | |
177 | + return ret; | |
178 | + | |
179 | + __asm__ __volatile__( | |
180 | + "movl $0x00005380,%%eax\n\t" | |
181 | + "movl %6,%%ebx\n\t" | |
182 | + "movl %7,%%ecx\n\t" | |
183 | + "movl %8,%%edi\n\t" | |
184 | + "movl %9,%%esi\n\t" | |
185 | + "xorl %%edx,%%edx\n\t" | |
186 | + "movw %10,%%dx\n\t" | |
187 | + "out %%al,%%dx\n\t" /* trigger SMI to SMBIOS */ | |
188 | + "out %%al,$0x4F\n\t" | |
189 | + "movl %%eax,%0\n\t" | |
190 | + "movl %%ebx,%1\n\t" | |
191 | + "movl %%ecx,%2\n\t" | |
192 | + "movl %%edx,%3\n\t" | |
193 | + "movl %%edi,%4\n\t" | |
194 | + "movl %%esi,%5\n\t" | |
195 | + :"=m"(tmpEAX), | |
196 | + "=m"(tmpEBX), | |
197 | + "=m"(tmpECX), | |
198 | + "=m"(tmpEDX), | |
199 | + "=m"(tmpEDI), | |
200 | + "=m"(tmpESI) | |
201 | + :"m"(inEBX), "m"(inECX), "m"(inEDI), "m"(inESI), | |
202 | + "m"((u16)smapi_port) | |
203 | + :"%eax", "%ebx", "%ecx", "%edx", "%edi", | |
204 | + "%esi"); | |
205 | + | |
206 | + thinkpad_ec_invalidate(); | |
207 | + thinkpad_ec_unlock(); | |
208 | + | |
209 | + /* Don't let the next SMAPI access happen too quickly, | |
210 | + * may case problems. (We're hold smapi_mutex). */ | |
211 | + msleep(50); | |
212 | + | |
213 | + if (outEBX) *outEBX = tmpEBX; | |
214 | + if (outECX) *outECX = tmpECX; | |
215 | + if (outEDX) *outEDX = tmpEDX; | |
216 | + if (outESI) *outESI = tmpESI; | |
217 | + if (outEDI) *outEDI = tmpEDI; | |
218 | + | |
219 | + /* Look up error code */ | |
220 | + rc = (tmpEAX>>8)&0xFF; | |
221 | + for (i = 0; smapi_retcode[i].rc != SMAPI_RETCODE_EOF && | |
222 | + smapi_retcode[i].rc != rc; ++i) {} | |
223 | + ret = smapi_retcode[i].ret; | |
224 | + if (msg) | |
225 | + *msg = smapi_retcode[i].msg; | |
226 | + | |
227 | + DPRINTK("req_out: AX=%x BX=%x CX=%x DX=%x DI=%x SI=%x r=%d", | |
228 | + tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI, ret); | |
229 | + if (ret) | |
230 | + TPRINTK(KERN_NOTICE, "SMAPI error: %s (func=%x)", | |
231 | + smapi_retcode[i].msg, inEBX); | |
232 | + | |
233 | + if (ret != -EBUSY) | |
234 | + return ret; | |
235 | + } | |
236 | + return ret; | |
237 | +} | |
238 | + | |
239 | +/* Convenience wrapper: discard output arguments */ | |
240 | +static int smapi_write(u32 inEBX, u32 inECX, | |
241 | + u32 inEDI, u32 inESI, const char **msg) | |
242 | +{ | |
243 | + return smapi_request(inEBX, inECX, inEDI, inESI, | |
244 | + NULL, NULL, NULL, NULL, NULL, msg); | |
245 | +} | |
246 | + | |
247 | + | |
248 | +/********************************************************************* | |
249 | + * Specific SMAPI services | |
250 | + * All of these functions return 0 upon success, and a negative errno | |
251 | + * constant (see smapi_retcode) on failure. | |
252 | + */ | |
253 | + | |
254 | +enum thresh_type { | |
255 | + THRESH_STOP = 0, /* the code assumes this is 0 for brevity */ | |
256 | + THRESH_START | |
257 | +}; | |
258 | +#define THRESH_NAME(which) ((which == THRESH_START) ? "start" : "stop") | |
259 | + | |
260 | +/** | |
261 | + * __get_real_thresh - read battery charge start/stop threshold from SMAPI | |
262 | + * @bat: battery number (0 or 1) | |
263 | + * @which: THRESH_START or THRESH_STOP | |
264 | + * @thresh: 1..99, 0=default 1..99, 0=default (pass this as-is to SMAPI) | |
265 | + * @outEDI: some additional state that needs to be preserved, meaning unknown | |
266 | + * @outESI: some additional state that needs to be preserved, meaning unknown | |
267 | + */ | |
268 | +static int __get_real_thresh(int bat, enum thresh_type which, int *thresh, | |
269 | + u32 *outEDI, u32 *outESI) | |
270 | +{ | |
271 | + u32 ebx = (which == THRESH_START) ? SMAPI_GET_THRESH_START | |
272 | + : SMAPI_GET_THRESH_STOP; | |
273 | + u32 ecx = (bat+1)<<8; | |
274 | + const char *msg; | |
275 | + int ret = smapi_request(ebx, ecx, 0, 0, NULL, | |
276 | + &ecx, NULL, outEDI, outESI, &msg); | |
277 | + if (ret) { | |
278 | + TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: %s", | |
279 | + THRESH_NAME(which), bat, msg); | |
280 | + return ret; | |
281 | + } | |
282 | + if (!(ecx&0x00000100)) { | |
283 | + TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: ecx=0%x", | |
284 | + THRESH_NAME(which), bat, ecx); | |
285 | + return -EIO; | |
286 | + } | |
287 | + if (thresh) | |
288 | + *thresh = ecx&0xFF; | |
289 | + return 0; | |
290 | +} | |
291 | + | |
292 | +/** | |
293 | + * get_real_thresh - read battery charge start/stop threshold from SMAPI | |
294 | + * @bat: battery number (0 or 1) | |
295 | + * @which: THRESH_START or THRESH_STOP | |
296 | + * @thresh: 1..99, 0=default (passes as-is to SMAPI) | |
297 | + */ | |
298 | +static int get_real_thresh(int bat, enum thresh_type which, int *thresh) | |
299 | +{ | |
300 | + return __get_real_thresh(bat, which, thresh, NULL, NULL); | |
301 | +} | |
302 | + | |
303 | +/** | |
304 | + * set_real_thresh - write battery start/top charge threshold to SMAPI | |
305 | + * @bat: battery number (0 or 1) | |
306 | + * @which: THRESH_START or THRESH_STOP | |
307 | + * @thresh: 1..99, 0=default (passes as-is to SMAPI) | |
308 | + */ | |
309 | +static int set_real_thresh(int bat, enum thresh_type which, int thresh) | |
310 | +{ | |
311 | + u32 ebx = (which == THRESH_START) ? SMAPI_SET_THRESH_START | |
312 | + : SMAPI_SET_THRESH_STOP; | |
313 | + u32 ecx = ((bat+1)<<8) + thresh; | |
314 | + u32 getDI, getSI; | |
315 | + const char *msg; | |
316 | + int ret; | |
317 | + | |
318 | + /* verify read before writing */ | |
319 | + ret = __get_real_thresh(bat, which, NULL, &getDI, &getSI); | |
320 | + if (ret) | |
321 | + return ret; | |
322 | + | |
323 | + ret = smapi_write(ebx, ecx, getDI, getSI, &msg); | |
324 | + if (ret) | |
325 | + TPRINTK(KERN_NOTICE, "set %s to %d for bat=%d failed: %s", | |
326 | + THRESH_NAME(which), thresh, bat, msg); | |
327 | + else | |
328 | + TPRINTK(KERN_INFO, "set %s to %d for bat=%d", | |
329 | + THRESH_NAME(which), thresh, bat); | |
330 | + return ret; | |
331 | +} | |
332 | + | |
333 | +/** | |
334 | + * __get_inhibit_charge_minutes - get inhibit charge period from SMAPI | |
335 | + * @bat: battery number (0 or 1) | |
336 | + * @minutes: period in minutes (1..65535 minutes, 0=disabled) | |
337 | + * @outECX: some additional state that needs to be preserved, meaning unknown | |
338 | + * Note that @minutes is the originally set value, it does not count down. | |
339 | + */ | |
340 | +static int __get_inhibit_charge_minutes(int bat, int *minutes, u32 *outECX) | |
341 | +{ | |
342 | + u32 ecx = (bat+1)<<8; | |
343 | + u32 esi; | |
344 | + const char *msg; | |
345 | + int ret = smapi_request(SMAPI_GET_INHIBIT_CHARGE, ecx, 0, 0, | |
346 | + NULL, &ecx, NULL, NULL, &esi, &msg); | |
347 | + if (ret) { | |
348 | + TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg); | |
349 | + return ret; | |
350 | + } | |
351 | + if (!(ecx&0x0100)) { | |
352 | + TPRINTK(KERN_NOTICE, "bad ecx=0x%x for bat=%d", ecx, bat); | |
353 | + return -EIO; | |
354 | + } | |
355 | + if (minutes) | |
356 | + *minutes = (ecx&0x0001)?esi:0; | |
357 | + if (outECX) | |
358 | + *outECX = ecx; | |
359 | + return 0; | |
360 | +} | |
361 | + | |
362 | +/** | |
363 | + * get_inhibit_charge_minutes - get inhibit charge period from SMAPI | |
364 | + * @bat: battery number (0 or 1) | |
365 | + * @minutes: period in minutes (1..65535 minutes, 0=disabled) | |
366 | + * Note that @minutes is the originally set value, it does not count down. | |
367 | + */ | |
368 | +static int get_inhibit_charge_minutes(int bat, int *minutes) | |
369 | +{ | |
370 | + return __get_inhibit_charge_minutes(bat, minutes, NULL); | |
371 | +} | |
372 | + | |
373 | +/** | |
374 | + * set_inhibit_charge_minutes - write inhibit charge period to SMAPI | |
375 | + * @bat: battery number (0 or 1) | |
376 | + * @minutes: period in minutes (1..65535 minutes, 0=disabled) | |
377 | + */ | |
378 | +static int set_inhibit_charge_minutes(int bat, int minutes) | |
379 | +{ | |
380 | + u32 ecx; | |
381 | + const char *msg; | |
382 | + int ret; | |
383 | + | |
384 | + /* verify read before writing */ | |
385 | + ret = __get_inhibit_charge_minutes(bat, NULL, &ecx); | |
386 | + if (ret) | |
387 | + return ret; | |
388 | + | |
389 | + ecx = ((bat+1)<<8) | (ecx&0x00FE) | (minutes > 0 ? 0x0001 : 0x0000); | |
390 | + if (minutes > 0xFFFF) | |
391 | + minutes = 0xFFFF; | |
392 | + ret = smapi_write(SMAPI_SET_INHIBIT_CHARGE, ecx, 0, minutes, &msg); | |
393 | + if (ret) | |
394 | + TPRINTK(KERN_NOTICE, | |
395 | + "set to %d failed for bat=%d: %s", minutes, bat, msg); | |
396 | + else | |
397 | + TPRINTK(KERN_INFO, "set to %d for bat=%d\n", minutes, bat); | |
398 | + return ret; | |
399 | +} | |
400 | + | |
401 | + | |
402 | +/** | |
403 | + * get_force_discharge - get status of forced discharging from SMAPI | |
404 | + * @bat: battery number (0 or 1) | |
405 | + * @enabled: 1 if forced discharged is enabled, 0 if not | |
406 | + */ | |
407 | +static int get_force_discharge(int bat, int *enabled) | |
408 | +{ | |
409 | + u32 ecx = (bat+1)<<8; | |
410 | + const char *msg; | |
411 | + int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0, | |
412 | + NULL, &ecx, NULL, NULL, NULL, &msg); | |
413 | + if (ret) { | |
414 | + TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg); | |
415 | + return ret; | |
416 | + } | |
417 | + *enabled = (!(ecx&0x00000100) && (ecx&0x00000001))?1:0; | |
418 | + return 0; | |
419 | +} | |
420 | + | |
421 | +/** | |
422 | + * set_force_discharge - write status of forced discharging to SMAPI | |
423 | + * @bat: battery number (0 or 1) | |
424 | + * @enabled: 1 if forced discharged is enabled, 0 if not | |
425 | + */ | |
426 | +static int set_force_discharge(int bat, int enabled) | |
427 | +{ | |
428 | + u32 ecx = (bat+1)<<8; | |
429 | + const char *msg; | |
430 | + int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0, | |
431 | + NULL, &ecx, NULL, NULL, NULL, &msg); | |
432 | + if (ret) { | |
433 | + TPRINTK(KERN_NOTICE, "get failed for bat=%d: %s", bat, msg); | |
434 | + return ret; | |
435 | + } | |
436 | + if (ecx&0x00000100) { | |
437 | + TPRINTK(KERN_NOTICE, "cannot force discharge bat=%d", bat); | |
438 | + return -EIO; | |
439 | + } | |
440 | + | |
441 | + ecx = ((bat+1)<<8) | (ecx&0x000000FA) | (enabled?0x00000001:0); | |
442 | + ret = smapi_write(SMAPI_SET_FORCE_DISCHARGE, ecx, 0, 0, &msg); | |
443 | + if (ret) | |
444 | + TPRINTK(KERN_NOTICE, "set to %d failed for bat=%d: %s", | |
445 | + enabled, bat, msg); | |
446 | + else | |
447 | + TPRINTK(KERN_INFO, "set to %d for bat=%d", enabled, bat); | |
448 | + return ret; | |
449 | +} | |
450 | + | |
451 | + | |
452 | +/********************************************************************* | |
453 | + * Wrappers to threshold-related SMAPI functions, which handle default | |
454 | + * thresholds and related quirks. | |
455 | + */ | |
456 | + | |
457 | +/* Minimum, default and minimum difference for battery charging thresholds: */ | |
458 | +#define MIN_THRESH_DELTA 4 /* Min delta between start and stop thresh */ | |
459 | +#define MIN_THRESH_START 2 | |
460 | +#define MAX_THRESH_START (100-MIN_THRESH_DELTA) | |
461 | +#define MIN_THRESH_STOP (MIN_THRESH_START + MIN_THRESH_DELTA) | |
462 | +#define MAX_THRESH_STOP 100 | |
463 | +#define DEFAULT_THRESH_START MAX_THRESH_START | |
464 | +#define DEFAULT_THRESH_STOP MAX_THRESH_STOP | |
465 | + | |
466 | +/* The GUI of IBM's Battery Maximizer seems to show a start threshold that | |
467 | + * is 1 more than the value we set/get via SMAPI. Since the threshold is | |
468 | + * maintained across reboot, this can be confusing. So we kludge our | |
469 | + * interface for interoperability: */ | |
470 | +#define BATMAX_FIX 1 | |
471 | + | |
472 | +/* Get charge start/stop threshold (1..100), | |
473 | + * substituting default values if needed and applying BATMAT_FIX. */ | |
474 | +static int get_thresh(int bat, enum thresh_type which, int *thresh) | |
475 | +{ | |
476 | + int ret = get_real_thresh(bat, which, thresh); | |
477 | + if (ret) | |
478 | + return ret; | |
479 | + if (*thresh == 0) | |
480 | + *thresh = (which == THRESH_START) ? DEFAULT_THRESH_START | |
481 | + : DEFAULT_THRESH_STOP; | |
482 | + else if (which == THRESH_START) | |
483 | + *thresh += BATMAX_FIX; | |
484 | + return 0; | |
485 | +} | |
486 | + | |
487 | + | |
488 | +/* Set charge start/stop threshold (1..100), | |
489 | + * substituting default values if needed and applying BATMAT_FIX. */ | |
490 | +static int set_thresh(int bat, enum thresh_type which, int thresh) | |
491 | +{ | |
492 | + if (which == THRESH_STOP && thresh == DEFAULT_THRESH_STOP) | |
493 | + thresh = 0; /* 100 is out of range, but default means 100 */ | |
494 | + if (which == THRESH_START) | |
495 | + thresh -= BATMAX_FIX; | |
496 | + return set_real_thresh(bat, which, thresh); | |
497 | +} | |
498 | + | |
499 | +/********************************************************************* | |
500 | + * ThinkPad embedded controller readout and basic functions | |
501 | + */ | |
502 | + | |
503 | +/** | |
504 | + * read_tp_ec_row - read data row from the ThinkPad embedded controller | |
505 | + * @arg0: EC command code | |
506 | + * @bat: battery number, 0 or 1 | |
507 | + * @j: the byte value to be used for "junk" (unused) input/outputs | |
508 | + * @dataval: result vector | |
509 | + */ | |
510 | +static int read_tp_ec_row(u8 arg0, int bat, u8 j, u8 *dataval) | |
511 | +{ | |
512 | + int ret; | |
513 | + const struct thinkpad_ec_row args = { .mask = 0xFFFF, | |
514 | + .val = {arg0, j,j,j,j,j,j,j,j,j,j,j,j,j,j, (u8)bat} }; | |
515 | + struct thinkpad_ec_row data = { .mask = 0xFFFF }; | |
516 | + | |
517 | + ret = thinkpad_ec_lock(); | |
518 | + if (ret) | |
519 | + return ret; | |
520 | + ret = thinkpad_ec_read_row(&args, &data); | |
521 | + thinkpad_ec_unlock(); | |
522 | + memcpy(dataval, &data.val, TP_CONTROLLER_ROW_LEN); | |
523 | + return ret; | |
524 | +} | |
525 | + | |
526 | +/** | |
527 | + * power_device_present - check for presence of battery or AC power | |
528 | + * @bat: 0 for battery 0, 1 for battery 1, otherwise AC power | |
529 | + * Returns 1 if present, 0 if not present, negative if error. | |
530 | + */ | |
531 | +static int power_device_present(int bat) | |
532 | +{ | |
533 | + u8 row[TP_CONTROLLER_ROW_LEN]; | |
534 | + u8 test; | |
535 | + int ret = read_tp_ec_row(1, bat, 0, row); | |
536 | + if (ret) | |
537 | + return ret; | |
538 | + switch (bat) { | |
539 | + case 0: test = 0x40; break; /* battery 0 */ | |
540 | + case 1: test = 0x20; break; /* battery 1 */ | |
541 | + default: test = 0x80; /* AC power */ | |
542 | + } | |
543 | + return (row[0] & test) ? 1 : 0; | |
544 | +} | |
545 | + | |
546 | +/** | |
547 | + * bat_has_status - check if battery can report detailed status | |
548 | + * @bat: 0 for battery 0, 1 for battery 1 | |
549 | + * Returns 1 if yes, 0 if no, negative if error. | |
550 | + */ | |
551 | +static int bat_has_status(int bat) | |
552 | +{ | |
553 | + u8 row[TP_CONTROLLER_ROW_LEN]; | |
554 | + int ret = read_tp_ec_row(1, bat, 0, row); | |
555 | + if (ret) | |
556 | + return ret; | |
557 | + if ((row[0] & (bat?0x20:0x40)) == 0) /* no battery */ | |
558 | + return 0; | |
559 | + if ((row[1] & (0x60)) == 0) /* no status */ | |
560 | + return 0; | |
561 | + return 1; | |
562 | +} | |
563 | + | |
564 | +/** | |
565 | + * get_tp_ec_bat_16 - read a 16-bit value from EC battery status data | |
566 | + * @arg0: first argument to EC | |
567 | + * @off: offset in row returned from EC | |
568 | + * @bat: battery (0 or 1) | |
569 | + * @val: the 16-bit value obtained | |
570 | + * Returns nonzero on error. | |
571 | + */ | |
572 | +static int get_tp_ec_bat_16(u8 arg0, int offset, int bat, u16 *val) | |
573 | +{ | |
574 | + u8 row[TP_CONTROLLER_ROW_LEN]; | |
575 | + int ret; | |
576 | + if (bat_has_status(bat) != 1) | |
577 | + return -ENXIO; | |
578 | + ret = read_tp_ec_row(arg0, bat, 0, row); | |
579 | + if (ret) | |
580 | + return ret; | |
581 | + *val = *(u16 *)(row+offset); | |
582 | + return 0; | |
583 | +} | |
584 | + | |
585 | +/********************************************************************* | |
586 | + * sysfs attributes for batteries - | |
587 | + * definitions and helper functions | |
588 | + */ | |
589 | + | |
590 | +/* A custom device attribute struct which holds a battery number */ | |
591 | +struct bat_device_attribute { | |
592 | + struct device_attribute dev_attr; | |
593 | + int bat; | |
594 | +}; | |
595 | + | |
596 | +/** | |
597 | + * attr_get_bat - get the battery to which the attribute belongs | |
598 | + */ | |
599 | +static int attr_get_bat(struct device_attribute *attr) | |
600 | +{ | |
601 | + return container_of(attr, struct bat_device_attribute, dev_attr)->bat; | |
602 | +} | |
603 | + | |
604 | +/** | |
605 | + * show_tp_ec_bat_u16 - show an unsigned 16-bit battery attribute | |
606 | + * @arg0: specified 1st argument of EC raw to read | |
607 | + * @offset: byte offset in EC raw data | |
608 | + * @mul: correction factor to multiply by | |
609 | + * @na_msg: string to output is value not available (0xFFFFFFFF) | |
610 | + * @attr: battery attribute | |
611 | + * @buf: output buffer | |
612 | + * The 16-bit value is read from the EC, treated as unsigned, | |
613 | + * transformed as x->mul*x, and printed to the buffer. | |
614 | + * If the value is 0xFFFFFFFF and na_msg!=%NULL, na_msg is printed instead. | |
615 | + */ | |
616 | +static ssize_t show_tp_ec_bat_u16(u8 arg0, int offset, int mul, | |
617 | + const char *na_msg, | |
618 | + struct device_attribute *attr, char *buf) | |
619 | +{ | |
620 | + u16 val; | |
621 | + int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val); | |
622 | + if (ret) | |
623 | + return ret; | |
624 | + if (na_msg && val == 0xFFFF) | |
625 | + return sprintf(buf, "%s\n", na_msg); | |
626 | + else | |
627 | + return sprintf(buf, "%u\n", mul*(unsigned int)val); | |
628 | +} | |
629 | + | |
630 | +/** | |
631 | + * show_tp_ec_bat_s16 - show an signed 16-bit battery attribute | |
632 | + * @arg0: specified 1st argument of EC raw to read | |
633 | + * @offset: byte offset in EC raw data | |
634 | + * @mul: correction factor to multiply by | |
635 | + * @add: correction term to add after multiplication | |
636 | + * @attr: battery attribute | |
637 | + * @buf: output buffer | |
638 | + * The 16-bit value is read from the EC, treated as signed, | |
639 | + * transformed as x->mul*x+add, and printed to the buffer. | |
640 | + */ | |
641 | +static ssize_t show_tp_ec_bat_s16(u8 arg0, int offset, int mul, int add, | |
642 | + struct device_attribute *attr, char *buf) | |
643 | +{ | |
644 | + u16 val; | |
645 | + int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val); | |
646 | + if (ret) | |
647 | + return ret; | |
648 | + return sprintf(buf, "%d\n", mul*(s16)val+add); | |
649 | +} | |
650 | + | |
651 | +/** | |
652 | + * show_tp_ec_bat_str - show a string from EC battery status data | |
653 | + * @arg0: specified 1st argument of EC raw to read | |
654 | + * @offset: byte offset in EC raw data | |
655 | + * @maxlen: maximum string length | |
656 | + * @attr: battery attribute | |
657 | + * @buf: output buffer | |
658 | + */ | |
659 | +static ssize_t show_tp_ec_bat_str(u8 arg0, int offset, int maxlen, | |
660 | + struct device_attribute *attr, char *buf) | |
661 | +{ | |
662 | + int bat = attr_get_bat(attr); | |
663 | + u8 row[TP_CONTROLLER_ROW_LEN]; | |
664 | + int ret; | |
665 | + if (bat_has_status(bat) != 1) | |
666 | + return -ENXIO; | |
667 | + ret = read_tp_ec_row(arg0, bat, 0, row); | |
668 | + if (ret) | |
669 | + return ret; | |
670 | + strncpy(buf, (char *)row+offset, maxlen); | |
671 | + buf[maxlen] = 0; | |
672 | + strcat(buf, "\n"); | |
673 | + return strlen(buf); | |
674 | +} | |
675 | + | |
676 | +/** | |
677 | + * show_tp_ec_bat_power - show a power readout from EC battery status data | |
678 | + * @arg0: specified 1st argument of EC raw to read | |
679 | + * @offV: byte offset of voltage in EC raw data | |
680 | + * @offI: byte offset of current in EC raw data | |
681 | + * @attr: battery attribute | |
682 | + * @buf: output buffer | |
683 | + * Computes the power as current*voltage from the two given readout offsets. | |
684 | + */ | |
685 | +static ssize_t show_tp_ec_bat_power(u8 arg0, int offV, int offI, | |
686 | + struct device_attribute *attr, char *buf) | |
687 | +{ | |
688 | + u8 row[TP_CONTROLLER_ROW_LEN]; | |
689 | + int milliamp, millivolt, ret; | |
690 | + int bat = attr_get_bat(attr); | |
691 | + if (bat_has_status(bat) != 1) | |
692 | + return -ENXIO; | |
693 | + ret = read_tp_ec_row(1, bat, 0, row); | |
694 | + if (ret) | |
695 | + return ret; | |
696 | + millivolt = *(u16 *)(row+offV); | |
697 | + milliamp = *(s16 *)(row+offI); | |
698 | + return sprintf(buf, "%d\n", milliamp*millivolt/1000); /* units: mW */ | |
699 | +} | |
700 | + | |
701 | +/** | |
702 | + * show_tp_ec_bat_date - decode and show a date from EC battery status data | |
703 | + * @arg0: specified 1st argument of EC raw to read | |
704 | + * @offset: byte offset in EC raw data | |
705 | + * @attr: battery attribute | |
706 | + * @buf: output buffer | |
707 | + */ | |
708 | +static ssize_t show_tp_ec_bat_date(u8 arg0, int offset, | |
709 | + struct device_attribute *attr, char *buf) | |
710 | +{ | |
711 | + u8 row[TP_CONTROLLER_ROW_LEN]; | |
712 | + u16 v; | |
713 | + int ret; | |
714 | + int day, month, year; | |
715 | + int bat = attr_get_bat(attr); | |
716 | + if (bat_has_status(bat) != 1) | |
717 | + return -ENXIO; | |
718 | + ret = read_tp_ec_row(arg0, bat, 0, row); | |
719 | + if (ret) | |
720 | + return ret; | |
721 | + | |
722 | + /* Decode bit-packed: v = day | (month<<5) | ((year-1980)<<9) */ | |
723 | + v = *(u16 *)(row+offset); | |
724 | + day = v & 0x1F; | |
725 | + month = (v >> 5) & 0xF; | |
726 | + year = (v >> 9) + 1980; | |
727 | + | |
728 | + return sprintf(buf, "%04d-%02d-%02d\n", year, month, day); | |
729 | +} | |
730 | + | |
731 | + | |
732 | +/********************************************************************* | |
733 | + * sysfs attribute I/O for batteries - | |
734 | + * the actual attribute show/store functions | |
735 | + */ | |
736 | + | |
737 | +static ssize_t show_battery_start_charge_thresh(struct device *dev, | |
738 | + struct device_attribute *attr, char *buf) | |
739 | +{ | |
740 | + int thresh; | |
741 | + int bat = attr_get_bat(attr); | |
742 | + int ret = get_thresh(bat, THRESH_START, &thresh); | |
743 | + if (ret) | |
744 | + return ret; | |
745 | + return sprintf(buf, "%d\n", thresh); /* units: percent */ | |
746 | +} | |
747 | + | |
748 | +static ssize_t show_battery_stop_charge_thresh(struct device *dev, | |
749 | + struct device_attribute *attr, char *buf) | |
750 | +{ | |
751 | + int thresh; | |
752 | + int bat = attr_get_bat(attr); | |
753 | + int ret = get_thresh(bat, THRESH_STOP, &thresh); | |
754 | + if (ret) | |
755 | + return ret; | |
756 | + return sprintf(buf, "%d\n", thresh); /* units: percent */ | |
757 | +} | |
758 | + | |
759 | +/** | |
760 | + * store_battery_start_charge_thresh - store battery_start_charge_thresh attr | |
761 | + * Since this is a kernel<->user interface, we ensure a valid state for | |
762 | + * the hardware. We do this by clamping the requested threshold to the | |
763 | + * valid range and, if necessary, moving the other threshold so that | |
764 | + * it's MIN_THRESH_DELTA away from this one. | |
765 | + */ | |
766 | +static ssize_t store_battery_start_charge_thresh(struct device *dev, | |
767 | + struct device_attribute *attr, const char *buf, size_t count) | |
768 | +{ | |
769 | + int thresh, other_thresh, ret; | |
770 | + int bat = attr_get_bat(attr); | |
771 | + | |
772 | + if (sscanf(buf, "%d", &thresh) != 1 || thresh < 1 || thresh > 100) | |
773 | + return -EINVAL; | |
774 | + | |
775 | + if (thresh < MIN_THRESH_START) /* clamp up to MIN_THRESH_START */ | |
776 | + thresh = MIN_THRESH_START; | |
777 | + if (thresh > MAX_THRESH_START) /* clamp down to MAX_THRESH_START */ | |
778 | + thresh = MAX_THRESH_START; | |
779 | + | |
780 | + down(&smapi_mutex); | |
781 | + ret = get_thresh(bat, THRESH_STOP, &other_thresh); | |
782 | + if (ret != -EOPNOTSUPP && ret != -ENXIO) { | |
783 | + if (ret) /* other threshold is set? */ | |
784 | + goto out; | |
785 | + ret = get_real_thresh(bat, THRESH_START, NULL); | |
786 | + if (ret) /* this threshold is set? */ | |
787 | + goto out; | |
788 | + if (other_thresh < thresh+MIN_THRESH_DELTA) { | |
789 | + /* move other thresh to keep it above this one */ | |
790 | + ret = set_thresh(bat, THRESH_STOP, | |
791 | + thresh+MIN_THRESH_DELTA); | |
792 | + if (ret) | |
793 | + goto out; | |
794 | + } | |
795 | + } | |
796 | + ret = set_thresh(bat, THRESH_START, thresh); | |
797 | +out: | |
798 | + up(&smapi_mutex); | |
799 | + return count; | |
800 | + | |
801 | +} | |
802 | + | |
803 | +/** | |
804 | + * store_battery_stop_charge_thresh - store battery_stop_charge_thresh attr | |
805 | + * Since this is a kernel<->user interface, we ensure a valid state for | |
806 | + * the hardware. We do this by clamping the requested threshold to the | |
807 | + * valid range and, if necessary, moving the other threshold so that | |
808 | + * it's MIN_THRESH_DELTA away from this one. | |
809 | + */ | |
810 | +static ssize_t store_battery_stop_charge_thresh(struct device *dev, | |
811 | + struct device_attribute *attr, const char *buf, size_t count) | |
812 | +{ | |
813 | + int thresh, other_thresh, ret; | |
814 | + int bat = attr_get_bat(attr); | |
815 | + | |
816 | + if (sscanf(buf, "%d", &thresh) != 1 || thresh < 1 || thresh > 100) | |
817 | + return -EINVAL; | |
818 | + | |
819 | + if (thresh < MIN_THRESH_STOP) /* clamp up to MIN_THRESH_STOP */ | |
820 | + thresh = MIN_THRESH_STOP; | |
821 | + | |
822 | + down(&smapi_mutex); | |
823 | + ret = get_thresh(bat, THRESH_START, &other_thresh); | |
824 | + if (ret != -EOPNOTSUPP && ret != -ENXIO) { /* other threshold exists? */ | |
825 | + if (ret) | |
826 | + goto out; | |
827 | + /* this threshold exists? */ | |
828 | + ret = get_real_thresh(bat, THRESH_STOP, NULL); | |
829 | + if (ret) | |
830 | + goto out; | |
831 | + if (other_thresh >= thresh-MIN_THRESH_DELTA) { | |
832 | + /* move other thresh to be below this one */ | |
833 | + ret = set_thresh(bat, THRESH_START, | |
834 | + thresh-MIN_THRESH_DELTA); | |
835 | + if (ret) | |
836 | + goto out; | |
837 | + } | |
838 | + } | |
839 | + ret = set_thresh(bat, THRESH_STOP, thresh); | |
840 | +out: | |
841 | + up(&smapi_mutex); | |
842 | + return count; | |
843 | +} | |
844 | + | |
845 | +static ssize_t show_battery_inhibit_charge_minutes(struct device *dev, | |
846 | + struct device_attribute *attr, char *buf) | |
847 | +{ | |
848 | + int minutes; | |
849 | + int bat = attr_get_bat(attr); | |
850 | + int ret = get_inhibit_charge_minutes(bat, &minutes); | |
851 | + if (ret) | |
852 | + return ret; | |
853 | + return sprintf(buf, "%d\n", minutes); /* units: minutes */ | |
854 | +} | |
855 | + | |
856 | +static ssize_t store_battery_inhibit_charge_minutes(struct device *dev, | |
857 | + struct device_attribute *attr, | |
858 | + const char *buf, size_t count) | |
859 | +{ | |
860 | + int ret; | |
861 | + int minutes; | |
862 | + int bat = attr_get_bat(attr); | |
863 | + if (sscanf(buf, "%d", &minutes) != 1 || minutes < 0) { | |
864 | + TPRINTK(KERN_ERR, "inhibit_charge_minutes: " | |
865 | + "must be a non-negative integer"); | |
866 | + return -EINVAL; | |
867 | + } | |
868 | + ret = set_inhibit_charge_minutes(bat, minutes); | |
869 | + if (ret) | |
870 | + return ret; | |
871 | + return count; | |
872 | +} | |
873 | + | |
874 | +static ssize_t show_battery_force_discharge(struct device *dev, | |
875 | + struct device_attribute *attr, char *buf) | |
876 | +{ | |
877 | + int enabled; | |
878 | + int bat = attr_get_bat(attr); | |
879 | + int ret = get_force_discharge(bat, &enabled); | |
880 | + if (ret) | |
881 | + return ret; | |
882 | + return sprintf(buf, "%d\n", enabled); /* type: boolean */ | |
883 | +} | |
884 | + | |
885 | +static ssize_t store_battery_force_discharge(struct device *dev, | |
886 | + struct device_attribute *attr, const char *buf, size_t count) | |
887 | +{ | |
888 | + int ret; | |
889 | + int enabled; | |
890 | + int bat = attr_get_bat(attr); | |
891 | + if (sscanf(buf, "%d", &enabled) != 1 || enabled < 0 || enabled > 1) | |
892 | + return -EINVAL; | |
893 | + ret = set_force_discharge(bat, enabled); | |
894 | + if (ret) | |
895 | + return ret; | |
896 | + return count; | |
897 | +} | |
898 | + | |
899 | +static ssize_t show_battery_installed( | |
900 | + struct device *dev, struct device_attribute *attr, char *buf) | |
901 | +{ | |
902 | + int bat = attr_get_bat(attr); | |
903 | + int ret = power_device_present(bat); | |
904 | + if (ret < 0) | |
905 | + return ret; | |
906 | + return sprintf(buf, "%d\n", ret); /* type: boolean */ | |
907 | +} | |
908 | + | |
909 | +static ssize_t show_battery_state( | |
910 | + struct device *dev, struct device_attribute *attr, char *buf) | |
911 | +{ | |
912 | + u8 row[TP_CONTROLLER_ROW_LEN]; | |
913 | + const char *txt; | |
914 | + int ret; | |
915 | + int bat = attr_get_bat(attr); | |
916 | + if (bat_has_status(bat) != 1) | |
917 | + return sprintf(buf, "none\n"); | |
918 | + ret = read_tp_ec_row(1, bat, 0, row); | |
919 | + if (ret) | |
920 | + return ret; | |
921 | + switch (row[1] & 0xf0) { | |
922 | + case 0xc0: txt = "idle"; break; | |
923 | + case 0xd0: txt = "discharging"; break; | |
924 | + case 0xe0: txt = "charging"; break; | |
925 | + default: return sprintf(buf, "unknown (0x%x)\n", row[1]); | |
926 | + } | |
927 | + return sprintf(buf, "%s\n", txt); /* type: string from fixed set */ | |
928 | +} | |
929 | + | |
930 | +static ssize_t show_battery_manufacturer( | |
931 | + struct device *dev, struct device_attribute *attr, char *buf) | |
932 | +{ | |
933 | + /* type: string. SBS spec v1.1 p34: ManufacturerName() */ | |
934 | + return show_tp_ec_bat_str(4, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); | |
935 | +} | |
936 | + | |
937 | +static ssize_t show_battery_model( | |
938 | + struct device *dev, struct device_attribute *attr, char *buf) | |
939 | +{ | |
940 | + /* type: string. SBS spec v1.1 p34: DeviceName() */ | |
941 | + return show_tp_ec_bat_str(5, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); | |
942 | +} | |
943 | + | |
944 | +static ssize_t show_battery_barcoding( | |
945 | + struct device *dev, struct device_attribute *attr, char *buf) | |
946 | +{ | |
947 | + /* type: string */ | |
948 | + return show_tp_ec_bat_str(7, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); | |
949 | +} | |
950 | + | |
951 | +static ssize_t show_battery_chemistry( | |
952 | + struct device *dev, struct device_attribute *attr, char *buf) | |
953 | +{ | |
954 | + /* type: string. SBS spec v1.1 p34-35: DeviceChemistry() */ | |
955 | + return show_tp_ec_bat_str(6, 2, 5, attr, buf); | |
956 | +} | |
957 | + | |
958 | +static ssize_t show_battery_voltage( | |
959 | + struct device *dev, struct device_attribute *attr, char *buf) | |
960 | +{ | |
961 | + /* units: mV. SBS spec v1.1 p24: Voltage() */ | |
962 | + return show_tp_ec_bat_u16(1, 6, 1, NULL, attr, buf); | |
963 | +} | |
964 | + | |
965 | +static ssize_t show_battery_design_voltage( | |
966 | + struct device *dev, struct device_attribute *attr, char *buf) | |
967 | +{ | |
968 | + /* units: mV. SBS spec v1.1 p32: DesignVoltage() */ | |
969 | + return show_tp_ec_bat_u16(3, 4, 1, NULL, attr, buf); | |
970 | +} | |
971 | + | |
972 | +static ssize_t show_battery_charging_max_voltage( | |
973 | + struct device *dev, struct device_attribute *attr, char *buf) | |
974 | +{ | |
975 | + /* units: mV. SBS spec v1.1 p37,39: ChargingVoltage() */ | |
976 | + return show_tp_ec_bat_u16(9, 8, 1, NULL, attr, buf); | |
977 | +} | |
978 | + | |
979 | +static ssize_t show_battery_group0_voltage( | |
980 | + struct device *dev, struct device_attribute *attr, char *buf) | |
981 | +{ | |
982 | + /* units: mV */ | |
983 | + return show_tp_ec_bat_u16(0xA, 12, 1, NULL, attr, buf); | |
984 | +} | |
985 | + | |
986 | +static ssize_t show_battery_group1_voltage( | |
987 | + struct device *dev, struct device_attribute *attr, char *buf) | |
988 | +{ | |
989 | + /* units: mV */ | |
990 | + return show_tp_ec_bat_u16(0xA, 10, 1, NULL, attr, buf); | |
991 | +} | |
992 | + | |
993 | +static ssize_t show_battery_group2_voltage( | |
994 | + struct device *dev, struct device_attribute *attr, char *buf) | |
995 | +{ | |
996 | + /* units: mV */ | |
997 | + return show_tp_ec_bat_u16(0xA, 8, 1, NULL, attr, buf); | |
998 | +} | |
999 | + | |
1000 | +static ssize_t show_battery_group3_voltage( | |
1001 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1002 | +{ | |
1003 | + /* units: mV */ | |
1004 | + return show_tp_ec_bat_u16(0xA, 6, 1, NULL, attr, buf); | |
1005 | +} | |
1006 | + | |
1007 | +static ssize_t show_battery_current_now( | |
1008 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1009 | +{ | |
1010 | + /* units: mA. SBS spec v1.1 p24: Current() */ | |
1011 | + return show_tp_ec_bat_s16(1, 8, 1, 0, attr, buf); | |
1012 | +} | |
1013 | + | |
1014 | +static ssize_t show_battery_current_avg( | |
1015 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1016 | +{ | |
1017 | + /* units: mA. SBS spec v1.1 p24: AverageCurrent() */ | |
1018 | + return show_tp_ec_bat_s16(1, 10, 1, 0, attr, buf); | |
1019 | +} | |
1020 | + | |
1021 | +static ssize_t show_battery_charging_max_current( | |
1022 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1023 | +{ | |
1024 | + /* units: mA. SBS spec v1.1 p36,38: ChargingCurrent() */ | |
1025 | + return show_tp_ec_bat_s16(9, 6, 1, 0, attr, buf); | |
1026 | +} | |
1027 | + | |
1028 | +static ssize_t show_battery_power_now( | |
1029 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1030 | +{ | |
1031 | + /* units: mW. SBS spec v1.1: Voltage()*Current() */ | |
1032 | + return show_tp_ec_bat_power(1, 6, 8, attr, buf); | |
1033 | +} | |
1034 | + | |
1035 | +static ssize_t show_battery_power_avg( | |
1036 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1037 | +{ | |
1038 | + /* units: mW. SBS spec v1.1: Voltage()*AverageCurrent() */ | |
1039 | + return show_tp_ec_bat_power(1, 6, 10, attr, buf); | |
1040 | +} | |
1041 | + | |
1042 | +static ssize_t show_battery_remaining_percent( | |
1043 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1044 | +{ | |
1045 | + /* units: percent. SBS spec v1.1 p25: RelativeStateOfCharge() */ | |
1046 | + return show_tp_ec_bat_u16(1, 12, 1, NULL, attr, buf); | |
1047 | +} | |
1048 | + | |
1049 | +static ssize_t show_battery_remaining_percent_error( | |
1050 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1051 | +{ | |
1052 | + /* units: percent. SBS spec v1.1 p25: MaxError() */ | |
1053 | + return show_tp_ec_bat_u16(9, 4, 1, NULL, attr, buf); | |
1054 | +} | |
1055 | + | |
1056 | +static ssize_t show_battery_remaining_charging_time( | |
1057 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1058 | +{ | |
1059 | + /* units: minutes. SBS spec v1.1 p27: AverageTimeToFull() */ | |
1060 | + return show_tp_ec_bat_u16(2, 8, 1, "not_charging", attr, buf); | |
1061 | +} | |
1062 | + | |
1063 | +static ssize_t show_battery_remaining_running_time( | |
1064 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1065 | +{ | |
1066 | + /* units: minutes. SBS spec v1.1 p27: RunTimeToEmpty() */ | |
1067 | + return show_tp_ec_bat_u16(2, 6, 1, "not_discharging", attr, buf); | |
1068 | +} | |
1069 | + | |
1070 | +static ssize_t show_battery_remaining_running_time_now( | |
1071 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1072 | +{ | |
1073 | + /* units: minutes. SBS spec v1.1 p27: RunTimeToEmpty() */ | |
1074 | + return show_tp_ec_bat_u16(2, 4, 1, "not_discharging", attr, buf); | |
1075 | +} | |
1076 | + | |
1077 | +static ssize_t show_battery_remaining_capacity( | |
1078 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1079 | +{ | |
1080 | + /* units: mWh. SBS spec v1.1 p26. */ | |
1081 | + return show_tp_ec_bat_u16(1, 14, 10, "", attr, buf); | |
1082 | +} | |
1083 | + | |
1084 | +static ssize_t show_battery_last_full_capacity( | |
1085 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1086 | +{ | |
1087 | + /* units: mWh. SBS spec v1.1 p26: FullChargeCapacity() */ | |
1088 | + return show_tp_ec_bat_u16(2, 2, 10, "", attr, buf); | |
1089 | +} | |
1090 | + | |
1091 | +static ssize_t show_battery_design_capacity( | |
1092 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1093 | +{ | |
1094 | + /* units: mWh. SBS spec v1.1 p32: DesignCapacity() */ | |
1095 | + return show_tp_ec_bat_u16(3, 2, 10, "", attr, buf); | |
1096 | +} | |
1097 | + | |
1098 | +static ssize_t show_battery_cycle_count( | |
1099 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1100 | +{ | |
1101 | + /* units: ordinal. SBS spec v1.1 p32: CycleCount() */ | |
1102 | + return show_tp_ec_bat_u16(2, 12, 1, "", attr, buf); | |
1103 | +} | |
1104 | + | |
1105 | +static ssize_t show_battery_temperature( | |
1106 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1107 | +{ | |
1108 | + /* units: millicelsius. SBS spec v1.1: Temperature()*10 */ | |
1109 | + return show_tp_ec_bat_s16(1, 4, 100, -273100, attr, buf); | |
1110 | +} | |
1111 | + | |
1112 | +static ssize_t show_battery_serial( | |
1113 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1114 | +{ | |
1115 | + /* type: int. SBS spec v1.1 p34: SerialNumber() */ | |
1116 | + return show_tp_ec_bat_u16(3, 10, 1, "", attr, buf); | |
1117 | +} | |
1118 | + | |
1119 | +static ssize_t show_battery_manufacture_date( | |
1120 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1121 | +{ | |
1122 | + /* type: YYYY-MM-DD. SBS spec v1.1 p34: ManufactureDate() */ | |
1123 | + return show_tp_ec_bat_date(3, 8, attr, buf); | |
1124 | +} | |
1125 | + | |
1126 | +static ssize_t show_battery_first_use_date( | |
1127 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1128 | +{ | |
1129 | + /* type: YYYY-MM-DD */ | |
1130 | + return show_tp_ec_bat_date(8, 2, attr, buf); | |
1131 | +} | |
1132 | + | |
1133 | +/** | |
1134 | + * show_battery_dump - show the battery's dump attribute | |
1135 | + * The dump attribute gives a hex dump of all EC readouts related to a | |
1136 | + * battery. Some of the enumerated values don't really exist (i.e., the | |
1137 | + * EC function just leaves them untouched); we use a kludge to detect and | |
1138 | + * denote these. | |
1139 | + */ | |
1140 | +#define MIN_DUMP_ARG0 0x00 | |
1141 | +#define MAX_DUMP_ARG0 0x0a /* 0x0b is useful too but hangs old EC firmware */ | |
1142 | +static ssize_t show_battery_dump( | |
1143 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1144 | +{ | |
1145 | + int i; | |
1146 | + char *p = buf; | |
1147 | + int bat = attr_get_bat(attr); | |
1148 | + u8 arg0; /* first argument to EC */ | |
1149 | + u8 rowa[TP_CONTROLLER_ROW_LEN], | |
1150 | + rowb[TP_CONTROLLER_ROW_LEN]; | |
1151 | + const u8 junka = 0xAA, | |
1152 | + junkb = 0x55; /* junk values for testing changes */ | |
1153 | + int ret; | |
1154 | + | |
1155 | + for (arg0 = MIN_DUMP_ARG0; arg0 <= MAX_DUMP_ARG0; ++arg0) { | |
1156 | + if ((p-buf) > PAGE_SIZE-TP_CONTROLLER_ROW_LEN*5) | |
1157 | + return -ENOMEM; /* don't overflow sysfs buf */ | |
1158 | + /* Read raw twice with different junk values, | |
1159 | + * to detect unused output bytes which are left unchaged: */ | |
1160 | + ret = read_tp_ec_row(arg0, bat, junka, rowa); | |
1161 | + if (ret) | |
1162 | + return ret; | |
1163 | + ret = read_tp_ec_row(arg0, bat, junkb, rowb); | |
1164 | + if (ret) | |
1165 | + return ret; | |
1166 | + for (i = 0; i < TP_CONTROLLER_ROW_LEN; i++) { | |
1167 | + if (rowa[i] == junka && rowb[i] == junkb) | |
1168 | + p += sprintf(p, "-- "); /* unused by EC */ | |
1169 | + else | |
1170 | + p += sprintf(p, "%02x ", rowa[i]); | |
1171 | + } | |
1172 | + p += sprintf(p, "\n"); | |
1173 | + } | |
1174 | + return p-buf; | |
1175 | +} | |
1176 | + | |
1177 | + | |
1178 | +/********************************************************************* | |
1179 | + * sysfs attribute I/O, other than batteries | |
1180 | + */ | |
1181 | + | |
1182 | +static ssize_t show_ac_connected( | |
1183 | + struct device *dev, struct device_attribute *attr, char *buf) | |
1184 | +{ | |
1185 | + int ret = power_device_present(0xFF); | |
1186 | + if (ret < 0) | |
1187 | + return ret; | |
1188 | + return sprintf(buf, "%d\n", ret); /* type: boolean */ | |
1189 | +} | |
1190 | + | |
1191 | +/********************************************************************* | |
1192 | + * The the "smapi_request" sysfs attribute executes a raw SMAPI call. | |
1193 | + * You write to make a request and read to get the result. The state | |
1194 | + * is saved globally rather than per fd (sysfs limitation), so | |
1195 | + * simultaenous requests may get each other's results! So this is for | |
1196 | + * development and debugging only. | |
1197 | + */ | |
1198 | +#define MAX_SMAPI_ATTR_ANSWER_LEN 128 | |
1199 | +static char smapi_attr_answer[MAX_SMAPI_ATTR_ANSWER_LEN] = ""; | |
1200 | + | |
1201 | +static ssize_t show_smapi_request(struct device *dev, | |
1202 | + struct device_attribute *attr, char *buf) | |
1203 | +{ | |
1204 | + int ret = snprintf(buf, PAGE_SIZE, "%s", smapi_attr_answer); | |
1205 | + smapi_attr_answer[0] = '\0'; | |
1206 | + return ret; | |
1207 | +} | |
1208 | + | |
1209 | +static ssize_t store_smapi_request(struct device *dev, | |
1210 | + struct device_attribute *attr, | |
1211 | + const char *buf, size_t count) | |
1212 | +{ | |
1213 | + unsigned int inEBX, inECX, inEDI, inESI; | |
1214 | + u32 outEBX, outECX, outEDX, outEDI, outESI; | |
1215 | + const char *msg; | |
1216 | + int ret; | |
1217 | + if (sscanf(buf, "%x %x %x %x", &inEBX, &inECX, &inEDI, &inESI) != 4) { | |
1218 | + smapi_attr_answer[0] = '\0'; | |
1219 | + return -EINVAL; | |
1220 | + } | |
1221 | + ret = smapi_request( | |
1222 | + inEBX, inECX, inEDI, inESI, | |
1223 | + &outEBX, &outECX, &outEDX, &outEDI, &outESI, &msg); | |
1224 | + snprintf(smapi_attr_answer, MAX_SMAPI_ATTR_ANSWER_LEN, | |
1225 | + "%x %x %x %x %x %d '%s'\n", | |
1226 | + (unsigned int)outEBX, (unsigned int)outECX, | |
1227 | + (unsigned int)outEDX, (unsigned int)outEDI, | |
1228 | + (unsigned int)outESI, ret, msg); | |
1229 | + if (ret) | |
1230 | + return ret; | |
1231 | + else | |
1232 | + return count; | |
1233 | +} | |
1234 | + | |
1235 | +/********************************************************************* | |
1236 | + * Power management: the embedded controller forgets the battery | |
1237 | + * thresholds when the system is suspended to disk and unplugged from | |
1238 | + * AC and battery, so we restore it upon resume. | |
1239 | + */ | |
1240 | + | |
1241 | +static int saved_threshs[4] = {-1, -1, -1, -1}; /* -1 = don't know */ | |
1242 | + | |
1243 | +static int tp_suspend(struct platform_device *dev, pm_message_t state) | |
1244 | +{ | |
1245 | + int restore = (state.event == PM_EVENT_HIBERNATE || | |
1246 | + state.event == PM_EVENT_FREEZE); | |
1247 | + if (!restore || get_real_thresh(0, THRESH_STOP , &saved_threshs[0])) | |
1248 | + saved_threshs[0] = -1; | |
1249 | + if (!restore || get_real_thresh(0, THRESH_START, &saved_threshs[1])) | |
1250 | + saved_threshs[1] = -1; | |
1251 | + if (!restore || get_real_thresh(1, THRESH_STOP , &saved_threshs[2])) | |
1252 | + saved_threshs[2] = -1; | |
1253 | + if (!restore || get_real_thresh(1, THRESH_START, &saved_threshs[3])) | |
1254 | + saved_threshs[3] = -1; | |
1255 | + DPRINTK("suspend saved: %d %d %d %d", saved_threshs[0], | |
1256 | + saved_threshs[1], saved_threshs[2], saved_threshs[3]); | |
1257 | + return 0; | |
1258 | +} | |
1259 | + | |
1260 | +static int tp_resume(struct platform_device *dev) | |
1261 | +{ | |
1262 | + DPRINTK("resume restoring: %d %d %d %d", saved_threshs[0], | |
1263 | + saved_threshs[1], saved_threshs[2], saved_threshs[3]); | |
1264 | + if (saved_threshs[0] >= 0) | |
1265 | + set_real_thresh(0, THRESH_STOP , saved_threshs[0]); | |
1266 | + if (saved_threshs[1] >= 0) | |
1267 | + set_real_thresh(0, THRESH_START, saved_threshs[1]); | |
1268 | + if (saved_threshs[2] >= 0) | |
1269 | + set_real_thresh(1, THRESH_STOP , saved_threshs[2]); | |
1270 | + if (saved_threshs[3] >= 0) | |
1271 | + set_real_thresh(1, THRESH_START, saved_threshs[3]); | |
1272 | + return 0; | |
1273 | +} | |
1274 | + | |
1275 | + | |
1276 | +/********************************************************************* | |
1277 | + * Driver model | |
1278 | + */ | |
1279 | + | |
1280 | +static struct platform_driver tp_driver = { | |
1281 | + .suspend = tp_suspend, | |
1282 | + .resume = tp_resume, | |
1283 | + .driver = { | |
1284 | + .name = "smapi", | |
1285 | + .owner = THIS_MODULE | |
1286 | + }, | |
1287 | +}; | |
1288 | + | |
1289 | + | |
1290 | +/********************************************************************* | |
1291 | + * Sysfs device model | |
1292 | + */ | |
1293 | + | |
1294 | +/* Attributes in /sys/devices/platform/smapi/ */ | |
1295 | + | |
1296 | +static DEVICE_ATTR(ac_connected, 0444, show_ac_connected, NULL); | |
1297 | +static DEVICE_ATTR(smapi_request, 0600, show_smapi_request, | |
1298 | + store_smapi_request); | |
1299 | + | |
1300 | +static struct attribute *tp_root_attributes[] = { | |
1301 | + &dev_attr_ac_connected.attr, | |
1302 | + &dev_attr_smapi_request.attr, | |
1303 | + NULL | |
1304 | +}; | |
1305 | +static struct attribute_group tp_root_attribute_group = { | |
1306 | + .attrs = tp_root_attributes | |
1307 | +}; | |
1308 | + | |
1309 | +/* Attributes under /sys/devices/platform/smapi/BAT{0,1}/ : | |
1310 | + * Every attribute needs to be defined (i.e., statically allocated) for | |
1311 | + * each battery, and then referenced in the attribute list of each battery. | |
1312 | + * We use preprocessor voodoo to avoid duplicating the list of attributes 4 | |
1313 | + * times. The preprocessor output is just normal sysfs attributes code. | |
1314 | + */ | |
1315 | + | |
1316 | +/** | |
1317 | + * FOREACH_BAT_ATTR - invoke the given macros on all our battery attributes | |
1318 | + * @_BAT: battery number (0 or 1) | |
1319 | + * @_ATTR_RW: macro to invoke for each read/write attribute | |
1320 | + * @_ATTR_R: macro to invoke for each read-only attribute | |
1321 | + */ | |
1322 | +#define FOREACH_BAT_ATTR(_BAT, _ATTR_RW, _ATTR_R) \ | |
1323 | + _ATTR_RW(_BAT, start_charge_thresh) \ | |
1324 | + _ATTR_RW(_BAT, stop_charge_thresh) \ | |
1325 | + _ATTR_RW(_BAT, inhibit_charge_minutes) \ | |
1326 | + _ATTR_RW(_BAT, force_discharge) \ | |
1327 | + _ATTR_R(_BAT, installed) \ | |
1328 | + _ATTR_R(_BAT, state) \ | |
1329 | + _ATTR_R(_BAT, manufacturer) \ | |
1330 | + _ATTR_R(_BAT, model) \ | |
1331 | + _ATTR_R(_BAT, barcoding) \ | |
1332 | + _ATTR_R(_BAT, chemistry) \ | |
1333 | + _ATTR_R(_BAT, voltage) \ | |
1334 | + _ATTR_R(_BAT, group0_voltage) \ | |
1335 | + _ATTR_R(_BAT, group1_voltage) \ | |
1336 | + _ATTR_R(_BAT, group2_voltage) \ | |
1337 | + _ATTR_R(_BAT, group3_voltage) \ | |
1338 | + _ATTR_R(_BAT, current_now) \ | |
1339 | + _ATTR_R(_BAT, current_avg) \ | |
1340 | + _ATTR_R(_BAT, charging_max_current) \ | |
1341 | + _ATTR_R(_BAT, power_now) \ | |
1342 | + _ATTR_R(_BAT, power_avg) \ | |
1343 | + _ATTR_R(_BAT, remaining_percent) \ | |
1344 | + _ATTR_R(_BAT, remaining_percent_error) \ | |
1345 | + _ATTR_R(_BAT, remaining_charging_time) \ | |
1346 | + _ATTR_R(_BAT, remaining_running_time) \ | |
1347 | + _ATTR_R(_BAT, remaining_running_time_now) \ | |
1348 | + _ATTR_R(_BAT, remaining_capacity) \ | |
1349 | + _ATTR_R(_BAT, last_full_capacity) \ | |
1350 | + _ATTR_R(_BAT, design_voltage) \ | |
1351 | + _ATTR_R(_BAT, charging_max_voltage) \ | |
1352 | + _ATTR_R(_BAT, design_capacity) \ | |
1353 | + _ATTR_R(_BAT, cycle_count) \ | |
1354 | + _ATTR_R(_BAT, temperature) \ | |
1355 | + _ATTR_R(_BAT, serial) \ | |
1356 | + _ATTR_R(_BAT, manufacture_date) \ | |
1357 | + _ATTR_R(_BAT, first_use_date) \ | |
1358 | + _ATTR_R(_BAT, dump) | |
1359 | + | |
1360 | +/* Define several macros we will feed into FOREACH_BAT_ATTR: */ | |
1361 | + | |
1362 | +#define DEFINE_BAT_ATTR_RW(_BAT,_NAME) \ | |
1363 | + static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \ | |
1364 | + .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, \ | |
1365 | + store_battery_##_NAME), \ | |
1366 | + .bat = _BAT \ | |
1367 | + }; | |
1368 | + | |
1369 | +#define DEFINE_BAT_ATTR_R(_BAT,_NAME) \ | |
1370 | + static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \ | |
1371 | + .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, 0), \ | |
1372 | + .bat = _BAT \ | |
1373 | + }; | |
1374 | + | |
1375 | +#define REF_BAT_ATTR(_BAT,_NAME) \ | |
1376 | + &dev_attr_##_NAME##_##_BAT.dev_attr.attr, | |
1377 | + | |
1378 | +/* This provide all attributes for one battery: */ | |
1379 | + | |
1380 | +#define PROVIDE_BAT_ATTRS(_BAT) \ | |
1381 | + FOREACH_BAT_ATTR(_BAT, DEFINE_BAT_ATTR_RW, DEFINE_BAT_ATTR_R) \ | |
1382 | + static struct attribute *tp_bat##_BAT##_attributes[] = { \ | |
1383 | + FOREACH_BAT_ATTR(_BAT, REF_BAT_ATTR, REF_BAT_ATTR) \ | |
1384 | + NULL \ | |
1385 | + }; \ | |
1386 | + static struct attribute_group tp_bat##_BAT##_attribute_group = { \ | |
1387 | + .name = "BAT" #_BAT, \ | |
1388 | + .attrs = tp_bat##_BAT##_attributes \ | |
1389 | + }; | |
1390 | + | |
1391 | +/* Finally genereate the attributes: */ | |
1392 | + | |
1393 | +PROVIDE_BAT_ATTRS(0) | |
1394 | +PROVIDE_BAT_ATTRS(1) | |
1395 | + | |
1396 | +/* List of attribute groups */ | |
1397 | + | |
1398 | +static struct attribute_group *attr_groups[] = { | |
1399 | + &tp_root_attribute_group, | |
1400 | + &tp_bat0_attribute_group, | |
1401 | + &tp_bat1_attribute_group, | |
1402 | + NULL | |
1403 | +}; | |
1404 | + | |
1405 | + | |
1406 | +/********************************************************************* | |
1407 | + * Init and cleanup | |
1408 | + */ | |
1409 | + | |
1410 | +static struct attribute_group **next_attr_group; /* next to register */ | |
1411 | + | |
1412 | +static int __init tp_init(void) | |
1413 | +{ | |
1414 | + int ret; | |
1415 | + printk(KERN_INFO "tp_smapi " TP_VERSION " loading...\n"); | |
1416 | + | |
1417 | + ret = find_smapi_port(); | |
1418 | + if (ret < 0) | |
1419 | + goto err; | |
1420 | + else | |
1421 | + smapi_port = ret; | |
1422 | + | |
1423 | + if (!request_region(smapi_port, 1, "smapi")) { | |
1424 | + printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", | |
1425 | + smapi_port); | |
1426 | + ret = -ENXIO; | |
1427 | + goto err; | |
1428 | + } | |
1429 | + | |
1430 | + if (!request_region(SMAPI_PORT2, 1, "smapi")) { | |
1431 | + printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", | |
1432 | + SMAPI_PORT2); | |
1433 | + ret = -ENXIO; | |
1434 | + goto err_port1; | |
1435 | + } | |
1436 | + | |
1437 | + ret = platform_driver_register(&tp_driver); | |
1438 | + if (ret) | |
1439 | + goto err_port2; | |
1440 | + | |
1441 | + pdev = platform_device_alloc("smapi", -1); | |
1442 | + if (!pdev) { | |
1443 | + ret = -ENOMEM; | |
1444 | + goto err_driver; | |
1445 | + } | |
1446 | + | |
1447 | + ret = platform_device_add(pdev); | |
1448 | + if (ret) | |
1449 | + goto err_device_free; | |
1450 | + | |
1451 | + for (next_attr_group = attr_groups; *next_attr_group; | |
1452 | + ++next_attr_group) { | |
1453 | + ret = sysfs_create_group(&pdev->dev.kobj, *next_attr_group); | |
1454 | + if (ret) | |
1455 | + goto err_attr; | |
1456 | + } | |
1457 | + | |
1458 | + printk(KERN_INFO "tp_smapi successfully loaded (smapi_port=0x%x).\n", | |
1459 | + smapi_port); | |
1460 | + return 0; | |
1461 | + | |
1462 | +err_attr: | |
1463 | + while (--next_attr_group >= attr_groups) | |
1464 | + sysfs_remove_group(&pdev->dev.kobj, *next_attr_group); | |
1465 | + platform_device_unregister(pdev); | |
1466 | +err_device_free: | |
1467 | + platform_device_put(pdev); | |
1468 | +err_driver: | |
1469 | + platform_driver_unregister(&tp_driver); | |
1470 | +err_port2: | |
1471 | + release_region(SMAPI_PORT2, 1); | |
1472 | +err_port1: | |
1473 | + release_region(smapi_port, 1); | |
1474 | +err: | |
1475 | + printk(KERN_ERR "tp_smapi init failed (ret=%d)!\n", ret); | |
1476 | + return ret; | |
1477 | +} | |
1478 | + | |
1479 | +static void __exit tp_exit(void) | |
1480 | +{ | |
1481 | + while (next_attr_group && --next_attr_group >= attr_groups) | |
1482 | + sysfs_remove_group(&pdev->dev.kobj, *next_attr_group); | |
1483 | + platform_device_unregister(pdev); | |
1484 | + platform_driver_unregister(&tp_driver); | |
1485 | + release_region(SMAPI_PORT2, 1); | |
1486 | + if (smapi_port) | |
1487 | + release_region(smapi_port, 1); | |
1488 | + | |
1489 | + printk(KERN_INFO "tp_smapi unloaded.\n"); | |
1490 | +} | |
1491 | + | |
1492 | +module_init(tp_init); | |
1493 | +module_exit(tp_exit); |
@@ -0,0 +1,24 @@ | ||
1 | +# Copyright 2011 The Android-x86 Open Source Project | |
2 | + | |
3 | +#ifeq ($(BOARD_USES_WACOMINPUT),true) | |
4 | + | |
5 | +LOCAL_PATH := $(call my-dir) | |
6 | +include $(CLEAR_VARS) | |
7 | + | |
8 | +LOCAL_SRC_FILES:= wactablet.c wacserial.c wacusb.c wacom-input.c | |
9 | + | |
10 | +LOCAL_C_INCLUDES := $(KERNEL_HEADERS) | |
11 | + | |
12 | +LOCAL_CFLAGS := -O2 -Wall -Wno-unused-parameter | |
13 | + | |
14 | +ifeq ($(TARGET_ARCH),x86) | |
15 | +LOCAL_CFLAGS += -Ulinux | |
16 | +endif | |
17 | + | |
18 | +LOCAL_MODULE := wacom-input | |
19 | +LOCAL_MODULE_TAGS := optional | |
20 | +#LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) | |
21 | + | |
22 | +include $(BUILD_EXECUTABLE) | |
23 | + | |
24 | +#endif |
@@ -0,0 +1,17 @@ | ||
1 | +CC=gcc | |
2 | +CFLAGS=-c -Wall -O2 | |
3 | +#LDFLAGS=-lncurses | |
4 | +SOURCES=wactablet.c wacserial.c wacusb.c wacom-input.c | |
5 | +OBJECTS=$(SOURCES:.c=.o) | |
6 | +EXECUTABLE=wacom-input | |
7 | + | |
8 | +all: $(SOURCES) $(EXECUTABLE) | |
9 | + | |
10 | +$(EXECUTABLE): $(OBJECTS) | |
11 | + $(CC) $(LDFLAGS) $(OBJECTS) -o $@ | |
12 | + | |
13 | +.c.o: | |
14 | + $(CC) $(CFLAGS) $< -o $@ | |
15 | + | |
16 | +clean: | |
17 | + rm $(OBJECTS) $(EXECUTABLE) |
@@ -0,0 +1,241 @@ | ||
1 | +/***************************************************************************** | |
2 | + ** wacom-input.c | |
3 | + ** | |
4 | + ** Copyright (C) 2011 Stefan Seidel | |
5 | + ** | |
6 | + ** This program is free software; you can redistribute it and/or | |
7 | + ** modify it under the terms of the GNU General Public License | |
8 | + ** as published by the Free Software Foundation; either version 2 | |
9 | + ** of the License, or (at your option) any later version. | |
10 | + ** | |
11 | + ** This program is distributed in the hope that it will be useful, | |
12 | + ** but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | + ** GNU General Public License for more details. | |
15 | + ** | |
16 | + ** You should have received a copy of the GNU General Public License | |
17 | + ** along with this program; if not, write to the Free Software | |
18 | + ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
19 | + ** | |
20 | + ** | |
21 | + ** Code inspired by wacdump.c from http://linuxwacom.sourceforge.net and | |
22 | + ** uniput-sample.c from http://thiemonge.org/getting-started-with-uinput | |
23 | + ** | |
24 | + ** | |
25 | + ** Version history: | |
26 | + ** 0.1 - 2011-03-29 - initial support for "tpc" device | |
27 | + ** 0.2 - 2011-04-13 - support command-line options for device type, node, pressure | |
28 | + ** | |
29 | + ****************************************************************************/ | |
30 | + | |
31 | +#include "wactablet.h" | |
32 | +#include "wacserial.h" | |
33 | +#include "wacusb.h" | |
34 | + | |
35 | +#include <ctype.h> | |
36 | +#include <time.h> | |
37 | +#include <stdio.h> | |
38 | +#include <stdlib.h> | |
39 | +#include <string.h> | |
40 | +#include <unistd.h> | |
41 | +#include <fcntl.h> | |
42 | +#include <errno.h> | |
43 | +#include <signal.h> | |
44 | +#include <linux/input.h> | |
45 | +#include <linux/uinput.h> | |
46 | + | |
47 | +#define die(str, args...) do { \ | |
48 | + perror(str, ## args); \ | |
49 | + exit(EXIT_FAILURE); \ | |
50 | + } while(0) | |
51 | + | |
52 | +int fd; | |
53 | +WACOMENGINE hEngine = NULL; | |
54 | +WACOMTABLET hTablet = NULL; | |
55 | + | |
56 | +void wacom_report_event(__u16 type, __u16 code, __s32 value) { | |
57 | + struct input_event ev; | |
58 | + if (value == -1) { | |
59 | + return; | |
60 | + } | |
61 | + memset(&ev, 0, sizeof(struct input_event)); | |
62 | + ev.type = type; | |
63 | + ev.code = code; | |
64 | + ev.value = value; | |
65 | + if (write(fd, &ev, sizeof(struct input_event)) < 0) | |
66 | + perror("error: write"); | |
67 | +} | |
68 | + | |
69 | +static void signal_handler(int signo) { | |
70 | + if (ioctl(fd, UI_DEV_DESTROY) < 0) { | |
71 | + die("error: cannot destroy uinput device\n"); | |
72 | + } | |
73 | + close(fd); | |
74 | + WacomCloseTablet(hTablet); | |
75 | + WacomTermEngine(hEngine); | |
76 | + exit(EXIT_SUCCESS); | |
77 | +} | |
78 | + | |
79 | +int main(int argc, char** argv) { | |
80 | + const char* arg; | |
81 | + const char* devName = "tpc"; | |
82 | + const char* devNode = "/dev/ttyS0"; | |
83 | + struct uinput_user_dev uidev; | |
84 | + WACOMSTATE state = WACOMSTATE_INIT; | |
85 | + WACOMMODEL model = { 0 }; | |
86 | + unsigned char uchBuf[64]; | |
87 | + int nLength = 0; | |
88 | + int minPress = 20; | |
89 | + | |
90 | + // parse arguments | |
91 | + while (*argv) { | |
92 | + arg = *(argv++); | |
93 | + | |
94 | + /* handle options */ | |
95 | + if (arg[0] == '-') { | |
96 | + /* device type */ | |
97 | + if (strcmp(arg, "-h") == 0) { | |
98 | + fprintf( | |
99 | + stderr, | |
100 | + "Usage: wacom-input [-t DeviceType] [-d DeviceNode] [-p PressureThreshold]\n\t-t defaults to \"tpc\"\n\t-d defaults to \"/dev/ttyS0\"\n\t-p defaults to 40\n"); | |
101 | + exit(0); | |
102 | + /* device type */ | |
103 | + } else if (strcmp(arg, "-t") == 0) { | |
104 | + arg = *(argv++); | |
105 | + if (arg == NULL || arg[0] == '-') { | |
106 | + die("Missing device type"); | |
107 | + } | |
108 | + devName = arg; | |
109 | + /* device node */ | |
110 | + } else if (strcmp(arg, "-d") == 0) { | |
111 | + arg = *(argv++); | |
112 | + if (arg == NULL || arg[0] == '-') { | |
113 | + die("Missing device node"); | |
114 | + } | |
115 | + devNode = arg; | |
116 | + /* pressure */ | |
117 | + } else if (strcmp(arg, "-p") == 0) { | |
118 | + arg = *(argv++); | |
119 | + if (arg == NULL || (minPress = atoi(arg)) == 0) { | |
120 | + die("Wrong pressure threshold"); | |
121 | + } | |
122 | + } | |
123 | + } | |
124 | + } | |
125 | + // end parse arguments | |
126 | + | |
127 | + if (signal(SIGINT, signal_handler) == SIG_ERR) { | |
128 | + die("error registering signal handler\n"); | |
129 | + } | |
130 | + | |
131 | + // connect to wacom device | |
132 | + hEngine = WacomInitEngine(); | |
133 | + if (!hEngine) { | |
134 | + close(fd); | |
135 | + die("failed to open tablet engine"); | |
136 | + } | |
137 | + | |
138 | + /* open tablet */ | |
139 | + model.uClass = WACOMCLASS_SERIAL; | |
140 | + model.uDevice = WacomGetDeviceFromName(devName, model.uClass); | |
141 | + hTablet = WacomOpenTablet(hEngine, devNode, &model); | |
142 | + if (!hTablet) { | |
143 | + close(fd); | |
144 | + WacomTermEngine(hEngine); | |
145 | + die ("WacomOpenTablet"); | |
146 | + } | |
147 | + WacomGetState(hTablet, &state); | |
148 | + // wacom device is set up properly | |
149 | + | |
150 | + // set up uinput | |
151 | + fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); | |
152 | + if (fd < 0) { | |
153 | + fd = open("/dev/input/uinput", O_WRONLY | O_NONBLOCK); | |
154 | + } | |
155 | + | |
156 | + if (fd < 0) | |
157 | + die("error: opening /dev/[input/]uinput failed"); | |
158 | + | |
159 | + // report that we have TOUCH events ... | |
160 | + if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) | |
161 | + die("error: ioctl"); | |
162 | + if (ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH) < 0) | |
163 | + die("error: ioctl"); | |
164 | + | |
165 | + // and absolute x, y, pressure data | |
166 | + if (ioctl(fd, UI_SET_EVBIT, EV_ABS) < 0) | |
167 | + die("error: ioctl"); | |
168 | + if (ioctl(fd, UI_SET_ABSBIT, ABS_X) < 0) | |
169 | + die("error: ioctl"); | |
170 | + if (ioctl(fd, UI_SET_ABSBIT, ABS_Y) < 0) | |
171 | + die("error: ioctl"); | |
172 | + if (ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE) < 0) | |
173 | + die("error: ioctl"); | |
174 | + | |
175 | + // this is for simulated mouse middle/right button | |
176 | + // if(ioctl(fd, UI_SET_KEYBIT, BTN_MOUSE) < 0) | |
177 | + // die("error: ioctl"); | |
178 | + // if(ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT) < 0) | |
179 | + // die("error: ioctl"); | |
180 | + // if(ioctl(fd, UI_SET_KEYBIT, BTN_LEFT) < 0) | |
181 | + // die("error: ioctl"); | |
182 | + | |
183 | + // register uinput device | |
184 | + memset(&uidev, 0, sizeof(uidev)); | |
185 | + uidev.absmin[ABS_X] = state.values[WACOMFIELD_POSITION_X].nMin; | |
186 | + uidev.absmax[ABS_X] = state.values[WACOMFIELD_POSITION_X].nMax; | |
187 | + uidev.absmin[ABS_Y] = state.values[WACOMFIELD_POSITION_Y].nMin; | |
188 | + uidev.absmax[ABS_Y] = state.values[WACOMFIELD_POSITION_Y].nMax; | |
189 | + uidev.absmin[ABS_PRESSURE] = state.values[WACOMFIELD_PRESSURE].nMin; | |
190 | + uidev.absmax[ABS_PRESSURE] = state.values[WACOMFIELD_PRESSURE].nMax; | |
191 | + // this could be more detailed, but in the end, who cares? | |
192 | + snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "wacom-input"); | |
193 | + uidev.id.bustype = BUS_RS232; | |
194 | + uidev.id.vendor = 0x056a; | |
195 | + uidev.id.product = 0xffff; | |
196 | + uidev.id.version = 1; | |
197 | + | |
198 | + if (write(fd, &uidev, sizeof(uidev)) < 0) | |
199 | + die("error: set virtual device info 1"); | |
200 | + | |
201 | + if (ioctl(fd, UI_DEV_CREATE) < 0) | |
202 | + die("error: create uinput device 1"); | |
203 | + // uinput is set up | |
204 | + | |
205 | + while (1) { | |
206 | + if ((nLength = WacomReadRaw(hTablet, uchBuf, sizeof(uchBuf))) < 0) { | |
207 | + continue; | |
208 | + } | |
209 | + if (WacomParseData(hTablet, uchBuf, nLength, &state)) { | |
210 | + continue; | |
211 | + } | |
212 | + if (!state.values[WACOMFIELD_PROXIMITY].nValue) { | |
213 | + // no tool in proximity | |
214 | + wacom_report_event(EV_ABS, ABS_PRESSURE, 0); | |
215 | + wacom_report_event(EV_KEY, BTN_TOUCH, 0); | |
216 | + // wacom_report_event(EV_KEY, BTN_RIGHT, 0); | |
217 | + // wacom_report_event(EV_KEY, BTN_MIDDLE, 0); | |
218 | + wacom_report_event(EV_SYN, SYN_REPORT, 0); | |
219 | + continue; | |
220 | + } | |
221 | + | |
222 | + wacom_report_event(EV_ABS, ABS_X, | |
223 | + state.values[WACOMFIELD_POSITION_X].nValue); | |
224 | + wacom_report_event(EV_ABS, ABS_Y, | |
225 | + state.values[WACOMFIELD_POSITION_Y].nValue); | |
226 | + wacom_report_event(EV_ABS, ABS_PRESSURE, | |
227 | + state.values[WACOMFIELD_PRESSURE].nValue); | |
228 | + wacom_report_event(EV_KEY, BTN_TOUCH, | |
229 | + state.values[WACOMFIELD_PRESSURE].nValue > minPress); | |
230 | + // wacom_report_event(EV_KEY, BTN_RIGHT, state.values[WACOMFIELD_BUTTONS].nValue == WACOMBUTTON_STYLUS); | |
231 | + // wacom_report_event(EV_KEY, BTN_MIDDLE, state.values[WACOMFIELD_TOOLTYPE].nValue == WACOMTOOLTYPE_ERASER); | |
232 | + wacom_report_event(EV_SYN, SYN_REPORT, 0); | |
233 | + } | |
234 | + | |
235 | + if (ioctl(fd, UI_DEV_DESTROY) < 0) | |
236 | + die("error: ioctl"); | |
237 | + | |
238 | + close(fd); | |
239 | + | |
240 | + return 0; | |
241 | +} |
@@ -0,0 +1,1711 @@ | ||
1 | +/***************************************************************************** | |
2 | +** wacserial.c | |
3 | +** | |
4 | +** Copyright (C) 2002, 2003 - John E. Joganic | |
5 | +** Copyright (C) 2002 - 2008 - Ping Cheng | |
6 | +** | |
7 | +** This program is free software; you can redistribute it and/or | |
8 | +** modify it under the terms of the GNU General Public License | |
9 | +** as published by the Free Software Foundation; either version 2 | |
10 | +** of the License, or (at your option) any later version. | |
11 | +** | |
12 | +** This program is distributed in the hope that it will be useful, | |
13 | +** but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | +** GNU General Public License for more details. | |
16 | +** | |
17 | +** You should have received a copy of the GNU General Public License | |
18 | +** along with this program; if not, write to the Free Software | |
19 | +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
20 | +** | |
21 | +****************************************************************************/ | |
22 | + | |
23 | +#include "wacserial.h" | |
24 | + | |
25 | +#include <stdio.h> | |
26 | +#include <stdlib.h> | |
27 | +#include <string.h> | |
28 | +#include <errno.h> | |
29 | +#include <termios.h> | |
30 | +#include <ctype.h> | |
31 | +#include <unistd.h> | |
32 | +#include <assert.h> | |
33 | + | |
34 | +/***************************************************************************** | |
35 | +** Serial Tablet Object | |
36 | +*****************************************************************************/ | |
37 | + | |
38 | +typedef struct _SERIALTABLET SERIALTABLET; | |
39 | +typedef struct _SERIALSUBTYPE SERIALSUBTYPE; | |
40 | +typedef struct _SERIALDEVICE SERIALDEVICE; | |
41 | +typedef struct _SERIALVENDOR SERIALVENDOR; | |
42 | + | |
43 | +typedef int (*IDENTFUNC)(SERIALTABLET* pSerial); | |
44 | +typedef int (*INITFUNC)(SERIALTABLET* pSerial); | |
45 | +typedef int (*PARSEFUNC)(SERIALTABLET* pSerial, const unsigned char* puchData, | |
46 | + unsigned int uLength, WACOMSTATE* pState); | |
47 | + | |
48 | +struct _SERIALTABLET | |
49 | +{ | |
50 | + WACOMTABLET_PRIV tablet; | |
51 | + WACOMENGINE hEngine; | |
52 | + int fd; | |
53 | + SERIALVENDOR* pVendor; | |
54 | + SERIALDEVICE* pDevice; | |
55 | + SERIALSUBTYPE* pSubType; | |
56 | + unsigned int uPacketLength; | |
57 | + int nVerMajor, nVerMinor, nVerRelease; | |
58 | + IDENTFUNC pfnIdent; | |
59 | + INITFUNC pfnInit; | |
60 | + PARSEFUNC pfnParse; | |
61 | + int nToolID; | |
62 | + WACOMSTATE state; | |
63 | + int nBitErrors; | |
64 | + WACOMMODEL modelRequested; | |
65 | +}; | |
66 | + | |
67 | +/***************************************************************************** | |
68 | +** Internal structures | |
69 | +*****************************************************************************/ | |
70 | + | |
71 | +struct _SERIALSUBTYPE | |
72 | +{ | |
73 | + const char* pszName; | |
74 | + const char* pszDesc; | |
75 | + unsigned int uSubType; | |
76 | + const char* pszIdent; | |
77 | + INITFUNC pfnInit; | |
78 | +}; | |
79 | + | |
80 | +struct _SERIALDEVICE | |
81 | +{ | |
82 | + const char* pszName; | |
83 | + const char* pszDesc; | |
84 | + unsigned int uDevice; | |
85 | + SERIALSUBTYPE* pSubTypes; | |
86 | + int nProtocol; | |
87 | + unsigned int uPacketLength; | |
88 | + unsigned int uCaps; | |
89 | + int nMinBaudRate; | |
90 | + IDENTFUNC pfnIdent; | |
91 | +}; | |
92 | + | |
93 | +struct _SERIALVENDOR | |
94 | +{ | |
95 | + const char* pszName; | |
96 | + const char* pszDesc; | |
97 | + unsigned int uVendor; | |
98 | + SERIALDEVICE* pDevices; | |
99 | +}; | |
100 | + | |
101 | +/***************************************************************************** | |
102 | +** Static operations | |
103 | +*****************************************************************************/ | |
104 | + | |
105 | +static void SerialClose(WACOMTABLET_PRIV* pTablet); | |
106 | +static WACOMMODEL SerialGetModel(WACOMTABLET_PRIV* pTablet); | |
107 | +static const char* SerialGetVendorName(WACOMTABLET_PRIV* pTablet); | |
108 | +static const char* SerialGetClassName(WACOMTABLET_PRIV* pTablet); | |
109 | +static const char* SerialGetDeviceName(WACOMTABLET_PRIV* pTablet); | |
110 | +static const char* SerialGetSubTypeName(WACOMTABLET_PRIV* pTablet); | |
111 | +static const char* SerialGetModelName(WACOMTABLET_PRIV* pTablet); | |
112 | +static int SerialGetROMVer(WACOMTABLET_PRIV* pTablet, int* pnMajor, | |
113 | + int* pnMinor, int* pnRelease); | |
114 | +static int SerialGetCaps(WACOMTABLET_PRIV* pTablet); | |
115 | +static int SerialGetState(WACOMTABLET_PRIV* pTablet, WACOMSTATE* pState); | |
116 | +static int SerialGetFD(WACOMTABLET_PRIV* pTablet); | |
117 | +static int SerialReadRaw(WACOMTABLET_PRIV* pTablet, unsigned char* puchData, | |
118 | + unsigned int uSize); | |
119 | +static int SerialParseData(WACOMTABLET_PRIV* pTablet, | |
120 | + const unsigned char* puchData, unsigned int uLength, | |
121 | + WACOMSTATE* pState); | |
122 | + | |
123 | +static int SerialReset(SERIALTABLET* pSerial, WACOMMODEL* pModel); | |
124 | +static int SerialConfigTTY(SERIALTABLET* pSerial); | |
125 | +static int SerialResetAtBaud(SERIALTABLET* pSerial, struct termios* pTIOS, | |
126 | + int nBaud); | |
127 | +static int SerialSetDevice(SERIALTABLET* pSerial, SERIALVENDOR* pVendor, | |
128 | + SERIALDEVICE* pDevice, SERIALSUBTYPE* pSubType); | |
129 | + | |
130 | +static int SerialIdentDefault(SERIALTABLET* pSerial); | |
131 | +static int SerialIdentTabletPC(SERIALTABLET* pSerial); | |
132 | +static int SerialInitTabletPC(SERIALTABLET* pSerial); | |
133 | +static int SerialIdentWacom(SERIALTABLET* pSerial); | |
134 | +static int SerialInitWacom(SERIALTABLET* pSerial); | |
135 | + | |
136 | +static int SerialParseWacomV(SERIALTABLET* pSerial, | |
137 | + const unsigned char* puchData, unsigned int uLength, | |
138 | + WACOMSTATE* pState); | |
139 | +static int SerialParseWacomIV_1_4(SERIALTABLET* pSerial, | |
140 | + const unsigned char* puchData, unsigned int uLength, | |
141 | + WACOMSTATE* pState); | |
142 | +static int SerialParseWacomIV_1_3(SERIALTABLET* pSerial, | |
143 | + const unsigned char* puchData, unsigned int uLength, | |
144 | + WACOMSTATE* pState); | |
145 | +static int SerialParseWacomIV_1_2(SERIALTABLET* pSerial, | |
146 | + const unsigned char* puchData, unsigned int uLength, | |
147 | + WACOMSTATE* pState); | |
148 | +static int SerialParseTabletPC(SERIALTABLET* pSerial, | |
149 | + const unsigned char* puchData, unsigned int uLength, | |
150 | + WACOMSTATE* pState); | |
151 | + | |
152 | +static void SerialError(SERIALTABLET* pSerial, const char* pszFmt, ...); | |
153 | +static void SerialWarn(SERIALTABLET* pSerial, const char* pszFmt, ...); | |
154 | +static void SerialInfo(SERIALTABLET* pSerial, const char* pszFmt, ...); | |
155 | +static void SerialDump(SERIALTABLET* pSerial, const void* pvData, int nCnt); | |
156 | +/* NOT USED, YET | |
157 | +static void SerialCritical(SERIALTABLET* pSerial, const char* pszFmt, ...); | |
158 | +static void SerialDebug(SERIALTABLET* pSerial, const char* pszFmt, ...); | |
159 | +static void SerialTrace(SERIALTABLET* pSerial, const char* pszFmt, ...); | |
160 | +*/ | |
161 | + | |
162 | +/***************************************************************************** | |
163 | +** Defines | |
164 | +*****************************************************************************/ | |
165 | + | |
166 | +#ifndef BIT | |
167 | +#undef BIT | |
168 | +#define BIT(x) (1<<(x)) | |
169 | +#endif | |
170 | + | |
171 | +#define WACOMVALID(x) BIT(WACOMFIELD_##x) | |
172 | + | |
173 | +#define ARTPAD_CAPS (WACOMVALID(TOOLTYPE)|WACOMVALID(PROXIMITY)| \ | |
174 | + WACOMVALID(BUTTONS)|WACOMVALID(POSITION_X)|WACOMVALID(POSITION_Y)| \ | |
175 | + WACOMVALID(PRESSURE)) | |
176 | + | |
177 | +#define ARTPADII_CAPS (WACOMVALID(TOOLTYPE)|WACOMVALID(PROXIMITY)| \ | |
178 | + WACOMVALID(BUTTONS)|WACOMVALID(POSITION_X)|WACOMVALID(POSITION_Y)| \ | |
179 | + WACOMVALID(PRESSURE)) | |
180 | + | |
181 | +#define DIGITIZER_CAPS (WACOMVALID(TOOLTYPE)|WACOMVALID(PROXIMITY)| \ | |
182 | + WACOMVALID(BUTTONS)|WACOMVALID(POSITION_X)|WACOMVALID(POSITION_Y)| \ | |
183 | + WACOMVALID(PRESSURE)|WACOMVALID(TILT_X)|WACOMVALID(TILT_Y)) | |
184 | + | |
185 | +#define DIGITIZERII_CAPS (WACOMVALID(TOOLTYPE)|WACOMVALID(PROXIMITY)| \ | |
186 | + WACOMVALID(BUTTONS)|WACOMVALID(POSITION_X)|WACOMVALID(POSITION_Y)| \ | |
187 | + WACOMVALID(PRESSURE)|WACOMVALID(TILT_X)|WACOMVALID(TILT_Y)) | |
188 | + | |
189 | +#define PENPARTNER_CAPS (WACOMVALID(TOOLTYPE)|WACOMVALID(PROXIMITY)| \ | |
190 | + WACOMVALID(BUTTONS)|WACOMVALID(POSITION_X)|WACOMVALID(POSITION_Y)| \ | |
191 | + WACOMVALID(PRESSURE)) | |
192 | + | |
193 | +#define GRAPHIRE_CAPS (WACOMVALID(TOOLTYPE)|WACOMVALID(PROXIMITY)| \ | |
194 | + WACOMVALID(BUTTONS)|WACOMVALID(POSITION_X)|WACOMVALID(POSITION_Y)| \ | |
195 | + WACOMVALID(PRESSURE)|WACOMVALID(TILT_X)|WACOMVALID(TILT_Y)) | |
196 | + | |
197 | +#define CINTIQ_CAPS (WACOMVALID(TOOLTYPE)|WACOMVALID(PROXIMITY)| \ | |
198 | + WACOMVALID(BUTTONS)|WACOMVALID(POSITION_X)|WACOMVALID(POSITION_Y)| \ | |
199 | + WACOMVALID(PRESSURE)|WACOMVALID(TILT_X)|WACOMVALID(TILT_Y)) | |
200 | + | |
201 | +#define INTUOS_CAPS (WACOMVALID(TOOLTYPE)|WACOMVALID(SERIAL)| \ | |
202 | + WACOMVALID(PROXIMITY)|WACOMVALID(BUTTONS)|WACOMVALID(POSITION_X)| \ | |
203 | + WACOMVALID(POSITION_Y)|WACOMVALID(ROTATION_Z)|WACOMVALID(DISTANCE)| \ | |
204 | + WACOMVALID(PRESSURE)|WACOMVALID(TILT_X)|WACOMVALID(TILT_Y)| \ | |
205 | + WACOMVALID(ABSWHEEL)|WACOMVALID(RELWHEEL)|WACOMVALID(THROTTLE)) | |
206 | + | |
207 | +#define TABLETPC_CAPS (WACOMVALID(TOOLTYPE)|WACOMVALID(SERIAL)| \ | |
208 | + WACOMVALID(PROXIMITY)|WACOMVALID(BUTTONS)|WACOMVALID(POSITION_X)| \ | |
209 | + WACOMVALID(POSITION_Y)|WACOMVALID(PRESSURE)) | |
210 | + | |
211 | +#define INTUOS2_CAPS INTUOS_CAPS | |
212 | + | |
213 | +#define PROTOCOL_4 4 | |
214 | +#define PROTOCOL_5 5 | |
215 | + | |
216 | +#define WACOM_SUBTYPE(id,d,s) \ | |
217 | + { id, d, s, id, SerialInitWacom } | |
218 | +#define TPC_SUBTYPE(id,d,s) \ | |
219 | + { id, d, s, id, SerialInitTabletPC } | |
220 | + | |
221 | +#define WACOM_DEVICE_P4(n,d,i,s,c) \ | |
222 | + { n, d, i, s, PROTOCOL_4, 7, c, 9600, SerialIdentWacom } | |
223 | +#define WACOM_DEVICE_P5(n,d,i,s,c) \ | |
224 | + { n, d, i, s, PROTOCOL_5, 9, c, 9600, SerialIdentWacom } | |
225 | +#define TPC_DEVICE(n,d,i,s,c) \ | |
226 | + { n, d, i, s, 0, 9, c, 19200, SerialIdentTabletPC } | |
227 | + | |
228 | +/***************************************************************************** | |
229 | +** Globals | |
230 | +*****************************************************************************/ | |
231 | + | |
232 | + static SERIALSUBTYPE xArtPadII[] = | |
233 | + { | |
234 | + WACOM_SUBTYPE("KT-0405-R", "Wacom ArtPadII 4x5", 1), | |
235 | + { NULL } | |
236 | + }; | |
237 | + | |
238 | + static SERIALSUBTYPE xDigitizerII[] = | |
239 | + { | |
240 | + WACOM_SUBTYPE("UD-0608-R", "Wacom DigitizerII 6x8", 1), | |
241 | + WACOM_SUBTYPE("UD-1212-R", "Wacom DigitizerII 12x12", 2), | |
242 | + WACOM_SUBTYPE("UD-1218-R", "Wacom DigitizerII 12x18", 3), | |
243 | + WACOM_SUBTYPE("UD-1825-R", "Wacom DigitizerII 18x25", 4), | |
244 | + { NULL } | |
245 | + }; | |
246 | + | |
247 | + static SERIALSUBTYPE xPenPartner[] = | |
248 | + { | |
249 | + WACOM_SUBTYPE("CT-0405-R", "Wacom PenPartner", 1), | |
250 | + { NULL } | |
251 | + }; | |
252 | + | |
253 | + static SERIALSUBTYPE xGraphire[] = | |
254 | + { | |
255 | + WACOM_SUBTYPE("ET-0405-R", "Wacom Graphire", 1), | |
256 | + { NULL } | |
257 | + }; | |
258 | + | |
259 | + static SERIALSUBTYPE xIntuos[] = | |
260 | + { | |
261 | + WACOM_SUBTYPE("GD-0405-R", "Wacom Intuos 4x5", 1), | |
262 | + WACOM_SUBTYPE("GD-0608-R", "Wacom Intuos 6x8", 2), | |
263 | + WACOM_SUBTYPE("GD-0912-R", "Wacom Intuos 9x12", 3), | |
264 | + WACOM_SUBTYPE("GD-1212-R", "Wacom Intuos 12x12", 4), | |
265 | + WACOM_SUBTYPE("GD-1218-R", "Wacom Intuos 12x18", 5), | |
266 | + { NULL } | |
267 | + }; | |
268 | + | |
269 | + static SERIALSUBTYPE xIntuos2[] = | |
270 | + { | |
271 | + WACOM_SUBTYPE("XD-0405-R", "Wacom Intuos2 4x5", 1), | |
272 | + WACOM_SUBTYPE("XD-0608-R", "Wacom Intuos2 6x8", 2), | |
273 | + WACOM_SUBTYPE("XD-0912-R", "Wacom Intuos2 9x12", 3), | |
274 | + WACOM_SUBTYPE("XD-1212-R", "Wacom Intuos2 12x12", 4), | |
275 | + WACOM_SUBTYPE("XD-1218-R", "Wacom Intuos2 12x18", 5), | |
276 | + { NULL } | |
277 | + }; | |
278 | + | |
279 | + static SERIALSUBTYPE xCintiq[] = | |
280 | + { | |
281 | + WACOM_SUBTYPE("PL-250", "Wacom PL-250", 1), | |
282 | + WACOM_SUBTYPE("PL-270", "Wacom PL-270", 2), | |
283 | + WACOM_SUBTYPE("PL-400", "Wacom PL-400", 3), | |
284 | + WACOM_SUBTYPE("PL-500", "Wacom PL-500", 4), | |
285 | + WACOM_SUBTYPE("PL-550", "Wacom PL-550", 5), | |
286 | + WACOM_SUBTYPE("PL-600", "Wacom PL-600", 6), | |
287 | + WACOM_SUBTYPE("PL-600SX", "Wacom PL-600SX", 7), | |
288 | + WACOM_SUBTYPE("PL-800", "Wacom PL-800", 8), | |
289 | + { NULL } | |
290 | + }; | |
291 | + | |
292 | + static SERIALDEVICE xWacomDevices[] = | |
293 | + { | |
294 | + WACOM_DEVICE_P4("art", "ArtPad", WACOMDEVICE_ARTPAD, | |
295 | + NULL, ARTPAD_CAPS), | |
296 | + WACOM_DEVICE_P4("art2", "ArtPadII", WACOMDEVICE_ARTPADII, | |
297 | + xArtPadII, ARTPADII_CAPS), | |
298 | + WACOM_DEVICE_P4("dig", "Digitizer", WACOMDEVICE_DIGITIZER, | |
299 | + NULL, DIGITIZERII_CAPS), | |
300 | + WACOM_DEVICE_P4("dig2", "Digitizer II", WACOMDEVICE_DIGITIZERII, | |
301 | + xDigitizerII, DIGITIZERII_CAPS), | |
302 | + WACOM_DEVICE_P4("pp", "PenPartner", WACOMDEVICE_PENPARTNER, | |
303 | + xPenPartner, PENPARTNER_CAPS), | |
304 | + WACOM_DEVICE_P4("gr", "Graphire", WACOMDEVICE_GRAPHIRE, | |
305 | + xGraphire, GRAPHIRE_CAPS), | |
306 | + WACOM_DEVICE_P4("pl", "Cintiq (PL)", WACOMDEVICE_CINTIQ, | |
307 | + xCintiq, CINTIQ_CAPS), | |
308 | + WACOM_DEVICE_P5("int", "Intuos", WACOMDEVICE_INTUOS, | |
309 | + xIntuos, INTUOS_CAPS), | |
310 | + WACOM_DEVICE_P5("int2", "Intuos2", WACOMDEVICE_INTUOS2, | |
311 | + xIntuos2, INTUOS2_CAPS), | |
312 | + { NULL } | |
313 | + }; | |
314 | + | |
315 | + /* This one is reverse engineered at this point */ | |
316 | + static SERIALSUBTYPE xTabletPC[] = | |
317 | + { | |
318 | + TPC_SUBTYPE("tpc", "Tablet PC Screen", 1), | |
319 | + { NULL } | |
320 | + }; | |
321 | + | |
322 | + static SERIALDEVICE xtpcDevices[] = | |
323 | + { | |
324 | + TPC_DEVICE("tpc", "TabletPC", WACOMDEVICE_TPC, | |
325 | + xTabletPC, TABLETPC_CAPS), | |
326 | + { NULL } | |
327 | + }; | |
328 | + | |
329 | + static SERIALVENDOR xWacomVendor = | |
330 | + { "wacom", "Wacom", WACOMVENDOR_WACOM, xWacomDevices }; | |
331 | + | |
332 | + static SERIALVENDOR xtpcVendor = | |
333 | + { "Wacom", "Wacom", WACOMVENDOR_TPC, xtpcDevices }; | |
334 | + | |
335 | + static SERIALVENDOR* xVendors[] = | |
336 | + { | |
337 | + &xWacomVendor, | |
338 | + &xtpcVendor, | |
339 | + NULL | |
340 | + }; | |
341 | + | |
342 | +/***************************************************************************** | |
343 | +** Static Prototypes | |
344 | +*****************************************************************************/ | |
345 | + | |
346 | +static int SerialSendReset(SERIALTABLET* pSerial); | |
347 | +static int SerialSendStop(SERIALTABLET* pSerial); | |
348 | +static int SerialSendStart(SERIALTABLET* pSerial); | |
349 | + | |
350 | +static int SerialSend(SERIALTABLET* pSerial, const char* pszData); | |
351 | +static int SerialSendRaw(SERIALTABLET* pSerial, const void* pvData, | |
352 | + unsigned int uSize); | |
353 | +static int WacomFlush(SERIALTABLET* pSerial); | |
354 | +static int SerialSendRequest(SERIALTABLET* pSerial, const char* pszRequest, | |
355 | + char* pchResponse, unsigned int uSize); | |
356 | + | |
357 | +/***************************************************************************** | |
358 | +** Public Functions | |
359 | +*****************************************************************************/ | |
360 | + | |
361 | +typedef struct | |
362 | +{ | |
363 | + void (*pfnFree)(void* pv); | |
364 | +} DEVLIST_INTERNAL; | |
365 | + | |
366 | +static void SerialFreeDeviceList(void* pv) | |
367 | +{ | |
368 | + DEVLIST_INTERNAL* pInt = ((DEVLIST_INTERNAL*)pv) - 1; | |
369 | + free(pInt); | |
370 | +} | |
371 | + | |
372 | +int WacomGetSupportedSerialDeviceList(WACOMDEVICEREC** ppList, int* pnSize) | |
373 | +{ | |
374 | + int nIndex=0, nCnt=0; | |
375 | + DEVLIST_INTERNAL* pInt; | |
376 | + SERIALDEVICE* pDev; | |
377 | + SERIALVENDOR** ppVendor; | |
378 | + WACOMDEVICEREC* pRec; | |
379 | + | |
380 | + if (!ppList || !pnSize) { errno = EINVAL; return 1; } | |
381 | + | |
382 | + /* for each vendor, count up devices */ | |
383 | + for (ppVendor=xVendors; *ppVendor; ++ppVendor) | |
384 | + { | |
385 | + /* count up devices */ | |
386 | + for (pDev=(*ppVendor)->pDevices; pDev->pszName; ++pDev, ++nCnt) ; | |
387 | + } | |
388 | + | |
389 | + /* allocate enough memory to hold internal structure and all records */ | |
390 | + pInt = (DEVLIST_INTERNAL*)malloc(sizeof(DEVLIST_INTERNAL) + | |
391 | + (sizeof(WACOMDEVICEREC) * nCnt)); | |
392 | + | |
393 | + pInt->pfnFree = SerialFreeDeviceList; | |
394 | + pRec = (WACOMDEVICEREC*)(pInt + 1); | |
395 | + | |
396 | + /* for each vendor, add devices */ | |
397 | + for (ppVendor=xVendors; *ppVendor; ++ppVendor) | |
398 | + { | |
399 | + for (pDev=(*ppVendor)->pDevices; pDev->pszName; ++pDev, ++nIndex) | |
400 | + { | |
401 | + pRec[nIndex].pszName = pDev->pszName; | |
402 | + pRec[nIndex].pszDesc = pDev->pszDesc; | |
403 | + pRec[nIndex].pszVendorName = (*ppVendor)->pszName; | |
404 | + pRec[nIndex].pszVendorDesc = (*ppVendor)->pszDesc; | |
405 | + pRec[nIndex].pszClass = "serial"; | |
406 | + pRec[nIndex].model.uClass = WACOMCLASS_SERIAL; | |
407 | + pRec[nIndex].model.uVendor = (*ppVendor)->uVendor; | |
408 | + pRec[nIndex].model.uDevice = pDev->uDevice; | |
409 | + pRec[nIndex].model.uSubType = 0; | |
410 | + } | |
411 | + } | |
412 | + assert(nIndex == nCnt); | |
413 | + | |
414 | + *ppList = pRec; | |
415 | + *pnSize = nCnt; | |
416 | + return 0; | |
417 | +} | |
418 | + | |
419 | +unsigned int WacomGetSerialDeviceFromName(const char* pszName) | |
420 | +{ | |
421 | + SERIALDEVICE* pDev; | |
422 | + SERIALVENDOR** ppVendor; | |
423 | + | |
424 | + if (!pszName) { errno = EINVAL; return 0; } | |
425 | + | |
426 | + /* for each vendor, look for device */ | |
427 | + for (ppVendor=xVendors; *ppVendor; ++ppVendor) | |
428 | + { | |
429 | + /* count up devices */ | |
430 | + for (pDev=(*ppVendor)->pDevices; pDev->pszName; ++pDev) | |
431 | + { | |
432 | + if (strcasecmp(pszName,pDev->pszName) == 0) | |
433 | + return pDev->uDevice; | |
434 | + } | |
435 | + } | |
436 | + | |
437 | + errno = ENOENT; | |
438 | + return 0; | |
439 | +} | |
440 | + | |
441 | +static int SerialFindModel(WACOMMODEL* pModel, SERIALVENDOR** ppVendor, | |
442 | + SERIALDEVICE** ppDevice, SERIALSUBTYPE** ppSubType) | |
443 | +{ | |
444 | + SERIALVENDOR** ppPos; | |
445 | + SERIALDEVICE* pDev; | |
446 | + SERIALSUBTYPE* pSub; | |
447 | + | |
448 | + /* device type must be specified */ | |
449 | + if (!pModel) | |
450 | + { errno = EINVAL; return 1; } | |
451 | + | |
452 | + /* no device specified, nothing found. */ | |
453 | + if (!pModel->uDevice) | |
454 | + { | |
455 | + *ppVendor = NULL; | |
456 | + *ppDevice = NULL; | |
457 | + *ppSubType = NULL; | |
458 | + return 0; | |
459 | + } | |
460 | + | |
461 | + /* for each vendor */ | |
462 | + for (ppPos=xVendors; *ppPos; ++ppPos) | |
463 | + { | |
464 | + /* check vendor */ | |
465 | + if (!pModel->uVendor || (pModel->uVendor == (*ppPos)->uVendor)) | |
466 | + { | |
467 | + /* for each device */ | |
468 | + for (pDev=(*ppPos)->pDevices; pDev->pszName; ++pDev) | |
469 | + { | |
470 | + /* if device matches */ | |
471 | + if (pModel->uDevice == pDev->uDevice) | |
472 | + { | |
473 | + /* no subtype specified, use it */ | |
474 | + if (!pModel->uSubType) | |
475 | + { | |
476 | + *ppVendor = *ppPos; | |
477 | + *ppDevice = pDev; | |
478 | + *ppSubType = NULL; | |
479 | + return 0; | |
480 | + } | |
481 | + | |
482 | + /* for each subtype */ | |
483 | + for (pSub=pDev->pSubTypes; pSub->pszName; ++pSub) | |
484 | + { | |
485 | + /* if subtype matches */ | |
486 | + if (pModel->uSubType == pSub->uSubType) | |
487 | + { | |
488 | + *ppVendor = *ppPos; | |
489 | + *ppDevice = pDev; | |
490 | + *ppSubType = pSub; | |
491 | + return 0; | |
492 | + } | |
493 | + } | |
494 | + | |
495 | + /* wrong subtype? maybe try another vendor */ | |
496 | + if (!pModel->uVendor) break; | |
497 | + | |
498 | + /* otherwise, no match. */ | |
499 | + errno = ENOENT; | |
500 | + return 1; | |
501 | + } | |
502 | + } /* next device */ | |
503 | + | |
504 | + /* if vendor matches, but device does not, no match. */ | |
505 | + if (pModel->uVendor) | |
506 | + { | |
507 | + errno = ENOENT; | |
508 | + return 1; | |
509 | + } | |
510 | + } | |
511 | + } /* next vendor */ | |
512 | + | |
513 | + /* no match */ | |
514 | + errno = ENOENT; | |
515 | + return 1; | |
516 | +} | |
517 | + | |
518 | +WACOMTABLET WacomOpenSerialTablet(WACOMENGINE hEngine, int fd, | |
519 | + WACOMMODEL* pModel) | |
520 | +{ | |
521 | + SERIALTABLET* pSerial = NULL; | |
522 | + | |
523 | + /* Allocate tablet */ | |
524 | + pSerial = (SERIALTABLET*)malloc(sizeof(SERIALTABLET)); | |
525 | + memset(pSerial,0,sizeof(*pSerial)); | |
526 | + pSerial->tablet.Close = SerialClose; | |
527 | + pSerial->tablet.GetModel = SerialGetModel; | |
528 | + pSerial->tablet.GetVendorName = SerialGetVendorName; | |
529 | + pSerial->tablet.GetClassName = SerialGetClassName; | |
530 | + pSerial->tablet.GetDeviceName = SerialGetDeviceName; | |
531 | + pSerial->tablet.GetSubTypeName = SerialGetSubTypeName; | |
532 | + pSerial->tablet.GetModelName = SerialGetModelName; | |
533 | + pSerial->tablet.GetROMVer = SerialGetROMVer; | |
534 | + pSerial->tablet.GetCaps = SerialGetCaps; | |
535 | + pSerial->tablet.GetState = SerialGetState; | |
536 | + pSerial->tablet.GetFD = SerialGetFD; | |
537 | + pSerial->tablet.ReadRaw = SerialReadRaw; | |
538 | + pSerial->tablet.ParseData = SerialParseData; | |
539 | + | |
540 | + pSerial->hEngine = hEngine; | |
541 | + pSerial->fd = fd; | |
542 | + pSerial->state.uValueCnt = WACOMFIELD_MAX; | |
543 | + | |
544 | + /* remember what model was request */ | |
545 | + if (pModel) | |
546 | + pSerial->modelRequested = *pModel; | |
547 | + | |
548 | + if (SerialReset(pSerial,pModel)) | |
549 | + { | |
550 | + free(pSerial); | |
551 | + return NULL; | |
552 | + } | |
553 | + | |
554 | + return (WACOMTABLET)pSerial; | |
555 | +} | |
556 | + | |
557 | +static int SerialReset(SERIALTABLET* pSerial, WACOMMODEL* pModel) | |
558 | +{ | |
559 | + SERIALVENDOR* pVendor = NULL; | |
560 | + SERIALDEVICE* pDevice = NULL; | |
561 | + SERIALSUBTYPE* pSubType = NULL; | |
562 | + | |
563 | + /* If model is specified, break it down into vendor, device, and subtype */ | |
564 | + if (pModel && SerialFindModel(pModel,&pVendor,&pDevice,&pSubType)) | |
565 | + return 1; | |
566 | + | |
567 | + /* Set the tablet device */ | |
568 | + if (SerialSetDevice(pSerial,pVendor,pDevice,pSubType)) | |
569 | + return 1; | |
570 | + | |
571 | + /* configure the TTY for initial operation */ | |
572 | + if (SerialConfigTTY(pSerial)) | |
573 | + return 1; | |
574 | + | |
575 | + /* Identify the tablet */ | |
576 | + if (!pSerial->pfnIdent || pSerial->pfnIdent(pSerial)) | |
577 | + return 1; | |
578 | + | |
579 | + /* Initialize the tablet */ | |
580 | + if (!pSerial->pfnInit || pSerial->pfnInit(pSerial)) | |
581 | + return 1; | |
582 | + | |
583 | + /* Send start */ | |
584 | + SerialSendStart(pSerial); | |
585 | + | |
586 | + return 0; | |
587 | +} | |
588 | + | |
589 | +/***************************************************************************** | |
590 | +** Serial Tablet Functions | |
591 | +*****************************************************************************/ | |
592 | + | |
593 | +static int SerialConfigTTY(SERIALTABLET* pSerial) | |
594 | +{ | |
595 | + struct termios tios; | |
596 | + int nBaudRate = 9600; | |
597 | + | |
598 | + /* configure tty */ | |
599 | + if (isatty(pSerial->fd)) | |
600 | + { | |
601 | + /* set up default port parameters */ | |
602 | + if (tcgetattr (pSerial->fd, &tios)) | |
603 | + { | |
604 | + SerialError(pSerial,"Failed to get port params: %s", | |
605 | + strerror(errno)); | |
606 | + return 1; | |
607 | + } | |
608 | + | |
609 | + tios.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); | |
610 | + tios.c_oflag &= ~OPOST; | |
611 | + tios.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); | |
612 | + tios.c_cflag &= ~(CSIZE|PARENB); | |
613 | + tios.c_cflag |= CS8|CLOCAL; | |
614 | + tios.c_cflag &= ~(CSTOPB); /* 1 stop bit */ | |
615 | + tios.c_cflag &= ~(CSIZE); /* 8 data bits */ | |
616 | + tios.c_cflag |= CS8; | |
617 | + tios.c_cflag &= ~(PARENB); /* no parity */ | |
618 | + tios.c_iflag |= IXOFF; /* flow control XOff */ | |
619 | + tios.c_cc[VMIN] = 1; /* vmin value */ | |
620 | + tios.c_cc[VTIME] = 0; /* vtime value */ | |
621 | + | |
622 | + if (tcsetattr (pSerial->fd, TCSANOW, &tios)) | |
623 | + { | |
624 | + SerialError(pSerial,"Failed to set port params: %s", | |
625 | + strerror(errno)); | |
626 | + return 1; | |
627 | + } | |
628 | + | |
629 | + /* get minumum baud rate for given device, if specified */ | |
630 | + if (pSerial->pDevice) | |
631 | + nBaudRate = pSerial->pDevice->nMinBaudRate; | |
632 | + | |
633 | + /* set 38400 baud and reset */ | |
634 | + if (SerialResetAtBaud(pSerial,&tios,38400)) | |
635 | + return 1; | |
636 | + | |
637 | + /* if valid, set 19200 baud and reset */ | |
638 | + if ((nBaudRate <= 19200) && (SerialResetAtBaud(pSerial,&tios,19200))) | |
639 | + return 1; | |
640 | + | |
641 | + /* if valid, set 9600 baud and reset */ | |
642 | + if ((nBaudRate <= 9600) && (SerialResetAtBaud(pSerial,&tios,9600))) | |
643 | + return 1; | |
644 | + | |
645 | + /* lower than 9600 baud? for testing, maybe */ | |
646 | + if ((nBaudRate < 9600) && (SerialResetAtBaud(pSerial,&tios,nBaudRate))) | |
647 | + return 1; | |
648 | + } | |
649 | + else /* not tty */ | |
650 | + { | |
651 | + if (SerialSendReset(pSerial)) return 1; | |
652 | + } | |
653 | + | |
654 | + /* Send stop */ | |
655 | + if (SerialSendStop(pSerial) || WacomFlush(pSerial)) | |
656 | + return 1; | |
657 | + | |
658 | + return 0; | |
659 | +} | |
660 | + | |
661 | + | |
662 | +static int SerialSetDevice(SERIALTABLET* pSerial, SERIALVENDOR* pVendor, | |
663 | + SERIALDEVICE* pDevice, SERIALSUBTYPE* pSubType) | |
664 | +{ | |
665 | + pSerial->pVendor = pVendor; | |
666 | + pSerial->pDevice = pDevice; | |
667 | + pSerial->pSubType = pSubType; | |
668 | + | |
669 | + /* if we know the device, use its functions */ | |
670 | + if (pSerial->pDevice) | |
671 | + { | |
672 | + pSerial->pfnIdent = pSerial->pDevice->pfnIdent; | |
673 | + if (!pSerial->pfnIdent) { errno = EPERM; return 1; } | |
674 | + } | |
675 | + else | |
676 | + pSerial->pfnIdent = SerialIdentDefault; | |
677 | + | |
678 | + return 0; | |
679 | +} | |
680 | + | |
681 | +static int SerialIdentDefault(SERIALTABLET* pSerial) | |
682 | +{ | |
683 | + return SerialIdentWacom(pSerial); | |
684 | +} | |
685 | + | |
686 | +static int SerialIdentWacom(SERIALTABLET* pSerial) | |
687 | +{ | |
688 | + char* pszPos; | |
689 | + SERIALVENDOR* pVendor = &xWacomVendor; | |
690 | + SERIALDEVICE* pDev; | |
691 | + SERIALSUBTYPE* pSub; | |
692 | + char chResp[64]; | |
693 | + | |
694 | + /* send wacom identification request */ | |
695 | + if (SerialSendRequest(pSerial,"~#\r",chResp,sizeof(chResp))) | |
696 | + { | |
697 | + if (errno != ETIMEDOUT) return 1; | |
698 | + | |
699 | + /* try again, sometimes the first one gets garbled */ | |
700 | + if (SerialSendRequest(pSerial,"~#\r",chResp,sizeof(chResp))) | |
701 | + return 1; | |
702 | + } | |
703 | + | |
704 | + /* look through device table for information */ | |
705 | + for (pDev=pVendor->pDevices; pDev->pszName; ++pDev) | |
706 | + { | |
707 | + for (pSub=pDev->pSubTypes; pSub && pSub->pszName; ++pSub) | |
708 | + { | |
709 | + if (strncmp(chResp,pSub->pszIdent, strlen(pSub->pszIdent)) == 0) | |
710 | + { | |
711 | + pSerial->pVendor = pVendor; | |
712 | + pSerial->pDevice = pDev; | |
713 | + pSerial->pSubType = pSub; | |
714 | + pSerial->state.uValid = pDev->uCaps; | |
715 | + pSerial->uPacketLength = pDev->uPacketLength; | |
716 | + pSerial->pfnInit = pSub->pfnInit ? | |
717 | + pSub->pfnInit : SerialInitWacom; | |
718 | + | |
719 | + /* get version number */ | |
720 | + pszPos = chResp; | |
721 | + while (*pszPos) ++pszPos; | |
722 | + while ((pszPos > chResp) && (pszPos[-1] != 'V')) --pszPos; | |
723 | + if (sscanf(pszPos,"%d.%d-%d",&pSerial->nVerMajor, | |
724 | + &pSerial->nVerMinor,&pSerial->nVerRelease) != 3) | |
725 | + { | |
726 | + pSerial->nVerRelease = 0; | |
727 | + if (sscanf(pszPos,"%d.%d",&pSerial->nVerMajor, | |
728 | + &pSerial->nVerMinor) != 2) | |
729 | + { | |
730 | + errno = EINVAL; | |
731 | + SerialError(pSerial,"bad version number: %s",pszPos); | |
732 | + return 1; | |
733 | + } | |
734 | + } | |
735 | + return 0; | |
736 | + } | |
737 | + } | |
738 | + } | |
739 | + | |
740 | + SerialError(pSerial,"UNIDENTIFIED TABLET: %s",chResp); | |
741 | + return 1; | |
742 | +} | |
743 | + | |
744 | +static int SerialInitWacom(SERIALTABLET* pSerial) | |
745 | +{ | |
746 | + char chResp[32]; | |
747 | + | |
748 | + /* Request tablet dimensions */ | |
749 | + if (SerialSendRequest(pSerial,"~C\r",chResp,sizeof(chResp))) | |
750 | + return 1; | |
751 | + | |
752 | + /* parse position range */ | |
753 | + if (sscanf(chResp,"%d,%d", | |
754 | + &pSerial->state.values[WACOMFIELD_POSITION_X].nMax, | |
755 | + &pSerial->state.values[WACOMFIELD_POSITION_Y].nMax) != 2) | |
756 | + { | |
757 | + errno=EINVAL; | |
758 | + SerialError(pSerial,"Bad dimension response [%s]",chResp); | |
759 | + return 1; | |
760 | + } | |
761 | + | |
762 | + /* tablet specific initialization */ | |
763 | + switch (pSerial->pDevice->uDevice) | |
764 | + { | |
765 | + case WACOMDEVICE_PENPARTNER: | |
766 | + /* pressure mode */ | |
767 | + SerialSend(pSerial, "PH1\r"); | |
768 | + break; | |
769 | + | |
770 | + case WACOMDEVICE_INTUOS: | |
771 | + case WACOMDEVICE_INTUOS2: | |
772 | + /* multi-mode, max-rate */ | |
773 | + SerialSend(pSerial, "MT1\rID1\rIT0\r"); | |
774 | + } | |
775 | + | |
776 | + if (pSerial->pDevice->nProtocol == PROTOCOL_4) | |
777 | + { | |
778 | + /* multi-mode (MU), upper-origin (OC), all-macro (M0), | |
779 | + * no-macro1 (M1), max-rate (IT), no-inc (IN), | |
780 | + * stream-mode (SR), Z-filter (ZF) */ | |
781 | + | |
782 | +/* if (SerialSend(pSerial->fd, "MU1\rOC1\r~M0\r~M1\rIT0\rIN0\rSR\rZF1\r")) | |
783 | + return 1; | |
784 | + */ | |
785 | + | |
786 | + if (pSerial->nVerMajor == 1) | |
787 | + { | |
788 | + if (pSerial->nVerMinor >= 4) | |
789 | + { | |
790 | + /* enable tilt mode */ | |
791 | + if (SerialSend(pSerial,"FM1\r")) return 1; | |
792 | + | |
793 | + pSerial->pfnParse = SerialParseWacomIV_1_4; | |
794 | + pSerial->uPacketLength = 9; | |
795 | + pSerial->state.values[WACOMFIELD_PRESSURE].nMax = 255; | |
796 | + pSerial->state.values[WACOMFIELD_TILT_X].nMin = -64; | |
797 | + pSerial->state.values[WACOMFIELD_TILT_X].nMax = 63; | |
798 | + pSerial->state.values[WACOMFIELD_TILT_Y].nMin = -64; | |
799 | + pSerial->state.values[WACOMFIELD_TILT_Y].nMax = 63; | |
800 | + } | |
801 | + else if (pSerial->nVerMinor == 3) | |
802 | + { | |
803 | + pSerial->pfnParse = SerialParseWacomIV_1_3; | |
804 | + pSerial->state.values[WACOMFIELD_PRESSURE].nMax = 255; | |
805 | + } | |
806 | + else if (pSerial->nVerMinor == 2) | |
807 | + { | |
808 | + pSerial->pfnParse = SerialParseWacomIV_1_2; | |
809 | + pSerial->state.values[WACOMFIELD_PRESSURE].nMax = 255; | |
810 | + } | |
811 | + else if (pSerial->nVerMinor < 2) | |
812 | + { | |
813 | + pSerial->pfnParse = SerialParseWacomIV_1_2; | |
814 | + pSerial->state.values[WACOMFIELD_PRESSURE].nMax = 120; | |
815 | + } | |
816 | + } | |
817 | + } | |
818 | + else if (pSerial->pDevice->nProtocol == PROTOCOL_5) | |
819 | + { | |
820 | + pSerial->pfnParse = SerialParseWacomV; | |
821 | + pSerial->state.values[WACOMFIELD_PRESSURE].nMax = 1023; | |
822 | + pSerial->state.values[WACOMFIELD_ABSWHEEL].nMax = 1023; | |
823 | + pSerial->state.values[WACOMFIELD_ROTATION_Z].nMin = -900; | |
824 | + pSerial->state.values[WACOMFIELD_ROTATION_Z].nMax = 899; | |
825 | + pSerial->state.values[WACOMFIELD_THROTTLE].nMin = -1023; | |
826 | + pSerial->state.values[WACOMFIELD_THROTTLE].nMax = 1023; | |
827 | + pSerial->state.values[WACOMFIELD_TILT_X].nMin = -64; | |
828 | + pSerial->state.values[WACOMFIELD_TILT_X].nMax = 63; | |
829 | + pSerial->state.values[WACOMFIELD_TILT_Y].nMin = -64; | |
830 | + pSerial->state.values[WACOMFIELD_TILT_Y].nMax = 63; | |
831 | + } | |
832 | + else { errno=EINVAL; return 1; } | |
833 | + | |
834 | + return 0; | |
835 | +} | |
836 | + | |
837 | + | |
838 | +static int SerialIdentTabletPC(SERIALTABLET* pSerial) | |
839 | +{ | |
840 | + /* sanity check */ | |
841 | + if ((pSerial->pVendor != &xtpcVendor) || | |
842 | + (pSerial->pDevice == NULL)) { return EPERM; return 1; } | |
843 | + | |
844 | + /* use first one */ | |
845 | + pSerial->pSubType = pSerial->pDevice->pSubTypes; | |
846 | + | |
847 | + /* sanity check again */ | |
848 | + if (pSerial->pSubType->pszName == NULL) { return EPERM; return 1; } | |
849 | + | |
850 | + pSerial->state.uValid = pSerial->pDevice->uCaps; | |
851 | + pSerial->uPacketLength = pSerial->pDevice->uPacketLength; | |
852 | + pSerial->pfnInit = pSerial->pSubType->pfnInit; | |
853 | + pSerial->nVerMajor = 0; | |
854 | + pSerial->nVerMinor = 0; | |
855 | + pSerial->nVerRelease = 0; | |
856 | + | |
857 | + return 0; | |
858 | +} | |
859 | + | |
860 | +static int SerialInitTabletPC(SERIALTABLET* pSerial) | |
861 | +{ | |
862 | + pSerial->pfnParse = SerialParseTabletPC; | |
863 | + pSerial->state.values[WACOMFIELD_POSITION_X].nMax = 0x6000; | |
864 | + pSerial->state.values[WACOMFIELD_POSITION_Y].nMax = 0x4800; | |
865 | + pSerial->state.values[WACOMFIELD_PRESSURE].nMax = 255; | |
866 | + return 0; | |
867 | +} | |
868 | + | |
869 | +static int SerialParseWacomV(SERIALTABLET* pSerial, | |
870 | + const unsigned char* puchData, unsigned int uLength, | |
871 | + WACOMSTATE* pState) | |
872 | +{ | |
873 | + int x=0, y=0, rot=0, tiltx=0, tilty=0, wheel=0, | |
874 | + tool=WACOMTOOLTYPE_NONE, button=0, press=0, throttle=0, | |
875 | + nButtonValue; | |
876 | + | |
877 | + /* Wacom V | |
878 | + * Supports: 1024 pressure, eraser, 2 side-switch, tilt, throttle, wheel | |
879 | + * Limitation: no tilt */ | |
880 | + | |
881 | + if (uLength != 9) { errno=EINVAL; return 1; } | |
882 | + | |
883 | + /* in */ | |
884 | + if ((puchData[0] & 0xFC) == 0xC0) | |
885 | + { | |
886 | + int toolid = (((int)puchData[1]&0x7F) << 5) | | |
887 | + (((int)puchData[2]&0x7C) >> 2); | |
888 | + | |
889 | + int serial = ((((int)puchData[2] & 0x03) << 30) | | |
890 | + (((int)puchData[3] & 0x7f) << 23) | | |
891 | + (((int)puchData[4] & 0x7f) << 16) | | |
892 | + (((int)puchData[5] & 0x7f) << 9) | | |
893 | + (((int)puchData[6] & 0x7f) << 2) | | |
894 | + (((int)puchData[7] & 0x60) >> 5)); | |
895 | + | |
896 | + switch (toolid) | |
897 | + { | |
898 | + case 0x812: /* Intuos2 ink pen XP-110-00A */ | |
899 | + case 0x012: /* Inking pen */ | |
900 | + tool = WACOMTOOLTYPE_PENCIL; break; | |
901 | + | |
902 | + case 0x822: /* Intuos Pen GP-300E-01H */ | |
903 | + case 0x852: /* Intuos2 Grip Pen XP-501E-00A */ | |
904 | + case 0x842: /* added from Cheng */ | |
905 | + case 0x022: | |
906 | + tool = WACOMTOOLTYPE_PEN; break; | |
907 | + | |
908 | + case 0x832: /* Intuos2 stroke pen XP-120-00A */ | |
909 | + case 0x032: /* Stroke pen */ | |
910 | + tool = WACOMTOOLTYPE_BRUSH; break; | |
911 | + | |
912 | + case 0x007: /* 2D Mouse */ | |
913 | + case 0x09C: /* ?? Mouse */ | |
914 | + case 0x094: /* 4D Mouse */ | |
915 | + tool = WACOMTOOLTYPE_MOUSE; break; | |
916 | + | |
917 | + case 0x096: /* Lens cursor */ | |
918 | + tool = WACOMTOOLTYPE_LENS; break; | |
919 | + | |
920 | + case 0x82a: | |
921 | + case 0x85a: | |
922 | + case 0x91a: | |
923 | + case 0x0fa: /* Eraser */ | |
924 | + tool = WACOMTOOLTYPE_ERASER; break; | |
925 | + | |
926 | + case 0x112: /* Airbrush */ | |
927 | + tool = WACOMTOOLTYPE_AIRBRUSH; break; | |
928 | + | |
929 | + default: /* Unknown tool */ | |
930 | + tool = WACOMTOOLTYPE_PEN; break; | |
931 | + } | |
932 | + | |
933 | + pSerial->nToolID = toolid; | |
934 | + pSerial->state.values[WACOMFIELD_PROXIMITY].nValue = 1; | |
935 | + pSerial->state.values[WACOMFIELD_SERIAL].nValue = serial; | |
936 | + pSerial->state.values[WACOMFIELD_TOOLTYPE].nValue = tool; | |
937 | + return pState ? WacomCopyState(pState,&pSerial->state) : 0; | |
938 | + } | |
939 | + | |
940 | + /* out */ | |
941 | + if ((puchData[0] & 0xFE) == 0x80) | |
942 | + { | |
943 | + pSerial->nToolID = 0; | |
944 | + memset(&pSerial->state.values, 0, | |
945 | + pSerial->state.uValueCnt * sizeof(WACOMVALUE)); | |
946 | + return pState ? WacomCopyState(pState,&pSerial->state) : 0; | |
947 | + } | |
948 | + | |
949 | + /* pen data */ | |
950 | + if (((puchData[0] & 0xB8) == 0xA0) || ((puchData[0] & 0xBE) == 0xB4)) | |
951 | + { | |
952 | + x = ((((int)puchData[1] & 0x7f) << 9) | | |
953 | + (((int)puchData[2] & 0x7f) << 2) | | |
954 | + (((int)puchData[3] & 0x60) >> 5)); | |
955 | + y = ((((int)puchData[3] & 0x1f) << 11) | | |
956 | + (((int)puchData[4] & 0x7f) << 4) | | |
957 | + (((int)puchData[5] & 0x78) >> 3)); | |
958 | + tiltx = (puchData[7] & 0x3F); | |
959 | + tilty = (puchData[8] & 0x3F); | |
960 | + if (puchData[7] & 0x40) tiltx -= 0x40; | |
961 | + if (puchData[8] & 0x40) tilty -= 0x40; | |
962 | + | |
963 | + /* pen packet */ | |
964 | + if ((puchData[0] & 0xB8) == 0xA0) | |
965 | + { | |
966 | + press = ((((int)puchData[5] & 0x07) << 7) | ((int)puchData[6] & 0x7f)); | |
967 | + button = (press > 10) ? BIT(WACOMBUTTON_TOUCH) : 0; | |
968 | + button |= (puchData[0] & 0x02) ? BIT(WACOMBUTTON_STYLUS) : 0; | |
969 | + button |= (puchData[0] & 0x04) ? BIT(WACOMBUTTON_STYLUS2) : 0; | |
970 | + } | |
971 | + | |
972 | + /* 2nd airbrush packet */ | |
973 | + else | |
974 | + { | |
975 | + wheel = ((((int)puchData[5] & 0x07) << 7) | | |
976 | + ((int)puchData[6] & 0x7f)); | |
977 | + } | |
978 | + | |
979 | + pSerial->state.values[WACOMFIELD_POSITION_X].nValue = x; | |
980 | + pSerial->state.values[WACOMFIELD_POSITION_Y].nValue = y; | |
981 | + pSerial->state.values[WACOMFIELD_TILT_X].nValue = tiltx; | |
982 | + pSerial->state.values[WACOMFIELD_TILT_Y].nValue = tilty; | |
983 | + pSerial->state.values[WACOMFIELD_PRESSURE].nValue = press; | |
984 | + pSerial->state.values[WACOMFIELD_BUTTONS].nValue = button; | |
985 | + pSerial->state.values[WACOMFIELD_ABSWHEEL].nValue = wheel; | |
986 | + return pState ? WacomCopyState(pState,&pSerial->state) : 0; | |
987 | + } | |
988 | + | |
989 | + /* mouse packet */ | |
990 | + if (((puchData[0] & 0xBE) == 0xA8) || ((puchData[0] & 0xBE) == 0xB0)) | |
991 | + { | |
992 | + x = ((((int)puchData[1] & 0x7f) << 9) | | |
993 | + (((int)puchData[2] & 0x7f) << 2) | | |
994 | + (((int)puchData[3] & 0x60) >> 5)); | |
995 | + y = ((((int)puchData[3] & 0x1f) << 11) | | |
996 | + (((int)puchData[4] & 0x7f) << 4) | | |
997 | + (((int)puchData[5] & 0x78) >> 3)); | |
998 | + throttle = ((((int)puchData[5] & 0x07) << 7) | (puchData[6] & 0x7f)); | |
999 | + if (puchData[8] & 0x08) throttle = -throttle; | |
1000 | + | |
1001 | + /* 4D mouse */ | |
1002 | + if (pSerial->nToolID == 0x094) | |
1003 | + { | |
1004 | + button = (((puchData[8] & 0x70) >> 1) | (puchData[8] & 0x07)); | |
1005 | + } | |
1006 | + /* lens cursor */ | |
1007 | + else if (pSerial->nToolID == 0x096) | |
1008 | + { | |
1009 | + button = puchData[8] & 0x1F; | |
1010 | + } | |
1011 | + /* 2D mouse */ | |
1012 | + else | |
1013 | + { | |
1014 | + button = (puchData[8] & 0x1C) >> 2; | |
1015 | + wheel = - (puchData[8] & 1) + ((puchData[8] & 2) >> 1); | |
1016 | + } | |
1017 | + | |
1018 | + pSerial->state.values[WACOMFIELD_POSITION_X].nValue = x; | |
1019 | + pSerial->state.values[WACOMFIELD_POSITION_Y].nValue = y; | |
1020 | + pSerial->state.values[WACOMFIELD_RELWHEEL].nValue = wheel; | |
1021 | + pSerial->state.values[WACOMFIELD_THROTTLE].nValue = throttle; | |
1022 | + | |
1023 | + /* button values */ | |
1024 | + nButtonValue = pSerial->state.values[WACOMFIELD_BUTTONS].nValue & | |
1025 | + ~(BIT(WACOMBUTTON_LEFT) | | |
1026 | + BIT(WACOMBUTTON_RIGHT) | BIT(WACOMBUTTON_MIDDLE) | | |
1027 | + BIT(WACOMBUTTON_EXTRA) | BIT(WACOMBUTTON_SIDE)); | |
1028 | + if (button & 1) nButtonValue |= BIT(WACOMBUTTON_LEFT); | |
1029 | + if (button & 2) nButtonValue |= BIT(WACOMBUTTON_MIDDLE); | |
1030 | + if (button & 4) nButtonValue |= BIT(WACOMBUTTON_RIGHT); | |
1031 | + if (button & 8) nButtonValue |= BIT(WACOMBUTTON_EXTRA); | |
1032 | + if (button & 16) nButtonValue |= BIT(WACOMBUTTON_SIDE); | |
1033 | + pSerial->state.values[WACOMFIELD_BUTTONS].nValue = nButtonValue; | |
1034 | + | |
1035 | + return pState ? WacomCopyState(pState,&pSerial->state) : 0; | |
1036 | + } | |
1037 | + | |
1038 | + /* 2nd 4D mouse packet */ | |
1039 | + if ((puchData[0] & 0xBE) == 0xAA) | |
1040 | + { | |
1041 | + x = ((((int)puchData[1] & 0x7f) << 9) | | |
1042 | + (((int)puchData[2] & 0x7f) << 2) | | |
1043 | + (((int)puchData[3] & 0x60) >> 5)); | |
1044 | + y = ((((int)puchData[3] & 0x1f) << 11) | | |
1045 | + (((int)puchData[4] & 0x7f) << 4) | | |
1046 | + (((int)puchData[5] & 0x78) >> 3)); | |
1047 | + rot = ((((int)puchData[6] & 0x0f) << 7) | | |
1048 | + ((int)puchData[7] & 0x7f)); | |
1049 | + | |
1050 | + /* FIX ROT */ | |
1051 | + if (rot < 900) rot = -rot; | |
1052 | + else rot = 1799 - rot; | |
1053 | + | |
1054 | + pSerial->state.values[WACOMFIELD_POSITION_X].nValue = x; | |
1055 | + pSerial->state.values[WACOMFIELD_POSITION_Y].nValue = y; | |
1056 | + pSerial->state.values[WACOMFIELD_ROTATION_Z].nValue = rot; | |
1057 | + return pState ? WacomCopyState(pState,&pSerial->state) : 0; | |
1058 | + } | |
1059 | + | |
1060 | + errno = EINVAL; | |
1061 | + return 1; | |
1062 | +} | |
1063 | + | |
1064 | +static int SerialParseWacomIV_1_4(SERIALTABLET* pSerial, | |
1065 | + const unsigned char* puchData, unsigned int uLength, | |
1066 | + WACOMSTATE* pState) | |
1067 | +{ | |
1068 | + /* Wacom IV, Rom 1.4 | |
1069 | + * Supports: 256 pressure, eraser, 2 side-switch, tilt */ | |
1070 | + | |
1071 | + if ((uLength != 7) && (uLength != 9)) { errno=EINVAL; return 1; } | |
1072 | + | |
1073 | + if (SerialParseWacomIV_1_3(pSerial,puchData,7,pState)) | |
1074 | + return 1; | |
1075 | + | |
1076 | + /* tilt mode */ | |
1077 | + if (uLength == 9) | |
1078 | + { | |
1079 | + int tiltx, tilty; | |
1080 | + | |
1081 | + tiltx = puchData[7] & 0x3F; | |
1082 | + tilty = puchData[8] & 0x3F; | |
1083 | + if (puchData[7] & 0x40) tiltx -= 64; | |
1084 | + if (puchData[8] & 0x40) tilty -= 64; | |
1085 | + | |
1086 | + pSerial->state.values[WACOMFIELD_TILT_X].nValue = tiltx; | |
1087 | + pSerial->state.values[WACOMFIELD_TILT_Y].nValue = tilty; | |
1088 | + | |
1089 | + if (pState) | |
1090 | + { | |
1091 | + pState->values[WACOMFIELD_TILT_X].nValue = tiltx; | |
1092 | + pState->values[WACOMFIELD_TILT_Y].nValue = tilty; | |
1093 | + } | |
1094 | + } | |
1095 | + | |
1096 | + return 0; | |
1097 | +} | |
1098 | + | |
1099 | +static int SerialParseWacomIV_1_3(SERIALTABLET* pSerial, | |
1100 | + const unsigned char* puchData, unsigned int uLength, | |
1101 | + WACOMSTATE* pState) | |
1102 | +{ | |
1103 | + int x=0, y=0, prox=0, tool=WACOMTOOLTYPE_NONE, | |
1104 | + button=0, press=0, stylus, eraser; | |
1105 | + | |
1106 | + /* Wacom IV, Rom 1.3 (ArtPadII) | |
1107 | + * Supports: 256 pressure, eraser, 2 side-switch | |
1108 | + * Limitation: no tilt */ | |
1109 | + | |
1110 | + if (uLength != 7) { errno=EINVAL; return 1; } | |
1111 | + | |
1112 | + prox = puchData[0] & 0x40 ? 1 : 0; | |
1113 | + if (prox) | |
1114 | + { | |
1115 | + stylus = puchData[0] & 0x20 ? 1 : 0; | |
1116 | + press = (puchData[6] & 0x3F) << 1 | ((puchData[3] & 0x4) >> 2); | |
1117 | + press |= (puchData[6] & 0x40) ? 0 : 0x80; | |
1118 | + eraser = (puchData[3] & 0x20) ? 1 : 0; | |
1119 | + | |
1120 | + if (stylus) | |
1121 | + { | |
1122 | + /* if entering proximity, choose eraser or stylus2 for bit */ | |
1123 | + if (pSerial->state.values[WACOMFIELD_PROXIMITY].nValue == 0) | |
1124 | + { | |
1125 | + if (eraser) tool = WACOMTOOLTYPE_ERASER; | |
1126 | + else tool = WACOMTOOLTYPE_PEN; | |
1127 | + } | |
1128 | + | |
1129 | + /* otherwise, keep the last tool */ | |
1130 | + else tool = pSerial->state.values[WACOMFIELD_TOOLTYPE].nValue; | |
1131 | + | |
1132 | + button = (press > 10) ? BIT(WACOMBUTTON_TOUCH) : 0; | |
1133 | + | |
1134 | + /* pen has 2 side-switch, eraser has none */ | |
1135 | + if (tool == WACOMTOOLTYPE_PEN) | |
1136 | + { | |
1137 | + button |= (puchData[3] & 0x10) ? | |
1138 | + BIT(WACOMBUTTON_STYLUS) : 0; | |
1139 | + button |= (eraser) ? BIT(WACOMBUTTON_STYLUS2) : 0; | |
1140 | + } | |
1141 | + } | |
1142 | + else | |
1143 | + { | |
1144 | + tool = WACOMTOOLTYPE_MOUSE; | |
1145 | + button = (puchData[3] & 0x78) >> 3; /* not tested */ | |
1146 | + } | |
1147 | + | |
1148 | + x = puchData[2] | ((int)puchData[1] << 7) | | |
1149 | + (((int)puchData[0] & 0x3) << 14); | |
1150 | + y = puchData[5] | ((int)puchData[4] << 7) | | |
1151 | + (((int)puchData[3] & 0x3) << 14); | |
1152 | + } | |
1153 | + | |
1154 | + /* set valid fields */ | |
1155 | + pSerial->state.values[WACOMFIELD_PROXIMITY].nValue = prox; | |
1156 | + pSerial->state.values[WACOMFIELD_TOOLTYPE].nValue = tool; | |
1157 | + pSerial->state.values[WACOMFIELD_POSITION_X].nValue = x; | |
1158 | + pSerial->state.values[WACOMFIELD_POSITION_Y].nValue = y; | |
1159 | + pSerial->state.values[WACOMFIELD_PRESSURE].nValue = press; | |
1160 | + pSerial->state.values[WACOMFIELD_BUTTONS].nValue = button; | |
1161 | + | |
1162 | + return pState ? WacomCopyState(pState,&pSerial->state) : 0; | |
1163 | +} | |
1164 | + | |
1165 | +static int SerialParseWacomIV_1_2(SERIALTABLET* pSerial, | |
1166 | + const unsigned char* puchData, unsigned int uLength, | |
1167 | + WACOMSTATE* pState) | |
1168 | +{ | |
1169 | + int x=0, y=0, prox=0, tool=0, button=WACOMTOOLTYPE_NONE, | |
1170 | + press=0, stylus; | |
1171 | + | |
1172 | + /* Wacom IV, Rom 1.2, 1.1, and 1.0 | |
1173 | + * Supports: 256 pressure (120 for 1.1 and 1.0), multi-mode | |
1174 | + * Limitation: no stylus2, no tilt, no eraser */ | |
1175 | + | |
1176 | + if (uLength != 7) { errno=EINVAL; return 1; } | |
1177 | + | |
1178 | + prox = puchData[0] & 0x40 ? 1 : 0; | |
1179 | + if (prox) | |
1180 | + { | |
1181 | + stylus = puchData[0] & 0x20 ? 1 : 0; | |
1182 | + if (pSerial->nVerMinor == 2) | |
1183 | + press = ((puchData[6] & 0x3F) << 1) | ((puchData[3] & 0x4) >> 2) | | |
1184 | + ((puchData[6] & 0x40) ? 0 : 0x80); | |
1185 | + else | |
1186 | + press = (puchData[6] & 0x3F) + ((puchData[6] & 0x40) ? 0 : 64); | |
1187 | + | |
1188 | + if (stylus) | |
1189 | + { | |
1190 | + tool = WACOMTOOLTYPE_PEN; | |
1191 | + button = (press > 10) ? BIT(WACOMBUTTON_TOUCH) : 0; | |
1192 | + button |= (puchData[3] & 0x10) ? BIT(WACOMBUTTON_STYLUS) : 0; | |
1193 | + } | |
1194 | + else | |
1195 | + { | |
1196 | + tool = WACOMTOOLTYPE_MOUSE; | |
1197 | + button = (puchData[3] & 0x78) >> 3; /* not tested */ | |
1198 | + } | |
1199 | + | |
1200 | + x = puchData[2] | ((int)puchData[1] << 7) | | |
1201 | + (((int)puchData[0] & 0x3) << 14); | |
1202 | + y = puchData[5] | ((int)puchData[4] << 7) | | |
1203 | + (((int)puchData[3] & 0x3) << 14); | |
1204 | + } | |
1205 | + | |
1206 | + /* set valid fields */ | |
1207 | + pSerial->state.values[WACOMFIELD_PROXIMITY].nValue = prox; | |
1208 | + pSerial->state.values[WACOMFIELD_TOOLTYPE].nValue = tool; | |
1209 | + pSerial->state.values[WACOMFIELD_POSITION_X].nValue = x; | |
1210 | + pSerial->state.values[WACOMFIELD_POSITION_Y].nValue = y; | |
1211 | + pSerial->state.values[WACOMFIELD_PRESSURE].nValue = press; | |
1212 | + pSerial->state.values[WACOMFIELD_BUTTONS].nValue = button; | |
1213 | + | |
1214 | + return pState ? WacomCopyState(pState,&pSerial->state) : 0; | |
1215 | +} | |
1216 | + | |
1217 | +static int SerialParseTabletPC(SERIALTABLET* pSerial, | |
1218 | + const unsigned char* puchData, unsigned int uLength, | |
1219 | + WACOMSTATE* pState) | |
1220 | +{ | |
1221 | + int x=0, y=0, prox=0, tool=WACOMTOOLTYPE_NONE, | |
1222 | + button=0, press=0, eraser; | |
1223 | + | |
1224 | + /* Tablet PC Supports: 256 pressure, eraser, 1/2 side-switch */ | |
1225 | + | |
1226 | + if (uLength != 9) { errno=EINVAL; return 1; } | |
1227 | + | |
1228 | + prox = puchData[0] & 0x20 ? 1 : 0; | |
1229 | + if (prox) | |
1230 | + { | |
1231 | + eraser = (puchData[0] & 0x04) ? 1 : 0; | |
1232 | + press = ((puchData[6] & 0x01) << 7) | (puchData[5] & 0x7F); | |
1233 | + | |
1234 | + /* tools are distinguishable */ | |
1235 | + if (eraser) tool = WACOMTOOLTYPE_ERASER; | |
1236 | + else tool = WACOMTOOLTYPE_PEN; | |
1237 | + | |
1238 | + button = (puchData[0] & 0x01) ? BIT(WACOMBUTTON_TOUCH) : 0; | |
1239 | + | |
1240 | + /* pen has side-switch(es), eraser has none */ | |
1241 | + if (tool == WACOMTOOLTYPE_PEN) | |
1242 | + { | |
1243 | + button |= (puchData[0] & 0x02) ? | |
1244 | + BIT(WACOMBUTTON_STYLUS) : 0; | |
1245 | + button |= (puchData[0] & 0x04) ? | |
1246 | + BIT(WACOMBUTTON_STYLUS2) : 0; | |
1247 | + } | |
1248 | + | |
1249 | + x = (((int)puchData[6] & 0x60) >> 5) | | |
1250 | + ((int)puchData[2] << 2) | | |
1251 | + ((int)puchData[1] << 9); | |
1252 | + y = (((int)puchData[6] & 0x18) >> 3) | | |
1253 | + ((int)puchData[4] << 2) | | |
1254 | + ((int)puchData[3] << 9); | |
1255 | + } | |
1256 | + | |
1257 | + /* set valid fields */ | |
1258 | + pSerial->state.values[WACOMFIELD_PROXIMITY].nValue = prox; | |
1259 | + pSerial->state.values[WACOMFIELD_TOOLTYPE].nValue = tool; | |
1260 | + pSerial->state.values[WACOMFIELD_POSITION_X].nValue = x; | |
1261 | + pSerial->state.values[WACOMFIELD_POSITION_Y].nValue = y; | |
1262 | + pSerial->state.values[WACOMFIELD_PRESSURE].nValue = press; | |
1263 | + pSerial->state.values[WACOMFIELD_BUTTONS].nValue = button; | |
1264 | + | |
1265 | + return pState ? WacomCopyState(pState,&pSerial->state) : 0; | |
1266 | +} | |
1267 | + | |
1268 | + | |
1269 | + | |
1270 | +/***************************************************************************** | |
1271 | +** Internal Functions | |
1272 | +*****************************************************************************/ | |
1273 | + | |
1274 | +static int SerialSendReset(SERIALTABLET* pSerial) | |
1275 | +{ | |
1276 | + SerialInfo(pSerial,"Sending reset"); | |
1277 | + | |
1278 | + /* reset to Wacom II-S command set, and factory defaults */ | |
1279 | + if (SerialSend(pSerial,"\r$\r")) return 1; | |
1280 | + usleep(250000); /* 250 milliseconds */ | |
1281 | + | |
1282 | + /* reset tablet to Wacom IV command set */ | |
1283 | + if (SerialSend(pSerial,"#\r")) return 1; | |
1284 | + usleep(75000); /* 75 milliseconds */ | |
1285 | + | |
1286 | + return 0; | |
1287 | +} | |
1288 | + | |
1289 | +static int SerialSendStop(SERIALTABLET* pSerial) | |
1290 | +{ | |
1291 | + if (SerialSend(pSerial,"\rSP\r")) return 1; | |
1292 | + usleep(100000); | |
1293 | + return 0; | |
1294 | +} | |
1295 | + | |
1296 | +static int SerialSendStart(SERIALTABLET* pSerial) | |
1297 | +{ | |
1298 | + return SerialSend(pSerial,"ST\r"); | |
1299 | +} | |
1300 | + | |
1301 | +static int SerialSend(SERIALTABLET* pSerial, const char* pszMsg) | |
1302 | +{ | |
1303 | + return SerialSendRaw(pSerial,pszMsg,(unsigned)strlen(pszMsg)); | |
1304 | +} | |
1305 | + | |
1306 | +static int SerialSendRaw(SERIALTABLET* pSerial, const void* pvData, | |
1307 | + unsigned int uSize) | |
1308 | +{ | |
1309 | + int nXfer; | |
1310 | + unsigned int uCnt=0; | |
1311 | + unsigned char* puchData = (unsigned char*)pvData; | |
1312 | + | |
1313 | + while (uCnt < uSize) | |
1314 | + { | |
1315 | + nXfer = write(pSerial->fd,puchData+uCnt,uSize-uCnt); | |
1316 | + if (nXfer <= 0) | |
1317 | + { | |
1318 | + SerialError(pSerial,"Failed to write to port: %s",strerror(errno)); | |
1319 | + return 1; | |
1320 | + } | |
1321 | + uCnt += nXfer; | |
1322 | + } | |
1323 | + | |
1324 | + return 0; | |
1325 | +} | |
1326 | + | |
1327 | +static int WacomFlush(SERIALTABLET* pSerial) | |
1328 | +{ | |
1329 | + char ch[16]; | |
1330 | + fd_set fdsRead; | |
1331 | + struct timeval timeout; | |
1332 | + | |
1333 | + if (tcflush(pSerial->fd, TCIFLUSH) == 0) | |
1334 | + return 0; | |
1335 | + | |
1336 | + timeout.tv_sec = 0; | |
1337 | + timeout.tv_usec = 0; | |
1338 | + | |
1339 | + while (1) | |
1340 | + { | |
1341 | + FD_ZERO(&fdsRead); | |
1342 | + FD_SET(pSerial->fd, &fdsRead); | |
1343 | + if (select(FD_SETSIZE,&fdsRead,NULL,NULL,&timeout) <= 0) | |
1344 | + break; | |
1345 | + read(pSerial->fd,&ch,sizeof(ch)); | |
1346 | + } | |
1347 | + | |
1348 | + return 0; | |
1349 | +} | |
1350 | + | |
1351 | +static int SerialSendRequest(SERIALTABLET* pSerial, const char* pszRequest, | |
1352 | + char* pchResponse, unsigned int uSize) | |
1353 | +{ | |
1354 | + int nXfer; | |
1355 | + fd_set fdsRead; | |
1356 | + unsigned int uLen, uCnt; | |
1357 | + struct timeval timeout; | |
1358 | + | |
1359 | + uLen = strlen(pszRequest); | |
1360 | + if (SerialSendRaw(pSerial,pszRequest,uLen)) return 1; | |
1361 | + --uLen; | |
1362 | + | |
1363 | + if (uSize < uLen) | |
1364 | + { | |
1365 | + errno=EINVAL; | |
1366 | + SerialError(pSerial,"Invalid size to SerialSendRequest: %u < %u", | |
1367 | + uSize,uLen); | |
1368 | + return 1; | |
1369 | + } | |
1370 | + | |
1371 | + /* read until first header character */ | |
1372 | + while (1) | |
1373 | + { | |
1374 | + timeout.tv_sec = 0; | |
1375 | + timeout.tv_usec = 500000; | |
1376 | + | |
1377 | + FD_ZERO(&fdsRead); | |
1378 | + FD_SET(pSerial->fd, &fdsRead); | |
1379 | + if (select(FD_SETSIZE,&fdsRead,NULL,NULL,&timeout) <= 0) | |
1380 | + { | |
1381 | + errno = ETIMEDOUT; | |
1382 | + return 1; | |
1383 | + } | |
1384 | + | |
1385 | + nXfer = read(pSerial->fd,pchResponse,1); | |
1386 | + if (nXfer <= 0) | |
1387 | + { | |
1388 | + SerialError(pSerial,"Truncated response header"); | |
1389 | + return 1; | |
1390 | + } | |
1391 | + if (*pchResponse == *pszRequest) break; | |
1392 | + SerialWarn(pSerial,"Discarding %02X", *((unsigned char*)pchResponse)); | |
1393 | + } | |
1394 | + | |
1395 | + /* read response header */ | |
1396 | + for (uCnt=1; uCnt<uLen; uCnt+=nXfer) | |
1397 | + { | |
1398 | + nXfer = read(pSerial->fd,pchResponse+uCnt,uLen-uCnt); | |
1399 | + if (nXfer <= 0) | |
1400 | + { | |
1401 | + SerialError(pSerial,"Truncated response header (2)"); | |
1402 | + return 1; | |
1403 | + } | |
1404 | + } | |
1405 | + | |
1406 | + /* check the header */ | |
1407 | + if (strncmp(pszRequest,pchResponse,uLen) != 0) | |
1408 | + { | |
1409 | + SerialError(pSerial,"Incorrect response [%s,%s]", | |
1410 | + pszRequest,pchResponse); | |
1411 | + return 1; | |
1412 | + } | |
1413 | + | |
1414 | + /* get the rest of the response */ | |
1415 | + for (uCnt=0; uCnt<uSize; ++uCnt) | |
1416 | + { | |
1417 | + nXfer = read(pSerial->fd,pchResponse+uCnt,1); | |
1418 | + if (nXfer <= 0) | |
1419 | + { | |
1420 | + SerialError(pSerial,"Failed to read response: %s",strerror(errno)); | |
1421 | + return 1; | |
1422 | + } | |
1423 | + | |
1424 | + /* stop on CR */ | |
1425 | + if (pchResponse[uCnt] == '\r') | |
1426 | + { | |
1427 | + pchResponse[uCnt] = '\0'; | |
1428 | + return 0; | |
1429 | + } | |
1430 | + } | |
1431 | + | |
1432 | + errno = EINVAL; | |
1433 | + SerialError(pSerial,"Invalid response"); | |
1434 | + return 1; | |
1435 | +} | |
1436 | + | |
1437 | +/***************************************************************************** | |
1438 | +** Virtual Functions | |
1439 | +*****************************************************************************/ | |
1440 | + | |
1441 | +static void SerialClose(WACOMTABLET_PRIV* pTablet) | |
1442 | +{ | |
1443 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1444 | + close(pSerial->fd); | |
1445 | + free(pSerial); | |
1446 | +} | |
1447 | + | |
1448 | +static WACOMMODEL SerialGetModel(WACOMTABLET_PRIV* pTablet) | |
1449 | +{ | |
1450 | + WACOMMODEL model = { 0 }; | |
1451 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1452 | + model.uClass = WACOMCLASS_SERIAL; | |
1453 | + model.uVendor = pSerial->pVendor->uVendor; | |
1454 | + model.uDevice = pSerial->pDevice->uDevice; | |
1455 | + model.uSubType = pSerial->pSubType->uSubType; | |
1456 | + return model; | |
1457 | +} | |
1458 | + | |
1459 | +static const char* SerialGetVendorName(WACOMTABLET_PRIV* pTablet) | |
1460 | +{ | |
1461 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1462 | + return pSerial->pVendor->pszDesc; | |
1463 | +} | |
1464 | + | |
1465 | +static const char* SerialGetClassName(WACOMTABLET_PRIV* pTablet) | |
1466 | +{ | |
1467 | + return "Serial"; | |
1468 | +} | |
1469 | + | |
1470 | +static const char* SerialGetDeviceName(WACOMTABLET_PRIV* pTablet) | |
1471 | +{ | |
1472 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1473 | + return pSerial->pDevice->pszDesc; | |
1474 | +} | |
1475 | + | |
1476 | +static const char* SerialGetSubTypeName(WACOMTABLET_PRIV* pTablet) | |
1477 | +{ | |
1478 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1479 | + return pSerial->pSubType->pszName; | |
1480 | +} | |
1481 | + | |
1482 | +static const char* SerialGetModelName(WACOMTABLET_PRIV* pTablet) | |
1483 | +{ | |
1484 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1485 | + return pSerial->pSubType->pszDesc; | |
1486 | +} | |
1487 | + | |
1488 | +static int SerialGetROMVer(WACOMTABLET_PRIV* pTablet, int* pnMajor, | |
1489 | + int* pnMinor, int* pnRelease) | |
1490 | +{ | |
1491 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1492 | + if (!pnMajor) { errno=EINVAL; return 1; } | |
1493 | + *pnMajor = pSerial->nVerMajor; | |
1494 | + if (pnMinor) *pnMinor = pSerial->nVerMinor; | |
1495 | + if (pnRelease) *pnRelease = pSerial->nVerRelease; | |
1496 | + return 0; | |
1497 | +} | |
1498 | + | |
1499 | +static int SerialGetCaps(WACOMTABLET_PRIV* pTablet) | |
1500 | +{ | |
1501 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1502 | + return pSerial->pDevice->uCaps; | |
1503 | +} | |
1504 | + | |
1505 | +static int SerialGetState(WACOMTABLET_PRIV* pTablet, WACOMSTATE* pState) | |
1506 | +{ | |
1507 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1508 | + return WacomCopyState(pState,&pSerial->state); | |
1509 | +} | |
1510 | + | |
1511 | +static int SerialGetFD(WACOMTABLET_PRIV* pTablet) | |
1512 | +{ | |
1513 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1514 | + return pSerial->fd; | |
1515 | +} | |
1516 | + | |
1517 | +static int SerialReadRaw(WACOMTABLET_PRIV* pTablet, unsigned char* puchData, | |
1518 | + unsigned int uSize) | |
1519 | +{ | |
1520 | + int nXfer; | |
1521 | + fd_set fdsRead; | |
1522 | + unsigned char* pPos, *pEnd; | |
1523 | + struct timeval timeout; | |
1524 | + unsigned int uCnt, uPacketLength; | |
1525 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1526 | + | |
1527 | + if (!pSerial) { errno=EBADF; return 0; } | |
1528 | + uPacketLength = pSerial->uPacketLength; | |
1529 | + | |
1530 | + /* check size of buffer */ | |
1531 | + if (uSize < uPacketLength) { errno=EINVAL; return 0; } | |
1532 | + | |
1533 | + /* check for errors, reset after 3 */ | |
1534 | + if (pSerial->nBitErrors > 3) | |
1535 | + { | |
1536 | + pSerial->nBitErrors = 0; | |
1537 | + SerialWarn(pSerial,"Resetting tablet due to bit errors"); | |
1538 | + (void)SerialReset(pSerial,&pSerial->modelRequested); | |
1539 | + } | |
1540 | + | |
1541 | + timeout.tv_sec = 0; | |
1542 | + timeout.tv_usec = 500000; /* 0.5 seconds for now */ | |
1543 | + | |
1544 | + for (uCnt=0; uCnt<uPacketLength; uCnt+=nXfer) | |
1545 | + { | |
1546 | + FD_ZERO(&fdsRead); | |
1547 | + FD_SET(pSerial->fd, &fdsRead); | |
1548 | + if (select(FD_SETSIZE,&fdsRead,NULL,NULL,&timeout) <= 0) | |
1549 | + break; | |
1550 | + | |
1551 | + nXfer = read(pSerial->fd,puchData+uCnt,uPacketLength-uCnt); | |
1552 | + if (nXfer <= 0) return nXfer; | |
1553 | + | |
1554 | + /* look for high-bit */ | |
1555 | + pEnd = puchData + nXfer; | |
1556 | + for (pPos=puchData; pPos<pEnd; ++pPos) | |
1557 | + { | |
1558 | + if (*pPos & 0x80) break; | |
1559 | + } | |
1560 | + | |
1561 | + /* not where it was expected? fix it. */ | |
1562 | + if (pPos != puchData) | |
1563 | + { | |
1564 | + ++pSerial->nBitErrors; | |
1565 | + SerialWarn(pSerial,"Bad high bit, discarding %d bytes", | |
1566 | + pPos - puchData); | |
1567 | + | |
1568 | + /* copy remaining bytes down, if any */ | |
1569 | + memmove(puchData,pPos,(nXfer - (pPos-puchData))); | |
1570 | + nXfer -= pPos - puchData; | |
1571 | + } | |
1572 | + } | |
1573 | + | |
1574 | + if (uCnt < uPacketLength) | |
1575 | + { | |
1576 | + errno = ETIMEDOUT; | |
1577 | + return -1; | |
1578 | + } | |
1579 | + | |
1580 | + return (signed)uCnt; | |
1581 | +} | |
1582 | + | |
1583 | +static int SerialParseData(WACOMTABLET_PRIV* pTablet, | |
1584 | + const unsigned char* puchData, unsigned int uLength, | |
1585 | + WACOMSTATE* pState) | |
1586 | +{ | |
1587 | + int i; | |
1588 | + SERIALTABLET* pSerial = (SERIALTABLET*)pTablet; | |
1589 | + if (!pSerial) { errno=EBADF; return 1; } | |
1590 | + | |
1591 | + /* check synchronization */ | |
1592 | + if (!(puchData[0] & 0x80)) | |
1593 | + { | |
1594 | + ++pSerial->nBitErrors; | |
1595 | + SerialError(pSerial,"HIBIT FAIL"); | |
1596 | + errno=EINVAL; | |
1597 | + return 1; | |
1598 | + } | |
1599 | + for (i=1; i<uLength; ++i) | |
1600 | + { | |
1601 | + if (puchData[i] & 0x80) | |
1602 | + { | |
1603 | + ++pSerial->nBitErrors; | |
1604 | + SerialError(pSerial,"LOBIT FAIL"); | |
1605 | + SerialDump(pSerial,puchData,uLength); | |
1606 | + errno=EINVAL; | |
1607 | + return 1; | |
1608 | + } | |
1609 | + } | |
1610 | + | |
1611 | + /* reset bit error count */ | |
1612 | + pSerial->nBitErrors = 0; | |
1613 | + | |
1614 | + /* dispatch to parser */ | |
1615 | + if (pSerial->pfnParse) | |
1616 | + return (*pSerial->pfnParse)(pSerial,puchData,uLength,pState); | |
1617 | + | |
1618 | + errno=EINVAL; | |
1619 | + return 1; | |
1620 | +} | |
1621 | + | |
1622 | +static int SerialResetAtBaud(SERIALTABLET* pSerial, struct termios* pTIOS, | |
1623 | + int nBaud) | |
1624 | +{ | |
1625 | + /* conver baud rate to tios macro */ | |
1626 | + int baudRate = B9600; | |
1627 | + switch (nBaud) | |
1628 | + { | |
1629 | + case 38400: baudRate = B38400; break; | |
1630 | + case 19200: baudRate = B19200; break; | |
1631 | + case 9600: baudRate = B9600; break; | |
1632 | + case 4800: baudRate = B4800; break; /* for testing, maybe */ | |
1633 | + case 2400: baudRate = B2400; break; /* for testing, maybe */ | |
1634 | + case 1200: baudRate = B1200; break; /* for testing, maybe */ | |
1635 | + } | |
1636 | + | |
1637 | + SerialInfo(pSerial,"Setting baud rate to %d",nBaud); | |
1638 | + | |
1639 | + /* change baud rate */ | |
1640 | + cfsetispeed(pTIOS, baudRate); | |
1641 | + cfsetospeed(pTIOS, baudRate); | |
1642 | + if (tcsetattr (pSerial->fd, TCSANOW, pTIOS)) | |
1643 | + return 1; | |
1644 | + | |
1645 | + /* send reset command */ | |
1646 | + return SerialSendReset(pSerial); | |
1647 | +} | |
1648 | + | |
1649 | +/***************************************************************************** | |
1650 | +** Log Functions | |
1651 | +*****************************************************************************/ | |
1652 | + | |
1653 | +#define SERIALLOG(l) do { \ | |
1654 | + va_list a; \ | |
1655 | + va_start(a,pszFmt); \ | |
1656 | + WacomLogV(pSerial->hEngine,l,pszFmt,a); \ | |
1657 | + va_end(a); } while (0) | |
1658 | + | |
1659 | +static void SerialError(SERIALTABLET* pSerial, const char* pszFmt, ...) | |
1660 | + { SERIALLOG(WACOMLOGLEVEL_ERROR); } | |
1661 | +static void SerialWarn(SERIALTABLET* pSerial, const char* pszFmt, ...) | |
1662 | + { SERIALLOG(WACOMLOGLEVEL_WARN); } | |
1663 | +static void SerialInfo(SERIALTABLET* pSerial, const char* pszFmt, ...) | |
1664 | + { SERIALLOG(WACOMLOGLEVEL_INFO); } | |
1665 | + | |
1666 | +static void SerialDump(SERIALTABLET* pSerial, const void* pvData, int nCnt) | |
1667 | +{ | |
1668 | + int i; | |
1669 | + const unsigned char* pData = (const unsigned char*)pvData; | |
1670 | + char chLine[80]; | |
1671 | + unsigned int uAddr = 0; | |
1672 | + int nPos = 0; | |
1673 | + | |
1674 | + while (nCnt > 0) | |
1675 | + { | |
1676 | + nPos = 0; | |
1677 | + for (i=0; i<16; ++i) | |
1678 | + { | |
1679 | + if (i < nCnt) | |
1680 | + nPos += snprintf(chLine+nPos,sizeof(chLine)-nPos, | |
1681 | + "%02X",pData[i]); | |
1682 | + else | |
1683 | + nPos += snprintf(chLine+nPos,sizeof(chLine)-nPos, | |
1684 | + " "); | |
1685 | + } | |
1686 | + nPos += snprintf(chLine+nPos,sizeof(chLine)-nPos," - "); | |
1687 | + for (i=0; i<16; ++i) | |
1688 | + { | |
1689 | + if (i < nCnt) | |
1690 | + nPos += snprintf(chLine+nPos,sizeof(chLine)-nPos, | |
1691 | + "%c",isprint(pData[i]) ? pData[i] : '.'); | |
1692 | + else | |
1693 | + nPos += snprintf(chLine+nPos,sizeof(chLine)-nPos, | |
1694 | + " "); | |
1695 | + } | |
1696 | + | |
1697 | + WacomLog(pSerial->hEngine,WACOMLOGLEVEL_DEBUG,"%04X: %s",uAddr,chLine); | |
1698 | + uAddr += 16; | |
1699 | + nCnt -= 16; | |
1700 | + pData += 16; | |
1701 | + } | |
1702 | +} | |
1703 | + | |
1704 | +/* NOT USED, YET | |
1705 | +static void SerialCritical(SERIALTABLET* pSerial, const char* pszFmt, ...) | |
1706 | + { SERIALLOG(WACOMLOGLEVEL_CRITICAL); } | |
1707 | +static void SerialDebug(SERIALTABLET* pSerial, const char* pszFmt, ...) | |
1708 | + { SERIALLOG(WACOMLOGLEVEL_DEBUG); } | |
1709 | +static void SerialTrace(SERIALTABLET* pSerial, const char* pszFmt, ...) | |
1710 | + { SERIALLOG(WACOMLOGLEVEL_TRACE); } | |
1711 | +*/ |
@@ -0,0 +1,32 @@ | ||
1 | +/***************************************************************************** | |
2 | +** wacserial.h | |
3 | +** | |
4 | +** Copyright (C) 2002,2003 - John E. Joganic | |
5 | +** | |
6 | +** This program is free software; you can redistribute it and/or | |
7 | +** modify it under the terms of the GNU General Public License | |
8 | +** as published by the Free Software Foundation; either version 2 | |
9 | +** of the License, or (at your option) any later version. | |
10 | +** | |
11 | +** This program is distributed in the hope that it will be useful, | |
12 | +** but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | +** GNU General Public License for more details. | |
15 | +** | |
16 | +** You should have received a copy of the GNU General Public License | |
17 | +** along with this program; if not, write to the Free Software | |
18 | +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
19 | +** | |
20 | +****************************************************************************/ | |
21 | + | |
22 | +#ifndef __LINUXWACOM_WACSERIAL_H | |
23 | +#define __LINUXWACOM_WACSERIAL_H | |
24 | + | |
25 | +#include "wactablet.h" | |
26 | + | |
27 | +int WacomGetSupportedSerialDeviceList(WACOMDEVICEREC** ppList, int* pnSize); | |
28 | +unsigned int WacomGetSerialDeviceFromName(const char* pszName); | |
29 | +WACOMTABLET WacomOpenSerialTablet(WACOMENGINE hEngine, int fd, | |
30 | + WACOMMODEL* pModel); | |
31 | + | |
32 | +#endif /* __LINUXWACOM_WACSERIAL_H */ |
@@ -0,0 +1,441 @@ | ||
1 | +/***************************************************************************** | |
2 | +** wactablet.c | |
3 | +** | |
4 | +** Copyright (C) 2002 - 2003 - John E. Joganic | |
5 | +** Copyright (C) 2004 - 2005 - Ping Cheng | |
6 | +** | |
7 | +** This program is free software; you can redistribute it and/or | |
8 | +** modify it under the terms of the GNU General Public License | |
9 | +** as published by the Free Software Foundation; either version 2 | |
10 | +** of the License, or (at your option) any later version. | |
11 | +** | |
12 | +** This program is distributed in the hope that it will be useful, | |
13 | +** but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | +** GNU General Public License for more details. | |
16 | +** | |
17 | +** You should have received a copy of the GNU General Public License | |
18 | +** along with this program; if not, write to the Free Software | |
19 | +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
20 | +** | |
21 | +****************************************************************************/ | |
22 | + | |
23 | +#include "wactablet.h" | |
24 | +#include "wacserial.h" | |
25 | +#include "wacusb.h" | |
26 | + | |
27 | +#include <stdio.h> | |
28 | +#include <stdlib.h> | |
29 | +#include <errno.h> | |
30 | +#include <memory.h> | |
31 | +#include <fcntl.h> | |
32 | +#include <unistd.h> | |
33 | +#include <assert.h> | |
34 | +#include <stdarg.h> | |
35 | + | |
36 | +#ifdef WCM_ENABLE_LINUXINPUT | |
37 | +#include <linux/input.h> | |
38 | +#endif | |
39 | + | |
40 | +typedef void (*FREEFUNC)(void* pv); | |
41 | + | |
42 | +typedef struct | |
43 | +{ | |
44 | + FREEFUNC pfnFree; | |
45 | +} CLSLIST_INTERNAL; | |
46 | + | |
47 | +typedef struct | |
48 | +{ | |
49 | + WACOMDEVICEREC* pSerialList; | |
50 | + WACOMDEVICEREC* pUSBList; | |
51 | + FREEFUNC pfnFree; | |
52 | +} DEVICELIST_INTERNAL; | |
53 | + | |
54 | +typedef struct _ENGINE_PRIV ENGINE_PRIV; | |
55 | + | |
56 | +struct _ENGINE_PRIV | |
57 | +{ | |
58 | + WACOMLOGFUNC pfnLog; | |
59 | + WACOMLOGLEVEL level; | |
60 | + char chLogBuf[1024]; | |
61 | +}; | |
62 | + | |
63 | +/***************************************************************************** | |
64 | +** Implementation | |
65 | +*****************************************************************************/ | |
66 | + | |
67 | +WACOMENGINE WacomInitEngine(void) | |
68 | +{ | |
69 | + ENGINE_PRIV* pEngine = NULL; | |
70 | + pEngine = (ENGINE_PRIV*)malloc(sizeof(ENGINE_PRIV)); | |
71 | + memset(pEngine,0,sizeof(*pEngine)); | |
72 | + pEngine->level = WACOMLOGLEVEL_WARN; | |
73 | + return (WACOMENGINE)pEngine; | |
74 | +} | |
75 | + | |
76 | +void WacomTermEngine(WACOMENGINE hEngine) | |
77 | +{ | |
78 | + ENGINE_PRIV* pEngine = (ENGINE_PRIV*)hEngine; | |
79 | + if (!pEngine) return; | |
80 | + | |
81 | + free(pEngine); | |
82 | +} | |
83 | + | |
84 | +void WacomSetLogFunc(WACOMENGINE hEngine, WACOMLOGFUNC pfnLog) | |
85 | +{ | |
86 | + ENGINE_PRIV* pEngine = (ENGINE_PRIV*)hEngine; | |
87 | + if (!pEngine) return; | |
88 | + pEngine->pfnLog = pfnLog; | |
89 | +} | |
90 | + | |
91 | +void WacomSetLogLevel(WACOMENGINE hEngine, WACOMLOGLEVEL level) | |
92 | +{ | |
93 | + ENGINE_PRIV* pEngine = (ENGINE_PRIV*)hEngine; | |
94 | + if (!pEngine) return; | |
95 | + | |
96 | + if (level < WACOMLOGLEVEL_CRITICAL) | |
97 | + level = WACOMLOGLEVEL_CRITICAL; | |
98 | + if (level > WACOMLOGLEVEL_TRACE) | |
99 | + level = WACOMLOGLEVEL_TRACE; | |
100 | + | |
101 | + pEngine->level = level; | |
102 | +} | |
103 | + | |
104 | +void WacomLogV(WACOMENGINE hEngine, WACOMLOGLEVEL level, const char* pszFmt, | |
105 | + va_list args) | |
106 | +{ | |
107 | + struct timeval tv; | |
108 | + ENGINE_PRIV* pEngine = (ENGINE_PRIV*)hEngine; | |
109 | + if (!pEngine || !pEngine->pfnLog || (pEngine->level < level)) return; | |
110 | + | |
111 | + gettimeofday(&tv,NULL); | |
112 | + | |
113 | + vsnprintf(pEngine->chLogBuf,sizeof(pEngine->chLogBuf),pszFmt,args); | |
114 | + pEngine->pfnLog(tv,level,pEngine->chLogBuf); | |
115 | +} | |
116 | + | |
117 | +void WacomLog(WACOMENGINE hEngine, WACOMLOGLEVEL level, const char* pszFmt, ...) | |
118 | +{ | |
119 | + va_list args; | |
120 | + va_start(args, pszFmt); | |
121 | + WacomLogV(hEngine,level,pszFmt,args); | |
122 | + va_end(args); | |
123 | +} | |
124 | + | |
125 | +static void FreeClassList(void* pv) | |
126 | +{ | |
127 | + CLSLIST_INTERNAL* pInt = ((CLSLIST_INTERNAL*)pv) - 1; | |
128 | + free(pInt); | |
129 | +} | |
130 | + | |
131 | +int WacomGetSupportedClassList(WACOMCLASSREC** ppList, int* pnSize) | |
132 | +{ | |
133 | + int nIndex=0, nCnt=0; | |
134 | + CLSLIST_INTERNAL* pInt; | |
135 | + WACOMCLASSREC* pRec; | |
136 | + | |
137 | + if (!ppList || !pnSize) { errno = EINVAL; return 1; } | |
138 | + | |
139 | + /* serial */ | |
140 | + ++nCnt; | |
141 | + | |
142 | + /* USB */ | |
143 | + #ifdef WCM_ENABLE_LINUXINPUT | |
144 | + ++nCnt; | |
145 | + #endif | |
146 | + | |
147 | + /* allocate enough memory to hold internal structure and all records */ | |
148 | + pInt = (CLSLIST_INTERNAL*)malloc(sizeof(CLSLIST_INTERNAL) + | |
149 | + (sizeof(WACOMCLASSREC) * nCnt)); | |
150 | + | |
151 | + pInt->pfnFree = FreeClassList; | |
152 | + pRec = (WACOMCLASSREC*)(pInt + 1); | |
153 | + | |
154 | + /* serial */ | |
155 | + pRec[nIndex].pszName = "serial"; | |
156 | + pRec[nIndex].pszDesc = "Serial TTY interface"; | |
157 | + pRec[nIndex].uDeviceClass = WACOMCLASS_SERIAL; | |
158 | + ++nIndex; | |
159 | + | |
160 | + /* USB */ | |
161 | + #ifdef WCM_ENABLE_LINUXINPUT | |
162 | + pRec[nIndex].pszName = "usb"; | |
163 | + pRec[nIndex].pszDesc = "Linux USB event interface"; | |
164 | + pRec[nIndex].uDeviceClass = WACOMCLASS_USB; | |
165 | + ++nIndex; | |
166 | + #endif | |
167 | + | |
168 | + assert(nIndex == nCnt); | |
169 | + *ppList = pRec; | |
170 | + *pnSize = nCnt; | |
171 | + return 0; | |
172 | +} | |
173 | + | |
174 | +static void FreeDeviceList(void* pv) | |
175 | +{ | |
176 | + DEVICELIST_INTERNAL* pInt = ((DEVICELIST_INTERNAL*)pv) - 1; | |
177 | + WacomFreeList(pInt->pSerialList); | |
178 | + WacomFreeList(pInt->pUSBList); | |
179 | + free(pInt); | |
180 | +} | |
181 | + | |
182 | +int WacomGetSupportedDeviceList(unsigned int uDeviceClass, | |
183 | + WACOMDEVICEREC** ppList, int* pnSize) | |
184 | +{ | |
185 | + int nSerialCnt=0, nUSBCnt=0, nTotalBytes; | |
186 | + WACOMDEVICEREC* pSerial=NULL, *pUSB=NULL, *pList; | |
187 | + DEVICELIST_INTERNAL* pInt; | |
188 | + | |
189 | + if (!ppList || !pnSize) { errno = EINVAL; return 1; } | |
190 | + | |
191 | + /* get serial list */ | |
192 | + if (((!uDeviceClass) || (uDeviceClass == WACOMCLASS_SERIAL)) && | |
193 | + WacomGetSupportedSerialDeviceList(&pSerial, &nSerialCnt)) return 1; | |
194 | + | |
195 | + /* get usb list */ | |
196 | + if (((!uDeviceClass) || (uDeviceClass == WACOMCLASS_USB)) && | |
197 | + WacomGetSupportedUSBDeviceList(&pUSB, &nUSBCnt)) | |
198 | + { | |
199 | + if (pSerial) WacomFreeList(pSerial); | |
200 | + return 1; | |
201 | + } | |
202 | + | |
203 | + /* need memory for duplicate records and list internal structure */ | |
204 | + nTotalBytes = sizeof(WACOMDEVICEREC) * (nSerialCnt + nUSBCnt) + | |
205 | + sizeof(DEVICELIST_INTERNAL); | |
206 | + | |
207 | + /* allocate memory */ | |
208 | + pInt = (DEVICELIST_INTERNAL*)malloc(nTotalBytes); | |
209 | + | |
210 | + /* copy initial list pointers */ | |
211 | + pInt->pSerialList = pSerial; | |
212 | + pInt->pUSBList = pUSB; | |
213 | + pInt->pfnFree = FreeDeviceList; | |
214 | + | |
215 | + /* copy records */ | |
216 | + pList = (WACOMDEVICEREC*)(pInt + 1); | |
217 | + if (pSerial) | |
218 | + memcpy(pList,pSerial,sizeof(WACOMDEVICEREC) * nSerialCnt); | |
219 | + if (pUSB) | |
220 | + memcpy(pList + nSerialCnt, pUSB, sizeof(WACOMDEVICEREC) * nUSBCnt); | |
221 | + | |
222 | + *ppList = pList; | |
223 | + *pnSize = nSerialCnt + nUSBCnt; | |
224 | + | |
225 | + return 0; | |
226 | +} | |
227 | + | |
228 | +void WacomFreeList(void* pvList) | |
229 | +{ | |
230 | + FREEFUNC pfnFree; | |
231 | + if (!pvList) return; | |
232 | + pfnFree = ((FREEFUNC*)pvList)[-1]; | |
233 | + (*pfnFree)(pvList); | |
234 | +} | |
235 | + | |
236 | +unsigned int WacomGetClassFromName(const char* pszName) | |
237 | +{ | |
238 | + if (strcasecmp(pszName, "serial") == 0) | |
239 | + return WACOMCLASS_SERIAL; | |
240 | + else if (strcasecmp(pszName, "usb") == 0) | |
241 | + return WACOMCLASS_USB; | |
242 | + return 0; | |
243 | +} | |
244 | + | |
245 | +unsigned int WacomGetDeviceFromName(const char* pszName, | |
246 | + unsigned int uDeviceClass) | |
247 | +{ | |
248 | + unsigned int uDeviceType = 0; | |
249 | + | |
250 | + if (!uDeviceClass || (uDeviceClass == WACOMCLASS_SERIAL)) | |
251 | + { | |
252 | + uDeviceType = WacomGetSerialDeviceFromName(pszName); | |
253 | + if (uDeviceType) return uDeviceType; | |
254 | + } | |
255 | + | |
256 | + if (!uDeviceClass || (uDeviceClass == WACOMCLASS_USB)) | |
257 | + { | |
258 | + uDeviceType = WacomGetUSBDeviceFromName(pszName); | |
259 | + if (uDeviceType) return uDeviceType; | |
260 | + } | |
261 | + | |
262 | + errno = ENOENT; | |
263 | + return 0; | |
264 | +} | |
265 | + | |
266 | +static int WacomIsSerial(int fd) | |
267 | +{ | |
268 | + return isatty(fd); | |
269 | +} | |
270 | + | |
271 | +static int WacomIsUSB(int fd) | |
272 | +{ | |
273 | +#ifdef WCM_ENABLE_LINUXINPUT | |
274 | + short sID[4]; | |
275 | + if (ioctl(fd,EVIOCGID,sID) < 0) return 0; | |
276 | + return 1; | |
277 | +#else | |
278 | + return 0; | |
279 | +#endif | |
280 | +} | |
281 | + | |
282 | +WACOMTABLET WacomOpenTablet(WACOMENGINE hEngine, const char* pszDevice, | |
283 | + WACOMMODEL* pModel) | |
284 | +{ | |
285 | + int fd, e; | |
286 | + WACOMTABLET hTablet = NULL; | |
287 | + unsigned int uClass = pModel ? pModel->uClass : 0; | |
288 | + | |
289 | + /* open device for read/write access */ | |
290 | + fd = open(pszDevice,O_RDWR); | |
291 | + if (fd < 0) | |
292 | + { | |
293 | + e = errno; | |
294 | + WacomLog(hEngine,WACOMLOGLEVEL_ERROR,"Failed to open %s: %s", | |
295 | + pszDevice, strerror(errno)); | |
296 | + errno = e; | |
297 | + return NULL; | |
298 | + } | |
299 | + | |
300 | + /* configure serial */ | |
301 | + if ((!uClass || (uClass == WACOMCLASS_SERIAL)) && WacomIsSerial(fd)) | |
302 | + { | |
303 | + hTablet = WacomOpenSerialTablet(hEngine,fd,pModel); | |
304 | + if (!hTablet) { e=errno; close(fd); errno=e; return NULL; } | |
305 | + } | |
306 | + | |
307 | + /* configure usb */ | |
308 | + else if ((!uClass || (uClass == WACOMCLASS_USB)) && WacomIsUSB(fd)) | |
309 | + { | |
310 | + hTablet = WacomOpenUSBTablet(hEngine,fd,pModel); | |
311 | + if (!hTablet) { e=errno; close(fd); errno=e; return NULL; } | |
312 | + } | |
313 | + | |
314 | + /* give up */ | |
315 | + else | |
316 | + { | |
317 | + close(fd); | |
318 | + errno = EINVAL; | |
319 | + return NULL; | |
320 | + } | |
321 | + | |
322 | + return hTablet; | |
323 | +} | |
324 | + | |
325 | +int WacomCopyState(WACOMSTATE* pDest, WACOMSTATE* pSrc) | |
326 | +{ | |
327 | + unsigned int uCnt; | |
328 | + if (!pSrc || !pDest || !pSrc->uValueCnt || !pDest->uValueCnt) | |
329 | + { errno=EINVAL; return 1; } | |
330 | + | |
331 | + /* copy valid bits over */ | |
332 | + pDest->uValid = pSrc->uValid; | |
333 | + | |
334 | + /* determine how many values to transfer */ | |
335 | + uCnt = (pDest->uValueCnt < pSrc->uValueCnt) ? | |
336 | + pDest->uValueCnt : pSrc->uValueCnt; | |
337 | + | |
338 | + /* copy them over */ | |
339 | + memcpy(pDest->values,pSrc->values,uCnt*sizeof(WACOMVALUE)); | |
340 | + | |
341 | + return 0; | |
342 | +} | |
343 | + | |
344 | +/***************************************************************************** | |
345 | +** Virtual Functions | |
346 | +*****************************************************************************/ | |
347 | + | |
348 | +void WacomCloseTablet(WACOMTABLET hTablet) | |
349 | +{ | |
350 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
351 | + if (!pTablet || !pTablet->Close) return; | |
352 | + pTablet->Close(pTablet); | |
353 | +} | |
354 | + | |
355 | +WACOMMODEL WacomGetModel(WACOMTABLET hTablet) | |
356 | +{ | |
357 | + WACOMMODEL xBadModel = { 0 }; | |
358 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
359 | + if (!pTablet || !pTablet->GetModel) { errno=EBADF; return xBadModel; } | |
360 | + return pTablet->GetModel(pTablet); | |
361 | +} | |
362 | + | |
363 | +const char* WacomGetVendorName(WACOMTABLET hTablet) | |
364 | +{ | |
365 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
366 | + if (!pTablet || !pTablet->GetVendorName) { errno=EBADF; return NULL; } | |
367 | + return pTablet->GetVendorName(pTablet); | |
368 | +} | |
369 | + | |
370 | +const char* WacomGetClassName(WACOMTABLET hTablet) | |
371 | +{ | |
372 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
373 | + if (!pTablet || !pTablet->GetClassName) { errno=EBADF; return NULL; } | |
374 | + return pTablet->GetClassName(pTablet); | |
375 | +} | |
376 | + | |
377 | +const char* WacomGetDeviceName(WACOMTABLET hTablet) | |
378 | +{ | |
379 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
380 | + if (!pTablet || !pTablet->GetDeviceName) { errno=EBADF; return NULL; } | |
381 | + return pTablet->GetDeviceName(pTablet); | |
382 | +} | |
383 | + | |
384 | +const char* WacomGetSubTypeName(WACOMTABLET hTablet) | |
385 | +{ | |
386 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
387 | + if (!pTablet || !pTablet->GetSubTypeName) { errno=EBADF; return NULL; } | |
388 | + return pTablet->GetSubTypeName(pTablet); | |
389 | +} | |
390 | + | |
391 | +const char* WacomGetModelName(WACOMTABLET hTablet) | |
392 | +{ | |
393 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
394 | + if (!pTablet || !pTablet->GetModelName) { errno=EBADF; return NULL; } | |
395 | + return pTablet->GetModelName(pTablet); | |
396 | +} | |
397 | + | |
398 | +int WacomGetROMVersion(WACOMTABLET hTablet, int* pnMajor, int* pnMinor, | |
399 | + int* pnRelease) | |
400 | +{ | |
401 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
402 | + if (!pTablet || !pTablet->GetROMVer) { errno=EBADF; return 0; } | |
403 | + return pTablet->GetROMVer(pTablet,pnMajor,pnMinor,pnRelease); | |
404 | +} | |
405 | + | |
406 | +int WacomGetCapabilities(WACOMTABLET hTablet) | |
407 | +{ | |
408 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
409 | + if (!pTablet || !pTablet->GetCaps) { errno=EBADF; return 0; } | |
410 | + return pTablet->GetCaps(pTablet); | |
411 | +} | |
412 | + | |
413 | +int WacomGetState(WACOMTABLET hTablet, WACOMSTATE* pState) | |
414 | +{ | |
415 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
416 | + if (!pTablet || !pTablet->GetState) { errno=EBADF; return 0; } | |
417 | + return pTablet->GetState(pTablet,pState); | |
418 | +} | |
419 | + | |
420 | +int WacomGetFileDescriptor(WACOMTABLET hTablet) | |
421 | +{ | |
422 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
423 | + if (!pTablet || !pTablet->GetFD) { errno=EBADF; return -1; } | |
424 | + return pTablet->GetFD(pTablet); | |
425 | +} | |
426 | + | |
427 | +int WacomReadRaw(WACOMTABLET hTablet, unsigned char* puchData, | |
428 | + unsigned int uSize) | |
429 | +{ | |
430 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
431 | + if (!pTablet || !pTablet->ReadRaw) { errno=EBADF; return 0; } | |
432 | + return pTablet->ReadRaw(pTablet,puchData,uSize); | |
433 | +} | |
434 | + | |
435 | +int WacomParseData(WACOMTABLET hTablet, const unsigned char* puchData, | |
436 | + unsigned int uLength, WACOMSTATE* pState) | |
437 | +{ | |
438 | + WACOMTABLET_PRIV* pTablet = (WACOMTABLET_PRIV*)hTablet; | |
439 | + if (!pTablet || !pTablet->ParseData) { errno=EBADF; return 0; } | |
440 | + return pTablet->ParseData(pTablet,puchData,uLength,pState); | |
441 | +} |
@@ -0,0 +1,328 @@ | ||
1 | +/***************************************************************************** | |
2 | +** wactablet.h | |
3 | +** | |
4 | +** Copyright (C) 2002 - 2004 - John E. Joganic | |
5 | +** Copyright (C) 2003 - 2008 - Ping Cheng | |
6 | +** | |
7 | +** This program is free software; you can redistribute it and/or | |
8 | +** modify it under the terms of the GNU General Public License | |
9 | +** as published by the Free Software Foundation; either version 2 | |
10 | +** of the License, or (at your option) any later version. | |
11 | +** | |
12 | +** This program is distributed in the hope that it will be useful, | |
13 | +** but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | +** GNU General Public License for more details. | |
16 | +** | |
17 | +** You should have received a copy of the GNU General Public License | |
18 | +** along with this program; if not, write to the Free Software | |
19 | +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
20 | +** | |
21 | +****************************************************************************/ | |
22 | + | |
23 | +#ifndef __LINUXWACOM_WACTABLET_H | |
24 | +#define __LINUXWACOM_WACTABLET_H | |
25 | + | |
26 | +#include <stdarg.h> | |
27 | +#include <sys/time.h> | |
28 | + | |
29 | +#define WACOMVENDOR_UNKNOWN 0x0000 | |
30 | +#define WACOMVENDOR_WACOM 0x056A | |
31 | +#define WACOMVENDOR_TPC 0xFFFFFF01 | |
32 | + | |
33 | +#define WACOMCLASS_SERIAL 0x0001 | |
34 | +#define WACOMCLASS_USB 0x0002 | |
35 | + | |
36 | +#define WACOMDEVICE_UNKNOWN 0x0000 | |
37 | +#define WACOMDEVICE_ARTPAD 0x0001 | |
38 | +#define WACOMDEVICE_ARTPADII 0x0002 | |
39 | +#define WACOMDEVICE_DIGITIZER 0x0003 | |
40 | +#define WACOMDEVICE_DIGITIZERII 0x0004 | |
41 | +#define WACOMDEVICE_PENPARTNER 0x0005 | |
42 | +#define WACOMDEVICE_GRAPHIRE 0x0006 | |
43 | +#define WACOMDEVICE_GRAPHIRE2 0x0007 | |
44 | +#define WACOMDEVICE_GRAPHIRE3 0x0008 | |
45 | +#define WACOMDEVICE_GRAPHIRE4 0x0009 | |
46 | +#define WACOMDEVICE_INTUOS 0x000A | |
47 | +#define WACOMDEVICE_INTUOS2 0x000B | |
48 | +#define WACOMDEVICE_CINTIQ 0x000C | |
49 | +#define WACOMDEVICE_PTU 0x000D | |
50 | +#define WACOMDEVICE_VOLITO 0x000E | |
51 | +#define WACOMDEVICE_VOLITO2 0x000F | |
52 | +#define WACOMDEVICE_TPC 0x0010 | |
53 | +#define WACOMDEVICE_INTUOS3 0x0011 | |
54 | +#define WACOMDEVICE_CINTIQV5 0x0012 | |
55 | +#define WACOMDEVICE_MO 0x0013 | |
56 | + | |
57 | +typedef struct _WACOMMODEL WACOMMODEL; | |
58 | +struct _WACOMMODEL | |
59 | +{ | |
60 | + unsigned int uClass; | |
61 | + unsigned int uVendor; | |
62 | + unsigned int uDevice; | |
63 | + unsigned int uSubType; | |
64 | +}; | |
65 | + | |
66 | +#define WACOMTOOLTYPE_NONE 0x00 | |
67 | +#define WACOMTOOLTYPE_PEN 0x01 | |
68 | +#define WACOMTOOLTYPE_PENCIL 0x02 | |
69 | +#define WACOMTOOLTYPE_BRUSH 0x03 | |
70 | +#define WACOMTOOLTYPE_ERASER 0x04 | |
71 | +#define WACOMTOOLTYPE_AIRBRUSH 0x05 | |
72 | +#define WACOMTOOLTYPE_MOUSE 0x06 | |
73 | +#define WACOMTOOLTYPE_LENS 0x07 | |
74 | +#define WACOMTOOLTYPE_PAD 0x08 | |
75 | +#define WACOMTOOLTYPE_TOUCH 0x09 | |
76 | +#define WACOMTOOLTYPE_MAX 0x0A | |
77 | + | |
78 | +#define WACOMBUTTON_LEFT 0 | |
79 | +#define WACOMBUTTON_MIDDLE 1 | |
80 | +#define WACOMBUTTON_RIGHT 2 | |
81 | +#define WACOMBUTTON_EXTRA 3 | |
82 | +#define WACOMBUTTON_SIDE 4 | |
83 | +#define WACOMBUTTON_TOUCH 5 | |
84 | +#define WACOMBUTTON_STYLUS 6 | |
85 | +#define WACOMBUTTON_STYLUS2 7 | |
86 | +#define WACOMBUTTON_BT0 8 | |
87 | +#define WACOMBUTTON_BT1 9 | |
88 | +#define WACOMBUTTON_BT2 10 | |
89 | +#define WACOMBUTTON_BT3 11 | |
90 | +#define WACOMBUTTON_BT4 12 | |
91 | +#define WACOMBUTTON_BT5 13 | |
92 | +#define WACOMBUTTON_BT6 14 | |
93 | +#define WACOMBUTTON_BT7 15 | |
94 | +#define WACOMBUTTON_BT8 16 | |
95 | +#define WACOMBUTTON_BT9 17 | |
96 | +#define WACOMBUTTON_BT10 18 | |
97 | +#define WACOMBUTTON_BT11 19 | |
98 | +#define WACOMBUTTON_BT12 20 | |
99 | +#define WACOMBUTTON_BT13 21 | |
100 | +#define WACOMBUTTON_BT14 22 | |
101 | +#define WACOMBUTTON_BT15 23 | |
102 | +#define WACOMBUTTON_BT16 24 | |
103 | +#define WACOMBUTTON_BT17 25 | |
104 | +#define WACOMBUTTON_BT18 26 | |
105 | +#define WACOMBUTTON_BT19 27 | |
106 | +#define WACOMBUTTON_BT20 28 | |
107 | +#define WACOMBUTTON_BT21 29 | |
108 | +#define WACOMBUTTON_BT22 30 | |
109 | +#define WACOMBUTTON_BT23 31 | |
110 | +#define WACOMBUTTON_MAX 32 | |
111 | + | |
112 | +#define WACOMFIELD_TOOLTYPE 0 | |
113 | +#define WACOMFIELD_SERIAL 1 | |
114 | +#define WACOMFIELD_PROXIMITY 2 | |
115 | +#define WACOMFIELD_BUTTONS 3 | |
116 | +#define WACOMFIELD_POSITION_X 4 | |
117 | +#define WACOMFIELD_POSITION_Y 5 | |
118 | +#define WACOMFIELD_ROTATION_Z 6 | |
119 | +#define WACOMFIELD_DISTANCE 7 | |
120 | +#define WACOMFIELD_PRESSURE 8 | |
121 | +#define WACOMFIELD_TILT_X 9 | |
122 | +#define WACOMFIELD_TILT_Y 10 | |
123 | +#define WACOMFIELD_ABSWHEEL 11 | |
124 | +#define WACOMFIELD_RELWHEEL 12 | |
125 | +#define WACOMFIELD_THROTTLE 13 | |
126 | +#define WACOMFIELD_MAX 14 | |
127 | + | |
128 | +typedef struct | |
129 | +{ | |
130 | + int nValue; | |
131 | + int nMin; | |
132 | + int nMax; | |
133 | + int nReserved; | |
134 | +} WACOMVALUE; | |
135 | + | |
136 | +typedef struct | |
137 | +{ | |
138 | + unsigned int uValueCnt; /* This MUST be set to WACOMFIELD_MAX. */ | |
139 | + unsigned int uValid; /* Bit mask of WACOMFIELD_xxx bits. */ | |
140 | + WACOMVALUE values[WACOMFIELD_MAX]; | |
141 | +} WACOMSTATE; | |
142 | + | |
143 | +#define WACOMSTATE_INIT { WACOMFIELD_MAX } | |
144 | + | |
145 | +typedef struct | |
146 | +{ | |
147 | + const char* pszName; | |
148 | + const char* pszDesc; | |
149 | + unsigned int uDeviceClass; | |
150 | +} WACOMCLASSREC; | |
151 | + | |
152 | +typedef struct | |
153 | +{ | |
154 | + const char* pszName; | |
155 | + const char* pszDesc; | |
156 | + const char* pszVendorName; | |
157 | + const char* pszVendorDesc; | |
158 | + const char* pszClass; | |
159 | + WACOMMODEL model; | |
160 | +} WACOMDEVICEREC; | |
161 | + | |
162 | +typedef enum | |
163 | +{ | |
164 | + WACOMLOGLEVEL_NONE, | |
165 | + WACOMLOGLEVEL_CRITICAL, | |
166 | + WACOMLOGLEVEL_ERROR, | |
167 | + WACOMLOGLEVEL_WARN, | |
168 | + WACOMLOGLEVEL_INFO, | |
169 | + WACOMLOGLEVEL_DEBUG, | |
170 | + WACOMLOGLEVEL_TRACE, | |
171 | + WACOMLOGLEVEL_MAX | |
172 | +} WACOMLOGLEVEL; | |
173 | + | |
174 | +typedef void (*WACOMLOGFUNC)(struct timeval tv, WACOMLOGLEVEL level, | |
175 | + const char* pszLog); | |
176 | + | |
177 | +/***************************************************************************** | |
178 | +** Public structures | |
179 | +*****************************************************************************/ | |
180 | + | |
181 | +typedef struct { int __unused; } *WACOMENGINE; | |
182 | +typedef struct { int __unused; } *WACOMTABLET; | |
183 | + | |
184 | +/***************************************************************************** | |
185 | +** Public API | |
186 | +*****************************************************************************/ | |
187 | + | |
188 | + WACOMENGINE WacomInitEngine(void); | |
189 | + /* Initialize the tablet engine */ | |
190 | + | |
191 | + void WacomTermEngine(WACOMENGINE hEngine); | |
192 | + /* Shutdown the tablet engine */ | |
193 | + | |
194 | + void WacomSetLogFunc(WACOMENGINE hEngine, WACOMLOGFUNC pfnLog); | |
195 | + void WacomSetLogLevel(WACOMENGINE hEngine, WACOMLOGLEVEL level); | |
196 | + void WacomLogV(WACOMENGINE hEngine, WACOMLOGLEVEL level, | |
197 | + const char* pszFmt, va_list args); | |
198 | + void WacomLog(WACOMENGINE hEngine, WACOMLOGLEVEL level, | |
199 | + const char* pszFmt, ...); | |
200 | + /* Logging functions. Default log function does nothing. */ | |
201 | + | |
202 | + int WacomGetSupportedClassList(WACOMCLASSREC** ppList, int* pnSize); | |
203 | + /* Returns 0 on success. Pointer to class record list is returned to | |
204 | + * ppList, and size of list to pnSize. Use WacomFreeList to release. */ | |
205 | + | |
206 | + int WacomGetSupportedDeviceList(unsigned int uDeviceClass, | |
207 | + WACOMDEVICEREC** ppList, int* pnSize); | |
208 | + /* Returns 0 on success. If device class is specified, only devices | |
209 | + * of the request type are returned. A value of 0 will return all | |
210 | + * devices for all classes. Pointer to device record list is returned to | |
211 | + * ppList, and size of list to pnSize. Use WacomFreeList to release. */ | |
212 | + | |
213 | + void WacomFreeList(void* pvList); | |
214 | + /* Releases list memory. */ | |
215 | + | |
216 | + int WacomCopyState(WACOMSTATE* pDest, WACOMSTATE* pSrc); | |
217 | + /* Returns 0 on success. Copies tablet state structures. | |
218 | + * Source and destination structures must be properly initialized, | |
219 | + * particularly the uValueCnt field must be set WACOMFIELD_MAX. | |
220 | + * Returns 0 on success. */ | |
221 | + | |
222 | + unsigned int WacomGetClassFromName(const char* pszName); | |
223 | + /* Returns the device class for a given name. Returns 0, if unknown. */ | |
224 | + | |
225 | + unsigned int WacomGetDeviceFromName(const char* pszName, | |
226 | + unsigned int uDeviceClass); | |
227 | + /* Returns the device type for a given device name. If the | |
228 | + * device class is specified, only that class will be searched. | |
229 | + * Returns 0, if unknown. */ | |
230 | + | |
231 | + WACOMTABLET WacomOpenTablet(WACOMENGINE hEngine, | |
232 | + const char* pszDevice, WACOMMODEL* pModel); | |
233 | + /* Returns tablet handle on success, NULL otherwise. | |
234 | + * pszDevice is pathname to device. Model may be NULL; if any model | |
235 | + * parameters are 0, detection is automatic. */ | |
236 | + | |
237 | + void WacomCloseTablet(WACOMTABLET hTablet); | |
238 | + /* Releases all resource associated with tablet and closes device. */ | |
239 | + | |
240 | + WACOMMODEL WacomGetModel(WACOMTABLET hTablet); | |
241 | + /* Returns model (vendor, class, and device) of specified tablet. */ | |
242 | + | |
243 | + const char* WacomGetVendorName(WACOMTABLET hTablet); | |
244 | + /* Returns vendor name as human-readable string. String is valid as | |
245 | + * long as tablet handle is valid and does not need to be freed. */ | |
246 | + | |
247 | + const char* WacomGetClassName(WACOMTABLET hTablet); | |
248 | + /* Returns class name as human-readable string. String is valid as | |
249 | + * long as tablet handle is valid and does not need to be freed. */ | |
250 | + | |
251 | + const char* WacomGetDeviceName(WACOMTABLET hTablet); | |
252 | + /* Returns device name as human-readable string. String is valid as | |
253 | + * long as tablet handle is valid and does not need to be freed. */ | |
254 | + | |
255 | + const char* WacomGetSubTypeName(WACOMTABLET hTablet); | |
256 | + /* Returns subtype name as human-readable string. This is typically | |
257 | + * the model number (eg. ABC-1234). String is valid as long as tablet | |
258 | + * handle is valid and does not need to be freed. */ | |
259 | + | |
260 | + const char* WacomGetModelName(WACOMTABLET hTablet); | |
261 | + /* Returns model name as human-readable string. This is typically | |
262 | + * the full model name (eg. FooCo FooModel 9x12). String is valid as | |
263 | + * long as tablet handle is valid and does not need to be freed. */ | |
264 | + | |
265 | + int WacomGetROMVersion(WACOMTABLET hTablet, int* pnMajor, int* pnMinor, | |
266 | + int* pnRelease); | |
267 | + /* Returns 0 on success. ROM version of the tablet firmware is returned | |
268 | + * in major, minor, and release values. If the caller needs to | |
269 | + * distinguish between ROM versions to work around a bug, please consider | |
270 | + * submitting a patch to this library. All tablets should appear | |
271 | + * equivalent through this interface. This call is provided for | |
272 | + * information purposes. */ | |
273 | + | |
274 | + int WacomGetCapabilities(WACOMTABLET hTablet); | |
275 | + /* Returns bitmask of valid fields. Use (1 << WACOMFIELD_xxx) to | |
276 | + * translate field identifiers to bit positions. This API will only | |
277 | + * support 32 capabilities. */ | |
278 | + | |
279 | + int WacomGetState(WACOMTABLET hTablet, WACOMSTATE* pState); | |
280 | + /* Returns 0 on success. Tablet state is copied to specified structure. | |
281 | + * Data is only a snapshot of the device and may not accurately reflect | |
282 | + * the position of any specific tool. This is particularly true of | |
283 | + * multi-tool mode tablets. NOTE: pState must point to an initialized | |
284 | + * structure; the uValueCnt field must be correctly set to | |
285 | + * WACOMFIELD_MAX. */ | |
286 | + | |
287 | + int WacomGetFileDescriptor(WACOMTABLET hTablet); | |
288 | + /* Returns the file descriptor of the tablet or -1 on error. */ | |
289 | + | |
290 | + int WacomReadRaw(WACOMTABLET hTablet, unsigned char* puchData, | |
291 | + unsigned int uSize); | |
292 | + /* Returns number of bytes read, typically a single packet. Call will | |
293 | + * block. uSize should be the maximum size of the given data buffer. */ | |
294 | + | |
295 | + int WacomParseData(WACOMTABLET hTablet, const unsigned char* puchData, | |
296 | + unsigned int uLength, WACOMSTATE* pState); | |
297 | + /* Updates the tablet state from a given packet. If pState is specified, | |
298 | + * the tablet state is copied to the given structure. This structure | |
299 | + * must be correctly initialized; the uValueCnt member must be set to | |
300 | + * WACOMFIELD_MAX. Returns 0 on success. */ | |
301 | + | |
302 | +/***************************************************************************** | |
303 | +** Private structures | |
304 | +*****************************************************************************/ | |
305 | + | |
306 | +typedef struct _WACOMTABLET_PRIV WACOMTABLET_PRIV; | |
307 | + | |
308 | +struct _WACOMTABLET_PRIV | |
309 | +{ | |
310 | + void (*Close)(WACOMTABLET_PRIV* pTablet); | |
311 | + WACOMMODEL (*GetModel)(WACOMTABLET_PRIV* pTablet); | |
312 | + const char* (*GetVendorName)(WACOMTABLET_PRIV* pTablet); | |
313 | + const char* (*GetClassName)(WACOMTABLET_PRIV* pTablet); | |
314 | + const char* (*GetDeviceName)(WACOMTABLET_PRIV* pTablet); | |
315 | + const char* (*GetSubTypeName)(WACOMTABLET_PRIV* pTablet); | |
316 | + const char* (*GetModelName)(WACOMTABLET_PRIV* pTablet); | |
317 | + int (*GetROMVer)(WACOMTABLET_PRIV* pTablet, int* pnMajor, int* pnMinor, | |
318 | + int* pnRelease); | |
319 | + int (*GetCaps)(WACOMTABLET_PRIV* pTablet); | |
320 | + int (*GetState)(WACOMTABLET_PRIV* pTablet, WACOMSTATE* pState); | |
321 | + int (*GetFD)(WACOMTABLET_PRIV* pTablet); | |
322 | + int (*ReadRaw)(WACOMTABLET_PRIV* pTablet, unsigned char* puchData, | |
323 | + unsigned int uSize); | |
324 | + int (*ParseData)(WACOMTABLET_PRIV* pTablet, const unsigned char* puchData, | |
325 | + unsigned int uLength, WACOMSTATE* pState); | |
326 | +}; | |
327 | + | |
328 | +#endif /* __LINUXWACOM_WACTABLET_H */ |
@@ -0,0 +1,1054 @@ | ||
1 | +/***************************************************************************** | |
2 | +** wacusb.c | |
3 | +** | |
4 | +** Copyright (C) 2002 - 2004 - John E. Joganic | |
5 | +** Copyright (C) 2003 - 2009 - Ping Cheng | |
6 | +** | |
7 | +** This program is free software; you can redistribute it and/or | |
8 | +** modify it under the terms of the GNU General Public License | |
9 | +** as published by the Free Software Foundation; either version 2 | |
10 | +** of the License, or (at your option) any later version. | |
11 | +** | |
12 | +** This program is distributed in the hope that it will be useful, | |
13 | +** but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | +** GNU General Public License for more details. | |
16 | +** | |
17 | +** You should have received a copy of the GNU General Public License | |
18 | +** along with this program; if not, write to the Free Software | |
19 | +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
20 | +** | |
21 | +****************************************************************************/ | |
22 | + | |
23 | +#include "wacusb.h" | |
24 | + | |
25 | +#include <stdio.h> | |
26 | +#include <stdlib.h> | |
27 | +#include <string.h> | |
28 | +#include <errno.h> | |
29 | +#include <fcntl.h> | |
30 | +#include <unistd.h> | |
31 | +#include <sys/ioctl.h> | |
32 | +#include <assert.h> | |
33 | + | |
34 | +/***************************************************************************** | |
35 | +** Begin USB Linux Input Subsystem | |
36 | +*****************************************************************************/ | |
37 | + | |
38 | +#ifdef WCM_ENABLE_LINUXINPUT | |
39 | +#include <linux/input.h> | |
40 | + | |
41 | +#ifndef EV_MSC | |
42 | +#define EV_MSC 0x04 | |
43 | +#endif | |
44 | + | |
45 | +#ifndef MSC_SERIAL | |
46 | +#define MSC_SERIAL 0x00 | |
47 | +#endif | |
48 | + | |
49 | +#ifndef BTN_TOOL_DOUBLETAP | |
50 | +#define BTN_TOOL_DOUBLETAP 0x14d | |
51 | +#endif | |
52 | + | |
53 | +/***************************************************************************** | |
54 | +** Defines | |
55 | +*****************************************************************************/ | |
56 | + | |
57 | +/* from linux/input.h */ | |
58 | +#define BITS_PER_LONG (sizeof(long) * 8) | |
59 | +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) | |
60 | +#define BIT(x) (1UL<<((x)%BITS_PER_LONG)) | |
61 | +#define LONG(x) ((x)/BITS_PER_LONG) | |
62 | +#define ISBITSET(x,y) ((x)[LONG(y)] & BIT(y)) | |
63 | + | |
64 | +#define MAX_CHANNELS 2 | |
65 | +#define MAX_USB_EVENTS 32 | |
66 | + | |
67 | +/***************************************************************************** | |
68 | +** Structures | |
69 | +*****************************************************************************/ | |
70 | + | |
71 | +typedef struct _USBSUBTYPE USBSUBTYPE; | |
72 | +typedef struct _USBDEVICE USBDEVICE; | |
73 | +typedef struct _USBVENDOR USBVENDOR; | |
74 | + | |
75 | +struct _USBSUBTYPE | |
76 | +{ | |
77 | + const char* pszName; | |
78 | + const char* pszDesc; | |
79 | + unsigned int uSubType; | |
80 | + unsigned int uProduct; | |
81 | +}; | |
82 | + | |
83 | +struct _USBDEVICE | |
84 | +{ | |
85 | + const char* pszName; | |
86 | + const char* pszDesc; | |
87 | + unsigned int uDevice; | |
88 | + USBSUBTYPE* pSubTypes; | |
89 | + unsigned int uChannelCnt; | |
90 | +}; | |
91 | + | |
92 | +struct _USBVENDOR | |
93 | +{ | |
94 | + const char* pszName; | |
95 | + const char* pszDesc; | |
96 | + unsigned int uVendor; | |
97 | + USBDEVICE* pDevices; | |
98 | +}; | |
99 | + | |
100 | +/***************************************************************************** | |
101 | +** USB Tablet object | |
102 | +*****************************************************************************/ | |
103 | + | |
104 | +typedef struct _USBTABLET USBTABLET; | |
105 | + | |
106 | +struct _USBTABLET | |
107 | +{ | |
108 | + WACOMTABLET_PRIV tablet; | |
109 | + WACOMENGINE hEngine; | |
110 | + int fd; | |
111 | + USBVENDOR* pVendor; | |
112 | + USBDEVICE* pDevice; | |
113 | + USBSUBTYPE* pSubType; | |
114 | + int nBus; | |
115 | + int nVersion; | |
116 | + WACOMSTATE state[MAX_CHANNELS]; | |
117 | + int nToolProx[MAX_CHANNELS]; | |
118 | + int nChannel; | |
119 | + int nLastToolSerial; | |
120 | + int nEventCnt; | |
121 | + struct input_event events[MAX_USB_EVENTS]; | |
122 | +}; | |
123 | + | |
124 | +/***************************************************************************** | |
125 | +** Autodetect pad key codes | |
126 | +*****************************************************************************/ | |
127 | + | |
128 | +static unsigned short padkey_codes [] = | |
129 | +{ | |
130 | + BTN_0, BTN_1, BTN_2, BTN_3, BTN_4, | |
131 | + BTN_5, BTN_6, BTN_7, BTN_8, BTN_9, | |
132 | + BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, | |
133 | + BTN_BASE, BTN_BASE2, BTN_BASE3, | |
134 | + BTN_BASE4, BTN_BASE5, BTN_BASE6, | |
135 | + BTN_TL, BTN_TR, BTN_TL2, BTN_TR2, BTN_SELECT | |
136 | +}; | |
137 | + | |
138 | +int gPadKeys [WACOMBUTTON_MAX]; | |
139 | +int gNumPadKeys; | |
140 | + | |
141 | +/***************************************************************************** | |
142 | +** Static Prototypes | |
143 | +*****************************************************************************/ | |
144 | + | |
145 | +static void USBClose(WACOMTABLET_PRIV* pTablet); | |
146 | +static WACOMMODEL USBGetModel(WACOMTABLET_PRIV* pTablet); | |
147 | +static const char* USBGetVendorName(WACOMTABLET_PRIV* pTablet); | |
148 | +static const char* USBGetClassName(WACOMTABLET_PRIV* pTablet); | |
149 | +static const char* USBGetDeviceName(WACOMTABLET_PRIV* pTablet); | |
150 | +static const char* USBGetSubTypeName(WACOMTABLET_PRIV* pTablet); | |
151 | +static const char* USBGetModelName(WACOMTABLET_PRIV* pTablet); | |
152 | +static int USBGetROMVer(WACOMTABLET_PRIV* pTablet, int* pnMajor, | |
153 | + int* pnMinor, int* pnRelease); | |
154 | +static int USBGetCaps(WACOMTABLET_PRIV* pTablet); | |
155 | +static int USBGetState(WACOMTABLET_PRIV* pTablet, WACOMSTATE* pState); | |
156 | +static int USBGetFD(WACOMTABLET_PRIV* pTablet); | |
157 | +static int USBReadRaw(WACOMTABLET_PRIV* pTablet, unsigned char* puchData, | |
158 | + unsigned int uSize); | |
159 | +static int USBParseData(WACOMTABLET_PRIV* pTablet, | |
160 | + const unsigned char* puchData, unsigned int uLength, | |
161 | + WACOMSTATE* pState); | |
162 | + | |
163 | +static int USBFindModel(USBTABLET* pUSB, unsigned int uVendor, | |
164 | + unsigned int uProduct); | |
165 | +static int USBIdentifyModel(USBTABLET* pUSB); | |
166 | + | |
167 | +/***************************************************************************** | |
168 | +** Globals | |
169 | +*****************************************************************************/ | |
170 | + | |
171 | + static USBSUBTYPE xPenPartner[] = | |
172 | + { | |
173 | + { "MODEL_PP_0405", "Wacom PenPartner", 1, 0x00 }, | |
174 | + { NULL } | |
175 | + }; | |
176 | + | |
177 | + static USBSUBTYPE xGraphire[] = | |
178 | + { | |
179 | + { "ET_0405", "Wacom Graphire", 1, 0x10 }, | |
180 | + { NULL } | |
181 | + }; | |
182 | + | |
183 | + static USBSUBTYPE xGraphire2[] = | |
184 | + { | |
185 | + { "ET_0405", "Wacom Graphire2 4x5", 1, 0x11 }, | |
186 | + { "ET_0507", "Wacom Graphire2 5x7", 2, 0x12 }, | |
187 | + { NULL } | |
188 | + }; | |
189 | + | |
190 | + static USBSUBTYPE xGraphire3[] = | |
191 | + { | |
192 | + { "ET_0405", "Wacom Graphire3 4x5", 1, 0x13 }, | |
193 | + { "ET_0608", "Wacom Graphire3 6x8", 2, 0x14 }, | |
194 | + { NULL } | |
195 | + }; | |
196 | + | |
197 | + static USBSUBTYPE xGraphire4[] = | |
198 | + { | |
199 | + { "CTE_440", "Wacom Graphire4 4x5", 1, 0x15 }, | |
200 | + { "CTE_640", "Wacom Graphire4 6x8", 2, 0x16 }, | |
201 | + { NULL } | |
202 | + }; | |
203 | + | |
204 | + static USBSUBTYPE xIntuos[] = | |
205 | + { | |
206 | + { "GD_0405-U", "Wacom Intuos 4x5", 1, 0x20 }, | |
207 | + { "GD_0608-U", "Wacom Intuos 6x8", 2, 0x21 }, | |
208 | + { "GD_0912-U", "Wacom Intuos 9x12", 3, 0x22 }, | |
209 | + { "GD_1212-U", "Wacom Intuos 12x12", 4, 0x23 }, | |
210 | + { "GD_1218-U", "Wacom Intuos 12x18", 5, 0x24 }, | |
211 | + { NULL } | |
212 | + }; | |
213 | + | |
214 | + static USBSUBTYPE xCintiq[] = | |
215 | + { | |
216 | + { "PL400", "Wacom PL400", 1, 0x30 }, | |
217 | + { "PL500", "Wacom PL500", 2, 0x31 }, | |
218 | + { "PL600", "Wacom PL600", 3, 0x32 }, | |
219 | + { "PL600SX", "Wacom PL600SX", 4, 0x33 }, | |
220 | + { "PL550", "Wacom PL550", 5, 0x34 }, | |
221 | + { "PL800", "Wacom PL800", 6, 0x35 }, | |
222 | + { "PL700", "Wacom PL700", 7, 0x37 }, | |
223 | + { "PL510", "Wacom PL510", 8, 0x38 }, | |
224 | + { "DTU710", "Wacom PL710", 9, 0x39 }, | |
225 | + { "DTF720", "Wacom DTF720", 10, 0xC0 }, | |
226 | + { "DTF521", "Wacom DTF521", 11, 0xC4 }, | |
227 | + { "DTU1931", "Wacom DTU1931", 12, 0xC7 }, | |
228 | + { "DTU2231", "Wacom DTU2231", 13, 0xCE }, | |
229 | + { "DTU1631", "Wacom DTU1631", 14, 0xF0 }, | |
230 | + { NULL } | |
231 | + }; | |
232 | + | |
233 | + static USBSUBTYPE xIntuos2[] = | |
234 | + { | |
235 | + { "XD-0405-U", "Wacom Intuos2 4x5", 1, 0x41 }, | |
236 | + { "XD-0608-U", "Wacom Intuos2 6x8", 2, 0x42 }, | |
237 | + { "XD-0912-U", "Wacom Intuos2 9x12", 3, 0x43 }, | |
238 | + { "XD-1212-U", "Wacom Intuos2 12x12", 4, 0x44 }, | |
239 | + { "XD-1218-U", "Wacom Intuos2 12x18", 5, 0x45 }, | |
240 | + | |
241 | + /* fix for I2 6x8's reporting as 0x47 */ | |
242 | + { "XD-0608-U", "Wacom Intuos2 6x8", 2, 0x47 }, | |
243 | + | |
244 | + { NULL } | |
245 | + }; | |
246 | + | |
247 | + static USBSUBTYPE xVolito[] = | |
248 | + { | |
249 | + { "MODEL-VOL", "Wacom Volito", 1, 0x60 }, | |
250 | + { NULL } | |
251 | + }; | |
252 | + | |
253 | + static USBSUBTYPE xVolito2[] = | |
254 | + { | |
255 | + { "FT-0203-U", "Wacom PenStation", 1, 0x61 }, | |
256 | + { "CTF-420-U", "Wacom Volito2 4x5", 2, 0x62 }, | |
257 | + { "CTF-220-U", "Wacom Volito2 2x3", 3, 0x63 }, | |
258 | + { "CTF-421-U", "Wacom PenPartner2", 4, 0x64 }, | |
259 | + { "CTF_430-U", "Wacom Bamboo1", 5, 0x69 }, | |
260 | + { NULL } | |
261 | + }; | |
262 | + | |
263 | + static USBSUBTYPE xBamboo[] = | |
264 | + { | |
265 | + { "MTE_450", "Wacom Bamboo", 1, 0x65 }, | |
266 | + { "CTE_450", "Wacom BambooFun 4x5", 2, 0x17 }, | |
267 | + { "CTE_650", "Wacom BambooFun 6x8", 3, 0x18 }, | |
268 | + { "CTE_631", "Wacom Bamboo1 Medium", 4, 0x19 }, | |
269 | + { NULL } | |
270 | + }; | |
271 | + | |
272 | + static USBSUBTYPE xCintiqPartner[] = | |
273 | + { | |
274 | + { "PTU-600", "Wacom Cintiq Partner", 1, 0x03 }, | |
275 | + { NULL } | |
276 | + }; | |
277 | + | |
278 | + static USBSUBTYPE xTabletPC[] = | |
279 | + { | |
280 | + { "TPC-090", "Wacom Tablet PC90", 1, 0x90 }, | |
281 | + { "TPC-093", "Wacom Tablet PC93", 2, 0x93 }, | |
282 | + { "TPC-09A", "Wacom Tablet PC9A", 3, 0x9A }, | |
283 | + { NULL } | |
284 | + }; | |
285 | + | |
286 | + static USBSUBTYPE xCintiqV5[] = | |
287 | + { | |
288 | + { "DTZ-21ux", "Wacom Cintiq 21UX", 1, 0x3F }, | |
289 | + { "DTZ-20wsx", "Wacom Cintiq 20WSX", 2, 0xC5 }, | |
290 | + { "DTZ-12wx", "Wacom Cintiq 12WX", 3, 0xC6 }, | |
291 | + { "DTZ-21ux2", "Wacom Cintiq 21UX2", 4, 0xCC }, | |
292 | + { NULL } | |
293 | + }; | |
294 | + | |
295 | + static USBSUBTYPE xIntuos3[] = | |
296 | + { | |
297 | + { "PTZ-430", "Wacom Intuos3 4x5", 1, 0xB0 }, | |
298 | + { "PTZ-630", "Wacom Intuos3 6x8", 2, 0xB1 }, | |
299 | + { "PTZ-930", "Wacom Intuos3 9x12", 3, 0xB2 }, | |
300 | + { "PTZ-1230", "Wacom Intuos3 12x12", 4, 0xB3 }, | |
301 | + { "PTZ-1231W", "Wacom Intuos3 12x19", 5, 0xB4 }, | |
302 | + { "PTZ-631W", "Wacom Intuos3 6x11", 6, 0xB5 }, | |
303 | + { "PTZ-431W", "Wacom Intuos3 4x6", 7, 0xB7 }, | |
304 | + { NULL } | |
305 | + }; | |
306 | + | |
307 | + static USBSUBTYPE xIntuos4[] = | |
308 | + { | |
309 | + { "PTK-440", "Wacom Intuos4 4x6", 1, 0xB8 }, | |
310 | + { "PTK-640", "Wacom Intuos4 6x9", 2, 0xB9 }, | |
311 | + { "PTK-840", "Wacom Intuos4 8x13", 3, 0xBA }, | |
312 | + { "PTK-1240", "Wacom Intuos4 12x19", 4, 0xBB }, | |
313 | + { "PTK-540WL", "Wacom Intuos4 WLUSB", 5, 0xBC }, | |
314 | + { "PTK-540WL", "Wacom Intuos4 WLBT", 6, 0xBD }, | |
315 | + { NULL } | |
316 | + }; | |
317 | + | |
318 | + static USBDEVICE xWacomDevices[] = | |
319 | + { | |
320 | + { "pp", "PenPartner", WACOMDEVICE_PENPARTNER, xPenPartner, 1 }, | |
321 | + { "gr", "Graphire", WACOMDEVICE_GRAPHIRE, xGraphire, 1 }, | |
322 | + { "gr2", "Graphire2", WACOMDEVICE_GRAPHIRE2, xGraphire2, 1 }, | |
323 | + { "gr3", "Graphire3", WACOMDEVICE_GRAPHIRE3, xGraphire3, 1 }, | |
324 | + { "gr4", "Graphire4", WACOMDEVICE_GRAPHIRE4, xGraphire4, 2 }, | |
325 | + { "int", "Intuos", WACOMDEVICE_INTUOS, xIntuos, 2 }, | |
326 | + { "int2", "Intuos2", WACOMDEVICE_INTUOS2, xIntuos2, 2 }, | |
327 | + { "int3", "Intuos3", WACOMDEVICE_INTUOS3, xIntuos3, 2 }, | |
328 | + { "int4", "Intuos4", WACOMDEVICE_INTUOS3, xIntuos4, 2 }, | |
329 | + { "ctq", "Cintiq (V5)", WACOMDEVICE_CINTIQV5, xCintiqV5, 2 }, | |
330 | + { "pl", "Cintiq (PL)", WACOMDEVICE_CINTIQ, xCintiq, 1 }, | |
331 | + { "ptu", "Cintiq Partner (PTU)", WACOMDEVICE_PTU, xCintiqPartner, 1 }, | |
332 | + { "vol", "Volito", WACOMDEVICE_VOLITO, xVolito, 1 }, | |
333 | + { "vol2", "Volito2", WACOMDEVICE_VOLITO2, xVolito2, 1 }, | |
334 | + { "mo", "Bamboo", WACOMDEVICE_MO, xBamboo, 2 }, | |
335 | + { "tpc", "Tablet PC", WACOMDEVICE_TPC, xTabletPC, 1 }, | |
336 | + { NULL } | |
337 | + }; | |
338 | + | |
339 | + static USBVENDOR xVendors[] = | |
340 | + { | |
341 | + { "wacom", "Wacom", WACOMVENDOR_WACOM, xWacomDevices }, | |
342 | + { NULL }, | |
343 | + }; | |
344 | + | |
345 | +/***************************************************************************** | |
346 | +** Public Functions | |
347 | +*****************************************************************************/ | |
348 | + | |
349 | +typedef struct | |
350 | +{ | |
351 | + void (*pfnFree)(void* pv); | |
352 | +} DEVLIST_INTERNAL; | |
353 | + | |
354 | +static void USBFreeDeviceList(void* pv) | |
355 | +{ | |
356 | + DEVLIST_INTERNAL* pInt = ((DEVLIST_INTERNAL*)pv) - 1; | |
357 | + free(pInt); | |
358 | +} | |
359 | + | |
360 | +int WacomGetSupportedUSBDeviceList(WACOMDEVICEREC** ppList, int* pnSize) | |
361 | +{ | |
362 | + int nIndex=0, nCnt=0; | |
363 | + DEVLIST_INTERNAL* pInt; | |
364 | + USBDEVICE* pDev; | |
365 | + USBVENDOR* pVendor; | |
366 | + WACOMDEVICEREC* pRec; | |
367 | + | |
368 | + if (!ppList || !pnSize) { errno = EINVAL; return 1; } | |
369 | + | |
370 | + /* for each vendor, count up devices */ | |
371 | + for (pVendor=xVendors; pVendor->pszName; ++pVendor) | |
372 | + { | |
373 | + /* count up devices */ | |
374 | + for (pDev=pVendor->pDevices; pDev->pszName; ++pDev, ++nCnt) ; | |
375 | + } | |
376 | + | |
377 | + /* allocate enough memory to hold internal structure and all records */ | |
378 | + pInt = (DEVLIST_INTERNAL*)malloc(sizeof(DEVLIST_INTERNAL) + | |
379 | + (sizeof(WACOMDEVICEREC) * nCnt)); | |
380 | + | |
381 | + pInt->pfnFree = USBFreeDeviceList; | |
382 | + pRec = (WACOMDEVICEREC*)(pInt + 1); | |
383 | + | |
384 | + /* for each vendor, add devices */ | |
385 | + for (pVendor=xVendors; pVendor->pszName; ++pVendor) | |
386 | + { | |
387 | + for (pDev=pVendor->pDevices; pDev->pszName; ++pDev, ++nIndex) | |
388 | + { | |
389 | + pRec[nIndex].pszName = pDev->pszName; | |
390 | + pRec[nIndex].pszDesc = pDev->pszDesc; | |
391 | + pRec[nIndex].pszVendorName = pVendor->pszName; | |
392 | + pRec[nIndex].pszVendorDesc = pVendor->pszDesc; | |
393 | + pRec[nIndex].pszClass = "usb"; | |
394 | + pRec[nIndex].model.uClass = WACOMCLASS_USB; | |
395 | + pRec[nIndex].model.uVendor = pVendor->uVendor; | |
396 | + pRec[nIndex].model.uDevice = pDev->uDevice; | |
397 | + pRec[nIndex].model.uSubType = 0; | |
398 | + } | |
399 | + } | |
400 | + assert(nIndex == nCnt); | |
401 | + | |
402 | + *ppList = pRec; | |
403 | + *pnSize = nCnt; | |
404 | + return 0; | |
405 | +} | |
406 | + | |
407 | +unsigned int WacomGetUSBDeviceFromName(const char* pszName) | |
408 | +{ | |
409 | + USBDEVICE* pDev; | |
410 | + USBVENDOR* pVendor; | |
411 | + | |
412 | + if (!pszName) { errno = EINVAL; return 0; } | |
413 | + | |
414 | + /* for each vendor, look for device */ | |
415 | + for (pVendor=xVendors; pVendor->pszName; ++pVendor) | |
416 | + { | |
417 | + /* count up devices */ | |
418 | + for (pDev=pVendor->pDevices; pDev->pszName; ++pDev) | |
419 | + { | |
420 | + if (strcasecmp(pszName,pDev->pszName) == 0) | |
421 | + return pDev->uDevice; | |
422 | + } | |
423 | + } | |
424 | + | |
425 | + errno = ENOENT; | |
426 | + return 0; | |
427 | +} | |
428 | + | |
429 | +WACOMTABLET WacomOpenUSBTablet(WACOMENGINE hEngine, int fd, WACOMMODEL* pModel) | |
430 | +{ | |
431 | + USBTABLET* pUSB = NULL; | |
432 | + | |
433 | + /* Allocate tablet */ | |
434 | + pUSB = (USBTABLET*)malloc(sizeof(USBTABLET)); | |
435 | + memset(pUSB,0,sizeof(*pUSB)); | |
436 | + pUSB->tablet.Close = USBClose; | |
437 | + pUSB->tablet.GetModel = USBGetModel; | |
438 | + pUSB->tablet.GetVendorName = USBGetVendorName; | |
439 | + pUSB->tablet.GetClassName = USBGetClassName; | |
440 | + pUSB->tablet.GetDeviceName = USBGetDeviceName; | |
441 | + pUSB->tablet.GetSubTypeName = USBGetSubTypeName; | |
442 | + pUSB->tablet.GetModelName = USBGetModelName; | |
443 | + pUSB->tablet.GetROMVer = USBGetROMVer; | |
444 | + pUSB->tablet.GetCaps = USBGetCaps; | |
445 | + pUSB->tablet.GetState = USBGetState; | |
446 | + pUSB->tablet.GetFD = USBGetFD; | |
447 | + pUSB->tablet.ReadRaw = USBReadRaw; | |
448 | + pUSB->tablet.ParseData = USBParseData; | |
449 | + pUSB->hEngine = hEngine; | |
450 | + pUSB->fd = fd; | |
451 | + | |
452 | + /* Identify and initialize the model */ | |
453 | + if (USBIdentifyModel(pUSB)) | |
454 | + { | |
455 | + WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR, | |
456 | + "Failed to identify model: %s",strerror(errno)); | |
457 | + free(pUSB); | |
458 | + return NULL; | |
459 | + } | |
460 | + | |
461 | + return (WACOMTABLET)pUSB; | |
462 | +} | |
463 | + | |
464 | +/***************************************************************************** | |
465 | +** Tablet Functions | |
466 | +*****************************************************************************/ | |
467 | + | |
468 | +static void USBClose(WACOMTABLET_PRIV* pTablet) | |
469 | +{ | |
470 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
471 | + close(pUSB->fd); | |
472 | + free(pUSB); | |
473 | +} | |
474 | + | |
475 | +static WACOMMODEL USBGetModel(WACOMTABLET_PRIV* pTablet) | |
476 | +{ | |
477 | + WACOMMODEL model = { 0 }; | |
478 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
479 | + model.uClass = WACOMCLASS_USB; | |
480 | + model.uVendor = pUSB->pVendor->uVendor; | |
481 | + model.uDevice = pUSB->pDevice->uDevice; | |
482 | + model.uSubType = pUSB->pSubType->uSubType; | |
483 | + return model; | |
484 | +} | |
485 | + | |
486 | +static int USBGetRange(USBTABLET* pUSB, unsigned long* pBits, int nAbsField, | |
487 | + unsigned int uField) | |
488 | +{ | |
489 | + int nAbs[5]; | |
490 | + | |
491 | + if (!ISBITSET(pBits,nAbsField)) | |
492 | + return 0; | |
493 | + if (ioctl(pUSB->fd, EVIOCGABS(nAbsField), nAbs) != 0) | |
494 | + return 1; | |
495 | + | |
496 | + pUSB->state[0].values[uField].nMin = nAbs[1]; | |
497 | + pUSB->state[0].values[uField].nMax = nAbs[2]; | |
498 | + pUSB->state[0].uValid |= BIT(uField); | |
499 | + return 0; | |
500 | +} | |
501 | + | |
502 | +static int USBFindModel(USBTABLET* pUSB, unsigned int uVendor, | |
503 | + unsigned int uProduct) | |
504 | +{ | |
505 | + USBVENDOR* pVendor = NULL; | |
506 | + USBDEVICE* pDev = NULL; | |
507 | + USBSUBTYPE* pSub = NULL; | |
508 | + | |
509 | + static USBSUBTYPE xUnkSub[] = | |
510 | + { | |
511 | + { "UNKNOWN", "Unknown", 1, 0x00 }, | |
512 | + { NULL } | |
513 | + }; | |
514 | + | |
515 | + static USBDEVICE xUnkDev[] = | |
516 | + { | |
517 | + { "unk", "Unknown", WACOMDEVICE_UNKNOWN, xUnkSub, MAX_CHANNELS }, | |
518 | + { NULL } | |
519 | + }; | |
520 | + | |
521 | + static USBVENDOR xUnkVendor = | |
522 | + { "unknown", "Unknown", WACOMVENDOR_UNKNOWN, xUnkDev }; | |
523 | + | |
524 | + for (pVendor=xVendors; pVendor->pszName; ++pVendor) | |
525 | + { | |
526 | + if (pVendor->uVendor == uVendor) | |
527 | + { | |
528 | + for (pDev=pVendor->pDevices; pDev->pszName; ++pDev) | |
529 | + { | |
530 | + for (pSub=pDev->pSubTypes; pSub->pszName; ++pSub) | |
531 | + { | |
532 | + if (pSub->uProduct == uProduct) | |
533 | + { | |
534 | + pUSB->pVendor = pVendor; | |
535 | + pUSB->pDevice = pDev; | |
536 | + pUSB->pSubType = pSub; | |
537 | + return 0; | |
538 | + } | |
539 | + } | |
540 | + } | |
541 | + | |
542 | + /* only one vendor of this type, so we're done */ | |
543 | + break; | |
544 | + } | |
545 | + } | |
546 | + | |
547 | + /* unknown vendor */ | |
548 | + pUSB->pVendor = &xUnkVendor; | |
549 | + pUSB->pDevice = pUSB->pVendor->pDevices; | |
550 | + pUSB->pSubType = pUSB->pDevice->pSubTypes; | |
551 | + return 0; | |
552 | +} | |
553 | + | |
554 | +static int USBIdentifyModel(USBTABLET* pUSB) | |
555 | +{ | |
556 | + int nCnt, chcnt; | |
557 | + short sID[4]; | |
558 | + unsigned int uVendor, uProduct; | |
559 | + unsigned long evbits[NBITS(EV_MAX)]; | |
560 | + unsigned long absbits[NBITS(ABS_MAX)]; | |
561 | + unsigned long relbits[NBITS(REL_MAX)]; | |
562 | + unsigned long keybits[NBITS(KEY_MAX)]; | |
563 | + | |
564 | + /* Get device name and id */ | |
565 | + if (ioctl(pUSB->fd,EVIOCGID,sID) < 0) | |
566 | + return 1; | |
567 | + | |
568 | + /* initialize state structure */ | |
569 | + pUSB->state[0].uValueCnt = WACOMFIELD_MAX; | |
570 | + | |
571 | + /* Get event types supported */ | |
572 | + nCnt = ioctl(pUSB->fd,EVIOCGBIT(0 /*EV*/,sizeof(evbits)),evbits); | |
573 | + if (nCnt < 0) | |
574 | + { | |
575 | + WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR, | |
576 | + "Failed to CGBIT ev: %s",strerror(errno)); | |
577 | + return 1; | |
578 | + } | |
579 | + assert(nCnt == sizeof(evbits)); | |
580 | + | |
581 | + /* absolute events */ | |
582 | + if (ISBITSET(evbits,EV_ABS)) | |
583 | + { | |
584 | + nCnt = ioctl(pUSB->fd,EVIOCGBIT(EV_ABS,sizeof(absbits)),absbits); | |
585 | + if (nCnt < 0) | |
586 | + { | |
587 | + WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR, | |
588 | + "Failed to CGBIT abs: %s",strerror(errno)); | |
589 | + return 1; | |
590 | + } | |
591 | + | |
592 | + /* the following line has problem on Debian systems | |
593 | + assert(nCnt == sizeof(absbits)); | |
594 | + */ | |
595 | + | |
596 | + if (USBGetRange(pUSB,absbits,ABS_X,WACOMFIELD_POSITION_X) || | |
597 | + USBGetRange(pUSB,absbits,ABS_Y,WACOMFIELD_POSITION_Y) || | |
598 | + USBGetRange(pUSB,absbits,ABS_RZ,WACOMFIELD_ROTATION_Z) || | |
599 | + USBGetRange(pUSB,absbits,ABS_DISTANCE,WACOMFIELD_DISTANCE) || | |
600 | + USBGetRange(pUSB,absbits,ABS_PRESSURE,WACOMFIELD_PRESSURE) || | |
601 | + USBGetRange(pUSB,absbits,ABS_TILT_X,WACOMFIELD_TILT_X) || | |
602 | + USBGetRange(pUSB,absbits,ABS_TILT_Y,WACOMFIELD_TILT_Y) || | |
603 | + USBGetRange(pUSB,absbits,ABS_WHEEL,WACOMFIELD_ABSWHEEL) || | |
604 | + USBGetRange(pUSB,absbits,ABS_THROTTLE,WACOMFIELD_THROTTLE)) | |
605 | + return 1; | |
606 | + | |
607 | + } | |
608 | + | |
609 | + /* relative events */ | |
610 | + if (ISBITSET(evbits,EV_REL)) | |
611 | + { | |
612 | + nCnt = ioctl(pUSB->fd,EVIOCGBIT(EV_REL,sizeof(relbits)),relbits); | |
613 | + if (nCnt < 0) | |
614 | + { | |
615 | + WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR, | |
616 | + "Failed to CGBIT rel: %s",strerror(errno)); | |
617 | + return 1; | |
618 | + } | |
619 | + assert(nCnt == sizeof(relbits)); | |
620 | + | |
621 | + if (ISBITSET(relbits,REL_WHEEL)) | |
622 | + { | |
623 | + pUSB->state[0].uValid |= BIT(WACOMFIELD_RELWHEEL); | |
624 | + pUSB->state[0].values[WACOMFIELD_RELWHEEL].nMin = -1; | |
625 | + pUSB->state[0].values[WACOMFIELD_RELWHEEL].nMax = 1; | |
626 | + } | |
627 | + } | |
628 | + | |
629 | + /* key events */ | |
630 | + gNumPadKeys = 0; | |
631 | + if (ISBITSET(evbits,EV_KEY)) | |
632 | + { | |
633 | + nCnt = ioctl(pUSB->fd,EVIOCGBIT(EV_KEY,sizeof(keybits)),keybits); | |
634 | + if (nCnt < 0) | |
635 | + { | |
636 | + WacomLog(pUSB->hEngine,WACOMLOGLEVEL_ERROR, | |
637 | + "Failed to CGBIT key: %s",strerror(errno)); | |
638 | + return 1; | |
639 | + } | |
640 | + assert(nCnt == sizeof(keybits)); | |
641 | + | |
642 | + /* button events */ | |
643 | + if (ISBITSET(keybits,BTN_LEFT) || | |
644 | + ISBITSET(keybits,BTN_RIGHT) || | |
645 | + ISBITSET(keybits,BTN_MIDDLE) || | |
646 | + ISBITSET(keybits,BTN_SIDE) || | |
647 | + ISBITSET(keybits,BTN_EXTRA)) | |
648 | + pUSB->state[0].uValid |= BIT(WACOMFIELD_BUTTONS); | |
649 | + | |
650 | + /* tool events */ | |
651 | + if (ISBITSET(keybits,BTN_TOOL_PEN) || | |
652 | + ISBITSET(keybits,BTN_TOOL_RUBBER) || | |
653 | + ISBITSET(keybits,BTN_TOOL_BRUSH) || | |
654 | + ISBITSET(keybits,BTN_TOOL_PENCIL) || | |
655 | + ISBITSET(keybits,BTN_TOOL_AIRBRUSH) || | |
656 | + ISBITSET(keybits,BTN_TOOL_FINGER) || | |
657 | + ISBITSET(keybits,BTN_TOOL_MOUSE) || | |
658 | + ISBITSET(keybits,BTN_TOOL_LENS) || | |
659 | + ISBITSET(keybits,BTN_TOOL_DOUBLETAP)) | |
660 | + pUSB->state[0].uValid |= BIT(WACOMFIELD_PROXIMITY) | | |
661 | + BIT(WACOMFIELD_TOOLTYPE); | |
662 | + | |
663 | + /* Detect button codes */ | |
664 | + for (nCnt = 0; nCnt < WACOMBUTTON_MAX; nCnt++) | |
665 | + if (ISBITSET (keybits, padkey_codes [nCnt])) | |
666 | + gPadKeys [gNumPadKeys++] = padkey_codes [nCnt]; | |
667 | + } | |
668 | + | |
669 | + /* set identification */ | |
670 | + pUSB->nBus = sID[0]; | |
671 | + uVendor = sID[1]; | |
672 | + uProduct = sID[2]; | |
673 | + pUSB->nVersion = sID[3]; | |
674 | + | |
675 | + if (USBFindModel(pUSB,uVendor,uProduct)) | |
676 | + return 1; | |
677 | + | |
678 | + /* add additional capabilities by device type */ | |
679 | + switch (pUSB->pDevice->uDevice) | |
680 | + { | |
681 | + case WACOMDEVICE_MO: | |
682 | + case WACOMDEVICE_GRAPHIRE4: | |
683 | + case WACOMDEVICE_INTUOS: | |
684 | + case WACOMDEVICE_INTUOS2: | |
685 | + case WACOMDEVICE_INTUOS3: | |
686 | + case WACOMDEVICE_CINTIQV5: | |
687 | + pUSB->state[0].uValid |= BIT(WACOMFIELD_SERIAL); | |
688 | + default: ; | |
689 | + } | |
690 | + | |
691 | + /* Initialize all channels with the same values */ | |
692 | + for(chcnt=1; chcnt<MAX_CHANNELS; chcnt++) | |
693 | + pUSB->state[chcnt] = pUSB->state[0]; | |
694 | + | |
695 | + return 0; | |
696 | +} | |
697 | + | |
698 | +static const char* USBGetVendorName(WACOMTABLET_PRIV* pTablet) | |
699 | +{ | |
700 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
701 | + return pUSB->pVendor->pszDesc; | |
702 | +} | |
703 | + | |
704 | +static const char* USBGetClassName(WACOMTABLET_PRIV* pTablet) | |
705 | +{ | |
706 | + return "USB"; | |
707 | +} | |
708 | + | |
709 | +static const char* USBGetDeviceName(WACOMTABLET_PRIV* pTablet) | |
710 | +{ | |
711 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
712 | + return pUSB->pDevice->pszDesc; | |
713 | +} | |
714 | + | |
715 | +static const char* USBGetSubTypeName(WACOMTABLET_PRIV* pTablet) | |
716 | +{ | |
717 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
718 | + return pUSB->pSubType->pszName; | |
719 | +} | |
720 | + | |
721 | +static const char* USBGetModelName(WACOMTABLET_PRIV* pTablet) | |
722 | +{ | |
723 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
724 | + return pUSB->pSubType->pszDesc; | |
725 | +} | |
726 | + | |
727 | +static int USBGetROMVer(WACOMTABLET_PRIV* pTablet, int* pnMajor, | |
728 | + int* pnMinor, int* pnRelease) | |
729 | +{ | |
730 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
731 | + if (!pnMajor) { errno=EINVAL; return 1; } | |
732 | + | |
733 | + /* how is the version number broken down? | |
734 | + * mine says 0x115. is that 1.1.5 or 1.15? */ | |
735 | + *pnMajor = pUSB->nVersion >> 8; | |
736 | + *pnMinor = (pUSB->nVersion >> 4) & 0xF; | |
737 | + *pnRelease = (pUSB->nVersion & 0xF); | |
738 | + return 0; | |
739 | +} | |
740 | + | |
741 | +static int USBGetCaps(WACOMTABLET_PRIV* pTablet) | |
742 | +{ | |
743 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
744 | + return pUSB->state[0].uValid; | |
745 | +} | |
746 | + | |
747 | +static int USBGetState(WACOMTABLET_PRIV* pTablet, WACOMSTATE* pState) | |
748 | +{ | |
749 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
750 | + return WacomCopyState(pState,&pUSB->state[pUSB->nChannel]); | |
751 | +} | |
752 | + | |
753 | +static int USBGetFD(WACOMTABLET_PRIV* pTablet) | |
754 | +{ | |
755 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
756 | + return pUSB->fd; | |
757 | +} | |
758 | + | |
759 | +static int USBReadRaw(WACOMTABLET_PRIV* pTablet, unsigned char* puchData, | |
760 | + unsigned int uSize) | |
761 | +{ | |
762 | + int nXfer; | |
763 | + unsigned int uCnt, uPacketLength; | |
764 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
765 | + uPacketLength = sizeof(struct input_event); | |
766 | + | |
767 | + /* check size of buffer */ | |
768 | + if (uSize < uPacketLength) { errno=EINVAL; return 0; } | |
769 | + | |
770 | + for (uCnt=0; uCnt<uPacketLength; uCnt+=nXfer) | |
771 | + { | |
772 | + nXfer = read(pUSB->fd,puchData+uCnt,uPacketLength-uCnt); | |
773 | + if (nXfer <= 0) return nXfer; | |
774 | + } | |
775 | + | |
776 | + return (signed)uCnt; | |
777 | +} | |
778 | + | |
779 | +static int USBParseMSC(USBTABLET* pUSB, struct input_event* pEv) | |
780 | +{ | |
781 | + if (pEv->code == MSC_SERIAL && pEv->value) | |
782 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_SERIAL].nValue = pEv->value; | |
783 | + if (!pUSB->state[pUSB->nChannel].values[WACOMFIELD_PROXIMITY].nValue) | |
784 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_SERIAL].nValue = 0; | |
785 | + return 0; | |
786 | +} | |
787 | + | |
788 | +static int USBParseKEY(USBTABLET* pUSB, struct input_event* pEv) | |
789 | +{ | |
790 | + int i, button=-1, tool=0; | |
791 | + switch (pEv->code) | |
792 | + { | |
793 | + case BTN_LEFT: button = WACOMBUTTON_LEFT; break; | |
794 | + case BTN_RIGHT: button = WACOMBUTTON_RIGHT; break; | |
795 | + case BTN_MIDDLE: button = WACOMBUTTON_MIDDLE; break; | |
796 | + case BTN_SIDE: button = WACOMBUTTON_SIDE; break; | |
797 | + case BTN_EXTRA: button = WACOMBUTTON_EXTRA; break; | |
798 | + case BTN_TOUCH: button = WACOMBUTTON_TOUCH; break; | |
799 | + case BTN_STYLUS: button = WACOMBUTTON_STYLUS; break; | |
800 | + case BTN_STYLUS2: button = WACOMBUTTON_STYLUS2; break; | |
801 | + case BTN_TOOL_PEN: tool = WACOMTOOLTYPE_PEN; break; | |
802 | + case BTN_TOOL_PENCIL: tool = WACOMTOOLTYPE_PENCIL; break; | |
803 | + case BTN_TOOL_BRUSH: tool = WACOMTOOLTYPE_BRUSH; break; | |
804 | + case BTN_TOOL_RUBBER: tool = WACOMTOOLTYPE_ERASER; break; | |
805 | + case BTN_TOOL_AIRBRUSH: tool = WACOMTOOLTYPE_AIRBRUSH; break; | |
806 | + case BTN_TOOL_MOUSE: tool = WACOMTOOLTYPE_MOUSE; break; | |
807 | + case BTN_TOOL_FINGER: tool = WACOMTOOLTYPE_PAD; break; | |
808 | + case BTN_TOOL_LENS: tool = WACOMTOOLTYPE_LENS; break; | |
809 | + case BTN_TOOL_DOUBLETAP: tool = WACOMTOOLTYPE_TOUCH; break; | |
810 | + default: | |
811 | + for (i = 0; i < gNumPadKeys; i++) | |
812 | + if (pEv->code == gPadKeys [i]) | |
813 | + { | |
814 | + button = WACOMBUTTON_BT0 + i; | |
815 | + break; | |
816 | + } | |
817 | + } | |
818 | + | |
819 | + /* if button was specified */ | |
820 | + if (button != -1) | |
821 | + { | |
822 | + /* button state change */ | |
823 | + if (pEv->value) | |
824 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_BUTTONS].nValue |= BIT(button); | |
825 | + else | |
826 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_BUTTONS].nValue &= ~BIT(button); | |
827 | + } | |
828 | + | |
829 | + /* if a tool was specified */ | |
830 | + if (tool) | |
831 | + { | |
832 | + /* coming into proximity */ | |
833 | + if (pEv->value) | |
834 | + { | |
835 | + /* no prior tool in proximity */ | |
836 | + if (!(pUSB->nToolProx[pUSB->nChannel] & BIT(tool))) | |
837 | + { | |
838 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_PROXIMITY].nValue = 1; | |
839 | + } | |
840 | + | |
841 | + /* remember tool in prox */ | |
842 | + pUSB->nToolProx[pUSB->nChannel] |= BIT(tool); | |
843 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_TOOLTYPE].nValue = tool; | |
844 | + } | |
845 | + | |
846 | + /* otherwise, going out of proximity */ | |
847 | + else | |
848 | + { | |
849 | + /* forget tool in prox */ | |
850 | + if (pUSB->nToolProx[pUSB->nChannel] & BIT(tool)) | |
851 | + { | |
852 | + pUSB->nToolProx[pUSB->nChannel] &= ~BIT(tool); | |
853 | + } | |
854 | + | |
855 | + /* single input */ | |
856 | + if (!(pUSB->state[pUSB->nChannel].uValid & BIT(WACOMFIELD_SERIAL))) | |
857 | + pUSB->nToolProx[pUSB->nChannel] = 0; | |
858 | + | |
859 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_PROXIMITY].nValue = 0; | |
860 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_TOOLTYPE].nValue = 0; | |
861 | + | |
862 | + /* nobody left? out of proximity */ | |
863 | + if (!pUSB->nToolProx[pUSB->nChannel]) | |
864 | + memset(pUSB->state[pUSB->nChannel].values, 0, | |
865 | + pUSB->state[pUSB->nChannel].uValueCnt * sizeof(WACOMVALUE)); | |
866 | + /* otherwise, switch to next tool */ | |
867 | + else | |
868 | + { | |
869 | + for (i=WACOMTOOLTYPE_PEN; i<WACOMTOOLTYPE_MAX; ++i) | |
870 | + { | |
871 | + if (pUSB->nToolProx[pUSB->nChannel] & BIT(i)) | |
872 | + { | |
873 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_TOOLTYPE].nValue = i; | |
874 | + } | |
875 | + } | |
876 | + } | |
877 | + } /* out of prox */ | |
878 | + } /* end if tool */ | |
879 | + | |
880 | + return 0; | |
881 | +} | |
882 | + | |
883 | +static int USBParseABS(USBTABLET* pUSB, struct input_event* pEv) | |
884 | +{ | |
885 | + int field = 0; | |
886 | + switch (pEv->code) | |
887 | + { | |
888 | + case ABS_X: field = WACOMFIELD_POSITION_X; break; | |
889 | + case ABS_Y: field = WACOMFIELD_POSITION_Y; break; | |
890 | + case ABS_RZ: field = WACOMFIELD_ROTATION_Z; break; | |
891 | + case ABS_DISTANCE: field = WACOMFIELD_DISTANCE; break; | |
892 | + case ABS_PRESSURE: field = WACOMFIELD_PRESSURE; break; | |
893 | + case ABS_TILT_X: field = WACOMFIELD_TILT_X; break; | |
894 | + case ABS_TILT_Y: field = WACOMFIELD_TILT_Y; break; | |
895 | + case ABS_WHEEL: field = WACOMFIELD_ABSWHEEL; break; | |
896 | + case ABS_THROTTLE: field = WACOMFIELD_THROTTLE; break; | |
897 | + } | |
898 | + | |
899 | + if (field) | |
900 | + pUSB->state[pUSB->nChannel].values[field].nValue = pEv->value; | |
901 | + | |
902 | + return 0; | |
903 | +} | |
904 | + | |
905 | +static int USBParseREL(USBTABLET* pUSB, struct input_event* pEv) | |
906 | +{ | |
907 | + int field = 0; | |
908 | + switch (pEv->code) | |
909 | + { | |
910 | + case REL_WHEEL: field = WACOMFIELD_RELWHEEL; break; | |
911 | + } | |
912 | + | |
913 | + if (field) | |
914 | + pUSB->state[pUSB->nChannel].values[field].nValue = pEv->value; | |
915 | + | |
916 | + return 0; | |
917 | +} | |
918 | + | |
919 | +static int USBParseData(WACOMTABLET_PRIV* pTablet, | |
920 | + const unsigned char* puchData, unsigned int uLength, | |
921 | + WACOMSTATE* pState) | |
922 | +{ | |
923 | + int evcnt, chcnt; | |
924 | + USBTABLET* pUSB = (USBTABLET*)pTablet; | |
925 | + struct input_event* pEv = (struct input_event*)puchData; | |
926 | + if (uLength != sizeof(struct input_event)) return 1; | |
927 | + | |
928 | + /* store event until we receive a MSC_SERIAL/SYN_REPORT | |
929 | + * so we can figure out with channel to use */ | |
930 | + if (pUSB->nEventCnt >= MAX_USB_EVENTS) | |
931 | + { | |
932 | + /* no more buffer space */ | |
933 | + pUSB->nEventCnt = 0; | |
934 | + pUSB->nLastToolSerial = 0; | |
935 | + errno = ENOBUFS; | |
936 | + return -1; | |
937 | + } | |
938 | + pUSB->events[pUSB->nEventCnt++] = *pEv; | |
939 | + | |
940 | + if ((pEv->type == EV_MSC) && (pEv->code == MSC_SERIAL)) | |
941 | + { | |
942 | + /* store the serial for the tool for later use */ | |
943 | + pUSB->nLastToolSerial = pEv->value; | |
944 | +#ifdef EV_SYN | |
945 | + /* Kernel 2.4 uses MSC_SERIAL as an end-of-report, 2.6 | |
946 | + * don't so we're not done yet */ | |
947 | + return 0; | |
948 | + } | |
949 | + else if ((pEv->type == EV_SYN) && (pEv->code == SYN_REPORT)) | |
950 | + { | |
951 | + /* Kernel 2.6 used SYN_REPORT as an end-of-record, | |
952 | + * fall through */ | |
953 | +#endif | |
954 | + } | |
955 | + else | |
956 | + { | |
957 | + /* Not a MSC_SERIAL or SYN_REPORT, we're not done yet */ | |
958 | + return 0; | |
959 | + } | |
960 | + | |
961 | + /* Select channel here based on the serial number, tablets | |
962 | + * with only one channel will always use channel 0, so no | |
963 | + * check. | |
964 | + */ | |
965 | + if (pUSB->pDevice->uChannelCnt > 1) | |
966 | + { | |
967 | + pUSB->nChannel = -1; | |
968 | + /* Find existing channel */ | |
969 | + for (chcnt=0; chcnt<pUSB->pDevice->uChannelCnt; chcnt++) | |
970 | + { | |
971 | + if (pUSB->state[chcnt].values[WACOMFIELD_SERIAL].nValue == pUSB->nLastToolSerial) | |
972 | + { | |
973 | + pUSB->nChannel = chcnt; | |
974 | + break; | |
975 | + } | |
976 | + } | |
977 | + | |
978 | + /* Find en empty channel */ | |
979 | + if(pUSB->nChannel == -1) | |
980 | + { | |
981 | + for (chcnt=0; chcnt<pUSB->pDevice->uChannelCnt; chcnt++) | |
982 | + { | |
983 | + if(!pUSB->nToolProx[chcnt]) | |
984 | + { | |
985 | + pUSB->nChannel = chcnt; | |
986 | + break; | |
987 | + } | |
988 | + } | |
989 | + } | |
990 | + | |
991 | + /* no more channels?? */ | |
992 | + if(pUSB->nChannel == -1) | |
993 | + { | |
994 | + pUSB->nChannel = 0; | |
995 | + pUSB->nEventCnt = 0; | |
996 | + pUSB->nLastToolSerial = 0; | |
997 | + errno = ENOBUFS; | |
998 | + return -1; | |
999 | + } | |
1000 | + } | |
1001 | + | |
1002 | + /* Channel found/allocated, lets process events */ | |
1003 | + pUSB->state[pUSB->nChannel].values[WACOMFIELD_RELWHEEL].nValue = 0; | |
1004 | + | |
1005 | + for (evcnt=0; evcnt<pUSB->nEventCnt; evcnt++) | |
1006 | + { | |
1007 | + pEv = pUSB->events + evcnt; | |
1008 | + /* dispatch event */ | |
1009 | + switch (pEv->type) | |
1010 | + { | |
1011 | +#ifdef EV_SYN | |
1012 | + case EV_SYN: /* kernel 2.6 */ | |
1013 | +#endif | |
1014 | + case EV_MSC: /* kernel 2.4 */ | |
1015 | + if (USBParseMSC(pUSB,pEv)) return pEv->type; break; | |
1016 | + case EV_KEY: if (USBParseKEY(pUSB,pEv)) return pEv->type; break; | |
1017 | + case EV_ABS: if (USBParseABS(pUSB,pEv)) return pEv->type; break; | |
1018 | + case EV_REL: if (USBParseREL(pUSB,pEv)) return pEv->type; break; | |
1019 | + default: errno = EINVAL; return pEv->type; | |
1020 | + } | |
1021 | + } | |
1022 | + | |
1023 | + pUSB->nEventCnt = 0; | |
1024 | + pUSB->nLastToolSerial = 0; | |
1025 | + return pState ? WacomCopyState(pState,pUSB->state + pUSB->nChannel) : 0; | |
1026 | +} | |
1027 | + | |
1028 | +/***************************************************************************** | |
1029 | +** End USB Linux Input Subsystem | |
1030 | +*****************************************************************************/ | |
1031 | + | |
1032 | +#else /* WCM_ENABLE_LINUXWACOM */ | |
1033 | + | |
1034 | +WACOMTABLET WacomOpenUSBTablet(WACOMENGINE hEngine, int fd, WACOMMODEL* pModel) | |
1035 | +{ | |
1036 | + errno = EPERM; | |
1037 | + return NULL; | |
1038 | +} | |
1039 | + | |
1040 | +unsigned int WacomGetUSBDeviceFromName(const char* pszName) | |
1041 | +{ | |
1042 | + errno = ENOENT; | |
1043 | + return 0; | |
1044 | +} | |
1045 | + | |
1046 | +int WacomGetSupportedUSBDeviceList(WACOMDEVICEREC** ppList, int* pnSize) | |
1047 | +{ | |
1048 | + if (!ppList || !pnSize) { errno = EINVAL; return 1; } | |
1049 | + *ppList = NULL; | |
1050 | + *pnSize = 0; | |
1051 | + return 0; | |
1052 | +} | |
1053 | + | |
1054 | +#endif /* WCM_ENABLE_LINUXWACOM */ |
@@ -0,0 +1,31 @@ | ||
1 | +/***************************************************************************** | |
2 | +** wacusb.h | |
3 | +** | |
4 | +** Copyright (C) 2002, 2003 - John E. Joganic | |
5 | +** | |
6 | +** This program is free software; you can redistribute it and/or | |
7 | +** modify it under the terms of the GNU General Public License | |
8 | +** as published by the Free Software Foundation; either version 2 | |
9 | +** of the License, or (at your option) any later version. | |
10 | +** | |
11 | +** This program is distributed in the hope that it will be useful, | |
12 | +** but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | +** GNU General Public License for more details. | |
15 | +** | |
16 | +** You should have received a copy of the GNU General Public License | |
17 | +** along with this program; if not, write to the Free Software | |
18 | +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
19 | +** | |
20 | +****************************************************************************/ | |
21 | + | |
22 | +#ifndef __LINUXWACOM_WACUSB_H | |
23 | +#define __LINUXWACOM_WACUSB_H | |
24 | + | |
25 | +#include "wactablet.h" | |
26 | + | |
27 | +int WacomGetSupportedUSBDeviceList(WACOMDEVICEREC** ppList, int* pnSize); | |
28 | +unsigned int WacomGetUSBDeviceFromName(const char* pszName); | |
29 | +WACOMTABLET WacomOpenUSBTablet(WACOMENGINE hEngine, int fd, WACOMMODEL* pModel); | |
30 | + | |
31 | +#endif /* __LINUXWACOM_WACUSB_H */ |