All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Andreas Färber" <afaerber@suse.de>
To: linux-lpwan@lists.infradead.org, linux-wpan@vger.kernel.org
Cc: "Alexander Aring" <alex.aring@gmail.com>,
	"Stefan Schmidt" <stefan@datenfreihafen.org>,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	support@enocean.com, "Andreas Färber" <afaerber@suse.de>
Subject: [RFC net-next 4/4] net: enocean: Prepare ESP2 support
Date: Tue, 29 Jan 2019 06:01:30 +0100	[thread overview]
Message-ID: <20190129050130.10932-5-afaerber@suse.de> (raw)
In-Reply-To: <20190129050130.10932-1-afaerber@suse.de>

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 <afaerber@suse.de>
---
 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

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 <linux/serdev.h>
+
+#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);
+}
-- 
2.16.4


      parent reply	other threads:[~2019-01-29  5:01 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-01-29  5:01 [RFC net-next 0/4] net: EnOcean prototype driver Andreas Färber
2019-01-29  5:01 ` [RFC net-next 1/4] net: Reserve protocol identifiers for EnOcean Andreas Färber
2019-01-29 12:57   ` Alexander Aring
2019-01-30  1:42     ` Andreas Färber
2019-02-01  0:58       ` Alexander Aring
2019-02-18  4:41         ` Andreas Färber
2019-01-29  5:01 ` [RFC net-next 2/4] net: Prepare EnOcean device drivers Andreas Färber
2019-01-29  5:01 ` [RFC net-next 3/4] net: enocean: Add ESP3 driver Andreas Färber
2019-01-29  5:01 ` Andreas Färber [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190129050130.10932-5-afaerber@suse.de \
    --to=afaerber@suse.de \
    --cc=alex.aring@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-lpwan@lists.infradead.org \
    --cc=linux-wpan@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=stefan@datenfreihafen.org \
    --cc=support@enocean.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.