// SPDX-License-Identifier: GPL-2.0
/*
* mtu3_core.c - hardware access layer and gadget init/exit of
* MediaTek usb3 Dual-Role Controller Driver
*
* Copyright (C) 2016 MediaTek Inc.
*
* Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
*/
#include <linux/dma-mapping.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include "mtu3.h"
#include "mtu3_dr.h"
#include "mtu3_debug.h"
#include "mtu3_trace.h"
static int ep_fifo_alloc(struct mtu3_ep *mep, u32 seg_size)
{
struct mtu3_fifo_info *fifo = mep->fifo;
u32 num_bits = DIV_ROUND_UP(seg_size, MTU3_EP_FIFO_UNIT);
u32 start_bit;
/* ensure that @mep->fifo_seg_size is power of two */
num_bits = roundup_pow_of_two(num_bits);
if (num_bits > fifo->limit)
return -EINVAL;
mep->fifo_seg_size = num_bits * MTU3_EP_FIFO_UNIT;
num_bits = num_bits * (mep->slot + 1);
start_bit = bitmap_find_next_zero_area(fifo->bitmap,
fifo->limit, 0, num_bits, 0);
if (start_bit >= fifo->limit)
return -EOVERFLOW;
bitmap_set(fifo->bitmap, start_bit, num_bits);
mep->fifo_size = num_bits * MTU3_EP_FIFO_UNIT;
mep->fifo_addr = fifo->base + MTU3_EP_FIFO_UNIT * start_bit;
dev_dbg(mep->mtu->dev, "%s fifo:%#x/%#x, start_bit: %d\n",
__func__, mep->fifo_seg_size, mep->fifo_size, start_bit);
return mep->fifo_addr;
}
static void ep_fifo_free(struct mtu3_ep *mep)
{
struct mtu3_fifo_info *fifo = mep->fifo;
u32 addr = mep->fifo_addr;
u32 bits = mep->fifo_size / MTU3_EP_FIFO_UNIT;
u32 start_bit;
if (unlikely(addr < fifo->base || bits > fifo->limit))
return;
start_bit = (addr - fifo->base) / MTU3_EP_FIFO_UNIT;
bitmap_clear(fifo->bitmap, start_bit, bits);
mep->fifo_size = 0;
mep->fifo_seg_size = 0;
dev_dbg(mep->mtu->dev, "%s size:%#x/%#x, start_bit: %d\n",
__func__, mep->fifo_seg_size, mep->fifo_size, start_bit);
}
/* enable/disable U3D SS function */
static inline void mtu3_ss_func_set(struct mtu3 *mtu, bool enable)
{
/* If usb3_en==0, LTSSM will go to SS.Disable state */
if (enable)
mtu3_setbits(mtu->mac_base, U3D_USB3_CONFIG, USB3_EN);
else
mtu3_clrbits(mtu->mac_base, U3D_USB3_CONFIG, USB3_EN);
dev_dbg(mtu->dev, "USB3_EN = %d\n", !!enable);
}
/* set/clear U3D HS device soft connect */
static inline void mtu3_hs_softconn_set(struct mtu3 *mtu, bool enable)
{
if (enable) {
mtu3_setbits(mtu->mac_base, U3D_POWER_MANAGEMENT,
SOFT_CONN | SUSPENDM_ENABLE);
} else {
mtu3_clrbits(mtu->mac_base, U3D_POWER_MANAGEMENT,
SOFT_CONN | SUSPENDM_ENABLE);
}
dev_dbg(mtu->dev, "SOFTCONN = %d\n", !!enable);
}
/* only port0 of U2/U3 supports device mode */
static int mtu3_device_enable(struct mtu3 *mtu)
{
void __iomem *ibase = mtu->ippc_base;
u32 check_clk = 0;
mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
if (mtu->u3_capable) {
check_clk = SSUSB_U3_MAC_RST_B_STS;
mtu3_clrbits(ibase, SSUSB_U3_CTRL(0),
(SSUSB_U3_PORT_DIS | SSUSB_U3_PORT_PDN |
SSUSB_U3_PORT_HOST_SEL));
}
mtu3_clrbits(ibase, SSUSB_U2_CTRL(0),
(SSUSB_U2_PORT_DIS | SSUSB_U2_PORT_PDN |
SSUSB_U2_PORT_HOST_SEL));
if (mtu->ssusb->dr_mode == USB_DR_MODE_OTG) {
mtu3_setbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_OTG_SEL);
if (mtu->u3_capable)
mtu3_setbits(ibase, SSUSB_U3_CTRL(0),
SSUSB_U3_PORT_DUAL_MODE);
}
return ssusb_check_clocks(mtu->ssusb, check_clk);
}
static void mtu3_device_disable(struct mtu3 *mtu)
{
void __iomem *ibase = mtu->ippc_base;
if (mtu->u3_capable)
mtu3_setbits(ibase, SSUSB_U3_CTRL(0),
(SSUSB_U3_PORT_DIS | SSUSB_U3_PORT_PDN));
mtu3_setbits(ibase, SSUSB_U2_CTRL(0),
SSUSB_U2_PORT_DIS | SSUSB_U2_PORT_PDN);
if (mtu->ssusb->dr_mode == USB_DR_MODE_OTG) {
mtu3_clrbits(ibase, SSUSB_U2_CTRL(0), SSUSB_U2_PORT_OTG_SEL);
if (mtu->u3_capable)
mtu3_clrbits(ibase, SSUSB_U3_CTRL(0),
SSUSB_U3_PORT_DUAL_MODE);
}
mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
}
static void mtu3_dev_power_on(struct mtu3 *mtu)
{
void __iomem *ibase = mtu->ippc_base;
mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL2, SSUSB_IP_DEV_PDN);
if (mtu->u3_capable)
mtu3_clrbits(ibase, SSUSB_U3_CTRL(0