mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-11 13:27:06 +09:00
[ARM] tegra: usb_phy: Add support for usb2 ulpi external phy
Change-Id: Ie2ed0d22abae1319996fe0a6caf28ec7d7e4313d Signed-off-by: Benoit Goby <benoit@android.com>
This commit is contained in:
@@ -32,6 +32,11 @@ struct tegra_utmip_config {
|
||||
u8 xcvr_lsrslew;
|
||||
};
|
||||
|
||||
struct tegra_ulpi_config {
|
||||
int reset_gpio;
|
||||
const char *clk;
|
||||
};
|
||||
|
||||
enum tegra_usb_phy_port_speed {
|
||||
TEGRA_USB_PHY_PORT_SPEED_FULL = 0,
|
||||
TEGRA_USB_PHY_PORT_SPEED_LOW,
|
||||
@@ -55,16 +60,16 @@ struct tegra_usb_phy {
|
||||
int freq_sel;
|
||||
void __iomem *regs;
|
||||
void __iomem *pad_regs;
|
||||
struct clk *clk;
|
||||
struct clk *pll_u;
|
||||
struct clk *pad_clk;
|
||||
enum tegra_usb_phy_mode mode;
|
||||
struct tegra_utmip_config *config;
|
||||
void *config;
|
||||
struct tegra_utmip_context context;
|
||||
};
|
||||
|
||||
struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
|
||||
struct tegra_utmip_config *config,
|
||||
enum tegra_usb_phy_mode phy_mode);
|
||||
void *config, enum tegra_usb_phy_mode phy_mode);
|
||||
|
||||
int tegra_usb_phy_power_on(struct tegra_usb_phy *phy);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <asm/mach-types.h>
|
||||
#include <mach/usb_phy.h>
|
||||
#include <mach/iomap.h>
|
||||
@@ -31,10 +32,23 @@
|
||||
#define USB_USBSTS 0x144
|
||||
#define USB_USBSTS_PCI (1 << 2)
|
||||
|
||||
#define ULPI_VIEWPORT 0x170
|
||||
#define ULPI_WAKEUP (1 << 31)
|
||||
#define ULPI_RUN (1 << 30)
|
||||
#define ULPI_RD_RW_WRITE (1 << 29)
|
||||
#define ULPI_RD_RW_READ (0 << 29)
|
||||
#define ULPI_PORT(x) (((x) & 0x7) << 24)
|
||||
#define ULPI_ADDR(x) (((x) & 0xff) << 16)
|
||||
#define ULPI_DATA_RD(x) (((x) & 0xff) << 8)
|
||||
#define ULPI_DATA_WR(x) (((x) & 0xff) << 0)
|
||||
|
||||
#define USB_PORTSC1 0x184
|
||||
#define USB_PORTSC1_PTS(x) (((x) & 0x3) << 30)
|
||||
#define USB_PORTSC1_PSPD(x) (((x) & 0x3) << 26)
|
||||
#define USB_PORTSC1_PHCD (1 << 23)
|
||||
#define USB_PORTSC1_WKOC (1 << 22)
|
||||
#define USB_PORTSC1_WKDS (1 << 21)
|
||||
#define USB_PORTSC1_WKCN (1 << 20)
|
||||
#define USB_PORTSC1_PTC(x) (((x) & 0xf) << 16)
|
||||
#define USB_PORTSC1_PP (1 << 12)
|
||||
#define USB_PORTSC1_SUSP (1 << 7)
|
||||
@@ -47,7 +61,9 @@
|
||||
#define USB_SUSP_CLR (1 << 5)
|
||||
#define USB_PHY_CLK_VALID (1 << 7)
|
||||
#define UTMIP_RESET (1 << 11)
|
||||
#define UHSIC_RESET (1 << 11)
|
||||
#define UTMIP_PHY_ENABLE (1 << 12)
|
||||
#define ULPI_PHY_ENABLE (1 << 13)
|
||||
#define USB_SUSP_SET (1 << 14)
|
||||
|
||||
#define USB1_LEGACY_CTRL 0x410
|
||||
@@ -59,6 +75,18 @@
|
||||
#define USB1_VBUS_SENSE_CTL_AB_SESS_VLD (2 << 1)
|
||||
#define USB1_VBUS_SENSE_CTL_A_SESS_VLD (3 << 1)
|
||||
|
||||
#define ULPI_TIMING_CTRL_0 0x424
|
||||
#define ULPI_OUTPUT_PINMUX_BYP (1 << 10)
|
||||
#define ULPI_CLKOUT_PINMUX_BYP (1 << 11)
|
||||
|
||||
#define ULPI_TIMING_CTRL_1 0x428
|
||||
#define ULPI_DATA_TRIMMER_LOAD (1 << 0)
|
||||
#define ULPI_DATA_TRIMMER_SEL(x) (((x) & 0x7) << 1)
|
||||
#define ULPI_STPDIRNXT_TRIMMER_LOAD (1 << 16)
|
||||
#define ULPI_STPDIRNXT_TRIMMER_SEL(x) (((x) & 0x7) << 17)
|
||||
#define ULPI_DIR_TRIMMER_LOAD (1 << 24)
|
||||
#define ULPI_DIR_TRIMMER_SEL(x) (((x) & 0x7) << 25)
|
||||
|
||||
#define UTMIP_PLL_CFG1 0x804
|
||||
#define UTMIP_XTAL_FREQ_COUNT(x) (((x) & 0xfff) << 0)
|
||||
#define UTMIP_PLLU_ENABLE_DLY_COUNT(x) (((x) & 0x1f) << 27)
|
||||
@@ -446,11 +474,125 @@ static void utmi_phy_power_off(struct tegra_usb_phy *phy)
|
||||
utmip_pad_power_off(phy);
|
||||
}
|
||||
|
||||
static void ulpi_viewport_write(struct tegra_usb_phy *phy, u8 addr, u8 data)
|
||||
{
|
||||
unsigned long val;
|
||||
void __iomem *base = phy->regs;
|
||||
|
||||
val = ULPI_RUN | ULPI_RD_RW_WRITE | ULPI_PORT(0);
|
||||
val |= ULPI_ADDR(addr) | ULPI_DATA_WR(data);
|
||||
writel(val, base + ULPI_VIEWPORT);
|
||||
|
||||
if (utmi_wait_register(base + ULPI_VIEWPORT, ULPI_RUN, 0))
|
||||
pr_err("%s: timeout accessing ulpi phy\n", __func__);
|
||||
}
|
||||
|
||||
static void ulpi_phy_power_on(struct tegra_usb_phy *phy)
|
||||
{
|
||||
unsigned long val;
|
||||
void __iomem *base = phy->regs;
|
||||
struct tegra_ulpi_config *config = phy->config;
|
||||
|
||||
gpio_direction_output(config->reset_gpio, 0);
|
||||
msleep(5);
|
||||
gpio_direction_output(config->reset_gpio, 1);
|
||||
|
||||
clk_enable(phy->clk);
|
||||
msleep(1);
|
||||
|
||||
val = readl(base + USB_SUSP_CTRL);
|
||||
val |= UHSIC_RESET;
|
||||
writel(val, base + USB_SUSP_CTRL);
|
||||
|
||||
val = readl(base + ULPI_TIMING_CTRL_0);
|
||||
val |= ULPI_OUTPUT_PINMUX_BYP | ULPI_CLKOUT_PINMUX_BYP;
|
||||
writel(val, base + ULPI_TIMING_CTRL_0);
|
||||
|
||||
val = readl(base + USB_SUSP_CTRL);
|
||||
val |= ULPI_PHY_ENABLE;
|
||||
writel(val, base + USB_SUSP_CTRL);
|
||||
|
||||
val = 0;
|
||||
writel(val, base + ULPI_TIMING_CTRL_1);
|
||||
|
||||
val |= ULPI_DATA_TRIMMER_SEL(4);
|
||||
val |= ULPI_STPDIRNXT_TRIMMER_SEL(4);
|
||||
val |= ULPI_DIR_TRIMMER_SEL(4);
|
||||
writel(val, base + ULPI_TIMING_CTRL_1);
|
||||
udelay(10);
|
||||
|
||||
val |= ULPI_DATA_TRIMMER_LOAD;
|
||||
val |= ULPI_STPDIRNXT_TRIMMER_LOAD;
|
||||
val |= ULPI_DIR_TRIMMER_LOAD;
|
||||
writel(val, base + ULPI_TIMING_CTRL_1);
|
||||
|
||||
val = ULPI_WAKEUP | ULPI_RD_RW_WRITE | ULPI_PORT(0);
|
||||
writel(val, base + ULPI_VIEWPORT);
|
||||
|
||||
if (utmi_wait_register(base + ULPI_VIEWPORT, ULPI_WAKEUP, 0)) {
|
||||
pr_err("%s: timeout waiting for ulpi phy wakeup\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Fix VbusInvalid due to floating VBUS */
|
||||
ulpi_viewport_write(phy, 0x08, 0x40);
|
||||
ulpi_viewport_write(phy, 0x0B, 0x80);
|
||||
|
||||
val = readl(base + USB_PORTSC1);
|
||||
val |= USB_PORTSC1_WKOC | USB_PORTSC1_WKDS | USB_PORTSC1_WKCN;
|
||||
writel(val, base + USB_PORTSC1);
|
||||
|
||||
val = readl(base + USB_SUSP_CTRL);
|
||||
val |= USB_SUSP_CLR;
|
||||
writel(val, base + USB_SUSP_CTRL);
|
||||
udelay(100);
|
||||
|
||||
val = readl(base + USB_SUSP_CTRL);
|
||||
val &= ~USB_SUSP_CLR;
|
||||
writel(val, base + USB_SUSP_CTRL);
|
||||
}
|
||||
|
||||
static void ulpi_phy_power_off(struct tegra_usb_phy *phy)
|
||||
{
|
||||
unsigned long val;
|
||||
void __iomem *base = phy->regs;
|
||||
|
||||
/* Programming the ULPI register function control */
|
||||
ulpi_viewport_write(phy, 0x04, 0x4D);
|
||||
|
||||
/* Resetting the ULPI register IndicatorPassThru */
|
||||
ulpi_viewport_write(phy, 0x09, 0x40);
|
||||
|
||||
/* USB Interrupt Rising - making sure vbus comparator and id are off */
|
||||
ulpi_viewport_write(phy, 0x0D, 0x00);
|
||||
|
||||
/* USB Interrupt Falling */
|
||||
ulpi_viewport_write(phy, 0x10, 0x00);
|
||||
|
||||
/* Carkit Control */
|
||||
ulpi_viewport_write(phy, 0x19, 0x00);
|
||||
|
||||
/* Disabling ID float Rise/Fall (Carkit Enable) */
|
||||
ulpi_viewport_write(phy, 0x1D, 0x00);
|
||||
|
||||
/* USB I/O and power */
|
||||
ulpi_viewport_write(phy, 0x39, 0x00);
|
||||
|
||||
/* Clear WKCN/WKDS/WKOC wake-on events that can cause the USB
|
||||
* Controller to immediately bring the ULPI PHY out of low power
|
||||
*/
|
||||
val = readl(base + USB_PORTSC1);
|
||||
val &= ~(USB_PORTSC1_WKOC | USB_PORTSC1_WKDS | USB_PORTSC1_WKCN);
|
||||
writel(val, base + USB_PORTSC1);
|
||||
|
||||
clk_disable(phy->clk);
|
||||
}
|
||||
|
||||
struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
|
||||
struct tegra_utmip_config *config,
|
||||
enum tegra_usb_phy_mode phy_mode)
|
||||
void *config, enum tegra_usb_phy_mode phy_mode)
|
||||
{
|
||||
struct tegra_usb_phy *phy;
|
||||
struct tegra_ulpi_config *ulpi_config;
|
||||
unsigned long parent_rate;
|
||||
int freq_sel;
|
||||
int err;
|
||||
@@ -465,8 +607,14 @@ struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
|
||||
phy->context.valid = false;
|
||||
phy->mode = phy_mode;
|
||||
|
||||
if (!phy->config)
|
||||
phy->config = &utmip_default[instance];
|
||||
if (!phy->config) {
|
||||
if (instance == 1) {
|
||||
pr_err("%s: ulpi phy configuration missing", __func__);
|
||||
goto err0;
|
||||
} else {
|
||||
phy->config = &utmip_default[instance];
|
||||
}
|
||||
}
|
||||
|
||||
phy->pll_u = clk_get_sys(NULL, "pll_u");
|
||||
if (IS_ERR(phy->pll_u)) {
|
||||
@@ -488,8 +636,17 @@ struct tegra_usb_phy *tegra_usb_phy_open(int instance, void __iomem *regs,
|
||||
}
|
||||
phy->freq_sel = freq_sel;
|
||||
|
||||
/* TODO usb2 ulpi */
|
||||
if (phy->instance != 1) {
|
||||
if (phy->instance == 1) {
|
||||
ulpi_config = config;
|
||||
phy->clk = clk_get_sys(NULL, ulpi_config->clk);
|
||||
if (IS_ERR(phy->clk)) {
|
||||
pr_err("%s: can't get ulpi clock\n", __func__);
|
||||
goto err1;
|
||||
}
|
||||
tegra_gpio_enable(ulpi_config->reset_gpio);
|
||||
gpio_request(ulpi_config->reset_gpio, "ulpi_phy_reset_b");
|
||||
gpio_direction_output(ulpi_config->reset_gpio, 0);
|
||||
} else {
|
||||
err = utmip_pad_open(phy);
|
||||
if (err < 0)
|
||||
goto err1;
|
||||
@@ -507,8 +664,9 @@ err0:
|
||||
|
||||
int tegra_usb_phy_power_on(struct tegra_usb_phy *phy)
|
||||
{
|
||||
/* TODO usb2 ulpi */
|
||||
if (phy->instance != 1)
|
||||
if (phy->instance == 1)
|
||||
ulpi_phy_power_on(phy);
|
||||
else
|
||||
utmi_phy_power_on(phy);
|
||||
|
||||
return 0;
|
||||
@@ -516,8 +674,9 @@ int tegra_usb_phy_power_on(struct tegra_usb_phy *phy)
|
||||
|
||||
int tegra_usb_phy_power_off(struct tegra_usb_phy *phy)
|
||||
{
|
||||
/* TODO usb2 ulpi */
|
||||
if (phy->instance != 1)
|
||||
if (phy->instance == 1)
|
||||
ulpi_phy_power_off(phy);
|
||||
else
|
||||
utmi_phy_power_off(phy);
|
||||
|
||||
return 0;
|
||||
@@ -541,7 +700,9 @@ int tegra_usb_phy_clk_enable(struct tegra_usb_phy *phy)
|
||||
|
||||
int tegra_usb_phy_close(struct tegra_usb_phy *phy)
|
||||
{
|
||||
if (phy->instance != 1)
|
||||
if (phy->instance == 1)
|
||||
clk_put(phy->clk);
|
||||
else
|
||||
utmip_pad_close(phy);
|
||||
clk_disable(phy->pll_u);
|
||||
clk_put(phy->pll_u);
|
||||
|
||||
Reference in New Issue
Block a user