Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 716a6031 authored by derfelot's avatar derfelot
Browse files

drivers: misc: Add FPC1145 fingerprint driver from Sony kernel

Taken from Sony 47.2.A.10.107 stock kernel
parent 33dc678e
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -844,4 +844,10 @@ config INPUT_STMVL53L0
	 To compile this driver as a module, choose M here: the
	 module will be called stmvl53l0.

config FPC1145_PLATFORM
	tristate "Platform FPC1145 finger print driver"
	default n
	help
	  Select this module to enable FPC1145 tee finger print driver

endif
+1 −0
Original line number Diff line number Diff line
@@ -81,3 +81,4 @@ obj-$(CONFIG_INPUT_YEALINK) += yealink.o
obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR)	+= ideapad_slidebar.o
obj-$(CONFIG_INPUT_PIXART_OTS_PAT9125_SWITCH)	+= ots_pat9125/
obj-$(CONFIG_INPUT_STMVL53L0)                 += vl53L0/
obj-$(CONFIG_FPC1145_PLATFORM)		+= fpc1145_platform.o
+695 −0
Original line number Diff line number Diff line
/*
 * FPC1145 Fingerprint sensor device driver
 *
 * This driver will control the platform resources that the FPC fingerprint
 * sensor needs to operate. The major things are probing the sensor to check
 * that it is actually connected and let the Kernel know this and with that also
 * enabling and disabling of regulators, sensor reset line, sensor IRQ line.
 *
 * The driver will expose most of its available functionality in sysfs which
 * enables dynamic control of these features from eg. a user space process.
 *
 * The sensor's IRQ events will be pushed to Kernel's event handling system and
 * are exposed in the drivers event node.
 *
 * Copyright (c) 2016 Fingerprint Cards AB <tech@fingerprints.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License Version 2
 * as published by the Free Software Foundation.
 */

#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/platform_device.h>
#include <linux/pm_wakeirq.h>

#define FPC1145_RESET_LOW_US 1000
#define FPC1145_RESET_HIGH1_US 100
#define FPC1145_RESET_HIGH2_US 1250
#define PWR_ON_STEP_SLEEP 100
#define PWR_ON_STEP_RANGE1 100
#define PWR_ON_STEP_RANGE2 900
#define NUM_PARAMS_REG_ENABLE_SET 2

static const char * const pctl_names[] = {
	"fpc1145_reset_reset",
	"fpc1145_reset_active",
	"fpc1145_irq_active",
};

struct vreg_config {
	char *name;
	unsigned long vmin;
	unsigned long vmax;
	int ua_load;
};

enum vreg {
	VDD_ANA,
	VDD_SPI,
	VDD_IO,
};

static struct vreg_config vreg_conf[] = {
	[VDD_ANA] = { "vdd_ana", 1800000UL, 1800000UL, 6000, },
	[VDD_SPI] = { "vcc_spi", 1800000UL, 1800000UL, 10, },
	[VDD_IO] = { "vdd_io", 1800000UL, 1800000UL, 6000, },
};

struct fpc1145_data {
	struct device *dev;
	struct pinctrl *fingerprint_pinctrl;
	struct pinctrl_state *pinctrl_state[ARRAY_SIZE(pctl_names)];
	struct regulator *vreg[ARRAY_SIZE(vreg_conf)];

	int irq_gpio;
	int rst_gpio;
	int ldo_gpio;
	struct mutex lock;
	bool prepared;
	atomic_t wakeup_enabled;
	bool pm_wakeup;
	bool vcc_spi;
	bool vdd_io;
	bool vdd_ana;
};

static irqreturn_t fpc1145_irq_handler(int irq, void *handle);

