From patchwork Tue Jan 29 05:01:27 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 156926 Delivered-To: patch@linaro.org Received: by 2002:a02:48:0:0:0:0:0 with SMTP id 69csp4229212jaa; Mon, 28 Jan 2019 21:02:17 -0800 (PST) X-Google-Smtp-Source: ALg8bN5srPA6t6udnQvHBQiV+Zrte8VCrUVHTO6WLfIYaR5hdFVXyNN+Ke2Q65KB2IGD3JPZA03T X-Received: by 2002:a17:902:850c:: with SMTP id bj12mr24144858plb.46.1548738137223; Mon, 28 Jan 2019 21:02:17 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1548738137; cv=none; d=google.com; s=arc-20160816; b=CO6o/RFqoH4pxgdyKAkIRbt82vuB1SNnZmO1AAWlkkklCd3IP2lg7/RFkTTVF6UBFj Gar6M5DyuAdmlS8rXNbG/bndK4tOlef0GpFPPSi4Rz9O8sOkXPRWxa/IQhtFnAm1ueTC kBSD6vgWvArX3/AqezuCx4wsdgvH3DEp499WJgyx2hCiNsVVWTm4xvhAP9JaOKqvcpTI unV1GCZNtqnzTppAg6TzzZfFObT/csHTFGrtoz6DMRMymcGgrVdr5ySvvVZ9DAG0E21A GDmVmT/it4FUxC0YxaE08pNchlqAv05QFiT0WAPgO+twf0SX0bWJoBMDGdVJ1VI3iPUj jzNA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=NF0WOtx69cgSgaLjm7AZIvECO83NsDODQ/+vyTDQyxQ=; b=eA221bDVYCZGy51AKoCftaECMFgv0SSGZT38uh+6DZ4vAL9yY9gjJnaUH9GhJGsjUW FNO5BjEgXq0i0FkzS6V25yZEvaU/A69uYhbcK2P52J4Rr1wl/dfeHWDj8CeHzOBIe38X n3xNv3I2RR+sGGN89fDCyNWeJyUh1ste9a6UI7ARAMBGItr6p5pAhi3GgJ2roxS0m4Ne sG71I8ue02SYeRY1YP4FjQUF7EUy6pdu9uBBYyo3uVRdI3+4Cw56d/s4vJVrr3NZsYaz EMujlsI8qql9en9jVCdsUOGGZ45aC5m2bADnQ/gghSa4KSqQaoTzw5CxX3De4LwHyHp/ 9WWg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id t75si34775739pfa.170.2019.01.28.21.02.16; Mon, 28 Jan 2019 21:02:17 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1725769AbfA2FBo (ORCPT + 31 others); Tue, 29 Jan 2019 00:01:44 -0500 Received: from mx2.suse.de ([195.135.220.15]:53334 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1725268AbfA2FBn (ORCPT ); Tue, 29 Jan 2019 00:01:43 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 0F95EADC3; Tue, 29 Jan 2019 05:01:42 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-lpwan@lists.infradead.org, linux-wpan@vger.kernel.org Cc: Alexander Aring , Stefan Schmidt , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, support@enocean.com, =?utf-8?q?Andreas_F=C3=A4rber?= Subject: [RFC net-next 1/4] net: Reserve protocol identifiers for EnOcean Date: Tue, 29 Jan 2019 06:01:27 +0100 Message-Id: <20190129050130.10932-2-afaerber@suse.de> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20190129050130.10932-1-afaerber@suse.de> References: <20190129050130.10932-1-afaerber@suse.de> MIME-Version: 1.0 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org EnOcean wireless technology is based on ASK (ERP1) and FSK (ERP2) modulations for sub-GHz and on IEEE 802.15.4 for 2.4 GHz. ARPHRD_ENOCEAN ETH_P_ERP{1,2} Signed-off-by: Andreas Färber --- include/uapi/linux/if_arp.h | 1 + include/uapi/linux/if_ether.h | 2 ++ 2 files changed, 3 insertions(+) -- 2.16.4 diff --git a/include/uapi/linux/if_arp.h b/include/uapi/linux/if_arp.h index dd7992a441c9..327ef052329f 100644 --- a/include/uapi/linux/if_arp.h +++ b/include/uapi/linux/if_arp.h @@ -102,6 +102,7 @@ #define ARPHRD_LORAWAN 828 /* LoRaWAN */ #define ARPHRD_OOK 829 /* On/Off Keying modulation */ #define ARPHRD_FSK 830 /* Frequency Shift Keying modulation */ +#define ARPHRD_ENOCEAN 832 /* EnOcean */ #define ARPHRD_VOID 0xFFFF /* Void type, nothing is known */ #define ARPHRD_NONE 0xFFFE /* zero header length */ diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h index 0b5c30f78261..3e22948cc329 100644 --- a/include/uapi/linux/if_ether.h +++ b/include/uapi/linux/if_ether.h @@ -152,6 +152,8 @@ #define ETH_P_OOK 0x00FC /* On/Off Keying modulation */ #define ETH_P_FSK 0x00FD /* Frequency Shift Keying mod. */ #define ETH_P_FLRC 0x00FE /* Fast Long Range Communication */ +#define ETH_P_ERP1 0x00FF /* EnOcean Radio Protocol 1 */ +#define ETH_P_ERP2 0x0100 /* EnOcean Radio Protocol 2 */ /* * This is an Ethernet frame header. From patchwork Tue Jan 29 05:01:29 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 156924 Delivered-To: patch@linaro.org Received: by 2002:a02:48:0:0:0:0:0 with SMTP id 69csp4228947jaa; Mon, 28 Jan 2019 21:02:01 -0800 (PST) X-Google-Smtp-Source: ALg8bN6zql2SelAABcjLVOPpwxiaHqVuhu46RM9MU6APYP5ihGy2/NxOPRc2vnAX/oD+f6QUTw/X X-Received: by 2002:a62:13c3:: with SMTP id 64mr24716354pft.93.1548738121267; Mon, 28 Jan 2019 21:02:01 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1548738121; cv=none; d=google.com; s=arc-20160816; b=HguFA1Q29w0OPpaei6jGeC5CDjylWWhMAK/T0VUuJjsbq2N3elnf2iOyznS9iZAR+n gLV4BYHidAvqjY0aHZsCgbcuRpx0sAl03Eo7IGY16mck5OrhcWCSocZEVuK8SyoH12KY nQ36HPJSFpzHyYnrYr2sodNHnCzlNu6+vaHlJrhX3P999KPOzzEtRe+o4pDPIfIZbG86 v3Cb+KQe7T1Wwjql2ktrn0T7FSxv7ZIHyICdO1qGr+ksZNqt7Zhzjsk1Uc0wWEqgkpQz sYAIOgaNyr/JbQwWrdbIo6GaBHWSVX1Y5CQxJEgsoxuLTPy4uMtIfLa3bEB8eFSHs/H5 JJ4g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=7eYhNlNh2vJo26LhpvgHvfGyjPOhupZO96OPzkM1UBk=; b=knhNMzTUTtcW/qpL+hwsPpGHeBDEgso3ovqJTp9c3h2//2/CbM55zoUggq9ugue6MB fR8v5KPQRBXmBK1hjtQm+GE9hQrukgH+19RzA0Y1rgq171LBq2TbMudXHoV6ZqYTW2+u fgcKVv43wvRV0RYg9TtVZ/BsHWu4/GTlRxrRx2d1jhhfZS1KN7FBFfdjsI65/ETtw8G5 IWUCUvTRwjzX0mH9D4mERDx87V/1BdRe16ptHMnT6Hmig2UxiEdCTkjMnvjmuVfboZBT pLvV/J74QwZ8SVIN85hVMkXhcZVk7Nvq8MhYbQSc2Xb7jsWAQLdGNxYMaIxFiK8yALyw l4gA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id t75si34775739pfa.170.2019.01.28.21.02.01; Mon, 28 Jan 2019 21:02:01 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726117AbfA2FBt (ORCPT + 31 others); Tue, 29 Jan 2019 00:01:49 -0500 Received: from mx2.suse.de ([195.135.220.15]:53368 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1725298AbfA2FBo (ORCPT ); Tue, 29 Jan 2019 00:01:44 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 5AADFADE4; Tue, 29 Jan 2019 05:01:42 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-lpwan@lists.infradead.org, linux-wpan@vger.kernel.org Cc: Alexander Aring , Stefan Schmidt , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, support@enocean.com, =?utf-8?q?Andreas_F=C3=A4rber?= Subject: [RFC net-next 3/4] net: enocean: Add ESP3 driver Date: Tue, 29 Jan 2019 06:01:29 +0100 Message-Id: <20190129050130.10932-4-afaerber@suse.de> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20190129050130.10932-1-afaerber@suse.de> References: <20190129050130.10932-1-afaerber@suse.de> MIME-Version: 1.0 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This implements the EnOcean Serial Protocol 3. Rudimentary sending is prepared. Error handling is lacking and reception handling is missing. Tested with EnOcean TCM310 gateway module. Signed-off-by: Andreas Färber --- drivers/net/enocean/Makefile | 4 + drivers/net/enocean/enocean_esp.c | 277 +++++++++++++++++++++++++++ drivers/net/enocean/enocean_esp.h | 38 ++++ drivers/net/enocean/enocean_esp3.c | 372 +++++++++++++++++++++++++++++++++++++ 4 files changed, 691 insertions(+) create mode 100644 drivers/net/enocean/enocean_esp.c create mode 100644 drivers/net/enocean/enocean_esp.h create mode 100644 drivers/net/enocean/enocean_esp3.c -- 2.16.4 diff --git a/drivers/net/enocean/Makefile b/drivers/net/enocean/Makefile index efb3cd16c7f2..4492e3d48c0a 100644 --- a/drivers/net/enocean/Makefile +++ b/drivers/net/enocean/Makefile @@ -1,2 +1,6 @@ obj-m += enocean-dev.o enocean-dev-y := enocean.o + +obj-m += enocean-esp.o +enocean-esp-y := enocean_esp.o +enocean-esp-y += enocean_esp3.o diff --git a/drivers/net/enocean/enocean_esp.c b/drivers/net/enocean/enocean_esp.c new file mode 100644 index 000000000000..61bddb77762d --- /dev/null +++ b/drivers/net/enocean/enocean_esp.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EnOcean Serial Protocol + * + * Copyright (c) 2019 Andreas Färber + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "enocean_esp.h" + +struct enocean_esp_version { + int version; + int baudrate; + const struct serdev_device_ops *serdev_client_ops; + int (*init)(struct enocean_device *edev); + int (*send)(struct enocean_device *edev, u32 dest, const void *data, int data_len); + void (*cleanup)(struct enocean_device *edev); +}; + +struct enocean_esp_priv { + struct enocean_dev_priv priv; + + struct enocean_device *edev; + + struct sk_buff *tx_skb; + int tx_len; + + struct workqueue_struct *wq; + struct work_struct tx_work; +}; + +static netdev_tx_t enocean_dev_start_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct enocean_esp_priv *priv = netdev_priv(netdev); + + netdev_dbg(netdev, "%s\n", __func__); + + if (skb->protocol != htons(ETH_P_ERP1) && + skb->protocol != htons(ETH_P_ERP2)) { + kfree_skb(skb); + netdev->stats.tx_dropped++; + return NETDEV_TX_OK; + } + + netif_stop_queue(netdev); + priv->tx_skb = skb; + queue_work(priv->wq, &priv->tx_work); + + return NETDEV_TX_OK; +} + +static int enocean_esp_tx(struct enocean_device *edev, void *data, int data_len) +{ + if (!edev->version->send) + return -ENOTSUPP; + return edev->version->send(edev, 0xffffffff, data, data_len); +} + +static void enocean_esp_tx_work_handler(struct work_struct *ws) +{ + struct enocean_esp_priv *priv = container_of(ws, struct enocean_esp_priv, tx_work); + struct enocean_device *edev = priv->edev; + struct net_device *netdev = edev->netdev; + + netdev_dbg(netdev, "%s\n", __func__); + + if (priv->tx_skb) { + enocean_esp_tx(edev, priv->tx_skb->data, priv->tx_skb->len); + priv->tx_len = 1 + priv->tx_skb->len; + if (!(netdev->flags & IFF_ECHO) || + priv->tx_skb->pkt_type != PACKET_LOOPBACK || + (priv->tx_skb->protocol != htons(ETH_P_ERP1) && + priv->tx_skb->protocol != htons(ETH_P_ERP2))) + kfree_skb(priv->tx_skb); + priv->tx_skb = NULL; + } +} + +void enocean_esp_tx_done(struct enocean_device *edev) +{ + struct net_device *netdev = edev->netdev; + struct enocean_esp_priv *priv = netdev_priv(netdev); + + netdev_info(netdev, "TX done.\n"); + netdev->stats.tx_packets++; + netdev->stats.tx_bytes += priv->tx_len - 1; + priv->tx_len = 0; + netif_wake_queue(netdev); +} + +static int enocean_dev_open(struct net_device *netdev) +{ + struct enocean_esp_priv *priv = netdev_priv(netdev); + int ret; + + netdev_dbg(netdev, "%s\n", __func__); + + ret = open_enocean_dev(netdev); + if (ret) + return ret; + + priv->tx_skb = NULL; + priv->tx_len = 0; + + priv->wq = alloc_workqueue("enocean_esp_wq", WQ_FREEZABLE | WQ_MEM_RECLAIM, 0); + INIT_WORK(&priv->tx_work, enocean_esp_tx_work_handler); + + netif_wake_queue(netdev); + + return 0; +} + +static int enocean_dev_stop(struct net_device *netdev) +{ + struct enocean_esp_priv *priv = netdev_priv(netdev); + + netdev_dbg(netdev, "%s\n", __func__); + + close_enocean_dev(netdev); + + destroy_workqueue(priv->wq); + priv->wq = NULL; + + if (priv->tx_skb || priv->tx_len) + netdev->stats.tx_errors++; + if (priv->tx_skb) + dev_kfree_skb(priv->tx_skb); + priv->tx_skb = NULL; + priv->tx_len = 0; + + return 0; +} + +static const struct net_device_ops enocean_esp_netdev_ops = { + .ndo_open = enocean_dev_open, + .ndo_stop = enocean_dev_stop, + .ndo_start_xmit = enocean_dev_start_xmit, +}; + +static void enocean_esp_cleanup(struct enocean_device *edev) +{ + if (edev->version->cleanup) + edev->version->cleanup(edev); +} + +static const struct enocean_esp_version enocean_esp3 = { + .version = 3, + .baudrate = 57600, + .serdev_client_ops = &enocean_esp3_serdev_client_ops, + .init = enocean_esp3_init, + .send = enocean_esp3_send, + .cleanup = enocean_esp3_cleanup, +}; + +static const struct of_device_id enocean_of_match[] = { + { .compatible = "enocean,esp3", .data = &enocean_esp3 }, + {} +}; +MODULE_DEVICE_TABLE(of, enocean_of_match); + +static int enocean_probe(struct serdev_device *sdev) +{ + struct enocean_esp_priv *priv; + struct enocean_device *edev; + int ret; + + dev_dbg(&sdev->dev, "Probing"); + + edev = devm_kzalloc(&sdev->dev, sizeof(*edev), GFP_KERNEL); + if (!edev) + return -ENOMEM; + + edev->version = of_device_get_match_data(&sdev->dev); + if (!edev->version) + return -ENOTSUPP; + + dev_dbg(&sdev->dev, "ESP%d\n", edev->version->version); + + edev->serdev = sdev; + INIT_LIST_HEAD(&edev->esp_dispatchers); + serdev_device_set_drvdata(sdev, edev); + + ret = serdev_device_open(sdev); + if (ret) { + dev_err(&sdev->dev, "Failed to open (%d)\n", ret); + return ret; + } + + serdev_device_set_baudrate(sdev, edev->version->baudrate); + serdev_device_set_flow_control(sdev, false); + serdev_device_set_client_ops(sdev, edev->version->serdev_client_ops); + + if (edev->version->init) { + ret = edev->version->init(edev); + if (ret) { + serdev_device_close(sdev); + return ret; + } + } + + edev->netdev = devm_alloc_enocean_dev(&sdev->dev, sizeof(struct enocean_esp_priv)); + if (!edev->netdev) { + enocean_esp_cleanup(edev); + serdev_device_close(sdev); + return -ENOMEM; + } + + edev->netdev->netdev_ops = &enocean_esp_netdev_ops; + edev->netdev->flags |= IFF_ECHO; + SET_NETDEV_DEV(edev->netdev, &sdev->dev); + + priv = netdev_priv(edev->netdev); + priv->edev = edev; + + ret = register_enocean_dev(edev->netdev); + if (ret) { + enocean_esp_cleanup(edev); + serdev_device_close(sdev); + return ret; + } + + dev_dbg(&sdev->dev, "Done.\n"); + + return 0; +} + +static void enocean_remove(struct serdev_device *sdev) +{ + struct enocean_device *edev = serdev_device_get_drvdata(sdev); + + unregister_enocean_dev(edev->netdev); + enocean_esp_cleanup(edev); + serdev_device_close(sdev); + + dev_dbg(&sdev->dev, "Removed\n"); +} + +static struct serdev_device_driver enocean_serdev_driver = { + .probe = enocean_probe, + .remove = enocean_remove, + .driver = { + .name = "enocean-esp", + .of_match_table = enocean_of_match, + }, +}; + +static int __init enocean_init(void) +{ + int ret; + + enocean_esp3_crc8_populate(); + + ret = serdev_device_driver_register(&enocean_serdev_driver); + if (ret) + return ret; + + return 0; +} + +static void __exit enocean_exit(void) +{ + serdev_device_driver_unregister(&enocean_serdev_driver); +} + +module_init(enocean_init); +module_exit(enocean_exit); + +MODULE_DESCRIPTION("EnOcean serdev driver"); +MODULE_AUTHOR("Andreas Färber "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/enocean/enocean_esp.h b/drivers/net/enocean/enocean_esp.h new file mode 100644 index 000000000000..e02bf5352d61 --- /dev/null +++ b/drivers/net/enocean/enocean_esp.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EnOcean Serial Protocol + * + * Copyright (c) 2019 Andreas Färber + */ +#ifndef ENOCEAN_H +#define ENOCEAN_H + +#include +#include +#include + +struct enocean_esp_version; + +struct enocean_device { + struct serdev_device *serdev; + const struct enocean_esp_version *version; + + struct net_device *netdev; + + struct list_head esp_dispatchers; + + void *priv; +}; + +extern const struct serdev_device_ops enocean_esp3_serdev_client_ops; + +void enocean_esp3_crc8_populate(void); + +int enocean_esp3_init(struct enocean_device *edev); + +int enocean_esp3_send(struct enocean_device *edev, u32 dest, const void *data, int data_len); +void enocean_esp_tx_done(struct enocean_device *edev); + +void enocean_esp3_cleanup(struct enocean_device *edev); + +#endif diff --git a/drivers/net/enocean/enocean_esp3.c b/drivers/net/enocean/enocean_esp3.c new file mode 100644 index 000000000000..707c4054ac69 --- /dev/null +++ b/drivers/net/enocean/enocean_esp3.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EnOcean Serial Protocol 3 + * + * Copyright (c) 2019 Andreas Färber + */ + +#include +#include +#include +#include +#include +#include + +#include "enocean_esp.h" + +/* G(x) = x^8 + x^2 + x^1 + x^0 */ +#define ESP3_CRC8_POLY_MSB 0x07 + +DECLARE_CRC8_TABLE(enocean_esp3_crc8_table); + +void enocean_esp3_crc8_populate(void) +{ + crc8_populate_msb(enocean_esp3_crc8_table, ESP3_CRC8_POLY_MSB); +} + +static inline u8 enocean_esp3_crc8(u8 *pdata, size_t nbytes) +{ + return crc8(enocean_esp3_crc8_table, pdata, nbytes, 0x00); +} + +#define ESP3_SYNC_WORD 0x55 + +struct enocean_esp3_dispatcher { + struct list_head list; + u8 packet_type; + void (*dispatch)(const u8 *data, u16 data_len, struct enocean_esp3_dispatcher *d); +}; + +static void enocean_add_esp3_dispatcher(struct enocean_device *edev, + struct enocean_esp3_dispatcher *entry) +{ + list_add_tail_rcu(&entry->list, &edev->esp_dispatchers); +} + +static void enocean_remove_esp3_dispatcher(struct enocean_device *edev, + struct enocean_esp3_dispatcher *entry) +{ + list_del_rcu(&entry->list); +} + +struct enocean_esp3_response { + struct enocean_esp3_dispatcher disp; + + u8 code; + void *data; + u16 data_len; + + struct completion comp; +}; + +static void enocean_esp3_response_dispatch(const u8 *data, u16 data_len, + struct enocean_esp3_dispatcher *d) +{ + struct enocean_esp3_response *resp = + container_of(d, struct enocean_esp3_response, disp); + + if (completion_done(&resp->comp)) + return; + + if (data_len < 1) + return; + + resp->code = data[0]; + if (data_len > 1) { + resp->data = kzalloc(data_len - 1, GFP_KERNEL); + if (resp->data) + memcpy(resp->data, data + 1, data_len - 1); + resp->data_len = data_len - 1; + } else { + resp->data = NULL; + resp->data_len = 0; + } + + complete(&resp->comp); +} + +struct enocean_esp3_priv { + struct enocean_device *edev; + struct enocean_esp3_dispatcher radio_erp1_response; +}; + +static inline int enocean_esp3_packet_size(u16 data_len, u8 optional_len) +{ + return 1 + 4 + 1 + data_len + optional_len + 1; +} + +static int enocean_send_esp3_packet(struct enocean_device *edev, u8 packet_type, + const void *data, u16 data_len, const void *optional_data, u8 optional_len, + unsigned long timeout) +{ + int len = enocean_esp3_packet_size(data_len, optional_len); + u8 *buf; + int ret; + + buf = kzalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = ESP3_SYNC_WORD; + put_unaligned_be16(data_len, buf + 1); + buf[3] = optional_len; + buf[4] = packet_type; + buf[5] = enocean_esp3_crc8(buf + 1, 4); + dev_dbg(&edev->serdev->dev, "CRC8H = %02x\n", (unsigned int)buf[5]); + memcpy(buf + 6, data, data_len); + memcpy(buf + 6 + data_len, optional_data, optional_len); + buf[6 + data_len + optional_len] = enocean_esp3_crc8(buf + 6, data_len + optional_len); + dev_dbg(&edev->serdev->dev, "CRC8D = %02x\n", (unsigned int)buf[6 + data_len + optional_len]); + + ret = serdev_device_write(edev->serdev, buf, len, timeout); + + kfree(buf); + + if (ret < 0) + return ret; + if (ret > 0 && ret < len) + return -EIO; + return 0; +} + +#define ESP3_RADIO_ERP1 0x1 +#define ESP3_RESPONSE 0x2 +#define ESP3_COMMON_COMMAND 0x5 + +static void enocean_esp3_radio_erp1_response_dispatch(const u8 *data, u16 data_len, + struct enocean_esp3_dispatcher *d) +{ + struct enocean_esp3_priv *priv = container_of(d, struct enocean_esp3_priv, radio_erp1_response); + struct enocean_device *edev = priv->edev; + int ret; + + enocean_remove_esp3_dispatcher(edev, d); + + if (data_len < 1) + return; + + switch (data[0]) { + case 0: + enocean_esp_tx_done(edev); + break; + case 2: + ret = -ENOTSUPP; + break; + case 3: + ret = -EINVAL; + break; + case 5: + ret = -EIO; + break; + default: + ret = -EIO; + break; + } +} + +static int enocean_esp3_send_radio_erp1(struct enocean_device *edev, u32 dest, + const u8 *data, int data_len, unsigned long timeout) +{ + struct enocean_esp3_priv *priv = edev->priv; + struct esp3_radio_erp1_optional { + u8 sub_tel_num; + __be32 destination_id; + u8 dbm; + u8 security_level; + } __packed opt = { + .sub_tel_num = 3, + .destination_id = cpu_to_be32(dest), + .dbm = 0xff, + .security_level = 0, + }; + int ret; + + enocean_add_esp3_dispatcher(edev, &priv->radio_erp1_response); + + ret = enocean_send_esp3_packet(edev, ESP3_RADIO_ERP1, data, data_len, &opt, sizeof(opt), timeout); + if (ret) { + enocean_remove_esp3_dispatcher(edev, &priv->radio_erp1_response); + return ret; + } + + return 0; +} + +static int enocean_esp3_reset(struct enocean_device *edev, unsigned long timeout) +{ + struct enocean_esp3_response resp; + const u8 buf[1] = { 0x02 }; + int ret; + + init_completion(&resp.comp); + resp.disp.packet_type = ESP3_RESPONSE; + resp.disp.dispatch = enocean_esp3_response_dispatch; + enocean_add_esp3_dispatcher(edev, &resp.disp); + + ret = enocean_send_esp3_packet(edev, ESP3_COMMON_COMMAND, buf, sizeof(buf), NULL, 0, timeout); + if (ret) { + enocean_remove_esp3_dispatcher(edev, &resp.disp); + return ret; + } + + timeout = wait_for_completion_timeout(&resp.comp, timeout); + enocean_remove_esp3_dispatcher(edev, &resp.disp); + if (!timeout) + return -ETIMEDOUT; + + switch (resp.code) { + case 0: + return 0; + case 1: + return -EIO; + case 2: + return -ENOTSUPP; + default: + return -EIO; + } +} + +struct enocean_esp3_version { + u8 app_version[4]; + u8 api_version[4]; + __be32 chip_id; + __be32 chip_version; + char app_desc[16]; +} __packed; + +static int enocean_esp3_read_version(struct enocean_device *edev, unsigned long timeout) +{ + struct enocean_esp3_response resp; + struct enocean_esp3_version *ver; + const u8 buf[1] = { 0x03 }; + int ret; + + init_completion(&resp.comp); + resp.disp.packet_type = ESP3_RESPONSE; + resp.disp.dispatch = enocean_esp3_response_dispatch; + enocean_add_esp3_dispatcher(edev, &resp.disp); + + ret = enocean_send_esp3_packet(edev, ESP3_COMMON_COMMAND, buf, sizeof(buf), NULL, 0, timeout); + if (ret) { + enocean_remove_esp3_dispatcher(edev, &resp.disp); + return ret; + } + + timeout = wait_for_completion_timeout(&resp.comp, timeout); + enocean_remove_esp3_dispatcher(edev, &resp.disp); + if (!timeout) + return -ETIMEDOUT; + + switch (resp.code) { + case 0: + if (!resp.data) + return -ENOMEM; + break; + case 2: + if (resp.data) + kfree(resp.data); + return -ENOTSUPP; + default: + if (resp.data) + kfree(resp.data); + return -EIO; + } + + ver = resp.data; + ver->app_desc[15] = '\0'; + dev_info(&edev->serdev->dev, "'%s'\n", ver->app_desc); + kfree(resp.data); + + return 0; +} + +static int enocean_esp3_receive_buf(struct serdev_device *sdev, const u8 *data, size_t count) +{ + struct enocean_device *edev = serdev_device_get_drvdata(sdev); + struct enocean_esp3_dispatcher *e; + u8 crc8h, crc8d, optional_len; + u16 data_len; + + dev_dbg(&sdev->dev, "Receive (%zu)\n", count); + + if (data[0] != ESP3_SYNC_WORD) { + dev_warn(&sdev->dev, "not Sync Word (found 0x%02x), skipping\n", + (unsigned int)data[0]); + return 1; + } + + if (count < 6) + return 0; + + crc8h = enocean_esp3_crc8((u8*)data + 1, 4); + if (data[5] != crc8h) { + dev_warn(&sdev->dev, "invalid CRC8H (expected 0x%02x, found 0x%02x), skipping\n", + (unsigned int)crc8h, (unsigned int)data[5]); + return 1; + } + + data_len = be16_to_cpup((__be16 *)(data + 1)); + optional_len = data[3]; + if (count < enocean_esp3_packet_size(data_len, optional_len)) + return 0; + + crc8d = enocean_esp3_crc8((u8*)data + 6, data_len + optional_len); + if (data[6 + data_len + optional_len] != crc8d) { + dev_warn(&sdev->dev, "invalid CRC8D (expected 0x%02x, found 0x%02x), skipping\n", + (unsigned int)crc8d, (unsigned int)data[6 + data_len + optional_len]); + return 1; + } + + print_hex_dump_debug("received: ", DUMP_PREFIX_NONE, 16, 1, data, + enocean_esp3_packet_size(data_len, optional_len), false); + + list_for_each_entry_rcu(e, &edev->esp_dispatchers, list) { + if (e->packet_type == data[4]) + e->dispatch(data + 6, data_len, e); + } + + return enocean_esp3_packet_size(data_len, optional_len); +} + +const struct serdev_device_ops enocean_esp3_serdev_client_ops = { + .receive_buf = enocean_esp3_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +int enocean_esp3_send(struct enocean_device *edev, u32 dest, const void *data, int data_len) +{ + return enocean_esp3_send_radio_erp1(edev, dest, data, data_len, HZ / 10); +} + +int enocean_esp3_init(struct enocean_device *edev) +{ + struct enocean_esp3_priv *priv; + int ret; + + ret = enocean_esp3_reset(edev, HZ / 10); + if (ret) + return ret; + + msleep(100); /* XXX */ + + ret = enocean_esp3_read_version(edev, HZ / 10); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->edev = edev; + edev->priv = priv; + + priv->radio_erp1_response.packet_type = ESP3_RESPONSE; + priv->radio_erp1_response.dispatch = enocean_esp3_radio_erp1_response_dispatch; + + return 0; +} + +void enocean_esp3_cleanup(struct enocean_device *edev) +{ + struct enocean_esp3_priv *priv = edev->priv; + + kfree(priv); +} From patchwork Tue Jan 29 05:01:30 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 156923 Delivered-To: patch@linaro.org Received: by 2002:a02:48:0:0:0:0:0 with SMTP id 69csp4228833jaa; Mon, 28 Jan 2019 21:01:54 -0800 (PST) X-Google-Smtp-Source: ALg8bN4mSWHaJjltImwNzDiDxg4/t1InLHw3RJpoDvE06SAvceIDZkqjk7pIbSHE0wfaEbD98Asc X-Received: by 2002:a62:99dd:: with SMTP id t90mr24472756pfk.179.1548738114792; Mon, 28 Jan 2019 21:01:54 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1548738114; cv=none; d=google.com; s=arc-20160816; b=CJvFTN59XVKaFxvSSytCWpzHNNSQLF5MkX5Nn0gEZdoH5XNuJkwn9ySv0XJ9B3EU0g yezdVWx9nUe4hWzO52iaDBDgGOLJe0f0YXliHjKWDYCRULxE2u6u3bGJy7Wsa5VCF8yA lYHG9Qijp6yeJdkDDa/A6EMoWyOkEHq2FO/7rFvVujvc7GUx1AA7S7gphAmH2v4mdNx1 pZygFcPS0S7K6j+xRzfCpYJjDTz18B41I/JQ61qtGtM8ZqLBReBVCb38IoDwLJy6jnET 2iDM1ZEJ4/3tWUBrQSHJsTL7xkcrRM6QIOqKeCBFloA3/mAtt6BIBsU4Wpmwinb1HJ2c RGNQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=gExgHB+XjIHo4fUlysH2WDbwKGlyf73hRgvIA0A4OOE=; b=d9BoWLjLOMh0/ipzU/ARGjdInIBmwZFh4GG/nYgCcigYpqRc2jBtapIrMwgSSb9bdB sYRm1Z3nfgSY3+32iB1NfifATlPqUH9ZuWTssJnYdkOU1CYF7nJeyOXLgf1Q9rgwg9xl 0m4DaS9HQEyQTmBQXhCrTZYzMY5Tj5e7+31pcuXA9TMJSJjuW6oUpfF1CMvR2bZr2k58 ixgrWPWs7P5IVj6VQxWWUPe/FwTBTwVL4M8ThRr8y7KrfrzdnFj/W5lLtadHkJPFEnGw xxDNrFdSUbaEYjGarJbeXKY44wz1Py89Esa0q0L+4mtgdnkpOt9rj0uIPfmagHDzkX+z Qf3w== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id x61si35920825plb.303.2019.01.28.21.01.54; Mon, 28 Jan 2019 21:01:54 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726282AbfA2FBw (ORCPT + 31 others); Tue, 29 Jan 2019 00:01:52 -0500 Received: from mx2.suse.de ([195.135.220.15]:53388 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1725308AbfA2FBo (ORCPT ); Tue, 29 Jan 2019 00:01:44 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 8D09FADFF; Tue, 29 Jan 2019 05:01:42 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-lpwan@lists.infradead.org, linux-wpan@vger.kernel.org Cc: Alexander Aring , Stefan Schmidt , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, support@enocean.com, =?utf-8?q?Andreas_F=C3=A4rber?= Subject: [RFC net-next 4/4] net: enocean: Prepare ESP2 support Date: Tue, 29 Jan 2019 06:01:30 +0100 Message-Id: <20190129050130.10932-5-afaerber@suse.de> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20190129050130.10932-1-afaerber@suse.de> References: <20190129050130.10932-1-afaerber@suse.de> MIME-Version: 1.0 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org The EnOcean Serial Protocol 2 used subtelegram frames different from ESP3. It also uses ORG identifiers differing from RORG identifiers in ERP. Only checksumming has been tested. Signed-off-by: Andreas Färber --- drivers/net/enocean/Makefile | 1 + drivers/net/enocean/enocean_esp.c | 10 ++ drivers/net/enocean/enocean_esp.h | 8 ++ drivers/net/enocean/enocean_esp2.c | 276 +++++++++++++++++++++++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 drivers/net/enocean/enocean_esp2.c -- 2.16.4 diff --git a/drivers/net/enocean/Makefile b/drivers/net/enocean/Makefile index 4492e3d48c0a..f34b098997e4 100644 --- a/drivers/net/enocean/Makefile +++ b/drivers/net/enocean/Makefile @@ -3,4 +3,5 @@ enocean-dev-y := enocean.o obj-m += enocean-esp.o enocean-esp-y := enocean_esp.o +enocean-esp-y += enocean_esp2.o enocean-esp-y += enocean_esp3.o diff --git a/drivers/net/enocean/enocean_esp.c b/drivers/net/enocean/enocean_esp.c index 61bddb77762d..74720da49369 100644 --- a/drivers/net/enocean/enocean_esp.c +++ b/drivers/net/enocean/enocean_esp.c @@ -150,6 +150,15 @@ static void enocean_esp_cleanup(struct enocean_device *edev) edev->version->cleanup(edev); } +static const struct enocean_esp_version enocean_esp2 = { + .version = 2, + .baudrate = 9600, + .serdev_client_ops = &enocean_esp2_serdev_client_ops, + .init = enocean_esp2_init, + .send = enocean_esp2_send, + .cleanup = enocean_esp2_cleanup, +}; + static const struct enocean_esp_version enocean_esp3 = { .version = 3, .baudrate = 57600, @@ -160,6 +169,7 @@ static const struct enocean_esp_version enocean_esp3 = { }; static const struct of_device_id enocean_of_match[] = { + { .compatible = "enocean,esp2", .data = &enocean_esp2 }, { .compatible = "enocean,esp3", .data = &enocean_esp3 }, {} }; diff --git a/drivers/net/enocean/enocean_esp.h b/drivers/net/enocean/enocean_esp.h index e02bf5352d61..44660238043a 100644 --- a/drivers/net/enocean/enocean_esp.h +++ b/drivers/net/enocean/enocean_esp.h @@ -24,15 +24,23 @@ struct enocean_device { void *priv; }; +extern const struct serdev_device_ops enocean_esp2_serdev_client_ops; extern const struct serdev_device_ops enocean_esp3_serdev_client_ops; void enocean_esp3_crc8_populate(void); +int enocean_esp2_init(struct enocean_device *edev); int enocean_esp3_init(struct enocean_device *edev); +int enocean_esp2_send(struct enocean_device *edev, u32 dest, const void *data, int data_len); int enocean_esp3_send(struct enocean_device *edev, u32 dest, const void *data, int data_len); void enocean_esp_tx_done(struct enocean_device *edev); +void enocean_esp2_cleanup(struct enocean_device *edev); void enocean_esp3_cleanup(struct enocean_device *edev); +#define ENOCEAN_RORG_RPS 0xF6 +#define ENOCEAN_RORG_1BS 0xD5 +#define ENOCEAN_RORG_4BS 0xA5 + #endif diff --git a/drivers/net/enocean/enocean_esp2.c b/drivers/net/enocean/enocean_esp2.c new file mode 100644 index 000000000000..d4cb787de22e --- /dev/null +++ b/drivers/net/enocean/enocean_esp2.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EnOcean Serial Protocol 2 + * + * Copyright (c) 2019 Andreas Färber + */ + +#include + +#include "enocean_esp.h" + +#define ESP2_SYNC_BYTE1 0xA5 +#define ESP2_SYNC_BYTE0 0x5A + +struct enocean_esp2_packet { + u8 sync[2]; +#ifdef CONFIG_CPU_BIG_ENDIAN + u8 h_seq:3; + u8 length:5; +#else + u8 length:5; + u8 h_seq:3; +#endif + u8 org; + u8 data[4]; + u8 id[4]; + u8 status; + u8 checksum; +} __packed; + +#define ESP2_H_SEQ_TRT 0x3 +#define ESP2_H_SEQ_RCT 0x4 +#define ESP2_H_SEQ_TCT 0x5 + +#define ESP2_TELEGRAM_RESET 0x0A + +#define ENOCEAN_ORG_RPS 0x05 +#define ENOCEAN_ORG_1BS 0x06 +#define ENOCEAN_ORG_4BS 0x07 + +/* Cf. EnOcean Equipment Profiles, Telegram Types (RORG) */ +static inline u8 enocean_org_to_rorg(u8 org) +{ + switch (org) { + case ENOCEAN_ORG_RPS: + return ENOCEAN_RORG_RPS; + case ENOCEAN_ORG_1BS: + return ENOCEAN_RORG_1BS; + case ENOCEAN_ORG_4BS: + return ENOCEAN_RORG_4BS; + default: + return org; + } +} + +static u8 enocean_rorg_to_org(u8 rorg) +{ + switch (rorg) { + case ENOCEAN_RORG_RPS: + return ENOCEAN_ORG_RPS; + case ENOCEAN_RORG_1BS: + return ENOCEAN_ORG_1BS; + case ENOCEAN_RORG_4BS: + return ENOCEAN_ORG_4BS; + default: + return rorg; + } +} + +struct enocean_esp2_dispatcher { + struct list_head list; + u8 h_seq; + void (*dispatch)(const u8 *data, u8 data_len, struct enocean_esp2_dispatcher *d); +}; + +static void enocean_add_esp2_dispatcher(struct enocean_device *edev, + struct enocean_esp2_dispatcher *entry) +{ + list_add_tail_rcu(&entry->list, &edev->esp_dispatchers); +} + +static void enocean_remove_esp2_dispatcher(struct enocean_device *edev, + struct enocean_esp2_dispatcher *entry) +{ + list_del_rcu(&entry->list); +} + +#define ESP2_RESPONSE_ERR_SYNTAX_H_SEQ 0x08 +#define ESP2_RESPONSE_ERR_SYNTAX_LENGTH 0x09 +#define ESP2_RESPONSE_ERR_SYNTAX_CHKSUM 0x0A +#define ESP2_RESPONSE_ERR_SYNTAX_ORG 0x0B +#define ESP2_RESPONSE_ERR 0x19 +#define ESP2_RESPONSE_ERR_IDRANGE 0x1A +#define ESP2_RESPONSE_ERR_TX_IDRANGE 0x22 +#define ESP2_RESPONSE_OK 0x58 + +struct enocean_esp2_priv { + struct enocean_device *edev; + struct enocean_esp2_dispatcher tx_telegram_response; +}; + +static u8 enocean_esp2_checksum(const u8 *data, int len) +{ + u8 chksum = 0; + int i; + + for (i = 0; i < len; i++) { + chksum += data[i]; + } + return chksum; +} + +static int enocean_esp2_send_telegram(struct enocean_device *edev, u8 h_seq, u8 org, + const u8 *data, int data_len, unsigned long timeout) +{ + struct enocean_esp2_packet pkt; + int ret; + + memset(&pkt, 0, sizeof(pkt)); + pkt.sync[0] = ESP2_SYNC_BYTE1; + pkt.sync[1] = ESP2_SYNC_BYTE0; + pkt.h_seq = h_seq; + pkt.length = 11; + dev_dbg(&edev->serdev->dev, "H_SEQ | LENGTH = %02x\n", (unsigned int)(((u8*)&pkt)[2])); + pkt.org = org; + if (data_len > 0) + memcpy(pkt.data, data, min(data_len, 9)); + pkt.checksum = enocean_esp2_checksum(((u8 *)&pkt) + 2, sizeof(pkt) - 2 - 1); + dev_dbg(&edev->serdev->dev, "checksum = %02x\n", (unsigned int)pkt.checksum); + + ret = serdev_device_write(edev->serdev, (const u8 *)&pkt, sizeof(pkt), timeout); + if (ret < 0) + return ret; + if (ret > 0 && ret < sizeof(pkt)) + return -EIO; + return 0; +} + +static void enocean_esp2_tx_telegram_response_dispatch(const u8 *data, u8 data_len, + struct enocean_esp2_dispatcher *d) +{ + struct enocean_esp2_priv *priv = container_of(d, struct enocean_esp2_priv, tx_telegram_response); + struct enocean_device *edev = priv->edev; + + enocean_remove_esp2_dispatcher(edev, d); + + if (data_len < 1) + return; + + switch (data[0]) { + case ESP2_RESPONSE_OK: + enocean_esp_tx_done(edev); + break; + case ESP2_RESPONSE_ERR: + case ESP2_RESPONSE_ERR_TX_IDRANGE: + default: + break; + } +} + +static int enocean_esp2_tx_telegram(struct enocean_device *edev, u8 org, + const u8 *data, int data_len, unsigned long timeout) +{ + struct enocean_esp2_priv *priv = edev->priv; + int ret; + + enocean_add_esp2_dispatcher(edev, &priv->tx_telegram_response); + + ret = enocean_esp2_send_telegram(edev, ESP2_H_SEQ_TRT, + org, data, data_len, timeout); + if (ret) { + enocean_remove_esp2_dispatcher(edev, &priv->tx_telegram_response); + return ret; + } + + return 0; +} + +static int enocean_esp2_reset(struct enocean_device *edev, unsigned long timeout) +{ + return enocean_esp2_send_telegram(edev, ESP2_H_SEQ_TCT, ESP2_TELEGRAM_RESET, NULL, 0, timeout); + /* no RCT */ +} + +static int enocean_esp2_receive_buf(struct serdev_device *sdev, const u8 *data, size_t count) +{ + struct enocean_device *edev = serdev_device_get_drvdata(sdev); + struct enocean_esp2_dispatcher *e; + u8 h_seq, length, chksum; + + dev_dbg(&sdev->dev, "Receive (%zu)\n", count); + + if (data[0] != ESP2_SYNC_BYTE1) { + dev_warn(&sdev->dev, "not first Sync Byte (found 0x%02x), skipping\n", + (unsigned int)data[0]); + return 1; + } + + if (count < 2) + return 0; + + if (data[1] != ESP2_SYNC_BYTE0) { + dev_warn(&sdev->dev, "not second Sync Byte (found 0x%02x 0x%02x), skipping\n", + ESP2_SYNC_BYTE1, (unsigned int)data[1]); + return 1; + } + + if (count < 3) + return 0; + + h_seq = data[2] >> 5; + length = data[2] & 0x1f; + + if (count < 3 + length) + return 0; + + chksum = enocean_esp2_checksum(data + 2, 1 + length - 1); + if (data[3 + length - 1] != chksum) { + dev_warn(&sdev->dev, "invalid checksum (expected 0x%02x, found %02x), skipping\n", + (unsigned int)chksum, (unsigned int)data[3 + length - 1]); + return 2; /* valid second Sync Byte is not a valid first Sync Byte */ + } + + print_hex_dump_bytes("received: ", DUMP_PREFIX_OFFSET, data, 3 + length); + + list_for_each_entry_rcu(e, &edev->esp_dispatchers, list) { + if (e->h_seq == h_seq) + e->dispatch(data + 3, length, e); + } + + return 3 + length; +} + +const struct serdev_device_ops enocean_esp2_serdev_client_ops = { + .receive_buf = enocean_esp2_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +int enocean_esp2_send(struct enocean_device *edev, u32 dest, const void *data, int data_len) +{ + const u8 *buf = data; + + return enocean_esp2_tx_telegram(edev, + enocean_rorg_to_org(buf[0]), buf + 1, data_len - 1, HZ); +} + +int enocean_esp2_init(struct enocean_device *edev) +{ + struct enocean_esp2_priv *priv; + int ret; + + ret = enocean_esp2_reset(edev, HZ); + if (ret) + return ret; + + msleep(100); /* XXX */ + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->edev = edev; + edev->priv = priv; + + priv->tx_telegram_response.h_seq = ESP2_H_SEQ_RCT; + priv->tx_telegram_response.dispatch = enocean_esp2_tx_telegram_response_dispatch; + + return 0; +} + +void enocean_esp2_cleanup(struct enocean_device *edev) +{ + struct enocean_esp2_priv *priv = edev->priv; + + kfree(priv); +}