NFC: Add NFC I2C and I3C drivers for SNxxx

Add new slave driver to support NFC over I3C interface.

I2C driver is a refactored driver. The base for this is
'commit dea45e3d54b5 ("NFC: Add support for eSE cold reset")'.

Change-Id: I58eaee72bd2bc07e855acd294b9659038e2357f3
Signed-off-by: Karthik Poosa <kpoosa2@codeaurora.org>
This commit is contained in:
Karthik Poosa 2019-12-16 18:38:59 +05:30
commit c872c472be
9 changed files with 2330 additions and 0 deletions

View file

@ -60,3 +60,21 @@ source "drivers/nfc/nxp-nci/Kconfig"
source "drivers/nfc/s3fwrn5/Kconfig"
source "drivers/nfc/st95hf/Kconfig"
endmenu
config NFC_QTI_I2C
tristate "QTI NCI based NFC I2C Slave Driver for SNxxx"
depends on I2C
help
This enables the NFC driver for SNxxx based devices.
This is for I2C connected version. NCI protocol logic
resides in the usermode and it has no other NFC dependencies.
If unsure, say N.
config NFC_QTI_I3C
tristate "QTI NCI based NFC I3C Slave Driver for SNxxx"
depends on I3C
help
This enables the NFC driver for SNxxx based devices.
This is for I3C connected version. NCI protocol logic
resides in the usermode and it has no other NFC dependencies.

View file

@ -17,3 +17,4 @@ obj-$(CONFIG_NFC_ST_NCI) += st-nci/
obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/
obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5/
obj-$(CONFIG_NFC_ST95HF) += st95hf/
obj-y += qti/

10
drivers/nfc/qti/Makefile Normal file
View file

@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for qti nfc drivers
#
obj-$(CONFIG_NFC_QTI_I2C) += nfc_i2c_drv.o
nfc_i2c_drv-objs:= nfc_common.o nfc_i2c_drv.o
obj-$(CONFIG_NFC_QTI_I3C) += nfc_i3c_drv.o
nfc_i3c_drv-objs:= nfc_common.o nfc_i3c_drv.o

View file

