mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-29 23:28:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
224
drivers/usb/phy/Kconfig
Normal file
224
drivers/usb/phy/Kconfig
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
#
|
||||
# Physical Layer USB driver configuration
|
||||
#
|
||||
menu "USB Physical Layer drivers"
|
||||
|
||||
config USB_PHY
|
||||
def_bool n
|
||||
|
||||
config USB_OTG_WAKELOCK
|
||||
bool "Hold a wakelock when USB connected"
|
||||
depends on WAKELOCK
|
||||
select USB_OTG_UTILS
|
||||
help
|
||||
Select this to automatically hold a wakelock when USB is
|
||||
connected, preventing suspend.
|
||||
|
||||
#
|
||||
# USB Transceiver Drivers
|
||||
#
|
||||
config AB8500_USB
|
||||
tristate "AB8500 USB Transceiver Driver"
|
||||
depends on AB8500_CORE
|
||||
select USB_PHY
|
||||
help
|
||||
Enable this to support the USB OTG transceiver in AB8500 chip.
|
||||
This transceiver supports high and full speed devices plus,
|
||||
in host mode, low speed.
|
||||
|
||||
config FSL_USB2_OTG
|
||||
bool "Freescale USB OTG Transceiver Driver"
|
||||
depends on USB_EHCI_FSL && USB_FSL_USB2 && USB_OTG_FSM && PM_RUNTIME
|
||||
select USB_OTG
|
||||
select USB_PHY
|
||||
help
|
||||
Enable this to support Freescale USB OTG transceiver.
|
||||
|
||||
config ISP1301_OMAP
|
||||
tristate "Philips ISP1301 with OMAP OTG"
|
||||
depends on I2C && ARCH_OMAP_OTG
|
||||
depends on USB
|
||||
select USB_PHY
|
||||
help
|
||||
If you say yes here you get support for the Philips ISP1301
|
||||
USB-On-The-Go transceiver working with the OMAP OTG controller.
|
||||
The ISP1301 is a full speed USB transceiver which is used in
|
||||
products including H2, H3, and H4 development boards for Texas
|
||||
Instruments OMAP processors.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called phy-isp1301-omap.
|
||||
|
||||
config KEYSTONE_USB_PHY
|
||||
tristate "Keystone USB PHY Driver"
|
||||
depends on ARCH_KEYSTONE || COMPILE_TEST
|
||||
select NOP_USB_XCEIV
|
||||
help
|
||||
Enable this to support Keystone USB phy. This driver provides
|
||||
interface to interact with USB 2.0 and USB 3.0 PHY that is part
|
||||
of the Keystone SOC.
|
||||
|
||||
config NOP_USB_XCEIV
|
||||
tristate "NOP USB Transceiver Driver"
|
||||
select USB_PHY
|
||||
help
|
||||
This driver is to be used by all the usb transceiver which are either
|
||||
built-in with usb ip or which are autonomous and doesn't require any
|
||||
phy programming such as ISP1x04 etc.
|
||||
|
||||
config AM335X_CONTROL_USB
|
||||
tristate
|
||||
|
||||
config AM335X_PHY_USB
|
||||
tristate "AM335x USB PHY Driver"
|
||||
depends on ARM || COMPILE_TEST
|
||||
select USB_PHY
|
||||
select AM335X_CONTROL_USB
|
||||
select NOP_USB_XCEIV
|
||||
help
|
||||
This driver provides PHY support for that phy which part for the
|
||||
AM335x SoC.
|
||||
|
||||
config SAMSUNG_USBPHY
|
||||
tristate
|
||||
help
|
||||
Enable this to support Samsung USB phy helper driver for Samsung SoCs.
|
||||
This driver provides common interface to interact, for Samsung USB 2.0 PHY
|
||||
driver and later for Samsung USB 3.0 PHY driver.
|
||||
|
||||
config TWL6030_USB
|
||||
tristate "TWL6030 USB Transceiver Driver"
|
||||
depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS
|
||||
help
|
||||
Enable this to support the USB OTG transceiver on TWL6030
|
||||
family chips. This TWL6030 transceiver has the VBUS and ID GND
|
||||
and OTG SRP events capabilities. For all other transceiver functionality
|
||||
UTMI PHY is embedded in OMAP4430. The internal PHY configurations APIs
|
||||
are hooked to this driver through platform_data structure.
|
||||
The definition of internal PHY APIs are in the mach-omap2 layer.
|
||||
|
||||
config USB_GPIO_VBUS
|
||||
tristate "GPIO based peripheral-only VBUS sensing 'transceiver'"
|
||||
depends on GPIOLIB
|
||||
select USB_PHY
|
||||
help
|
||||
Provides simple GPIO VBUS sensing for controllers with an
|
||||
internal transceiver via the usb_phy interface, and
|
||||
optionally control of a D+ pullup GPIO as well as a VBUS
|
||||
current limit regulator.
|
||||
|
||||
config OMAP_OTG
|
||||
tristate "OMAP USB OTG controller driver"
|
||||
depends on ARCH_OMAP_OTG && EXTCON
|
||||
help
|
||||
Enable this to support some transceivers on OMAP1 platforms. OTG
|
||||
controller is needed to switch between host and peripheral modes.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called phy-omap-otg.
|
||||
|
||||
config TAHVO_USB
|
||||
tristate "Tahvo USB transceiver driver"
|
||||
depends on MFD_RETU && EXTCON
|
||||
select USB_PHY
|
||||
help
|
||||
Enable this to support USB transceiver on Tahvo. This is used
|
||||
at least on Nokia 770.
|
||||
|
||||
config TAHVO_USB_HOST_BY_DEFAULT
|
||||
depends on TAHVO_USB
|
||||
boolean "Device in USB host mode by default"
|
||||
help
|
||||
Say Y here, if you want the device to enter USB host mode
|
||||
by default on bootup.
|
||||
|
||||
config USB_ISP1301
|
||||
tristate "NXP ISP1301 USB transceiver support"
|
||||
depends on USB || USB_GADGET
|
||||
depends on I2C
|
||||
select USB_PHY
|
||||
help
|
||||
Say Y here to add support for the NXP ISP1301 USB transceiver driver.
|
||||
This chip is typically used as USB transceiver for USB host, gadget
|
||||
and OTG drivers (to be selected separately).
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called phy-isp1301.
|
||||
|
||||
config USB_MSM_OTG
|
||||
tristate "Qualcomm on-chip USB OTG controller support"
|
||||
depends on (USB || USB_GADGET) && (ARCH_MSM || ARCH_QCOM || COMPILE_TEST)
|
||||
depends on RESET_CONTROLLER
|
||||
select USB_PHY
|
||||
help
|
||||
Enable this to support the USB OTG transceiver on Qualcomm chips. It
|
||||
handles PHY initialization, clock management, and workarounds
|
||||
required after resetting the hardware and power management.
|
||||
This driver is required even for peripheral only or host only
|
||||
mode configurations.
|
||||
This driver is not supported on boards like trout which
|
||||
has an external PHY.
|
||||
|
||||
config USB_MV_OTG
|
||||
tristate "Marvell USB OTG support"
|
||||
depends on USB_EHCI_MV && USB_MV_UDC && PM_RUNTIME
|
||||
select USB_OTG
|
||||
select USB_PHY
|
||||
help
|
||||
Say Y here if you want to build Marvell USB OTG transciever
|
||||
driver in kernel (including PXA and MMP series). This driver
|
||||
implements role switch between EHCI host driver and gadget driver.
|
||||
|
||||
To compile this driver as a module, choose M here.
|
||||
|
||||
config USB_MXS_PHY
|
||||
tristate "Freescale MXS USB PHY support"
|
||||
depends on ARCH_MXC || ARCH_MXS
|
||||
select STMP_DEVICE
|
||||
select USB_PHY
|
||||
help
|
||||
Enable this to support the Freescale MXS USB PHY.
|
||||
|
||||
MXS Phy is used by some of the i.MX SoCs, for example imx23/28/6x.
|
||||
|
||||
config USB_RCAR_PHY
|
||||
tristate "Renesas R-Car USB PHY support"
|
||||
depends on USB || USB_GADGET
|
||||
depends on ARCH_R8A7778 || ARCH_R8A7779 || COMPILE_TEST
|
||||
select USB_PHY
|
||||
help
|
||||
Say Y here to add support for the Renesas R-Car USB common PHY driver.
|
||||
This chip is typically used as USB PHY for USB host, gadget.
|
||||
This driver supports R8A7778 and R8A7779.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called phy-rcar-usb.
|
||||
|
||||
config USB_RCAR_GEN2_PHY
|
||||
tristate "Renesas R-Car Gen2 USB PHY support"
|
||||
depends on ARCH_R8A7790 || ARCH_R8A7791 || COMPILE_TEST
|
||||
select USB_PHY
|
||||
help
|
||||
Say Y here to add support for the Renesas R-Car Gen2 USB PHY driver.
|
||||
It is typically used to control internal USB PHY for USBHS,
|
||||
and to configure shared USB channels 0 and 2.
|
||||
This driver supports R8A7790 and R8A7791.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called phy-rcar-gen2-usb.
|
||||
|
||||
config USB_ULPI
|
||||
bool "Generic ULPI Transceiver Driver"
|
||||
depends on ARM || ARM64
|
||||
help
|
||||
Enable this to support ULPI connected USB OTG transceivers which
|
||||
are likely found on embedded boards.
|
||||
|
||||
config USB_ULPI_VIEWPORT
|
||||
bool
|
||||
depends on USB_ULPI
|
||||
help
|
||||
Provides read/write operations to the ULPI phy register set for
|
||||
controllers with a viewport register (e.g. Chipidea/ARC controllers).
|
||||
|
||||
endmenu
|
||||
29
drivers/usb/phy/Makefile
Normal file
29
drivers/usb/phy/Makefile
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#
|
||||
# Makefile for physical layer USB drivers
|
||||
#
|
||||
obj-$(CONFIG_USB_PHY) += phy.o
|
||||
obj-$(CONFIG_OF) += of.o
|
||||
obj-$(CONFIG_USB_OTG_WAKELOCK) += otg-wakelock.o
|
||||
# transceiver drivers, keep the list sorted
|
||||
|
||||
obj-$(CONFIG_AB8500_USB) += phy-ab8500-usb.o
|
||||
obj-$(CONFIG_FSL_USB2_OTG) += phy-fsl-usb.o
|
||||
obj-$(CONFIG_ISP1301_OMAP) += phy-isp1301-omap.o
|
||||
obj-$(CONFIG_NOP_USB_XCEIV) += phy-generic.o
|
||||
obj-$(CONFIG_TAHVO_USB) += phy-tahvo.o
|
||||
obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o
|
||||
obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o
|
||||
obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o
|
||||
obj-$(CONFIG_SAMSUNG_USBPHY) += phy-samsung-usb.o
|
||||
obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o
|
||||
obj-$(CONFIG_USB_EHCI_TEGRA) += phy-tegra-usb.o
|
||||
obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o
|
||||
obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o
|
||||
obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o
|
||||
obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o
|
||||
obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o
|
||||
obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o
|
||||
obj-$(CONFIG_USB_RCAR_GEN2_PHY) += phy-rcar-gen2-usb.o
|
||||
obj-$(CONFIG_USB_ULPI) += phy-ulpi.o
|
||||
obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o
|
||||
obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o
|
||||
21
drivers/usb/phy/am35x-phy-control.h
Normal file
21
drivers/usb/phy/am35x-phy-control.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef _AM335x_PHY_CONTROL_H_
|
||||
#define _AM335x_PHY_CONTROL_H_
|
||||
|
||||
struct phy_control {
|
||||
void (*phy_power)(struct phy_control *phy_ctrl, u32 id, bool on);
|
||||
void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on);
|
||||
};
|
||||
|
||||
static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, bool on)
|
||||
{
|
||||
phy_ctrl->phy_power(phy_ctrl, id, on);
|
||||
}
|
||||
|
||||
static inline void phy_ctrl_wkup(struct phy_control *phy_ctrl, u32 id, bool on)
|
||||
{
|
||||
phy_ctrl->phy_wkup(phy_ctrl, id, on);
|
||||
}
|
||||
|
||||
struct phy_control *am335x_get_phy_control(struct device *dev);
|
||||
|
||||
#endif
|
||||
47
drivers/usb/phy/of.c
Normal file
47
drivers/usb/phy/of.c
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* USB of helper code
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/usb/of.h>
|
||||
#include <linux/usb/otg.h>
|
||||
|
||||
static const char *const usbphy_modes[] = {
|
||||
[USBPHY_INTERFACE_MODE_UNKNOWN] = "",
|
||||
[USBPHY_INTERFACE_MODE_UTMI] = "utmi",
|
||||
[USBPHY_INTERFACE_MODE_UTMIW] = "utmi_wide",
|
||||
[USBPHY_INTERFACE_MODE_ULPI] = "ulpi",
|
||||
[USBPHY_INTERFACE_MODE_SERIAL] = "serial",
|
||||
[USBPHY_INTERFACE_MODE_HSIC] = "hsic",
|
||||
};
|
||||
|
||||
/**
|
||||
* of_usb_get_phy_mode - Get phy mode for given device_node
|
||||
* @np: Pointer to the given device_node
|
||||
*
|
||||
* The function gets phy interface string from property 'phy_type',
|
||||
* and returns the correspondig enum usb_phy_interface
|
||||
*/
|
||||
enum usb_phy_interface of_usb_get_phy_mode(struct device_node *np)
|
||||
{
|
||||
const char *phy_type;
|
||||
int err, i;
|
||||
|
||||
err = of_property_read_string(np, "phy_type", &phy_type);
|
||||
if (err < 0)
|
||||
return USBPHY_INTERFACE_MODE_UNKNOWN;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(usbphy_modes); i++)
|
||||
if (!strcmp(phy_type, usbphy_modes[i]))
|
||||
return i;
|
||||
|
||||
return USBPHY_INTERFACE_MODE_UNKNOWN;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_usb_get_phy_mode);
|
||||
173
drivers/usb/phy/otg-wakelock.c
Normal file
173
drivers/usb/phy/otg-wakelock.c
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* otg-wakelock.c
|
||||
*
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/wakelock.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/usb/otg.h>
|
||||
|
||||
#define TEMPORARY_HOLD_TIME 2000
|
||||
|
||||
static bool enabled = true;
|
||||
static struct usb_phy *otgwl_xceiv;
|
||||
static struct notifier_block otgwl_nb;
|
||||
|
||||
/*
|
||||
* otgwl_spinlock is held while the VBUS lock is grabbed or dropped and the
|
||||
* held field is updated to match.
|
||||
*/
|
||||
|
||||
static DEFINE_SPINLOCK(otgwl_spinlock);
|
||||
|
||||
/*
|
||||
* Only one lock, but since these 3 fields are associated with each other...
|
||||
*/
|
||||
|
||||
struct otgwl_lock {
|
||||
char name[40];
|
||||
struct wake_lock wakelock;
|
||||
bool held;
|
||||
};
|
||||
|
||||
/*
|
||||
* VBUS present lock. Also used as a timed lock on charger
|
||||
* connect/disconnect and USB host disconnect, to allow the system
|
||||
* to react to the change in power.
|
||||
*/
|
||||
|
||||
static struct otgwl_lock vbus_lock;
|
||||
|
||||
static void otgwl_hold(struct otgwl_lock *lock)
|
||||
{
|
||||
if (!lock->held) {
|
||||
wake_lock(&lock->wakelock);
|
||||
lock->held = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void otgwl_temporary_hold(struct otgwl_lock *lock)
|
||||
{
|
||||
wake_lock_timeout(&lock->wakelock,
|
||||
msecs_to_jiffies(TEMPORARY_HOLD_TIME));
|
||||
lock->held = false;
|
||||
}
|
||||
|
||||
static void otgwl_drop(struct otgwl_lock *lock)
|
||||
{
|
||||
if (lock->held) {
|
||||
wake_unlock(&lock->wakelock);
|
||||
lock->held = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void otgwl_handle_event(unsigned long event)
|
||||
{
|
||||
unsigned long irqflags;
|
||||
|
||||
spin_lock_irqsave(&otgwl_spinlock, irqflags);
|
||||
|
||||
if (!enabled) {
|
||||
otgwl_drop(&vbus_lock);
|
||||
spin_unlock_irqrestore(&otgwl_spinlock, irqflags);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case USB_EVENT_VBUS:
|
||||
case USB_EVENT_ENUMERATED:
|
||||
otgwl_hold(&vbus_lock);
|
||||
break;
|
||||
|
||||
case USB_EVENT_NONE:
|
||||
case USB_EVENT_ID:
|
||||
case USB_EVENT_CHARGER:
|
||||
otgwl_temporary_hold(&vbus_lock);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&otgwl_spinlock, irqflags);
|
||||
}
|
||||
|
||||
static int otgwl_otg_notifications(struct notifier_block *nb,
|
||||
unsigned long event, void *unused)
|
||||
{
|
||||
otgwl_handle_event(event);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int set_enabled(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
int rv = param_set_bool(val, kp);
|
||||
|
||||
if (rv)
|
||||
return rv;
|
||||
|
||||
if (otgwl_xceiv)
|
||||
otgwl_handle_event(otgwl_xceiv->last_event);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kernel_param_ops enabled_param_ops = {
|
||||
.set = set_enabled,
|
||||
.get = param_get_bool,
|
||||
};
|
||||
|
||||
module_param_cb(enabled, &enabled_param_ops, &enabled, 0644);
|
||||
MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present");
|
||||
|
||||
static int __init otg_wakelock_init(void)
|
||||
{
|
||||
int ret;
|
||||
struct usb_phy *phy;
|
||||
|
||||
phy = usb_get_phy(USB_PHY_TYPE_USB2);
|
||||
|
||||
if (IS_ERR(phy)) {
|
||||
pr_err("%s: No USB transceiver found\n", __func__);
|
||||
return PTR_ERR(phy);
|
||||
}
|
||||
otgwl_xceiv = phy;
|
||||
|
||||
snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s",
|
||||
dev_name(otgwl_xceiv->dev));
|
||||
wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND,
|
||||
vbus_lock.name);
|
||||
|
||||
otgwl_nb.notifier_call = otgwl_otg_notifications;
|
||||
ret = usb_register_notifier(otgwl_xceiv, &otgwl_nb);
|
||||
|
||||
if (ret) {
|
||||
pr_err("%s: usb_register_notifier on transceiver %s"
|
||||
" failed\n", __func__,
|
||||
dev_name(otgwl_xceiv->dev));
|
||||
otgwl_xceiv = NULL;
|
||||
wake_lock_destroy(&vbus_lock.wakelock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
otgwl_handle_event(otgwl_xceiv->last_event);
|
||||
return ret;
|
||||
}
|
||||
|
||||
late_initcall(otg_wakelock_init);
|
||||
1526
drivers/usb/phy/phy-ab8500-usb.c
Normal file
1526
drivers/usb/phy/phy-ab8500-usb.c
Normal file
File diff suppressed because it is too large
Load diff
187
drivers/usb/phy/phy-am335x-control.c
Normal file
187
drivers/usb/phy/phy-am335x-control.c
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include "am35x-phy-control.h"
|
||||
|
||||
struct am335x_control_usb {
|
||||
struct device *dev;
|
||||
void __iomem *phy_reg;
|
||||
void __iomem *wkup;
|
||||
spinlock_t lock;
|
||||
struct phy_control phy_ctrl;
|
||||
};
|
||||
|
||||
#define AM335X_USB0_CTRL 0x0
|
||||
#define AM335X_USB1_CTRL 0x8
|
||||
#define AM335x_USB_WKUP 0x0
|
||||
|
||||
#define USBPHY_CM_PWRDN (1 << 0)
|
||||
#define USBPHY_OTG_PWRDN (1 << 1)
|
||||
#define USBPHY_OTGVDET_EN (1 << 19)
|
||||
#define USBPHY_OTGSESSEND_EN (1 << 20)
|
||||
|
||||
#define AM335X_PHY0_WK_EN (1 << 0)
|
||||
#define AM335X_PHY1_WK_EN (1 << 8)
|
||||
|
||||
static void am335x_phy_wkup(struct phy_control *phy_ctrl, u32 id, bool on)
|
||||
{
|
||||
struct am335x_control_usb *usb_ctrl;
|
||||
u32 val;
|
||||
u32 reg;
|
||||
|
||||
usb_ctrl = container_of(phy_ctrl, struct am335x_control_usb, phy_ctrl);
|
||||
|
||||
switch (id) {
|
||||
case 0:
|
||||
reg = AM335X_PHY0_WK_EN;
|
||||
break;
|
||||
case 1:
|
||||
reg = AM335X_PHY1_WK_EN;
|
||||
break;
|
||||
default:
|
||||
WARN_ON(1);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock(&usb_ctrl->lock);
|
||||
val = readl(usb_ctrl->wkup);
|
||||
|
||||
if (on)
|
||||
val |= reg;
|
||||
else
|
||||
val &= ~reg;
|
||||
|
||||
writel(val, usb_ctrl->wkup);
|
||||
spin_unlock(&usb_ctrl->lock);
|
||||
}
|
||||
|
||||
static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, bool on)
|
||||
{
|
||||
struct am335x_control_usb *usb_ctrl;
|
||||
u32 val;
|
||||
u32 reg;
|
||||
|
||||
usb_ctrl = container_of(phy_ctrl, struct am335x_control_usb, phy_ctrl);
|
||||
|
||||
switch (id) {
|
||||
case 0:
|
||||
reg = AM335X_USB0_CTRL;
|
||||
break;
|
||||
case 1:
|
||||
reg = AM335X_USB1_CTRL;
|
||||
break;
|
||||
default:
|
||||
WARN_ON(1);
|
||||
return;
|
||||
}
|
||||
|
||||
val = readl(usb_ctrl->phy_reg + reg);
|
||||
if (on) {
|
||||
val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN);
|
||||
val |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN;
|
||||
} else {
|
||||
val |= USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN;
|
||||
}
|
||||
|
||||
writel(val, usb_ctrl->phy_reg + reg);
|
||||
|
||||
/*
|
||||
* Give the PHY ~1ms to complete the power up operation.
|
||||
* Tests have shown unstable behaviour if other USB PHY related
|
||||
* registers are written too shortly after such a transition.
|
||||
*/
|
||||
if (on)
|
||||
mdelay(1);
|
||||
}
|
||||
|
||||
static const struct phy_control ctrl_am335x = {
|
||||
.phy_power = am335x_phy_power,
|
||||
.phy_wkup = am335x_phy_wkup,
|
||||
};
|
||||
|
||||
static const struct of_device_id omap_control_usb_id_table[] = {
|
||||
{ .compatible = "ti,am335x-usb-ctrl-module", .data = &ctrl_am335x },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_control_usb_id_table);
|
||||
|
||||
static struct platform_driver am335x_control_driver;
|
||||
static int match(struct device *dev, void *data)
|
||||
{
|
||||
struct device_node *node = (struct device_node *)data;
|
||||
return dev->of_node == node &&
|
||||
dev->driver == &am335x_control_driver.driver;
|
||||
}
|
||||
|
||||
struct phy_control *am335x_get_phy_control(struct device *dev)
|
||||
{
|
||||
struct device_node *node;
|
||||
struct am335x_control_usb *ctrl_usb;
|
||||
|
||||
node = of_parse_phandle(dev->of_node, "ti,ctrl_mod", 0);
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
dev = bus_find_device(&platform_bus_type, NULL, node, match);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
ctrl_usb = dev_get_drvdata(dev);
|
||||
if (!ctrl_usb)
|
||||
return NULL;
|
||||
return &ctrl_usb->phy_ctrl;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(am335x_get_phy_control);
|
||||
|
||||
static int am335x_control_usb_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
struct am335x_control_usb *ctrl_usb;
|
||||
const struct of_device_id *of_id;
|
||||
const struct phy_control *phy_ctrl;
|
||||
|
||||
of_id = of_match_node(omap_control_usb_id_table, pdev->dev.of_node);
|
||||
if (!of_id)
|
||||
return -EINVAL;
|
||||
|
||||
phy_ctrl = of_id->data;
|
||||
|
||||
ctrl_usb = devm_kzalloc(&pdev->dev, sizeof(*ctrl_usb), GFP_KERNEL);
|
||||
if (!ctrl_usb) {
|
||||
dev_err(&pdev->dev, "unable to alloc memory for control usb\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ctrl_usb->dev = &pdev->dev;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl");
|
||||
ctrl_usb->phy_reg = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(ctrl_usb->phy_reg))
|
||||
return PTR_ERR(ctrl_usb->phy_reg);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wakeup");
|
||||
ctrl_usb->wkup = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(ctrl_usb->wkup))
|
||||
return PTR_ERR(ctrl_usb->wkup);
|
||||
|
||||
spin_lock_init(&ctrl_usb->lock);
|
||||
ctrl_usb->phy_ctrl = *phy_ctrl;
|
||||
|
||||
dev_set_drvdata(ctrl_usb->dev, ctrl_usb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver am335x_control_driver = {
|
||||
.probe = am335x_control_usb_probe,
|
||||
.driver = {
|
||||
.name = "am335x-control-usb",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = omap_control_usb_id_table,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(am335x_control_driver);
|
||||
MODULE_LICENSE("GPL v2");
|
||||
147
drivers/usb/phy/phy-am335x.c
Normal file
147
drivers/usb/phy/phy-am335x.c
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
|
||||
#include "am35x-phy-control.h"
|
||||
#include "phy-generic.h"
|
||||
|
||||
struct am335x_phy {
|
||||
struct usb_phy_generic usb_phy_gen;
|
||||
struct phy_control *phy_ctrl;
|
||||
int id;
|
||||
};
|
||||
|
||||
static int am335x_init(struct usb_phy *phy)
|
||||
{
|
||||
struct am335x_phy *am_phy = dev_get_drvdata(phy->dev);
|
||||
|
||||
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void am335x_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct am335x_phy *am_phy = dev_get_drvdata(phy->dev);
|
||||
|
||||
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false);
|
||||
}
|
||||
|
||||
static int am335x_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct am335x_phy *am_phy;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret;
|
||||
|
||||
am_phy = devm_kzalloc(dev, sizeof(*am_phy), GFP_KERNEL);
|
||||
if (!am_phy)
|
||||
return -ENOMEM;
|
||||
|
||||
am_phy->phy_ctrl = am335x_get_phy_control(dev);
|
||||
if (!am_phy->phy_ctrl)
|
||||
return -EPROBE_DEFER;
|
||||
am_phy->id = of_alias_get_id(pdev->dev.of_node, "phy");
|
||||
if (am_phy->id < 0) {
|
||||
dev_err(&pdev->dev, "Missing PHY id: %d\n", am_phy->id);
|
||||
return am_phy->id;
|
||||
}
|
||||
|
||||
ret = usb_phy_gen_create_phy(dev, &am_phy->usb_phy_gen, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = usb_add_phy_dev(&am_phy->usb_phy_gen.phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
am_phy->usb_phy_gen.phy.init = am335x_init;
|
||||
am_phy->usb_phy_gen.phy.shutdown = am335x_shutdown;
|
||||
|
||||
platform_set_drvdata(pdev, am_phy);
|
||||
device_init_wakeup(dev, true);
|
||||
|
||||
/*
|
||||
* If we leave PHY wakeup enabled then AM33XX wakes up
|
||||
* immediately from DS0. To avoid this we mark dev->power.can_wakeup
|
||||
* to false. The same is checked in suspend routine to decide
|
||||
* on whether to enable PHY wakeup or not.
|
||||
* PHY wakeup works fine in standby mode, there by allowing us to
|
||||
* handle remote wakeup, wakeup on disconnect and connect.
|
||||
*/
|
||||
|
||||
device_set_wakeup_enable(dev, false);
|
||||
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int am335x_phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct am335x_phy *am_phy = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&am_phy->usb_phy_gen.phy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int am335x_phy_suspend(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct am335x_phy *am_phy = platform_get_drvdata(pdev);
|
||||
|
||||
/*
|
||||
* Enable phy wakeup only if dev->power.can_wakeup is true.
|
||||
* Make sure to enable wakeup to support remote wakeup in
|
||||
* standby mode ( same is not supported in OFF(DS0) mode).
|
||||
* Enable it by doing
|
||||
* echo enabled > /sys/bus/platform/devices/<usb-phy-id>/power/wakeup
|
||||
*/
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, true);
|
||||
|
||||
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int am335x_phy_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct am335x_phy *am_phy = platform_get_drvdata(pdev);
|
||||
|
||||
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, true);
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(am335x_pm_ops, am335x_phy_suspend, am335x_phy_resume);
|
||||
|
||||
static const struct of_device_id am335x_phy_ids[] = {
|
||||
{ .compatible = "ti,am335x-usb-phy" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, am335x_phy_ids);
|
||||
|
||||
static struct platform_driver am335x_phy_driver = {
|
||||
.probe = am335x_phy_probe,
|
||||
.remove = am335x_phy_remove,
|
||||
.driver = {
|
||||
.name = "am335x-phy-driver",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &am335x_pm_ops,
|
||||
.of_match_table = am335x_phy_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(am335x_phy_driver);
|
||||
MODULE_LICENSE("GPL v2");
|
||||
1228
drivers/usb/phy/phy-fsl-usb.c
Normal file
1228
drivers/usb/phy/phy-fsl-usb.c
Normal file
File diff suppressed because it is too large
Load diff
406
drivers/usb/phy/phy-fsl-usb.h
Normal file
406
drivers/usb/phy/phy-fsl-usb.h
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
/* Copyright (C) 2007,2008 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/usb/otg-fsm.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
/* USB Command Register Bit Masks */
|
||||
#define USB_CMD_RUN_STOP (0x1<<0)
|
||||
#define USB_CMD_CTRL_RESET (0x1<<1)
|
||||
#define USB_CMD_PERIODIC_SCHEDULE_EN (0x1<<4)
|
||||
#define USB_CMD_ASYNC_SCHEDULE_EN (0x1<<5)
|
||||
#define USB_CMD_INT_AA_DOORBELL (0x1<<6)
|
||||
#define USB_CMD_ASP (0x3<<8)
|
||||
#define USB_CMD_ASYNC_SCH_PARK_EN (0x1<<11)
|
||||
#define USB_CMD_SUTW (0x1<<13)
|
||||
#define USB_CMD_ATDTW (0x1<<14)
|
||||
#define USB_CMD_ITC (0xFF<<16)
|
||||
|
||||
/* bit 15,3,2 are frame list size */
|
||||
#define USB_CMD_FRAME_SIZE_1024 (0x0<<15 | 0x0<<2)
|
||||
#define USB_CMD_FRAME_SIZE_512 (0x0<<15 | 0x1<<2)
|
||||
#define USB_CMD_FRAME_SIZE_256 (0x0<<15 | 0x2<<2)
|
||||
#define USB_CMD_FRAME_SIZE_128 (0x0<<15 | 0x3<<2)
|
||||
#define USB_CMD_FRAME_SIZE_64 (0x1<<15 | 0x0<<2)
|
||||
#define USB_CMD_FRAME_SIZE_32 (0x1<<15 | 0x1<<2)
|
||||
#define USB_CMD_FRAME_SIZE_16 (0x1<<15 | 0x2<<2)
|
||||
#define USB_CMD_FRAME_SIZE_8 (0x1<<15 | 0x3<<2)
|
||||
|
||||
/* bit 9-8 are async schedule park mode count */
|
||||
#define USB_CMD_ASP_00 (0x0<<8)
|
||||
#define USB_CMD_ASP_01 (0x1<<8)
|
||||
#define USB_CMD_ASP_10 (0x2<<8)
|
||||
#define USB_CMD_ASP_11 (0x3<<8)
|
||||
#define USB_CMD_ASP_BIT_POS (8)
|
||||
|
||||
/* bit 23-16 are interrupt threshold control */
|
||||
#define USB_CMD_ITC_NO_THRESHOLD (0x00<<16)
|
||||
#define USB_CMD_ITC_1_MICRO_FRM (0x01<<16)
|
||||
#define USB_CMD_ITC_2_MICRO_FRM (0x02<<16)
|
||||
#define USB_CMD_ITC_4_MICRO_FRM (0x04<<16)
|
||||
#define USB_CMD_ITC_8_MICRO_FRM (0x08<<16)
|
||||
#define USB_CMD_ITC_16_MICRO_FRM (0x10<<16)
|
||||
#define USB_CMD_ITC_32_MICRO_FRM (0x20<<16)
|
||||
#define USB_CMD_ITC_64_MICRO_FRM (0x40<<16)
|
||||
#define USB_CMD_ITC_BIT_POS (16)
|
||||
|
||||
/* USB Status Register Bit Masks */
|
||||
#define USB_STS_INT (0x1<<0)
|
||||
#define USB_STS_ERR (0x1<<1)
|
||||
#define USB_STS_PORT_CHANGE (0x1<<2)
|
||||
#define USB_STS_FRM_LST_ROLL (0x1<<3)
|
||||
#define USB_STS_SYS_ERR (0x1<<4)
|
||||
#define USB_STS_IAA (0x1<<5)
|
||||
#define USB_STS_RESET_RECEIVED (0x1<<6)
|
||||
#define USB_STS_SOF (0x1<<7)
|
||||
#define USB_STS_DCSUSPEND (0x1<<8)
|
||||
#define USB_STS_HC_HALTED (0x1<<12)
|
||||
#define USB_STS_RCL (0x1<<13)
|
||||
#define USB_STS_PERIODIC_SCHEDULE (0x1<<14)
|
||||
#define USB_STS_ASYNC_SCHEDULE (0x1<<15)
|
||||
|
||||
/* USB Interrupt Enable Register Bit Masks */
|
||||
#define USB_INTR_INT_EN (0x1<<0)
|
||||
#define USB_INTR_ERR_INT_EN (0x1<<1)
|
||||
#define USB_INTR_PC_DETECT_EN (0x1<<2)
|
||||
#define USB_INTR_FRM_LST_ROLL_EN (0x1<<3)
|
||||
#define USB_INTR_SYS_ERR_EN (0x1<<4)
|
||||
#define USB_INTR_ASYN_ADV_EN (0x1<<5)
|
||||
#define USB_INTR_RESET_EN (0x1<<6)
|
||||
#define USB_INTR_SOF_EN (0x1<<7)
|
||||
#define USB_INTR_DEVICE_SUSPEND (0x1<<8)
|
||||
|
||||
/* Device Address bit masks */
|
||||
#define USB_DEVICE_ADDRESS_MASK (0x7F<<25)
|
||||
#define USB_DEVICE_ADDRESS_BIT_POS (25)
|
||||
/* PORTSC Register Bit Masks,Only one PORT in OTG mode*/
|
||||
#define PORTSC_CURRENT_CONNECT_STATUS (0x1<<0)
|
||||
#define PORTSC_CONNECT_STATUS_CHANGE (0x1<<1)
|
||||
#define PORTSC_PORT_ENABLE (0x1<<2)
|
||||
#define PORTSC_PORT_EN_DIS_CHANGE (0x1<<3)
|
||||
#define PORTSC_OVER_CURRENT_ACT (0x1<<4)
|
||||
#define PORTSC_OVER_CUURENT_CHG (0x1<<5)
|
||||
#define PORTSC_PORT_FORCE_RESUME (0x1<<6)
|
||||
#define PORTSC_PORT_SUSPEND (0x1<<7)
|
||||
#define PORTSC_PORT_RESET (0x1<<8)
|
||||
#define PORTSC_LINE_STATUS_BITS (0x3<<10)
|
||||
#define PORTSC_PORT_POWER (0x1<<12)
|
||||
#define PORTSC_PORT_INDICTOR_CTRL (0x3<<14)
|
||||
#define PORTSC_PORT_TEST_CTRL (0xF<<16)
|
||||
#define PORTSC_WAKE_ON_CONNECT_EN (0x1<<20)
|
||||
#define PORTSC_WAKE_ON_CONNECT_DIS (0x1<<21)
|
||||
#define PORTSC_WAKE_ON_OVER_CURRENT (0x1<<22)
|
||||
#define PORTSC_PHY_LOW_POWER_SPD (0x1<<23)
|
||||
#define PORTSC_PORT_FORCE_FULL_SPEED (0x1<<24)
|
||||
#define PORTSC_PORT_SPEED_MASK (0x3<<26)
|
||||
#define PORTSC_TRANSCEIVER_WIDTH (0x1<<28)
|
||||
#define PORTSC_PHY_TYPE_SEL (0x3<<30)
|
||||
/* bit 11-10 are line status */
|
||||
#define PORTSC_LINE_STATUS_SE0 (0x0<<10)
|
||||
#define PORTSC_LINE_STATUS_JSTATE (0x1<<10)
|
||||
#define PORTSC_LINE_STATUS_KSTATE (0x2<<10)
|
||||
#define PORTSC_LINE_STATUS_UNDEF (0x3<<10)
|
||||
#define PORTSC_LINE_STATUS_BIT_POS (10)
|
||||
|
||||
/* bit 15-14 are port indicator control */
|
||||
#define PORTSC_PIC_OFF (0x0<<14)
|
||||
#define PORTSC_PIC_AMBER (0x1<<14)
|
||||
#define PORTSC_PIC_GREEN (0x2<<14)
|
||||
#define PORTSC_PIC_UNDEF (0x3<<14)
|
||||
#define PORTSC_PIC_BIT_POS (14)
|
||||
|
||||
/* bit 19-16 are port test control */
|
||||
#define PORTSC_PTC_DISABLE (0x0<<16)
|
||||
#define PORTSC_PTC_JSTATE (0x1<<16)
|
||||
#define PORTSC_PTC_KSTATE (0x2<<16)
|
||||
#define PORTSC_PTC_SEQNAK (0x3<<16)
|
||||
#define PORTSC_PTC_PACKET (0x4<<16)
|
||||
#define PORTSC_PTC_FORCE_EN (0x5<<16)
|
||||
#define PORTSC_PTC_BIT_POS (16)
|
||||
|
||||
/* bit 27-26 are port speed */
|
||||
#define PORTSC_PORT_SPEED_FULL (0x0<<26)
|
||||
#define PORTSC_PORT_SPEED_LOW (0x1<<26)
|
||||
#define PORTSC_PORT_SPEED_HIGH (0x2<<26)
|
||||
#define PORTSC_PORT_SPEED_UNDEF (0x3<<26)
|
||||
#define PORTSC_SPEED_BIT_POS (26)
|
||||
|
||||
/* bit 28 is parallel transceiver width for UTMI interface */
|
||||
#define PORTSC_PTW (0x1<<28)
|
||||
#define PORTSC_PTW_8BIT (0x0<<28)
|
||||
#define PORTSC_PTW_16BIT (0x1<<28)
|
||||
|
||||
/* bit 31-30 are port transceiver select */
|
||||
#define PORTSC_PTS_UTMI (0x0<<30)
|
||||
#define PORTSC_PTS_ULPI (0x2<<30)
|
||||
#define PORTSC_PTS_FSLS_SERIAL (0x3<<30)
|
||||
#define PORTSC_PTS_BIT_POS (30)
|
||||
|
||||
#define PORTSC_W1C_BITS \
|
||||
(PORTSC_CONNECT_STATUS_CHANGE | \
|
||||
PORTSC_PORT_EN_DIS_CHANGE | \
|
||||
PORTSC_OVER_CUURENT_CHG)
|
||||
|
||||
/* OTG Status Control Register Bit Masks */
|
||||
#define OTGSC_CTRL_VBUS_DISCHARGE (0x1<<0)
|
||||
#define OTGSC_CTRL_VBUS_CHARGE (0x1<<1)
|
||||
#define OTGSC_CTRL_OTG_TERMINATION (0x1<<3)
|
||||
#define OTGSC_CTRL_DATA_PULSING (0x1<<4)
|
||||
#define OTGSC_CTRL_ID_PULL_EN (0x1<<5)
|
||||
#define OTGSC_HA_DATA_PULSE (0x1<<6)
|
||||
#define OTGSC_HA_BA (0x1<<7)
|
||||
#define OTGSC_STS_USB_ID (0x1<<8)
|
||||
#define OTGSC_STS_A_VBUS_VALID (0x1<<9)
|
||||
#define OTGSC_STS_A_SESSION_VALID (0x1<<10)
|
||||
#define OTGSC_STS_B_SESSION_VALID (0x1<<11)
|
||||
#define OTGSC_STS_B_SESSION_END (0x1<<12)
|
||||
#define OTGSC_STS_1MS_TOGGLE (0x1<<13)
|
||||
#define OTGSC_STS_DATA_PULSING (0x1<<14)
|
||||
#define OTGSC_INTSTS_USB_ID (0x1<<16)
|
||||
#define OTGSC_INTSTS_A_VBUS_VALID (0x1<<17)
|
||||
#define OTGSC_INTSTS_A_SESSION_VALID (0x1<<18)
|
||||
#define OTGSC_INTSTS_B_SESSION_VALID (0x1<<19)
|
||||
#define OTGSC_INTSTS_B_SESSION_END (0x1<<20)
|
||||
#define OTGSC_INTSTS_1MS (0x1<<21)
|
||||
#define OTGSC_INTSTS_DATA_PULSING (0x1<<22)
|
||||
#define OTGSC_INTR_USB_ID_EN (0x1<<24)
|
||||
#define OTGSC_INTR_A_VBUS_VALID_EN (0x1<<25)
|
||||
#define OTGSC_INTR_A_SESSION_VALID_EN (0x1<<26)
|
||||
#define OTGSC_INTR_B_SESSION_VALID_EN (0x1<<27)
|
||||
#define OTGSC_INTR_B_SESSION_END_EN (0x1<<28)
|
||||
#define OTGSC_INTR_1MS_TIMER_EN (0x1<<29)
|
||||
#define OTGSC_INTR_DATA_PULSING_EN (0x1<<30)
|
||||
#define OTGSC_INTSTS_MASK (0x00ff0000)
|
||||
|
||||
/* USB MODE Register Bit Masks */
|
||||
#define USB_MODE_CTRL_MODE_IDLE (0x0<<0)
|
||||
#define USB_MODE_CTRL_MODE_DEVICE (0x2<<0)
|
||||
#define USB_MODE_CTRL_MODE_HOST (0x3<<0)
|
||||
#define USB_MODE_CTRL_MODE_RSV (0x1<<0)
|
||||
#define USB_MODE_SETUP_LOCK_OFF (0x1<<3)
|
||||
#define USB_MODE_STREAM_DISABLE (0x1<<4)
|
||||
#define USB_MODE_ES (0x1<<2) /* Endian Select */
|
||||
|
||||
/* control Register Bit Masks */
|
||||
#define USB_CTRL_IOENB (0x1<<2)
|
||||
#define USB_CTRL_ULPI_INT0EN (0x1<<0)
|
||||
|
||||
/* BCSR5 */
|
||||
#define BCSR5_INT_USB (0x02)
|
||||
|
||||
/* USB module clk cfg */
|
||||
#define SCCR_OFFS (0xA08)
|
||||
#define SCCR_USB_CLK_DISABLE (0x00000000) /* USB clk disable */
|
||||
#define SCCR_USB_MPHCM_11 (0x00c00000)
|
||||
#define SCCR_USB_MPHCM_01 (0x00400000)
|
||||
#define SCCR_USB_MPHCM_10 (0x00800000)
|
||||
#define SCCR_USB_DRCM_11 (0x00300000)
|
||||
#define SCCR_USB_DRCM_01 (0x00100000)
|
||||
#define SCCR_USB_DRCM_10 (0x00200000)
|
||||
|
||||
#define SICRL_OFFS (0x114)
|
||||
#define SICRL_USB0 (0x40000000)
|
||||
#define SICRL_USB1 (0x20000000)
|
||||
|
||||
#define SICRH_OFFS (0x118)
|
||||
#define SICRH_USB_UTMI (0x00020000)
|
||||
|
||||
/* OTG interrupt enable bit masks */
|
||||
#define OTGSC_INTERRUPT_ENABLE_BITS_MASK \
|
||||
(OTGSC_INTR_USB_ID_EN | \
|
||||
OTGSC_INTR_1MS_TIMER_EN | \
|
||||
OTGSC_INTR_A_VBUS_VALID_EN | \
|
||||
OTGSC_INTR_A_SESSION_VALID_EN | \
|
||||
OTGSC_INTR_B_SESSION_VALID_EN | \
|
||||
OTGSC_INTR_B_SESSION_END_EN | \
|
||||
OTGSC_INTR_DATA_PULSING_EN)
|
||||
|
||||
/* OTG interrupt status bit masks */
|
||||
#define OTGSC_INTERRUPT_STATUS_BITS_MASK \
|
||||
(OTGSC_INTSTS_USB_ID | \
|
||||
OTGSC_INTR_1MS_TIMER_EN | \
|
||||
OTGSC_INTSTS_A_VBUS_VALID | \
|
||||
OTGSC_INTSTS_A_SESSION_VALID | \
|
||||
OTGSC_INTSTS_B_SESSION_VALID | \
|
||||
OTGSC_INTSTS_B_SESSION_END | \
|
||||
OTGSC_INTSTS_DATA_PULSING)
|
||||
|
||||
/*
|
||||
* A-DEVICE timing constants
|
||||
*/
|
||||
|
||||
/* Wait for VBUS Rise */
|
||||
#define TA_WAIT_VRISE (100) /* a_wait_vrise 100 ms, section: 6.6.5.1 */
|
||||
|
||||
/* Wait for B-Connect */
|
||||
#define TA_WAIT_BCON (10000) /* a_wait_bcon > 1 sec, section: 6.6.5.2
|
||||
* This is only used to get out of
|
||||
* OTG_STATE_A_WAIT_BCON state if there was
|
||||
* no connection for these many milliseconds
|
||||
*/
|
||||
|
||||
/* A-Idle to B-Disconnect */
|
||||
/* It is necessary for this timer to be more than 750 ms because of a bug in OPT
|
||||
* test 5.4 in which B OPT disconnects after 750 ms instead of 75ms as stated
|
||||
* in the test description
|
||||
*/
|
||||
#define TA_AIDL_BDIS (5000) /* a_suspend minimum 200 ms, section: 6.6.5.3 */
|
||||
|
||||
/* B-Idle to A-Disconnect */
|
||||
#define TA_BIDL_ADIS (12) /* 3 to 200 ms */
|
||||
|
||||
/* B-device timing constants */
|
||||
|
||||
|
||||
/* Data-Line Pulse Time*/
|
||||
#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms, section:5.3.3 */
|
||||
#define TB_DATA_PLS_MIN (5) /* minimum 5 ms */
|
||||
#define TB_DATA_PLS_MAX (10) /* maximum 10 ms */
|
||||
|
||||
/* SRP Initiate Time */
|
||||
#define TB_SRP_INIT (100) /* b_srp_init,maximum 100 ms, section:5.3.8 */
|
||||
|
||||
/* SRP Fail Time */
|
||||
#define TB_SRP_FAIL (7000) /* b_srp_init,Fail time 5~30s, section:6.8.2.2*/
|
||||
|
||||
/* SRP result wait time */
|
||||
#define TB_SRP_WAIT (60)
|
||||
|
||||
/* VBus time */
|
||||
#define TB_VBUS_PLS (30) /* time to keep vbus pulsing asserted */
|
||||
|
||||
/* Discharge time */
|
||||
/* This time should be less than 10ms. It varies from system to system. */
|
||||
#define TB_VBUS_DSCHRG (8)
|
||||
|
||||
/* A-SE0 to B-Reset */
|
||||
#define TB_ASE0_BRST (20) /* b_wait_acon, mini 3.125 ms,section:6.8.2.4 */
|
||||
|
||||
/* A bus suspend timer before we can switch to b_wait_aconn */
|
||||
#define TB_A_SUSPEND (7)
|
||||
#define TB_BUS_RESUME (12)
|
||||
|
||||
/* SE0 Time Before SRP */
|
||||
#define TB_SE0_SRP (2) /* b_idle,minimum 2 ms, section:5.3.2 */
|
||||
|
||||
#define SET_OTG_STATE(otg_ptr, newstate) ((otg_ptr)->state = newstate)
|
||||
|
||||
struct usb_dr_mmap {
|
||||
/* Capability register */
|
||||
u8 res1[256];
|
||||
u16 caplength; /* Capability Register Length */
|
||||
u16 hciversion; /* Host Controller Interface Version */
|
||||
u32 hcsparams; /* Host Controller Structual Parameters */
|
||||
u32 hccparams; /* Host Controller Capability Parameters */
|
||||
u8 res2[20];
|
||||
u32 dciversion; /* Device Controller Interface Version */
|
||||
u32 dccparams; /* Device Controller Capability Parameters */
|
||||
u8 res3[24];
|
||||
/* Operation register */
|
||||
u32 usbcmd; /* USB Command Register */
|
||||
u32 usbsts; /* USB Status Register */
|
||||
u32 usbintr; /* USB Interrupt Enable Register */
|
||||
u32 frindex; /* Frame Index Register */
|
||||
u8 res4[4];
|
||||
u32 deviceaddr; /* Device Address */
|
||||
u32 endpointlistaddr; /* Endpoint List Address Register */
|
||||
u8 res5[4];
|
||||
u32 burstsize; /* Master Interface Data Burst Size Register */
|
||||
u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */
|
||||
u8 res6[8];
|
||||
u32 ulpiview; /* ULPI register access */
|
||||
u8 res7[12];
|
||||
u32 configflag; /* Configure Flag Register */
|
||||
u32 portsc; /* Port 1 Status and Control Register */
|
||||
u8 res8[28];
|
||||
u32 otgsc; /* On-The-Go Status and Control */
|
||||
u32 usbmode; /* USB Mode Register */
|
||||
u32 endptsetupstat; /* Endpoint Setup Status Register */
|
||||
u32 endpointprime; /* Endpoint Initialization Register */
|
||||
u32 endptflush; /* Endpoint Flush Register */
|
||||
u32 endptstatus; /* Endpoint Status Register */
|
||||
u32 endptcomplete; /* Endpoint Complete Register */
|
||||
u32 endptctrl[6]; /* Endpoint Control Registers */
|
||||
u8 res9[552];
|
||||
u32 snoop1;
|
||||
u32 snoop2;
|
||||
u32 age_cnt_thresh; /* Age Count Threshold Register */
|
||||
u32 pri_ctrl; /* Priority Control Register */
|
||||
u32 si_ctrl; /* System Interface Control Register */
|
||||
u8 res10[236];
|
||||
u32 control; /* General Purpose Control Register */
|
||||
};
|
||||
|
||||
struct fsl_otg_timer {
|
||||
unsigned long expires; /* Number of count increase to timeout */
|
||||
unsigned long count; /* Tick counter */
|
||||
void (*function)(unsigned long); /* Timeout function */
|
||||
unsigned long data; /* Data passed to function */
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
inline struct fsl_otg_timer *otg_timer_initializer
|
||||
(void (*function)(unsigned long), unsigned long expires, unsigned long data)
|
||||
{
|
||||
struct fsl_otg_timer *timer;
|
||||
|
||||
timer = kmalloc(sizeof(struct fsl_otg_timer), GFP_KERNEL);
|
||||
if (!timer)
|
||||
return NULL;
|
||||
timer->function = function;
|
||||
timer->expires = expires;
|
||||
timer->data = data;
|
||||
return timer;
|
||||
}
|
||||
|
||||
struct fsl_otg {
|
||||
struct usb_phy phy;
|
||||
struct otg_fsm fsm;
|
||||
struct usb_dr_mmap *dr_mem_map;
|
||||
struct delayed_work otg_event;
|
||||
|
||||
/* used for usb host */
|
||||
struct work_struct work_wq;
|
||||
u8 host_working;
|
||||
|
||||
int irq;
|
||||
};
|
||||
|
||||
struct fsl_otg_config {
|
||||
u8 otg_port;
|
||||
};
|
||||
|
||||
/* For SRP and HNP handle */
|
||||
#define FSL_OTG_MAJOR 240
|
||||
#define FSL_OTG_NAME "fsl-usb2-otg"
|
||||
/* Command to OTG driver ioctl */
|
||||
#define OTG_IOCTL_MAGIC FSL_OTG_MAJOR
|
||||
/* if otg work as host, it should return 1, otherwise return 0 */
|
||||
#define GET_OTG_STATUS _IOR(OTG_IOCTL_MAGIC, 1, int)
|
||||
#define SET_A_SUSPEND_REQ _IOW(OTG_IOCTL_MAGIC, 2, int)
|
||||
#define SET_A_BUS_DROP _IOW(OTG_IOCTL_MAGIC, 3, int)
|
||||
#define SET_A_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 4, int)
|
||||
#define SET_B_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 5, int)
|
||||
#define GET_A_SUSPEND_REQ _IOR(OTG_IOCTL_MAGIC, 6, int)
|
||||
#define GET_A_BUS_DROP _IOR(OTG_IOCTL_MAGIC, 7, int)
|
||||
#define GET_A_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 8, int)
|
||||
#define GET_B_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 9, int)
|
||||
|
||||
void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer);
|
||||
void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer);
|
||||
void fsl_otg_pulse_vbus(void);
|
||||
309
drivers/usb/phy/phy-generic.c
Normal file
309
drivers/usb/phy/phy-generic.c
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* drivers/usb/otg/nop-usb-xceiv.c
|
||||
*
|
||||
* NOP USB transceiver for all USB transceiver which are either built-in
|
||||
* into USB IP or which are mostly autonomous.
|
||||
*
|
||||
* Copyright (C) 2009 Texas Instruments Inc
|
||||
* Author: Ajay Kumar Gupta <ajay.gupta@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* Current status:
|
||||
* This provides a "nop" transceiver for PHYs which are
|
||||
* autonomous such as isp1504, isp1707, etc.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "phy-generic.h"
|
||||
|
||||
struct platform_device *usb_phy_generic_register(void)
|
||||
{
|
||||
return platform_device_register_simple("usb_phy_generic",
|
||||
PLATFORM_DEVID_AUTO, NULL, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_phy_generic_register);
|
||||
|
||||
void usb_phy_generic_unregister(struct platform_device *pdev)
|
||||
{
|
||||
platform_device_unregister(pdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_phy_generic_unregister);
|
||||
|
||||
static int nop_set_suspend(struct usb_phy *x, int suspend)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nop_reset_set(struct usb_phy_generic *nop, int asserted)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (!gpio_is_valid(nop->gpio_reset))
|
||||
return;
|
||||
|
||||
value = asserted;
|
||||
if (nop->reset_active_low)
|
||||
value = !value;
|
||||
|
||||
gpio_set_value_cansleep(nop->gpio_reset, value);
|
||||
|
||||
if (!asserted)
|
||||
usleep_range(10000, 20000);
|
||||
}
|
||||
|
||||
int usb_gen_phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
|
||||
|
||||
if (!IS_ERR(nop->vcc)) {
|
||||
if (regulator_enable(nop->vcc))
|
||||
dev_err(phy->dev, "Failed to enable power\n");
|
||||
}
|
||||
|
||||
if (!IS_ERR(nop->clk))
|
||||
clk_prepare_enable(nop->clk);
|
||||
|
||||
/* De-assert RESET */
|
||||
nop_reset_set(nop, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_gen_phy_init);
|
||||
|
||||
void usb_gen_phy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
|
||||
|
||||
/* Assert RESET */
|
||||
nop_reset_set(nop, 1);
|
||||
|
||||
if (!IS_ERR(nop->clk))
|
||||
clk_disable_unprepare(nop->clk);
|
||||
|
||||
if (!IS_ERR(nop->vcc)) {
|
||||
if (regulator_disable(nop->vcc))
|
||||
dev_err(phy->dev, "Failed to disable power\n");
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_gen_phy_shutdown);
|
||||
|
||||
static int nop_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget)
|
||||
{
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
if (!gadget) {
|
||||
otg->gadget = NULL;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
otg->gadget = gadget;
|
||||
otg->phy->state = OTG_STATE_B_IDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nop_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
if (!host) {
|
||||
otg->host = NULL;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
otg->host = host;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop,
|
||||
struct usb_phy_generic_platform_data *pdata)
|
||||
{
|
||||
enum usb_phy_type type = USB_PHY_TYPE_USB2;
|
||||
int err;
|
||||
|
||||
u32 clk_rate = 0;
|
||||
bool needs_vcc = false;
|
||||
|
||||
nop->reset_active_low = true; /* default behaviour */
|
||||
|
||||
if (dev->of_node) {
|
||||
struct device_node *node = dev->of_node;
|
||||
enum of_gpio_flags flags = 0;
|
||||
|
||||
if (of_property_read_u32(node, "clock-frequency", &clk_rate))
|
||||
clk_rate = 0;
|
||||
|
||||
needs_vcc = of_property_read_bool(node, "vcc-supply");
|
||||
nop->gpio_reset = of_get_named_gpio_flags(node, "reset-gpios",
|
||||
0, &flags);
|
||||
if (nop->gpio_reset == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
nop->reset_active_low = flags & OF_GPIO_ACTIVE_LOW;
|
||||
|
||||
} else if (pdata) {
|
||||
type = pdata->type;
|
||||
clk_rate = pdata->clk_rate;
|
||||
needs_vcc = pdata->needs_vcc;
|
||||
nop->gpio_reset = pdata->gpio_reset;
|
||||
} else {
|
||||
nop->gpio_reset = -1;
|
||||
}
|
||||
|
||||
nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg),
|
||||
GFP_KERNEL);
|
||||
if (!nop->phy.otg)
|
||||
return -ENOMEM;
|
||||
|
||||
nop->clk = devm_clk_get(dev, "main_clk");
|
||||
if (IS_ERR(nop->clk)) {
|
||||
dev_dbg(dev, "Can't get phy clock: %ld\n",
|
||||
PTR_ERR(nop->clk));
|
||||
}
|
||||
|
||||
if (!IS_ERR(nop->clk) && clk_rate) {
|
||||
err = clk_set_rate(nop->clk, clk_rate);
|
||||
if (err) {
|
||||
dev_err(dev, "Error setting clock rate\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
nop->vcc = devm_regulator_get(dev, "vcc");
|
||||
if (IS_ERR(nop->vcc)) {
|
||||
dev_dbg(dev, "Error getting vcc regulator: %ld\n",
|
||||
PTR_ERR(nop->vcc));
|
||||
if (needs_vcc)
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
if (gpio_is_valid(nop->gpio_reset)) {
|
||||
unsigned long gpio_flags;
|
||||
|
||||
/* Assert RESET */
|
||||
if (nop->reset_active_low)
|
||||
gpio_flags = GPIOF_OUT_INIT_LOW;
|
||||
else
|
||||
gpio_flags = GPIOF_OUT_INIT_HIGH;
|
||||
|
||||
err = devm_gpio_request_one(dev, nop->gpio_reset,
|
||||
gpio_flags, dev_name(dev));
|
||||
if (err) {
|
||||
dev_err(dev, "Error requesting RESET GPIO %d\n",
|
||||
nop->gpio_reset);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
nop->dev = dev;
|
||||
nop->phy.dev = nop->dev;
|
||||
nop->phy.label = "nop-xceiv";
|
||||
nop->phy.set_suspend = nop_set_suspend;
|
||||
nop->phy.state = OTG_STATE_UNDEFINED;
|
||||
nop->phy.type = type;
|
||||
|
||||
nop->phy.otg->phy = &nop->phy;
|
||||
nop->phy.otg->set_host = nop_set_host;
|
||||
nop->phy.otg->set_peripheral = nop_set_peripheral;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_phy_gen_create_phy);
|
||||
|
||||
static int usb_phy_generic_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct usb_phy_generic *nop;
|
||||
int err;
|
||||
|
||||
nop = devm_kzalloc(dev, sizeof(*nop), GFP_KERNEL);
|
||||
if (!nop)
|
||||
return -ENOMEM;
|
||||
|
||||
err = usb_phy_gen_create_phy(dev, nop, dev_get_platdata(&pdev->dev));
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
nop->phy.init = usb_gen_phy_init;
|
||||
nop->phy.shutdown = usb_gen_phy_shutdown;
|
||||
|
||||
err = usb_add_phy_dev(&nop->phy);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "can't register transceiver, err: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, nop);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_phy_generic_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct usb_phy_generic *nop = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&nop->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id nop_xceiv_dt_ids[] = {
|
||||
{ .compatible = "usb-nop-xceiv" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, nop_xceiv_dt_ids);
|
||||
|
||||
static struct platform_driver usb_phy_generic_driver = {
|
||||
.probe = usb_phy_generic_probe,
|
||||
.remove = usb_phy_generic_remove,
|
||||
.driver = {
|
||||
.name = "usb_phy_generic",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = nop_xceiv_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init usb_phy_generic_init(void)
|
||||
{
|
||||
return platform_driver_register(&usb_phy_generic_driver);
|
||||
}
|
||||
subsys_initcall(usb_phy_generic_init);
|
||||
|
||||
static void __exit usb_phy_generic_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&usb_phy_generic_driver);
|
||||
}
|
||||
module_exit(usb_phy_generic_exit);
|
||||
|
||||
MODULE_ALIAS("platform:usb_phy_generic");
|
||||
MODULE_AUTHOR("Texas Instruments Inc");
|
||||
MODULE_DESCRIPTION("NOP USB Transceiver driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
21
drivers/usb/phy/phy-generic.h
Normal file
21
drivers/usb/phy/phy-generic.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef _PHY_GENERIC_H_
|
||||
#define _PHY_GENERIC_H_
|
||||
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
|
||||
struct usb_phy_generic {
|
||||
struct usb_phy phy;
|
||||
struct device *dev;
|
||||
struct clk *clk;
|
||||
struct regulator *vcc;
|
||||
int gpio_reset;
|
||||
bool reset_active_low;
|
||||
};
|
||||
|
||||
int usb_gen_phy_init(struct usb_phy *phy);
|
||||
void usb_gen_phy_shutdown(struct usb_phy *phy);
|
||||
|
||||
int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop,
|
||||
struct usb_phy_generic_platform_data *pdata);
|
||||
|
||||
#endif
|
||||
396
drivers/usb/phy/phy-gpio-vbus-usb.c
Normal file
396
drivers/usb/phy/phy-gpio-vbus-usb.c
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
/*
|
||||
* gpio-vbus.c - simple GPIO VBUS sensing driver for B peripheral devices
|
||||
*
|
||||
* Copyright (c) 2008 Philipp Zabel <philipp.zabel@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb/gpio_vbus.h>
|
||||
#include <linux/usb/otg.h>
|
||||
|
||||
|
||||
/*
|
||||
* A simple GPIO VBUS sensing driver for B peripheral only devices
|
||||
* with internal transceivers. It can control a D+ pullup GPIO and
|
||||
* a regulator to limit the current drawn from VBUS.
|
||||
*
|
||||
* Needs to be loaded before the UDC driver that will use it.
|
||||
*/
|
||||
struct gpio_vbus_data {
|
||||
struct usb_phy phy;
|
||||
struct device *dev;
|
||||
struct regulator *vbus_draw;
|
||||
int vbus_draw_enabled;
|
||||
unsigned mA;
|
||||
struct delayed_work work;
|
||||
int vbus;
|
||||
int irq;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* This driver relies on "both edges" triggering. VBUS has 100 msec to
|
||||
* stabilize, so the peripheral controller driver may need to cope with
|
||||
* some bouncing due to current surges (e.g. charging local capacitance)
|
||||
* and contact chatter.
|
||||
*
|
||||
* REVISIT in desperate straits, toggling between rising and falling
|
||||
* edges might be workable.
|
||||
*/
|
||||
#define VBUS_IRQ_FLAGS \
|
||||
(IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
|
||||
|
||||
|
||||
/* interface to regulator framework */
|
||||
static void set_vbus_draw(struct gpio_vbus_data *gpio_vbus, unsigned mA)
|
||||
{
|
||||
struct regulator *vbus_draw = gpio_vbus->vbus_draw;
|
||||
int enabled;
|
||||
int ret;
|
||||
|
||||
if (!vbus_draw)
|
||||
return;
|
||||
|
||||
enabled = gpio_vbus->vbus_draw_enabled;
|
||||
if (mA) {
|
||||
regulator_set_current_limit(vbus_draw, 0, 1000 * mA);
|
||||
if (!enabled) {
|
||||
ret = regulator_enable(vbus_draw);
|
||||
if (ret < 0)
|
||||
return;
|
||||
gpio_vbus->vbus_draw_enabled = 1;
|
||||
}
|
||||
} else {
|
||||
if (enabled) {
|
||||
ret = regulator_disable(vbus_draw);
|
||||
if (ret < 0)
|
||||
return;
|
||||
gpio_vbus->vbus_draw_enabled = 0;
|
||||
}
|
||||
}
|
||||
gpio_vbus->mA = mA;
|
||||
}
|
||||
|
||||
static int is_vbus_powered(struct gpio_vbus_mach_info *pdata)
|
||||
{
|
||||
int vbus;
|
||||
|
||||
vbus = gpio_get_value(pdata->gpio_vbus);
|
||||
if (pdata->gpio_vbus_inverted)
|
||||
vbus = !vbus;
|
||||
|
||||
return vbus;
|
||||
}
|
||||
|
||||
static void gpio_vbus_work(struct work_struct *work)
|
||||
{
|
||||
struct gpio_vbus_data *gpio_vbus =
|
||||
container_of(work, struct gpio_vbus_data, work.work);
|
||||
struct gpio_vbus_mach_info *pdata = dev_get_platdata(gpio_vbus->dev);
|
||||
int gpio, status, vbus;
|
||||
|
||||
if (!gpio_vbus->phy.otg->gadget)
|
||||
return;
|
||||
|
||||
vbus = is_vbus_powered(pdata);
|
||||
if ((vbus ^ gpio_vbus->vbus) == 0)
|
||||
return;
|
||||
gpio_vbus->vbus = vbus;
|
||||
|
||||
/* Peripheral controllers which manage the pullup themselves won't have
|
||||
* gpio_pullup configured here. If it's configured here, we'll do what
|
||||
* isp1301_omap::b_peripheral() does and enable the pullup here... although
|
||||
* that may complicate usb_gadget_{,dis}connect() support.
|
||||
*/
|
||||
gpio = pdata->gpio_pullup;
|
||||
|
||||
if (vbus) {
|
||||
status = USB_EVENT_VBUS;
|
||||
gpio_vbus->phy.state = OTG_STATE_B_PERIPHERAL;
|
||||
gpio_vbus->phy.last_event = status;
|
||||
usb_gadget_vbus_connect(gpio_vbus->phy.otg->gadget);
|
||||
|
||||
/* drawing a "unit load" is *always* OK, except for OTG */
|
||||
set_vbus_draw(gpio_vbus, 100);
|
||||
|
||||
/* optionally enable D+ pullup */
|
||||
if (gpio_is_valid(gpio))
|
||||
gpio_set_value(gpio, !pdata->gpio_pullup_inverted);
|
||||
|
||||
atomic_notifier_call_chain(&gpio_vbus->phy.notifier,
|
||||
status, gpio_vbus->phy.otg->gadget);
|
||||
} else {
|
||||
/* optionally disable D+ pullup */
|
||||
if (gpio_is_valid(gpio))
|
||||
gpio_set_value(gpio, pdata->gpio_pullup_inverted);
|
||||
|
||||
set_vbus_draw(gpio_vbus, 0);
|
||||
|
||||
usb_gadget_vbus_disconnect(gpio_vbus->phy.otg->gadget);
|
||||
status = USB_EVENT_NONE;
|
||||
gpio_vbus->phy.state = OTG_STATE_B_IDLE;
|
||||
gpio_vbus->phy.last_event = status;
|
||||
|
||||
atomic_notifier_call_chain(&gpio_vbus->phy.notifier,
|
||||
status, gpio_vbus->phy.otg->gadget);
|
||||
}
|
||||
}
|
||||
|
||||
/* VBUS change IRQ handler */
|
||||
static irqreturn_t gpio_vbus_irq(int irq, void *data)
|
||||
{
|
||||
struct platform_device *pdev = data;
|
||||
struct gpio_vbus_mach_info *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev);
|
||||
struct usb_otg *otg = gpio_vbus->phy.otg;
|
||||
|
||||
dev_dbg(&pdev->dev, "VBUS %s (gadget: %s)\n",
|
||||
is_vbus_powered(pdata) ? "supplied" : "inactive",
|
||||
otg->gadget ? otg->gadget->name : "none");
|
||||
|
||||
if (otg->gadget)
|
||||
schedule_delayed_work(&gpio_vbus->work, msecs_to_jiffies(100));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* OTG transceiver interface */
|
||||
|
||||
/* bind/unbind the peripheral controller */
|
||||
static int gpio_vbus_set_peripheral(struct usb_otg *otg,
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
struct gpio_vbus_data *gpio_vbus;
|
||||
struct gpio_vbus_mach_info *pdata;
|
||||
struct platform_device *pdev;
|
||||
int gpio;
|
||||
|
||||
gpio_vbus = container_of(otg->phy, struct gpio_vbus_data, phy);
|
||||
pdev = to_platform_device(gpio_vbus->dev);
|
||||
pdata = dev_get_platdata(gpio_vbus->dev);
|
||||
gpio = pdata->gpio_pullup;
|
||||
|
||||
if (!gadget) {
|
||||
dev_dbg(&pdev->dev, "unregistering gadget '%s'\n",
|
||||
otg->gadget->name);
|
||||
|
||||
/* optionally disable D+ pullup */
|
||||
if (gpio_is_valid(gpio))
|
||||
gpio_set_value(gpio, pdata->gpio_pullup_inverted);
|
||||
|
||||
set_vbus_draw(gpio_vbus, 0);
|
||||
|
||||
usb_gadget_vbus_disconnect(otg->gadget);
|
||||
otg->phy->state = OTG_STATE_UNDEFINED;
|
||||
|
||||
otg->gadget = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
otg->gadget = gadget;
|
||||
dev_dbg(&pdev->dev, "registered gadget '%s'\n", gadget->name);
|
||||
|
||||
/* initialize connection state */
|
||||
gpio_vbus->vbus = 0; /* start with disconnected */
|
||||
gpio_vbus_irq(gpio_vbus->irq, pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* effective for B devices, ignored for A-peripheral */
|
||||
static int gpio_vbus_set_power(struct usb_phy *phy, unsigned mA)
|
||||
{
|
||||
struct gpio_vbus_data *gpio_vbus;
|
||||
|
||||
gpio_vbus = container_of(phy, struct gpio_vbus_data, phy);
|
||||
|
||||
if (phy->state == OTG_STATE_B_PERIPHERAL)
|
||||
set_vbus_draw(gpio_vbus, mA);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* for non-OTG B devices: set/clear transceiver suspend mode */
|
||||
static int gpio_vbus_set_suspend(struct usb_phy *phy, int suspend)
|
||||
{
|
||||
struct gpio_vbus_data *gpio_vbus;
|
||||
|
||||
gpio_vbus = container_of(phy, struct gpio_vbus_data, phy);
|
||||
|
||||
/* draw max 0 mA from vbus in suspend mode; or the previously
|
||||
* recorded amount of current if not suspended
|
||||
*
|
||||
* NOTE: high powered configs (mA > 100) may draw up to 2.5 mA
|
||||
* if they're wake-enabled ... we don't handle that yet.
|
||||
*/
|
||||
return gpio_vbus_set_power(phy, suspend ? 0 : gpio_vbus->mA);
|
||||
}
|
||||
|
||||
/* platform driver interface */
|
||||
|
||||
static int gpio_vbus_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_vbus_mach_info *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct gpio_vbus_data *gpio_vbus;
|
||||
struct resource *res;
|
||||
int err, gpio, irq;
|
||||
unsigned long irqflags;
|
||||
|
||||
if (!pdata || !gpio_is_valid(pdata->gpio_vbus))
|
||||
return -EINVAL;
|
||||
gpio = pdata->gpio_vbus;
|
||||
|
||||
gpio_vbus = devm_kzalloc(&pdev->dev, sizeof(struct gpio_vbus_data),
|
||||
GFP_KERNEL);
|
||||
if (!gpio_vbus)
|
||||
return -ENOMEM;
|
||||
|
||||
gpio_vbus->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg),
|
||||
GFP_KERNEL);
|
||||
if (!gpio_vbus->phy.otg)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, gpio_vbus);
|
||||
gpio_vbus->dev = &pdev->dev;
|
||||
gpio_vbus->phy.label = "gpio-vbus";
|
||||
gpio_vbus->phy.dev = gpio_vbus->dev;
|
||||
gpio_vbus->phy.set_power = gpio_vbus_set_power;
|
||||
gpio_vbus->phy.set_suspend = gpio_vbus_set_suspend;
|
||||
gpio_vbus->phy.state = OTG_STATE_UNDEFINED;
|
||||
|
||||
gpio_vbus->phy.otg->phy = &gpio_vbus->phy;
|
||||
gpio_vbus->phy.otg->set_peripheral = gpio_vbus_set_peripheral;
|
||||
|
||||
err = devm_gpio_request(&pdev->dev, gpio, "vbus_detect");
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "can't request vbus gpio %d, err: %d\n",
|
||||
gpio, err);
|
||||
return err;
|
||||
}
|
||||
gpio_direction_input(gpio);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
if (res) {
|
||||
irq = res->start;
|
||||
irqflags = (res->flags & IRQF_TRIGGER_MASK) | IRQF_SHARED;
|
||||
} else {
|
||||
irq = gpio_to_irq(gpio);
|
||||
irqflags = VBUS_IRQ_FLAGS;
|
||||
}
|
||||
|
||||
gpio_vbus->irq = irq;
|
||||
|
||||
/* if data line pullup is in use, initialize it to "not pulling up" */
|
||||
gpio = pdata->gpio_pullup;
|
||||
if (gpio_is_valid(gpio)) {
|
||||
err = devm_gpio_request(&pdev->dev, gpio, "udc_pullup");
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"can't request pullup gpio %d, err: %d\n",
|
||||
gpio, err);
|
||||
return err;
|
||||
}
|
||||
gpio_direction_output(gpio, pdata->gpio_pullup_inverted);
|
||||
}
|
||||
|
||||
err = devm_request_irq(&pdev->dev, irq, gpio_vbus_irq, irqflags,
|
||||
"vbus_detect", pdev);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "can't request irq %i, err: %d\n",
|
||||
irq, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&gpio_vbus->work, gpio_vbus_work);
|
||||
|
||||
gpio_vbus->vbus_draw = devm_regulator_get(&pdev->dev, "vbus_draw");
|
||||
if (IS_ERR(gpio_vbus->vbus_draw)) {
|
||||
dev_dbg(&pdev->dev, "can't get vbus_draw regulator, err: %ld\n",
|
||||
PTR_ERR(gpio_vbus->vbus_draw));
|
||||
gpio_vbus->vbus_draw = NULL;
|
||||
}
|
||||
|
||||
/* only active when a gadget is registered */
|
||||
err = usb_add_phy(&gpio_vbus->phy, USB_PHY_TYPE_USB2);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "can't register transceiver, err: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
device_init_wakeup(&pdev->dev, pdata->wakeup);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_vbus_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev);
|
||||
|
||||
device_init_wakeup(&pdev->dev, 0);
|
||||
cancel_delayed_work_sync(&gpio_vbus->work);
|
||||
|
||||
usb_remove_phy(&gpio_vbus->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int gpio_vbus_pm_suspend(struct device *dev)
|
||||
{
|
||||
struct gpio_vbus_data *gpio_vbus = dev_get_drvdata(dev);
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
enable_irq_wake(gpio_vbus->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_vbus_pm_resume(struct device *dev)
|
||||
{
|
||||
struct gpio_vbus_data *gpio_vbus = dev_get_drvdata(dev);
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
disable_irq_wake(gpio_vbus->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops gpio_vbus_dev_pm_ops = {
|
||||
.suspend = gpio_vbus_pm_suspend,
|
||||
.resume = gpio_vbus_pm_resume,
|
||||
};
|
||||
#endif
|
||||
|
||||
MODULE_ALIAS("platform:gpio-vbus");
|
||||
|
||||
static struct platform_driver gpio_vbus_driver = {
|
||||
.driver = {
|
||||
.name = "gpio-vbus",
|
||||
.owner = THIS_MODULE,
|
||||
#ifdef CONFIG_PM
|
||||
.pm = &gpio_vbus_dev_pm_ops,
|
||||
#endif
|
||||
},
|
||||
.probe = gpio_vbus_probe,
|
||||
.remove = gpio_vbus_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(gpio_vbus_driver);
|
||||
|
||||
MODULE_DESCRIPTION("simple GPIO controlled OTG transceiver driver");
|
||||
MODULE_AUTHOR("Philipp Zabel");
|
||||
MODULE_LICENSE("GPL");
|
||||
1651
drivers/usb/phy/phy-isp1301-omap.c
Normal file
1651
drivers/usb/phy/phy-isp1301-omap.c
Normal file
File diff suppressed because it is too large
Load diff
163
drivers/usb/phy/phy-isp1301.c
Normal file
163
drivers/usb/phy/phy-isp1301.c
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* NXP ISP1301 USB transceiver driver
|
||||
*
|
||||
* Copyright (C) 2012 Roland Stigge
|
||||
*
|
||||
* Author: Roland Stigge <stigge@antcom.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/usb/phy.h>
|
||||
#include <linux/usb/isp1301.h>
|
||||
|
||||
#define DRV_NAME "isp1301"
|
||||
|
||||
struct isp1301 {
|
||||
struct usb_phy phy;
|
||||
struct mutex mutex;
|
||||
|
||||
struct i2c_client *client;
|
||||
};
|
||||
|
||||
#define phy_to_isp(p) (container_of((p), struct isp1301, phy))
|
||||
|
||||
static const struct i2c_device_id isp1301_id[] = {
|
||||
{ "isp1301", 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct i2c_client *isp1301_i2c_client;
|
||||
|
||||
static int __isp1301_write(struct isp1301 *isp, u8 reg, u8 value, u8 clear)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(isp->client, reg | clear, value);
|
||||
}
|
||||
|
||||
static int isp1301_write(struct isp1301 *isp, u8 reg, u8 value)
|
||||
{
|
||||
return __isp1301_write(isp, reg, value, 0);
|
||||
}
|
||||
|
||||
static int isp1301_clear(struct isp1301 *isp, u8 reg, u8 value)
|
||||
{
|
||||
return __isp1301_write(isp, reg, value, ISP1301_I2C_REG_CLEAR_ADDR);
|
||||
}
|
||||
|
||||
static int isp1301_phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct isp1301 *isp = phy_to_isp(phy);
|
||||
|
||||
/* Disable transparent UART mode first */
|
||||
isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_UART_EN);
|
||||
isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_1, ~MC1_SPEED_REG);
|
||||
isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_SPEED_REG);
|
||||
isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_2, ~0);
|
||||
isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_2, (MC2_BI_DI | MC2_PSW_EN
|
||||
| MC2_SPD_SUSP_CTRL));
|
||||
|
||||
isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, ~0);
|
||||
isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_DAT_SE0);
|
||||
isp1301_write(isp, ISP1301_I2C_OTG_CONTROL_1, (OTG1_DM_PULLDOWN
|
||||
| OTG1_DP_PULLDOWN));
|
||||
isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, (OTG1_DM_PULLUP
|
||||
| OTG1_DP_PULLUP));
|
||||
|
||||
/* mask all interrupts */
|
||||
isp1301_clear(isp, ISP1301_I2C_INTERRUPT_LATCH, ~0);
|
||||
isp1301_clear(isp, ISP1301_I2C_INTERRUPT_FALLING, ~0);
|
||||
isp1301_clear(isp, ISP1301_I2C_INTERRUPT_RISING, ~0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isp1301_phy_set_vbus(struct usb_phy *phy, int on)
|
||||
{
|
||||
struct isp1301 *isp = phy_to_isp(phy);
|
||||
|
||||
if (on)
|
||||
isp1301_write(isp, ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV);
|
||||
else
|
||||
isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isp1301_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *i2c_id)
|
||||
{
|
||||
struct isp1301 *isp;
|
||||
struct usb_phy *phy;
|
||||
|
||||
isp = devm_kzalloc(&client->dev, sizeof(*isp), GFP_KERNEL);
|
||||
if (!isp)
|
||||
return -ENOMEM;
|
||||
|
||||
isp->client = client;
|
||||
mutex_init(&isp->mutex);
|
||||
|
||||
phy = &isp->phy;
|
||||
phy->dev = &client->dev;
|
||||
phy->label = DRV_NAME;
|
||||
phy->init = isp1301_phy_init;
|
||||
phy->set_vbus = isp1301_phy_set_vbus;
|
||||
phy->type = USB_PHY_TYPE_USB2;
|
||||
|
||||
i2c_set_clientdata(client, isp);
|
||||
usb_add_phy_dev(phy);
|
||||
|
||||
isp1301_i2c_client = client;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isp1301_remove(struct i2c_client *client)
|
||||
{
|
||||
struct isp1301 *isp = i2c_get_clientdata(client);
|
||||
|
||||
usb_remove_phy(&isp->phy);
|
||||
isp1301_i2c_client = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver isp1301_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
},
|
||||
.probe = isp1301_probe,
|
||||
.remove = isp1301_remove,
|
||||
.id_table = isp1301_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(isp1301_driver);
|
||||
|
||||
static int match(struct device *dev, void *data)
|
||||
{
|
||||
struct device_node *node = (struct device_node *)data;
|
||||
return (dev->of_node == node) &&
|
||||
(dev->driver == &isp1301_driver.driver);
|
||||
}
|
||||
|
||||
struct i2c_client *isp1301_get_client(struct device_node *node)
|
||||
{
|
||||
if (node) { /* reference of ISP1301 I2C node via DT */
|
||||
struct device *dev = bus_find_device(&i2c_bus_type, NULL,
|
||||
node, match);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
return to_i2c_client(dev);
|
||||
} else { /* non-DT: only one ISP1301 chip supported */
|
||||
return isp1301_i2c_client;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isp1301_get_client);
|
||||
|
||||
MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>");
|
||||
MODULE_DESCRIPTION("NXP ISP1301 USB transceiver driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
136
drivers/usb/phy/phy-keystone.c
Normal file
136
drivers/usb/phy/phy-keystone.c
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* phy-keystone - USB PHY, talking to dwc3 controller in Keystone.
|
||||
*
|
||||
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Author: WingMan Kwok <w-kwok2@ti.com>
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/usb/usb_phy_generic.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "phy-generic.h"
|
||||
|
||||
/* USB PHY control register offsets */
|
||||
#define USB_PHY_CTL_UTMI 0x0000
|
||||
#define USB_PHY_CTL_PIPE 0x0004
|
||||
#define USB_PHY_CTL_PARAM_1 0x0008
|
||||
#define USB_PHY_CTL_PARAM_2 0x000c
|
||||
#define USB_PHY_CTL_CLOCK 0x0010
|
||||
#define USB_PHY_CTL_PLL 0x0014
|
||||
|
||||
#define PHY_REF_SSP_EN BIT(29)
|
||||
|
||||
struct keystone_usbphy {
|
||||
struct usb_phy_generic usb_phy_gen;
|
||||
void __iomem *phy_ctrl;
|
||||
};
|
||||
|
||||
static inline u32 keystone_usbphy_readl(void __iomem *base, u32 offset)
|
||||
{
|
||||
return readl(base + offset);
|
||||
}
|
||||
|
||||
static inline void keystone_usbphy_writel(void __iomem *base,
|
||||
u32 offset, u32 value)
|
||||
{
|
||||
writel(value, base + offset);
|
||||
}
|
||||
|
||||
static int keystone_usbphy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct keystone_usbphy *k_phy = dev_get_drvdata(phy->dev);
|
||||
u32 val;
|
||||
|
||||
val = keystone_usbphy_readl(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK);
|
||||
keystone_usbphy_writel(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK,
|
||||
val | PHY_REF_SSP_EN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void keystone_usbphy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct keystone_usbphy *k_phy = dev_get_drvdata(phy->dev);
|
||||
u32 val;
|
||||
|
||||
val = keystone_usbphy_readl(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK);
|
||||
keystone_usbphy_writel(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK,
|
||||
val &= ~PHY_REF_SSP_EN);
|
||||
}
|
||||
|
||||
static int keystone_usbphy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct keystone_usbphy *k_phy;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
k_phy = devm_kzalloc(dev, sizeof(*k_phy), GFP_KERNEL);
|
||||
if (!k_phy)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
k_phy->phy_ctrl = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(k_phy->phy_ctrl))
|
||||
return PTR_ERR(k_phy->phy_ctrl);
|
||||
|
||||
ret = usb_phy_gen_create_phy(dev, &k_phy->usb_phy_gen, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
k_phy->usb_phy_gen.phy.init = keystone_usbphy_init;
|
||||
k_phy->usb_phy_gen.phy.shutdown = keystone_usbphy_shutdown;
|
||||
|
||||
platform_set_drvdata(pdev, k_phy);
|
||||
|
||||
ret = usb_add_phy_dev(&k_phy->usb_phy_gen.phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int keystone_usbphy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct keystone_usbphy *k_phy = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&k_phy->usb_phy_gen.phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id keystone_usbphy_ids[] = {
|
||||
{ .compatible = "ti,keystone-usbphy" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, keystone_usbphy_ids);
|
||||
|
||||
static struct platform_driver keystone_usbphy_driver = {
|
||||
.probe = keystone_usbphy_probe,
|
||||
.remove = keystone_usbphy_remove,
|
||||
.driver = {
|
||||
.name = "keystone-usbphy",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = keystone_usbphy_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(keystone_usbphy_driver);
|
||||
|
||||
MODULE_ALIAS("platform:keystone-usbphy");
|
||||
MODULE_AUTHOR("Texas Instruments Inc.");
|
||||
MODULE_DESCRIPTION("Keystone USB phy driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
1852
drivers/usb/phy/phy-msm-usb.c
Normal file
1852
drivers/usb/phy/phy-msm-usb.c
Normal file
File diff suppressed because it is too large
Load diff
907
drivers/usb/phy/phy-mv-usb.c
Normal file
907
drivers/usb/phy/phy-mv-usb.c
Normal file
|
|
@ -0,0 +1,907 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Marvell International Ltd. All rights reserved.
|
||||
* Author: Chao Xie <chao.xie@marvell.com>
|
||||
* Neil Zhang <zhangwm@marvell.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb/hcd.h>
|
||||
#include <linux/platform_data/mv_usb.h>
|
||||
|
||||
#include "phy-mv-usb.h"
|
||||
|
||||
#define DRIVER_DESC "Marvell USB OTG transceiver driver"
|
||||
#define DRIVER_VERSION "Jan 20, 2010"
|
||||
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_VERSION(DRIVER_VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static const char driver_name[] = "mv-otg";
|
||||
|
||||
static char *state_string[] = {
|
||||
"undefined",
|
||||
"b_idle",
|
||||
"b_srp_init",
|
||||
"b_peripheral",
|
||||
"b_wait_acon",
|
||||
"b_host",
|
||||
"a_idle",
|
||||
"a_wait_vrise",
|
||||
"a_wait_bcon",
|
||||
"a_host",
|
||||
"a_suspend",
|
||||
"a_peripheral",
|
||||
"a_wait_vfall",
|
||||
"a_vbus_err"
|
||||
};
|
||||
|
||||
static int mv_otg_set_vbus(struct usb_otg *otg, bool on)
|
||||
{
|
||||
struct mv_otg *mvotg = container_of(otg->phy, struct mv_otg, phy);
|
||||
if (mvotg->pdata->set_vbus == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
return mvotg->pdata->set_vbus(on);
|
||||
}
|
||||
|
||||
static int mv_otg_set_host(struct usb_otg *otg,
|
||||
struct usb_bus *host)
|
||||
{
|
||||
otg->host = host;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_set_peripheral(struct usb_otg *otg,
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
otg->gadget = gadget;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mv_otg_run_state_machine(struct mv_otg *mvotg,
|
||||
unsigned long delay)
|
||||
{
|
||||
dev_dbg(&mvotg->pdev->dev, "transceiver is updated\n");
|
||||
if (!mvotg->qwork)
|
||||
return;
|
||||
|
||||
queue_delayed_work(mvotg->qwork, &mvotg->work, delay);
|
||||
}
|
||||
|
||||
static void mv_otg_timer_await_bcon(unsigned long data)
|
||||
{
|
||||
struct mv_otg *mvotg = (struct mv_otg *) data;
|
||||
|
||||
mvotg->otg_ctrl.a_wait_bcon_timeout = 1;
|
||||
|
||||
dev_info(&mvotg->pdev->dev, "B Device No Response!\n");
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
}
|
||||
|
||||
static int mv_otg_cancel_timer(struct mv_otg *mvotg, unsigned int id)
|
||||
{
|
||||
struct timer_list *timer;
|
||||
|
||||
if (id >= OTG_TIMER_NUM)
|
||||
return -EINVAL;
|
||||
|
||||
timer = &mvotg->otg_ctrl.timer[id];
|
||||
|
||||
if (timer_pending(timer))
|
||||
del_timer(timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_set_timer(struct mv_otg *mvotg, unsigned int id,
|
||||
unsigned long interval,
|
||||
void (*callback) (unsigned long))
|
||||
{
|
||||
struct timer_list *timer;
|
||||
|
||||
if (id >= OTG_TIMER_NUM)
|
||||
return -EINVAL;
|
||||
|
||||
timer = &mvotg->otg_ctrl.timer[id];
|
||||
if (timer_pending(timer)) {
|
||||
dev_err(&mvotg->pdev->dev, "Timer%d is already running\n", id);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
init_timer(timer);
|
||||
timer->data = (unsigned long) mvotg;
|
||||
timer->function = callback;
|
||||
timer->expires = jiffies + interval;
|
||||
add_timer(timer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_reset(struct mv_otg *mvotg)
|
||||
{
|
||||
unsigned int loops;
|
||||
u32 tmp;
|
||||
|
||||
/* Stop the controller */
|
||||
tmp = readl(&mvotg->op_regs->usbcmd);
|
||||
tmp &= ~USBCMD_RUN_STOP;
|
||||
writel(tmp, &mvotg->op_regs->usbcmd);
|
||||
|
||||
/* Reset the controller to get default values */
|
||||
writel(USBCMD_CTRL_RESET, &mvotg->op_regs->usbcmd);
|
||||
|
||||
loops = 500;
|
||||
while (readl(&mvotg->op_regs->usbcmd) & USBCMD_CTRL_RESET) {
|
||||
if (loops == 0) {
|
||||
dev_err(&mvotg->pdev->dev,
|
||||
"Wait for RESET completed TIMEOUT\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
loops--;
|
||||
udelay(20);
|
||||
}
|
||||
|
||||
writel(0x0, &mvotg->op_regs->usbintr);
|
||||
tmp = readl(&mvotg->op_regs->usbsts);
|
||||
writel(tmp, &mvotg->op_regs->usbsts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mv_otg_init_irq(struct mv_otg *mvotg)
|
||||
{
|
||||
u32 otgsc;
|
||||
|
||||
mvotg->irq_en = OTGSC_INTR_A_SESSION_VALID
|
||||
| OTGSC_INTR_A_VBUS_VALID;
|
||||
mvotg->irq_status = OTGSC_INTSTS_A_SESSION_VALID
|
||||
| OTGSC_INTSTS_A_VBUS_VALID;
|
||||
|
||||
if (mvotg->pdata->vbus == NULL) {
|
||||
mvotg->irq_en |= OTGSC_INTR_B_SESSION_VALID
|
||||
| OTGSC_INTR_B_SESSION_END;
|
||||
mvotg->irq_status |= OTGSC_INTSTS_B_SESSION_VALID
|
||||
| OTGSC_INTSTS_B_SESSION_END;
|
||||
}
|
||||
|
||||
if (mvotg->pdata->id == NULL) {
|
||||
mvotg->irq_en |= OTGSC_INTR_USB_ID;
|
||||
mvotg->irq_status |= OTGSC_INTSTS_USB_ID;
|
||||
}
|
||||
|
||||
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||
otgsc |= mvotg->irq_en;
|
||||
writel(otgsc, &mvotg->op_regs->otgsc);
|
||||
}
|
||||
|
||||
static void mv_otg_start_host(struct mv_otg *mvotg, int on)
|
||||
{
|
||||
#ifdef CONFIG_USB
|
||||
struct usb_otg *otg = mvotg->phy.otg;
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
if (!otg->host)
|
||||
return;
|
||||
|
||||
dev_info(&mvotg->pdev->dev, "%s host\n", on ? "start" : "stop");
|
||||
|
||||
hcd = bus_to_hcd(otg->host);
|
||||
|
||||
if (on) {
|
||||
usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
|
||||
device_wakeup_enable(hcd->self.controller);
|
||||
} else {
|
||||
usb_remove_hcd(hcd);
|
||||
}
|
||||
#endif /* CONFIG_USB */
|
||||
}
|
||||
|
||||
static void mv_otg_start_periphrals(struct mv_otg *mvotg, int on)
|
||||
{
|
||||
struct usb_otg *otg = mvotg->phy.otg;
|
||||
|
||||
if (!otg->gadget)
|
||||
return;
|
||||
|
||||
dev_info(mvotg->phy.dev, "gadget %s\n", on ? "on" : "off");
|
||||
|
||||
if (on)
|
||||
usb_gadget_vbus_connect(otg->gadget);
|
||||
else
|
||||
usb_gadget_vbus_disconnect(otg->gadget);
|
||||
}
|
||||
|
||||
static void otg_clock_enable(struct mv_otg *mvotg)
|
||||
{
|
||||
clk_prepare_enable(mvotg->clk);
|
||||
}
|
||||
|
||||
static void otg_clock_disable(struct mv_otg *mvotg)
|
||||
{
|
||||
clk_disable_unprepare(mvotg->clk);
|
||||
}
|
||||
|
||||
static int mv_otg_enable_internal(struct mv_otg *mvotg)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
if (mvotg->active)
|
||||
return 0;
|
||||
|
||||
dev_dbg(&mvotg->pdev->dev, "otg enabled\n");
|
||||
|
||||
otg_clock_enable(mvotg);
|
||||
if (mvotg->pdata->phy_init) {
|
||||
retval = mvotg->pdata->phy_init(mvotg->phy_regs);
|
||||
if (retval) {
|
||||
dev_err(&mvotg->pdev->dev,
|
||||
"init phy error %d\n", retval);
|
||||
otg_clock_disable(mvotg);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
mvotg->active = 1;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int mv_otg_enable(struct mv_otg *mvotg)
|
||||
{
|
||||
if (mvotg->clock_gating)
|
||||
return mv_otg_enable_internal(mvotg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mv_otg_disable_internal(struct mv_otg *mvotg)
|
||||
{
|
||||
if (mvotg->active) {
|
||||
dev_dbg(&mvotg->pdev->dev, "otg disabled\n");
|
||||
if (mvotg->pdata->phy_deinit)
|
||||
mvotg->pdata->phy_deinit(mvotg->phy_regs);
|
||||
otg_clock_disable(mvotg);
|
||||
mvotg->active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void mv_otg_disable(struct mv_otg *mvotg)
|
||||
{
|
||||
if (mvotg->clock_gating)
|
||||
mv_otg_disable_internal(mvotg);
|
||||
}
|
||||
|
||||
static void mv_otg_update_inputs(struct mv_otg *mvotg)
|
||||
{
|
||||
struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl;
|
||||
u32 otgsc;
|
||||
|
||||
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||
|
||||
if (mvotg->pdata->vbus) {
|
||||
if (mvotg->pdata->vbus->poll() == VBUS_HIGH) {
|
||||
otg_ctrl->b_sess_vld = 1;
|
||||
otg_ctrl->b_sess_end = 0;
|
||||
} else {
|
||||
otg_ctrl->b_sess_vld = 0;
|
||||
otg_ctrl->b_sess_end = 1;
|
||||
}
|
||||
} else {
|
||||
otg_ctrl->b_sess_vld = !!(otgsc & OTGSC_STS_B_SESSION_VALID);
|
||||
otg_ctrl->b_sess_end = !!(otgsc & OTGSC_STS_B_SESSION_END);
|
||||
}
|
||||
|
||||
if (mvotg->pdata->id)
|
||||
otg_ctrl->id = !!mvotg->pdata->id->poll();
|
||||
else
|
||||
otg_ctrl->id = !!(otgsc & OTGSC_STS_USB_ID);
|
||||
|
||||
if (mvotg->pdata->otg_force_a_bus_req && !otg_ctrl->id)
|
||||
otg_ctrl->a_bus_req = 1;
|
||||
|
||||
otg_ctrl->a_sess_vld = !!(otgsc & OTGSC_STS_A_SESSION_VALID);
|
||||
otg_ctrl->a_vbus_vld = !!(otgsc & OTGSC_STS_A_VBUS_VALID);
|
||||
|
||||
dev_dbg(&mvotg->pdev->dev, "%s: ", __func__);
|
||||
dev_dbg(&mvotg->pdev->dev, "id %d\n", otg_ctrl->id);
|
||||
dev_dbg(&mvotg->pdev->dev, "b_sess_vld %d\n", otg_ctrl->b_sess_vld);
|
||||
dev_dbg(&mvotg->pdev->dev, "b_sess_end %d\n", otg_ctrl->b_sess_end);
|
||||
dev_dbg(&mvotg->pdev->dev, "a_vbus_vld %d\n", otg_ctrl->a_vbus_vld);
|
||||
dev_dbg(&mvotg->pdev->dev, "a_sess_vld %d\n", otg_ctrl->a_sess_vld);
|
||||
}
|
||||
|
||||
static void mv_otg_update_state(struct mv_otg *mvotg)
|
||||
{
|
||||
struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl;
|
||||
struct usb_phy *phy = &mvotg->phy;
|
||||
int old_state = phy->state;
|
||||
|
||||
switch (old_state) {
|
||||
case OTG_STATE_UNDEFINED:
|
||||
phy->state = OTG_STATE_B_IDLE;
|
||||
/* FALL THROUGH */
|
||||
case OTG_STATE_B_IDLE:
|
||||
if (otg_ctrl->id == 0)
|
||||
phy->state = OTG_STATE_A_IDLE;
|
||||
else if (otg_ctrl->b_sess_vld)
|
||||
phy->state = OTG_STATE_B_PERIPHERAL;
|
||||
break;
|
||||
case OTG_STATE_B_PERIPHERAL:
|
||||
if (!otg_ctrl->b_sess_vld || otg_ctrl->id == 0)
|
||||
phy->state = OTG_STATE_B_IDLE;
|
||||
break;
|
||||
case OTG_STATE_A_IDLE:
|
||||
if (otg_ctrl->id)
|
||||
phy->state = OTG_STATE_B_IDLE;
|
||||
else if (!(otg_ctrl->a_bus_drop) &&
|
||||
(otg_ctrl->a_bus_req || otg_ctrl->a_srp_det))
|
||||
phy->state = OTG_STATE_A_WAIT_VRISE;
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VRISE:
|
||||
if (otg_ctrl->a_vbus_vld)
|
||||
phy->state = OTG_STATE_A_WAIT_BCON;
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
if (otg_ctrl->id || otg_ctrl->a_bus_drop
|
||||
|| otg_ctrl->a_wait_bcon_timeout) {
|
||||
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
|
||||
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
|
||||
phy->state = OTG_STATE_A_WAIT_VFALL;
|
||||
otg_ctrl->a_bus_req = 0;
|
||||
} else if (!otg_ctrl->a_vbus_vld) {
|
||||
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
|
||||
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
|
||||
phy->state = OTG_STATE_A_VBUS_ERR;
|
||||
} else if (otg_ctrl->b_conn) {
|
||||
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
|
||||
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
|
||||
phy->state = OTG_STATE_A_HOST;
|
||||
}
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
if (otg_ctrl->id || !otg_ctrl->b_conn
|
||||
|| otg_ctrl->a_bus_drop)
|
||||
phy->state = OTG_STATE_A_WAIT_BCON;
|
||||
else if (!otg_ctrl->a_vbus_vld)
|
||||
phy->state = OTG_STATE_A_VBUS_ERR;
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
if (otg_ctrl->id
|
||||
|| (!otg_ctrl->b_conn && otg_ctrl->a_sess_vld)
|
||||
|| otg_ctrl->a_bus_req)
|
||||
phy->state = OTG_STATE_A_IDLE;
|
||||
break;
|
||||
case OTG_STATE_A_VBUS_ERR:
|
||||
if (otg_ctrl->id || otg_ctrl->a_clr_err
|
||||
|| otg_ctrl->a_bus_drop) {
|
||||
otg_ctrl->a_clr_err = 0;
|
||||
phy->state = OTG_STATE_A_WAIT_VFALL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void mv_otg_work(struct work_struct *work)
|
||||
{
|
||||
struct mv_otg *mvotg;
|
||||
struct usb_phy *phy;
|
||||
struct usb_otg *otg;
|
||||
int old_state;
|
||||
|
||||
mvotg = container_of(to_delayed_work(work), struct mv_otg, work);
|
||||
|
||||
run:
|
||||
/* work queue is single thread, or we need spin_lock to protect */
|
||||
phy = &mvotg->phy;
|
||||
otg = phy->otg;
|
||||
old_state = phy->state;
|
||||
|
||||
if (!mvotg->active)
|
||||
return;
|
||||
|
||||
mv_otg_update_inputs(mvotg);
|
||||
mv_otg_update_state(mvotg);
|
||||
|
||||
if (old_state != phy->state) {
|
||||
dev_info(&mvotg->pdev->dev, "change from state %s to %s\n",
|
||||
state_string[old_state],
|
||||
state_string[phy->state]);
|
||||
|
||||
switch (phy->state) {
|
||||
case OTG_STATE_B_IDLE:
|
||||
otg->default_a = 0;
|
||||
if (old_state == OTG_STATE_B_PERIPHERAL)
|
||||
mv_otg_start_periphrals(mvotg, 0);
|
||||
mv_otg_reset(mvotg);
|
||||
mv_otg_disable(mvotg);
|
||||
break;
|
||||
case OTG_STATE_B_PERIPHERAL:
|
||||
mv_otg_enable(mvotg);
|
||||
mv_otg_start_periphrals(mvotg, 1);
|
||||
break;
|
||||
case OTG_STATE_A_IDLE:
|
||||
otg->default_a = 1;
|
||||
mv_otg_enable(mvotg);
|
||||
if (old_state == OTG_STATE_A_WAIT_VFALL)
|
||||
mv_otg_start_host(mvotg, 0);
|
||||
mv_otg_reset(mvotg);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VRISE:
|
||||
mv_otg_set_vbus(otg, 1);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
if (old_state != OTG_STATE_A_HOST)
|
||||
mv_otg_start_host(mvotg, 1);
|
||||
mv_otg_set_timer(mvotg, A_WAIT_BCON_TIMER,
|
||||
T_A_WAIT_BCON,
|
||||
mv_otg_timer_await_bcon);
|
||||
/*
|
||||
* Now, we directly enter A_HOST. So set b_conn = 1
|
||||
* here. In fact, it need host driver to notify us.
|
||||
*/
|
||||
mvotg->otg_ctrl.b_conn = 1;
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
/*
|
||||
* Now, we has exited A_HOST. So set b_conn = 0
|
||||
* here. In fact, it need host driver to notify us.
|
||||
*/
|
||||
mvotg->otg_ctrl.b_conn = 0;
|
||||
mv_otg_set_vbus(otg, 0);
|
||||
break;
|
||||
case OTG_STATE_A_VBUS_ERR:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
goto run;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t mv_otg_irq(int irq, void *dev)
|
||||
{
|
||||
struct mv_otg *mvotg = dev;
|
||||
u32 otgsc;
|
||||
|
||||
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||
writel(otgsc, &mvotg->op_regs->otgsc);
|
||||
|
||||
/*
|
||||
* if we have vbus, then the vbus detection for B-device
|
||||
* will be done by mv_otg_inputs_irq().
|
||||
*/
|
||||
if (mvotg->pdata->vbus)
|
||||
if ((otgsc & OTGSC_STS_USB_ID) &&
|
||||
!(otgsc & OTGSC_INTSTS_USB_ID))
|
||||
return IRQ_NONE;
|
||||
|
||||
if ((otgsc & mvotg->irq_status) == 0)
|
||||
return IRQ_NONE;
|
||||
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t mv_otg_inputs_irq(int irq, void *dev)
|
||||
{
|
||||
struct mv_otg *mvotg = dev;
|
||||
|
||||
/* The clock may disabled at this time */
|
||||
if (!mvotg->active) {
|
||||
mv_otg_enable(mvotg);
|
||||
mv_otg_init_irq(mvotg);
|
||||
}
|
||||
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n",
|
||||
mvotg->otg_ctrl.a_bus_req);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
set_a_bus_req(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
|
||||
if (count > 2)
|
||||
return -1;
|
||||
|
||||
/* We will use this interface to change to A device */
|
||||
if (mvotg->phy.state != OTG_STATE_B_IDLE
|
||||
&& mvotg->phy.state != OTG_STATE_A_IDLE)
|
||||
return -1;
|
||||
|
||||
/* The clock may disabled and we need to set irq for ID detected */
|
||||
mv_otg_enable(mvotg);
|
||||
mv_otg_init_irq(mvotg);
|
||||
|
||||
if (buf[0] == '1') {
|
||||
mvotg->otg_ctrl.a_bus_req = 1;
|
||||
mvotg->otg_ctrl.a_bus_drop = 0;
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: a_bus_req = 1\n");
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req,
|
||||
set_a_bus_req);
|
||||
|
||||
static ssize_t
|
||||
set_a_clr_err(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
if (!mvotg->phy.otg->default_a)
|
||||
return -1;
|
||||
|
||||
if (count > 2)
|
||||
return -1;
|
||||
|
||||
if (buf[0] == '1') {
|
||||
mvotg->otg_ctrl.a_clr_err = 1;
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: a_clr_err = 1\n");
|
||||
}
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err);
|
||||
|
||||
static ssize_t
|
||||
get_a_bus_drop(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
return scnprintf(buf, PAGE_SIZE, "%d\n",
|
||||
mvotg->otg_ctrl.a_bus_drop);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
set_a_bus_drop(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||
if (!mvotg->phy.otg->default_a)
|
||||
return -1;
|
||||
|
||||
if (count > 2)
|
||||
return -1;
|
||||
|
||||
if (buf[0] == '0') {
|
||||
mvotg->otg_ctrl.a_bus_drop = 0;
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: a_bus_drop = 0\n");
|
||||
} else if (buf[0] == '1') {
|
||||
mvotg->otg_ctrl.a_bus_drop = 1;
|
||||
mvotg->otg_ctrl.a_bus_req = 0;
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: a_bus_drop = 1\n");
|
||||
dev_dbg(&mvotg->pdev->dev,
|
||||
"User request: and a_bus_req = 0\n");
|
||||
}
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR,
|
||||
get_a_bus_drop, set_a_bus_drop);
|
||||
|
||||
static struct attribute *inputs_attrs[] = {
|
||||
&dev_attr_a_bus_req.attr,
|
||||
&dev_attr_a_clr_err.attr,
|
||||
&dev_attr_a_bus_drop.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group inputs_attr_group = {
|
||||
.name = "inputs",
|
||||
.attrs = inputs_attrs,
|
||||
};
|
||||
|
||||
static int mv_otg_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mv_otg *mvotg = platform_get_drvdata(pdev);
|
||||
|
||||
sysfs_remove_group(&mvotg->pdev->dev.kobj, &inputs_attr_group);
|
||||
|
||||
if (mvotg->qwork) {
|
||||
flush_workqueue(mvotg->qwork);
|
||||
destroy_workqueue(mvotg->qwork);
|
||||
}
|
||||
|
||||
mv_otg_disable(mvotg);
|
||||
|
||||
usb_remove_phy(&mvotg->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct mv_otg *mvotg;
|
||||
struct usb_otg *otg;
|
||||
struct resource *r;
|
||||
int retval = 0, i;
|
||||
|
||||
if (pdata == NULL) {
|
||||
dev_err(&pdev->dev, "failed to get platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mvotg = devm_kzalloc(&pdev->dev, sizeof(*mvotg), GFP_KERNEL);
|
||||
if (!mvotg) {
|
||||
dev_err(&pdev->dev, "failed to allocate memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
|
||||
if (!otg)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, mvotg);
|
||||
|
||||
mvotg->pdev = pdev;
|
||||
mvotg->pdata = pdata;
|
||||
|
||||
mvotg->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(mvotg->clk))
|
||||
return PTR_ERR(mvotg->clk);
|
||||
|
||||
mvotg->qwork = create_singlethread_workqueue("mv_otg_queue");
|
||||
if (!mvotg->qwork) {
|
||||
dev_dbg(&pdev->dev, "cannot create workqueue for OTG\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&mvotg->work, mv_otg_work);
|
||||
|
||||
/* OTG common part */
|
||||
mvotg->pdev = pdev;
|
||||
mvotg->phy.dev = &pdev->dev;
|
||||
mvotg->phy.otg = otg;
|
||||
mvotg->phy.label = driver_name;
|
||||
mvotg->phy.state = OTG_STATE_UNDEFINED;
|
||||
|
||||
otg->phy = &mvotg->phy;
|
||||
otg->set_host = mv_otg_set_host;
|
||||
otg->set_peripheral = mv_otg_set_peripheral;
|
||||
otg->set_vbus = mv_otg_set_vbus;
|
||||
|
||||
for (i = 0; i < OTG_TIMER_NUM; i++)
|
||||
init_timer(&mvotg->otg_ctrl.timer[i]);
|
||||
|
||||
r = platform_get_resource_byname(mvotg->pdev,
|
||||
IORESOURCE_MEM, "phyregs");
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no phy I/O memory resource defined\n");
|
||||
retval = -ENODEV;
|
||||
goto err_destroy_workqueue;
|
||||
}
|
||||
|
||||
mvotg->phy_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
|
||||
if (mvotg->phy_regs == NULL) {
|
||||
dev_err(&pdev->dev, "failed to map phy I/O memory\n");
|
||||
retval = -EFAULT;
|
||||
goto err_destroy_workqueue;
|
||||
}
|
||||
|
||||
r = platform_get_resource_byname(mvotg->pdev,
|
||||
IORESOURCE_MEM, "capregs");
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no I/O memory resource defined\n");
|
||||
retval = -ENODEV;
|
||||
goto err_destroy_workqueue;
|
||||
}
|
||||
|
||||
mvotg->cap_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
|
||||
if (mvotg->cap_regs == NULL) {
|
||||
dev_err(&pdev->dev, "failed to map I/O memory\n");
|
||||
retval = -EFAULT;
|
||||
goto err_destroy_workqueue;
|
||||
}
|
||||
|
||||
/* we will acces controller register, so enable the udc controller */
|
||||
retval = mv_otg_enable_internal(mvotg);
|
||||
if (retval) {
|
||||
dev_err(&pdev->dev, "mv otg enable error %d\n", retval);
|
||||
goto err_destroy_workqueue;
|
||||
}
|
||||
|
||||
mvotg->op_regs =
|
||||
(struct mv_otg_regs __iomem *) ((unsigned long) mvotg->cap_regs
|
||||
+ (readl(mvotg->cap_regs) & CAPLENGTH_MASK));
|
||||
|
||||
if (pdata->id) {
|
||||
retval = devm_request_threaded_irq(&pdev->dev, pdata->id->irq,
|
||||
NULL, mv_otg_inputs_irq,
|
||||
IRQF_ONESHOT, "id", mvotg);
|
||||
if (retval) {
|
||||
dev_info(&pdev->dev,
|
||||
"Failed to request irq for ID\n");
|
||||
pdata->id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata->vbus) {
|
||||
mvotg->clock_gating = 1;
|
||||
retval = devm_request_threaded_irq(&pdev->dev, pdata->vbus->irq,
|
||||
NULL, mv_otg_inputs_irq,
|
||||
IRQF_ONESHOT, "vbus", mvotg);
|
||||
if (retval) {
|
||||
dev_info(&pdev->dev,
|
||||
"Failed to request irq for VBUS, "
|
||||
"disable clock gating\n");
|
||||
mvotg->clock_gating = 0;
|
||||
pdata->vbus = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdata->disable_otg_clock_gating)
|
||||
mvotg->clock_gating = 0;
|
||||
|
||||
mv_otg_reset(mvotg);
|
||||
mv_otg_init_irq(mvotg);
|
||||
|
||||
r = platform_get_resource(mvotg->pdev, IORESOURCE_IRQ, 0);
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no IRQ resource defined\n");
|
||||
retval = -ENODEV;
|
||||
goto err_disable_clk;
|
||||
}
|
||||
|
||||
mvotg->irq = r->start;
|
||||
if (devm_request_irq(&pdev->dev, mvotg->irq, mv_otg_irq, IRQF_SHARED,
|
||||
driver_name, mvotg)) {
|
||||
dev_err(&pdev->dev, "Request irq %d for OTG failed\n",
|
||||
mvotg->irq);
|
||||
mvotg->irq = 0;
|
||||
retval = -ENODEV;
|
||||
goto err_disable_clk;
|
||||
}
|
||||
|
||||
retval = usb_add_phy(&mvotg->phy, USB_PHY_TYPE_USB2);
|
||||
if (retval < 0) {
|
||||
dev_err(&pdev->dev, "can't register transceiver, %d\n",
|
||||
retval);
|
||||
goto err_disable_clk;
|
||||
}
|
||||
|
||||
retval = sysfs_create_group(&pdev->dev.kobj, &inputs_attr_group);
|
||||
if (retval < 0) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"Can't register sysfs attr group: %d\n", retval);
|
||||
goto err_remove_phy;
|
||||
}
|
||||
|
||||
spin_lock_init(&mvotg->wq_lock);
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 2 * HZ);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev,
|
||||
"successful probe OTG device %s clock gating.\n",
|
||||
mvotg->clock_gating ? "with" : "without");
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_phy:
|
||||
usb_remove_phy(&mvotg->phy);
|
||||
err_disable_clk:
|
||||
mv_otg_disable_internal(mvotg);
|
||||
err_destroy_workqueue:
|
||||
flush_workqueue(mvotg->qwork);
|
||||
destroy_workqueue(mvotg->qwork);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int mv_otg_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct mv_otg *mvotg = platform_get_drvdata(pdev);
|
||||
|
||||
if (mvotg->phy.state != OTG_STATE_B_IDLE) {
|
||||
dev_info(&pdev->dev,
|
||||
"OTG state is not B_IDLE, it is %d!\n",
|
||||
mvotg->phy.state);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
if (!mvotg->clock_gating)
|
||||
mv_otg_disable_internal(mvotg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct mv_otg *mvotg = platform_get_drvdata(pdev);
|
||||
u32 otgsc;
|
||||
|
||||
if (!mvotg->clock_gating) {
|
||||
mv_otg_enable_internal(mvotg);
|
||||
|
||||
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||
otgsc |= mvotg->irq_en;
|
||||
writel(otgsc, &mvotg->op_regs->otgsc);
|
||||
|
||||
if (spin_trylock(&mvotg->wq_lock)) {
|
||||
mv_otg_run_state_machine(mvotg, 0);
|
||||
spin_unlock(&mvotg->wq_lock);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct platform_driver mv_otg_driver = {
|
||||
.probe = mv_otg_probe,
|
||||
.remove = mv_otg_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = driver_name,
|
||||
},
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = mv_otg_suspend,
|
||||
.resume = mv_otg_resume,
|
||||
#endif
|
||||
};
|
||||
module_platform_driver(mv_otg_driver);
|
||||
164
drivers/usb/phy/phy-mv-usb.h
Normal file
164
drivers/usb/phy/phy-mv-usb.h
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Marvell International Ltd. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __MV_USB_OTG_CONTROLLER__
|
||||
#define __MV_USB_OTG_CONTROLLER__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/* Command Register Bit Masks */
|
||||
#define USBCMD_RUN_STOP (0x00000001)
|
||||
#define USBCMD_CTRL_RESET (0x00000002)
|
||||
|
||||
/* otgsc Register Bit Masks */
|
||||
#define OTGSC_CTRL_VUSB_DISCHARGE 0x00000001
|
||||
#define OTGSC_CTRL_VUSB_CHARGE 0x00000002
|
||||
#define OTGSC_CTRL_OTG_TERM 0x00000008
|
||||
#define OTGSC_CTRL_DATA_PULSING 0x00000010
|
||||
#define OTGSC_STS_USB_ID 0x00000100
|
||||
#define OTGSC_STS_A_VBUS_VALID 0x00000200
|
||||
#define OTGSC_STS_A_SESSION_VALID 0x00000400
|
||||
#define OTGSC_STS_B_SESSION_VALID 0x00000800
|
||||
#define OTGSC_STS_B_SESSION_END 0x00001000
|
||||
#define OTGSC_STS_1MS_TOGGLE 0x00002000
|
||||
#define OTGSC_STS_DATA_PULSING 0x00004000
|
||||
#define OTGSC_INTSTS_USB_ID 0x00010000
|
||||
#define OTGSC_INTSTS_A_VBUS_VALID 0x00020000
|
||||
#define OTGSC_INTSTS_A_SESSION_VALID 0x00040000
|
||||
#define OTGSC_INTSTS_B_SESSION_VALID 0x00080000
|
||||
#define OTGSC_INTSTS_B_SESSION_END 0x00100000
|
||||
#define OTGSC_INTSTS_1MS 0x00200000
|
||||
#define OTGSC_INTSTS_DATA_PULSING 0x00400000
|
||||
#define OTGSC_INTR_USB_ID 0x01000000
|
||||
#define OTGSC_INTR_A_VBUS_VALID 0x02000000
|
||||
#define OTGSC_INTR_A_SESSION_VALID 0x04000000
|
||||
#define OTGSC_INTR_B_SESSION_VALID 0x08000000
|
||||
#define OTGSC_INTR_B_SESSION_END 0x10000000
|
||||
#define OTGSC_INTR_1MS_TIMER 0x20000000
|
||||
#define OTGSC_INTR_DATA_PULSING 0x40000000
|
||||
|
||||
#define CAPLENGTH_MASK (0xff)
|
||||
|
||||
/* Timer's interval, unit 10ms */
|
||||
#define T_A_WAIT_VRISE 100
|
||||
#define T_A_WAIT_BCON 2000
|
||||
#define T_A_AIDL_BDIS 100
|
||||
#define T_A_BIDL_ADIS 20
|
||||
#define T_B_ASE0_BRST 400
|
||||
#define T_B_SE0_SRP 300
|
||||
#define T_B_SRP_FAIL 2000
|
||||
#define T_B_DATA_PLS 10
|
||||
#define T_B_SRP_INIT 100
|
||||
#define T_A_SRP_RSPNS 10
|
||||
#define T_A_DRV_RSM 5
|
||||
|
||||
enum otg_function {
|
||||
OTG_B_DEVICE = 0,
|
||||
OTG_A_DEVICE
|
||||
};
|
||||
|
||||
enum mv_otg_timer {
|
||||
A_WAIT_BCON_TIMER = 0,
|
||||
OTG_TIMER_NUM
|
||||
};
|
||||
|
||||
/* PXA OTG state machine */
|
||||
struct mv_otg_ctrl {
|
||||
/* internal variables */
|
||||
u8 a_set_b_hnp_en; /* A-Device set b_hnp_en */
|
||||
u8 b_srp_done;
|
||||
u8 b_hnp_en;
|
||||
|
||||
/* OTG inputs */
|
||||
u8 a_bus_drop;
|
||||
u8 a_bus_req;
|
||||
u8 a_clr_err;
|
||||
u8 a_bus_resume;
|
||||
u8 a_bus_suspend;
|
||||
u8 a_conn;
|
||||
u8 a_sess_vld;
|
||||
u8 a_srp_det;
|
||||
u8 a_vbus_vld;
|
||||
u8 b_bus_req; /* B-Device Require Bus */
|
||||
u8 b_bus_resume;
|
||||
u8 b_bus_suspend;
|
||||
u8 b_conn;
|
||||
u8 b_se0_srp;
|
||||
u8 b_sess_end;
|
||||
u8 b_sess_vld;
|
||||
u8 id;
|
||||
u8 a_suspend_req;
|
||||
|
||||
/*Timer event */
|
||||
u8 a_aidl_bdis_timeout;
|
||||
u8 b_ase0_brst_timeout;
|
||||
u8 a_bidl_adis_timeout;
|
||||
u8 a_wait_bcon_timeout;
|
||||
|
||||
struct timer_list timer[OTG_TIMER_NUM];
|
||||
};
|
||||
|
||||
#define VUSBHS_MAX_PORTS 8
|
||||
|
||||
struct mv_otg_regs {
|
||||
u32 usbcmd; /* Command register */
|
||||
u32 usbsts; /* Status register */
|
||||
u32 usbintr; /* Interrupt enable */
|
||||
u32 frindex; /* Frame index */
|
||||
u32 reserved1[1];
|
||||
u32 deviceaddr; /* Device Address */
|
||||
u32 eplistaddr; /* Endpoint List Address */
|
||||
u32 ttctrl; /* HOST TT status and control */
|
||||
u32 burstsize; /* Programmable Burst Size */
|
||||
u32 txfilltuning; /* Host Transmit Pre-Buffer Packet Tuning */
|
||||
u32 reserved[4];
|
||||
u32 epnak; /* Endpoint NAK */
|
||||
u32 epnaken; /* Endpoint NAK Enable */
|
||||
u32 configflag; /* Configured Flag register */
|
||||
u32 portsc[VUSBHS_MAX_PORTS]; /* Port Status/Control x, x = 1..8 */
|
||||
u32 otgsc;
|
||||
u32 usbmode; /* USB Host/Device mode */
|
||||
u32 epsetupstat; /* Endpoint Setup Status */
|
||||
u32 epprime; /* Endpoint Initialize */
|
||||
u32 epflush; /* Endpoint De-initialize */
|
||||
u32 epstatus; /* Endpoint Status */
|
||||
u32 epcomplete; /* Endpoint Interrupt On Complete */
|
||||
u32 epctrlx[16]; /* Endpoint Control, where x = 0.. 15 */
|
||||
u32 mcr; /* Mux Control */
|
||||
u32 isr; /* Interrupt Status */
|
||||
u32 ier; /* Interrupt Enable */
|
||||
};
|
||||
|
||||
struct mv_otg {
|
||||
struct usb_phy phy;
|
||||
struct mv_otg_ctrl otg_ctrl;
|
||||
|
||||
/* base address */
|
||||
void __iomem *phy_regs;
|
||||
void __iomem *cap_regs;
|
||||
struct mv_otg_regs __iomem *op_regs;
|
||||
|
||||
struct platform_device *pdev;
|
||||
int irq;
|
||||
u32 irq_status;
|
||||
u32 irq_en;
|
||||
|
||||
struct delayed_work work;
|
||||
struct workqueue_struct *qwork;
|
||||
|
||||
spinlock_t wq_lock;
|
||||
|
||||
struct mv_usb_platform_data *pdata;
|
||||
|
||||
unsigned int active;
|
||||
unsigned int clock_gating;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
#endif
|
||||
516
drivers/usb/phy/phy-mxs-usb.c
Normal file
516
drivers/usb/phy/phy-mxs-usb.c
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
/*
|
||||
* Copyright 2012-2014 Freescale Semiconductor, Inc.
|
||||
* Copyright (C) 2012 Marek Vasut <marex@denx.de>
|
||||
* on behalf of DENX Software Engineering GmbH
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/stmp_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
|
||||
#define DRIVER_NAME "mxs_phy"
|
||||
|
||||
#define HW_USBPHY_PWD 0x00
|
||||
#define HW_USBPHY_CTRL 0x30
|
||||
#define HW_USBPHY_CTRL_SET 0x34
|
||||
#define HW_USBPHY_CTRL_CLR 0x38
|
||||
|
||||
#define HW_USBPHY_DEBUG_SET 0x54
|
||||
#define HW_USBPHY_DEBUG_CLR 0x58
|
||||
|
||||
#define HW_USBPHY_IP 0x90
|
||||
#define HW_USBPHY_IP_SET 0x94
|
||||
#define HW_USBPHY_IP_CLR 0x98
|
||||
|
||||
#define BM_USBPHY_CTRL_SFTRST BIT(31)
|
||||
#define BM_USBPHY_CTRL_CLKGATE BIT(30)
|
||||
#define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26)
|
||||
#define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE BIT(25)
|
||||
#define BM_USBPHY_CTRL_ENVBUSCHG_WKUP BIT(23)
|
||||
#define BM_USBPHY_CTRL_ENIDCHG_WKUP BIT(22)
|
||||
#define BM_USBPHY_CTRL_ENDPDMCHG_WKUP BIT(21)
|
||||
#define BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD BIT(20)
|
||||
#define BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE BIT(19)
|
||||
#define BM_USBPHY_CTRL_ENAUTO_PWRON_PLL BIT(18)
|
||||
#define BM_USBPHY_CTRL_ENUTMILEVEL3 BIT(15)
|
||||
#define BM_USBPHY_CTRL_ENUTMILEVEL2 BIT(14)
|
||||
#define BM_USBPHY_CTRL_ENHOSTDISCONDETECT BIT(1)
|
||||
|
||||
#define BM_USBPHY_IP_FIX (BIT(17) | BIT(18))
|
||||
|
||||
#define BM_USBPHY_DEBUG_CLKGATE BIT(30)
|
||||
|
||||
/* Anatop Registers */
|
||||
#define ANADIG_ANA_MISC0 0x150
|
||||
#define ANADIG_ANA_MISC0_SET 0x154
|
||||
#define ANADIG_ANA_MISC0_CLR 0x158
|
||||
|
||||
#define ANADIG_USB1_VBUS_DET_STAT 0x1c0
|
||||
#define ANADIG_USB2_VBUS_DET_STAT 0x220
|
||||
|
||||
#define ANADIG_USB1_LOOPBACK_SET 0x1e4
|
||||
#define ANADIG_USB1_LOOPBACK_CLR 0x1e8
|
||||
#define ANADIG_USB2_LOOPBACK_SET 0x244
|
||||
#define ANADIG_USB2_LOOPBACK_CLR 0x248
|
||||
|
||||
#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG BIT(12)
|
||||
#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL BIT(11)
|
||||
|
||||
#define BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3)
|
||||
#define BM_ANADIG_USB2_VBUS_DET_STAT_VBUS_VALID BIT(3)
|
||||
|
||||
#define BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 BIT(2)
|
||||
#define BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN BIT(5)
|
||||
#define BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 BIT(2)
|
||||
#define BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN BIT(5)
|
||||
|
||||
#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy)
|
||||
|
||||
/* Do disconnection between PHY and controller without vbus */
|
||||
#define MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS BIT(0)
|
||||
|
||||
/*
|
||||
* The PHY will be in messy if there is a wakeup after putting
|
||||
* bus to suspend (set portsc.suspendM) but before setting PHY to low
|
||||
* power mode (set portsc.phcd).
|
||||
*/
|
||||
#define MXS_PHY_ABNORMAL_IN_SUSPEND BIT(1)
|
||||
|
||||
/*
|
||||
* The SOF sends too fast after resuming, it will cause disconnection
|
||||
* between host and high speed device.
|
||||
*/
|
||||
#define MXS_PHY_SENDING_SOF_TOO_FAST BIT(2)
|
||||
|
||||
/*
|
||||
* IC has bug fixes logic, they include
|
||||
* MXS_PHY_ABNORMAL_IN_SUSPEND and MXS_PHY_SENDING_SOF_TOO_FAST
|
||||
* which are described at above flags, the RTL will handle it
|
||||
* according to different versions.
|
||||
*/
|
||||
#define MXS_PHY_NEED_IP_FIX BIT(3)
|
||||
|
||||
struct mxs_phy_data {
|
||||
unsigned int flags;
|
||||
};
|
||||
|
||||
static const struct mxs_phy_data imx23_phy_data = {
|
||||
.flags = MXS_PHY_ABNORMAL_IN_SUSPEND | MXS_PHY_SENDING_SOF_TOO_FAST,
|
||||
};
|
||||
|
||||
static const struct mxs_phy_data imx6q_phy_data = {
|
||||
.flags = MXS_PHY_SENDING_SOF_TOO_FAST |
|
||||
MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
|
||||
MXS_PHY_NEED_IP_FIX,
|
||||
};
|
||||
|
||||
static const struct mxs_phy_data imx6sl_phy_data = {
|
||||
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
|
||||
MXS_PHY_NEED_IP_FIX,
|
||||
};
|
||||
|
||||
static const struct mxs_phy_data vf610_phy_data = {
|
||||
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
|
||||
MXS_PHY_NEED_IP_FIX,
|
||||
};
|
||||
|
||||
static const struct mxs_phy_data imx6sx_phy_data = {
|
||||
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
|
||||
MXS_PHY_NEED_IP_FIX,
|
||||
};
|
||||
|
||||
static const struct of_device_id mxs_phy_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx6sx-usbphy", .data = &imx6sx_phy_data, },
|
||||
{ .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, },
|
||||
{ .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, },
|
||||
{ .compatible = "fsl,imx23-usbphy", .data = &imx23_phy_data, },
|
||||
{ .compatible = "fsl,vf610-usbphy", .data = &vf610_phy_data, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids);
|
||||
|
||||
struct mxs_phy {
|
||||
struct usb_phy phy;
|
||||
struct clk *clk;
|
||||
const struct mxs_phy_data *data;
|
||||
struct regmap *regmap_anatop;
|
||||
int port_id;
|
||||
};
|
||||
|
||||
static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy)
|
||||
{
|
||||
return mxs_phy->data == &imx6q_phy_data;
|
||||
}
|
||||
|
||||
static inline bool is_imx6sl_phy(struct mxs_phy *mxs_phy)
|
||||
{
|
||||
return mxs_phy->data == &imx6sl_phy_data;
|
||||
}
|
||||
|
||||
/*
|
||||
* PHY needs some 32K cycles to switch from 32K clock to
|
||||
* bus (such as AHB/AXI, etc) clock.
|
||||
*/
|
||||
static void mxs_phy_clock_switch_delay(void)
|
||||
{
|
||||
usleep_range(300, 400);
|
||||
}
|
||||
|
||||
static int mxs_phy_hw_init(struct mxs_phy *mxs_phy)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *base = mxs_phy->phy.io_priv;
|
||||
|
||||
ret = stmp_reset_block(base + HW_USBPHY_CTRL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Power up the PHY */
|
||||
writel(0, base + HW_USBPHY_PWD);
|
||||
|
||||
/*
|
||||
* USB PHY Ctrl Setting
|
||||
* - Auto clock/power on
|
||||
* - Enable full/low speed support
|
||||
*/
|
||||
writel(BM_USBPHY_CTRL_ENAUTOSET_USBCLKS |
|
||||
BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE |
|
||||
BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD |
|
||||
BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE |
|
||||
BM_USBPHY_CTRL_ENAUTO_PWRON_PLL |
|
||||
BM_USBPHY_CTRL_ENUTMILEVEL2 |
|
||||
BM_USBPHY_CTRL_ENUTMILEVEL3,
|
||||
base + HW_USBPHY_CTRL_SET);
|
||||
|
||||
if (mxs_phy->data->flags & MXS_PHY_NEED_IP_FIX)
|
||||
writel(BM_USBPHY_IP_FIX, base + HW_USBPHY_IP_SET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return true if the vbus is there */
|
||||
static bool mxs_phy_get_vbus_status(struct mxs_phy *mxs_phy)
|
||||
{
|
||||
unsigned int vbus_value;
|
||||
|
||||
if (mxs_phy->port_id == 0)
|
||||
regmap_read(mxs_phy->regmap_anatop,
|
||||
ANADIG_USB1_VBUS_DET_STAT,
|
||||
&vbus_value);
|
||||
else if (mxs_phy->port_id == 1)
|
||||
regmap_read(mxs_phy->regmap_anatop,
|
||||
ANADIG_USB2_VBUS_DET_STAT,
|
||||
&vbus_value);
|
||||
|
||||
if (vbus_value & BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect)
|
||||
{
|
||||
void __iomem *base = mxs_phy->phy.io_priv;
|
||||
u32 reg;
|
||||
|
||||
if (disconnect)
|
||||
writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
|
||||
base + HW_USBPHY_DEBUG_CLR);
|
||||
|
||||
if (mxs_phy->port_id == 0) {
|
||||
reg = disconnect ? ANADIG_USB1_LOOPBACK_SET
|
||||
: ANADIG_USB1_LOOPBACK_CLR;
|
||||
regmap_write(mxs_phy->regmap_anatop, reg,
|
||||
BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 |
|
||||
BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN);
|
||||
} else if (mxs_phy->port_id == 1) {
|
||||
reg = disconnect ? ANADIG_USB2_LOOPBACK_SET
|
||||
: ANADIG_USB2_LOOPBACK_CLR;
|
||||
regmap_write(mxs_phy->regmap_anatop, reg,
|
||||
BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 |
|
||||
BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN);
|
||||
}
|
||||
|
||||
if (!disconnect)
|
||||
writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
|
||||
base + HW_USBPHY_DEBUG_SET);
|
||||
|
||||
/* Delay some time, and let Linestate be SE0 for controller */
|
||||
if (disconnect)
|
||||
usleep_range(500, 1000);
|
||||
}
|
||||
|
||||
static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on)
|
||||
{
|
||||
bool vbus_is_on = false;
|
||||
|
||||
/* If the SoCs don't need to disconnect line without vbus, quit */
|
||||
if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS))
|
||||
return;
|
||||
|
||||
/* If the SoCs don't have anatop, quit */
|
||||
if (!mxs_phy->regmap_anatop)
|
||||
return;
|
||||
|
||||
vbus_is_on = mxs_phy_get_vbus_status(mxs_phy);
|
||||
|
||||
if (on && !vbus_is_on)
|
||||
__mxs_phy_disconnect_line(mxs_phy, true);
|
||||
else
|
||||
__mxs_phy_disconnect_line(mxs_phy, false);
|
||||
|
||||
}
|
||||
|
||||
static int mxs_phy_init(struct usb_phy *phy)
|
||||
{
|
||||
int ret;
|
||||
struct mxs_phy *mxs_phy = to_mxs_phy(phy);
|
||||
|
||||
mxs_phy_clock_switch_delay();
|
||||
ret = clk_prepare_enable(mxs_phy->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return mxs_phy_hw_init(mxs_phy);
|
||||
}
|
||||
|
||||
static void mxs_phy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct mxs_phy *mxs_phy = to_mxs_phy(phy);
|
||||
|
||||
writel(BM_USBPHY_CTRL_CLKGATE,
|
||||
phy->io_priv + HW_USBPHY_CTRL_SET);
|
||||
|
||||
clk_disable_unprepare(mxs_phy->clk);
|
||||
}
|
||||
|
||||
static int mxs_phy_suspend(struct usb_phy *x, int suspend)
|
||||
{
|
||||
int ret;
|
||||
struct mxs_phy *mxs_phy = to_mxs_phy(x);
|
||||
|
||||
if (suspend) {
|
||||
writel(0xffffffff, x->io_priv + HW_USBPHY_PWD);
|
||||
writel(BM_USBPHY_CTRL_CLKGATE,
|
||||
x->io_priv + HW_USBPHY_CTRL_SET);
|
||||
clk_disable_unprepare(mxs_phy->clk);
|
||||
} else {
|
||||
mxs_phy_clock_switch_delay();
|
||||
ret = clk_prepare_enable(mxs_phy->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
writel(BM_USBPHY_CTRL_CLKGATE,
|
||||
x->io_priv + HW_USBPHY_CTRL_CLR);
|
||||
writel(0, x->io_priv + HW_USBPHY_PWD);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxs_phy_set_wakeup(struct usb_phy *x, bool enabled)
|
||||
{
|
||||
struct mxs_phy *mxs_phy = to_mxs_phy(x);
|
||||
u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP |
|
||||
BM_USBPHY_CTRL_ENDPDMCHG_WKUP |
|
||||
BM_USBPHY_CTRL_ENIDCHG_WKUP;
|
||||
if (enabled) {
|
||||
mxs_phy_disconnect_line(mxs_phy, true);
|
||||
writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_SET);
|
||||
} else {
|
||||
writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_CLR);
|
||||
mxs_phy_disconnect_line(mxs_phy, false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxs_phy_on_connect(struct usb_phy *phy,
|
||||
enum usb_device_speed speed)
|
||||
{
|
||||
dev_dbg(phy->dev, "%s device has connected\n",
|
||||
(speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
|
||||
|
||||
if (speed == USB_SPEED_HIGH)
|
||||
writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
|
||||
phy->io_priv + HW_USBPHY_CTRL_SET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxs_phy_on_disconnect(struct usb_phy *phy,
|
||||
enum usb_device_speed speed)
|
||||
{
|
||||
dev_dbg(phy->dev, "%s device has disconnected\n",
|
||||
(speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
|
||||
|
||||
if (speed == USB_SPEED_HIGH)
|
||||
writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
|
||||
phy->io_priv + HW_USBPHY_CTRL_CLR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxs_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
struct mxs_phy *mxs_phy;
|
||||
int ret;
|
||||
const struct of_device_id *of_id =
|
||||
of_match_device(mxs_phy_dt_ids, &pdev->dev);
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev,
|
||||
"can't get the clock, err=%ld", PTR_ERR(clk));
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
mxs_phy = devm_kzalloc(&pdev->dev, sizeof(*mxs_phy), GFP_KERNEL);
|
||||
if (!mxs_phy) {
|
||||
dev_err(&pdev->dev, "Failed to allocate USB PHY structure!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Some SoCs don't have anatop registers */
|
||||
if (of_get_property(np, "fsl,anatop", NULL)) {
|
||||
mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle
|
||||
(np, "fsl,anatop");
|
||||
if (IS_ERR(mxs_phy->regmap_anatop)) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"failed to find regmap for anatop\n");
|
||||
return PTR_ERR(mxs_phy->regmap_anatop);
|
||||
}
|
||||
}
|
||||
|
||||
ret = of_alias_get_id(np, "usbphy");
|
||||
if (ret < 0)
|
||||
dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret);
|
||||
mxs_phy->port_id = ret;
|
||||
|
||||
mxs_phy->phy.io_priv = base;
|
||||
mxs_phy->phy.dev = &pdev->dev;
|
||||
mxs_phy->phy.label = DRIVER_NAME;
|
||||
mxs_phy->phy.init = mxs_phy_init;
|
||||
mxs_phy->phy.shutdown = mxs_phy_shutdown;
|
||||
mxs_phy->phy.set_suspend = mxs_phy_suspend;
|
||||
mxs_phy->phy.notify_connect = mxs_phy_on_connect;
|
||||
mxs_phy->phy.notify_disconnect = mxs_phy_on_disconnect;
|
||||
mxs_phy->phy.type = USB_PHY_TYPE_USB2;
|
||||
mxs_phy->phy.set_wakeup = mxs_phy_set_wakeup;
|
||||
|
||||
mxs_phy->clk = clk;
|
||||
mxs_phy->data = of_id->data;
|
||||
|
||||
platform_set_drvdata(pdev, mxs_phy);
|
||||
|
||||
device_set_wakeup_capable(&pdev->dev, true);
|
||||
|
||||
ret = usb_add_phy_dev(&mxs_phy->phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxs_phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mxs_phy *mxs_phy = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&mxs_phy->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on)
|
||||
{
|
||||
unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
|
||||
|
||||
/* If the SoCs don't have anatop, quit */
|
||||
if (!mxs_phy->regmap_anatop)
|
||||
return;
|
||||
|
||||
if (is_imx6q_phy(mxs_phy))
|
||||
regmap_write(mxs_phy->regmap_anatop, reg,
|
||||
BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG);
|
||||
else if (is_imx6sl_phy(mxs_phy))
|
||||
regmap_write(mxs_phy->regmap_anatop,
|
||||
reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL);
|
||||
}
|
||||
|
||||
static int mxs_phy_system_suspend(struct device *dev)
|
||||
{
|
||||
struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
mxs_phy_enable_ldo_in_suspend(mxs_phy, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxs_phy_system_resume(struct device *dev)
|
||||
{
|
||||
struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
mxs_phy_enable_ldo_in_suspend(mxs_phy, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(mxs_phy_pm, mxs_phy_system_suspend,
|
||||
mxs_phy_system_resume);
|
||||
|
||||
static struct platform_driver mxs_phy_driver = {
|
||||
.probe = mxs_phy_probe,
|
||||
.remove = mxs_phy_remove,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = mxs_phy_dt_ids,
|
||||
.pm = &mxs_phy_pm,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init mxs_phy_module_init(void)
|
||||
{
|
||||
return platform_driver_register(&mxs_phy_driver);
|
||||
}
|
||||
postcore_initcall(mxs_phy_module_init);
|
||||
|
||||
static void __exit mxs_phy_module_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&mxs_phy_driver);
|
||||
}
|
||||
module_exit(mxs_phy_module_exit);
|
||||
|
||||
MODULE_ALIAS("platform:mxs-usb-phy");
|
||||
MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
|
||||
MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>");
|
||||
MODULE_DESCRIPTION("Freescale MXS USB PHY driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
169
drivers/usb/phy/phy-omap-otg.c
Normal file
169
drivers/usb/phy/phy-omap-otg.c
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* OMAP OTG controller driver
|
||||
*
|
||||
* Based on code from tahvo-usb.c and isp1301_omap.c drivers.
|
||||
*
|
||||
* Copyright (C) 2005-2006 Nokia Corporation
|
||||
* Copyright (C) 2004 Texas Instruments
|
||||
* Copyright (C) 2004 David Brownell
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General
|
||||
* Public License. See the file "COPYING" in the main directory of this
|
||||
* archive for more details.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/extcon.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/usb-omap1.h>
|
||||
|
||||
struct otg_device {
|
||||
void __iomem *base;
|
||||
bool id;
|
||||
bool vbus;
|
||||
struct extcon_specific_cable_nb vbus_dev;
|
||||
struct extcon_specific_cable_nb id_dev;
|
||||
struct notifier_block vbus_nb;
|
||||
struct notifier_block id_nb;
|
||||
};
|
||||
|
||||
#define OMAP_OTG_CTRL 0x0c
|
||||
#define OMAP_OTG_ASESSVLD (1 << 20)
|
||||
#define OMAP_OTG_BSESSEND (1 << 19)
|
||||
#define OMAP_OTG_BSESSVLD (1 << 18)
|
||||
#define OMAP_OTG_VBUSVLD (1 << 17)
|
||||
#define OMAP_OTG_ID (1 << 16)
|
||||
#define OMAP_OTG_XCEIV_OUTPUTS \
|
||||
(OMAP_OTG_ASESSVLD | OMAP_OTG_BSESSEND | OMAP_OTG_BSESSVLD | \
|
||||
OMAP_OTG_VBUSVLD | OMAP_OTG_ID)
|
||||
|
||||
static void omap_otg_ctrl(struct otg_device *otg_dev, u32 outputs)
|
||||
{
|
||||
u32 l;
|
||||
|
||||
l = readl(otg_dev->base + OMAP_OTG_CTRL);
|
||||
l &= ~OMAP_OTG_XCEIV_OUTPUTS;
|
||||
l |= outputs;
|
||||
writel(l, otg_dev->base + OMAP_OTG_CTRL);
|
||||
}
|
||||
|
||||
static void omap_otg_set_mode(struct otg_device *otg_dev)
|
||||
{
|
||||
if (!otg_dev->id && otg_dev->vbus)
|
||||
/* Set B-session valid. */
|
||||
omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSVLD);
|
||||
else if (otg_dev->vbus)
|
||||
/* Set A-session valid. */
|
||||
omap_otg_ctrl(otg_dev, OMAP_OTG_ASESSVLD);
|
||||
else if (!otg_dev->id)
|
||||
/* Set B-session end to indicate no VBUS. */
|
||||
omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSEND);
|
||||
}
|
||||
|
||||
static int omap_otg_id_notifier(struct notifier_block *nb,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct otg_device *otg_dev = container_of(nb, struct otg_device, id_nb);
|
||||
|
||||
otg_dev->id = event;
|
||||
omap_otg_set_mode(otg_dev);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int omap_otg_vbus_notifier(struct notifier_block *nb,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct otg_device *otg_dev = container_of(nb, struct otg_device,
|
||||
vbus_nb);
|
||||
|
||||
otg_dev->vbus = event;
|
||||
omap_otg_set_mode(otg_dev);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int omap_otg_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct omap_usb_config *config = pdev->dev.platform_data;
|
||||
struct otg_device *otg_dev;
|
||||
struct extcon_dev *extcon;
|
||||
int ret;
|
||||
u32 rev;
|
||||
|
||||
if (!config || !config->extcon)
|
||||
return -ENODEV;
|
||||
|
||||
extcon = extcon_get_extcon_dev(config->extcon);
|
||||
if (!extcon)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
otg_dev = devm_kzalloc(&pdev->dev, sizeof(*otg_dev), GFP_KERNEL);
|
||||
if (!otg_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
otg_dev->base = devm_ioremap_resource(&pdev->dev, &pdev->resource[0]);
|
||||
if (IS_ERR(otg_dev->base))
|
||||
return PTR_ERR(otg_dev->base);
|
||||
|
||||
otg_dev->id_nb.notifier_call = omap_otg_id_notifier;
|
||||
otg_dev->vbus_nb.notifier_call = omap_otg_vbus_notifier;
|
||||
|
||||
ret = extcon_register_interest(&otg_dev->id_dev, config->extcon,
|
||||
"USB-HOST", &otg_dev->id_nb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = extcon_register_interest(&otg_dev->vbus_dev, config->extcon,
|
||||
"USB", &otg_dev->vbus_nb);
|
||||
if (ret) {
|
||||
extcon_unregister_interest(&otg_dev->id_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
otg_dev->id = extcon_get_cable_state(extcon, "USB-HOST");
|
||||
otg_dev->vbus = extcon_get_cable_state(extcon, "USB");
|
||||
omap_otg_set_mode(otg_dev);
|
||||
|
||||
rev = readl(otg_dev->base);
|
||||
|
||||
dev_info(&pdev->dev,
|
||||
"OMAP USB OTG controller rev %d.%d (%s, id=%d, vbus=%d)\n",
|
||||
(rev >> 4) & 0xf, rev & 0xf, config->extcon, otg_dev->id,
|
||||
otg_dev->vbus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_otg_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct otg_device *otg_dev = platform_get_drvdata(pdev);
|
||||
|
||||
extcon_unregister_interest(&otg_dev->id_dev);
|
||||
extcon_unregister_interest(&otg_dev->vbus_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver omap_otg_driver = {
|
||||
.probe = omap_otg_probe,
|
||||
.remove = omap_otg_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "omap_otg",
|
||||
},
|
||||
};
|
||||
module_platform_driver(omap_otg_driver);
|
||||
|
||||
MODULE_DESCRIPTION("OMAP USB OTG controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
|
||||
248
drivers/usb/phy/phy-rcar-gen2-usb.c
Normal file
248
drivers/usb/phy/phy-rcar-gen2-usb.c
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* Renesas R-Car Gen2 USB phy driver
|
||||
*
|
||||
* Copyright (C) 2013 Renesas Solutions Corp.
|
||||
* Copyright (C) 2013 Cogent Embedded, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/usb-rcar-gen2-phy.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/usb/otg.h>
|
||||
|
||||
struct rcar_gen2_usb_phy_priv {
|
||||
struct usb_phy phy;
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
spinlock_t lock;
|
||||
int usecount;
|
||||
u32 ugctrl2;
|
||||
};
|
||||
|
||||
#define usb_phy_to_priv(p) container_of(p, struct rcar_gen2_usb_phy_priv, phy)
|
||||
|
||||
/* Low Power Status register */
|
||||
#define USBHS_LPSTS_REG 0x02
|
||||
#define USBHS_LPSTS_SUSPM (1 << 14)
|
||||
|
||||
/* USB General control register */
|
||||
#define USBHS_UGCTRL_REG 0x80
|
||||
#define USBHS_UGCTRL_CONNECT (1 << 2)
|
||||
#define USBHS_UGCTRL_PLLRESET (1 << 0)
|
||||
|
||||
/* USB General control register 2 */
|
||||
#define USBHS_UGCTRL2_REG 0x84
|
||||
#define USBHS_UGCTRL2_USB0_PCI (1 << 4)
|
||||
#define USBHS_UGCTRL2_USB0_HS (3 << 4)
|
||||
#define USBHS_UGCTRL2_USB2_PCI (0 << 31)
|
||||
#define USBHS_UGCTRL2_USB2_SS (1 << 31)
|
||||
|
||||
/* USB General status register */
|
||||
#define USBHS_UGSTS_REG 0x88
|
||||
#define USBHS_UGSTS_LOCK (3 << 8)
|
||||
|
||||
/* Enable USBHS internal phy */
|
||||
static int __rcar_gen2_usbhs_phy_enable(void __iomem *base)
|
||||
{
|
||||
u32 val;
|
||||
int i;
|
||||
|
||||
/* USBHS PHY power on */
|
||||
val = ioread32(base + USBHS_UGCTRL_REG);
|
||||
val &= ~USBHS_UGCTRL_PLLRESET;
|
||||
iowrite32(val, base + USBHS_UGCTRL_REG);
|
||||
|
||||
val = ioread16(base + USBHS_LPSTS_REG);
|
||||
val |= USBHS_LPSTS_SUSPM;
|
||||
iowrite16(val, base + USBHS_LPSTS_REG);
|
||||
|
||||
for (i = 0; i < 20; i++) {
|
||||
val = ioread32(base + USBHS_UGSTS_REG);
|
||||
if ((val & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) {
|
||||
val = ioread32(base + USBHS_UGCTRL_REG);
|
||||
val |= USBHS_UGCTRL_CONNECT;
|
||||
iowrite32(val, base + USBHS_UGCTRL_REG);
|
||||
return 0;
|
||||
}
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
/* Timed out waiting for the PLL lock */
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* Disable USBHS internal phy */
|
||||
static int __rcar_gen2_usbhs_phy_disable(void __iomem *base)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* USBHS PHY power off */
|
||||
val = ioread32(base + USBHS_UGCTRL_REG);
|
||||
val &= ~USBHS_UGCTRL_CONNECT;
|
||||
iowrite32(val, base + USBHS_UGCTRL_REG);
|
||||
|
||||
val = ioread16(base + USBHS_LPSTS_REG);
|
||||
val &= ~USBHS_LPSTS_SUSPM;
|
||||
iowrite16(val, base + USBHS_LPSTS_REG);
|
||||
|
||||
val = ioread32(base + USBHS_UGCTRL_REG);
|
||||
val |= USBHS_UGCTRL_PLLRESET;
|
||||
iowrite32(val, base + USBHS_UGCTRL_REG);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Setup USB channels */
|
||||
static void __rcar_gen2_usb_phy_init(struct rcar_gen2_usb_phy_priv *priv)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
clk_prepare_enable(priv->clk);
|
||||
|
||||
/* Set USB channels in the USBHS UGCTRL2 register */
|
||||
val = ioread32(priv->base + USBHS_UGCTRL2_REG);
|
||||
val &= ~(USBHS_UGCTRL2_USB0_HS | USBHS_UGCTRL2_USB2_SS);
|
||||
val |= priv->ugctrl2;
|
||||
iowrite32(val, priv->base + USBHS_UGCTRL2_REG);
|
||||
}
|
||||
|
||||
/* Shutdown USB channels */
|
||||
static void __rcar_gen2_usb_phy_shutdown(struct rcar_gen2_usb_phy_priv *priv)
|
||||
{
|
||||
__rcar_gen2_usbhs_phy_disable(priv->base);
|
||||
clk_disable_unprepare(priv->clk);
|
||||
}
|
||||
|
||||
static int rcar_gen2_usb_phy_set_suspend(struct usb_phy *phy, int suspend)
|
||||
{
|
||||
struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy);
|
||||
unsigned long flags;
|
||||
int retval;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
retval = suspend ? __rcar_gen2_usbhs_phy_disable(priv->base) :
|
||||
__rcar_gen2_usbhs_phy_enable(priv->base);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int rcar_gen2_usb_phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
/*
|
||||
* Enable the clock and setup USB channels
|
||||
* if it's the first user
|
||||
*/
|
||||
if (!priv->usecount++)
|
||||
__rcar_gen2_usb_phy_init(priv);
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rcar_gen2_usb_phy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
if (!priv->usecount) {
|
||||
dev_warn(phy->dev, "Trying to disable phy with 0 usecount\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Disable everything if it's the last user */
|
||||
if (!--priv->usecount)
|
||||
__rcar_gen2_usb_phy_shutdown(priv);
|
||||
out:
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
}
|
||||
|
||||
static int rcar_gen2_usb_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rcar_gen2_phy_platform_data *pdata;
|
||||
struct rcar_gen2_usb_phy_priv *priv;
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
int retval;
|
||||
|
||||
pdata = dev_get_platdata(dev);
|
||||
if (!pdata) {
|
||||
dev_err(dev, "No platform data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clk = devm_clk_get(dev, "usbhs");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(dev, "Can't get the clock\n");
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
dev_err(dev, "Memory allocation failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
spin_lock_init(&priv->lock);
|
||||
priv->clk = clk;
|
||||
priv->base = base;
|
||||
priv->ugctrl2 = pdata->chan0_pci ?
|
||||
USBHS_UGCTRL2_USB0_PCI : USBHS_UGCTRL2_USB0_HS;
|
||||
priv->ugctrl2 |= pdata->chan2_pci ?
|
||||
USBHS_UGCTRL2_USB2_PCI : USBHS_UGCTRL2_USB2_SS;
|
||||
priv->phy.dev = dev;
|
||||
priv->phy.label = dev_name(dev);
|
||||
priv->phy.init = rcar_gen2_usb_phy_init;
|
||||
priv->phy.shutdown = rcar_gen2_usb_phy_shutdown;
|
||||
priv->phy.set_suspend = rcar_gen2_usb_phy_set_suspend;
|
||||
|
||||
retval = usb_add_phy_dev(&priv->phy);
|
||||
if (retval < 0) {
|
||||
dev_err(dev, "Failed to add USB phy\n");
|
||||
return retval;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int rcar_gen2_usb_phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rcar_gen2_usb_phy_priv *priv = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&priv->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver rcar_gen2_usb_phy_driver = {
|
||||
.driver = {
|
||||
.name = "usb_phy_rcar_gen2",
|
||||
},
|
||||
.probe = rcar_gen2_usb_phy_probe,
|
||||
.remove = rcar_gen2_usb_phy_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(rcar_gen2_usb_phy_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Renesas R-Car Gen2 USB phy");
|
||||
MODULE_AUTHOR("Valentine Barshak <valentine.barshak@cogentembedded.com>");
|
||||
251
drivers/usb/phy/phy-rcar-usb.c
Normal file
251
drivers/usb/phy/phy-rcar-usb.c
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* Renesas R-Car USB phy driver
|
||||
*
|
||||
* Copyright (C) 2012-2013 Renesas Solutions Corp.
|
||||
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
||||
* Copyright (C) 2013 Cogent Embedded, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/usb-rcar-phy.h>
|
||||
|
||||
/* REGS block */
|
||||
#define USBPCTRL0 0x00
|
||||
#define USBPCTRL1 0x04
|
||||
#define USBST 0x08
|
||||
#define USBEH0 0x0C
|
||||
#define USBOH0 0x1C
|
||||
#define USBCTL0 0x58
|
||||
|
||||
/* High-speed signal quality characteristic control registers (R8A7778 only) */
|
||||
#define HSQCTL1 0x24
|
||||
#define HSQCTL2 0x28
|
||||
|
||||
/* USBPCTRL0 */
|
||||
#define OVC2 (1 << 10) /* (R8A7779 only) */
|
||||
/* Switches the OVC input pin for port 2: */
|
||||
/* 1: USB_OVC2, 0: OVC2 */
|
||||
#define OVC1_VBUS1 (1 << 9) /* Switches the OVC input pin for port 1: */
|
||||
/* 1: USB_OVC1, 0: OVC1/VBUS1 */
|
||||
/* Function mode: set to 0 */
|
||||
#define OVC0 (1 << 8) /* Switches the OVC input pin for port 0: */
|
||||
/* 1: USB_OVC0 pin, 0: OVC0 */
|
||||
#define OVC2_ACT (1 << 6) /* (R8A7779 only) */
|
||||
/* Host mode: OVC2 polarity: */
|
||||
/* 1: active-high, 0: active-low */
|
||||
#define PENC (1 << 4) /* Function mode: output level of PENC1 pin: */
|
||||
/* 1: high, 0: low */
|
||||
#define OVC0_ACT (1 << 3) /* Host mode: OVC0 polarity: */
|
||||
/* 1: active-high, 0: active-low */
|
||||
#define OVC1_ACT (1 << 1) /* Host mode: OVC1 polarity: */
|
||||
/* 1: active-high, 0: active-low */
|
||||
/* Function mode: be sure to set to 1 */
|
||||
#define PORT1 (1 << 0) /* Selects port 1 mode: */
|
||||
/* 1: function, 0: host */
|
||||
/* USBPCTRL1 */
|
||||
#define PHY_RST (1 << 2)
|
||||
#define PLL_ENB (1 << 1)
|
||||
#define PHY_ENB (1 << 0)
|
||||
|
||||
/* USBST */
|
||||
#define ST_ACT (1 << 31)
|
||||
#define ST_PLL (1 << 30)
|
||||
|
||||
struct rcar_usb_phy_priv {
|
||||
struct usb_phy phy;
|
||||
spinlock_t lock;
|
||||
|
||||
void __iomem *reg0;
|
||||
void __iomem *reg1;
|
||||
int counter;
|
||||
};
|
||||
|
||||
#define usb_phy_to_priv(p) container_of(p, struct rcar_usb_phy_priv, phy)
|
||||
|
||||
|
||||
/*
|
||||
* USB initial/install operation.
|
||||
*
|
||||
* This function setup USB phy.
|
||||
* The used value and setting order came from
|
||||
* [USB :: Initial setting] on datasheet.
|
||||
*/
|
||||
static int rcar_usb_phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy);
|
||||
struct device *dev = phy->dev;
|
||||
struct rcar_phy_platform_data *pdata = dev_get_platdata(dev);
|
||||
void __iomem *reg0 = priv->reg0;
|
||||
void __iomem *reg1 = priv->reg1;
|
||||
static const u8 ovcn_act[] = { OVC0_ACT, OVC1_ACT, OVC2_ACT };
|
||||
int i;
|
||||
u32 val;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
if (priv->counter++ == 0) {
|
||||
|
||||
/*
|
||||
* USB phy start-up
|
||||
*/
|
||||
|
||||
/* (1) USB-PHY standby release */
|
||||
iowrite32(PHY_ENB, (reg0 + USBPCTRL1));
|
||||
|
||||
/* (2) start USB-PHY internal PLL */
|
||||
iowrite32(PHY_ENB | PLL_ENB, (reg0 + USBPCTRL1));
|
||||
|
||||
/* (3) set USB-PHY in accord with the conditions of usage */
|
||||
if (reg1) {
|
||||
u32 hsqctl1 = pdata->ferrite_bead ? 0x41 : 0;
|
||||
u32 hsqctl2 = pdata->ferrite_bead ? 0x0d : 7;
|
||||
|
||||
iowrite32(hsqctl1, reg1 + HSQCTL1);
|
||||
iowrite32(hsqctl2, reg1 + HSQCTL2);
|
||||
}
|
||||
|
||||
/* (4) USB module status check */
|
||||
for (i = 0; i < 1024; i++) {
|
||||
udelay(10);
|
||||
val = ioread32(reg0 + USBST);
|
||||
if (val == (ST_ACT | ST_PLL))
|
||||
break;
|
||||
}
|
||||
|
||||
if (val != (ST_ACT | ST_PLL)) {
|
||||
dev_err(dev, "USB phy not ready\n");
|
||||
goto phy_init_end;
|
||||
}
|
||||
|
||||
/* (5) USB-PHY reset clear */
|
||||
iowrite32(PHY_ENB | PLL_ENB | PHY_RST, (reg0 + USBPCTRL1));
|
||||
|
||||
/* Board specific port settings */
|
||||
val = 0;
|
||||
if (pdata->port1_func)
|
||||
val |= PORT1;
|
||||
if (pdata->penc1)
|
||||
val |= PENC;
|
||||
for (i = 0; i < 3; i++) {
|
||||
/* OVCn bits follow each other in the right order */
|
||||
if (pdata->ovc_pin[i].select_3_3v)
|
||||
val |= OVC0 << i;
|
||||
/* OVCn_ACT bits are spaced by irregular intervals */
|
||||
if (pdata->ovc_pin[i].active_high)
|
||||
val |= ovcn_act[i];
|
||||
}
|
||||
iowrite32(val, (reg0 + USBPCTRL0));
|
||||
|
||||
/*
|
||||
* Bus alignment settings
|
||||
*/
|
||||
|
||||
/* (1) EHCI bus alignment (little endian) */
|
||||
iowrite32(0x00000000, (reg0 + USBEH0));
|
||||
|
||||
/* (1) OHCI bus alignment (little endian) */
|
||||
iowrite32(0x00000000, (reg0 + USBOH0));
|
||||
}
|
||||
|
||||
phy_init_end:
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rcar_usb_phy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy);
|
||||
void __iomem *reg0 = priv->reg0;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&priv->lock, flags);
|
||||
|
||||
if (priv->counter-- == 1) /* last user */
|
||||
iowrite32(0x00000000, (reg0 + USBPCTRL1));
|
||||
|
||||
spin_unlock_irqrestore(&priv->lock, flags);
|
||||
}
|
||||
|
||||
static int rcar_usb_phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rcar_usb_phy_priv *priv;
|
||||
struct resource *res0, *res1;
|
||||
struct device *dev = &pdev->dev;
|
||||
void __iomem *reg0, *reg1 = NULL;
|
||||
int ret;
|
||||
|
||||
if (!dev_get_platdata(&pdev->dev)) {
|
||||
dev_err(dev, "No platform data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
reg0 = devm_ioremap_resource(dev, res0);
|
||||
if (IS_ERR(reg0))
|
||||
return PTR_ERR(reg0);
|
||||
|
||||
res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
if (res1) {
|
||||
reg1 = devm_ioremap_resource(dev, res1);
|
||||
if (IS_ERR(reg1))
|
||||
return PTR_ERR(reg1);
|
||||
}
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
dev_err(dev, "priv data allocation error\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
priv->reg0 = reg0;
|
||||
priv->reg1 = reg1;
|
||||
priv->counter = 0;
|
||||
priv->phy.dev = dev;
|
||||
priv->phy.label = dev_name(dev);
|
||||
priv->phy.init = rcar_usb_phy_init;
|
||||
priv->phy.shutdown = rcar_usb_phy_shutdown;
|
||||
spin_lock_init(&priv->lock);
|
||||
|
||||
ret = usb_add_phy(&priv->phy, USB_PHY_TYPE_USB2);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "usb phy addition error\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rcar_usb_phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rcar_usb_phy_priv *priv = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&priv->phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver rcar_usb_phy_driver = {
|
||||
.driver = {
|
||||
.name = "rcar_usb_phy",
|
||||
},
|
||||
.probe = rcar_usb_phy_probe,
|
||||
.remove = rcar_usb_phy_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(rcar_usb_phy_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Renesas R-Car USB phy");
|
||||
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
|
||||
457
drivers/usb/phy/phy-tahvo.c
Normal file
457
drivers/usb/phy/phy-tahvo.c
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
* Tahvo USB transceiver driver
|
||||
*
|
||||
* Copyright (C) 2005-2006 Nokia Corporation
|
||||
*
|
||||
* Parts copied from isp1301_omap.c.
|
||||
* Copyright (C) 2004 Texas Instruments
|
||||
* Copyright (C) 2004 David Brownell
|
||||
*
|
||||
* Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs.
|
||||
* Modified for Retu/Tahvo MFD by Aaro Koskinen.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General
|
||||
* Public License. See the file "COPYING" in the main directory of this
|
||||
* archive for more details.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/extcon.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/mfd/retu.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#define DRIVER_NAME "tahvo-usb"
|
||||
|
||||
#define TAHVO_REG_IDSR 0x02
|
||||
#define TAHVO_REG_USBR 0x06
|
||||
|
||||
#define USBR_SLAVE_CONTROL (1 << 8)
|
||||
#define USBR_VPPVIO_SW (1 << 7)
|
||||
#define USBR_SPEED (1 << 6)
|
||||
#define USBR_REGOUT (1 << 5)
|
||||
#define USBR_MASTER_SW2 (1 << 4)
|
||||
#define USBR_MASTER_SW1 (1 << 3)
|
||||
#define USBR_SLAVE_SW (1 << 2)
|
||||
#define USBR_NSUSPEND (1 << 1)
|
||||
#define USBR_SEMODE (1 << 0)
|
||||
|
||||
#define TAHVO_MODE_HOST 0
|
||||
#define TAHVO_MODE_PERIPHERAL 1
|
||||
|
||||
struct tahvo_usb {
|
||||
struct platform_device *pt_dev;
|
||||
struct usb_phy phy;
|
||||
int vbus_state;
|
||||
struct mutex serialize;
|
||||
struct clk *ick;
|
||||
int irq;
|
||||
int tahvo_mode;
|
||||
struct extcon_dev extcon;
|
||||
};
|
||||
|
||||
static const char *tahvo_cable[] = {
|
||||
"USB-HOST",
|
||||
"USB",
|
||||
NULL,
|
||||
};
|
||||
|
||||
static ssize_t vbus_state_show(struct device *device,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct tahvo_usb *tu = dev_get_drvdata(device);
|
||||
return sprintf(buf, "%s\n", tu->vbus_state ? "on" : "off");
|
||||
}
|
||||
static DEVICE_ATTR(vbus, 0444, vbus_state_show, NULL);
|
||||
|
||||
static void check_vbus_state(struct tahvo_usb *tu)
|
||||
{
|
||||
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
|
||||
int reg, prev_state;
|
||||
|
||||
reg = retu_read(rdev, TAHVO_REG_IDSR);
|
||||
if (reg & TAHVO_STAT_VBUS) {
|
||||
switch (tu->phy.state) {
|
||||
case OTG_STATE_B_IDLE:
|
||||
/* Enable the gadget driver */
|
||||
if (tu->phy.otg->gadget)
|
||||
usb_gadget_vbus_connect(tu->phy.otg->gadget);
|
||||
tu->phy.state = OTG_STATE_B_PERIPHERAL;
|
||||
break;
|
||||
case OTG_STATE_A_IDLE:
|
||||
/*
|
||||
* Session is now valid assuming the USB hub is driving
|
||||
* Vbus.
|
||||
*/
|
||||
tu->phy.state = OTG_STATE_A_HOST;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dev_info(&tu->pt_dev->dev, "USB cable connected\n");
|
||||
} else {
|
||||
switch (tu->phy.state) {
|
||||
case OTG_STATE_B_PERIPHERAL:
|
||||
if (tu->phy.otg->gadget)
|
||||
usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
|
||||
tu->phy.state = OTG_STATE_B_IDLE;
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
tu->phy.state = OTG_STATE_A_IDLE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dev_info(&tu->pt_dev->dev, "USB cable disconnected\n");
|
||||
}
|
||||
|
||||
prev_state = tu->vbus_state;
|
||||
tu->vbus_state = reg & TAHVO_STAT_VBUS;
|
||||
if (prev_state != tu->vbus_state) {
|
||||
extcon_set_cable_state(&tu->extcon, "USB", tu->vbus_state);
|
||||
sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state");
|
||||
}
|
||||
}
|
||||
|
||||
static void tahvo_usb_become_host(struct tahvo_usb *tu)
|
||||
{
|
||||
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
|
||||
|
||||
extcon_set_cable_state(&tu->extcon, "USB-HOST", true);
|
||||
|
||||
/* Power up the transceiver in USB host mode */
|
||||
retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND |
|
||||
USBR_MASTER_SW2 | USBR_MASTER_SW1);
|
||||
tu->phy.state = OTG_STATE_A_IDLE;
|
||||
|
||||
check_vbus_state(tu);
|
||||
}
|
||||
|
||||
static void tahvo_usb_stop_host(struct tahvo_usb *tu)
|
||||
{
|
||||
tu->phy.state = OTG_STATE_A_IDLE;
|
||||
}
|
||||
|
||||
static void tahvo_usb_become_peripheral(struct tahvo_usb *tu)
|
||||
{
|
||||
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
|
||||
|
||||
extcon_set_cable_state(&tu->extcon, "USB-HOST", false);
|
||||
|
||||
/* Power up transceiver and set it in USB peripheral mode */
|
||||
retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT |
|
||||
USBR_NSUSPEND | USBR_SLAVE_SW);
|
||||
tu->phy.state = OTG_STATE_B_IDLE;
|
||||
|
||||
check_vbus_state(tu);
|
||||
}
|
||||
|
||||
static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu)
|
||||
{
|
||||
if (tu->phy.otg->gadget)
|
||||
usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
|
||||
tu->phy.state = OTG_STATE_B_IDLE;
|
||||
}
|
||||
|
||||
static void tahvo_usb_power_off(struct tahvo_usb *tu)
|
||||
{
|
||||
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
|
||||
|
||||
/* Disable gadget controller if any */
|
||||
if (tu->phy.otg->gadget)
|
||||
usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
|
||||
|
||||
/* Power off transceiver */
|
||||
retu_write(rdev, TAHVO_REG_USBR, 0);
|
||||
tu->phy.state = OTG_STATE_UNDEFINED;
|
||||
}
|
||||
|
||||
static int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend)
|
||||
{
|
||||
struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy);
|
||||
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
|
||||
u16 w;
|
||||
|
||||
dev_dbg(&tu->pt_dev->dev, "%s\n", __func__);
|
||||
|
||||
w = retu_read(rdev, TAHVO_REG_USBR);
|
||||
if (suspend)
|
||||
w &= ~USBR_NSUSPEND;
|
||||
else
|
||||
w |= USBR_NSUSPEND;
|
||||
retu_write(rdev, TAHVO_REG_USBR, w);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
struct tahvo_usb *tu = container_of(otg->phy, struct tahvo_usb, phy);
|
||||
|
||||
dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, host);
|
||||
|
||||
mutex_lock(&tu->serialize);
|
||||
|
||||
if (host == NULL) {
|
||||
if (tu->tahvo_mode == TAHVO_MODE_HOST)
|
||||
tahvo_usb_power_off(tu);
|
||||
otg->host = NULL;
|
||||
mutex_unlock(&tu->serialize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tu->tahvo_mode == TAHVO_MODE_HOST) {
|
||||
otg->host = NULL;
|
||||
tahvo_usb_become_host(tu);
|
||||
}
|
||||
|
||||
otg->host = host;
|
||||
|
||||
mutex_unlock(&tu->serialize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tahvo_usb_set_peripheral(struct usb_otg *otg,
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
struct tahvo_usb *tu = container_of(otg->phy, struct tahvo_usb, phy);
|
||||
|
||||
dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, gadget);
|
||||
|
||||
mutex_lock(&tu->serialize);
|
||||
|
||||
if (!gadget) {
|
||||
if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
|
||||
tahvo_usb_power_off(tu);
|
||||
tu->phy.otg->gadget = NULL;
|
||||
mutex_unlock(&tu->serialize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
tu->phy.otg->gadget = gadget;
|
||||
if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
|
||||
tahvo_usb_become_peripheral(tu);
|
||||
|
||||
mutex_unlock(&tu->serialize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu)
|
||||
{
|
||||
struct tahvo_usb *tu = _tu;
|
||||
|
||||
mutex_lock(&tu->serialize);
|
||||
check_vbus_state(tu);
|
||||
mutex_unlock(&tu->serialize);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static ssize_t otg_mode_show(struct device *device,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct tahvo_usb *tu = dev_get_drvdata(device);
|
||||
|
||||
switch (tu->tahvo_mode) {
|
||||
case TAHVO_MODE_HOST:
|
||||
return sprintf(buf, "host\n");
|
||||
case TAHVO_MODE_PERIPHERAL:
|
||||
return sprintf(buf, "peripheral\n");
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static ssize_t otg_mode_store(struct device *device,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tahvo_usb *tu = dev_get_drvdata(device);
|
||||
int r;
|
||||
|
||||
mutex_lock(&tu->serialize);
|
||||
if (count >= 4 && strncmp(buf, "host", 4) == 0) {
|
||||
if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
|
||||
tahvo_usb_stop_peripheral(tu);
|
||||
tu->tahvo_mode = TAHVO_MODE_HOST;
|
||||
if (tu->phy.otg->host) {
|
||||
dev_info(device, "HOST mode: host controller present\n");
|
||||
tahvo_usb_become_host(tu);
|
||||
} else {
|
||||
dev_info(device, "HOST mode: no host controller, powering off\n");
|
||||
tahvo_usb_power_off(tu);
|
||||
}
|
||||
r = strlen(buf);
|
||||
} else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) {
|
||||
if (tu->tahvo_mode == TAHVO_MODE_HOST)
|
||||
tahvo_usb_stop_host(tu);
|
||||
tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
|
||||
if (tu->phy.otg->gadget) {
|
||||
dev_info(device, "PERIPHERAL mode: gadget driver present\n");
|
||||
tahvo_usb_become_peripheral(tu);
|
||||
} else {
|
||||
dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n");
|
||||
tahvo_usb_power_off(tu);
|
||||
}
|
||||
r = strlen(buf);
|
||||
} else {
|
||||
r = -EINVAL;
|
||||
}
|
||||
mutex_unlock(&tu->serialize);
|
||||
|
||||
return r;
|
||||
}
|
||||
static DEVICE_ATTR(otg_mode, 0644, otg_mode_show, otg_mode_store);
|
||||
|
||||
static struct attribute *tahvo_attributes[] = {
|
||||
&dev_attr_vbus.attr,
|
||||
&dev_attr_otg_mode.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group tahvo_attr_group = {
|
||||
.attrs = tahvo_attributes,
|
||||
};
|
||||
|
||||
static int tahvo_usb_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
|
||||
struct tahvo_usb *tu;
|
||||
int ret;
|
||||
|
||||
tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL);
|
||||
if (!tu)
|
||||
return -ENOMEM;
|
||||
|
||||
tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg),
|
||||
GFP_KERNEL);
|
||||
if (!tu->phy.otg)
|
||||
return -ENOMEM;
|
||||
|
||||
tu->pt_dev = pdev;
|
||||
|
||||
/* Default mode */
|
||||
#ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT
|
||||
tu->tahvo_mode = TAHVO_MODE_HOST;
|
||||
#else
|
||||
tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
|
||||
#endif
|
||||
|
||||
mutex_init(&tu->serialize);
|
||||
|
||||
tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick");
|
||||
if (!IS_ERR(tu->ick))
|
||||
clk_enable(tu->ick);
|
||||
|
||||
/*
|
||||
* Set initial state, so that we generate kevents only on state changes.
|
||||
*/
|
||||
tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS;
|
||||
|
||||
tu->extcon.name = DRIVER_NAME;
|
||||
tu->extcon.supported_cable = tahvo_cable;
|
||||
tu->extcon.dev.parent = &pdev->dev;
|
||||
|
||||
ret = extcon_dev_register(&tu->extcon);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "could not register extcon device: %d\n",
|
||||
ret);
|
||||
goto err_disable_clk;
|
||||
}
|
||||
|
||||
/* Set the initial cable state. */
|
||||
extcon_set_cable_state(&tu->extcon, "USB-HOST",
|
||||
tu->tahvo_mode == TAHVO_MODE_HOST);
|
||||
extcon_set_cable_state(&tu->extcon, "USB", tu->vbus_state);
|
||||
|
||||
/* Create OTG interface */
|
||||
tahvo_usb_power_off(tu);
|
||||
tu->phy.dev = &pdev->dev;
|
||||
tu->phy.state = OTG_STATE_UNDEFINED;
|
||||
tu->phy.label = DRIVER_NAME;
|
||||
tu->phy.set_suspend = tahvo_usb_set_suspend;
|
||||
|
||||
tu->phy.otg->phy = &tu->phy;
|
||||
tu->phy.otg->set_host = tahvo_usb_set_host;
|
||||
tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral;
|
||||
|
||||
ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "cannot register USB transceiver: %d\n",
|
||||
ret);
|
||||
goto err_extcon_unreg;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&pdev->dev, tu);
|
||||
|
||||
tu->irq = platform_get_irq(pdev, 0);
|
||||
ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt, 0,
|
||||
"tahvo-vbus", tu);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n",
|
||||
ret);
|
||||
goto err_remove_phy;
|
||||
}
|
||||
|
||||
/* Attributes */
|
||||
ret = sysfs_create_group(&pdev->dev.kobj, &tahvo_attr_group);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "cannot create sysfs group: %d\n", ret);
|
||||
goto err_free_irq;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_irq:
|
||||
free_irq(tu->irq, tu);
|
||||
err_remove_phy:
|
||||
usb_remove_phy(&tu->phy);
|
||||
err_extcon_unreg:
|
||||
extcon_dev_unregister(&tu->extcon);
|
||||
err_disable_clk:
|
||||
if (!IS_ERR(tu->ick))
|
||||
clk_disable(tu->ick);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tahvo_usb_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tahvo_usb *tu = platform_get_drvdata(pdev);
|
||||
|
||||
sysfs_remove_group(&pdev->dev.kobj, &tahvo_attr_group);
|
||||
free_irq(tu->irq, tu);
|
||||
usb_remove_phy(&tu->phy);
|
||||
extcon_dev_unregister(&tu->extcon);
|
||||
if (!IS_ERR(tu->ick))
|
||||
clk_disable(tu->ick);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver tahvo_usb_driver = {
|
||||
.probe = tahvo_usb_probe,
|
||||
.remove = tahvo_usb_remove,
|
||||
.driver = {
|
||||
.name = "tahvo-usb",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
module_platform_driver(tahvo_usb_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Tahvo USB transceiver driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs");
|
||||
MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
|
||||
1096
drivers/usb/phy/phy-tegra-usb.c
Normal file
1096
drivers/usb/phy/phy-tegra-usb.c
Normal file
File diff suppressed because it is too large
Load diff
453
drivers/usb/phy/phy-twl6030-usb.c
Normal file
453
drivers/usb/phy/phy-twl6030-usb.c
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
/*
|
||||
* twl6030_usb - TWL6030 USB transceiver, talking to OMAP OTG driver.
|
||||
*
|
||||
* Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Author: Hema HK <hemahk@ti.com>
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/usb/musb-omap.h>
|
||||
#include <linux/usb/phy_companion.h>
|
||||
#include <linux/phy/omap_usb.h>
|
||||
#include <linux/i2c/twl.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
/* usb register definitions */
|
||||
#define USB_VENDOR_ID_LSB 0x00
|
||||
#define USB_VENDOR_ID_MSB 0x01
|
||||
#define USB_PRODUCT_ID_LSB 0x02
|
||||
#define USB_PRODUCT_ID_MSB 0x03
|
||||
#define USB_VBUS_CTRL_SET 0x04
|
||||
#define USB_VBUS_CTRL_CLR 0x05
|
||||
#define USB_ID_CTRL_SET 0x06
|
||||
#define USB_ID_CTRL_CLR 0x07
|
||||
#define USB_VBUS_INT_SRC 0x08
|
||||
#define USB_VBUS_INT_LATCH_SET 0x09
|
||||
#define USB_VBUS_INT_LATCH_CLR 0x0A
|
||||
#define USB_VBUS_INT_EN_LO_SET 0x0B
|
||||
#define USB_VBUS_INT_EN_LO_CLR 0x0C
|
||||
#define USB_VBUS_INT_EN_HI_SET 0x0D
|
||||
#define USB_VBUS_INT_EN_HI_CLR 0x0E
|
||||
#define USB_ID_INT_SRC 0x0F
|
||||
#define USB_ID_INT_LATCH_SET 0x10
|
||||
#define USB_ID_INT_LATCH_CLR 0x11
|
||||
|
||||
#define USB_ID_INT_EN_LO_SET 0x12
|
||||
#define USB_ID_INT_EN_LO_CLR 0x13
|
||||
#define USB_ID_INT_EN_HI_SET 0x14
|
||||
#define USB_ID_INT_EN_HI_CLR 0x15
|
||||
#define USB_OTG_ADP_CTRL 0x16
|
||||
#define USB_OTG_ADP_HIGH 0x17
|
||||
#define USB_OTG_ADP_LOW 0x18
|
||||
#define USB_OTG_ADP_RISE 0x19
|
||||
#define USB_OTG_REVISION 0x1A
|
||||
|
||||
/* to be moved to LDO */
|
||||
#define TWL6030_MISC2 0xE5
|
||||
#define TWL6030_CFG_LDO_PD2 0xF5
|
||||
#define TWL6030_BACKUP_REG 0xFA
|
||||
|
||||
#define STS_HW_CONDITIONS 0x21
|
||||
|
||||
/* In module TWL6030_MODULE_PM_MASTER */
|
||||
#define STS_HW_CONDITIONS 0x21
|
||||
#define STS_USB_ID BIT(2)
|
||||
|
||||
/* In module TWL6030_MODULE_PM_RECEIVER */
|
||||
#define VUSB_CFG_TRANS 0x71
|
||||
#define VUSB_CFG_STATE 0x72
|
||||
#define VUSB_CFG_VOLTAGE 0x73
|
||||
|
||||
/* in module TWL6030_MODULE_MAIN_CHARGE */
|
||||
|
||||
#define CHARGERUSB_CTRL1 0x8
|
||||
|
||||
#define CONTROLLER_STAT1 0x03
|
||||
#define VBUS_DET BIT(2)
|
||||
|
||||
struct twl6030_usb {
|
||||
struct phy_companion comparator;
|
||||
struct device *dev;
|
||||
|
||||
/* for vbus reporting with irqs disabled */
|
||||
spinlock_t lock;
|
||||
|
||||
struct regulator *usb3v3;
|
||||
|
||||
/* used to set vbus, in atomic path */
|
||||
struct work_struct set_vbus_work;
|
||||
|
||||
int irq1;
|
||||
int irq2;
|
||||
enum omap_musb_vbus_id_status linkstat;
|
||||
u8 asleep;
|
||||
bool vbus_enable;
|
||||
const char *regulator;
|
||||
};
|
||||
|
||||
#define comparator_to_twl(x) container_of((x), struct twl6030_usb, comparator)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static inline int twl6030_writeb(struct twl6030_usb *twl, u8 module,
|
||||
u8 data, u8 address)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = twl_i2c_write_u8(module, data, address);
|
||||
if (ret < 0)
|
||||
dev_err(twl->dev,
|
||||
"Write[0x%x] Error %d\n", address, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline u8 twl6030_readb(struct twl6030_usb *twl, u8 module, u8 address)
|
||||
{
|
||||
u8 data;
|
||||
int ret;
|
||||
|
||||
ret = twl_i2c_read_u8(module, &data, address);
|
||||
if (ret >= 0)
|
||||
ret = data;
|
||||
else
|
||||
dev_err(twl->dev,
|
||||
"readb[0x%x,0x%x] Error %d\n",
|
||||
module, address, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int twl6030_start_srp(struct phy_companion *comparator)
|
||||
{
|
||||
struct twl6030_usb *twl = comparator_to_twl(comparator);
|
||||
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0x24, USB_VBUS_CTRL_SET);
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0x84, USB_VBUS_CTRL_SET);
|
||||
|
||||
mdelay(100);
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0xa0, USB_VBUS_CTRL_CLR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl6030_usb_ldo_init(struct twl6030_usb *twl)
|
||||
{
|
||||
/* Set to OTG_REV 1.3 and turn on the ID_WAKEUP_COMP */
|
||||
twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_BACKUP_REG);
|
||||
|
||||
/* Program CFG_LDO_PD2 register and set VUSB bit */
|
||||
twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_CFG_LDO_PD2);
|
||||
|
||||
/* Program MISC2 register and set bit VUSB_IN_VBAT */
|
||||
twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x10, TWL6030_MISC2);
|
||||
|
||||
twl->usb3v3 = regulator_get(twl->dev, twl->regulator);
|
||||
if (IS_ERR(twl->usb3v3))
|
||||
return -ENODEV;
|
||||
|
||||
/* Program the USB_VBUS_CTRL_SET and set VBUS_ACT_COMP bit */
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0x4, USB_VBUS_CTRL_SET);
|
||||
|
||||
/*
|
||||
* Program the USB_ID_CTRL_SET register to enable GND drive
|
||||
* and the ID comparators
|
||||
*/
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0x14, USB_ID_CTRL_SET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t twl6030_usb_vbus_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct twl6030_usb *twl = dev_get_drvdata(dev);
|
||||
unsigned long flags;
|
||||
int ret = -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&twl->lock, flags);
|
||||
|
||||
switch (twl->linkstat) {
|
||||
case OMAP_MUSB_VBUS_VALID:
|
||||
ret = snprintf(buf, PAGE_SIZE, "vbus\n");
|
||||
break;
|
||||
case OMAP_MUSB_ID_GROUND:
|
||||
ret = snprintf(buf, PAGE_SIZE, "id\n");
|
||||
break;
|
||||
case OMAP_MUSB_VBUS_OFF:
|
||||
ret = snprintf(buf, PAGE_SIZE, "none\n");
|
||||
break;
|
||||
default:
|
||||
ret = snprintf(buf, PAGE_SIZE, "UNKNOWN\n");
|
||||
}
|
||||
spin_unlock_irqrestore(&twl->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR(vbus, 0444, twl6030_usb_vbus_show, NULL);
|
||||
|
||||
static irqreturn_t twl6030_usb_irq(int irq, void *_twl)
|
||||
{
|
||||
struct twl6030_usb *twl = _twl;
|
||||
enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN;
|
||||
u8 vbus_state, hw_state;
|
||||
int ret;
|
||||
|
||||
hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS);
|
||||
|
||||
vbus_state = twl6030_readb(twl, TWL_MODULE_MAIN_CHARGE,
|
||||
CONTROLLER_STAT1);
|
||||
if (!(hw_state & STS_USB_ID)) {
|
||||
if (vbus_state & VBUS_DET) {
|
||||
ret = regulator_enable(twl->usb3v3);
|
||||
if (ret)
|
||||
dev_err(twl->dev, "Failed to enable usb3v3\n");
|
||||
|
||||
twl->asleep = 1;
|
||||
status = OMAP_MUSB_VBUS_VALID;
|
||||
twl->linkstat = status;
|
||||
omap_musb_mailbox(status);
|
||||
} else {
|
||||
if (twl->linkstat != OMAP_MUSB_UNKNOWN) {
|
||||
status = OMAP_MUSB_VBUS_OFF;
|
||||
twl->linkstat = status;
|
||||
omap_musb_mailbox(status);
|
||||
if (twl->asleep) {
|
||||
regulator_disable(twl->usb3v3);
|
||||
twl->asleep = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sysfs_notify(&twl->dev->kobj, NULL, "vbus");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl)
|
||||
{
|
||||
struct twl6030_usb *twl = _twl;
|
||||
enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN;
|
||||
u8 hw_state;
|
||||
int ret;
|
||||
|
||||
hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS);
|
||||
|
||||
if (hw_state & STS_USB_ID) {
|
||||
ret = regulator_enable(twl->usb3v3);
|
||||
if (ret)
|
||||
dev_err(twl->dev, "Failed to enable usb3v3\n");
|
||||
|
||||
twl->asleep = 1;
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_CLR);
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_SET);
|
||||
status = OMAP_MUSB_ID_GROUND;
|
||||
twl->linkstat = status;
|
||||
omap_musb_mailbox(status);
|
||||
} else {
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_CLR);
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET);
|
||||
}
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, status, USB_ID_INT_LATCH_CLR);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int twl6030_enable_irq(struct twl6030_usb *twl)
|
||||
{
|
||||
twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET);
|
||||
twl6030_interrupt_unmask(0x05, REG_INT_MSK_LINE_C);
|
||||
twl6030_interrupt_unmask(0x05, REG_INT_MSK_STS_C);
|
||||
|
||||
twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK,
|
||||
REG_INT_MSK_LINE_C);
|
||||
twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK,
|
||||
REG_INT_MSK_STS_C);
|
||||
twl6030_usb_irq(twl->irq2, twl);
|
||||
twl6030_usbotg_irq(twl->irq1, twl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void otg_set_vbus_work(struct work_struct *data)
|
||||
{
|
||||
struct twl6030_usb *twl = container_of(data, struct twl6030_usb,
|
||||
set_vbus_work);
|
||||
|
||||
/*
|
||||
* Start driving VBUS. Set OPA_MODE bit in CHARGERUSB_CTRL1
|
||||
* register. This enables boost mode.
|
||||
*/
|
||||
|
||||
if (twl->vbus_enable)
|
||||
twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x40,
|
||||
CHARGERUSB_CTRL1);
|
||||
else
|
||||
twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x00,
|
||||
CHARGERUSB_CTRL1);
|
||||
}
|
||||
|
||||
static int twl6030_set_vbus(struct phy_companion *comparator, bool enabled)
|
||||
{
|
||||
struct twl6030_usb *twl = comparator_to_twl(comparator);
|
||||
|
||||
twl->vbus_enable = enabled;
|
||||
schedule_work(&twl->set_vbus_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl6030_usb_probe(struct platform_device *pdev)
|
||||
{
|
||||
u32 ret;
|
||||
struct twl6030_usb *twl;
|
||||
int status, err;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct twl4030_usb_data *pdata = dev_get_platdata(dev);
|
||||
|
||||
twl = devm_kzalloc(dev, sizeof(*twl), GFP_KERNEL);
|
||||
if (!twl)
|
||||
return -ENOMEM;
|
||||
|
||||
twl->dev = &pdev->dev;
|
||||
twl->irq1 = platform_get_irq(pdev, 0);
|
||||
twl->irq2 = platform_get_irq(pdev, 1);
|
||||
twl->linkstat = OMAP_MUSB_UNKNOWN;
|
||||
|
||||
twl->comparator.set_vbus = twl6030_set_vbus;
|
||||
twl->comparator.start_srp = twl6030_start_srp;
|
||||
|
||||
ret = omap_usb2_set_comparator(&twl->comparator);
|
||||
if (ret == -ENODEV) {
|
||||
dev_info(&pdev->dev, "phy not ready, deferring probe");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
if (np) {
|
||||
twl->regulator = "usb";
|
||||
} else if (pdata) {
|
||||
if (pdata->features & TWL6032_SUBCLASS)
|
||||
twl->regulator = "ldousb";
|
||||
else
|
||||
twl->regulator = "vusb";
|
||||
} else {
|
||||
dev_err(&pdev->dev, "twl6030 initialized without pdata\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* init spinlock for workqueue */
|
||||
spin_lock_init(&twl->lock);
|
||||
|
||||
err = twl6030_usb_ldo_init(twl);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "ldo init failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, twl);
|
||||
if (device_create_file(&pdev->dev, &dev_attr_vbus))
|
||||
dev_warn(&pdev->dev, "could not create sysfs file\n");
|
||||
|
||||
INIT_WORK(&twl->set_vbus_work, otg_set_vbus_work);
|
||||
|
||||
status = request_threaded_irq(twl->irq1, NULL, twl6030_usbotg_irq,
|
||||
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
"twl6030_usb", twl);
|
||||
if (status < 0) {
|
||||
dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
|
||||
twl->irq1, status);
|
||||
device_remove_file(twl->dev, &dev_attr_vbus);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = request_threaded_irq(twl->irq2, NULL, twl6030_usb_irq,
|
||||
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
"twl6030_usb", twl);
|
||||
if (status < 0) {
|
||||
dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
|
||||
twl->irq2, status);
|
||||
free_irq(twl->irq1, twl);
|
||||
device_remove_file(twl->dev, &dev_attr_vbus);
|
||||
return status;
|
||||
}
|
||||
|
||||
twl->asleep = 0;
|
||||
twl6030_enable_irq(twl);
|
||||
dev_info(&pdev->dev, "Initialized TWL6030 USB module\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl6030_usb_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct twl6030_usb *twl = platform_get_drvdata(pdev);
|
||||
|
||||
twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK,
|
||||
REG_INT_MSK_LINE_C);
|
||||
twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK,
|
||||
REG_INT_MSK_STS_C);
|
||||
free_irq(twl->irq1, twl);
|
||||
free_irq(twl->irq2, twl);
|
||||
regulator_put(twl->usb3v3);
|
||||
device_remove_file(twl->dev, &dev_attr_vbus);
|
||||
cancel_work_sync(&twl->set_vbus_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id twl6030_usb_id_table[] = {
|
||||
{ .compatible = "ti,twl6030-usb" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, twl6030_usb_id_table);
|
||||
#endif
|
||||
|
||||
static struct platform_driver twl6030_usb_driver = {
|
||||
.probe = twl6030_usb_probe,
|
||||
.remove = twl6030_usb_remove,
|
||||
.driver = {
|
||||
.name = "twl6030_usb",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(twl6030_usb_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init twl6030_usb_init(void)
|
||||
{
|
||||
return platform_driver_register(&twl6030_usb_driver);
|
||||
}
|
||||
subsys_initcall(twl6030_usb_init);
|
||||
|
||||
static void __exit twl6030_usb_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&twl6030_usb_driver);
|
||||
}
|
||||
module_exit(twl6030_usb_exit);
|
||||
|
||||
MODULE_ALIAS("platform:twl6030_usb");
|
||||
MODULE_AUTHOR("Hema HK <hemahk@ti.com>");
|
||||
MODULE_DESCRIPTION("TWL6030 USB transceiver driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
82
drivers/usb/phy/phy-ulpi-viewport.c
Normal file
82
drivers/usb/phy/phy-ulpi-viewport.c
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/ulpi.h>
|
||||
|
||||
#define ULPI_VIEW_WAKEUP (1 << 31)
|
||||
#define ULPI_VIEW_RUN (1 << 30)
|
||||
#define ULPI_VIEW_WRITE (1 << 29)
|
||||
#define ULPI_VIEW_READ (0 << 29)
|
||||
#define ULPI_VIEW_ADDR(x) (((x) & 0xff) << 16)
|
||||
#define ULPI_VIEW_DATA_READ(x) (((x) >> 8) & 0xff)
|
||||
#define ULPI_VIEW_DATA_WRITE(x) ((x) & 0xff)
|
||||
|
||||
static int ulpi_viewport_wait(void __iomem *view, u32 mask)
|
||||
{
|
||||
unsigned long usec = 2000;
|
||||
|
||||
while (usec--) {
|
||||
if (!(readl(view) & mask))
|
||||
return 0;
|
||||
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int ulpi_viewport_read(struct usb_phy *otg, u32 reg)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *view = otg->io_priv;
|
||||
|
||||
writel(ULPI_VIEW_WAKEUP | ULPI_VIEW_WRITE, view);
|
||||
ret = ulpi_viewport_wait(view, ULPI_VIEW_WAKEUP);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
writel(ULPI_VIEW_RUN | ULPI_VIEW_READ | ULPI_VIEW_ADDR(reg), view);
|
||||
ret = ulpi_viewport_wait(view, ULPI_VIEW_RUN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ULPI_VIEW_DATA_READ(readl(view));
|
||||
}
|
||||
|
||||
static int ulpi_viewport_write(struct usb_phy *otg, u32 val, u32 reg)
|
||||
{
|
||||
int ret;
|
||||
void __iomem *view = otg->io_priv;
|
||||
|
||||
writel(ULPI_VIEW_WAKEUP | ULPI_VIEW_WRITE, view);
|
||||
ret = ulpi_viewport_wait(view, ULPI_VIEW_WAKEUP);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
writel(ULPI_VIEW_RUN | ULPI_VIEW_WRITE | ULPI_VIEW_DATA_WRITE(val) |
|
||||
ULPI_VIEW_ADDR(reg), view);
|
||||
|
||||
return ulpi_viewport_wait(view, ULPI_VIEW_RUN);
|
||||
}
|
||||
|
||||
struct usb_phy_io_ops ulpi_viewport_access_ops = {
|
||||
.read = ulpi_viewport_read,
|
||||
.write = ulpi_viewport_write,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(ulpi_viewport_access_ops);
|
||||
286
drivers/usb/phy/phy-ulpi.c
Normal file
286
drivers/usb/phy/phy-ulpi.c
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Generic ULPI USB transceiver support
|
||||
*
|
||||
* Copyright (C) 2009 Daniel Mack <daniel@caiaq.de>
|
||||
*
|
||||
* Based on sources from
|
||||
*
|
||||
* Sascha Hauer <s.hauer@pengutronix.de>
|
||||
* Freescale Semiconductors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/ulpi.h>
|
||||
|
||||
|
||||
struct ulpi_info {
|
||||
unsigned int id;
|
||||
char *name;
|
||||
};
|
||||
|
||||
#define ULPI_ID(vendor, product) (((vendor) << 16) | (product))
|
||||
#define ULPI_INFO(_id, _name) \
|
||||
{ \
|
||||
.id = (_id), \
|
||||
.name = (_name), \
|
||||
}
|
||||
|
||||
/* ULPI hardcoded IDs, used for probing */
|
||||
static struct ulpi_info ulpi_ids[] = {
|
||||
ULPI_INFO(ULPI_ID(0x04cc, 0x1504), "NXP ISP1504"),
|
||||
ULPI_INFO(ULPI_ID(0x0424, 0x0006), "SMSC USB331x"),
|
||||
ULPI_INFO(ULPI_ID(0x0424, 0x0007), "SMSC USB3320"),
|
||||
ULPI_INFO(ULPI_ID(0x0424, 0x0009), "SMSC USB334x"),
|
||||
ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"),
|
||||
};
|
||||
|
||||
static int ulpi_set_otg_flags(struct usb_phy *phy)
|
||||
{
|
||||
unsigned int flags = ULPI_OTG_CTRL_DP_PULLDOWN |
|
||||
ULPI_OTG_CTRL_DM_PULLDOWN;
|
||||
|
||||
if (phy->flags & ULPI_OTG_ID_PULLUP)
|
||||
flags |= ULPI_OTG_CTRL_ID_PULLUP;
|
||||
|
||||
/*
|
||||
* ULPI Specification rev.1.1 default
|
||||
* for Dp/DmPulldown is enabled.
|
||||
*/
|
||||
if (phy->flags & ULPI_OTG_DP_PULLDOWN_DIS)
|
||||
flags &= ~ULPI_OTG_CTRL_DP_PULLDOWN;
|
||||
|
||||
if (phy->flags & ULPI_OTG_DM_PULLDOWN_DIS)
|
||||
flags &= ~ULPI_OTG_CTRL_DM_PULLDOWN;
|
||||
|
||||
if (phy->flags & ULPI_OTG_EXTVBUSIND)
|
||||
flags |= ULPI_OTG_CTRL_EXTVBUSIND;
|
||||
|
||||
return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
|
||||
}
|
||||
|
||||
static int ulpi_set_fc_flags(struct usb_phy *phy)
|
||||
{
|
||||
unsigned int flags = 0;
|
||||
|
||||
/*
|
||||
* ULPI Specification rev.1.1 default
|
||||
* for XcvrSelect is Full Speed.
|
||||
*/
|
||||
if (phy->flags & ULPI_FC_HS)
|
||||
flags |= ULPI_FUNC_CTRL_HIGH_SPEED;
|
||||
else if (phy->flags & ULPI_FC_LS)
|
||||
flags |= ULPI_FUNC_CTRL_LOW_SPEED;
|
||||
else if (phy->flags & ULPI_FC_FS4LS)
|
||||
flags |= ULPI_FUNC_CTRL_FS4LS;
|
||||
else
|
||||
flags |= ULPI_FUNC_CTRL_FULL_SPEED;
|
||||
|
||||
if (phy->flags & ULPI_FC_TERMSEL)
|
||||
flags |= ULPI_FUNC_CTRL_TERMSELECT;
|
||||
|
||||
/*
|
||||
* ULPI Specification rev.1.1 default
|
||||
* for OpMode is Normal Operation.
|
||||
*/
|
||||
if (phy->flags & ULPI_FC_OP_NODRV)
|
||||
flags |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING;
|
||||
else if (phy->flags & ULPI_FC_OP_DIS_NRZI)
|
||||
flags |= ULPI_FUNC_CTRL_OPMODE_DISABLE_NRZI;
|
||||
else if (phy->flags & ULPI_FC_OP_NSYNC_NEOP)
|
||||
flags |= ULPI_FUNC_CTRL_OPMODE_NOSYNC_NOEOP;
|
||||
else
|
||||
flags |= ULPI_FUNC_CTRL_OPMODE_NORMAL;
|
||||
|
||||
/*
|
||||
* ULPI Specification rev.1.1 default
|
||||
* for SuspendM is Powered.
|
||||
*/
|
||||
flags |= ULPI_FUNC_CTRL_SUSPENDM;
|
||||
|
||||
return usb_phy_io_write(phy, flags, ULPI_FUNC_CTRL);
|
||||
}
|
||||
|
||||
static int ulpi_set_ic_flags(struct usb_phy *phy)
|
||||
{
|
||||
unsigned int flags = 0;
|
||||
|
||||
if (phy->flags & ULPI_IC_AUTORESUME)
|
||||
flags |= ULPI_IFC_CTRL_AUTORESUME;
|
||||
|
||||
if (phy->flags & ULPI_IC_EXTVBUS_INDINV)
|
||||
flags |= ULPI_IFC_CTRL_EXTERNAL_VBUS;
|
||||
|
||||
if (phy->flags & ULPI_IC_IND_PASSTHRU)
|
||||
flags |= ULPI_IFC_CTRL_PASSTHRU;
|
||||
|
||||
if (phy->flags & ULPI_IC_PROTECT_DIS)
|
||||
flags |= ULPI_IFC_CTRL_PROTECT_IFC_DISABLE;
|
||||
|
||||
return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL);
|
||||
}
|
||||
|
||||
static int ulpi_set_flags(struct usb_phy *phy)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ulpi_set_otg_flags(phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ulpi_set_ic_flags(phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ulpi_set_fc_flags(phy);
|
||||
}
|
||||
|
||||
static int ulpi_check_integrity(struct usb_phy *phy)
|
||||
{
|
||||
int ret, i;
|
||||
unsigned int val = 0x55;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
ret = usb_phy_io_write(phy, val, ULPI_SCRATCH);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = usb_phy_io_read(phy, ULPI_SCRATCH);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret != val) {
|
||||
pr_err("ULPI integrity check: failed!");
|
||||
return -ENODEV;
|
||||
}
|
||||
val = val << 1;
|
||||
}
|
||||
|
||||
pr_info("ULPI integrity check: passed.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ulpi_init(struct usb_phy *phy)
|
||||
{
|
||||
int i, vid, pid, ret;
|
||||
u32 ulpi_id = 0;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
ret = usb_phy_io_read(phy, ULPI_PRODUCT_ID_HIGH - i);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ulpi_id = (ulpi_id << 8) | ret;
|
||||
}
|
||||
vid = ulpi_id & 0xffff;
|
||||
pid = ulpi_id >> 16;
|
||||
|
||||
pr_info("ULPI transceiver vendor/product ID 0x%04x/0x%04x\n", vid, pid);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ulpi_ids); i++) {
|
||||
if (ulpi_ids[i].id == ULPI_ID(vid, pid)) {
|
||||
pr_info("Found %s ULPI transceiver.\n",
|
||||
ulpi_ids[i].name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ulpi_check_integrity(phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ulpi_set_flags(phy);
|
||||
}
|
||||
|
||||
static int ulpi_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
struct usb_phy *phy = otg->phy;
|
||||
unsigned int flags = usb_phy_io_read(phy, ULPI_IFC_CTRL);
|
||||
|
||||
if (!host) {
|
||||
otg->host = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
otg->host = host;
|
||||
|
||||
flags &= ~(ULPI_IFC_CTRL_6_PIN_SERIAL_MODE |
|
||||
ULPI_IFC_CTRL_3_PIN_SERIAL_MODE |
|
||||
ULPI_IFC_CTRL_CARKITMODE);
|
||||
|
||||
if (phy->flags & ULPI_IC_6PIN_SERIAL)
|
||||
flags |= ULPI_IFC_CTRL_6_PIN_SERIAL_MODE;
|
||||
else if (phy->flags & ULPI_IC_3PIN_SERIAL)
|
||||
flags |= ULPI_IFC_CTRL_3_PIN_SERIAL_MODE;
|
||||
else if (phy->flags & ULPI_IC_CARKIT)
|
||||
flags |= ULPI_IFC_CTRL_CARKITMODE;
|
||||
|
||||
return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL);
|
||||
}
|
||||
|
||||
static int ulpi_set_vbus(struct usb_otg *otg, bool on)
|
||||
{
|
||||
struct usb_phy *phy = otg->phy;
|
||||
unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL);
|
||||
|
||||
flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT);
|
||||
|
||||
if (on) {
|
||||
if (phy->flags & ULPI_OTG_DRVVBUS)
|
||||
flags |= ULPI_OTG_CTRL_DRVVBUS;
|
||||
|
||||
if (phy->flags & ULPI_OTG_DRVVBUS_EXT)
|
||||
flags |= ULPI_OTG_CTRL_DRVVBUS_EXT;
|
||||
}
|
||||
|
||||
return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
|
||||
}
|
||||
|
||||
struct usb_phy *
|
||||
otg_ulpi_create(struct usb_phy_io_ops *ops,
|
||||
unsigned int flags)
|
||||
{
|
||||
struct usb_phy *phy;
|
||||
struct usb_otg *otg;
|
||||
|
||||
phy = kzalloc(sizeof(*phy), GFP_KERNEL);
|
||||
if (!phy)
|
||||
return NULL;
|
||||
|
||||
otg = kzalloc(sizeof(*otg), GFP_KERNEL);
|
||||
if (!otg) {
|
||||
kfree(phy);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
phy->label = "ULPI";
|
||||
phy->flags = flags;
|
||||
phy->io_ops = ops;
|
||||
phy->otg = otg;
|
||||
phy->init = ulpi_init;
|
||||
|
||||
otg->phy = phy;
|
||||
otg->set_host = ulpi_set_host;
|
||||
otg->set_vbus = ulpi_set_vbus;
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(otg_ulpi_create);
|
||||
|
||||
448
drivers/usb/phy/phy.c
Normal file
448
drivers/usb/phy/phy.c
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
/*
|
||||
* phy.c -- USB phy handling
|
||||
*
|
||||
* Copyright (C) 2004-2013 Texas Instruments
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <linux/usb/phy.h>
|
||||
|
||||
static LIST_HEAD(phy_list);
|
||||
static LIST_HEAD(phy_bind_list);
|
||||
static DEFINE_SPINLOCK(phy_lock);
|
||||
|
||||
static struct usb_phy *__usb_find_phy(struct list_head *list,
|
||||
enum usb_phy_type type)
|
||||
{
|
||||
struct usb_phy *phy = NULL;
|
||||
|
||||
list_for_each_entry(phy, list, head) {
|
||||
if (phy->type != type)
|
||||
continue;
|
||||
|
||||
return phy;
|
||||
}
|
||||
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static struct usb_phy *__usb_find_phy_dev(struct device *dev,
|
||||
struct list_head *list, u8 index)
|
||||
{
|
||||
struct usb_phy_bind *phy_bind = NULL;
|
||||
|
||||
list_for_each_entry(phy_bind, list, list) {
|
||||
if (!(strcmp(phy_bind->dev_name, dev_name(dev))) &&
|
||||
phy_bind->index == index) {
|
||||
if (phy_bind->phy)
|
||||
return phy_bind->phy;
|
||||
else
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
}
|
||||
}
|
||||
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static struct usb_phy *__of_usb_find_phy(struct device_node *node)
|
||||
{
|
||||
struct usb_phy *phy;
|
||||
|
||||
list_for_each_entry(phy, &phy_list, head) {
|
||||
if (node != phy->dev->of_node)
|
||||
continue;
|
||||
|
||||
return phy;
|
||||
}
|
||||
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static void devm_usb_phy_release(struct device *dev, void *res)
|
||||
{
|
||||
struct usb_phy *phy = *(struct usb_phy **)res;
|
||||
|
||||
usb_put_phy(phy);
|
||||
}
|
||||
|
||||
static int devm_usb_phy_match(struct device *dev, void *res, void *match_data)
|
||||
{
|
||||
struct usb_phy **phy = res;
|
||||
|
||||
return *phy == match_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_usb_get_phy - find the USB PHY
|
||||
* @dev - device that requests this phy
|
||||
* @type - the type of the phy the controller requires
|
||||
*
|
||||
* Gets the phy using usb_get_phy(), and associates a device with it using
|
||||
* devres. On driver detach, release function is invoked on the devres data,
|
||||
* then, devres data is freed.
|
||||
*
|
||||
* For use by USB host and peripheral drivers.
|
||||
*/
|
||||
struct usb_phy *devm_usb_get_phy(struct device *dev, enum usb_phy_type type)
|
||||
{
|
||||
struct usb_phy **ptr, *phy;
|
||||
|
||||
ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
phy = usb_get_phy(type);
|
||||
if (!IS_ERR(phy)) {
|
||||
*ptr = phy;
|
||||
devres_add(dev, ptr);
|
||||
} else
|
||||
devres_free(ptr);
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_usb_get_phy);
|
||||
|
||||
/**
|
||||
* usb_get_phy - find the USB PHY
|
||||
* @type - the type of the phy the controller requires
|
||||
*
|
||||
* Returns the phy driver, after getting a refcount to it; or
|
||||
* -ENODEV if there is no such phy. The caller is responsible for
|
||||
* calling usb_put_phy() to release that count.
|
||||
*
|
||||
* For use by USB host and peripheral drivers.
|
||||
*/
|
||||
struct usb_phy *usb_get_phy(enum usb_phy_type type)
|
||||
{
|
||||
struct usb_phy *phy = NULL;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&phy_lock, flags);
|
||||
|
||||
phy = __usb_find_phy(&phy_list, type);
|
||||
if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) {
|
||||
pr_debug("PHY: unable to find transceiver of type %s\n",
|
||||
usb_phy_type_string(type));
|
||||
if (!IS_ERR(phy))
|
||||
phy = ERR_PTR(-ENODEV);
|
||||
|
||||
goto err0;
|
||||
}
|
||||
|
||||
get_device(phy->dev);
|
||||
|
||||
err0:
|
||||
spin_unlock_irqrestore(&phy_lock, flags);
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_get_phy);
|
||||
|
||||
/**
|
||||
* devm_usb_get_phy_by_phandle - find the USB PHY by phandle
|
||||
* @dev - device that requests this phy
|
||||
* @phandle - name of the property holding the phy phandle value
|
||||
* @index - the index of the phy
|
||||
*
|
||||
* Returns the phy driver associated with the given phandle value,
|
||||
* after getting a refcount to it, -ENODEV if there is no such phy or
|
||||
* -EPROBE_DEFER if there is a phandle to the phy, but the device is
|
||||
* not yet loaded. While at that, it also associates the device with
|
||||
* the phy using devres. On driver detach, release function is invoked
|
||||
* on the devres data, then, devres data is freed.
|
||||
*
|
||||
* For use by USB host and peripheral drivers.
|
||||
*/
|
||||
struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev,
|
||||
const char *phandle, u8 index)
|
||||
{
|
||||
struct usb_phy *phy = ERR_PTR(-ENOMEM), **ptr;
|
||||
unsigned long flags;
|
||||
struct device_node *node;
|
||||
|
||||
if (!dev->of_node) {
|
||||
dev_dbg(dev, "device does not have a device node entry\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
node = of_parse_phandle(dev->of_node, phandle, index);
|
||||
if (!node) {
|
||||
dev_dbg(dev, "failed to get %s phandle in %s node\n", phandle,
|
||||
dev->of_node->full_name);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr) {
|
||||
dev_dbg(dev, "failed to allocate memory for devres\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&phy_lock, flags);
|
||||
|
||||
phy = __of_usb_find_phy(node);
|
||||
if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) {
|
||||
phy = ERR_PTR(-EPROBE_DEFER);
|
||||
devres_free(ptr);
|
||||
goto err1;
|
||||
}
|
||||
|
||||
*ptr = phy;
|
||||
devres_add(dev, ptr);
|
||||
|
||||
get_device(phy->dev);
|
||||
|
||||
err1:
|
||||
spin_unlock_irqrestore(&phy_lock, flags);
|
||||
|
||||
err0:
|
||||
of_node_put(node);
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_phandle);
|
||||
|
||||
/**
|
||||
* usb_get_phy_dev - find the USB PHY
|
||||
* @dev - device that requests this phy
|
||||
* @index - the index of the phy
|
||||
*
|
||||
* Returns the phy driver, after getting a refcount to it; or
|
||||
* -ENODEV if there is no such phy. The caller is responsible for
|
||||
* calling usb_put_phy() to release that count.
|
||||
*
|
||||
* For use by USB host and peripheral drivers.
|
||||
*/
|
||||
struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index)
|
||||
{
|
||||
struct usb_phy *phy = NULL;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&phy_lock, flags);
|
||||
|
||||
phy = __usb_find_phy_dev(dev, &phy_bind_list, index);
|
||||
if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) {
|
||||
dev_dbg(dev, "unable to find transceiver\n");
|
||||
if (!IS_ERR(phy))
|
||||
phy = ERR_PTR(-ENODEV);
|
||||
|
||||
goto err0;
|
||||
}
|
||||
|
||||
get_device(phy->dev);
|
||||
|
||||
err0:
|
||||
spin_unlock_irqrestore(&phy_lock, flags);
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_get_phy_dev);
|
||||
|
||||
/**
|
||||
* devm_usb_get_phy_dev - find the USB PHY using device ptr and index
|
||||
* @dev - device that requests this phy
|
||||
* @index - the index of the phy
|
||||
*
|
||||
* Gets the phy using usb_get_phy_dev(), and associates a device with it using
|
||||
* devres. On driver detach, release function is invoked on the devres data,
|
||||
* then, devres data is freed.
|
||||
*
|
||||
* For use by USB host and peripheral drivers.
|
||||
*/
|
||||
struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index)
|
||||
{
|
||||
struct usb_phy **ptr, *phy;
|
||||
|
||||
ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
phy = usb_get_phy_dev(dev, index);
|
||||
if (!IS_ERR(phy)) {
|
||||
*ptr = phy;
|
||||
devres_add(dev, ptr);
|
||||
} else
|
||||
devres_free(ptr);
|
||||
|
||||
return phy;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_usb_get_phy_dev);
|
||||
|
||||
/**
|
||||
* devm_usb_put_phy - release the USB PHY
|
||||
* @dev - device that wants to release this phy
|
||||
* @phy - the phy returned by devm_usb_get_phy()
|
||||
*
|
||||
* destroys the devres associated with this phy and invokes usb_put_phy
|
||||
* to release the phy.
|
||||
*
|
||||
* For use by USB host and peripheral drivers.
|
||||
*/
|
||||
void devm_usb_put_phy(struct device *dev, struct usb_phy *phy)
|
||||
{
|
||||
int r;
|
||||
|
||||
r = devres_destroy(dev, devm_usb_phy_release, devm_usb_phy_match, phy);
|
||||
dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n");
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_usb_put_phy);
|
||||
|
||||
/**
|
||||
* usb_put_phy - release the USB PHY
|
||||
* @x: the phy returned by usb_get_phy()
|
||||
*
|
||||
* Releases a refcount the caller received from usb_get_phy().
|
||||
*
|
||||
* For use by USB host and peripheral drivers.
|
||||
*/
|
||||
void usb_put_phy(struct usb_phy *x)
|
||||
{
|
||||
if (x) {
|
||||
struct module *owner = x->dev->driver->owner;
|
||||
|
||||
put_device(x->dev);
|
||||
module_put(owner);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_put_phy);
|
||||
|
||||
/**
|
||||
* usb_add_phy - declare the USB PHY
|
||||
* @x: the USB phy to be used; or NULL
|
||||
* @type - the type of this PHY
|
||||
*
|
||||
* This call is exclusively for use by phy drivers, which
|
||||
* coordinate the activities of drivers for host and peripheral
|
||||
* controllers, and in some cases for VBUS current regulation.
|
||||
*/
|
||||
int usb_add_phy(struct usb_phy *x, enum usb_phy_type type)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags;
|
||||
struct usb_phy *phy;
|
||||
|
||||
if (x->type != USB_PHY_TYPE_UNDEFINED) {
|
||||
dev_err(x->dev, "not accepting initialized PHY %s\n", x->label);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier);
|
||||
|
||||
spin_lock_irqsave(&phy_lock, flags);
|
||||
|
||||
list_for_each_entry(phy, &phy_list, head) {
|
||||
if (phy->type == type) {
|
||||
ret = -EBUSY;
|
||||
dev_err(x->dev, "transceiver type %s already exists\n",
|
||||
usb_phy_type_string(type));
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
x->type = type;
|
||||
list_add_tail(&x->head, &phy_list);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&phy_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_add_phy);
|
||||
|
||||
/**
|
||||
* usb_add_phy_dev - declare the USB PHY
|
||||
* @x: the USB phy to be used; or NULL
|
||||
*
|
||||
* This call is exclusively for use by phy drivers, which
|
||||
* coordinate the activities of drivers for host and peripheral
|
||||
* controllers, and in some cases for VBUS current regulation.
|
||||
*/
|
||||
int usb_add_phy_dev(struct usb_phy *x)
|
||||
{
|
||||
struct usb_phy_bind *phy_bind;
|
||||
unsigned long flags;
|
||||
|
||||
if (!x->dev) {
|
||||
dev_err(x->dev, "no device provided for PHY\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier);
|
||||
|
||||
spin_lock_irqsave(&phy_lock, flags);
|
||||
list_for_each_entry(phy_bind, &phy_bind_list, list)
|
||||
if (!(strcmp(phy_bind->phy_dev_name, dev_name(x->dev))))
|
||||
phy_bind->phy = x;
|
||||
|
||||
list_add_tail(&x->head, &phy_list);
|
||||
|
||||
spin_unlock_irqrestore(&phy_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_add_phy_dev);
|
||||
|
||||
/**
|
||||
* usb_remove_phy - remove the OTG PHY
|
||||
* @x: the USB OTG PHY to be removed;
|
||||
*
|
||||
* This reverts the effects of usb_add_phy
|
||||
*/
|
||||
void usb_remove_phy(struct usb_phy *x)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct usb_phy_bind *phy_bind;
|
||||
|
||||
spin_lock_irqsave(&phy_lock, flags);
|
||||
if (x) {
|
||||
list_for_each_entry(phy_bind, &phy_bind_list, list)
|
||||
if (phy_bind->phy == x)
|
||||
phy_bind->phy = NULL;
|
||||
list_del(&x->head);
|
||||
}
|
||||
spin_unlock_irqrestore(&phy_lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_remove_phy);
|
||||
|
||||
/**
|
||||
* usb_bind_phy - bind the phy and the controller that uses the phy
|
||||
* @dev_name: the device name of the device that will bind to the phy
|
||||
* @index: index to specify the port number
|
||||
* @phy_dev_name: the device name of the phy
|
||||
*
|
||||
* Fills the phy_bind structure with the dev_name and phy_dev_name. This will
|
||||
* be used when the phy driver registers the phy and when the controller
|
||||
* requests this phy.
|
||||
*
|
||||
* To be used by platform specific initialization code.
|
||||
*/
|
||||
int usb_bind_phy(const char *dev_name, u8 index,
|
||||
const char *phy_dev_name)
|
||||
{
|
||||
struct usb_phy_bind *phy_bind;
|
||||
unsigned long flags;
|
||||
|
||||
phy_bind = kzalloc(sizeof(*phy_bind), GFP_KERNEL);
|
||||
if (!phy_bind)
|
||||
return -ENOMEM;
|
||||
|
||||
phy_bind->dev_name = dev_name;
|
||||
phy_bind->phy_dev_name = phy_dev_name;
|
||||
phy_bind->index = index;
|
||||
|
||||
spin_lock_irqsave(&phy_lock, flags);
|
||||
list_add_tail(&phy_bind->list, &phy_bind_list);
|
||||
spin_unlock_irqrestore(&phy_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(usb_bind_phy);
|
||||
Loading…
Add table
Add a link
Reference in a new issue