// SPDX-License-Identifier: GPL-2.0
/*
* core.c - ChipIdea USB IP core family device controller
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
* Copyright (C) 2020 NXP
*
* Author: David Lopo
* Peter Chen <peter.chen@nxp.com>
*
* Main Features:
* - Four transfers are supported, usbtest is passed
* - USB Certification for gadget: CH9 and Mass Storage are passed
* - Low power mode
* - USB wakeup
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/extcon.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/pinctrl/consumer.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
#include <linux/usb/chipidea.h>
#include <linux/usb/of.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/ehci_def.h>
#include "ci.h"
#include "udc.h"
#include "bits.h"
#include "host.h"
#include "otg.h"
#include "otg_fsm.h"
/* Controller register map */
static const u8 ci_regs_nolpm[] = {
[CAP_CAPLENGTH] = 0x00U,
[CAP_HCCPARAMS] = 0x08U,
[CAP_DCCPARAMS] = 0x24U,
[CAP_TESTMODE] = 0x38U,
[OP_USBCMD] = 0x00U,
[OP_USBSTS] = 0x04U,
[OP_USBINTR] = 0x08U,
[OP_FRINDEX] = 0x0CU,
[OP_DEVICEADDR] = 0x14U,
[OP_ENDPTLISTADDR] = 0x18U,
[OP_TTCTRL] = 0x1CU,
[OP_BURSTSIZE] = 0x20U,
[OP_ULPI_VIEWPORT] = 0x30U,
[OP_PORTSC] = 0x44U,
[OP_DEVLC] = 0x84U,
[OP_OTGSC] = 0x64U,
[OP_USBMODE] = 0x68U,
[OP_ENDPTSETUPSTAT] = 0x6CU,
[OP_ENDPTPRIME] = 0x70U,
[OP_ENDPTFLUSH] = 0x74U,
[OP_ENDPTSTAT] = 0x78U,
[OP_ENDPTCOMPLETE] = 0x7CU,
[OP_ENDPTCTRL] = 0x80U,
};
static const u8 ci_regs_lpm[] = {
[CAP_CAPLENGTH] = 0x00U,
[CAP_HCCPARAMS] = 0x08U,
[CAP_DCCPARAMS] = 0x24U,
[CAP_TESTMODE] = 0xFCU,
[OP_USBCMD] = 0x00U,
[OP_USBSTS] = 0x04U,
[OP_USBINTR] = 0x08U,
[OP_FRINDEX] = 0x0CU,
[OP_DEVICEADDR] = 0x14U,
[OP_ENDPTLISTADDR] = 0x18U,
[OP_TTCTRL] = 0x1CU,
[OP_BURSTSIZE] = 0x20U,
[OP_ULPI_VIEWPORT] = 0x30U,
[OP_PORTSC] = 0x44U,
[OP_DEVLC] = 0x84U,
[OP_OTGSC] = 0xC4U,
[OP_USBMODE] = 0xC8U,
[OP_ENDPTSETUPSTAT] = 0xD8U,
[OP_ENDPTPRIME] = 0xDCU,
[OP_ENDPTFLUSH] = 0xE0U,
[OP_ENDPTSTAT] = 0xE4U,
[OP_ENDPTCOMPLETE] = 0xE8U,
[OP_ENDPTCTRL] = 0xECU,
};
static void hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm)
{
int i;
for (i = 0; i < OP_ENDPTCTRL; i++)
ci->hw_bank.regmap[i] =
(i <= CAP_LAST ? ci->hw_bank.cap : ci->hw_bank.op) +
(is_lpm ? ci_regs_lpm[i] : ci_regs_nolpm[i]);
for (; i <= OP_LAST; i++)
ci->hw_bank.regmap[i] = ci->hw_bank.op +
4 * (i - OP_ENDPTCTRL) +
(is_lpm
? ci_regs_lpm[OP_ENDPTCTRL]
: ci_regs_nolpm[OP_ENDPTCTRL]);
}
static enum ci_revision ci_get_revision(struct ci_hdrc *ci)
{
int ver = hw_read_id_reg(ci, ID_ID, VERSION) >> __ffs(VERSION);
enum ci_revision rev = CI_REVISION_UNKNOWN;
if (ver == 0x2) {
rev = hw_read_id_reg(ci, ID_ID, REVISION)
>> __ffs(REVISION);
rev += CI_REVISION_20;
} else if (ver == 0x0) {
rev = CI_REVISION_1X;
}
return rev;
}
/**
* hw_read_intr_enable: returns interrupt enable register
*
* @ci: the controller
*
* This function returns register data
*/
u32 hw_read_intr_enable(struct ci_hdrc *ci)
{
return hw_read(ci, OP_USBINTR, ~0);
}
/**
* hw_read_intr_status: returns interrupt status register
*
* @ci: the controller
*
* This function returns register data
*/
u32 hw_read_intr_status(struct ci_hdrc *ci)
{
return hw_read(ci, OP_USBSTS, ~0);
}
/**
* hw_port_test_set: writes port test mode (execute without interruption)
* @ci: the controller
* @mode: new value
*
* This function returns an error code
*/
int hw_port_test_set(struct ci_hdrc *ci, u8 mode)
{
const u8 TEST_MODE_MAX = 7;
if (mode > TEST_MODE_MAX)
return -EINVAL;
hw_write(ci, OP_PORTSC, PORTSC_PTC, mode << __ffs(PORTSC_PTC));
return 0;
}
/**
* hw_port_test_get: reads port test mode value
*
* @ci: the controller
*
* This function returns port test mode value
*/
u8 hw_port_test_get(struct ci_hdrc *ci)
{
return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> __ffs(PORTSC_PTC);
}
static void hw_wait_phy_stable(void)
{
/*
* The phy needs some delay to output the stable status from low
* power mode. And for OTGSC, the status inputs are debounced
* using a 1 ms time constant, so, delay 2ms for controller to get
* the stable status, like vbus and id when the phy leaves low power.
*/
usleep_range(2000, 2500);
}
/* The PHY enters/leaves low power mode */
static void ci_hdrc_enter_lpm_common(struct ci_hdrc *ci, bool enable)
{
enum ci_hw_regs reg = ci->hw_bank.lpm ? OP_DEVLC : OP_PORTSC;
bool lpm = !!(hw_read(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm)));
if (enable && !lpm)
hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm),
PORTSC_PHCD(ci->hw_bank.lpm));
else if (!enable && lpm)
hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm),
0);
}
static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable)
{
return ci->platdata->enter_lpm(ci, enable);
}
static int hw_device_init(struct ci_hdrc *ci, void __iomem *base)
{
u32 reg;
/* bank is a module variable */
ci->hw_bank.abs = base;
ci->hw_bank.cap = ci->hw_bank.abs;
ci->hw_bank.cap += ci->platdata->capoffset;
ci->hw_bank.op = ci->hw_bank.cap + (ioread32(ci->hw_bank.cap) & 0xff);
hw_alloc_regmap(ci, false);
reg = hw_read(ci, CAP_HCCPARAMS, HCCPARAMS_LEN) >>
__ffs(HCCPARAMS_LEN);
ci->hw_bank.lpm = reg;
if (reg)
hw_alloc_regmap(ci, !!reg);
ci->hw_bank.size = ci->hw_bank.op - ci->hw_bank.abs;
ci->hw_bank.size += OP_LAST;
ci->hw_bank.size /= sizeof(u32);
reg = hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DEN) >>
__ffs(DCCPARAMS_DEN);
ci->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */
if (ci->hw_ep_max > ENDPT_MAX)
return -ENODEV;
ci_hdrc_enter_lpm(ci, false);
/* Disable all interrupts bits */
hw_write(ci, OP_USBINTR, 0xffffffff, 0);
/* Clear all interrupts status bits*/
hw_write(ci, OP_USBSTS, 0xffffffff, 0xffffffff);
ci->rev = ci_get_revision(ci);
dev_dbg(ci->dev,
"revision: %d, lpm: %d; cap: %px op: %px\n",
ci->rev, ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op);
/* setup lock mode ? */
/* ENDPTSETUPSTAT is '0' by default */
/* HCSPARAMS.bf.ppc SHOULD BE zero for device */
return 0;
}
void hw_phymode_configure(struct ci_hdrc *ci)
{
u32 portsc, lpm, sts = 0;
switch (ci->platdata->phy_mode) {
case USBPHY_INTERFACE_MODE_UTMI:
portsc = PORTSC_PTS(PTS_UTMI);
lpm = DEVLC_PTS(PTS_UTMI);
break;
case USBPHY_INTERFACE_MODE_UTMIW:
portsc = PORTSC_PTS(PTS_UTMI) | PORTSC_PTW;
lpm = DEVLC_PTS(PTS_UTMI) | DEVLC_PTW;
break;
case USBPHY_INTERFACE_MODE_ULPI:
portsc = PORTSC_PTS(PTS_ULPI);
lpm = DEVLC_PTS(PTS_ULPI);
break;
case USBPHY_INTERFACE_MODE_SERIAL:
portsc = PORTSC_PTS(PTS_SERIAL);
lpm = DEVLC_PTS(PTS_SERIAL);
sts = 1;
break;
case USBPHY_INTERFACE_MODE_HSIC:
portsc = PORTSC_PTS(PTS_HSIC);
lpm = DEVLC_PTS(PTS_HSIC);
break;
default:
return;
}
if (ci->hw_bank.lpm) {
hw_write(ci, OP_DEVLC, DEVLC_PTS(7) | DEVLC_PTW, lpm);
if (sts)
hw_write(ci, OP_DEVLC, DEVLC_STS, DEVLC_STS);
} else {
hw_write(ci, OP_PORTSC, PORTSC_PTS(7) | PORTSC_PTW, portsc);
if (sts)
hw_write(ci, OP_PORTSC, PORTSC_STS, PORTSC_STS);
}
}
EXPORT_SYMBOL_GPL(hw_phymode_configure);
/**
* _ci_usb_phy_init: initialize phy taking in account both phy and usb_phy
* interfaces
* @ci: the controller
*
* This function returns an error code if the phy failed to init
*/
static int _ci_usb_phy_init(struct ci_hdrc *ci)
{
int ret;
if (ci->phy) {
ret = phy_init(ci->phy);
if (ret)
return ret;
ret = phy_power_on(ci->phy);
if (ret) {
phy_exit(c
|