vcom5s.c 9.41 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*
 * Veridicom 5thSense driver for libfprint
 * Copyright (C) 2008 Daniel Drake <dsd@gentoo.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define FP_COMPONENT "vcom5s"

22
#include "drivers_api.h"
23

24 25
/* TODO:
 * calibration?
26 27 28
 * image size: windows gets 300x300 through vpas enrollment util?
 * (probably just increase bulk read size?)
 * powerdown? does windows do anything special on exit?
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
 */

#define CTRL_IN 0xc0
#define CTRL_OUT 0x40
#define CTRL_TIMEOUT	1000
#define EP_IN			(1 | LIBUSB_ENDPOINT_IN)

#define IMG_WIDTH		300
#define IMG_HEIGHT		288
#define ROWS_PER_RQ		12
#define NR_REQS			(IMG_HEIGHT / ROWS_PER_RQ)
#define RQ_SIZE			(IMG_WIDTH * ROWS_PER_RQ)
#define IMG_SIZE		(IMG_WIDTH * IMG_HEIGHT)

struct v5s_dev {
	int capture_iteration;
	struct fp_img *capture_img;
	gboolean loop_running;
	gboolean deactivating;
};

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
enum v5s_regs {
	/* when using gain 0x29:
	 * a value of 0x00 produces mostly-black image
	 * 0x09 destroys ridges (too white)
	 * 0x01 or 0x02 seem good values */
	REG_CONTRAST = 0x02,

	/* when using contrast 0x01:
	 * a value of 0x00 will produce an all-black image.
	 * 0x29 produces a good contrast image: ridges quite dark, but some
	 * light grey noise as background
	 * 0x46 produces all-white image with grey ridges (not very dark) */
	REG_GAIN = 0x03,
};

enum v5s_cmd {
	/* scan one row. has parameter, at a guess this is which row to scan? */
	CMD_SCAN_ONE_ROW = 0xc0,

	/* scan whole image */
	CMD_SCAN = 0xc1,
};

73 74 75 76
/***** REGISTER I/O *****/

static void sm_write_reg_cb(struct libusb_transfer *transfer)
{
Bastien Nocera's avatar
Bastien Nocera committed
77
	fpi_ssm *ssm = transfer->user_data;
78 79

	if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
80
		fpi_ssm_mark_failed(ssm, -EIO);
81 82 83 84 85 86 87
	else
		fpi_ssm_next_state(ssm);

	g_free(transfer->buffer);
	libusb_free_transfer(transfer);
}

88 89 90 91 92
static void
sm_write_reg(fpi_ssm           *ssm,
	     struct fp_img_dev *dev,
	     unsigned char      reg,
	     unsigned char      value)
93
{
94
	struct libusb_transfer *transfer = fpi_usb_alloc();
95 96 97 98 99 100
	unsigned char *data;
	int r;
	
	fp_dbg("set %02x=%02x", reg, value);
	data = g_malloc(LIBUSB_CONTROL_SETUP_SIZE);
	libusb_fill_control_setup(data, CTRL_OUT, reg, value, 0, 0);
101
	libusb_fill_control_transfer(transfer, fpi_dev_get_usb_dev(FP_DEV(dev)), data, sm_write_reg_cb,
102 103 104 105 106
		ssm, CTRL_TIMEOUT);
	r = libusb_submit_transfer(transfer);
	if (r < 0) {
		g_free(data);
		libusb_free_transfer(transfer);
107
		fpi_ssm_mark_failed(ssm, r);
108 109 110 111 112
	}
}

static void sm_exec_cmd_cb(struct libusb_transfer *transfer)
{
Bastien Nocera's avatar
Bastien Nocera committed
113
	fpi_ssm *ssm = transfer->user_data;
114 115

	if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
116
		fpi_ssm_mark_failed(ssm, -EIO);
117 118 119 120 121 122 123
	else
		fpi_ssm_next_state(ssm);

	g_free(transfer->buffer);
	libusb_free_transfer(transfer);
}

124 125 126 127 128
static void
sm_exec_cmd(fpi_ssm           *ssm,
	    struct fp_img_dev *dev,
	    unsigned char      cmd,
	    unsigned char      param)
