Commit 4b20a07e authored by Oleksij Rempel's avatar Oleksij Rempel Committed by David S. Miller
Browse files

net: dsa: microchip: ksz8795: add support for ksz88xx chips



We add support for the ksz8863 and ksz8873 chips which are
using the same register patterns but other offsets as the
ksz8795.

Signed-off-by: default avatarMichael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: default avatarOleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 9f73e112
Loading
Loading
Loading
Loading
+262 −49
Original line number Diff line number Diff line
@@ -72,9 +72,60 @@ static const u8 ksz8795_shifts[] = {
	[DYNAMIC_MAC_SRC_PORT]		= 24,
};

static const struct {
static const u8 ksz8863_regs[] = {
	[REG_IND_CTRL_0]		= 0x79,
	[REG_IND_DATA_8]		= 0x7B,
	[REG_IND_DATA_CHECK]		= 0x7B,
	[REG_IND_DATA_HI]		= 0x7C,
	[REG_IND_DATA_LO]		= 0x80,
	[REG_IND_MIB_CHECK]		= 0x80,
	[P_FORCE_CTRL]			= 0x0C,
	[P_LINK_STATUS]			= 0x0E,
	[P_LOCAL_CTRL]			= 0x0C,
	[P_NEG_RESTART_CTRL]		= 0x0D,
	[P_REMOTE_STATUS]		= 0x0E,
	[P_SPEED_STATUS]		= 0x0F,
	[S_TAIL_TAG_CTRL]		= 0x03,
};

static const u32 ksz8863_masks[] = {
	[PORT_802_1P_REMAPPING]		= BIT(3),
	[SW_TAIL_TAG_ENABLE]		= BIT(6),
	[MIB_COUNTER_OVERFLOW]		= BIT(7),
	[MIB_COUNTER_VALID]		= BIT(6),
	[VLAN_TABLE_FID]		= GENMASK(15, 12),
	[VLAN_TABLE_MEMBERSHIP]		= GENMASK(18, 16),
	[VLAN_TABLE_VALID]		= BIT(19),
	[STATIC_MAC_TABLE_VALID]	= BIT(19),
	[STATIC_MAC_TABLE_USE_FID]	= BIT(21),
	[STATIC_MAC_TABLE_FID]		= GENMASK(29, 26),
	[STATIC_MAC_TABLE_OVERRIDE]	= BIT(20),
	[STATIC_MAC_TABLE_FWD_PORTS]	= GENMASK(18, 16),
	[DYNAMIC_MAC_TABLE_ENTRIES_H]	= GENMASK(5, 0),
	[DYNAMIC_MAC_TABLE_MAC_EMPTY]	= BIT(7),
	[DYNAMIC_MAC_TABLE_NOT_READY]	= BIT(7),
	[DYNAMIC_MAC_TABLE_ENTRIES]	= GENMASK(31, 28),
	[DYNAMIC_MAC_TABLE_FID]		= GENMASK(19, 16),
	[DYNAMIC_MAC_TABLE_SRC_PORT]	= GENMASK(21, 20),
	[DYNAMIC_MAC_TABLE_TIMESTAMP]	= GENMASK(23, 22),
};

static u8 ksz8863_shifts[] = {
	[VLAN_TABLE_MEMBERSHIP_S]	= 16,
	[STATIC_MAC_FWD_PORTS]		= 16,
	[STATIC_MAC_FID]		= 22,
	[DYNAMIC_MAC_ENTRIES_H]		= 3,
	[DYNAMIC_MAC_ENTRIES]		= 24,
	[DYNAMIC_MAC_FID]		= 16,
	[DYNAMIC_MAC_TIMESTAMP]		= 24,
	[DYNAMIC_MAC_SRC_PORT]		= 20,
};

struct mib_names {
	char string[ETH_GSTRING_LEN];
} mib_names[] = {
};

static const struct mib_names ksz87xx_mib_names[] = {
	{ "rx_hi" },
	{ "rx_undersize" },
	{ "rx_fragments" },
@@ -113,6 +164,48 @@ static const struct {
	{ "tx_discards" },
};

static const struct mib_names ksz88xx_mib_names[] = {
	{ "rx" },
	{ "rx_hi" },
	{ "rx_undersize" },
	{ "rx_fragments" },
	{ "rx_oversize" },
	{ "rx_jabbers" },
	{ "rx_symbol_err" },
	{ "rx_crc_err" },
	{ "rx_align_err" },
	{ "rx_mac_ctrl" },
	{ "rx_pause" },
	{ "rx_bcast" },
	{ "rx_mcast" },
	{ "rx_ucast" },
	{ "rx_64_or_less" },
	{ "rx_65_127" },
	{ "rx_128_255" },
	{ "rx_256_511" },
	{ "rx_512_1023" },
	{ "rx_1024_1522" },
	{ "tx" },
	{ "tx_hi" },
	{ "tx_late_col" },
	{ "tx_pause" },
	{ "tx_bcast" },
	{ "tx_mcast" },
	{ "tx_ucast" },
	{ "tx_deferred" },
	{ "tx_total_col" },
	{ "tx_exc_col" },
	{ "tx_single_col" },
	{ "tx_mult_col" },
	{ "rx_discards" },
	{ "tx_discards" },
};

static bool ksz_is_ksz88x3(struct ksz_device *dev)
{
	return dev->chip_id == 0x8830;
}

static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set)
{
	regmap_update_bits(dev->regmap[0], addr, bits, set ? bits : 0);
@@ -127,10 +220,18 @@ static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits,

static int ksz8_reset_switch(struct ksz_device *dev)
{
	if (ksz_is_ksz88x3(dev)) {
		/* reset switch */
		ksz_cfg(dev, KSZ8863_REG_SW_RESET,
			KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, true);
		ksz_cfg(dev, KSZ8863_REG_SW_RESET,
			KSZ8863_GLOBAL_SOFTWARE_RESET | KSZ8863_PCS_RESET, false);
	} else {
		/* reset switch */
		ksz_write8(dev, REG_POWER_MANAGEMENT_1,
			   SW_SOFTWARE_POWER_DOWN << SW_POWER_MANAGEMENT_MODE_S);
		ksz_write8(dev, REG_POWER_MANAGEMENT_1, 0);
	}

	return 0;
}
@@ -204,7 +305,7 @@ static void ksz8_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt)
	mutex_unlock(&dev->alu_mutex);
}