@ -0,0 +1,602 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015-2019, The Linux Foundation. All rights reserved.
*/
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/delay.h>
#include "nfc_common.h"
int nfc_parse_dt(struct device *dev, struct platform_gpio *nfc_gpio,
uint8_t interface)
{
struct device_node *np = dev->of_node;
if (!np) {
pr_err("nfc of_node NULL\n");
return -EINVAL;
}
if (interface == PLATFORM_IF_I2C) {
nfc_gpio->irq = of_get_named_gpio(np, DTS_IRQ_GPIO_STR, 0);
if ((!gpio_is_valid(nfc_gpio->irq))) {
pr_err("nfc irq gpio invalid %d\n", nfc_gpio->irq);
return -EINVAL;
}
pr_info("%s: irq %d\n", __func__, nfc_gpio->irq);
}
nfc_gpio->ven = of_get_named_gpio(np, DTS_VEN_GPIO_STR, 0);
if ((!gpio_is_valid(nfc_gpio->ven))) {
pr_err("nfc ven gpio invalid %d\n", nfc_gpio->ven);
return -EINVAL;
}
nfc_gpio->dwl_req = of_get_named_gpio(np, DTS_FWDN_GPIO_STR, 0);
if ((!gpio_is_valid(nfc_gpio->dwl_req))) {
pr_err("nfc dwl_req gpio invalid %d\n", nfc_gpio->dwl_req);
return -EINVAL;
}
nfc_gpio->clkreq = of_get_named_gpio(np, DTS_CLKREQ_GPIO_STR, 0);
if (!gpio_is_valid(nfc_gpio->clkreq)) {
dev_err(dev, "clkreq gpio invalid %d\n", nfc_gpio->dwl_req);
return -EINVAL;
}
pr_info("%s: ven %d, dwl req %d, clkreq %d\n", __func__,
nfc_gpio->ven, nfc_gpio->dwl_req, nfc_gpio->clkreq);
return 0;
}
EXPORT_SYMBOL(nfc_parse_dt);
void gpio_set_ven(struct nfc_dev *nfc_dev, int value)
{
if (gpio_get_value(nfc_dev->gpio.ven) != value) {
gpio_set_value(nfc_dev->gpio.ven, value);
// hardware dependent delay
usleep_range(10000, 10100);
}
}
int configure_gpio(unsigned int gpio, int flag)
{
int ret;
pr_debug("%s: nfc gpio [%d] flag [%01x]\n", __func__, gpio, flag);
if (gpio_is_valid(gpio)) {
ret = gpio_request(gpio, "nfc_gpio");
if (ret) {
pr_err("%s: unable to request nfc gpio [%d]\n",
__func__, gpio);
return ret;
}
// set direction and value for output pin
if (flag & GPIO_OUTPUT)
ret = gpio_direction_output(gpio, (GPIO_HIGH & flag));
else
ret = gpio_direction_input(gpio);
if (ret) {
pr_err
("%s: unable to set direction for nfc gpio [%d]\n",
__func__, gpio);
gpio_free(gpio);
return ret;
}
// Consider value as control for input IRQ pin
if (flag & GPIO_IRQ) {
ret = gpio_to_irq(gpio);
if (ret < 0) {
pr_err("%s: unable to set irq for nfc gpio [%d]\n",
__func__, gpio);
gpio_free(gpio);
return ret;
}
pr_debug
("%s: gpio_to_irq successful [%d]\n",
__func__, gpio);
return ret;
}
} else {
pr_err("%s: invalid gpio\n", __func__);
ret = -EINVAL;
}
return ret;
}
EXPORT_SYMBOL(configure_gpio);
void nfc_misc_remove(struct nfc_dev *nfc_dev, int count)
{
pr_debug("%s: entry\n", __func__);
kfree(nfc_dev->kbuf);
device_destroy(nfc_dev->nfc_class, nfc_dev->devno);
cdev_del(&nfc_dev->c_dev);
class_destroy(nfc_dev->nfc_class);
unregister_chrdev_region(nfc_dev->devno, count);
}
EXPORT_SYMBOL(nfc_misc_remove);
int nfc_misc_probe(struct nfc_dev *nfc_dev,
const struct file_operations *nfc_fops, int count,
char *devname, char *classname)
{
int ret = 0;
ret = alloc_chrdev_region(&nfc_dev->devno, 0, count, devname);
if (ret < 0) {
pr_err("%s: failed to alloc chrdev region ret %d\n",
__func__, ret);
return ret;
}
nfc_dev->nfc_class = class_create(THIS_MODULE, classname);
if (IS_ERR(nfc_dev->nfc_class)) {
ret = PTR_ERR(nfc_dev->nfc_class);
pr_err("%s: failed to register device class ret %d\n",
__func__, ret);
unregister_chrdev_region(nfc_dev->devno, count);
return ret;
}
cdev_init(&nfc_dev->c_dev, nfc_fops);
ret = cdev_add(&nfc_dev->c_dev, nfc_dev->devno, count);
if (ret < 0) {
pr_err("%s: failed to add cdev ret %d\n", __func__, ret);
class_destroy(nfc_dev->nfc_class);
unregister_chrdev_region(nfc_dev->devno, count);
return ret;
}
nfc_dev->nfc_device = device_create(nfc_dev->nfc_class, NULL,
nfc_dev->devno, nfc_dev, devname);
if (IS_ERR(nfc_dev->nfc_device)) {
ret = PTR_ERR(nfc_dev->nfc_device);
pr_err("%s: failed to create the device ret %d\n",
__func__, ret);
cdev_del(&nfc_dev->c_dev);
class_destroy(nfc_dev->nfc_class);
unregister_chrdev_region(nfc_dev->devno, count);
return ret;
}
nfc_dev->kbuflen = MAX_BUFFER_SIZE;
nfc_dev->kbuf = kzalloc(MAX_BUFFER_SIZE, GFP_KERNEL | GFP_DMA);
if (!nfc_dev->kbuf)
return -ENOMEM;
nfc_dev->cold_reset.rsp_pending = false;
nfc_dev->cold_reset.is_nfc_enabled = false;
init_waitqueue_head(&nfc_dev->cold_reset.read_wq);
return 0;
}
EXPORT_SYMBOL(nfc_misc_probe);
static void enable_interrupt(struct nfc_dev *nfc_dev)
{
if (nfc_dev->interface == PLATFORM_IF_I2C)
i2c_enable_irq(&nfc_dev->i2c_dev);
else
i3c_enable_ibi(&nfc_dev->i3c_dev);
}
static void disable_interrupt(struct nfc_dev *nfc_dev)
{
if (nfc_dev->interface == PLATFORM_IF_I2C)
i2c_disable_irq(&nfc_dev->i2c_dev);
else
i3c_disable_ibi(&nfc_dev->i3c_dev);
}
static int send_cold_reset_cmd(struct nfc_dev *nfc_dev)
{
int ret = 0;
char *cold_reset_cmd = NULL;
cold_reset_cmd = kzalloc(COLD_RESET_CMD_LEN, GFP_DMA | GFP_KERNEL);
if (!cold_reset_cmd)
return -ENOMEM;
if (gpio_get_value(nfc_dev->gpio.dwl_req)) {
pr_err("FW download in-progress\n");
ret = -EBUSY;
goto error;
}
if (!gpio_get_value(nfc_dev->gpio.ven)) {
pr_err("VEN LOW - NFCC powered off\n");
ret = -ENODEV;
goto error;
}
cold_reset_cmd[0] = COLD_RESET_CMD_GID;
cold_reset_cmd[1] = COLD_RESET_OID;
cold_reset_cmd[2] = COLD_RESET_CMD_PAYLOAD_LEN;
if (nfc_dev->interface == PLATFORM_IF_I2C)
ret = i2c_write(&nfc_dev->i2c_dev, cold_reset_cmd,
COLD_RESET_CMD_LEN, MAX_RETRY_COUNT);
else
ret = i3c_write(&nfc_dev->i3c_dev, cold_reset_cmd,
COLD_RESET_CMD_LEN, MAX_RETRY_COUNT);
if (ret <= 0)
pr_err("%s: write failed after max retry, ret %d\n",
__func__, ret);
error:
kfree(cold_reset_cmd);
return ret;
}
void read_cold_reset_rsp(struct nfc_dev *nfc_dev, char *header)
{
int ret = -1;
char *cold_reset_rsp = NULL;
struct cold_reset *cold_reset = &nfc_dev->cold_reset;
cold_reset_rsp = kzalloc(COLD_RESET_RSP_LEN, GFP_DMA | GFP_KERNEL);
if (!cold_reset_rsp)
return;
/*
* read header also if NFC is disabled
* for enable case, will be taken care by nfc read thread
*/
if ((!cold_reset->is_nfc_enabled) &&
(nfc_dev->interface == PLATFORM_IF_I2C)) {
ret = i2c_read(&nfc_dev->i2c_dev, cold_reset_rsp,
NCI_HDR_LEN);
if (ret <= 0) {
pr_err("%s: failure to read cold reset rsp header\n",
__func__);
goto error;
}
} else {
/* For I3C driver, header is read by the worker thread */
memcpy(cold_reset_rsp, header, NCI_HDR_LEN);
}
if ((cold_reset_rsp[0] != COLD_RESET_RSP_GID)
|| (cold_reset_rsp[1] != COLD_RESET_OID)) {
pr_err("%s: - invalid response GID or OID for cold_reset\n",
__func__);
ret = -EINVAL;
goto error;
}
if ((NCI_HDR_LEN + cold_reset_rsp[2]) > COLD_RESET_RSP_LEN) {
pr_err("%s: - invalid response for cold_reset\n", __func__);
ret = -EINVAL;
goto error;
}
if (nfc_dev->interface == PLATFORM_IF_I2C)
ret = i2c_read(&nfc_dev->i2c_dev,
&cold_reset_rsp[NCI_PAYLOAD_IDX],
cold_reset_rsp[2]);
else
ret = i3c_read(&nfc_dev->i3c_dev,
&cold_reset_rsp[NCI_PAYLOAD_IDX],
cold_reset_rsp[2]);
if (ret <= 0) {
pr_err("%s: failure to read cold reset rsp payload\n",
__func__);
goto error;
}
cold_reset->status = cold_reset_rsp[NCI_PAYLOAD_IDX];
error:
kfree(cold_reset_rsp);
}
EXPORT_SYMBOL(read_cold_reset_rsp);
/*
* Power management of the eSE
* eSE and NFCC both are powered using VEN gpio,
* VEN HIGH - eSE and NFCC both are powered on
* VEN LOW - eSE and NFCC both are power down
*/
int nfc_ese_pwr(struct nfc_dev *nfc_dev, unsigned long arg)
{
int ret = 0;
if (arg == ESE_POWER_ON) {
/*
* Let's store the NFC VEN pin state
* will check stored value in case of eSE power off request,
* to find out if NFC MW also sent request to set VEN HIGH
* VEN state will remain HIGH if NFC is enabled otherwise
* it will be set as LOW
*/
nfc_dev->nfc_ven_enabled = gpio_get_value(nfc_dev->gpio.ven);
if (!nfc_dev->nfc_ven_enabled) {
pr_debug("eSE HAL service setting ven HIGH\n");
gpio_set_ven(nfc_dev, 1);
} else {
pr_debug("ven already HIGH\n");
}
} else if (arg == ESE_POWER_OFF) {
if (!nfc_dev->nfc_ven_enabled) {
pr_debug("NFC not enabled, disabling ven\n");
gpio_set_ven(nfc_dev, 0);
} else {
pr_debug("keep ven high as NFC is enabled\n");
}
} else if (arg == ESE_COLD_RESET) {
// set default value for status as failure
nfc_dev->cold_reset.status = -EIO;
ret = send_cold_reset_cmd(nfc_dev);
if (ret <= 0) {
pr_err("failed to send cold reset command\n");
return nfc_dev->cold_reset.status;
}
nfc_dev->cold_reset.rsp_pending = true;
// check if NFC is enabled
if (nfc_dev->cold_reset.is_nfc_enabled) {
/*
* nfc_read thread will initiate cold reset response
* and it will signal for data available
*/
wait_event_interruptible(nfc_dev->cold_reset.read_wq,
!nfc_dev->cold_reset.rsp_pending);
} else {
// Read data as NFC thread is not active
enable_interrupt(nfc_dev);
if (nfc_dev->interface == PLATFORM_IF_I2C) {
ret = wait_event_interruptible_timeout(
nfc_dev->read_wq,
!nfc_dev->i2c_dev.irq_enabled,
msecs_to_jiffies(MAX_IRQ_WAIT_TIME));
if (ret <= 0) {
disable_interrupt(nfc_dev);
nfc_dev->cold_reset.rsp_pending = false;
return nfc_dev->cold_reset.status;
}
read_cold_reset_rsp(nfc_dev, NULL);
nfc_dev->cold_reset.rsp_pending = false;
} else {
wait_event_interruptible(
nfc_dev->cold_reset.read_wq,
!nfc_dev->cold_reset.rsp_pending);
disable_interrupt(nfc_dev);
}
}
ret = nfc_dev->cold_reset.status;
} else if (arg == ESE_POWER_STATE) {
// eSE power state
ret = gpio_get_value(nfc_dev->gpio.ven);
} else {
pr_err("%s bad arg %lu\n", __func__, arg);
ret = -ENOIOCTLCMD;
}
return ret;
}
EXPORT_SYMBOL(nfc_ese_pwr);
/*
* nfc_ioctl_power_states() - power control
* @nfc_dev: nfc device data structure
* @arg: mode that we want to move to
*
* Device power control. Depending on the arg value, device moves to
* different states, refer nfcc_ioctl_request in nfc_common.h for args
*
* Return: -ENOIOCTLCMD if arg is not supported, 0 in any other case
*/
static int nfc_ioctl_power_states(struct nfc_dev *nfc_dev, unsigned long arg)
{
int ret = 0;
if (arg == NFC_POWER_OFF) {
/*
* We are attempting a hardware reset so let us disable
* interrupts to avoid spurious notifications to upper
* layers.
*/
disable_interrupt(nfc_dev);
pr_debug("gpio firm disable\n");
if (gpio_is_valid(nfc_dev->gpio.dwl_req)) {
gpio_set_value(nfc_dev->gpio.dwl_req, 0);
usleep_range(10000, 10100);
}
pr_debug("Set ven to low\n");
gpio_set_ven(nfc_dev, 0);
nfc_dev->nfc_ven_enabled = false;
} else if (arg == NFC_POWER_ON) {
enable_interrupt(nfc_dev);
pr_debug("gpio_set_value enable: %s:\n", __func__);
if (gpio_is_valid(nfc_dev->gpio.dwl_req)) {
gpio_set_value(nfc_dev->gpio.dwl_req, 0);
usleep_range(10000, 10100);
}
gpio_set_ven(nfc_dev, 1);
nfc_dev->nfc_ven_enabled = true;
if (nfc_dev->interface == PLATFORM_IF_I3C)
nfc_dev->i3c_dev.read_hdr = NCI_HDR_LEN;
} else if (arg == NFC_FW_DWL_VEN_TOGGLE) {
/*
* We are switching to download Mode, toggle the enable pin
* in order to set the NFCC in the new mode
*/
gpio_set_ven(nfc_dev, 1);
if (gpio_is_valid(nfc_dev->gpio.dwl_req)) {
gpio_set_value(nfc_dev->gpio.dwl_req, 1);
usleep_range(10000, 10100);
}
if (nfc_dev->interface == PLATFORM_IF_I2C) {
gpio_set_ven(nfc_dev, 0);
gpio_set_ven(nfc_dev, 1);
}
} else if (arg == NFC_FW_DWL_HIGH) {
/*
* Setting firmware download gpio to HIGH
* before FW download start
*/
pr_debug("set fw gpio high\n");
if (gpio_is_valid(nfc_dev->gpio.dwl_req)) {
gpio_set_value(nfc_dev->gpio.dwl_req, 1);
usleep_range(10000, 10100);
} else
pr_debug("gpio.dwl_req is invalid\n");
} else if (arg == NFC_VEN_FORCED_HARD_RESET
&& nfc_dev->interface == PLATFORM_IF_I2C) {
/*
* TODO: Enable Ven reset for I3C, after hot join integration
*/
gpio_set_value(nfc_dev->gpio.ven, 0);
usleep_range(10000, 10100);
gpio_set_value(nfc_dev->gpio.ven, 1);
usleep_range(10000, 10100);
pr_info("%s VEN forced reset done\n", __func__);
} else if (arg == NFC_FW_DWL_LOW) {
/*
* Setting firmware download gpio to LOW
* FW download finished
*/
pr_debug("set fw gpio LOW\n");
gpio_set_value(nfc_dev->gpio.dwl_req, 0);
usleep_range(10000, 10100);
if (nfc_dev->interface == PLATFORM_IF_I3C)
nfc_dev->i3c_dev.read_hdr = NCI_HDR_LEN;
} else if (arg == NFC_FW_HDR_LEN) {
if (nfc_dev->interface == PLATFORM_IF_I3C)
nfc_dev->i3c_dev.read_hdr = FW_HDR_LEN;
} else if (arg == NFC_ENABLE) {
/*
* Setting flag true when NFC is enabled
*/
nfc_dev->cold_reset.is_nfc_enabled = true;
} else if (arg == NFC_DISABLE) {
/*
* Setting flag true when NFC is disabled
*/
nfc_dev->cold_reset.is_nfc_enabled = false;
} else {
pr_err("%s bad arg %lu\n", __func__, arg);
ret = -ENOIOCTLCMD;
}
return ret;
}
/** @brief IOCTL function to be used to set or get data from upper layer.
*
* @param pfile fil node for opened device.
* @cmd IOCTL type from upper layer.
* @arg IOCTL arg from upper layer.
*
* @return 0 on success, error code for failures.
*/
long nfc_dev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct nfc_dev *nfc_dev = pfile->private_data;
if (!nfc_dev)
return -ENODEV;
pr_debug("%s cmd = %x arg = %zx\n", __func__, cmd, arg);
switch (cmd) {
case NFC_SET_PWR:
ret = nfc_ioctl_power_states(nfc_dev, arg);
break;
case ESE_SET_PWR:
ret = nfc_ese_pwr(nfc_dev, arg);
break;
case ESE_GET_PWR:
ret = nfc_ese_pwr(nfc_dev, ESE_POWER_STATE);
break;
default:
pr_err("%s bad cmd %lu\n", __func__, arg);
ret = -ENOIOCTLCMD;
}
return ret;
}
EXPORT_SYMBOL(nfc_dev_ioctl);
int nfc_dev_open(struct inode *inode, struct file *filp)
{
struct nfc_dev *nfc_dev = container_of(inode->i_cdev,
struct nfc_dev, c_dev);
if (!nfc_dev)
return -ENODEV;
pr_debug("%s: %d, %d\n", __func__, imajor(inode), iminor(inode));
mutex_lock(&nfc_dev->dev_ref_mutex);
filp->private_data = nfc_dev;
if (nfc_dev->dev_ref_count == 0) {
if (gpio_is_valid(nfc_dev->gpio.dwl_req)) {
gpio_set_value(nfc_dev->gpio.dwl_req, 0);
usleep_range(10000, 10100);
}
enable_interrupt(nfc_dev);
}
nfc_dev->dev_ref_count = nfc_dev->dev_ref_count + 1;
mutex_unlock(&nfc_dev->dev_ref_mutex);
return 0;
}
EXPORT_SYMBOL(nfc_dev_open);
int nfc_dev_close(struct inode *inode, struct file *filp)
{
struct nfc_dev *nfc_dev = container_of(inode->i_cdev,
struct nfc_dev, c_dev);
if (!nfc_dev)
return -ENODEV;
pr_debug("%s: %d, %d\n", __func__, imajor(inode), iminor(inode));
mutex_lock(&nfc_dev->dev_ref_mutex);
if (nfc_dev->dev_ref_count == 1) {
disable_interrupt(nfc_dev);
if (gpio_is_valid(nfc_dev->gpio.dwl_req)) {
gpio_set_value(nfc_dev->gpio.dwl_req, 0);
usleep_range(10000, 10100);
}
}
if (nfc_dev->dev_ref_count > 0)
nfc_dev->dev_ref_count = nfc_dev->dev_ref_count - 1;
filp->private_data = NULL;
mutex_unlock(&nfc_dev->dev_ref_mutex);
return 0;
}
EXPORT_SYMBOL(nfc_dev_close);

