Commit 35630df6 authored by Christophe Ricard's avatar Christophe Ricard Committed by Samuel Ortiz

NFC: st21nfcb: Add driver for STMicroelectronics ST21NFCB NFC chip

Add driver for STMicroelectronics ST21NFCB NFC controller.
ST21NFCB is using NCI protocol and a proprietary low level transport
protocol called NDLC used on top.

NDLC:
The protocol defines 2 types of frame:
- One type carrying NCI data (referred as DATAFRAME frames).
- One type carrying protocol information used for flow control and error
control mechanisms (referred as SUPERVISOR frames).

After each frame transmission to the NFC controller, the device host
SHALL waitfor  an ACK (SUPERVISOR frame) reception before sending a
new frame.
The NFC controller MAY send a frame at anytime to the device host.
The NFC controller MAY send a specific WAIT supervisor frame to indicate
to device host that a NCI data packet has been received but that it could
take significant time before the NFC controller sends an ACK and thus
allows next data reception.
Signed-off-by: default avatarChristophe Ricard <[email protected]>
Signed-off-by: default avatarSamuel Ortiz <[email protected]>
parent 55537c7e
......@@ -72,5 +72,5 @@ source "drivers/nfc/pn544/Kconfig"
source "drivers/nfc/microread/Kconfig"
source "drivers/nfc/nfcmrvl/Kconfig"
source "drivers/nfc/st21nfca/Kconfig"
source "drivers/nfc/st21nfcb/Kconfig"
endmenu
......@@ -11,6 +11,7 @@ obj-$(CONFIG_NFC_SIM) += nfcsim.o
obj-$(CONFIG_NFC_PORT100) += port100.o
obj-$(CONFIG_NFC_MRVL) += nfcmrvl/
obj-$(CONFIG_NFC_TRF7970A) += trf7970a.o
obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/
obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/
obj-$(CONFIG_NFC_ST21NFCB) += st21nfcb/
ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG
config NFC_ST21NFCB
tristate "STMicroelectronics ST21NFCB NFC driver"
depends on NFC_NCI
default n
---help---
STMicroelectronics ST21NFCB core driver. It implements the chipset
NCI logic and hooks into the NFC kernel APIs. Physical layers will
register against it.
To compile this driver as a module, choose m here. The module will
be called st21nfcb.
Say N if unsure.
config NFC_ST21NFCB_I2C
tristate "NFC ST21NFCB i2c support"
depends on NFC_ST21NFCB && I2C
---help---
This module adds support for the STMicroelectronics st21nfcb i2c interface.
Select this if your platform is using the i2c bus.
If you choose to build a module, it'll be called st21nfcb_i2c.
Say N if unsure.
#
# Makefile for ST21NFCB NCI based NFC driver
#
st21nfcb_i2c-objs = i2c.o
obj-$(CONFIG_NFC_ST21NFCB) += st21nfcb.o ndlc.o
obj-$(CONFIG_NFC_ST21NFCB_I2C) += st21nfcb_i2c.o
This diff is collapsed.
/*
* Low Level Transport (NDLC) Driver for STMicroelectronics NFC Chip
*
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/sched.h>
#include <net/nfc/nci_core.h>
#include "ndlc.h"
#include "st21nfcb.h"
#define NDLC_TIMER_T1 100
#define NDLC_TIMER_T1_WAIT 400
#define NDLC_TIMER_T2 1200
#define PCB_TYPE_DATAFRAME 0x80
#define PCB_TYPE_SUPERVISOR 0xc0
#define PCB_TYPE_MASK PCB_TYPE_SUPERVISOR
#define PCB_SYNC_ACK 0x20
#define PCB_SYNC_NACK 0x10
#define PCB_SYNC_WAIT 0x30
#define PCB_SYNC_NOINFO 0x00
#define PCB_SYNC_MASK PCB_SYNC_WAIT
#define PCB_DATAFRAME_RETRANSMIT_YES 0x00
#define PCB_DATAFRAME_RETRANSMIT_NO 0x04
#define PCB_DATAFRAME_RETRANSMIT_MASK PCB_DATAFRAME_RETRANSMIT_NO
#define PCB_SUPERVISOR_RETRANSMIT_YES 0x00
#define PCB_SUPERVISOR_RETRANSMIT_NO 0x02
#define PCB_SUPERVISOR_RETRANSMIT_MASK PCB_SUPERVISOR_RETRANSMIT_NO
#define PCB_FRAME_CRC_INFO_PRESENT 0x08
#define PCB_FRAME_CRC_INFO_NOTPRESENT 0x00
#define PCB_FRAME_CRC_INFO_MASK PCB_FRAME_CRC_INFO_PRESENT
#define NDLC_DUMP_SKB(info, skb) \
do { \
pr_debug("%s:\n", info); \
print_hex_dump(KERN_DEBUG, "ndlc: ", DUMP_PREFIX_OFFSET, \
16, 1, skb->data, skb->len, 0); \
} while (0)
int ndlc_open(struct llt_ndlc *ndlc)
{
/* toggle reset pin */
ndlc->ops->enable(ndlc->phy_id);
return 0;
}
EXPORT_SYMBOL(ndlc_open);
void ndlc_close(struct llt_ndlc *ndlc)
{
/* toggle reset pin */
ndlc->ops->disable(ndlc->phy_id);
}
EXPORT_SYMBOL(ndlc_close);
int ndlc_send(struct llt_ndlc *ndlc, struct sk_buff *skb)
{
/* add ndlc header */
u8 pcb = PCB_TYPE_DATAFRAME | PCB_DATAFRAME_RETRANSMIT_NO |
PCB_FRAME_CRC_INFO_NOTPRESENT;
*skb_push(skb, 1) = pcb;
skb_queue_tail(&ndlc->send_q, skb);
schedule_work(&ndlc->sm_work);
return 0;
}
EXPORT_SYMBOL(ndlc_send);
static void llt_ndlc_send_queue(struct llt_ndlc *ndlc)
{
struct sk_buff *skb;
int r;
unsigned long time_sent;
if (ndlc->send_q.qlen)
pr_debug("sendQlen=%d unackQlen=%d\n",
ndlc->send_q.qlen, ndlc->ack_pending_q.qlen);
while (ndlc->send_q.qlen) {
skb = skb_dequeue(&ndlc->send_q);
NDLC_DUMP_SKB("ndlc frame written", skb);
r = ndlc->ops->write(ndlc->phy_id, skb);
if (r < 0) {
ndlc->hard_fault = r;
break;
}
time_sent = jiffies;
*(unsigned long *)skb->cb = time_sent;
skb_queue_tail(&ndlc->ack_pending_q, skb);
/* start timer t1 for ndlc aknowledge */
ndlc->t1_active = true;
mod_timer(&ndlc->t1_timer, time_sent +
msecs_to_jiffies(NDLC_TIMER_T1));
}
}
static void llt_ndlc_requeue_data_pending(struct llt_ndlc *ndlc)
{
struct sk_buff *skb;
u8 pcb;
while ((skb = skb_dequeue_tail(&ndlc->ack_pending_q))) {
pcb = skb->data[0];
switch (pcb & PCB_TYPE_MASK) {
case PCB_TYPE_SUPERVISOR:
skb->data[0] = (pcb & ~PCB_SUPERVISOR_RETRANSMIT_MASK) |
PCB_SUPERVISOR_RETRANSMIT_YES;
break;
case PCB_TYPE_DATAFRAME:
skb->data[0] = (pcb & ~PCB_DATAFRAME_RETRANSMIT_MASK) |
PCB_DATAFRAME_RETRANSMIT_YES;
break;
default:
pr_err("UNKNOWN Packet Control Byte=%d\n", pcb);
kfree_skb(skb);
break;
}
skb_queue_head(&ndlc->send_q, skb);
}
}
static void llt_ndlc_rcv_queue(struct llt_ndlc *ndlc)
{
struct sk_buff *skb;
u8 pcb;
unsigned long time_sent;
if (ndlc->rcv_q.qlen)
pr_debug("rcvQlen=%d\n", ndlc->rcv_q.qlen);
while ((skb = skb_dequeue(&ndlc->rcv_q)) != NULL) {
pcb = skb->data[0];
skb_pull(skb, 1);
if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_SUPERVISOR) {
switch (pcb & PCB_SYNC_MASK) {
case PCB_SYNC_ACK:
del_timer_sync(&ndlc->t1_timer);
del_timer_sync(&ndlc->t2_timer);
ndlc->t2_active = false;
ndlc->t1_active = false;
break;
case PCB_SYNC_NACK:
llt_ndlc_requeue_data_pending(ndlc);
llt_ndlc_send_queue(ndlc);
/* start timer t1 for ndlc aknowledge */
time_sent = jiffies;
ndlc->t1_active = true;
mod_timer(&ndlc->t1_timer, time_sent +
msecs_to_jiffies(NDLC_TIMER_T1));
break;
case PCB_SYNC_WAIT:
time_sent = jiffies;
ndlc->t1_active = true;
mod_timer(&ndlc->t1_timer, time_sent +
msecs_to_jiffies(NDLC_TIMER_T1_WAIT));
break;
default:
pr_err("UNKNOWN Packet Control Byte=%d\n", pcb);
kfree_skb(skb);
break;
}
} else {
nci_recv_frame(ndlc->ndev, skb);
}
}
}
static void llt_ndlc_sm_work(struct work_struct *work)
{
struct llt_ndlc *ndlc = container_of(work, struct llt_ndlc, sm_work);
llt_ndlc_send_queue(ndlc);
llt_ndlc_rcv_queue(ndlc);
if (ndlc->t1_active && timer_pending(&ndlc->t1_timer) == 0) {
pr_debug
("Handle T1(recv SUPERVISOR) elapsed (T1 now inactive)\n");
ndlc->t1_active = false;
llt_ndlc_requeue_data_pending(ndlc);
llt_ndlc_send_queue(ndlc);
}
if (ndlc->t2_active && timer_pending(&ndlc->t2_timer) == 0) {
pr_debug("Handle T2(recv DATA) elapsed (T2 now inactive)\n");
ndlc->t2_active = false;
ndlc->t1_active = false;
del_timer_sync(&ndlc->t1_timer);
ndlc_close(ndlc);
ndlc->hard_fault = -EREMOTEIO;
}
}
void ndlc_recv(struct llt_ndlc *ndlc, struct sk_buff *skb)
{
if (skb == NULL) {
pr_err("NULL Frame -> link is dead\n");
ndlc->hard_fault = -EREMOTEIO;
ndlc_close(ndlc);
} else {
NDLC_DUMP_SKB("incoming frame", skb);
skb_queue_tail(&ndlc->rcv_q, skb);
}
schedule_work(&ndlc->sm_work);
}
EXPORT_SYMBOL(ndlc_recv);
static void ndlc_t1_timeout(unsigned long data)
{
struct llt_ndlc *ndlc = (struct llt_ndlc *)data;
pr_debug("\n");
schedule_work(&ndlc->sm_work);
}
static void ndlc_t2_timeout(unsigned long data)
{
struct llt_ndlc *ndlc = (struct llt_ndlc *)data;
pr_debug("\n");
schedule_work(&ndlc->sm_work);
}
int ndlc_probe(void *phy_id, struct nfc_phy_ops *phy_ops, struct device *dev,
int phy_headroom, int phy_tailroom, struct llt_ndlc **ndlc_id)
{
struct llt_ndlc *ndlc;
ndlc = devm_kzalloc(dev, sizeof(struct llt_ndlc), GFP_KERNEL);
if (!ndlc) {
nfc_err(dev, "Cannot allocate memory for ndlc.\n");
return -ENOMEM;
}
ndlc->ops = phy_ops;
ndlc->phy_id = phy_id;
ndlc->dev = dev;
*ndlc_id = ndlc;
/* start timers */
init_timer(&ndlc->t1_timer);
ndlc->t1_timer.data = (unsigned long)ndlc;
ndlc->t1_timer.function = ndlc_t1_timeout;
init_timer(&ndlc->t2_timer);
ndlc->t2_timer.data = (unsigned long)ndlc;
ndlc->t2_timer.function = ndlc_t2_timeout;
skb_queue_head_init(&ndlc->rcv_q);
skb_queue_head_init(&ndlc->send_q);
skb_queue_head_init(&ndlc->ack_pending_q);
INIT_WORK(&ndlc->sm_work, llt_ndlc_sm_work);
return st21nfcb_nci_probe(ndlc, phy_headroom, phy_tailroom);
}
EXPORT_SYMBOL(ndlc_probe);
void ndlc_remove(struct llt_ndlc *ndlc)
{
/* cancel timers */
del_timer_sync(&ndlc->t1_timer);
del_timer_sync(&ndlc->t2_timer);
ndlc->t2_active = false;
ndlc->t1_active = false;
skb_queue_purge(&ndlc->rcv_q);
skb_queue_purge(&ndlc->send_q);
st21nfcb_nci_remove(ndlc->ndev);
kfree(ndlc);
}
EXPORT_SYMBOL(ndlc_remove);
/*
* NCI based Driver for STMicroelectronics NFC Chip
*
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __LOCAL_NDLC_H_
#define __LOCAL_NDLC_H_
#include <linux/skbuff.h>
#include <net/nfc/nfc.h>
/* Low Level Transport description */
struct llt_ndlc {
struct nci_dev *ndev;
struct nfc_phy_ops *ops;
void *phy_id;
struct timer_list t1_timer;
bool t1_active;
struct timer_list t2_timer;
bool t2_active;
struct sk_buff_head rcv_q;
struct sk_buff_head send_q;
struct sk_buff_head ack_pending_q;
struct work_struct sm_work;
struct device *dev;
int hard_fault;
};
int ndlc_open(struct llt_ndlc *ndlc);
void ndlc_close(struct llt_ndlc *ndlc);
int ndlc_send(struct llt_ndlc *ndlc, struct sk_buff *skb);
void ndlc_recv(struct llt_ndlc *ndlc, struct sk_buff *skb);
int ndlc_probe(void *phy_id, struct nfc_phy_ops *phy_ops, struct device *dev,
int phy_headroom, int phy_tailroom, struct llt_ndlc **ndlc_id);
void ndlc_remove(struct llt_ndlc *ndlc);
#endif /* __LOCAL_NDLC_H__ */
/*
* NCI based Driver for STMicroelectronics NFC Chip
*
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/module.h>
#include <linux/nfc.h>
#include <net/nfc/nci.h>
#include <net/nfc/nci_core.h>
#include "st21nfcb.h"
#include "ndlc.h"
#define DRIVER_DESC "NCI NFC driver for ST21NFCB"
static int st21nfcb_nci_open(struct nci_dev *ndev)
{
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
int r;
if (test_and_set_bit(ST21NFCB_NCI_RUNNING, &info->flags))
return 0;
r = ndlc_open(info->ndlc);
if (r)
clear_bit(ST21NFCB_NCI_RUNNING, &info->flags);
return r;
}
static int st21nfcb_nci_close(struct nci_dev *ndev)
{
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
if (!test_and_clear_bit(ST21NFCB_NCI_RUNNING, &info->flags))
return 0;
ndlc_close(info->ndlc);
return 0;
}
static int st21nfcb_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
{
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
skb->dev = (void *)ndev;
if (!test_bit(ST21NFCB_NCI_RUNNING, &info->flags))
return -EBUSY;
return ndlc_send(info->ndlc, skb);
}
static struct nci_ops st21nfcb_nci_ops = {
.open = st21nfcb_nci_open,
.close = st21nfcb_nci_close,
.send = st21nfcb_nci_send,
};
int st21nfcb_nci_probe(struct llt_ndlc *ndlc, int phy_headroom,
int phy_tailroom)
{
struct st21nfcb_nci_info *info;
int r;
u32 protocols;
info = devm_kzalloc(ndlc->dev,
sizeof(struct st21nfcb_nci_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
protocols = NFC_PROTO_JEWEL_MASK
| NFC_PROTO_MIFARE_MASK
| NFC_PROTO_FELICA_MASK
| NFC_PROTO_ISO14443_MASK
| NFC_PROTO_ISO14443_B_MASK
| NFC_PROTO_NFC_DEP_MASK;
ndlc->ndev = nci_allocate_device(&st21nfcb_nci_ops, protocols,
phy_headroom, phy_tailroom);
if (!ndlc->ndev) {
pr_err("Cannot allocate nfc ndev\n");
r = -ENOMEM;
goto err_alloc_ndev;
}
info->ndlc = ndlc;
nci_set_drvdata(ndlc->ndev, info);
r = nci_register_device(ndlc->ndev);
if (r)
goto err_regdev;
return r;
err_regdev:
nci_free_device(ndlc->ndev);
err_alloc_ndev:
kfree(info);
return r;
}
EXPORT_SYMBOL_GPL(st21nfcb_nci_probe);
void st21nfcb_nci_remove(struct nci_dev *ndev)
{
struct st21nfcb_nci_info *info = nci_get_drvdata(ndev);
nci_unregister_device(ndev);
nci_free_device(ndev);
kfree(info);
}
EXPORT_SYMBOL_GPL(st21nfcb_nci_remove);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION(DRIVER_DESC);
/*
* NCI based Driver for STMicroelectronics NFC Chip
*
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __LOCAL_ST21NFCB_H_
#define __LOCAL_ST21NFCB_H_
#include <net/nfc/nci_core.h>
#include "ndlc.h"
/* Define private flags: */
#define ST21NFCB_NCI_RUNNING 1
struct st21nfcb_nci_info {
struct llt_ndlc *ndlc;
unsigned long flags;
};
void st21nfcb_nci_remove(struct nci_dev *ndev);
int st21nfcb_nci_probe(struct llt_ndlc *ndlc, int phy_headroom,
int phy_tailroom);
#endif /* __LOCAL_ST21NFCB_H_ */
/*
* Driver include for the ST21NFCB NFC chip.
*
* Copyright (C) 2014 STMicroelectronics SAS. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _ST21NFCA_HCI_H_
#define _ST21NFCA_HCI_H_
#include <linux/i2c.h>
#define ST21NFCB_NCI_DRIVER_NAME "st21nfcb_nci"
struct st21nfcb_nfc_platform_data {
unsigned int gpio_irq;
unsigned int gpio_reset;
unsigned int irq_polarity;
};
#endif /* _ST21NFCA_HCI_H_ */
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