xref: /openbmc/linux/drivers/usb/dwc3/ulpi.c (revision fca3f138)
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 
10*fca3f138SSerge Semin #include <linux/delay.h>
11*fca3f138SSerge 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 
22*fca3f138SSerge Semin #define DWC3_ULPI_BASE_DELAY	DIV_ROUND_UP(NSEC_PER_SEC, 60000000L)
23*fca3f138SSerge Semin 
24*fca3f138SSerge Semin static int dwc3_ulpi_busyloop(struct dwc3 *dwc, u8 addr, bool read)
2588bc9d19SHeikki Krogerus {
26*fca3f138SSerge Semin 	unsigned long ns = 5L * DWC3_ULPI_BASE_DELAY;
272a499b45SFelipe Balbi 	unsigned int count = 1000;
2888bc9d19SHeikki Krogerus 	u32 reg;
2988bc9d19SHeikki Krogerus 
30*fca3f138SSerge Semin 	if (addr >= ULPI_EXT_VENDOR_SPECIFIC)
31*fca3f138SSerge Semin 		ns += DWC3_ULPI_BASE_DELAY;
32*fca3f138SSerge Semin 
33*fca3f138SSerge Semin 	if (read)
34*fca3f138SSerge Semin 		ns += DWC3_ULPI_BASE_DELAY;
35*fca3f138SSerge Semin 
3688bc9d19SHeikki Krogerus 	while (count--) {
37*fca3f138SSerge Semin 		ndelay(ns);
3888bc9d19SHeikki Krogerus 		reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYACC(0));
39ce722da6SSerge Semin 		if (reg & DWC3_GUSB2PHYACC_DONE)
4088bc9d19SHeikki Krogerus 			return 0;
4188bc9d19SHeikki Krogerus 		cpu_relax();
4288bc9d19SHeikki Krogerus 	}
4388bc9d19SHeikki Krogerus 
4488bc9d19SHeikki Krogerus 	return -ETIMEDOUT;
4588bc9d19SHeikki Krogerus }
4688bc9d19SHeikki Krogerus 
47b7cf1dc3STal Shorer static int dwc3_ulpi_read(struct device *dev, u8 addr)
4888bc9d19SHeikki Krogerus {
49b7cf1dc3STal Shorer 	struct dwc3 *dwc = dev_get_drvdata(dev);
5088bc9d19SHeikki Krogerus 	u32 reg;
5188bc9d19SHeikki Krogerus 	int ret;
5288bc9d19SHeikki Krogerus 
53e0082698SFelipe Balbi 	reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
54e0082698SFelipe Balbi 	if (reg & DWC3_GUSB2PHYCFG_SUSPHY) {
55e0082698SFelipe Balbi 		reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
56e0082698SFelipe Balbi 		dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
57e0082698SFelipe Balbi 	}
58e0082698SFelipe Balbi 
5988bc9d19SHeikki Krogerus 	reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr);
6088bc9d19SHeikki Krogerus 	dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg);
6188bc9d19SHeikki Krogerus 
62*fca3f138SSerge Semin 	ret = dwc3_ulpi_busyloop(dwc, addr, true);
6388bc9d19SHeikki Krogerus 	if (ret)
6488bc9d19SHeikki Krogerus 		return ret;
6588bc9d19SHeikki Krogerus 
6688bc9d19SHeikki Krogerus 	reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYACC(0));
6788bc9d19SHeikki Krogerus 
6888bc9d19SHeikki Krogerus 	return DWC3_GUSB2PHYACC_DATA(reg);
6988bc9d19SHeikki Krogerus }
7088bc9d19SHeikki Krogerus 
71b7cf1dc3STal Shorer static int dwc3_ulpi_write(struct device *dev, u8 addr, u8 val)
7288bc9d19SHeikki Krogerus {
73b7cf1dc3STal Shorer 	struct dwc3 *dwc = dev_get_drvdata(dev);
7488bc9d19SHeikki Krogerus 	u32 reg;
7588bc9d19SHeikki Krogerus 
76e0082698SFelipe Balbi 	reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
77e0082698SFelipe Balbi 	if (reg & DWC3_GUSB2PHYCFG_SUSPHY) {
78e0082698SFelipe Balbi 		reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
79e0082698SFelipe Balbi 		dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
80e0082698SFelipe Balbi 	}
81e0082698SFelipe Balbi 
8288bc9d19SHeikki Krogerus 	reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr);
8388bc9d19SHeikki Krogerus 	reg |= DWC3_GUSB2PHYACC_WRITE | val;
8488bc9d19SHeikki Krogerus 	dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg);
8588bc9d19SHeikki Krogerus 
86*fca3f138SSerge Semin 	return dwc3_ulpi_busyloop(dwc, addr, false);
8788bc9d19SHeikki Krogerus }
8888bc9d19SHeikki Krogerus 
89073c47acSTal Shorer static const struct ulpi_ops dwc3_ulpi_ops = {
90e6f74849STal Shorer 	.read = dwc3_ulpi_read,
91e6f74849STal Shorer 	.write = dwc3_ulpi_write,
9288bc9d19SHeikki Krogerus };
9388bc9d19SHeikki Krogerus 
9488bc9d19SHeikki Krogerus int dwc3_ulpi_init(struct dwc3 *dwc)
9588bc9d19SHeikki Krogerus {
9688bc9d19SHeikki Krogerus 	/* Register the interface */
9788bc9d19SHeikki Krogerus 	dwc->ulpi = ulpi_register_interface(dwc->dev, &dwc3_ulpi_ops);
9888bc9d19SHeikki Krogerus 	if (IS_ERR(dwc->ulpi)) {
9988bc9d19SHeikki Krogerus 		dev_err(dwc->dev, "failed to register ULPI interface");
10088bc9d19SHeikki Krogerus 		return PTR_ERR(dwc->ulpi);
10188bc9d19SHeikki Krogerus 	}
10288bc9d19SHeikki Krogerus 
10388bc9d19SHeikki Krogerus 	return 0;
10488bc9d19SHeikki Krogerus }
10588bc9d19SHeikki Krogerus 
10688bc9d19SHeikki Krogerus void dwc3_ulpi_exit(struct dwc3 *dwc)
10788bc9d19SHeikki Krogerus {
10888bc9d19SHeikki Krogerus 	if (dwc->ulpi) {
10988bc9d19SHeikki Krogerus 		ulpi_unregister_interface(dwc->ulpi);
11088bc9d19SHeikki Krogerus 		dwc->ulpi = NULL;
11188bc9d19SHeikki Krogerus 	}
11288bc9d19SHeikki Krogerus }
113