View file

@ -0,0 +1,193 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2015-2019, The Linux Foundation. All rights reserved.
*/
#ifndef _NFC_COMMON_H_
#define _NFC_COMMON_H_
#include <linux/types.h>
#include <linux/version.h>
#include <linux/semaphore.h>
#include <linux/completion.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/spinlock.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include "nfc_i2c_drv.h"
#include "nfc_i3c_drv.h"
// Max device count for this driver
#define DEV_COUNT 1
// NFC device class
#define CLASS_NAME "nfc"
// NFC character device name, this will be in /dev/
#define NFC_CHAR_DEV_NAME "nq-nci"
// HDR length of NCI packet
#define NCI_HDR_LEN 3
#define NCI_PAYLOAD_IDX 3
#define COLD_RESET_CMD_LEN 3
#define COLD_RESET_RSP_LEN 4
#define COLD_RESET_CMD_GID 0x2F
#define COLD_RESET_CMD_PAYLOAD_LEN 0x00
#define COLD_RESET_RSP_GID 0x4F
#define COLD_RESET_OID 0x1E
#define MAX_NCI_PAYLOAD_LEN (255)
#define MAX_BUFFER_SIZE (NCI_HDR_LEN + MAX_NCI_PAYLOAD_LEN)
// Maximum retry count for standby writes
#define MAX_RETRY_COUNT (3)
// Retry count for normal write
#define NO_RETRY (1)
#define MAX_IRQ_WAIT_TIME (90)
#define WAKEUP_SRC_TIMEOUT (2000)
#define NFC_MAGIC 0xE9
// Ioctls
// The type should be aligned with MW HAL definitions
#define NFC_SET_PWR _IOW(NFC_MAGIC, 0x01, unsigned int)
#define ESE_SET_PWR _IOW(NFC_MAGIC, 0x02, unsigned int)
#define ESE_GET_PWR _IOR(NFC_MAGIC, 0x03, unsigned int)
#define DTS_IRQ_GPIO_STR "qcom,sn-irq"
#define DTS_VEN_GPIO_STR "qcom,sn-ven"
#define DTS_FWDN_GPIO_STR "qcom,sn-firm"
#define DTS_CLKREQ_GPIO_STR "qcom,sn-clkreq"
#define DTS_CLKSRC_GPIO_STR "qcom,clk-src"
enum ese_ioctl_request {
/* eSE POWER ON */
ESE_POWER_ON = 0,
/* eSE POWER OFF */
ESE_POWER_OFF,
/* eSE COLD RESET */
ESE_COLD_RESET,
/* eSE POWER STATE */
ESE_POWER_STATE
};
enum nfcc_ioctl_request {
/* NFC disable request with VEN LOW */
NFC_POWER_OFF = 0,
/* NFC enable request with VEN Toggle */
NFC_POWER_ON,
/* firmware download request with VEN Toggle */
NFC_FW_DWL_VEN_TOGGLE,
/* ISO reset request */
NFC_ISO_RESET,
/* request for firmware download gpio HIGH */
NFC_FW_DWL_HIGH,
/* VEN hard reset request */
NFC_VEN_FORCED_HARD_RESET,
/* request for firmware download gpio LOW */
NFC_FW_DWL_LOW,
/* NFC enable without VEN gpio modification */
NFC_ENABLE,
/* NFC disable without VEN gpio modification */
NFC_DISABLE,
/*for HDR size change in FW mode */
NFC_FW_HDR_LEN,
};
/*nfc platform interface type*/
enum interface_flags {
/*I2C physical IF for NFCC */
PLATFORM_IF_I2C = 0,
/*I3C physical IF for NFCC */
PLATFORM_IF_I3C,
};
/*
* Power state for IBI handing, mainly needed to defer the IBI handling
* for the IBI received in suspend state to do it later in resume call
*/
enum pm_state_flags {
PM_STATE_NORMAL = 0,
PM_STATE_SUSPEND,
PM_STATE_IBI_BEFORE_RESUME,
};
/* Enum for GPIO values*/
enum gpio_values {
GPIO_INPUT = 0x0,
GPIO_OUTPUT = 0x1,
GPIO_HIGH = 0x2,
GPIO_OUTPUT_HIGH = 0x3,
GPIO_IRQ = 0x4,
};
// NFC GPIO variables
struct platform_gpio {
unsigned int irq;
unsigned int ven;
unsigned int clkreq;
unsigned int dwl_req;
};
//Features specific Parameters
struct cold_reset {
wait_queue_head_t read_wq;
bool rsp_pending;
uint8_t status;
/* Is NFC enabled from UI */
bool is_nfc_enabled;
};
/* Device specific structure */
struct nfc_dev {
wait_queue_head_t read_wq;
struct mutex read_mutex;
struct mutex dev_ref_mutex;
unsigned int dev_ref_count;
struct class *nfc_class;
struct device *nfc_device;
struct cdev c_dev;
dev_t devno;
/* Interface flag */
uint8_t interface;
/* NFC VEN pin state */
bool nfc_ven_enabled;
union {
struct i2c_dev i2c_dev;
struct i3c_dev i3c_dev;
};
struct platform_gpio gpio;
struct cold_reset cold_reset;
/* read buffer*/
size_t kbuflen;
u8 *kbuf;
};
int nfc_dev_open(struct inode *inode, struct file *filp);
int nfc_dev_close(struct inode *inode, struct file *filp);
long nfc_dev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg);
int nfc_parse_dt(struct device *dev, struct platform_gpio *nfc_gpio,
uint8_t interface);
int nfc_misc_probe(struct nfc_dev *nfc_dev,
const struct file_operations *nfc_fops, int count,
char *devname, char *classname);
void nfc_misc_remove(struct nfc_dev *nfc_dev, int count);
int configure_gpio(unsigned int gpio, int flag);
void read_cold_reset_rsp(struct nfc_dev *nfc_dev, char *header);
void gpio_set_ven(struct nfc_dev *nfc_dev, int value);
#endif //_NFC_COMMON_H_

View file

