diff options
author | Robert Baldyga <r.baldyga@samsung.com> | 2015-08-20 11:26:02 -0400 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2015-08-20 15:23:39 -0400 |
commit | c04c674fadeb4a8e6522fc838d4620f7cfd4c621 (patch) | |
tree | 39a753081f21fd69630a235a228e3eaa47c9f554 | |
parent | 025a0cb8380b7100d39fb426db9192b6c59595dc (diff) |
nfc: s3fwrn5: Add driver for Samsung S3FWRN5 NFC Chip
Add driver for Samsung S3FWRN5 NFC controller.
S3FWRN5 is using NCI protocol and I2C communication interface.
Signed-off-by: Robert Baldyga <r.baldyga@samsung.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
-rw-r--r-- | Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt | 27 | ||||
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rw-r--r-- | drivers/nfc/Kconfig | 1 | ||||
-rw-r--r-- | drivers/nfc/Makefile | 1 | ||||
-rw-r--r-- | drivers/nfc/s3fwrn5/Kconfig | 19 | ||||
-rw-r--r-- | drivers/nfc/s3fwrn5/Makefile | 11 | ||||
-rw-r--r-- | drivers/nfc/s3fwrn5/core.c | 219 | ||||
-rw-r--r-- | drivers/nfc/s3fwrn5/firmware.c | 511 | ||||
-rw-r--r-- | drivers/nfc/s3fwrn5/firmware.h | 111 | ||||
-rw-r--r-- | drivers/nfc/s3fwrn5/i2c.c | 306 | ||||
-rw-r--r-- | drivers/nfc/s3fwrn5/nci.c | 165 | ||||
-rw-r--r-- | drivers/nfc/s3fwrn5/nci.h | 89 | ||||
-rw-r--r-- | drivers/nfc/s3fwrn5/s3fwrn5.h | 99 |
13 files changed, 1565 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt b/Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt new file mode 100644 index 000000000000..fb1e75facf1b --- /dev/null +++ b/Documentation/devicetree/bindings/net/nfc/s3fwrn5.txt | |||
@@ -0,0 +1,27 @@ | |||
1 | * Samsung S3FWRN5 NCI NFC Controller | ||
2 | |||
3 | Required properties: | ||
4 | - compatible: Should be "samsung,s3fwrn5-i2c". | ||
5 | - reg: address on the bus | ||
6 | - interrupt-parent: phandle for the interrupt gpio controller | ||
7 | - interrupts: GPIO interrupt to which the chip is connected | ||
8 | - s3fwrn5,en-gpios: Output GPIO pin used for enabling/disabling the chip | ||
9 | - s3fwrn5,fw-gpios: Output GPIO pin used to enter firmware mode and | ||
10 | sleep/wakeup control | ||
11 | |||
12 | Example: | ||
13 | |||
14 | &hsi2c_4 { | ||
15 | status = "okay"; | ||
16 | s3fwrn5@27 { | ||
17 | compatible = "samsung,s3fwrn5-i2c"; | ||
18 | |||
19 | reg = <0x27>; | ||
20 | |||
21 | interrupt-parent = <&gpa1>; | ||
22 | interrupts = <3 0 0>; | ||
23 | |||
24 | s3fwrn5,en-gpios = <&gpf1 4 0>; | ||
25 | s3fwrn5,fw-gpios = <&gpj0 2 0>; | ||
26 | }; | ||
27 | }; | ||
diff --git a/MAINTAINERS b/MAINTAINERS index ca51eba9fe5d..7a3b1b901d22 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -8871,6 +8871,12 @@ L: linux-media@vger.kernel.org | |||
8871 | S: Supported | 8871 | S: Supported |
8872 | F: drivers/media/i2c/s5k5baf.c | 8872 | F: drivers/media/i2c/s5k5baf.c |
8873 | 8873 | ||
8874 | SAMSUNG S3FWRN5 NFC DRIVER | ||
8875 | M: Robert Baldyga <r.baldyga@samsung.com> | ||
8876 | L: linux-nfc@lists.01.org (moderated for non-subscribers) | ||
8877 | S: Supported | ||
8878 | F: drivers/nfc/s3fwrn5 | ||
8879 | |||
8874 | SAMSUNG SOC CLOCK DRIVERS | 8880 | SAMSUNG SOC CLOCK DRIVERS |
8875 | M: Sylwester Nawrocki <s.nawrocki@samsung.com> | 8881 | M: Sylwester Nawrocki <s.nawrocki@samsung.com> |
8876 | M: Tomasz Figa <tomasz.figa@gmail.com> | 8882 | M: Tomasz Figa <tomasz.figa@gmail.com> |
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig index 722673cb785b..6639cd1cae36 100644 --- a/drivers/nfc/Kconfig +++ b/drivers/nfc/Kconfig | |||
@@ -74,4 +74,5 @@ source "drivers/nfc/nfcmrvl/Kconfig" | |||
74 | source "drivers/nfc/st21nfca/Kconfig" | 74 | source "drivers/nfc/st21nfca/Kconfig" |
75 | source "drivers/nfc/st-nci/Kconfig" | 75 | source "drivers/nfc/st-nci/Kconfig" |
76 | source "drivers/nfc/nxp-nci/Kconfig" | 76 | source "drivers/nfc/nxp-nci/Kconfig" |
77 | source "drivers/nfc/s3fwrn5/Kconfig" | ||
77 | endmenu | 78 | endmenu |
diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile index 368b6dfe71b3..2757fe1b8aa5 100644 --- a/drivers/nfc/Makefile +++ b/drivers/nfc/Makefile | |||
@@ -14,3 +14,4 @@ obj-$(CONFIG_NFC_TRF7970A) += trf7970a.o | |||
14 | obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/ | 14 | obj-$(CONFIG_NFC_ST21NFCA) += st21nfca/ |
15 | obj-$(CONFIG_NFC_ST_NCI) += st-nci/ | 15 | obj-$(CONFIG_NFC_ST_NCI) += st-nci/ |
16 | obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/ | 16 | obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/ |
17 | obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5/ | ||
diff --git a/drivers/nfc/s3fwrn5/Kconfig b/drivers/nfc/s3fwrn5/Kconfig new file mode 100644 index 000000000000..7e3b255b3f99 --- /dev/null +++ b/drivers/nfc/s3fwrn5/Kconfig | |||
@@ -0,0 +1,19 @@ | |||
1 | config NFC_S3FWRN5 | ||
2 | tristate | ||
3 | ---help--- | ||
4 | Core driver for Samsung S3FWRN5 NFC chip. Contains core utilities | ||
5 | of chip. It's intended to be used by PHYs to avoid duplicating lots | ||
6 | of common code. | ||
7 | |||
8 | config NFC_S3FWRN5_I2C | ||
9 | tristate "Samsung S3FWRN5 I2C support" | ||
10 | depends on NFC_NCI && I2C | ||
11 | select NFC_S3FWRN5 | ||
12 | default n | ||
13 | ---help--- | ||
14 | This module adds support for an I2C interface to the S3FWRN5 chip. | ||
15 | Select this if your platform is using the I2C bus. | ||
16 | |||
17 | To compile this driver as a module, choose m here. The module will | ||
18 | be called s3fwrn5_i2c.ko. | ||
19 | Say N if unsure. | ||
diff --git a/drivers/nfc/s3fwrn5/Makefile b/drivers/nfc/s3fwrn5/Makefile new file mode 100644 index 000000000000..3381c34faf62 --- /dev/null +++ b/drivers/nfc/s3fwrn5/Makefile | |||
@@ -0,0 +1,11 @@ | |||
1 | # | ||
2 | # Makefile for Samsung S3FWRN5 NFC driver | ||
3 | # | ||
4 | |||
5 | s3fwrn5-objs = core.o firmware.o nci.o | ||
6 | s3fwrn5_i2c-objs = i2c.o | ||
7 | |||
8 | obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5.o | ||
9 | obj-$(CONFIG_NFC_S3FWRN5_I2C) += s3fwrn5_i2c.o | ||
10 | |||
11 | ccflags-$(CONFIG_NFC_DEBUG) := -DDEBUG | ||
diff --git a/drivers/nfc/s3fwrn5/core.c b/drivers/nfc/s3fwrn5/core.c new file mode 100644 index 000000000000..0d866ca295e3 --- /dev/null +++ b/drivers/nfc/s3fwrn5/core.c | |||
@@ -0,0 +1,219 @@ | |||
1 | /* | ||
2 | * NCI based driver for Samsung S3FWRN5 NFC chip | ||
3 | * | ||
4 | * Copyright (C) 2015 Samsung Electrnoics | ||
5 | * Robert Baldyga <r.baldyga@samsung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms and conditions of the GNU General Public License, | ||
9 | * version 2 or later, as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <net/nfc/nci_core.h> | ||
22 | |||
23 | #include "s3fwrn5.h" | ||
24 | #include "firmware.h" | ||
25 | #include "nci.h" | ||
26 | |||
27 | #define S3FWRN5_NFC_PROTOCOLS (NFC_PROTO_JEWEL_MASK | \ | ||
28 | NFC_PROTO_MIFARE_MASK | \ | ||
29 | NFC_PROTO_FELICA_MASK | \ | ||
30 | NFC_PROTO_ISO14443_MASK | \ | ||
31 | NFC_PROTO_ISO14443_B_MASK | \ | ||
32 | NFC_PROTO_ISO15693_MASK) | ||
33 | |||
34 | static int s3fwrn5_firmware_update(struct s3fwrn5_info *info) | ||
35 | { | ||
36 | bool need_update; | ||
37 | int ret; | ||
38 | |||
39 | s3fwrn5_fw_init(&info->fw_info, "sec_s3fwrn5_firmware.bin"); | ||
40 | |||
41 | /* Update firmware */ | ||
42 | |||
43 | s3fwrn5_set_wake(info, false); | ||
44 | s3fwrn5_set_mode(info, S3FWRN5_MODE_FW); | ||
45 | |||
46 | ret = s3fwrn5_fw_setup(&info->fw_info); | ||
47 | if (ret < 0) | ||
48 | return ret; | ||
49 | |||
50 | need_update = s3fwrn5_fw_check_version(&info->fw_info, | ||
51 | info->ndev->manufact_specific_info); | ||
52 | if (!need_update) | ||
53 | goto out; | ||
54 | |||
55 | dev_info(&info->ndev->nfc_dev->dev, "Detected new firmware version\n"); | ||
56 | |||
57 | ret = s3fwrn5_fw_download(&info->fw_info); | ||
58 | if (ret < 0) | ||
59 | goto out; | ||
60 | |||
61 | /* Update RF configuration */ | ||
62 | |||
63 | s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI); | ||
64 | |||
65 | s3fwrn5_set_wake(info, true); | ||
66 | ret = s3fwrn5_nci_rf_configure(info, "sec_s3fwrn5_rfreg.bin"); | ||
67 | s3fwrn5_set_wake(info, false); | ||
68 | |||
69 | out: | ||
70 | s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); | ||
71 | s3fwrn5_fw_cleanup(&info->fw_info); | ||
72 | return ret; | ||
73 | } | ||
74 | |||
75 | static int s3fwrn5_nci_open(struct nci_dev *ndev) | ||
76 | { | ||
77 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); | ||
78 | |||
79 | if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_COLD) | ||
80 | return -EBUSY; | ||
81 | |||
82 | s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI); | ||
83 | s3fwrn5_set_wake(info, true); | ||
84 | |||
85 | return 0; | ||
86 | } | ||
87 | |||
88 | static int s3fwrn5_nci_close(struct nci_dev *ndev) | ||
89 | { | ||
90 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); | ||
91 | |||
92 | s3fwrn5_set_wake(info, false); | ||
93 | s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); | ||
94 | |||
95 | return 0; | ||
96 | } | ||
97 | |||
98 | static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb) | ||
99 | { | ||
100 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); | ||
101 | int ret; | ||
102 | |||
103 | mutex_lock(&info->mutex); | ||
104 | |||
105 | if (s3fwrn5_get_mode(info) != S3FWRN5_MODE_NCI) { | ||
106 | mutex_unlock(&info->mutex); | ||
107 | return -EINVAL; | ||
108 | } | ||
109 | |||
110 | ret = s3fwrn5_write(info, skb); | ||
111 | if (ret < 0) | ||
112 | kfree_skb(skb); | ||
113 | |||
114 | mutex_unlock(&info->mutex); | ||
115 | return ret; | ||
116 | } | ||
117 | |||
118 | static int s3fwrn5_nci_post_setup(struct nci_dev *ndev) | ||
119 | { | ||
120 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); | ||
121 | int ret; | ||
122 | |||
123 | ret = s3fwrn5_firmware_update(info); | ||
124 | if (ret < 0) | ||
125 | goto out; | ||
126 | |||
127 | /* NCI core reset */ | ||
128 | |||
129 | s3fwrn5_set_mode(info, S3FWRN5_MODE_NCI); | ||
130 | s3fwrn5_set_wake(info, true); | ||
131 | |||
132 | ret = nci_core_reset(info->ndev); | ||
133 | if (ret < 0) | ||
134 | goto out; | ||
135 | |||
136 | ret = nci_core_init(info->ndev); | ||
137 | |||
138 | out: | ||
139 | return ret; | ||
140 | } | ||
141 | |||
142 | static struct nci_ops s3fwrn5_nci_ops = { | ||
143 | .open = s3fwrn5_nci_open, | ||
144 | .close = s3fwrn5_nci_close, | ||
145 | .send = s3fwrn5_nci_send, | ||
146 | .post_setup = s3fwrn5_nci_post_setup, | ||
147 | }; | ||
148 | |||
149 | int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev, | ||
150 | struct s3fwrn5_phy_ops *phy_ops, unsigned int max_payload) | ||
151 | { | ||
152 | struct s3fwrn5_info *info; | ||
153 | int ret; | ||
154 | |||
155 | info = devm_kzalloc(pdev, sizeof(*info), GFP_KERNEL); | ||
156 | if (!info) | ||
157 | return -ENOMEM; | ||
158 | |||
159 | info->phy_id = phy_id; | ||
160 | info->pdev = pdev; | ||
161 | info->phy_ops = phy_ops; | ||
162 | info->max_payload = max_payload; | ||
163 | mutex_init(&info->mutex); | ||
164 | |||
165 | s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); | ||
166 | |||
167 | s3fwrn5_nci_get_prop_ops(&s3fwrn5_nci_ops.prop_ops, | ||
168 | &s3fwrn5_nci_ops.n_prop_ops); | ||
169 | |||
170 | info->ndev = nci_allocate_device(&s3fwrn5_nci_ops, | ||
171 | S3FWRN5_NFC_PROTOCOLS, 0, 0); | ||
172 | if (!info->ndev) | ||
173 | return -ENOMEM; | ||
174 | |||
175 | nci_set_parent_dev(info->ndev, pdev); | ||
176 | nci_set_drvdata(info->ndev, info); | ||
177 | |||
178 | ret = nci_register_device(info->ndev); | ||
179 | if (ret < 0) { | ||
180 | nci_free_device(info->ndev); | ||
181 | return ret; | ||
182 | } | ||
183 | |||
184 | info->fw_info.ndev = info->ndev; | ||
185 | |||
186 | *ndev = info->ndev; | ||
187 | |||
188 | return ret; | ||
189 | } | ||
190 | EXPORT_SYMBOL(s3fwrn5_probe); | ||
191 | |||
192 | void s3fwrn5_remove(struct nci_dev *ndev) | ||
193 | { | ||
194 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); | ||
195 | |||
196 | s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD); | ||
197 | |||
198 | nci_unregister_device(ndev); | ||
199 | nci_free_device(ndev); | ||
200 | } | ||
201 | EXPORT_SYMBOL(s3fwrn5_remove); | ||
202 | |||
203 | int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb, | ||
204 | enum s3fwrn5_mode mode) | ||
205 | { | ||
206 | switch (mode) { | ||
207 | case S3FWRN5_MODE_NCI: | ||
208 | return nci_recv_frame(ndev, skb); | ||
209 | case S3FWRN5_MODE_FW: | ||
210 | return s3fwrn5_fw_recv_frame(ndev, skb); | ||
211 | default: | ||
212 | return -ENODEV; | ||
213 | } | ||
214 | } | ||
215 | EXPORT_SYMBOL(s3fwrn5_recv_frame); | ||
216 | |||
217 | MODULE_LICENSE("GPL"); | ||
218 | MODULE_DESCRIPTION("Samsung S3FWRN5 NFC driver"); | ||
219 | MODULE_AUTHOR("Robert Baldyga <r.baldyga@samsung.com>"); | ||
diff --git a/drivers/nfc/s3fwrn5/firmware.c b/drivers/nfc/s3fwrn5/firmware.c new file mode 100644 index 000000000000..64a90252c57f --- /dev/null +++ b/drivers/nfc/s3fwrn5/firmware.c | |||
@@ -0,0 +1,511 @@ | |||
1 | /* | ||
2 | * NCI based driver for Samsung S3FWRN5 NFC chip | ||
3 | * | ||
4 | * Copyright (C) 2015 Samsung Electrnoics | ||
5 | * Robert Baldyga <r.baldyga@samsung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms and conditions of the GNU General Public License, | ||
9 | * version 2 or later, as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #include <linux/completion.h> | ||
21 | #include <linux/firmware.h> | ||
22 | #include <linux/crypto.h> | ||
23 | #include <crypto/sha.h> | ||
24 | |||
25 | #include "s3fwrn5.h" | ||
26 | #include "firmware.h" | ||
27 | |||
28 | struct s3fwrn5_fw_version { | ||
29 | __u8 major; | ||
30 | __u8 build1; | ||
31 | __u8 build2; | ||
32 | __u8 target; | ||
33 | }; | ||
34 | |||
35 | static int s3fwrn5_fw_send_msg(struct s3fwrn5_fw_info *fw_info, | ||
36 | struct sk_buff *msg, struct sk_buff **rsp) | ||
37 | { | ||
38 | struct s3fwrn5_info *info = | ||
39 | container_of(fw_info, struct s3fwrn5_info, fw_info); | ||
40 | long ret; | ||
41 | |||
42 | reinit_completion(&fw_info->completion); | ||
43 | |||
44 | ret = s3fwrn5_write(info, msg); | ||
45 | if (ret < 0) | ||
46 | return ret; | ||
47 | |||
48 | ret = wait_for_completion_interruptible_timeout( | ||
49 | &fw_info->completion, msecs_to_jiffies(1000)); | ||
50 | if (ret < 0) | ||
51 | return ret; | ||
52 | else if (ret == 0) | ||
53 | return -ENXIO; | ||
54 | |||
55 | if (!fw_info->rsp) | ||
56 | return -EINVAL; | ||
57 | |||
58 | *rsp = fw_info->rsp; | ||
59 | fw_info->rsp = NULL; | ||
60 | |||
61 | return 0; | ||
62 | } | ||
63 | |||
64 | static int s3fwrn5_fw_prep_msg(struct s3fwrn5_fw_info *fw_info, | ||
65 | struct sk_buff **msg, u8 type, u8 code, const void *data, u16 len) | ||
66 | { | ||
67 | struct s3fwrn5_fw_header hdr; | ||
68 | struct sk_buff *skb; | ||
69 | |||
70 | hdr.type = type | fw_info->parity; | ||
71 | fw_info->parity ^= 0x80; | ||
72 | hdr.code = code; | ||
73 | hdr.len = len; | ||
74 | |||
75 | skb = alloc_skb(S3FWRN5_FW_HDR_SIZE + len, GFP_KERNEL); | ||
76 | if (!skb) | ||
77 | return -ENOMEM; | ||
78 | |||
79 | memcpy(skb_put(skb, S3FWRN5_FW_HDR_SIZE), &hdr, S3FWRN5_FW_HDR_SIZE); | ||
80 | if (len) | ||
81 | memcpy(skb_put(skb, len), data, len); | ||
82 | |||
83 | *msg = skb; | ||
84 | |||
85 | return 0; | ||
86 | } | ||
87 | |||
88 | static int s3fwrn5_fw_get_bootinfo(struct s3fwrn5_fw_info *fw_info, | ||
89 | struct s3fwrn5_fw_cmd_get_bootinfo_rsp *bootinfo) | ||
90 | { | ||
91 | struct sk_buff *msg, *rsp = NULL; | ||
92 | struct s3fwrn5_fw_header *hdr; | ||
93 | int ret; | ||
94 | |||
95 | /* Send GET_BOOTINFO command */ | ||
96 | |||
97 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD, | ||
98 | S3FWRN5_FW_CMD_GET_BOOTINFO, NULL, 0); | ||
99 | if (ret < 0) | ||
100 | return ret; | ||
101 | |||
102 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | ||
103 | kfree_skb(msg); | ||
104 | if (ret < 0) | ||
105 | return ret; | ||
106 | |||
107 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | ||
108 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | ||
109 | ret = -EINVAL; | ||
110 | goto out; | ||
111 | } | ||
112 | |||
113 | memcpy(bootinfo, rsp->data + S3FWRN5_FW_HDR_SIZE, 10); | ||
114 | |||
115 | out: | ||
116 | kfree_skb(rsp); | ||
117 | return ret; | ||
118 | } | ||
119 | |||
120 | static int s3fwrn5_fw_enter_update_mode(struct s3fwrn5_fw_info *fw_info, | ||
121 | const void *hash_data, u16 hash_size, | ||
122 | const void *sig_data, u16 sig_size) | ||
123 | { | ||
124 | struct s3fwrn5_fw_cmd_enter_updatemode args; | ||
125 | struct sk_buff *msg, *rsp = NULL; | ||
126 | struct s3fwrn5_fw_header *hdr; | ||
127 | int ret; | ||
128 | |||
129 | /* Send ENTER_UPDATE_MODE command */ | ||
130 | |||
131 | args.hashcode_size = hash_size; | ||
132 | args.signature_size = sig_size; | ||
133 | |||
134 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD, | ||
135 | S3FWRN5_FW_CMD_ENTER_UPDATE_MODE, &args, sizeof(args)); | ||
136 | if (ret < 0) | ||
137 | return ret; | ||
138 | |||
139 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | ||
140 | kfree_skb(msg); | ||
141 | if (ret < 0) | ||
142 | return ret; | ||
143 | |||
144 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | ||
145 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | ||
146 | ret = -EPROTO; | ||
147 | goto out; | ||
148 | } | ||
149 | |||
150 | kfree_skb(rsp); | ||
151 | |||
152 | /* Send hashcode data */ | ||
153 | |||
154 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_DATA, 0, | ||
155 | hash_data, hash_size); | ||
156 | if (ret < 0) | ||
157 | return ret; | ||
158 | |||
159 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | ||
160 | kfree_skb(msg); | ||
161 | if (ret < 0) | ||
162 | return ret; | ||
163 | |||
164 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | ||
165 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | ||
166 | ret = -EPROTO; | ||
167 | goto out; | ||
168 | } | ||
169 | |||
170 | kfree_skb(rsp); | ||
171 | |||
172 | /* Send signature data */ | ||
173 | |||
174 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_DATA, 0, | ||
175 | sig_data, sig_size); | ||
176 | if (ret < 0) | ||
177 | return ret; | ||
178 | |||
179 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | ||
180 | kfree_skb(msg); | ||
181 | if (ret < 0) | ||
182 | return ret; | ||
183 | |||
184 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | ||
185 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) | ||
186 | ret = -EPROTO; | ||
187 | |||
188 | out: | ||
189 | kfree_skb(rsp); | ||
190 | return ret; | ||
191 | } | ||
192 | |||
193 | static int s3fwrn5_fw_update_sector(struct s3fwrn5_fw_info *fw_info, | ||
194 | u32 base_addr, const void *data) | ||
195 | { | ||
196 | struct s3fwrn5_fw_cmd_update_sector args; | ||
197 | struct sk_buff *msg, *rsp = NULL; | ||
198 | struct s3fwrn5_fw_header *hdr; | ||
199 | int ret, i; | ||
200 | |||
201 | /* Send UPDATE_SECTOR command */ | ||
202 | |||
203 | args.base_address = base_addr; | ||
204 | |||
205 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD, | ||
206 | S3FWRN5_FW_CMD_UPDATE_SECTOR, &args, sizeof(args)); | ||
207 | if (ret < 0) | ||
208 | return ret; | ||
209 | |||
210 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | ||
211 | kfree_skb(msg); | ||
212 | if (ret < 0) | ||
213 | return ret; | ||
214 | |||
215 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | ||
216 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | ||
217 | ret = -EPROTO; | ||
218 | goto err; | ||
219 | } | ||
220 | |||
221 | kfree_skb(rsp); | ||
222 | |||
223 | /* Send data split into 256-byte packets */ | ||
224 | |||
225 | for (i = 0; i < 16; ++i) { | ||
226 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, | ||
227 | S3FWRN5_FW_MSG_DATA, 0, data+256*i, 256); | ||
228 | if (ret < 0) | ||
229 | break; | ||
230 | |||
231 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | ||
232 | kfree_skb(msg); | ||
233 | if (ret < 0) | ||
234 | break; | ||
235 | |||
236 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | ||
237 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) { | ||
238 | ret = -EPROTO; | ||
239 | goto err; | ||
240 | } | ||
241 | |||
242 | kfree_skb(rsp); | ||
243 | } | ||
244 | |||
245 | return ret; | ||
246 | |||
247 | err: | ||
248 | kfree_skb(rsp); | ||
249 | return ret; | ||
250 | } | ||
251 | |||
252 | static int s3fwrn5_fw_complete_update_mode(struct s3fwrn5_fw_info *fw_info) | ||
253 | { | ||
254 | struct sk_buff *msg, *rsp = NULL; | ||
255 | struct s3fwrn5_fw_header *hdr; | ||
256 | int ret; | ||
257 | |||
258 | /* Send COMPLETE_UPDATE_MODE command */ | ||
259 | |||
260 | ret = s3fwrn5_fw_prep_msg(fw_info, &msg, S3FWRN5_FW_MSG_CMD, | ||
261 | S3FWRN5_FW_CMD_COMPLETE_UPDATE_MODE, NULL, 0); | ||
262 | if (ret < 0) | ||
263 | return ret; | ||
264 | |||
265 | ret = s3fwrn5_fw_send_msg(fw_info, msg, &rsp); | ||
266 | kfree_skb(msg); | ||
267 | if (ret < 0) | ||
268 | return ret; | ||
269 | |||
270 | hdr = (struct s3fwrn5_fw_header *) rsp->data; | ||
271 | if (hdr->code != S3FWRN5_FW_RET_SUCCESS) | ||
272 | ret = -EPROTO; | ||
273 | |||
274 | kfree_skb(rsp); | ||
275 | |||
276 | return ret; | ||
277 | } | ||
278 | |||
279 | /* | ||
280 | * Firmware header stucture: | ||
281 | * | ||
282 | * 0x00 - 0x0B : Date and time string (w/o NUL termination) | ||
283 | * 0x10 - 0x13 : Firmware version | ||
284 | * 0x14 - 0x17 : Signature address | ||
285 | * 0x18 - 0x1B : Signature size | ||
286 | * 0x1C - 0x1F : Firmware image address | ||
287 | * 0x20 - 0x23 : Firmware sectors count | ||
288 | * 0x24 - 0x27 : Custom signature address | ||
289 | * 0x28 - 0x2B : Custom signature size | ||
290 | */ | ||
291 | |||
292 | #define S3FWRN5_FW_IMAGE_HEADER_SIZE 44 | ||
293 | |||
294 | static int s3fwrn5_fw_request_firmware(struct s3fwrn5_fw_info *fw_info) | ||
295 | { | ||
296 | struct s3fwrn5_fw_image *fw = &fw_info->fw; | ||
297 | u32 sig_off; | ||
298 | u32 image_off; | ||
299 | u32 custom_sig_off; | ||
300 | int ret; | ||
301 | |||
302 | ret = request_firmware(&fw->fw, fw_info->fw_name, | ||
303 | &fw_info->ndev->nfc_dev->dev); | ||
304 | if (ret < 0) | ||
305 | return ret; | ||
306 | |||
307 | if (fw->fw->size < S3FWRN5_FW_IMAGE_HEADER_SIZE) | ||
308 | return -EINVAL; | ||
309 | |||
310 | memcpy(fw->date, fw->fw->data + 0x00, 12); | ||
311 | fw->date[12] = '\0'; | ||
312 | |||
313 | memcpy(&fw->version, fw->fw->data + 0x10, 4); | ||
314 | |||
315 | memcpy(&sig_off, fw->fw->data + 0x14, 4); | ||
316 | fw->sig = fw->fw->data + sig_off; | ||
317 | memcpy(&fw->sig_size, fw->fw->data + 0x18, 4); | ||
318 | |||
319 | memcpy(&image_off, fw->fw->data + 0x1C, 4); | ||
320 | fw->image = fw->fw->data + image_off; | ||
321 | memcpy(&fw->image_sectors, fw->fw->data + 0x20, 4); | ||
322 | |||
323 | memcpy(&custom_sig_off, fw->fw->data + 0x24, 4); | ||
324 | fw->custom_sig = fw->fw->data + custom_sig_off; | ||
325 | memcpy(&fw->custom_sig_size, fw->fw->data + 0x28, 4); | ||
326 | |||
327 | return 0; | ||
328 | } | ||
329 | |||
330 | static void s3fwrn5_fw_release_firmware(struct s3fwrn5_fw_info *fw_info) | ||
331 | { | ||
332 | release_firmware(fw_info->fw.fw); | ||
333 | } | ||
334 | |||
335 | static int s3fwrn5_fw_get_base_addr( | ||
336 | struct s3fwrn5_fw_cmd_get_bootinfo_rsp *bootinfo, u32 *base_addr) | ||
337 | { | ||
338 | int i; | ||
339 | struct { | ||
340 | u8 version[4]; | ||
341 | u32 base_addr; | ||
342 | } match[] = { | ||
343 | {{0x05, 0x00, 0x00, 0x00}, 0x00005000}, | ||
344 | {{0x05, 0x00, 0x00, 0x01}, 0x00003000}, | ||
345 | {{0x05, 0x00, 0x00, 0x02}, 0x00003000}, | ||
346 | {{0x05, 0x00, 0x00, 0x03}, 0x00003000}, | ||
347 | {{0x05, 0x00, 0x00, 0x05}, 0x00003000} | ||
348 | }; | ||
349 | |||
350 | for (i = 0; i < ARRAY_SIZE(match); ++i) | ||
351 | if (bootinfo->hw_version[0] == match[i].version[0] && | ||
352 | bootinfo->hw_version[1] == match[i].version[1] && | ||
353 | bootinfo->hw_version[3] == match[i].version[3]) { | ||
354 | *base_addr = match[i].base_addr; | ||
355 | return 0; | ||
356 | } | ||
357 | |||
358 | return -EINVAL; | ||
359 | } | ||
360 | |||
361 | static inline bool | ||
362 | s3fwrn5_fw_is_custom(struct s3fwrn5_fw_cmd_get_bootinfo_rsp *bootinfo) | ||
363 | { | ||
364 | return !!bootinfo->hw_version[2]; | ||
365 | } | ||
366 | |||
367 | int s3fwrn5_fw_setup(struct s3fwrn5_fw_info *fw_info) | ||
368 | { | ||
369 | struct s3fwrn5_fw_cmd_get_bootinfo_rsp bootinfo; | ||
370 | int ret; | ||
371 | |||
372 | /* Get firmware data */ | ||
373 | |||
374 | ret = s3fwrn5_fw_request_firmware(fw_info); | ||
375 | if (ret < 0) { | ||
376 | dev_err(&fw_info->ndev->nfc_dev->dev, | ||
377 | "Failed to get fw file, ret=%02x\n", ret); | ||
378 | return ret; | ||
379 | } | ||
380 | |||
381 | /* Get bootloader info */ | ||
382 | |||
383 | ret = s3fwrn5_fw_get_bootinfo(fw_info, &bootinfo); | ||
384 | if (ret < 0) { | ||
385 | dev_err(&fw_info->ndev->nfc_dev->dev, | ||
386 | "Failed to get bootinfo, ret=%02x\n", ret); | ||
387 | goto err; | ||
388 | } | ||
389 | |||
390 | /* Match hardware version to obtain firmware base address */ | ||
391 | |||
392 | ret = s3fwrn5_fw_get_base_addr(&bootinfo, &fw_info->base_addr); | ||
393 | if (ret < 0) { | ||
394 | dev_err(&fw_info->ndev->nfc_dev->dev, | ||
395 | "Unknown hardware version\n"); | ||
396 | goto err; | ||
397 | } | ||
398 | |||
399 | fw_info->sector_size = bootinfo.sector_size; | ||
400 | |||
401 | fw_info->sig_size = s3fwrn5_fw_is_custom(&bootinfo) ? | ||
402 | fw_info->fw.custom_sig_size : fw_info->fw.sig_size; | ||
403 | fw_info->sig = s3fwrn5_fw_is_custom(&bootinfo) ? | ||
404 | fw_info->fw.custom_sig : fw_info->fw.sig; | ||
405 | |||
406 | return 0; | ||
407 | |||
408 | err: | ||
409 | s3fwrn5_fw_release_firmware(fw_info); | ||
410 | return ret; | ||
411 | } | ||
412 | |||
413 | bool s3fwrn5_fw_check_version(struct s3fwrn5_fw_info *fw_info, u32 version) | ||
414 | { | ||
415 | struct s3fwrn5_fw_version *new = (void *) &fw_info->fw.version; | ||
416 | struct s3fwrn5_fw_version *old = (void *) &version; | ||
417 | |||
418 | if (new->major > old->major) | ||
419 | return true; | ||
420 | if (new->build1 > old->build1) | ||
421 | return true; | ||
422 | if (new->build2 > old->build2) | ||
423 | return true; | ||
424 | |||
425 | return false; | ||
426 | } | ||
427 | |||
428 | int s3fwrn5_fw_download(struct s3fwrn5_fw_info *fw_info) | ||
429 | { | ||
430 | struct s3fwrn5_fw_image *fw = &fw_info->fw; | ||
431 | u8 hash_data[SHA1_DIGEST_SIZE]; | ||
432 | struct scatterlist sg; | ||
433 | struct hash_desc desc; | ||
434 | u32 image_size, off; | ||
435 | int ret; | ||
436 | |||
437 | image_size = fw_info->sector_size * fw->image_sectors; | ||
438 | |||
439 | /* Compute SHA of firmware data */ | ||
440 | |||
441 | sg_init_one(&sg, fw->image, image_size); | ||
442 | desc.tfm = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC); | ||
443 | crypto_hash_init(&desc); | ||
444 | crypto_hash_update(&desc, &sg, image_size); | ||
445 | crypto_hash_final(&desc, hash_data); | ||
446 | crypto_free_hash(desc.tfm); | ||
447 | |||
448 | /* Firmware update process */ | ||
449 | |||
450 | dev_info(&fw_info->ndev->nfc_dev->dev, | ||
451 | "Firmware update: %s\n", fw_info->fw_name); | ||
452 | |||
453 | ret = s3fwrn5_fw_enter_update_mode(fw_info, hash_data, | ||
454 | SHA1_DIGEST_SIZE, fw_info->sig, fw_info->sig_size); | ||
455 | if (ret < 0) { | ||
456 | dev_err(&fw_info->ndev->nfc_dev->dev, | ||
457 | "Unable to enter update mode\n"); | ||
458 | goto out; | ||
459 | } | ||
460 | |||
461 | for (off = 0; off < image_size; off += fw_info->sector_size) { | ||
462 | ret = s3fwrn5_fw_update_sector(fw_info, | ||
463 | fw_info->base_addr + off, fw->image + off); | ||
464 | if (ret < 0) { | ||
465 | dev_err(&fw_info->ndev->nfc_dev->dev, | ||
466 | "Firmware update error (code=%d)\n", ret); | ||
467 | goto out; | ||
468 | } | ||
469 | } | ||
470 | |||
471 | ret = s3fwrn5_fw_complete_update_mode(fw_info); | ||
472 | if (ret < 0) { | ||
473 | dev_err(&fw_info->ndev->nfc_dev->dev, | ||
474 | "Unable to complete update mode\n"); | ||
475 | goto out; | ||
476 | } | ||
477 | |||
478 | dev_info(&fw_info->ndev->nfc_dev->dev, | ||
479 | "Firmware update: success\n"); | ||
480 | |||
481 | out: | ||
482 | return ret; | ||
483 | } | ||
484 | |||
485 | void s3fwrn5_fw_init(struct s3fwrn5_fw_info *fw_info, const char *fw_name) | ||
486 | { | ||
487 | fw_info->parity = 0x00; | ||
488 | fw_info->rsp = NULL; | ||
489 | fw_info->fw.fw = NULL; | ||
490 | strcpy(fw_info->fw_name, fw_name); | ||
491 | init_completion(&fw_info->completion); | ||
492 | } | ||
493 | |||
494 | void s3fwrn5_fw_cleanup(struct s3fwrn5_fw_info *fw_info) | ||
495 | { | ||
496 | s3fwrn5_fw_release_firmware(fw_info); | ||
497 | } | ||
498 | |||
499 | int s3fwrn5_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb) | ||
500 | { | ||
501 | struct s3fwrn5_info *info = nci_get_drvdata(ndev); | ||
502 | struct s3fwrn5_fw_info *fw_info = &info->fw_info; | ||
503 | |||
504 | BUG_ON(fw_info->rsp); | ||
505 | |||
506 | fw_info->rsp = skb; | ||
507 | |||
508 | complete(&fw_info->completion); | ||
509 | |||
510 | return 0; | ||
511 | } | ||
diff --git a/drivers/nfc/s3fwrn5/firmware.h b/drivers/nfc/s3fwrn5/firmware.h new file mode 100644 index 000000000000..1ec0647ab917 --- /dev/null +++ b/drivers/nfc/s3fwrn5/firmware.h | |||
@@ -0,0 +1,111 @@ | |||
1 | /* | ||
2 | * NCI based driver for Samsung S3FWRN5 NFC chip | ||
3 | * | ||
4 | * Copyright (C) 2015 Samsung Electrnoics | ||
5 | * Robert Baldyga <r.baldyga@samsung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms and conditions of the GNU General Public License, | ||
9 | * version 2 or later, as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #ifndef __LOCAL_S3FWRN5_FIRMWARE_H_ | ||
21 | #define __LOCAL_S3FWRN5_FIRMWARE_H_ | ||
22 | |||
23 | /* FW Message Types */ | ||
24 | #define S3FWRN5_FW_MSG_CMD 0x00 | ||
25 | #define S3FWRN5_FW_MSG_RSP 0x01 | ||
26 | #define S3FWRN5_FW_MSG_DATA 0x02 | ||
27 | |||
28 | /* FW Return Codes */ | ||
29 | #define S3FWRN5_FW_RET_SUCCESS 0x00 | ||
30 | #define S3FWRN5_FW_RET_MESSAGE_TYPE_INVALID 0x01 | ||
31 | #define S3FWRN5_FW_RET_COMMAND_INVALID 0x02 | ||
32 | #define S3FWRN5_FW_RET_PAGE_DATA_OVERFLOW 0x03 | ||
33 | #define S3FWRN5_FW_RET_SECT_DATA_OVERFLOW 0x04 | ||
34 | #define S3FWRN5_FW_RET_AUTHENTICATION_FAIL 0x05 | ||
35 | #define S3FWRN5_FW_RET_FLASH_OPERATION_FAIL 0x06 | ||
36 | #define S3FWRN5_FW_RET_ADDRESS_OUT_OF_RANGE 0x07 | ||
37 | #define S3FWRN5_FW_RET_PARAMETER_INVALID 0x08 | ||
38 | |||
39 | /* ---- FW Packet structures ---- */ | ||
40 | #define S3FWRN5_FW_HDR_SIZE 4 | ||
41 | |||
42 | struct s3fwrn5_fw_header { | ||
43 | __u8 type; | ||
44 | __u8 code; | ||
45 | __u16 len; | ||
46 | }; | ||
47 | |||
48 | #define S3FWRN5_FW_CMD_RESET 0x00 | ||
49 | |||
50 | #define S3FWRN5_FW_CMD_GET_BOOTINFO 0x01 | ||
51 | |||
52 | struct s3fwrn5_fw_cmd_get_bootinfo_rsp { | ||
53 | __u8 hw_version[4]; | ||
54 | __u16 sector_size; | ||
55 | __u16 page_size; | ||
56 | __u16 frame_max_size; | ||
57 | __u16 hw_buffer_size; | ||
58 | }; | ||
59 | |||
60 | #define S3FWRN5_FW_CMD_ENTER_UPDATE_MODE 0x02 | ||
61 | |||
62 | struct s3fwrn5_fw_cmd_enter_updatemode { | ||
63 | __u16 hashcode_size; | ||
64 | __u16 signature_size; | ||
65 | }; | ||
66 | |||
67 | #define S3FWRN5_FW_CMD_UPDATE_SECTOR 0x04 | ||
68 | |||
69 | struct s3fwrn5_fw_cmd_update_sector { | ||
70 | __u32 base_address; | ||
71 | }; | ||
72 | |||
73 | #define S3FWRN5_FW_CMD_COMPLETE_UPDATE_MODE 0x05 | ||
74 | |||
75 | struct s3fwrn5_fw_image { | ||
76 | const struct firmware *fw; | ||
77 | |||
78 | char date[13]; | ||
79 | u32 version; | ||
80 | const void *sig; | ||
81 | u32 sig_size; | ||
82 | const void *image; | ||
83 | u32 image_sectors; | ||
84 | const void *custom_sig; | ||
85 | u32 custom_sig_size; | ||
86 | }; | ||
87 | |||
88 | struct s3fwrn5_fw_info { | ||
89 | struct nci_dev *ndev; | ||
90 | struct s3fwrn5_fw_image fw; | ||
91 | char fw_name[NFC_FIRMWARE_NAME_MAXSIZE + 1]; | ||
92 | |||
93 | const void *sig; | ||
94 | u32 sig_size; | ||
95 | u32 sector_size; | ||
96 | u32 base_addr; | ||
97 | |||
98 | struct completion completion; | ||
99 | struct sk_buff *rsp; | ||
100 | char parity; | ||
101 | }; | ||
102 | |||
103 | void s3fwrn5_fw_init(struct s3fwrn5_fw_info *fw_info, const char *fw_name); | ||
104 | int s3fwrn5_fw_setup(struct s3fwrn5_fw_info *fw_info); | ||
105 | bool s3fwrn5_fw_check_version(struct s3fwrn5_fw_info *fw_info, u32 version); | ||
106 | int s3fwrn5_fw_download(struct s3fwrn5_fw_info *fw_info); | ||
107 | void s3fwrn5_fw_cleanup(struct s3fwrn5_fw_info *fw_info); | ||
108 | |||
109 | int s3fwrn5_fw_recv_frame(struct nci_dev *ndev, struct sk_buff *skb); | ||
110 | |||
111 | #endif /* __LOCAL_S3FWRN5_FIRMWARE_H_ */ | ||
diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c new file mode 100644 index 000000000000..b4dd7dd47473 --- /dev/null +++ b/drivers/nfc/s3fwrn5/i2c.c | |||
@@ -0,0 +1,306 @@ | |||
1 | /* | ||
2 | * I2C Link Layer for Samsung S3FWRN5 NCI based Driver | ||
3 | * | ||
4 | * Copyright (C) 2015 Samsung Electrnoics | ||
5 | * Robert Baldyga <r.baldyga@samsung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms and conditions of the GNU General Public License, | ||
9 | * version 2 or later, as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #include <linux/i2c.h> | ||
21 | #include <linux/gpio.h> | ||
22 | #include <linux/delay.h> | ||
23 | #include <linux/of_gpio.h> | ||
24 | #include <linux/of_irq.h> | ||
25 | #include <linux/module.h> | ||
26 | |||
27 | #include <net/nfc/nfc.h> | ||
28 | |||
29 | #include "s3fwrn5.h" | ||
30 | |||
31 | #define S3FWRN5_I2C_DRIVER_NAME "s3fwrn5_i2c" | ||
32 | |||
33 | #define S3FWRN5_I2C_MAX_PAYLOAD 32 | ||
34 | #define S3FWRN5_EN_WAIT_TIME 150 | ||
35 | |||
36 | struct s3fwrn5_i2c_phy { | ||
37 | struct i2c_client *i2c_dev; | ||
38 | struct nci_dev *ndev; | ||
39 | |||
40 | unsigned int gpio_en; | ||
41 | unsigned int gpio_fw_wake; | ||
42 | |||
43 | struct mutex mutex; | ||
44 | |||
45 | enum s3fwrn5_mode mode; | ||
46 | unsigned int irq_skip:1; | ||
47 | }; | ||
48 | |||
49 | static void s3fwrn5_i2c_set_wake(void *phy_id, bool wake) | ||
50 | { | ||
51 | struct s3fwrn5_i2c_phy *phy = phy_id; | ||
52 | |||
53 | mutex_lock(&phy->mutex); | ||
54 | gpio_set_value(phy->gpio_fw_wake, wake); | ||
55 | msleep(S3FWRN5_EN_WAIT_TIME/2); | ||
56 | mutex_unlock(&phy->mutex); | ||
57 | } | ||
58 | |||
59 | static void s3fwrn5_i2c_set_mode(void *phy_id, enum s3fwrn5_mode mode) | ||
60 | { | ||
61 | struct s3fwrn5_i2c_phy *phy = phy_id; | ||
62 | |||
63 | mutex_lock(&phy->mutex); | ||
64 | |||
65 | if (phy->mode == mode) | ||
66 | goto out; | ||
67 | |||
68 | phy->mode = mode; | ||
69 | |||
70 | gpio_set_value(phy->gpio_en, 1); | ||
71 | gpio_set_value(phy->gpio_fw_wake, 0); | ||
72 | if (mode == S3FWRN5_MODE_FW) | ||
73 | gpio_set_value(phy->gpio_fw_wake, 1); | ||
74 | |||
75 | if (mode != S3FWRN5_MODE_COLD) { | ||
76 | msleep(S3FWRN5_EN_WAIT_TIME); | ||
77 | gpio_set_value(phy->gpio_en, 0); | ||
78 | msleep(S3FWRN5_EN_WAIT_TIME/2); | ||
79 | } | ||
80 | |||
81 | phy->irq_skip = true; | ||
82 | |||
83 | out: | ||
84 | mutex_unlock(&phy->mutex); | ||
85 | } | ||
86 | |||
87 | static enum s3fwrn5_mode s3fwrn5_i2c_get_mode(void *phy_id) | ||
88 | { | ||
89 | struct s3fwrn5_i2c_phy *phy = phy_id; | ||
90 | enum s3fwrn5_mode mode; | ||
91 | |||
92 | mutex_lock(&phy->mutex); | ||
93 | |||
94 | mode = phy->mode; | ||
95 | |||
96 | mutex_unlock(&phy->mutex); | ||
97 | |||
98 | return mode; | ||
99 | } | ||
100 | |||
101 | static int s3fwrn5_i2c_write(void *phy_id, struct sk_buff *skb) | ||
102 | { | ||
103 | struct s3fwrn5_i2c_phy *phy = phy_id; | ||
104 | int ret; | ||
105 | |||
106 | mutex_lock(&phy->mutex); | ||
107 | |||
108 | phy->irq_skip = false; | ||
109 | |||
110 | ret = i2c_master_send(phy->i2c_dev, skb->data, skb->len); | ||
111 | if (ret == -EREMOTEIO) { | ||
112 | /* Retry, chip was in standby */ | ||
113 | usleep_range(110000, 120000); | ||
114 | ret = i2c_master_send(phy->i2c_dev, skb->data, skb->len); | ||
115 | } | ||
116 | |||
117 | mutex_unlock(&phy->mutex); | ||
118 | |||
119 | if (ret < 0) | ||
120 | return ret; | ||
121 | |||
122 | if (ret != skb->len) | ||
123 | return -EREMOTEIO; | ||
124 | |||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static struct s3fwrn5_phy_ops i2c_phy_ops = { | ||
129 | .set_wake = s3fwrn5_i2c_set_wake, | ||
130 | .set_mode = s3fwrn5_i2c_set_mode, | ||
131 | .get_mode = s3fwrn5_i2c_get_mode, | ||
132 | .write = s3fwrn5_i2c_write, | ||
133 | }; | ||
134 | |||
135 | static int s3fwrn5_i2c_read(struct s3fwrn5_i2c_phy *phy) | ||
136 | { | ||
137 | struct sk_buff *skb; | ||
138 | size_t hdr_size; | ||
139 | size_t data_len; | ||
140 | char hdr[4]; | ||
141 | int ret; | ||
142 | |||
143 | hdr_size = (phy->mode == S3FWRN5_MODE_NCI) ? | ||
144 | NCI_CTRL_HDR_SIZE : S3FWRN5_FW_HDR_SIZE; | ||
145 | ret = i2c_master_recv(phy->i2c_dev, hdr, hdr_size); | ||
146 | if (ret < 0) | ||
147 | return ret; | ||
148 | |||
149 | if (ret < hdr_size) | ||
150 | return -EBADMSG; | ||
151 | |||
152 | data_len = (phy->mode == S3FWRN5_MODE_NCI) ? | ||
153 | ((struct nci_ctrl_hdr *)hdr)->plen : | ||
154 | ((struct s3fwrn5_fw_header *)hdr)->len; | ||
155 | |||
156 | skb = alloc_skb(hdr_size + data_len, GFP_KERNEL); | ||
157 | if (!skb) | ||
158 | return -ENOMEM; | ||
159 | |||
160 | memcpy(skb_put(skb, hdr_size), hdr, hdr_size); | ||
161 | |||
162 | if (data_len == 0) | ||
163 | goto out; | ||
164 | |||
165 | ret = i2c_master_recv(phy->i2c_dev, skb_put(skb, data_len), data_len); | ||
166 | if (ret != data_len) { | ||
167 | kfree_skb(skb); | ||
168 | return -EBADMSG; | ||
169 | } | ||
170 | |||
171 | out: | ||
172 | return s3fwrn5_recv_frame(phy->ndev, skb, phy->mode); | ||
173 | } | ||
174 | |||
175 | static irqreturn_t s3fwrn5_i2c_irq_thread_fn(int irq, void *phy_id) | ||
176 | { | ||
177 | struct s3fwrn5_i2c_phy *phy = phy_id; | ||
178 | int ret = 0; | ||
179 | |||
180 | if (!phy || !phy->ndev) { | ||
181 | WARN_ON_ONCE(1); | ||
182 | return IRQ_NONE; | ||
183 | } | ||
184 | |||
185 | mutex_lock(&phy->mutex); | ||
186 | |||
187 | if (phy->irq_skip) | ||
188 | goto out; | ||
189 | |||
190 | switch (phy->mode) { | ||
191 | case S3FWRN5_MODE_NCI: | ||
192 | case S3FWRN5_MODE_FW: | ||
193 | ret = s3fwrn5_i2c_read(phy); | ||
194 | break; | ||
195 | case S3FWRN5_MODE_COLD: | ||
196 | ret = -EREMOTEIO; | ||
197 | break; | ||
198 | } | ||
199 | |||
200 | out: | ||
201 | mutex_unlock(&phy->mutex); | ||
202 | |||
203 | return IRQ_HANDLED; | ||
204 | } | ||
205 | |||
206 | static int s3fwrn5_i2c_parse_dt(struct i2c_client *client) | ||
207 | { | ||
208 | struct s3fwrn5_i2c_phy *phy = i2c_get_clientdata(client); | ||
209 | struct device_node *np = client->dev.of_node; | ||
210 | |||
211 | if (!np) | ||
212 | return -ENODEV; | ||
213 | |||
214 | phy->gpio_en = of_get_named_gpio(np, "s3fwrn5,en-gpios", 0); | ||
215 | if (!gpio_is_valid(phy->gpio_en)) | ||
216 | return -ENODEV; | ||
217 | |||
218 | phy->gpio_fw_wake = of_get_named_gpio(np, "s3fwrn5,fw-gpios", 0); | ||
219 | if (!gpio_is_valid(phy->gpio_fw_wake)) | ||
220 | return -ENODEV; | ||
221 | |||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | static int s3fwrn5_i2c_probe(struct i2c_client *client, | ||
226 | const struct i2c_device_id *id) | ||
227 | { | ||
228 | struct s3fwrn5_i2c_phy *phy; | ||
229 | int ret; | ||
230 | |||
231 | phy = devm_kzalloc(&client->dev, sizeof(*phy), GFP_KERNEL); | ||
232 | if (!phy) | ||
233 | return -ENOMEM; | ||
234 | |||
235 | mutex_init(&phy->mutex); | ||
236 | phy->mode = S3FWRN5_MODE_COLD; | ||
237 | phy->irq_skip = true; | ||
238 | |||
239 | phy->i2c_dev = client; | ||
240 | i2c_set_clientdata(client, phy); | ||
241 | |||
242 | ret = s3fwrn5_i2c_parse_dt(client); | ||
243 | if (ret < 0) | ||
244 | return ret; | ||
245 | |||
246 | ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->gpio_en, | ||
247 | GPIOF_OUT_INIT_HIGH, "s3fwrn5_en"); | ||
248 | if (ret < 0) | ||
249 | return ret; | ||
250 | |||
251 | ret = devm_gpio_request_one(&phy->i2c_dev->dev, phy->gpio_fw_wake, | ||
252 | GPIOF_OUT_INIT_LOW, "s3fwrn5_fw_wake"); | ||
253 | if (ret < 0) | ||
254 | return ret; | ||
255 | |||
256 | ret = s3fwrn5_probe(&phy->ndev, phy, &phy->i2c_dev->dev, &i2c_phy_ops, | ||
257 | S3FWRN5_I2C_MAX_PAYLOAD); | ||
258 | if (ret < 0) | ||
259 | return ret; | ||
260 | |||
261 | ret = request_threaded_irq(phy->i2c_dev->irq, NULL, | ||
262 | s3fwrn5_i2c_irq_thread_fn, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, | ||
263 | S3FWRN5_I2C_DRIVER_NAME, phy); | ||
264 | if (ret) | ||
265 | s3fwrn5_remove(phy->ndev); | ||
266 | |||
267 | return ret; | ||
268 | } | ||
269 | |||
270 | static int s3fwrn5_i2c_remove(struct i2c_client *client) | ||
271 | { | ||
272 | struct s3fwrn5_i2c_phy *phy = i2c_get_clientdata(client); | ||
273 | |||
274 | s3fwrn5_remove(phy->ndev); | ||
275 | |||
276 | return 0; | ||
277 | } | ||
278 | |||
279 | static struct i2c_device_id s3fwrn5_i2c_id_table[] = { | ||
280 | {S3FWRN5_I2C_DRIVER_NAME, 0}, | ||
281 | {} | ||
282 | }; | ||
283 | MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table); | ||
284 | |||
285 | static const struct of_device_id of_s3fwrn5_i2c_match[] = { | ||
286 | { .compatible = "samsung,s3fwrn5-i2c", }, | ||
287 | {} | ||
288 | }; | ||
289 | MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match); | ||
290 | |||
291 | static struct i2c_driver s3fwrn5_i2c_driver = { | ||
292 | .driver = { | ||
293 | .owner = THIS_MODULE, | ||
294 | .name = S3FWRN5_I2C_DRIVER_NAME, | ||
295 | .of_match_table = of_match_ptr(of_s3fwrn5_i2c_match), | ||
296 | }, | ||
297 | .probe = s3fwrn5_i2c_probe, | ||
298 | .remove = s3fwrn5_i2c_remove, | ||
299 | .id_table = s3fwrn5_i2c_id_table, | ||
300 | }; | ||
301 | |||
302 | module_i2c_driver(s3fwrn5_i2c_driver); | ||
303 | |||
304 | MODULE_LICENSE("GPL"); | ||
305 | MODULE_DESCRIPTION("I2C driver for Samsung S3FWRN5"); | ||
306 | MODULE_AUTHOR("Robert Baldyga <r.baldyga@samsung.com>"); | ||
diff --git a/drivers/nfc/s3fwrn5/nci.c b/drivers/nfc/s3fwrn5/nci.c new file mode 100644 index 000000000000..ace0071c5339 --- /dev/null +++ b/drivers/nfc/s3fwrn5/nci.c | |||
@@ -0,0 +1,165 @@ | |||
1 | /* | ||
2 | * NCI based driver for Samsung S3FWRN5 NFC chip | ||
3 | * | ||
4 | * Copyright (C) 2015 Samsung Electrnoics | ||
5 | * Robert Baldyga <r.baldyga@samsung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms and conditions of the GNU General Public License, | ||
9 | * version 2 or later, as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #include <linux/completion.h> | ||
21 | #include <linux/firmware.h> | ||
22 | |||
23 | #include "s3fwrn5.h" | ||
24 | #include "nci.h" | ||
25 | |||
26 | static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb) | ||
27 | { | ||
28 | __u8 status = skb->data[0]; | ||
29 | |||
30 | nci_req_complete(ndev, status); | ||
31 | return 0; | ||
32 | } | ||
33 | |||
34 | static struct nci_prop_ops s3fwrn5_nci_prop_ops[] = { | ||
35 | { | ||
36 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | ||
37 | NCI_PROP_AGAIN), | ||
38 | .rsp = s3fwrn5_nci_prop_rsp, | ||
39 | }, | ||
40 | { | ||
41 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | ||
42 | NCI_PROP_GET_RFREG), | ||
43 | .rsp = s3fwrn5_nci_prop_rsp, | ||
44 | }, | ||
45 | { | ||
46 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | ||
47 | NCI_PROP_SET_RFREG), | ||
48 | .rsp = s3fwrn5_nci_prop_rsp, | ||
49 | }, | ||
50 | { | ||
51 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | ||
52 | NCI_PROP_GET_RFREG_VER), | ||
53 | .rsp = s3fwrn5_nci_prop_rsp, | ||
54 | }, | ||
55 | { | ||
56 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | ||
57 | NCI_PROP_SET_RFREG_VER), | ||
58 | .rsp = s3fwrn5_nci_prop_rsp, | ||
59 | }, | ||
60 | { | ||
61 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | ||
62 | NCI_PROP_START_RFREG), | ||
63 | .rsp = s3fwrn5_nci_prop_rsp, | ||
64 | }, | ||
65 | { | ||
66 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | ||
67 | NCI_PROP_STOP_RFREG), | ||
68 | .rsp = s3fwrn5_nci_prop_rsp, | ||
69 | }, | ||
70 | { | ||
71 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | ||
72 | NCI_PROP_FW_CFG), | ||
73 | .rsp = s3fwrn5_nci_prop_rsp, | ||
74 | }, | ||
75 | { | ||
76 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, | ||
77 | NCI_PROP_WR_RESET), | ||
78 | .rsp = s3fwrn5_nci_prop_rsp, | ||
79 | }, | ||
80 | }; | ||
81 | |||
82 | void s3fwrn5_nci_get_prop_ops(struct nci_prop_ops **ops, size_t *n) | ||
83 | { | ||
84 | *ops = s3fwrn5_nci_prop_ops; | ||
85 | *n = ARRAY_SIZE(s3fwrn5_nci_prop_ops); | ||
86 | } | ||
87 | |||
88 | #define S3FWRN5_RFREG_SECTION_SIZE 252 | ||
89 | |||
90 | int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name) | ||
91 | { | ||
92 | const struct firmware *fw; | ||
93 | struct nci_prop_fw_cfg_cmd fw_cfg; | ||
94 | struct nci_prop_set_rfreg_cmd set_rfreg; | ||
95 | struct nci_prop_stop_rfreg_cmd stop_rfreg; | ||
96 | u32 checksum; | ||
97 | int i, len; | ||
98 | int ret; | ||
99 | |||
100 | ret = request_firmware(&fw, fw_name, &info->ndev->nfc_dev->dev); | ||
101 | if (ret < 0) | ||
102 | return ret; | ||
103 | |||
104 | /* Compute rfreg checksum */ | ||
105 | |||
106 | checksum = 0; | ||
107 | for (i = 0; i < fw->size; i += 4) | ||
108 | checksum += *((u32 *)(fw->data+i)); | ||
109 | |||
110 | /* Set default clock configuration for external crystal */ | ||
111 | |||
112 | fw_cfg.clk_type = 0x01; | ||
113 | fw_cfg.clk_speed = 0xff; | ||
114 | fw_cfg.clk_req = 0xff; | ||
115 | ret = nci_prop_cmd(info->ndev, NCI_PROP_FW_CFG, | ||
116 | sizeof(fw_cfg), (__u8 *)&fw_cfg); | ||
117 | if (ret < 0) | ||
118 | goto out; | ||
119 | |||
120 | /* Start rfreg configuration */ | ||
121 | |||
122 | dev_info(&info->ndev->nfc_dev->dev, | ||
123 | "rfreg configuration update: %s\n", fw_name); | ||
124 | |||
125 | ret = nci_prop_cmd(info->ndev, NCI_PROP_START_RFREG, 0, NULL); | ||
126 | if (ret < 0) { | ||
127 | dev_err(&info->ndev->nfc_dev->dev, | ||
128 | "Unable to start rfreg update\n"); | ||
129 | goto out; | ||
130 | } | ||
131 | |||
132 | /* Update rfreg */ | ||
133 | |||
134 | set_rfreg.index = 0; | ||
135 | for (i = 0; i < fw->size; i += S3FWRN5_RFREG_SECTION_SIZE) { | ||
136 | len = (fw->size - i < S3FWRN5_RFREG_SECTION_SIZE) ? | ||
137 | (fw->size - i) : S3FWRN5_RFREG_SECTION_SIZE; | ||
138 | memcpy(set_rfreg.data, fw->data+i, len); | ||
139 | ret = nci_prop_cmd(info->ndev, NCI_PROP_SET_RFREG, | ||
140 | len+1, (__u8 *)&set_rfreg); | ||
141 | if (ret < 0) { | ||
142 | dev_err(&info->ndev->nfc_dev->dev, | ||
143 | "rfreg update error (code=%d)\n", ret); | ||
144 | goto out; | ||
145 | } | ||
146 | set_rfreg.index++; | ||
147 | } | ||
148 | |||
149 | /* Finish rfreg configuration */ | ||
150 | |||
151 | stop_rfreg.checksum = checksum & 0xffff; | ||
152 | ret = nci_prop_cmd(info->ndev, NCI_PROP_STOP_RFREG, | ||
153 | sizeof(stop_rfreg), (__u8 *)&stop_rfreg); | ||
154 | if (ret < 0) { | ||
155 | dev_err(&info->ndev->nfc_dev->dev, | ||
156 | "Unable to stop rfreg update\n"); | ||
157 | goto out; | ||
158 | } | ||
159 | |||
160 | dev_info(&info->ndev->nfc_dev->dev, | ||
161 | "rfreg configuration update: success\n"); | ||
162 | out: | ||
163 | release_firmware(fw); | ||
164 | return ret; | ||
165 | } | ||
diff --git a/drivers/nfc/s3fwrn5/nci.h b/drivers/nfc/s3fwrn5/nci.h new file mode 100644 index 000000000000..0e68d439dde6 --- /dev/null +++ b/drivers/nfc/s3fwrn5/nci.h | |||
@@ -0,0 +1,89 @@ | |||
1 | /* | ||
2 | * NCI based driver for Samsung S3FWRN5 NFC chip | ||
3 | * | ||
4 | * Copyright (C) 2015 Samsung Electrnoics | ||
5 | * Robert Baldyga <r.baldyga@samsung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms and conditions of the GNU General Public License, | ||
9 | * version 2 or later, as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #ifndef __LOCAL_S3FWRN5_NCI_H_ | ||
21 | #define __LOCAL_S3FWRN5_NCI_H_ | ||
22 | |||
23 | #include "s3fwrn5.h" | ||
24 | |||
25 | #define NCI_PROP_AGAIN 0x01 | ||
26 | |||
27 | #define NCI_PROP_GET_RFREG 0x21 | ||
28 | #define NCI_PROP_SET_RFREG 0x22 | ||
29 | |||
30 | struct nci_prop_set_rfreg_cmd { | ||
31 | __u8 index; | ||
32 | __u8 data[252]; | ||
33 | }; | ||
34 | |||
35 | struct nci_prop_set_rfreg_rsp { | ||
36 | __u8 status; | ||
37 | }; | ||
38 | |||
39 | #define NCI_PROP_GET_RFREG_VER 0x24 | ||
40 | |||
41 | struct nci_prop_get_rfreg_ver_rsp { | ||
42 | __u8 status; | ||
43 | __u8 data[8]; | ||
44 | }; | ||
45 | |||
46 | #define NCI_PROP_SET_RFREG_VER 0x25 | ||
47 | |||
48 | struct nci_prop_set_rfreg_ver_cmd { | ||
49 | __u8 data[8]; | ||
50 | }; | ||
51 | |||
52 | struct nci_prop_set_rfreg_ver_rsp { | ||
53 | __u8 status; | ||
54 | }; | ||
55 | |||
56 | #define NCI_PROP_START_RFREG 0x26 | ||
57 | |||
58 | struct nci_prop_start_rfreg_rsp { | ||
59 | __u8 status; | ||
60 | }; | ||
61 | |||
62 | #define NCI_PROP_STOP_RFREG 0x27 | ||
63 | |||
64 | struct nci_prop_stop_rfreg_cmd { | ||
65 | __u16 checksum; | ||
66 | }; | ||
67 | |||
68 | struct nci_prop_stop_rfreg_rsp { | ||
69 | __u8 status; | ||
70 | }; | ||
71 | |||
72 | #define NCI_PROP_FW_CFG 0x28 | ||
73 | |||
74 | struct nci_prop_fw_cfg_cmd { | ||
75 | __u8 clk_type; | ||
76 | __u8 clk_speed; | ||
77 | __u8 clk_req; | ||
78 | }; | ||
79 | |||
80 | struct nci_prop_fw_cfg_rsp { | ||
81 | __u8 status; | ||
82 | }; | ||
83 | |||
84 | #define NCI_PROP_WR_RESET 0x2f | ||
85 | |||
86 | void s3fwrn5_nci_get_prop_ops(struct nci_prop_ops **ops, size_t *n); | ||
87 | int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name); | ||
88 | |||
89 | #endif /* __LOCAL_S3FWRN5_NCI_H_ */ | ||
diff --git a/drivers/nfc/s3fwrn5/s3fwrn5.h b/drivers/nfc/s3fwrn5/s3fwrn5.h new file mode 100644 index 000000000000..89210d4828b8 --- /dev/null +++ b/drivers/nfc/s3fwrn5/s3fwrn5.h | |||
@@ -0,0 +1,99 @@ | |||
1 | /* | ||
2 | * NCI based driver for Samsung S3FWRN5 NFC chip | ||
3 | * | ||
4 | * Copyright (C) 2015 Samsung Electrnoics | ||
5 | * Robert Baldyga <r.baldyga@samsung.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms and conditions of the GNU General Public License, | ||
9 | * version 2 or later, as published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #ifndef __LOCAL_S3FWRN5_H_ | ||
21 | #define __LOCAL_S3FWRN5_H_ | ||
22 | |||
23 | #include <linux/nfc.h> | ||
24 | |||
25 | #include <net/nfc/nci_core.h> | ||
26 | |||
27 | #include "firmware.h" | ||
28 | |||
29 | enum s3fwrn5_mode { | ||
30 | S3FWRN5_MODE_COLD, | ||
31 | S3FWRN5_MODE_NCI, | ||
32 | S3FWRN5_MODE_FW, | ||
33 | }; | ||
34 | |||
35 | struct s3fwrn5_phy_ops { | ||
36 | void (*set_wake)(void *id, bool sleep); | ||
37 | void (*set_mode)(void *id, enum s3fwrn5_mode); | ||
38 | enum s3fwrn5_mode (*get_mode)(void *id); | ||
39 | int (*write)(void *id, struct sk_buff *skb); | ||
40 | }; | ||
41 | |||
42 | struct s3fwrn5_info { | ||
43 | struct nci_dev *ndev; | ||
44 | void *phy_id; | ||
45 | struct device *pdev; | ||
46 | |||
47 | struct s3fwrn5_phy_ops *phy_ops; | ||
48 | unsigned int max_payload; | ||
49 | |||
50 | struct s3fwrn5_fw_info fw_info; | ||
51 | |||
52 | struct mutex mutex; | ||
53 | }; | ||
54 | |||
55 | static inline int s3fwrn5_set_mode(struct s3fwrn5_info *info, | ||
56 | enum s3fwrn5_mode mode) | ||
57 | { | ||
58 | if (!info->phy_ops->set_mode) | ||
59 | return -ENOTSUPP; | ||
60 | |||
61 | info->phy_ops->set_mode(info->phy_id, mode); | ||
62 | |||
63 | return 0; | ||
64 | } | ||
65 | |||
66 | static inline enum s3fwrn5_mode s3fwrn5_get_mode(struct s3fwrn5_info *info) | ||
67 | { | ||
68 | if (!info->phy_ops->get_mode) | ||
69 | return -ENOTSUPP; | ||
70 | |||
71 | return info->phy_ops->get_mode(info->phy_id); | ||
72 | } | ||
73 | |||
74 | static inline int s3fwrn5_set_wake(struct s3fwrn5_info *info, bool wake) | ||
75 | { | ||
76 | if (!info->phy_ops->set_wake) | ||
77 | return -ENOTSUPP; | ||
78 | |||
79 | info->phy_ops->set_wake(info->phy_id, wake); | ||
80 | |||
81 | return 0; | ||
82 | } | ||
83 | |||
84 | static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb) | ||
85 | { | ||
86 | if (!info->phy_ops->write) | ||
87 | return -ENOTSUPP; | ||
88 | |||
89 | return info->phy_ops->write(info->phy_id, skb); | ||
90 | } | ||
91 | |||
92 | int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev, | ||
93 | struct s3fwrn5_phy_ops *phy_ops, unsigned int max_payload); | ||
94 | void s3fwrn5_remove(struct nci_dev *ndev); | ||
95 | |||
96 | int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb, | ||
97 | enum s3fwrn5_mode mode); | ||
98 | |||
99 | #endif /* __LOCAL_S3FWRN5_H_ */ | ||