mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Maxime Chevallier <maxime.chevallier@bootlin.com>
To: Daniel Golle <daniel@makrotopia.org>,
	Andrew Lunn <andrew@lunn.ch>, Vladimir Oltean <olteanv@gmail.com>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Russell King <linux@armlinux.org.uk>,
	linux-kernel@vger.kernel.org, netdev@vger.kernel.org
Subject: Re: [PATCH v2 4/4] net: dsa: mxl862xx: add support for SerDes ports
Date: Thu, 21 May 2026 14:54:44 +0200	[thread overview]
Message-ID: <3681b6a5-7196-4b5a-8a4d-347abb62305c@bootlin.com> (raw)
In-Reply-To: <dba64139e769c0e8a2f829363ece1abc1ccea6fe.1779330653.git.daniel@makrotopia.org>

Hi Daniel,

I've only very briefly looked at this, so I'm afraid my comments aren't 
really about the logic, see bellow :(

On 5/21/26 04:41, Daniel Golle wrote:
> The MxL862xx has two XPCS/SerDes interfaces (XPCS0 for ports 9-12,
> XPCS1 for ports 13-16). Each can operate in various single-lane modes
> (SGMII, 1000Base-X, 2500Base-X, 10GBase-R, 10GBase-KR, USXGMII) or as
> QSGMII or 10G_QXGMII providing four sub-ports per interface.
> 
> Implement phylink PCS operations using the firmware's XPCS API:
> 
>    - pcs_config: configure negotiation mode and CL37/SGMII advertising.
>    - pcs_get_state: read link/speed/duplex/LPA from firmware and decode
>      using phylink's standard CL37, SGMII, and USXGMII decoders, with
>      firmware-resolved speed/duplex override for downshift detection.
>    - pcs_an_restart: restart CL37 or CL73 auto-negotiation.
>    - pcs_link_up: force speed/duplex for SGMII.
>    - pcs_inband_caps: report per-mode in-band status capabilities.
> 
> Register a PCS instance for each SerDes interface and
> QSGMII/10G_QXGMII sub-ports during setup. Advertise the supported
> interface modes in phylink_get_caps based on port number.
> 
> Lacking support for expressing PHY-side role modes in Linux only the
> MAC-side of SGMII, QSGMII, USXGMII and 10G_QXGMII are implemented for
> now.
> 
> Signed-off-by: Daniel Golle <daniel@makrotopia.org>
> ---
> v2:
>   * add __{LE,BE}_BITFIELD layouts to ABI structs
>   * per-sub-port QSGMII AN restart via usx_subport / usx_lane_mode
>   * shared-SerDes refcount in pcs_disable via per-XPCS slot bitmap
>   * let every sub-port call pcs_config
>   * cache phy_interface_t instead of firmware type
>   * skip pcs_link_up when inband-AN enabled
>   * gate phylink_get_caps SerDes modes on same FW version as select_pcs
>   * interpret xpcs_pcs_cfg.result as signed (s16)
>   * drop dead MXL862XX_PCS_PORT macro
>   * drop misleading "downshift detection" line from commit message
> 
>   drivers/net/dsa/mxl862xx/mxl862xx-api.h     | 358 ++++++++++++++++++++
>   drivers/net/dsa/mxl862xx/mxl862xx-cmd.h     |   9 +
>   drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 332 +++++++++++++++++-
>   drivers/net/dsa/mxl862xx/mxl862xx-phylink.h |   7 +
>   drivers/net/dsa/mxl862xx/mxl862xx.c         |   6 +-
>   drivers/net/dsa/mxl862xx/mxl862xx.h         |  31 ++
>   6 files changed, 740 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
> index fb21ddc1bf1c..f55544772376 100644
> --- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
> +++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
> @@ -1366,4 +1366,362 @@ struct mxl862xx_rmon_port_cnt {
>   	__le64 tx_good_bytes;
>   } __packed;
>   
> +/**
> + * enum mxl862xx_xpcs_if_mode - XPCS interface mode
> + * @MXL862XX_XPCS_IF_SGMII: SGMII
> + * @MXL862XX_XPCS_IF_1000BASEX: 1000BASE-X
> + * @MXL862XX_XPCS_IF_2500BASEX: 2500BASE-X
> + * @MXL862XX_XPCS_IF_USXGMII: USXGMII (single or quad)
> + * @MXL862XX_XPCS_IF_10GBASER: 10GBASE-R
> + * @MXL862XX_XPCS_IF_10GKR: 10GBASE-KR
> + * @MXL862XX_XPCS_IF_5GBASER: 5GBASE-R
> + * @MXL862XX_XPCS_IF_QSGMII: QSGMII
> + */
> +enum mxl862xx_xpcs_if_mode {
> +	MXL862XX_XPCS_IF_SGMII = 0,
> +	MXL862XX_XPCS_IF_1000BASEX = 1,
> +	MXL862XX_XPCS_IF_2500BASEX = 2,
> +	MXL862XX_XPCS_IF_USXGMII = 3,
> +	MXL862XX_XPCS_IF_10GBASER = 4,
> +	MXL862XX_XPCS_IF_10GKR = 5,
> +	MXL862XX_XPCS_IF_5GBASER = 6,
> +	MXL862XX_XPCS_IF_QSGMII = 7,
> +};
> +
> +/**
> + * enum mxl862xx_xpcs_neg_mode - PCS negotiation mode
> + * @MXL862XX_XPCS_NEG_NONE: no inband negotiation
> + * @MXL862XX_XPCS_NEG_INBAND_AN_OFF: inband selected but AN disabled
> + * @MXL862XX_XPCS_NEG_INBAND_AN_ON: inband with AN enabled
> + */
> +enum mxl862xx_xpcs_neg_mode {
> +	MXL862XX_XPCS_NEG_NONE = 0,
> +	MXL862XX_XPCS_NEG_INBAND_AN_OFF = 1,
> +	MXL862XX_XPCS_NEG_INBAND_AN_ON = 2,
> +};
> +
> +/**
> + * enum mxl862xx_xpcs_role - PCS protocol role
> + * @MXL862XX_XPCS_ROLE_MAC: local end is MAC side (TX_CONFIG = 0)
> + * @MXL862XX_XPCS_ROLE_PHY: local end is PHY side (TX_CONFIG = 1)
> + *
> + * Selects the role the XPCS plays in protocols that have an asymmetric
> + * AN code word (Cisco SGMII / QSGMII / USXGMII). Drives
> + * VR_MII_AN_CTRL.TX_CONFIG: 0 means the local end receives the partner's
> + * AN word, 1 means it sources one. Ignored for symmetric protocols
> + * (1000BASE-X, 2500BASE-X, 10GBASE-R/KR).
> + */
> +enum mxl862xx_xpcs_role {
> +	MXL862XX_XPCS_ROLE_MAC = 0,
> +	MXL862XX_XPCS_ROLE_PHY = 1,
> +};
> +
> +/**
> + * enum mxl862xx_xpcs_usx_lane_mode - USXGMII lane mode
> + * @MXL862XX_XPCS_USX_SINGLE: single USXGMII lane
> + * @MXL862XX_XPCS_USX_QUAD: quad USXGMII (4 ports per lane)
> + */
> +enum mxl862xx_xpcs_usx_lane_mode {
> +	MXL862XX_XPCS_USX_SINGLE = 0,
> +	MXL862XX_XPCS_USX_QUAD = 1,
> +};
> +
> +/**
> + * union mxl862xx_xpcs_an_word - XPCS AN code word, tagged by interface mode
> + * @cl37: 16-bit base-page word exchanged over the CL37 hardware AN path
> + *        (SR_MII_AN_ADV on write, SR_MII_LP_BABL on read). Carries the
> + *        802.3 CL37 base page for 1000BASE-X/2500BASE-X and the Cisco
> + *        SGMII config word for SGMII/QSGMII.
> + * @usx: USXGMII 16-bit AN code word, MDIO_USXGMII_* layout
> + * @cl73: CL73 48-bit base page (10GBASE-KR), three 16-bit registers per
> + *        802.3 Annex 28C
> + * @cl73.adv1: CL73 SR_AN_ADV1 / SR_AN_LP_ABL1
> + * @cl73.adv2: CL73 SR_AN_ADV2 / SR_AN_LP_ABL2
> + * @cl73.adv3: CL73 SR_AN_ADV3 / SR_AN_LP_ABL3
> + *
> + * The host picks the right member based on the interface field of the
> + * surrounding struct (and, for the asymmetric protocols, on the role).
> + */
> +union mxl862xx_xpcs_an_word {
> +	__le16 cl37;
> +	__le16 usx;
> +	struct {
> +		__le16 adv1;
> +		__le16 adv2;
> +		__le16 adv3;
> +	} cl73;
> +} __packed;
> +
> +/**
> + * enum mxl862xx_xpcs_speed - PCS speed values
> + * @MXL862XX_XPCS_SPEED_UNKNOWN: unknown speed
> + * @MXL862XX_XPCS_SPEED_10: 10 Mbps
> + * @MXL862XX_XPCS_SPEED_100: 100 Mbps
> + * @MXL862XX_XPCS_SPEED_1000: 1000 Mbps
> + * @MXL862XX_XPCS_SPEED_2500: 2500 Mbps
> + * @MXL862XX_XPCS_SPEED_5000: 5000 Mbps
> + * @MXL862XX_XPCS_SPEED_10000: 10000 Mbps
> + */
> +enum mxl862xx_xpcs_speed {
> +	MXL862XX_XPCS_SPEED_UNKNOWN = 0,
> +	MXL862XX_XPCS_SPEED_10 = 10,
> +	MXL862XX_XPCS_SPEED_100 = 100,
> +	MXL862XX_XPCS_SPEED_1000 = 1000,
> +	MXL862XX_XPCS_SPEED_2500 = 2500,
> +	MXL862XX_XPCS_SPEED_5000 = 5000,
> +	MXL862XX_XPCS_SPEED_10000 = 10000,
> +};

These seem unused

> +
> +/**
> + * enum mxl862xx_xpcs_duplex - PCS duplex mode
> + * @MXL862XX_XPCS_DUPLEX_HALF: half duplex
> + * @MXL862XX_XPCS_DUPLEX_FULL: full duplex
> + */
> +enum mxl862xx_xpcs_duplex {
> +	MXL862XX_XPCS_DUPLEX_HALF = 0,
> +	MXL862XX_XPCS_DUPLEX_FULL = 1,
> +};
> +
> +/**
> + * enum mxl862xx_xpcs_loopback_mode - XPCS loopback mode
> + * @MXL862XX_XPCS_LB_DISABLE: disable all loopback
> + * @MXL862XX_XPCS_LB_PCS_SERIAL: PCS TX-to-RX serial loopback
> + * @MXL862XX_XPCS_LB_PCS_PARALLEL: PCS RX-to-TX parallel loopback
> + * @MXL862XX_XPCS_LB_PMA_SERIAL: PMA TX-to-RX serial loopback
> + * @MXL862XX_XPCS_LB_PMA_PARALLEL: PMA RX-to-TX parallel loopback
> + */
> +enum mxl862xx_xpcs_loopback_mode {
> +	MXL862XX_XPCS_LB_DISABLE = 0,
> +	MXL862XX_XPCS_LB_PCS_SERIAL = 1,
> +	MXL862XX_XPCS_LB_PCS_PARALLEL = 2,
> +	MXL862XX_XPCS_LB_PMA_SERIAL = 3,
> +	MXL862XX_XPCS_LB_PMA_PARALLEL = 4,
> +};
> +
> +/**
> + * enum mxl862xx_xpcs_reset_type - XPCS reset type
> + * @MXL862XX_XPCS_RESET_VR: vendor-specific reset (fast)
> + * @MXL862XX_XPCS_RESET_SOFT: PCS soft reset
> + * @MXL862XX_XPCS_RESET_HARD: full hardware reset
> + */
> +enum mxl862xx_xpcs_reset_type {
> +	MXL862XX_XPCS_RESET_VR = 0,
> +	MXL862XX_XPCS_RESET_SOFT = 1,
> +	MXL862XX_XPCS_RESET_HARD = 2,
> +};

These too

> +
> +/**
> + * struct mxl862xx_xpcs_pcs_cfg - PCS configuration parameters
> + * @port_id: XPCS port index (0-3)
> + * @interface: PCS interface mode. See &enum mxl862xx_xpcs_if_mode
> + * @neg_mode: PCS negotiation mode. See &enum mxl862xx_xpcs_neg_mode
> + * @permit_pause: Allow pause to MAC
> + * @usx_lane_mode: USXGMII lane mode.
> + *                 See &enum mxl862xx_xpcs_usx_lane_mode
> + * @role: PCS protocol role. See &enum mxl862xx_xpcs_role
> + * @usx_subport: Sub-port (0-3) within the XPCS. Used by the firmware
> + *               to set MAC pause per sub-port; ignored for the
> + *               XPCS-wide bringup, which is idempotent across slots.
> + * @advertising: AN code word the local end transmits. The active union
> + *               member is selected by @interface (and, for the asymmetric
> + *               protocols, by @role). Ignored when the local end does
> + *               not transmit an AN word (role=MAC for SGMII/QSGMII/
> + *               USXGMII, 10GBASE-R, 5GBASE-R) or when @neg_mode is not
> + *               INBAND_AN_ON. Pass all-zero to keep the firmware default
> + *               advertisement.
> + * @result: Firmware result. >0 means the host must follow with an AN
> + *          restart, 0 means no host follow-up is needed, <0 is an errno.
> + */
> +struct mxl862xx_xpcs_pcs_cfg {
> +#ifdef __LITTLE_ENDIAN_BITFIELD
> +	u8 port_id:2;
> +	u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
> +	u8 neg_mode:2; /* enum mxl862xx_xpcs_neg_mode */
> +	u8 permit_pause:1;
> +	u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
> +	u8 role:1; /* enum mxl862xx_xpcs_role */
> +	u8 usx_subport:2;
> +#elif defined(__BIG_ENDIAN_BITFIELD)
> +	u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
> +	u8 port_id:2;
> +	u8 usx_subport:2;
> +	u8 role:1; /* enum mxl862xx_xpcs_role */
> +	u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
> +	u8 permit_pause:1;
> +	u8 neg_mode:2; /* enum mxl862xx_xpcs_neg_mode */
> +#else
> +#error "Unknown bitfield endianness"
> +#endif
> +	union mxl862xx_xpcs_an_word advertising;
> +	__le16 result;
> +} __packed;
> +
> +/**
> + * struct mxl862xx_xpcs_pcs_state - PCS link state
> + * @port_id: XPCS port index (0-3) (input)
> + * @interface: PCS interface mode (input).
> + *             See &enum mxl862xx_xpcs_if_mode
> + * @usx_lane_mode: USX lane mode (input)
> + * @usx_subport: USX sub-port 0-3 (input)
> + * @link: Link up (1) / down (0) (output)
> + * @an_complete: Auto-negotiation complete (output)
> + * @duplex: Duplex mode (output). See &enum mxl862xx_xpcs_duplex
> + * @pcs_fault: PCS fault (output)
> + * @pause: Pause negotiation result, bit 0 symmetric, bit 1 asymmetric
> + *         (output)
> + * @lp_eee_cap: Link partner supports EEE (output)
> + * @lp_eee_cs_cap: Link partner supports EEE clock-stop (output)
> + * @__rsv: reserved
> + * @__pad: padding
> + * @speed: Resolved speed (output). See &enum mxl862xx_xpcs_speed
> + * @lpa: Link partner ability word (output). Same union as
> + *       &union mxl862xx_xpcs_an_word; the host picks the member based on
> + *       @interface.
> + */
> +struct mxl862xx_xpcs_pcs_state {
> +#ifdef __LITTLE_ENDIAN_BITFIELD
> +	u8 port_id:2;
> +	u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
> +	u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
> +	u8 usx_subport:2;
> +	u8 link:1;
> +	u8 an_complete:1;
> +	u8 duplex:1; /* enum mxl862xx_xpcs_duplex */
> +	u8 pcs_fault:1;
> +	u8 pause:2;
> +	u8 lp_eee_cap:1;
> +	u8 lp_eee_cs_cap:1;
> +	u8 __rsv:4;
> +#elif defined(__BIG_ENDIAN_BITFIELD)
> +	u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
> +	u8 port_id:2;
> +	u8 pcs_fault:1;
> +	u8 duplex:1; /* enum mxl862xx_xpcs_duplex */
> +	u8 an_complete:1;
> +	u8 link:1;
> +	u8 usx_subport:2;
> +	u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
> +	u8 __rsv:4;
> +	u8 lp_eee_cs_cap:1;
> +	u8 lp_eee_cap:1;
> +	u8 pause:2;
> +#else
> +#error "Unknown bitfield endianness"
> +#endif
> +	u8 __pad;
> +	__le16 speed; /* enum mxl862xx_xpcs_speed */
> +	union mxl862xx_xpcs_an_word lpa;
> +} __packed;
> +
> +/**
> + * struct mxl862xx_xpcs_pcs_disable - PCS disable parameters
> + * @port_id: XPCS port index
> + * @__pad: padding
> + * @result: Firmware result. 0 on success, <0 on error.
> + *
> + * Asserts IDDQ + PHY + XPCS resets to power down the SERDES when the
> + * port is admin-down or no module is plugged in. The next PCS config
> + * implicitly powers it back up and reprograms the desired interface.
> + */
> +struct mxl862xx_xpcs_pcs_disable {
> +	u8 port_id;
> +	u8 __pad;
> +	__le16 result;
> +} __packed;
> +
> +/**
> + * struct mxl862xx_xpcs_an_restart - AN restart parameters
> + * @port_id: XPCS port index (0-3)
> + * @interface: PCS interface mode. See &enum mxl862xx_xpcs_if_mode
> + * @usx_lane_mode: USX lane mode
> + * @usx_subport: Sub-port (0-3) within the XPCS. Selects the lane
> + *               whose AN is restarted for QSGMII and QUSXGMII;
> + *               ignored by single-lane modes.
> + * @__rsv: reserved
> + * @result: Firmware result. 0 on success, <0 on error.
> + *
> + * Restarts auto-negotiation on a single sub-port of the XPCS. The
> + * SERDES must already be configured.
> + */
> +struct mxl862xx_xpcs_an_restart {
> +#ifdef __LITTLE_ENDIAN_BITFIELD
> +	u8 port_id:2;
> +	u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
> +	u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
> +	u8 usx_subport:2;
> +	u8 __rsv:4;
> +#elif defined(__BIG_ENDIAN_BITFIELD)
> +	u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
> +	u8 port_id:2;
> +	u8 __rsv:4;
> +	u8 usx_subport:2;
> +	u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
> +#else
> +#error "Unknown bitfield endianness"
> +#endif
> +	__le16 result;
> +} __packed;
> +
> +/**
> + * struct mxl862xx_xpcs_pcs_link_up - PCS link-up parameters
> + * @port_id: XPCS port index (0-3)
> + * @interface: PCS interface mode. See &enum mxl862xx_xpcs_if_mode
> + * @duplex: Duplex mode. See &enum mxl862xx_xpcs_duplex
> + * @usx_lane_mode: USX lane mode (USXGMII only; ignored otherwise).
> + *                 See &enum mxl862xx_xpcs_usx_lane_mode
> + * @usx_subport: USX sub-port 0-3 (QUSXGMII only; ignored otherwise)
> + * @__rsv0: reserved
> + * @speed: Resolved speed. See &enum mxl862xx_xpcs_speed
> + * @result: Firmware result. 0 on success, <0 is errno.
> + *
> + * Called once per link-up event after the host has resolved the
> + * line-side speed/duplex (from the PHY's read_status, from a preceding
> + * PCS get-state, or from a fixed-link description).
> + */
> +struct mxl862xx_xpcs_pcs_link_up {
> +#ifdef __LITTLE_ENDIAN_BITFIELD
> +	u8 port_id:2;
> +	u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
> +	u8 duplex:1; /* enum mxl862xx_xpcs_duplex */
> +	u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
> +	u8 usx_subport:2;
> +	u8 __rsv0:3;
> +#elif defined(__BIG_ENDIAN_BITFIELD)
> +	u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
> +	u8 port_id:2;
> +	u8 __rsv0:3;
> +	u8 usx_subport:2;
> +	u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
> +	u8 duplex:1; /* enum mxl862xx_xpcs_duplex */
> +#else
> +#error "Unknown bitfield endianness"
> +#endif
> +	__le16 speed; /* enum mxl862xx_xpcs_speed */
> +	__le16 result;
> +} __packed;
> +
> +/**
> + * struct mxl862xx_xpcs_loopback_cfg - loopback control
> + * @port_id: XPCS port index
> + * @mode: loopback mode. See &enum mxl862xx_xpcs_loopback_mode
> + * @result: firmware result
> + */
> +struct mxl862xx_xpcs_loopback_cfg {
> +	u8 port_id;
> +	u8 mode; /* enum mxl862xx_xpcs_loopback_mode */
> +	__le16 result;
> +} __packed;

This too

> +
> +/**
> + * struct mxl862xx_xpcs_reset_cfg - XPCS reset
> + * @port_id: XPCS port index
> + * @reset_type: reset type. See &enum mxl862xx_xpcs_reset_type
> + * @result: firmware result
> + */
> +struct mxl862xx_xpcs_reset_cfg {
> +	u8 port_id;
> +	u8 reset_type; /* enum mxl862xx_xpcs_reset_type */
> +	__le16 result;
> +} __packed;

This too

> +
>   #endif /* __MXL862XX_API_H */
> diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
> index f1ea40aa7ea0..c87a955c13c4 100644
> --- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
> +++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
> @@ -24,6 +24,7 @@
>   #define MXL862XX_SS_MAGIC		0x1600
>   #define GPY_GPY2XX_MAGIC		0x1800
>   #define SYS_MISC_MAGIC			0x1900
> +#define MXL862XX_XPCS_MAGIC		0x1a00
>   
>   #define MXL862XX_COMMON_CFGGET		(MXL862XX_COMMON_MAGIC + 0x9)
>   #define MXL862XX_COMMON_CFGSET		(MXL862XX_COMMON_MAGIC + 0xa)
> @@ -71,6 +72,14 @@
>   
>   #define SYS_MISC_FW_VERSION		(SYS_MISC_MAGIC + 0x2)
>   
> +#define MXL862XX_XPCS_PCS_CONFIG	(MXL862XX_XPCS_MAGIC + 0x1)
> +#define MXL862XX_XPCS_PCS_GET_STATE	(MXL862XX_XPCS_MAGIC + 0x2)
> +#define MXL862XX_XPCS_PCS_DISABLE	(MXL862XX_XPCS_MAGIC + 0x4)
> +#define MXL862XX_XPCS_AN_RESTART	(MXL862XX_XPCS_MAGIC + 0x5)
> +#define MXL862XX_XPCS_PCS_LINK_UP	(MXL862XX_XPCS_MAGIC + 0x7)
> +#define MXL862XX_XPCS_LOOPBACK		(MXL862XX_XPCS_MAGIC + 0x8)
> +#define MXL862XX_XPCS_RESET		(MXL862XX_XPCS_MAGIC + 0x9)
> +
>   #define MMD_API_MAXIMUM_ID		0x7fff
>   
>   #endif /* __MXL862XX_CMD_H */
> diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
> index f17c429d1f1d..55be53d77cec 100644
> --- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
> +++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
> @@ -11,16 +11,343 @@
>   #include <net/dsa.h>
>   
>   #include "mxl862xx.h"
> +#include "mxl862xx-api.h"
> +#include "mxl862xx-cmd.h"
> +#include "mxl862xx-host.h"
>   #include "mxl862xx-phylink.h"
>   
>   void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
>   			       struct phylink_config *config)
>   {
> +	struct mxl862xx_priv *priv = ds->priv;
> +
>   	config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
>   				   MAC_100 | MAC_1000 | MAC_2500FD;
>   
> -	__set_bit(PHY_INTERFACE_MODE_INTERNAL,
> -		  config->supported_interfaces);
> +	switch (port) {
> +	case 1 ... 8:
> +		__set_bit(PHY_INTERFACE_MODE_INTERNAL,
> +			  config->supported_interfaces);
> +		break;
> +	case 9:
> +	case 13:
> +		if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84))
> +			break;
> +		__set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces);
> +		__set_bit(PHY_INTERFACE_MODE_1000BASEX, config->supported_interfaces);
> +		__set_bit(PHY_INTERFACE_MODE_2500BASEX, config->supported_interfaces);
> +		__set_bit(PHY_INTERFACE_MODE_10GBASER, config->supported_interfaces);
> +		__set_bit(PHY_INTERFACE_MODE_10GKR, config->supported_interfaces);
> +		__set_bit(PHY_INTERFACE_MODE_USXGMII, config->supported_interfaces);
> +		fallthrough;
> +	case 10 ... 12:
> +	case 14 ... 16:
> +		if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84))
> +			break;
> +		__set_bit(PHY_INTERFACE_MODE_QSGMII, config->supported_interfaces);
> +		__set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->supported_interfaces);
> +
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	if (port == 9 || port == 13)
> +		config->mac_capabilities |= MAC_10000FD | MAC_5000FD;
> +}
> +
> +static struct mxl862xx_pcs *pcs_to_mxl862xx_pcs(struct phylink_pcs *pcs)
> +{
> +	return container_of(pcs, struct mxl862xx_pcs, pcs);
> +}
> +
> +static int mxl862xx_xpcs_if_mode(phy_interface_t interface)
> +{
> +	switch (interface) {
> +	case PHY_INTERFACE_MODE_SGMII:
> +		return MXL862XX_XPCS_IF_SGMII;
> +	case PHY_INTERFACE_MODE_QSGMII:
> +		return MXL862XX_XPCS_IF_QSGMII;
> +	case PHY_INTERFACE_MODE_1000BASEX:
> +		return MXL862XX_XPCS_IF_1000BASEX;
> +	case PHY_INTERFACE_MODE_2500BASEX:
> +		return MXL862XX_XPCS_IF_2500BASEX;
> +	case PHY_INTERFACE_MODE_USXGMII:
> +	case PHY_INTERFACE_MODE_10G_QXGMII:
> +		return MXL862XX_XPCS_IF_USXGMII;
> +	case PHY_INTERFACE_MODE_10GBASER:
> +		return MXL862XX_XPCS_IF_10GBASER;
> +	case PHY_INTERFACE_MODE_10GKR:
> +		return MXL862XX_XPCS_IF_10GKR;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int mxl862xx_xpcs_neg_mode(unsigned int neg_mode)
> +{
> +	if (!(neg_mode & PHYLINK_PCS_NEG_INBAND))
> +		return MXL862XX_XPCS_NEG_NONE;
> +	if (neg_mode & PHYLINK_PCS_NEG_ENABLED)
> +		return MXL862XX_XPCS_NEG_INBAND_AN_ON;
> +	return MXL862XX_XPCS_NEG_INBAND_AN_OFF;
> +}
> +
> +static void mxl862xx_pcs_disable(struct phylink_pcs *pcs)
> +{
> +	struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
> +	struct mxl862xx_priv *priv = mpcs->priv;
> +	struct mxl862xx_xpcs_pcs_disable dis = {};

Reverse xmas tree I guess :)

> +
> +	/* The SerDes is shared across QSGMII/QUSXGMII sub-ports; only
> +	 * power it down once the last active sub-port goes away.
> +	 */
> +	priv->serdes_active[mpcs->serdes_id] &= ~BIT(mpcs->slot);
> +	if (priv->serdes_active[mpcs->serdes_id])
> +		return;
> +
> +	dis.port_id = mpcs->serdes_id;
> +
> +	MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_DISABLE, dis);
> +}
> +
> +static int mxl862xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
> +			       phy_interface_t interface,
> +			       const unsigned long *advertising,
> +			       bool permit_pause_to_mac)
> +{
> +	struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
> +	struct mxl862xx_priv *priv = mpcs->priv;
> +	struct mxl862xx_xpcs_pcs_cfg cfg = {};
> +	int if_mode, ret;
> +	u16 adv;
> +
> +	mpcs->interface = interface;
> +	priv->serdes_active[mpcs->serdes_id] |= BIT(mpcs->slot);
> +
> +	if_mode = mxl862xx_xpcs_if_mode(interface);
> +	if (if_mode < 0) {
> +		dev_err(priv->ds->dev, "unsupported interface: %s\n",
> +			phy_modes(interface));
> +		return if_mode;
> +	}
> +
> +	/* The XPCS bringup is per-instance and idempotent in the
> +	 * firmware: every QSGMII/QUSXGMII sub-port may call pcs_config
> +	 * and the firmware will skip the bringup if the requested mode
> +	 * matches the cached one, then update MAC pause for the
> +	 * sub-port indicated by @usx_subport.
> +	 */
> +	cfg.port_id = mpcs->serdes_id;
> +	cfg.usx_subport = mpcs->slot;
> +	cfg.usx_lane_mode = (interface == PHY_INTERFACE_MODE_10G_QXGMII) ?
> +			    MXL862XX_XPCS_USX_QUAD : MXL862XX_XPCS_USX_SINGLE;
> +	cfg.interface = if_mode;
> +	cfg.neg_mode = mxl862xx_xpcs_neg_mode(neg_mode);
> +	cfg.role = MXL862XX_XPCS_ROLE_MAC;
> +	cfg.permit_pause = permit_pause_to_mac ? 1 : 0;
> +
> +	if (neg_mode & PHYLINK_PCS_NEG_INBAND) {
> +		switch (interface) {
> +		case PHY_INTERFACE_MODE_1000BASEX:
> +		case PHY_INTERFACE_MODE_2500BASEX:
> +			adv = linkmode_adv_to_mii_adv_x(advertising,
> +				ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
> +			cfg.advertising.cl37 = cpu_to_le16(adv);
> +			break;
> +		case PHY_INTERFACE_MODE_SGMII:
> +		case PHY_INTERFACE_MODE_QSGMII:
> +			cfg.advertising.cl37 = cpu_to_le16(ADVERTISE_SGMII);
> +			break;
> +		default:
> +			break;
> +		}
> +	}
> +
> +	ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PCS_CONFIG, cfg);
> +	if (ret)
> +		return ret;
> +
> +	return (s16)le16_to_cpu(cfg.result) > 0 ? 1 : 0;
> +}
> +
> +static void mxl862xx_pcs_get_state(struct phylink_pcs *pcs,
> +				   unsigned int neg_mode,
> +				   struct phylink_link_state *state)
> +{
> +	struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
> +	struct mxl862xx_priv *priv = mpcs->priv;
> +	struct mxl862xx_xpcs_pcs_state st = {};
> +	int if_mode, ret;
> +	u16 bmsr;
> +
> +	if_mode = mxl862xx_xpcs_if_mode(state->interface);
> +	if (if_mode < 0)
> +		return;
> +
> +	st.port_id = mpcs->serdes_id;
> +	st.interface = if_mode;
> +	st.usx_subport = mpcs->slot;
> +
> +	ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PCS_GET_STATE, st);
> +	if (ret)
> +		return;
> +
> +	state->link = st.link && !st.pcs_fault;
> +	state->an_complete = st.an_complete;
> +
> +	switch (state->interface) {
> +	case PHY_INTERFACE_MODE_1000BASEX:
> +	case PHY_INTERFACE_MODE_2500BASEX:
> +	case PHY_INTERFACE_MODE_SGMII:
> +	case PHY_INTERFACE_MODE_QSGMII:
> +		bmsr = (state->link ? BMSR_LSTATUS : 0) |
> +		       (state->an_complete ? BMSR_ANEGCOMPLETE : 0);
> +		phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr,
> +						 le16_to_cpu(st.lpa.cl37));
> +		break;
> +
> +	case PHY_INTERFACE_MODE_USXGMII:
> +	case PHY_INTERFACE_MODE_10G_QXGMII:
> +		if (state->link)
> +			phylink_decode_usxgmii_word(state,
> +						    le16_to_cpu(st.lpa.usx));
> +		break;
> +
> +	case PHY_INTERFACE_MODE_10GBASER:
> +	case PHY_INTERFACE_MODE_10GKR:
> +		if (state->link) {
> +			state->speed = SPEED_10000;
> +			state->duplex = DUPLEX_FULL;
> +		}
> +		break;
> +
> +	default:
> +		state->link = false;
> +		break;
> +	}
> +}
> +
> +static void mxl862xx_pcs_an_restart(struct phylink_pcs *pcs)
> +{
> +	struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
> +	struct mxl862xx_priv *priv = mpcs->priv;
> +	struct mxl862xx_xpcs_an_restart an = {};
> +	int if_mode;
> +
> +	if_mode = mxl862xx_xpcs_if_mode(mpcs->interface);
> +	if (if_mode < 0)
> +		return;
> +
> +	an.port_id = mpcs->serdes_id;
> +	an.interface = if_mode;
> +	an.usx_subport = mpcs->slot;
> +	an.usx_lane_mode = (mpcs->interface == PHY_INTERFACE_MODE_10G_QXGMII) ?
> +			   MXL862XX_XPCS_USX_QUAD : MXL862XX_XPCS_USX_SINGLE;
> +
> +	MXL862XX_API_WRITE(priv, MXL862XX_XPCS_AN_RESTART, an);
> +}
> +
> +static void mxl862xx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
> +				 phy_interface_t interface, int speed,
> +				 int duplex)
> +{
> +	struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
> +	struct mxl862xx_priv *priv = mpcs->priv;
> +	struct mxl862xx_xpcs_pcs_link_up lu = {};
> +	int if_mode;

here aswell

> +
> +	/* With inband-AN enabled (role=MAC), the XPCS auto-resolves
> +	 * speed/duplex from the partner's AN word and the firmware
> +	 * short-circuits link_up.  Skip the firmware round-trip, same
> +	 * as pcs-mtk-lynxi.
> +	 */
> +	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
> +		return;
> +
> +	if_mode = mxl862xx_xpcs_if_mode(interface);
> +	if (if_mode < 0)
> +		return;
> +
> +	lu.port_id = mpcs->serdes_id;
> +	lu.interface = if_mode;
> +	lu.duplex = (duplex == DUPLEX_FULL) ? MXL862XX_XPCS_DUPLEX_FULL :
> +					      MXL862XX_XPCS_DUPLEX_HALF;
> +	lu.speed = cpu_to_le16(speed);
> +
> +	MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_LINK_UP, lu);
> +}
> +
> +static unsigned int mxl862xx_pcs_inband_caps(struct phylink_pcs *pcs,
> +					     phy_interface_t interface)
> +{
> +	switch (interface) {
> +	case PHY_INTERFACE_MODE_SGMII:
> +	case PHY_INTERFACE_MODE_QSGMII:
> +	case PHY_INTERFACE_MODE_1000BASEX:
> +	case PHY_INTERFACE_MODE_2500BASEX:
> +		return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
> +	case PHY_INTERFACE_MODE_USXGMII:
> +	case PHY_INTERFACE_MODE_10G_QXGMII:
> +	case PHY_INTERFACE_MODE_10GKR:
> +		return LINK_INBAND_ENABLE;
> +	case PHY_INTERFACE_MODE_10GBASER:
> +		return LINK_INBAND_DISABLE;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static const struct phylink_pcs_ops mxl862xx_pcs_ops = {
> +	.pcs_disable = mxl862xx_pcs_disable,
> +	.pcs_config = mxl862xx_pcs_config,
> +	.pcs_get_state = mxl862xx_pcs_get_state,
> +	.pcs_an_restart = mxl862xx_pcs_an_restart,
> +	.pcs_link_up = mxl862xx_pcs_link_up,
> +	.pcs_inband_caps = mxl862xx_pcs_inband_caps,
> +};
> +
> +void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
> +			int port)
> +{
> +	pcs->priv = priv;
> +	pcs->serdes_id = MXL862XX_SERDES_PORT_ID(port);
> +	pcs->slot = MXL862XX_SERDES_SLOT(port);
> +	pcs->interface = PHY_INTERFACE_MODE_NA;
> +
> +	pcs->pcs.ops = &mxl862xx_pcs_ops;
> +	pcs->pcs.poll = true;
> +
> +	__set_bit(PHY_INTERFACE_MODE_QSGMII, pcs->pcs.supported_interfaces);
> +	__set_bit(PHY_INTERFACE_MODE_10G_QXGMII, pcs->pcs.supported_interfaces);
> +	if (pcs->slot != 0)
> +		return;
> +
> +	__set_bit(PHY_INTERFACE_MODE_SGMII, pcs->pcs.supported_interfaces);
> +	__set_bit(PHY_INTERFACE_MODE_1000BASEX, pcs->pcs.supported_interfaces);
> +	__set_bit(PHY_INTERFACE_MODE_2500BASEX, pcs->pcs.supported_interfaces);
> +	__set_bit(PHY_INTERFACE_MODE_10GBASER, pcs->pcs.supported_interfaces);
> +	__set_bit(PHY_INTERFACE_MODE_10GKR, pcs->pcs.supported_interfaces);
> +	__set_bit(PHY_INTERFACE_MODE_USXGMII, pcs->pcs.supported_interfaces);
> +}
> +
> +static struct phylink_pcs *
> +mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,
> +				phy_interface_t interface)
> +{
> +	struct dsa_port *dp = dsa_phylink_to_port(config);
> +	struct mxl862xx_priv *priv = dp->ds->priv;
> +	int port = dp->index;
> +
> +	if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84))
> +		return NULL;
> +
> +	switch (port) {
> +	case 9 ... 16:
> +		return &priv->serdes_ports[port - 9].pcs;
> +	default:
> +		return NULL;
> +	}
>   }
>   
>   static void mxl862xx_phylink_mac_config(struct phylink_config *config,
> @@ -48,4 +375,5 @@ const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
>   	.mac_config = mxl862xx_phylink_mac_config,
>   	.mac_link_down = mxl862xx_phylink_mac_link_down,
>   	.mac_link_up = mxl862xx_phylink_mac_link_up,
> +	.mac_select_pcs = mxl862xx_phylink_mac_select_pcs,
>   };
> diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
> index c3d5215bdf60..03bb9caad9aa 100644
> --- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
> +++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
> @@ -7,8 +7,15 @@
>   
>   #include "mxl862xx.h"
>   
> +#define MXL862XX_SERDES_SLOT(port) \
> +	(((port) - MXL862XX_FIRST_SERDES_PORT) % MXL862XX_SERDES_SLOTS)
> +#define MXL862XX_SERDES_PORT_ID(port) \
> +	(((port) - MXL862XX_FIRST_SERDES_PORT) / MXL862XX_SERDES_SLOTS)
> +
>   extern const struct phylink_mac_ops mxl862xx_phylink_mac_ops;
>   void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
>   			       struct phylink_config *config);
> +void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
> +			int port);
>   
>   #endif /* __MXL862XX_PHYLINK_H */
> diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
> index 0b1a23364eb5..0af41efccbc6 100644
> --- a/drivers/net/dsa/mxl862xx/mxl862xx.c
> +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
> @@ -622,7 +622,7 @@ static int mxl862xx_setup(struct dsa_switch *ds)
>   	int n_user_ports = 0, max_vlans;
>   	int ingress_finals, vid_rules;
>   	struct dsa_port *dp;
> -	int ret;
> +	int ret, i;
>   
>   	ret = mxl862xx_reset(priv);
>   	if (ret)
> @@ -632,6 +632,10 @@ static int mxl862xx_setup(struct dsa_switch *ds)
>   	if (ret)
>   		return ret;
>   
> +	for (i = 0; i < ARRAY_SIZE(priv->serdes_ports); i++)
> +		mxl862xx_setup_pcs(priv, &priv->serdes_ports[i],
> +				   i + MXL862XX_FIRST_SERDES_PORT);
> +
>   	/* Calculate Extended VLAN block sizes.
>   	 * With VLAN Filter handling VID membership checks:
>   	 *   Ingress: only final catchall rules (PVID insertion, 802.1Q
> diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
> index 79fd32c4db4e..d5879094ffd3 100644
> --- a/drivers/net/dsa/mxl862xx/mxl862xx.h
> +++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
> @@ -11,6 +11,9 @@
>   struct mxl862xx_priv;
>   
>   #define MXL862XX_MAX_PORTS		17
> +#define MXL862XX_FIRST_SERDES_PORT	9
> +#define MXL862XX_SERDES_SLOTS		4
> +
>   #define MXL862XX_DEFAULT_BRIDGE		0
>   #define MXL862XX_MAX_BRIDGES		48
>   #define MXL862XX_MAX_BRIDGE_PORTS	128
> @@ -242,6 +245,26 @@ struct mxl862xx_port {
>   	spinlock_t stats_lock; /* protects stats accumulators */
>   };
>   
> +/**
> + * struct mxl862xx_pcs - link SerDes interfaces to bridge ports
> + * @pcs:       &struct phylink_pcs instance
> + * @priv:      pointer to &struct mxl862xx_priv
> + * @serdes_id: SerDes instance index (0 or 1)
> + * @slot:      slot within the SerDes (0-3 for QSGMII/QUSXGMII, 0 otherwise)
> + * @interface: cached PHY interface, last value passed to pcs_config().
> + *             %PHY_INTERFACE_MODE_NA before the first successful
> + *             pcs_config().  Used by pcs_an_restart() to populate the
> + *             firmware command and by pcs_disable() to skip the
> + *             firmware power-down for shared (QSGMII/QUSXGMII) modes.
> + */
> +struct mxl862xx_pcs {
> +	struct phylink_pcs pcs;
> +	struct mxl862xx_priv *priv;
> +	int serdes_id;
> +	int slot;
> +	phy_interface_t interface;
> +};
> +
>   /**
>    * union mxl862xx_fw_version - firmware version for comparison and display
>    * @major: firmware major version
> @@ -293,6 +316,12 @@ union mxl862xx_fw_version {
>    *                      flooding)
>    * @fw_version:         cached firmware version, populated at probe and
>    *                      compared with MXL862XX_FW_VER_MIN()
> + * @serdes_ports:       SerDes interfaces incl. sub-interfaces in case of
> + *                      10G_QXGMII or QSGMII
> + * @serdes_active:      per-XPCS bitmap, BIT(slot) set while that
> + *                      sub-port's PCS is enabled by phylink.  pcs_disable
> + *                      only powers the XPCS down once the last bit
> + *                      clears.  Updates are serialised by rtnl.
>    * @ports:              per-port state, indexed by switch port number
>    * @bridges:            maps DSA bridge number to firmware bridge ID;
>    *                      zero means no firmware bridge allocated for that
> @@ -311,6 +340,8 @@ struct mxl862xx_priv {
>   	unsigned long flags;
>   	u16 drop_meter;
>   	union mxl862xx_fw_version fw_version;
> +	struct mxl862xx_pcs serdes_ports[8];
> +	u8 serdes_active[2];
>   	struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
>   	u16 bridges[MXL862XX_MAX_BRIDGES + 1];
>   	u16 evlan_ingress_size;

Thanks :)


      reply	other threads:[~2026-05-21 12:54 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-21  2:39 [PATCH v2 0/4] net: dsa: mxl862xx: add " Daniel Golle
2026-05-21  2:40 ` [PATCH v2 1/4] net: dsa: mxl862xx: store firmware version for feature gating Daniel Golle
2026-05-21  2:40 ` [PATCH v2 2/4] net: dsa: mxl862xx: move phylink stubs to mxl862xx-phylink.c Daniel Golle
2026-05-21 14:07   ` Maxime Chevallier
2026-05-21  2:40 ` [PATCH v2 3/4] net: dsa: mxl862xx: move API macros to mxl862xx-host.h Daniel Golle
2026-05-21 14:07   ` Maxime Chevallier
2026-05-21  2:41 ` [PATCH v2 4/4] net: dsa: mxl862xx: add support for SerDes ports Daniel Golle
2026-05-21 12:54   ` Maxime Chevallier [this message]

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=3681b6a5-7196-4b5a-8a4d-347abb62305c@bootlin.com \
    --to=maxime.chevallier@bootlin.com \
    --cc=andrew@lunn.ch \
    --cc=daniel@makrotopia.org \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@armlinux.org.uk \
    --cc=netdev@vger.kernel.org \
    --cc=olteanv@gmail.com \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

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

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

all inboxes | Powered by JetHome®