Commit a06347c0 authored by Robert Dolca's avatar Robert Dolca Committed by Samuel Ortiz

NFC: Add Intel Fields Peak NFC solution driver

Fields Peak complies with the ISO/IEC 14443A/B, 15693, 18092,
and JIS X 6319-4. It is an NCI based controller.

RF Protocols supported:
 - NFC Forum Type 1 Tags (Jewel, Topaz)
 - NFC Forum Type 2 Tags (Mifare UL)
 - NFC Forum Type 3 Tags (FeliCa)
 - NFC Forum Type 4A (ISO/IEC 14443 A-4 106kbps to 848kbps)
 - NFC Forum Type 4B (ISO/IEC 14443 B-4 106kbps to 848kbps)
 - NFCIP in passive and active modes (ISO/IEC 18092 106kbps
   to 424kbps)
 - B’ (based on ISO/IEC 14443 B-2)
 - iCLASS (based on ISO/IEC 15693-2)
 - Vicinity cards (ISO/IEC 15693-3)
 - Kovio tags (NFC Forum Type 2)

The device can be enumerated using ACPI using the id INT339A.
The 1st GPIO is the IRQ and the 2nd is the RESET pin.
Signed-off-by: default avatarRobert Dolca <robert.dolca@intel.com>
Signed-off-by: default avatarSamuel Ortiz <sameo@linux.intel.com>
parent 85b9ce9a
......@@ -68,6 +68,7 @@ config NFC_PORT100
If unsure, say N.
source "drivers/nfc/fdp/Kconfig"
source "drivers/nfc/pn544/Kconfig"
source "drivers/nfc/microread/Kconfig"
source "drivers/nfc/nfcmrvl/Kconfig"
......
......@@ -2,6 +2,7 @@
# Makefile for nfc devices
#
obj-$(CONFIG_NFC_FDP) += fdp/
obj-$(CONFIG_NFC_PN544) += pn544/
obj-$(CONFIG_NFC_MICROREAD) += microread/
obj-$(CONFIG_NFC_PN533) += pn533.o
......
config NFC_FDP
tristate "Intel FDP NFC driver"
depends on NFC_NCI
select CRC_CCITT
default n
---help---
Intel Fields Peak NFC controller core driver.
This is a driver based on the NCI NFC kernel layers.
To compile this driver as a module, choose m here. The module will
be called fdp.
Say N if unsure.
config NFC_FDP_I2C
tristate "NFC FDP i2c support"
depends on NFC_FDP && I2C
---help---
This module adds support for the Intel Fields Peak NFC controller
i2c interface.
Select this if your platform is using the i2c bus.
If you choose to build a module, it'll be called fdp_i2c.
Say N if unsure.
#
# Makefile for FDP NCI based NFC driver
#
obj-$(CONFIG_NFC_FDP) += fdp.o
obj-$(CONFIG_NFC_FDP_I2C) += fdp_i2c.o
fdp_i2c-objs = i2c.o
This diff is collapsed.
/* -------------------------------------------------------------------------
* Copyright (C) 2014-2016, Intel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* -------------------------------------------------------------------------
*/
#ifndef __LOCAL_FDP_H_
#define __LOCAL_FDP_H_
#include <net/nfc/nci_core.h>
#include <linux/gpio/consumer.h>
struct fdp_i2c_phy {
struct i2c_client *i2c_dev;
struct gpio_desc *power_gpio;
struct nci_dev *ndev;
/* < 0 if i2c error occurred */
int hard_fault;
uint16_t next_read_size;
};
int fdp_nci_probe(struct fdp_i2c_phy *phy, struct nfc_phy_ops *phy_ops,
struct nci_dev **ndev, int tx_headroom, int tx_tailroom,
u8 clock_type, u32 clock_freq, u8 *fw_vsc_cfg);
void fdp_nci_remove(struct nci_dev *ndev);
int fdp_nci_recv_frame(struct nci_dev *ndev, struct sk_buff *skb);
#endif /* __LOCAL_FDP_H_ */
/* -------------------------------------------------------------------------
* Copyright (C) 2014-2016, Intel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* -------------------------------------------------------------------------
*/
#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/nfc.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <net/nfc/nfc.h>
#include <net/nfc/nci_core.h>
#include "fdp.h"
#define FDP_I2C_DRIVER_NAME "fdp_nci_i2c"
#define FDP_DP_POWER_GPIO_NAME "power"
#define FDP_DP_CLOCK_TYPE_NAME "clock-type"
#define FDP_DP_CLOCK_FREQ_NAME "clock-freq"
#define FDP_DP_FW_VSC_CFG_NAME "fw-vsc-cfg"
#define FDP_FRAME_HEADROOM 2
#define FDP_FRAME_TAILROOM 1
#define FDP_NCI_I2C_MIN_PAYLOAD 5
#define FDP_NCI_I2C_MAX_PAYLOAD 261
#define FDP_POWER_OFF 0
#define FDP_POWER_ON 1
#define fdp_nci_i2c_dump_skb(dev, prefix, skb) \
print_hex_dump(KERN_DEBUG, prefix": ", DUMP_PREFIX_OFFSET, \
16, 1, (skb)->data, (skb)->len, 0)
static void fdp_nci_i2c_reset(struct fdp_i2c_phy *phy)
{
/* Reset RST/WakeUP for at least 100 micro-second */
gpiod_set_value_cansleep(phy->power_gpio, FDP_POWER_OFF);
usleep_range(1000, 4000);
gpiod_set_value_cansleep(phy->power_gpio, FDP_POWER_ON);
usleep_range(10000, 14000);
}
static int fdp_nci_i2c_enable(void *phy_id)
{
struct fdp_i2c_phy *phy = phy_id;
dev_dbg(&phy->i2c_dev->dev, "%s\n", __func__);
fdp_nci_i2c_reset(phy);
return 0;
}
static void fdp_nci_i2c_disable(void *phy_id)
{
struct fdp_i2c_phy *phy = phy_id;
dev_dbg(&phy->i2c_dev->dev, "%s\n", __func__);
fdp_nci_i2c_reset(phy);
}
static void fdp_nci_i2c_add_len_lrc(struct sk_buff *skb)
{
u8 lrc = 0;
u16 len, i;
/* Add length header */
len = skb->len;
*skb_push(skb, 1) = len & 0xff;
*skb_push(skb, 1) = len >> 8;
/* Compute and add lrc */
for (i = 0; i < len + 2; i++)
lrc ^= skb->data[i];
*skb_put(skb, 1) = lrc;
}
static void fdp_nci_i2c_remove_len_lrc(struct sk_buff *skb)
{
skb_pull(skb, FDP_FRAME_HEADROOM);
skb_trim(skb, skb->len - FDP_FRAME_TAILROOM);
}
static int fdp_nci_i2c_write(void *phy_id, struct sk_buff *skb)
{
struct fdp_i2c_phy *phy = phy_id;
struct i2c_client *client = phy->i2c_dev;
int r;
if (phy->hard_fault != 0)
return phy->hard_fault;
fdp_nci_i2c_add_len_lrc(skb);
fdp_nci_i2c_dump_skb(&client->dev, "fdp_wr", skb);
r = i2c_master_send(client, skb->data, skb->len);
if (r == -EREMOTEIO) { /* Retry, chip was in standby */
usleep_range(1000, 4000);
r = i2c_master_send(client, skb->data, skb->len);
}
if (r < 0 || r != skb->len)
dev_dbg(&client->dev, "%s: error err=%d len=%d\n",
__func__, r, skb->len);
if (r >= 0) {
if (r != skb->len) {
phy->hard_fault = r;
r = -EREMOTEIO;
} else {
r = 0;
}
}
fdp_nci_i2c_remove_len_lrc(skb);
return r;
}
static struct nfc_phy_ops i2c_phy_ops = {
.write = fdp_nci_i2c_write,
.enable = fdp_nci_i2c_enable,
.disable = fdp_nci_i2c_disable,
};
static int fdp_nci_i2c_read(struct fdp_i2c_phy *phy, struct sk_buff **skb)
{
int r, len;
u8 tmp[FDP_NCI_I2C_MAX_PAYLOAD], lrc, k;
u16 i;
struct i2c_client *client = phy->i2c_dev;
*skb = NULL;
/* Read the length packet and the data packet */
for (k = 0; k < 2; k++) {
len = phy->next_read_size;
r = i2c_master_recv(client, tmp, len);
if (r != len) {
dev_dbg(&client->dev, "%s: i2c recv err: %d\n",
__func__, r);
goto flush;
}
/* Check packet integruty */
for (lrc = i = 0; i < r; i++)
lrc ^= tmp[i];
/*
* LRC check failed. This may due to transmission error or
* desynchronization between driver and FDP. Drop the paquet
* and force resynchronization
*/
if (lrc) {
dev_dbg(&client->dev, "%s: corrupted packet\n",
__func__);
phy->next_read_size = 5;
goto flush;
}
/* Packet that contains a length */
if (tmp[0] == 0 && tmp[1] == 0) {
phy->next_read_size = (tmp[2] << 8) + tmp[3] + 3;
} else {
phy->next_read_size = FDP_NCI_I2C_MIN_PAYLOAD;
*skb = alloc_skb(len, GFP_KERNEL);
if (*skb == NULL) {
r = -ENOMEM;
goto flush;
}
memcpy(skb_put(*skb, len), tmp, len);
fdp_nci_i2c_dump_skb(&client->dev, "fdp_rd", *skb);
fdp_nci_i2c_remove_len_lrc(*skb);
}
}
return 0;
flush:
/* Flush the remaining data */
if (i2c_master_recv(client, tmp, sizeof(tmp)) < 0)
r = -EREMOTEIO;
return r;
}
static irqreturn_t fdp_nci_i2c_irq_thread_fn(int irq, void *phy_id)
{
struct fdp_i2c_phy *phy = phy_id;
struct i2c_client *client;
struct sk_buff *skb;
int r;
client = phy->i2c_dev;
dev_dbg(&client->dev, "%s\n", __func__);
if (!phy || irq != phy->i2c_dev->irq) {
WARN_ON_ONCE(1);
return IRQ_NONE;
}
r = fdp_nci_i2c_read(phy, &skb);
if (r == -EREMOTEIO)
return IRQ_HANDLED;
else if (r == -ENOMEM || r == -EBADMSG)
return IRQ_HANDLED;
if (skb != NULL)
fdp_nci_recv_frame(phy->ndev, skb);
return IRQ_HANDLED;
}
static void fdp_nci_i2c_read_device_properties(struct device *dev,
u8 *clock_type, u32 *clock_freq,
u8 **fw_vsc_cfg)
{
int r;
u8 len;
r = device_property_read_u8(dev, FDP_DP_CLOCK_TYPE_NAME, clock_type);
if (r) {
dev_dbg(dev, "Using default clock type");
*clock_type = 0;
}
r = device_property_read_u32(dev, FDP_DP_CLOCK_FREQ_NAME, clock_freq);
if (r) {
dev_dbg(dev, "Using default clock frequency\n");
*clock_freq = 26000;
}
if (device_property_present(dev, FDP_DP_FW_VSC_CFG_NAME)) {
r = device_property_read_u8(dev, FDP_DP_FW_VSC_CFG_NAME,
&len);
if (r || len <= 0)
goto vsc_read_err;
/* Add 1 to the length to inclue the length byte itself */
len++;
*fw_vsc_cfg = devm_kmalloc(dev,
len * sizeof(**fw_vsc_cfg),
GFP_KERNEL);
r = device_property_read_u8_array(dev, FDP_DP_FW_VSC_CFG_NAME,
*fw_vsc_cfg, len);
if (r) {
devm_kfree(dev, fw_vsc_cfg);
goto vsc_read_err;
}
} else {
vsc_read_err:
dev_dbg(dev, "FW vendor specific commands not present\n");
*fw_vsc_cfg = NULL;
}
dev_dbg(dev, "Clock type: %d, clock frequency: %d, VSC: %s",
*clock_type, *clock_freq, *fw_vsc_cfg != NULL ? "yes" : "no");
}
static int fdp_nci_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct fdp_i2c_phy *phy;
struct device *dev = &client->dev;
u8 *fw_vsc_cfg;
u8 clock_type;
u32 clock_freq;
int r = 0;
dev_dbg(dev, "%s\n", __func__);
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
nfc_err(dev, "No I2C_FUNC_I2C support\n");
return -ENODEV;
}
phy = devm_kzalloc(dev, sizeof(struct fdp_i2c_phy),
GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->i2c_dev = client;
phy->next_read_size = FDP_NCI_I2C_MIN_PAYLOAD;
i2c_set_clientdata(client, phy);
/* Checking if we have an irq */
if (client->irq <= 0) {
dev_err(dev, "IRQ not present\n");
return -ENODEV;
}
r = request_threaded_irq(client->irq, NULL, fdp_nci_i2c_irq_thread_fn,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
FDP_I2C_DRIVER_NAME, phy);
if (r < 0) {
nfc_err(&client->dev, "Unable to register IRQ handler\n");
return r;
}
/* Requesting the power gpio */
phy->power_gpio = devm_gpiod_get(dev, FDP_DP_POWER_GPIO_NAME,
GPIOD_OUT_LOW);
if (IS_ERR(phy->power_gpio)) {
nfc_err(dev, "Power GPIO request failed\n");
return PTR_ERR(phy->power_gpio);
}
/* read device properties to get the clock and production settings */
fdp_nci_i2c_read_device_properties(dev, &clock_type, &clock_freq,
&fw_vsc_cfg);
/* Call the NFC specific probe function */
r = fdp_nci_probe(phy, &i2c_phy_ops, &phy->ndev,
FDP_FRAME_HEADROOM, FDP_FRAME_TAILROOM,
clock_type, clock_freq, fw_vsc_cfg);
if (r < 0) {
nfc_err(dev, "NCI probing error\n");
return r;
}
dev_dbg(dev, "I2C driver loaded\n");
return 0;
}
static int fdp_nci_i2c_remove(struct i2c_client *client)
{
struct fdp_i2c_phy *phy = i2c_get_clientdata(client);
dev_dbg(&client->dev, "%s\n", __func__);
fdp_nci_remove(phy->ndev);
fdp_nci_i2c_disable(phy);
return 0;
}
static struct i2c_device_id fdp_nci_i2c_id_table[] = {
{"int339a", 0},
{}
};
MODULE_DEVICE_TABLE(i2c, fdp_nci_i2c_id_table);
static const struct acpi_device_id fdp_nci_i2c_acpi_match[] = {
{"INT339A", 0},
{}
};
MODULE_DEVICE_TABLE(acpi, fdp_nci_i2c_acpi_match);
static struct i2c_driver fdp_nci_i2c_driver = {
.driver = {
.name = FDP_I2C_DRIVER_NAME,
.acpi_match_table = ACPI_PTR(fdp_nci_i2c_acpi_match),
},
.id_table = fdp_nci_i2c_id_table,
.probe = fdp_nci_i2c_probe,
.remove = fdp_nci_i2c_remove,
};
module_i2c_driver(fdp_nci_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("I2C driver for Intel Fields Peak NFC controller");
MODULE_AUTHOR("Robert Dolca <robert.dolca@intel.com>");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment