commit
809e0e398a
@ -0,0 +1,65 @@ |
|||||||
|
// SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
||||||
|
/* |
||||||
|
* Copyright (C) 2018 Amarula Solutions B.V. |
||||||
|
* Author: Jagan Teki <jagan@amarulasolutions.com> |
||||||
|
*/ |
||||||
|
|
||||||
|
/dts-v1/; |
||||||
|
|
||||||
|
#include "sun50i-a64.dtsi" |
||||||
|
|
||||||
|
#include <dt-bindings/gpio/gpio.h> |
||||||
|
|
||||||
|
/ { |
||||||
|
model = "Amarula A64-Relic"; |
||||||
|
compatible = "amarula,a64-relic", "allwinner,sun50i-a64"; |
||||||
|
|
||||||
|
aliases { |
||||||
|
serial0 = &uart0; |
||||||
|
}; |
||||||
|
|
||||||
|
chosen { |
||||||
|
stdout-path = "serial0:115200n8"; |
||||||
|
}; |
||||||
|
|
||||||
|
reg_vcc3v3: vcc3v3 { |
||||||
|
compatible = "regulator-fixed"; |
||||||
|
regulator-name = "vcc3v3"; |
||||||
|
regulator-min-microvolt = <3300000>; |
||||||
|
regulator-max-microvolt = <3300000>; |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
&ehci0 { |
||||||
|
status = "okay"; |
||||||
|
}; |
||||||
|
|
||||||
|
&mmc2 { |
||||||
|
pinctrl-names = "default"; |
||||||
|
pinctrl-0 = <&mmc2_pins>; |
||||||
|
vmmc-supply = <®_vcc3v3>; |
||||||
|
bus-width = <8>; |
||||||
|
non-removable; |
||||||
|
cap-mmc-hw-reset; |
||||||
|
status = "okay"; |
||||||
|
}; |
||||||
|
|
||||||
|
&ohci0 { |
||||||
|
status = "okay"; |
||||||
|
}; |
||||||
|
|
||||||
|
&uart0 { |
||||||
|
pinctrl-names = "default"; |
||||||
|
pinctrl-0 = <&uart0_pins_a>; |
||||||
|
status = "okay"; |
||||||
|
}; |
||||||
|
|
||||||
|
&usb_otg { |
||||||
|
dr_mode = "otg"; |
||||||
|
status = "okay"; |
||||||
|
}; |
||||||
|
|
||||||
|
&usbphy { |
||||||
|
usb0_id_det-gpios = <&pio 7 9 GPIO_ACTIVE_HIGH>; /* PH9 */ |
||||||
|
status = "okay"; |
||||||
|
}; |
@ -1,20 +0,0 @@ |
|||||||
/* SPDX-License-Identifier: GPL-2.0+ */ |
|
||||||
/*
|
|
||||||
* Sunxi usb-phy code |
|
||||||
* |
|
||||||
* Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com> |
|
||||||
* Copyright (C) 2014 Roman Byshko <rbyshko@gmail.com> |
|
||||||
* |
|
||||||
* Based on code from |
|
||||||
* Allwinner Technology Co., Ltd. <www.allwinnertech.com> |
|
||||||
*/ |
|
||||||
|
|
||||||
int sunxi_usb_phy_probe(void); |
|
||||||
int sunxi_usb_phy_remove(void); |
|
||||||
void sunxi_usb_phy_init(int index); |
|
||||||
void sunxi_usb_phy_exit(int index); |
|
||||||
void sunxi_usb_phy_power_on(int index); |
|
||||||
void sunxi_usb_phy_power_off(int index); |
|
||||||
int sunxi_usb_phy_vbus_detect(int index); |
|
||||||
int sunxi_usb_phy_id_detect(int index); |
|
||||||
void sunxi_usb_phy_enable_squelch_detect(int index, int enable); |
|
@ -1,406 +0,0 @@ |
|||||||
// SPDX-License-Identifier: GPL-2.0+
|
|
||||||
/*
|
|
||||||
* Sunxi usb-phy code |
|
||||||
* |
|
||||||
* Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com> |
|
||||||
* Copyright (C) 2014 Roman Byshko <rbyshko@gmail.com> |
|
||||||
* |
|
||||||
* Based on code from |
|
||||||
* Allwinner Technology Co., Ltd. <www.allwinnertech.com> |
|
||||||
*/ |
|
||||||
|
|
||||||
#include <common.h> |
|
||||||
#include <asm/arch/clock.h> |
|
||||||
#include <asm/arch/cpu.h> |
|
||||||
#include <asm/arch/usb_phy.h> |
|
||||||
#include <asm/gpio.h> |
|
||||||
#include <asm/io.h> |
|
||||||
#include <errno.h> |
|
||||||
|
|
||||||
#if defined(CONFIG_MACH_SUN4I) || \ |
|
||||||
defined(CONFIG_MACH_SUN5I) || \
|
|
||||||
defined(CONFIG_MACH_SUN6I) || \
|
|
||||||
defined(CONFIG_MACH_SUN7I) || \
|
|
||||||
defined(CONFIG_MACH_SUN8I_A23) || \
|
|
||||||
defined(CONFIG_MACH_SUN9I) |
|
||||||
#define SUNXI_USB_CSR 0x404 |
|
||||||
#else |
|
||||||
#define SUNXI_USB_CSR 0x410 |
|
||||||
#endif |
|
||||||
|
|
||||||
#define SUNXI_USB_PMU_IRQ_ENABLE 0x800 |
|
||||||
#define SUNXI_USB_PASSBY_EN 1 |
|
||||||
|
|
||||||
#define SUNXI_EHCI_AHB_ICHR8_EN (1 << 10) |
|
||||||
#define SUNXI_EHCI_AHB_INCR4_BURST_EN (1 << 9) |
|
||||||
#define SUNXI_EHCI_AHB_INCRX_ALIGN_EN (1 << 8) |
|
||||||
#define SUNXI_EHCI_ULPI_BYPASS_EN (1 << 0) |
|
||||||
|
|
||||||
#define REG_PHY_UNK_H3 0x420 |
|
||||||
#define REG_PMU_UNK_H3 0x810 |
|
||||||
|
|
||||||
/* A83T specific control bits for PHY0 */ |
|
||||||
#define SUNXI_PHY_CTL_VBUSVLDEXT BIT(5) |
|
||||||
#define SUNXI_PHY_CTL_SIDDQ BIT(3) |
|
||||||
|
|
||||||
/* A83T HSIC specific bits */ |
|
||||||
#define SUNXI_EHCI_HS_FORCE BIT(20) |
|
||||||
#define SUNXI_EHCI_CONNECT_DET BIT(17) |
|
||||||
#define SUNXI_EHCI_CONNECT_INT BIT(16) |
|
||||||
#define SUNXI_EHCI_HSIC BIT(1) |
|
||||||
|
|
||||||
static struct sunxi_usb_phy { |
|
||||||
int usb_rst_mask; |
|
||||||
int gpio_vbus; |
|
||||||
int gpio_vbus_det; |
|
||||||
int gpio_id_det; |
|
||||||
int id; |
|
||||||
int init_count; |
|
||||||
int power_on_count; |
|
||||||
ulong base; |
|
||||||
} sunxi_usb_phy[] = { |
|
||||||
{ |
|
||||||
.usb_rst_mask = CCM_USB_CTRL_PHY0_RST | CCM_USB_CTRL_PHY0_CLK, |
|
||||||
.id = 0, |
|
||||||
.base = SUNXI_USB0_BASE, |
|
||||||
}, |
|
||||||
{ |
|
||||||
.usb_rst_mask = CCM_USB_CTRL_PHY1_RST | CCM_USB_CTRL_PHY1_CLK, |
|
||||||
.id = 1, |
|
||||||
.base = SUNXI_USB1_BASE, |
|
||||||
}, |
|
||||||
#if CONFIG_SUNXI_USB_PHYS >= 3 |
|
||||||
{ |
|
||||||
#ifdef CONFIG_MACH_SUN8I_A83T |
|
||||||
.usb_rst_mask = CCM_USB_CTRL_HSIC_RST | CCM_USB_CTRL_HSIC_CLK | |
|
||||||
CCM_USB_CTRL_12M_CLK, |
|
||||||
#else |
|
||||||
.usb_rst_mask = CCM_USB_CTRL_PHY2_RST | CCM_USB_CTRL_PHY2_CLK, |
|
||||||
#endif |
|
||||||
.id = 2, |
|
||||||
.base = SUNXI_USB2_BASE, |
|
||||||
}, |
|
||||||
#endif |
|
||||||
#if CONFIG_SUNXI_USB_PHYS >= 4 |
|
||||||
{ |
|
||||||
.usb_rst_mask = CCM_USB_CTRL_PHY3_RST | CCM_USB_CTRL_PHY3_CLK, |
|
||||||
.id = 3, |
|
||||||
.base = SUNXI_USB3_BASE, |
|
||||||
} |
|
||||||
#endif |
|
||||||
}; |
|
||||||
|
|
||||||
static int initial_usb_scan_delay = CONFIG_INITIAL_USB_SCAN_DELAY; |
|
||||||
|
|
||||||
static int get_vbus_gpio(int index) |
|
||||||
{ |
|
||||||
switch (index) { |
|
||||||
case 0: return sunxi_name_to_gpio(CONFIG_USB0_VBUS_PIN); |
|
||||||
case 1: return sunxi_name_to_gpio(CONFIG_USB1_VBUS_PIN); |
|
||||||
case 2: return sunxi_name_to_gpio(CONFIG_USB2_VBUS_PIN); |
|
||||||
case 3: return sunxi_name_to_gpio(CONFIG_USB3_VBUS_PIN); |
|
||||||
} |
|
||||||
return -EINVAL; |
|
||||||
} |
|
||||||
|
|
||||||
static int get_vbus_detect_gpio(int index) |
|
||||||
{ |
|
||||||
switch (index) { |
|
||||||
case 0: return sunxi_name_to_gpio(CONFIG_USB0_VBUS_DET); |
|
||||||
} |
|
||||||
return -EINVAL; |
|
||||||
} |
|
||||||
|
|
||||||
static int get_id_detect_gpio(int index) |
|
||||||
{ |
|
||||||
switch (index) { |
|
||||||
case 0: return sunxi_name_to_gpio(CONFIG_USB0_ID_DET); |
|
||||||
} |
|
||||||
return -EINVAL; |
|
||||||
} |
|
||||||
|
|
||||||
__maybe_unused static void usb_phy_write(struct sunxi_usb_phy *phy, int addr, |
|
||||||
int data, int len) |
|
||||||
{ |
|
||||||
int j = 0, usbc_bit = 0; |
|
||||||
void *dest = (void *)SUNXI_USB0_BASE + SUNXI_USB_CSR; |
|
||||||
|
|
||||||
#ifdef CONFIG_MACH_SUN8I_A33 |
|
||||||
/* CSR needs to be explicitly initialized to 0 on A33 */ |
|
||||||
writel(0, dest); |
|
||||||
#endif |
|
||||||
|
|
||||||
usbc_bit = 1 << (phy->id * 2); |
|
||||||
for (j = 0; j < len; j++) { |
|
||||||
/* set the bit address to be written */ |
|
||||||
clrbits_le32(dest, 0xff << 8); |
|
||||||
setbits_le32(dest, (addr + j) << 8); |
|
||||||
|
|
||||||
clrbits_le32(dest, usbc_bit); |
|
||||||
/* set data bit */ |
|
||||||
if (data & 0x1) |
|
||||||
setbits_le32(dest, 1 << 7); |
|
||||||
else |
|
||||||
clrbits_le32(dest, 1 << 7); |
|
||||||
|
|
||||||
setbits_le32(dest, usbc_bit); |
|
||||||
|
|
||||||
clrbits_le32(dest, usbc_bit); |
|
||||||
|
|
||||||
data >>= 1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#if defined(CONFIG_MACH_SUNXI_H3_H5) || defined(CONFIG_MACH_SUN50I) |
|
||||||
static void sunxi_usb_phy_config(struct sunxi_usb_phy *phy) |
|
||||||
{ |
|
||||||
#if defined CONFIG_MACH_SUNXI_H3_H5 |
|
||||||
if (phy->id == 0) |
|
||||||
clrbits_le32(SUNXI_USBPHY_BASE + REG_PHY_UNK_H3, 0x01); |
|
||||||
#endif |
|
||||||
clrbits_le32(phy->base + REG_PMU_UNK_H3, 0x02); |
|
||||||
} |
|
||||||
#elif defined CONFIG_MACH_SUN8I_A83T |
|
||||||
static void sunxi_usb_phy_config(struct sunxi_usb_phy *phy) |
|
||||||
{ |
|
||||||
} |
|
||||||
#else |
|
||||||
static void sunxi_usb_phy_config(struct sunxi_usb_phy *phy) |
|
||||||
{ |
|
||||||
/* The following comments are machine
|
|
||||||
* translated from Chinese, you have been warned! |
|
||||||
*/ |
|
||||||
|
|
||||||
/* Regulation 45 ohms */ |
|
||||||
if (phy->id == 0) |
|
||||||
usb_phy_write(phy, 0x0c, 0x01, 1); |
|
||||||
|
|
||||||
/* adjust PHY's magnitude and rate */ |
|
||||||
usb_phy_write(phy, 0x20, 0x14, 5); |
|
||||||
|
|
||||||
/* threshold adjustment disconnect */ |
|
||||||
#if defined CONFIG_MACH_SUN5I || defined CONFIG_MACH_SUN7I |
|
||||||
usb_phy_write(phy, 0x2a, 2, 2); |
|
||||||
#else |
|
||||||
usb_phy_write(phy, 0x2a, 3, 2); |
|
||||||
#endif |
|
||||||
|
|
||||||
return; |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
static void sunxi_usb_phy_passby(struct sunxi_usb_phy *phy, int enable) |
|
||||||
{ |
|
||||||
unsigned long bits = 0; |
|
||||||
void *addr; |
|
||||||
|
|
||||||
addr = (void *)phy->base + SUNXI_USB_PMU_IRQ_ENABLE; |
|
||||||
|
|
||||||
bits = SUNXI_EHCI_AHB_ICHR8_EN | |
|
||||||
SUNXI_EHCI_AHB_INCR4_BURST_EN | |
|
||||||
SUNXI_EHCI_AHB_INCRX_ALIGN_EN | |
|
||||||
SUNXI_EHCI_ULPI_BYPASS_EN; |
|
||||||
|
|
||||||
#ifdef CONFIG_MACH_SUN8I_A83T |
|
||||||
if (phy->id == 2) |
|
||||||
bits |= SUNXI_EHCI_HS_FORCE | |
|
||||||
SUNXI_EHCI_CONNECT_INT | |
|
||||||
SUNXI_EHCI_HSIC; |
|
||||||
#endif |
|
||||||
|
|
||||||
if (enable) |
|
||||||
setbits_le32(addr, bits); |
|
||||||
else |
|
||||||
clrbits_le32(addr, bits); |
|
||||||
|
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
void sunxi_usb_phy_enable_squelch_detect(int index, int enable) |
|
||||||
{ |
|
||||||
#ifndef CONFIG_MACH_SUN8I_A83T |
|
||||||
struct sunxi_usb_phy *phy = &sunxi_usb_phy[index]; |
|
||||||
|
|
||||||
usb_phy_write(phy, 0x3c, enable ? 0 : 2, 2); |
|
||||||
#endif |
|
||||||
} |
|
||||||
|
|
||||||
void sunxi_usb_phy_init(int index) |
|
||||||
{ |
|
||||||
struct sunxi_usb_phy *phy = &sunxi_usb_phy[index]; |
|
||||||
struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; |
|
||||||
|
|
||||||
phy->init_count++; |
|
||||||
if (phy->init_count != 1) |
|
||||||
return; |
|
||||||
|
|
||||||
setbits_le32(&ccm->usb_clk_cfg, phy->usb_rst_mask); |
|
||||||
|
|
||||||
sunxi_usb_phy_config(phy); |
|
||||||
|
|
||||||
if (phy->id != 0) |
|
||||||
sunxi_usb_phy_passby(phy, SUNXI_USB_PASSBY_EN); |
|
||||||
|
|
||||||
#ifdef CONFIG_MACH_SUN8I_A83T |
|
||||||
if (phy->id == 0) { |
|
||||||
setbits_le32(SUNXI_USB0_BASE + SUNXI_USB_CSR, |
|
||||||
SUNXI_PHY_CTL_VBUSVLDEXT); |
|
||||||
clrbits_le32(SUNXI_USB0_BASE + SUNXI_USB_CSR, |
|
||||||
SUNXI_PHY_CTL_SIDDQ); |
|
||||||
} |
|
||||||
#endif |
|
||||||
} |
|
||||||
|
|
||||||
void sunxi_usb_phy_exit(int index) |
|
||||||
{ |
|
||||||
struct sunxi_usb_phy *phy = &sunxi_usb_phy[index]; |
|
||||||
struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; |
|
||||||
|
|
||||||
phy->init_count--; |
|
||||||
if (phy->init_count != 0) |
|
||||||
return; |
|
||||||
|
|
||||||
if (phy->id != 0) |
|
||||||
sunxi_usb_phy_passby(phy, !SUNXI_USB_PASSBY_EN); |
|
||||||
|
|
||||||
#ifdef CONFIG_MACH_SUN8I_A83T |
|
||||||
if (phy->id == 0) { |
|
||||||
setbits_le32(SUNXI_USB0_BASE + SUNXI_USB_CSR, |
|
||||||
SUNXI_PHY_CTL_SIDDQ); |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
clrbits_le32(&ccm->usb_clk_cfg, phy->usb_rst_mask); |
|
||||||
} |
|
||||||
|
|
||||||
void sunxi_usb_phy_power_on(int index) |
|
||||||
{ |
|
||||||
struct sunxi_usb_phy *phy = &sunxi_usb_phy[index]; |
|
||||||
|
|
||||||
if (initial_usb_scan_delay) { |
|
||||||
mdelay(initial_usb_scan_delay); |
|
||||||
initial_usb_scan_delay = 0; |
|
||||||
} |
|
||||||
|
|
||||||
phy->power_on_count++; |
|
||||||
if (phy->power_on_count != 1) |
|
||||||
return; |
|
||||||
|
|
||||||
if (phy->gpio_vbus >= 0) |
|
||||||
gpio_set_value(phy->gpio_vbus, 1); |
|
||||||
} |
|
||||||
|
|
||||||
void sunxi_usb_phy_power_off(int index) |
|
||||||
{ |
|
||||||
struct sunxi_usb_phy *phy = &sunxi_usb_phy[index]; |
|
||||||
|
|
||||||
phy->power_on_count--; |
|
||||||
if (phy->power_on_count != 0) |
|
||||||
return; |
|
||||||
|
|
||||||
if (phy->gpio_vbus >= 0) |
|
||||||
gpio_set_value(phy->gpio_vbus, 0); |
|
||||||
} |
|
||||||
|
|
||||||
int sunxi_usb_phy_vbus_detect(int index) |
|
||||||
{ |
|
||||||
struct sunxi_usb_phy *phy = &sunxi_usb_phy[index]; |
|
||||||
int err, retries = 3; |
|
||||||
|
|
||||||
if (phy->gpio_vbus_det < 0) |
|
||||||
return phy->gpio_vbus_det; |
|
||||||
|
|
||||||
err = gpio_get_value(phy->gpio_vbus_det); |
|
||||||
/*
|
|
||||||
* Vbus may have been provided by the board and just been turned of |
|
||||||
* some milliseconds ago on reset, what we're measuring then is a |
|
||||||
* residual charge on Vbus, sleep a bit and try again. |
|
||||||
*/ |
|
||||||
while (err > 0 && retries--) { |
|
||||||
mdelay(100); |
|
||||||
err = gpio_get_value(phy->gpio_vbus_det); |
|
||||||
} |
|
||||||
|
|
||||||
return err; |
|
||||||
} |
|
||||||
|
|
||||||
int sunxi_usb_phy_id_detect(int index) |
|
||||||
{ |
|
||||||
struct sunxi_usb_phy *phy = &sunxi_usb_phy[index]; |
|
||||||
|
|
||||||
if (phy->gpio_id_det < 0) |
|
||||||
return phy->gpio_id_det; |
|
||||||
|
|
||||||
return gpio_get_value(phy->gpio_id_det); |
|
||||||
} |
|
||||||
|
|
||||||
int sunxi_usb_phy_probe(void) |
|
||||||
{ |
|
||||||
struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; |
|
||||||
struct sunxi_usb_phy *phy; |
|
||||||
int i, ret = 0; |
|
||||||
|
|
||||||
for (i = 0; i < CONFIG_SUNXI_USB_PHYS; i++) { |
|
||||||
phy = &sunxi_usb_phy[i]; |
|
||||||
|
|
||||||
phy->gpio_vbus = get_vbus_gpio(i); |
|
||||||
if (phy->gpio_vbus >= 0) { |
|
||||||
ret = gpio_request(phy->gpio_vbus, "usb_vbus"); |
|
||||||
if (ret) |
|
||||||
return ret; |
|
||||||
ret = gpio_direction_output(phy->gpio_vbus, 0); |
|
||||||
if (ret) |
|
||||||
return ret; |
|
||||||
} |
|
||||||
|
|
||||||
phy->gpio_vbus_det = get_vbus_detect_gpio(i); |
|
||||||
if (phy->gpio_vbus_det >= 0) { |
|
||||||
ret = gpio_request(phy->gpio_vbus_det, "usb_vbus_det"); |
|
||||||
if (ret) |
|
||||||
return ret; |
|
||||||
ret = gpio_direction_input(phy->gpio_vbus_det); |
|
||||||
if (ret) |
|
||||||
return ret; |
|
||||||
} |
|
||||||
|
|
||||||
phy->gpio_id_det = get_id_detect_gpio(i); |
|
||||||
if (phy->gpio_id_det >= 0) { |
|
||||||
ret = gpio_request(phy->gpio_id_det, "usb_id_det"); |
|
||||||
if (ret) |
|
||||||
return ret; |
|
||||||
ret = gpio_direction_input(phy->gpio_id_det); |
|
||||||
if (ret) |
|
||||||
return ret; |
|
||||||
sunxi_gpio_set_pull(phy->gpio_id_det, |
|
||||||
SUNXI_GPIO_PULL_UP); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
setbits_le32(&ccm->usb_clk_cfg, CCM_USB_CTRL_PHYGATE); |
|
||||||
|
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
int sunxi_usb_phy_remove(void) |
|
||||||
{ |
|
||||||
struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; |
|
||||||
struct sunxi_usb_phy *phy; |
|
||||||
int i; |
|
||||||
|
|
||||||
clrbits_le32(&ccm->usb_clk_cfg, CCM_USB_CTRL_PHYGATE); |
|
||||||
|
|
||||||
for (i = 0; i < CONFIG_SUNXI_USB_PHYS; i++) { |
|
||||||
phy = &sunxi_usb_phy[i]; |
|
||||||
|
|
||||||
if (phy->gpio_vbus >= 0) |
|
||||||
gpio_free(phy->gpio_vbus); |
|
||||||
|
|
||||||
if (phy->gpio_vbus_det >= 0) |
|
||||||
gpio_free(phy->gpio_vbus_det); |
|
||||||
|
|
||||||
if (phy->gpio_id_det >= 0) |
|
||||||
gpio_free(phy->gpio_id_det); |
|
||||||
} |
|
||||||
|
|
||||||
return 0; |
|
||||||
} |
|
@ -0,0 +1,17 @@ |
|||||||
|
CONFIG_ARM=y |
||||||
|
CONFIG_ARCH_SUNXI=y |
||||||
|
CONFIG_SPL=y |
||||||
|
CONFIG_MACH_SUN50I=y |
||||||
|
CONFIG_DRAM_ODT_EN=y |
||||||
|
CONFIG_RESERVE_ALLWINNER_BOOT0_HEADER=y |
||||||
|
CONFIG_MMC_SUNXI_SLOT_EXTRA=2 |
||||||
|
CONFIG_DEFAULT_DEVICE_TREE="sun50i-a64-amarula-relic" |
||||||
|
# CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set |
||||||
|
# CONFIG_VIDEO_DE2 is not set |
||||||
|
# CONFIG_CMD_FLASH is not set |
||||||
|
# CONFIG_CMD_FPGA is not set |
||||||
|
# CONFIG_SPL_DOS_PARTITION is not set |
||||||
|
# CONFIG_SPL_ISO_PARTITION is not set |
||||||
|
# CONFIG_SPL_EFI_PARTITION is not set |
||||||
|
CONFIG_USB_MUSB_GADGET=y |
||||||
|
CONFIG_SYS_USB_EVENT_POLL_VIA_INT_QUEUE=y |
@ -0,0 +1,65 @@ |
|||||||
|
Allwinner sun4i USB PHY |
||||||
|
----------------------- |
||||||
|
|
||||||
|
Required properties: |
||||||
|
- compatible : should be one of |
||||||
|
* allwinner,sun4i-a10-usb-phy |
||||||
|
* allwinner,sun5i-a13-usb-phy |
||||||
|
* allwinner,sun6i-a31-usb-phy |
||||||
|
* allwinner,sun7i-a20-usb-phy |
||||||
|
* allwinner,sun8i-a23-usb-phy |
||||||
|
* allwinner,sun8i-a33-usb-phy |
||||||
|
* allwinner,sun8i-a83t-usb-phy |
||||||
|
* allwinner,sun8i-h3-usb-phy |
||||||
|
* allwinner,sun8i-v3s-usb-phy |
||||||
|
* allwinner,sun50i-a64-usb-phy |
||||||
|
- reg : a list of offset + length pairs |
||||||
|
- reg-names : |
||||||
|
* "phy_ctrl" |
||||||
|
* "pmu0" for H3, V3s and A64 |
||||||
|
* "pmu1" |
||||||
|
* "pmu2" for sun4i, sun6i, sun7i, sun8i-a83t or sun8i-h3 |
||||||
|
* "pmu3" for sun8i-h3 |
||||||
|
- #phy-cells : from the generic phy bindings, must be 1 |
||||||
|
- clocks : phandle + clock specifier for the phy clocks |
||||||
|
- clock-names : |
||||||
|
* "usb_phy" for sun4i, sun5i or sun7i |
||||||
|
* "usb0_phy", "usb1_phy" and "usb2_phy" for sun6i |
||||||
|
* "usb0_phy", "usb1_phy" for sun8i |
||||||
|
* "usb0_phy", "usb1_phy", "usb2_phy" and "usb2_hsic_12M" for sun8i-a83t |
||||||
|
* "usb0_phy", "usb1_phy", "usb2_phy" and "usb3_phy" for sun8i-h3 |
||||||
|
- resets : a list of phandle + reset specifier pairs |
||||||
|
- reset-names : |
||||||
|
* "usb0_reset" |
||||||
|
* "usb1_reset" |
||||||
|
* "usb2_reset" for sun4i, sun6i, sun7i, sun8i-a83t or sun8i-h3 |
||||||
|
* "usb3_reset" for sun8i-h3 |
||||||
|
|
||||||
|
Optional properties: |
||||||
|
- usb0_id_det-gpios : gpio phandle for reading the otg id pin value |
||||||
|
- usb0_vbus_det-gpios : gpio phandle for detecting the presence of usb0 vbus |
||||||
|
- usb0_vbus_power-supply: power-supply phandle for usb0 vbus presence detect |
||||||
|
- usb0_vbus-supply : regulator phandle for controller usb0 vbus |
||||||
|
- usb1_vbus-supply : regulator phandle for controller usb1 vbus |
||||||
|
- usb2_vbus-supply : regulator phandle for controller usb2 vbus |
||||||
|
- usb3_vbus-supply : regulator phandle for controller usb3 vbus |
||||||
|
|
||||||
|
Example: |
||||||
|
usbphy: phy@01c13400 { |
||||||
|
#phy-cells = <1>; |
||||||
|
compatible = "allwinner,sun4i-a10-usb-phy"; |
||||||
|
/* phy base regs, phy1 pmu reg, phy2 pmu reg */ |
||||||
|
reg = <0x01c13400 0x10 0x01c14800 0x4 0x01c1c800 0x4>; |
||||||
|
reg-names = "phy_ctrl", "pmu1", "pmu2"; |
||||||
|
clocks = <&usb_clk 8>; |
||||||
|
clock-names = "usb_phy"; |
||||||
|
resets = <&usb_clk 0>, <&usb_clk 1>, <&usb_clk 2>; |
||||||
|
reset-names = "usb0_reset", "usb1_reset", "usb2_reset"; |
||||||
|
pinctrl-names = "default"; |
||||||
|
pinctrl-0 = <&usb0_id_detect_pin>, <&usb0_vbus_detect_pin>; |
||||||
|
usb0_id_det-gpios = <&pio 7 19 GPIO_ACTIVE_HIGH>; /* PH19 */ |
||||||
|
usb0_vbus_det-gpios = <&pio 7 22 GPIO_ACTIVE_HIGH>; /* PH22 */ |
||||||
|
usb0_vbus-supply = <®_usb0_vbus>; |
||||||
|
usb1_vbus-supply = <®_usb1_vbus>; |
||||||
|
usb2_vbus-supply = <®_usb2_vbus>; |
||||||
|
}; |
@ -0,0 +1,13 @@ |
|||||||
|
# |
||||||
|
# Phy drivers for Allwinner platforms |
||||||
|
# |
||||||
|
config PHY_SUN4I_USB |
||||||
|
bool "Allwinner Sun4I USB PHY driver" |
||||||
|
depends on ARCH_SUNXI |
||||||
|
select PHY |
||||||
|
help |
||||||
|
Enable this to support the transceiver that is part of Allwinner |
||||||
|
sunxi SoCs. |
||||||
|
|
||||||
|
This driver controls the entire USB PHY block, both the USB OTG |
||||||
|
parts, as well as the 2 regular USB 2 host PHYs. |
@ -0,0 +1,6 @@ |
|||||||
|
# Copyright (C) 2016 Amarula Solutions
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-2.0+
|
||||||
|
#
|
||||||
|
|
||||||
|
obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o
|
@ -0,0 +1,575 @@ |
|||||||
|
/*
|
||||||
|
* Allwinner sun4i USB PHY driver |
||||||
|
* |
||||||
|
* Copyright (C) 2017 Jagan Teki <jagan@amarulasolutions.com> |
||||||
|
* Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com> |
||||||
|
* Copyright (C) 2014 Roman Byshko <rbyshko@gmail.com> |
||||||
|
* |
||||||
|
* Modelled arch/arm/mach-sunxi/usb_phy.c to compatible with generic-phy. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-2.0+ |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <common.h> |
||||||
|
#include <dm.h> |
||||||
|
#include <dm/device.h> |
||||||
|
#include <generic-phy.h> |
||||||
|
#include <phy-sun4i-usb.h> |
||||||
|
#include <asm/gpio.h> |
||||||
|
#include <asm/io.h> |
||||||
|
#include <asm/arch/clock.h> |
||||||
|
#include <asm/arch/cpu.h> |
||||||
|
|
||||||
|
#define REG_ISCR 0x00 |
||||||
|
#define REG_PHYCTL_A10 0x04 |
||||||
|
#define REG_PHYBIST 0x08 |
||||||
|
#define REG_PHYTUNE 0x0c |
||||||
|
#define REG_PHYCTL_A33 0x10 |
||||||
|
#define REG_PHY_OTGCTL 0x20 |
||||||
|
#define REG_PMU_UNK1 0x10 |
||||||
|
|
||||||
|
/* Common Control Bits for Both PHYs */ |
||||||
|
#define PHY_PLL_BW 0x03 |
||||||
|
#define PHY_RES45_CAL_EN 0x0c |
||||||
|
|
||||||
|
/* Private Control Bits for Each PHY */ |
||||||
|
#define PHY_TX_AMPLITUDE_TUNE 0x20 |
||||||
|
#define PHY_TX_SLEWRATE_TUNE 0x22 |
||||||
|
#define PHY_DISCON_TH_SEL 0x2a |
||||||
|
#define PHY_SQUELCH_DETECT 0x3c |
||||||
|
|
||||||
|
#define PHYCTL_DATA BIT(7) |
||||||
|
#define OTGCTL_ROUTE_MUSB BIT(0) |
||||||
|
|
||||||
|
#define PHY_TX_RATE BIT(4) |
||||||
|
#define PHY_TX_MAGNITUDE BIT(2) |
||||||
|
#define PHY_TX_AMPLITUDE_LEN 5 |
||||||
|
|
||||||
|
#define PHY_RES45_CAL_DATA BIT(0) |
||||||
|
#define PHY_RES45_CAL_LEN 1 |
||||||
|
#define PHY_DISCON_TH_LEN 2 |
||||||
|
|
||||||
|
#define SUNXI_AHB_ICHR8_EN BIT(10) |
||||||
|
#define SUNXI_AHB_INCR4_BURST_EN BIT(9) |
||||||
|
#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) |
||||||
|
#define SUNXI_ULPI_BYPASS_EN BIT(0) |
||||||
|
|
||||||
|
/* A83T specific control bits for PHY0 */ |
||||||
|
#define PHY_CTL_VBUSVLDEXT BIT(5) |
||||||
|
#define PHY_CTL_SIDDQ BIT(3) |
||||||
|
|
||||||
|
/* A83T specific control bits for PHY2 HSIC */ |
||||||
|
#define SUNXI_EHCI_HS_FORCE BIT(20) |
||||||
|
#define SUNXI_HSIC_CONNECT_INT BIT(16) |
||||||
|
#define SUNXI_HSIC BIT(1) |
||||||
|
|
||||||
|
#define MAX_PHYS 4 |
||||||
|
|
||||||
|
enum sun4i_usb_phy_type { |
||||||
|
sun4i_a10_phy, |
||||||
|
sun6i_a31_phy, |
||||||
|
sun8i_a33_phy, |
||||||
|
sun8i_a83t_phy, |
||||||
|
sun8i_h3_phy, |
||||||
|
sun8i_v3s_phy, |
||||||
|
sun50i_a64_phy, |
||||||
|
}; |
||||||
|
|
||||||
|
struct sun4i_usb_phy_cfg { |
||||||
|
int num_phys; |
||||||
|
enum sun4i_usb_phy_type type; |
||||||
|
u32 disc_thresh; |
||||||
|
u8 phyctl_offset; |
||||||
|
bool enable_pmu_unk1; |
||||||
|
bool phy0_dual_route; |
||||||
|
}; |
||||||
|
|
||||||
|
struct sun4i_usb_phy_info { |
||||||
|
const char *gpio_vbus; |
||||||
|
const char *gpio_vbus_det; |
||||||
|
const char *gpio_id_det; |
||||||
|
int rst_mask; |
||||||
|
} phy_info[] = { |
||||||
|
{ |
||||||
|
.gpio_vbus = CONFIG_USB0_VBUS_PIN, |
||||||
|
.gpio_vbus_det = CONFIG_USB0_VBUS_DET, |
||||||
|
.gpio_id_det = CONFIG_USB0_ID_DET, |
||||||
|
.rst_mask = (CCM_USB_CTRL_PHY0_RST | CCM_USB_CTRL_PHY0_CLK), |
||||||
|
}, |
||||||
|
{ |
||||||
|
.gpio_vbus = CONFIG_USB1_VBUS_PIN, |
||||||
|
.gpio_vbus_det = NULL, |
||||||
|
.gpio_id_det = NULL, |
||||||
|
.rst_mask = (CCM_USB_CTRL_PHY1_RST | CCM_USB_CTRL_PHY1_CLK), |
||||||
|
}, |
||||||
|
{ |
||||||
|
.gpio_vbus = CONFIG_USB2_VBUS_PIN, |
||||||
|
.gpio_vbus_det = NULL, |
||||||
|
.gpio_id_det = NULL, |
||||||
|
#ifdef CONFIG_MACH_SUN8I_A83T |
||||||
|
.rst_mask = (CCM_USB_CTRL_HSIC_RST | CCM_USB_CTRL_HSIC_CLK | |
||||||
|
CCM_USB_CTRL_12M_CLK), |
||||||
|
#else |
||||||
|
.rst_mask = (CCM_USB_CTRL_PHY2_RST | CCM_USB_CTRL_PHY2_CLK), |
||||||
|
#endif |
||||||
|
}, |
||||||
|
{ |
||||||
|
.gpio_vbus = CONFIG_USB3_VBUS_PIN, |
||||||
|
.gpio_vbus_det = NULL, |
||||||
|
.gpio_id_det = NULL, |
||||||
|
#ifdef CONFIG_MACH_SUN6I |
||||||
|
.rst_mask = (CCM_USB_CTRL_PHY3_RST | CCM_USB_CTRL_PHY3_CLK), |
||||||
|
#endif |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
struct sun4i_usb_phy_plat { |
||||||
|
void __iomem *pmu; |
||||||
|
int power_on_count; |
||||||
|
int gpio_vbus; |
||||||
|
int gpio_vbus_det; |
||||||
|
int gpio_id_det; |
||||||
|
int rst_mask; |
||||||
|
int id; |
||||||
|
}; |
||||||
|
|
||||||
|
struct sun4i_usb_phy_data { |
||||||
|
void __iomem *base; |
||||||
|
struct sunxi_ccm_reg *ccm; |
||||||
|
const struct sun4i_usb_phy_cfg *cfg; |
||||||
|
struct sun4i_usb_phy_plat *usb_phy; |
||||||
|
}; |
||||||
|
|
||||||
|
static int initial_usb_scan_delay = CONFIG_INITIAL_USB_SCAN_DELAY; |
||||||
|
|
||||||
|
static void sun4i_usb_phy_write(struct phy *phy, u32 addr, u32 data, int len) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_data *phy_data = dev_get_priv(phy->dev); |
||||||
|
struct sun4i_usb_phy_plat *usb_phy = &phy_data->usb_phy[phy->id]; |
||||||
|
u32 temp, usbc_bit = BIT(usb_phy->id * 2); |
||||||
|
void __iomem *phyctl = phy_data->base + phy_data->cfg->phyctl_offset; |
||||||
|
int i; |
||||||
|
|
||||||
|
if (phy_data->cfg->phyctl_offset == REG_PHYCTL_A33) { |
||||||
|
/* SoCs newer than A33 need us to set phyctl to 0 explicitly */ |
||||||
|
writel(0, phyctl); |
||||||
|
} |
||||||
|
|
||||||
|
for (i = 0; i < len; i++) { |
||||||
|
temp = readl(phyctl); |
||||||
|
|
||||||
|
/* clear the address portion */ |
||||||
|
temp &= ~(0xff << 8); |
||||||
|
|
||||||
|
/* set the address */ |
||||||
|
temp |= ((addr + i) << 8); |
||||||
|
writel(temp, phyctl); |
||||||
|
|
||||||
|
/* set the data bit and clear usbc bit*/ |
||||||
|
temp = readb(phyctl); |
||||||
|
if (data & 0x1) |
||||||
|
temp |= PHYCTL_DATA; |
||||||
|
else |
||||||
|
temp &= ~PHYCTL_DATA; |
||||||
|
temp &= ~usbc_bit; |
||||||
|
writeb(temp, phyctl); |
||||||
|
|
||||||
|
/* pulse usbc_bit */ |
||||||
|
temp = readb(phyctl); |
||||||
|
temp |= usbc_bit; |
||||||
|
writeb(temp, phyctl); |
||||||
|
|
||||||
|
temp = readb(phyctl); |
||||||
|
temp &= ~usbc_bit; |
||||||
|
writeb(temp, phyctl); |
||||||
|
|
||||||
|
data >>= 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void sun4i_usb_phy_passby(struct phy *phy, bool enable) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); |
||||||
|
struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; |
||||||
|
u32 bits, reg_value; |
||||||
|
|
||||||
|
if (!usb_phy->pmu) |
||||||
|
return; |
||||||
|
|
||||||
|
bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN | |
||||||
|
SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN; |
||||||
|
|
||||||
|
/* A83T USB2 is HSIC */ |
||||||
|
if (data->cfg->type == sun8i_a83t_phy && usb_phy->id == 2) |
||||||
|
bits |= SUNXI_EHCI_HS_FORCE | SUNXI_HSIC_CONNECT_INT | |
||||||
|
SUNXI_HSIC; |
||||||
|
|
||||||
|
reg_value = readl(usb_phy->pmu); |
||||||
|
|
||||||
|
if (enable) |
||||||
|
reg_value |= bits; |
||||||
|
else |
||||||
|
reg_value &= ~bits; |
||||||
|
|
||||||
|
writel(reg_value, usb_phy->pmu); |
||||||
|
} |
||||||
|
|
||||||
|
static int sun4i_usb_phy_power_on(struct phy *phy) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); |
||||||
|
struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; |
||||||
|
|
||||||
|
if (initial_usb_scan_delay) { |
||||||
|
mdelay(initial_usb_scan_delay); |
||||||
|
initial_usb_scan_delay = 0; |
||||||
|
} |
||||||
|
|
||||||
|
usb_phy->power_on_count++; |
||||||
|
if (usb_phy->power_on_count != 1) |
||||||
|
return 0; |
||||||
|
|
||||||
|
if (usb_phy->gpio_vbus >= 0) |
||||||
|
gpio_set_value(usb_phy->gpio_vbus, SUNXI_GPIO_PULL_UP); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int sun4i_usb_phy_power_off(struct phy *phy) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); |
||||||
|
struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; |
||||||
|
|
||||||
|
usb_phy->power_on_count--; |
||||||
|
if (usb_phy->power_on_count != 0) |
||||||
|
return 0; |
||||||
|
|
||||||
|
if (usb_phy->gpio_vbus >= 0) |
||||||
|
gpio_set_value(usb_phy->gpio_vbus, SUNXI_GPIO_PULL_DISABLE); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void sun4i_usb_phy0_reroute(struct sun4i_usb_phy_data *data, bool id_det) |
||||||
|
{ |
||||||
|
u32 regval; |
||||||
|
|
||||||
|
regval = readl(data->base + REG_PHY_OTGCTL); |
||||||
|
if (!id_det) { |
||||||
|
/* Host mode. Route phy0 to EHCI/OHCI */ |
||||||
|
regval &= ~OTGCTL_ROUTE_MUSB; |
||||||
|
} else { |
||||||
|
/* Peripheral mode. Route phy0 to MUSB */ |
||||||
|
regval |= OTGCTL_ROUTE_MUSB; |
||||||
|
} |
||||||
|
writel(regval, data->base + REG_PHY_OTGCTL); |
||||||
|
} |
||||||
|
|
||||||
|
static int sun4i_usb_phy_init(struct phy *phy) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); |
||||||
|
struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; |
||||||
|
u32 val; |
||||||
|
|
||||||
|
setbits_le32(&data->ccm->usb_clk_cfg, usb_phy->rst_mask); |
||||||
|
|
||||||
|
if (data->cfg->type == sun8i_a83t_phy) { |
||||||
|
if (phy->id == 0) { |
||||||
|
val = readl(data->base + data->cfg->phyctl_offset); |
||||||
|
val |= PHY_CTL_VBUSVLDEXT; |
||||||
|
val &= ~PHY_CTL_SIDDQ; |
||||||
|
writel(val, data->base + data->cfg->phyctl_offset); |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (usb_phy->pmu && data->cfg->enable_pmu_unk1) { |
||||||
|
val = readl(usb_phy->pmu + REG_PMU_UNK1); |
||||||
|
writel(val & ~2, usb_phy->pmu + REG_PMU_UNK1); |
||||||
|
} |
||||||
|
|
||||||
|
if (usb_phy->id == 0) |
||||||
|
sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, |
||||||
|
PHY_RES45_CAL_DATA, |
||||||
|
PHY_RES45_CAL_LEN); |
||||||
|
|
||||||
|
/* Adjust PHY's magnitude and rate */ |
||||||
|
sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, |
||||||
|
PHY_TX_MAGNITUDE | PHY_TX_RATE, |
||||||
|
PHY_TX_AMPLITUDE_LEN); |
||||||
|
|
||||||
|
/* Disconnect threshold adjustment */ |
||||||
|
sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, |
||||||
|
data->cfg->disc_thresh, PHY_DISCON_TH_LEN); |
||||||
|
} |
||||||
|
|
||||||
|
if (usb_phy->id != 0) |
||||||
|
sun4i_usb_phy_passby(phy, true); |
||||||
|
|
||||||
|
sun4i_usb_phy0_reroute(data, true); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int sun4i_usb_phy_exit(struct phy *phy) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); |
||||||
|
struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; |
||||||
|
|
||||||
|
if (phy->id == 0) { |
||||||
|
if (data->cfg->type == sun8i_a83t_phy) { |
||||||
|
void __iomem *phyctl = data->base + |
||||||
|
data->cfg->phyctl_offset; |
||||||
|
|
||||||
|
writel(readl(phyctl) | PHY_CTL_SIDDQ, phyctl); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
sun4i_usb_phy_passby(phy, false); |
||||||
|
|
||||||
|
clrbits_le32(&data->ccm->usb_clk_cfg, usb_phy->rst_mask); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int sun4i_usb_phy_xlate(struct phy *phy, |
||||||
|
struct ofnode_phandle_args *args) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); |
||||||
|
|
||||||
|
if (args->args_count >= data->cfg->num_phys) |
||||||
|
return -EINVAL; |
||||||
|
|
||||||
|
if (args->args_count) |
||||||
|
phy->id = args->args[0]; |
||||||
|
else |
||||||
|
phy->id = 0; |
||||||
|
|
||||||
|
debug("%s: phy_id = %ld\n", __func__, phy->id); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int sun4i_usb_phy_vbus_detect(struct phy *phy) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); |
||||||
|
struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; |
||||||
|
int err, retries = 3; |
||||||
|
|
||||||
|
debug("%s: id_det = %d\n", __func__, usb_phy->gpio_id_det); |
||||||
|
|
||||||
|
if (usb_phy->gpio_vbus_det < 0) |
||||||
|
return usb_phy->gpio_vbus_det; |
||||||
|
|
||||||
|
err = gpio_get_value(usb_phy->gpio_vbus_det); |
||||||
|
/*
|
||||||
|
* Vbus may have been provided by the board and just been turned of |
||||||
|
* some milliseconds ago on reset, what we're measuring then is a |
||||||
|
* residual charge on Vbus, sleep a bit and try again. |
||||||
|
*/ |
||||||
|
while (err > 0 && retries--) { |
||||||
|
mdelay(100); |
||||||
|
err = gpio_get_value(usb_phy->gpio_vbus_det); |
||||||
|
} |
||||||
|
|
||||||
|
return err; |
||||||
|
} |
||||||
|
|
||||||
|
int sun4i_usb_phy_id_detect(struct phy *phy) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); |
||||||
|
struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; |
||||||
|
|
||||||
|
debug("%s: id_det = %d\n", __func__, usb_phy->gpio_id_det); |
||||||
|
|
||||||
|
if (usb_phy->gpio_id_det < 0) |
||||||
|
return usb_phy->gpio_id_det; |
||||||
|
|
||||||
|
return gpio_get_value(usb_phy->gpio_id_det); |
||||||
|
} |
||||||
|
|
||||||
|
void sun4i_usb_phy_set_squelch_detect(struct phy *phy, bool enabled) |
||||||
|
{ |
||||||
|
sun4i_usb_phy_write(phy, PHY_SQUELCH_DETECT, enabled ? 0 : 2, 2); |
||||||
|
} |
||||||
|
|
||||||
|
static struct phy_ops sun4i_usb_phy_ops = { |
||||||
|
.of_xlate = sun4i_usb_phy_xlate, |
||||||
|
.init = sun4i_usb_phy_init, |
||||||
|
.power_on = sun4i_usb_phy_power_on, |
||||||
|
.power_off = sun4i_usb_phy_power_off, |
||||||
|
.exit = sun4i_usb_phy_exit, |
||||||
|
}; |
||||||
|
|
||||||
|
static int sun4i_usb_phy_probe(struct udevice *dev) |
||||||
|
{ |
||||||
|
struct sun4i_usb_phy_plat *plat = dev_get_platdata(dev); |
||||||
|
struct sun4i_usb_phy_data *data = dev_get_priv(dev); |
||||||
|
int i, ret; |
||||||
|
|
||||||
|
data->cfg = (const struct sun4i_usb_phy_cfg *)dev_get_driver_data(dev); |
||||||
|
if (!data->cfg) |
||||||
|
return -EINVAL; |
||||||
|
|
||||||
|
data->base = (void __iomem *)devfdt_get_addr_name(dev, "phy_ctrl"); |
||||||
|
if (IS_ERR(data->base)) |
||||||
|
return PTR_ERR(data->base); |
||||||
|
|
||||||
|
data->ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; |
||||||
|
if (IS_ERR(data->ccm)) |
||||||
|
return PTR_ERR(data->ccm); |
||||||
|
|
||||||
|
data->usb_phy = plat; |
||||||
|
for (i = 0; i < data->cfg->num_phys; i++) { |
||||||
|
struct sun4i_usb_phy_plat *phy = &plat[i]; |
||||||
|
struct sun4i_usb_phy_info *info = &phy_info[i]; |
||||||
|
char name[16]; |
||||||
|
|
||||||
|
phy->gpio_vbus = sunxi_name_to_gpio(info->gpio_vbus); |
||||||
|
if (phy->gpio_vbus >= 0) { |
||||||
|
ret = gpio_request(phy->gpio_vbus, "usb_vbus"); |
||||||
|
if (ret) |
||||||
|
return ret; |
||||||
|
ret = gpio_direction_output(phy->gpio_vbus, 0); |
||||||
|
if (ret) |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
phy->gpio_vbus_det = sunxi_name_to_gpio(info->gpio_vbus_det); |
||||||
|
if (phy->gpio_vbus_det >= 0) { |
||||||
|
ret = gpio_request(phy->gpio_vbus_det, "usb_vbus_det"); |
||||||
|
if (ret) |
||||||
|
return ret; |
||||||
|
ret = gpio_direction_input(phy->gpio_vbus_det); |
||||||
|
if (ret) |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
phy->gpio_id_det = sunxi_name_to_gpio(info->gpio_id_det); |
||||||
|
if (phy->gpio_id_det >= 0) { |
||||||
|
ret = gpio_request(phy->gpio_id_det, "usb_id_det"); |
||||||
|
if (ret) |
||||||
|
return ret; |
||||||
|
ret = gpio_direction_input(phy->gpio_id_det); |
||||||
|
if (ret) |
||||||
|
return ret; |
||||||
|
sunxi_gpio_set_pull(phy->gpio_id_det, SUNXI_GPIO_PULL_UP); |
||||||
|
} |
||||||
|
|
||||||
|
if (i || data->cfg->phy0_dual_route) { |
||||||
|
snprintf(name, sizeof(name), "pmu%d", i); |
||||||
|
phy->pmu = (void __iomem *)devfdt_get_addr_name(dev, name); |
||||||
|
if (IS_ERR(phy->pmu)) |
||||||
|
return PTR_ERR(phy->pmu); |
||||||
|
} |
||||||
|
|
||||||
|
phy->id = i; |
||||||
|
phy->rst_mask = info->rst_mask; |
||||||
|
}; |
||||||
|
|
||||||
|
setbits_le32(&data->ccm->usb_clk_cfg, CCM_USB_CTRL_PHYGATE); |
||||||
|
|
||||||
|
debug("Allwinner Sun4I USB PHY driver loaded\n"); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = { |
||||||
|
.num_phys = 3, |
||||||
|
.type = sun4i_a10_phy, |
||||||
|
.disc_thresh = 3, |
||||||
|
.phyctl_offset = REG_PHYCTL_A10, |
||||||
|
.enable_pmu_unk1 = false, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = { |
||||||
|
.num_phys = 2, |
||||||
|
.type = sun4i_a10_phy, |
||||||
|
.disc_thresh = 2, |
||||||
|
.phyctl_offset = REG_PHYCTL_A10, |
||||||
|
.enable_pmu_unk1 = false, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = { |
||||||
|
.num_phys = 3, |
||||||
|
.type = sun6i_a31_phy, |
||||||
|
.disc_thresh = 3, |
||||||
|
.phyctl_offset = REG_PHYCTL_A10, |
||||||
|
.enable_pmu_unk1 = false, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = { |
||||||
|
.num_phys = 3, |
||||||
|
.type = sun4i_a10_phy, |
||||||
|
.disc_thresh = 2, |
||||||
|
.phyctl_offset = REG_PHYCTL_A10, |
||||||
|
.enable_pmu_unk1 = false, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = { |
||||||
|
.num_phys = 2, |
||||||
|
.type = sun4i_a10_phy, |
||||||
|
.disc_thresh = 3, |
||||||
|
.phyctl_offset = REG_PHYCTL_A10, |
||||||
|
.enable_pmu_unk1 = false, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = { |
||||||
|
.num_phys = 2, |
||||||
|
.type = sun8i_a33_phy, |
||||||
|
.disc_thresh = 3, |
||||||
|
.phyctl_offset = REG_PHYCTL_A33, |
||||||
|
.enable_pmu_unk1 = false, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun8i_a83t_cfg = { |
||||||
|
.num_phys = 3, |
||||||
|
.type = sun8i_a83t_phy, |
||||||
|
.phyctl_offset = REG_PHYCTL_A33, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { |
||||||
|
.num_phys = 4, |
||||||
|
.type = sun8i_h3_phy, |
||||||
|
.disc_thresh = 3, |
||||||
|
.phyctl_offset = REG_PHYCTL_A33, |
||||||
|
.enable_pmu_unk1 = true, |
||||||
|
.phy0_dual_route = true, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun8i_v3s_cfg = { |
||||||
|
.num_phys = 1, |
||||||
|
.type = sun8i_v3s_phy, |
||||||
|
.disc_thresh = 3, |
||||||
|
.phyctl_offset = REG_PHYCTL_A33, |
||||||
|
.enable_pmu_unk1 = true, |
||||||
|
.phy0_dual_route = true, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = { |
||||||
|
.num_phys = 2, |
||||||
|
.type = sun50i_a64_phy, |
||||||
|
.disc_thresh = 3, |
||||||
|
.phyctl_offset = REG_PHYCTL_A33, |
||||||
|
.enable_pmu_unk1 = true, |
||||||
|
.phy0_dual_route = true, |
||||||
|
}; |
||||||
|
|
||||||
|
static const struct udevice_id sun4i_usb_phy_ids[] = { |
||||||
|
{ .compatible = "allwinner,sun4i-a10-usb-phy", .data = (ulong)&sun4i_a10_cfg }, |
||||||
|
{ .compatible = "allwinner,sun5i-a13-usb-phy", .data = (ulong)&sun5i_a13_cfg }, |
||||||
|
{ .compatible = "allwinner,sun6i-a31-usb-phy", .data = (ulong)&sun6i_a31_cfg }, |
||||||
|
{ .compatible = "allwinner,sun7i-a20-usb-phy", .data = (ulong)&sun7i_a20_cfg }, |
||||||
|
{ .compatible = "allwinner,sun8i-a23-usb-phy", .data = (ulong)&sun8i_a23_cfg }, |
||||||
|
{ .compatible = "allwinner,sun8i-a33-usb-phy", .data = (ulong)&sun8i_a33_cfg }, |
||||||
|
{ .compatible = "allwinner,sun8i-a83t-usb-phy", .data = (ulong)&sun8i_a83t_cfg }, |
||||||
|
{ .compatible = "allwinner,sun8i-h3-usb-phy", .data = (ulong)&sun8i_h3_cfg }, |
||||||
|
{ .compatible = "allwinner,sun8i-v3s-usb-phy", .data = (ulong)&sun8i_v3s_cfg }, |
||||||
|
{ .compatible = "allwinner,sun50i-a64-usb-phy", .data = (ulong)&sun50i_a64_cfg}, |
||||||
|
{ } |
||||||
|
}; |
||||||
|
|
||||||
|
U_BOOT_DRIVER(sun4i_usb_phy) = { |
||||||
|
.name = "sun4i_usb_phy", |
||||||
|
.id = UCLASS_PHY, |
||||||
|
.of_match = sun4i_usb_phy_ids, |
||||||
|
.ops = &sun4i_usb_phy_ops, |
||||||
|
.probe = sun4i_usb_phy_probe, |
||||||
|
.platdata_auto_alloc_size = sizeof(struct sun4i_usb_phy_plat[MAX_PHYS]), |
||||||
|
.priv_auto_alloc_size = sizeof(struct sun4i_usb_phy_data), |
||||||
|
}; |
@ -0,0 +1,34 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Jagan Teki <jagan@amarulasolutions.com> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-2.0+ |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef __GENERIC_PHY_SUN4I_USB_H |
||||||
|
#define __GENERIC_PHY_SUN4I_USB_H |
||||||
|
|
||||||
|
/**
|
||||||
|
* sun4i_usb_phy_id_detect - detect ID pin of USB PHY |
||||||
|
* |
||||||
|
* @phy: USB PHY port to detect ID pin |
||||||
|
* @return 0 if OK, or a negative error code |
||||||
|
*/ |
||||||
|
int sun4i_usb_phy_id_detect(struct phy *phy); |
||||||
|
|
||||||
|
/**
|
||||||
|
* sun4i_usb_phy_vbus_detect - detect VBUS pin of USB PHY |
||||||
|
* |
||||||
|
* @phy: USB PHY port to detect VBUS pin |
||||||
|
* @return 0 if OK, or a negative error code |
||||||
|
*/ |
||||||
|
int sun4i_usb_phy_vbus_detect(struct phy *phy); |
||||||
|
|
||||||
|
/**
|
||||||
|
* sun4i_usb_phy_set_squelch_detect() - Enable/disable squelch detect |
||||||
|
* |
||||||
|
* @phy: reference to a sun4i usb phy |
||||||
|
* @enabled: wether to enable or disable squelch detect |
||||||
|
*/ |
||||||
|
void sun4i_usb_phy_set_squelch_detect(struct phy *phy, bool enabled); |
||||||
|
|
||||||
|
#endif /*__GENERIC_PHY_SUN4I_USB_H */ |
Loading…
Reference in new issue