diff --git a/Documentation/devicetree/bindings/net/cpsw.txt b/Documentation/devicetree/bindings/net/cpsw.txt index b3acebe08eb0..3264e1978d25 100644 --- a/Documentation/devicetree/bindings/net/cpsw.txt +++ b/Documentation/devicetree/bindings/net/cpsw.txt @@ -22,7 +22,8 @@ Required properties: - cpsw-phy-sel : Specifies the phandle to the CPSW phy mode selection device. See also cpsw-phy-sel.txt for it's binding. Note that in legacy cases cpsw-phy-sel may be - a child device instead of a phandle. + a child device instead of a phandle + (DEPRECATED, use phys property instead). Optional properties: - ti,hwmods : Must be "cpgmac0" @@ -44,6 +45,7 @@ Optional properties: Slave Properties: Required properties: - phy-mode : See ethernet.txt file in the same directory +- phys : phandle on phy-gmii-sel PHY (see phy/ti-phy-gmii-sel.txt) Optional properties: - dual_emac_res_vlan : Specifies VID to be used to segregate the ports @@ -85,12 +87,14 @@ Examples: phy-mode = "rgmii-txid"; /* Filled in by U-Boot */ mac-address = [ 00 00 00 00 00 00 ]; + phys = <&phy_gmii_sel 1 0>; }; cpsw_emac1: slave@1 { phy_id = <&davinci_mdio>, <1>; phy-mode = "rgmii-txid"; /* Filled in by U-Boot */ mac-address = [ 00 00 00 00 00 00 ]; + phys = <&phy_gmii_sel 2 0>; }; }; @@ -114,11 +118,13 @@ Examples: phy-mode = "rgmii-txid"; /* Filled in by U-Boot */ mac-address = [ 00 00 00 00 00 00 ]; + phys = <&phy_gmii_sel 1 0>; }; cpsw_emac1: slave@1 { phy_id = <&davinci_mdio>, <1>; phy-mode = "rgmii-txid"; /* Filled in by U-Boot */ mac-address = [ 00 00 00 00 00 00 ]; + phys = <&phy_gmii_sel 2 0>; }; }; diff --git a/Documentation/devicetree/bindings/phy/fsl,imx8mq-usb-phy.txt b/Documentation/devicetree/bindings/phy/fsl,imx8mq-usb-phy.txt new file mode 100644 index 000000000000..a22e853d710c --- /dev/null +++ b/Documentation/devicetree/bindings/phy/fsl,imx8mq-usb-phy.txt @@ -0,0 +1,17 @@ +* Freescale i.MX8MQ USB3 PHY binding + +Required properties: +- compatible: Should be "fsl,imx8mq-usb-phy" +- #phys-cells: must be 0 (see phy-bindings.txt in this directory) +- reg: The base address and length of the registers +- clocks: phandles to the clocks for each clock listed in clock-names +- clock-names: must contain "phy" + +Example: + usb3_phy0: phy@381f0040 { + compatible = "fsl,imx8mq-usb-phy"; + reg = <0x381f0040 0x40>; + clocks = <&clk IMX8MQ_CLK_USB1_PHY_ROOT>; + clock-names = "phy"; + #phy-cells = <0>; + }; diff --git a/Documentation/devicetree/bindings/phy/phy-cadence-sierra.txt b/Documentation/devicetree/bindings/phy/phy-cadence-sierra.txt new file mode 100644 index 000000000000..6e1b47bfce43 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-cadence-sierra.txt @@ -0,0 +1,67 @@ +Cadence Sierra PHY +----------------------- + +Required properties: +- compatible: cdns,sierra-phy-t0 +- clocks: Must contain an entry in clock-names. + See ../clocks/clock-bindings.txt for details. +- clock-names: Must be "phy_clk" +- resets: Must contain an entry for each in reset-names. + See ../reset/reset.txt for details. +- reset-names: Must include "sierra_reset" and "sierra_apb". + "sierra_reset" must control the reset line to the PHY. + "sierra_apb" must control the reset line to the APB PHY + interface. +- reg: register range for the PHY. +- #address-cells: Must be 1 +- #size-cells: Must be 0 + +Optional properties: +- cdns,autoconf: A boolean property whose presence indicates that the + PHY registers will be configured by hardware. If not + present, all sub-node optional properties must be + provided. + +Sub-nodes: + Each group of PHY lanes with a single master lane should be represented as + a sub-node. Note that the actual configuration of each lane is determined by + hardware strapping, and must match the configuration specified here. + +Sub-node required properties: +- #phy-cells: Generic PHY binding; must be 0. +- reg: The master lane number. This is the lowest numbered lane + in the lane group. +- resets: Must contain one entry which controls the reset line for the + master lane of the sub-node. + See ../reset/reset.txt for details. + +Sub-node optional properties: +- cdns,num-lanes: Number of lanes in this group. From 1 to 4. The + group is made up of consecutive lanes. +- cdns,phy-type: Can be PHY_TYPE_PCIE or PHY_TYPE_USB3, depending on + configuration of lanes. + +Example: + pcie_phy4: pcie-phy@fd240000 { + compatible = "cdns,sierra-phy-t0"; + reg = <0x0 0xfd240000 0x0 0x40000>; + resets = <&phyrst 0>, <&phyrst 1>; + reset-names = "sierra_reset", "sierra_apb"; + clocks = <&phyclock>; + clock-names = "phy_clk"; + #address-cells = <1>; + #size-cells = <0>; + pcie0_phy0: pcie-phy@0 { + reg = <0>; + resets = <&phyrst 2>; + cdns,num-lanes = <2>; + #phy-cells = <0>; + cdns,phy-type = ; + }; + pcie0_phy1: pcie-phy@2 { + reg = <2>; + resets = <&phyrst 4>; + cdns,num-lanes = <1>; + #phy-cells = <0>; + cdns,phy-type = ; + }; diff --git a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt index fbc198d5dd39..41a1074228ba 100644 --- a/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt +++ b/Documentation/devicetree/bindings/phy/qcom-qmp-phy.txt @@ -25,10 +25,6 @@ Required properties: - For all others: - The reg-names property shouldn't be defined. - - #clock-cells: must be 1 - - Phy pll outputs a bunch of clocks for Tx, Rx and Pipe - interface (for pipe based PHYs). These clock are then gate-controlled - by gcc. - #address-cells: must be 1 - #size-cells: must be 1 - ranges: must be present @@ -82,27 +78,33 @@ Required nodes: - Each device node of QMP phy is required to have as many child nodes as the number of lanes the PHY has. -Required properties for child node: +Required properties for child nodes of PCIe PHYs (one child per lane): - reg: list of offset and length pairs of register sets for PHY blocks - - - index 0: tx - - index 1: rx - - index 2: pcs - - index 3: pcs_misc (optional) - + tx, rx, pcs, and pcs_misc (optional). - #phy-cells: must be 0 -Required properties child node of pcie and usb3 qmp phys: +Required properties for a single "lanes" child node of non-PCIe PHYs: + - reg: list of offset and length pairs of register sets for PHY blocks + For 1-lane devices: + tx, rx, pcs, and (optionally) pcs_misc + For 2-lane devices: + tx0, rx0, pcs, tx1, rx1, and (optionally) pcs_misc + - #phy-cells: must be 0 + +Required properties for child node of PCIe and USB3 qmp phys: - clocks: a list of phandles and clock-specifier pairs, one for each entry in clock-names. - clock-names: Must contain following: "pipe" for pipe clock specific to each lane. - clock-output-names: Name of the PHY clock that will be the parent for the above pipe clock. - For "qcom,ipq8074-qmp-pcie-phy": - "pcie20_phy0_pipe_clk" Pipe Clock parent (or) "pcie20_phy1_pipe_clk" + - #clock-cells: must be 0 + - Phy pll outputs pipe clocks for pipe based PHYs. These clocks are then + gate-controlled by the gcc. Required properties for child node of PHYs with lane reset, AKA: "qcom,msm8996-qmp-pcie-phy" @@ -115,7 +117,6 @@ Example: phy@34000 { compatible = "qcom,msm8996-qmp-pcie-phy"; reg = <0x34000 0x488>; - #clock-cells = <1>; #address-cells = <1>; #size-cells = <1>; ranges; @@ -137,6 +138,7 @@ Example: reg = <0x35000 0x130>, <0x35200 0x200>, <0x35400 0x1dc>; + #clock-cells = <0>; #phy-cells = <0>; clocks = <&gcc GCC_PCIE_0_PIPE_CLK>; @@ -150,3 +152,54 @@ Example: ... ... }; + + phy@88eb000 { + compatible = "qcom,sdm845-qmp-usb3-uni-phy"; + reg = <0x88eb000 0x18c>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + clocks = <&gcc GCC_USB3_SEC_PHY_AUX_CLK>, + <&gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>, + <&gcc GCC_USB3_SEC_CLKREF_CLK>, + <&gcc GCC_USB3_SEC_PHY_COM_AUX_CLK>; + clock-names = "aux", "cfg_ahb", "ref", "com_aux"; + + resets = <&gcc GCC_USB3PHY_PHY_SEC_BCR>, + <&gcc GCC_USB3_PHY_SEC_BCR>; + reset-names = "phy", "common"; + + lane@88eb200 { + reg = <0x88eb200 0x128>, + <0x88eb400 0x1fc>, + <0x88eb800 0x218>, + <0x88eb600 0x70>; + #clock-cells = <0>; + #phy-cells = <0>; + clocks = <&gcc GCC_USB3_SEC_PHY_PIPE_CLK>; + clock-names = "pipe0"; + clock-output-names = "usb3_uni_phy_pipe_clk_src"; + }; + }; + + phy@1d87000 { + compatible = "qcom,sdm845-qmp-ufs-phy"; + reg = <0x1d87000 0x18c>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + clock-names = "ref", + "ref_aux"; + clocks = <&gcc GCC_UFS_MEM_CLKREF_CLK>, + <&gcc GCC_UFS_PHY_PHY_AUX_CLK>; + + lanes@1d87400 { + reg = <0x1d87400 0x108>, + <0x1d87600 0x1e0>, + <0x1d87c00 0x1dc>, + <0x1d87800 0x108>, + <0x1d87a00 0x1e0>; + #phy-cells = <0>; + }; + }; diff --git a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt index 07ca4ec4a745..f2e120af17f0 100644 --- a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt +++ b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt @@ -14,13 +14,14 @@ Required properties: * allwinner,sun8i-r40-usb-phy * allwinner,sun8i-v3s-usb-phy * allwinner,sun50i-a64-usb-phy + * allwinner,sun50i-h6-usb-phy - reg : a list of offset + length pairs - reg-names : * "phy_ctrl" - * "pmu0" for H3, V3s and A64 + * "pmu0" for H3, V3s, A64 or H6 * "pmu1" * "pmu2" for sun4i, sun6i, sun7i, sun8i-a83t or sun8i-h3 - * "pmu3" for sun8i-h3 + * "pmu3" for sun8i-h3 or sun50i-h6 - #phy-cells : from the generic phy bindings, must be 1 - clocks : phandle + clock specifier for the phy clocks - clock-names : @@ -29,12 +30,13 @@ Required properties: * "usb0_phy", "usb1_phy" for sun8i * "usb0_phy", "usb1_phy", "usb2_phy" and "usb2_hsic_12M" for sun8i-a83t * "usb0_phy", "usb1_phy", "usb2_phy" and "usb3_phy" for sun8i-h3 + * "usb0_phy" and "usb3_phy" for sun50i-h6 - resets : a list of phandle + reset specifier pairs - reset-names : * "usb0_reset" * "usb1_reset" * "usb2_reset" for sun4i, sun6i, sun7i, sun8i-a83t or sun8i-h3 - * "usb3_reset" for sun8i-h3 + * "usb3_reset" for sun8i-h3 and sun50i-h6 Optional properties: - usb0_id_det-gpios : gpio phandle for reading the otg id pin value diff --git a/Documentation/devicetree/bindings/phy/ti-phy-gmii-sel.txt b/Documentation/devicetree/bindings/phy/ti-phy-gmii-sel.txt new file mode 100644 index 000000000000..50ce9ae0f7a5 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/ti-phy-gmii-sel.txt @@ -0,0 +1,68 @@ +CPSW Port's Interface Mode Selection PHY Tree Bindings +----------------------------------------------- + +TI am335x/am437x/dra7(am5)/dm814x CPSW3G Ethernet Subsystem supports +two 10/100/1000 Ethernet ports with selectable G/MII, RMII, and RGMII interfaces. +The interface mode is selected by configuring the MII mode selection register(s) +(GMII_SEL) in the System Control Module chapter (SCM). GMII_SEL register(s) and +bit fields placement in SCM are different between SoCs while fields meaning +is the same. + +--------------+ + +-------------------------------+ |SCM | + | CPSW | | +---------+ | + | +--------------------------------+gmii_sel | | + | | | | +---------+ | + | +----v---+ +--------+ | +--------------+ + | |Port 1..<--+-->GMII/MII<-------> + | | | | | | | + | +--------+ | +--------+ | + | | | + | | +--------+ | + | | | RMII <-------> + | +--> | | + | | +--------+ | + | | | + | | +--------+ | + | | | RGMII <-------> + | +--> | | + | +--------+ | + +-------------------------------+ + +CPSW Port's Interface Mode Selection PHY describes MII interface mode between +CPSW Port and Ethernet PHY which depends on Eth PHY and board configuration. + +CPSW Port's Interface Mode Selection PHY device should defined as child device +of SCM node (scm_conf) and can be attached to each CPSW port node using standard +PHY bindings (See phy/phy-bindings.txt). + +Required properties: +- compatible : Should be "ti,am3352-phy-gmii-sel" for am335x platform + "ti,dra7xx-phy-gmii-sel" for dra7xx/am57xx platform + "ti,am43xx-phy-gmii-sel" for am43xx platform + "ti,dm814-phy-gmii-sel" for dm814x platform +- reg : Address and length of the register set for the device +- #phy-cells : must be 2. + cell 1 - CPSW port number (starting from 1) + cell 2 - RMII refclk mode + +Examples: + phy_gmii_sel: phy-gmii-sel { + compatible = "ti,am3352-phy-gmii-sel"; + reg = <0x650 0x4>; + #phy-cells = <2>; + }; + + mac: ethernet@4a100000 { + compatible = "ti,am335x-cpsw","ti,cpsw"; + ... + + cpsw_emac0: slave@4a100200 { + ... + phys = <&phy_gmii_sel 1 1>; + }; + + cpsw_emac1: slave@4a100300 { + ... + phys = <&phy_gmii_sel 2 1>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 8119141a926f..46bf7ff995f1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6261,6 +6261,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy.git S: Supported F: drivers/phy/ F: include/linux/phy/ +F: Documentation/devicetree/bindings/phy/ GENERIC PINCTRL I2C DEMULTIPLEXER DRIVER M: Wolfram Sang diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c index 125ea99418df..4551ec134494 100644 --- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c +++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c @@ -1165,28 +1165,13 @@ static void mvpp22_gop_setup_irq(struct mvpp2_port *port) */ static int mvpp22_comphy_init(struct mvpp2_port *port) { - enum phy_mode mode; int ret; if (!port->comphy) return 0; - switch (port->phy_interface) { - case PHY_INTERFACE_MODE_SGMII: - case PHY_INTERFACE_MODE_1000BASEX: - mode = PHY_MODE_SGMII; - break; - case PHY_INTERFACE_MODE_2500BASEX: - mode = PHY_MODE_2500SGMII; - break; - case PHY_INTERFACE_MODE_10GKR: - mode = PHY_MODE_10GKR; - break; - default: - return -EINVAL; - } - - ret = phy_set_mode(port->comphy, mode); + ret = phy_set_mode_ext(port->comphy, PHY_MODE_ETHERNET, + port->phy_interface); if (ret) return ret; diff --git a/drivers/net/ethernet/mscc/ocelot.c b/drivers/net/ethernet/mscc/ocelot.c index 3238b9ee42f3..3edb6082c670 100644 --- a/drivers/net/ethernet/mscc/ocelot.c +++ b/drivers/net/ethernet/mscc/ocelot.c @@ -472,7 +472,6 @@ static int ocelot_port_open(struct net_device *dev) { struct ocelot_port *port = netdev_priv(dev); struct ocelot *ocelot = port->ocelot; - enum phy_mode phy_mode; int err; /* Enable receiving frames on the port, and activate auto-learning of @@ -484,12 +483,8 @@ static int ocelot_port_open(struct net_device *dev) ANA_PORT_PORT_CFG, port->chip_port); if (port->serdes) { - if (port->phy_mode == PHY_INTERFACE_MODE_SGMII) - phy_mode = PHY_MODE_SGMII; - else - phy_mode = PHY_MODE_QSGMII; - - err = phy_set_mode(port->serdes, phy_mode); + err = phy_set_mode_ext(port->serdes, PHY_MODE_ETHERNET, + port->phy_mode); if (err) { netdev_err(dev, "Could not set mode of SerDes\n"); return err; diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c index 500f7ed8c58c..94e5e5b791ec 100644 --- a/drivers/net/ethernet/ti/cpsw.c +++ b/drivers/net/ethernet/ti/cpsw.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -387,6 +388,7 @@ struct cpsw_slave_data { int phy_if; u8 mac_addr[ETH_ALEN]; u16 dual_emac_res_vlan; /* Reserved VLAN for DualEMAC */ + struct phy *ifphy; }; struct cpsw_platform_data { @@ -1510,7 +1512,12 @@ static void cpsw_slave_open(struct cpsw_slave *slave, struct cpsw_priv *priv) phy_start(slave->phy); /* Configure GMII_SEL register */ - cpsw_phy_sel(cpsw->dev, slave->phy->interface, slave->slave_num); + if (!IS_ERR(slave->data->ifphy)) + phy_set_mode_ext(slave->data->ifphy, PHY_MODE_ETHERNET, + slave->data->phy_if); + else + cpsw_phy_sel(cpsw->dev, slave->phy->interface, + slave->slave_num); } static inline void cpsw_add_default_vlan(struct cpsw_priv *priv) @@ -3147,6 +3154,16 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data, if (strcmp(slave_node->name, "slave")) continue; + slave_data->ifphy = devm_of_phy_get(&pdev->dev, slave_node, + NULL); + if (!IS_ENABLED(CONFIG_TI_CPSW_PHY_SEL) && + IS_ERR(slave_data->ifphy)) { + ret = PTR_ERR(slave_data->ifphy); + dev_err(&pdev->dev, + "%d: Error retrieving port phy: %d\n", i, ret); + return ret; + } + slave_data->phy_node = of_parse_phandle(slave_node, "phy-handle", 0); parp = of_get_property(slave_node, "phy_id", &lenp); diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 60f949e2a684..250abe290ca1 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -15,6 +15,14 @@ config GENERIC_PHY phy users can obtain reference to the PHY. All the users of this framework should select this config. +config GENERIC_PHY_MIPI_DPHY + bool + help + Generic MIPI D-PHY support. + + Provides a number of helpers a core functions for MIPI D-PHY + drivers to us. + config PHY_LPC18XX_USB_OTG tristate "NXP LPC18xx/43xx SoC USB OTG PHY driver" depends on OF && (ARCH_LPC18XX || COMPILE_TEST) @@ -44,6 +52,7 @@ source "drivers/phy/allwinner/Kconfig" source "drivers/phy/amlogic/Kconfig" source "drivers/phy/broadcom/Kconfig" source "drivers/phy/cadence/Kconfig" +source "drivers/phy/freescale/Kconfig" source "drivers/phy/hisilicon/Kconfig" source "drivers/phy/lantiq/Kconfig" source "drivers/phy/marvell/Kconfig" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 0301e25d07c1..0d9fddc498a6 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -4,6 +4,7 @@ # obj-$(CONFIG_GENERIC_PHY) += phy-core.o +obj-$(CONFIG_GENERIC_PHY_MIPI_DPHY) += phy-core-mipi-dphy.o obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o obj-$(CONFIG_PHY_XGENE) += phy-xgene.o obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o @@ -16,6 +17,7 @@ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-y += broadcom/ \ cadence/ \ + freescale/ \ hisilicon/ \ marvell/ \ motorola/ \ diff --git a/drivers/phy/allwinner/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c index d4dcd39b8d76..5163097b43df 100644 --- a/drivers/phy/allwinner/phy-sun4i-usb.c +++ b/drivers/phy/allwinner/phy-sun4i-usb.c @@ -115,6 +115,7 @@ enum sun4i_usb_phy_type { sun8i_r40_phy, sun8i_v3s_phy, sun50i_a64_phy, + sun50i_h6_phy, }; struct sun4i_usb_phy_cfg { @@ -126,6 +127,7 @@ struct sun4i_usb_phy_cfg { bool dedicated_clocks; bool enable_pmu_unk1; bool phy0_dual_route; + int missing_phys; }; struct sun4i_usb_phy_data { @@ -294,7 +296,8 @@ static int sun4i_usb_phy_init(struct phy *_phy) return ret; } - if (data->cfg->type == sun8i_a83t_phy) { + if (data->cfg->type == sun8i_a83t_phy || + data->cfg->type == sun50i_h6_phy) { if (phy->index == 0) { val = readl(data->base + data->cfg->phyctl_offset); val |= PHY_CTL_VBUSVLDEXT; @@ -343,7 +346,8 @@ static int sun4i_usb_phy_exit(struct phy *_phy) struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); if (phy->index == 0) { - if (data->cfg->type == sun8i_a83t_phy) { + if (data->cfg->type == sun8i_a83t_phy || + data->cfg->type == sun50i_h6_phy) { void __iomem *phyctl = data->base + data->cfg->phyctl_offset; @@ -474,7 +478,8 @@ static int sun4i_usb_phy_power_off(struct phy *_phy) return 0; } -static int sun4i_usb_phy_set_mode(struct phy *_phy, enum phy_mode mode) +static int sun4i_usb_phy_set_mode(struct phy *_phy, + enum phy_mode mode, int submode) { struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); @@ -646,6 +651,9 @@ static struct phy *sun4i_usb_phy_xlate(struct device *dev, if (args->args[0] >= data->cfg->num_phys) return ERR_PTR(-ENODEV); + if (data->cfg->missing_phys & BIT(args->args[0])) + return ERR_PTR(-ENODEV); + return data->phys[args->args[0]].phy; } @@ -741,6 +749,9 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev) struct sun4i_usb_phy *phy = data->phys + i; char name[16]; + if (data->cfg->missing_phys & BIT(i)) + continue; + snprintf(name, sizeof(name), "usb%d_vbus", i); phy->vbus = devm_regulator_get_optional(dev, name); if (IS_ERR(phy->vbus)) { @@ -952,6 +963,17 @@ static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = { .phy0_dual_route = true, }; +static const struct sun4i_usb_phy_cfg sun50i_h6_cfg = { + .num_phys = 4, + .type = sun50i_h6_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .dedicated_clocks = true, + .enable_pmu_unk1 = true, + .phy0_dual_route = true, + .missing_phys = BIT(1) | BIT(2), +}; + static const struct of_device_id sun4i_usb_phy_of_match[] = { { .compatible = "allwinner,sun4i-a10-usb-phy", .data = &sun4i_a10_cfg }, { .compatible = "allwinner,sun5i-a13-usb-phy", .data = &sun5i_a13_cfg }, @@ -965,6 +987,7 @@ static const struct of_device_id sun4i_usb_phy_of_match[] = { { .compatible = "allwinner,sun8i-v3s-usb-phy", .data = &sun8i_v3s_cfg }, { .compatible = "allwinner,sun50i-a64-usb-phy", .data = &sun50i_a64_cfg}, + { .compatible = "allwinner,sun50i-h6-usb-phy", .data = &sun50i_h6_cfg }, { }, }; MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb2.c b/drivers/phy/amlogic/phy-meson-gxl-usb2.c index 9f9b5414b97a..148ef0bdb9c1 100644 --- a/drivers/phy/amlogic/phy-meson-gxl-usb2.c +++ b/drivers/phy/amlogic/phy-meson-gxl-usb2.c @@ -152,7 +152,8 @@ static int phy_meson_gxl_usb2_reset(struct phy *phy) return 0; } -static int phy_meson_gxl_usb2_set_mode(struct phy *phy, enum phy_mode mode) +static int phy_meson_gxl_usb2_set_mode(struct phy *phy, + enum phy_mode mode, int submode) { struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); @@ -209,7 +210,7 @@ static int phy_meson_gxl_usb2_power_on(struct phy *phy) /* power on the PHY by taking it out of reset mode */ regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, 0); - ret = phy_meson_gxl_usb2_set_mode(phy, priv->mode); + ret = phy_meson_gxl_usb2_set_mode(phy, priv->mode, 0); if (ret) { phy_meson_gxl_usb2_power_off(phy); diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb3.c b/drivers/phy/amlogic/phy-meson-gxl-usb3.c index d37d94ddf9c0..c0e9e4c16149 100644 --- a/drivers/phy/amlogic/phy-meson-gxl-usb3.c +++ b/drivers/phy/amlogic/phy-meson-gxl-usb3.c @@ -119,7 +119,8 @@ static int phy_meson_gxl_usb3_power_off(struct phy *phy) return 0; } -static int phy_meson_gxl_usb3_set_mode(struct phy *phy, enum phy_mode mode) +static int phy_meson_gxl_usb3_set_mode(struct phy *phy, + enum phy_mode mode, int submode) { struct phy_meson_gxl_usb3_priv *priv = phy_get_drvdata(phy); @@ -164,7 +165,7 @@ static int phy_meson_gxl_usb3_init(struct phy *phy) if (ret) goto err_disable_clk_phy; - ret = phy_meson_gxl_usb3_set_mode(phy, priv->mode); + ret = phy_meson_gxl_usb3_set_mode(phy, priv->mode, 0); if (ret) goto err_disable_clk_peripheral; diff --git a/drivers/phy/cadence/Kconfig b/drivers/phy/cadence/Kconfig index 57fff7de4031..2b8c0851ff33 100644 --- a/drivers/phy/cadence/Kconfig +++ b/drivers/phy/cadence/Kconfig @@ -1,5 +1,5 @@ # -# Phy driver for Cadence MHDP DisplayPort controller +# Phy drivers for Cadence PHYs # config PHY_CADENCE_DP tristate "Cadence MHDP DisplayPort PHY driver" @@ -8,3 +8,10 @@ config PHY_CADENCE_DP select GENERIC_PHY help Support for Cadence MHDP DisplayPort PHY. + +config PHY_CADENCE_SIERRA + tristate "Cadence Sierra PHY Driver" + depends on OF && HAS_IOMEM && RESET_CONTROLLER + select GENERIC_PHY + help + Enable this to support the Cadence Sierra PHY driver \ No newline at end of file diff --git a/drivers/phy/cadence/Makefile b/drivers/phy/cadence/Makefile index e5b0a11cf28a..412349af0492 100644 --- a/drivers/phy/cadence/Makefile +++ b/drivers/phy/cadence/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_PHY_CADENCE_DP) += phy-cadence-dp.o +obj-$(CONFIG_PHY_CADENCE_SIERRA) += phy-cadence-sierra.o diff --git a/drivers/phy/cadence/phy-cadence-sierra.c b/drivers/phy/cadence/phy-cadence-sierra.c new file mode 100644 index 000000000000..de10402f2931 --- /dev/null +++ b/drivers/phy/cadence/phy-cadence-sierra.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cadence Sierra PHY Driver + * + * Copyright (c) 2018 Cadence Design Systems + * Author: Alan Douglas + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PHY register offsets */ +#define SIERRA_PHY_PLL_CFG (0xc00e << 2) +#define SIERRA_DET_STANDEC_A (0x4000 << 2) +#define SIERRA_DET_STANDEC_B (0x4001 << 2) +#define SIERRA_DET_STANDEC_C (0x4002 << 2) +#define SIERRA_DET_STANDEC_D (0x4003 << 2) +#define SIERRA_DET_STANDEC_E (0x4004 << 2) +#define SIERRA_PSM_LANECAL (0x4008 << 2) +#define SIERRA_PSM_DIAG (0x4015 << 2) +#define SIERRA_PSC_TX_A0 (0x4028 << 2) +#define SIERRA_PSC_TX_A1 (0x4029 << 2) +#define SIERRA_PSC_TX_A2 (0x402A << 2) +#define SIERRA_PSC_TX_A3 (0x402B << 2) +#define SIERRA_PSC_RX_A0 (0x4030 << 2) +#define SIERRA_PSC_RX_A1 (0x4031 << 2) +#define SIERRA_PSC_RX_A2 (0x4032 << 2) +#define SIERRA_PSC_RX_A3 (0x4033 << 2) +#define SIERRA_PLLCTRL_SUBRATE (0x403A << 2) +#define SIERRA_PLLCTRL_GEN_D (0x403E << 2) +#define SIERRA_DRVCTRL_ATTEN (0x406A << 2) +#define SIERRA_CLKPATHCTRL_TMR (0x4081 << 2) +#define SIERRA_RX_CREQ_FLTR_A_MODE1 (0x4087 << 2) +#define SIERRA_RX_CREQ_FLTR_A_MODE0 (0x4088 << 2) +#define SIERRA_CREQ_CCLKDET_MODE01 (0x408E << 2) +#define SIERRA_RX_CTLE_MAINTENANCE (0x4091 << 2) +#define SIERRA_CREQ_FSMCLK_SEL (0x4092 << 2) +#define SIERRA_CTLELUT_CTRL (0x4098 << 2) +#define SIERRA_DFE_ECMP_RATESEL (0x40C0 << 2) +#define SIERRA_DFE_SMP_RATESEL (0x40C1 << 2) +#define SIERRA_DEQ_VGATUNE_CTRL (0x40E1 << 2) +#define SIERRA_TMRVAL_MODE3 (0x416E << 2) +#define SIERRA_TMRVAL_MODE2 (0x416F << 2) +#define SIERRA_TMRVAL_MODE1 (0x4170 << 2) +#define SIERRA_TMRVAL_MODE0 (0x4171 << 2) +#define SIERRA_PICNT_MODE1 (0x4174 << 2) +#define SIERRA_CPI_OUTBUF_RATESEL (0x417C << 2) +#define SIERRA_LFPSFILT_NS (0x418A << 2) +#define SIERRA_LFPSFILT_RD (0x418B << 2) +#define SIERRA_LFPSFILT_MP (0x418C << 2) +#define SIERRA_SDFILT_H2L_A (0x4191 << 2) + +#define SIERRA_MACRO_ID 0x00007364 +#define SIERRA_MAX_LANES 4 + +struct cdns_sierra_inst { + struct phy *phy; + u32 phy_type; + u32 num_lanes; + u32 mlane; + struct reset_control *lnk_rst; +}; + +struct cdns_reg_pairs { + u16 val; + u32 off; +}; + +struct cdns_sierra_data { + u32 id_value; + u32 pcie_regs; + u32 usb_regs; + struct cdns_reg_pairs *pcie_vals; + struct cdns_reg_pairs *usb_vals; +}; + +struct cdns_sierra_phy { + struct device *dev; + void __iomem *base; + struct cdns_sierra_data *init_data; + struct cdns_sierra_inst phys[SIERRA_MAX_LANES]; + struct reset_control *phy_rst; + struct reset_control *apb_rst; + struct clk *clk; + int nsubnodes; + bool autoconf; +}; + +static void cdns_sierra_phy_init(struct phy *gphy) +{ + struct cdns_sierra_inst *ins = phy_get_drvdata(gphy); + struct cdns_sierra_phy *phy = dev_get_drvdata(gphy->dev.parent); + int i, j; + struct cdns_reg_pairs *vals; + u32 num_regs; + + if (ins->phy_type == PHY_TYPE_PCIE) { + num_regs = phy->init_data->pcie_regs; + vals = phy->init_data->pcie_vals; + } else if (ins->phy_type == PHY_TYPE_USB3) { + num_regs = phy->init_data->usb_regs; + vals = phy->init_data->usb_vals; + } else { + return; + } + for (i = 0; i < ins->num_lanes; i++) + for (j = 0; j < num_regs ; j++) + writel(vals[j].val, phy->base + + vals[j].off + (i + ins->mlane) * 0x800); +} + +static int cdns_sierra_phy_on(struct phy *gphy) +{ + struct cdns_sierra_inst *ins = phy_get_drvdata(gphy); + + /* Take the PHY lane group out of reset */ + return reset_control_deassert(ins->lnk_rst); +} + +static int cdns_sierra_phy_off(struct phy *gphy) +{ + struct cdns_sierra_inst *ins = phy_get_drvdata(gphy); + + return reset_control_assert(ins->lnk_rst); +} + +static const struct phy_ops ops = { + .power_on = cdns_sierra_phy_on, + .power_off = cdns_sierra_phy_off, + .owner = THIS_MODULE, +}; + +static int cdns_sierra_get_optional(struct cdns_sierra_inst *inst, + struct device_node *child) +{ + if (of_property_read_u32(child, "reg", &inst->mlane)) + return -EINVAL; + + if (of_property_read_u32(child, "cdns,num-lanes", &inst->num_lanes)) + return -EINVAL; + + if (of_property_read_u32(child, "cdns,phy-type", &inst->phy_type)) + return -EINVAL; + + return 0; +} + +static const struct of_device_id cdns_sierra_id_table[]; + +static int cdns_sierra_phy_probe(struct platform_device *pdev) +{ + struct cdns_sierra_phy *sp; + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + const struct of_device_id *match; + struct resource *res; + int i, ret, node = 0; + struct device_node *dn = dev->of_node, *child; + + if (of_get_child_count(dn) == 0) + return -ENODEV; + + sp = devm_kzalloc(dev, sizeof(*sp), GFP_KERNEL); + if (!sp) + return -ENOMEM; + dev_set_drvdata(dev, sp); + sp->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sp->base = devm_ioremap_resource(dev, res); + if (IS_ERR(sp->base)) { + dev_err(dev, "missing \"reg\"\n"); + return PTR_ERR(sp->base); + } + + /* Get init data for this PHY */ + match = of_match_device(cdns_sierra_id_table, dev); + if (!match) + return -EINVAL; + sp->init_data = (struct cdns_sierra_data *)match->data; + + platform_set_drvdata(pdev, sp); + + sp->clk = devm_clk_get(dev, "phy_clk"); + if (IS_ERR(sp->clk)) { + dev_err(dev, "failed to get clock phy_clk\n"); + return PTR_ERR(sp->clk); + } + + sp->phy_rst = devm_reset_control_get(dev, "sierra_reset"); + if (IS_ERR(sp->phy_rst)) { + dev_err(dev, "failed to get reset\n"); + return PTR_ERR(sp->phy_rst); + } + + sp->apb_rst = devm_reset_control_get(dev, "sierra_apb"); + if (IS_ERR(sp->apb_rst)) { + dev_err(dev, "failed to get apb reset\n"); + return PTR_ERR(sp->apb_rst); + } + + ret = clk_prepare_enable(sp->clk); + if (ret) + return ret; + + /* Enable APB */ + reset_control_deassert(sp->apb_rst); + + /* Check that PHY is present */ + if (sp->init_data->id_value != readl(sp->base)) { + ret = -EINVAL; + goto clk_disable; + } + + sp->autoconf = of_property_read_bool(dn, "cdns,autoconf"); + + for_each_available_child_of_node(dn, child) { + struct phy *gphy; + + sp->phys[node].lnk_rst = + of_reset_control_get_exclusive_by_index(child, 0); + + if (IS_ERR(sp->phys[node].lnk_rst)) { + dev_err(dev, "failed to get reset %s\n", + child->full_name); + ret = PTR_ERR(sp->phys[node].lnk_rst); + goto put_child2; + } + + if (!sp->autoconf) { + ret = cdns_sierra_get_optional(&sp->phys[node], child); + if (ret) { + dev_err(dev, "missing property in node %s\n", + child->name); + goto put_child; + } + } + + gphy = devm_phy_create(dev, child, &ops); + + if (IS_ERR(gphy)) { + ret = PTR_ERR(gphy); + goto put_child; + } + sp->phys[node].phy = gphy; + phy_set_drvdata(gphy, &sp->phys[node]); + + /* Initialise the PHY registers, unless auto configured */ + if (!sp->autoconf) + cdns_sierra_phy_init(gphy); + + node++; + } + sp->nsubnodes = node; + + /* If more than one subnode, configure the PHY as multilink */ + if (!sp->autoconf && sp->nsubnodes > 1) + writel(2, sp->base + SIERRA_PHY_PLL_CFG); + + pm_runtime_enable(dev); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + reset_control_deassert(sp->phy_rst); + return PTR_ERR_OR_ZERO(phy_provider); + +put_child: + node++; +put_child2: + for (i = 0; i < node; i++) + reset_control_put(sp->phys[i].lnk_rst); + of_node_put(child); +clk_disable: + clk_disable_unprepare(sp->clk); + reset_control_assert(sp->apb_rst); + return ret; +} + +static int cdns_sierra_phy_remove(struct platform_device *pdev) +{ + struct cdns_sierra_phy *phy = dev_get_drvdata(pdev->dev.parent); + int i; + + reset_control_assert(phy->phy_rst); + reset_control_assert(phy->apb_rst); + pm_runtime_disable(&pdev->dev); + + /* + * The device level resets will be put automatically. + * Need to put the subnode resets here though. + */ + for (i = 0; i < phy->nsubnodes; i++) { + reset_control_assert(phy->phys[i].lnk_rst); + reset_control_put(phy->phys[i].lnk_rst); + } + return 0; +} + +static struct cdns_reg_pairs cdns_usb_regs[] = { + /* + * Write USB configuration parameters to the PHY. + * These values are specific to this specific hardware + * configuration. + */ + {0xFE0A, SIERRA_DET_STANDEC_A}, + {0x000F, SIERRA_DET_STANDEC_B}, + {0x55A5, SIERRA_DET_STANDEC_C}, + {0x69AD, SIERRA_DET_STANDEC_D}, + {0x0241, SIERRA_DET_STANDEC_E}, + {0x0110, SIERRA_PSM_LANECAL}, + {0xCF00, SIERRA_PSM_DIAG}, + {0x001F, SIERRA_PSC_TX_A0}, + {0x0007, SIERRA_PSC_TX_A1}, + {0x0003, SIERRA_PSC_TX_A2}, + {0x0003, SIERRA_PSC_TX_A3}, + {0x0FFF, SIERRA_PSC_RX_A0}, + {0x0003, SIERRA_PSC_RX_A1}, + {0x0003, SIERRA_PSC_RX_A2}, + {0x0001, SIERRA_PSC_RX_A3}, + {0x0001, SIERRA_PLLCTRL_SUBRATE}, + {0x0406, SIERRA_PLLCTRL_GEN_D}, + {0x0000, SIERRA_DRVCTRL_ATTEN}, + {0x823E, SIERRA_CLKPATHCTRL_TMR}, + {0x078F, SIERRA_RX_CREQ_FLTR_A_MODE1}, + {0x078F, SIERRA_RX_CREQ_FLTR_A_MODE0}, + {0x7B3C, SIERRA_CREQ_CCLKDET_MODE01}, + {0x023C, SIERRA_RX_CTLE_MAINTENANCE}, + {0x3232, SIERRA_CREQ_FSMCLK_SEL}, + {0x8452, SIERRA_CTLELUT_CTRL}, + {0x4121, SIERRA_DFE_ECMP_RATESEL}, + {0x4121, SIERRA_DFE_SMP_RATESEL}, + {0x9999, SIERRA_DEQ_VGATUNE_CTRL}, + {0x0330, SIERRA_TMRVAL_MODE0}, + {0x01FF, SIERRA_PICNT_MODE1}, + {0x0009, SIERRA_CPI_OUTBUF_RATESEL}, + {0x000F, SIERRA_LFPSFILT_NS}, + {0x0009, SIERRA_LFPSFILT_RD}, + {0x0001, SIERRA_LFPSFILT_MP}, + {0x8013, SIERRA_SDFILT_H2L_A}, + {0x0400, SIERRA_TMRVAL_MODE1}, +}; + +static struct cdns_reg_pairs cdns_pcie_regs[] = { + /* + * Write PCIe configuration parameters to the PHY. + * These values are specific to this specific hardware + * configuration. + */ + {0x891f, SIERRA_DET_STANDEC_D}, + {0x0053, SIERRA_DET_STANDEC_E}, + {0x0400, SIERRA_TMRVAL_MODE2}, + {0x0200, SIERRA_TMRVAL_MODE3}, +}; + +static const struct cdns_sierra_data cdns_map_sierra = { + SIERRA_MACRO_ID, + ARRAY_SIZE(cdns_pcie_regs), + ARRAY_SIZE(cdns_usb_regs), + cdns_pcie_regs, + cdns_usb_regs +}; + +static const struct of_device_id cdns_sierra_id_table[] = { + { + .compatible = "cdns,sierra-phy-t0", + .data = &cdns_map_sierra, + }, + {} +}; +MODULE_DEVICE_TABLE(of, cdns_sierra_id_table); + +static struct platform_driver cdns_sierra_driver = { + .probe = cdns_sierra_phy_probe, + .remove = cdns_sierra_phy_remove, + .driver = { + .name = "cdns-sierra-phy", + .of_match_table = cdns_sierra_id_table, + }, +}; +module_platform_driver(cdns_sierra_driver); + +MODULE_ALIAS("platform:cdns_sierra"); +MODULE_AUTHOR("Cadence Design Systems"); +MODULE_DESCRIPTION("CDNS sierra phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig new file mode 100644 index 000000000000..f050bd4e97e0 --- /dev/null +++ b/drivers/phy/freescale/Kconfig @@ -0,0 +1,5 @@ +config PHY_FSL_IMX8MQ_USB + tristate "Freescale i.MX8M USB3 PHY" + depends on OF && HAS_IOMEM + select GENERIC_PHY + default SOC_IMX8MQ diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile new file mode 100644 index 000000000000..dc2b3f1f2f80 --- /dev/null +++ b/drivers/phy/freescale/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PHY_FSL_IMX8MQ_USB) += phy-fsl-imx8mq-usb.o diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c new file mode 100644 index 000000000000..d6ea5ce8afa5 --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (c) 2017 NXP. */ + +#include +#include +#include +#include +#include + +#define PHY_CTRL0 0x0 +#define PHY_CTRL0_REF_SSP_EN BIT(2) + +#define PHY_CTRL1 0x4 +#define PHY_CTRL1_RESET BIT(0) +#define PHY_CTRL1_COMMONONN BIT(1) +#define PHY_CTRL1_ATERESET BIT(3) +#define PHY_CTRL1_VDATSRCENB0 BIT(19) +#define PHY_CTRL1_VDATDETENB0 BIT(20) + +#define PHY_CTRL2 0x8 +#define PHY_CTRL2_TXENABLEN0 BIT(8) + +struct imx8mq_usb_phy { + struct phy *phy; + struct clk *clk; + void __iomem *base; +}; + +static int imx8mq_usb_phy_init(struct phy *phy) +{ + struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); + u32 value; + + value = readl(imx_phy->base + PHY_CTRL1); + value &= ~(PHY_CTRL1_VDATSRCENB0 | PHY_CTRL1_VDATDETENB0 | + PHY_CTRL1_COMMONONN); + value |= PHY_CTRL1_RESET | PHY_CTRL1_ATERESET; + writel(value, imx_phy->base + PHY_CTRL1); + + value = readl(imx_phy->base + PHY_CTRL0); + value |= PHY_CTRL0_REF_SSP_EN; + writel(value, imx_phy->base + PHY_CTRL0); + + value = readl(imx_phy->base + PHY_CTRL2); + value |= PHY_CTRL2_TXENABLEN0; + writel(value, imx_phy->base + PHY_CTRL2); + + value = readl(imx_phy->base + PHY_CTRL1); + value &= ~(PHY_CTRL1_RESET | PHY_CTRL1_ATERESET); + writel(value, imx_phy->base + PHY_CTRL1); + + return 0; +} + +static int imx8mq_phy_power_on(struct phy *phy) +{ + struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); + + return clk_prepare_enable(imx_phy->clk); +} + +static int imx8mq_phy_power_off(struct phy *phy) +{ + struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); + + clk_disable_unprepare(imx_phy->clk); + + return 0; +} + +static struct phy_ops imx8mq_usb_phy_ops = { + .init = imx8mq_usb_phy_init, + .power_on = imx8mq_phy_power_on, + .power_off = imx8mq_phy_power_off, + .owner = THIS_MODULE, +}; + +static int imx8mq_usb_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct imx8mq_usb_phy *imx_phy; + struct resource *res; + + imx_phy = devm_kzalloc(dev, sizeof(*imx_phy), GFP_KERNEL); + if (!imx_phy) + return -ENOMEM; + + imx_phy->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(imx_phy->clk)) { + dev_err(dev, "failed to get imx8mq usb phy clock\n"); + return PTR_ERR(imx_phy->clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + imx_phy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(imx_phy->base)) + return PTR_ERR(imx_phy->base); + + imx_phy->phy = devm_phy_create(dev, NULL, &imx8mq_usb_phy_ops); + if (IS_ERR(imx_phy->phy)) + return PTR_ERR(imx_phy->phy); + + phy_set_drvdata(imx_phy->phy, imx_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id imx8mq_usb_phy_of_match[] = { + {.compatible = "fsl,imx8mq-usb-phy",}, + { }, +}; +MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match); + +static struct platform_driver imx8mq_usb_phy_driver = { + .probe = imx8mq_usb_phy_probe, + .driver = { + .name = "imx8mq-usb-phy", + .of_match_table = imx8mq_usb_phy_of_match, + } +}; +module_platform_driver(imx8mq_usb_phy_driver); + +MODULE_DESCRIPTION("FSL IMX8MQ USB PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/marvell/phy-mvebu-cp110-comphy.c b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c index 86a5f7b9448b..187cccde53b5 100644 --- a/drivers/phy/marvell/phy-mvebu-cp110-comphy.c +++ b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -114,43 +115,45 @@ #define MVEBU_COMPHY_LANES 6 #define MVEBU_COMPHY_PORTS 3 -struct mvebu_comhy_conf { +struct mvebu_comphy_conf { enum phy_mode mode; + int submode; unsigned lane; unsigned port; u32 mux; }; -#define MVEBU_COMPHY_CONF(_lane, _port, _mode, _mux) \ +#define MVEBU_COMPHY_CONF(_lane, _port, _submode, _mux) \ { \ .lane = _lane, \ .port = _port, \ - .mode = _mode, \ + .mode = PHY_MODE_ETHERNET, \ + .submode = _submode, \ .mux = _mux, \ } -static const struct mvebu_comhy_conf mvebu_comphy_cp110_modes[] = { +static const struct mvebu_comphy_conf mvebu_comphy_cp110_modes[] = { /* lane 0 */ - MVEBU_COMPHY_CONF(0, 1, PHY_MODE_SGMII, 0x1), - MVEBU_COMPHY_CONF(0, 1, PHY_MODE_2500SGMII, 0x1), + MVEBU_COMPHY_CONF(0, 1, PHY_INTERFACE_MODE_SGMII, 0x1), + MVEBU_COMPHY_CONF(0, 1, PHY_INTERFACE_MODE_2500BASEX, 0x1), /* lane 1 */ - MVEBU_COMPHY_CONF(1, 2, PHY_MODE_SGMII, 0x1), - MVEBU_COMPHY_CONF(1, 2, PHY_MODE_2500SGMII, 0x1), + MVEBU_COMPHY_CONF(1, 2, PHY_INTERFACE_MODE_SGMII, 0x1), + MVEBU_COMPHY_CONF(1, 2, PHY_INTERFACE_MODE_2500BASEX, 0x1), /* lane 2 */ - MVEBU_COMPHY_CONF(2, 0, PHY_MODE_SGMII, 0x1), - MVEBU_COMPHY_CONF(2, 0, PHY_MODE_2500SGMII, 0x1), - MVEBU_COMPHY_CONF(2, 0, PHY_MODE_10GKR, 0x1), + MVEBU_COMPHY_CONF(2, 0, PHY_INTERFACE_MODE_SGMII, 0x1), + MVEBU_COMPHY_CONF(2, 0, PHY_INTERFACE_MODE_2500BASEX, 0x1), + MVEBU_COMPHY_CONF(2, 0, PHY_INTERFACE_MODE_10GKR, 0x1), /* lane 3 */ - MVEBU_COMPHY_CONF(3, 1, PHY_MODE_SGMII, 0x2), - MVEBU_COMPHY_CONF(3, 1, PHY_MODE_2500SGMII, 0x2), + MVEBU_COMPHY_CONF(3, 1, PHY_INTERFACE_MODE_SGMII, 0x2), + MVEBU_COMPHY_CONF(3, 1, PHY_INTERFACE_MODE_2500BASEX, 0x2), /* lane 4 */ - MVEBU_COMPHY_CONF(4, 0, PHY_MODE_SGMII, 0x2), - MVEBU_COMPHY_CONF(4, 0, PHY_MODE_2500SGMII, 0x2), - MVEBU_COMPHY_CONF(4, 0, PHY_MODE_10GKR, 0x2), - MVEBU_COMPHY_CONF(4, 1, PHY_MODE_SGMII, 0x1), + MVEBU_COMPHY_CONF(4, 0, PHY_INTERFACE_MODE_SGMII, 0x2), + MVEBU_COMPHY_CONF(4, 0, PHY_INTERFACE_MODE_2500BASEX, 0x2), + MVEBU_COMPHY_CONF(4, 0, PHY_INTERFACE_MODE_10GKR, 0x2), + MVEBU_COMPHY_CONF(4, 1, PHY_INTERFACE_MODE_SGMII, 0x1), /* lane 5 */ - MVEBU_COMPHY_CONF(5, 2, PHY_MODE_SGMII, 0x1), - MVEBU_COMPHY_CONF(5, 2, PHY_MODE_2500SGMII, 0x1), + MVEBU_COMPHY_CONF(5, 2, PHY_INTERFACE_MODE_SGMII, 0x1), + MVEBU_COMPHY_CONF(5, 2, PHY_INTERFACE_MODE_2500BASEX, 0x1), }; struct mvebu_comphy_priv { @@ -163,10 +166,12 @@ struct mvebu_comphy_lane { struct mvebu_comphy_priv *priv; unsigned id; enum phy_mode mode; + int submode; int port; }; -static int mvebu_comphy_get_mux(int lane, int port, enum phy_mode mode) +static int mvebu_comphy_get_mux(int lane, int port, + enum phy_mode mode, int submode) { int i, n = ARRAY_SIZE(mvebu_comphy_cp110_modes); @@ -177,7 +182,8 @@ static int mvebu_comphy_get_mux(int lane, int port, enum phy_mode mode) for (i = 0; i < n; i++) { if (mvebu_comphy_cp110_modes[i].lane == lane && mvebu_comphy_cp110_modes[i].port == port && - mvebu_comphy_cp110_modes[i].mode == mode) + mvebu_comphy_cp110_modes[i].mode == mode && + mvebu_comphy_cp110_modes[i].submode == submode) break; } @@ -187,8 +193,7 @@ static int mvebu_comphy_get_mux(int lane, int port, enum phy_mode mode) return mvebu_comphy_cp110_modes[i].mux; } -static void mvebu_comphy_ethernet_init_reset(struct mvebu_comphy_lane *lane, - enum phy_mode mode) +static void mvebu_comphy_ethernet_init_reset(struct mvebu_comphy_lane *lane) { struct mvebu_comphy_priv *priv = lane->priv; u32 val; @@ -206,14 +211,14 @@ static void mvebu_comphy_ethernet_init_reset(struct mvebu_comphy_lane *lane, MVEBU_COMPHY_SERDES_CFG0_HALF_BUS | MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0xf) | MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0xf)); - if (mode == PHY_MODE_10GKR) + if (lane->submode == PHY_INTERFACE_MODE_10GKR) val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0xe) | MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0xe); - else if (mode == PHY_MODE_2500SGMII) + else if (lane->submode == PHY_INTERFACE_MODE_2500BASEX) val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0x8) | MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0x8) | MVEBU_COMPHY_SERDES_CFG0_HALF_BUS; - else if (mode == PHY_MODE_SGMII) + else if (lane->submode == PHY_INTERFACE_MODE_SGMII) val |= MVEBU_COMPHY_SERDES_CFG0_GEN_RX(0x6) | MVEBU_COMPHY_SERDES_CFG0_GEN_TX(0x6) | MVEBU_COMPHY_SERDES_CFG0_HALF_BUS; @@ -243,7 +248,7 @@ static void mvebu_comphy_ethernet_init_reset(struct mvebu_comphy_lane *lane, /* refclk selection */ val = readl(priv->base + MVEBU_COMPHY_MISC_CTRL0(lane->id)); val &= ~MVEBU_COMPHY_MISC_CTRL0_REFCLK_SEL; - if (mode == PHY_MODE_10GKR) + if (lane->submode == PHY_INTERFACE_MODE_10GKR) val |= MVEBU_COMPHY_MISC_CTRL0_ICP_FORCE; writel(val, priv->base + MVEBU_COMPHY_MISC_CTRL0(lane->id)); @@ -261,8 +266,7 @@ static void mvebu_comphy_ethernet_init_reset(struct mvebu_comphy_lane *lane, writel(val, priv->base + MVEBU_COMPHY_LOOPBACK(lane->id)); } -static int mvebu_comphy_init_plls(struct mvebu_comphy_lane *lane, - enum phy_mode mode) +static int mvebu_comphy_init_plls(struct mvebu_comphy_lane *lane) { struct mvebu_comphy_priv *priv = lane->priv; u32 val; @@ -303,13 +307,13 @@ static int mvebu_comphy_init_plls(struct mvebu_comphy_lane *lane, return 0; } -static int mvebu_comphy_set_mode_sgmii(struct phy *phy, enum phy_mode mode) +static int mvebu_comphy_set_mode_sgmii(struct phy *phy) { struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); struct mvebu_comphy_priv *priv = lane->priv; u32 val; - mvebu_comphy_ethernet_init_reset(lane, mode); + mvebu_comphy_ethernet_init_reset(lane); val = readl(priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id)); val &= ~MVEBU_COMPHY_RX_CTRL1_CLK8T_EN; @@ -330,7 +334,7 @@ static int mvebu_comphy_set_mode_sgmii(struct phy *phy, enum phy_mode mode) val |= MVEBU_COMPHY_GEN1_S0_TX_EMPH(0x1); writel(val, priv->base + MVEBU_COMPHY_GEN1_S0(lane->id)); - return mvebu_comphy_init_plls(lane, PHY_MODE_SGMII); + return mvebu_comphy_init_plls(lane); } static int mvebu_comphy_set_mode_10gkr(struct phy *phy) @@ -339,7 +343,7 @@ static int mvebu_comphy_set_mode_10gkr(struct phy *phy) struct mvebu_comphy_priv *priv = lane->priv; u32 val; - mvebu_comphy_ethernet_init_reset(lane, PHY_MODE_10GKR); + mvebu_comphy_ethernet_init_reset(lane); val = readl(priv->base + MVEBU_COMPHY_RX_CTRL1(lane->id)); val |= MVEBU_COMPHY_RX_CTRL1_RXCLK2X_SEL | @@ -469,7 +473,7 @@ static int mvebu_comphy_set_mode_10gkr(struct phy *phy) val |= MVEBU_COMPHY_EXT_SELV_RX_SAMPL(0x1a); writel(val, priv->base + MVEBU_COMPHY_EXT_SELV(lane->id)); - return mvebu_comphy_init_plls(lane, PHY_MODE_10GKR); + return mvebu_comphy_init_plls(lane); } static int mvebu_comphy_power_on(struct phy *phy) @@ -479,7 +483,8 @@ static int mvebu_comphy_power_on(struct phy *phy) int ret, mux; u32 val; - mux = mvebu_comphy_get_mux(lane->id, lane->port, lane->mode); + mux = mvebu_comphy_get_mux(lane->id, lane->port, + lane->mode, lane->submode); if (mux < 0) return -ENOTSUPP; @@ -492,12 +497,12 @@ static int mvebu_comphy_power_on(struct phy *phy) val |= mux << MVEBU_COMPHY_SELECTOR_PHY(lane->id); regmap_write(priv->regmap, MVEBU_COMPHY_SELECTOR, val); - switch (lane->mode) { - case PHY_MODE_SGMII: - case PHY_MODE_2500SGMII: - ret = mvebu_comphy_set_mode_sgmii(phy, lane->mode); + switch (lane->submode) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_2500BASEX: + ret = mvebu_comphy_set_mode_sgmii(phy); break; - case PHY_MODE_10GKR: + case PHY_INTERFACE_MODE_10GKR: ret = mvebu_comphy_set_mode_10gkr(phy); break; default: @@ -512,14 +517,22 @@ static int mvebu_comphy_power_on(struct phy *phy) return ret; } -static int mvebu_comphy_set_mode(struct phy *phy, enum phy_mode mode) +static int mvebu_comphy_set_mode(struct phy *phy, + enum phy_mode mode, int submode) { struct mvebu_comphy_lane *lane = phy_get_drvdata(phy); - if (mvebu_comphy_get_mux(lane->id, lane->port, mode) < 0) + if (mode != PHY_MODE_ETHERNET) + return -EINVAL; + + if (submode == PHY_INTERFACE_MODE_1000BASEX) + submode = PHY_INTERFACE_MODE_SGMII; + + if (mvebu_comphy_get_mux(lane->id, lane->port, mode, submode) < 0) return -EINVAL; lane->mode = mode; + lane->submode = submode; return 0; } diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c index 3eb8e1bd7b78..5b6a470ca145 100644 --- a/drivers/phy/mediatek/phy-mtk-tphy.c +++ b/drivers/phy/mediatek/phy-mtk-tphy.c @@ -971,7 +971,7 @@ static int mtk_phy_exit(struct phy *phy) return 0; } -static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode) +static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) { struct mtk_phy_instance *instance = phy_get_drvdata(phy); struct mtk_tphy *tphy = dev_get_drvdata(phy->dev.parent); diff --git a/drivers/phy/mediatek/phy-mtk-xsphy.c b/drivers/phy/mediatek/phy-mtk-xsphy.c index 020cd0227397..8c51131945c0 100644 --- a/drivers/phy/mediatek/phy-mtk-xsphy.c +++ b/drivers/phy/mediatek/phy-mtk-xsphy.c @@ -426,7 +426,7 @@ static int mtk_phy_exit(struct phy *phy) return 0; } -static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode) +static int mtk_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) { struct xsphy_instance *inst = phy_get_drvdata(phy); struct mtk_xsphy *xsphy = dev_get_drvdata(phy->dev.parent); diff --git a/drivers/phy/motorola/phy-mapphone-mdm6600.c b/drivers/phy/motorola/phy-mapphone-mdm6600.c index 25d456a323c2..ee184d5607bd 100644 --- a/drivers/phy/motorola/phy-mapphone-mdm6600.c +++ b/drivers/phy/motorola/phy-mapphone-mdm6600.c @@ -16,6 +16,7 @@ #include #include #include +#include #define PHY_MDM6600_PHY_DELAY_MS 4000 /* PHY enable 2.2s to 3.5s */ #define PHY_MDM6600_ENABLED_DELAY_MS 8000 /* 8s more total for MDM6600 */ @@ -120,12 +121,22 @@ static int phy_mdm6600_power_on(struct phy *x) { struct phy_mdm6600 *ddata = phy_get_drvdata(x); struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; + int error; if (!ddata->enabled) return -ENODEV; + error = pinctrl_pm_select_default_state(ddata->dev); + if (error) + dev_warn(ddata->dev, "%s: error with default_state: %i\n", + __func__, error); + gpiod_set_value_cansleep(enable_gpio, 1); + /* Allow aggressive PM for USB, it's only needed for n_gsm port */ + if (pm_runtime_enabled(&x->dev)) + phy_pm_runtime_put(x); + return 0; } @@ -133,12 +144,26 @@ static int phy_mdm6600_power_off(struct phy *x) { struct phy_mdm6600 *ddata = phy_get_drvdata(x); struct gpio_desc *enable_gpio = ddata->ctrl_gpios[PHY_MDM6600_ENABLE]; + int error; if (!ddata->enabled) return -ENODEV; + /* Paired with phy_pm_runtime_put() in phy_mdm6600_power_on() */ + if (pm_runtime_enabled(&x->dev)) { + error = phy_pm_runtime_get(x); + if (error < 0 && error != -EINPROGRESS) + dev_warn(ddata->dev, "%s: phy_pm_runtime_get: %i\n", + __func__, error); + } + gpiod_set_value_cansleep(enable_gpio, 0); + error = pinctrl_pm_select_sleep_state(ddata->dev); + if (error) + dev_warn(ddata->dev, "%s: error with sleep_state: %i\n", + __func__, error); + return 0; } @@ -529,28 +554,17 @@ static int phy_mdm6600_probe(struct platform_device *pdev) ddata->dev = &pdev->dev; platform_set_drvdata(pdev, ddata); + /* Active state selected in phy_mdm6600_power_on() */ + error = pinctrl_pm_select_sleep_state(ddata->dev); + if (error) + dev_warn(ddata->dev, "%s: error with sleep_state: %i\n", + __func__, error); + error = phy_mdm6600_init_lines(ddata); if (error) return error; phy_mdm6600_init_irq(ddata); - - ddata->generic_phy = devm_phy_create(ddata->dev, NULL, &gpio_usb_ops); - if (IS_ERR(ddata->generic_phy)) { - error = PTR_ERR(ddata->generic_phy); - goto cleanup; - } - - phy_set_drvdata(ddata->generic_phy, ddata); - - ddata->phy_provider = - devm_of_phy_provider_register(ddata->dev, - of_phy_simple_xlate); - if (IS_ERR(ddata->phy_provider)) { - error = PTR_ERR(ddata->phy_provider); - goto cleanup; - } - schedule_delayed_work(&ddata->bootup_work, 0); /* @@ -574,14 +588,31 @@ static int phy_mdm6600_probe(struct platform_device *pdev) if (error < 0) { dev_warn(ddata->dev, "failed to wake modem: %i\n", error); pm_runtime_put_noidle(ddata->dev); + goto cleanup; } + + ddata->generic_phy = devm_phy_create(ddata->dev, NULL, &gpio_usb_ops); + if (IS_ERR(ddata->generic_phy)) { + error = PTR_ERR(ddata->generic_phy); + goto idle; + } + + phy_set_drvdata(ddata->generic_phy, ddata); + + ddata->phy_provider = + devm_of_phy_provider_register(ddata->dev, + of_phy_simple_xlate); + if (IS_ERR(ddata->phy_provider)) + error = PTR_ERR(ddata->phy_provider); + +idle: pm_runtime_mark_last_busy(ddata->dev); pm_runtime_put_autosuspend(ddata->dev); - return 0; - cleanup: - phy_mdm6600_device_power_off(ddata); + if (error < 0) + phy_mdm6600_device_power_off(ddata); + return error; } diff --git a/drivers/phy/mscc/phy-ocelot-serdes.c b/drivers/phy/mscc/phy-ocelot-serdes.c index cbb49d9da6f9..77c46f639fbf 100644 --- a/drivers/phy/mscc/phy-ocelot-serdes.c +++ b/drivers/phy/mscc/phy-ocelot-serdes.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -104,20 +105,24 @@ struct serdes_mux { u8 idx; u8 port; enum phy_mode mode; + int submode; u32 mask; u32 mux; }; -#define SERDES_MUX(_idx, _port, _mode, _mask, _mux) { \ +#define SERDES_MUX(_idx, _port, _mode, _submode, _mask, _mux) { \ .idx = _idx, \ .port = _port, \ .mode = _mode, \ + .submode = _submode, \ .mask = _mask, \ .mux = _mux, \ } -#define SERDES_MUX_SGMII(i, p, m, c) SERDES_MUX(i, p, PHY_MODE_SGMII, m, c) -#define SERDES_MUX_QSGMII(i, p, m, c) SERDES_MUX(i, p, PHY_MODE_QSGMII, m, c) +#define SERDES_MUX_SGMII(i, p, m, c) \ + SERDES_MUX(i, p, PHY_MODE_ETHERNET, PHY_INTERFACE_MODE_SGMII, m, c) +#define SERDES_MUX_QSGMII(i, p, m, c) \ + SERDES_MUX(i, p, PHY_MODE_ETHERNET, PHY_INTERFACE_MODE_QSGMII, m, c) static const struct serdes_mux ocelot_serdes_muxes[] = { SERDES_MUX_SGMII(SERDES1G(0), 0, 0, 0), @@ -154,22 +159,27 @@ static const struct serdes_mux ocelot_serdes_muxes[] = { SERDES_MUX_SGMII(SERDES6G(1), 8, 0, 0), SERDES_MUX_SGMII(SERDES6G(2), 10, HSIO_HW_CFG_PCIE_ENA | HSIO_HW_CFG_DEV2G5_10_MODE, 0), - SERDES_MUX(SERDES6G(2), 10, PHY_MODE_PCIE, HSIO_HW_CFG_PCIE_ENA, + SERDES_MUX(SERDES6G(2), 10, PHY_MODE_PCIE, 0, HSIO_HW_CFG_PCIE_ENA, HSIO_HW_CFG_PCIE_ENA), }; -static int serdes_set_mode(struct phy *phy, enum phy_mode mode) +static int serdes_set_mode(struct phy *phy, enum phy_mode mode, int submode) { struct serdes_macro *macro = phy_get_drvdata(phy); unsigned int i; int ret; + /* As of now only PHY_MODE_ETHERNET is supported */ + if (mode != PHY_MODE_ETHERNET) + return -EOPNOTSUPP; + for (i = 0; i < ARRAY_SIZE(ocelot_serdes_muxes); i++) { if (macro->idx != ocelot_serdes_muxes[i].idx || - mode != ocelot_serdes_muxes[i].mode) + mode != ocelot_serdes_muxes[i].mode || + submode != ocelot_serdes_muxes[i].submode) continue; - if (mode != PHY_MODE_QSGMII && + if (submode != PHY_INTERFACE_MODE_QSGMII && macro->port != ocelot_serdes_muxes[i].port) continue; diff --git a/drivers/phy/phy-core-mipi-dphy.c b/drivers/phy/phy-core-mipi-dphy.c new file mode 100644 index 000000000000..465fa1b91a5f --- /dev/null +++ b/drivers/phy/phy-core-mipi-dphy.c @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2013 NVIDIA Corporation + * Copyright (C) 2018 Cadence Design Systems Inc. + */ + +#include +#include +#include +#include + +#include +#include + +#define PSEC_PER_SEC 1000000000000LL + +/* + * Minimum D-PHY timings based on MIPI D-PHY specification. Derived + * from the valid ranges specified in Section 6.9, Table 14, Page 41 + * of the D-PHY specification (v2.1). + */ +int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, + unsigned int bpp, + unsigned int lanes, + struct phy_configure_opts_mipi_dphy *cfg) +{ + unsigned long long hs_clk_rate; + unsigned long long ui; + + if (!cfg) + return -EINVAL; + + hs_clk_rate = pixel_clock * bpp; + do_div(hs_clk_rate, lanes); + + ui = ALIGN(PSEC_PER_SEC, hs_clk_rate); + do_div(ui, hs_clk_rate); + + cfg->clk_miss = 0; + cfg->clk_post = 60000 + 52 * ui; + cfg->clk_pre = 8000; + cfg->clk_prepare = 38000; + cfg->clk_settle = 95000; + cfg->clk_term_en = 0; + cfg->clk_trail = 60000; + cfg->clk_zero = 262000; + cfg->d_term_en = 0; + cfg->eot = 0; + cfg->hs_exit = 100000; + cfg->hs_prepare = 40000 + 4 * ui; + cfg->hs_zero = 105000 + 6 * ui; + cfg->hs_settle = 85000 + 6 * ui; + cfg->hs_skip = 40000; + + /* + * The MIPI D-PHY specification (Section 6.9, v1.2, Table 14, Page 40) + * contains this formula as: + * + * T_HS-TRAIL = max(n * 8 * ui, 60 + n * 4 * ui) + * + * where n = 1 for forward-direction HS mode and n = 4 for reverse- + * direction HS mode. There's only one setting and this function does + * not parameterize on anything other that ui, so this code will + * assumes that reverse-direction HS mode is supported and uses n = 4. + */ + cfg->hs_trail = max(4 * 8 * ui, 60000 + 4 * 4 * ui); + + cfg->init = 100000000; + cfg->lpx = 60000; + cfg->ta_get = 5 * cfg->lpx; + cfg->ta_go = 4 * cfg->lpx; + cfg->ta_sure = 2 * cfg->lpx; + cfg->wakeup = 1000000000; + + cfg->hs_clk_rate = hs_clk_rate; + cfg->lanes = lanes; + + return 0; +} +EXPORT_SYMBOL(phy_mipi_dphy_get_default_config); + +/* + * Validate D-PHY configuration according to MIPI D-PHY specification + * (v1.2, Section Section 6.9 "Global Operation Timing Parameters"). + */ +int phy_mipi_dphy_config_validate(struct phy_configure_opts_mipi_dphy *cfg) +{ + unsigned long long ui; + + if (!cfg) + return -EINVAL; + + ui = ALIGN(PSEC_PER_SEC, cfg->hs_clk_rate); + do_div(ui, cfg->hs_clk_rate); + + if (cfg->clk_miss > 60000) + return -EINVAL; + + if (cfg->clk_post < (60000 + 52 * ui)) + return -EINVAL; + + if (cfg->clk_pre < 8000) + return -EINVAL; + + if (cfg->clk_prepare < 38000 || cfg->clk_prepare > 95000) + return -EINVAL; + + if (cfg->clk_settle < 95000 || cfg->clk_settle > 300000) + return -EINVAL; + + if (cfg->clk_term_en > 38000) + return -EINVAL; + + if (cfg->clk_trail < 60000) + return -EINVAL; + + if ((cfg->clk_prepare + cfg->clk_zero) < 300000) + return -EINVAL; + + if (cfg->d_term_en > (35000 + 4 * ui)) + return -EINVAL; + + if (cfg->eot > (105000 + 12 * ui)) + return -EINVAL; + + if (cfg->hs_exit < 100000) + return -EINVAL; + + if (cfg->hs_prepare < (40000 + 4 * ui) || + cfg->hs_prepare > (85000 + 6 * ui)) + return -EINVAL; + + if ((cfg->hs_prepare + cfg->hs_zero) < (145000 + 10 * ui)) + return -EINVAL; + + if ((cfg->hs_settle < (85000 + 6 * ui)) || + (cfg->hs_settle > (145000 + 10 * ui))) + return -EINVAL; + + if (cfg->hs_skip < 40000 || cfg->hs_skip > (55000 + 4 * ui)) + return -EINVAL; + + if (cfg->hs_trail < max(8 * ui, 60000 + 4 * ui)) + return -EINVAL; + + if (cfg->init < 100000000) + return -EINVAL; + + if (cfg->lpx < 50000) + return -EINVAL; + + if (cfg->ta_get != (5 * cfg->lpx)) + return -EINVAL; + + if (cfg->ta_go != (4 * cfg->lpx)) + return -EINVAL; + + if (cfg->ta_sure < cfg->lpx || cfg->ta_sure > (2 * cfg->lpx)) + return -EINVAL; + + if (cfg->wakeup < 1000000000) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL(phy_mipi_dphy_config_validate); diff --git a/drivers/phy/phy-core.c b/drivers/phy/phy-core.c index 35fd38c5a4a1..19b05e824ee4 100644 --- a/drivers/phy/phy-core.c +++ b/drivers/phy/phy-core.c @@ -360,7 +360,7 @@ int phy_power_off(struct phy *phy) } EXPORT_SYMBOL_GPL(phy_power_off); -int phy_set_mode(struct phy *phy, enum phy_mode mode) +int phy_set_mode_ext(struct phy *phy, enum phy_mode mode, int submode) { int ret; @@ -368,14 +368,14 @@ int phy_set_mode(struct phy *phy, enum phy_mode mode) return 0; mutex_lock(&phy->mutex); - ret = phy->ops->set_mode(phy, mode); + ret = phy->ops->set_mode(phy, mode, submode); if (!ret) phy->attrs.mode = mode; mutex_unlock(&phy->mutex); return ret; } -EXPORT_SYMBOL_GPL(phy_set_mode); +EXPORT_SYMBOL_GPL(phy_set_mode_ext); int phy_reset(struct phy *phy) { @@ -407,6 +407,70 @@ int phy_calibrate(struct phy *phy) } EXPORT_SYMBOL_GPL(phy_calibrate); +/** + * phy_configure() - Changes the phy parameters + * @phy: the phy returned by phy_get() + * @opts: New configuration to apply + * + * Used to change the PHY parameters. phy_init() must have been called + * on the phy. The configuration will be applied on the current phy + * mode, that can be changed using phy_set_mode(). + * + * Returns: 0 if successful, an negative error code otherwise + */ +int phy_configure(struct phy *phy, union phy_configure_opts *opts) +{ + int ret; + + if (!phy) + return -EINVAL; + + if (!phy->ops->configure) + return -EOPNOTSUPP; + + mutex_lock(&phy->mutex); + ret = phy->ops->configure(phy, opts); + mutex_unlock(&phy->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phy_configure); + +/** + * phy_validate() - Checks the phy parameters + * @phy: the phy returned by phy_get() + * @mode: phy_mode the configuration is applicable to. + * @submode: PHY submode the configuration is applicable to. + * @opts: Configuration to check + * + * Used to check that the current set of parameters can be handled by + * the phy. Implementations are free to tune the parameters passed as + * arguments if needed by some implementation detail or + * constraints. It will not change any actual configuration of the + * PHY, so calling it as many times as deemed fit will have no side + * effect. + * + * Returns: 0 if successful, an negative error code otherwise + */ +int phy_validate(struct phy *phy, enum phy_mode mode, int submode, + union phy_configure_opts *opts) +{ + int ret; + + if (!phy) + return -EINVAL; + + if (!phy->ops->validate) + return -EOPNOTSUPP; + + mutex_lock(&phy->mutex); + ret = phy->ops->validate(phy, mode, submode, opts); + mutex_unlock(&phy->mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phy_validate); + /** * _of_phy_get() - lookup and obtain a reference to a phy by phandle * @np: device_node for which to get the phy diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c index a83332411026..b4006818e1b6 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -72,6 +72,9 @@ #define MAX_PROP_NAME 32 +/* Define the assumed distance between lanes for underspecified device trees. */ +#define QMP_PHY_LEGACY_LANE_STRIDE 0x400 + struct qmp_phy_init_tbl { unsigned int offset; unsigned int val; @@ -733,9 +736,6 @@ struct qmp_phy_cfg { bool has_phy_dp_com_ctrl; /* true, if PHY has secondary tx/rx lanes to be configured */ bool is_dual_lane_phy; - /* Register offset of secondary tx/rx lanes for USB DP combo PHY */ - unsigned int tx_b_lane_offset; - unsigned int rx_b_lane_offset; /* true, if PCS block has no separate SW_RESET register */ bool no_pcs_sw_reset; @@ -748,6 +748,8 @@ struct qmp_phy_cfg { * @tx: iomapped memory space for lane's tx * @rx: iomapped memory space for lane's rx * @pcs: iomapped memory space for lane's pcs + * @tx2: iomapped memory space for second lane's tx (in dual lane PHYs) + * @rx2: iomapped memory space for second lane's rx (in dual lane PHYs) * @pcs_misc: iomapped memory space for lane's pcs_misc * @pipe_clk: pipe lock * @index: lane index @@ -759,6 +761,8 @@ struct qmp_phy { void __iomem *tx; void __iomem *rx; void __iomem *pcs; + void __iomem *tx2; + void __iomem *rx2; void __iomem *pcs_misc; struct clk *pipe_clk; unsigned int index; @@ -975,8 +979,6 @@ static const struct qmp_phy_cfg qmp_v3_usb3phy_cfg = { .has_phy_dp_com_ctrl = true, .is_dual_lane_phy = true, - .tx_b_lane_offset = 0x400, - .rx_b_lane_offset = 0x400, }; static const struct qmp_phy_cfg qmp_v3_usb3_uniphy_cfg = { @@ -1031,9 +1033,6 @@ static const struct qmp_phy_cfg sdm845_ufsphy_cfg = { .mask_pcs_ready = PCS_READY, .is_dual_lane_phy = true, - .tx_b_lane_offset = 0x400, - .rx_b_lane_offset = 0x400, - .no_pcs_sw_reset = true, }; @@ -1238,12 +1237,12 @@ static int qcom_qmp_phy_init(struct phy *phy) qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num); /* Configuration for other LANE for USB-DP combo PHY */ if (cfg->is_dual_lane_phy) - qcom_qmp_phy_configure(tx + cfg->tx_b_lane_offset, cfg->regs, + qcom_qmp_phy_configure(qphy->tx2, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num); qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num); if (cfg->is_dual_lane_phy) - qcom_qmp_phy_configure(rx + cfg->rx_b_lane_offset, cfg->regs, + qcom_qmp_phy_configure(qphy->rx2, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num); qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num); @@ -1365,7 +1364,8 @@ static int qcom_qmp_phy_poweron(struct phy *phy) return ret; } -static int qcom_qmp_phy_set_mode(struct phy *phy, enum phy_mode mode) +static int qcom_qmp_phy_set_mode(struct phy *phy, + enum phy_mode mode, int submode) { struct qmp_phy *qphy = phy_get_drvdata(phy); struct qcom_qmp *qmp = qphy->qmp; @@ -1542,6 +1542,11 @@ static int qcom_qmp_phy_clk_init(struct device *dev) return devm_clk_bulk_get(dev, num, qmp->clks); } +static void phy_pipe_clk_release_provider(void *res) +{ + of_clk_del_provider(res); +} + /* * Register a fixed rate pipe clock. * @@ -1588,7 +1593,23 @@ static int phy_pipe_clk_register(struct qcom_qmp *qmp, struct device_node *np) fixed->fixed_rate = 125000000; fixed->hw.init = &init; - return devm_clk_hw_register(qmp->dev, &fixed->hw); + ret = devm_clk_hw_register(qmp->dev, &fixed->hw); + if (ret) + return ret; + + ret = of_clk_add_hw_provider(np, of_clk_hw_simple_get, &fixed->hw); + if (ret) + return ret; + + /* + * Roll a devm action because the clock provider is the child node, but + * the child node is not actually a device. + */ + ret = devm_add_action(qmp->dev, phy_pipe_clk_release_provider, np); + if (ret) + phy_pipe_clk_release_provider(np); + + return ret; } static const struct phy_ops qcom_qmp_phy_gen_ops = { @@ -1614,8 +1635,9 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) /* * Get memory resources for each phy lane: - * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2; and - * pcs_misc (optional) -> 3. + * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2. + * For dual lane PHYs: tx2 -> 3, rx2 -> 4, pcs_misc (optional) -> 5 + * For single lane PHYs: pcs_misc (optional) -> 3. */ qphy->tx = of_iomap(np, 0); if (!qphy->tx) @@ -1629,7 +1651,32 @@ int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) if (!qphy->pcs) return -ENOMEM; - qphy->pcs_misc = of_iomap(np, 3); + /* + * If this is a dual-lane PHY, then there should be registers for the + * second lane. Some old device trees did not specify this, so fall + * back to old legacy behavior of assuming they can be reached at an + * offset from the first lane. + */ + if (qmp->cfg->is_dual_lane_phy) { + qphy->tx2 = of_iomap(np, 3); + qphy->rx2 = of_iomap(np, 4); + if (!qphy->tx2 || !qphy->rx2) { + dev_warn(dev, + "Underspecified device tree, falling back to legacy register regions\n"); + + /* In the old version, pcs_misc is at index 3. */ + qphy->pcs_misc = qphy->tx2; + qphy->tx2 = qphy->tx + QMP_PHY_LEGACY_LANE_STRIDE; + qphy->rx2 = qphy->rx + QMP_PHY_LEGACY_LANE_STRIDE; + + } else { + qphy->pcs_misc = of_iomap(np, 5); + } + + } else { + qphy->pcs_misc = of_iomap(np, 3); + } + if (!qphy->pcs_misc) dev_vdbg(dev, "PHY pcs_misc-reg not used\n"); diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c index 6d4b44b569bc..9177989f22d1 100644 --- a/drivers/phy/qualcomm/phy-qcom-qusb2.c +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c @@ -425,7 +425,8 @@ static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy) HSTX_TRIM_MASK); } -static int qusb2_phy_set_mode(struct phy *phy, enum phy_mode mode) +static int qusb2_phy_set_mode(struct phy *phy, + enum phy_mode mode, int submode) { struct qusb2_phy *qphy = phy_get_drvdata(phy); diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c index ba1895b76a5d..1e0d4f2046a4 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c @@ -65,7 +65,8 @@ static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy) } static -int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy, enum phy_mode mode) +int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy, + enum phy_mode mode, int submode) { struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c index 49f435c71147..aef40f7a41d4 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c @@ -84,7 +84,8 @@ static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy) } static -int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy, enum phy_mode mode) +int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy, + enum phy_mode mode, int submode) { struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); diff --git a/drivers/phy/qualcomm/phy-qcom-usb-hs.c b/drivers/phy/qualcomm/phy-qcom-usb-hs.c index abbbe75070da..04934f8dac91 100644 --- a/drivers/phy/qualcomm/phy-qcom-usb-hs.c +++ b/drivers/phy/qualcomm/phy-qcom-usb-hs.c @@ -42,7 +42,8 @@ struct qcom_usb_hs_phy { struct notifier_block vbus_notify; }; -static int qcom_usb_hs_phy_set_mode(struct phy *phy, enum phy_mode mode) +static int qcom_usb_hs_phy_set_mode(struct phy *phy, + enum phy_mode mode, int submode) { struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); u8 addr; diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c index d0f412c25981..0a34782aaaa2 100644 --- a/drivers/phy/renesas/phy-rcar-gen3-usb2.c +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -307,16 +307,21 @@ static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) void __iomem *usb2_base = ch->base; u32 val; + /* Should not use functions of read-modify-write a register */ + val = readl(usb2_base + USB2_LINECTRL1); + val = (val & ~USB2_LINECTRL1_DP_RPD) | USB2_LINECTRL1_DPRPD_EN | + USB2_LINECTRL1_DMRPD_EN | USB2_LINECTRL1_DM_RPD; + writel(val, usb2_base + USB2_LINECTRL1); + val = readl(usb2_base + USB2_VBCTRL); writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL); - writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); - rcar_gen3_control_otg_irq(ch, 1); val = readl(usb2_base + USB2_ADPCTRL); writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); - val = readl(usb2_base + USB2_LINECTRL1); - rcar_gen3_set_linectrl(ch, 0, 0); - writel(val | USB2_LINECTRL1_DPRPD_EN | USB2_LINECTRL1_DMRPD_EN, - usb2_base + USB2_LINECTRL1); + + msleep(20); + + writel(0xffffffff, usb2_base + USB2_OBINTSTA); + writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); rcar_gen3_device_recognition(ch); } diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 24bd2717abdb..91fba60267a0 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -1168,8 +1168,8 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev) struct phy *phy; /* This driver aims to support both otg-port and host-port */ - if (of_node_cmp(child_np->name, "host-port") && - of_node_cmp(child_np->name, "otg-port")) + if (!of_node_name_eq(child_np, "host-port") && + !of_node_name_eq(child_np, "otg-port")) goto next_child; phy = devm_phy_create(dev, child_np, &rockchip_usb2phy_ops); @@ -1183,7 +1183,7 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev) phy_set_drvdata(rport->phy, rport); /* initialize otg/host port separately */ - if (!of_node_cmp(child_np->name, "host-port")) { + if (of_node_name_eq(child_np, "host-port")) { ret = rockchip_usb2phy_host_port_init(rphy, rport, child_np); if (ret) diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c index c57e496f0b0c..e32edeebcd63 100644 --- a/drivers/phy/rockchip/phy-rockchip-typec.c +++ b/drivers/phy/rockchip/phy-rockchip-typec.c @@ -1176,10 +1176,10 @@ static int rockchip_typec_phy_probe(struct platform_device *pdev) for_each_available_child_of_node(np, child_np) { struct phy *phy; - if (!of_node_cmp(child_np->name, "dp-port")) + if (of_node_name_eq(child_np, "dp-port")) phy = devm_phy_create(dev, child_np, &rockchip_dp_phy_ops); - else if (!of_node_cmp(child_np->name, "usb3-port")) + else if (of_node_name_eq(child_np, "usb3-port")) phy = devm_phy_create(dev, child_np, &rockchip_usb3_phy_ops); else diff --git a/drivers/phy/ti/Kconfig b/drivers/phy/ti/Kconfig index 20503562666c..f137e0107764 100644 --- a/drivers/phy/ti/Kconfig +++ b/drivers/phy/ti/Kconfig @@ -76,3 +76,13 @@ config TWL4030_USB family chips (including the TWL5030 and TPS659x0 devices). This transceiver supports high and full speed devices plus, in host mode, low speed. + +config PHY_TI_GMII_SEL + tristate + default y if TI_CPSW=y + depends on TI_CPSW || COMPILE_TEST + select GENERIC_PHY + default m + help + This driver supports configuring of the TI CPSW Port mode depending on + the Ethernet PHY connected to the CPSW Port. diff --git a/drivers/phy/ti/Makefile b/drivers/phy/ti/Makefile index 9f361756eaf2..bea8f25a137a 100644 --- a/drivers/phy/ti/Makefile +++ b/drivers/phy/ti/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o +obj-$(CONFIG_PHY_TI_GMII_SEL) += phy-gmii-sel.o diff --git a/drivers/phy/ti/phy-da8xx-usb.c b/drivers/phy/ti/phy-da8xx-usb.c index befb886ff121..d5f4fbc32b52 100644 --- a/drivers/phy/ti/phy-da8xx-usb.c +++ b/drivers/phy/ti/phy-da8xx-usb.c @@ -93,7 +93,8 @@ static int da8xx_usb20_phy_power_off(struct phy *phy) return 0; } -static int da8xx_usb20_phy_set_mode(struct phy *phy, enum phy_mode mode) +static int da8xx_usb20_phy_set_mode(struct phy *phy, + enum phy_mode mode, int submode) { struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); u32 val; diff --git a/drivers/phy/ti/phy-gmii-sel.c b/drivers/phy/ti/phy-gmii-sel.c new file mode 100644 index 000000000000..77fdaa551977 --- /dev/null +++ b/drivers/phy/ti/phy-gmii-sel.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments CPSW Port's PHY Interface Mode selection Driver + * + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ + * + * Based on cpsw-phy-sel.c driver created by Mugunthan V N + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* AM33xx SoC specific definitions for the CONTROL port */ +#define AM33XX_GMII_SEL_MODE_MII 0 +#define AM33XX_GMII_SEL_MODE_RMII 1 +#define AM33XX_GMII_SEL_MODE_RGMII 2 + +enum { + PHY_GMII_SEL_PORT_MODE, + PHY_GMII_SEL_RGMII_ID_MODE, + PHY_GMII_SEL_RMII_IO_CLK_EN, + PHY_GMII_SEL_LAST, +}; + +struct phy_gmii_sel_phy_priv { + struct phy_gmii_sel_priv *priv; + u32 id; + struct phy *if_phy; + int rmii_clock_external; + int phy_if_mode; + struct regmap_field *fields[PHY_GMII_SEL_LAST]; +}; + +struct phy_gmii_sel_soc_data { + u32 num_ports; + u32 features; + const struct reg_field (*regfields)[PHY_GMII_SEL_LAST]; +}; + +struct phy_gmii_sel_priv { + struct device *dev; + const struct phy_gmii_sel_soc_data *soc_data; + struct regmap *regmap; + struct phy_provider *phy_provider; + struct phy_gmii_sel_phy_priv *if_phys; +}; + +static int phy_gmii_sel_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct phy_gmii_sel_phy_priv *if_phy = phy_get_drvdata(phy); + const struct phy_gmii_sel_soc_data *soc_data = if_phy->priv->soc_data; + struct device *dev = if_phy->priv->dev; + struct regmap_field *regfield; + int ret, rgmii_id = 0; + u32 gmii_sel_mode = 0; + + if (mode != PHY_MODE_ETHERNET) + return -EINVAL; + + switch (submode) { + case PHY_INTERFACE_MODE_RMII: + gmii_sel_mode = AM33XX_GMII_SEL_MODE_RMII; + break; + + case PHY_INTERFACE_MODE_RGMII: + gmii_sel_mode = AM33XX_GMII_SEL_MODE_RGMII; + break; + + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + gmii_sel_mode = AM33XX_GMII_SEL_MODE_RGMII; + rgmii_id = 1; + break; + + case PHY_INTERFACE_MODE_MII: + mode = AM33XX_GMII_SEL_MODE_MII; + break; + + default: + dev_warn(dev, + "port%u: unsupported mode: \"%s\". Defaulting to MII.\n", + if_phy->id, phy_modes(rgmii_id)); + return -EINVAL; + } + + if_phy->phy_if_mode = submode; + + dev_dbg(dev, "%s id:%u mode:%u rgmii_id:%d rmii_clk_ext:%d\n", + __func__, if_phy->id, mode, rgmii_id, + if_phy->rmii_clock_external); + + regfield = if_phy->fields[PHY_GMII_SEL_PORT_MODE]; + ret = regmap_field_write(regfield, gmii_sel_mode); + if (ret) { + dev_err(dev, "port%u: set mode fail %d", if_phy->id, ret); + return ret; + } + + if (soc_data->features & BIT(PHY_GMII_SEL_RGMII_ID_MODE) && + if_phy->fields[PHY_GMII_SEL_RGMII_ID_MODE]) { + regfield = if_phy->fields[PHY_GMII_SEL_RGMII_ID_MODE]; + ret = regmap_field_write(regfield, rgmii_id); + if (ret) + return ret; + } + + if (soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN) && + if_phy->fields[PHY_GMII_SEL_RMII_IO_CLK_EN]) { + regfield = if_phy->fields[PHY_GMII_SEL_RMII_IO_CLK_EN]; + ret = regmap_field_write(regfield, + if_phy->rmii_clock_external); + } + + return 0; +} + +static const +struct reg_field phy_gmii_sel_fields_am33xx[][PHY_GMII_SEL_LAST] = { + { + [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x650, 0, 1), + [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD(0x650, 4, 4), + [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD(0x650, 6, 6), + }, + { + [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x650, 2, 3), + [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD(0x650, 5, 5), + [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD(0x650, 7, 7), + }, +}; + +static const +struct phy_gmii_sel_soc_data phy_gmii_sel_soc_am33xx = { + .num_ports = 2, + .features = BIT(PHY_GMII_SEL_RGMII_ID_MODE) | + BIT(PHY_GMII_SEL_RMII_IO_CLK_EN), + .regfields = phy_gmii_sel_fields_am33xx, +}; + +static const +struct reg_field phy_gmii_sel_fields_dra7[][PHY_GMII_SEL_LAST] = { + { + [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x554, 0, 1), + [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0), + [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0), + }, + { + [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x554, 4, 5), + [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0), + [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0), + }, +}; + +static const +struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dra7 = { + .num_ports = 2, + .regfields = phy_gmii_sel_fields_dra7, +}; + +static const +struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dm814 = { + .num_ports = 2, + .features = BIT(PHY_GMII_SEL_RGMII_ID_MODE), + .regfields = phy_gmii_sel_fields_am33xx, +}; + +static const struct of_device_id phy_gmii_sel_id_table[] = { + { + .compatible = "ti,am3352-phy-gmii-sel", + .data = &phy_gmii_sel_soc_am33xx, + }, + { + .compatible = "ti,dra7xx-phy-gmii-sel", + .data = &phy_gmii_sel_soc_dra7, + }, + { + .compatible = "ti,am43xx-phy-gmii-sel", + .data = &phy_gmii_sel_soc_am33xx, + }, + { + .compatible = "ti,dm814-phy-gmii-sel", + .data = &phy_gmii_sel_soc_dm814, + }, + {} +}; +MODULE_DEVICE_TABLE(of, phy_gmii_sel_id_table); + +static const struct phy_ops phy_gmii_sel_ops = { + .set_mode = phy_gmii_sel_mode, + .owner = THIS_MODULE, +}; + +static struct phy *phy_gmii_sel_of_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct phy_gmii_sel_priv *priv = dev_get_drvdata(dev); + int phy_id = args->args[0]; + + if (args->args_count < 1) + return ERR_PTR(-EINVAL); + if (priv->soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN) && + args->args_count < 2) + return ERR_PTR(-EINVAL); + if (!priv || !priv->if_phys) + return ERR_PTR(-ENODEV); + if (phy_id > priv->soc_data->num_ports) + return ERR_PTR(-EINVAL); + if (phy_id != priv->if_phys[phy_id - 1].id) + return ERR_PTR(-EINVAL); + + phy_id--; + if (priv->soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN)) + priv->if_phys[phy_id].rmii_clock_external = args->args[1]; + dev_dbg(dev, "%s id:%u ext:%d\n", __func__, + priv->if_phys[phy_id].id, args->args[1]); + + return priv->if_phys[phy_id].if_phy; +} + +static int phy_gmii_sel_init_ports(struct phy_gmii_sel_priv *priv) +{ + const struct phy_gmii_sel_soc_data *soc_data = priv->soc_data; + struct device *dev = priv->dev; + struct phy_gmii_sel_phy_priv *if_phys; + int i, num_ports, ret; + + num_ports = priv->soc_data->num_ports; + + if_phys = devm_kcalloc(priv->dev, num_ports, + sizeof(*if_phys), GFP_KERNEL); + if (!if_phys) + return -ENOMEM; + dev_dbg(dev, "%s %d\n", __func__, num_ports); + + for (i = 0; i < num_ports; i++) { + const struct reg_field *field; + struct regmap_field *regfield; + + if_phys[i].id = i + 1; + if_phys[i].priv = priv; + + field = &soc_data->regfields[i][PHY_GMII_SEL_PORT_MODE]; + dev_dbg(dev, "%s field %x %d %d\n", __func__, + field->reg, field->msb, field->lsb); + + regfield = devm_regmap_field_alloc(dev, priv->regmap, *field); + if (IS_ERR(regfield)) + return PTR_ERR(regfield); + if_phys[i].fields[PHY_GMII_SEL_PORT_MODE] = regfield; + + field = &soc_data->regfields[i][PHY_GMII_SEL_RGMII_ID_MODE]; + if (field->reg != (~0)) { + regfield = devm_regmap_field_alloc(dev, + priv->regmap, + *field); + if (IS_ERR(regfield)) + return PTR_ERR(regfield); + if_phys[i].fields[PHY_GMII_SEL_RGMII_ID_MODE] = + regfield; + } + + field = &soc_data->regfields[i][PHY_GMII_SEL_RMII_IO_CLK_EN]; + if (field->reg != (~0)) { + regfield = devm_regmap_field_alloc(dev, + priv->regmap, + *field); + if (IS_ERR(regfield)) + return PTR_ERR(regfield); + if_phys[i].fields[PHY_GMII_SEL_RMII_IO_CLK_EN] = + regfield; + } + + if_phys[i].if_phy = devm_phy_create(dev, + priv->dev->of_node, + &phy_gmii_sel_ops); + if (IS_ERR(if_phys[i].if_phy)) { + ret = PTR_ERR(if_phys[i].if_phy); + dev_err(dev, "Failed to create phy%d %d\n", i, ret); + return ret; + } + phy_set_drvdata(if_phys[i].if_phy, &if_phys[i]); + } + + priv->if_phys = if_phys; + return 0; +} + +static int phy_gmii_sel_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + const struct of_device_id *of_id; + struct phy_gmii_sel_priv *priv; + int ret; + + of_id = of_match_node(phy_gmii_sel_id_table, pdev->dev.of_node); + if (!of_id) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + priv->soc_data = of_id->data; + + priv->regmap = syscon_node_to_regmap(node->parent); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(dev, "Failed to get syscon %d\n", ret); + return ret; + } + + ret = phy_gmii_sel_init_ports(priv); + if (ret) + return ret; + + dev_set_drvdata(&pdev->dev, priv); + + priv->phy_provider = + devm_of_phy_provider_register(dev, + phy_gmii_sel_of_xlate); + if (IS_ERR(priv->phy_provider)) { + ret = PTR_ERR(priv->phy_provider); + dev_err(dev, "Failed to create phy provider %d\n", ret); + return ret; + } + + return 0; +} + +static struct platform_driver phy_gmii_sel_driver = { + .probe = phy_gmii_sel_probe, + .driver = { + .name = "phy-gmii-sel", + .of_match_table = phy_gmii_sel_id_table, + }, +}; +module_platform_driver(phy_gmii_sel_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Grygorii Strashko "); +MODULE_DESCRIPTION("TI CPSW Port's PHY Interface Mode selection Driver"); diff --git a/drivers/phy/ti/phy-tusb1210.c b/drivers/phy/ti/phy-tusb1210.c index b8ec39ac4dfc..329fb938099a 100644 --- a/drivers/phy/ti/phy-tusb1210.c +++ b/drivers/phy/ti/phy-tusb1210.c @@ -53,7 +53,7 @@ static int tusb1210_power_off(struct phy *phy) return 0; } -static int tusb1210_set_mode(struct phy *phy, enum phy_mode mode) +static int tusb1210_set_mode(struct phy *phy, enum phy_mode mode, int submode) { struct tusb1210 *tusb = phy_get_drvdata(phy); int ret; diff --git a/include/linux/phy/phy-mipi-dphy.h b/include/linux/phy/phy-mipi-dphy.h new file mode 100644 index 000000000000..c08aacc0ac35 --- /dev/null +++ b/include/linux/phy/phy-mipi-dphy.h @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Cadence Design Systems Inc. + */ + +#ifndef __PHY_MIPI_DPHY_H_ +#define __PHY_MIPI_DPHY_H_ + +#include