// SPDX-License-Identifier: GPL-2.0
/*
 * Intel / Lantiq GSWIP V2.0 PMAC tag support
 *
 * Copyright (C) 2017 - 2018 Hauke Mehrtens <hauke@hauke-m.de>
 */

#include <linux/bitops.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <net/dsa.h>

#include "dsa_priv.h"

#define GSWIP_TX_HEADER_LEN		4

/* special tag in TX path header */
/* Byte 0 */
#define GSWIP_TX_SLPID_SHIFT		0	/* source port ID */
#define  GSWIP_TX_SLPID_CPU		2
#define  GSWIP_TX_SLPID_APP1		3
#define  GSWIP_TX_SLPID_APP2		4
#define  GSWIP_TX_SLPID_APP3		5
#define  GSWIP_TX_SLPID_APP4		6
#define  GSWIP_TX_SLPID_APP5		7

/* Byte 1 */
#define GSWIP_TX_CRCGEN_DIS		BIT(7)
#define GSWIP_TX_DPID_SHIFT		0	/* destination group ID */
#define  GSWIP_TX_DPID_ELAN		0
#define  GSWIP_TX_DPID_EWAN		1
#define  GSWIP_TX_DPID_CPU		2
#define  GSWIP_TX_DPID_APP1		3
#define  GSWIP_TX_DPID_APP2		4
#define  GSWIP_TX_DPID_APP3		5
#define  GSWIP_TX_DPID_APP4		6
#define  GSWIP_TX_DPID_APP5		7

/* Byte 2 */
#define GSWIP_TX_PORT_MAP_EN		BIT(7)
#define GSWIP_TX_PORT_MAP_SEL		BIT(6)
#define GSWIP_TX_LRN_DIS		BIT(5)
#define GSWIP_TX_CLASS_EN		BIT(4)
#define GSWIP_TX_CLASS_SHIFT		0
#define GSWIP_TX_CLASS_MASK		GENMASK(3, 0)

/* Byte 3 */
#define GSWIP_TX_DPID_EN		BIT(0)
#define GSWIP_TX_PORT_MAP_SHIFT		1
#define GSWIP_TX_PORT_MAP_MASK		GENMASK(6, 1)

#define GSWIP_RX_HEADER_LEN	8

/* special tag in RX path header */
/* Byte 7 */
#define GSWIP_RX_SPPID_SHIFT		4
#define GSWIP_RX_SPPID_MASK		GENMASK(6, 4)

static struct sk_buff *gswip_tag_xmit(struct sk_buff *skb,
				      struct net_device *dev)
{
	struct dsa_port *dp = dsa_slave_to_port(dev);
	int err;
	u8 *gswip_tag;

	err = skb_cow_head(skb, GSWIP_TX_HEADER_LEN);
	if (err)
		return NULL;

	skb_push(skb, GSWIP_TX_HEADER_LEN);

	gswip_tag = skb->data;
	gswip_tag[0] = GSWIP_TX_SLPID_CPU;
	gswip_tag[1] = GSWIP_TX_DPID_ELAN;
	gswip_tag[2] = GSWIP_TX_PORT_MAP_EN | GSWIP_TX_PORT_MAP_SEL;
	gswip_tag[3] = BIT(dp->index + GSWIP_TX_PORT_MAP_SHIFT) & GSWIP_TX_PORT_MAP_MASK;
	gswip_tag[3] |= GSWIP_TX_DPID_EN;

	return skb;
}

static struct sk_buff *gswip_tag_rcv(struct sk_buff *skb,
				     struct net_device *dev,
				     struct packet_type *pt)
{
	int port;
	u8 *gswip_tag;

	if (unlikely(!pskb_may_pull(skb, GSWIP_RX_HEADER_LEN)))
		return NULL;

	gswip_tag = skb->data - ETH_HLEN;

	/* Get source port information */
	port = (gswip_tag[7] & GSWIP_RX_SPPID_MASK) >> GSWIP_RX_SPPID_SHIFT;
	skb->dev = dsa_master_find_slave(dev, 0, port);
	if (!skb->dev)
		return NULL;

	/* remove GSWIP tag */
	skb_pull_rcsum(skb, GSWIP_RX_HEADER_LEN);

	return skb;
}

static const struct dsa_device_ops gswip_netdev_ops = {
	.name = "gswip",
	.proto	= DSA_TAG_PROTO_GSWIP,
	.xmit = gswip_tag_xmit,
	.rcv = gswip_tag_rcv,
	.overhead = GSWIP_RX_HEADER_LEN,
};

MODULE_LICENSE("GPL");
MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_GSWIP);

module_dsa_tag_driver(gswip_netdev_ops);
