mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 13:41:51 +00:00
phy: Add USB3 PHY support for Intel LGM SoC
Add support for USB PHY on Intel LGM SoC. Signed-off-by: Ramuthevar Vadivel Murugan <vadivel.muruganx.ramuthevar@linux.intel.com> Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de> Link: https://lore.kernel.org/r/20200828022312.52724-3-vadivel.muruganx.ramuthevar@linux.intel.com Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
parent
8a676e1be4
commit
1cce8f73a5
@ -49,6 +49,16 @@ config PHY_XGENE
|
||||
help
|
||||
This option enables support for APM X-Gene SoC multi-purpose PHY.
|
||||
|
||||
config USB_LGM_PHY
|
||||
tristate "INTEL Lightning Mountain USB PHY Driver"
|
||||
select USB_PHY
|
||||
select REGULATOR
|
||||
select REGULATOR_FIXED_VOLTAGE
|
||||
help
|
||||
Enable this to support Intel DWC3 PHY USB phy. This driver provides
|
||||
interface to interact with USB GEN-II and USB 3.x PHY that is part
|
||||
of the Intel network SOC.
|
||||
|
||||
source "drivers/phy/allwinner/Kconfig"
|
||||
source "drivers/phy/amlogic/Kconfig"
|
||||
source "drivers/phy/broadcom/Kconfig"
|
||||
|
@ -8,6 +8,7 @@ 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
|
||||
obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
|
||||
obj-y += allwinner/ \
|
||||
amlogic/ \
|
||||
broadcom/ \
|
||||
|
284
drivers/phy/phy-lgm-usb.c
Normal file
284
drivers/phy/phy-lgm-usb.c
Normal file
@ -0,0 +1,284 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Intel LGM USB PHY driver
|
||||
*
|
||||
* Copyright (C) 2020 Intel Corporation.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/usb/phy.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#define CTRL1_OFFSET 0x14
|
||||
#define SRAM_EXT_LD_DONE BIT(25)
|
||||
#define SRAM_INIT_DONE BIT(26)
|
||||
|
||||
#define TCPC_OFFSET 0x1014
|
||||
#define TCPC_MUX_CTL GENMASK(1, 0)
|
||||
#define MUX_NC 0
|
||||
#define MUX_USB 1
|
||||
#define MUX_DP 2
|
||||
#define MUX_USBDP 3
|
||||
#define TCPC_FLIPPED BIT(2)
|
||||
#define TCPC_LOW_POWER_EN BIT(3)
|
||||
#define TCPC_VALID BIT(4)
|
||||
#define TCPC_CONN \
|
||||
(TCPC_VALID | FIELD_PREP(TCPC_MUX_CTL, MUX_USB))
|
||||
#define TCPC_DISCONN \
|
||||
(TCPC_VALID | FIELD_PREP(TCPC_MUX_CTL, MUX_NC) | TCPC_LOW_POWER_EN)
|
||||
|
||||
static const char *const PHY_RESETS[] = { "phy31", "phy", };
|
||||
static const char *const CTL_RESETS[] = { "apb", "ctrl", };
|
||||
|
||||
struct tca_apb {
|
||||
struct reset_control *resets[ARRAY_SIZE(PHY_RESETS)];
|
||||
struct regulator *vbus;
|
||||
struct work_struct wk;
|
||||
struct usb_phy phy;
|
||||
|
||||
bool regulator_enabled;
|
||||
bool phy_initialized;
|
||||
bool connected;
|
||||
};
|
||||
|
||||
static int get_flipped(struct tca_apb *ta, bool *flipped)
|
||||
{
|
||||
union extcon_property_value property;
|
||||
int ret;
|
||||
|
||||
ret = extcon_get_property(ta->phy.edev, EXTCON_USB_HOST,
|
||||
EXTCON_PROP_USB_TYPEC_POLARITY, &property);
|
||||
if (ret) {
|
||||
dev_err(ta->phy.dev, "no polarity property from extcon\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*flipped = property.intval;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct tca_apb *ta = container_of(phy, struct tca_apb, phy);
|
||||
void __iomem *ctrl1 = phy->io_priv + CTRL1_OFFSET;
|
||||
int val, ret, i;
|
||||
|
||||
if (ta->phy_initialized)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++)
|
||||
reset_control_deassert(ta->resets[i]);
|
||||
|
||||
ret = readl_poll_timeout(ctrl1, val, val & SRAM_INIT_DONE, 10, 10 * 1000);
|
||||
if (ret) {
|
||||
dev_err(ta->phy.dev, "SRAM init failed, 0x%x\n", val);
|
||||
return ret;
|
||||
}
|
||||
|
||||
writel(readl(ctrl1) | SRAM_EXT_LD_DONE, ctrl1);
|
||||
|
||||
ta->phy_initialized = true;
|
||||
if (!ta->phy.edev) {
|
||||
writel(TCPC_CONN, ta->phy.io_priv + TCPC_OFFSET);
|
||||
return phy->set_vbus(phy, true);
|
||||
}
|
||||
|
||||
schedule_work(&ta->wk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void phy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct tca_apb *ta = container_of(phy, struct tca_apb, phy);
|
||||
int i;
|
||||
|
||||
if (!ta->phy_initialized)
|
||||
return;
|
||||
|
||||
ta->phy_initialized = false;
|
||||
flush_work(&ta->wk);
|
||||
ta->phy.set_vbus(&ta->phy, false);
|
||||
|
||||
ta->connected = false;
|
||||
writel(TCPC_DISCONN, ta->phy.io_priv + TCPC_OFFSET);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++)
|
||||
reset_control_assert(ta->resets[i]);
|
||||
}
|
||||
|
||||
static int phy_set_vbus(struct usb_phy *phy, int on)
|
||||
{
|
||||
struct tca_apb *ta = container_of(phy, struct tca_apb, phy);
|
||||
int ret;
|
||||
|
||||
if (!!on == ta->regulator_enabled)
|
||||
return 0;
|
||||
|
||||
if (on)
|
||||
ret = regulator_enable(ta->vbus);
|
||||
else
|
||||
ret = regulator_disable(ta->vbus);
|
||||
|
||||
if (!ret)
|
||||
ta->regulator_enabled = on;
|
||||
|
||||
dev_dbg(ta->phy.dev, "set vbus: %d\n", on);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tca_work(struct work_struct *work)
|
||||
{
|
||||
struct tca_apb *ta = container_of(work, struct tca_apb, wk);
|
||||
bool connected;
|
||||
bool flipped = false;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = get_flipped(ta, &flipped);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
connected = extcon_get_state(ta->phy.edev, EXTCON_USB_HOST);
|
||||
if (connected == ta->connected)
|
||||
return;
|
||||
|
||||
ta->connected = connected;
|
||||
if (connected) {
|
||||
val = TCPC_CONN;
|
||||
if (flipped)
|
||||
val |= TCPC_FLIPPED;
|
||||
dev_dbg(ta->phy.dev, "connected%s\n", flipped ? " flipped" : "");
|
||||
} else {
|
||||
val = TCPC_DISCONN;
|
||||
dev_dbg(ta->phy.dev, "disconnected\n");
|
||||
}
|
||||
|
||||
writel(val, ta->phy.io_priv + TCPC_OFFSET);
|
||||
|
||||
ret = ta->phy.set_vbus(&ta->phy, connected);
|
||||
if (ret)
|
||||
dev_err(ta->phy.dev, "failed to set VBUS\n");
|
||||
}
|
||||
|
||||
static int id_notifier(struct notifier_block *nb, unsigned long event, void *ptr)
|
||||
{
|
||||
struct tca_apb *ta = container_of(nb, struct tca_apb, phy.id_nb);
|
||||
|
||||
if (ta->phy_initialized)
|
||||
schedule_work(&ta->wk);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int vbus_notifier(struct notifier_block *nb, unsigned long evnt, void *ptr)
|
||||
{
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct reset_control *resets[ARRAY_SIZE(CTL_RESETS)];
|
||||
struct device *dev = &pdev->dev;
|
||||
struct usb_phy *phy;
|
||||
struct tca_apb *ta;
|
||||
int i;
|
||||
|
||||
ta = devm_kzalloc(dev, sizeof(*ta), GFP_KERNEL);
|
||||
if (!ta)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, ta);
|
||||
INIT_WORK(&ta->wk, tca_work);
|
||||
|
||||
phy = &ta->phy;
|
||||
phy->dev = dev;
|
||||
phy->label = dev_name(dev);
|
||||
phy->type = USB_PHY_TYPE_USB3;
|
||||
phy->init = phy_init;
|
||||
phy->shutdown = phy_shutdown;
|
||||
phy->set_vbus = phy_set_vbus;
|
||||
phy->id_nb.notifier_call = id_notifier;
|
||||
phy->vbus_nb.notifier_call = vbus_notifier;
|
||||
|
||||
phy->io_priv = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(phy->io_priv))
|
||||
return PTR_ERR(phy->io_priv);
|
||||
|
||||
ta->vbus = devm_regulator_get(dev, "vbus");
|
||||
if (IS_ERR(ta->vbus))
|
||||
return PTR_ERR(ta->vbus);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(CTL_RESETS); i++) {
|
||||
resets[i] = devm_reset_control_get_exclusive(dev, CTL_RESETS[i]);
|
||||
if (IS_ERR(resets[i])) {
|
||||
dev_err(dev, "%s reset not found\n", CTL_RESETS[i]);
|
||||
return PTR_ERR(resets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++) {
|
||||
ta->resets[i] = devm_reset_control_get_exclusive(dev, PHY_RESETS[i]);
|
||||
if (IS_ERR(ta->resets[i])) {
|
||||
dev_err(dev, "%s reset not found\n", PHY_RESETS[i]);
|
||||
return PTR_ERR(ta->resets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(CTL_RESETS); i++)
|
||||
reset_control_assert(resets[i]);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(PHY_RESETS); i++)
|
||||
reset_control_assert(ta->resets[i]);
|
||||
/*
|
||||
* Out-of-band reset of the controller after PHY reset will cause
|
||||
* controller malfunctioning, so we should use in-band controller
|
||||
* reset only and leave the controller de-asserted here.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(CTL_RESETS); i++)
|
||||
reset_control_deassert(resets[i]);
|
||||
|
||||
/* Need to wait at least 20us after de-assert the controller */
|
||||
usleep_range(20, 100);
|
||||
|
||||
return usb_add_phy_dev(phy);
|
||||
}
|
||||
|
||||
static int phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tca_apb *ta = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&ta->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id intel_usb_phy_dt_ids[] = {
|
||||
{ .compatible = "intel,lgm-usb-phy" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, intel_usb_phy_dt_ids);
|
||||
|
||||
static struct platform_driver lgm_phy_driver = {
|
||||
.driver = {
|
||||
.name = "lgm-usb-phy",
|
||||
.of_match_table = intel_usb_phy_dt_ids,
|
||||
},
|
||||
.probe = phy_probe,
|
||||
.remove = phy_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(lgm_phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Intel LGM USB PHY driver");
|
||||
MODULE_AUTHOR("Li Yin <yin1.li@intel.com>");
|
||||
MODULE_AUTHOR("Vadivel Murugan R <vadivel.muruganx.ramuthevar@linux.intel.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Reference in New Issue
Block a user