diff options
Diffstat (limited to 'drivers/nfc/mei_phy.c')
-rw-r--r-- | drivers/nfc/mei_phy.c | 295 |
1 files changed, 269 insertions, 26 deletions
diff --git a/drivers/nfc/mei_phy.c b/drivers/nfc/mei_phy.c index 11c7cbdade66..2b77ccf77f81 100644 --- a/drivers/nfc/mei_phy.c +++ b/drivers/nfc/mei_phy.c @@ -32,6 +32,51 @@ struct mei_nfc_hdr { u16 data_size; } __packed; +struct mei_nfc_cmd { + struct mei_nfc_hdr hdr; + u8 sub_command; + u8 data[]; +} __packed; + +struct mei_nfc_reply { + struct mei_nfc_hdr hdr; + u8 sub_command; + u8 reply_status; + u8 data[]; +} __packed; + +struct mei_nfc_if_version { + u8 radio_version_sw[3]; + u8 reserved[3]; + u8 radio_version_hw[3]; + u8 i2c_addr; + u8 fw_ivn; + u8 vendor_id; + u8 radio_type; +} __packed; + +struct mei_nfc_connect { + u8 fw_ivn; + u8 vendor_id; +} __packed; + +struct mei_nfc_connect_resp { + u8 fw_ivn; + u8 vendor_id; + u16 me_major; + u16 me_minor; + u16 me_hotfix; + u16 me_build; +} __packed; + + +#define MEI_NFC_CMD_MAINTENANCE 0x00 +#define MEI_NFC_CMD_HCI_SEND 0x01 +#define MEI_NFC_CMD_HCI_RECV 0x02 + +#define MEI_NFC_SUBCMD_CONNECT 0x00 +#define MEI_NFC_SUBCMD_IF_VERSION 0x01 + #define MEI_NFC_MAX_READ (MEI_NFC_HEADER_SIZE + MEI_NFC_MAX_HCI_PAYLOAD) #define MEI_DUMP_SKB_IN(info, skb) \ @@ -45,51 +90,169 @@ do { \ do { \ pr_debug("%s:\n", info); \ print_hex_dump_debug("mei out: ", DUMP_PREFIX_OFFSET, \ - 16, 1, (skb)->data, (skb)->len, false); \ + 16, 1, (skb)->data, (skb)->len, false); \ } while (0) -int nfc_mei_phy_enable(void *phy_id) +#define MEI_DUMP_NFC_HDR(info, _hdr) \ +do { \ + pr_debug("%s:\n", info); \ + pr_debug("cmd=%02d status=%d req_id=%d rsvd=%d size=%d\n", \ + (_hdr)->cmd, (_hdr)->status, (_hdr)->req_id, \ + (_hdr)->reserved, (_hdr)->data_size); \ +} while (0) + +static int mei_nfc_if_version(struct nfc_mei_phy *phy) { - int r; - struct nfc_mei_phy *phy = phy_id; + + struct mei_nfc_cmd cmd; + struct mei_nfc_reply *reply = NULL; + struct mei_nfc_if_version *version; + size_t if_version_length; + int bytes_recv, r; pr_info("%s\n", __func__); - if (phy->powered == 1) - return 0; + memset(&cmd, 0, sizeof(struct mei_nfc_cmd)); + cmd.hdr.cmd = MEI_NFC_CMD_MAINTENANCE; + cmd.hdr.data_size = 1; + cmd.sub_command = MEI_NFC_SUBCMD_IF_VERSION; - r = mei_cl_enable_device(phy->device); + MEI_DUMP_NFC_HDR("version", &cmd.hdr); + r = mei_cl_send(phy->device, (u8 *)&cmd, sizeof(struct mei_nfc_cmd)); if (r < 0) { - pr_err("Could not enable device\n"); + pr_err("Could not send IF version cmd\n"); return r; } - r = mei_cl_register_event_cb(phy->device, nfc_mei_event_cb, phy); - if (r) { - pr_err("Event cb registration failed\n"); - mei_cl_disable_device(phy->device); - phy->powered = 0; + /* to be sure on the stack we alloc memory */ + if_version_length = sizeof(struct mei_nfc_reply) + + sizeof(struct mei_nfc_if_version); - return r; + reply = kzalloc(if_version_length, GFP_KERNEL); + if (!reply) + return -ENOMEM; + + bytes_recv = mei_cl_recv(phy->device, (u8 *)reply, if_version_length); + if (bytes_recv < 0 || bytes_recv < sizeof(struct mei_nfc_reply)) { + pr_err("Could not read IF version\n"); + r = -EIO; + goto err; } - phy->powered = 1; + version = (struct mei_nfc_if_version *)reply->data; - return 0; + phy->fw_ivn = version->fw_ivn; + phy->vendor_id = version->vendor_id; + phy->radio_type = version->radio_type; + +err: + kfree(reply); + return r; } -EXPORT_SYMBOL_GPL(nfc_mei_phy_enable); -void nfc_mei_phy_disable(void *phy_id) +static int mei_nfc_connect(struct nfc_mei_phy *phy) { - struct nfc_mei_phy *phy = phy_id; + struct mei_nfc_cmd *cmd, *reply; + struct mei_nfc_connect *connect; + struct mei_nfc_connect_resp *connect_resp; + size_t connect_length, connect_resp_length; + int bytes_recv, r; pr_info("%s\n", __func__); - mei_cl_disable_device(phy->device); + connect_length = sizeof(struct mei_nfc_cmd) + + sizeof(struct mei_nfc_connect); - phy->powered = 0; + connect_resp_length = sizeof(struct mei_nfc_cmd) + + sizeof(struct mei_nfc_connect_resp); + + cmd = kzalloc(connect_length, GFP_KERNEL); + if (!cmd) + return -ENOMEM; + connect = (struct mei_nfc_connect *)cmd->data; + + reply = kzalloc(connect_resp_length, GFP_KERNEL); + if (!reply) { + kfree(cmd); + return -ENOMEM; + } + + connect_resp = (struct mei_nfc_connect_resp *)reply->data; + + cmd->hdr.cmd = MEI_NFC_CMD_MAINTENANCE; + cmd->hdr.data_size = 3; + cmd->sub_command = MEI_NFC_SUBCMD_CONNECT; + connect->fw_ivn = phy->fw_ivn; + connect->vendor_id = phy->vendor_id; + + MEI_DUMP_NFC_HDR("connect request", &cmd->hdr); + r = mei_cl_send(phy->device, (u8 *)cmd, connect_length); + if (r < 0) { + pr_err("Could not send connect cmd %d\n", r); + goto err; + } + + bytes_recv = mei_cl_recv(phy->device, (u8 *)reply, connect_resp_length); + if (bytes_recv < 0) { + r = bytes_recv; + pr_err("Could not read connect response %d\n", r); + goto err; + } + + MEI_DUMP_NFC_HDR("connect reply", &reply->hdr); + + pr_info("IVN 0x%x Vendor ID 0x%x\n", + connect_resp->fw_ivn, connect_resp->vendor_id); + + pr_info("ME FW %d.%d.%d.%d\n", + connect_resp->me_major, connect_resp->me_minor, + connect_resp->me_hotfix, connect_resp->me_build); + + r = 0; + +err: + kfree(reply); + kfree(cmd); + + return r; +} + +static int mei_nfc_send(struct nfc_mei_phy *phy, u8 *buf, size_t length) +{ + struct mei_nfc_hdr *hdr; + u8 *mei_buf; + int err; + + err = -ENOMEM; + mei_buf = kzalloc(length + MEI_NFC_HEADER_SIZE, GFP_KERNEL); + if (!mei_buf) + goto out; + + hdr = (struct mei_nfc_hdr *)mei_buf; + hdr->cmd = MEI_NFC_CMD_HCI_SEND; + hdr->status = 0; + hdr->req_id = phy->req_id; + hdr->reserved = 0; + hdr->data_size = length; + + MEI_DUMP_NFC_HDR("send", hdr); + + memcpy(mei_buf + MEI_NFC_HEADER_SIZE, buf, length); + err = mei_cl_send(phy->device, mei_buf, length + MEI_NFC_HEADER_SIZE); + if (err < 0) + goto out; + + if (!wait_event_interruptible_timeout(phy->send_wq, + phy->recv_req_id == phy->req_id, HZ)) { + pr_err("NFC MEI command timeout\n"); + err = -ETIME; + } else { + phy->req_id++; + } +out: + kfree(mei_buf); + return err; } -EXPORT_SYMBOL_GPL(nfc_mei_phy_disable); /* * Writing a frame must not return the number of written bytes. @@ -103,14 +266,38 @@ static int nfc_mei_phy_write(void *phy_id, struct sk_buff *skb) MEI_DUMP_SKB_OUT("mei frame sent", skb); - r = mei_cl_send(phy->device, skb->data, skb->len); + r = mei_nfc_send(phy, skb->data, skb->len); if (r > 0) r = 0; return r; } -void nfc_mei_event_cb(struct mei_cl_device *device, u32 events, void *context) +static int mei_nfc_recv(struct nfc_mei_phy *phy, u8 *buf, size_t length) +{ + struct mei_nfc_hdr *hdr; + int received_length; + + received_length = mei_cl_recv(phy->device, buf, length); + if (received_length < 0) + return received_length; + + hdr = (struct mei_nfc_hdr *) buf; + + MEI_DUMP_NFC_HDR("receive", hdr); + if (hdr->cmd == MEI_NFC_CMD_HCI_SEND) { + phy->recv_req_id = hdr->req_id; + wake_up(&phy->send_wq); + + return 0; + } + + return received_length; +} + + +static void nfc_mei_event_cb(struct mei_cl_device *device, u32 events, + void *context) { struct nfc_mei_phy *phy = context; @@ -125,7 +312,7 @@ void nfc_mei_event_cb(struct mei_cl_device *device, u32 events, void *context) if (!skb) return; - reply_size = mei_cl_recv(device, skb->data, MEI_NFC_MAX_READ); + reply_size = mei_nfc_recv(phy, skb->data, MEI_NFC_MAX_READ); if (reply_size < MEI_NFC_HEADER_SIZE) { kfree_skb(skb); return; @@ -139,7 +326,61 @@ void nfc_mei_event_cb(struct mei_cl_device *device, u32 events, void *context) nfc_hci_recv_frame(phy->hdev, skb); } } -EXPORT_SYMBOL_GPL(nfc_mei_event_cb); + +static int nfc_mei_phy_enable(void *phy_id) +{ + int r; + struct nfc_mei_phy *phy = phy_id; + + pr_info("%s\n", __func__); + + if (phy->powered == 1) + return 0; + + r = mei_cl_enable_device(phy->device); + if (r < 0) { + pr_err("Could not enable device %d\n", r); + return r; + } + + r = mei_nfc_if_version(phy); + if (r < 0) { + pr_err("Could not enable device %d\n", r); + goto err; + } + + r = mei_nfc_connect(phy); + if (r < 0) { + pr_err("Could not connect to device %d\n", r); + goto err; + } + + r = mei_cl_register_event_cb(phy->device, nfc_mei_event_cb, phy); + if (r) { + pr_err("Event cb registration failed %d\n", r); + goto err; + } + + phy->powered = 1; + + return 0; + +err: + phy->powered = 0; + mei_cl_disable_device(phy->device); + return r; +} + +static void nfc_mei_phy_disable(void *phy_id) +{ + struct nfc_mei_phy *phy = phy_id; + + pr_info("%s\n", __func__); + + mei_cl_disable_device(phy->device); + + phy->powered = 0; +} struct nfc_phy_ops mei_phy_ops = { .write = nfc_mei_phy_write, @@ -157,6 +398,7 @@ struct nfc_mei_phy *nfc_mei_phy_alloc(struct mei_cl_device *device) return NULL; phy->device = device; + init_waitqueue_head(&phy->send_wq); mei_cl_set_drvdata(device, phy); return phy; @@ -165,6 +407,7 @@ EXPORT_SYMBOL_GPL(nfc_mei_phy_alloc); void nfc_mei_phy_free(struct nfc_mei_phy *phy) { + mei_cl_disable_device(phy->device); kfree(phy); } EXPORT_SYMBOL_GPL(nfc_mei_phy_free); |