Commit ed0a773b authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

Merge tag 'phy-for-4.21_v1' of...

Merge tag 'phy-for-4.21_v1' of git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy into usb-next

Kishon writes:

phy: for 4.21

 *) Change phy set_mode ops to take both mode and setmode as arguments
 *) Add phy_configure() and phy_validate() API's mostly used for MIPI D-PHY
 *) Add helpers to get default values of parameters define in MIPI D-PHY spec
 *) Add driver for TI's CPSW Port PHY Interface Mode selection
 *) Add driver for Cadence Sierra PHY used with USB and PCIe
 *) Add driver for Freescale i.MX8MQ USB3 PHY
 *) Fixes QMP PHY bindings to allow the clocks provided by the PHY to be
    pointed at in device tree
 *) Fix for using fully specified regions (in device tree) for configuring
    the second lane in dual lane PHYs in QMP PHY
 *) Add support for Allwinner H6 USB2 PHY in phy-sun4i-usb driver
 *) Update phy-rcar-gen3-usb driver to follow the hardware manual
 *) Add support for fine grained power management in mapphone-mdm6600 driver
Signed-off-by: default avatarKishon Vijay Abraham I <kishon@ti.com>

* tag 'phy-for-4.21_v1' of git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy: (30 commits)
  phy: qcom-qmp: Expose provided clocks to DT
  dt-bindings: phy-qcom-qmp: Move #clock-cells to child
  phy: qcom-qmp: Utilize fully-specified DT registers
  dt-bindings: phy-qcom-qmp: Fix register underspecification
  phy: ti: fix semicolon.cocci warnings
  phy: dphy: Add configuration helpers
  phy: Add MIPI D-PHY configuration options
  phy: Add configuration interface
  phy: Add MIPI D-PHY mode
  phy: add driver for Freescale i.MX8MQ USB3 PHY
  dt-bindings: phy: add binding for Freescale i.MX8MQ USB3 PHY
  phy: Use of_node_name_eq for node name comparisons
  net: ethernet: ti: cpsw: add support for port interface mode selection phy
  dt-bindings: net: ti: cpsw: switch to use phy-gmii-sel phy
  phy: ti: introduce phy-gmii-sel driver
  dt-bindings: phy: add cpsw port interface mode selection phy bindings
  phy: mvebu-cp110-comphy: fix spelling in structure name
  phy: mapphone-mdm6600: Improve phy related runtime PM calls
  phy: renesas: rcar-gen3-usb2: follow the hardware manual procedure
  phy: cadence: Add driver for Sierra PHY
  ...
