From: Rafał Miłecki <rafal@xxxxxxxxxx> This driver initializes BCM4908 USB PHYs so USB can be utilized. Signed-off-by: Rafał Miłecki <rafal@xxxxxxxxxx> --- drivers/phy/broadcom/Kconfig | 9 ++ drivers/phy/broadcom/Makefile | 1 + drivers/phy/broadcom/phy-bcm4908-usb.c | 204 +++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 drivers/phy/broadcom/phy-bcm4908-usb.c diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig index a1f1a9c90d0d..ff2ca2b77f38 100644 --- a/drivers/phy/broadcom/Kconfig +++ b/drivers/phy/broadcom/Kconfig @@ -78,6 +78,15 @@ config PHY_NS2_USB_DRD If unsure, say N. +config PHY_BCM4908_USB + tristate "Broadcom BCM4908 USB PHYs support" + depends on OF && (ARCH_BCM4908 || COMPILE_TEST) + select GENERIC_PHY + default ARCH_BCM4908 + help + Enable this to support the Broadcom BCM4908 USB 2.0 PHY and USB 3.0 + PHY. This driver initializes PHYs so USB can be utilized. + config PHY_BRCM_SATA tristate "Broadcom SATA PHY driver" depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || \ diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile index 7024127f86ad..8e6a9b748421 100644 --- a/drivers/phy/broadcom/Makefile +++ b/drivers/phy/broadcom/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o obj-$(CONFIG_PHY_NS2_USB_DRD) += phy-bcm-ns2-usbdrd.o +obj-$(CONFIG_PHY_BCM4908_USB) += phy-bcm4908-usb.o obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o obj-$(CONFIG_PHY_BRCM_USB) += phy-brcm-usb-dvr.o diff --git a/drivers/phy/broadcom/phy-bcm4908-usb.c b/drivers/phy/broadcom/phy-bcm4908-usb.c new file mode 100644 index 000000000000..bf3803a11710 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm4908-usb.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013 Broadcom + * Copyright (C) 2020 Rafał Miłecki <rafal@xxxxxxxxxx> + */ + +#include <linux/delay.h> +#include <linux/mdio.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> + +enum bcm4908_usb_ver { + BCM4908_UNKNOWN, + BCM4908_USB_2, + BCM4908_USB_3, +}; + +struct bcm4908_usb { + struct mdio_device *mdiodev; + struct device *dev; + struct phy *phy; + enum bcm4908_usb_ver ver; +}; + +static int bcm4908_usb_mdio_phy_read(struct bcm4908_usb *usb, u16 reg) +{ + struct mdio_device *mdiodev = usb->mdiodev; + + return mdiobus_read(mdiodev->bus, mdiodev->addr, reg); +} + +static int bcm4908_usb_mdio_phy_write(struct bcm4908_usb *usb, u16 reg, + u16 value) +{ + struct mdio_device *mdiodev = usb->mdiodev; + + return mdiobus_write(mdiodev->bus, mdiodev->addr, reg, value); +} + +static int bcm4908_usb_2_init(struct bcm4908_usb *usb) +{ + int err; + + /* Eye fix */ + + err = bcm4908_usb_mdio_phy_write(usb, 0x1f, 0x80a0); + if (err < 0) + return err; + + err = bcm4908_usb_mdio_phy_write(usb, 0x0a, 0xc6a0); + if (err < 0) + return err; + + return 0; +} + +static void bcm4908_usb_3_ssc_enable(struct bcm4908_usb *usb) +{ + u32 val; + + /* Enable USB 3.0 TX spread spectrum */ + bcm4908_usb_mdio_phy_write(usb, 0x1f, 0x8040); + + val = bcm4908_usb_mdio_phy_read(usb, 0x01); + val |= 0x0f; + bcm4908_usb_mdio_phy_write(usb, 0x01, val); + + bcm4908_usb_mdio_phy_write(usb, 0x1f, 0x9040); + + val = bcm4908_usb_mdio_phy_read(usb, 0x01); + val |= 0x0f; + bcm4908_usb_mdio_phy_write(usb, 0x01, val); +} + +static void bcm4908_usb_3_enable_pipe_reset(struct bcm4908_usb *usb) +{ + u32 val; + + /* Re-enable USB 3.0 pipe reset */ + bcm4908_usb_mdio_phy_write(usb, 0x1f, 0x8000); + + val = bcm4908_usb_mdio_phy_read(usb, 0x0f); + val |= 0x200; + bcm4908_usb_mdio_phy_write(usb, 0x0f, val); +} + + +static void bcm4908_usb_3_enable_sigdet(struct bcm4908_usb *usb) +{ + u32 val; + int i; + + for (i = 0; i < 2; i++) { + u32 offset = i * 0x1000; + + /* Set correct default for sigdet */ + bcm4908_usb_mdio_phy_write(usb, 0x1f, 0x8080 + offset); + + val = bcm4908_usb_mdio_phy_read(usb, 0x05); + val = (val & ~0x800f) | 0x800d; + bcm4908_usb_mdio_phy_write(usb, 0x05, val); + } +} + + +static void bcm4908_usb_3_enable_skip_align(struct bcm4908_usb *usb) +{ + u32 val; + int i; + + for (i = 0; i < 2; i++) { + u32 offset = i * 0x1000; + + /* Set correct default for SKIP align */ + bcm4908_usb_mdio_phy_write(usb, 0x1f, 0x8060 + offset); + + val = bcm4908_usb_mdio_phy_read(usb, 0x01); + val |= 0x200; + bcm4908_usb_mdio_phy_write(usb, 0x01, val); + } +} + +static int bcm4908_usb_3_init(struct bcm4908_usb *usb) +{ + struct device *dev = usb->dev; + + bcm4908_usb_3_ssc_enable(usb); + bcm4908_usb_3_enable_pipe_reset(usb); + bcm4908_usb_3_enable_sigdet(usb); + bcm4908_usb_3_enable_skip_align(usb); + + mdelay(300); + + return 0; +} + +static int bcm4908_usb_init(struct phy *phy) +{ + struct bcm4908_usb *usb = phy_get_drvdata(phy); + + switch (usb->ver) { + case BCM4908_USB_2: + return bcm4908_usb_2_init(usb); + case BCM4908_USB_3: + return bcm4908_usb_3_init(usb); + default: + WARN_ON(1); + return -EINVAL; + } +} + +static const struct phy_ops bcm4908_usb_phy_ops = { + .init = bcm4908_usb_init, + .owner = THIS_MODULE, +}; + +static int bcm4908_usb_probe(struct mdio_device *mdiodev) +{ + struct device *dev = &mdiodev->dev; + struct phy_provider *phy_provider; + struct bcm4908_usb *usb; + + usb = devm_kzalloc(dev, sizeof(*usb), GFP_KERNEL); + if (!usb) + return -ENOMEM; + + usb->dev = dev; + usb->mdiodev = mdiodev; + usb->ver = (uintptr_t)of_device_get_match_data(dev); + + usb->phy = devm_phy_create(dev, NULL, &bcm4908_usb_phy_ops); + if (IS_ERR(usb->phy)) { + dev_err(dev, "Failed to create PHY\n"); + return PTR_ERR(usb->phy); + } + + phy_set_drvdata(usb->phy, usb); + + 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 bcm4908_usb_id_table[] = { + { .compatible = "brcm,bcm4908-usb2-phy", .data = (void *)BCM4908_USB_2 }, + { .compatible = "brcm,bcm4908-usb3-phy", .data = (void *)BCM4908_USB_3 }, + {}, +}; + +static struct mdio_driver bcm4908_usb_mdio_driver = { + .mdiodrv = { + .driver = { + .name = "bcm4908_usb", + .of_match_table = bcm4908_usb_id_table, + }, + }, + .probe = bcm4908_usb_probe, +}; + +mdio_module_driver(bcm4908_usb_mdio_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DEVICE_TABLE(of, bcm4908_usb_id_table); -- 2.26.2