#include <linux/of.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include "glue.h"
enum dwc3_apple_state {
DWC3_APPLE_PROBE_PENDING,
DWC3_APPLE_NO_CABLE,
DWC3_APPLE_HOST,
DWC3_APPLE_DEVICE,
};
struct dwc3_apple {
struct dwc3 dwc;
struct device *dev;
struct resource *mmio_resource;
void __iomem *apple_regs;
struct reset_control *reset;
struct usb_role_switch *role_sw;
struct mutex lock;
enum dwc3_apple_state state;
};
#define to_dwc3_apple(d) container_of((d), struct dwc3_apple, dwc)
#define APPLE_DWC3_REGS_START 0xcd00
#define APPLE_DWC3_REGS_END 0xcdff
#define APPLE_DWC3_CIO_LFPS_OFFSET 0xcd38
#define APPLE_DWC3_CIO_LFPS_OFFSET_VALUE 0xf800f80
#define APPLE_DWC3_CIO_BW_NGT_OFFSET 0xcd3c
#define APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE 0xfc00fc0
#define APPLE_DWC3_CIO_LINK_TIMER 0xcd40
#define APPLE_DWC3_CIO_PENDING_HP_TIMER GENMASK(23, 16)
#define APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE 0x14
#define APPLE_DWC3_CIO_PM_LC_TIMER GENMASK(15, 8)
#define APPLE_DWC3_CIO_PM_LC_TIMER_VALUE 0xa
#define APPLE_DWC3_CIO_PM_ENTRY_TIMER GENMASK(7, 0)
#define APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE 0x10
static inline void dwc3_apple_writel(struct dwc3_apple *appledwc, u32 offset, u32 value)
{
writel(value, appledwc->apple_regs + offset - APPLE_DWC3_REGS_START);
}
static inline u32 dwc3_apple_readl(struct dwc3_apple *appledwc, u32 offset)
{
return readl(appledwc->apple_regs + offset - APPLE_DWC3_REGS_START);
}
static inline void dwc3_apple_mask(struct dwc3_apple *appledwc, u32 offset, u32 mask, u32 value)
{
u32 reg;
reg = dwc3_apple_readl(appledwc, offset);
reg &= ~mask;
reg |= value;
dwc3_apple_writel(appledwc, offset, reg);
}
static void dwc3_apple_setup_cio(struct dwc3_apple *appledwc)
{
dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_LFPS_OFFSET, APPLE_DWC3_CIO_LFPS_OFFSET_VALUE);
dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_BW_NGT_OFFSET,
APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE);
dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PENDING_HP_TIMER,
FIELD_PREP(APPLE_DWC3_CIO_PENDING_HP_TIMER,
APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE));
dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER,
FIELD_PREP(APPLE_DWC3_CIO_PM_LC_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER_VALUE));
dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_ENTRY_TIMER,
FIELD_PREP(APPLE_DWC3_CIO_PM_ENTRY_TIMER,
APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE));
}
static void dwc3_apple_set_ptrcap(struct dwc3_apple *appledwc, u32 mode)
{
guard(spinlock_irqsave)(&appledwc->dwc.lock);
dwc3_set_prtcap(&appledwc->dwc, mode, false);
}
static int dwc3_apple_core_probe(struct dwc3_apple *appledwc)
{
struct dwc3_probe_data probe_data = {};
int ret;
lockdep_assert_held(&appledwc->lock);
WARN_ON_ONCE(appledwc->state != DWC3_APPLE_PROBE_PENDING);
appledwc->dwc.dev = appledwc->dev;
probe_data.dwc = &appledwc->dwc;
probe_data.res = appledwc->mmio_resource;
probe_data.ignore_clocks_and_resets = true;
probe_data.skip_core_init_mode = true;
probe_data.properties = DWC3_DEFAULT_PROPERTIES;
ret = dwc3_core_probe(&probe_data);
if (ret)
return ret;
appledwc->state = DWC3_APPLE_NO_CABLE;
return 0;
}
static int dwc3_apple_core_init(struct dwc3_apple *appledwc)
{
int ret;
lockdep_assert_held(&appledwc->lock);
switch (appledwc->state) {
case DWC3_APPLE_PROBE_PENDING:
ret = dwc3_apple_core_probe(appledwc);
if (ret)
dev_err(appledwc->dev, "Failed to probe DWC3 Core, err=%d\n", ret);
break;
case DWC3_APPLE_NO_CABLE:
ret = dwc3_core_init(&appledwc->dwc);
if (ret)
dev_err(appledwc->dev, "Failed to initialize DWC3 Core, err=%d\n", ret);
break;
default:
WARN_ON_ONCE(1);
ret = -EINVAL;
break;
}
return ret;
}
static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state state)
{
int ret, ret_reset;
lockdep_assert_held(&appledwc->lock);
switch (state) {
case DWC3_APPLE_HOST:
phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_HOST);
break;
case DWC3_APPLE_DEVICE:
phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_DEVICE);
break;
default:
return -EINVAL;
}
ret = reset_control_deassert(appledwc->reset);
if (ret) {
dev_err(appledwc->dev, "Failed to deassert reset, err=%d\n", ret);
return ret;
}
ret = dwc3_apple_core_init(appledwc);
if (ret)
goto reset_assert;
dwc3_apple_setup_cio(appledwc);
switch (state) {
case DWC3_APPLE_HOST:
appledwc->dwc.dr_mode = USB_DR_MODE_HOST;
dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_HOST);
dwc3_enable_susphy(&appledwc->dwc, true);
phy_set_mode(appledwc->dwc.usb3_generic_phy[0], PHY_MODE_USB_HOST);
ret = dwc3_host_init(&appledwc->dwc);
if (ret) {
dev_err(appledwc->dev, "Failed to initialize host, ret=%d\n", ret);
goto core_exit;
}
break;
case DWC3_APPLE_DEVICE:
appledwc->dwc.dr_mode = USB_DR_MODE_PERIPHERAL;
dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_DEVICE);
dwc3_enable_susphy(&appledwc->dwc, true);
phy_set_mode(appledwc->dwc.usb3_generic_phy[0], PHY_MODE_USB_DEVICE);
ret = dwc3_gadget_init(&appledwc->dwc);
if (ret) {
dev_err(appledwc->dev, "Failed to initialize gadget, ret=%d\n", ret);
goto core_exit;
}
break;
default:
WARN_ON_ONCE(1);
ret = -EINVAL;
goto core_exit;
}
appledwc->state = state;
return 0;
core_exit:
dwc3_core_exit(&appledwc->dwc);
reset_assert:
ret_reset = reset_control_assert(appledwc->reset);
if (ret_reset)
dev_warn(appledwc->dev, "Failed to assert reset, err=%d\n", ret_reset);
return ret;
}
static int dwc3_apple_exit(struct dwc3_apple *appledwc)
{
int ret = 0;
lockdep_assert_held(&appledwc->lock);
switch (appledwc->state) {
case DWC3_APPLE_PROBE_PENDING:
case DWC3_APPLE_NO_CABLE:
return 0;
case DWC3_APPLE_DEVICE:
dwc3_gadget_exit(&appledwc->dwc);
break;
case DWC3_APPLE_HOST:
dwc3_host_exit(&appledwc->dwc);
break;
}
dwc3_enable_susphy(&appledwc->dwc, true);
dwc3_core_exit(&appledwc->dwc);
appledwc->state = DWC3_APPLE_NO_CABLE;
ret = reset_control_assert(appledwc->reset);
if (ret) {
dev_err(appledwc->dev, "Failed to assert reset, err=%d\n", ret);
return ret;
}
return 0;
}
static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
{
struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw);
int ret;
guard(mutex)(&appledwc->lock);
if (appledwc->state == DWC3_APPLE_HOST && role == USB_ROLE_HOST)
return 0;
if (appledwc->state == DWC3_APPLE_DEVICE && role == USB_ROLE_DEVICE)
return 0;
ret = dwc3_apple_exit(appledwc);
if (ret)
return ret;
switch (role) {
case USB_ROLE_NONE:
return 0;
case USB_ROLE_HOST:
return dwc3_apple_init(appledwc, DWC3_APPLE_HOST);
case USB_ROLE_DEVICE:
return dwc3_apple_init(appledwc, DWC3_APPLE_DEVICE);
default:
dev_err(appledwc->dev, "Invalid target role: %d\n", role);
return -EINVAL;
}
}
static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
{
struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw);
guard(mutex)(&appledwc->lock);
switch (appledwc->state) {
case DWC3_APPLE_HOST:
return USB_ROLE_HOST;
case DWC3_APPLE_DEVICE:
return USB_ROLE_DEVICE;
case DWC3_APPLE_NO_CABLE:
case DWC3_APPLE_PROBE_PENDING:
return USB_ROLE_NONE;
default:
dev_err(appledwc->dev, "Invalid internal state: %d\n", appledwc->state);
return USB_ROLE_NONE;
}
}
static int dwc3_apple_setup_role_switch(struct dwc3_apple *appledwc)
{
struct usb_role_switch_desc dwc3_role_switch = { NULL };
dwc3_role_switch.fwnode = dev_fwnode(appledwc->dev);
dwc3_role_switch.set = dwc3_usb_role_switch_set;
dwc3_role_switch.get = dwc3_usb_role_switch_get;
dwc3_role_switch.driver_data = appledwc;
appledwc->role_sw = usb_role_switch_register(appledwc->dev, &dwc3_role_switch);
if (IS_ERR(appledwc->role_sw))
return PTR_ERR(appledwc->role_sw);
return 0;
}
static int dwc3_apple_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dwc3_apple *appledwc;
int ret;
appledwc = devm_kzalloc(&pdev->dev, sizeof(*appledwc), GFP_KERNEL);
if (!appledwc)
return -ENOMEM;
appledwc->dev = &pdev->dev;
mutex_init(&appledwc->lock);
appledwc->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(appledwc->reset))
return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->reset),
"Failed to get reset control\n");
ret = reset_control_assert(appledwc->reset);
if (ret) {
dev_err(&pdev->dev, "Failed to assert reset, err=%d\n", ret);
return ret;
}
appledwc->mmio_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dwc3-core");
if (!appledwc->mmio_resource) {
dev_err(dev, "Failed to get DWC3 MMIO\n");
return -EINVAL;
}
appledwc->apple_regs = devm_platform_ioremap_resource_byname(pdev, "dwc3-apple");
if (IS_ERR(appledwc->apple_regs))
return dev_err_probe(dev, PTR_ERR(appledwc->apple_regs),
"Failed to map Apple-specific MMIO\n");
appledwc->state = DWC3_APPLE_PROBE_PENDING;
ret = dwc3_apple_setup_role_switch(appledwc);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to setup role switch\n");
return 0;
}
static void dwc3_apple_remove(struct platform_device *pdev)
{
struct dwc3 *dwc = platform_get_drvdata(pdev);
struct dwc3_apple *appledwc = to_dwc3_apple(dwc);
guard(mutex)(&appledwc->lock);
usb_role_switch_unregister(appledwc->role_sw);
dwc3_apple_exit(appledwc);
if (appledwc->state != DWC3_APPLE_PROBE_PENDING)
dwc3_core_remove(&appledwc->dwc);
}
static const struct of_device_id dwc3_apple_of_match[] = {
{ .compatible = "apple,t8103-dwc3" },
{}
};
MODULE_DEVICE_TABLE(of, dwc3_apple_of_match);
static struct platform_driver dwc3_apple_driver = {
.probe = dwc3_apple_probe,
.remove = dwc3_apple_remove,
.driver = {
.name = "dwc3-apple",
.of_match_table = dwc3_apple_of_match,
},
};
module_platform_driver(dwc3_apple_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sven Peter <sven@kernel.org>");
MODULE_DESCRIPTION("DesignWare DWC3 Apple Silicon Glue Driver");