1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * NCI based driver for Samsung S3FWRN5 NFC chip |
4 | * |
5 | * Copyright (C) 2015 Samsung Electrnoics |
6 | * Robert Baldyga <r.baldyga@samsung.com> |
7 | */ |
8 | |
9 | #include <linux/completion.h> |
10 | #include <linux/firmware.h> |
11 | |
12 | #include "s3fwrn5.h" |
13 | #include "nci.h" |
14 | |
15 | static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb) |
16 | { |
17 | __u8 status = skb->data[0]; |
18 | |
19 | nci_req_complete(ndev, result: status); |
20 | return 0; |
21 | } |
22 | |
23 | const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = { |
24 | { |
25 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, |
26 | NCI_PROP_SET_RFREG), |
27 | .rsp = s3fwrn5_nci_prop_rsp, |
28 | }, |
29 | { |
30 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, |
31 | NCI_PROP_START_RFREG), |
32 | .rsp = s3fwrn5_nci_prop_rsp, |
33 | }, |
34 | { |
35 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, |
36 | NCI_PROP_STOP_RFREG), |
37 | .rsp = s3fwrn5_nci_prop_rsp, |
38 | }, |
39 | { |
40 | .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY, |
41 | NCI_PROP_FW_CFG), |
42 | .rsp = s3fwrn5_nci_prop_rsp, |
43 | }, |
44 | }; |
45 | |
46 | #define S3FWRN5_RFREG_SECTION_SIZE 252 |
47 | |
48 | int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name) |
49 | { |
50 | struct device *dev = &info->ndev->nfc_dev->dev; |
51 | const struct firmware *fw; |
52 | struct nci_prop_fw_cfg_cmd fw_cfg; |
53 | struct nci_prop_set_rfreg_cmd set_rfreg; |
54 | struct nci_prop_stop_rfreg_cmd stop_rfreg; |
55 | u32 checksum; |
56 | int i, len; |
57 | int ret; |
58 | |
59 | ret = request_firmware(fw: &fw, name: fw_name, device: dev); |
60 | if (ret < 0) |
61 | return ret; |
62 | |
63 | /* Compute rfreg checksum */ |
64 | |
65 | checksum = 0; |
66 | for (i = 0; i < fw->size; i += 4) |
67 | checksum += *((u32 *)(fw->data+i)); |
68 | |
69 | /* Set default clock configuration for external crystal */ |
70 | |
71 | fw_cfg.clk_type = 0x01; |
72 | fw_cfg.clk_speed = 0xff; |
73 | fw_cfg.clk_req = 0xff; |
74 | ret = nci_prop_cmd(ndev: info->ndev, NCI_PROP_FW_CFG, |
75 | len: sizeof(fw_cfg), payload: (__u8 *)&fw_cfg); |
76 | if (ret < 0) |
77 | goto out; |
78 | |
79 | /* Start rfreg configuration */ |
80 | |
81 | dev_info(dev, "rfreg configuration update: %s\n" , fw_name); |
82 | |
83 | ret = nci_prop_cmd(ndev: info->ndev, NCI_PROP_START_RFREG, len: 0, NULL); |
84 | if (ret < 0) { |
85 | dev_err(dev, "Unable to start rfreg update\n" ); |
86 | goto out; |
87 | } |
88 | |
89 | /* Update rfreg */ |
90 | |
91 | set_rfreg.index = 0; |
92 | for (i = 0; i < fw->size; i += S3FWRN5_RFREG_SECTION_SIZE) { |
93 | len = (fw->size - i < S3FWRN5_RFREG_SECTION_SIZE) ? |
94 | (fw->size - i) : S3FWRN5_RFREG_SECTION_SIZE; |
95 | memcpy(set_rfreg.data, fw->data+i, len); |
96 | ret = nci_prop_cmd(ndev: info->ndev, NCI_PROP_SET_RFREG, |
97 | len: len+1, payload: (__u8 *)&set_rfreg); |
98 | if (ret < 0) { |
99 | dev_err(dev, "rfreg update error (code=%d)\n" , ret); |
100 | goto out; |
101 | } |
102 | set_rfreg.index++; |
103 | } |
104 | |
105 | /* Finish rfreg configuration */ |
106 | |
107 | stop_rfreg.checksum = checksum & 0xffff; |
108 | ret = nci_prop_cmd(ndev: info->ndev, NCI_PROP_STOP_RFREG, |
109 | len: sizeof(stop_rfreg), payload: (__u8 *)&stop_rfreg); |
110 | if (ret < 0) { |
111 | dev_err(dev, "Unable to stop rfreg update\n" ); |
112 | goto out; |
113 | } |
114 | |
115 | dev_info(dev, "rfreg configuration update: success\n" ); |
116 | out: |
117 | release_firmware(fw); |
118 | return ret; |
119 | } |
120 | |