xref: /openbmc/linux/drivers/usb/dwc3/ulpi.c (revision e5f4ca3f)
15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2fb678a5aSLee Jones /*
388bc9d19SHeikki Krogerus  * ulpi.c - DesignWare USB3 Controller's ULPI PHY interface
488bc9d19SHeikki Krogerus  *
588bc9d19SHeikki Krogerus  * Copyright (C) 2015 Intel Corporation
688bc9d19SHeikki Krogerus  *
788bc9d19SHeikki Krogerus  * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
888bc9d19SHeikki Krogerus  */
988bc9d19SHeikki Krogerus 
10fca3f138SSerge Semin #include <linux/delay.h>
11fca3f138SSerge Semin #include <linux/time64.h>
1288bc9d19SHeikki Krogerus #include <linux/ulpi/regs.h>
1388bc9d19SHeikki Krogerus 
1488bc9d19SHeikki Krogerus #include "core.h"
1588bc9d19SHeikki Krogerus #include "io.h"
1688bc9d19SHeikki Krogerus 
1788bc9d19SHeikki Krogerus #define DWC3_ULPI_ADDR(a) \
1888bc9d19SHeikki Krogerus 		((a >= ULPI_EXT_VENDOR_SPECIFIC) ? \
1988bc9d19SHeikki Krogerus 		DWC3_GUSB2PHYACC_ADDR(ULPI_ACCESS_EXTENDED) | \
2088bc9d19SHeikki Krogerus 		DWC3_GUSB2PHYACC_EXTEND_ADDR(a) : DWC3_GUSB2PHYACC_ADDR(a))
2188bc9d19SHeikki Krogerus 
22fca3f138SSerge Semin #define DWC3_ULPI_BASE_DELAY	DIV_ROUND_UP(NSEC_PER_SEC, 60000000L)
23fca3f138SSerge Semin 
dwc3_ulpi_busyloop(struct dwc3 * dwc,u8 addr,bool read)24fca3f138SSerge Semin static int dwc3_ulpi_busyloop(struct dwc3 *dwc, u8 addr, bool read)
2588bc9d19SHeikki Krogerus {
26fca3f138SSerge Semin 	unsigned long ns = 5L * DWC3_ULPI_BASE_DELAY;
27*e5f4ca3fSSerge Semin 	unsigned int count = 10000;
2888bc9d19SHeikki Krogerus 	u32 reg;
2988bc9d19SHeikki Krogerus 
30fca3f138SSerge Semin 	if (addr >= ULPI_EXT_VENDOR_SPECIFIC)
31fca3f138SSerge Semin 		ns += DWC3_ULPI_BASE_DELAY;
32fca3f138SSerge Semin 
33fca3f138SSerge Semin 	if (read)
34fca3f138SSerge Semin 		ns += DWC3_ULPI_BASE_DELAY;
35fca3f138SSerge Semin 
36*e5f4ca3fSSerge Semin 	reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
37*e5f4ca3fSSerge Semin 	if (reg & DWC3_GUSB2PHYCFG_SUSPHY)
38*e5f4ca3fSSerge Semin 		usleep_range(1000, 1200);
39*e5f4ca3fSSerge Semin 
4088bc9d19SHeikki Krogerus 	while (count--) {
41fca3f138SSerge Semin 		ndelay(ns);
4288bc9d19SHeikki Krogerus 		reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYACC(0));
43ce722da6SSerge Semin 		if (reg & DWC3_GUSB2PHYACC_DONE)
4488bc9d19SHeikki Krogerus 			return 0;
4588bc9d19SHeikki Krogerus 		cpu_relax();
4688bc9d19SHeikki Krogerus 	}
4788bc9d19SHeikki Krogerus 
4888bc9d19SHeikki Krogerus 	return -ETIMEDOUT;
4988bc9d19SHeikki Krogerus }
5088bc9d19SHeikki Krogerus 
dwc3_ulpi_read(struct device * dev,u8 addr)51b7cf1dc3STal Shorer static int dwc3_ulpi_read(struct device *dev, u8 addr)
5288bc9d19SHeikki Krogerus {
53b7cf1dc3STal Shorer 	struct dwc3 *dwc = dev_get_drvdata(dev);
5488bc9d19SHeikki Krogerus 	u32 reg;
5588bc9d19SHeikki Krogerus 	int ret;
5688bc9d19SHeikki Krogerus 
5788bc9d19SHeikki Krogerus 	reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr);
5888bc9d19SHeikki Krogerus 	dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg);
5988bc9d19SHeikki Krogerus 
60fca3f138SSerge Semin 	ret = dwc3_ulpi_busyloop(dwc, addr, true);
6188bc9d19SHeikki Krogerus 	if (ret)
6288bc9d19SHeikki Krogerus 		return ret;
6388bc9d19SHeikki Krogerus 
6488bc9d19SHeikki Krogerus 	reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYACC(0));
6588bc9d19SHeikki Krogerus 
6688bc9d19SHeikki Krogerus 	return DWC3_GUSB2PHYACC_DATA(reg);
6788bc9d19SHeikki Krogerus }
6888bc9d19SHeikki Krogerus 
dwc3_ulpi_write(struct device * dev,u8 addr,u8 val)69b7cf1dc3STal Shorer static int dwc3_ulpi_write(struct device *dev, u8 addr, u8 val)
7088bc9d19SHeikki Krogerus {
71b7cf1dc3STal Shorer 	struct dwc3 *dwc = dev_get_drvdata(dev);
7288bc9d19SHeikki Krogerus 	u32 reg;
7388bc9d19SHeikki Krogerus 
7488bc9d19SHeikki Krogerus 	reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr);
7588bc9d19SHeikki Krogerus 	reg |= DWC3_GUSB2PHYACC_WRITE | val;
7688bc9d19SHeikki Krogerus 	dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg);
7788bc9d19SHeikki Krogerus 
78fca3f138SSerge Semin 	return dwc3_ulpi_busyloop(dwc, addr, false);
7988bc9d19SHeikki Krogerus }
8088bc9d19SHeikki Krogerus 
81073c47acSTal Shorer static const struct ulpi_ops dwc3_ulpi_ops = {
82e6f74849STal Shorer 	.read = dwc3_ulpi_read,
83e6f74849STal Shorer 	.write = dwc3_ulpi_write,
8488bc9d19SHeikki Krogerus };
8588bc9d19SHeikki Krogerus 
dwc3_ulpi_init(struct dwc3 * dwc)8688bc9d19SHeikki Krogerus int dwc3_ulpi_init(struct dwc3 *dwc)
8788bc9d19SHeikki Krogerus {
8888bc9d19SHeikki Krogerus 	/* Register the interface */
8988bc9d19SHeikki Krogerus 	dwc->ulpi = ulpi_register_interface(dwc->dev, &dwc3_ulpi_ops);
9088bc9d19SHeikki Krogerus 	if (IS_ERR(dwc->ulpi)) {
9188bc9d19SHeikki Krogerus 		dev_err(dwc->dev, "failed to register ULPI interface");
9288bc9d19SHeikki Krogerus 		return PTR_ERR(dwc->ulpi);
9388bc9d19SHeikki Krogerus 	}
9488bc9d19SHeikki Krogerus 
9588bc9d19SHeikki Krogerus 	return 0;
9688bc9d19SHeikki Krogerus }
9788bc9d19SHeikki Krogerus 
dwc3_ulpi_exit(struct dwc3 * dwc)9888bc9d19SHeikki Krogerus void dwc3_ulpi_exit(struct dwc3 *dwc)
9988bc9d19SHeikki Krogerus {
10088bc9d19SHeikki Krogerus 	if (dwc->ulpi) {
10188bc9d19SHeikki Krogerus 		ulpi_unregister_interface(dwc->ulpi);
10288bc9d19SHeikki Krogerus 		dwc->ulpi = NULL;
10388bc9d19SHeikki Krogerus 	}
10488bc9d19SHeikki Krogerus }
105