blob: ef5524bd66b0b7ecdaea11fd148af20d78838f63 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Meson C2 USB2 PHY driver
*
* Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
* Copyright (C) 2018 BayLibre, SAS
* Author: Neil Armstrong <narmstron@baylibre.com>
*/
#include <common.h>
#include <asm/io.h>
#include <bitfield.h>
#include <dm.h>
#include <errno.h>
#include <generic-phy.h>
#include <regmap.h>
#include <power/regulator.h>
#include <clk.h>
#include <asm/arch/usb.h>
#include <amlogic/cpu_id.h>
#include <linux/compat.h>
#include <linux/ioport.h>
#include <asm-generic/gpio.h>
#define U2_CTRL_SLEEP_SHIFT 24
#define U2_HHI_MEM_PD_MASK 0x3
#define U2_HHI_MEM_PD_SHIFT 0x0
#define U2_CTRL_ISO_SHIFT 24
#define PHY20_RESET_LEVEL_BIT 4
#define USB_RESET_BIT 3
#define USB2_PHY_PLL_OFFSET_40 (0x0816a010)
#define USB2_PHY_PLL_OFFSET_44 (0x000a72f2)
#define USB2_PHY_PLL_OFFSET_34 (0x70000)
#define USB2_PHY_PLL_OFFSET_50 (0xfe18)
#define USB2_PHY_PLL_OFFSET_54 (0x2a)
#define TUNING_DISCONNECT_THRESHOLD 0x34
#define P_AO_RTI_GEN_PWR_SLEEP0 0xfe013008
#define P_AO_RTI_GEN_PWR_ISO0 0xfe013004
#define P_HHI_MEM_PD_REG0 0xfe013050
struct phy usb_phys[2];
int get_usbphy_baseinfo(struct phy *usb_phys)
{
struct udevice *bus;
struct uclass *uc;
int ret, i;
unsigned int count;
if (usb_phys[0].dev && usb_phys[1].dev)
return 0;
ret = uclass_get(UCLASS_USB, &uc);
if (ret)
return ret;
uclass_foreach_dev(bus, uc) {
debug("bus->name=%s, bus->driver->name =%s\n",
bus->name, bus->driver->name);
count = dev_count_phandle_with_args(bus, "phys", "#phy-cells");
debug("usb phy count=%u\n", count);
if (count <= 0)
return count;
for (i = 0; i < count; i++) {
ret = generic_phy_get_by_index(bus, i, &usb_phys[i]);
if (ret && ret != -ENOENT) {
pr_err("Failed to get USB PHY%d for %s\n",
i, bus->name);
return ret;
}
ret = generic_phy_getinfo(&usb_phys[i]);
if (ret)
return ret;
}
}
return 0;
}
static void usb_set_power_domain (void)
{
writel((readl(P_AO_RTI_GEN_PWR_SLEEP0) & (~(0x1 << U2_CTRL_SLEEP_SHIFT))),
P_AO_RTI_GEN_PWR_SLEEP0);
writel((readl(P_AO_RTI_GEN_PWR_ISO0) & (~(0x1 << U2_CTRL_ISO_SHIFT))),
P_AO_RTI_GEN_PWR_ISO0);
writel((readl(P_HHI_MEM_PD_REG0)
& (~(U2_HHI_MEM_PD_MASK << U2_HHI_MEM_PD_SHIFT))), P_HHI_MEM_PD_REG0);
return;
}
static void usb_set_calibration_trim(uint32_t phy2_pll_base, uint8_t mode)
{
uint32_t cali, value,i, tmp;
uint8_t cali_en;
if (mode == DEVICE_MODE) {
value = readl((unsigned long)(phy2_pll_base + 0x10));
value |= 0xfff;
writel(value, (unsigned long)(phy2_pll_base + 0x10));
return;
}
cali = readl((unsigned long)SYSCTRL_SEC_STATUS_REG12);
cali_en = (cali >> 30) & 0x1;
tmp = cali >> 26;
if (cali_en) {
tmp =tmp & 0xf;
cali = tmp;
if (cali > 12)
cali = 12;
value = readl((unsigned long)(phy2_pll_base + 0x10));
value &= (~0xfff);
for (i = 0; i < cali; i++)
value |= (1 << i);
writel(value, (unsigned long)(phy2_pll_base + 0x10));
}
}
static void usb_set_clock_freq(unsigned int clock_addr)
{
unsigned int val = (1 << 8) | (2 << 9) | (9 << 0);
writel(val, (unsigned long)clock_addr);
return;
}
void usb_reset(unsigned int reset_addr, int bit){
*(volatile unsigned int *)(unsigned long)reset_addr = (1 << bit);
}
static void usb_enable_phy_pll (void)
{
*(volatile uint32_t *)RESETCTRL_RESET1_LEVEL |= (1 << PHY20_RESET_LEVEL_BIT);
}
static void usb_disable_phy_pll (void)
{
*(volatile uint32_t *)RESETCTRL_RESET1_LEVEL &= ~(1 << PHY20_RESET_LEVEL_BIT);
}
void set_usb_pll(uint32_t phy2_pll_base)
{
uint32_t tmp, retry = 5;
__retry:
(*(volatile uint32_t *)((unsigned long)phy2_pll_base + 0x40))=
(USB2_PHY_PLL_OFFSET_40 | USB_PHY2_RESET);
(*(volatile uint32_t *)((unsigned long)phy2_pll_base + 0x44)) =
USB2_PHY_PLL_OFFSET_44;
udelay(5);
(*(volatile uint32_t *)((unsigned long)phy2_pll_base + 0x40))=
(USB2_PHY_PLL_OFFSET_40 | USB_PHY2_RESET | USB_PHY2_ENABLE);
udelay(5);
(*(volatile uint32_t *)((unsigned long)phy2_pll_base + 0x40))=
(USB2_PHY_PLL_OFFSET_40 | USB_PHY2_ENABLE);
udelay(10);
(*(volatile uint32_t *)((unsigned long)phy2_pll_base + 0x44))=
(USB2_PHY_PLL_OFFSET_44 | USBPLL_LK_OD_EN);
udelay(200);
tmp = *(volatile uint32_t *)((unsigned long)phy2_pll_base + 0x40);
if (tmp >> USBPLL_LOCKFLAG_BIT) {
goto __setphyparameter;
} else {
retry --;
if (!retry)
goto __setphyparameter;
goto __retry;
}
__setphyparameter:
(*(volatile uint32_t *)(unsigned long)((unsigned long)phy2_pll_base + 0x50))
= USB2_PHY_PLL_OFFSET_50;
(*(volatile uint32_t *)(unsigned long)((unsigned long)phy2_pll_base + 0x54))
= USB2_PHY_PLL_OFFSET_54;
(*(volatile uint32_t *)((unsigned long)phy2_pll_base + 0xc)) =
TUNING_DISCONNECT_THRESHOLD;
(*(volatile uint32_t *)(unsigned long)((unsigned long)phy2_pll_base + 0x34))
= USB2_PHY_PLL_OFFSET_34;
debug("tuning_disconnect_threshold=0x%x\n", TUNING_DISCONNECT_THRESHOLD);
}
int usb_save_phy_dev (unsigned int number, struct phy *phy)
{
usb_phys[number].dev = phy->dev;
usb_phys[number].id = phy->id;
return 0;
}
int usb2_phy_init (struct phy *phy) {
struct phy_aml_usb2_priv *priv = dev_get_priv(phy->dev);
struct u2p_aml_regs *u2p_aml_reg;
u2p_r0_t dev_u2p_r0;
u2p_r1_t dev_u2p_r1;
int i,cnt;
usb_save_phy_dev(0, phy);
usb_enable_phy_pll();
usb_set_power_domain();
priv->clktree_usb_bus_ctrl_addr = dev_read_addr_index(phy->dev, 2 + priv->u2_port_num);
if (priv->clktree_usb_bus_ctrl_addr == FDT_ADDR_T_NONE) {
pr_err("Coun't get clktree_usb_bus_ctrl addr index %d\n", 2 + priv->u2_port_num);
} else {
usb_set_clock_freq(priv->clktree_usb_bus_ctrl_addr);
}
usb_reset(priv->reset_addr, USB_RESET_BIT);
udelay(500);
priv->usbphy_reset_bit[0] = PHY20_RESET_LEVEL_BIT;
for (i = 0; i < priv->u2_port_num; i++) {
u2p_aml_reg = (struct u2p_aml_regs *)((ulong)(priv->base_addr + i * PHY_REGISTER_SIZE));
dev_u2p_r0.d32 = u2p_aml_reg->u2p_r0;
dev_u2p_r0.b.host_device= 1;
dev_u2p_r0.b.POR= 0;
u2p_aml_reg->u2p_r0 = dev_u2p_r0.d32;
udelay(10);
/***USB PHY RESET : reset1 and reset1_level both need set ***/
/***reset1: usb_reset ***/
/***reset1_level: usb_enable_phy_pll ***/
usb_reset(priv->reset_addr, priv->usbphy_reset_bit[i]);
usb_set_calibration_trim(priv->usb_phy2_pll_base_addr[i], HOST_MODE);
udelay(50);
/* wait for phy ready */
dev_u2p_r1.d32 = u2p_aml_reg->u2p_r1;
cnt = 0;
while (dev_u2p_r1.b.phy_rdy != 1) {
dev_u2p_r1.d32 = u2p_aml_reg->u2p_r1;
/*we wait phy ready max 1ms, common is 100us*/
if (cnt > 200)
break;
else {
cnt++;
udelay(5);
}
}
}
for (i = 0; i < priv->u2_port_num; i++) {
debug("------set usb pll\n");
set_usb_pll(priv->usb_phy2_pll_base_addr[i]);
writel(0xfe18, (unsigned long)(priv->usb_phy2_pll_base_addr[i] + 0x50));
}
return 0;
}
int usb2_phy_tuning(uint32_t phy2_pll_base, int port)
{
return 0;
}
/**************************************************************/
/* device mode config */
/**************************************************************/
unsigned int usb_get_dwc_a_base_addr(void)
{
struct phy_aml_usb2_priv *usb2_priv;
int ret;
if (!usb_phys[0].dev) {
ret = get_usbphy_baseinfo(usb_phys);
if (ret) {
printf("get usb dts failed\n");
return 0;
}
}
usb2_priv = dev_get_priv(usb_phys[0].dev);
return usb2_priv->dwc2_a_addr;
}
unsigned int usb_get_device_mode_phy_base(void)
{
struct phy_aml_usb2_priv *usb2_priv;
int ret;
if (!usb_phys[0].dev) {
ret = get_usbphy_baseinfo(usb_phys);
if (ret) {
printf("get usb dts failed\n");
return 0;
}
}
usb2_priv = dev_get_priv(usb_phys[0].dev);
return usb2_priv->usb_phy2_pll_base_addr[0];
}
void usb_phy_tuning_reset(void)
{
return;
}
void usb_device_mode_init(void){
u2p_r0_t dev_u2p_r0;
u2p_r1_t dev_u2p_r1;
usb_r0_t dev_usb_r0;
usb_r4_t dev_usb_r4;
int cnt, ret;
u2p_aml_regs_t * u2p_aml_regs;
usb_aml_regs_t *usb_aml_regs;
struct phy_aml_usb2_priv *usb2_priv;
struct phy_aml_usb3_priv *usb3_priv;
unsigned int phy_base_addr, reset_addr;
usb_enable_phy_pll();
ret = get_usbphy_baseinfo(usb_phys);
if (ret) {
printf("get usb dts failed\n");
return;
}
usb2_priv = dev_get_priv(usb_phys[0].dev);
usb3_priv = dev_get_priv(usb_phys[1].dev);
if (!usb2_priv || !usb3_priv) {
printf("get usb phy address from dts failed\n");
return;
}
u2p_aml_regs = (u2p_aml_regs_t * )((ulong)usb2_priv->base_addr);
usb_aml_regs = (usb_aml_regs_t * )((ulong)usb3_priv->base_addr);
phy_base_addr = usb2_priv->usb_phy2_pll_base_addr[0];
reset_addr = usb2_priv->reset_addr;
usb_set_power_domain();
usb_set_clock_freq(CLKTREE_USB_BUSCLK_CTRL);
printf("PHY2=0x%08x\n", usb2_priv->base_addr);
if ((*(volatile uint32_t *)((ulong)(phy_base_addr + 0x38))) != 0) {
usb_phy_tuning_reset();
mdelay(150);
}
//step 1: usb controller reset
usb_reset(reset_addr, USB_RESET_BIT);
// step 3: enable usb INT internal USB
dev_usb_r0.d32 = usb_aml_regs->usb_r0;
dev_usb_r0.b.u2d_ss_scaledown_mode = 0;
dev_usb_r0.b.u2d_act = 1;
usb_aml_regs->usb_r0 = dev_usb_r0.d32;
// step 4: disable usb phy sleep
dev_usb_r4.d32 = usb_aml_regs->usb_r4;
dev_usb_r4.b.p21_SLEEPM0 = 1;
usb_aml_regs->usb_r4 = dev_usb_r4.d32;
// step 5: config phy21 device mode
dev_u2p_r0.d32 = u2p_aml_regs->u2p_r0;
dev_u2p_r0.b.host_device= 0;
dev_u2p_r0.b.POR= 0;
u2p_aml_regs->u2p_r0 = dev_u2p_r0.d32;
udelay(10);
//step 6: phy21 reset
usb_reset(reset_addr, PHY20_RESET_LEVEL_BIT);
udelay(50);
usb_set_calibration_trim(phy_base_addr, DEVICE_MODE);
udelay(50);
// step 6: wait for phy ready
dev_u2p_r1.d32 = u2p_aml_regs->u2p_r1;
cnt = 0;
while ((dev_u2p_r1.d32 & 0x00000001) != 1) {
dev_u2p_r1.d32 = u2p_aml_regs->u2p_r1;
if (cnt > 200)
break;
else {
cnt++;
udelay(5);
}
}
set_usb_pll(phy_base_addr);
writel(0xbe18, (unsigned long)(phy_base_addr + 0x50));
//--------------------------------------------------
// ------------- usb phy21 initinal end ----------
//--------------------------------------------------
}
/**************************************************************/
/* BC Detect */
/**************************************************************/
#define USB_AML_BC_OFFSET 0xA0
static void usb_bc_set_device(uint32_t phy2_config_base){
struct u2p_aml_regs * u2p_aml_regs = (struct u2p_aml_regs * )((ulong)phy2_config_base);
u2p_r0_t u2p_r0;
u2p_r0.d32 = u2p_aml_regs->u2p_r0;
u2p_r0.b.host_device = 0;
u2p_aml_regs->u2p_r0 = u2p_r0.d32;
return;
}
static void usb_bc_en_det(uint32_t bc_reg_base, uint32_t usb3_config_base)
{
bc_reg_list_t * bc_reg_s = (struct bc_reg_list *)bc_reg_base;
bc_ctrl_t ctrl;
struct usb_aml_regs * usb3_config_reg = (struct usb_aml_regs *)usb3_config_base;
usb_r0_t cfg0_reg;
ctrl.d32 = bc_reg_s->bc_ctrl;
ctrl.b.bc_en = 1;
bc_reg_s->bc_ctrl = ctrl.d32;
cfg0_reg.d32 = usb3_config_reg->usb_r0;
cfg0_reg.b.u2d_act = 1;
usb3_config_reg->usb_r0 = cfg0_reg.d32;
mdelay(200);
}
static void usb_bc_disable_det(uint32_t bc_reg_base)
{
bc_reg_list_t * bc_reg_s = (struct bc_reg_list *)bc_reg_base;
bc_ctrl_t ctrl;
ctrl.d32 = bc_reg_s->bc_ctrl;
ctrl.b.bc_en = 0;
bc_reg_s->bc_ctrl = ctrl.d32;
}
static void usb_bc_clean_det(uint32_t bc_reg_base)
{
bc_reg_list_t * bc_reg_s = (struct bc_reg_list *)bc_reg_base;
bc_ctrl_t ctrl;
ctrl.d32 = bc_reg_s->bc_ctrl;
ctrl.b.bc_det_clean = 1;
ctrl.b.bc_int_clean = 1;
bc_reg_s->bc_ctrl = ctrl.d32;
}
static int usb_bc_read_bc_status(uint32_t bc_reg_base)
{
bc_reg_list_t *bc_reg_s = (bc_reg_list_t *)bc_reg_base;
bc_status_t b_status;
int ret;
b_status.d32 = bc_reg_s->bc_status;
printf("BC STATUS is : ");
switch (b_status.b.port_status) {
case 0:
env_set("charger_type","UNKOWN");
pr_info("port status: default\n");
break;
case 1:
env_set("charger_type","SDP");
pr_info("SDP:Standard downstream port\n");
break;
case 2:
env_set("charger_type","DCP");
pr_info("DCP: Delicated charging port\n");
break;
case 3:
env_set("charger_type","CDP");
pr_info("CDP: charging downstream port\n");
break;
case 4:
pr_info("ACA_A: ACA with ID resistance of RID_A\n");
break;
case 5:
pr_info("ACA_B: ACA with ID resistance of RID_B\n");
break;
case 6:
pr_info("ACA_C: ACA with ID resistance of RID_C\n");
break;
case 7:
pr_info("ACA_DOCK: Equivalent to a charging hub\n");
break;
case 8:
pr_info("port status: ACA GND error\n");
break;
case 9:
pr_info("port status: analog output error\n");
break;
case 10:
pr_info("VBUS remove\n");
break;
case 11:
pr_info("VBUS invalid\n");
break;
default:
pr_info("port status: reserved\n");
break;
}
if (b_status.b.port_status >= 4)
env_set("charger_type","UNKOWN");
ret = b_status.b.port_status;
usb_bc_clean_det(bc_reg_base);
return ret;
}
void usb_bc_detect(void)
{
struct phy_aml_usb2_priv *usb2_priv;
struct phy_aml_usb3_priv *usb3_priv;
int ret;
uint32_t phy2_cfg_base, phy3_cfg_base, bc_cfg_base;
ret = get_usbphy_baseinfo(usb_phys);
if (ret) {
pr_err("get usb dts failed\n");
return;
}
usb2_priv = dev_get_priv(usb_phys[0].dev);
usb3_priv = dev_get_priv(usb_phys[1].dev);
phy2_cfg_base = usb2_priv->base_addr;
bc_cfg_base = phy2_cfg_base - PHY_REGISTER_SIZE + USB_AML_BC_OFFSET;
phy3_cfg_base = usb3_priv->base_addr;
usb_bc_set_device(phy2_cfg_base);
usb_bc_en_det(bc_cfg_base, phy3_cfg_base);
usb_bc_read_bc_status(bc_cfg_base);
usb_bc_disable_det(bc_cfg_base);
}
static void usb_disable_phy(uint32_t phy2_pll_base)
{
(*(volatile uint32_t *)((unsigned long)phy2_pll_base + 0x40))=
((USB2_PHY_PLL_OFFSET_40 | USB_PHY2_RESET) & (~USB_PHY2_ENABLE));
udelay(5);
}
static void usb_print_usb_baseinfo
(struct phy_aml_usb2_priv *usb2_priv, struct phy_aml_usb3_priv *usb3_priv)
{
if (usb3_priv) {
printf("priv->usb3 port num = %d, config addr=0x%08x\n",
usb3_priv->usb3_port_num, usb3_priv->base_addr);
}
if (usb2_priv) {
printf("usb2 phy: config addr = 0x%08x, reset addr=0x%08x\n",
usb2_priv->base_addr, usb2_priv->reset_addr);
printf("usb2 phy: portnum=%d, phy-addr1= 0x%08x, phy-addr2= 0x%08x\n",
usb2_priv->u2_port_num, usb2_priv->usb_phy2_pll_base_addr[0],
usb2_priv->usb_phy2_pll_base_addr[1]);
printf("dwc2_a base addr: 0x%08x\n", usb2_priv->dwc2_a_addr);
}
}
int usb_aml_detect_operation(int argc, char * const argv[])
{
struct phy_aml_usb2_priv *usb2_priv;
struct phy_aml_usb3_priv *usb3_priv;
int ret;
ret = get_usbphy_baseinfo(usb_phys);
if (ret) {
printf("get usb dts failed\n");
return 0;
}
usb2_priv = dev_get_priv(usb_phys[0].dev);
usb3_priv = dev_get_priv(usb_phys[1].dev);
if (argc >= 2) {
if (strncmp(argv[1], "bc", 2) == 0) {
usb_bc_detect();
return 0;
}
if (strncmp(argv[1], "disable", 7) == 0) {
usb_disable_phy_pll();
usb_disable_phy(usb2_priv->usb_phy2_pll_base_addr[0]);
printf("disable USB phy\n");
return 0;
}
if (strncmp(argv[1], "info", 4) == 0) {
usb_print_usb_baseinfo(usb2_priv, usb3_priv);
return 0;
}
}
return CMD_RET_USAGE;
}