static void ksz8_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
static void ksz8795_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
			      u64 *dropped, u64 *cnt)
{
	struct ksz8 *ksz8 = dev->priv;
@@ -219,8 +320,8 @@ static void ksz8_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
	regs = ksz8->regs;

	addr -= dev->reg_mib_cnt;
	ctrl_addr = (KS_MIB_TOTAL_RX_1 - KS_MIB_TOTAL_RX_0) * port;
	ctrl_addr += addr + KS_MIB_TOTAL_RX_0;
	ctrl_addr = (KSZ8795_MIB_TOTAL_RX_1 - KSZ8795_MIB_TOTAL_RX_0) * port;
	ctrl_addr += addr + KSZ8795_MIB_TOTAL_RX_0;
	ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ);

	mutex_lock(&dev->alu_mutex);
@@ -257,8 +358,52 @@ static void ksz8_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
	mutex_unlock(&dev->alu_mutex);
}

static void ksz8863_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
			      u64 *dropped, u64 *cnt)
{
	struct ksz8 *ksz8 = dev->priv;
	const u8 *regs = ksz8->regs;
	u32 *last = (u32 *)dropped;
	u16 ctrl_addr;
	u32 data;
	u32 cur;

	addr -= dev->reg_mib_cnt;
	ctrl_addr = addr ? KSZ8863_MIB_PACKET_DROPPED_TX_0 :
			   KSZ8863_MIB_PACKET_DROPPED_RX_0;
	ctrl_addr += port;
	ctrl_addr |= IND_ACC_TABLE(TABLE_MIB | TABLE_READ);

	mutex_lock(&dev->alu_mutex);
	ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr);
	ksz_read32(dev, regs[REG_IND_DATA_LO], &data);
	mutex_unlock(&dev->alu_mutex);

	data &= MIB_PACKET_DROPPED;
	cur = last[addr];
	if (data != cur) {
		last[addr] = data;
		if (data < cur)
			data += MIB_PACKET_DROPPED + 1;
		data -= cur;
		*cnt += data;
	}
}

static void ksz8_r_mib_pkt(struct ksz_device *dev, int port, u16 addr,
			   u64 *dropped, u64 *cnt)
{
	if (ksz_is_ksz88x3(dev))
		ksz8863_r_mib_pkt(dev, port, addr, dropped, cnt);
	else
		ksz8795_r_mib_pkt(dev, port, addr, dropped, cnt);
}

static void ksz8_freeze_mib(struct ksz_device *dev, int port, bool freeze)
{
	if (ksz_is_ksz88x3(dev))
		return;

	/* enable the port for flush/freeze function */
	if (freeze)
		ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), true);
