From patchwork Thu Oct 25 13:29:34 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Lars Poeschel X-Patchwork-Id: 10655783 X-Patchwork-Delegate: sameo@linux.intel.com Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 0F1E713A4 for ; Thu, 25 Oct 2018 13:31:36 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id F31612B9E8 for ; Thu, 25 Oct 2018 13:31:35 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id F15EA2B9FF; Thu, 25 Oct 2018 13:31:35 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.9 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D866C2B9E8 for ; Thu, 25 Oct 2018 13:31:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727441AbeJYWEN (ORCPT ); Thu, 25 Oct 2018 18:04:13 -0400 Received: from smtp1-3.goneo.de ([85.220.129.32]:35650 "EHLO smtp1-3.goneo.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727234AbeJYWEN (ORCPT ); Thu, 25 Oct 2018 18:04:13 -0400 Received: from localhost (localhost [127.0.0.1]) by smtp1.goneo.de (Postfix) with ESMTP id 1E5D1242010; Thu, 25 Oct 2018 15:31:24 +0200 (CEST) X-Virus-Scanned: by goneo Received: from smtp1.goneo.de ([127.0.0.1]) by localhost (smtp1.goneo.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id FiYykymQNZrc; Thu, 25 Oct 2018 15:31:21 +0200 (CEST) Received: from lem-wkst-02.lemonage.de. (hq.lemonage.de [87.138.178.34]) by smtp1.goneo.de (Postfix) with ESMTPSA id B999F241024; Thu, 25 Oct 2018 15:31:21 +0200 (CEST) From: Lars Poeschel To: devicetree@vger.kernel.org, Samuel Ortiz , "GitAuthor: Lars Poeschel" , linux-kernel@vger.kernel.org (open list), linux-wireless@vger.kernel.org (open list:NFC SUBSYSTEM) Subject: [PATCH v3 3/5] nfc: pn533: add UART phy driver Date: Thu, 25 Oct 2018 15:29:34 +0200 Message-Id: <20181025132937.24405-3-poeschel@lemonage.de> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20181025132937.24405-1-poeschel@lemonage.de> References: <20181025132937.24405-1-poeschel@lemonage.de> MIME-Version: 1.0 Sender: linux-wireless-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-wireless@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This adds the UART phy interface for the pn533 driver. The pn533 driver can be used through UART interface this way. It is implemented as a serdev device. Signed-off-by: Lars Poeschel --- Changes in v3: - depend on SERIAL_DEV_BUS in Kconfig Changes in v2: - switched from tty line discipline to serdev, resulting in many simplifications - SPDX License Identifier drivers/nfc/pn533/Kconfig | 11 ++ drivers/nfc/pn533/Makefile | 2 + drivers/nfc/pn533/pn533.h | 8 ++ drivers/nfc/pn533/uart.c | 283 +++++++++++++++++++++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 drivers/nfc/pn533/uart.c diff --git a/drivers/nfc/pn533/Kconfig b/drivers/nfc/pn533/Kconfig index d94122dd30e4..29df7d97d209 100644 --- a/drivers/nfc/pn533/Kconfig +++ b/drivers/nfc/pn533/Kconfig @@ -25,3 +25,14 @@ config NFC_PN533_I2C If you choose to build a module, it'll be called pn533_i2c. Say N if unsure. + +config NFC_PN532_UART + tristate "NFC PN532 device support (UART)" + depends on SERIAL_DEV_BUS + select NFC_PN533 + ---help--- + This module adds support for the NXP pn532 UART interface. + Select this if your platform is using the UART bus. + + If you choose to build a module, it'll be called pn532_uart. + Say N if unsure. diff --git a/drivers/nfc/pn533/Makefile b/drivers/nfc/pn533/Makefile index 51d24c622fcb..bdfd6860d5e4 100644 --- a/drivers/nfc/pn533/Makefile +++ b/drivers/nfc/pn533/Makefile @@ -3,7 +3,9 @@ # pn533_usb-objs = usb.o pn533_i2c-objs = i2c.o +pn532_uart-objs = uart.o obj-$(CONFIG_NFC_PN533) += pn533.o obj-$(CONFIG_NFC_PN533_USB) += pn533_usb.o obj-$(CONFIG_NFC_PN533_I2C) += pn533_i2c.o +obj-$(CONFIG_NFC_PN532_UART) += pn532_uart.o diff --git a/drivers/nfc/pn533/pn533.h b/drivers/nfc/pn533/pn533.h index 88d569666c51..ca00508eefff 100644 --- a/drivers/nfc/pn533/pn533.h +++ b/drivers/nfc/pn533/pn533.h @@ -55,6 +55,11 @@ /* Preamble (1), SoPC (2), ACK Code (2), Postamble (1) */ #define PN533_STD_FRAME_ACK_SIZE 6 +/* + * Preamble (1), SoPC (2), Packet Length (1), Packet Length Checksum (1), + * Specific Application Level Error Code (1) , Postamble (1) + */ +#define PN533_STD_ERROR_FRAME_SIZE 8 #define PN533_STD_FRAME_CHECKSUM(f) (f->data[f->datalen]) #define PN533_STD_FRAME_POSTAMBLE(f) (f->data[f->datalen + 1]) /* Half start code (3), LEN (4) should be 0xffff for extended frame */ @@ -96,6 +101,9 @@ #define PN533_CMD_MI_MASK 0x40 #define PN533_CMD_RET_SUCCESS 0x00 +#define PN533_FRAME_DATALEN_ACK 0x00 +#define PN533_FRAME_DATALEN_ERROR 0x01 +#define PN533_FRAME_DATALEN_EXTENDED 0xFF enum pn533_protocol_type { PN533_PROTO_REQ_ACK_RESP = 0, diff --git a/drivers/nfc/pn533/uart.c b/drivers/nfc/pn533/uart.c new file mode 100644 index 000000000000..8fd71c8953ca --- /dev/null +++ b/drivers/nfc/pn533/uart.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for NXP PN532 NFC Chip - UART transport layer + * + * Copyright (C) 2018 Lemonage Software GmbH + * Author: Lars Pöschel + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pn533.h" + +#define VERSION "0.1" +#define PN532_UART_DRIVER_NAME "pn532_uart" + +#define PN532_UART_SKB_BUFF_LEN (PN533_CMD_DATAEXCH_DATA_MAXLEN * 2) + +struct pn532_uart_phy { + struct serdev_device *serdev; + struct sk_buff *recv_skb; + struct pn533 *priv; + int send_wakeup; + struct timer_list cmd_timeout; + struct sk_buff *cur_out_buf; +}; + +static int pn532_uart_send_frame(struct pn533 *dev, + struct sk_buff *out) +{ + static const u8 wakeup[] = { + 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + /* wakeup sequence and dummy bytes for waiting time */ + struct pn532_uart_phy *pn532 = dev->phy; + int count; + + print_hex_dump_debug("PN532_uart TX: ", DUMP_PREFIX_NONE, 16, 1, + out->data, out->len, false); + + pn532->cur_out_buf = out; + if (pn532->send_wakeup) + count = serdev_device_write(pn532->serdev, + wakeup, sizeof(wakeup), + MAX_SCHEDULE_TIMEOUT); + + count = serdev_device_write(pn532->serdev, out->data, out->len, + MAX_SCHEDULE_TIMEOUT); + if (PN533_FRAME_CMD(((struct pn533_std_frame *)out->data)) == + PN533_CMD_SAM_CONFIGURATION) + pn532->send_wakeup = 0; + + mod_timer(&pn532->cmd_timeout, HZ / 40 + jiffies); + return 0; +} + +static int pn532_uart_send_ack(struct pn533 *dev, gfp_t flags) +{ + struct pn532_uart_phy *pn532 = dev->phy; + static const u8 ack[PN533_STD_FRAME_ACK_SIZE] = { + 0x00, 0x00, 0xff, 0x00, 0xff, 0x00}; + /* spec 7.1.1.3: Preamble, SoPC (2), ACK Code (2), Postamble */ + int rc; + + rc = serdev_device_write(pn532->serdev, ack, sizeof(ack), + MAX_SCHEDULE_TIMEOUT); + + return 0; +} + +static void pn532_uart_abort_cmd(struct pn533 *dev, gfp_t flags) +{ + /* An ack will cancel the last issued command */ + pn532_uart_send_ack(dev, flags); + /* schedule cmd_complete_work to finish current command execution */ + pn533_recv_frame(dev, NULL, -ENOENT); +} + +static struct pn533_phy_ops uart_phy_ops = { + .send_frame = pn532_uart_send_frame, + .send_ack = pn532_uart_send_ack, + .abort_cmd = pn532_uart_abort_cmd, +}; + +static void pn532_cmd_timeout(struct timer_list *t) +{ + struct pn532_uart_phy *dev = from_timer(dev, t, cmd_timeout); + + pn532_uart_send_frame(dev->priv, dev->cur_out_buf); +} + +/* + * scans the buffer if it contains a pn532 frame. It is not checked if the + * frame is really valid. This is later done with pn533_rx_frame_is_valid. + * This is useful for malformed or errornous transmitted frames. Adjusts the + * bufferposition where the frame starts, since pn533_recv_frame expects a + * well formed frame. + */ +static int pn532_uart_rx_is_frame(struct sk_buff *skb) +{ + int i; + u16 frame_len; + struct pn533_std_frame *std; + struct pn533_ext_frame *ext; + + for (i = 0; i + PN533_STD_FRAME_ACK_SIZE <= skb->len; i++) { + std = (struct pn533_std_frame *)&skb->data[i]; + /* search start code */ + if (std->start_frame != cpu_to_be16(PN533_STD_FRAME_SOF)) + continue; + + /* frame type */ + switch (std->datalen) { + case PN533_FRAME_DATALEN_ACK: + if (std->datalen_checksum == 0xff) { + skb_pull(skb, i); + return 1; + } + + break; + case PN533_FRAME_DATALEN_ERROR: + if ((std->datalen_checksum == 0xff) && + (skb->len >= + PN533_STD_ERROR_FRAME_SIZE)) { + skb_pull(skb, i); + return 1; + } + + break; + case PN533_FRAME_DATALEN_EXTENDED: + ext = (struct pn533_ext_frame *)&skb->data[i]; + frame_len = ext->datalen; + if (skb->len >= frame_len + + sizeof(struct pn533_ext_frame) + + 2 /* CKS + Postamble */) { + skb_pull(skb, i); + return 1; + } + + break; + default: /* normal information frame */ + frame_len = std->datalen; + if (skb->len >= frame_len + + sizeof(struct pn533_std_frame) + + 2 /* CKS + Postamble */) { + skb_pull(skb, i); + return 1; + } + + break; + } + } + + return 0; +} + +static int pn532_receive_buf(struct serdev_device *serdev, + const unsigned char *data, size_t count) +{ + struct pn532_uart_phy *dev = serdev_device_get_drvdata(serdev); + size_t i; + + del_timer(&dev->cmd_timeout); + for (i = 0; i < count; i++) { + skb_put_u8(dev->recv_skb, *data++); + if (!pn532_uart_rx_is_frame(dev->recv_skb)) + continue; + + pn533_recv_frame(dev->priv, dev->recv_skb, 0); + dev->recv_skb = alloc_skb(PN532_UART_SKB_BUFF_LEN, GFP_KERNEL); + if (dev->recv_skb == NULL) + return 0; + } + + return i; +} + +static struct serdev_device_ops pn532_serdev_ops = { + .receive_buf = pn532_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static const struct of_device_id pn532_uart_of_match[] = { + { .compatible = "nxp,pn532-uart", }, + {}, +}; +MODULE_DEVICE_TABLE(of, pn532_uart_of_match); + +static int pn532_uart_probe(struct serdev_device *serdev) +{ + struct pn532_uart_phy *pn532; + struct pn533 *priv; + int err; + + err = -ENOMEM; + pn532 = kzalloc(sizeof(*pn532), GFP_KERNEL); + if (pn532 == NULL) + goto err_exit; + + pn532->recv_skb = alloc_skb(PN532_UART_SKB_BUFF_LEN, GFP_KERNEL); + if (pn532->recv_skb == NULL) + goto err_free; + + pn532->serdev = serdev; + priv = pn533_register_device(PN533_DEVICE_PN532, + PN533_NO_TYPE_B_PROTOCOLS, + PN533_PROTO_REQ_ACK_RESP, + pn532, &uart_phy_ops, NULL, + &pn532->serdev->dev, + &serdev->dev); + + if (IS_ERR(priv)) { + err = PTR_ERR(priv); + goto err_skb; + } + + pn532->priv = priv; + serdev_device_set_drvdata(serdev, pn532); + serdev_device_set_client_ops(serdev, &pn532_serdev_ops); + err = serdev_device_open(serdev); + if (err) { + dev_err(&serdev->dev, "Unable to open device %s\n", + serdev->dev.init_name); + goto err_unregister; + } + + err = serdev_device_set_baudrate(serdev, 115200); + if (err != 115200) { + err = -EINVAL; + goto err_serdev; + } + + serdev_device_set_flow_control(serdev, false); + pn532->send_wakeup = 1; + timer_setup(&pn532->cmd_timeout, pn532_cmd_timeout, 0); + err = pn533_finalize_setup(pn532->priv); + if (err) + goto err_serdev; + + return 0; + +err_serdev: + serdev_device_close(serdev); +err_unregister: + pn533_unregister_device(pn532->priv); +err_skb: + kfree_skb(pn532->recv_skb); +err_free: + kfree(pn532); +err_exit: + return err; +} + +static void pn532_uart_remove(struct serdev_device *serdev) +{ + struct pn532_uart_phy *pn532 = serdev_device_get_drvdata(serdev); + + pn533_unregister_device(pn532->priv); + serdev_device_close(serdev); + kfree_skb(pn532->recv_skb); + kfree(pn532); +} + +static struct serdev_device_driver pn532_uart_driver = { + .probe = pn532_uart_probe, + .remove = pn532_uart_remove, + .driver = { + .name = PN532_UART_DRIVER_NAME, + .of_match_table = of_match_ptr(pn532_uart_of_match), + }, +}; + +module_serdev_device_driver(pn532_uart_driver); + +MODULE_AUTHOR("Lars Pöschel "); +MODULE_DESCRIPTION("PN532 UART driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL");