984 lines
25 KiB
C
984 lines
25 KiB
C
/*
|
|
* FPC1020 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, enabling and disabling of platform
|
|
* clocks, controlling GPIOs such as SPI chip select, sensor reset line, sensor
|
|
* IRQ line, MISO and MOSI lines.
|
|
*
|
|
* 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. This makes it possible for a user
|
|
* space process to poll the input node and receive IRQ events easily. Usually
|
|
* this node is available under /dev/input/eventX where 'X' is a number given by
|
|
* the event system. A user space process will need to traverse all the event
|
|
* nodes and ask for its parent's name (through EVIOCGNAME) which should match
|
|
* the value in device tree named input-device-name.
|
|
*
|
|
* This driver will NOT send any SPI commands to the sensor it only controls the
|
|
* electrical parts.
|
|
*
|
|
*
|
|
* Copyright (c) 2015 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/clk.h>
|
|
#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/spi/spi.h>
|
|
#include <soc/qcom/scm.h>
|
|
#if defined(CONFIG_QSEECOM)
|
|
#include <linux/clk.h>
|
|
#include <linux/clk/msm-clk.h>
|
|
#include <linux/of_gpio.h>
|
|
#endif
|
|
#include <linux/wakelock.h>
|
|
#define FPC1020_RESET_LOW_US 1000
|
|
#define FPC1020_RESET_HIGH1_US 100
|
|
#define FPC1020_RESET_HIGH2_US 1250
|
|
|
|
#define SUPPLY_1V8 1800000UL
|
|
#define SUPPLY_3V3 3300000UL
|
|
#define SUPPLY_SPI_MIN SUPPLY_1V8
|
|
#define SUPPLY_SPI_MAX SUPPLY_1V8
|
|
|
|
#define SUPPLY_IO_MIN SUPPLY_1V8
|
|
#define SUPPLY_IO_MAX SUPPLY_1V8
|
|
|
|
#define SUPPLY_ANA_MIN SUPPLY_1V8
|
|
#define SUPPLY_ANA_MAX SUPPLY_1V8
|
|
|
|
#define SUPPLY_TX_MIN SUPPLY_3V3
|
|
#define SUPPLY_TX_MAX SUPPLY_3V3
|
|
|
|
#define SUPPLY_SPI_REQ_CURRENT 10U
|
|
#define SUPPLY_IO_REQ_CURRENT 6000U
|
|
#define SUPPLY_ANA_REQ_CURRENT 6000U
|
|
|
|
#define FPC_TTW_HOLD_TIME 1000
|
|
|
|
static const char * const pctl_names[] = {
|
|
"fpc1020_spi_active",
|
|
"fpc1020_reset_reset",
|
|
"fpc1020_reset_active",
|
|
"fpc1020_cs_low",
|
|
"fpc1020_cs_high",
|
|
"fpc1020_cs_active",
|
|
"fpc1020_irq_active",
|
|
};
|
|
|
|
struct vreg_config {
|
|
char *name;
|
|
unsigned long vmin;
|
|
unsigned long vmax;
|
|
int ua_load;
|
|
};
|
|
|
|
static const struct vreg_config const vreg_conf[] = {
|
|
{ "vdd_ana", 1800000UL, 1800000UL, 6000, },
|
|
{ "vcc_spi", 1800000UL, 1800000UL, 10, },
|
|
{ "vdd_io", 1800000UL, 1800000UL, 6000, },
|
|
};
|
|
|
|
struct fpc1020_data {
|
|
struct device *dev;
|
|
struct spi_device *spi;
|
|
struct pinctrl *fingerprint_pinctrl;
|
|
struct pinctrl_state *pinctrl_state[ARRAY_SIZE(pctl_names)];
|
|
struct clk *iface_clk;
|
|
struct clk *core_clk;
|
|
struct regulator *vreg[ARRAY_SIZE(vreg_conf)];
|
|
struct regulator *vdd_io;
|
|
struct wake_lock ttw_wl;
|
|
int irq_gpio;
|
|
int cs0_gpio;
|
|
int rst_gpio;
|
|
int qup_id;
|
|
struct mutex lock;
|
|
bool prepared;
|
|
bool wakeup_enabled;
|
|
bool power_enabled;
|
|
bool clocks_enabled;
|
|
bool clocks_suspended;
|
|
};
|
|
|
|
int fpc1020_io_regulator_release(struct fpc1020_data *fpc1020)
|
|
{
|
|
if (fpc1020->vdd_io != NULL) {
|
|
regulator_put(fpc1020->vdd_io);
|
|
fpc1020->vdd_io = NULL;
|
|
}
|
|
|
|
fpc1020->power_enabled = false;
|
|
return 0;
|
|
}
|
|
|
|
int fpc1020_io_regulator_configure(struct fpc1020_data *fpc1020)
|
|
{
|
|
int error = 0;
|
|
|
|
dev_dbg(&fpc1020->spi->dev, "%s\n", __func__);
|
|
|
|
fpc1020->vdd_io = regulator_get(&fpc1020->spi->dev, "vdd_io");
|
|
if (IS_ERR(fpc1020->vdd_io)) {
|
|
error = PTR_ERR(fpc1020->vdd_io);
|
|
dev_err(&fpc1020->spi->dev,
|
|
"vdd_io get failed, error=%d\n", error);
|
|
goto supply_err;
|
|
}
|
|
|
|
if (regulator_count_voltages(fpc1020->vdd_io) > 0) {
|
|
error = regulator_set_voltage(fpc1020->vdd_io,
|
|
SUPPLY_TX_MIN, SUPPLY_TX_MAX);
|
|
if (error) {
|
|
dev_err(&fpc1020->spi->dev,
|
|
"vdd_io set(tx) failed, error=%d\n", error);
|
|
goto supply_err;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
supply_err:
|
|
fpc1020_io_regulator_release(fpc1020);
|
|
return error;
|
|
}
|
|
|
|
int fpc1020_io_regulator_set(struct fpc1020_data *fpc1020, bool enable)
|
|
{
|
|
int error = 0;
|
|
|
|
dev_dbg(&fpc1020->spi->dev, "%s enable %d\n", __func__, enable);
|
|
|
|
if (fpc1020->vdd_io == NULL) {
|
|
dev_err(&fpc1020->spi->dev,
|
|
"vdd_io id not set\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (enable) {
|
|
/*
|
|
Do we really need to set current?
|
|
How would it affect the vibrator that shares this regulator?
|
|
|
|
regulator_set_optimum_mode(fpc1020->vcc_spi,
|
|
SUPPLY_SPI_REQ_CURRENT);
|
|
*/
|
|
error = (regulator_is_enabled(fpc1020->vdd_io) == 0) ?
|
|
regulator_enable(fpc1020->vdd_io) : 0;
|
|
if (error) {
|
|
dev_err(&fpc1020->spi->dev,
|
|
"vdd_io enable failed, error=%d\n",
|
|
error);
|
|
goto out_err;
|
|
}
|
|
} else {
|
|
error = (fpc1020->power_enabled &&
|
|
regulator_is_enabled(fpc1020->vdd_io) > 0) ?
|
|
regulator_disable(fpc1020->vdd_io) : 0;
|
|
if (error) {
|
|
dev_err(&fpc1020->spi->dev,
|
|
"vdd_io disable failed, error=%d\n",
|
|
error);
|
|
goto out_err;
|
|
}
|
|
}
|
|
|
|
fpc1020->power_enabled = enable;
|
|
return 0;
|
|
|
|
out_err:
|
|
fpc1020_io_regulator_release(fpc1020);
|
|
return error;
|
|
}
|
|
|
|
static int vreg_setup(struct fpc1020_data *fpc1020,
|
|
const char *name, bool enable)
|
|
{
|
|
struct regulator *vreg;
|
|
struct device *dev = fpc1020->dev;
|
|
size_t i;
|
|
int rc = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(fpc1020->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 = fpc1020->vreg[i];
|
|
if (enable) {
|
|
if (!vreg) {
|
|
vreg = regulator_get(dev, name);
|
|
if (!vreg) {
|
|
dev_err(dev, "unable to get %s\n", name);
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
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_optimum_mode(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;
|
|
}
|
|
|
|
fpc1020->vreg[i] = vreg;
|
|
} else {
|
|
if (vreg) {
|
|
if (regulator_is_enabled(vreg)) {
|
|
regulator_disable(vreg);
|
|
dev_dbg(dev, "disabled %s\n", name);
|
|
}
|
|
regulator_put(vreg);
|
|
fpc1020->vreg[i] = NULL;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Prepare or unprepare the SPI master that we are soon to transfer something
|
|
* over SPI.
|
|
*
|
|
* Please see Linux Kernel manual for SPI master methods for more information.
|
|
*
|
|
* @see Linux SPI master methods
|
|
*/
|
|
static int spi_set_fabric(struct fpc1020_data *fpc1020, bool active)
|
|
{
|
|
struct spi_master *master = fpc1020->spi->master;
|
|
int rc = active ?
|
|
master->prepare_transfer_hardware(master) :
|
|
master->unprepare_transfer_hardware(master);
|
|
if (rc)
|
|
dev_err(fpc1020->dev, "%s: error %d\n", __func__, rc);
|
|
else
|
|
dev_dbg(fpc1020->dev, "%s: %d ok\n", __func__, active);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Changes ownership of SPI transfers from TEE to REE side or vice versa.
|
|
*
|
|
* SPI transfers can be owned only by one of TEE or REE side at any given time.
|
|
* This can be changed dynamically if needed but of course that needs support
|
|
* from underlaying layers. This function will transfer the ownership from REE
|
|
* to TEE or vice versa.
|
|
*
|
|
* If REE side uses the SPI master when TEE owns the pipe or vice versa the
|
|
* system will most likely crash dump.
|
|
*
|
|
* If available this should be set at boot time to eg. TEE side and not
|
|
* dynamically as that will increase the security of the system. This however
|
|
* implies that there are no other SPI slaves connected that should be handled
|
|
* from REE side.
|
|
*
|
|
* @see SET_PIPE_OWNERSHIP
|
|
*/
|
|
static int set_pipe_ownership(struct fpc1020_data *fpc1020, bool to_tz)
|
|
{
|
|
const u32 TZ_BLSP_MODIFY_OWNERSHIP_ID = 3;
|
|
const u32 TZBSP_APSS_ID = 1;
|
|
const u32 TZBSP_TZ_ID = 3;
|
|
int rc;
|
|
struct scm_desc desc = {
|
|
.arginfo = SCM_ARGS(2),
|
|
.args[0] = fpc1020->qup_id,
|
|
.args[1] = to_tz ? TZBSP_TZ_ID : TZBSP_APSS_ID,
|
|
};
|
|
|
|
rc = scm_call2(SCM_SIP_FNID(SCM_SVC_TZ, TZ_BLSP_MODIFY_OWNERSHIP_ID),
|
|
&desc);
|
|
|
|
if (rc || desc.ret[0]) {
|
|
dev_err(fpc1020->dev, "%s: scm_call2: responce %llu, rc %d\n",
|
|
__func__, desc.ret[0], rc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(fpc1020->dev, "%s: scm_call2: ok\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int set_clks(struct fpc1020_data *fpc1020, bool enable)
|
|
{
|
|
int rc = 0;
|
|
|
|
mutex_lock(&fpc1020->lock);
|
|
|
|
if (enable == fpc1020->clocks_enabled)
|
|
goto out;
|
|
|
|
if (enable) {
|
|
rc = clk_prepare_enable(fpc1020->core_clk);
|
|
if (rc) {
|
|
dev_err(fpc1020->dev,
|
|
"%s: Error enabling core clk: %d\n",
|
|
__func__, rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = clk_prepare_enable(fpc1020->iface_clk);
|
|
if (rc) {
|
|
dev_err(fpc1020->dev,
|
|
"%s: Error enabling iface clk: %d\n",
|
|
__func__, rc);
|
|
clk_disable_unprepare(fpc1020->core_clk);
|
|
goto out;
|
|
}
|
|
dev_dbg(fpc1020->dev, "%s ok. clk rate %u hz\n", __func__,
|
|
fpc1020->spi->max_speed_hz);
|
|
fpc1020->clocks_enabled = true;
|
|
} else {
|
|
clk_disable_unprepare(fpc1020->iface_clk);
|
|
clk_disable_unprepare(fpc1020->core_clk);
|
|
fpc1020->clocks_enabled = false;
|
|
}
|
|
out:
|
|
mutex_unlock(&fpc1020->lock);
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t clk_enable_set(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
|
return set_clks(fpc1020, (*buf == '1')) ? : count;
|
|
}
|
|
static DEVICE_ATTR(clk_enable, S_IWUSR, NULL, clk_enable_set);
|
|
|
|
/**
|
|
* 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 fpc1020_probe
|
|
*/
|
|
static int select_pin_ctl(struct fpc1020_data *fpc1020, const char *name)
|
|
{
|
|
struct device *dev = fpc1020->dev;
|
|
size_t i;
|
|
int rc;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(fpc1020->pinctrl_state); i++) {
|
|
const char *n = pctl_names[i];
|
|
if (!strncmp(n, name, strlen(n))) {
|
|
rc = pinctrl_select_state(fpc1020->fingerprint_pinctrl,
|
|
fpc1020->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;
|
|
}
|
|
|
|
/**
|
|
* sysfs node handler to support dynamic change of SPI transfers' ownership
|
|
* between TEE and REE side.
|
|
*
|
|
* An owner in this context is REE or TEE.
|
|
*
|
|
* @see set_pipe_ownership
|
|
* @see SET_PIPE_OWNERSHIP
|
|
*/
|
|
static ssize_t spi_owner_set(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
|
int rc;
|
|
bool to_tz;
|
|
|
|
if (!strncmp(buf, "tz", strlen("tz")))
|
|
to_tz = true;
|
|
else if (!strncmp(buf, "app", strlen("app")))
|
|
to_tz = false;
|
|
else
|
|
return -EINVAL;
|
|
|
|
rc = set_pipe_ownership(fpc1020, to_tz);
|
|
return rc ? rc : count;
|
|
}
|
|
static DEVICE_ATTR(spi_owner, S_IWUSR, NULL, spi_owner_set);
|
|
|
|
static ssize_t pinctl_set(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
|
int rc = select_pin_ctl(fpc1020, buf);
|
|
return rc ? rc : count;
|
|
}
|
|
static DEVICE_ATTR(pinctl_set, S_IWUSR, NULL, pinctl_set);
|
|
|
|
/**
|
|
* Will indicate to the SPI driver that a message is soon to be delivered over
|
|
* it.
|
|
*
|
|
* Exactly what fabric resources are requested is up to the SPI device driver.
|
|
*
|
|
* @see spi_set_fabric
|
|
*/
|
|
static ssize_t fabric_vote_set(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
|
int rc = spi_set_fabric(fpc1020, *buf == '1');
|
|
return rc ? rc : count;
|
|
}
|
|
static DEVICE_ATTR(fabric_vote, S_IWUSR, NULL, fabric_vote_set);
|
|
|
|
static ssize_t regulator_enable_set(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
|
char name[16];
|
|
char op;
|
|
int rc;
|
|
bool enable;
|
|
|
|
if (2 != 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(fpc1020, name, enable);
|
|
return rc ? rc : count;
|
|
}
|
|
static DEVICE_ATTR(regulator_enable, S_IWUSR, NULL, regulator_enable_set);
|
|
|
|
static ssize_t spi_bus_lock_set(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
|
|
|
if (!strncmp(buf, "lock", strlen("lock")))
|
|
spi_bus_lock(fpc1020->spi->master);
|
|
else if (!strncmp(buf, "unlock", strlen("unlock")))
|
|
spi_bus_unlock(fpc1020->spi->master);
|
|
else
|
|
return -EINVAL;
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR(bus_lock, S_IWUSR, NULL, spi_bus_lock_set);
|
|
|
|
static int hw_reset(struct fpc1020_data *fpc1020)
|
|
{
|
|
int irq_gpio;
|
|
struct device *dev = fpc1020->dev;
|
|
|
|
int rc = select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
|
if (rc)
|
|
goto exit;
|
|
usleep_range(FPC1020_RESET_HIGH1_US, FPC1020_RESET_HIGH1_US + 100);
|
|
|
|
rc = select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
|
if (rc)
|
|
goto exit;
|
|
usleep_range(FPC1020_RESET_LOW_US, FPC1020_RESET_LOW_US + 100);
|
|
|
|
rc = select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
|
if (rc)
|
|
goto exit;
|
|
usleep_range(FPC1020_RESET_HIGH1_US, FPC1020_RESET_HIGH1_US + 100);
|
|
|
|
irq_gpio = gpio_get_value(fpc1020->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 fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
|
|
|
if (!strncmp(buf, "reset", DSTRLEN("reset")))
|
|
rc = hw_reset(fpc1020);
|
|
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 fpc1020_data *fpc1020, bool enable)
|
|
{
|
|
int rc = 0;
|
|
int error = 0;
|
|
|
|
mutex_lock(&fpc1020->lock);
|
|
if (enable && !fpc1020->prepared) {
|
|
spi_bus_lock(fpc1020->spi->master);
|
|
fpc1020->prepared = true;
|
|
|
|
dev_info(&fpc1020->spi->dev, "%s: power on!!!!\n", __func__);
|
|
|
|
error = fpc1020_io_regulator_configure(fpc1020);
|
|
if (error) {
|
|
dev_err(&fpc1020->spi->dev,
|
|
"fpc1020_probe - io regulator configuration failed.\n");
|
|
}
|
|
|
|
error = fpc1020_io_regulator_set(fpc1020, true);
|
|
if (error) {
|
|
dev_err(&fpc1020->spi->dev,
|
|
"fpc1020_probe - io regulator enable failed.\n");
|
|
}
|
|
|
|
usleep_range(100, 1000);
|
|
|
|
rc = spi_set_fabric(fpc1020, true);
|
|
if (rc)
|
|
goto exit_3;
|
|
|
|
usleep_range(100, 200);
|
|
|
|
#if defined(CONFIG_QSEECOM)
|
|
rc = set_pipe_ownership(fpc1020, true);
|
|
if (rc)
|
|
goto exit_5;
|
|
#endif
|
|
} else if (!enable && fpc1020->prepared) {
|
|
#if defined(CONFIG_QSEECOM)
|
|
(void)set_pipe_ownership(fpc1020, false);
|
|
exit_5:
|
|
#endif
|
|
|
|
(void)spi_set_fabric(fpc1020, false);
|
|
exit_3:
|
|
(void)select_pin_ctl(fpc1020, "fpc1020_cs_high");
|
|
(void)select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
|
usleep_range(100, 1000);
|
|
(void)select_pin_ctl(fpc1020, "fpc1020_cs_low");
|
|
|
|
fpc1020->prepared = false;
|
|
spi_bus_unlock(fpc1020->spi->master);
|
|
}
|
|
mutex_unlock(&fpc1020->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t spi_prepare_set(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
|
int rc;
|
|
|
|
if (!strncmp(buf, "enable", DSTRLEN("enable")))
|
|
rc = device_prepare(fpc1020, true);
|
|
else if (!strncmp(buf, "disable", DSTRLEN("disable")))
|
|
rc = device_prepare(fpc1020, false);
|
|
else
|
|
return -EINVAL;
|
|
|
|
return rc ? rc : count;
|
|
}
|
|
static DEVICE_ATTR(spi_prepare, S_IWUSR, NULL, spi_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 fpc1020_data *fpc1020 = dev_get_drvdata(dev);
|
|
|
|
if (!strncmp(buf, "enable", DSTRLEN("enable"))) {
|
|
fpc1020->wakeup_enabled = true;
|
|
smp_wmb();
|
|
} else if (!strncmp(buf, "disable", DSTRLEN("disable"))) {
|
|
fpc1020->wakeup_enabled = false;
|
|
smp_wmb();
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR(wakeup_enable, S_IWUSR, NULL, wakeup_enable_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 fpc1020_data* fpc1020 = dev_get_drvdata(device);
|
|
int irq = gpio_get_value(fpc1020->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 fpc1020_data* fpc1020 = dev_get_drvdata(device);
|
|
dev_dbg(fpc1020->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_owner.attr,
|
|
&dev_attr_spi_prepare.attr,
|
|
&dev_attr_fabric_vote.attr,
|
|
&dev_attr_regulator_enable.attr,
|
|
&dev_attr_bus_lock.attr,
|
|
&dev_attr_hw_reset.attr,
|
|
&dev_attr_wakeup_enable.attr,
|
|
&dev_attr_clk_enable.attr,
|
|
&dev_attr_irq.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group attribute_group = {
|
|
.attrs = attributes,
|
|
};
|
|
|
|
static irqreturn_t fpc1020_irq_handler(int irq, void *handle)
|
|
{
|
|
struct fpc1020_data *fpc1020 = handle;
|
|
dev_dbg(fpc1020->dev, "%s\n", __func__);
|
|
|
|
/* Make sure 'wakeup_enabled' is updated before using it
|
|
** since this is interrupt context (other thread...) */
|
|
smp_rmb();
|
|
|
|
if (fpc1020->wakeup_enabled ) {
|
|
wake_lock_timeout(&fpc1020->ttw_wl, msecs_to_jiffies(FPC_TTW_HOLD_TIME));
|
|
}
|
|
|
|
sysfs_notify(&fpc1020->dev->kobj, NULL, dev_attr_irq.attr.name);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int fpc1020_request_named_gpio(struct fpc1020_data *fpc1020,
|
|
const char *label, int *gpio)
|
|
{
|
|
struct device *dev = fpc1020->dev;
|
|
struct device_node *np = dev->of_node;
|
|
int rc = 0;
|
|
|
|
rc = of_get_named_gpio(np, label, 0);
|
|
if (rc < 0) {
|
|
dev_err(dev, "failed to get '%s'\n", label);
|
|
return rc;
|
|
} else {
|
|
*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 fpc1020_probe(struct spi_device *spi)
|
|
{
|
|
struct device *dev = &spi->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct fpc1020_data *fpc1020;
|
|
size_t i;
|
|
int irqf = 0;
|
|
int rc = 0;
|
|
u32 val;
|
|
fpc1020 = devm_kzalloc(dev, sizeof(*fpc1020), GFP_KERNEL);
|
|
if (!fpc1020) {
|
|
dev_err(dev, "failed to allocate memory\n");
|
|
rc = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
fpc1020->dev = dev;
|
|
dev_set_drvdata(dev, fpc1020);
|
|
fpc1020->spi = spi;
|
|
|
|
if (!np) {
|
|
dev_err(dev, "of node found\n");
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_irq",
|
|
&fpc1020->irq_gpio);
|
|
if (rc)
|
|
goto exit;
|
|
|
|
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_cs0",
|
|
&fpc1020->cs0_gpio);
|
|
if (rc)
|
|
goto exit;
|
|
|
|
rc = fpc1020_request_named_gpio(fpc1020, "fpc,gpio_rst",
|
|
&fpc1020->rst_gpio);
|
|
if (rc)
|
|
goto exit;
|
|
|
|
fpc1020->iface_clk = clk_get(dev, "iface_clk");
|
|
if (IS_ERR(fpc1020->iface_clk)) {
|
|
dev_err(dev, "%s: failed to get iface_clk\n", __func__);
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
fpc1020->core_clk = clk_get(dev, "core_clk");
|
|
if (IS_ERR(fpc1020->core_clk)) {
|
|
dev_err(dev, "%s: failed to get core_clk\n", __func__);
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
rc = of_property_read_u32(np, "qcom,spi-qup-id", &val);
|
|
if (rc < 0) {
|
|
dev_err(dev, "qcom,spi-qup-id not found\n");
|
|
goto exit;
|
|
}
|
|
|
|
fpc1020->qup_id = val;
|
|
dev_dbg(dev, "qcom,spi-qup-id %d\n", fpc1020->qup_id);
|
|
|
|
fpc1020->fingerprint_pinctrl = devm_pinctrl_get(dev);
|
|
if (IS_ERR(fpc1020->fingerprint_pinctrl)) {
|
|
if (PTR_ERR(fpc1020->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");
|
|
fpc1020->fingerprint_pinctrl = NULL;
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(fpc1020->pinctrl_state); i++) {
|
|
const char *n = pctl_names[i];
|
|
struct pinctrl_state *state =
|
|
pinctrl_lookup_state(fpc1020->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);
|
|
fpc1020->pinctrl_state[i] = state;
|
|
}
|
|
|
|
select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
|
udelay(100);
|
|
select_pin_ctl(fpc1020, "fpc1020_reset_reset");
|
|
udelay(1000);
|
|
select_pin_ctl(fpc1020, "fpc1020_reset_active");
|
|
udelay(1250);
|
|
rc = select_pin_ctl(fpc1020, "fpc1020_irq_active");
|
|
if (rc)
|
|
goto exit;
|
|
|
|
rc = select_pin_ctl(fpc1020, "fpc1020_spi_active");
|
|
if (rc)
|
|
goto exit;
|
|
|
|
fpc1020->wakeup_enabled = false;
|
|
fpc1020->clocks_enabled = false;
|
|
fpc1020->clocks_suspended = false;
|
|
irqf = IRQF_TRIGGER_RISING | IRQF_ONESHOT;
|
|
if (of_property_read_bool(dev->of_node, "fpc,enable-wakeup")) {
|
|
irqf |= IRQF_NO_SUSPEND;
|
|
device_init_wakeup(dev, 1);
|
|
}
|
|
|
|
mutex_init(&fpc1020->lock);
|
|
rc = devm_request_threaded_irq(dev, gpio_to_irq(fpc1020->irq_gpio),
|
|
NULL, fpc1020_irq_handler, irqf,
|
|
dev_name(dev), fpc1020);
|
|
if (rc) {
|
|
dev_err(dev, "could not request irq %d\n",
|
|
gpio_to_irq(fpc1020->irq_gpio));
|
|
goto exit;
|
|
}
|
|
|
|
dev_dbg(dev, "requested irq %d\n", gpio_to_irq(fpc1020->irq_gpio));
|
|
|
|
/* Request that the interrupt should be wakeable */
|
|
enable_irq_wake( gpio_to_irq( fpc1020->irq_gpio ) );
|
|
|
|
wake_lock_init(&fpc1020->ttw_wl, WAKE_LOCK_SUSPEND, "fpc_ttw_wl");
|
|
|
|
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(fpc1020, true);
|
|
(void)set_clks(fpc1020, false);
|
|
}
|
|
|
|
dev_info(dev, "%s: end\n", __func__);
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
static int fpc1020_remove(struct spi_device *spi)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(&spi->dev);
|
|
|
|
sysfs_remove_group(&spi->dev.kobj, &attribute_group);
|
|
mutex_destroy(&fpc1020->lock);
|
|
wake_lock_destroy(&fpc1020->ttw_wl);
|
|
(void)vreg_setup(fpc1020, "vdd_io", false);
|
|
(void)vreg_setup(fpc1020, "vcc_spi", false);
|
|
(void)vreg_setup(fpc1020, "vdd_ana", false);
|
|
dev_info(&spi->dev, "%s\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
static int fpc1020_suspend(struct spi_device * spi, pm_message_t mesg)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(&spi->dev);
|
|
fpc1020->clocks_suspended = fpc1020->clocks_enabled;
|
|
set_clks(fpc1020, false);
|
|
return 0;
|
|
}
|
|
|
|
static int fpc1020_resume(struct spi_device *spi)
|
|
{
|
|
struct fpc1020_data *fpc1020 = dev_get_drvdata(&spi->dev);
|
|
if (fpc1020->clocks_suspended)
|
|
set_clks(fpc1020, true);
|
|
return 0;
|
|
}
|
|
|
|
static struct of_device_id fpc1020_of_match[] = {
|
|
{ .compatible = "fpc,fpc1020", },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, fpc1020_of_match);
|
|
|
|
static struct spi_driver fpc1020_driver = {
|
|
.driver = {
|
|
.name = "fpc1020",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = fpc1020_of_match,
|
|
},
|
|
.probe = fpc1020_probe,
|
|
.remove = fpc1020_remove,
|
|
.suspend = fpc1020_suspend,
|
|
.resume = fpc1020_resume,
|
|
};
|
|
|
|
static int __init fpc1020_init(void)
|
|
{
|
|
int rc = spi_register_driver(&fpc1020_driver);
|
|
|
|
if (!rc)
|
|
pr_info("%s OK\n", __func__);
|
|
else
|
|
pr_err("%s %d\n", __func__, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void __exit fpc1020_exit(void)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
spi_unregister_driver(&fpc1020_driver);
|
|
}
|
|
|
|
module_init(fpc1020_init);
|
|
module_exit(fpc1020_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("Aleksej Makarov");
|
|
MODULE_AUTHOR("Henrik Tillman <henrik.tillman@fingerprints.com>");
|
|
MODULE_DESCRIPTION("FPC1020 Fingerprint sensor device driver.");
|