129
{
130
	struct libusb_transfer *transfer = fpi_usb_alloc();
131 132 133 134 135 136
	unsigned char *data;
	int r;

	fp_dbg("cmd %02x param %02x", cmd, param);
	data = g_malloc(LIBUSB_CONTROL_SETUP_SIZE);
	libusb_fill_control_setup(data, CTRL_IN, cmd, param, 0, 0);
137
	libusb_fill_control_transfer(transfer, fpi_dev_get_usb_dev(FP_DEV(dev)), data, sm_exec_cmd_cb,
138 139 140 141 142
		ssm, CTRL_TIMEOUT);
	r = libusb_submit_transfer(transfer);
	if (r < 0) {
		g_free(data);
		libusb_free_transfer(transfer);
143
		fpi_ssm_mark_failed(ssm, r);
144 145 146
	}
}

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
/***** FINGER DETECTION *****/

/* We take 64x64 pixels at the center of the image, determine the average
 * pixel intensity, and threshold it. */
#define DETBOX_ROW_START 111
#define DETBOX_COL_START 117
#define DETBOX_ROWS 64
#define DETBOX_COLS 64
#define DETBOX_ROW_END (DETBOX_ROW_START + DETBOX_ROWS)
#define DETBOX_COL_END (DETBOX_COL_START + DETBOX_COLS)
#define FINGER_PRESENCE_THRESHOLD 100

static gboolean finger_is_present(unsigned char *data)
{
	int row;
	uint16_t imgavg = 0;

	for (row = DETBOX_ROW_START; row < DETBOX_ROW_END; row++) {
		unsigned char *rowdata = data + (row * IMG_WIDTH);
		uint16_t rowavg = 0;
		int col;

		for (col = DETBOX_COL_START; col < DETBOX_COL_END; col++)
			rowavg += rowdata[col];
		rowavg /= DETBOX_COLS;
		imgavg += rowavg;
	}
	imgavg /= DETBOX_ROWS;
	fp_dbg("img avg %d", imgavg);

	return (imgavg <= FINGER_PRESENCE_THRESHOLD);
}



182 183
/***** IMAGE ACQUISITION *****/

184
static void capture_iterate(fpi_ssm *ssm, struct fp_img_dev *dev);
185 186 187

static void capture_cb(struct libusb_transfer *transfer)
{
Bastien Nocera's avatar
Bastien Nocera committed
188
	fpi_ssm *ssm = transfer->user_data;
189
	struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
190
	struct v5s_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
191 192

	if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
193
		fpi_ssm_mark_failed(ssm, -EIO);
194 195 196 197 198 199 200 201 202 203
		goto out;
	}

	if (++vdev->capture_iteration == NR_REQS) {
		struct fp_img *img = vdev->capture_img;
		/* must clear this early, otherwise the call chain takes us into
		 * loopsm_complete where we would free it, when in fact we are
		 * supposed to be handing off this image */
		vdev->capture_img = NULL;

204
		fpi_imgdev_report_finger_status(dev, finger_is_present(img->data));
205 206 207
		fpi_imgdev_image_captured(dev, img);
		fpi_ssm_next_state(ssm);
	} else {
208
		capture_iterate(ssm, dev);
209 210 211 212 213 214
	}

out:
	libusb_free_transfer(transfer);
}

215 216 217
static void
capture_iterate(fpi_ssm           *ssm,
		struct fp_img_dev *dev)
218
{
219
	struct v5s_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
220
	int iteration = vdev->capture_iteration;
221
	struct libusb_transfer *transfer = fpi_usb_alloc();
222 223
	int r;

224
	libusb_fill_bulk_transfer(transfer, fpi_dev_get_usb_dev(FP_DEV(dev)), EP_IN,
225 226 227 228 229 230
		vdev->capture_img->data + (RQ_SIZE * iteration), RQ_SIZE,
		capture_cb, ssm, CTRL_TIMEOUT);
	transfer->flags = LIBUSB_TRANSFER_SHORT_NOT_OK;
	r = libusb_submit_transfer(transfer);
	if (r < 0) {
		libusb_free_transfer(transfer);
231
		fpi_ssm_mark_failed(ssm, r);
232 233 234 235
	}
}


236 237 238
static void
sm_do_capture(fpi_ssm           *ssm,
	      struct fp_img_dev *dev)
239
{
240
	struct v5s_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
241

242
	G_DEBUG_HERE();
243 244
	vdev->capture_img = fpi_img_new_for_imgdev(dev);
	vdev->capture_iteration = 0;
245
	capture_iterate(ssm, dev);
246 247 248 249 250
}

/***** CAPTURE LOOP *****/

enum loop_states {
251 252 253
	LOOP_SET_CONTRAST,
	LOOP_SET_GAIN,
	LOOP_CMD_SCAN,
254 255 256 257 258
	LOOP_CAPTURE,
	LOOP_CAPTURE_DONE,
	LOOP_NUM_STATES,
};