@@ -272,11 +417,14 @@ static void ksz8_freeze_mib(struct ksz_device *dev, int port, bool freeze)
static void ksz8_port_init_cnt(struct ksz_device *dev, int port)
{
	struct ksz_port_mib *mib = &dev->ports[port].mib;
	u64 *dropped;

	if (!ksz_is_ksz88x3(dev)) {
		/* flush all enabled port MIB counters */
		ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), true);
		ksz_cfg(dev, REG_SW_CTRL_6, SW_MIB_COUNTER_FLUSH, true);
		ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), false);
	}

	mib->cnt_ptr = 0;

@@ -287,10 +435,13 @@ static void ksz8_port_init_cnt(struct ksz_device *dev, int port)
		++mib->cnt_ptr;
	}

	/* last one in storage */
	dropped = &mib->counters[dev->mib_cnt];

	/* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */
	while (mib->cnt_ptr < dev->mib_cnt) {
		dev->dev_ops->r_mib_pkt(dev, port, mib->cnt_ptr,
					NULL, &mib->counters[mib->cnt_ptr]);
					dropped, &mib->counters[mib->cnt_ptr]);
		++mib->cnt_ptr;
	}
	mib->cnt_ptr = 0;
@@ -588,8 +739,13 @@ static void ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
			data |= PHY_LOOPBACK;
		if (ctrl & PORT_FORCE_100_MBIT)
			data |= PHY_SPEED_100MBIT;
		if (ksz_is_ksz88x3(dev)) {
			if ((ctrl & PORT_AUTO_NEG_ENABLE))
				data |= PHY_AUTO_NEG_ENABLE;
		} else {
			if (!(ctrl & PORT_AUTO_NEG_DISABLE))
				data |= PHY_AUTO_NEG_ENABLE;
		}
		if (restart & PORT_POWER_DOWN)
			data |= PHY_POWER_DOWN;
		if (restart & PORT_AUTO_NEG_RESTART)
@@ -623,6 +779,9 @@ static void ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
		data = KSZ8795_ID_HI;
		break;
	case PHY_REG_ID_2:
		if (ksz_is_ksz88x3(dev))
			data = KSZ8863_ID_LO;
		else
			data = KSZ8795_ID_LO;
		break;
	case PHY_REG_AUTO_NEGOTIATION:
@@ -686,6 +845,12 @@ static void ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
			ksz_pwrite8(dev, p, regs[P_SPEED_STATUS], data);
		ksz_pread8(dev, p, regs[P_FORCE_CTRL], &ctrl);
		data = ctrl;
		if (ksz_is_ksz88x3(dev)) {
			if ((val & PHY_AUTO_NEG_ENABLE))
				data |= PORT_AUTO_NEG_ENABLE;
			else
				data &= ~PORT_AUTO_NEG_ENABLE;
		} else {
			if (!(val & PHY_AUTO_NEG_ENABLE))
				data |= PORT_AUTO_NEG_DISABLE;
			else
@@ -694,6 +859,8 @@ static void ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
			/* Fiber port does not support auto-negotiation. */
			if (dev->ports[p].fiber)
				data |= PORT_AUTO_NEG_DISABLE;
		}

		if (val & PHY_SPEED_100MBIT)
			data |= PORT_FORCE_100_MBIT;
		else
@@ -767,7 +934,11 @@ static enum dsa_tag_protocol ksz8_get_tag_protocol(struct dsa_switch *ds,
						   int port,
						   enum dsa_tag_protocol mp)
{
	return DSA_TAG_PROTO_KSZ8795;
	struct ksz_device *dev = ds->priv;

	/* ksz88x3 uses the same tag schema as KSZ9893 */
	return ksz_is_ksz88x3(dev) ?
		DSA_TAG_PROTO_KSZ9893 : DSA_TAG_PROTO_KSZ8795;
}

