diff options
author | Waldemar Rymarkiewicz <waldemar.rymarkiewicz@tieto.com> | 2013-04-03 08:02:13 +0200 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2013-04-11 16:29:07 +0200 |
commit | 53cf48399ad3b08c9115b4fce73dee0b6e726c91 (patch) | |
tree | 69048951e15fc273952de4c019f0f26c8a799bf8 | |
parent | 58520373d8aff974f4b3f1bd9eb84c1ff3d6bd8b (diff) |
NFC: pn533: Add support for ACS ACR122U reader
ACS ACR122U is an USB NFC reader, PC/SC and CCID compilant, based
on NXP PN532 chip.
Internally, it's build of MCU, PN532 and an antenna. MCU makes the
device CCID and PC/SC compilant and provide USB connection.
In this achitecture, a host cannot talk directly to PN532 and must
rely on MCU. Luckily, MCU exposes pseud-APDU through PC/SC Escape
mechanism which let the host to transmit standard PN532 commands
directly to PN532 chip with some limitations.
The frame roughly looks like:
CCID header | APDU header | PN532 header
(pc_to_rdr_escape) | (pseudo apdu Direct Tramsmit) | (len, TFI, cmd, params)
Accordign to limitations, ACR122U does't provide any mechanism to
abort last issued command.
Signed-off-by: Waldemar Rymarkiewicz <waldemar.rymarkiewicz@tieto.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
-rw-r--r-- | drivers/nfc/pn533.c | 219 |
1 files changed, 217 insertions, 2 deletions
diff --git a/drivers/nfc/pn533.c b/drivers/nfc/pn533.c index 2fd8029f377d..697f154331ed 100644 --- a/drivers/nfc/pn533.c +++ b/drivers/nfc/pn533.c @@ -38,8 +38,12 @@ #define SONY_VENDOR_ID 0x054c #define PASORI_PRODUCT_ID 0x02e1 -#define PN533_DEVICE_STD 0x1 -#define PN533_DEVICE_PASORI 0x2 +#define ACS_VENDOR_ID 0x072f +#define ACR122U_PRODUCT_ID 0x2200 + +#define PN533_DEVICE_STD 0x1 +#define PN533_DEVICE_PASORI 0x2 +#define PN533_DEVICE_ACR122U 0x3 #define PN533_ALL_PROTOCOLS (NFC_PROTO_JEWEL_MASK | NFC_PROTO_MIFARE_MASK |\ NFC_PROTO_FELICA_MASK | NFC_PROTO_ISO14443_MASK |\ @@ -68,6 +72,11 @@ static const struct usb_device_id pn533_table[] = { .idProduct = PASORI_PRODUCT_ID, .driver_info = PN533_DEVICE_PASORI, }, + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = ACS_VENDOR_ID, + .idProduct = ACR122U_PRODUCT_ID, + .driver_info = PN533_DEVICE_ACR122U, + }, { } }; MODULE_DEVICE_TABLE(usb, pn533_table); @@ -99,6 +108,21 @@ MODULE_DEVICE_TABLE(usb, pn533_table); #define PN533_STD_FRAME_DIR_OUT 0xD4 #define PN533_STD_FRAME_DIR_IN 0xD5 +/* ACS ACR122 pn533 frame definitions */ +#define PN533_ACR122_TX_FRAME_HEADER_LEN (sizeof(struct pn533_acr122_tx_frame) \ + + 2) +#define PN533_ACR122_TX_FRAME_TAIL_LEN 0 +#define PN533_ACR122_RX_FRAME_HEADER_LEN (sizeof(struct pn533_acr122_rx_frame) \ + + 2) +#define PN533_ACR122_RX_FRAME_TAIL_LEN 2 +#define PN533_ACR122_FRAME_MAX_PAYLOAD_LEN PN533_STD_FRAME_MAX_PAYLOAD_LEN + +/* CCID messages types */ +#define PN533_ACR122_PC_TO_RDR_ICCPOWERON 0x62 +#define PN533_ACR122_PC_TO_RDR_ESCAPE 0x6B + +#define PN533_ACR122_RDR_TO_PC_ESCAPE 0x83 + /* PN533 Commands */ #define PN533_STD_FRAME_CMD(f) (f->data[1]) @@ -394,6 +418,116 @@ struct pn533_frame_ops { u8 (*get_cmd_code)(void *frame); }; +struct pn533_acr122_ccid_hdr { + u8 type; + u32 datalen; + u8 slot; + u8 seq; + u8 params[3]; /* 3 msg specific bytes or status, error and 1 specific + byte for reposnse msg */ + u8 data[]; /* payload */ +} __packed; + +struct pn533_acr122_apdu_hdr { + u8 class; + u8 ins; + u8 p1; + u8 p2; +} __packed; + +struct pn533_acr122_tx_frame { + struct pn533_acr122_ccid_hdr ccid; + struct pn533_acr122_apdu_hdr apdu; + u8 datalen; + u8 data[]; /* pn533 frame: TFI ... */ +} __packed; + +struct pn533_acr122_rx_frame { + struct pn533_acr122_ccid_hdr ccid; + u8 data[]; /* pn533 frame : TFI ... */ +} __packed; + +static void pn533_acr122_tx_frame_init(void *_frame, u8 cmd_code) +{ + struct pn533_acr122_tx_frame *frame = _frame; + + frame->ccid.type = PN533_ACR122_PC_TO_RDR_ESCAPE; + frame->ccid.datalen = sizeof(frame->apdu) + 1; /* sizeof(apdu_hdr) + + sizeof(datalen) */ + frame->ccid.slot = 0; + frame->ccid.seq = 0; + frame->ccid.params[0] = 0; + frame->ccid.params[1] = 0; + frame->ccid.params[2] = 0; + + frame->data[0] = PN533_STD_FRAME_DIR_OUT; + frame->data[1] = cmd_code; + frame->datalen = 2; /* data[0] + data[1] */ + + frame->apdu.class = 0xFF; + frame->apdu.ins = 0; + frame->apdu.p1 = 0; + frame->apdu.p2 = 0; +} + +static void pn533_acr122_tx_frame_finish(void *_frame) +{ + struct pn533_acr122_tx_frame *frame = _frame; + + frame->ccid.datalen += frame->datalen; +} + +static void pn533_acr122_tx_update_payload_len(void *_frame, int len) +{ + struct pn533_acr122_tx_frame *frame = _frame; + + frame->datalen += len; +} + +static bool pn533_acr122_is_rx_frame_valid(void *_frame) +{ + struct pn533_acr122_rx_frame *frame = _frame; + + if (frame->ccid.type != 0x83) + return false; + + if (frame->data[frame->ccid.datalen - 2] == 0x63) + return false; + + return true; +} + +static int pn533_acr122_rx_frame_size(void *frame) +{ + struct pn533_acr122_rx_frame *f = frame; + + /* f->ccid.datalen already includes tail length */ + return sizeof(struct pn533_acr122_rx_frame) + f->ccid.datalen; +} + +static u8 pn533_acr122_get_cmd_code(void *frame) +{ + struct pn533_acr122_rx_frame *f = frame; + + return PN533_STD_FRAME_CMD(f); +} + +static struct pn533_frame_ops pn533_acr122_frame_ops = { + .tx_frame_init = pn533_acr122_tx_frame_init, + .tx_frame_finish = pn533_acr122_tx_frame_finish, + .tx_update_payload_len = pn533_acr122_tx_update_payload_len, + .tx_header_len = PN533_ACR122_TX_FRAME_HEADER_LEN, + .tx_tail_len = PN533_ACR122_TX_FRAME_TAIL_LEN, + + .rx_is_frame_valid = pn533_acr122_is_rx_frame_valid, + .rx_header_len = PN533_ACR122_RX_FRAME_HEADER_LEN, + .rx_tail_len = PN533_ACR122_RX_FRAME_TAIL_LEN, + .rx_frame_size = pn533_acr122_rx_frame_size, + + .max_payload_len = PN533_ACR122_FRAME_MAX_PAYLOAD_LEN, + .get_cmd_code = pn533_acr122_get_cmd_code, +}; + /* The rule: value + checksum = 0 */ static inline u8 pn533_std_checksum(u8 value) { @@ -2335,6 +2469,72 @@ static int pn533_pasori_fw_reset(struct pn533 *dev) return 0; } +struct pn533_acr122_poweron_rdr_arg { + int rc; + struct completion done; +}; + +static void pn533_acr122_poweron_rdr_resp(struct urb *urb) +{ + struct pn533_acr122_poweron_rdr_arg *arg = urb->context; + + nfc_dev_dbg(&urb->dev->dev, "%s", __func__); + + print_hex_dump(KERN_ERR, "ACR122 RX: ", DUMP_PREFIX_NONE, 16, 1, + urb->transfer_buffer, urb->transfer_buffer_length, + false); + + arg->rc = urb->status; + complete(&arg->done); +} + +static int pn533_acr122_poweron_rdr(struct pn533 *dev) +{ + /* Power on th reader (CCID cmd) */ + u8 cmd[10] = {PN533_ACR122_PC_TO_RDR_ICCPOWERON, + 0, 0, 0, 0, 0, 0, 3, 0, 0}; + u8 buf[255]; + int rc; + void *cntx; + struct pn533_acr122_poweron_rdr_arg arg; + + nfc_dev_dbg(&dev->interface->dev, "%s", __func__); + + init_completion(&arg.done); + cntx = dev->in_urb->context; /* backup context */ + + dev->in_urb->transfer_buffer = buf; + dev->in_urb->transfer_buffer_length = 255; + dev->in_urb->complete = pn533_acr122_poweron_rdr_resp; + dev->in_urb->context = &arg; + + dev->out_urb->transfer_buffer = cmd; + dev->out_urb->transfer_buffer_length = sizeof(cmd); + + print_hex_dump(KERN_ERR, "ACR122 TX: ", DUMP_PREFIX_NONE, 16, 1, + cmd, sizeof(cmd), false); + + rc = usb_submit_urb(dev->out_urb, GFP_KERNEL); + if (rc) { + nfc_dev_err(&dev->interface->dev, + "Reader power on cmd error %d", rc); + return rc; + } + + rc = usb_submit_urb(dev->in_urb, GFP_KERNEL); + if (rc) { + nfc_dev_err(&dev->interface->dev, + "Can't submit for reader power on cmd response %d", + rc); + return rc; + } + + wait_for_completion(&arg.done); + dev->in_urb->context = cntx; /* restore context */ + + return arg.rc; +} + static struct nfc_ops pn533_nfc_ops = { .dev_up = NULL, .dev_down = NULL, @@ -2369,6 +2569,7 @@ static int pn533_setup(struct pn533 *dev) break; case PN533_DEVICE_PASORI: + case PN533_DEVICE_ACR122U: max_retries.mx_rty_atr = 0x2; max_retries.mx_rty_psl = 0x1; max_retries.mx_rty_passive_act = @@ -2510,6 +2711,20 @@ static int pn533_probe(struct usb_interface *interface, protocols = PN533_NO_TYPE_B_PROTOCOLS; break; + case PN533_DEVICE_ACR122U: + protocols = PN533_NO_TYPE_B_PROTOCOLS; + dev->ops = &pn533_acr122_frame_ops; + dev->protocol_type = PN533_PROTO_REQ_RESP, + + rc = pn533_acr122_poweron_rdr(dev); + if (rc < 0) { + nfc_dev_err(&dev->interface->dev, + "Couldn't poweron the reader (error %d)", + rc); + goto destroy_wq; + } + break; + default: nfc_dev_err(&dev->interface->dev, "Unknown device type %d\n", dev->device_type); |