static int vreg_setup(struct fpc1145_data *fpc1145, const char *name,
	bool enable)
{
	size_t i;
	int rc;
	struct regulator *vreg;
	struct device *dev = fpc1145->dev;

	for (i = 0; i < ARRAY_SIZE(fpc1145->vreg); i++) {
		const char *n = vreg_conf[i].name;

		if (!strncmp(n, name, strlen(n)))
			goto found;
	}
	dev_err(dev, "Regulator %s not found\n", name);
	return -EINVAL;
found:
	vreg = fpc1145->vreg[i];
	if (enable) {
		if (!vreg) {
			vreg = regulator_get(dev, name);
			if (IS_ERR(vreg)) {
				dev_err(dev, "Unable to get %s\n", name);
				return PTR_ERR(vreg);
			}
		}
		if (regulator_count_voltages(vreg) > 0) {
			rc = regulator_set_voltage(vreg, vreg_conf[i].vmin,
					vreg_conf[i].vmax);
			if (rc)
				dev_err(dev,
					"Unable to set voltage on %s, %d\n",
					name, rc);
		}
		rc = regulator_set_load(vreg, vreg_conf[i].ua_load);
		if (rc < 0)
			dev_err(dev, "Unable to set current on %s, %d\n",
					name, rc);
		rc = regulator_enable(vreg);
		if (rc) {
			dev_err(dev, "error enabling %s: %d\n", name, rc);
			regulator_put(vreg);
			vreg = NULL;
		}
		fpc1145->vreg[i] = vreg;
		dev_dbg(dev, "Vreg %s enable OK\n", name);
	} else {
		if (vreg) {
			if (regulator_is_enabled(vreg)) {
				regulator_disable(vreg);
				dev_dbg(dev, "disabled %s\n", name);
			}
			regulator_put(vreg);
			fpc1145->vreg[i] = NULL;
		}
		rc = 0;
	}
	return rc;
}

/**
 * Will try to select the set of pins (GPIOS) defined in a pin control node of
 * the device tree named @p name.
 *
 * The node can contain several eg. GPIOs that is controlled when selecting it.
 * The node may activate or deactivate the pins it contains, the action is
 * defined in the device tree node itself and not here. The states used
 * internally is fetched at probe time.
 *
 * @see pctl_names
 * @see fpc1145_probe
 */
static int select_pin_ctl(struct fpc1145_data *fpc1145, const char *name)
{
	size_t i;
	int rc;
	struct device *dev = fpc1145->dev;

	for (i = 0; i < ARRAY_SIZE(fpc1145->pinctrl_state); i++) {
		const char *n = pctl_names[i];

		if (!strncmp(n, name, strlen(n))) {
			rc = pinctrl_select_state(fpc1145->fingerprint_pinctrl,
					fpc1145->pinctrl_state[i]);
			if (rc)
				dev_err(dev, "cannot select '%s'\n", name);
			else
				dev_dbg(dev, "Selected '%s'\n", name);
			goto exit;
		}
	}
	rc = -EINVAL;
	dev_err(dev, "%s:'%s' not found\n", __func__, name);
exit:
	return rc;
}

static ssize_t pinctl_set(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	struct fpc1145_data *fpc1145 = dev_get_drvdata(dev);
	int rc = select_pin_ctl(fpc1145, buf);

	return rc ? rc : count;
}
static DEVICE_ATTR(pinctl_set, S_IWUSR, NULL, pinctl_set);

static ssize_t regulator_enable_set(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	struct fpc1145_data *fpc1145 = dev_get_drvdata(dev);
	char op;
	char name[16];
	int rc;
	bool enable;

	if (NUM_PARAMS_REG_ENABLE_SET != sscanf(buf, "%15s,%c", name, &op))
		return -EINVAL;
	if (op == 'e')
		enable = true;
	else if (op == 'd')
		enable = false;
	else
		return -EINVAL;
	rc = vreg_setup(fpc1145, name, enable);
	return rc ? rc : count;
}
static DEVICE_ATTR(regulator_enable, S_IWUSR, NULL, regulator_enable_set);

static int hw_reset(struct fpc1145_data *fpc1145)
{
	int irq_gpio;
	struct device *dev = fpc1145->dev;
	int rc = select_pin_ctl(fpc1145, "fpc1145_reset_active");

	if (rc)
		goto exit;
	usleep_range(FPC1145_RESET_HIGH1_US, FPC1145_RESET_HIGH1_US + 100);

	rc = select_pin_ctl(fpc1145, "fpc1145_reset_reset");
	if (rc)
		goto exit;
	usleep_range(FPC1145_RESET_LOW_US, FPC1145_RESET_LOW_US + 100);

	rc = select_pin_ctl(fpc1145, "fpc1145_reset_active");
	if (rc)
		goto exit;
	usleep_range(FPC1145_RESET_HIGH1_US, FPC1145_RESET_HIGH1_US + 100);

	irq_gpio = gpio_get_value(fpc1145->irq_gpio);
	dev_info(dev, "IRQ after reset %d\n", irq_gpio);
exit:
	return rc;
}

static ssize_t hw_reset_set(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	int rc;
	struct fpc1145_data *fpc1145 = dev_get_drvdata(dev);

	if (!strncmp(buf, "reset", strlen("reset")))
		rc = hw_reset(fpc1145);
	else
		return -EINVAL;
	return rc ? rc : count;
}
static DEVICE_ATTR(hw_reset, S_IWUSR, NULL, hw_reset_set);

/**
 * Will setup clocks, GPIOs, and regulators to correctly initialize the touch
 * sensor to be ready for work.
 *
 * In the correct order according to the sensor spec this function will
 * enable/disable regulators, SPI platform clocks, and reset line, all to set
 * the sensor in a correct power on or off state "electrical" wise.
 *
 * @see  spi_prepare_set
 * @note This function will not send any commands to the sensor it will only
 *       control it "electrically".
 */
static int device_prepare(struct fpc1145_data *fpc1145, bool enable)
{
	int rc;

	mutex_lock(&fpc1145->lock);
	if (enable && !fpc1145->prepared) {

		fpc1145->prepared = true;
		select_pin_ctl(fpc1145, "fpc1145_reset_reset");

		if (fpc1145->vcc_spi) {
			rc = vreg_setup(fpc1145, "vcc_spi", true);
			if (rc)
				goto exit;
		}

		if (fpc1145->vdd_io) {
			rc = vreg_setup(fpc1145, "vdd_io", true);
			if (rc)
				goto exit_1;
		}

		if (fpc1145->vdd_ana) {
			rc = vreg_setup(fpc1145, "vdd_ana", true);
			if (rc)
				goto exit_2;
		}

		if (fpc1145->ldo_gpio != -1)
			gpio_set_value(fpc1145->ldo_gpio, 1);

		usleep_range(PWR_ON_STEP_SLEEP,
				PWR_ON_STEP_SLEEP + PWR_ON_STEP_RANGE2);

		(void)select_pin_ctl(fpc1145, "fpc1145_reset_active");
		usleep_range(PWR_ON_STEP_SLEEP,
				PWR_ON_STEP_SLEEP + PWR_ON_STEP_RANGE1);

	} else if (!enable && fpc1145->prepared) {
		rc = 0;

		(void)select_pin_ctl(fpc1145, "fpc1145_reset_reset");
		usleep_range(PWR_ON_STEP_SLEEP,
				PWR_ON_STEP_SLEEP + PWR_ON_STEP_RANGE2);

		if (fpc1145->ldo_gpio != -1)
			gpio_set_value(fpc1145->ldo_gpio, 0);

		if (fpc1145->vdd_ana)
			(void)vreg_setup(fpc1145, "vdd_ana", false);
exit_2:
		if (fpc1145->vdd_io)
			(void)vreg_setup(fpc1145, "vdd_io", false);
exit_1:
		if (fpc1145->vcc_spi)
			(void)vreg_setup(fpc1145, "vcc_spi", false);
exit:
		fpc1145->prepared = false;
	} else {
		rc = 0;
	}
	mutex_unlock(&fpc1145->lock);
	return rc;
}

static ssize_t device_prepare_set(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	int rc;
	struct fpc1145_data *fpc1145 = dev_get_drvdata(dev);

	if (!strncmp(buf, "enable", strlen("enable")))
		rc = device_prepare(fpc1145, true);
	else if (!strncmp(buf, "disable", strlen("disable")))
		rc = device_prepare(fpc1145, false);
	else
		return -EINVAL;
	return rc ? rc : count;
}
static DEVICE_ATTR(spi_prepare, S_IWUSR, NULL, device_prepare_set);

