Commit | Line | Data |
---|---|---|
c04c674f RB |
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 | ||
22e4bd09 | 34 | static struct nci_driver_ops s3fwrn5_nci_prop_ops[] = { |
c04c674f RB |
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 | ||
22e4bd09 | 82 | void s3fwrn5_nci_get_prop_ops(struct nci_driver_ops **ops, size_t *n) |
c04c674f RB |
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 | } |