static void ksz8_get_strings(struct dsa_switch *ds, int port,
@@ -777,8 +948,8 @@ static void ksz8_get_strings(struct dsa_switch *ds, int port,
	int i;

	for (i = 0; i < dev->mib_cnt; i++) {
		memcpy(buf + i * ETH_GSTRING_LEN, mib_names[i].string,
		       ETH_GSTRING_LEN);
		memcpy(buf + i * ETH_GSTRING_LEN,
		       dev->mib_names[i].string, ETH_GSTRING_LEN);
	}
}

@@ -904,6 +1075,9 @@ static int ksz8_port_vlan_filtering(struct dsa_switch *ds, int port, bool flag,
{
	struct ksz_device *dev = ds->priv;

	if (ksz_is_ksz88x3(dev))
		return -ENOTSUPP;

	ksz_cfg(dev, S_MIRROR_CTRL, SW_VLAN_ENABLE, flag);

	return 0;
@@ -918,6 +1092,9 @@ static int ksz8_port_vlan_add(struct dsa_switch *ds, int port,
	u16 data, new_pvid = 0;
	u8 fid, member, valid;

	if (ksz_is_ksz88x3(dev))
		return -ENOTSUPP;

	ksz_port_cfg(dev, port, P_TAG_CTRL, PORT_REMOVE_TAG, untagged);

	ksz8_r_vlan_table(dev, vlan->vid, &data);
@@ -958,6 +1135,9 @@ static int ksz8_port_vlan_del(struct dsa_switch *ds, int port,
	u16 data, pvid, new_pvid = 0;
	u8 fid, member, valid;

	if (ksz_is_ksz88x3(dev))
		return -ENOTSUPP;

	ksz_pread16(dev, port, REG_PORT_CTRL_VID, &pvid);
	pvid = pvid & 0xFFF;

@@ -1091,6 +1271,7 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
	/* enable broadcast storm limit */
	ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true);

	if (!ksz_is_ksz88x3(dev))
		ksz8795_set_prio_queue(dev, port, 4);

	/* disable DiffServ priority */
@@ -1104,6 +1285,7 @@ static void ksz8_port_setup(struct ksz_device *dev, int port, bool cpu_port)
	ksz_port_cfg(dev, port, P_PRIO_CTRL, PORT_802_1P_ENABLE, true);

	if (cpu_port) {
		if (!ksz_is_ksz88x3(dev))
			ksz8795_cpu_interface_select(dev, port);

		member = dev->port_mask;
@@ -1156,9 +1338,11 @@ static void ksz8_config_cpu_port(struct dsa_switch *ds)
		p = &dev->ports[i];
		if (!p->on)
			continue;
		if (!ksz_is_ksz88x3(dev)) {
			ksz_pread8(dev, i, regs[P_REMOTE_STATUS], &remote);
			if (remote & PORT_FIBER_MODE)
				p->fiber = 1;
		}
		if (p->fiber)
			ksz_port_cfg(dev, i, P_STP_CTRL, PORT_FORCE_FLOW_CTRL,
				     true);
@@ -1277,20 +1461,31 @@ static int ksz8_switch_detect(struct ksz_device *dev)

	id1 = id16 >> 8;
	id2 = id16 & SW_CHIP_ID_M;
	if (id1 != FAMILY_ID ||
	    (id2 != CHIP_ID_94 && id2 != CHIP_ID_95))

	switch (id1) {
	case KSZ87_FAMILY_ID:
		if ((id2 != CHIP_ID_94 && id2 != CHIP_ID_95))
			return -ENODEV;

		if (id2 == CHIP_ID_95) {
			u8 val;

			id2 = 0x95;
		ksz_read8(dev, REG_PORT_1_STATUS_0, &val);
			ksz_read8(dev, REG_PORT_STATUS_0, &val);
			if (val & PORT_FIBER_MODE)
				id2 = 0x65;
		} else if (id2 == CHIP_ID_94) {
			id2 = 0x94;
		}
		break;
	case KSZ88_FAMILY_ID:
		if (id2 != CHIP_ID_63)
			return -ENODEV;
		break;
	default:
		dev_err(dev->dev, "invalid family id: %d\n", id1);
		return -ENODEV;
	}
	id16 &= ~0xff;
	id16 |= id2;
	dev->chip_id = id16;
@@ -1350,6 +1545,15 @@ static const struct ksz_chip_data ksz8_switch_chips[] = {
		.cpu_ports = 0x10,	/* can be configured as cpu port */
		.port_cnt = 5,		/* total cpu and user ports */
	},
	{
		.chip_id = 0x8830,
		.dev_name = "KSZ8863/KSZ8873",
		.num_vlans = 16,
		.num_alus = 0,
		.num_statics = 8,
		.cpu_ports = 0x4,	/* can be configured as cpu port */
		.port_cnt = 3,
	},
};

static int ksz8_switch_init(struct ksz_device *dev)
@@ -1382,12 +1586,21 @@ static int ksz8_switch_init(struct ksz_device *dev)
	if (!dev->cpu_ports)
		return -ENODEV;

	if (ksz_is_ksz88x3(dev)) {
		ksz8->regs = ksz8863_regs;
		ksz8->masks = ksz8863_masks;
		ksz8->shifts = ksz8863_shifts;
		dev->mib_cnt = ARRAY_SIZE(ksz88xx_mib_names);
		dev->mib_names = ksz88xx_mib_names;
	} else {
		ksz8->regs = ksz8795_regs;
		ksz8->masks = ksz8795_masks;
		ksz8->shifts = ksz8795_shifts;
		dev->mib_cnt = ARRAY_SIZE(ksz87xx_mib_names);
		dev->mib_names = ksz87xx_mib_names;
	}

	dev->reg_mib_cnt = KSZ8795_COUNTER_NUM;
	dev->mib_cnt = ARRAY_SIZE(mib_names);
	dev->reg_mib_cnt = MIB_COUNTER_NUM;

	dev->ports = devm_kzalloc(dev->dev,
				  dev->port_cnt * sizeof(struct ksz_port),
+18 −22
Original line number Diff line number Diff line
@@ -16,7 +16,8 @@

#define REG_CHIP_ID0			0x00

#define FAMILY_ID			0x87
#define KSZ87_FAMILY_ID			0x87
#define KSZ88_FAMILY_ID			0x88

#define REG_CHIP_ID1			0x01

@@ -28,6 +29,12 @@

#define CHIP_ID_94			0x60
#define CHIP_ID_95			0x90
#define CHIP_ID_63			0x30

#define KSZ8863_REG_SW_RESET		0x43

#define KSZ8863_GLOBAL_SOFTWARE_RESET	BIT(4)
#define KSZ8863_PCS_RESET		BIT(0)

#define REG_SW_CTRL_0			0x02

@@ -267,6 +274,7 @@
#define REG_PORT_3_CTRL_9		0x3C
#define REG_PORT_4_CTRL_9		0x4C

#define PORT_AUTO_NEG_ENABLE		BIT(7)
#define PORT_AUTO_NEG_DISABLE		BIT(7)
#define PORT_FORCE_100_MBIT		BIT(6)
#define PORT_FORCE_FULL_DUPLEX		BIT(5)
@@ -800,6 +808,7 @@

#define KSZ8795_ID_HI			0x0022
#define KSZ8795_ID_LO			0x1550
#define KSZ8863_ID_LO			0x1430

#define KSZ8795_SW_ID			0x8795

@@ -830,7 +839,7 @@

#define KS_PRIO_IN_REG			4

#define KSZ8795_COUNTER_NUM		0x20
#define MIB_COUNTER_NUM		0x20

/* Common names used by other drivers */

@@ -876,26 +885,13 @@

#define MIB_COUNTER_VALUE		0x3FFFFFFF

#define KS_MIB_TOTAL_RX_0		0x100
#define KS_MIB_TOTAL_TX_0		0x101
#define KS_MIB_PACKET_DROPPED_RX_0	0x102
#define KS_MIB_PACKET_DROPPED_TX_0	0x103
#define KS_MIB_TOTAL_RX_1		0x104
#define KS_MIB_TOTAL_TX_1		0x105
#define KS_MIB_PACKET_DROPPED_TX_1	0x106
#define KS_MIB_PACKET_DROPPED_RX_1	0x107
#define KS_MIB_TOTAL_RX_2		0x108
#define KS_MIB_TOTAL_TX_2		0x109
#define KS_MIB_PACKET_DROPPED_TX_2	0x10A
#define KS_MIB_PACKET_DROPPED_RX_2	0x10B
#define KS_MIB_TOTAL_RX_3		0x10C
#define KS_MIB_TOTAL_TX_3		0x10D
#define KS_MIB_PACKET_DROPPED_TX_3	0x10E
#define KS_MIB_PACKET_DROPPED_RX_3	0x10F
#define KS_MIB_TOTAL_RX_4		0x110
#define KS_MIB_TOTAL_TX_4		0x111
#define KS_MIB_PACKET_DROPPED_TX_4	0x112
#define KS_MIB_PACKET_DROPPED_RX_4	0x113
#define KSZ8795_MIB_TOTAL_RX_0		0x100
#define KSZ8795_MIB_TOTAL_TX_0		0x101
#define KSZ8795_MIB_TOTAL_RX_1		0x104
#define KSZ8795_MIB_TOTAL_TX_1		0x105

#define KSZ8863_MIB_PACKET_DROPPED_TX_0 0x100
#define KSZ8863_MIB_PACKET_DROPPED_RX_0 0x105

#define MIB_PACKET_DROPPED		0x0000FFFF

+1 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ struct ksz_device {
	int port_cnt;
	int reg_mib_cnt;
	int mib_cnt;
	const struct mib_names *mib_names;
	phy_interface_t compat_interface;
	u32 regs_size;
	bool phy_errata_9477;