diff options
| author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2019-02-12 14:59:43 +0100 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2019-02-12 14:59:43 +0100 |
| commit | 0220dcd1138b77bd93ae554f651d1b61ad2403fa (patch) | |
| tree | 891e7bdf8d08d9b2d0255e5d91ebdd29ca50f3b2 /drivers/phy | |
| parent | a8ded8eb7765233471b24bd23bdd65b44da5583a (diff) | |
| parent | 203d9b11928cf68907344c24bd78726fa69de6cb (diff) | |
| download | linux-0220dcd1138b77bd93ae554f651d1b61ad2403fa.tar.gz linux-0220dcd1138b77bd93ae554f651d1b61ad2403fa.tar.bz2 linux-0220dcd1138b77bd93ae554f651d1b61ad2403fa.zip | |
Merge tag 'phy-for-5.1' of git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy into usb-next
Kishon writes:
phy: for 5.1
*) Add a new driver to support Armada 3700 COMPHY IP (supports SATA, USB3,
PCIe)
*) Add a new driver to support Armada UTMI PHY
*) Add a new driver to support Cadence D-PHY
*) Extend omap-usb2 PHY driver to be used for AM654 USB2 PHY
*) Extend qcom-qmp PHY driver to be used for UFS PHY and USB3 PHY in Qualcomm
MSM8998
*) Extend qcom-qusb2 PHY driver to support QUSB2 PHY in Qualcomm MSM8998
*) Remove module specific code that is present for drivers that can be only
built-in
*) Allow Freescale IMX8MQ USB to be used for multiple SoCs and not just
i.MX8MQ
*) Cleanups such as switch to SPDX identifier, use readl_poll_timeout macro,
remove unused headers etc.,
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
* tag 'phy-for-5.1' of git://git.kernel.org/pub/scm/linux/kernel/git/kishon/linux-phy: (32 commits)
phy: qcom-qmp: Add QMP UFS PHY support for msm8998
dt-bindings: phy-qcom-qmp: Add qcom,msm8998-qmp-ufs-phy
phy: bcm-sr-pcie: Change operation when PIPEMUX=1
phy: Add Cadence D-PHY support
dt-bindings: phy: Move the Cadence D-PHY bindings
phy: dphy: Clarify lanes parameter documentation
phy: dphy: Change units of wakeup and init parameters
phy: dphy: Remove unused header
MAINTAINERS: phy: fill Armada 3700 PHY drivers entry
dt-bindings: phy: mvebu-utmi: add UTMI PHY bindings
phy: add A3700 UTMI PHY driver
MAINTAINERS: phy: add entry for Armada 3700 COMPHY driver
dt-bindings: phy: mvebu-comphy: extend the file to describe a3700 bindings
phy: add A3700 COMPHY support
phy: mvebu-cp110-comphy: fix port check in ->xlate()
phy: armada375-usb2: switch to SPDX license identifier
phy: make phy-armada375-usb2 explicitly non-modular
phy: make phy-mvebu-sata explicitly non-modular
phy: make phy-core explicitly non-modular
phy: qcom-qusb2: Add QUSB2 PHY support for msm8998
...
Diffstat (limited to 'drivers/phy')
| -rw-r--r-- | drivers/phy/broadcom/phy-bcm-sr-pcie.c | 4 | ||||
| -rw-r--r-- | drivers/phy/cadence/Kconfig | 13 | ||||
| -rw-r--r-- | drivers/phy/cadence/Makefile | 1 | ||||
| -rw-r--r-- | drivers/phy/cadence/cdns-dphy.c | 391 | ||||
| -rw-r--r-- | drivers/phy/freescale/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/phy/marvell/Kconfig | 21 | ||||
| -rw-r--r-- | drivers/phy/marvell/Makefile | 2 | ||||
| -rw-r--r-- | drivers/phy/marvell/phy-armada375-usb2.c | 13 | ||||
| -rw-r--r-- | drivers/phy/marvell/phy-mvebu-a3700-comphy.c | 318 | ||||
| -rw-r--r-- | drivers/phy/marvell/phy-mvebu-a3700-utmi.c | 278 | ||||
| -rw-r--r-- | drivers/phy/marvell/phy-mvebu-cp110-comphy.c | 2 | ||||
| -rw-r--r-- | drivers/phy/marvell/phy-mvebu-sata.c | 9 | ||||
| -rw-r--r-- | drivers/phy/phy-core-mipi-dphy.c | 8 | ||||
| -rw-r--r-- | drivers/phy/phy-core.c | 12 | ||||
| -rw-r--r-- | drivers/phy/qualcomm/phy-qcom-qmp.c | 143 | ||||
| -rw-r--r-- | drivers/phy/qualcomm/phy-qcom-qmp.h | 4 | ||||
| -rw-r--r-- | drivers/phy/qualcomm/phy-qcom-qusb2.c | 40 | ||||
| -rw-r--r-- | drivers/phy/qualcomm/phy-qcom-ufs-i.h | 19 | ||||
| -rw-r--r-- | drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 48 | ||||
| -rw-r--r-- | drivers/phy/ti/Kconfig | 6 | ||||
| -rw-r--r-- | drivers/phy/ti/phy-omap-usb2.c | 105 |
21 files changed, 1309 insertions, 130 deletions
diff --git a/drivers/phy/broadcom/phy-bcm-sr-pcie.c b/drivers/phy/broadcom/phy-bcm-sr-pcie.c index c10e95f86de5..96a3af126a78 100644 --- a/drivers/phy/broadcom/phy-bcm-sr-pcie.c +++ b/drivers/phy/broadcom/phy-bcm-sr-pcie.c @@ -78,8 +78,8 @@ struct sr_pcie_phy_core { static const u8 pipemux_table[] = { /* PIPEMUX = 0, EP 1x16 */ 0x00, - /* PIPEMUX = 1, EP 2x8 */ - 0x00, + /* PIPEMUX = 1, EP 1x8 + RC 1x8, core 7 */ + 0x80, /* PIPEMUX = 2, EP 4x4 */ 0x00, /* PIPEMUX = 3, RC 2x8, cores 0, 7 */ diff --git a/drivers/phy/cadence/Kconfig b/drivers/phy/cadence/Kconfig index 2b8c0851ff33..31f18b67dd7c 100644 --- a/drivers/phy/cadence/Kconfig +++ b/drivers/phy/cadence/Kconfig @@ -1,6 +1,7 @@ # # Phy drivers for Cadence PHYs # + config PHY_CADENCE_DP tristate "Cadence MHDP DisplayPort PHY driver" depends on OF @@ -9,9 +10,19 @@ config PHY_CADENCE_DP help Support for Cadence MHDP DisplayPort PHY. +config PHY_CADENCE_DPHY + tristate "Cadence D-PHY Support" + depends on HAS_IOMEM && OF + select GENERIC_PHY + select GENERIC_PHY_MIPI_DPHY + help + Choose this option if you have a Cadence D-PHY in your + system. If M is selected, the module will be called + cdns-dphy. + 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 + Enable this to support the Cadence Sierra PHY driver diff --git a/drivers/phy/cadence/Makefile b/drivers/phy/cadence/Makefile index 412349af0492..2f9e3457b954 100644 --- a/drivers/phy/cadence/Makefile +++ b/drivers/phy/cadence/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_PHY_CADENCE_DP) += phy-cadence-dp.o +obj-$(CONFIG_PHY_CADENCE_DPHY) += cdns-dphy.o obj-$(CONFIG_PHY_CADENCE_SIERRA) += phy-cadence-sierra.o diff --git a/drivers/phy/cadence/cdns-dphy.c b/drivers/phy/cadence/cdns-dphy.c new file mode 100644 index 000000000000..90c4e9b5aac8 --- /dev/null +++ b/drivers/phy/cadence/cdns-dphy.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright: 2017-2018 Cadence Design Systems, Inc. + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <linux/phy/phy.h> +#include <linux/phy/phy-mipi-dphy.h> + +#define REG_WAKEUP_TIME_NS 800 +#define DPHY_PLL_RATE_HZ 108000000 + +/* DPHY registers */ +#define DPHY_PMA_CMN(reg) (reg) +#define DPHY_PMA_LCLK(reg) (0x100 + (reg)) +#define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg)) +#define DPHY_PMA_RCLK(reg) (0x600 + (reg)) +#define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg)) +#define DPHY_PCS(reg) (0xb00 + (reg)) + +#define DPHY_CMN_SSM DPHY_PMA_CMN(0x20) +#define DPHY_CMN_SSM_EN BIT(0) +#define DPHY_CMN_TX_MODE_EN BIT(9) + +#define DPHY_CMN_PWM DPHY_PMA_CMN(0x40) +#define DPHY_CMN_PWM_DIV(x) ((x) << 20) +#define DPHY_CMN_PWM_LOW(x) ((x) << 10) +#define DPHY_CMN_PWM_HIGH(x) (x) + +#define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c) +#define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22)) +#define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21)) + +#define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50) +#define DPHY_CMN_IPDIV_FROM_REG BIT(0) +#define DPHY_CMN_IPDIV(x) ((x) << 1) +#define DPHY_CMN_OPDIV_FROM_REG BIT(6) +#define DPHY_CMN_OPDIV(x) ((x) << 7) + +#define DPHY_PSM_CFG DPHY_PCS(0x4) +#define DPHY_PSM_CFG_FROM_REG BIT(0) +#define DPHY_PSM_CLK_DIV(x) ((x) << 1) + +#define DSI_HBP_FRAME_OVERHEAD 12 +#define DSI_HSA_FRAME_OVERHEAD 14 +#define DSI_HFP_FRAME_OVERHEAD 6 +#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4 +#define DSI_BLANKING_FRAME_OVERHEAD 6 +#define DSI_NULL_FRAME_OVERHEAD 6 +#define DSI_EOT_PKT_SIZE 4 + +struct cdns_dphy_cfg { + u8 pll_ipdiv; + u8 pll_opdiv; + u16 pll_fbdiv; + unsigned int nlanes; +}; + +enum cdns_dphy_clk_lane_cfg { + DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0, + DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1, + DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2, + DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3, +}; + +struct cdns_dphy; +struct cdns_dphy_ops { + int (*probe)(struct cdns_dphy *dphy); + void (*remove)(struct cdns_dphy *dphy); + void (*set_psm_div)(struct cdns_dphy *dphy, u8 div); + void (*set_clk_lane_cfg)(struct cdns_dphy *dphy, + enum cdns_dphy_clk_lane_cfg cfg); + void (*set_pll_cfg)(struct cdns_dphy *dphy, + const struct cdns_dphy_cfg *cfg); + unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy); +}; + +struct cdns_dphy { + struct cdns_dphy_cfg cfg; + void __iomem *regs; + struct clk *psm_clk; + struct clk *pll_ref_clk; + const struct cdns_dphy_ops *ops; + struct phy *phy; +}; + +static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy, + struct cdns_dphy_cfg *cfg, + struct phy_configure_opts_mipi_dphy *opts, + unsigned int *dsi_hfp_ext) +{ + unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk); + u64 dlane_bps; + + memset(cfg, 0, sizeof(*cfg)); + + if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000) + return -EINVAL; + else if (pll_ref_hz < 19200000) + cfg->pll_ipdiv = 1; + else if (pll_ref_hz < 38400000) + cfg->pll_ipdiv = 2; + else if (pll_ref_hz < 76800000) + cfg->pll_ipdiv = 4; + else + cfg->pll_ipdiv = 8; + + dlane_bps = opts->hs_clk_rate; + + if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL) + return -EINVAL; + else if (dlane_bps >= 1250000000) + cfg->pll_opdiv = 1; + else if (dlane_bps >= 630000000) + cfg->pll_opdiv = 2; + else if (dlane_bps >= 320000000) + cfg->pll_opdiv = 4; + else if (dlane_bps >= 160000000) + cfg->pll_opdiv = 8; + + cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv * + cfg->pll_ipdiv, + pll_ref_hz); + + return 0; +} + +static int cdns_dphy_setup_psm(struct cdns_dphy *dphy) +{ + unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk); + unsigned long psm_div; + + if (!psm_clk_hz || psm_clk_hz > 100000000) + return -EINVAL; + + psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000); + if (dphy->ops->set_psm_div) + dphy->ops->set_psm_div(dphy, psm_div); + + return 0; +} + +static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy, + enum cdns_dphy_clk_lane_cfg cfg) +{ + if (dphy->ops->set_clk_lane_cfg) + dphy->ops->set_clk_lane_cfg(dphy, cfg); +} + +static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy, + const struct cdns_dphy_cfg *cfg) +{ + if (dphy->ops->set_pll_cfg) + dphy->ops->set_pll_cfg(dphy, cfg); +} + +static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy) +{ + return dphy->ops->get_wakeup_time_ns(dphy); +} + +static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy) +{ + /* Default wakeup time is 800 ns (in a simulated environment). */ + return 800; +} + +static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy, + const struct cdns_dphy_cfg *cfg) +{ + u32 fbdiv_low, fbdiv_high; + + fbdiv_low = (cfg->pll_fbdiv / 4) - 2; + fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2; + + writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG | + DPHY_CMN_IPDIV(cfg->pll_ipdiv) | + DPHY_CMN_OPDIV(cfg->pll_opdiv), + dphy->regs + DPHY_CMN_OPIPDIV); + writel(DPHY_CMN_FBDIV_FROM_REG | + DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high), + dphy->regs + DPHY_CMN_FBDIV); + writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) | + DPHY_CMN_PWM_DIV(0x8), + dphy->regs + DPHY_CMN_PWM); +} + +static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div) +{ + writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div), + dphy->regs + DPHY_PSM_CFG); +} + +/* + * This is the reference implementation of DPHY hooks. Specific integration of + * this IP may have to re-implement some of them depending on how they decided + * to wire things in the SoC. + */ +static const struct cdns_dphy_ops ref_dphy_ops = { + .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns, + .set_pll_cfg = cdns_dphy_ref_set_pll_cfg, + .set_psm_div = cdns_dphy_ref_set_psm_div, +}; + +static int cdns_dphy_config_from_opts(struct phy *phy, + struct phy_configure_opts_mipi_dphy *opts, + struct cdns_dphy_cfg *cfg) +{ + struct cdns_dphy *dphy = phy_get_drvdata(phy); + unsigned int dsi_hfp_ext = 0; + int ret; + + ret = phy_mipi_dphy_config_validate(opts); + if (ret) + return ret; + + ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg, + opts, &dsi_hfp_ext); + if (ret) + return ret; + + opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000; + + return 0; +} + +static int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode, + union phy_configure_opts *opts) +{ + struct cdns_dphy_cfg cfg = { 0 }; + + if (mode != PHY_MODE_MIPI_DPHY) + return -EINVAL; + + return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); +} + +static int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts) +{ + struct cdns_dphy *dphy = phy_get_drvdata(phy); + struct cdns_dphy_cfg cfg = { 0 }; + int ret; + + ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg); + if (ret) + return ret; + + /* + * Configure the internal PSM clk divider so that the DPHY has a + * 1MHz clk (or something close). + */ + ret = cdns_dphy_setup_psm(dphy); + if (ret) + return ret; + + /* + * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes + * and 8 data lanes, each clk lane can be attache different set of + * data lanes. The 2 groups are named 'left' and 'right', so here we + * just say that we want the 'left' clk lane to drive the 'left' data + * lanes. + */ + cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT); + + /* + * Configure the DPHY PLL that will be used to generate the TX byte + * clk. + */ + cdns_dphy_set_pll_cfg(dphy, &cfg); + + return 0; +} + +static int cdns_dphy_power_on(struct phy *phy) +{ + struct cdns_dphy *dphy = phy_get_drvdata(phy); + + clk_prepare_enable(dphy->psm_clk); + clk_prepare_enable(dphy->pll_ref_clk); + + /* Start TX state machine. */ + writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN, + dphy->regs + DPHY_CMN_SSM); + + return 0; +} + +static int cdns_dphy_power_off(struct phy *phy) +{ + struct cdns_dphy *dphy = phy_get_drvdata(phy); + + clk_disable_unprepare(dphy->pll_ref_clk); + clk_disable_unprepare(dphy->psm_clk); + + return 0; +} + +static const struct phy_ops cdns_dphy_ops = { + .configure = cdns_dphy_configure, + .validate = cdns_dphy_validate, + .power_on = cdns_dphy_power_on, + .power_off = cdns_dphy_power_off, +}; + +static int cdns_dphy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct cdns_dphy *dphy; + struct resource *res; + int ret; + + dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); + if (!dphy) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, dphy); + + dphy->ops = of_device_get_match_data(&pdev->dev); + if (!dphy->ops) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dphy->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dphy->regs)) + return PTR_ERR(dphy->regs); + + dphy->psm_clk = devm_clk_get(&pdev->dev, "psm"); + if (IS_ERR(dphy->psm_clk)) + return PTR_ERR(dphy->psm_clk); + + dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref"); + if (IS_ERR(dphy->pll_ref_clk)) + return PTR_ERR(dphy->pll_ref_clk); + + if (dphy->ops->probe) { + ret = dphy->ops->probe(dphy); + if (ret) + return ret; + } + + dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops); + if (IS_ERR(dphy->phy)) { + dev_err(&pdev->dev, "failed to create PHY\n"); + if (dphy->ops->remove) + dphy->ops->remove(dphy); + return PTR_ERR(dphy->phy); + } + + phy_set_drvdata(dphy->phy, dphy); + phy_provider = devm_of_phy_provider_register(&pdev->dev, + of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static int cdns_dphy_remove(struct platform_device *pdev) +{ + struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev); + + if (dphy->ops->remove) + dphy->ops->remove(dphy); + + return 0; +} + +static const struct of_device_id cdns_dphy_of_match[] = { + { .compatible = "cdns,dphy", .data = &ref_dphy_ops }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, cdns_dphy_of_match); + +static struct platform_driver cdns_dphy_platform_driver = { + .probe = cdns_dphy_probe, + .remove = cdns_dphy_remove, + .driver = { + .name = "cdns-mipi-dphy", + .of_match_table = cdns_dphy_of_match, + }, +}; +module_platform_driver(cdns_dphy_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); +MODULE_DESCRIPTION("Cadence MIPI D-PHY Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig index f050bd4e97e0..832670b4952b 100644 --- a/drivers/phy/freescale/Kconfig +++ b/drivers/phy/freescale/Kconfig @@ -2,4 +2,4 @@ config PHY_FSL_IMX8MQ_USB tristate "Freescale i.MX8M USB3 PHY" depends on OF && HAS_IOMEM select GENERIC_PHY - default SOC_IMX8MQ + default ARCH_MXC && ARM64 diff --git a/drivers/phy/marvell/Kconfig b/drivers/phy/marvell/Kconfig index 6fb4b56e4c14..b8e9dd38ad0d 100644 --- a/drivers/phy/marvell/Kconfig +++ b/drivers/phy/marvell/Kconfig @@ -21,6 +21,27 @@ config PHY_BERLIN_USB help Enable this to support the USB PHY on Marvell Berlin SoCs. +config PHY_MVEBU_A3700_COMPHY + tristate "Marvell A3700 comphy driver" + depends on ARCH_MVEBU || COMPILE_TEST + depends on OF + depends on HAVE_ARM_SMCCC + default y + select GENERIC_PHY + help + This driver allows to control the comphy, a hardware block providing + shared serdes PHYs on Marvell Armada 3700. Its serdes lanes can be + used by various controllers: Ethernet, SATA, USB3, PCIe. + +config PHY_MVEBU_A3700_UTMI + tristate "Marvell A3700 UTMI driver" + depends on ARCH_MVEBU || COMPILE_TEST + depends on OF + default y + select GENERIC_PHY + help + Enable this to support Marvell A3700 UTMI PHY driver. + config PHY_MVEBU_CP110_COMPHY tristate "Marvell CP110 comphy driver" depends on ARCH_MVEBU || COMPILE_TEST diff --git a/drivers/phy/marvell/Makefile b/drivers/phy/marvell/Makefile index 3975b144f8ec..82f291cf59ee 100644 --- a/drivers/phy/marvell/Makefile +++ b/drivers/phy/marvell/Makefile @@ -2,6 +2,8 @@ obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o +obj-$(CONFIG_PHY_MVEBU_A3700_COMPHY) += phy-mvebu-a3700-comphy.o +obj-$(CONFIG_PHY_MVEBU_A3700_UTMI) += phy-mvebu-a3700-utmi.o obj-$(CONFIG_PHY_MVEBU_CP110_COMPHY) += phy-mvebu-cp110-comphy.o obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o diff --git a/drivers/phy/marvell/phy-armada375-usb2.c b/drivers/phy/marvell/phy-armada375-usb2.c index 1a3db288c0a9..fa5dc9462d09 100644 --- a/drivers/phy/marvell/phy-armada375-usb2.c +++ b/drivers/phy/marvell/phy-armada375-usb2.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * USB cluster support for Armada 375 platform. * @@ -5,10 +6,6 @@ * * Gregory CLEMENT <gregory.clement@free-electrons.com> * - * This file is licensed under the terms of the GNU General Public - * License version 2 or later. This program is licensed "as is" - * without any warranty of any kind, whether express or implied. - * * Armada 375 comes with an USB2 host and device controller and an * USB3 controller. The USB cluster control register allows to manage * common features of both USB controllers. @@ -18,7 +15,6 @@ #include <linux/init.h> #include <linux/io.h> #include <linux/kernel.h> -#include <linux/module.h> #include <linux/of_address.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> @@ -142,7 +138,6 @@ static const struct of_device_id of_usb_cluster_table[] = { { .compatible = "marvell,armada-375-usb-cluster", }, { /* end of list */ }, }; -MODULE_DEVICE_TABLE(of, of_usb_cluster_table); static struct platform_driver armada375_usb_phy_driver = { .probe = armada375_usb_phy_probe, @@ -151,8 +146,4 @@ static struct platform_driver armada375_usb_phy_driver = { .name = "armada-375-usb-cluster", } }; -module_platform_driver(armada375_usb_phy_driver); - -MODULE_DESCRIPTION("Armada 375 USB cluster driver"); -MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@free-electrons.com>"); -MODULE_LICENSE("GPL"); +builtin_platform_driver(armada375_usb_phy_driver); diff --git a/drivers/phy/marvell/phy-mvebu-a3700-comphy.c b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c new file mode 100644 index 000000000000..8812a104c233 --- /dev/null +++ b/drivers/phy/marvell/phy-mvebu-a3700-comphy.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Marvell + * + * Authors: + * Evan Wang <xswang@marvell.com> + * Miquèl Raynal <miquel.raynal@bootlin.com> + * + * Structure inspired from phy-mvebu-cp110-comphy.c written by Antoine Tenart. + * SMC call initial support done by Grzegorz Jaszczyk. + */ + +#include <linux/arm-smccc.h> +#include <linux/io.h> +#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> + +#define MVEBU_A3700_COMPHY_LANES 3 +#define MVEBU_A3700_COMPHY_PORTS 2 + +/* COMPHY Fast SMC function identifiers */ +#define COMPHY_SIP_POWER_ON 0x82000001 +#define COMPHY_SIP_POWER_OFF 0x82000002 +#define COMPHY_SIP_PLL_LOCK 0x82000003 + +#define COMPHY_FW_MODE_SATA 0x1 +#define COMPHY_FW_MODE_SGMII 0x2 +#define COMPHY_FW_MODE_HS_SGMII 0x3 +#define COMPHY_FW_MODE_USB3H 0x4 +#define COMPHY_FW_MODE_USB3D 0x5 +#define COMPHY_FW_MODE_PCIE 0x6 +#define COMPHY_FW_MODE_RXAUI 0x7 +#define COMPHY_FW_MODE_XFI 0x8 +#define COMPHY_FW_MODE_SFI 0x9 +#define COMPHY_FW_MODE_USB3 0xa + +#define COMPHY_FW_SPEED_1_25G 0 /* SGMII 1G */ +#define COMPHY_FW_SPEED_2_5G 1 +#define COMPHY_FW_SPEED_3_125G 2 /* SGMII 2.5G */ +#define COMPHY_FW_SPEED_5G 3 +#define COMPHY_FW_SPEED_5_15625G 4 /* XFI 5G */ +#define COMPHY_FW_SPEED_6G 5 +#define COMPHY_FW_SPEED_10_3125G 6 /* XFI 10G */ +#define COMPHY_FW_SPEED_MAX 0x3F + +#define COMPHY_FW_MODE(mode) ((mode) << 12) +#define COMPHY_FW_NET(mode, idx, speed) (COMPHY_FW_MODE(mode) | \ + ((idx) << 8) | \ + ((speed) << 2)) +#define COMPHY_FW_PCIE(mode, idx, speed, width) (COMPHY_FW_NET(mode, idx, speed) | \ + ((width) << 18)) + +struct mvebu_a3700_comphy_conf { + unsigned int lane; + enum phy_mode mode; + int submode; + unsigned int port; + u32 fw_mode; +}; + +#define MVEBU_A3700_COMPHY_CONF(_lane, _mode, _smode, _port, _fw) \ + { \ + .lane = _lane, \ + .mode = _mode, \ + .submode = _smode, \ + .port = _port, \ + .fw_mode = _fw, \ + } + +#define MVEBU_A3700_COMPHY_CONF_GEN(_lane, _mode, _port, _fw) \ + MVEBU_A3700_COMPHY_CONF(_lane, _mode, PHY_INTERFACE_MODE_NA, _port, _fw) + +#define MVEBU_A3700_COMPHY_CONF_ETH(_lane, _smode, _port, _fw) \ + MVEBU_A3700_COMPHY_CONF(_lane, PHY_MODE_ETHERNET, _smode, _port, _fw) + +static const struct mvebu_a3700_comphy_conf mvebu_a3700_comphy_modes[] = { + /* lane 0 */ + MVEBU_A3700_COMPHY_CONF_GEN(0, PHY_MODE_USB_HOST_SS, 0, + COMPHY_FW_MODE_USB3H), + MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_SGMII, 1, + COMPHY_FW_MODE_SGMII), + MVEBU_A3700_COMPHY_CONF_ETH(0, PHY_INTERFACE_MODE_2500BASEX, 1, + COMPHY_FW_MODE_HS_SGMII), + /* lane 1 */ + MVEBU_A3700_COMPHY_CONF_GEN(1, PHY_MODE_PCIE, 0, + COMPHY_FW_MODE_PCIE), + MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_SGMII, 0, + COMPHY_FW_MODE_SGMII), + MVEBU_A3700_COMPHY_CONF_ETH(1, PHY_INTERFACE_MODE_2500BASEX, 0, + COMPHY_FW_MODE_HS_SGMII), + /* lane 2 */ + MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_SATA, 0, + COMPHY_FW_MODE_SATA), + MVEBU_A3700_COMPHY_CONF_GEN(2, PHY_MODE_USB_HOST_SS, 0, + COMPHY_FW_MODE_USB3H), +}; + +struct mvebu_a3700_comphy_lane { + struct device *dev; + unsigned int id; + enum phy_mode mode; + int submode; + int port; +}; + +static int mvebu_a3700_comphy_smc(unsigned long function, unsigned long lane, + unsigned long mode) +{ + struct arm_smccc_res res; + + arm_smccc_smc(function, lane, mode, 0, 0, 0, 0, 0, &res); + + return res.a0; +} + +static int mvebu_a3700_comphy_get_fw_mode(int lane, int port, + enum phy_mode mode, + int submode) +{ + int i, n = ARRAY_SIZE(mvebu_a3700_comphy_modes); + + /* Unused PHY mux value is 0x0 */ + if (mode == PHY_MODE_INVALID) + return -EINVAL; + + for (i = 0; i < n; i++) { + if (mvebu_a3700_comphy_modes[i].lane == lane && + mvebu_a3700_comphy_modes[i].port == port && + mvebu_a3700_comphy_modes[i].mode == mode && + mvebu_a3700_comphy_modes[i].submode == submode) + break; + } + + if (i == n) + return -EINVAL; + + return mvebu_a3700_comphy_modes[i].fw_mode; +} + +static int mvebu_a3700_comphy_set_mode(struct phy *phy, enum phy_mode mode, + int submode) +{ + struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy); + int fw_mode; + + if (submode == PHY_INTERFACE_MODE_1000BASEX) + submode = PHY_INTERFACE_MODE_SGMII; + + fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, lane->port, mode, + submode); + if (fw_mode < 0) { + dev_err(lane->dev, "invalid COMPHY mode\n"); + return fw_mode; + } + + /* Just remember the mode, ->power_on() will do the real setup */ + lane->mode = mode; + lane->submode = submode; + + return 0; +} + +static int mvebu_a3700_comphy_power_on(struct phy *phy) +{ + struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy); + u32 fw_param; + int fw_mode; + + fw_mode = mvebu_a3700_comphy_get_fw_mode(lane->id, lane->port, + lane->mode, lane->submode); + if (fw_mode < 0) { + dev_err(lane->dev, "invalid COMPHY mode\n"); + return fw_mode; + } + + switch (lane->mode) { + case PHY_MODE_USB_HOST_SS: + dev_dbg(lane->dev, "set lane %d to USB3 host mode\n", lane->id); + fw_param = COMPHY_FW_MODE(fw_mode); + break; + case PHY_MODE_SATA: + dev_dbg(lane->dev, "set lane %d to SATA mode\n", lane->id); + fw_param = COMPHY_FW_MODE(fw_mode); + break; + case PHY_MODE_ETHERNET: + switch (lane->submode) { + case PHY_INTERFACE_MODE_SGMII: + dev_dbg(lane->dev, "set lane %d to SGMII mode\n", + lane->id); + fw_param = COMPHY_FW_NET(fw_mode, lane->port, + COMPHY_FW_SPEED_1_25G); + break; + case PHY_INTERFACE_MODE_2500BASEX: + dev_dbg(lane->dev, "set lane %d to HS SGMII mode\n", + lane->id); + fw_param = COMPHY_FW_NET(fw_mode, lane->port, + COMPHY_FW_SPEED_3_125G); + break; + default: + dev_err(lane->dev, "unsupported PHY submode (%d)\n", + lane->submode); + return -ENOTSUPP; + } + break; + case PHY_MODE_PCIE: + dev_dbg(lane->dev, "set lane %d to PCIe mode\n", lane->id); + fw_param = COMPHY_FW_PCIE(fw_mode, lane->port, + COMPHY_FW_SPEED_5G, + phy->attrs.bus_width); + break; + default: + dev_err(lane->dev, "unsupported PHY mode (%d)\n", lane->mode); + return -ENOTSUPP; + } + + return mvebu_a3700_comphy_smc(COMPHY_SIP_POWER_ON, lane->id, fw_param); +} + +static int mvebu_a3700_comphy_power_off(struct phy *phy) +{ + struct mvebu_a3700_comphy_lane *lane = phy_get_drvdata(phy); + + return mvebu_a3700_comphy_smc(COMPHY_SIP_POWER_OFF, lane->id, 0); +} + +static const struct phy_ops mvebu_a3700_comphy_ops = { + .power_on = mvebu_a3700_comphy_power_on, + .power_off = mvebu_a3700_comphy_power_off, + .set_mode = mvebu_a3700_comphy_set_mode, + .owner = THIS_MODULE, +}; + +static struct phy *mvebu_a3700_comphy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct mvebu_a3700_comphy_lane *lane; + struct phy *phy; + + if (WARN_ON(args->args[0] >= MVEBU_A3700_COMPHY_PORTS)) + return ERR_PTR(-EINVAL); + + phy = of_phy_simple_xlate(dev, args); + if (IS_ERR(phy)) + return phy; + + lane = phy_get_drvdata(phy); + lane->port = args->args[0]; + + return phy; +} + +static int mvebu_a3700_comphy_probe(struct platform_device *pdev) +{ + struct phy_provider *provider; + struct device_node *child; + + for_each_available_child_of_node(pdev->dev.of_node, child) { + struct mvebu_a3700_comphy_lane *lane; + struct phy *phy; + int ret; + u32 lane_id; + + ret = of_property_read_u32(child, "reg", &lane_id); + if (ret < 0) { + dev_err(&pdev->dev, "missing 'reg' property (%d)\n", + ret); + continue; + } + + if (lane_id >= MVEBU_A3700_COMPHY_LANES) { + dev_err(&pdev->dev, "invalid 'reg' property\n"); + continue; + } + + lane = devm_kzalloc(&pdev->dev, sizeof(*lane), GFP_KERNEL); + if (!lane) + return -ENOMEM; + + phy = devm_phy_create(&pdev->dev, child, + &mvebu_a3700_comphy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + lane->dev = &pdev->dev; + lane->mode = PHY_MODE_INVALID; + lane->submode = PHY_INTERFACE_MODE_NA; + lane->id = lane_id; + lane->port = -1; + phy_set_drvdata(phy, lane); + } + + provider = devm_of_phy_provider_register(&pdev->dev, + mvebu_a3700_comphy_xlate); + return PTR_ERR_OR_ZERO(provider); +} + +static const struct of_device_id mvebu_a3700_comphy_of_match_table[] = { + { .compatible = "marvell,comphy-a3700" }, + { }, +}; +MODULE_DEVICE_TABLE(of, mvebu_a3700_comphy_of_match_table); + +static struct platform_driver mvebu_a3700_comphy_driver = { + .probe = mvebu_a3700_comphy_probe, + .driver = { |
