These patches add support for Intel's FieldsPeak NFC solution.
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.
Changes since v3:
- replace u32 with le32 where appropriate
- make nci_prop_ops struct instances static
- simplify return for fdp_nci_setup and fdp_nci_post_setup
- change return type from u8 to int for nci_conn_max_data_pkt_payload_size
- set FDP_NCI_I2C_MAX_PAYLOAD to 261 (255 + NCI header (3) + lrc(2) + size byte)
- add sleep during patching to make sure that the NFCC processed the last data packet
- patch droped "nfc: nci: Use a separate mutex for nci open and close"
- patch droped "nfc: nci: Add a parameter to get the new connection id"
- add patch "nfc: nci: fix possible crash in nci_core_conn_create"
- add patch "nfc: nci: add nci_get_conn_info_by_id function"
- add patch "nfc: nci: rename nci_prop_ops to nci_driver_ops"
- edit "ecee665 nfc: nci: Allow the driver to set handler for core nci ops" to
add wrappers for core ops and prop ops in order to simplify the code
- removed "atomic_t intercept;" from fdp_nci_info struct
- make functions inline: fdp_nci_get_versions, fdp_nci_patch_cmd, fdp_nci_set_production_data
- moved otp and ram patching to separate functions that are called from fdp_nci_post_setup
Changes since v2:
- rename reset gpio to power gpio because it really is a power switch
- use named gpio for power with fallback to the indexed gpio 0
- use device managed gpio for power
- rely on the i2c subsystem to fill in the irq field of the i2c_client struct
- remove the internal driver state (the nfc subsystem takes care of what
we need)
- removed the power status field (not needed any more)
- change i2c / device tree id name s/INT339A/int339a
- make clock type and clock frequency configurable with device properties
(use defauls if not set)
- firmware vendor specific commands configurable with device properties
(default none)
- other small fixes
Changes since v1:
- removed .owner
- made local functions static
- droped "NFC: NCI: Adds NCI init and reset API for drivers"
- replaced nfc_info with dev_dbg
- make controller setup possible without OTP patch
- retoved the commint that was adding nci_init/reset and using
the newly added functions nci_core_init/reset
- fixed typo and use full controller name instead of abreviation in Kconfig
- added more info about the controller in the commit message
- moved NCI_OP_CORE_RESET_NTF, NCI_OP_CORE_GET_CONFIG_RSP and
NCI_OP_CORE_GET_CONFIG_CMD into the core header files
- added MODULE_DEVICE_TABLE for ACPI
- defined FDP_FW_UPDATE_DEST 0xC2
- release the firmware buffers
- settings the vendor configuration and clock on post setup
- platform data enumerations removed (ACPI enumeration left)
- packet reception intercept logic remove
- nci: removed nci_send_cmd symbol export
- nci: removed nci_request_driver and nci_req_complete_driver
- removed mutex from driver (no need for it)
- nci: check the setup return code before callig post_setup
- nci: add function to allow sending core commands from driver
- nci: Use a separate mutex for nci open and close
- nci: mutex for: Adds a way to get the new connection ID
- nci: Allow the driver to set handler for core nci ops
Robert Dolca (10):
nfc: nci: Export nci data send API
nfc: nci: Add function to get max packet size for conn
nfc: nci: Introduce new core opcodes
nfc: nci: Do not call post_setup when setup fails
nfc: nci: Introduce nci_core_cmd
nfc: nci: Allow the driver to set handler for core nci ops
nfc: nci: rename nci_prop_ops to nci_driver_ops
nfc: nci: fix possible crash in nci_core_conn_create
nfc: nci: add nci_get_conn_info_by_id function
nfc: Add Intel Fields Peak NFC solution driver
drivers/nfc/Kconfig | 1 +
drivers/nfc/Makefile | 1 +
drivers/nfc/fdp/Kconfig | 23 ++
drivers/nfc/fdp/Makefile | 9 +
drivers/nfc/fdp/fdp.c | 817 +++++++++++++++++++++++++++++++++++++++++++++
drivers/nfc/fdp/fdp.h | 38 +++
drivers/nfc/fdp/i2c.c | 388 +++++++++++++++++++++
drivers/nfc/s3fwrn5/nci.c | 4 +-
drivers/nfc/st-nci/core.c | 2 +-
include/net/nfc/nci.h | 7 +
include/net/nfc/nci_core.h | 18 +-
net/nfc/nci/core.c | 120 +++++--
net/nfc/nci/data.c | 13 +
net/nfc/nci/ntf.c | 3 +-
net/nfc/nci/rsp.c | 1 +
15 files changed, 1409 insertions(+), 36 deletions(-)
create mode 100644 drivers/nfc/fdp/Kconfig
create mode 100644 drivers/nfc/fdp/Makefile
create mode 100644 drivers/nfc/fdp/fdp.c
create mode 100644 drivers/nfc/fdp/fdp.h
create mode 100644 drivers/nfc/fdp/i2c.c
--
1.9.1
FDP driver needs to send the firmware as regular packets
(not fragmented). The driver should have a way to
get the max packet size for a given connection.
Signed-off-by: Robert Dolca <[email protected]>
---
include/net/nfc/nci_core.h | 1 +
net/nfc/nci/data.c | 12 ++++++++++++
2 files changed, 13 insertions(+)
diff --git a/include/net/nfc/nci_core.h b/include/net/nfc/nci_core.h
index d0d0f1e..aaada20 100644
--- a/include/net/nfc/nci_core.h
+++ b/include/net/nfc/nci_core.h
@@ -351,6 +351,7 @@ int nci_prop_ntf_packet(struct nci_dev *ndev, __u16 opcode,
void nci_rx_data_packet(struct nci_dev *ndev, struct sk_buff *skb);
int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload);
int nci_send_data(struct nci_dev *ndev, __u8 conn_id, struct sk_buff *skb);
+int nci_conn_max_data_pkt_payload_size(struct nci_dev *ndev, __u8 conn_id);
void nci_data_exchange_complete(struct nci_dev *ndev, struct sk_buff *skb,
__u8 conn_id, int err);
void nci_hci_data_received_cb(void *context, struct sk_buff *skb, int err);
diff --git a/net/nfc/nci/data.c b/net/nfc/nci/data.c
index 83acd18..dbd2425 100644
--- a/net/nfc/nci/data.c
+++ b/net/nfc/nci/data.c
@@ -90,6 +90,18 @@ static inline void nci_push_data_hdr(struct nci_dev *ndev,
nci_pbf_set((__u8 *)hdr, pbf);
}
+int nci_conn_max_data_pkt_payload_size(struct nci_dev *ndev, __u8 conn_id)
+{
+ struct nci_conn_info *conn_info;
+
+ conn_info = nci_get_conn_info_by_conn_id(ndev, conn_id);
+ if (!conn_info)
+ return -EPROTO;
+
+ return conn_info->max_pkt_payload_len;
+}
+EXPORT_SYMBOL(nci_conn_max_data_pkt_payload_size);
+
static int nci_queue_tx_data_frags(struct nci_dev *ndev,
__u8 conn_id,
struct sk_buff *skb) {
--
1.9.1
If the number of destination speific parameters supplied is 0 the call
will fail. If the first destination specific parameter does not have a
value, curr_id will be set to 0.
Signed-off-by: Robert Dolca <[email protected]>
---
net/nfc/nci/core.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
index f66a5da..9d5f7a2 100644
--- a/net/nfc/nci/core.c
+++ b/net/nfc/nci/core.c
@@ -602,12 +602,19 @@ int nci_core_conn_create(struct nci_dev *ndev, u8 destination_type,
if (!cmd)
return -ENOMEM;
+ if (!number_destination_params)
+ return -EINVAL;
+
cmd->destination_type = destination_type;
cmd->number_destination_params = number_destination_params;
memcpy(cmd->params, params, params_len);
data.cmd = cmd;
- ndev->cur_id = params->value[DEST_SPEC_PARAMS_ID_INDEX];
+
+ if (params->length > 0)
+ ndev->cur_id = params->value[DEST_SPEC_PARAMS_ID_INDEX];
+ else
+ ndev->cur_id = 0;
r = __nci_request(ndev, nci_core_conn_create_req,
(unsigned long)&data,
--
1.9.1
This functin takes as a parameter a pointer to the nci_dev struct and
the first byte from the values of the first domain specific parameter that
was used for the connection creation.
Signed-off-by: Robert Dolca <[email protected]>
---
include/net/nfc/nci_core.h | 1 +
net/nfc/nci/core.c | 13 +++++++++++++
2 files changed, 14 insertions(+)
diff --git a/include/net/nfc/nci_core.h b/include/net/nfc/nci_core.h
index 5daf004..0569cb2 100644
--- a/include/net/nfc/nci_core.h
+++ b/include/net/nfc/nci_core.h
@@ -374,6 +374,7 @@ void nci_clear_target_list(struct nci_dev *ndev);
void nci_req_complete(struct nci_dev *ndev, int result);
struct nci_conn_info *nci_get_conn_info_by_conn_id(struct nci_dev *ndev,
int conn_id);
+int nci_get_conn_info_by_id(struct nci_dev *ndev, u8 id);
/* ----- NCI status code ----- */
int nci_to_errno(__u8 code);
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
index 9d5f7a2..75bda34 100644
--- a/net/nfc/nci/core.c
+++ b/net/nfc/nci/core.c
@@ -64,6 +64,19 @@ struct nci_conn_info *nci_get_conn_info_by_conn_id(struct nci_dev *ndev,
return NULL;
}
+int nci_get_conn_info_by_id(struct nci_dev *ndev, u8 id)
+{
+ struct nci_conn_info *conn_info;
+
+ list_for_each_entry(conn_info, &ndev->conn_info_list, list) {
+ if (conn_info->id == id)
+ return conn_info->conn_id;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL(nci_get_conn_info_by_id);
+
/* ---- NCI requests ---- */
void nci_req_complete(struct nci_dev *ndev, int result)
--
1.9.1
Initially it was used to create hooks in the driver for proprietary
operations. Currently it is being used for hooks for both proprietary
and generic operations.
Signed-off-by: Robert Dolca <[email protected]>
---
drivers/nfc/s3fwrn5/nci.c | 4 ++--
drivers/nfc/st-nci/core.c | 2 +-
include/net/nfc/nci_core.h | 6 +++---
net/nfc/nci/core.c | 16 ++++++++--------
4 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/drivers/nfc/s3fwrn5/nci.c b/drivers/nfc/s3fwrn5/nci.c
index ace0071..075e4e8 100644
--- a/drivers/nfc/s3fwrn5/nci.c
+++ b/drivers/nfc/s3fwrn5/nci.c
@@ -31,7 +31,7 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)
return 0;
}
-static struct nci_prop_ops s3fwrn5_nci_prop_ops[] = {
+static struct nci_driver_ops s3fwrn5_nci_prop_ops[] = {
{
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
NCI_PROP_AGAIN),
@@ -79,7 +79,7 @@ static struct nci_prop_ops s3fwrn5_nci_prop_ops[] = {
},
};
-void s3fwrn5_nci_get_prop_ops(struct nci_prop_ops **ops, size_t *n)
+void s3fwrn5_nci_get_prop_ops(struct nci_driver_ops **ops, size_t *n)
{
*ops = s3fwrn5_nci_prop_ops;
*n = ARRAY_SIZE(s3fwrn5_nci_prop_ops);
diff --git a/drivers/nfc/st-nci/core.c b/drivers/nfc/st-nci/core.c
index c419d39..dd7b1c14 100644
--- a/drivers/nfc/st-nci/core.c
+++ b/drivers/nfc/st-nci/core.c
@@ -98,7 +98,7 @@ static int st_nci_prop_rsp_packet(struct nci_dev *ndev,
return 0;
}
-static struct nci_prop_ops st_nci_prop_ops[] = {
+static struct nci_driver_ops st_nci_prop_ops[] = {
{
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
ST_NCI_CORE_PROP),
diff --git a/include/net/nfc/nci_core.h b/include/net/nfc/nci_core.h
index d5a1caa..5daf004 100644
--- a/include/net/nfc/nci_core.h
+++ b/include/net/nfc/nci_core.h
@@ -67,7 +67,7 @@ enum nci_state {
struct nci_dev;
-struct nci_prop_ops {
+struct nci_driver_ops {
__u16 opcode;
int (*rsp)(struct nci_dev *dev, struct sk_buff *skb);
int (*ntf)(struct nci_dev *dev, struct sk_buff *skb);
@@ -94,10 +94,10 @@ struct nci_ops {
void (*hci_cmd_received)(struct nci_dev *ndev, u8 pipe, u8 cmd,
struct sk_buff *skb);
- struct nci_prop_ops *prop_ops;
+ struct nci_driver_ops *prop_ops;
size_t n_prop_ops;
- struct nci_prop_ops *core_ops;
+ struct nci_driver_ops *core_ops;
size_t n_core_ops;
};
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
index 30c2708..f66a5da 100644
--- a/net/nfc/nci/core.c
+++ b/net/nfc/nci/core.c
@@ -1242,12 +1242,12 @@ int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload)
}
/* Proprietary commands API */
-static struct nci_prop_ops *ops_cmd_lookup(struct nci_prop_ops *ops,
- size_t n_ops,
- __u16 opcode)
+static struct nci_driver_ops *ops_cmd_lookup(struct nci_driver_ops *ops,
+ size_t n_ops,
+ __u16 opcode)
{
size_t i;
- struct nci_prop_ops *op;
+ struct nci_driver_ops *op;
if (!ops || !n_ops)
return NULL;
@@ -1262,10 +1262,10 @@ static struct nci_prop_ops *ops_cmd_lookup(struct nci_prop_ops *ops,
}
static int nci_op_rsp_packet(struct nci_dev *ndev, __u16 rsp_opcode,
- struct sk_buff *skb, struct nci_prop_ops *ops,
+ struct sk_buff *skb, struct nci_driver_ops *ops,
size_t n_ops)
{
- struct nci_prop_ops *op;
+ struct nci_driver_ops *op;
op = ops_cmd_lookup(ops, n_ops, rsp_opcode);
if (!op || !op->rsp)
@@ -1275,10 +1275,10 @@ static int nci_op_rsp_packet(struct nci_dev *ndev, __u16 rsp_opcode,
}
static int nci_op_ntf_packet(struct nci_dev *ndev, __u16 ntf_opcode,
- struct sk_buff *skb, struct nci_prop_ops *ops,
+ struct sk_buff *skb, struct nci_driver_ops *ops,
size_t n_ops)
{
- struct nci_prop_ops *op;
+ struct nci_driver_ops *op;
op = ops_cmd_lookup(ops, n_ops, ntf_opcode);
if (!op || !op->ntf)
--
1.9.1
The driver may be required to act when some responses or notifications
arrive. For example the NCI core does not have a handler for
NCI_OP_CORE_GET_CONFIG_RSP. The NFCC can send a config response that has
to be read by the driver and the packet may contain vendor specific data.
The Fields Peak driver needs to take certain actions when a reset
notification arrives (packet also not handled by the nfc core).
The driver handlers do not interfere with the core and they are called
after the core processes the packet.
Signed-off-by: Robert Dolca <[email protected]>
---
include/net/nfc/nci_core.h | 11 +++++--
net/nfc/nci/core.c | 71 +++++++++++++++++++++++++++++++++-------------
net/nfc/nci/ntf.c | 3 +-
net/nfc/nci/rsp.c | 1 +
4 files changed, 63 insertions(+), 23 deletions(-)
diff --git a/include/net/nfc/nci_core.h b/include/net/nfc/nci_core.h
index 4ca1b6d..d5a1caa 100644
--- a/include/net/nfc/nci_core.h
+++ b/include/net/nfc/nci_core.h
@@ -96,6 +96,9 @@ struct nci_ops {
struct nci_prop_ops *prop_ops;
size_t n_prop_ops;
+
+ struct nci_prop_ops *core_ops;
+ size_t n_core_ops;
};
#define NCI_MAX_SUPPORTED_RF_INTERFACES 4
@@ -345,9 +348,13 @@ static inline int nci_set_vendor_cmds(struct nci_dev *ndev,
void nci_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb);
void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb);
-int nci_prop_rsp_packet(struct nci_dev *ndev, __u16 opcode,
+inline int nci_prop_rsp_packet(struct nci_dev *ndev, __u16 opcode,
+ struct sk_buff *skb);
+inline int nci_prop_ntf_packet(struct nci_dev *ndev, __u16 opcode,
+ struct sk_buff *skb);
+inline int nci_core_rsp_packet(struct nci_dev *ndev, __u16 opcode,
struct sk_buff *skb);
-int nci_prop_ntf_packet(struct nci_dev *ndev, __u16 opcode,
+inline int nci_core_ntf_packet(struct nci_dev *ndev, __u16 opcode,
struct sk_buff *skb);
void nci_rx_data_packet(struct nci_dev *ndev, struct sk_buff *skb);
int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload);
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
index 5b4f48a..30c2708 100644
--- a/net/nfc/nci/core.c
+++ b/net/nfc/nci/core.c
@@ -1242,46 +1242,77 @@ int nci_send_cmd(struct nci_dev *ndev, __u16 opcode, __u8 plen, void *payload)
}
/* Proprietary commands API */
-static struct nci_prop_ops *prop_cmd_lookup(struct nci_dev *ndev,
- __u16 opcode)
+static struct nci_prop_ops *ops_cmd_lookup(struct nci_prop_ops *ops,
+ size_t n_ops,
+ __u16 opcode)
{
size_t i;
- struct nci_prop_ops *prop_op;
+ struct nci_prop_ops *op;
- if (!ndev->ops->prop_ops || !ndev->ops->n_prop_ops)
+ if (!ops || !n_ops)
return NULL;
- for (i = 0; i < ndev->ops->n_prop_ops; i++) {
- prop_op = &ndev->ops->prop_ops[i];
- if (prop_op->opcode == opcode)
- return prop_op;
+ for (i = 0; i < n_ops; i++) {
+ op = &ops[i];
+ if (op->opcode == opcode)
+ return op;
}
return NULL;
}
-int nci_prop_rsp_packet(struct nci_dev *ndev, __u16 rsp_opcode,
- struct sk_buff *skb)
+static int nci_op_rsp_packet(struct nci_dev *ndev, __u16 rsp_opcode,
+ struct sk_buff *skb, struct nci_prop_ops *ops,
+ size_t n_ops)
{
- struct nci_prop_ops *prop_op;
+ struct nci_prop_ops *op;
- prop_op = prop_cmd_lookup(ndev, rsp_opcode);
- if (!prop_op || !prop_op->rsp)
+ op = ops_cmd_lookup(ops, n_ops, rsp_opcode);
+ if (!op || !op->rsp)
return -ENOTSUPP;
- return prop_op->rsp(ndev, skb);
+ return op->rsp(ndev, skb);
}
-int nci_prop_ntf_packet(struct nci_dev *ndev, __u16 ntf_opcode,
- struct sk_buff *skb)
+static int nci_op_ntf_packet(struct nci_dev *ndev, __u16 ntf_opcode,
+ struct sk_buff *skb, struct nci_prop_ops *ops,
+ size_t n_ops)
{
- struct nci_prop_ops *prop_op;
+ struct nci_prop_ops *op;
- prop_op = prop_cmd_lookup(ndev, ntf_opcode);
- if (!prop_op || !prop_op->ntf)
+ op = ops_cmd_lookup(ops, n_ops, ntf_opcode);
+ if (!op || !op->ntf)
return -ENOTSUPP;
- return prop_op->ntf(ndev, skb);
+ return op->ntf(ndev, skb);
+}
+
+inline int nci_prop_rsp_packet(struct nci_dev *ndev, __u16 opcode,
+ struct sk_buff *skb)
+{
+ return nci_op_rsp_packet(ndev, opcode, skb, ndev->ops->prop_ops,
+ ndev->ops->n_prop_ops);
+}
+
+inline int nci_prop_ntf_packet(struct nci_dev *ndev, __u16 opcode,
+ struct sk_buff *skb)
+{
+ return nci_op_ntf_packet(ndev, opcode, skb, ndev->ops->prop_ops,
+ ndev->ops->n_prop_ops);
+}
+
+inline int nci_core_rsp_packet(struct nci_dev *ndev, __u16 opcode,
+ struct sk_buff *skb)
+{
+ return nci_op_rsp_packet(ndev, opcode, skb, ndev->ops->core_ops,
+ ndev->ops->n_core_ops);
+}
+
+inline int nci_core_ntf_packet(struct nci_dev *ndev, __u16 opcode,
+ struct sk_buff *skb)
+{
+ return nci_op_ntf_packet(ndev, opcode, skb, ndev->ops->core_ops,
+ ndev->ops->n_core_ops);
}
/* ---- NCI TX Data worker thread ---- */
diff --git a/net/nfc/nci/ntf.c b/net/nfc/nci/ntf.c
index 5d1c2e3..2ada2b3 100644
--- a/net/nfc/nci/ntf.c
+++ b/net/nfc/nci/ntf.c
@@ -759,7 +759,7 @@ void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb)
skb_pull(skb, NCI_CTRL_HDR_SIZE);
if (nci_opcode_gid(ntf_opcode) == NCI_GID_PROPRIETARY) {
- if (nci_prop_ntf_packet(ndev, ntf_opcode, skb)) {
+ if (nci_prop_ntf_packet(ndev, ntf_opcode, skb) == -ENOTSUPP) {
pr_err("unsupported ntf opcode 0x%x\n",
ntf_opcode);
}
@@ -805,6 +805,7 @@ void nci_ntf_packet(struct nci_dev *ndev, struct sk_buff *skb)
break;
}
+ nci_core_ntf_packet(ndev, ntf_opcode, skb);
end:
kfree_skb(skb);
}
diff --git a/net/nfc/nci/rsp.c b/net/nfc/nci/rsp.c
index 408bd8f..9b6eb91 100644
--- a/net/nfc/nci/rsp.c
+++ b/net/nfc/nci/rsp.c
@@ -355,6 +355,7 @@ void nci_rsp_packet(struct nci_dev *ndev, struct sk_buff *skb)
break;
}
+ nci_core_rsp_packet(ndev, rsp_opcode, skb);
end:
kfree_skb(skb);
--
1.9.1
This allows sending core commands from the driver. The driver should be
able to send NCI core commands like CORE_GET_CONFIG_CMD.
Signed-off-by: Robert Dolca <[email protected]>
---
include/net/nfc/nci_core.h | 1 +
net/nfc/nci/core.c | 24 +++++++++++++++++++-----
2 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/include/net/nfc/nci_core.h b/include/net/nfc/nci_core.h
index aaada20..4ca1b6d 100644
--- a/include/net/nfc/nci_core.h
+++ b/include/net/nfc/nci_core.h
@@ -278,6 +278,7 @@ int nci_request(struct nci_dev *ndev,
unsigned long opt),
unsigned long opt, __u32 timeout);
int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload);
+int nci_core_cmd(struct nci_dev *ndev, __u16 opcode, size_t len, __u8 *payload);
int nci_core_reset(struct nci_dev *ndev);
int nci_core_init(struct nci_dev *ndev);
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
index 5362d8f..5b4f48a 100644
--- a/net/nfc/nci/core.c
+++ b/net/nfc/nci/core.c
@@ -325,32 +325,46 @@ static void nci_rf_deactivate_req(struct nci_dev *ndev, unsigned long opt)
sizeof(struct nci_rf_deactivate_cmd), &cmd);
}
-struct nci_prop_cmd_param {
+struct nci_cmd_param {
__u16 opcode;
size_t len;
__u8 *payload;
};
-static void nci_prop_cmd_req(struct nci_dev *ndev, unsigned long opt)
+static void nci_generic_req(struct nci_dev *ndev, unsigned long opt)
{
- struct nci_prop_cmd_param *param = (struct nci_prop_cmd_param *)opt;
+ struct nci_cmd_param *param =
+ (struct nci_cmd_param *)opt;
nci_send_cmd(ndev, param->opcode, param->len, param->payload);
}
int nci_prop_cmd(struct nci_dev *ndev, __u8 oid, size_t len, __u8 *payload)
{
- struct nci_prop_cmd_param param;
+ struct nci_cmd_param param;
param.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, oid);
param.len = len;
param.payload = payload;
- return __nci_request(ndev, nci_prop_cmd_req, (unsigned long)¶m,
+ return __nci_request(ndev, nci_generic_req, (unsigned long)¶m,
msecs_to_jiffies(NCI_CMD_TIMEOUT));
}
EXPORT_SYMBOL(nci_prop_cmd);
+int nci_core_cmd(struct nci_dev *ndev, __u16 opcode, size_t len, __u8 *payload)
+{
+ struct nci_cmd_param param;
+
+ param.opcode = opcode;
+ param.len = len;
+ param.payload = payload;
+
+ return __nci_request(ndev, nci_generic_req, (unsigned long)¶m,
+ msecs_to_jiffies(NCI_CMD_TIMEOUT));
+}
+EXPORT_SYMBOL(nci_core_cmd);
+
int nci_core_reset(struct nci_dev *ndev)
{
return __nci_request(ndev, nci_reset_req, 0,
--
1.9.1
For the firmware update the driver may use nci_send_data.
Signed-off-by: Robert Dolca <[email protected]>
---
net/nfc/nci/data.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/nfc/nci/data.c b/net/nfc/nci/data.c
index 566466d..83acd18 100644
--- a/net/nfc/nci/data.c
+++ b/net/nfc/nci/data.c
@@ -203,6 +203,7 @@ free_exit:
exit:
return rc;
}
+EXPORT_SYMBOL(nci_send_data);
/* ----------------- NCI RX Data ----------------- */
--
1.9.1
The driver should know that it can continue with post setup where
setup left off. Being able to execute post_setup when setup fails may
force the developer to keep this state in the driver.
Signed-off-by: Robert Dolca <[email protected]>
---
net/nfc/nci/core.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/net/nfc/nci/core.c b/net/nfc/nci/core.c
index 0909720..5362d8f 100644
--- a/net/nfc/nci/core.c
+++ b/net/nfc/nci/core.c
@@ -402,9 +402,8 @@ static int nci_open_device(struct nci_dev *ndev)
msecs_to_jiffies(NCI_INIT_TIMEOUT));
}
- if (ndev->ops->post_setup) {
+ if (!rc && ndev->ops->post_setup)
rc = ndev->ops->post_setup(ndev);
- }
if (!rc) {
rc = __nci_request(ndev, nci_init_complete_req, 0,
--
1.9.1
Add NCI_OP_CORE_GET_CONFIG_CMD, NCI_OP_CORE_GET_CONFIG_RSP
and NCI_OP_CORE_RESET_NTF.
Signed-off-by: Robert Dolca <[email protected]>
---
include/net/nfc/nci.h | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/include/net/nfc/nci.h b/include/net/nfc/nci.h
index 75d2e18..b495825 100644
--- a/include/net/nfc/nci.h
+++ b/include/net/nfc/nci.h
@@ -315,6 +315,8 @@ struct nci_nfcee_mode_set_cmd {
__u8 nfcee_mode;
} __packed;
+#define NCI_OP_CORE_GET_CONFIG_CMD nci_opcode_pack(NCI_GID_CORE, 0x03)
+
/* ----------------------- */
/* ---- NCI Responses ---- */
/* ----------------------- */
@@ -375,6 +377,9 @@ struct nci_nfcee_discover_rsp {
} __packed;
#define NCI_OP_NFCEE_MODE_SET_RSP nci_opcode_pack(NCI_GID_NFCEE_MGMT, 0x01)
+
+#define NCI_OP_CORE_GET_CONFIG_RSP nci_opcode_pack(NCI_GID_CORE, 0x03)
+
/* --------------------------- */
/* ---- NCI Notifications ---- */
/* --------------------------- */
@@ -528,4 +533,6 @@ struct nci_nfcee_discover_ntf {
struct nci_nfcee_information_tlv information_tlv;
} __packed;
+#define NCI_OP_CORE_RESET_NTF nci_opcode_pack(NCI_GID_CORE, 0x00)
+
#endif /* __NCI_H */
--
1.9.1
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: Robert Dolca <[email protected]>
---
drivers/nfc/Kconfig | 1 +
drivers/nfc/Makefile | 1 +
drivers/nfc/fdp/Kconfig | 23 ++
drivers/nfc/fdp/Makefile | 9 +
drivers/nfc/fdp/fdp.c | 817 +++++++++++++++++++++++++++++++++++++++++++++++
drivers/nfc/fdp/fdp.h | 38 +++
drivers/nfc/fdp/i2c.c | 388 ++++++++++++++++++++++
7 files changed, 1277 insertions(+)
create mode 100644 drivers/nfc/fdp/Kconfig
create mode 100644 drivers/nfc/fdp/Makefile
create mode 100644 drivers/nfc/fdp/fdp.c
create mode 100644 drivers/nfc/fdp/fdp.h
create mode 100644 drivers/nfc/fdp/i2c.c
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig
index 6639cd1..0d6003d 100644
--- a/drivers/nfc/Kconfig
+++ b/drivers/nfc/Kconfig
@@ -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"
diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile
index 2757fe1..e362141 100644
--- a/drivers/nfc/Makefile
+++ b/drivers/nfc/Makefile
@@ -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
diff --git a/drivers/nfc/fdp/Kconfig b/drivers/nfc/fdp/Kconfig
new file mode 100644
index 0000000..fbccd9d
--- /dev/null
+++ b/drivers/nfc/fdp/Kconfig
@@ -0,0 +1,23 @@
+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.
diff --git a/drivers/nfc/fdp/Makefile b/drivers/nfc/fdp/Makefile
new file mode 100644
index 0000000..e79d51b
--- /dev/null
+++ b/drivers/nfc/fdp/Makefile
@@ -0,0 +1,9 @@
+#
+# 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
+
diff --git a/drivers/nfc/fdp/fdp.c b/drivers/nfc/fdp/fdp.c
new file mode 100644
index 0000000..ccb07a1
--- /dev/null
+++ b/drivers/nfc/fdp/fdp.c
@@ -0,0 +1,817 @@
+/* -------------------------------------------------------------------------
+ * 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/nfc.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <net/nfc/nci_core.h>
+
+#include "fdp.h"
+
+#define FDP_OTP_PATCH_NAME "otp.bin"
+#define FDP_RAM_PATCH_NAME "ram.bin"
+#define FDP_FW_HEADER_SIZE 576
+#define FDP_FW_UPDATE_SLEEP 1000
+
+#define NCI_GET_VERSION_TIMEOUT 8000
+#define NCI_PATCH_REQUEST_TIMEOUT 8000
+#define FDP_PATCH_CONN_DEST 0xC2
+#define FDP_PATCH_CONN_PARAM_TYPE 0xA0
+
+#define NCI_PATCH_TYPE_RAM 0x00
+#define NCI_PATCH_TYPE_OTP 0x01
+#define NCI_PATCH_TYPE_EOT 0xFF
+
+#define NCI_PARAM_ID_FW_RAM_VERSION 0xA0
+#define NCI_PARAM_ID_FW_OTP_VERSION 0xA1
+#define NCI_PARAM_ID_OTP_LIMITED_VERSION 0xC5
+#define NCI_PARAM_ID_KEY_INDEX_ID 0xC6
+
+#define NCI_GID_PROP 0x0F
+#define NCI_OP_PROP_PATCH_OID 0x08
+#define NCI_OP_PROP_SET_PDATA_OID 0x23
+
+struct fdp_nci_info {
+ struct nfc_phy_ops *phy_ops;
+ struct fdp_i2c_phy *phy;
+ struct nci_dev *ndev;
+
+ const struct firmware *otp_patch;
+ const struct firmware *ram_patch;
+ u32 otp_patch_version;
+ u32 ram_patch_version;
+
+ u32 otp_version;
+ u32 ram_version;
+ u32 limited_otp_version;
+ u8 key_index;
+
+ u8 *fw_vsc_cfg;
+ u8 clock_type;
+ u32 clock_freq;
+
+ atomic_t data_pkt_counter;
+ void (*data_pkt_counter_cb)(struct nci_dev *ndev);
+ u8 setup_patch_sent;
+ u8 setup_patch_ntf;
+ u8 setup_patch_status;
+ u8 setup_reset_ntf;
+ wait_queue_head_t setup_wq;
+};
+
+static u8 nci_core_get_config_otp_ram_version[5] = {
+ 0x04,
+ NCI_PARAM_ID_FW_RAM_VERSION,
+ NCI_PARAM_ID_FW_OTP_VERSION,
+ NCI_PARAM_ID_OTP_LIMITED_VERSION,
+ NCI_PARAM_ID_KEY_INDEX_ID
+};
+
+struct nci_core_get_config_rsp {
+ u8 status;
+ u8 count;
+ u8 data[0];
+};
+
+static int fdp_nci_create_conn(struct nci_dev *ndev)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct core_conn_create_dest_spec_params param;
+ int r;
+
+ /* proprietary destination specific paramerer without value */
+ param.type = FDP_PATCH_CONN_PARAM_TYPE;
+ param.length = 0x00;
+
+ r = nci_core_conn_create(info->ndev, FDP_PATCH_CONN_DEST, 1,
+ sizeof(param), ¶m);
+ if (r)
+ return r;
+
+ return nci_get_conn_info_by_id(ndev, 0);
+}
+
+static inline int fdp_nci_get_versions(struct nci_dev *ndev)
+{
+ return nci_core_cmd(ndev, NCI_OP_CORE_GET_CONFIG_CMD,
+ sizeof(nci_core_get_config_otp_ram_version),
+ (__u8 *) &nci_core_get_config_otp_ram_version);
+}
+
+static inline int fdp_nci_patch_cmd(struct nci_dev *ndev, u8 type)
+{
+ return nci_prop_cmd(ndev, NCI_OP_PROP_PATCH_OID, sizeof(type), &type);
+}
+
+static inline int fdp_nci_set_production_data(struct nci_dev *ndev, u8 len,
+ char *data)
+{
+ return nci_prop_cmd(ndev, NCI_OP_PROP_SET_PDATA_OID, len, data);
+}
+
+static int fdp_nci_set_clock(struct nci_dev *ndev, u8 clock_type,
+ u32 clock_freq)
+{
+ u32 fc = 13560;
+ u32 nd, num, delta;
+ char data[9];
+
+ nd = (24 * fc) / clock_freq;
+ delta = 24 * fc - nd * clock_freq;
+ num = (32768 * delta) / clock_freq;
+
+ data[0] = 0x00;
+ data[1] = 0x00;
+ data[2] = 0x00;
+
+ data[3] = 0x10;
+ data[4] = 0x04;
+ data[5] = num & 0xFF;
+ data[6] = (num >> 8) & 0xff;
+ data[7] = nd;
+ data[8] = clock_type;
+
+ return fdp_nci_set_production_data(ndev, 9, data);
+}
+
+static void fdp_nci_send_patch_cb(struct nci_dev *ndev)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+
+ info->setup_patch_sent = 1;
+ wake_up(&info->setup_wq);
+}
+
+/**
+ * Register a packet sent counter and a callback
+ *
+ * We have no other way of knowing when all firmware packets were sent out
+ * on the i2c bus. We need to know that in order to close the connection and
+ * send the patch end message.
+ */
+static void fdp_nci_set_data_pkt_counter(struct nci_dev *ndev,
+ void (*cb)(struct nci_dev *ndev), int count)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+
+ dev_dbg(dev, "NCI data pkt counter %d\n", count);
+ atomic_set(&info->data_pkt_counter, count);
+ info->data_pkt_counter_cb = cb;
+}
+
+/**
+ * The device is expecting a stream of packets. All packets need to
+ * have the PBF flag set to 0x0 (last packet) even if the firmware
+ * file is segmented and there are multiple packets. If we give the
+ * whole firmware to nci_send_data it will segment it and it will set
+ * the PBF flag to 0x01 so we need to do the segmentation here.
+ *
+ * The firmware will be analyzed and applied when we send NCI_OP_PROP_PATCH_CMD
+ * command with NCI_PATCH_TYPE_EOT parameter. The device will send a
+ * NFCC_PATCH_NTF packaet and a NCI_OP_CORE_RESET_NTF packet.
+ */
+static int fdp_nci_send_patch(struct nci_dev *ndev, u8 conn_id, u8 type)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ const struct firmware *fw;
+ struct sk_buff *skb;
+ unsigned long len;
+ u8 max_size, payload_size;
+ int rc = 0;
+
+ if ((type == NCI_PATCH_TYPE_OTP && !info->otp_patch) ||
+ (type == NCI_PATCH_TYPE_RAM && !info->ram_patch))
+ return -EINVAL;
+
+ if (type == NCI_PATCH_TYPE_OTP)
+ fw = info->otp_patch;
+ else
+ fw = info->ram_patch;
+
+ max_size = nci_conn_max_data_pkt_payload_size(ndev, conn_id);
+ if (max_size <= 0)
+ return -EINVAL;
+
+ len = fw->size;
+
+ fdp_nci_set_data_pkt_counter(ndev, fdp_nci_send_patch_cb,
+ DIV_ROUND_UP(fw->size, max_size));
+
+ while (len) {
+
+ payload_size = min_t(unsigned long, (unsigned long) max_size,
+ len);
+
+ skb = nci_skb_alloc(ndev, (NCI_CTRL_HDR_SIZE + payload_size),
+ GFP_KERNEL);
+ if (!skb) {
+ fdp_nci_set_data_pkt_counter(ndev, NULL, 0);
+ return -ENOMEM;
+ }
+
+
+ skb_reserve(skb, NCI_CTRL_HDR_SIZE);
+
+ memcpy(skb_put(skb, payload_size), fw->data + (fw->size - len),
+ payload_size);
+
+ rc = nci_send_data(ndev, conn_id, skb);
+
+ if (rc) {
+ fdp_nci_set_data_pkt_counter(ndev, NULL, 0);
+ return rc;
+ }
+
+ len -= payload_size;
+ }
+
+ return rc;
+}
+
+static int fdp_nci_open(struct nci_dev *ndev)
+{
+ int r;
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ r = info->phy_ops->enable(info->phy);
+
+ return r;
+}
+
+static int fdp_nci_close(struct nci_dev *ndev)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+ return 0;
+}
+
+static int fdp_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (atomic_dec_and_test(&info->data_pkt_counter))
+ info->data_pkt_counter_cb(ndev);
+
+ return info->phy_ops->write(info->phy, skb);
+}
+
+int fdp_nci_recv_frame(struct nci_dev *ndev, struct sk_buff *skb)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+ return nci_recv_frame(ndev, skb);
+}
+EXPORT_SYMBOL(fdp_nci_recv_frame);
+
+static int fdp_nci_request_firmware(struct nci_dev *ndev)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+ u8 *data;
+ int r;
+
+ r = request_firmware(&info->ram_patch, FDP_RAM_PATCH_NAME, dev);
+ if (r < 0) {
+ nfc_err(dev, "RAM patch request error\n");
+ goto error;
+ }
+
+ data = (u8 *) info->ram_patch->data;
+ info->ram_patch_version =
+ data[FDP_FW_HEADER_SIZE] |
+ (data[FDP_FW_HEADER_SIZE + 1] << 8) |
+ (data[FDP_FW_HEADER_SIZE + 2] << 16) |
+ (data[FDP_FW_HEADER_SIZE + 3] << 24);
+
+ dev_dbg(dev, "RAM patch version: %d, size: %d\n",
+ info->ram_patch_version, (int) info->ram_patch->size);
+
+
+ r = request_firmware(&info->otp_patch, FDP_OTP_PATCH_NAME, dev);
+ if (r < 0) {
+ nfc_err(dev, "OTP patch request error\n");
+ goto out;
+ }
+
+ data = (u8 *) info->otp_patch->data;
+ info->otp_patch_version =
+ data[FDP_FW_HEADER_SIZE] |
+ (data[FDP_FW_HEADER_SIZE + 1] << 8) |
+ (data[FDP_FW_HEADER_SIZE+2] << 16) |
+ (data[FDP_FW_HEADER_SIZE+3] << 24);
+
+ dev_dbg(dev, "OTP patch version: %d, size: %d\n",
+ info->otp_patch_version, (int) info->otp_patch->size);
+out:
+ return 0;
+error:
+ return r;
+}
+
+static void fdp_nci_release_firmware(struct nci_dev *ndev)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+
+ if (info->otp_patch) {
+ release_firmware(info->otp_patch);
+ info->otp_patch = NULL;
+ }
+
+ if (info->ram_patch) {
+ release_firmware(info->ram_patch);
+ info->otp_patch = NULL;
+ }
+}
+
+static int fdp_nci_patch_otp(struct nci_dev *ndev)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+ u8 conn_id;
+ int r = 0;
+
+ if (info->otp_version >= info->otp_patch_version)
+ goto out;
+
+ info->setup_patch_sent = 0;
+ info->setup_reset_ntf = 0;
+ info->setup_patch_ntf = 0;
+
+ /* Patch init request */
+ r = fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_OTP);
+ if (r)
+ goto out;
+
+ /* Patch data connection creation */
+ conn_id = fdp_nci_create_conn(ndev);
+ if (conn_id < 0) {
+ r = conn_id;
+ goto out;
+ }
+
+ /* Send the patch over the data connection */
+ r = fdp_nci_send_patch(ndev, conn_id, NCI_PATCH_TYPE_OTP);
+ if (r)
+ goto out;
+
+ /* Wait for all the packets to be send over i2c */
+ wait_event_interruptible(info->setup_wq,
+ info->setup_patch_sent == 1);
+
+ /* make sure that the NFCC processed the last data packet */
+ msleep(FDP_FW_UPDATE_SLEEP);
+
+ /* Close the data connection */
+ r = nci_core_conn_close(info->ndev, conn_id);
+ if (r)
+ goto out;
+
+ /* Patch finish message */
+ if (fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_EOT)) {
+ nfc_err(dev, "OTP patch error 0x%x\n", r);
+ r = -EINVAL;
+ goto out;
+ }
+
+ /* If the patch notification didn't arrive yet, wait for it */
+ wait_event_interruptible(info->setup_wq, info->setup_patch_ntf);
+
+ /* Check if the patching was successful */
+ r = info->setup_patch_status;
+ if (r) {
+ nfc_err(dev, "OTP patch error 0x%x\n", r);
+ r = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * We need to wait for the reset notification before we
+ * can continue
+ */
+ wait_event_interruptible(info->setup_wq, info->setup_reset_ntf);
+
+out:
+ return r;
+}
+
+static int fdp_nci_patch_ram(struct nci_dev *ndev)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+ u8 conn_id;
+ int r = 0;
+
+ if (info->ram_version >= info->ram_patch_version)
+ goto out;
+
+ info->setup_patch_sent = 0;
+ info->setup_reset_ntf = 0;
+ info->setup_patch_ntf = 0;
+
+ /* Patch init request */
+ r = fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_RAM);
+ if (r)
+ goto out;
+
+ /* Patch data connection creation */
+ conn_id = fdp_nci_create_conn(ndev);
+ if (conn_id < 0) {
+ r = conn_id;
+ goto out;
+ }
+
+ /* Send the patch over the data connection */
+ r = fdp_nci_send_patch(ndev, conn_id, NCI_PATCH_TYPE_RAM);
+ if (r)
+ goto out;
+
+ /* Wait for all the packets to be send over i2c */
+ wait_event_interruptible(info->setup_wq,
+ info->setup_patch_sent == 1);
+
+ /* make sure that the NFCC processed the last data packet */
+ msleep(FDP_FW_UPDATE_SLEEP);
+
+ /* Close the data connection */
+ r = nci_core_conn_close(info->ndev, conn_id);
+ if (r)
+ goto out;
+
+ /* Patch finish message */
+ if (fdp_nci_patch_cmd(ndev, NCI_PATCH_TYPE_EOT)) {
+ nfc_err(dev, "RAM patch error 0x%x\n", r);
+ r = -EINVAL;
+ goto out;
+ }
+
+ /* If the patch notification didn't arrive yet, wait for it */
+ wait_event_interruptible(info->setup_wq, info->setup_patch_ntf);
+
+ /* Check if the patching was successful */
+ r = info->setup_patch_status;
+ if (r) {
+ nfc_err(dev, "RAM patch error 0x%x\n", r);
+ r = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * We need to wait for the reset notification before we
+ * can continue
+ */
+ wait_event_interruptible(info->setup_wq, info->setup_reset_ntf);
+
+out:
+ return r;
+}
+
+static int fdp_nci_setup(struct nci_dev *ndev)
+{
+ /* Format: total length followed by an NCI packet */
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+ int r;
+ u8 patched = 0;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ r = nci_core_init(ndev);
+ if (r)
+ goto error;
+
+ /* Get RAM and OTP version */
+ r = fdp_nci_get_versions(ndev);
+ if (r)
+ goto error;
+
+ /* Load firmware from disk */
+ r = fdp_nci_request_firmware(ndev);
+ if (r)
+ goto error;
+
+ /* Update OTP */
+ if (info->otp_version < info->otp_patch_version) {
+ r = fdp_nci_patch_otp(ndev);
+ if (r)
+ goto error;
+ patched = 1;
+ }
+
+ /* Update RAM */
+ if (info->ram_version < info->ram_patch_version) {
+ r = fdp_nci_patch_ram(ndev);
+ if (r)
+ goto error;
+ patched = 1;
+ }
+
+ /* Release the firmware buffers */
+ fdp_nci_release_firmware(ndev);
+
+ /* If a patch was applied the new version is checked */
+ if (patched) {
+ r = nci_core_init(ndev);
+ if (r)
+ goto error;
+
+ r = fdp_nci_get_versions(ndev);
+ if (r)
+ goto error;
+
+ if (info->otp_version != info->otp_patch_version ||
+ info->ram_version != info->ram_patch_version) {
+ nfc_err(dev, "Firmware update failed");
+ r = -EINVAL;
+ goto error;
+ }
+ }
+
+ /*
+ * We initialized the devices but the NFC subsystem expects
+ * it to not be initialized.
+ */
+ return nci_core_reset(ndev);
+
+error:
+ fdp_nci_release_firmware(ndev);
+ nfc_err(dev, "Setup error %d\n", r);
+ return r;
+}
+
+static int fdp_nci_post_setup(struct nci_dev *ndev)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+ int r;
+
+ /* Check if the device has VSC */
+ if (info->fw_vsc_cfg && info->fw_vsc_cfg[0]) {
+
+ /* Set the vendor specific configuration */
+ r = fdp_nci_set_production_data(ndev, info->fw_vsc_cfg[3],
+ &info->fw_vsc_cfg[4]);
+ if (r) {
+ nfc_err(dev, "Vendor specific config set error %d\n",
+ r);
+ return r;
+ }
+ }
+
+ /* Set clock type and frequency */
+ r = fdp_nci_set_clock(ndev, info->clock_type, info->clock_freq);
+ if (r) {
+ nfc_err(dev, "Clock set error %d\n", r);
+ return r;
+ }
+
+ /*
+ * In order to apply the VSC FDP needs a reset
+ */
+ r = nci_core_reset(ndev);
+ if (r)
+ return r;
+
+ /**
+ * The nci core was initialized when post setup was called
+ * so we leave it like that
+ */
+ return nci_core_init(ndev);
+}
+
+static int fdp_nci_core_reset_ntf_packet(struct nci_dev *ndev,
+ struct sk_buff *skb)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+ info->setup_reset_ntf = 1;
+ wake_up(&info->setup_wq);
+
+ return 0;
+}
+
+static int fdp_nci_prop_patch_ntf_packet(struct nci_dev *ndev,
+ struct sk_buff *skb)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+ info->setup_patch_ntf = 1;
+ info->setup_patch_status = skb->data[0];
+ wake_up(&info->setup_wq);
+
+ return 0;
+}
+
+static int fdp_nci_prop_patch_rsp_packet(struct nci_dev *ndev,
+ struct sk_buff *skb)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+ u8 status = skb->data[0];
+
+ dev_dbg(dev, "%s: status 0x%x\n", __func__, status);
+ nci_req_complete(ndev, status);
+
+ return 0;
+}
+
+static int fdp_nci_prop_set_production_data_rsp_packet(struct nci_dev *ndev,
+ struct sk_buff *skb)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+ u8 status = skb->data[0];
+
+ dev_dbg(dev, "%s: status 0x%x\n", __func__, status);
+ nci_req_complete(ndev, status);
+
+ return 0;
+}
+
+static int fdp_nci_core_get_config_rsp_packet(struct nci_dev *ndev,
+ struct sk_buff *skb)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+ struct nci_core_get_config_rsp *rsp = (void *) skb->data;
+ u8 i, *p;
+
+ if (rsp->status == NCI_STATUS_OK) {
+
+ p = rsp->data;
+ for (i = 0; i < 4; i++) {
+
+ switch (*p++) {
+ case NCI_PARAM_ID_FW_RAM_VERSION:
+ p++;
+ info->ram_version = le32_to_cpup((__le32 *) p);
+ p += 4;
+ break;
+ case NCI_PARAM_ID_FW_OTP_VERSION:
+ p++;
+ info->otp_version = le32_to_cpup((__le32 *) p);
+ p += 4;
+ break;
+ case NCI_PARAM_ID_OTP_LIMITED_VERSION:
+ p++;
+ info->otp_version = le32_to_cpup((__le32 *) p);
+ p += 4;
+ break;
+ case NCI_PARAM_ID_KEY_INDEX_ID:
+ p++;
+ info->key_index = *p++;
+ }
+ }
+ }
+
+ dev_dbg(dev, "OTP version %d\n", info->otp_version);
+ dev_dbg(dev, "RAM version %d\n", info->ram_version);
+ dev_dbg(dev, "key index %d\n", info->key_index);
+ dev_dbg(dev, "%s: status 0x%x\n", __func__, rsp->status);
+
+ nci_req_complete(ndev, rsp->status);
+
+ return 0;
+}
+
+static struct nci_driver_ops fdp_core_ops[] = {
+ {
+ .opcode = NCI_OP_CORE_GET_CONFIG_RSP,
+ .rsp = fdp_nci_core_get_config_rsp_packet,
+ },
+ {
+ .opcode = NCI_OP_CORE_RESET_NTF,
+ .ntf = fdp_nci_core_reset_ntf_packet,
+ },
+};
+
+static struct nci_driver_ops fdp_prop_ops[] = {
+ {
+ .opcode = nci_opcode_pack(NCI_GID_PROP, NCI_OP_PROP_PATCH_OID),
+ .rsp = fdp_nci_prop_patch_rsp_packet,
+ .ntf = fdp_nci_prop_patch_ntf_packet,
+ },
+ {
+ .opcode = nci_opcode_pack(NCI_GID_PROP,
+ NCI_OP_PROP_SET_PDATA_OID),
+ .rsp = fdp_nci_prop_set_production_data_rsp_packet,
+ },
+};
+
+struct nci_ops nci_ops = {
+ .open = fdp_nci_open,
+ .close = fdp_nci_close,
+ .send = fdp_nci_send,
+ .setup = fdp_nci_setup,
+ .post_setup = fdp_nci_post_setup,
+ .prop_ops = fdp_prop_ops,
+ .n_prop_ops = ARRAY_SIZE(fdp_prop_ops),
+ .core_ops = fdp_core_ops,
+ .n_core_ops = ARRAY_SIZE(fdp_core_ops),
+};
+
+int fdp_nci_probe(struct fdp_i2c_phy *phy, struct nfc_phy_ops *phy_ops,
+ struct nci_dev **ndevp, int tx_headroom,
+ int tx_tailroom, u8 clock_type, u32 clock_freq,
+ u8 *fw_vsc_cfg)
+{
+ struct device *dev = &phy->i2c_dev->dev;
+ struct fdp_nci_info *info;
+ struct nci_dev *ndev;
+ u32 protocols;
+ int r;
+
+ info = kzalloc(sizeof(struct fdp_nci_info), GFP_KERNEL);
+ if (!info) {
+ r = -ENOMEM;
+ goto err_info_alloc;
+ }
+
+ info->phy = phy;
+ info->phy_ops = phy_ops;
+ info->clock_type = clock_type;
+ info->clock_freq = clock_freq;
+ info->fw_vsc_cfg = fw_vsc_cfg;
+
+ init_waitqueue_head(&info->setup_wq);
+
+ 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 |
+ NFC_PROTO_ISO15693_MASK;
+
+ ndev = nci_allocate_device(&nci_ops, protocols, tx_headroom,
+ tx_tailroom);
+ if (!ndev) {
+ nfc_err(dev, "Cannot allocate nfc ndev\n");
+ r = -ENOMEM;
+ goto err_alloc_ndev;
+ }
+
+ r = nci_register_device(ndev);
+ if (r)
+ goto err_regdev;
+
+ *ndevp = ndev;
+ info->ndev = ndev;
+
+ nci_set_drvdata(ndev, info);
+
+ return 0;
+
+err_regdev:
+ nci_free_device(ndev);
+err_alloc_ndev:
+ kfree(info);
+err_info_alloc:
+ return r;
+}
+EXPORT_SYMBOL(fdp_nci_probe);
+
+void fdp_nci_remove(struct nci_dev *ndev)
+{
+ struct fdp_nci_info *info = nci_get_drvdata(ndev);
+ struct device *dev = &info->phy->i2c_dev->dev;
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ nci_unregister_device(ndev);
+ nci_free_device(ndev);
+ kfree(info);
+}
+EXPORT_SYMBOL(fdp_nci_remove);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("NFC NCI driver for Intel Fields Peak NFC controller");
+MODULE_AUTHOR("Robert Dolca <[email protected]>");
diff --git a/drivers/nfc/fdp/fdp.h b/drivers/nfc/fdp/fdp.h
new file mode 100644
index 0000000..0bd36c0
--- /dev/null
+++ b/drivers/nfc/fdp/fdp.h
@@ -0,0 +1,38 @@
+/* -------------------------------------------------------------------------
+ * 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_ */
diff --git a/drivers/nfc/fdp/i2c.c b/drivers/nfc/fdp/i2c.c
new file mode 100644
index 0000000..532db28
--- /dev/null
+++ b/drivers/nfc/fdp/i2c.c
@@ -0,0 +1,388 @@
+/* -------------------------------------------------------------------------
+ * 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 <[email protected]>");
--
1.9.1