/**
 * sysfs node for controlling whether the driver is allowed
 * to wake up the platform on interrupt.
 */
static ssize_t wakeup_enable_set(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	struct fpc1145_data *fpc1145 = dev_get_drvdata(dev);

	if (fpc1145->pm_wakeup) {
		if (!strncmp(buf, "enable", strlen("enable"))) {
			dev_info(fpc1145->dev, "%s enable\n", __func__);
			atomic_set(&fpc1145->wakeup_enabled, 1);
			pm_relax(fpc1145->dev);
		} else if (!strncmp(buf, "disable", strlen("disable"))) {
			dev_info(fpc1145->dev, "%s disable\n", __func__);
			atomic_set(&fpc1145->wakeup_enabled, 0);
		} else
			return -EINVAL;
	}
	return count;
}
static DEVICE_ATTR(wakeup_enable, S_IWUSR, NULL, wakeup_enable_set);

static ssize_t wake_lock_set(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
	struct fpc1145_data *fpc1145 = dev_get_drvdata(dev);
	int rc;
	int ms;

	rc = kstrtoint(buf, 10, &ms);
	if (!rc) {
		if (ms > 0) {
			dev_dbg(fpc1145->dev, "%s for %d\n",
					__func__, ms);
			pm_wakeup_event(fpc1145->dev, ms);
		} else if (!ms) {
			dev_dbg(fpc1145->dev, "%s\n", __func__);
			pm_stay_awake(fpc1145->dev);
		} else if (ms < 0) {
			dev_dbg(fpc1145->dev, "%s relax\n", __func__);
			pm_relax(fpc1145->dev);
		}
	}
	return rc ? rc : count;
}
static DEVICE_ATTR(wake_lock, S_IWUSR, NULL, wake_lock_set);

static ssize_t pm_wakeup_get(struct device *device,
	struct device_attribute *attribute,
	char *buffer)
{
	struct fpc1145_data *fpc1145 = dev_get_drvdata(device);

	return scnprintf(buffer, PAGE_SIZE, "%s\n",
			fpc1145->pm_wakeup ? "enabled" : "disabled");
}

static ssize_t pm_wakeup_set(struct device *device,
	struct device_attribute *attribute,
	const char *buffer, size_t count)
{
	struct fpc1145_data *fpc1145 = dev_get_drvdata(device);
	bool enable;

	if (!strncmp(buffer, "enable", strlen("enable")))
		enable = true;
	else if (!strncmp(buffer, "disable", strlen("disable")))
		enable = false;
	else
		return -EINVAL;

	if (enable != fpc1145->pm_wakeup) {
		dev_dbg(fpc1145->dev, "%s enable = %d\n", __func__,
				enable);
		fpc1145->pm_wakeup = enable;
		if (enable) {
			enable_irq_wake(gpio_to_irq(fpc1145->irq_gpio));
		} else {
			atomic_set(&fpc1145->wakeup_enabled, 0);
			disable_irq_wake(gpio_to_irq(fpc1145->irq_gpio));
			pm_relax(fpc1145->dev);
		}
	}
	return count;
}
static DEVICE_ATTR(pm_wakeup, S_IRUGO | S_IWUSR, pm_wakeup_get, pm_wakeup_set);

/**
 * sysf node to check the interrupt status of the sensor, the interrupt
 * handler should perform sysf_notify to allow userland to poll the node.
 */
static ssize_t irq_get(struct device *device,
	struct device_attribute *attribute,
	char *buffer)
{
	struct fpc1145_data *fpc1145 = dev_get_drvdata(device);
	int irq = gpio_get_value(fpc1145->irq_gpio);

	return scnprintf(buffer, PAGE_SIZE, "%i\n", irq);
}


/**
 * writing to the irq node will just drop a printk message
 * and return success, used for latency measurement.
 */
static ssize_t irq_ack(struct device *device,
	struct device_attribute *attribute,
	const char *buffer, size_t count)
{
	struct fpc1145_data *fpc1145 = dev_get_drvdata(device);

	dev_dbg(fpc1145->dev, "%s\n", __func__);
	return count;
}

static DEVICE_ATTR(irq, S_IRUSR | S_IWUSR, irq_get, irq_ack);

static struct attribute *attributes[] = {
	&dev_attr_pinctl_set.attr,
	&dev_attr_spi_prepare.attr,
	&dev_attr_regulator_enable.attr,
	&dev_attr_hw_reset.attr,
	&dev_attr_wakeup_enable.attr,
	&dev_attr_pm_wakeup.attr,
	&dev_attr_irq.attr,
	&dev_attr_wake_lock.attr,
	NULL
};

static const struct attribute_group attribute_group = {
	.attrs = attributes,
};

static irqreturn_t fpc1145_irq_handler(int irq, void *handle)
{
	struct fpc1145_data *fpc1145 = handle;

	if (atomic_read(&fpc1145->wakeup_enabled)) {
		pm_stay_awake(fpc1145->dev);
		dev_info(fpc1145->dev, "%s: wakeup mode\n", __func__);
	} else {
		dev_dbg(fpc1145->dev, "%s\n", __func__);
	}
	sysfs_notify(&fpc1145->dev->kobj, NULL, dev_attr_irq.attr.name);

	return IRQ_HANDLED;
}

static int fpc1145_request_named_gpio(struct fpc1145_data *fpc1145,
	const char *label, int *gpio)
{
	struct device *dev = fpc1145->dev;
	struct device_node *np = dev->of_node;
	int rc = of_get_named_gpio(np, label, 0);

	if (rc < 0) {
		dev_err(dev, "failed to get '%s'\n", label);
		return rc;
	}
	*gpio = rc;
	rc = devm_gpio_request(dev, *gpio, label);
	if (rc) {
		dev_err(dev, "failed to request gpio %d\n", *gpio);
		return rc;
	}
	dev_dbg(dev, "%s %d\n", label, *gpio);
	return 0;
}

static int fpc1145_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	int rc = 0;
	size_t i;
	int irqf;
	struct device_node *np = dev->of_node;
	u32 v;

	struct fpc1145_data *fpc1145 = devm_kzalloc(dev, sizeof(*fpc1145),
			GFP_KERNEL);
	if (!fpc1145) {
		rc = -ENOMEM;
		goto exit;
	}

	fpc1145->dev = dev;
	platform_set_drvdata(pdev, fpc1145);

	if (!np) {
		dev_err(dev, "no of node found\n");
		rc = -EINVAL;
		goto exit;
	}

	rc = fpc1145_request_named_gpio(fpc1145, "fpc,gpio_irq",
			&fpc1145->irq_gpio);
	if (rc)
		goto exit;
	rc = fpc1145_request_named_gpio(fpc1145, "fpc,gpio_rst",
			&fpc1145->rst_gpio);
	if (rc)
		goto exit;
	rc = fpc1145_request_named_gpio(fpc1145, "gpio_ldo_enabe",
			&fpc1145->ldo_gpio);
	if (rc)
		fpc1145->ldo_gpio = -1;


	fpc1145->vcc_spi = of_property_read_bool(dev->of_node,
			"vcc_spi-supply");
	fpc1145->vdd_io = of_property_read_bool(dev->of_node,
			"vdd_io-supply");
	fpc1145->vdd_ana = of_property_read_bool(dev->of_node,
			"vdd_ana-supply");
	if (!of_property_read_u32(dev->of_node, "vdd_ana-min-uV", &v)) {
		vreg_conf[VDD_ANA].vmin = v;
		dev_info(dev, "vdd_ana-min-uV %u\n", v);
	}
	if (!of_property_read_u32(dev->of_node, "vdd_ana-max-uV", &v)) {
		vreg_conf[VDD_ANA].vmax = v;
		dev_info(dev, "vdd_ana-max-uV %u\n", v);
	}

	fpc1145->fingerprint_pinctrl = devm_pinctrl_get(dev);
	if (IS_ERR(fpc1145->fingerprint_pinctrl)) {
		if (PTR_ERR(fpc1145->fingerprint_pinctrl) == -EPROBE_DEFER) {
			dev_info(dev, "pinctrl not ready\n");
			rc = -EPROBE_DEFER;
			goto exit;
		}
		dev_err(dev, "Target does not use pinctrl\n");
		fpc1145->fingerprint_pinctrl = NULL;
		rc = -EINVAL;
		goto exit;
	}

	for (i = 0; i < ARRAY_SIZE(fpc1145->pinctrl_state); i++) {
		const char *n = pctl_names[i];
		struct pinctrl_state *state =
			pinctrl_lookup_state(fpc1145->fingerprint_pinctrl, n);
		if (IS_ERR(state)) {
			dev_err(dev, "cannot find '%s'\n", n);
			rc = -EINVAL;
			goto exit;
		}
		dev_info(dev, "found pin control %s\n", n);
		fpc1145->pinctrl_state[i] = state;
	}

	rc = select_pin_ctl(fpc1145, "fpc1145_reset_reset");
	if (rc)
		goto exit;
	rc = select_pin_ctl(fpc1145, "fpc1145_irq_active");
	if (rc)
		goto exit;

	irqf = IRQF_TRIGGER_RISING | IRQF_ONESHOT;
	mutex_init(&fpc1145->lock);
	rc = devm_request_threaded_irq(dev, gpio_to_irq(fpc1145->irq_gpio),
			NULL, fpc1145_irq_handler, irqf,
			dev_name(dev), fpc1145);
	if (rc) {
		dev_err(dev, "could not request irq %d\n",
				gpio_to_irq(fpc1145->irq_gpio));
		goto exit;
	}
	dev_dbg(dev, "requested irq %d\n", gpio_to_irq(fpc1145->irq_gpio));

	rc = sysfs_create_group(&dev->kobj, &attribute_group);
	if (rc) {
		dev_err(dev, "could not create sysfs\n");
		goto exit;
	}

	if (of_property_read_bool(dev->of_node, "fpc,enable-on-boot")) {
		dev_info(dev, "Enabling hardware\n");
		(void)device_prepare(fpc1145, true);
	}
	device_init_wakeup(fpc1145->dev, true);
	dev_info(dev, "%s: ok\n", __func__);
exit:
	return rc;
}

static int fpc1145_remove(struct platform_device *pdev)
{
	struct fpc1145_data *fpc1145 = platform_get_drvdata(pdev);

	if (fpc1145->pm_wakeup)
		disable_irq_wake(gpio_to_irq(fpc1145->irq_gpio));
	device_init_wakeup(fpc1145->dev, false);
	sysfs_remove_group(&pdev->dev.kobj, &attribute_group);
	mutex_destroy(&fpc1145->lock);
	if (fpc1145->vdd_io)
		(void)vreg_setup(fpc1145, "vdd_io", false);
	if (fpc1145->vcc_spi)
		(void)vreg_setup(fpc1145, "vcc_spi", false);
	if (fpc1145->vdd_ana)
		(void)vreg_setup(fpc1145, "vdd_ana", false);
	dev_info(&pdev->dev, "%s\n", __func__);
	return 0;
}

static struct of_device_id fpc1145_of_match[] = {
	{ .compatible = "fpc,fpc1020", },
	{ .compatible = "fpc,fpc1145", },
	{}
};
MODULE_DEVICE_TABLE(of, fpc1145_of_match);

static struct platform_driver fpc1145_driver = {
	.driver = {
		.name = "fpc1145",
		.owner = THIS_MODULE,
		.of_match_table = fpc1145_of_match,
	},
	.probe = fpc1145_probe,
	.remove = fpc1145_remove,
};

static int __init fpc1145_init(void)
{
	int rc = platform_driver_register(&fpc1145_driver);

	if (!rc)
		pr_info("%s OK\n", __func__);
	else
		pr_err("%s %d\n", __func__, rc);
	return rc;
}

static void __exit fpc1145_exit(void)
{
	pr_info("%s\n", __func__);
	platform_driver_unregister(&fpc1145_driver);
}

module_init(fpc1145_init);
module_exit(fpc1145_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Aleksej Makarov <aleksej.makarov@sonymobile.com>");
MODULE_AUTHOR("Henrik Tillman <henrik.tillman@fingerprints.com>");
MODULE_DESCRIPTION("FPC1145 Fingerprint sensor device driver.");