From: Kristian Brox <isyourbrainfoss@proton.me>
To: oe-linux-nfc@lists.linux.dev
Cc: david@ixit.cz, krzk+dt@kernel.org, devicetree@vger.kernel.org,
dmitry.baryshkov@oss.qualcomm.com, luca.weiss@fairphone.com,
linux-arm-msm@vger.kernel.org, netdev@vger.kernel.org
Subject: [PATCH v2 2/3] nfc: st-nci: add raw NCI path for ST21NFCD
Date: Wed, 19 Aug 2026 21:09:32 +0000 [thread overview]
Message-ID: <20260819210906.2500789-3-isyourbrainfoss@proton.me> (raw)
In-Reply-To: <20260819210906.2500789-1-isyourbrainfoss@proton.me>
ST21NFCD does not use NDLC. When the compatible is st,st21nfcd,
talk raw NCI:
- do not add or strip an NDLC PCB
- do not run the T1/T2 ACK timers
- I2C reads are a 3-byte NCI header plus payload
- skip proprietary SET_NFC_MODE and HCI SE discovery
Optionally enable clocks (SYS_CLK) and vdd-io (VPS_IO) when the
DT describes them. Existing st21nfcb / st21nfcc boards keep the
NDLC path and do not need those properties.
Tested on Fairphone 5: adapter powers up and reads an NTAG 215.
Signed-off-by: Kristian Brox <isyourbrainfoss@proton.me>
---
diff --git a/drivers/nfc/st-nci/core.c b/drivers/nfc/st-nci/core.c
index a367136..2356f16 100644
--- a/drivers/nfc/st-nci/core.c
+++ b/drivers/nfc/st-nci/core.c
@@ -18,8 +18,13 @@
static int st_nci_init(struct nci_dev *ndev)
{
+ struct st_nci_info *info = nci_get_drvdata(ndev);
struct nci_mode_set_cmd cmd;
+ /* ST21NFCD has no NDLC proprietary SET_NFC_MODE */
+ if (info->ndlc->raw_nci)
+ return 0;
+
cmd.cmd_type = ST_NCI_SET_NFC_MODE;
cmd.mode = 1;
diff --git a/drivers/nfc/st-nci/i2c.c b/drivers/nfc/st-nci/i2c.c
index 416770a..cfddb7f 100644
--- a/drivers/nfc/st-nci/i2c.c
+++ b/drivers/nfc/st-nci/i2c.c
@@ -10,10 +10,13 @@
#include <linux/i2c.h>
#include <linux/gpio/consumer.h>
#include <linux/acpi.h>
+#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/nfc.h>
#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
#include "st-nci.h"
@@ -22,10 +25,17 @@
/* ndlc header */
#define ST_NCI_FRAME_HEADROOM 1
#define ST_NCI_FRAME_TAILROOM 0
+#define ST_NCI_RAW_FRAME_HEADROOM 0
#define ST_NCI_I2C_MIN_SIZE 4 /* PCB(1) + NCI Packet header(3) */
+#define ST_NCI_NCI_HDR_SIZE 3 /* raw NCI: MT/PBF/GID + OID + len */
#define ST_NCI_I2C_MAX_SIZE 250 /* req 4.2.1 */
+enum st_nci_i2c_proto {
+ ST_NCI_I2C_PROTO_NDLC = 0,
+ ST_NCI_I2C_PROTO_RAW_NCI,
+};
+
#define ST_NCI_DRIVER_NAME "st_nci"
#define ST_NCI_I2C_DRIVER_NAME "st_nci_i2c"
@@ -34,6 +44,7 @@ struct st_nci_i2c_phy {
struct llt_ndlc *ndlc;
bool irq_active;
+ bool raw_nci;
struct gpio_desc *gpiod_reset;
@@ -111,6 +122,42 @@ static int st_nci_i2c_read(struct st_nci_i2c_phy *phy,
u8 buf[ST_NCI_I2C_MAX_SIZE];
struct i2c_client *client = phy->i2c_dev;
+ if (phy->raw_nci) {
+ r = i2c_master_recv(client, buf, ST_NCI_NCI_HDR_SIZE);
+ if (r < 0) {
+ usleep_range(1000, 4000);
+ r = i2c_master_recv(client, buf, ST_NCI_NCI_HDR_SIZE);
+ }
+ if (r != ST_NCI_NCI_HDR_SIZE)
+ return -EREMOTEIO;
+
+ len = buf[2];
+ if (len > ST_NCI_I2C_MAX_SIZE) {
+ nfc_err(&client->dev, "invalid frame len\n");
+ return -EBADMSG;
+ }
+
+ *skb = alloc_skb(ST_NCI_NCI_HDR_SIZE + len, GFP_KERNEL);
+ if (!*skb)
+ return -ENOMEM;
+
+ skb_put(*skb, ST_NCI_NCI_HDR_SIZE);
+ memcpy((*skb)->data, buf, ST_NCI_NCI_HDR_SIZE);
+
+ if (!len)
+ return 0;
+
+ r = i2c_master_recv(client, buf, len);
+ if (r != len) {
+ kfree_skb(*skb);
+ return -EREMOTEIO;
+ }
+
+ skb_put(*skb, len);
+ memcpy((*skb)->data + ST_NCI_NCI_HDR_SIZE, buf, len);
+ return 0;
+ }
+
r = i2c_master_recv(client, buf, ST_NCI_I2C_MIN_SIZE);
if (r < 0) { /* Retry, chip was in standby */
usleep_range(1000, 4000);
@@ -211,6 +258,8 @@ static int st_nci_i2c_probe(struct i2c_client *client)
return -ENOMEM;
phy->i2c_dev = client;
+ phy->raw_nci = (uintptr_t)device_get_match_data(dev) ==
+ ST_NCI_I2C_PROTO_RAW_NCI;
i2c_set_clientdata(client, phy);
@@ -225,19 +274,31 @@ static int st_nci_i2c_probe(struct i2c_client *client)
return -ENODEV;
}
+ r = devm_regulator_get_enable_optional(dev, "vdd-io");
+ if (r && r != -ENODEV)
+ return dev_err_probe(dev, r, "failed to enable vdd-io\n");
+
+ r = PTR_ERR_OR_ZERO(devm_clk_get_optional_enabled(dev, NULL));
+ if (r)
+ return dev_err_probe(dev, r, "failed to enable clock\n");
+
phy->se_status.is_ese_present =
device_property_read_bool(dev, "ese-present");
phy->se_status.is_uicc_present =
device_property_read_bool(dev, "uicc-present");
r = ndlc_probe(phy, &i2c_phy_ops, &client->dev,
- ST_NCI_FRAME_HEADROOM, ST_NCI_FRAME_TAILROOM,
+ phy->raw_nci ? ST_NCI_RAW_FRAME_HEADROOM :
+ ST_NCI_FRAME_HEADROOM,
+ ST_NCI_FRAME_TAILROOM,
&phy->ndlc, &phy->se_status);
if (r < 0) {
nfc_err(&client->dev, "Unable to register ndlc layer\n");
return r;
}
+ phy->ndlc->raw_nci = phy->raw_nci;
+
phy->irq_active = true;
r = devm_request_threaded_irq(&client->dev, client->irq, NULL,
st_nci_irq_thread_fn,
@@ -273,6 +334,8 @@ static const struct of_device_id of_st_nci_i2c_match[] __maybe_unused = {
{ .compatible = "st,st21nfcb-i2c", },
{ .compatible = "st,st21nfcb_i2c", },
{ .compatible = "st,st21nfcc-i2c", },
+ { .compatible = "st,st21nfcd",
+ .data = (void *)ST_NCI_I2C_PROTO_RAW_NCI },
{}
};
MODULE_DEVICE_TABLE(of, of_st_nci_i2c_match);
diff --git a/drivers/nfc/st-nci/ndlc.c b/drivers/nfc/st-nci/ndlc.c
index be48088..b319246 100644
--- a/drivers/nfc/st-nci/ndlc.c
+++ b/drivers/nfc/st-nci/ndlc.c
@@ -62,8 +62,9 @@ void ndlc_close(struct llt_ndlc *ndlc)
/* toggle reset pin */
ndlc->ops->enable(ndlc->phy_id);
- nci_prop_cmd(ndlc->ndev, ST_NCI_CORE_PROP,
- sizeof(struct nci_mode_set_cmd), (__u8 *)&cmd);
+ if (!ndlc->raw_nci)
+ nci_prop_cmd(ndlc->ndev, ST_NCI_CORE_PROP,
+ sizeof(struct nci_mode_set_cmd), (__u8 *)&cmd);
ndlc->powered = 0;
ndlc->ops->disable(ndlc->phy_id);
@@ -72,11 +73,13 @@ 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;
+ if (!ndlc->raw_nci) {
+ /* add ndlc header */
+ u8 pcb = PCB_TYPE_DATAFRAME | PCB_DATAFRAME_RETRANSMIT_NO |
+ PCB_FRAME_CRC_INFO_NOTPRESENT;
- *(u8 *)skb_push(skb, 1) = pcb;
+ *(u8 *)skb_push(skb, 1) = pcb;
+ }
skb_queue_tail(&ndlc->send_q, skb);
schedule_work(&ndlc->sm_work);
@@ -103,6 +106,10 @@ static void llt_ndlc_send_queue(struct llt_ndlc *ndlc)
ndlc->hard_fault = r;
break;
}
+ if (ndlc->raw_nci) {
+ kfree_skb(skb);
+ continue;
+ }
time_sent = jiffies;
*(unsigned long *)skb->cb = time_sent;
@@ -154,6 +161,10 @@ static void llt_ndlc_rcv_queue(struct llt_ndlc *ndlc)
pr_debug("rcvQlen=%d\n", ndlc->rcv_q.qlen);
while ((skb = skb_dequeue(&ndlc->rcv_q)) != NULL) {
+ if (ndlc->raw_nci) {
+ nci_recv_frame(ndlc->ndev, skb);
+ continue;
+ }
pcb = skb->data[0];
skb_pull(skb, 1);
if ((pcb & PCB_TYPE_MASK) == PCB_TYPE_SUPERVISOR) {
diff --git a/drivers/nfc/st-nci/ndlc.h b/drivers/nfc/st-nci/ndlc.h
index c24ce9b..5c1f8ba 100644
--- a/drivers/nfc/st-nci/ndlc.h
+++ b/drivers/nfc/st-nci/ndlc.h
@@ -39,6 +39,8 @@ struct llt_ndlc {
*/
int hard_fault;
int powered;
+ /* ST21NFCD: raw NCI on the wire, no NDLC PCB / ACK timers */
+ bool raw_nci;
};
int ndlc_open(struct llt_ndlc *ndlc);
diff --git a/drivers/nfc/st-nci/se.c b/drivers/nfc/st-nci/se.c
index 607ec76..44cc102 100644
--- a/drivers/nfc/st-nci/se.c
+++ b/drivers/nfc/st-nci/se.c
@@ -621,6 +621,9 @@ int st_nci_discover_se(struct nci_dev *ndev)
int se_count = 0;
struct st_nci_info *info = nci_get_drvdata(ndev);
+ if (info->ndlc->raw_nci)
+ return 0;
+
r = st_nci_hci_network_init(ndev);
if (r != 0)
return r;
next prev parent reply other threads:[~2026-08-19 21:09 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20260818205940.1973944-1-isyourbrainfoss@proton.me>
[not found] ` <20260818205940.1973944-2-isyourbrainfoss@proton.me>
2026-08-18 23:22 ` [PATCH 1/3] dt-bindings: net: nfc: add st,st21nfcd-i2c David Heidelberg
2026-08-19 21:09 ` [PATCH v2 0/3] nfc: st-nci: Fairphone 5 NFC bring-up (ST21NFCD) Kristian Brox
2026-08-19 21:09 ` [PATCH v2 1/3] dt-bindings: net: nfc: add st,st21nfcd Kristian Brox
2026-08-19 21:47 ` David Heidelberg
2026-08-20 6:05 ` Krzysztof Kozlowski
2026-08-19 21:09 ` Kristian Brox [this message]
2026-08-19 21:09 ` [PATCH v2 3/3] arm64: dts: qcom: qcm6490-fairphone-fp5: add ST21NFCD NFC Kristian Brox
2026-08-19 21:54 ` David Heidelberg
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260819210906.2500789-3-isyourbrainfoss@proton.me \
--to=isyourbrainfoss@proton.me \
--cc=david@ixit.cz \
--cc=devicetree@vger.kernel.org \
--cc=dmitry.baryshkov@oss.qualcomm.com \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=luca.weiss@fairphone.com \
--cc=netdev@vger.kernel.org \
--cc=oe-linux-nfc@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox