On 13-04-23, 12:05, Sean Anderson wrote: > This adds support for the Lynx 10G "SerDes" devices found on various NXP > QorIQ SoCs. There may be up to four SerDes devices on each SoC, each > supporting up to eight lanes. Protocol support for each SerDes is highly > heterogeneous, with each SoC typically having a totally different > selection of supported protocols for each lane. Additionally, the SerDes > devices on each SoC also have differing support. One SerDes will > typically support Ethernet on most lanes, while the other will typically > support PCIe on most lanes. > > There is wide hardware support for this SerDes. It is present on QorIQ > T-Series and Layerscape processors. Because each SoC typically has > specific instructions and exceptions for its SerDes, I have limited the > initial scope of this module to just the LS1046A and LS1088A. > Additionally, I have only added support for Ethernet protocols. There is > not a great need for dynamic reconfiguration for other protocols (except > perhaps for M.2 cards), so support for them may never be added. > > Nevertheless, I have tried to provide an obvious path for adding support > for other SoCs as well as other protocols. SATA just needs support for > configuring LNmSSCR0. PCIe may need to configure the equalization > registers. It also uses multiple lanes. I have tried to write the driver > with multi-lane support in mind, so there should not need to be any > large changes. Although there are 6 protocols supported, I have only > tested SGMII and XFI. The rest have been implemented as described in > the datasheet. Most of these protocols should work "as-is", but > 10GBASE-KR will need PCS support for link training. > > Unlike some other phys where e.g. PCIe x4 will use 4 separate phys all > configured for PCIe, this driver uses one phy configured to use 4 lanes. > This is because while the individual lanes may be configured > individually, the protocol selection acts on all lanes at once. > Additionally, the order which lanes should be configured in is specified > by the datasheet. To coordinate this, lanes are reserved in phy_init, > and released in phy_exit. > > This driver was written with reference to the LS1046A reference manual. > However, it was informed by reference manuals for all processors with > mEMACs, especially the T4240 (which appears to have a "maxed-out" > configuration). The earlier P-series processors appear to be similar, but > have a different overall register layout (using "banks" instead of > separate SerDes). Perhaps this those use a "5G Lynx SerDes." > > Note that while I have used FIELD_GET/FIELD_PREP where possible, these > macros require const values for the field. This is incompatible with > dynamicly-generated fields, such as when the field is determined by a > variable. In these cases, I have used traditional shift/mask techniques. > > Signed-off-by: Sean Anderson <sean.ander...@seco.com> > --- > > Changes in v14: > - Add note about (lack of) use of FIELD_GET/PREP > > Changes in v10: > - Fix debugging print with incorrect error variable > > Changes in v9: > - Split off clock "driver" into its own patch to allow for better > review. > - Add ability to defer lane initialization to phy_init. This allows > for easier transitioning between firmware-managed serdes and Linux- > managed serdes, as the consumer (such as dpaa2, which knows what the > firmware is doing) has the last say on who gets control. > - phy-type -> fsl,phy > > Changes in v8: > - Remove unused variable from lynx_ls_mode_init > > Changes in v7: > - Break out call order into generic documentation > - Refuse to switch "major" protocols > - Update Kconfig to reflect restrictions > - Remove set/clear of "pcs reset" bit, since it doesn't seem to fix > anything. > > Changes in v6: > - Update MAINTAINERS to include new files > - Include bitfield.h and slab.h to allow compilation on non-arm64 > arches. > - Depend on COMMON_CLK and either layerscape/ppc > > Changes in v5: > - Remove references to PHY_INTERFACE_MODE_1000BASEKX to allow this > series to be applied directly to linux/master. > - Add fsl,lynx-10g.h to MAINTAINERS > > Changes in v4: > - Rework all debug statements to remove use of __func__. Additional > information has been provided as necessary. > - Consider alternative parent rates in round_rate and not in set_rate. > Trying to modify out parent's rate in set_rate will deadlock. > - Explicitly perform a stop/reset sequence in set_rate. This way we > always ensure that the PLL is properly stopped. > - Set the power-down bit when disabling the PLL. We can do this now that > enable/disable aren't abused during the set rate sequence. > - Fix typos in QSGMII_OFFSET and XFI_OFFSET > - Rename LNmTECR0_TEQ_TYPE_PRE to LNmTECR0_TEQ_TYPE_POST to better > reflect its function (adding post-cursor equalization). > - Use of_clk_hw_onecell_get instead of a custom function. > - Return struct clks from lynx_clks_init instead of embedding lynx_clk > in lynx_priv. > - Rework PCCR helper functions; T-series SoCs differ from Layerscape SoCs > primarily in the layout and offset of the PCCRs. This will help bring a > cleaner abstraction layer. The caps have been removed, since this handles > the > only current usage. > - Convert to use new binding format. As a result of this, we no longer need to > have protocols for PCIe or SATA. Additionally, modes now live in lynx_group > instead of lynx_priv. > - Remove teq from lynx_proto_params, since it can be determined from > preq_ratio/postq_ratio. > - Fix an early return from lynx_set_mode not releasing serdes->lock. > - Rename lynx_priv.conf to .cfg, since I kept mistyping it. > > Changes in v3: > - Rename remaining references to QorIQ SerDes to Lynx 10G > - Fix PLL enable sequence by waiting for our reset request to be cleared > before continuing. Do the same for the lock, even though it isn't as > critical. Because we will delay for 1.5ms on average, use prepare > instead of enable so we can sleep. > - Document the status of each protocol > - Fix offset of several bitfields in RECR0 > - Take into account PLLRST_B, SDRST_B, and SDEN when considering whether > a PLL is "enabled." > - Only power off unused lanes. > - Split mode lane mask into first/last lane (like group) > - Read modes from device tree > - Use caps to determine whether KX/KR are supported > - Move modes to lynx_priv > - Ensure that the protocol controller is not already in-use when we try > to configure a new mode. This should only occur if the device tree is > misconfigured (e.g. when QSGMII is selected on two lanes but there is > only one QSGMII controller). > - Split PLL drivers off into their own file > - Add clock for "ext_dly" instead of writing the bit directly (and > racing with any clock code). > - Use kasprintf instead of open-coding the snprintf dance > - Support 1000BASE-KX in lynx_lookup_proto. This still requires PCS > support, so nothing is truly "enabled" yet. > > Changes in v2: > - Rename driver to Lynx 10G (etc.) > - Fix not clearing group->pll after disabling it > - Support 1 and 2 phy-cells > - Power off lanes during probe > - Clear SGMIIaCR1_PCS_EN during probe > - Rename LYNX_PROTO_UNKNOWN to LYNX_PROTO_NONE > - Handle 1000BASE-KX in lynx_proto_mode_prep > > Documentation/driver-api/phy/index.rst | 1 + > Documentation/driver-api/phy/lynx_10g.rst | 58 + > MAINTAINERS | 2 + > drivers/phy/freescale/Kconfig | 18 +- > drivers/phy/freescale/Makefile | 1 + > drivers/phy/freescale/phy-fsl-lynx-10g.c | 1224 +++++++++++++++++++++ > 6 files changed, 1303 insertions(+), 1 deletion(-) > create mode 100644 Documentation/driver-api/phy/lynx_10g.rst > create mode 100644 drivers/phy/freescale/phy-fsl-lynx-10g.c > > diff --git a/Documentation/driver-api/phy/index.rst > b/Documentation/driver-api/phy/index.rst > index 69ba1216de72..c9b7a4698dab 100644 > --- a/Documentation/driver-api/phy/index.rst > +++ b/Documentation/driver-api/phy/index.rst > @@ -7,6 +7,7 @@ Generic PHY Framework > .. toctree:: > > phy > + lynx_10g > samsung-usb2 > > .. only:: subproject and html > diff --git a/Documentation/driver-api/phy/lynx_10g.rst > b/Documentation/driver-api/phy/lynx_10g.rst > new file mode 100644 > index 000000000000..17f9a9580e24 > --- /dev/null > +++ b/Documentation/driver-api/phy/lynx_10g.rst > @@ -0,0 +1,58 @@ > +.. SPDX-License-Identifier: GPL-2.0 > + > +=========================== > +Lynx 10G Phy (QorIQ SerDes) > +=========================== > + > +Using this phy > +-------------- > + > +:c:func:`phy_get` just gets (or creates) a new :c:type:`phy` with the lanes > +described in the phandle. :c:func:`phy_init` is what actually reserves the > +lanes for use. Unlike some other drivers, when the phy is created, there is > no > +default protocol. :c:func:`phy_set_mode <phy_set_mode_ext>` must be called in > +order to set the protocol. > + > +Supporting SoCs > +--------------- > + > +Each new SoC needs a :c:type:`struct lynx_conf <lynx_conf>`, containing the > +number of lanes in each device, the endianness of the device, and the helper > +functions to use when selecting protocol controllers. For example, the > +configuration for the LS1046A is:: > + > + static const struct lynx_cfg ls1046a_cfg = { > + .lanes = 4, > + .endian = REGMAP_ENDIAN_BIG, > + .mode_conflict = lynx_ls_mode_conflict, > + .mode_apply = lynx_ls_mode_apply, > + .mode_init = lynx_ls_mode_init, > + }; > + > +The ``mode_`` functions will generally be common to all SoCs in a series > (e.g. > +all Layerscape SoCs or all T-series SoCs). > + > +In addition, you will need to add a device node as documented in > +``Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml``. This lets the > +driver know which lanes are available to configure. > + > +Supporting Protocols > +-------------------- > + > +Each protocol is a combination of values which must be programmed into the > lane > +registers. To add a new protocol, first add it to :c:type:`enum lynx_protocol > +<lynx_protocol>`. Add a new entry to ``lynx_proto_params``, and populate the > +appropriate fields. Modify ``lynx_lookup_proto`` to map the :c:type:`enum > +phy_mode <phy_mode>` to :c:type:`enum lynx_protocol <lynx_protocol>`. > Finally, > +update the ``mode_conflict``, ``mode_apply``, and ``mode_init`` helpers to > +support your protocol. > + > +You may need to modify :c:func:`lynx_set_mode` in order to support your > +protocol. This can happen when you have added members to :c:type:`struct > +lynx_proto_params <lynx_proto_params>`. It can also happen if you have > specific > +clocking requirements, or protocol-specific registers to program. > + > +Internal API Reference > +---------------------- > + > +.. kernel-doc:: drivers/phy/freescale/phy-fsl-lynx-10g.c > diff --git a/MAINTAINERS b/MAINTAINERS > index 8da893681de6..870014ab14aa 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -12198,7 +12198,9 @@ T: git > https://github.com/linux-test-project/ltp.git > LYNX 10G SERDES DRIVER > M: Sean Anderson <sean.ander...@seco.com> > S: Maintained > +F: Documentation/driver-api/phy/lynx_10g.rst > F: drivers/clk/clk-fsl-lynx-10g.c > +F: drivers/phy/freescale/phy-fsl-lynx-10g.c > F: include/dt-bindings/clock/fsl,lynx-10g.h > F: include/linux/phy/lynx-10g.h > > diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig > index 5d461232276f..6bebe00f5889 100644 > --- a/drivers/phy/freescale/Kconfig > +++ b/drivers/phy/freescale/Kconfig > @@ -49,7 +49,23 @@ config PHY_FSL_LYNX_28G > Only useful for a restricted set of Ethernet protocols. > > config PHY_FSL_LYNX_10G > - tristate > + tristate "Freescale QorIQ Lynx 10G SerDes support" > depends on COMMON_CLK > depends on ARCH_LAYERSCAPE || PPC || COMPILE_TEST > + select GENERIC_PHY > select REGMAP_MMIO > + help > + This adds support for the Lynx "SerDes" devices found on various QorIQ > + SoCs. There may be up to four SerDes devices on each SoC, and each > + device supports up to eight lanes. The SerDes is configured by > + default by the RCW, but this module is necessary in order to support > + some modes (such as 2.5G SGMII or 1000BASE-KX), or clock setups (as > + only as subset of clock configurations are supported by the RCW). > + The hardware supports a variety of protocols, including Ethernet, > + SATA, PCIe, and more exotic links such as Interlaken and Aurora. This > + driver only supports Ethernet, but it will try not to touch lanes > + configured for other protocols. > + > + If you have a QorIQ processor and want to dynamically reconfigure your > + SerDes, say Y. If this driver is compiled as a module, it will be > + named phy-fsl-lynx-10g and clk-fsl-lynx-10g. > diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile > index cedb328bc4d2..32ad795be7c6 100644 > --- a/drivers/phy/freescale/Makefile > +++ b/drivers/phy/freescale/Makefile > @@ -3,4 +3,5 @@ obj-$(CONFIG_PHY_FSL_IMX8MQ_USB) += phy-fsl-imx8mq-usb.o > obj-$(CONFIG_PHY_MIXEL_LVDS_PHY) += phy-fsl-imx8qm-lvds-phy.o > obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-fsl-imx8-mipi-dphy.o > obj-$(CONFIG_PHY_FSL_IMX8M_PCIE) += phy-fsl-imx8m-pcie.o > +obj-$(CONFIG_PHY_FSL_LYNX_10G) += phy-fsl-lynx-10g.o > obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o > diff --git a/drivers/phy/freescale/phy-fsl-lynx-10g.c > b/drivers/phy/freescale/phy-fsl-lynx-10g.c > new file mode 100644 > index 000000000000..880f718b387f > --- /dev/null > +++ b/drivers/phy/freescale/phy-fsl-lynx-10g.c > @@ -0,0 +1,1224 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2022 Sean Anderson <sean.ander...@seco.com> > + * > + * This driver is for the Lynx 10G phys found on many QorIQ devices, > including > + * the Layerscape series. > + */ > + > +#include <dt-bindings/phy/phy.h> > +#include <linux/clk.h> > +#include <linux/platform_device.h> > +#include <linux/phy.h> > +#include <linux/phy/lynx-10g.h> > +#include <linux/phy/phy.h> > +#include <linux/regmap.h> > + > +#define TCALCR 0x90 > +#define TCALCR1 0x94 > +#define RCALCR 0xa0 > +#define RCALCR1 0xa4 > + > +#define CALCR_CALRST_B BIT(27) > + > +#define LS_PCCR_BASE 0x200 > +#define PCCR_STRIDE 0x4 > + > +#define LS_PCCRa(a) (LS_PCCR_BASE + (a) * PCCR_STRIDE) > + > +#define PCCR8_SGMIIa_KX BIT(3) > +#define PCCR8_SGMIIa_MASK GENMASK(3, 0) > +#define PCCR8_SGMIIa_SHIFT(a) (28 - (a) * 4) > + > +#define PCCR9_QSGMIIa_MASK GENMASK(2, 0) > +#define PCCR9_QSGMIIa_SHIFT(a) (28 - (a) * 4) > + > +#define PCCRB_XFIa_MASK GENMASK(2, 0) > +#define PCCRB_XFIa_SHIFT(a) (28 - (a) * 4) > + > +#define LANE_BASE 0x800 > +#define LANE_STRIDE 0x40 > +#define LNm(m, off) (LANE_BASE + (m) * LANE_STRIDE + (off)) > +#define LNmGCR0(m) LNm(m, 0x00) > +#define LNmGCR1(m) LNm(m, 0x04) > +#define LNmSSCR0(m) LNm(m, 0x0C) > +#define LNmRECR0(m) LNm(m, 0x10) > +#define LNmRECR1(m) LNm(m, 0x14) > +#define LNmTECR0(m) LNm(m, 0x18) > +#define LNmSSCR1(m) LNm(m, 0x1C) > +#define LNmTTLCR0(m) LNm(m, 0x20) > + > +#define LNmGCR0_RPLL_LES BIT(31) > +#define LNmGCR0_RRAT_SEL GENMASK(29, 28) > +#define LNmGCR0_TPLL_LES BIT(27) > +#define LNmGCR0_TRAT_SEL GENMASK(25, 24) > +#define LNmGCR0_RRST_B BIT(22) > +#define LNmGCR0_TRST_B BIT(21) > +#define LNmGCR0_RX_PD BIT(20) > +#define LNmGCR0_TX_PD BIT(19) > +#define LNmGCR0_IF20BIT_EN BIT(18) > +#define LNmGCR0_FIRST_LANE BIT(16) > +#define LNmGCR0_TTRM_VM_SEL GENMASK(13, 12) > +#define LNmGCR0_PROTS GENMASK(11, 7) > + > +#define LNmGCR0_RAT_SEL_SAME 0b00 > +#define LNmGCR0_RAT_SEL_HALF 0b01 > +#define LNmGCR0_RAT_SEL_QUARTER 0b10 > +#define LNmGCR0_RAT_SEL_DOUBLE 0b11 > + > +#define LNmGCR0_PROTS_PCIE 0b00000 > +#define LNmGCR0_PROTS_SGMII 0b00001 > +#define LNmGCR0_PROTS_SATA 0b00010 > +#define LNmGCR0_PROTS_XFI 0b01010 > + > +#define LNmGCR1_RDAT_INV BIT(31) > +#define LNmGCR1_TDAT_INV BIT(30) > +#define LNmGCR1_OPAD_CTL BIT(26) > +#define LNmGCR1_REIDL_TH GENMASK(22, 20) > +#define LNmGCR1_REIDL_EX_SEL GENMASK(19, 18) > +#define LNmGCR1_REIDL_ET_SEL GENMASK(17, 16) > +#define LNmGCR1_REIDL_EX_MSB BIT(15) > +#define LNmGCR1_REIDL_ET_MSB BIT(14) > +#define LNmGCR1_REQ_CTL_SNP BIT(13) > +#define LNmGCR1_REQ_CDR_SNP BIT(12) > +#define LNmGCR1_TRSTDIR BIT(7) > +#define LNmGCR1_REQ_BIN_SNP BIT(6) > +#define LNmGCR1_ISLEW_RCTL GENMASK(5, 4) > +#define LNmGCR1_OSLEW_RCTL GENMASK(1, 0) > + > +#define LNmRECR0_RXEQ_BST BIT(28) > +#define LNmRECR0_GK2OVD GENMASK(27, 24) > +#define LNmRECR0_GK3OVD GENMASK(19, 16) > +#define LNmRECR0_GK2OVD_EN BIT(15) > +#define LNmRECR0_GK3OVD_EN BIT(14) > +#define LNmRECR0_OSETOVD_EN BIT(13) > +#define LNmRECR0_BASE_WAND GENMASK(11, 10) > +#define LNmRECR0_OSETOVD GENMASK(6, 0) > + > +#define LNmRECR0_BASE_WAND_OFF 0b00 > +#define LNmRECR0_BASE_WAND_DEFAULT 0b01 > +#define LNmRECR0_BASE_WAND_ALTERNATE 0b10 > +#define LNmRECR0_BASE_WAND_OSETOVD 0b11 > + > +#define LNmTECR0_TEQ_TYPE GENMASK(29, 28) > +#define LNmTECR0_SGN_PREQ BIT(26) > +#define LNmTECR0_RATIO_PREQ GENMASK(25, 22) > +#define LNmTECR0_SGN_POST1Q BIT(21) > +#define LNmTECR0_RATIO_PST1Q GENMASK(20, 16) > +#define LNmTECR0_ADPT_EQ GENMASK(13, 8) > +#define LNmTECR0_AMP_RED GENMASK(5, 0) > + > +#define LNmTECR0_TEQ_TYPE_NONE 0b00 > +#define LNmTECR0_TEQ_TYPE_POST 0b01 > +#define LNmTECR0_TEQ_TYPE_BOTH 0b10 > + > +#define LNmTTLCR0_FLT_SEL GENMASK(29, 24) > + > +#define LS_SGMII_BASE 0x1800 > +#define LS_QSGMII_BASE 0x1880 > +#define LS_XFI_BASE 0x1980 > + > +#define PCS_STRIDE 0x10 > +#define CR_STRIDE 0x4 > +#define PCSa(a, base, cr) (base + (a) * PCS_STRIDE + (cr) * CR_STRIDE) > + > +#define PCSaCR1_MDEV_PORT GENMASK(31, 27) > + > +#define LS_SGMIIaCR1(a) PCSa(a, LS_SGMII_BASE, 1) > +#define SGMIIaCR1_SGPCS_EN BIT(11) > + > +enum lynx_protocol { > + LYNX_PROTO_NONE = 0, > + LYNX_PROTO_SGMII, > + LYNX_PROTO_SGMII25, /* Not tested */ > + LYNX_PROTO_1000BASEKX, /* Not tested */ > + LYNX_PROTO_QSGMII, /* Not tested */ > + LYNX_PROTO_XFI, > + LYNX_PROTO_10GKR, /* Link training unimplemented */ > + LYNX_PROTO_LAST, > +}; > + > +static const char lynx_proto_str[][16] = { > + [LYNX_PROTO_NONE] = "unknown", > + [LYNX_PROTO_SGMII] = "SGMII", > + [LYNX_PROTO_SGMII25] = "2.5G SGMII", > + [LYNX_PROTO_1000BASEKX] = "1000BASE-KX", > + [LYNX_PROTO_QSGMII] = "QSGMII", > + [LYNX_PROTO_XFI] = "XFI", > + [LYNX_PROTO_10GKR] = "10GBASE-KR", > +}; > + > +#define PROTO_MASK(proto) BIT(LYNX_PROTO_##proto) > + > +/** > + * struct lynx_proto_params - Parameters for configuring a protocol > + * @frate_khz: The PLL rate, in kHz > + * @rat_sel: The divider to get the line rate > + * @if20bit: Whether the proto is 20 bits or 10 bits > + * @prots: Lane protocol select > + * @reidl_th: Receiver electrical idle detection threshold > + * @reidl_ex: Exit electrical idle filter > + * @reidl_et: Enter idle filter > + * @slew: Slew control > + * @baseline_wander: Enable baseline wander correction > + * @gain: Adaptive equalization gain override > + * @offset_override: Adaptive equalization offset override > + * @preq_ratio: Ratio of full swing transition bit to pre-cursor > + * @postq_ratio: Ratio of full swing transition bit to first post-cursor. > + * @adpt_eq: Transmitter Adjustments for 8G/10G > + * @amp_red: Overall TX Amplitude Reduction > + * @flt_sel: TTL configuration selector > + */ > +struct lynx_proto_params { > + u32 frate_khz; > + u8 rat_sel; > + u8 prots; > + u8 reidl_th; > + u8 reidl_ex; > + u8 reidl_et; > + u8 slew; > + u8 gain; > + u8 baseline_wander; > + u8 offset_override; > + u8 preq_ratio; > + u8 postq_ratio; > + u8 adpt_eq; > + u8 amp_red; > + u8 flt_sel; > + bool if20bit; > +}; > + > +static const struct lynx_proto_params lynx_proto_params[] = { > + [LYNX_PROTO_SGMII] = { > + .frate_khz = 5000000, > + .rat_sel = LNmGCR0_RAT_SEL_QUARTER, > + .if20bit = false, > + .prots = LNmGCR0_PROTS_SGMII, > + .reidl_th = 0b001, > + .reidl_ex = 0b011, > + .reidl_et = 0b100, > + .slew = 0b01, > + .gain = 0b1111, > + .offset_override = 0b0011111, > + .adpt_eq = 0b110000, > + .amp_red = 0b000110, > + .flt_sel = 0b111001, > + }, > + [LYNX_PROTO_1000BASEKX] = { > + .frate_khz = 5000000, > + .rat_sel = LNmGCR0_RAT_SEL_QUARTER, > + .if20bit = false, > + .prots = LNmGCR0_PROTS_SGMII, > + .slew = 0b01, > + .gain = 0b1111, > + .offset_override = 0b0011111, > + .adpt_eq = 0b110000, > + .flt_sel = 0b111001, > + }, > + [LYNX_PROTO_SGMII25] = { > + .frate_khz = 3125000, > + .rat_sel = LNmGCR0_RAT_SEL_SAME, > + .if20bit = false, > + .prots = LNmGCR0_PROTS_SGMII, > + .slew = 0b10, > + .offset_override = 0b0011111, > + .postq_ratio = 0b00110, > + .adpt_eq = 0b110000, > + }, > + [LYNX_PROTO_QSGMII] = { > + .frate_khz = 5000000, > + .rat_sel = LNmGCR0_RAT_SEL_SAME, > + .if20bit = true, > + .prots = LNmGCR0_PROTS_SGMII, > + .slew = 0b01, > + .offset_override = 0b0011111, > + .postq_ratio = 0b00110, > + .adpt_eq = 0b110000, > + .amp_red = 0b000010, > + }, > + [LYNX_PROTO_XFI] = { > + .frate_khz = 5156250, > + .rat_sel = LNmGCR0_RAT_SEL_DOUBLE, > + .if20bit = true, > + .prots = LNmGCR0_PROTS_XFI, > + .slew = 0b01, > + .baseline_wander = LNmRECR0_BASE_WAND_DEFAULT, > + .offset_override = 0b1011111, > + .postq_ratio = 0b00011, > + .adpt_eq = 0b110000, > + .amp_red = 0b000111, > + }, > + [LYNX_PROTO_10GKR] = { > + .frate_khz = 5156250, > + .rat_sel = LNmGCR0_RAT_SEL_DOUBLE, > + .if20bit = true, > + .prots = LNmGCR0_PROTS_XFI, > + .slew = 0b01, > + .baseline_wander = LNmRECR0_BASE_WAND_DEFAULT, > + .offset_override = 0b1011111, > + .preq_ratio = 0b0011, > + .postq_ratio = 0b01100, > + .adpt_eq = 0b110000, > + }, > +}; > + > +/** > + * struct lynx_mode - A single configuration of a protocol controller > + * @protos: A bitmask of the &enum lynx_protocol this mode supports > + * @pccr: The number of the PCCR which contains this mode > + * @idx: The index of the protocol controller. For example, SGMIIB would have > + * index 1. > + * @cfg: The value to program into the controller to select this mode > + * > + * The serdes has multiple protocol controllers which can be each be selected > + * independently. Depending on their configuration, they may use multiple > lanes > + * at once (e.g. AUI or PCIe x4). Additionally, multiple protocols may be > + * supported by a single mode (XFI and 10GKR differ only in their protocol > + * parameters). > + */ > +struct lynx_mode { > + u16 protos; > + u8 pccr; > + u8 idx; > + u8 cfg; > +}; > + > +static_assert(LYNX_PROTO_LAST - 1 <= > + sizeof_field(struct lynx_mode, protos) * BITS_PER_BYTE); > + > +struct lynx_priv; > + > +/** > + * struct lynx_cfg - Configuration for a particular serdes > + * @lanes: Number of lanes > + * @endian: Endianness of the registers > + * @mode_conflict: Determine whether a protocol controller is already in use > + * (by another group). > + * @mode_apply: Apply a given protocol. This includes programming the > + * appropriate config into the PCCR, as well as > enabling/disabling > + * any other registers (such as the enabling MDIO access). > + * %LYNX_PROTO_NONE may be used to clear any associated > registers. > + * @mode_init: Finish initializing a mode. All fields are filled in except > for > + * protos. Type is one of PHY_TYPE_*. mode->protos should be > filled > + * in, and the other fields should be sanity-checked. > + */ > +struct lynx_cfg { > + unsigned int lanes; > + enum regmap_endian endian; > + bool (*mode_conflict)(struct lynx_priv *serdes, > + const struct lynx_mode *mode); > + void (*mode_apply)(struct lynx_priv *serdes, > + const struct lynx_mode *mode, > + enum lynx_protocol proto); > + int (*mode_init)(struct lynx_priv *serdes, struct lynx_mode *mode, > + int type); > +}; > + > +/** > + * struct lynx_group - Driver data for a group of lanes > + * @serdes: The parent serdes > + * @pll: The currently-used pll > + * @ex_dly: The ex_dly clock, if used > + * @modes: Valid protocol controller configurations > + * @mode_count: Number of modes in @modes > + * @first_lane: The first lane in the group > + * @last_lane: The last lane in the group > + * @proto: The currently-configured protocol > + * @initialized: Whether the complete state of @modes has been set > + * @prots: The protocol set up by the RCW > + */ > +struct lynx_group { > + struct lynx_priv *serdes; > + struct clk *pll, *ex_dly; > + const struct lynx_mode *modes; > + size_t mode_count; > + unsigned int first_lane; > + unsigned int last_lane; > + enum lynx_protocol proto; > + bool initialized; > + u8 prots; > +}; > + > +/** > + * struct lynx_priv - Driver data for the serdes > + * @lock: A lock protecting "common" registers in @regmap, as well as the > + * members of this struct. Lane-specific registers are protected by > the > + * phy's lock. PLL registers are protected by the clock's lock. > + * @dev: The serdes device > + * @regmap: The backing regmap > + * @cfg: SoC-specific configuration > + * @plls: The PLLs > + * @ex_dlys: The "ex_dly" clocks > + * @groups: Groups in the serdes > + * @group_count: Number of groups in @groups > + * @used_lanes: Bitmap of the lanes currently used by phys > + */ > +struct lynx_priv { > + struct mutex lock; > + struct device *dev; > + struct regmap *regmap; > + const struct lynx_cfg *cfg; > + struct clk *plls[2], *ex_dlys[2]; > + struct lynx_group *groups; > + unsigned int group_count; > + unsigned int used_lanes; > +}; > + > +static u32 lynx_read(struct lynx_priv *serdes, u32 reg) > +{ > + unsigned int ret = 0; > + > + WARN_ON_ONCE(regmap_read(serdes->regmap, reg, &ret)); > + dev_vdbg(serdes->dev, "%.8x <= %.8x\n", ret, reg); > + return ret; > +} > + > +static void lynx_write(struct lynx_priv *serdes, u32 val, u32 reg) > +{ > + dev_vdbg(serdes->dev, "%.8x => %.8x\n", val, reg); > + WARN_ON_ONCE(regmap_write(serdes->regmap, reg, val)); > +} > + > +/* > + * This is tricky. If first_lane=1 and last_lane=0, the condition will see 2, > + * 1, 0. But the loop body will see 1, 0. We do this to avoid underflow. We > + * can't pull the same trick when incrementing, because then we might have to > + * start at -1 if (e.g.) first_lane = 0. > + */ > +#define for_range(val, start, end) \ > + for (val = start < end ? start : start + 1; \ > + start < end ? val <= end : val-- > end; \ > + start < end ? val++ : 0) > +#define for_each_lane(lane, group) \ > + for_range(lane, group->first_lane, group->last_lane) > +#define for_each_lane_reverse(lane, group) \ > + for_range(lane, group->last_lane, group->first_lane) > + > +static int lynx_power_on(struct phy *phy) > +{ > + int i; > + struct lynx_group *group = phy_get_drvdata(phy); > + u32 gcr0; > + > + for_each_lane(i, group) { > + gcr0 = lynx_read(group->serdes, LNmGCR0(i)); > + gcr0 &= ~(LNmGCR0_RX_PD | LNmGCR0_TX_PD); > + lynx_write(group->serdes, gcr0, LNmGCR0(i)); > + > + usleep_range(15, 30); > + gcr0 |= LNmGCR0_RRST_B | LNmGCR0_TRST_B; > + lynx_write(group->serdes, gcr0, LNmGCR0(i)); > + } > + > + return 0; > +} > + > +static void lynx_power_off_group(struct lynx_group *group) > +{ > + int i; > + > + for_each_lane_reverse(i, group) { > + u32 gcr0 = lynx_read(group->serdes, LNmGCR0(i)); > + > + gcr0 |= LNmGCR0_RX_PD | LNmGCR0_TX_PD; > + gcr0 &= ~(LNmGCR0_RRST_B | LNmGCR0_TRST_B); > + lynx_write(group->serdes, gcr0, LNmGCR0(i)); > + } > +} > + > +static int lynx_power_off(struct phy *phy) > +{ > + lynx_power_off_group(phy_get_drvdata(phy)); > + return 0; > +} > + > +/** > + * lynx_lane_bitmap() - Get a bitmap for a group of lanes > + * @group: The group of lanes > + * > + * Return: A mask containing all bits between @group->first and @group->last > + */ > +static unsigned int lynx_lane_bitmap(struct lynx_group *group) > +{ > + if (group->first_lane > group->last_lane) > + return GENMASK(group->first_lane, group->last_lane); > + else > + return GENMASK(group->last_lane, group->first_lane); > +} > + > +/** > + * lynx_lookup_mode() - Get the mode for a group/protocol combination > + * @group: The group of lanes to use > + * @proto: The protocol to use > + * > + * Return: An appropriate mode to use, or %NULL if none match. > + */ > +static const struct lynx_mode *lynx_lookup_mode(struct lynx_group *group, > + enum lynx_protocol proto) > +{ > + int i; > + > + for (i = 0; i < group->mode_count; i++) { > + const struct lynx_mode *mode = &group->modes[i]; > + > + if (BIT(proto) & mode->protos) > + return mode; > + } > + > + return NULL; > +} > + > +/** > + * lynx_init_late() - Initialize group modes after probe() > + * @group: The group of lanes to initialize > + * > + * Disable all modes for a group, taking care not to disable other groups' > + * current modes. This ensures that whenever we select a mode, nothing else > is > + * interfering. Then, turn off the group. > + * > + * Return: 0 on success, or -%ENOMEM > + */ > +static int lynx_init_late(struct lynx_group *group) > +{ > + int i, j; > + struct lynx_priv *serdes = group->serdes; > + const struct lynx_mode **modes; > + > + modes = kcalloc(serdes->group_count, sizeof(*modes), GFP_KERNEL); > + if (!modes) > + return -ENOMEM; > + > + for (i = 0; i < serdes->group_count; i++) > + modes[i] = lynx_lookup_mode(&serdes->groups[i], > + serdes->groups[i].proto); > + > + for (i = 0; i < group->mode_count; i++) { > + for (j = 0; j < serdes->group_count; j++) { > + if (!modes[j]) > + continue; > + > + if (group->modes[i].pccr == modes[j]->pccr && > + group->modes[i].idx == modes[j]->idx) > + goto skip; > + } > + > + serdes->cfg->mode_apply(serdes, &group->modes[i], > + LYNX_PROTO_NONE); > +skip: ; > + } > + > + kfree(modes); > + lynx_power_off_group(group); > + group->initialized = true; > + return 0; > +} > + > +static int lynx_init(struct phy *phy) > +{ > + int ret = 0; > + struct lynx_group *group = phy_get_drvdata(phy); > + struct lynx_priv *serdes = group->serdes; > + unsigned int lane_mask = lynx_lane_bitmap(group); > + > + mutex_lock(&serdes->lock); > + if (serdes->used_lanes & lane_mask) { > + ret = -EBUSY; > + } else { > + if (!group->initialized) > + ret = lynx_init_late(group); > + > + if (!ret) > + serdes->used_lanes |= lane_mask; > + } > + mutex_unlock(&serdes->lock); > + return ret; > +} > + > +static int lynx_exit(struct phy *phy) > +{ > + struct lynx_group *group = phy_get_drvdata(phy); > + struct lynx_priv *serdes = group->serdes; > + > + clk_disable_unprepare(group->ex_dly); > + group->ex_dly = NULL; > + > + clk_disable_unprepare(group->pll); > + clk_rate_exclusive_put(group->pll); > + group->pll = NULL; > + > + mutex_lock(&serdes->lock); > + serdes->used_lanes &= ~lynx_lane_bitmap(group); > + mutex_unlock(&serdes->lock); > + return 0; > +} > + > +/** > + * lynx_lookup_proto() - Convert a phy-subsystem mode to a protocol > + * @mode: The mode to convert > + * @submode: The submode of @mode > + * > + * Return: A corresponding serdes-specific mode > + */ > +static enum lynx_protocol lynx_lookup_proto(enum phy_mode mode, int submode) > +{ > + switch (mode) { > + case PHY_MODE_ETHERNET: > + switch (submode) { > + case PHY_INTERFACE_MODE_SGMII: > + case PHY_INTERFACE_MODE_1000BASEX: > + return LYNX_PROTO_SGMII; > + case PHY_INTERFACE_MODE_2500BASEX: > + return LYNX_PROTO_SGMII25; > + case PHY_INTERFACE_MODE_QSGMII: > + return LYNX_PROTO_QSGMII; > + case PHY_INTERFACE_MODE_XGMII: > + case PHY_INTERFACE_MODE_10GBASER: > + return LYNX_PROTO_XFI; > + case PHY_INTERFACE_MODE_10GKR: > + return LYNX_PROTO_10GKR; > + default: > + return LYNX_PROTO_NONE; > + } > + default: > + return LYNX_PROTO_NONE; > + } > +} > + > +static int lynx_validate(struct phy *phy, enum phy_mode phy_mode, int > submode, > + union phy_configure_opts *opts) > +{ > + enum lynx_protocol proto; > + struct lynx_group *group = phy_get_drvdata(phy); > + const struct lynx_mode *mode; > + > + proto = lynx_lookup_proto(phy_mode, submode); > + if (proto == LYNX_PROTO_NONE) > + return -EINVAL; > + > + /* Nothing to do */ > + if (proto == group->proto) > + return 0; > + > + /* > + * FIXME: At the moment we don't support switching between major > + * protocols. From what I can tell, the serdes is working fine, but > + * something goes wrong in the PCS. > + */ > + if (lynx_proto_params[proto].prots != group->prots) > + return -EINVAL; > + > + mode = lynx_lookup_mode(group, proto); > + if (!mode) > + return -EINVAL; > + > + return 0; > +} > + > +#define abs_diff(a, b) ({ \ > + typeof(a) _a = (a); \ > + typeof(b) _b = (b); \ > + _a > _b ? _a - _b : _b - _a; \ > +}) > + > +static int lynx_set_mode(struct phy *phy, enum phy_mode phy_mode, int > submode) > +{ > + enum lynx_protocol proto; > + const struct lynx_proto_params *params; > + const struct lynx_mode *old_mode = NULL, *new_mode; > + int i, pll, ret; > + struct lynx_group *group = phy_get_drvdata(phy); > + struct lynx_priv *serdes = group->serdes; > + u32 tmp, teq; > + u32 gcr0 = 0, gcr1 = 0, recr0 = 0, tecr0 = 0; > + u32 gcr0_mask = 0, gcr1_mask = 0, recr0_mask = 0, tecr0_mask = 0; > + > + proto = lynx_lookup_proto(phy_mode, submode); > + if (proto == LYNX_PROTO_NONE) { > + dev_dbg(&phy->dev, "unknown mode/submode %d/%d\n", > + phy_mode, submode); > + return -EINVAL; > + } > + > + /* Nothing to do */ > + if (proto == group->proto) > + return 0; > + > + new_mode = lynx_lookup_mode(group, proto); > + if (!new_mode) { > + dev_dbg(&phy->dev, "could not find mode for %s on lanes %u to > %u\n", > + lynx_proto_str[proto], group->first_lane, > + group->last_lane); > + return -EINVAL; > + } > + > + if (group->proto != LYNX_PROTO_NONE) { > + old_mode = lynx_lookup_mode(group, group->proto); > + if (!old_mode) { > + dev_err(&phy->dev, "could not find mode for %s\n", > + lynx_proto_str[group->proto]); > + return -EBUSY; > + } > + } > + > + mutex_lock(&serdes->lock); > + if (serdes->cfg->mode_conflict(serdes, new_mode)) { > + dev_dbg(&phy->dev, "%s%c already in use\n", > + lynx_proto_str[__ffs(new_mode->protos)], > + 'A' + new_mode->idx); > + ret = -EBUSY; > + goto out; > + }
what are the cases that you envision to have a mode_conflict? > + > + clk_disable_unprepare(group->ex_dly); > + group->ex_dly = NULL; > + > + clk_disable_unprepare(group->pll); > + clk_rate_exclusive_put(group->pll); > + group->pll = NULL; > + > + /* First, try to use a PLL which already has the correct rate */ > + params = &lynx_proto_params[proto]; > + for (pll = 0; pll < ARRAY_SIZE(serdes->plls); pll++) { > + struct clk *clk = serdes->plls[pll]; > + unsigned long rate = clk_get_rate(clk); > + unsigned long error = abs_diff(rate, params->frate_khz); > + > + dev_dbg(&phy->dev, "pll%d has rate %lu (error=%lu)\n", pll, > + rate, error); > + /* Accept up to 100ppm deviation */ > + if (error && params->frate_khz / error < 10000) > + continue; > + > + if (!clk_set_rate_exclusive(clk, rate)) > + goto got_pll; > + /* > + * Someone else got a different rate first (or there was some > + * other error) > + */ > + } > + > + /* If neither PLL has the right rate, try setting it */ > + for (pll = 0; pll < 2; pll++) { > + ret = clk_set_rate_exclusive(serdes->plls[pll], > + params->frate_khz); > + if (!ret) > + goto got_pll; > + } > + > + dev_dbg(&phy->dev, "could not get a pll at %ukHz\n", > + params->frate_khz); > + goto out; > + > +got_pll: > + group->pll = serdes->plls[pll]; > + ret = clk_prepare_enable(group->pll); > + if (ret) > + goto out; > + > + gcr0_mask |= LNmGCR0_RRAT_SEL | LNmGCR0_TRAT_SEL; > + gcr0_mask |= LNmGCR0_RPLL_LES | LNmGCR0_TPLL_LES; > + gcr0_mask |= LNmGCR0_RRST_B | LNmGCR0_TRST_B; > + gcr0_mask |= LNmGCR0_RX_PD | LNmGCR0_TX_PD; > + gcr0_mask |= LNmGCR0_IF20BIT_EN | LNmGCR0_PROTS; > + gcr0 |= FIELD_PREP(LNmGCR0_RPLL_LES, !pll); > + gcr0 |= FIELD_PREP(LNmGCR0_TPLL_LES, !pll); > + gcr0 |= FIELD_PREP(LNmGCR0_RRAT_SEL, params->rat_sel); > + gcr0 |= FIELD_PREP(LNmGCR0_TRAT_SEL, params->rat_sel); > + gcr0 |= FIELD_PREP(LNmGCR0_IF20BIT_EN, params->if20bit); > + gcr0 |= FIELD_PREP(LNmGCR0_PROTS, params->prots); > + > + gcr1_mask |= LNmGCR1_RDAT_INV | LNmGCR1_TDAT_INV; > + gcr1_mask |= LNmGCR1_OPAD_CTL | LNmGCR1_REIDL_TH; > + gcr1_mask |= LNmGCR1_REIDL_EX_SEL | LNmGCR1_REIDL_ET_SEL; > + gcr1_mask |= LNmGCR1_REIDL_EX_MSB | LNmGCR1_REIDL_ET_MSB; > + gcr1_mask |= LNmGCR1_REQ_CTL_SNP | LNmGCR1_REQ_CDR_SNP; > + gcr1_mask |= LNmGCR1_TRSTDIR | LNmGCR1_REQ_BIN_SNP; > + gcr1_mask |= LNmGCR1_ISLEW_RCTL | LNmGCR1_OSLEW_RCTL; > + gcr1 |= FIELD_PREP(LNmGCR1_REIDL_TH, params->reidl_th); > + gcr1 |= FIELD_PREP(LNmGCR1_REIDL_EX_SEL, params->reidl_ex & 3); > + gcr1 |= FIELD_PREP(LNmGCR1_REIDL_ET_SEL, params->reidl_et & 3); > + gcr1 |= FIELD_PREP(LNmGCR1_REIDL_EX_MSB, params->reidl_ex >> 2); > + gcr1 |= FIELD_PREP(LNmGCR1_REIDL_ET_MSB, params->reidl_et >> 2); > + gcr1 |= FIELD_PREP(LNmGCR1_TRSTDIR, > + group->first_lane > group->last_lane); > + gcr1 |= FIELD_PREP(LNmGCR1_ISLEW_RCTL, params->slew); > + gcr1 |= FIELD_PREP(LNmGCR1_OSLEW_RCTL, params->slew); > + > + recr0_mask |= LNmRECR0_RXEQ_BST | LNmRECR0_BASE_WAND; > + recr0_mask |= LNmRECR0_GK2OVD | LNmRECR0_GK3OVD; > + recr0_mask |= LNmRECR0_GK2OVD_EN | LNmRECR0_GK3OVD_EN; > + recr0_mask |= LNmRECR0_OSETOVD_EN | LNmRECR0_OSETOVD; > + if (params->gain) { > + recr0 |= FIELD_PREP(LNmRECR0_GK2OVD, params->gain); > + recr0 |= FIELD_PREP(LNmRECR0_GK3OVD, params->gain); > + recr0 |= LNmRECR0_GK2OVD_EN | LNmRECR0_GK3OVD_EN; > + } > + recr0 |= FIELD_PREP(LNmRECR0_BASE_WAND, params->baseline_wander); > + recr0 |= FIELD_PREP(LNmRECR0_OSETOVD, params->offset_override); > + > + tecr0_mask |= LNmTECR0_TEQ_TYPE; > + tecr0_mask |= LNmTECR0_SGN_PREQ | LNmTECR0_RATIO_PREQ; > + tecr0_mask |= LNmTECR0_SGN_POST1Q | LNmTECR0_RATIO_PST1Q; > + tecr0_mask |= LNmTECR0_ADPT_EQ | LNmTECR0_AMP_RED; > + teq = LNmTECR0_TEQ_TYPE_NONE; > + if (params->postq_ratio) { > + teq = LNmTECR0_TEQ_TYPE_POST; > + tecr0 |= FIELD_PREP(LNmTECR0_SGN_POST1Q, 1); > + tecr0 |= FIELD_PREP(LNmTECR0_RATIO_PST1Q, params->postq_ratio); > + } > + if (params->preq_ratio) { > + teq = LNmTECR0_TEQ_TYPE_BOTH; > + tecr0 |= FIELD_PREP(LNmTECR0_SGN_PREQ, 1); > + tecr0 |= FIELD_PREP(LNmTECR0_RATIO_PREQ, params->preq_ratio); > + } > + tecr0 |= FIELD_PREP(LNmTECR0_TEQ_TYPE, teq); > + tecr0 |= FIELD_PREP(LNmTECR0_ADPT_EQ, params->adpt_eq); > + tecr0 |= FIELD_PREP(LNmTECR0_AMP_RED, params->amp_red); > + > + for_each_lane(i, group) { > + tmp = lynx_read(serdes, LNmGCR0(i)); > + tmp &= ~(LNmGCR0_RRST_B | LNmGCR0_TRST_B); > + lynx_write(serdes, tmp, LNmGCR0(i)); > + } > + > + ndelay(50); > + > + /* Disable the old controller */ > + if (old_mode) > + serdes->cfg->mode_apply(serdes, old_mode, LYNX_PROTO_NONE); > + > + for_each_lane(i, group) { > + tmp = lynx_read(serdes, LNmGCR0(i)); > + tmp &= ~gcr0_mask; > + tmp |= gcr0; > + tmp |= FIELD_PREP(LNmGCR0_FIRST_LANE, i == group->first_lane); > + lynx_write(serdes, tmp, LNmGCR0(i)); > + > + tmp = lynx_read(serdes, LNmGCR1(i)); > + tmp &= ~gcr1_mask; > + tmp |= gcr1; > + lynx_write(serdes, tmp, LNmGCR1(i)); > + > + tmp = lynx_read(serdes, LNmRECR0(i)); > + tmp &= ~recr0_mask; > + tmp |= recr0; > + lynx_write(serdes, tmp, LNmRECR0(i)); > + > + tmp = lynx_read(serdes, LNmTECR0(i)); > + tmp &= ~tecr0_mask; > + tmp |= tecr0; > + lynx_write(serdes, tmp, LNmTECR0(i)); > + > + tmp = lynx_read(serdes, LNmTTLCR0(i)); > + tmp &= ~LNmTTLCR0_FLT_SEL; > + tmp |= FIELD_PREP(LNmTTLCR0_FLT_SEL, params->flt_sel); > + lynx_write(serdes, tmp, LNmTTLCR0(i)); > + } > + > + ndelay(120); > + > + for_each_lane_reverse(i, group) { > + tmp = lynx_read(serdes, LNmGCR0(i)); > + tmp |= LNmGCR0_RRST_B | LNmGCR0_TRST_B; > + lynx_write(serdes, tmp, LNmGCR0(i)); > + } > + > + /* Enable the new controller */ > + serdes->cfg->mode_apply(serdes, new_mode, proto); > + if (proto == LYNX_PROTO_1000BASEKX) { > + group->ex_dly = serdes->ex_dlys[pll]; > + /* This should never fail since it's from our internal driver */ > + WARN_ON_ONCE(clk_prepare_enable(group->ex_dly)); > + } > + group->proto = proto; > + > + dev_dbg(&phy->dev, "set mode to %s on lanes %u to %u\n", > + lynx_proto_str[proto], group->first_lane, group->last_lane); > + > +out: > + mutex_unlock(&serdes->lock); > + return ret; > +} > + > +static const struct phy_ops lynx_phy_ops = { > + .init = lynx_init, > + .exit = lynx_exit, > + .power_on = lynx_power_on, > + .power_off = lynx_power_off, > + .set_mode = lynx_set_mode, > + .validate = lynx_validate, > + .owner = THIS_MODULE, > +}; > + > +static int lynx_read_u32(struct device *dev, struct fwnode_handle *fwnode, > + const char *prop, u32 *val) > +{ > + int ret; > + > + ret = fwnode_property_read_u32(fwnode, prop, val); > + if (ret) > + dev_err(dev, "could not read %s from %pfwP: %d\n", prop, > + fwnode, ret); > + return ret; > +} > + > +static int lynx_probe_group(struct lynx_priv *serdes, struct lynx_group > *group, > + struct fwnode_handle *fwnode, bool initialize) > +{ > + int i, lane_count, ret; > + struct device *dev = serdes->dev; > + struct fwnode_handle *mode_node; > + struct lynx_mode *modes; > + struct phy *phy; > + u32 *lanes = NULL; > + > + group->serdes = serdes; > + > + lane_count = fwnode_property_count_u32(fwnode, "reg"); > + if (lane_count < 0) { > + dev_err(dev, "could not read %s from %pfwP: %d\n", > + "reg", fwnode, lane_count); > + return lane_count; > + } > + > + lanes = kcalloc(lane_count, sizeof(*lanes), GFP_KERNEL); > + if (!lanes) > + return -ENOMEM; > + > + ret = fwnode_property_read_u32_array(fwnode, "reg", lanes, lane_count); > + if (ret) { > + dev_err(dev, "could not read %s from %pfwP: %d\n", > + "reg", fwnode, ret); > + goto out; > + } > + > + group->first_lane = lanes[0]; > + group->last_lane = lanes[lane_count - 1]; > + for (i = 0; i < lane_count; i++) { > + u32 prots, gcr0; > + > + if (lanes[i] > serdes->cfg->lanes) { > + ret = -EINVAL; > + dev_err(dev, "lane %d not in range 0 to %u\n", > + i, serdes->cfg->lanes); > + goto out; > + } > + > + if (lanes[i] != group->first_lane + > + i * !!(group->last_lane - group->first_lane)) { > + ret = -EINVAL; > + dev_err(dev, "lane %d is not monotonic\n", i); > + goto out; > + } > + > + gcr0 = lynx_read(serdes, LNmGCR0(lanes[i])); > + prots = FIELD_GET(LNmGCR0_PROTS, gcr0); > + if (i && group->prots != prots) { > + ret = -EIO; > + dev_err(dev, "lane %d protocol does not match lane 0\n", > + lanes[i]); > + goto out; > + } > + group->prots = prots; > + } > + > + fwnode_for_each_child_node(fwnode, mode_node) > + group->mode_count++; > + > + modes = devm_kcalloc(dev, group->mode_count, sizeof(*group->modes), > + GFP_KERNEL); > + if (!modes) { > + ret = -ENOMEM; > + goto out; > + } > + > + i = 0; > + fwnode_for_each_child_node(fwnode, mode_node) { > + struct lynx_mode *mode = &modes[i++]; > + u32 val; > + > + ret = lynx_read_u32(dev, mode_node, "fsl,pccr", &val); > + if (ret) > + goto out; > + mode->pccr = val; > + > + ret = lynx_read_u32(dev, mode_node, "fsl,index", &val); > + if (ret) > + goto out; > + mode->idx = val; > + > + ret = lynx_read_u32(dev, mode_node, "fsl,cfg", &val); > + if (ret) > + goto out; > + mode->cfg = val; > + > + ret = lynx_read_u32(dev, mode_node, "fsl,type", &val); > + if (ret) > + goto out; > + > + ret = serdes->cfg->mode_init(serdes, mode, val); > + if (ret) > + goto out; > + > + dev_dbg(dev, "mode PCCR%X.%s%c_CFG=%x on lanes %u to %u\n", > + mode->pccr, lynx_proto_str[__ffs(mode->protos)], > + 'A' + mode->idx, mode->cfg, group->first_lane, > + group->last_lane); > + } > + > + WARN_ON(i != group->mode_count); > + group->modes = modes; > + > + if (initialize) { > + /* Deselect anything configured by the RCW/bootloader */ > + for (i = 0; i < group->mode_count; i++) > + serdes->cfg->mode_apply(serdes, &group->modes[i], > + LYNX_PROTO_NONE); > + > + /* Disable the lanes for now */ > + lynx_power_off_group(group); > + group->initialized = true; > + } > + > + phy = devm_phy_create(dev, to_of_node(fwnode), &lynx_phy_ops); > + ret = PTR_ERR_OR_ZERO(phy); > + if (ret) > + dev_err_probe(dev, ret, "could not create phy\n"); > + else > + phy_set_drvdata(phy, group); > + > +out: > + kfree(lanes); > + return ret; > +} > + > +static int lynx_probe(struct platform_device *pdev) > +{ > + bool compat; > + int ret, i = 0; > + struct device *dev = &pdev->dev; > + struct fwnode_handle *group_node; > + struct lynx_priv *serdes; > + struct phy_provider *provider; > + struct regmap_config regmap_config = { > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > + .disable_locking = true, > + }; > + struct resource *res; > + void __iomem *base; > + > + serdes = devm_kzalloc(dev, sizeof(*serdes), GFP_KERNEL); > + if (!serdes) > + return -ENOMEM; > + > + serdes->dev = dev; > + platform_set_drvdata(pdev, serdes); > + mutex_init(&serdes->lock); > + serdes->cfg = device_get_match_data(dev); > + > + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); > + if (IS_ERR(base)) { > + ret = PTR_ERR(base); > + dev_err_probe(dev, ret, "could not get/map registers\n"); > + return ret; > + } > + > + regmap_config.val_format_endian = serdes->cfg->endian; > + regmap_config.max_register = res->end - res->start; > + serdes->regmap = devm_regmap_init_mmio(dev, base, ®map_config); > + if (IS_ERR(serdes->regmap)) { > + ret = PTR_ERR(serdes->regmap); > + dev_err_probe(dev, ret, "could not create regmap\n"); > + return ret; > + } > + > + compat = device_property_present(dev, "fsl,unused-lanes-reserved"); > + ret = lynx_clks_init(dev, serdes->regmap, serdes->plls, > + serdes->ex_dlys, compat); > + if (ret) > + return ret; > + > + serdes->group_count = device_get_child_node_count(dev); > + serdes->groups = devm_kcalloc(dev, serdes->group_count, > + sizeof(*serdes->groups), GFP_KERNEL); > + if (!serdes->groups) > + return -ENOMEM; > + > + device_for_each_child_node(dev, group_node) { > + ret = lynx_probe_group(serdes, &serdes->groups[i++], > + group_node, !compat); > + if (ret) > + return ret; > + } > + WARN_ON(i != serdes->group_count); > + > + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); > + ret = PTR_ERR_OR_ZERO(provider); > + if (ret) > + dev_err_probe(dev, ret, "could not register phy provider\n"); > + else > + dev_info(dev, "probed with %u lanes and %u groups\n", > + serdes->cfg->lanes, serdes->group_count); > + return ret; > +} > + > +/* > + * These are common helpers for the PCCRs found on (most) Layerscape SoCs. > + * There is an earlier layout used on most T-series SoCs, as well as the > + * LS1020A/21A/22A. > + */ > + > +static int lynx_ls_pccr_params(const struct lynx_mode *mode, u32 *off, > + u32 *shift, u32 *mask) > +{ > + if (mode->protos & PROTO_MASK(SGMII)) { > + *off = LS_PCCRa(0x8); > + *mask = PCCR8_SGMIIa_MASK; > + *shift = PCCR8_SGMIIa_SHIFT(mode->idx); > + } else if (mode->protos & PROTO_MASK(QSGMII)) { > + *off = LS_PCCRa(0x9); > + *mask = PCCR9_QSGMIIa_MASK; > + *shift = PCCR9_QSGMIIa_SHIFT(mode->idx); > + } else if (mode->protos & PROTO_MASK(XFI)) { > + *off = LS_PCCRa(0xB); > + *mask = PCCRB_XFIa_MASK; > + *shift = PCCRB_XFIa_SHIFT(mode->idx); > + } else { > + return -EINVAL; > + } > + > + return 0; > +} > + > +static bool lynx_ls_mode_conflict(struct lynx_priv *serdes, > + const struct lynx_mode *mode) > +{ > + u32 off, shift, mask; > + > + if (WARN_ON_ONCE(lynx_ls_pccr_params(mode, &off, &shift, &mask))) > + return true; > + > + return (lynx_read(serdes, off) >> shift) & mask; > +} > + > +static void lynx_ls_mode_apply(struct lynx_priv *serdes, > + const struct lynx_mode *mode, > + enum lynx_protocol proto) > +{ > + u32 pccr, off, shift, mask; > + > + if (WARN_ON_ONCE(proto != LYNX_PROTO_NONE && > + !(mode->protos & BIT(proto)))) > + return; > + if (WARN_ON_ONCE(lynx_ls_pccr_params(mode, &off, &shift, &mask))) > + return; > + > + dev_dbg(serdes->dev, "applying %s to PCCR%X.%s%c_CFG\n", > + lynx_proto_str[proto], mode->pccr, > + lynx_proto_str[__ffs(mode->protos)], 'A' + mode->idx); > + > + pccr = lynx_read(serdes, off); > + pccr &= ~(mask << shift); > + if (proto != LYNX_PROTO_NONE) > + pccr |= mode->cfg << shift; > + > + if (proto == LYNX_PROTO_1000BASEKX) > + pccr |= PCCR8_SGMIIa_KX << shift; > + lynx_write(serdes, pccr, off); > + > + if (mode->protos & PROTO_MASK(SGMII)) { > + u32 cr1 = lynx_read(serdes, LS_SGMIIaCR1(mode->idx)); > + > + cr1 &= ~SGMIIaCR1_SGPCS_EN; > + cr1 |= proto == LYNX_PROTO_NONE ? 0 : SGMIIaCR1_SGPCS_EN; > + lynx_write(serdes, cr1, LS_SGMIIaCR1(mode->idx)); > + } > +} > + > +static int lynx_ls_mode_init(struct lynx_priv *serdes, struct lynx_mode > *mode, > + int type) > +{ > + u32 max = 0, off, shift, mask; > + > + if (mode->pccr >= 0x10) { > + dev_err(serdes->dev, "PCCR index %u too large\n", mode->pccr); > + return -EINVAL; > + } > + > + switch (type) { > + case PHY_TYPE_2500BASEX: > + mode->protos = PROTO_MASK(SGMII25); > + fallthrough; > + case PHY_TYPE_SGMII: > + max = 8; > + mode->protos |= PROTO_MASK(SGMII) | PROTO_MASK(1000BASEKX); > + break; > + case PHY_TYPE_QSGMII: > + max = 4; > + mode->protos = PROTO_MASK(QSGMII); > + break; > + case PHY_TYPE_10GBASER: > + max = 8; > + mode->protos = PROTO_MASK(XFI) | PROTO_MASK(10GKR); > + break; > + default: > + dev_err(serdes->dev, "unknown mode type %d\n", type); > + return -EINVAL; > + } > + > + if (mode->idx >= max) { > + dev_err(serdes->dev, "%s index %u too large\n", > + lynx_proto_str[__ffs(mode->protos)], mode->idx); > + return -EINVAL; > + } > + > + if (WARN_ON_ONCE(lynx_ls_pccr_params(mode, &off, &shift, &mask))) > + return -EINVAL; > + > + if (!mode->cfg || mode->cfg & ~mask) { > + dev_err(serdes->dev, "bad value %x for %s%c_CFG\n", > + mode->cfg, lynx_proto_str[__ffs(mode->protos)], > + 'A' + mode->idx); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static const struct lynx_cfg ls1046a_cfg = { > + .lanes = 4, > + .endian = REGMAP_ENDIAN_BIG, > + .mode_conflict = lynx_ls_mode_conflict, > + .mode_apply = lynx_ls_mode_apply, > + .mode_init = lynx_ls_mode_init, > +}; > + > +static const struct lynx_cfg ls1088a_cfg = { > + .lanes = 4, > + .endian = REGMAP_ENDIAN_LITTLE, > + .mode_conflict = lynx_ls_mode_conflict, > + .mode_apply = lynx_ls_mode_apply, > + .mode_init = lynx_ls_mode_init, So you have cfg with mode_xxx pointing to same functions for both of the versions you support... so question is why do this and not call the functions directly? > +}; > + > +static const struct of_device_id lynx_of_match[] = { > + { .compatible = "fsl,ls1046a-serdes", .data = &ls1046a_cfg }, > + { .compatible = "fsl,ls1088a-serdes", .data = &ls1088a_cfg }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, lynx_of_match); > + > +static struct platform_driver lynx_driver = { > + .probe = lynx_probe, > + .driver = { > + .name = "lynx_10g", > + .of_match_table = lynx_of_match, > + }, > +}; > +module_platform_driver(lynx_driver); > + > +MODULE_AUTHOR("Sean Anderson <sean.ander...@seco.com>"); > +MODULE_DESCRIPTION("Lynx 10G SerDes driver"); > +MODULE_LICENSE("GPL"); > -- > 2.35.1.1320.gc452695387.dirty -- ~Vinod