259
static void loop_run_state(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
260
{
261
	struct fp_img_dev *dev = user_data;
262
	struct v5s_dev *vdev = FP_INSTANCE_DATA(_dev);
263

264
	switch (fpi_ssm_get_cur_state(ssm)) {
265
	case LOOP_SET_CONTRAST:
266
		sm_write_reg(ssm, dev, REG_CONTRAST, 0x01);
267
		break;
268
	case LOOP_SET_GAIN:
269
		sm_write_reg(ssm, dev, REG_GAIN, 0x29);
270
		break;
271
	case LOOP_CMD_SCAN:
272 273 274 275
		if (vdev->deactivating) {
			fp_dbg("deactivating, marking completed");
			fpi_ssm_mark_completed(ssm);
		} else
276
			sm_exec_cmd(ssm, dev, CMD_SCAN, 0x00);
277 278
		break;
	case LOOP_CAPTURE:
279
		sm_do_capture(ssm, dev);
280 281
		break;
	case LOOP_CAPTURE_DONE:
282
		fpi_ssm_jump_to_state(ssm, LOOP_CMD_SCAN);
283 284 285 286
		break;
	}
}

287
static void loopsm_complete(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
288
{
289
	struct fp_img_dev *dev = user_data;
290
	struct v5s_dev *vdev = FP_INSTANCE_DATA(_dev);
291
	int r = fpi_ssm_get_error(ssm);
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306

	fpi_ssm_free(ssm);
	fp_img_free(vdev->capture_img);
	vdev->capture_img = NULL;
	vdev->loop_running = FALSE;

	if (r)
		fpi_imgdev_session_error(dev, r);

	if (vdev->deactivating)
		fpi_imgdev_deactivate_complete(dev);
}

static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state)
{
307
	struct v5s_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
308
	fpi_ssm *ssm = fpi_ssm_new(FP_DEV(dev), loop_run_state,
309
		LOOP_NUM_STATES, dev);
310 311 312 313 314 315 316 317 318
	vdev->deactivating = FALSE;
	fpi_ssm_start(ssm, loopsm_complete);
	vdev->loop_running = TRUE;
	fpi_imgdev_activate_complete(dev, 0);
	return 0;
}

static void dev_deactivate(struct fp_img_dev *dev)
{
319
	struct v5s_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
320 321 322 323 324 325 326 327 328
	if (vdev->loop_running)
		vdev->deactivating = TRUE;
	else
		fpi_imgdev_deactivate_complete(dev);
}

static int dev_init(struct fp_img_dev *dev, unsigned long driver_data)
{
	int r;
329
	struct v5s_dev *v5s_dev;
330

331
	v5s_dev = g_malloc0(sizeof(struct v5s_dev));
332
	fp_dev_set_instance_data(FP_DEV(dev), v5s_dev);
333

334
	r = libusb_claim_interface(fpi_dev_get_usb_dev(FP_DEV(dev)), 0);
335
	if (r < 0)
336
		fp_err("could not claim interface 0: %s", libusb_error_name(r));
337 338 339 340 341 342 343 344 345

	if (r == 0)
		fpi_imgdev_open_complete(dev, 0);

	return r;
}

static void dev_deinit(struct fp_img_dev *dev)
{
346
	struct v5s_dev *v5s_dev;
347
	v5s_dev = FP_INSTANCE_DATA(FP_DEV(dev));
348
	g_free(v5s_dev);
349
	libusb_release_interface(fpi_dev_get_usb_dev(FP_DEV(dev)), 0);
350 351 352 353 354 355 356 357 358 359
	fpi_imgdev_close_complete(dev);
}

static const struct usb_id id_table[] = {
	{ .vendor = 0x061a, .product = 0x0110 },
	{ 0, 0, 0, },
};

struct fp_img_driver vcom5s_driver = {
	.driver = {
360
		.id = VCOM5S_ID,
361 362 363
		.name = FP_COMPONENT,
		.full_name = "Veridicom 5thSense",
		.id_table = id_table,
364
		.scan_type = FP_SCAN_TYPE_PRESS,
365 366 367 368 369 370 371 372 373 374 375
	},
	.flags = 0,
	.img_height = IMG_HEIGHT,
	.img_width = IMG_WIDTH,

	.open = dev_init,
	.close = dev_deinit,
	.activate = dev_activate,
	.deactivate = dev_deactivate,
};