parents b53bde66 2e38c2e7
......@@ -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>;
};
};
* 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>;
};
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 = <PHY_TYPE_PCIE>;
};
pcie0_phy1: pcie-phy@2 {
reg = <2>;
resets = <&phyrst 4>;
cdns,num-lanes = <1>;
#phy-cells = <0>;
cdns,phy-type = <PHY_TYPE_PCIE>;
};
......@@ -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 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 child node of pcie and usb3 qmp phys:
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<lane-number>" 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>;
};
};
......@@ -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
......
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>;
};
};
......@@ -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 <wsa+renesas@sang-engineering.com>
......
......@@ -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;
......
......@@ -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;
......
......@@ -26,6 +26,7 @@
#include <linux/netdevice.h>
#include <linux/net_tstamp.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/pm_runtime.h>
......@@ -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);
......
......@@ -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"
......
......@@ -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/ \
......
......@@ -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);
......
......@@ -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);
......
......@@ -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;
......
#
# 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
obj-$(CONFIG_PHY_CADENCE_DP) += phy-cadence-dp.o
obj-$(CONFIG_PHY_CADENCE_SIERRA) += phy-cadence-sierra.o
This diff is collapsed.
config PHY_FSL_IMX8MQ_USB
tristate "Freescale i.MX8M USB3 PHY"
depends on OF && HAS_IOMEM
select GENERIC_PHY
default SOC_IMX8MQ
obj-$(CONFIG_PHY_FSL_IMX8MQ_USB) += phy-fsl-imx8mq-usb.o
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) 2017 NXP. */
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#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");
......@@ -9,6 +9,7 @@
#include <linux/iopoll.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
......@@ -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;
}
......
......@@ -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);
......
......@@ -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);
......
......@@ -16,6 +16,7 @@
#include <linux/gpio/consumer.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/pinctrl/consumer.h>
#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:
if (error < 0)
phy_mdm6600_device_power_off(ddata);
return error;
}
......
......@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
......@@ -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;
......
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2013 NVIDIA Corporation
* Copyright (C) 2018 Cadence Design Systems Inc.
*/
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/time64.h>
#include <linux/phy/phy.h>
#include <linux/phy/phy-mipi-dphy.h>
#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);
......@@ -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
......
......@@ -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;
/*
* 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");
......
......@@ -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);
......
......@@ -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);
......
......@@ -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);
......
......@@ -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;
......
......@@ -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);
}
......
......@@ -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)
......
......@@ -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
......
......@@ -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.
......@@ -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
......@@ -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;
......
// 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 <mugunthanvnm@ti.com>
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/regmap.h>
/* 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 <grygorii.strashko@ti.com>");
MODULE_DESCRIPTION("TI CPSW Port's PHY Interface Mode selection Driver");
......@@ -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;
......
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2018 Cadence Design Systems Inc.
*/
#ifndef __PHY_MIPI_DPHY_H_
#define __PHY_MIPI_DPHY_H_
#include <video/videomode.h>
/**
* struct phy_configure_opts_mipi_dphy - MIPI D-PHY configuration set
*
* This structure is used to represent the configuration state of a
* MIPI D-PHY phy.
*/
struct phy_configure_opts_mipi_dphy {
/**
* @clk_miss:
*
* Timeout, in picoseconds, for receiver to detect absence of
* Clock transitions and disable the Clock Lane HS-RX.
*
* Maximum value: 60000 ps
*/
unsigned int clk_miss;
/**
* @clk_post:
*
* Time, in picoseconds, that the transmitter continues to
* send HS clock after the last associated Data Lane has
* transitioned to LP Mode. Interval is defined as the period
* from the end of @hs_trail to the beginning of @clk_trail.
*
* Minimum value: 60000 ps + 52 * @hs_clk_rate period in ps
*/
unsigned int clk_post;
/**
* @clk_pre:
*
* Time, in UI, that the HS clock shall be driven by
* the transmitter prior to any associated Data Lane beginning
* the transition from LP to HS mode.
*
* Minimum value: 8 UI
*/
unsigned int clk_pre;
/**
* @clk_prepare:
*
* Time, in picoseconds, that the transmitter drives the Clock
* Lane LP-00 Line state immediately before the HS-0 Line
* state starting the HS transmission.
*
* Minimum value: 38000 ps
* Maximum value: 95000 ps
*/
unsigned int clk_prepare;
/**
* @clk_settle:
*
* Time interval, in picoseconds, during which the HS receiver
* should ignore any Clock Lane HS transitions, starting from
* the beginning of @clk_prepare.
*
* Minimum value: 95000 ps
* Maximum value: 300000 ps
*/
unsigned int clk_settle;
/**
* @clk_term_en:
*
* Time, in picoseconds, for the Clock Lane receiver to enable
* the HS line termination.
*
* Maximum value: 38000 ps
*/
unsigned int clk_term_en;
/**
* @clk_trail:
*
* Time, in picoseconds, that the transmitter drives the HS-0
* state after the last payload clock bit of a HS transmission
* burst.
*
* Minimum value: 60000 ps
*/
unsigned int clk_trail;
/**
* @clk_zero:
*
* Time, in picoseconds, that the transmitter drives the HS-0
* state prior to starting the Clock.
*/
unsigned int clk_zero;
/**
* @d_term_en:
*
* Time, in picoseconds, for the Data Lane receiver to enable
* the HS line termination.
*
* Maximum value: 35000 ps + 4 * @hs_clk_rate period in ps
*/
unsigned int d_term_en;
/**
* @eot:
*
* Transmitted time interval, in picoseconds, from the start
* of @hs_trail or @clk_trail, to the start of the LP- 11
* state following a HS burst.
*
* Maximum value: 105000 ps + 12 * @hs_clk_rate period in ps
*/
unsigned int eot;
/**
* @hs_exit:
*
* Time, in picoseconds, that the transmitter drives LP-11
* following a HS burst.
*
* Minimum value: 100000 ps
*/
unsigned int hs_exit;
/**
* @hs_prepare:
*
* Time, in picoseconds, that the transmitter drives the Data
* Lane LP-00 Line state immediately before the HS-0 Line
* state starting the HS transmission.
*
* Minimum value: 40000 ps + 4 * @hs_clk_rate period in ps
* Maximum value: 85000 ps + 6 * @hs_clk_rate period in ps
*/
unsigned int hs_prepare;
/**
* @hs_settle:
*
* Time interval, in picoseconds, during which the HS receiver
* shall ignore any Data Lane HS transitions, starting from
* the beginning of @hs_prepare.
*
* Minimum value: 85000 ps + 6 * @hs_clk_rate period in ps
* Maximum value: 145000 ps + 10 * @hs_clk_rate period in ps
*/
unsigned int hs_settle;
/**
* @hs_skip:
*
* Time interval, in picoseconds, during which the HS-RX
* should ignore any transitions on the Data Lane, following a
* HS burst. The end point of the interval is defined as the
* beginning of the LP-11 state following the HS burst.
*
* Minimum value: 40000 ps
* Maximum value: 55000 ps + 4 * @hs_clk_rate period in ps
*/
unsigned int hs_skip;
/**
* @hs_trail:
*
* Time, in picoseconds, that the transmitter drives the
* flipped differential state after last payload data bit of a
* HS transmission burst
*
* Minimum value: max(8 * @hs_clk_rate period in ps,
* 60000 ps + 4 * @hs_clk_rate period in ps)
*/
unsigned int hs_trail;
/**
* @hs_zero:
*
* Time, in picoseconds, that the transmitter drives the HS-0
* state prior to transmitting the Sync sequence.
*/
unsigned int hs_zero;
/**
* @init:
*
* Time, in picoseconds for the initialization period to
* complete.
*
* Minimum value: 100000000 ps
*/
unsigned int init;
/**
* @lpx:
*
* Transmitted length, in picoseconds, of any Low-Power state
* period.
*
* Minimum value: 50000 ps
*/
unsigned int lpx;
/**
* @ta_get:
*
* Time, in picoseconds, that the new transmitter drives the
* Bridge state (LP-00) after accepting control during a Link
* Turnaround.
*
* Value: 5 * @lpx
*/
unsigned int ta_get;
/**
* @ta_go:
*
* Time, in picoseconds, that the transmitter drives the
* Bridge state (LP-00) before releasing control during a Link
* Turnaround.
*
* Value: 4 * @lpx
*/
unsigned int ta_go;
/**
* @ta_sure:
*
* Time, in picoseconds, that the new transmitter waits after
* the LP-10 state before transmitting the Bridge state
* (LP-00) during a Link Turnaround.
*
* Minimum value: @lpx
* Maximum value: 2 * @lpx
*/
unsigned int ta_sure;
/**
* @wakeup:
*
* Time, in picoseconds, that a transmitter drives a Mark-1
* state prior to a Stop state in order to initiate an exit
* from ULPS.
*
* Minimum value: 1000000000 ps
*/
unsigned int wakeup;
/**
* @hs_clk_rate:
*
* Clock rate, in Hertz, of the high-speed clock.
*/
unsigned long hs_clk_rate;
/**
* @lp_clk_rate:
*
* Clock rate, in Hertz, of the low-power clock.
*/
unsigned long lp_clk_rate;
/**
* @lanes:
*
* Number of active data lanes used for the transmissions.
*/
unsigned char lanes;
};
int phy_mipi_dphy_get_default_config(unsigned long pixel_clock,
unsigned int bpp,
unsigned int lanes,
struct phy_configure_opts_mipi_dphy *cfg);
int phy_mipi_dphy_config_validate(struct phy_configure_opts_mipi_dphy *cfg);
#endif /* __PHY_MIPI_DPHY_H_ */
......@@ -20,6 +20,8 @@
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/phy/phy-mipi-dphy.h>
struct phy;
enum phy_mode {
......@@ -35,13 +37,21 @@ enum phy_mode {
PHY_MODE_USB_DEVICE_HS,
PHY_MODE_USB_DEVICE_SS,
PHY_MODE_USB_OTG,
PHY_MODE_SGMII,
PHY_MODE_2500SGMII,
PHY_MODE_QSGMII,
PHY_MODE_10GKR,
PHY_MODE_UFS_HS_A,
PHY_MODE_UFS_HS_B,
PHY_MODE_PCIE,
PHY_MODE_ETHERNET,
PHY_MODE_MIPI_DPHY,
};
/**
* union phy_configure_opts - Opaque generic phy configuration
*
* @mipi_dphy: Configuration set applicable for phys supporting
* the MIPI_DPHY phy mode.
*/
union phy_configure_opts {
struct phy_configure_opts_mipi_dphy mipi_dphy;
};
/**
......@@ -60,7 +70,38 @@ struct phy_ops {
int (*exit)(struct phy *phy);
int (*power_on)(struct phy *phy);
int (*power_off)(struct phy *phy);
int (*set_mode)(struct phy *phy, enum phy_mode mode);
int (*set_mode)(struct phy *phy, enum phy_mode mode, int submode);
/**
* @configure:
*
* Optional.
*
* Used to change the PHY parameters. phy_init() must have
* been called on the phy.
*
* Returns: 0 if successful, an negative error code otherwise
*/
int (*configure)(struct phy *phy, union phy_configure_opts *opts);
/**
* @validate:
*
* Optional.
*
* 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 must not change
* any actual configuration of the PHY, so calling it as many
* times as deemed fit by the consumer must have no side
* effect.
*
* Returns: 0 if the configuration can be applied, an negative
* error code otherwise
*/
int (*validate)(struct phy *phy, enum phy_mode mode, int submode,
union phy_configure_opts *opts);
int (*reset)(struct phy *phy);
int (*calibrate)(struct phy *phy);
struct module *owner;
......@@ -164,7 +205,13 @@ int phy_init(struct phy *phy);
int phy_exit(struct phy *phy);
int phy_power_on(struct phy *phy);
int phy_power_off(struct phy *phy);
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);
#define phy_set_mode(phy, mode) \
phy_set_mode_ext(phy, mode, 0)
int phy_configure(struct phy *phy, union phy_configure_opts *opts);
int phy_validate(struct phy *phy, enum phy_mode mode, int submode,
union phy_configure_opts *opts);
static inline enum phy_mode phy_get_mode(struct phy *phy)
{
return phy->attrs.mode;
......@@ -278,13 +325,17 @@ static inline int phy_power_off(struct phy *phy)
return -ENOSYS;
}
static inline int phy_set_mode(struct phy *phy, enum phy_mode mode)
static inline int phy_set_mode_ext(struct phy *phy, enum phy_mode mode,
int submode)
{
if (!phy)
return 0;
return -ENOSYS;
}
#define phy_set_mode(phy, mode) \
phy_set_mode_ext(phy, mode, 0)
static inline enum phy_mode phy_get_mode(struct phy *phy)
{
return PHY_MODE_INVALID;
......@@ -304,6 +355,24 @@ static inline int phy_calibrate(struct phy *phy)
return -ENOSYS;
}
static inline int phy_configure(struct phy *phy,
union phy_configure_opts *opts)
{
if (!phy)
return 0;
return -ENOSYS;
}
static inline int phy_validate(struct phy *phy, enum phy_mode mode, int submode,
union phy_configure_opts *opts)
{
if (!phy)
return 0;
return -ENOSYS;
}
static inline int phy_get_bus_width(struct phy *phy)
{
return -ENOSYS;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment