/* * Copyright (c) 2014-2016, 2020 The Linux Foundation. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB2PHY_PORT_POWERDOWN 0xA4 #define POWER_UP BIT(0) #define POWER_DOWN 0 #define USB2PHY_PORT_UTMI_CTRL1 0x40 #define USB2PHY_PORT_UTMI_CTRL2 0x44 #define UTMI_ULPI_SEL BIT(7) #define UTMI_TEST_MUX_SEL BIT(6) #define HS_PHY_CTRL_REG 0x10 #define UTMI_OTG_VBUS_VALID BIT(20) #define SW_SESSVLD_SEL BIT(28) #define USB_PHY_CFG0 0x94 #define USB_PHY_UTMI_CTRL5 0x50 #define USB_PHY_FSEL_SEL 0xB8 #define USB_PHY_HS_PHY_CTRL_COMMON0 0x54 #define USB_PHY_REFCLK_CTRL 0xA0 #define USB_PHY_HS_PHY_CTRL2 0x64 #define USB_PHY_UTMI_CTRL0 0x3c #define UTMI_PHY_OVERRIDE_EN BIT(1) #define POR_EN BIT(1) #define FREQ_SEL BIT(0) #define COMMONONN BIT(7) #define FSEL BIT(4) #define RETENABLEN BIT(3) #define USB2_SUSPEND_N_SEL BIT(3) #define USB2_SUSPEND_N BIT(2) #define USB2_UTMI_CLK_EN BIT(1) #define CLKCORE BIT(1) #define ATERESET ~BIT(0) static void m31usb_write_readback(void *base, u32 offset, const u32 mask, u32 val); struct m31usb_phy { struct usb_phy phy; void __iomem *base; void __iomem *qscratch_base; struct reset_control *phy_reset; bool cable_connected; bool suspended; bool ulpi_mode; }; static void m31usb_reset(struct m31usb_phy *qphy, u32 action) { if (action == CLK_RESET_ASSERT) reset_control_assert(qphy->phy_reset); else reset_control_deassert(qphy->phy_reset); wmb(); /* ensure data is written to hw register */ } static void m31usb_phy_enable_clock(struct m31usb_phy *qphy) { /* Enable override ctrl */ writel(UTMI_PHY_OVERRIDE_EN, qphy->base + USB_PHY_CFG0); /* Enable POR*/ writel(POR_EN, qphy->base + USB_PHY_UTMI_CTRL5); udelay(15); /* Configure frequency select value*/ writel(FREQ_SEL, qphy->base + USB_PHY_FSEL_SEL); /* Configure refclk frequency */ writel(COMMONONN | FSEL | RETENABLEN, qphy->base + USB_PHY_HS_PHY_CTRL_COMMON0); /* select refclk source */ writel(CLKCORE, qphy->base + USB_PHY_REFCLK_CTRL); /* select ATERESET*/ writel(POR_EN & ATERESET, qphy->base + USB_PHY_UTMI_CTRL5); writel(USB2_SUSPEND_N_SEL | USB2_SUSPEND_N | USB2_UTMI_CLK_EN, qphy->base + USB_PHY_HS_PHY_CTRL2); writel(0x0, qphy->base + USB_PHY_UTMI_CTRL5); writel(USB2_SUSPEND_N | USB2_UTMI_CLK_EN, qphy->base + USB_PHY_HS_PHY_CTRL2); /* Disable override ctrl */ writel(0x0, qphy->base + USB_PHY_CFG0); } static int m31usb_phy_init(struct usb_phy *phy) { struct m31usb_phy *qphy = container_of(phy, struct m31usb_phy, phy); dev_dbg(phy->dev, "%s\n", __func__); /* Perform phy reset */ m31usb_reset(qphy, CLK_RESET_ASSERT); usleep_range(1, 5); m31usb_reset(qphy, CLK_RESET_DEASSERT); /* configure for ULPI mode if requested */ if (qphy->ulpi_mode) writel_relaxed(0x0, qphy->base + USB2PHY_PORT_UTMI_CTRL2); /* Enable the PHY */ writel_relaxed(POWER_UP, qphy->base + USB2PHY_PORT_POWERDOWN); /* Make sure above write completed */ wmb(); /* Turn on phy ref clock*/ m31usb_phy_enable_clock(qphy); /* Set OTG VBUS Valid from HSPHY to controller */ m31usb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG, UTMI_OTG_VBUS_VALID, UTMI_OTG_VBUS_VALID); /* Indicate value is driven by UTMI_OTG_VBUS_VALID bit */ m31usb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG, SW_SESSVLD_SEL, SW_SESSVLD_SEL); return 0; } static void m31usb_phy_shutdown(struct usb_phy *phy) { struct m31usb_phy *qphy = container_of(phy, struct m31usb_phy, phy); dev_dbg(phy->dev, "%s\n", __func__); /* Disable the PHY */ writel_relaxed(POWER_DOWN, qphy->base + USB2PHY_PORT_POWERDOWN); /* Make sure above write completed */ wmb(); } static void m31usb_write_readback(void *base, u32 offset, const u32 mask, u32 val) { u32 write_val, tmp = readl_relaxed(base + offset); tmp &= ~mask; /* retain other bits */ write_val = tmp | val; writel_relaxed(write_val, base + offset); /* Make sure above write completed */ wmb(); /* Read back to see if val was written */ tmp = readl_relaxed(base + offset); tmp &= mask; /* clear other bits */ if (tmp != val) pr_err("%s: write: %x to QSCRATCH: %x FAILED\n", __func__, val, offset); } static int m31usb_phy_notify_connect(struct usb_phy *phy, enum usb_device_speed speed) { struct m31usb_phy *qphy = container_of(phy, struct m31usb_phy, phy); qphy->cable_connected = true; dev_dbg(phy->dev, " cable_connected=%d\n", qphy->cable_connected); /* Set OTG VBUS Valid from HSPHY to controller */ m31usb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG, UTMI_OTG_VBUS_VALID, UTMI_OTG_VBUS_VALID); /* Indicate value is driven by UTMI_OTG_VBUS_VALID bit */ m31usb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG, SW_SESSVLD_SEL, SW_SESSVLD_SEL); dev_dbg(phy->dev, "M31USB2 phy connect notification\n"); return 0; } static int m31usb_phy_notify_disconnect(struct usb_phy *phy, enum usb_device_speed speed) { struct m31usb_phy *qphy = container_of(phy, struct m31usb_phy, phy); qphy->cable_connected = false; dev_dbg(phy->dev, " cable_connected=%d\n", qphy->cable_connected); /* Set OTG VBUS Valid from HSPHY to controller */ m31usb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG, UTMI_OTG_VBUS_VALID, 0); /* Indicate value is driven by UTMI_OTG_VBUS_VALID bit */ m31usb_write_readback(qphy->qscratch_base, HS_PHY_CTRL_REG, SW_SESSVLD_SEL, 0); dev_dbg(phy->dev, "M31USB2 phy disconnect notification\n"); return 0; } static int m31usb_phy_probe(struct platform_device *pdev) { struct m31usb_phy *qphy; struct device *dev = &pdev->dev; struct resource *res; int ret; const char *phy_type; qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); if (!qphy) return -ENOMEM; qphy->phy.dev = dev; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "m31usb_phy_base"); qphy->base = devm_ioremap_resource(dev, res); if (IS_ERR(qphy->base)) return PTR_ERR(qphy->base); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qscratch_base"); if (res) { qphy->qscratch_base = devm_ioremap_nocache(dev, res->start, resource_size(res)); if (IS_ERR(qphy->qscratch_base)) { dev_dbg(dev, "couldn't ioremap qscratch_base\n"); qphy->qscratch_base = NULL; } } qphy->phy_reset = devm_reset_control_get(dev, "usb2_phy_reset"); if (IS_ERR(qphy->phy_reset)) return PTR_ERR(qphy->phy_reset); qphy->ulpi_mode = false; ret = of_property_read_string(dev->of_node, "phy_type", &phy_type); if (!ret) { if (!strcasecmp(phy_type, "ulpi")) qphy->ulpi_mode = true; } else { dev_err(dev, "error reading phy_type property\n"); return ret; } platform_set_drvdata(pdev, qphy); qphy->phy.label = "qca-m31usb-phy"; qphy->phy.init = m31usb_phy_init; qphy->phy.shutdown = m31usb_phy_shutdown; qphy->phy.type = USB_PHY_TYPE_USB2; if (qphy->qscratch_base) { qphy->phy.notify_connect = m31usb_phy_notify_connect; qphy->phy.notify_disconnect = m31usb_phy_notify_disconnect; } ret = usb_add_phy_dev(&qphy->phy); return ret; } static int m31usb_phy_remove(struct platform_device *pdev) { struct m31usb_phy *qphy = platform_get_drvdata(pdev); usb_remove_phy(&qphy->phy); return 0; } static const struct of_device_id m31usb_phy_id_table[] = { { .compatible = "qca,m31-usb-hsphy", }, { }, }; MODULE_DEVICE_TABLE(of, m31usb_phy_id_table); static struct platform_driver m31usb_phy_driver = { .probe = m31usb_phy_probe, .remove = m31usb_phy_remove, .driver = { .name = "qca-m31usb-phy", .of_match_table = of_match_ptr(m31usb_phy_id_table), }, }; module_platform_driver(m31usb_phy_driver); MODULE_DESCRIPTION("USB2 QCA M31 HSPHY driver"); MODULE_LICENSE("GPL v2");