/* Copyright 2017 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include "common.h" #include "console.h" #include "endian.h" #include "fpc_bio_algorithm.h" #include "fpc_private.h" #include "fpsensor.h" #include "gpio.h" #include "link_defs.h" #include "spi.h" #include "system.h" #include "timer.h" #include "util.h" #include "driver/fingerprint/fpc/fpc_sensor.h" #define CPRINTF(format, args...) cprintf(CC_FP, format, ## args) #define CPRINTS(format, args...) cprints(CC_FP, format, ## args) /* Minimum reset duration */ #define FP_SENSOR_RESET_DURATION_US (10 * MSEC) /* Maximum delay for the interrupt to be asserted after the sensor is reset */ #define FP_SENSOR_IRQ_MAX_DELAY_US (5 * MSEC) /* Maximum number of attempts to initialise the sensor */ #define FP_SENSOR_MAX_INIT_ATTEMPTS 10 /* Delay between failed attempts of fp_sensor_open() */ #define FP_SENSOR_OPEN_DELAY_US (500 * MSEC) /* Decode internal error codes from FPC's sensor library */ #define FPC_GET_INTERNAL_CODE(res) (((res) & 0x000fc000) >> 14) /* There was a finger on the sensor when calibrating finger detect */ #define FPC_INTERNAL_FINGER_DFD FPC_ERROR_INTERNAL_38 /* * The sensor context is uncached as it contains the SPI buffers, * the binary library assumes that it is aligned. */ static uint8_t ctx[FP_SENSOR_CONTEXT_SIZE] __uncached __aligned(4); static bio_sensor_t bio_sensor; static uint8_t enroll_ctx[FP_ALGORITHM_ENROLLMENT_SIZE] __aligned(4); /* recorded error flags */ static uint16_t errors; /* Sensor description */ static struct ec_response_fp_info fpc1145_info = { /* Sensor identification */ .vendor_id = FOURCC('F', 'P', 'C', ' '), .product_id = 9, .model_id = 1, .version = 1, /* Image frame characteristics */ .frame_size = FP_SENSOR_IMAGE_SIZE, .pixel_format = V4L2_PIX_FMT_GREY, .width = FP_SENSOR_RES_X, .height = FP_SENSOR_RES_Y, .bpp = FP_SENSOR_RES_BPP, }; /* Sensor IC commands */ enum fpc_cmd { FPC_CMD_STATUS = 0x14, FPC_CMD_INT_STS = 0x18, FPC_CMD_INT_CLR = 0x1C, FPC_CMD_FINGER_QUERY = 0x20, FPC_CMD_SLEEP = 0x28, FPC_CMD_DEEPSLEEP = 0x2C, FPC_CMD_SOFT_RESET = 0xF8, FPC_CMD_HW_ID = 0xFC, }; /* Maximum size of a sensor command SPI transfer */ #define MAX_CMD_SPI_TRANSFER_SIZE 3 /* Uncached memory for the SPI transfer buffer */ static uint8_t spi_buf[MAX_CMD_SPI_TRANSFER_SIZE] __uncached; static int fpc_send_cmd(const uint8_t cmd) { spi_buf[0] = cmd; return spi_transaction(SPI_FP_DEVICE, spi_buf, 1, spi_buf, SPI_READBACK_ALL); } void fp_sensor_low_power(void) { /* * TODO(b/117620462): verify that sleep mode is WAI (no increased * latency, expected power consumption). */ if (0) fpc_send_cmd(FPC_CMD_SLEEP); } int fpc_check_hwid(void) { uint16_t id; int rc; /* Clear previous occurences of relevant |errors| flags. */ errors &= (~FP_ERROR_SPI_COMM & ~FP_ERROR_BAD_HWID); spi_buf[0] = FPC_CMD_HW_ID; rc = spi_transaction(SPI_FP_DEVICE, spi_buf, 3, spi_buf, SPI_READBACK_ALL); if (rc) { CPRINTS("FPC ID read failed %d", rc); errors |= FP_ERROR_SPI_COMM; return EC_ERROR_HW_INTERNAL; } id = (spi_buf[1] << 8) | spi_buf[2]; if ((id >> 4) != FP_SENSOR_HWID) { CPRINTS("FPC unknown silicon 0x%04x", id); errors |= FP_ERROR_BAD_HWID; return EC_ERROR_HW_INTERNAL; } CPRINTS(FP_SENSOR_NAME " id 0x%04x", id); return EC_SUCCESS; } static uint8_t fpc_read_clear_int(void) { spi_buf[0] = FPC_CMD_INT_CLR; spi_buf[1] = 0xff; if (spi_transaction(SPI_FP_DEVICE, spi_buf, 2, spi_buf, SPI_READBACK_ALL)) return 0xff; return spi_buf[1]; } /* * Toggle the h/w reset pins and clear any pending IRQs before initializing the * sensor contexts. * Returns: * - EC_SUCCESS on success. * - EC_ERROR_HW_INTERNAL on failure (and |errors| variable is updated where * appropriate). */ static int fpc_pulse_hw_reset(void) { int ret; int rc = EC_SUCCESS; /* Clear previous occurrence of possible error flags. */ errors &= ~FP_ERROR_NO_IRQ; /* Ensure we pulse reset low to initiate the startup */ gpio_set_level(GPIO_FP_RST_ODL, 0); usleep(FP_SENSOR_RESET_DURATION_US); gpio_set_level(GPIO_FP_RST_ODL, 1); /* the IRQ line should be set high by the sensor */ usleep(FP_SENSOR_IRQ_MAX_DELAY_US); if (!gpio_get_level(GPIO_FPS_INT)) { CPRINTS("Sensor IRQ not ready"); errors |= FP_ERROR_NO_IRQ; rc = EC_ERROR_HW_INTERNAL; } /* Check the Hardware ID */ ret = fpc_check_hwid(); if (ret != EC_SUCCESS) { CPRINTS("Failed to verify HW ID"); rc = EC_ERROR_HW_INTERNAL; } /* clear the pending 'ready' IRQ before enabling interrupts */ fpc_read_clear_int(); return rc; } /* Reset and initialize the sensor IC */ int fp_sensor_init(void) { int res; int attempt; errors = FP_ERROR_DEAD_PIXELS_UNKNOWN; /* Release any previously held resources from earlier iterations */ res = bio_sensor_destroy(bio_sensor); if (res) CPRINTS("FPC Sensor resources release failed: %d", res); bio_sensor = NULL; res = bio_algorithm_exit(); if (res) CPRINTS("FPC Algorithm resources release failed: %d", res); /* Print the binary libfpsensor.a library version */ CPRINTF("FPC libfpsensor.a v%s\n", fp_sensor_get_version()); cflush(); attempt = 0; do { attempt++; res = fpc_pulse_hw_reset(); if (res != EC_SUCCESS) { /* In case of failure, retry after a delay. */ CPRINTS("H/W sensor reset failed, error flags: 0x%x", errors); cflush(); usleep(FP_SENSOR_OPEN_DELAY_US); continue; } /* * Ensure that any previous context data is obliterated in case * of a sensor reset. */ memset(ctx, 0, FP_SENSOR_CONTEXT_SIZE); res = fp_sensor_open(ctx, FP_SENSOR_CONTEXT_SIZE); /* Flush messages from the PAL if any */ cflush(); CPRINTS("Sensor init (attempt %d): 0x%x", attempt, res); /* * Retry on failure. This typically happens if the user has left * their finger on the sensor after powering up the device, DFD * will fail in that case. We've seen other error modes in the * field, retry in all cases to be more resilient. */ if (!res) break; usleep(FP_SENSOR_OPEN_DELAY_US); } while (attempt < FP_SENSOR_MAX_INIT_ATTEMPTS); if (res) errors |= FP_ERROR_INIT_FAIL; res = bio_algorithm_init(); /* the PAL might have spewed a lot of traces, ensure they are visible */ cflush(); CPRINTS("Algorithm init: 0x%x", res); if (res < 0) errors |= FP_ERROR_INIT_FAIL; res = bio_sensor_create(&bio_sensor); CPRINTS("Sensor create: 0x%x", res); if (res < 0) errors |= FP_ERROR_INIT_FAIL; /* Go back to low power */ fp_sensor_low_power(); return EC_SUCCESS; } /* Deinitialize the sensor IC */ int fp_sensor_deinit(void) { /* * TODO(tomhughes): libfp doesn't have fp_sensor_close like BEP does. * We'll need FPC to either add it or verify that we don't have the same * problem with the libfp library as described in: * b/124773209#comment46 */ return EC_SUCCESS; } int fp_sensor_get_info(struct ec_response_fp_info *resp) { int rc; memcpy(resp, &fpc1145_info, sizeof(*resp)); spi_buf[0] = FPC_CMD_HW_ID; rc = spi_transaction(SPI_FP_DEVICE, spi_buf, 3, spi_buf, SPI_READBACK_ALL); if (rc) return EC_RES_ERROR; resp->model_id = (spi_buf[1] << 8) | spi_buf[2]; resp->errors = errors; return EC_SUCCESS; } int fp_finger_match(void *templ, uint32_t templ_count, uint8_t *image, int32_t *match_index, uint32_t *update_bitmap) { return bio_template_image_match_list(templ, templ_count, image, match_index, update_bitmap); } int fp_enrollment_begin(void) { int rc; bio_enrollment_t p = enroll_ctx; rc = bio_enrollment_begin(bio_sensor, &p); if (rc < 0) CPRINTS("begin failed %d", rc); return rc; } int fp_enrollment_finish(void *templ) { bio_template_t pt = templ; return bio_enrollment_finish(enroll_ctx, templ ? &pt : NULL); } int fp_finger_enroll(uint8_t *image, int *completion) { int rc = bio_enrollment_add_image(enroll_ctx, image); if (rc < 0) return rc; *completion = bio_enrollment_get_percent_complete(enroll_ctx); return rc; } int fp_maintenance(void) { return fpc_fp_maintenance(&errors); }