@ -0,0 +1,475 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015-2019, The Linux Foundation. All rights reserved.
*/
#include "nfc_common.h"
/**
* i2c_disable_irq()
*
* Check if interrupt is disabled or not
* and disable interrupt
*
* Return: void
*/
void i2c_disable_irq(struct i2c_dev *i2c_dev)
{
unsigned long flags;
spin_lock_irqsave(&i2c_dev->irq_enabled_lock, flags);
if (i2c_dev->irq_enabled) {
disable_irq_nosync(i2c_dev->client->irq);
i2c_dev->irq_enabled = false;
}
spin_unlock_irqrestore(&i2c_dev->irq_enabled_lock, flags);
}
/**
* i2c_enable_irq()
*
* Check if interrupt is enabled or not
* and enable interrupt
*
* Return: void
*/
void i2c_enable_irq(struct i2c_dev *i2c_dev)
{
unsigned long flags;
spin_lock_irqsave(&i2c_dev->irq_enabled_lock, flags);
if (!i2c_dev->irq_enabled) {
i2c_dev->irq_enabled = true;
enable_irq(i2c_dev->client->irq);
}
spin_unlock_irqrestore(&i2c_dev->irq_enabled_lock, flags);
}
static irqreturn_t i2c_irq_handler(int irq, void *dev_id)
{
struct nfc_dev *nfc_dev = dev_id;
struct i2c_dev *i2c_dev = &nfc_dev->i2c_dev;
if (device_may_wakeup(&i2c_dev->client->dev))
pm_wakeup_event(&i2c_dev->client->dev, WAKEUP_SRC_TIMEOUT);
i2c_disable_irq(i2c_dev);
wake_up(&nfc_dev->read_wq);
return IRQ_HANDLED;
}
int i2c_read(struct i2c_dev *i2c_dev, char *buf, size_t count)
{
int ret;
pr_debug("%s : reading %zu bytes.\n", __func__, count);
/* Read data */
ret = i2c_master_recv(i2c_dev->client, buf, count);
if (ret <= 0) {
pr_err("%s: i2c_master_recv returned %d\n", __func__, ret);
goto i2c_read_err;
}
if (ret > count) {
pr_err("%s: received too many bytes from i2c (%d)\n",
__func__, ret);
ret = -EIO;
}
/* delay for the slow nfc devices between susequent read operation */
usleep_range(1000, 1100);
i2c_read_err:
return ret;
}
EXPORT_SYMBOL(i2c_read);
int i2c_write(struct i2c_dev *i2c_dev, char *buf, size_t count,
int max_retry_cnt)
{
int ret = -EINVAL;
int retry_cnt;
pr_debug("%s : writing %zu bytes.\n", __func__, count);
for (retry_cnt = 1; retry_cnt <= max_retry_cnt; retry_cnt++) {
ret = i2c_master_send(i2c_dev->client, buf, count);
if (ret <= 0) {
pr_warn("%s: write failed, Maybe in Standby Mode - Retry(%d)\n",
__func__, retry_cnt);
usleep_range(1000, 1100);
} else if (ret == count)
break;
}
return ret;
}
EXPORT_SYMBOL(i2c_write);
ssize_t nfc_i2c_dev_read(struct file *filp, char __user *buf,
size_t count, loff_t *offset)
{
int ret;
char *tmp = NULL;
struct nfc_dev *nfc_dev = filp->private_data;
struct i2c_dev *i2c_dev = &nfc_dev->i2c_dev;
if (!nfc_dev)
return -ENODEV;
if (count > nfc_dev->kbuflen)
count = nfc_dev->kbuflen;
pr_debug("%s : reading %zu bytes.\n", __func__, count);
mutex_lock(&nfc_dev->read_mutex);
if (!gpio_get_value(nfc_dev->gpio.irq)) {
if (filp->f_flags & O_NONBLOCK) {
pr_err(":f_falg has O_NONBLOCK. EAGAIN\n");
ret = -EAGAIN;
goto err;
}
while (1) {
ret = 0;
if (!i2c_dev->irq_enabled) {
i2c_dev->irq_enabled = true;
enable_irq(i2c_dev->client->irq);
}
ret = wait_event_interruptible(nfc_dev->read_wq,
!i2c_dev->irq_enabled);
if (ret) {
pr_err("error wakeup of read wq\n");
goto err;
}
i2c_disable_irq(i2c_dev);
if (!gpio_get_value(nfc_dev->gpio.ven)) {
pr_info("%s: releasing read\n", __func__);
ret = -EIO;
goto err;
}
if (gpio_get_value(nfc_dev->gpio.irq))
break;
pr_warn("%s: spurious interrupt detected\n", __func__);
}
}
tmp = nfc_dev->kbuf;
if (!tmp) {
pr_err("%s: device doesn't exist anymore\n", __func__);
ret = -ENODEV;
goto err;
}
memset(tmp, 0x00, count);
/* Read data */
ret = i2c_read(i2c_dev, tmp, count);
if (ret <= 0) {
pr_err("%s: i2c_master_recv returned %d\n", __func__, ret);
goto err;
}
/* check if it's response of cold reset command
* NFC HAL process shouldn't receive this data as
* command was sent by SPI driver
*/
if (nfc_dev->cold_reset.rsp_pending
&& (tmp[0] == COLD_RESET_RSP_GID)
&& (tmp[1] == COLD_RESET_OID)) {
read_cold_reset_rsp(nfc_dev, tmp);
nfc_dev->cold_reset.rsp_pending = false;
wake_up_interruptible(&nfc_dev->cold_reset.read_wq);
mutex_unlock(&nfc_dev->read_mutex);
/*
* NFC process doesn't know about cold reset command
* being sent as it was initiated by eSE process
* we shouldn't return any data to NFC process
*/
return 0;
}
if (copy_to_user(buf, tmp, ret)) {
pr_warn("%s : failed to copy to user space\n", __func__);
ret = -EFAULT;
}
err:
mutex_unlock(&nfc_dev->read_mutex);
return ret;
}
ssize_t nfc_i2c_dev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *offset)
{
int ret;
char *tmp = NULL;
struct nfc_dev *nfc_dev = filp->private_data;
struct i2c_dev *i2c_dev = &nfc_dev->i2c_dev;
if (!nfc_dev) {
ret = -ENODEV;
goto out;
}
if (count > nfc_dev->kbuflen) {
pr_err("%s: out of memory\n", __func__);
ret = -ENOMEM;
goto out;
}
tmp = memdup_user(buf, count);
if (IS_ERR(tmp)) {
pr_err("%s: memdup_user failed\n", __func__);
ret = PTR_ERR(tmp);
goto out;
}
ret = i2c_write(i2c_dev, tmp, count, NO_RETRY);
if (ret != count) {
pr_err("%s: failed to write %d\n", __func__, ret);
ret = -EIO;
goto out_free;
}
out_free:
kfree(tmp);
out:
return ret;
}
static const struct file_operations nfc_i2c_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = nfc_i2c_dev_read,
.write = nfc_i2c_dev_write,
.open = nfc_dev_open,
.release = nfc_dev_close,
.unlocked_ioctl = nfc_dev_ioctl,
};
int nfc_i2c_dev_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
struct nfc_dev *nfc_dev = NULL;
struct i2c_dev *i2c_dev = NULL;
struct platform_gpio nfc_gpio;
pr_debug("%s: enter\n", __func__);
//retrieve details of gpios from dt
ret = nfc_parse_dt(&client->dev, &nfc_gpio, PLATFORM_IF_I2C);
if (ret) {
pr_err("%s : failed to parse dt\n", __func__);
goto err;
}
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
pr_err("%s : need I2C_FUNC_I2C\n", __func__);
ret = -ENODEV;
goto err;
}
nfc_dev = kzalloc(sizeof(struct nfc_dev), GFP_KERNEL);
if (nfc_dev == NULL) {
ret = -ENOMEM;
goto err;
}
nfc_dev->interface = PLATFORM_IF_I2C;
nfc_dev->i2c_dev.client = client;
i2c_dev = &nfc_dev->i2c_dev;
ret = configure_gpio(nfc_gpio.ven, GPIO_OUTPUT);
if (ret) {
pr_err("%s: unable to request nfc reset gpio [%d]\n",
__func__, nfc_gpio.ven);
goto err_free_nfc_dev;
}
ret = configure_gpio(nfc_gpio.irq, GPIO_IRQ);
if (ret <= 0) {
pr_err("%s: unable to request nfc irq gpio [%d]\n",
__func__, nfc_gpio.irq);
goto err_free_ven;
}
client->irq = ret;
ret = configure_gpio(nfc_gpio.dwl_req, GPIO_OUTPUT);
if (ret) {
pr_err("%s: unable to request nfc firm downl gpio [%d]\n",
__func__, nfc_gpio.dwl_req);
goto err_free_irq;
}
ret = configure_gpio(nfc_gpio.clkreq, GPIO_INPUT);
if (ret) {
pr_err("%s: unable to request nfc clkreq gpio [%d]\n",
__func__, nfc_gpio.clkreq);
goto err_free_dwl_req;
}
nfc_dev->gpio.ven = nfc_gpio.ven;
nfc_dev->gpio.irq = nfc_gpio.irq;
nfc_dev->gpio.dwl_req = nfc_gpio.dwl_req;
nfc_dev->gpio.clkreq = nfc_gpio.clkreq;
/* init mutex and queues */
init_waitqueue_head(&nfc_dev->read_wq);
mutex_init(&nfc_dev->read_mutex);
mutex_init(&nfc_dev->dev_ref_mutex);
spin_lock_init(&i2c_dev->irq_enabled_lock);
ret = nfc_misc_probe(nfc_dev, &nfc_i2c_dev_fops, DEV_COUNT,
NFC_CHAR_DEV_NAME, CLASS_NAME);
if (ret) {
pr_err("%s: nfc_misc_probe failed\n", __func__);
goto err_mutex_destroy;
}
/* interrupt initializations */
pr_info("%s : requesting IRQ %d\n", __func__, client->irq);
i2c_dev->irq_enabled = true;
ret = request_irq(client->irq, i2c_irq_handler,
IRQF_TRIGGER_HIGH, client->name, nfc_dev);
if (ret) {
pr_err("%s: request_irq failed\n", __func__);
goto err_nfc_misc_remove;
}
i2c_disable_irq(i2c_dev);
device_init_wakeup(&client->dev, true);
device_set_wakeup_capable(&client->dev, true);
i2c_set_clientdata(client, nfc_dev);
i2c_dev->irq_wake_up = false;
//SET VEN GPIO LOW and HIGH
gpio_set_value(nfc_dev->gpio.ven, 0);
usleep_range(10000, 10100);
gpio_set_value(nfc_dev->gpio.ven, 1);
usleep_range(10000, 10100);
nfc_dev->nfc_ven_enabled = true;
pr_info("%s success\n", __func__);
return 0;
err_nfc_misc_remove:
nfc_misc_remove(nfc_dev, DEV_COUNT);
err_mutex_destroy:
mutex_destroy(&nfc_dev->dev_ref_mutex);
mutex_destroy(&nfc_dev->read_mutex);
gpio_free(nfc_dev->gpio.clkreq);
err_free_dwl_req:
gpio_free(nfc_dev->gpio.dwl_req);
err_free_irq:
gpio_free(nfc_dev->gpio.irq);
err_free_ven:
gpio_free(nfc_dev->gpio.ven);
err_free_nfc_dev:
kfree(nfc_dev);
err:
pr_err("%s: failed\n", __func__);
return ret;
}
int nfc_i2c_dev_remove(struct i2c_client *client)
{
int ret = 0;
struct nfc_dev *nfc_dev = NULL;
pr_info("%s: remove device\n", __func__);
nfc_dev = i2c_get_clientdata(client);
if (!nfc_dev) {
pr_err("%s: device doesn't exist anymore\n", __func__);
ret = -ENODEV;
return ret;
}
free_irq(client->irq, nfc_dev);
nfc_misc_remove(nfc_dev, DEV_COUNT);
mutex_destroy(&nfc_dev->dev_ref_mutex);
mutex_destroy(&nfc_dev->read_mutex);
if (gpio_is_valid(nfc_dev->gpio.clkreq))
gpio_free(nfc_dev->gpio.clkreq);
if (gpio_is_valid(nfc_dev->gpio.dwl_req))
gpio_free(nfc_dev->gpio.dwl_req);
if (gpio_is_valid(nfc_dev->gpio.irq))
gpio_free(nfc_dev->gpio.irq);
if (gpio_is_valid(nfc_dev->gpio.ven))
gpio_free(nfc_dev->gpio.ven);
kfree(nfc_dev);
return ret;
}
int nfc_i2c_dev_suspend(struct device *device)
{
struct i2c_client *client = to_i2c_client(device);
struct nfc_dev *nfc_dev = i2c_get_clientdata(client);
struct i2c_dev *i2c_dev = &nfc_dev->i2c_dev;
if (device_may_wakeup(&client->dev) && i2c_dev->irq_enabled) {
if (!enable_irq_wake(client->irq))
i2c_dev->irq_wake_up = true;
}
return 0;
}
int nfc_i2c_dev_resume(struct device *device)
{
struct i2c_client *client = to_i2c_client(device);
struct nfc_dev *nfc_dev = i2c_get_clientdata(client);
struct i2c_dev *i2c_dev = &nfc_dev->i2c_dev;
if (device_may_wakeup(&client->dev) && i2c_dev->irq_wake_up) {
if (!disable_irq_wake(client->irq))
i2c_dev->irq_wake_up = false;
}
return 0;
}
static const struct i2c_device_id nfc_i2c_dev_id[] = {
{NFC_I2C_DEV_ID, 0},
{}
};
static const struct of_device_id nfc_i2c_dev_match_table[] = {
{.compatible = NFC_I2C_DRV_STR,},
{}
};
static const struct dev_pm_ops nfc_i2c_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(nfc_i2c_dev_suspend, nfc_i2c_dev_resume)
};
static struct i2c_driver nfc_i2c_dev_driver = {
.id_table = nfc_i2c_dev_id,
.probe = nfc_i2c_dev_probe,
.remove = nfc_i2c_dev_remove,
.driver = {
.name = NFC_I2C_DRV_STR,
.pm = &nfc_i2c_dev_pm_ops,
.of_match_table = nfc_i2c_dev_match_table,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
MODULE_DEVICE_TABLE(of, nfc_i2c_dev_match_table);
static int __init nfc_i2c_dev_init(void)
{
int ret = 0;
pr_info("Loading NFC I2C driver\n");
ret = i2c_add_driver(&nfc_i2c_dev_driver);
return ret;
}
module_init(nfc_i2c_dev_init);
static void __exit nfc_i2c_dev_exit(void)
{
pr_info("Unloading NFC I2C driver\n");
i2c_del_driver(&nfc_i2c_dev_driver);
}
module_exit(nfc_i2c_dev_exit);
MODULE_DESCRIPTION("QTI NFC I2C driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2015-2019, The Linux Foundation. All rights reserved.
*/
#ifndef _NFC_I2C_DRV_H_
#define _NFC_I2C_DRV_H_
#include <linux/i2c.h>
#define NFC_I2C_DRV_STR "qcom,sn-nci" /*kept same as dts */
#define NFC_I2C_DEV_ID "sn-i2c"
//Interface specific parameters
struct i2c_dev {
struct i2c_client *client;
// IRQ parameters
bool irq_enabled;
spinlock_t irq_enabled_lock;
// NFC_IRQ wake-up state
bool irq_wake_up;
};
long nfc_i2c_dev_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg);
int nfc_i2c_dev_probe(struct i2c_client *client,
const struct i2c_device_id *id);
int nfc_i2c_dev_remove(struct i2c_client *client);
int nfc_i2c_dev_suspend(struct device *device);
int nfc_i2c_dev_resume(struct device *device);
#ifdef CONFIG_NFC_QTI_I2C
void i2c_enable_irq(struct i2c_dev *i2c_dev);
void i2c_disable_irq(struct i2c_dev *i2c_dev);
int i2c_write(struct i2c_dev *i2c_dev, char *buf, size_t count,
int max_retry_cnt);
int i2c_read(struct i2c_dev *i2c_dev, char *buf, size_t count);
#else
static inline void i2c_enable_irq(struct i2c_dev *i2c_dev)
{
}
static inline void i2c_disable_irq(struct i2c_dev *i2c_dev)
{
}
static inline int i2c_write(struct i2c_dev *i2c_dev, char *buf, size_t count,
int max_retry_cnt)
{
return -ENXIO;
}
static inline int i2c_read(struct i2c_dev *i2c_dev, char *buf, size_t count)
{
return -ENXIO;
}
#endif
#endif //_NFC_I2C_DRV_H_

View file

@ -0,0 +1,845 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019, The Linux Foundation. All rights reserved.
*/
#include "nfc_common.h"
/** @brief This API used to write I3C data to I3C device.
*
* @param dev the i3c_dev for the i3c device.
* @param buf the data to write
* @param count the number of bytes of data to be written.
* @param max_retry_count the number of retries for the write operation
*
* @return ret number of bytes written ,negative error core otherwise.
*/
ssize_t i3c_write(struct i3c_dev *i3c_dev, const char *buf, const size_t count,
int max_retry_cnt)
{
int ret = -EIO;
int retry_count = 0;
struct i3c_priv_xfer write_buf = {
.rnw = NFC_I3C_WRITE,
.len = count,
.data.out = buf
};
do {
ret = i3c_device_do_priv_xfers(i3c_dev->device, &write_buf,
(sizeof(write_buf) / sizeof(struct i3c_priv_xfer)));
pr_debug("%s exit ret = %x\n", __func__, ret);
if (!ret) {
ret = count;
break;
}
pr_err("%s errno = %x\n", __func__, write_buf.err);
retry_count++;
usleep_range(1000, 1100);
} while (retry_count < max_retry_cnt);
return ret;
}
EXPORT_SYMBOL(i3c_write);
/** @brief This API used to read data from I3C device.
*
* @param dev the i3c_dev for the slave.
* @param buf the buffer to copy the data.
* @param count the number of bytes to be read.
*
* @return number of bytes read ,negative error code otherwise.
*/
ssize_t i3c_read(struct i3c_dev *i3c_dev, char *buf, size_t count)
{
int ret = -EIO;
struct i3c_priv_xfer read_buf = {
.rnw = NFC_I3C_READ,
.len = count,
.data.in = buf
};
ret = i3c_device_do_priv_xfers(i3c_dev->device, &read_buf,
(sizeof(read_buf) / sizeof(struct i3c_priv_xfer)));
pr_debug("%s exit ret = %x\n", __func__, ret);
if (!ret)
ret = count;
else
pr_err("%s errno = %x\n", __func__, read_buf.err);
return ret;
}
EXPORT_SYMBOL(i3c_read);
/** @brief This API can be used to write data to nci buf.
* The API will overwrite the existing memory if
* it reaches the end of total allocated memory.
*
* @param dev the dev structure for driver.
* @param readbuf the buffer to be copied data from
* @param count the number of bytes to copy to nci_buffer.
*
* @return number of bytes copied to nci buffer , error code otherwise
*/
static ssize_t i3c_kbuf_store(struct i3c_dev *i3c_dev, const char *buf,
const size_t count)
{
size_t buf_offset = 0;
size_t requested_size = count;
size_t available_size = 0;
if (i3c_dev == NULL)
return -ENODEV;
else if (buf == NULL || count == 0)
return -EINVAL;
pr_debug("%s enter\n", __func__);
if (count > i3c_dev->buf.total_size) {
pr_err("%s No memory to copy the data\n", __func__);
return -ENOMEM;
}
pr_debug("%s:total_size %zx write_off = %x read_off = %x\n",
__func__, i3c_dev->buf.total_size, i3c_dev->buf.write_offset,
i3c_dev->buf.read_offset);
mutex_lock(&i3c_dev->nci_buf_mutex);
available_size = i3c_dev->buf.total_size - i3c_dev->buf.write_offset;
/*
* When available buffer is less than requested count,
* copy the data upto available memory.
* The remaining data is copied to the start of memory.
* The write offset is incremented by the remaining copied bytes
* from the beginning.
*/
if (requested_size > available_size) {
pr_warn
("%s:out of mem req_size = %zx avail = %zx\n",
__func__, requested_size, available_size);
memcpy(i3c_dev->buf.kbuf + i3c_dev->buf.write_offset,
buf + buf_offset, available_size);
requested_size = requested_size - available_size;
i3c_dev->buf.write_offset = 0;
buf_offset = available_size;
pr_debug("%s: requested_size = %zx available_size = %zx\n",
__func__, requested_size, available_size);
}
if (requested_size) {
memcpy(i3c_dev->buf.kbuf + i3c_dev->buf.write_offset,
buf + buf_offset, requested_size);
i3c_dev->buf.write_offset += requested_size;
if (i3c_dev->buf.write_offset == i3c_dev->buf.total_size)
i3c_dev->buf.write_offset = 0;
}
complete(&i3c_dev->read_cplt);
mutex_unlock(&i3c_dev->nci_buf_mutex);
pr_debug("%s: total bytes req_size = %zx avail_size = %zx\n",
__func__, requested_size, available_size);
return count;
}
/** @brief This API can be used to retrieve data from driver buffer.
*
* When data is not available, it waits for required data to be present.
* When data is present it copies the data into buffer reuested by read.
* @param dev the dev structure for driver.
* @param buf the buffer to copy the data.
* @param count the number of bytes to be read.
*
* @return number of bytes copied , error code for failures .
*/
static ssize_t i3c_nci_kbuf_retrieve(struct i3c_dev *i3c_dev, char *buf,
size_t count)
{
size_t requested_size = count;
size_t available_size = 0;
size_t copied_size = 0;
int ret = 0;
if (i3c_dev == NULL)
return -ENODEV;
else if (buf == NULL || count == 0)
return -EINVAL;
pr_debug("%s enter\n", __func__);
/*
* When the requested data count is more than available data to read,
* wait on completion till the requested bytes are available.
* If write offset is more than read offset and available data is
* more than requested count, copy the requested bytes directly and
* increment the read_offset.
* If read offset is more than write offset,
* available size is total_size size - read_offset
* and upto write offset from the beginning of buffer.
*/
do {
pr_debug("%s: read_offset = %x write_offset = %x\n", __func__,
i3c_dev->buf.read_offset, i3c_dev->buf.write_offset);
mutex_lock(&i3c_dev->nci_buf_mutex);
if (i3c_dev->buf.read_offset <= i3c_dev->buf.write_offset)
available_size =
i3c_dev->buf.write_offset -
i3c_dev->buf.read_offset;
else
available_size =
(i3c_dev->buf.total_size -
i3c_dev->buf.read_offset) +
i3c_dev->buf.write_offset;
mutex_unlock(&i3c_dev->nci_buf_mutex);
if (available_size >= requested_size)
break;
reinit_completion(&i3c_dev->read_cplt);
ret = wait_for_completion_interruptible(&i3c_dev->read_cplt);
if (ret != 0) {
pr_err("didn't get completion, interrupted!! ret %d\n",
ret);
return -EINVAL;
}
} while (available_size < requested_size);
mutex_lock(&i3c_dev->nci_buf_mutex);
if (i3c_dev->buf.write_offset >=
i3c_dev->buf.read_offset + requested_size) {
/*
* Write offset is more than read offset + count , copy the data
* directly and increment the read offset
*/
memcpy(buf, i3c_dev->buf.kbuf + i3c_dev->buf.read_offset,
requested_size);
i3c_dev->buf.read_offset += requested_size;
} else {
copied_size =
i3c_dev->buf.total_size - i3c_dev->buf.read_offset;
if (copied_size > requested_size)
copied_size = requested_size;
/*
* Read offset is more than write offset. Copy requested data
* from read_offset to the total size and increment the read
* offset. If requested data is still greater than zero,
* copy the data from beginning of buffer.
*/
memcpy(buf, i3c_dev->buf.kbuf + i3c_dev->buf.read_offset,
copied_size);
requested_size = requested_size - copied_size;
i3c_dev->buf.read_offset += copied_size;
if (requested_size) {
pr_debug("%s remaining copied bytes\n", __func__);
memcpy(buf + copied_size, i3c_dev->buf.kbuf,
requested_size);
i3c_dev->buf.read_offset = requested_size;
}
}
mutex_unlock(&i3c_dev->nci_buf_mutex);
pr_debug("%s , count = %zx exit\n", __func__, count);
return count;
}
/** @brief This API can be used to read data from I3C device from HAL layer.
*
* This read function is registered during probe.
* When data is not available, it waits for required data to be present.
* @param filp the device file handle opened by HAL.
* @param buf the buffer to read the data.
* @param count the number of bytes to be read.
* @param offset the offset in the buf.
*
* @return Number of bytes read from I3C device ,error code for failures.
*/
ssize_t nfc_i3c_dev_read(struct file *filp, char __user *buf,
size_t count, loff_t *offset)
{
int ret;
char *tmp = NULL;
struct nfc_dev *nfc_dev = filp->private_data;
struct i3c_dev *i3c_dev = &nfc_dev->i3c_dev;
if (count > nfc_dev->kbuflen)
count = nfc_dev->kbuflen;
pr_debug("%s : reading %zu bytes\n", __func__, count);
mutex_lock(&nfc_dev->read_mutex);
tmp = nfc_dev->kbuf;
ret = i3c_nci_kbuf_retrieve(i3c_dev, tmp, count);
if (ret != count) {
pr_err("%s: buf read from I3C device returned error (%d)\n",
__func__, ret);
ret = -EIO;
} else if (copy_to_user(buf, tmp, ret)) {
pr_warn("%s : failed to copy to user space\n", __func__);
ret = -EFAULT;
}
mutex_unlock(&nfc_dev->read_mutex);
return ret;
}
/** @brief This API can be used to write data to I3C device.
*
* @param dev the i3c_dev for the slave.
* @param buf the buffer to copy the data.
* @param count the number of bytes to be read.
*
* @return ret count number of bytes written, error code for failures.
*/
ssize_t nfc_i3c_dev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *offset)
{
int ret;
char *tmp = NULL;
struct nfc_dev *nfc_dev = filp->private_data;
struct i3c_dev *i3c_dev = &nfc_dev->i3c_dev;
if (!nfc_dev)
return -ENODEV;
if (count > nfc_dev->kbuflen) {
pr_err("%s: out of memory\n", __func__);
return -ENOMEM;
}
tmp = memdup_user(buf, count);
if (IS_ERR(tmp)) {
pr_err("%s: memdup_user failed\n", __func__);
ret = PTR_ERR(tmp);
goto out;
}
ret = i3c_write(i3c_dev, tmp, count, NO_RETRY);
if (ret != count) {
pr_err("%s: failed to write %d\n", __func__, ret);
ret = -EIO;
goto out_free;
}
pr_debug("%s : i3c-%d: NfcNciTx %x %x %x\n", __func__,
iminor(file_inode(filp)), tmp[0], tmp[1], tmp[2]);
pr_debug("%s : ret = %x\n", __func__, ret);
out_free:
kfree(tmp);
out:
return ret;
}
/** @brief This API shall be called from workqueue queue from IBI handler.
* First it will read HDR byte from I3C chip.Based on the length byte
* it will read the next length bytes.Then it will write these bytes
* to nci write buf.
* @param work the work added into the workqueue.
*
* @return void
*/
static void i3c_workqueue_handler(struct work_struct *work)
{
int ret = 0;
int length_byte = 0;
unsigned char *tmp = NULL;
unsigned char hdr_len = NCI_HDR_LEN;
struct i3c_dev *i3c_dev = container_of(work, struct i3c_dev, work);
struct nfc_dev *nfc_dev = container_of(i3c_dev,
struct nfc_dev, i3c_dev);
if (!i3c_dev) {
pr_err("%s: dev not found\n", __func__);
return;
}
hdr_len = i3c_dev->read_hdr;
tmp = i3c_dev->read_kbuf;
if (!tmp) {
pr_err("%s: No memory to copy read data\n", __func__);
return;
}
pr_info("%s: hdr_len = %d\n", __func__, hdr_len);
memset(tmp, 0x00, i3c_dev->read_kbuf_len);
ret = i3c_read(i3c_dev, tmp, hdr_len);
if (ret < 0) {
pr_err("%s: i3c_read returned error %d\n", __func__, ret);
return;
}
if (hdr_len == FW_HDR_LEN)
length_byte = tmp[hdr_len - 1] + FW_CRC_LEN;
else
length_byte = tmp[hdr_len - 1];
/* check if it's response of cold reset command
* NFC HAL process shouldn't receive this data as
* command was sent by SPI driver
*/
if (nfc_dev->cold_reset.rsp_pending
&& (tmp[0] == COLD_RESET_RSP_GID)
&& (tmp[1] == COLD_RESET_OID)) {
read_cold_reset_rsp(nfc_dev, tmp);
nfc_dev->cold_reset.rsp_pending = false;
wake_up_interruptible(&nfc_dev->cold_reset.read_wq);
/*
* NFC process doesn't know about cold reset command
* being sent as it was initiated by eSE process
* we shouldn't return any data to NFC process
*/
return;
}
ret = i3c_read(i3c_dev, tmp + hdr_len, length_byte);
if (ret < 0) {
pr_err("%s: i3c_read returned error %d\n", __func__, ret);
i3c_kbuf_store(i3c_dev, tmp, hdr_len);
return;
}
i3c_kbuf_store(i3c_dev, tmp, hdr_len + length_byte);
}
/** @brief This API is used to handle IBI coming from the I3C device.
* This will add work into the workqueue , which will call workqueue
* handler to read data from I3C device.
*
* @param device I3C device.
* @param payload payload shall be NULL for NFC device.
*
* @return void.
*/
static void i3c_ibi_handler(struct i3c_device *device,
const struct i3c_ibi_payload *payload)
{
struct nfc_dev *nfc_dev = i3cdev_get_drvdata(device);
struct i3c_dev *i3c_dev = &nfc_dev->i3c_dev;
pr_debug("%s: Received read IBI request from slave\n", __func__);
if (device_may_wakeup(&device->dev))
pm_wakeup_event(&device->dev, WAKEUP_SRC_TIMEOUT);
if (atomic_read(&i3c_dev->pm_state) == PM_STATE_NORMAL) {
if (!queue_work(i3c_dev->wq, &i3c_dev->work))
pr_debug("%s: queue work success\n", __func__);
} else {
// got IBI when in suspend
atomic_set(&i3c_dev->pm_state, PM_STATE_IBI_BEFORE_RESUME);
}
}
/** @brief This API can be used to enable IBI from the I3C device.
*
* @param i3c_dev the i3c_dev for the slave.
*
* @return 0 on success, error code for failures.
*/
int i3c_enable_ibi(struct i3c_dev *i3c_dev)
{
int ret = 0;
int retry_count = 0;
if (!i3c_dev->ibi_enabled) {
do {
ret = i3c_device_enable_ibi(i3c_dev->device);
if (!ret) {
i3c_dev->ibi_enabled = true;
break;
}
pr_debug("en_ibi ret %d retrying..\n", ret);
retry_count++;
usleep_range(RETRY_WAIT_TIME_USEC,
(RETRY_WAIT_TIME_USEC+100));
} while (retry_count < RETRY_COUNT_IBI);
} else {
pr_debug("%s: already enabled\n", __func__);
}
return ret;
}
/** @brief This API can be used to disable IBI from the I3C device.
*
* @param i3c_dev the i3c_dev for the slave.
*
* @return 0 on success, error code for failures.
*/
int i3c_disable_ibi(struct i3c_dev *i3c_dev)
{
int ret = 0;
int retry_count = 0;
if (i3c_dev->ibi_enabled) {
do {
ret = i3c_device_disable_ibi(i3c_dev->device);
if (!ret) {
i3c_dev->ibi_enabled = false;
break;
}
pr_debug("dis_ibi ret %d retrying..\n", ret);
retry_count++;
usleep_range(RETRY_WAIT_TIME_USEC,
(RETRY_WAIT_TIME_USEC+100));
} while (retry_count < RETRY_COUNT_IBI);
} else {
pr_debug("%s: already disabled\n", __func__);
}
return ret;
}
/** @brief This API can be used to request IBI from the I3C device.
* This function will request IBI from master controller for the device
* and register ibi handler, enable IBI .
*
* @param i3c_dev the i3c_dev for the slave.
*
* @return 0 on success, error code for failures.
*/
static int i3c_request_ibi(struct i3c_dev *i3c_dev)
{
int ret = 0;
struct i3c_ibi_setup ibireq = {
.handler = i3c_ibi_handler,
.max_payload_len = MAX_IBI_PAYLOAD_LEN,
.num_slots = NUM_NFC_IBI_SLOT,
};
ret = i3c_device_request_ibi(i3c_dev->device, &ibireq);
pr_debug("%s Request IBI status = %d\n", __func__, ret);
return ret;
}
/** @brief This API can be used to create a workqueue for
* handling IBI request from I3C device.
*
* @param dev the i3c_dev for the slave.
*
* @return 0 on success, error code for failures.
*/
static int i3c_init_workqueue(struct i3c_dev *i3c_dev)
{
i3c_dev->wq = alloc_workqueue(NFC_I3C_WORKQUEUE, 0, 0);
if (!i3c_dev->wq)
return -ENOMEM;
INIT_WORK(&i3c_dev->work, i3c_workqueue_handler);
pr_debug("%s ibi workqueue created successfully\n", __func__);
return 0;
}
/** @brief This API can be used to set the NCI buf to zero.
*
* @param dev the dev for the driver.
*
* @return 0 on success, error code for failures.
*/
static int i3c_reset_nci_buf(struct i3c_dev *i3c_dev)
{
i3c_dev->buf.write_offset = 0;
i3c_dev->buf.read_offset = 0;
memset(i3c_dev->buf.kbuf, 0, i3c_dev->buf.total_size);
return 0;
}
static const struct file_operations nfc_i3c_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = nfc_i3c_dev_read,
.write = nfc_i3c_dev_write,
.open = nfc_dev_open,
.release = nfc_dev_close,
.unlocked_ioctl = nfc_dev_ioctl,
};
/** @brief This API can be used to probe I3c device.
*
* @param device the i3c_dev for the slave.
*
* @return 0 on success, error code for failures.
*/
int nfc_i3c_dev_probe(struct i3c_device *device)
{
int ret = 0;
struct nfc_dev *nfc_dev = NULL;
struct platform_gpio nfc_gpio;
pr_debug("%s: enter\n", __func__);
//retrieve gpio details from dt
ret = nfc_parse_dt(&device->dev, &nfc_gpio, PLATFORM_IF_I3C);
if (ret) {
pr_err("%s : failed to parse nfc dt node\n", __func__);
goto err;
}
nfc_dev = kzalloc(sizeof(struct nfc_dev), GFP_KERNEL);
if (nfc_dev == NULL) {
ret = -ENOMEM;
goto err;
}
/*
* Memory allocation for the read from the I3C before
* storing it in Kbuf store
*/
nfc_dev->i3c_dev.read_hdr = NCI_HDR_LEN;
nfc_dev->i3c_dev.read_kbuf_len = MAX_BUFFER_SIZE;
nfc_dev->i3c_dev.read_kbuf = kzalloc(MAX_BUFFER_SIZE,
GFP_DMA | GFP_KERNEL);
if (!nfc_dev->i3c_dev.read_kbuf) {
ret = -ENOMEM;
goto err_free_nfc_dev;
}
/*
* Kbuf memory for storing NCI/Firmware Mode Buffers before
* actual read from the user
*/
nfc_dev->i3c_dev.buf.kbuf = (char *)__get_free_pages(GFP_KERNEL, 0);
if (!nfc_dev->i3c_dev.buf.kbuf) {
ret = -ENOMEM;
goto err_free_kbuf;
}
nfc_dev->i3c_dev.buf.total_size = PAGE_SIZE;
i3c_reset_nci_buf(&nfc_dev->i3c_dev);
nfc_dev->interface = PLATFORM_IF_I3C;
nfc_dev->i3c_dev.device = device;
ret = configure_gpio(nfc_gpio.ven, GPIO_OUTPUT_HIGH);
if (ret) {
pr_err("%s: unable to request nfc reset gpio [%d]\n",
__func__, nfc_gpio.ven);
goto err_free_pages;
}
ret = configure_gpio(nfc_gpio.dwl_req, GPIO_OUTPUT);
if (ret) {
pr_err("%s: unable to request nfc firm downl gpio [%d]\n",
__func__, nfc_gpio.dwl_req);
goto err_free_ven;
}
ret = configure_gpio(nfc_gpio.clkreq, GPIO_INPUT);
if (ret) {
pr_err("%s: unable to request nfc clkreq gpio [%d]\n",
__func__, nfc_gpio.clkreq);
goto err_free_dwl_req;
}
nfc_dev->gpio.ven = nfc_gpio.ven;
nfc_dev->gpio.clkreq = nfc_gpio.clkreq;
nfc_dev->gpio.dwl_req = nfc_gpio.dwl_req;
/* init mutex and queues */
init_completion(&nfc_dev->i3c_dev.read_cplt);
mutex_init(&nfc_dev->i3c_dev.nci_buf_mutex);
mutex_init(&nfc_dev->dev_ref_mutex);
mutex_init(&nfc_dev->read_mutex);
ret = i3c_init_workqueue(&nfc_dev->i3c_dev);
if (ret) {
pr_err("%s: alloc workqueue failed\n", __func__);
goto err_mutex_destroy;
}
ret = nfc_misc_probe(nfc_dev, &nfc_i3c_dev_fops, DEV_COUNT,
NFC_CHAR_DEV_NAME, CLASS_NAME);
if (ret) {
pr_err("%s: nfc_misc_probe failed\n", __func__);
goto err_wq_destroy;
}
i3cdev_set_drvdata(device, nfc_dev);
ret = i3c_request_ibi(&nfc_dev->i3c_dev);
if (ret) {
pr_err("%s: i3c_request_ibi failed\n", __func__);
goto err_nfc_misc_remove;
}
atomic_set(&nfc_dev->i3c_dev.pm_state, PM_STATE_NORMAL);
device_init_wakeup(&device->dev, true);
device_set_wakeup_capable(&device->dev, true);
pr_info("%s success\n", __func__);
return 0;
err_nfc_misc_remove:
nfc_misc_remove(nfc_dev, DEV_COUNT);
err_wq_destroy:
destroy_workqueue(nfc_dev->i3c_dev.wq);
err_mutex_destroy:
mutex_destroy(&nfc_dev->read_mutex);
mutex_destroy(&nfc_dev->dev_ref_mutex);
mutex_destroy(&nfc_dev->i3c_dev.nci_buf_mutex);
gpio_free(nfc_dev->gpio.clkreq);
err_free_dwl_req:
gpio_free(nfc_dev->gpio.dwl_req);
err_free_ven:
gpio_free(nfc_dev->gpio.ven);
err_free_pages:
if (nfc_dev->i3c_dev.buf.kbuf)
free_pages((unsigned long)nfc_dev->i3c_dev.buf.kbuf, 0);
err_free_kbuf:
kfree(nfc_dev->i3c_dev.read_kbuf);
err_free_nfc_dev:
kfree(nfc_dev);
err:
pr_err("%s: failed\n", __func__);
return ret;
}
/** @brief This API is automatically called on shutdown or crash.
*
* @param device the i3c_dev for the slave.
*
* @return 0 on success, error code for failures.
*/
int nfc_i3c_dev_remove(struct i3c_device *device)
{
struct nfc_dev *nfc_dev = i3cdev_get_drvdata(device);
struct i3c_dev *i3c_dev = NULL;
if (!nfc_dev) {
pr_err("%s: device doesn't exist anymore\n", __func__);
return -ENODEV;
}
i3c_dev = &nfc_dev->i3c_dev;
i3c_device_free_ibi(device);
if (i3c_dev->wq)
destroy_workqueue(i3c_dev->wq);
if (i3c_dev->buf.kbuf)
free_pages((unsigned long)i3c_dev->buf.kbuf, 0);
nfc_misc_remove(nfc_dev, DEV_COUNT);
mutex_destroy(&nfc_dev->read_mutex);
mutex_destroy(&nfc_dev->dev_ref_mutex);
mutex_destroy(&i3c_dev->nci_buf_mutex);
if (gpio_is_valid(nfc_dev->gpio.clkreq))
gpio_free(nfc_dev->gpio.clkreq);
if (gpio_is_valid(nfc_dev->gpio.dwl_req))
gpio_free(nfc_dev->gpio.dwl_req);
if (gpio_is_valid(nfc_dev->gpio.ven))
gpio_free(nfc_dev->gpio.ven);
kfree(i3c_dev->read_kbuf);
kfree(nfc_dev);
return 0;
}
int nfc_i3c_dev_suspend(struct device *pdev)
{
struct i3c_device *device = dev_to_i3cdev(pdev);
struct nfc_dev *nfc_dev = i3cdev_get_drvdata(device);
pr_debug("%s: enter\n", __func__);
if (!nfc_dev) {
pr_err("%s: device doesn't exist anymore\n", __func__);
return -ENODEV;
}
if (device_may_wakeup(&device->dev) && nfc_dev->i3c_dev.ibi_enabled)
atomic_set(&nfc_dev->i3c_dev.pm_state, PM_STATE_SUSPEND);
return 0;
}
int nfc_i3c_dev_resume(struct device *pdev)
{
struct i3c_device *device = dev_to_i3cdev(pdev);
struct nfc_dev *nfc_dev = i3cdev_get_drvdata(device);
struct i3c_dev *i3c_dev = NULL;
pr_debug("%s: enter\n", __func__);
if (!nfc_dev) {
pr_err("%s: device doesn't exist anymore\n", __func__);
return -ENODEV;
}
i3c_dev = &nfc_dev->i3c_dev;
if (device_may_wakeup(&device->dev)) {
if (atomic_read(&i3c_dev->pm_state) ==
PM_STATE_IBI_BEFORE_RESUME) {
/*queue work in response to wakeup IBI */
if (!queue_work(i3c_dev->wq, &i3c_dev->work))
pr_debug("%s: Added workqueue successfully\n",
__func__);
}
atomic_set(&i3c_dev->pm_state, PM_STATE_NORMAL);
}
return 0;
}
static const struct i3c_device_id nfc_i3c_dev_id[] = {
I3C_DEVICE(NFC_I3C_MANU_ID, NFC_I3C_PART_ID, 0),
{},
};
static const struct of_device_id nfc_i3c_dev_match_table[] = {
{
.compatible = NFC_I3C_DRV_STR
},
{}
};
static const struct dev_pm_ops nfc_i3c_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(nfc_i3c_dev_suspend, nfc_i3c_dev_resume)
};
static struct i3c_driver nfc_i3c_dev_driver = {
.id_table = nfc_i3c_dev_id,
.probe = nfc_i3c_dev_probe,
.remove = nfc_i3c_dev_remove,
.driver = {
.owner = THIS_MODULE,
.name = NFC_I3C_DRV_STR,
.pm = &nfc_i3c_dev_pm_ops,
.of_match_table = nfc_i3c_dev_match_table,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
MODULE_DEVICE_TABLE(of, nfc_i3c_dev_match_table);
static int __init nfc_dev_i3c_init(void)
{
int ret = 0;
pr_info("Loading NFC I3C driver\n");
ret = i3c_driver_register_with_owner(&nfc_i3c_dev_driver, THIS_MODULE);
pr_debug("NFC i3c driver register ret = %d\n", ret);
return ret;
}
module_init(nfc_dev_i3c_init);
static void __exit nfc_i3c_dev_exit(void)
{
pr_info("Unloading NFC I3C driver\n");
i3c_driver_unregister(&nfc_i3c_dev_driver);
}
module_exit(nfc_i3c_dev_exit);
MODULE_DESCRIPTION("QTI NFC I3C driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,126 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2019, The Linux Foundation. All rights reserved.
*/
#ifndef _NFC_I3C_DRV_H_
#define _NFC_I3C_DRV_H_
#include <linux/i3c/master.h>
#include <linux/i3c/device.h>
//to to kept same as dt
#define NFC_I3C_DRV_STR "qcom,sn-nci-i3c"
#define NFC_I3C_MANU_ID (0x011B)
#define NFC_I3C_PART_ID (0)
//Byte indicating I3C Read
#define NFC_I3C_READ 1
//Byte indicating I3C Write
#define NFC_I3C_WRITE 0
// Maximum no of IBI slot
#define NUM_NFC_IBI_SLOT 1
// Maximum IBI payload length
#define MAX_IBI_PAYLOAD_LEN 0
// CRC len to be read
#define FW_CRC_LEN 2
// FW DNLD HDR length
#define FW_HDR_LEN 2
// Time to wait before retrying I3C writes, in micro seconds
#define RETRY_WAIT_TIME_USEC (2000)
// Retry count for enable/disable IBI CCC
#define RETRY_COUNT_IBI (3)
// I3C WorkQueue name
#define NFC_I3C_WORKQUEUE "nfc_i3c_workq"
/**
* struct nci_buf - NCI buffer used to store and retrieve read data from device.
* @read_offset: The offset pointing to data available to read in nci buf.
* @write_offset: The offset pointing to free buf available to write.
* @total_size: Size of nci buf.
* @kbuf: allocated nci buf.
*/
struct nci_buf {
unsigned int read_offset;
unsigned int write_offset;
size_t total_size;
char *kbuf;
};
/**
* struct i3c_dev Structure representing device and driver data.
* @i3c_device Structure to represent I3C device.
* @wq: NCI workqueue for handling IBI request.
* @work: Work added to workqueue to read data from IBI handler.
* @buf Driver buf store read data from device.Read call will
* fetch from this buffer.
* @nci_buf_mutex: mutex to protect NCI buf retrieve/store .
* @read_cplt: Completion to wait for read data to be available.
* @read_kbuf_len: Temp buf len to hold I3C data.
* @read_kbuf: Temp buf to hold I3C data.
* @read_hdr Header size for reads.
* @ibi_enabled: IBI enabled or not.
* @pm_state: PM state of NFC I3C device.
*/
struct i3c_dev {
struct i3c_device *device;
struct workqueue_struct *wq;
struct work_struct work;
struct nci_buf buf;
struct mutex nci_buf_mutex;
struct completion read_cplt;
size_t read_kbuf_len;
char *read_kbuf;
unsigned char read_hdr;
bool ibi_enabled;
atomic_t pm_state;
};
int nfc_i3c_dev_probe(struct i3c_device *device);
int nfc_i3c_dev_remove(struct i3c_device *device);
int nfc_i3c_dev_suspend(struct device *device);
int nfc_i3c_dev_resume(struct device *device);
#ifdef CONFIG_NFC_QTI_I3C
int i3c_enable_ibi(struct i3c_dev *i3c_dev);
int i3c_disable_ibi(struct i3c_dev *i3c_dev);
ssize_t i3c_write(struct i3c_dev *i3c_dev, const char *buf, const size_t count,
int max_retry_cnt);
ssize_t i3c_read(struct i3c_dev *i3c_dev, char *buf, size_t count);
#else
static inline int i3c_enable_ibi(struct i3c_dev *i3c_dev)
{
return -ENXIO;
}
static inline int i3c_disable_ibi(struct i3c_dev *i3c_dev)
{
return -ENXIO;
}
static inline ssize_t i3c_write(struct i3c_dev *i3c_dev,
const char *buf, const size_t count, int max_retry_cnt)
{
return -ENXIO;
}
static inline ssize_t i3c_read(struct i3c_dev *i3c_dev,
char *buf, size_t count);
{
return -ENXIO;
}
#endif
#endif //_NFC_I3C_DRV_H_