diff mbox series

[2/4] phy: Realtek Otto SerDes: add driver source

Message ID 20241004195712.1891488-3-markus.stockhausen@gmx.de
State Superseded
Headers show
Series phy: Realtek Otto SerDes: add new driver | expand

Commit Message

Markus Stockhausen Oct. 4, 2024, 7:56 p.m. UTC
This adds the source for the new Otto SerDes driver.
---
 drivers/phy/realtek/phy-rtl-otto-serdes.c | 1181 +++++++++++++++++++++
 1 file changed, 1181 insertions(+)
 create mode 100644 drivers/phy/realtek/phy-rtl-otto-serdes.c

--
2.44.0

Comments

Chris Packham Oct. 6, 2024, 9:33 p.m. UTC | #1
On 5/10/24 08:56, Markus Stockhausen wrote:
> This adds the source for the new Otto SerDes driver.
> ---
>   drivers/phy/realtek/phy-rtl-otto-serdes.c | 1181 +++++++++++++++++++++
>   1 file changed, 1181 insertions(+)
>   create mode 100644 drivers/phy/realtek/phy-rtl-otto-serdes.c
>
> diff --git a/drivers/phy/realtek/phy-rtl-otto-serdes.c b/drivers/phy/realtek/phy-rtl-otto-serdes.c
Should this be in drivers/net/phy/? drivers/net/phy seems to be more for 
things other than network devices (USB, SATA, PCIe). I know the SERDES 
is not a traditional Ethernet PHY so but there does appear to be other 
SERDES drivers under drivers/net/phy.
> new file mode 100644
> index 000000000000..c3a2584a69d0
> --- /dev/null
> +++ b/drivers/phy/realtek/phy-rtl-otto-serdes.c
> @@ -0,0 +1,1181 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Realtek RTL838x, RTL839x, RTL930x & RTL931x SerDes PHY driver
> + * Copyright (c) 2024 Markus Stockhausen <markus.stockhausen@gmx.de>
> + */
> +
> +#include "phy-rtl-otto-serdes.h"
> +
> +/*
> + * The Otto platform has a lot of undocumented features and registers that configure
> + * the SerDes behaviour. Trying to include that here would clutter the driver. To
> + * provide maximum flexibility the driver can run register modification sequences
> + * during operation, E.g. when calling phy_reset() or phy_power_on(). These sequences
> + * need to be stored in the device tree. More documentation over there.
> + */
> +
> +static const char *rtsds_events[RTSDS_EVENT_MAX + 1] = {
> +	[RTSDS_EVENT_SETUP]		= "cmd-setup",
> +	[RTSDS_EVENT_INIT]		= "cmd-init",
> +	[RTSDS_EVENT_POWER_ON]		= "cmd-power-on",
> +	[RTSDS_EVENT_PRE_SET_MODE]	= "cmd-pre-set-mode",
> +	[RTSDS_EVENT_POST_SET_MODE]	= "cmd-post-set-mode",
> +	[RTSDS_EVENT_PRE_RESET]		= "cmd-pre-reset",
> +	[RTSDS_EVENT_POST_RESET]	= "cmd-post-reset",
> +	[RTSDS_EVENT_PRE_POWER_OFF]	= "cmd-pre-power-off",
> +	[RTSDS_EVENT_POST_POWER_OFF]	= "cmd-post-power-off",
> +};

Ah, OK that's kind of similar to my idea of having the sequences as 
firmware. I'd expect some push-back from the devicetree maintainers on 
the idea of having these sequences in the device tree.

> +
> +static void rtsds_load_events(struct rtsds_ctrl *ctrl)
> +{
> +	int i, elems, sz = sizeof(struct rtsds_seq);
> +
> +	for (i = 0; i <= RTSDS_EVENT_MAX; i++) {
> +		elems = of_property_count_u16_elems(ctrl->dev->of_node ,rtsds_events[i]);
For portability can we use the device_property_*() APIs?
> +		if (elems <= 0)
> +			continue;
> +
> +		if ((elems * sizeof(u16)) % sz) {
> +			dev_err(ctrl->dev, "ignore sequence %s (incomplete data)\n", rtsds_events[i]);
> +			continue;
> +		}
> +
> +		/* alloc one more element to provide stop marker in case it is missing in dt */
> +		ctrl->sequence[i] = devm_kzalloc(ctrl->dev, elems * sizeof(u16) + sz, GFP_KERNEL);
> +		if (!ctrl->sequence[i]) {
> +			dev_err(ctrl->dev, "ignore sequence %s (allocation failed)\n", rtsds_events[i]);
> +			continue;
> +		}
> +
> +		if (of_property_read_u16_array(ctrl->dev->of_node, rtsds_events[i],
> +					       (u16 *)ctrl->sequence[i], elems)) {
> +			dev_err(ctrl->dev, "ignore sequence %s (DT load failed)\n", rtsds_events[i]);
> +			kfree(ctrl->sequence[i]);
> +			ctrl->sequence[i] = NULL;
> +			continue;
> +		}
> +	}
> +}
> +
> +static int rtsds_run_event(struct rtsds_ctrl *ctrl, u32 sid, int evt)
> +{
> +	struct rtsds_seq *seq;
> +	int ret, step = 1, delay = 0;
> +
> +	if (evt > RTSDS_EVENT_MAX || sid > ctrl->conf->max_sds)
> +		return -EINVAL;
> +
> +	seq = ctrl->sequence[evt];
> +
> +	if (!seq)
> +		return 0;
> +
> +	while (seq->action != RTSDS_SEQ_STOP) {
> +		if ((seq->action == RTSDS_SEQ_WAIT) && (seq->ports & BIT(sid)))
> +			delay = seq->val;
> +
> +		if (delay)
> +			usleep_range(delay << 10, (delay << 10) + 1000);
> +
> +		if ((seq->action == RTSDS_SEQ_MASK) && (seq->ports & BIT(sid))) {
> +			ret = ctrl->conf->mask(ctrl, sid, seq->page,
> +					 seq->reg, seq->val, seq->mask);
> +
> +			if (ret) {
> +				dev_err(ctrl->dev, "sequence %s failed at step %d", rtsds_events[evt], step);
> +				return -EIO;
> +			}
> +		}
> +
> +		seq++;
> +		step++;
> +	}
> +
> +	return 0;
> +}
> +
> +static int rtsds_hwmode_to_phymode(struct rtsds_ctrl *ctrl, int hwmode)
> +{
> +	for (int m = 0; m < PHY_INTERFACE_MODE_MAX; m++)
> +		if (ctrl->conf->mode_map[m] == hwmode)
> +			return m;
> +
> +	return PHY_INTERFACE_MODE_MAX;
> +}
> +
> +static void rtsds_83xx_soft_reset(struct rtsds_ctrl *ctrl, u32 sidlo, u32 sidhi, int usec)
> +{
> +	for (u32 sid = sidlo; sid <= sidhi; sid++)
> +		ctrl->conf->mask(ctrl, sid, 0x00, 0x03, 0x7146, 0xffff);
> +	usleep_range(usec, usec + 1000);
> +	for (u32 sid = sidlo; sid <= sidhi; sid++)
> +		ctrl->conf->mask(ctrl, sid, 0x00, 0x03, 0x7106, 0xffff);
> +}
> +
> +/*
> + * The RTL838x has 6 SerDes. The 16 bit registers start at 0xbb00e780 and are mapped
> + * directly into 32 bit memory addresses. High 16 bits are always empty. Quite confusing
> + * but the register ranges are cluttered and contain holes.
> + */
> +
> +static int rtsds_838x_offset(u32 sid, u32 page, u32 reg)
> +{
> +	if (page == 0 || page == 3)
> +		return (sid << 9) + (page << 7) + (reg << 2);
> +	else
> +		return 0xb80 + (sid << 8) + (page << 7) + (reg << 2);
> +}
> +
> +static int rtsds_838x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg)
> +{
> +	int offs;
> +
> +	if (sid > RTSDS_838X_MAX_SDS || page > RTSDS_838X_MAX_PAGE || reg > 31)
> +		return -EINVAL;
> +
> +	offs = rtsds_838x_offset(sid, page, reg);
> +
> +	/* read twice for link status latch */
> +	if (page == 2 && reg == 1)
> +		ioread32(ctrl->base + offs);
> +
> +	return ioread32(ctrl->base + offs);

As I mentioned on the other patch. These devices (well at least the 
RTL9300s) can be run with the CPU core disabled so using regmap instead 
of direct mmio accesses would be a bit more future proof.

> +}
> +
> +static int rtsds_838x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask)
> +{
> +	int offs;
> +
> +	if (sid > RTSDS_838X_MAX_SDS || page > RTSDS_838X_MAX_PAGE || reg > 31)
> +		return -EINVAL;
> +
> +	offs = rtsds_838x_offset(sid, page, reg);
> +
> +	/* read twice for link status latch */
> +	if (page == 2 && reg == 1)
> +		ioread32(ctrl->base + offs);
> +
> +	iomask32(mask, val, ctrl->base + offs);
> +
> +	return 0;
> +}
> +
> +static int rtsds_838x_reset(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	if (sid > RTSDS_838X_MAX_SDS)
> +		return -EINVAL;
> +
> +	/* RX reset */
> +	rtsds_838x_mask(ctrl, sid, 0x01, 0x09, 0x0200, 0x0200);
> +	rtsds_838x_mask(ctrl, sid, 0x01, 0x09, 0x0000, 0x0200);
> +
> +	/* CMU reset */
> +	rtsds_838x_mask(ctrl, sid, 0x01, 0x00, 0x4040, 0xffff);
> +	rtsds_838x_mask(ctrl, sid, 0x01, 0x00, 0x4740, 0xffff);
> +	rtsds_838x_mask(ctrl, sid, 0x01, 0x00, 0x47c0, 0xffff);
> +	rtsds_838x_mask(ctrl, sid, 0x01, 0x00, 0x4000, 0xffff);
> +
> +	rtsds_83xx_soft_reset(ctrl, sid, sid, 1000);
> +
> +	/* RX/TX reset */
> +	rtsds_838x_mask(ctrl, sid, 0x00, 0x00, 0x0400, 0xffff);
> +	rtsds_838x_mask(ctrl, sid, 0x00, 0x00, 0x0403, 0xffff);
> +
> +	return 0;
> +}
> +
> +static int rtsds_838x_set_mode(struct rtsds_ctrl *ctrl, u32 sid, int combomode)
> +{
> +	int shift, mode = RTSDS_MODE(combomode), submode = RTSDS_SUBMODE(combomode);
> +
> +	if (sid > RTSDS_838X_MAX_SDS)
> +		return -EINVAL;
> +
> +	if (sid == 4 || sid == 5) {
> +		shift = (sid - 4) * 3;
> +		iomask32(0x7 << shift, (submode & 0x7) << shift, RTSDS_838X_INT_MODE_CTRL);
> +	}
> +
> +	shift = 25 - sid * 5;
> +	iomask32(0x1f << shift, (mode & 0x1f) << shift, RTSDS_838X_SDS_MODE_SEL);
> +
> +	return 0;
> +}
> +
> +static int rtsds_838x_get_mode(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	int shift, mode, submode = 0;
> +
> +	if (sid < 0 || sid > RTSDS_838X_MAX_SDS)
> +		return -EINVAL;
> +
> +	if (sid == 4 || sid == 5) {
> +		shift = (sid - 4) * 3;
> +		submode = (ioread32(RTSDS_838X_INT_MODE_CTRL) >> shift) & 0x7;
> +	}
> +
> +	shift = 25 - sid * 5;
> +	mode = (ioread32(RTSDS_838X_SDS_MODE_SEL) >> shift) & 0x1f;
> +
> +	return RTSDS_COMBOMODE(mode, submode);
> +}
> +
> +/*
> + * The RLT839x has 14 SerDes starting at 0xbb00a000. 0-7, 10, 11 are 5GBit, 8, 9, 12, 13
> + * are 10GBit. Two adjacent SerDes are tightly coupled and share a 1024 bytes register area.
> + * Per 32 bit address two registers are stored. The first register is stored in the lower
> + * 2 bytes ("on the right" due to big endian) and the second register in the upper 2 bytes.
> + * We know the following register areas:
> + *
> + * - XSG0	(4 pages @ offset 0x000): for even SerDes
> + * - XSG1	(4 pages @ offset 0x100): for odd SerDes
> + * - TGRX	(4 pages @ offset 0x200): for even 10G SerDes
> + * - ANA_RG	(2 pages @ offset 0x300): for even 5G SerDes
> + * - ANA_RG	(2 pages @ offset 0x380): for odd 5G SerDes
> + * - ANA_TG	(2 pages @ offset 0x300): for even 10G SerDes
> + * - ANA_TG	(2 pages @ offset 0x380): for odd 10G SerDes
> + *
> + * The most consistent mapping we can achieve that aligns to the RTL93xx devices is:
> + *
> + *		even 5G SerDes	odd 5G SerDes	even 10G SerDes	odd 10G SerDes
> + * Page 0: 	XSG0/0		XSG1/0		XSG0/0		XSG1/0
> + * Page 1: 	XSG0/1		XSG1/1		XSG0/1		XSG1/1
> + * Page 2: 	XSG0/2		XSG1/2 		XSG0/2		XSG1/2
> + * Page 3: 	XSG0/3		XSG1/3		XSG0/3		XSG1/3
> + * Page 4: 	<zero>		<zero>		TGRX/0		<zero>
> + * Page 5: 	<zero>		<zero>		TGRX/1		<zero>
> + * Page 6: 	<zero>		<zero>		TGRX/2		<zero>
> + * Page 7: 	<zero>		<zero>		TGRX/3		<zero>
> + * Page 8: 	ANA_RG		ANA_RG		<zero>		<zero>
> + * Page 9: 	ANA_RG_EXT	ANA_RG_EXT	<zero>		<zero>
> + * Page 10: 	<zero>		<zero>		ANA_TG		ANA_TG
> + * Page 11: 	<zero>		<zero>		ANA_TG_EXT	ANA_TG_EXT
> + */
> +
> +static int rtsds_839x_offset(u32 sid, u32 page, u32 reg)
> +{
> +	int offs = ((sid & 0xfe) << 9) + ((reg & 0xfe) << 1);
> +
> +	if (page < 4) {
> +		offs += ((sid & 1) << 8) + (page << 6);
> +	} else if (page < 8) {
> +		if (sid != 8 && sid != 12)
> +			return -1;
> +		offs += 0x100 + (page << 6);
> +	} else if (page < 10) {
> +		if (sid == 8 || sid == 9 || sid == 12 || sid == 13)
> +			return -1;
> +		offs += 0x100 + ((sid & 1) << 7) + (page << 6);
> +	} else {
> +		if (sid != 8 && sid != 9 && sid != 12 && sid != 13)
> +			return -1;
> +		offs += 0x100 + ((sid & 1) << 7) + ((page - 2) << 6);
> +	}
> +
> +	return offs;
> +}
> +
> +static int rtsds_839x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg)
> +{
> +	int offs, shift = (reg << 4) & 0x10;
> +
> +	if (sid > RTSDS_839X_MAX_SDS || page > RTSDS_839X_MAX_PAGE || reg > 31)
> +		return -EINVAL;
> +
> +	offs = rtsds_839x_offset(sid, page, reg);
> +	if (offs < 0)
> +		return 0;
> +
> +	/* read twice for link status latch */
> +	if (page == 2 && reg == 1)
> +		ioread32(ctrl->base + offs);
> +
> +	return (ioread32(ctrl->base + offs) >> shift) & 0xffff;
> +}
> +
> +static int rtsds_839x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask)
> +{
> +	int oldval, offs = ((sid & 0xfe) << 9) + ((reg & 0xfe) << 1);
> +
> +	if (sid > RTSDS_839X_MAX_SDS || page > RTSDS_839X_MAX_PAGE || reg > 31)
> +		return -EINVAL;
> +
> +	offs = rtsds_839x_offset(sid, page, reg);
> +	if (offs < 0)
> +		return 0;
> +
> +	/* read twice for link status latch */
> +	if (page == 2 && reg == 1)
> +		ioread32(ctrl->base + offs);
> +
> +	oldval = ioread32(ctrl->base + offs);
> +	val = reg & 1 ? (oldval & ~(mask << 16)) | (val << 16) : (oldval & ~mask) | val;
> +	iowrite32(val, ctrl->base + offs);
> +
> +	return 0;
> +}
> +
> +static int rtsds_839x_set_mode(struct rtsds_ctrl *ctrl, u32 sid, int combomode)
> +{
> +	int shift = (sid & 7) << 2, offs = (sid >> 1) & ~3;
> +	int mode = RTSDS_MODE(combomode), submode = RTSDS_SUBMODE(combomode);
> +
> +	if (sid > RTSDS_839X_MAX_SDS)
> +		return -EINVAL;
> +
> +	rtsds_839x_mask(ctrl, sid, 0, 4, (submode << 12) & 0xf000, 0xf000);
> +	iomask32(0xf << shift, (mode & 0xf) << shift, RTSDS_839X_MAC_SERDES_IF_CTRL + offs);
> +
> +	return 0;
> +}
> +
> +static int rtsds_839x_get_mode(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	int mode, submode, shift = (sid & 7) << 2, offs = (sid >> 1) & ~3;
> +
> +	if (sid > RTSDS_839X_MAX_SDS)
> +		return -EINVAL;
> +
> +	submode = (rtsds_839x_read(ctrl, sid, 0, 4) >> 12) & 0xf;
> +	mode = (ioread32(RTSDS_839X_MAC_SERDES_IF_CTRL + offs) >> shift) & 0xf;
> +
> +	return RTSDS_COMBOMODE(mode, submode);
> +}
> +
> +static int rtsds_839x_reset(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	int lo = sid & ~1, hi = sid | 1;
> +
> +	if (sid > RTSDS_839X_MAX_SDS)
> +		return -EINVAL;
> +
> +	/*
> +	 * A reset basically consists of two steps. First a clock (CMU) reset and a
> +	 * digital soft reset afterwards. Some of the CMU registers are shared on
> +	 * adjacent SerDes so as of now we can only perform a reset on a pair.
> +	 */
> +
> +	if (lo < 8 || lo == 10) {
> +		rtsds_839x_mask(ctrl, hi, 0x09, 0x01, 0x0050, 0xffff);
> +		rtsds_839x_mask(ctrl, hi, 0x09, 0x01, 0x00f0, 0xffff);
> +		rtsds_839x_mask(ctrl, hi, 0x09, 0x01, 0x0000, 0xffff);
> +		rtsds_839x_mask(ctrl, lo, 0x08, 0x14, 0x0000, 0x0001);
> +		rtsds_839x_mask(ctrl, lo, 0x08, 0x14, 0x0200, 0x0200);
> +		usleep_range(100000, 101000);
> +		rtsds_839x_mask(ctrl, lo, 0x08, 0x14, 0x0000, 0x0200);
> +	} else {
> +		rtsds_839x_mask(ctrl, lo, 0x0a, 0x10, 0x0000, 0x0008);
> +		rtsds_839x_mask(ctrl, lo, 0x0b, 0x00, 0x8000, 0x8000);
> +		usleep_range(100000, 101000);
> +		rtsds_839x_mask(ctrl, lo, 0x0b, 0x00, 0x0000, 0x8000);
> +	}
> +
> +	rtsds_83xx_soft_reset(ctrl, lo, hi, 100000);
> +
> +	return 0;
> +}
> +
> +/*
> + * The RTL930x family has 12 SerdDes. They are accessed through two IO registers
> + * at 0xbb0003b0 which simulate commands to an internal MDIO bus. From the current
> + * observation there are 3 types of SerDes:
> + *
> + * - SerDes 0,1 are of unknown type
These are the 1G (QSGMII) SERDES used on the 9301 and 9302B.
> + * - SerDes 2-9 are USXGMII capabable with either quad or single configuration
Looks like 2-7 are fairly flexible between all the SERDES types. USXGMII 
definitely but others too. 8 and 9 seem to be 10GBase-R SERDES.
> + * - SerDes 10-11 are of unknown type

These are 10GBase-R.

The exception to what I have above is the RTL9303. It has 8 serdes 
(numbered 2-9) which seem to be able to operate in anything from 
2500Base-X up to 10GBase-R.

> + */
> +
> +static int rtsds_930x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg)
> +{
> +	int cnt = 100, cmd = (sid << 2) | (page << 7) | (reg << 13) | 1;
> +
> +	if (sid > RTSDS_930X_MAX_SDS || page > RTSDS_930X_MAX_PAGE || reg > 31)
> +		return -EINVAL;
> +
> +	iowrite32(cmd, ctrl->base);
> +
> +	while (--cnt && (ioread32(ctrl->base) & 1))
> +		usleep_range(50, 60);
There's probably a read_poll_timeout variant that can do this.
> +
> +	return cnt ? ioread32(ctrl->base + 4) & 0xffff : -EIO;
> +}
> +
> +static int rtsds_930x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask)
> +{
> +	int oldval, cnt = 100, cmd = (sid << 2) | (page << 7) | (reg << 13) | 3;
> +
> +	if (sid > RTSDS_930X_MAX_SDS || page > RTSDS_930X_MAX_PAGE || reg > 31)
> +		return -EINVAL;
> +
> +	if (mask != 0xffff) {
> +		oldval = rtsds_930x_read(ctrl, sid, page, reg);
> +		if (oldval < 0)
> +			return -EIO;
> +		oldval &= ~mask;
> +		val |= oldval;
> +	}
> +
> +	iowrite32(val, ctrl->base + 4);
> +	iowrite32(cmd, ctrl->base);
> +
> +	while (--cnt && (ioread32(ctrl->base) & 1))
> +		usleep_range(50, 60);
> +
> +	return cnt ? 0 : - EIO;
> +}
> +
> +static void rtsds_930x_mode_offset(int sid,
> +				   void __iomem __force **modereg, int *modeshift,
> +				   void __iomem __force **subreg, int *subshift)
> +{
> +	if (sid > 3) {
> +		*subreg = RTSDS_930X_SDS_SUBMODE_CTRL1;
> +		*subshift = (sid - 4) * 5;
> +	} else {
> +		*subreg = RTSDS_930X_SDS_SUBMODE_CTRL0;
> +		*subshift = (sid - 2) * 5;
> +	}
> +
> +	if (sid < 4) {
> +		*modeshift = sid * 6;
> +		*modereg = RTSDS_930X_SDS_MODE_SEL_0;
> +	} else if (sid < 8) {
> +		*modeshift = (sid - 4) * 6;
> +		*modereg = RTSDS_930X_SDS_MODE_SEL_1;
> +	} else if (sid < 10) {
> +		*modeshift = (sid - 8) * 6;
> +		*modereg = RTSDS_930X_SDS_MODE_SEL_2;
> +	} else {
> +		*modeshift = (sid - 10) * 6;
> +		*modereg = RTSDS_930X_SDS_MODE_SEL_3;
> +	}
> +}
> +
> +static int rtsds_930x_set_mode(struct rtsds_ctrl *ctrl, u32 sid, int combomode)
> +{
> +	int modeshift, subshift;
> +	int mode = RTSDS_MODE(combomode);
> +	int submode = RTSDS_SUBMODE(combomode);
> +	void __iomem __force *modereg;
> +	void __iomem __force *subreg;
> +
> +	if (sid > RTSDS_930X_MAX_SDS)
> +		return -EINVAL;
> +
> +	rtsds_930x_mode_offset(sid, &modereg, &modeshift, &subreg, &subshift);
> +	if (sid >= 2 || sid <= 9)
> +		iomask32(0x1f << subshift, (submode & 0x1f) << subshift, subreg);
> +	iomask32(0x1f << modeshift, (mode & 0x1f) << modeshift, modereg);
> +
> +	return 0;
> +}
> +
> +static int rtsds_930x_get_mode(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	int modeshift, subshift, mode, submode = 0;
> +	void __iomem __force *modereg;
> +	void __iomem __force *subreg;
> +
> +	if (sid > RTSDS_930X_MAX_SDS)
> +		return -EINVAL;
> +
> +	rtsds_930x_mode_offset(sid, &modereg, &modeshift, &subreg, &subshift);
> +	if (sid >= 2 || sid <= 9)
> +		submode = (ioread32(subreg) >> subshift) & 0x1f;
> +	mode = ioread32(modereg) >> modeshift & 0x1f;
> +
> +	return RTSDS_COMBOMODE(mode, submode);
> +}
> +
> +static int rtsds_930x_reset(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	int modecur, modeoff = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA];
> +
> +	if (sid > RTSDS_930X_MAX_SDS)
> +		return -EINVAL;
> +
> +	modecur = rtsds_930x_get_mode(ctrl, sid);
> +
> +	/* It is enough to power off SerDes and set to old mode again */
> +	if (modecur != modeoff) {
> +		rtsds_930x_set_mode(ctrl, sid, modeoff);
> +		rtsds_930x_set_mode(ctrl, sid, modecur);
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * The RTL931x family has 14 "frontend" SerDes that are magically cascaded. All
> + * operations (e.g. reset) work on this frontend view while their registers are
> + * distributed over a total of 32 background SerDes. Two types of SerDes have been
> + * identified:
> + *
> + * A "even" SerDes with numbers 0, 1, 2, 4, 6, 8, 10, 12 works on on two background
> + * SerDes. 64 analog und 64 XGMII data pages are coming from a first background
> + * SerDes while another 64 XGMII pages are served from a second SerDes.
> + *
> + * The "odd" SerDes with numbers 3, 5, 7, 9, 11 & 13 SerDes consist of a total of 3
> + * background SerDes (one analog and two XGMII) each with an own page/register set.
> + *
> + * As strange as this reads try to get this aligned and mix pages as follows
> + *
> + * frontend page		"even" frontend SerDes 	"odd" frontend SerDes
> + * page 0-63 (analog): 		back sid page 0-63	back sid page 0-63
> + * page 64-127 (XGMII1):	back sid page 0-63	back sid +1 page 0-63
> + * page 128-191 (XGMII2):	back sid +1 page 0-63	back sid +2 page 0-63
> + */
> +
> +static int rtsds_931x_backsid(u32 sid, u32 page)
> +{
> +	int map[] = {0, 1, 2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23};
> +	int backsid = map[sid];
> +
> +	if ((sid & 1) && (sid != 1))
> +		backsid += (page >> 6); /* distribute "odd" to 3 background SerDes */
> +	else if (page >= 128)
> +		backsid += 1; /* "distribute "even" to 2 background SerDes */
> +
> +	return backsid;
> +}
> +
> +static int rtsds_931x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg)
> +{
> +	int backsid, cmd, cnt = 100;
> +
> +	if (sid > RTSDS_931X_MAX_SDS || page > RTSDS_931X_MAX_PAGE || reg > 31)
> +		return -EINVAL;
> +
> +	backsid = rtsds_931x_backsid(sid, page);
> +	cmd = (backsid << 2) | ((page & 0x3f) << 7) | (reg << 13) | 1;
> +
> +	iowrite32(cmd, ctrl->base);
> +	while (--cnt && (ioread32(ctrl->base) & 1))
> +		usleep_range(50, 60);
> +
> +	return cnt ? ioread32(ctrl->base + 4) & 0xffff : -EIO;
> +}
> +
> +static int rtsds_931x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page,
> +			   u32 reg, u32 val, u32 mask)
> +{
> +	int backsid, cmd, oldval, cnt = 100;
> +
> +	if (sid > RTSDS_931X_MAX_SDS || page > RTSDS_931X_MAX_PAGE || reg > 31)
> +		return -EINVAL;
> +
> +	backsid = rtsds_931x_backsid(sid, page);
> +	cmd = (backsid << 2) | ((page & 0x3f) << 7) | (reg << 13) | 3;
> +
> +	if (mask != 0xffff) {
> +		oldval = rtsds_931x_read(ctrl, sid, page, reg);
> +		if (oldval < 0)
> +			return -EIO;
> +		oldval &= ~mask;
> +		val |= oldval;
> +	}
> +
> +	iowrite32(val, ctrl->base + 4);
> +	iowrite32(cmd, ctrl->base);
> +	while (--cnt && (ioread32(ctrl->base) & 1))
> +		usleep_range(50, 60);
> +
> +	return cnt ? 0 : - EIO;
> +}
> +
> +static int rtsds_931x_set_mode(struct rtsds_ctrl *ctrl, u32 sid, int combomode)
> +{
> +	int shift = (sid & 3) << 3, offs = sid & ~3;
> +	int mode = RTSDS_MODE(combomode);
> +	int submode = RTSDS_SUBMODE(combomode);
> +
> +	if (sid > RTSDS_931X_MAX_SDS)
> +		return -EINVAL;
> +
> +	rtsds_931x_mask(ctrl, sid, 31, 9, (submode & 0x3f << 6), 0x0fc0);
> +	iomask32(0xff << shift, ((mode | RTSDS_931X_SDS_FORCE_SETUP) & 0xff) << shift,
> +		 RTSDS_931X_SERDES_MODE_CTRL + offs);
> +
> +	return 0;
> +}
> +
> +static int rtsds_931x_get_mode(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	int mode, submode, shift = (sid & 3) << 3, offs = sid & ~3;
> +
> +	if (sid > RTSDS_931X_MAX_SDS)
> +		return -EINVAL;
> +
> +	submode = (rtsds_931x_read(ctrl, sid, 31, 9) >> 6) & 0x3f;
> +	mode = (ioread32(RTSDS_931X_SERDES_MODE_CTRL + offs) >> shift) & 0x1f;
> +
> +	return RTSDS_COMBOMODE(mode, submode);
> +}
> +
> +static int rtsds_931x_reset(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	int pwr, modecur, modeoff = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA];
> +
> +	if (sid > RTSDS_931X_MAX_SDS)
> +		return -EINVAL;
> +
> +	modecur = rtsds_931x_get_mode(ctrl, sid);
> +
> +	if (modecur != modeoff) {
> +		/* reset with mode switch cycle while being powered off */
> +		pwr = ioread32(RTSDS_931X_PS_SERDES_OFF_MODE_CTRL);
> +		iowrite32(pwr | BIT(sid), RTSDS_931X_PS_SERDES_OFF_MODE_CTRL);
> +		rtsds_931x_set_mode(ctrl, sid, modeoff);
> +		rtsds_931x_set_mode(ctrl, sid, modecur);
> +		iowrite32(pwr, RTSDS_931X_PS_SERDES_OFF_MODE_CTRL);
> +	}
> +
> +	return 0;
> +}
> +
> +int rtsds_read(struct phy *phy, u32 page, u32 reg)
> +{
> +	struct rtsds_macro *macro = phy_get_drvdata(phy);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	u32 sid = macro->sid;
> +
> +	return ctrl->conf->read(ctrl, sid, page, reg);
> +}
> +
> +int rtsds_mask(struct phy *phy, u32 page, u32 reg, u32 val, u32 mask)
> +{
> +	struct rtsds_macro *macro = phy_get_drvdata(phy);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	u32 sid = macro->sid;
> +
> +	if (!(ctrl->sds_mask & BIT(sid)))
> +		return -EACCES;
> +
> +	return ctrl->conf->mask(ctrl, sid, page, reg, val, mask);
> +}
> +
> +int rtsds_write(struct phy *phy, u32 page, u32 reg, u32 val)
> +{
> +	return rtsds_mask(phy, page, reg, val, 0xffff);
> +}
> +
> +static int rtsds_phy_init(struct phy *phy)
> +{
> +	struct rtsds_macro *macro = phy_get_drvdata(phy);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	u32 sid = macro->sid;
> +	int ret;
> +
> +	if (!(ctrl->sds_mask & BIT(sid)))
> +		return 0;
> +
> +	mutex_lock(&ctrl->lock);
> +	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_INIT);
> +	mutex_unlock(&ctrl->lock);
> +
> +	if (ret)
> +		dev_err(ctrl->dev, "init failed for SerDes %d\n", sid);
> +
> +	return ret;
> +}
> +
> +static int rtsds_phy_power_on(struct phy *phy)
> +{
> +	struct rtsds_macro *macro = phy_get_drvdata(phy);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	u32 sid = macro->sid;
> +	int ret;
> +
> +	if (!(ctrl->sds_mask & BIT(sid)))
> +		return 0;
> +
> +	mutex_lock(&ctrl->lock);
> +	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_POWER_ON);
> +	mutex_unlock(&ctrl->lock);
> +
> +	if (ret)
> +		dev_err(ctrl->dev, "power on failed for SerDes %d\n", sid);
> +
> +	return ret;
> +}
> +
> +static int rtsds_phy_power_off(struct phy *phy)
> +{
> +	struct rtsds_macro *macro = phy_get_drvdata(phy);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	u32 sid = macro->sid;
> +	int ret;
> +
> +	if (!(ctrl->sds_mask & BIT(sid)))
> +		return 0;
> +
> +	mutex_lock(&ctrl->lock);
> +	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_PRE_POWER_OFF);
> +	if (!ret)
> +		ret = ctrl->conf->set_mode(ctrl, sid, ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA]);
> +	if (!ret)
> +		ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_POST_POWER_OFF);
> +	mutex_unlock(&ctrl->lock);
> +
> +	if (ret)
> +		dev_err(ctrl->dev, "power off failed for SerDes %d\n", sid);
> +
> +	return ret;
> +}
> +
> +static int rtsds_phy_set_mode_int(struct rtsds_ctrl *ctrl, u32 sid, int phymode, int hwmode)
> +{
> +	int ret;
> +
> +	mutex_lock(&ctrl->lock);
> +	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_PRE_SET_MODE);
> +	if (!ret)
> +		ret = ctrl->conf->set_mode(ctrl, sid, hwmode);
> +	if (!ret) {
> +		ctrl->sds[sid].mode = phymode;
> +		ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_POST_SET_MODE);
> +	}
> +	mutex_unlock(&ctrl->lock);
> +
> +	if (ret)
> +		dev_err(ctrl->dev, "set mode failed for SerDes %d\n", sid);
> +
> +	return ret;
> +}
> +
> +static int rtsds_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
> +{
> +	struct rtsds_macro *macro = phy_get_drvdata(phy);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	u32 sid = macro->sid;
> +
> +	if (!(ctrl->sds_mask & BIT(sid)))
> +		return 0;
> +
> +	if (mode != PHY_MODE_ETHERNET)
> +		return -EINVAL;
> +
> +	return rtsds_phy_set_mode_int(ctrl, sid, submode, ctrl->conf->mode_map[submode]);
> +}
> +
> +static int rtsds_phy_reset_int(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	int ret;
> +
> +	mutex_lock(&ctrl->lock);
> +	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_PRE_RESET);
> +	if (!ret)
> +		ret = ctrl->conf->reset(ctrl, sid);
> +	if (!ret)
> +		ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_POST_RESET);
> +	mutex_unlock(&ctrl->lock);
> +
> +	if (ret)
> +		dev_err(ctrl->dev, "reset failed for SerDes %d\n", sid);
> +
> +	return ret;
> +}
> +
> +static int rtsds_phy_reset(struct phy *phy)
> +{
> +	struct rtsds_macro *macro = phy_get_drvdata(phy);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	u32 sid = macro->sid;
> +
> +	if (!(ctrl->sds_mask & BIT(sid)))
> +		return 0;
> +
> +	return rtsds_phy_reset_int(ctrl, sid);
> +}
> +
> +static const struct phy_ops rtsds_phy_ops = {
> +	.init		= rtsds_phy_init,
> +	.power_on	= rtsds_phy_power_on,
> +	.power_off	= rtsds_phy_power_off,
> +	.reset		= rtsds_phy_reset,
> +	.set_mode 	= rtsds_phy_set_mode,
> +	.owner		= THIS_MODULE,
> +};
> +
> +/*
> + * The SerDes offer a lot of magic that sill needs to be uncovered. To help further
> + * development provide some basic debugging about registers, modes and polarity. The
> + * mode can be changed on the fly and executes the normal setter including events.
> + */
> +
> +#ifdef CONFIG_DEBUG_FS
> +static const char *rtsds_page_name[RTSDS_PAGE_NAMES]	= {
> +	[0] = "SDS",		[1] = "SDS_EXT",
> +	[2] = "FIB",		[3] = "FIB_EXT",
> +	[4] = "DTE",		[5] = "DTE_EXT",
> +	[6] = "TGX",		[7] = "TGX_EXT",
> +	[8] = "ANA_RG",		[9] = "ANA_RG_EXT",
> +	[10] = "ANA_TG",	[11] = "ANA_TG_EXT",
> +	[31] = "ANA_WDIG",
> +	[32] = "ANA_MISC",	[33] = "ANA_COM",
> +	[34] = "ANA_SP",	[35] = "ANA_SP_EXT",
> +	[36] = "ANA_1G",	[37] = "ANA_1G_EXT",
> +	[38] = "ANA_2G",	[39] = "ANA_2G_EXT",
> +	[40] = "ANA_3G",	[41] = "ANA_3G_EXT",
> +	[42] = "ANA_5G",	[43] = "ANA_5G_EXT",
> +	[44] = "ANA_6G",	[45] = "ANA_6G_EXT",
> +	[46] = "ANA_10G",	[47] = "ANA_10G_EXT",
> +};
> +
> +static ssize_t rtsds_dbg_mode_show(struct seq_file *seqf, void *unused)
> +{
> +	struct rtsds_macro *macro = dev_get_drvdata(seqf->private);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	int mode, sid = macro->sid;
> +
> +	mutex_lock(&ctrl->lock);
> +	mode = ctrl->conf->get_mode(ctrl, sid);
> +	mutex_unlock(&ctrl->lock);
> +
> +	seq_printf(seqf, "hw mode: 0x%X\n", mode);
> +	seq_printf(seqf, "phy mode: ");
> +
> +	if (ctrl->sds[sid].mode == PHY_INTERFACE_MODE_NA)
> +		seq_printf(seqf, "off\n");
> +	else
> +		seq_printf(seqf, "%s\n", phy_modes(ctrl->sds[sid].mode));
> +
> +	return 0;
> +}
> +
> +static ssize_t rtsds_dbg_mode_write(struct file *file, const char __user *userbuf,
> +				size_t count, loff_t *ppos)
> +{
> +	struct seq_file *seqf = file->private_data;
> +	struct rtsds_macro *macro = dev_get_drvdata(seqf->private);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	int ret, hwmode, phymode, sid = macro->sid;
> +
> +	ret = kstrtou32_from_user(userbuf, count, 10, &hwmode);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * As we are still exploring the SerDes this debug function allows to set
> +	 * arbitrary modes into the SerDes. While this might confuse the internal
> +	 * driver handling it helps to avoid to rebuild & start from scratch for
> +	 * every test.
> +	 */
> +	phymode = rtsds_hwmode_to_phymode(ctrl, hwmode);
> +	rtsds_phy_set_mode_int(ctrl, sid, phymode, hwmode);
> +
> +	return count;
> +}
> +DEFINE_SHOW_STORE_ATTRIBUTE(rtsds_dbg_mode);
> +
> +static int rtsds_dbg_registers_show(struct seq_file *seqf, void *unused)
> +{
> +	struct rtsds_macro *macro = dev_get_drvdata(seqf->private);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	u32 page, reg, sid = macro->sid;
> +
> +	seq_printf(seqf, "%*s", 12 , "");
> +	for (int i = 0;i < 32; i++)
> +		seq_printf(seqf, "%*d", 5, i);
> +
> +	for (page = 0; page <= ctrl->conf->max_page; page++) {
> +		if (page < RTSDS_PAGE_NAMES && rtsds_page_name[page])
> +			seq_printf(seqf, "\n%*s: ", -11, rtsds_page_name[page]);
> +		else if (page == 64 || page == 128)
> +			seq_printf(seqf, "\nXGMII_%d    : ", page >> 6);
> +		else
> +			seq_printf(seqf, "\nPAGE_%03d   : ", page);
> +		for (reg = 0; reg < 32; reg++)
> +			seq_printf(seqf, "%04X ", ctrl->conf->read(ctrl, sid, page, reg));
> +	}
> +	seq_printf(seqf, "\n");
> +
> +	return 0;
> +}
> +DEFINE_SHOW_ATTRIBUTE(rtsds_dbg_registers);
> +
> +static int rtsds_dbg_polarity_show(struct seq_file *seqf, void *unused)
> +{
> +	struct rtsds_macro *macro = dev_get_drvdata(seqf->private);
> +	struct rtsds_ctrl *ctrl = macro->ctrl;
> +	u32 reg, sid = macro->sid;
> +
> +	reg = ctrl->conf->read(ctrl, sid, RTSDS_PAGE_SDS, 0);
> +
> +	seq_printf(seqf, "tx polarity: ");
> +	seq_printf(seqf, reg & RTSDS_INV_HSO ? "inverse" : "normal");
> +	seq_printf(seqf, "\nrx polarity: ");
> +	seq_printf(seqf, reg & RTSDS_INV_HSI ? "inverse" : "normal");
> +	seq_printf(seqf, "\n");
> +
> +	return 0;
> +}
> +DEFINE_SHOW_ATTRIBUTE(rtsds_dbg_polarity);
> +
> +static void rtsds_dbg_init(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	debugfs_create_file("mode", 0600, ctrl->sds[sid].phy->debugfs,
> +			    &ctrl->sds[sid].phy->dev, &rtsds_dbg_mode_fops);
> +
> +	debugfs_create_file("polarity", 0400, ctrl->sds[sid].phy->debugfs,
> +			    &ctrl->sds[sid].phy->dev, &rtsds_dbg_polarity_fops);
> +
> +	debugfs_create_file("registers", 0400, ctrl->sds[sid].phy->debugfs,
> +			    &ctrl->sds[sid].phy->dev, &rtsds_dbg_registers_fops);
> +}
> +#endif /* CONFIG_DEBUG_FS */
> +
> +static void rtsds_setup(struct rtsds_ctrl *ctrl)
> +{
> +	int hwmode, ret;
> +
> +	for (u32 sid = 0; sid <= ctrl->conf->max_sds; sid++) {
> +		if (ctrl->sds_mask & BIT(sid)) {
> +			/* power off controlled SerDes */
> +			hwmode = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA];
> +			ret = ctrl->conf->set_mode(ctrl, sid, hwmode);
> +			if (!ret)
> +				ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_SETUP);
> +			if (ret)
> +				dev_err(ctrl->dev, "setup failed for SerDes %d\n", sid);
> +		}
> +		/* in any case sync back hardware status */
> +		hwmode = ctrl->conf->get_mode(ctrl, sid);
> +		ctrl->sds[sid].mode = rtsds_hwmode_to_phymode(ctrl, hwmode);
> +	}
> +}
> +
> +static struct phy *rtsds_simple_xlate(struct device *dev,
> +				      const struct of_phandle_args *args)
> +{
> +	struct rtsds_ctrl *ctrl = dev_get_drvdata(dev);
> +	int sid, sid2, min_port, max_port;
> +
> +	/*
> +	 * Some Realtek Ethernet transceivers (e.g. RLT8218B) will be attached via a
> +	 * bonded 2x QSGMII link to two SerDes. Others (e.g. RTL8218D) allow to make
> +	 * use of single XGMII or dual QSGMII links. When a switch port tries to lookup
> +	 * the SerDes it is attached to we honour that by an enhanced mapping. We allow
> +	 * two possible configuration options. Standalone or linked to another. E.g.
> +	 *
> +	 * Single: port@24 { phys = <&serdes 4 -1 MinPort MaxPort>; };
> +	 * Dual:   port@24 { phys = <&serdes 4  5 MinPort MaxPort>; };
> +	 *
> +	 * As we can only hand over a single phy this function will return the primary
> +	 * phy. The secondary phy can be identified later on by the link attribute in
> +	 * the controller structure.
> +	 */
> +
> +	if (args->args_count != 4)
> +		return ERR_PTR(-EINVAL);
> +
> +	sid = args->args[0];
> +	if (sid < 0 || sid > ctrl->conf->max_sds)
> +		return ERR_PTR(-EINVAL);
> +
> +	sid2 = args->args[1];
> +	if (sid2 < -1 || sid2 > ctrl->conf->max_sds)
> +		return ERR_PTR(-EINVAL);
> +
> +	/*
> +	 * Additionally to a linked SerDes also get the ports whose traffic is going
> +	 * through this SerDes. As of now we do not care much about that but later on
> +	 * it might be helpful.
> +	 */
> +
> +	min_port = args->args[2];
> +	if (min_port < 0)
> +		return ERR_PTR(-EINVAL);
> +
> +	max_port = args->args[3];
> +	if (max_port < min_port)
> +		return ERR_PTR(-EINVAL);
> +
> +	ctrl->http://scanmail.trustwave.com/?c=20988&d=ociA55EFdZjr4RBPKs0gE-XY4i3O0HX1opelL56VRQ&u=http%3a%2f%2fsds%5bsid%5d%2elink = sid2;
> +	if (sid2 >= 0)
> +		ctrl->http://scanmail.trustwave.com/?c=20988&d=ociA55EFdZjr4RBPKs0gE-XY4i3O0HX1opb2K5jBFw&u=http%3a%2f%2fsds%5bsid2%5d%2elink = sid;
Grr, silly email scanner thinks that's a URL.
> +
> +	ctrl->sds[sid].min_port = min_port;
> +	ctrl->sds[sid].max_port = max_port;
> +
> +	return ctrl->sds[sid].phy;
> +}
> +
> +static int rtsds_phy_create(struct rtsds_ctrl *ctrl, u32 sid)
> +{
> +	struct rtsds_macro *macro;
> +
> +	ctrl->sds[sid].phy = devm_phy_create(ctrl->dev, NULL, &rtsds_phy_ops);
> +	if (IS_ERR(ctrl->sds[sid].phy))
> +		return PTR_ERR(ctrl->sds[sid].phy);
> +
> +	macro = devm_kzalloc(ctrl->dev, sizeof(*macro), GFP_KERNEL);
> +	if (!macro)
> +		return -ENOMEM;
> +
> +	macro->sid = sid;
> +	macro->ctrl = ctrl;
> +	phy_set_drvdata(ctrl->sds[sid].phy, macro);
> +
> +	ctrl->http://scanmail.trustwave.com/?c=20988&d=ociA55EFdZjr4RBPKs0gE-XY4i3O0HX1opelL56VRQ&u=http%3a%2f%2fsds%5bsid%5d%2elink = -1;
> +	ctrl->sds[sid].min_port = -1;
> +	ctrl->sds[sid].max_port = -1;
> +
> +#ifdef CONFIG_DEBUG_FS
> +	rtsds_dbg_init(ctrl, sid);
> +#endif
> +	return 0;
> +}
> +
> +static int rtsds_probe(struct platform_device *pdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct device *dev = &pdev->dev;
> +	struct phy_provider *provider;
> +	struct rtsds_ctrl *ctrl;
> +	int ret;
> +
> +	if (!np)
> +		return -EINVAL;
> +
> +	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
> +	if (!ctrl)
> +		return -ENOMEM;
> +
> +	ctrl->base = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(ctrl->base)) {
> +		dev_err(dev, "failed to map SerDes memory\n");
> +		return PTR_ERR(ctrl->base);
> +	}

     if (IS_ERR(ctrl->base))
        return dev_err_probe(dev, PTR_ERR(ctrl->base), "failed to map 
SerDes memory\n");

Also is there a convention on the capitalization? My preference is 
"SERDES" but I'd be happy to go with whatever the convention is.

> +
> +	ctrl->dev = dev;
> +	ctrl->conf = (struct rtsds_conf *)of_device_get_match_data(dev);
> +
> +	ret = of_property_read_u32(np, "controlled-ports", &ctrl->sds_mask);
> +	if (ret) {
> +		ctrl->sds_mask = 0;
> +		dev_warn(dev, "property controlled-ports not found, switched to read-only mode\n");
> +	}
Does this let us say "use whatever the earlier stages have setup". 
That'd be handy for me as I'm currently relying on the vendor U-Boot to 
do the SERDES init for me.
> +
> +	for (u32 sid = 0; sid <= ctrl->conf->max_sds; sid++) {
> +		ret = rtsds_phy_create(ctrl, sid);
> +		if (ret) {
> +			dev_err(dev, "failed to create PHY for SerDes %d\n", sid);
> +			return ret;
> +		}
> +	}
> +
> +	mutex_init(&ctrl->lock);
> +	dev_set_drvdata(dev, ctrl);
> +	provider = devm_of_phy_provider_register(dev, rtsds_simple_xlate);
> +
> +	rtsds_load_events(ctrl);
> +	rtsds_setup(ctrl);
> +
> +	dev_info(dev, "initialized (%d SerDes, %d pages, 32 registers, mask 0x%04x)",
> +		 ctrl->conf->max_sds + 1, ctrl->conf->max_page + 1, ctrl->sds_mask);
> +
> +	return PTR_ERR_OR_ZERO(provider);
> +}
> +
> +static const struct rtsds_conf rtsds_838x_conf = {
> +	.max_sds	= RTSDS_838X_MAX_SDS,
> +	.max_page	= RTSDS_838X_MAX_PAGE,
> +	.mask		= rtsds_838x_mask,
> +	.read		= rtsds_838x_read,
> +	.reset		= rtsds_838x_reset,
> +	.set_mode	= rtsds_838x_set_mode,
> +	.get_mode	= rtsds_838x_get_mode,
> +	.mode_map = {
> +		[PHY_INTERFACE_MODE_NA]		= RTSDS_COMBOMODE(0, 0),
> +		[PHY_INTERFACE_MODE_1000BASEX]	= RTSDS_COMBOMODE(4, 1), /* SerDes 4, 5 only */
> +		[PHY_INTERFACE_MODE_100BASEX]	= RTSDS_COMBOMODE(5, 1), /* SerDes 4, 5 only */
> +		[PHY_INTERFACE_MODE_QSGMII]	= RTSDS_COMBOMODE(6, 0),
> +	},
> +};
> +
> +static const struct rtsds_conf rtsds_839x_conf = {
> +	.max_sds	= RTSDS_839X_MAX_SDS,
> +	.max_page	= RTSDS_839X_MAX_PAGE,
> +	.mask		= rtsds_839x_mask,
> +	.read		= rtsds_839x_read,
> +	.reset		= rtsds_839x_reset,
> +	.set_mode	= rtsds_839x_set_mode,
> +	.get_mode	= rtsds_839x_get_mode,
> +	.mode_map = {
> +		[PHY_INTERFACE_MODE_NA]		= RTSDS_COMBOMODE(0, 0),
> +		[PHY_INTERFACE_MODE_10GBASER]	= RTSDS_COMBOMODE(1, 0), /* SerDes 8, 12 only */
> +		[PHY_INTERFACE_MODE_1000BASEX]	= RTSDS_COMBOMODE(7, 0), /* SerDes 12, 13 only */
> +		[PHY_INTERFACE_MODE_100BASEX]	= RTSDS_COMBOMODE(8, 0),
> +		[PHY_INTERFACE_MODE_QSGMII]	= RTSDS_COMBOMODE(6, 0),
> +		[PHY_INTERFACE_MODE_SGMII]	= RTSDS_COMBOMODE(7, 5), /* SerDes 8, 12, 13 only */
> +	},
> +};
> +
> +static const struct rtsds_conf rtsds_930x_conf = {
> +	.max_sds	= RTSDS_930X_MAX_SDS,
> +	.max_page	= RTSDS_930X_MAX_PAGE,
> +	.mask 		= rtsds_930x_mask,
> +	.read 		= rtsds_930x_read,
> +	.reset 		= rtsds_930x_reset,
> +	.set_mode 	= rtsds_930x_set_mode,
> +	.get_mode 	= rtsds_930x_get_mode,
> +	.mode_map = {
> +		[PHY_INTERFACE_MODE_NA]		= RTSDS_COMBOMODE(31, 0),
> +		[PHY_INTERFACE_MODE_10GBASER]	= RTSDS_COMBOMODE(26, 0),
> +		[PHY_INTERFACE_MODE_2500BASEX]  = RTSDS_COMBOMODE(22, 0),
> +		[PHY_INTERFACE_MODE_1000BASEX]	= RTSDS_COMBOMODE(4, 0),
> +		[PHY_INTERFACE_MODE_USXGMII]	= RTSDS_COMBOMODE(13, 0), /* SerDes 2-9 only */
> +		[PHY_INTERFACE_MODE_QUSGMII]	= RTSDS_COMBOMODE(13, 2), /* SerDes 2-9 only */
> +		[PHY_INTERFACE_MODE_QSGMII]	= RTSDS_COMBOMODE(6, 0),
> +	},
> +};
> +
> +static const struct rtsds_conf rtsds_931x_conf = {
> +	.max_sds	= RTSDS_931X_MAX_SDS,
> +	.max_page	= RTSDS_931X_MAX_PAGE,
> +	.mask		= rtsds_931x_mask,
> +	.read		= rtsds_931x_read,
> +	.reset		= rtsds_931x_reset,
> +	.set_mode	= rtsds_931x_set_mode,
> +	.get_mode	= rtsds_931x_get_mode,
> +	.mode_map = {
> +		[PHY_INTERFACE_MODE_NA]		= RTSDS_COMBOMODE(31, 63),
> +		[PHY_INTERFACE_MODE_10GBASER]	= RTSDS_COMBOMODE(31, 53),
> +		[PHY_INTERFACE_MODE_1000BASEX]	= RTSDS_COMBOMODE(31, 57), /* 1G/10G auto */
> +		[PHY_INTERFACE_MODE_USXGMII]	= RTSDS_COMBOMODE(13, 0),
> +		[PHY_INTERFACE_MODE_XGMII]	= RTSDS_COMBOMODE(16, 0),
> +		[PHY_INTERFACE_MODE_QSGMII]	= RTSDS_COMBOMODE(6, 0),
> +	},
> +};
> +
> +static const struct of_device_id rtsds_compatible_ids[] = {
> +	{ .compatible = "realtek,rtl8380-serdes",
> +	  .data = &rtsds_838x_conf,
> +	},
> +	{ .compatible = "realtek,rtl8390-serdes",
> +	  .data = &rtsds_839x_conf,
> +	},
> +	{ .compatible = "realtek,rtl9300-serdes",
> +	  .data = &rtsds_930x_conf,
> +	},
> +	{ .compatible = "realtek,rtl9310-serdes",
> +	  .data = &rtsds_931x_conf,
> +	},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, rtsds_compatible_ids);
> +
> +static struct platform_driver rtsds_platform_driver = {
> +	.probe		= rtsds_probe,
> +	.driver		= {
> +		.name	= "realtek,otto-serdes",
> +		.of_match_table = of_match_ptr(rtsds_compatible_ids),
> +	},
> +};
> +
> +module_platform_driver(rtsds_platform_driver);
> +
> +MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
> +MODULE_DESCRIPTION("SerDes driver for Realtek RTL83xx, RTL93xx switch SoCs");
> +MODULE_LICENSE("Dual MIT/GPL");
> --
> 2.44.0
Markus Stockhausen Oct. 7, 2024, 5:42 a.m. UTC | #2
> -----Ursprüngliche Nachricht-----
> Von: Chris Packham <chris.packham@alliedtelesis.co.nz> 
> Gesendet: Sonntag, 6. Oktober 2024 23:33
> An: Markus Stockhausen <markus.stockhausen@gmx.de>; linux-phy@lists.infradead.org
> Betreff: Re: [PATCH 2/4] phy: Realtek Otto SerDes: add driver source
>
> > ...
> > +
> > +
> > +	ret = of_property_read_u32(np, "controlled-ports", &ctrl->sds_mask);
> > +	if (ret) {
> > +		ctrl->sds_mask = 0;
> > +		dev_warn(dev, "property controlled-ports not found, switched to read-only mode\n");
> > +	}
> Does this let us say "use whatever the earlier stages have setup". 
> That'd be handy for me as I'm currently relying on the vendor U-Boot to 
> do the SERDES init for me.

So it is. This helps me to compare a self initialized SerDes with a U-Boot initialized one.

Markus
diff mbox series

Patch

diff --git a/drivers/phy/realtek/phy-rtl-otto-serdes.c b/drivers/phy/realtek/phy-rtl-otto-serdes.c
new file mode 100644
index 000000000000..c3a2584a69d0
--- /dev/null
+++ b/drivers/phy/realtek/phy-rtl-otto-serdes.c
@@ -0,0 +1,1181 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Realtek RTL838x, RTL839x, RTL930x & RTL931x SerDes PHY driver
+ * Copyright (c) 2024 Markus Stockhausen <markus.stockhausen@gmx.de>
+ */
+
+#include "phy-rtl-otto-serdes.h"
+
+/*
+ * The Otto platform has a lot of undocumented features and registers that configure
+ * the SerDes behaviour. Trying to include that here would clutter the driver. To
+ * provide maximum flexibility the driver can run register modification sequences
+ * during operation, E.g. when calling phy_reset() or phy_power_on(). These sequences
+ * need to be stored in the device tree. More documentation over there.
+ */
+
+static const char *rtsds_events[RTSDS_EVENT_MAX + 1] = {
+	[RTSDS_EVENT_SETUP]		= "cmd-setup",
+	[RTSDS_EVENT_INIT]		= "cmd-init",
+	[RTSDS_EVENT_POWER_ON]		= "cmd-power-on",
+	[RTSDS_EVENT_PRE_SET_MODE]	= "cmd-pre-set-mode",
+	[RTSDS_EVENT_POST_SET_MODE]	= "cmd-post-set-mode",
+	[RTSDS_EVENT_PRE_RESET]		= "cmd-pre-reset",
+	[RTSDS_EVENT_POST_RESET]	= "cmd-post-reset",
+	[RTSDS_EVENT_PRE_POWER_OFF]	= "cmd-pre-power-off",
+	[RTSDS_EVENT_POST_POWER_OFF]	= "cmd-post-power-off",
+};
+
+static void rtsds_load_events(struct rtsds_ctrl *ctrl)
+{
+	int i, elems, sz = sizeof(struct rtsds_seq);
+
+	for (i = 0; i <= RTSDS_EVENT_MAX; i++) {
+		elems = of_property_count_u16_elems(ctrl->dev->of_node ,rtsds_events[i]);
+		if (elems <= 0)
+			continue;
+
+		if ((elems * sizeof(u16)) % sz) {
+			dev_err(ctrl->dev, "ignore sequence %s (incomplete data)\n", rtsds_events[i]);
+			continue;
+		}
+
+		/* alloc one more element to provide stop marker in case it is missing in dt */
+		ctrl->sequence[i] = devm_kzalloc(ctrl->dev, elems * sizeof(u16) + sz, GFP_KERNEL);
+		if (!ctrl->sequence[i]) {
+			dev_err(ctrl->dev, "ignore sequence %s (allocation failed)\n", rtsds_events[i]);
+			continue;
+		}
+
+		if (of_property_read_u16_array(ctrl->dev->of_node, rtsds_events[i],
+					       (u16 *)ctrl->sequence[i], elems)) {
+			dev_err(ctrl->dev, "ignore sequence %s (DT load failed)\n", rtsds_events[i]);
+			kfree(ctrl->sequence[i]);
+			ctrl->sequence[i] = NULL;
+			continue;
+		}
+	}
+}
+
+static int rtsds_run_event(struct rtsds_ctrl *ctrl, u32 sid, int evt)
+{
+	struct rtsds_seq *seq;
+	int ret, step = 1, delay = 0;
+
+	if (evt > RTSDS_EVENT_MAX || sid > ctrl->conf->max_sds)
+		return -EINVAL;
+
+	seq = ctrl->sequence[evt];
+
+	if (!seq)
+		return 0;
+
+	while (seq->action != RTSDS_SEQ_STOP) {
+		if ((seq->action == RTSDS_SEQ_WAIT) && (seq->ports & BIT(sid)))
+			delay = seq->val;
+
+		if (delay)
+			usleep_range(delay << 10, (delay << 10) + 1000);
+
+		if ((seq->action == RTSDS_SEQ_MASK) && (seq->ports & BIT(sid))) {
+			ret = ctrl->conf->mask(ctrl, sid, seq->page,
+					 seq->reg, seq->val, seq->mask);
+
+			if (ret) {
+				dev_err(ctrl->dev, "sequence %s failed at step %d", rtsds_events[evt], step);
+				return -EIO;
+			}
+		}
+
+		seq++;
+		step++;
+	}
+
+	return 0;
+}
+
+static int rtsds_hwmode_to_phymode(struct rtsds_ctrl *ctrl, int hwmode)
+{
+	for (int m = 0; m < PHY_INTERFACE_MODE_MAX; m++)
+		if (ctrl->conf->mode_map[m] == hwmode)
+			return m;
+
+	return PHY_INTERFACE_MODE_MAX;
+}
+
+static void rtsds_83xx_soft_reset(struct rtsds_ctrl *ctrl, u32 sidlo, u32 sidhi, int usec)
+{
+	for (u32 sid = sidlo; sid <= sidhi; sid++)
+		ctrl->conf->mask(ctrl, sid, 0x00, 0x03, 0x7146, 0xffff);
+	usleep_range(usec, usec + 1000);
+	for (u32 sid = sidlo; sid <= sidhi; sid++)
+		ctrl->conf->mask(ctrl, sid, 0x00, 0x03, 0x7106, 0xffff);
+}
+
+/*
+ * The RTL838x has 6 SerDes. The 16 bit registers start at 0xbb00e780 and are mapped
+ * directly into 32 bit memory addresses. High 16 bits are always empty. Quite confusing
+ * but the register ranges are cluttered and contain holes.
+ */
+
+static int rtsds_838x_offset(u32 sid, u32 page, u32 reg)
+{
+	if (page == 0 || page == 3)
+		return (sid << 9) + (page << 7) + (reg << 2);
+	else
+		return 0xb80 + (sid << 8) + (page << 7) + (reg << 2);
+}
+
+static int rtsds_838x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg)
+{
+	int offs;
+
+	if (sid > RTSDS_838X_MAX_SDS || page > RTSDS_838X_MAX_PAGE || reg > 31)
+		return -EINVAL;
+
+	offs = rtsds_838x_offset(sid, page, reg);
+
+	/* read twice for link status latch */
+	if (page == 2 && reg == 1)
+		ioread32(ctrl->base + offs);
+
+	return ioread32(ctrl->base + offs);
+}
+
+static int rtsds_838x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask)
+{
+	int offs;
+
+	if (sid > RTSDS_838X_MAX_SDS || page > RTSDS_838X_MAX_PAGE || reg > 31)
+		return -EINVAL;
+
+	offs = rtsds_838x_offset(sid, page, reg);
+
+	/* read twice for link status latch */
+	if (page == 2 && reg == 1)
+		ioread32(ctrl->base + offs);
+
+	iomask32(mask, val, ctrl->base + offs);
+
+	return 0;
+}
+
+static int rtsds_838x_reset(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	if (sid > RTSDS_838X_MAX_SDS)
+		return -EINVAL;
+
+	/* RX reset */
+	rtsds_838x_mask(ctrl, sid, 0x01, 0x09, 0x0200, 0x0200);
+	rtsds_838x_mask(ctrl, sid, 0x01, 0x09, 0x0000, 0x0200);
+
+	/* CMU reset */
+	rtsds_838x_mask(ctrl, sid, 0x01, 0x00, 0x4040, 0xffff);
+	rtsds_838x_mask(ctrl, sid, 0x01, 0x00, 0x4740, 0xffff);
+	rtsds_838x_mask(ctrl, sid, 0x01, 0x00, 0x47c0, 0xffff);
+	rtsds_838x_mask(ctrl, sid, 0x01, 0x00, 0x4000, 0xffff);
+
+	rtsds_83xx_soft_reset(ctrl, sid, sid, 1000);
+
+	/* RX/TX reset */
+	rtsds_838x_mask(ctrl, sid, 0x00, 0x00, 0x0400, 0xffff);
+	rtsds_838x_mask(ctrl, sid, 0x00, 0x00, 0x0403, 0xffff);
+
+	return 0;
+}
+
+static int rtsds_838x_set_mode(struct rtsds_ctrl *ctrl, u32 sid, int combomode)
+{
+	int shift, mode = RTSDS_MODE(combomode), submode = RTSDS_SUBMODE(combomode);
+
+	if (sid > RTSDS_838X_MAX_SDS)
+		return -EINVAL;
+
+	if (sid == 4 || sid == 5) {
+		shift = (sid - 4) * 3;
+		iomask32(0x7 << shift, (submode & 0x7) << shift, RTSDS_838X_INT_MODE_CTRL);
+	}
+
+	shift = 25 - sid * 5;
+	iomask32(0x1f << shift, (mode & 0x1f) << shift, RTSDS_838X_SDS_MODE_SEL);
+
+	return 0;
+}
+
+static int rtsds_838x_get_mode(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	int shift, mode, submode = 0;
+
+	if (sid < 0 || sid > RTSDS_838X_MAX_SDS)
+		return -EINVAL;
+
+	if (sid == 4 || sid == 5) {
+		shift = (sid - 4) * 3;
+		submode = (ioread32(RTSDS_838X_INT_MODE_CTRL) >> shift) & 0x7;
+	}
+
+	shift = 25 - sid * 5;
+	mode = (ioread32(RTSDS_838X_SDS_MODE_SEL) >> shift) & 0x1f;
+
+	return RTSDS_COMBOMODE(mode, submode);
+}
+
+/*
+ * The RLT839x has 14 SerDes starting at 0xbb00a000. 0-7, 10, 11 are 5GBit, 8, 9, 12, 13
+ * are 10GBit. Two adjacent SerDes are tightly coupled and share a 1024 bytes register area.
+ * Per 32 bit address two registers are stored. The first register is stored in the lower
+ * 2 bytes ("on the right" due to big endian) and the second register in the upper 2 bytes.
+ * We know the following register areas:
+ *
+ * - XSG0	(4 pages @ offset 0x000): for even SerDes
+ * - XSG1	(4 pages @ offset 0x100): for odd SerDes
+ * - TGRX	(4 pages @ offset 0x200): for even 10G SerDes
+ * - ANA_RG	(2 pages @ offset 0x300): for even 5G SerDes
+ * - ANA_RG	(2 pages @ offset 0x380): for odd 5G SerDes
+ * - ANA_TG	(2 pages @ offset 0x300): for even 10G SerDes
+ * - ANA_TG	(2 pages @ offset 0x380): for odd 10G SerDes
+ *
+ * The most consistent mapping we can achieve that aligns to the RTL93xx devices is:
+ *
+ *		even 5G SerDes	odd 5G SerDes	even 10G SerDes	odd 10G SerDes
+ * Page 0: 	XSG0/0		XSG1/0		XSG0/0		XSG1/0
+ * Page 1: 	XSG0/1		XSG1/1		XSG0/1		XSG1/1
+ * Page 2: 	XSG0/2		XSG1/2 		XSG0/2		XSG1/2
+ * Page 3: 	XSG0/3		XSG1/3		XSG0/3		XSG1/3
+ * Page 4: 	<zero>		<zero>		TGRX/0		<zero>
+ * Page 5: 	<zero>		<zero>		TGRX/1		<zero>
+ * Page 6: 	<zero>		<zero>		TGRX/2		<zero>
+ * Page 7: 	<zero>		<zero>		TGRX/3		<zero>
+ * Page 8: 	ANA_RG		ANA_RG		<zero>		<zero>
+ * Page 9: 	ANA_RG_EXT	ANA_RG_EXT	<zero>		<zero>
+ * Page 10: 	<zero>		<zero>		ANA_TG		ANA_TG
+ * Page 11: 	<zero>		<zero>		ANA_TG_EXT	ANA_TG_EXT
+ */
+
+static int rtsds_839x_offset(u32 sid, u32 page, u32 reg)
+{
+	int offs = ((sid & 0xfe) << 9) + ((reg & 0xfe) << 1);
+
+	if (page < 4) {
+		offs += ((sid & 1) << 8) + (page << 6);
+	} else if (page < 8) {
+		if (sid != 8 && sid != 12)
+			return -1;
+		offs += 0x100 + (page << 6);
+	} else if (page < 10) {
+		if (sid == 8 || sid == 9 || sid == 12 || sid == 13)
+			return -1;
+		offs += 0x100 + ((sid & 1) << 7) + (page << 6);
+	} else {
+		if (sid != 8 && sid != 9 && sid != 12 && sid != 13)
+			return -1;
+		offs += 0x100 + ((sid & 1) << 7) + ((page - 2) << 6);
+	}
+
+	return offs;
+}
+
+static int rtsds_839x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg)
+{
+	int offs, shift = (reg << 4) & 0x10;
+
+	if (sid > RTSDS_839X_MAX_SDS || page > RTSDS_839X_MAX_PAGE || reg > 31)
+		return -EINVAL;
+
+	offs = rtsds_839x_offset(sid, page, reg);
+	if (offs < 0)
+		return 0;
+
+	/* read twice for link status latch */
+	if (page == 2 && reg == 1)
+		ioread32(ctrl->base + offs);
+
+	return (ioread32(ctrl->base + offs) >> shift) & 0xffff;
+}
+
+static int rtsds_839x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask)
+{
+	int oldval, offs = ((sid & 0xfe) << 9) + ((reg & 0xfe) << 1);
+
+	if (sid > RTSDS_839X_MAX_SDS || page > RTSDS_839X_MAX_PAGE || reg > 31)
+		return -EINVAL;
+
+	offs = rtsds_839x_offset(sid, page, reg);
+	if (offs < 0)
+		return 0;
+
+	/* read twice for link status latch */
+	if (page == 2 && reg == 1)
+		ioread32(ctrl->base + offs);
+
+	oldval = ioread32(ctrl->base + offs);
+	val = reg & 1 ? (oldval & ~(mask << 16)) | (val << 16) : (oldval & ~mask) | val;
+	iowrite32(val, ctrl->base + offs);
+
+	return 0;
+}
+
+static int rtsds_839x_set_mode(struct rtsds_ctrl *ctrl, u32 sid, int combomode)
+{
+	int shift = (sid & 7) << 2, offs = (sid >> 1) & ~3;
+	int mode = RTSDS_MODE(combomode), submode = RTSDS_SUBMODE(combomode);
+
+	if (sid > RTSDS_839X_MAX_SDS)
+		return -EINVAL;
+
+	rtsds_839x_mask(ctrl, sid, 0, 4, (submode << 12) & 0xf000, 0xf000);
+	iomask32(0xf << shift, (mode & 0xf) << shift, RTSDS_839X_MAC_SERDES_IF_CTRL + offs);
+
+	return 0;
+}
+
+static int rtsds_839x_get_mode(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	int mode, submode, shift = (sid & 7) << 2, offs = (sid >> 1) & ~3;
+
+	if (sid > RTSDS_839X_MAX_SDS)
+		return -EINVAL;
+
+	submode = (rtsds_839x_read(ctrl, sid, 0, 4) >> 12) & 0xf;
+	mode = (ioread32(RTSDS_839X_MAC_SERDES_IF_CTRL + offs) >> shift) & 0xf;
+
+	return RTSDS_COMBOMODE(mode, submode);
+}
+
+static int rtsds_839x_reset(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	int lo = sid & ~1, hi = sid | 1;
+
+	if (sid > RTSDS_839X_MAX_SDS)
+		return -EINVAL;
+
+	/*
+	 * A reset basically consists of two steps. First a clock (CMU) reset and a
+	 * digital soft reset afterwards. Some of the CMU registers are shared on
+	 * adjacent SerDes so as of now we can only perform a reset on a pair.
+	 */
+
+	if (lo < 8 || lo == 10) {
+		rtsds_839x_mask(ctrl, hi, 0x09, 0x01, 0x0050, 0xffff);
+		rtsds_839x_mask(ctrl, hi, 0x09, 0x01, 0x00f0, 0xffff);
+		rtsds_839x_mask(ctrl, hi, 0x09, 0x01, 0x0000, 0xffff);
+		rtsds_839x_mask(ctrl, lo, 0x08, 0x14, 0x0000, 0x0001);
+		rtsds_839x_mask(ctrl, lo, 0x08, 0x14, 0x0200, 0x0200);
+		usleep_range(100000, 101000);
+		rtsds_839x_mask(ctrl, lo, 0x08, 0x14, 0x0000, 0x0200);
+	} else {
+		rtsds_839x_mask(ctrl, lo, 0x0a, 0x10, 0x0000, 0x0008);
+		rtsds_839x_mask(ctrl, lo, 0x0b, 0x00, 0x8000, 0x8000);
+		usleep_range(100000, 101000);
+		rtsds_839x_mask(ctrl, lo, 0x0b, 0x00, 0x0000, 0x8000);
+	}
+
+	rtsds_83xx_soft_reset(ctrl, lo, hi, 100000);
+
+	return 0;
+}
+
+/*
+ * The RTL930x family has 12 SerdDes. They are accessed through two IO registers
+ * at 0xbb0003b0 which simulate commands to an internal MDIO bus. From the current
+ * observation there are 3 types of SerDes:
+ *
+ * - SerDes 0,1 are of unknown type
+ * - SerDes 2-9 are USXGMII capabable with either quad or single configuration
+ * - SerDes 10-11 are of unknown type
+ */
+
+static int rtsds_930x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg)
+{
+	int cnt = 100, cmd = (sid << 2) | (page << 7) | (reg << 13) | 1;
+
+	if (sid > RTSDS_930X_MAX_SDS || page > RTSDS_930X_MAX_PAGE || reg > 31)
+		return -EINVAL;
+
+	iowrite32(cmd, ctrl->base);
+
+	while (--cnt && (ioread32(ctrl->base) & 1))
+		usleep_range(50, 60);
+
+	return cnt ? ioread32(ctrl->base + 4) & 0xffff : -EIO;
+}
+
+static int rtsds_930x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg, u32 val, u32 mask)
+{
+	int oldval, cnt = 100, cmd = (sid << 2) | (page << 7) | (reg << 13) | 3;
+
+	if (sid > RTSDS_930X_MAX_SDS || page > RTSDS_930X_MAX_PAGE || reg > 31)
+		return -EINVAL;
+
+	if (mask != 0xffff) {
+		oldval = rtsds_930x_read(ctrl, sid, page, reg);
+		if (oldval < 0)
+			return -EIO;
+		oldval &= ~mask;
+		val |= oldval;
+	}
+
+	iowrite32(val, ctrl->base + 4);
+	iowrite32(cmd, ctrl->base);
+
+	while (--cnt && (ioread32(ctrl->base) & 1))
+		usleep_range(50, 60);
+
+	return cnt ? 0 : - EIO;
+}
+
+static void rtsds_930x_mode_offset(int sid,
+				   void __iomem __force **modereg, int *modeshift,
+				   void __iomem __force **subreg, int *subshift)
+{
+	if (sid > 3) {
+		*subreg = RTSDS_930X_SDS_SUBMODE_CTRL1;
+		*subshift = (sid - 4) * 5;
+	} else {
+		*subreg = RTSDS_930X_SDS_SUBMODE_CTRL0;
+		*subshift = (sid - 2) * 5;
+	}
+
+	if (sid < 4) {
+		*modeshift = sid * 6;
+		*modereg = RTSDS_930X_SDS_MODE_SEL_0;
+	} else if (sid < 8) {
+		*modeshift = (sid - 4) * 6;
+		*modereg = RTSDS_930X_SDS_MODE_SEL_1;
+	} else if (sid < 10) {
+		*modeshift = (sid - 8) * 6;
+		*modereg = RTSDS_930X_SDS_MODE_SEL_2;
+	} else {
+		*modeshift = (sid - 10) * 6;
+		*modereg = RTSDS_930X_SDS_MODE_SEL_3;
+	}
+}
+
+static int rtsds_930x_set_mode(struct rtsds_ctrl *ctrl, u32 sid, int combomode)
+{
+	int modeshift, subshift;
+	int mode = RTSDS_MODE(combomode);
+	int submode = RTSDS_SUBMODE(combomode);
+	void __iomem __force *modereg;
+	void __iomem __force *subreg;
+
+	if (sid > RTSDS_930X_MAX_SDS)
+		return -EINVAL;
+
+	rtsds_930x_mode_offset(sid, &modereg, &modeshift, &subreg, &subshift);
+	if (sid >= 2 || sid <= 9)
+		iomask32(0x1f << subshift, (submode & 0x1f) << subshift, subreg);
+	iomask32(0x1f << modeshift, (mode & 0x1f) << modeshift, modereg);
+
+	return 0;
+}
+
+static int rtsds_930x_get_mode(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	int modeshift, subshift, mode, submode = 0;
+	void __iomem __force *modereg;
+	void __iomem __force *subreg;
+
+	if (sid > RTSDS_930X_MAX_SDS)
+		return -EINVAL;
+
+	rtsds_930x_mode_offset(sid, &modereg, &modeshift, &subreg, &subshift);
+	if (sid >= 2 || sid <= 9)
+		submode = (ioread32(subreg) >> subshift) & 0x1f;
+	mode = ioread32(modereg) >> modeshift & 0x1f;
+
+	return RTSDS_COMBOMODE(mode, submode);
+}
+
+static int rtsds_930x_reset(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	int modecur, modeoff = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA];
+
+	if (sid > RTSDS_930X_MAX_SDS)
+		return -EINVAL;
+
+	modecur = rtsds_930x_get_mode(ctrl, sid);
+
+	/* It is enough to power off SerDes and set to old mode again */
+	if (modecur != modeoff) {
+		rtsds_930x_set_mode(ctrl, sid, modeoff);
+		rtsds_930x_set_mode(ctrl, sid, modecur);
+	}
+
+	return 0;
+}
+
+/*
+ * The RTL931x family has 14 "frontend" SerDes that are magically cascaded. All
+ * operations (e.g. reset) work on this frontend view while their registers are
+ * distributed over a total of 32 background SerDes. Two types of SerDes have been
+ * identified:
+ *
+ * A "even" SerDes with numbers 0, 1, 2, 4, 6, 8, 10, 12 works on on two background
+ * SerDes. 64 analog und 64 XGMII data pages are coming from a first background
+ * SerDes while another 64 XGMII pages are served from a second SerDes.
+ *
+ * The "odd" SerDes with numbers 3, 5, 7, 9, 11 & 13 SerDes consist of a total of 3
+ * background SerDes (one analog and two XGMII) each with an own page/register set.
+ *
+ * As strange as this reads try to get this aligned and mix pages as follows
+ *
+ * frontend page		"even" frontend SerDes 	"odd" frontend SerDes
+ * page 0-63 (analog): 		back sid page 0-63	back sid page 0-63
+ * page 64-127 (XGMII1):	back sid page 0-63	back sid +1 page 0-63
+ * page 128-191 (XGMII2):	back sid +1 page 0-63	back sid +2 page 0-63
+ */
+
+static int rtsds_931x_backsid(u32 sid, u32 page)
+{
+	int map[] = {0, 1, 2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23};
+	int backsid = map[sid];
+
+	if ((sid & 1) && (sid != 1))
+		backsid += (page >> 6); /* distribute "odd" to 3 background SerDes */
+	else if (page >= 128)
+		backsid += 1; /* "distribute "even" to 2 background SerDes */
+
+	return backsid;
+}
+
+static int rtsds_931x_read(struct rtsds_ctrl *ctrl, u32 sid, u32 page, u32 reg)
+{
+	int backsid, cmd, cnt = 100;
+
+	if (sid > RTSDS_931X_MAX_SDS || page > RTSDS_931X_MAX_PAGE || reg > 31)
+		return -EINVAL;
+
+	backsid = rtsds_931x_backsid(sid, page);
+	cmd = (backsid << 2) | ((page & 0x3f) << 7) | (reg << 13) | 1;
+
+	iowrite32(cmd, ctrl->base);
+	while (--cnt && (ioread32(ctrl->base) & 1))
+		usleep_range(50, 60);
+
+	return cnt ? ioread32(ctrl->base + 4) & 0xffff : -EIO;
+}
+
+static int rtsds_931x_mask(struct rtsds_ctrl *ctrl, u32 sid, u32 page,
+			   u32 reg, u32 val, u32 mask)
+{
+	int backsid, cmd, oldval, cnt = 100;
+
+	if (sid > RTSDS_931X_MAX_SDS || page > RTSDS_931X_MAX_PAGE || reg > 31)
+		return -EINVAL;
+
+	backsid = rtsds_931x_backsid(sid, page);
+	cmd = (backsid << 2) | ((page & 0x3f) << 7) | (reg << 13) | 3;
+
+	if (mask != 0xffff) {
+		oldval = rtsds_931x_read(ctrl, sid, page, reg);
+		if (oldval < 0)
+			return -EIO;
+		oldval &= ~mask;
+		val |= oldval;
+	}
+
+	iowrite32(val, ctrl->base + 4);
+	iowrite32(cmd, ctrl->base);
+	while (--cnt && (ioread32(ctrl->base) & 1))
+		usleep_range(50, 60);
+
+	return cnt ? 0 : - EIO;
+}
+
+static int rtsds_931x_set_mode(struct rtsds_ctrl *ctrl, u32 sid, int combomode)
+{
+	int shift = (sid & 3) << 3, offs = sid & ~3;
+	int mode = RTSDS_MODE(combomode);
+	int submode = RTSDS_SUBMODE(combomode);
+
+	if (sid > RTSDS_931X_MAX_SDS)
+		return -EINVAL;
+
+	rtsds_931x_mask(ctrl, sid, 31, 9, (submode & 0x3f << 6), 0x0fc0);
+	iomask32(0xff << shift, ((mode | RTSDS_931X_SDS_FORCE_SETUP) & 0xff) << shift,
+		 RTSDS_931X_SERDES_MODE_CTRL + offs);
+
+	return 0;
+}
+
+static int rtsds_931x_get_mode(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	int mode, submode, shift = (sid & 3) << 3, offs = sid & ~3;
+
+	if (sid > RTSDS_931X_MAX_SDS)
+		return -EINVAL;
+
+	submode = (rtsds_931x_read(ctrl, sid, 31, 9) >> 6) & 0x3f;
+	mode = (ioread32(RTSDS_931X_SERDES_MODE_CTRL + offs) >> shift) & 0x1f;
+
+	return RTSDS_COMBOMODE(mode, submode);
+}
+
+static int rtsds_931x_reset(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	int pwr, modecur, modeoff = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA];
+
+	if (sid > RTSDS_931X_MAX_SDS)
+		return -EINVAL;
+
+	modecur = rtsds_931x_get_mode(ctrl, sid);
+
+	if (modecur != modeoff) {
+		/* reset with mode switch cycle while being powered off */
+		pwr = ioread32(RTSDS_931X_PS_SERDES_OFF_MODE_CTRL);
+		iowrite32(pwr | BIT(sid), RTSDS_931X_PS_SERDES_OFF_MODE_CTRL);
+		rtsds_931x_set_mode(ctrl, sid, modeoff);
+		rtsds_931x_set_mode(ctrl, sid, modecur);
+		iowrite32(pwr, RTSDS_931X_PS_SERDES_OFF_MODE_CTRL);
+	}
+
+	return 0;
+}
+
+int rtsds_read(struct phy *phy, u32 page, u32 reg)
+{
+	struct rtsds_macro *macro = phy_get_drvdata(phy);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	u32 sid = macro->sid;
+
+	return ctrl->conf->read(ctrl, sid, page, reg);
+}
+
+int rtsds_mask(struct phy *phy, u32 page, u32 reg, u32 val, u32 mask)
+{
+	struct rtsds_macro *macro = phy_get_drvdata(phy);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	u32 sid = macro->sid;
+
+	if (!(ctrl->sds_mask & BIT(sid)))
+		return -EACCES;
+
+	return ctrl->conf->mask(ctrl, sid, page, reg, val, mask);
+}
+
+int rtsds_write(struct phy *phy, u32 page, u32 reg, u32 val)
+{
+	return rtsds_mask(phy, page, reg, val, 0xffff);
+}
+
+static int rtsds_phy_init(struct phy *phy)
+{
+	struct rtsds_macro *macro = phy_get_drvdata(phy);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	u32 sid = macro->sid;
+	int ret;
+
+	if (!(ctrl->sds_mask & BIT(sid)))
+		return 0;
+
+	mutex_lock(&ctrl->lock);
+	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_INIT);
+	mutex_unlock(&ctrl->lock);
+
+	if (ret)
+		dev_err(ctrl->dev, "init failed for SerDes %d\n", sid);
+
+	return ret;
+}
+
+static int rtsds_phy_power_on(struct phy *phy)
+{
+	struct rtsds_macro *macro = phy_get_drvdata(phy);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	u32 sid = macro->sid;
+	int ret;
+
+	if (!(ctrl->sds_mask & BIT(sid)))
+		return 0;
+
+	mutex_lock(&ctrl->lock);
+	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_POWER_ON);
+	mutex_unlock(&ctrl->lock);
+
+	if (ret)
+		dev_err(ctrl->dev, "power on failed for SerDes %d\n", sid);
+
+	return ret;
+}
+
+static int rtsds_phy_power_off(struct phy *phy)
+{
+	struct rtsds_macro *macro = phy_get_drvdata(phy);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	u32 sid = macro->sid;
+	int ret;
+
+	if (!(ctrl->sds_mask & BIT(sid)))
+		return 0;
+
+	mutex_lock(&ctrl->lock);
+	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_PRE_POWER_OFF);
+	if (!ret)
+		ret = ctrl->conf->set_mode(ctrl, sid, ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA]);
+	if (!ret)
+		ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_POST_POWER_OFF);
+	mutex_unlock(&ctrl->lock);
+
+	if (ret)
+		dev_err(ctrl->dev, "power off failed for SerDes %d\n", sid);
+
+	return ret;
+}
+
+static int rtsds_phy_set_mode_int(struct rtsds_ctrl *ctrl, u32 sid, int phymode, int hwmode)
+{
+	int ret;
+
+	mutex_lock(&ctrl->lock);
+	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_PRE_SET_MODE);
+	if (!ret)
+		ret = ctrl->conf->set_mode(ctrl, sid, hwmode);
+	if (!ret) {
+		ctrl->sds[sid].mode = phymode;
+		ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_POST_SET_MODE);
+	}
+	mutex_unlock(&ctrl->lock);
+
+	if (ret)
+		dev_err(ctrl->dev, "set mode failed for SerDes %d\n", sid);
+
+	return ret;
+}
+
+static int rtsds_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+	struct rtsds_macro *macro = phy_get_drvdata(phy);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	u32 sid = macro->sid;
+
+	if (!(ctrl->sds_mask & BIT(sid)))
+		return 0;
+
+	if (mode != PHY_MODE_ETHERNET)
+		return -EINVAL;
+
+	return rtsds_phy_set_mode_int(ctrl, sid, submode, ctrl->conf->mode_map[submode]);
+}
+
+static int rtsds_phy_reset_int(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	int ret;
+
+	mutex_lock(&ctrl->lock);
+	ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_PRE_RESET);
+	if (!ret)
+		ret = ctrl->conf->reset(ctrl, sid);
+	if (!ret)
+		ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_POST_RESET);
+	mutex_unlock(&ctrl->lock);
+
+	if (ret)
+		dev_err(ctrl->dev, "reset failed for SerDes %d\n", sid);
+
+	return ret;
+}
+
+static int rtsds_phy_reset(struct phy *phy)
+{
+	struct rtsds_macro *macro = phy_get_drvdata(phy);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	u32 sid = macro->sid;
+
+	if (!(ctrl->sds_mask & BIT(sid)))
+		return 0;
+
+	return rtsds_phy_reset_int(ctrl, sid);
+}
+
+static const struct phy_ops rtsds_phy_ops = {
+	.init		= rtsds_phy_init,
+	.power_on	= rtsds_phy_power_on,
+	.power_off	= rtsds_phy_power_off,
+	.reset		= rtsds_phy_reset,
+	.set_mode 	= rtsds_phy_set_mode,
+	.owner		= THIS_MODULE,
+};
+
+/*
+ * The SerDes offer a lot of magic that sill needs to be uncovered. To help further
+ * development provide some basic debugging about registers, modes and polarity. The
+ * mode can be changed on the fly and executes the normal setter including events.
+ */
+
+#ifdef CONFIG_DEBUG_FS
+static const char *rtsds_page_name[RTSDS_PAGE_NAMES]	= {
+	[0] = "SDS",		[1] = "SDS_EXT",
+	[2] = "FIB",		[3] = "FIB_EXT",
+	[4] = "DTE",		[5] = "DTE_EXT",
+	[6] = "TGX",		[7] = "TGX_EXT",
+	[8] = "ANA_RG",		[9] = "ANA_RG_EXT",
+	[10] = "ANA_TG",	[11] = "ANA_TG_EXT",
+	[31] = "ANA_WDIG",
+	[32] = "ANA_MISC",	[33] = "ANA_COM",
+	[34] = "ANA_SP",	[35] = "ANA_SP_EXT",
+	[36] = "ANA_1G",	[37] = "ANA_1G_EXT",
+	[38] = "ANA_2G",	[39] = "ANA_2G_EXT",
+	[40] = "ANA_3G",	[41] = "ANA_3G_EXT",
+	[42] = "ANA_5G",	[43] = "ANA_5G_EXT",
+	[44] = "ANA_6G",	[45] = "ANA_6G_EXT",
+	[46] = "ANA_10G",	[47] = "ANA_10G_EXT",
+};
+
+static ssize_t rtsds_dbg_mode_show(struct seq_file *seqf, void *unused)
+{
+	struct rtsds_macro *macro = dev_get_drvdata(seqf->private);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	int mode, sid = macro->sid;
+
+	mutex_lock(&ctrl->lock);
+	mode = ctrl->conf->get_mode(ctrl, sid);
+	mutex_unlock(&ctrl->lock);
+
+	seq_printf(seqf, "hw mode: 0x%X\n", mode);
+	seq_printf(seqf, "phy mode: ");
+
+	if (ctrl->sds[sid].mode == PHY_INTERFACE_MODE_NA)
+		seq_printf(seqf, "off\n");
+	else
+		seq_printf(seqf, "%s\n", phy_modes(ctrl->sds[sid].mode));
+
+	return 0;
+}
+
+static ssize_t rtsds_dbg_mode_write(struct file *file, const char __user *userbuf,
+				size_t count, loff_t *ppos)
+{
+	struct seq_file *seqf = file->private_data;
+	struct rtsds_macro *macro = dev_get_drvdata(seqf->private);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	int ret, hwmode, phymode, sid = macro->sid;
+
+	ret = kstrtou32_from_user(userbuf, count, 10, &hwmode);
+	if (ret)
+		return ret;
+
+	/*
+	 * As we are still exploring the SerDes this debug function allows to set
+	 * arbitrary modes into the SerDes. While this might confuse the internal
+	 * driver handling it helps to avoid to rebuild & start from scratch for
+	 * every test.
+	 */
+	phymode = rtsds_hwmode_to_phymode(ctrl, hwmode);
+	rtsds_phy_set_mode_int(ctrl, sid, phymode, hwmode);
+
+	return count;
+}
+DEFINE_SHOW_STORE_ATTRIBUTE(rtsds_dbg_mode);
+
+static int rtsds_dbg_registers_show(struct seq_file *seqf, void *unused)
+{
+	struct rtsds_macro *macro = dev_get_drvdata(seqf->private);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	u32 page, reg, sid = macro->sid;
+
+	seq_printf(seqf, "%*s", 12 , "");
+	for (int i = 0;i < 32; i++)
+		seq_printf(seqf, "%*d", 5, i);
+
+	for (page = 0; page <= ctrl->conf->max_page; page++) {
+		if (page < RTSDS_PAGE_NAMES && rtsds_page_name[page])
+			seq_printf(seqf, "\n%*s: ", -11, rtsds_page_name[page]);
+		else if (page == 64 || page == 128)
+			seq_printf(seqf, "\nXGMII_%d    : ", page >> 6);
+		else
+			seq_printf(seqf, "\nPAGE_%03d   : ", page);
+		for (reg = 0; reg < 32; reg++)
+			seq_printf(seqf, "%04X ", ctrl->conf->read(ctrl, sid, page, reg));
+	}
+	seq_printf(seqf, "\n");
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(rtsds_dbg_registers);
+
+static int rtsds_dbg_polarity_show(struct seq_file *seqf, void *unused)
+{
+	struct rtsds_macro *macro = dev_get_drvdata(seqf->private);
+	struct rtsds_ctrl *ctrl = macro->ctrl;
+	u32 reg, sid = macro->sid;
+
+	reg = ctrl->conf->read(ctrl, sid, RTSDS_PAGE_SDS, 0);
+
+	seq_printf(seqf, "tx polarity: ");
+	seq_printf(seqf, reg & RTSDS_INV_HSO ? "inverse" : "normal");
+	seq_printf(seqf, "\nrx polarity: ");
+	seq_printf(seqf, reg & RTSDS_INV_HSI ? "inverse" : "normal");
+	seq_printf(seqf, "\n");
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(rtsds_dbg_polarity);
+
+static void rtsds_dbg_init(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	debugfs_create_file("mode", 0600, ctrl->sds[sid].phy->debugfs,
+			    &ctrl->sds[sid].phy->dev, &rtsds_dbg_mode_fops);
+
+	debugfs_create_file("polarity", 0400, ctrl->sds[sid].phy->debugfs,
+			    &ctrl->sds[sid].phy->dev, &rtsds_dbg_polarity_fops);
+
+	debugfs_create_file("registers", 0400, ctrl->sds[sid].phy->debugfs,
+			    &ctrl->sds[sid].phy->dev, &rtsds_dbg_registers_fops);
+}
+#endif /* CONFIG_DEBUG_FS */
+
+static void rtsds_setup(struct rtsds_ctrl *ctrl)
+{
+	int hwmode, ret;
+
+	for (u32 sid = 0; sid <= ctrl->conf->max_sds; sid++) {
+		if (ctrl->sds_mask & BIT(sid)) {
+			/* power off controlled SerDes */
+			hwmode = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA];
+			ret = ctrl->conf->set_mode(ctrl, sid, hwmode);
+			if (!ret)
+				ret = rtsds_run_event(ctrl, sid, RTSDS_EVENT_SETUP);
+			if (ret)
+				dev_err(ctrl->dev, "setup failed for SerDes %d\n", sid);
+		}
+		/* in any case sync back hardware status */
+		hwmode = ctrl->conf->get_mode(ctrl, sid);
+		ctrl->sds[sid].mode = rtsds_hwmode_to_phymode(ctrl, hwmode);
+	}
+}
+
+static struct phy *rtsds_simple_xlate(struct device *dev,
+				      const struct of_phandle_args *args)
+{
+	struct rtsds_ctrl *ctrl = dev_get_drvdata(dev);
+	int sid, sid2, min_port, max_port;
+
+	/*
+	 * Some Realtek Ethernet transceivers (e.g. RLT8218B) will be attached via a
+	 * bonded 2x QSGMII link to two SerDes. Others (e.g. RTL8218D) allow to make
+	 * use of single XGMII or dual QSGMII links. When a switch port tries to lookup
+	 * the SerDes it is attached to we honour that by an enhanced mapping. We allow
+	 * two possible configuration options. Standalone or linked to another. E.g.
+	 *
+	 * Single: port@24 { phys = <&serdes 4 -1 MinPort MaxPort>; };
+	 * Dual:   port@24 { phys = <&serdes 4  5 MinPort MaxPort>; };
+	 *
+	 * As we can only hand over a single phy this function will return the primary
+	 * phy. The secondary phy can be identified later on by the link attribute in
+	 * the controller structure.
+	 */
+
+	if (args->args_count != 4)
+		return ERR_PTR(-EINVAL);
+
+	sid = args->args[0];
+	if (sid < 0 || sid > ctrl->conf->max_sds)
+		return ERR_PTR(-EINVAL);
+
+	sid2 = args->args[1];
+	if (sid2 < -1 || sid2 > ctrl->conf->max_sds)
+		return ERR_PTR(-EINVAL);
+
+	/*
+	 * Additionally to a linked SerDes also get the ports whose traffic is going
+	 * through this SerDes. As of now we do not care much about that but later on
+	 * it might be helpful.
+	 */
+
+	min_port = args->args[2];
+	if (min_port < 0)
+		return ERR_PTR(-EINVAL);
+
+	max_port = args->args[3];
+	if (max_port < min_port)
+		return ERR_PTR(-EINVAL);
+
+	ctrl->sds[sid].link = sid2;
+	if (sid2 >= 0)
+		ctrl->sds[sid2].link = sid;
+
+	ctrl->sds[sid].min_port = min_port;
+	ctrl->sds[sid].max_port = max_port;
+
+	return ctrl->sds[sid].phy;
+}
+
+static int rtsds_phy_create(struct rtsds_ctrl *ctrl, u32 sid)
+{
+	struct rtsds_macro *macro;
+
+	ctrl->sds[sid].phy = devm_phy_create(ctrl->dev, NULL, &rtsds_phy_ops);
+	if (IS_ERR(ctrl->sds[sid].phy))
+		return PTR_ERR(ctrl->sds[sid].phy);
+
+	macro = devm_kzalloc(ctrl->dev, sizeof(*macro), GFP_KERNEL);
+	if (!macro)
+		return -ENOMEM;
+
+	macro->sid = sid;
+	macro->ctrl = ctrl;
+	phy_set_drvdata(ctrl->sds[sid].phy, macro);
+
+	ctrl->sds[sid].link = -1;
+	ctrl->sds[sid].min_port = -1;
+	ctrl->sds[sid].max_port = -1;
+
+#ifdef CONFIG_DEBUG_FS
+	rtsds_dbg_init(ctrl, sid);
+#endif
+	return 0;
+}
+
+static int rtsds_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	struct phy_provider *provider;
+	struct rtsds_ctrl *ctrl;
+	int ret;
+
+	if (!np)
+		return -EINVAL;
+
+	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(ctrl->base)) {
+		dev_err(dev, "failed to map SerDes memory\n");
+		return PTR_ERR(ctrl->base);
+	}
+
+	ctrl->dev = dev;
+	ctrl->conf = (struct rtsds_conf *)of_device_get_match_data(dev);
+
+	ret = of_property_read_u32(np, "controlled-ports", &ctrl->sds_mask);
+	if (ret) {
+		ctrl->sds_mask = 0;
+		dev_warn(dev, "property controlled-ports not found, switched to read-only mode\n");
+	}
+
+	for (u32 sid = 0; sid <= ctrl->conf->max_sds; sid++) {
+		ret = rtsds_phy_create(ctrl, sid);
+		if (ret) {
+			dev_err(dev, "failed to create PHY for SerDes %d\n", sid);
+			return ret;
+		}
+	}
+
+	mutex_init(&ctrl->lock);
+	dev_set_drvdata(dev, ctrl);
+	provider = devm_of_phy_provider_register(dev, rtsds_simple_xlate);
+
+	rtsds_load_events(ctrl);
+	rtsds_setup(ctrl);
+
+	dev_info(dev, "initialized (%d SerDes, %d pages, 32 registers, mask 0x%04x)",
+		 ctrl->conf->max_sds + 1, ctrl->conf->max_page + 1, ctrl->sds_mask);
+
+	return PTR_ERR_OR_ZERO(provider);
+}
+
+static const struct rtsds_conf rtsds_838x_conf = {
+	.max_sds	= RTSDS_838X_MAX_SDS,
+	.max_page	= RTSDS_838X_MAX_PAGE,
+	.mask		= rtsds_838x_mask,
+	.read		= rtsds_838x_read,
+	.reset		= rtsds_838x_reset,
+	.set_mode	= rtsds_838x_set_mode,
+	.get_mode	= rtsds_838x_get_mode,
+	.mode_map = {
+		[PHY_INTERFACE_MODE_NA]		= RTSDS_COMBOMODE(0, 0),
+		[PHY_INTERFACE_MODE_1000BASEX]	= RTSDS_COMBOMODE(4, 1), /* SerDes 4, 5 only */
+		[PHY_INTERFACE_MODE_100BASEX]	= RTSDS_COMBOMODE(5, 1), /* SerDes 4, 5 only */
+		[PHY_INTERFACE_MODE_QSGMII]	= RTSDS_COMBOMODE(6, 0),
+	},
+};
+
+static const struct rtsds_conf rtsds_839x_conf = {
+	.max_sds	= RTSDS_839X_MAX_SDS,
+	.max_page	= RTSDS_839X_MAX_PAGE,
+	.mask		= rtsds_839x_mask,
+	.read		= rtsds_839x_read,
+	.reset		= rtsds_839x_reset,
+	.set_mode	= rtsds_839x_set_mode,
+	.get_mode	= rtsds_839x_get_mode,
+	.mode_map = {
+		[PHY_INTERFACE_MODE_NA]		= RTSDS_COMBOMODE(0, 0),
+		[PHY_INTERFACE_MODE_10GBASER]	= RTSDS_COMBOMODE(1, 0), /* SerDes 8, 12 only */
+		[PHY_INTERFACE_MODE_1000BASEX]	= RTSDS_COMBOMODE(7, 0), /* SerDes 12, 13 only */
+		[PHY_INTERFACE_MODE_100BASEX]	= RTSDS_COMBOMODE(8, 0),
+		[PHY_INTERFACE_MODE_QSGMII]	= RTSDS_COMBOMODE(6, 0),
+		[PHY_INTERFACE_MODE_SGMII]	= RTSDS_COMBOMODE(7, 5), /* SerDes 8, 12, 13 only */
+	},
+};
+
+static const struct rtsds_conf rtsds_930x_conf = {
+	.max_sds	= RTSDS_930X_MAX_SDS,
+	.max_page	= RTSDS_930X_MAX_PAGE,
+	.mask 		= rtsds_930x_mask,
+	.read 		= rtsds_930x_read,
+	.reset 		= rtsds_930x_reset,
+	.set_mode 	= rtsds_930x_set_mode,
+	.get_mode 	= rtsds_930x_get_mode,
+	.mode_map = {
+		[PHY_INTERFACE_MODE_NA]		= RTSDS_COMBOMODE(31, 0),
+		[PHY_INTERFACE_MODE_10GBASER]	= RTSDS_COMBOMODE(26, 0),
+		[PHY_INTERFACE_MODE_2500BASEX]  = RTSDS_COMBOMODE(22, 0),
+		[PHY_INTERFACE_MODE_1000BASEX]	= RTSDS_COMBOMODE(4, 0),
+		[PHY_INTERFACE_MODE_USXGMII]	= RTSDS_COMBOMODE(13, 0), /* SerDes 2-9 only */
+		[PHY_INTERFACE_MODE_QUSGMII]	= RTSDS_COMBOMODE(13, 2), /* SerDes 2-9 only */
+		[PHY_INTERFACE_MODE_QSGMII]	= RTSDS_COMBOMODE(6, 0),
+	},
+};
+
+static const struct rtsds_conf rtsds_931x_conf = {
+	.max_sds	= RTSDS_931X_MAX_SDS,
+	.max_page	= RTSDS_931X_MAX_PAGE,
+	.mask		= rtsds_931x_mask,
+	.read		= rtsds_931x_read,
+	.reset		= rtsds_931x_reset,
+	.set_mode	= rtsds_931x_set_mode,
+	.get_mode	= rtsds_931x_get_mode,
+	.mode_map = {
+		[PHY_INTERFACE_MODE_NA]		= RTSDS_COMBOMODE(31, 63),
+		[PHY_INTERFACE_MODE_10GBASER]	= RTSDS_COMBOMODE(31, 53),
+		[PHY_INTERFACE_MODE_1000BASEX]	= RTSDS_COMBOMODE(31, 57), /* 1G/10G auto */
+		[PHY_INTERFACE_MODE_USXGMII]	= RTSDS_COMBOMODE(13, 0),
+		[PHY_INTERFACE_MODE_XGMII]	= RTSDS_COMBOMODE(16, 0),
+		[PHY_INTERFACE_MODE_QSGMII]	= RTSDS_COMBOMODE(6, 0),
+	},
+};
+
+static const struct of_device_id rtsds_compatible_ids[] = {
+	{ .compatible = "realtek,rtl8380-serdes",
+	  .data = &rtsds_838x_conf,
+	},
+	{ .compatible = "realtek,rtl8390-serdes",
+	  .data = &rtsds_839x_conf,
+	},
+	{ .compatible = "realtek,rtl9300-serdes",
+	  .data = &rtsds_930x_conf,
+	},
+	{ .compatible = "realtek,rtl9310-serdes",
+	  .data = &rtsds_931x_conf,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, rtsds_compatible_ids);
+
+static struct platform_driver rtsds_platform_driver = {
+	.probe		= rtsds_probe,
+	.driver		= {
+		.name	= "realtek,otto-serdes",
+		.of_match_table = of_match_ptr(rtsds_compatible_ids),
+	},
+};
+
+module_platform_driver(rtsds_platform_driver);
+
+MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
+MODULE_DESCRIPTION("SerDes driver for Realtek RTL83xx, RTL93xx switch SoCs");
+MODULE_LICENSE("Dual MIT/GPL");