Merge tag 'usb-for-v3.10' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next
Felipe writes: usb: patches for v3.10 merge window Here is the big Gadget & PHY pull request. Many of us have been really busy lately getting multiple drivers to a better position. Since this pull request is so large, I will divide it in sections so it's easier to grasp what's included. - cleanups: . UDC drivers no longer touch gadget->dev, that's now udc-core responsibility . Many more UDC drivers converted to usb_gadget_map/unmap_request() . UDC drivers no longer initialize DMA-related fields from gadget's device structure . UDC drivers don't touch gadget.dev.driver directly . UDC drivers don't assign gadget.dev.release directly . Removal of some unused DMA_ADDR_INVALID . Introduction of CONFIG_USB_PHY . All phy drivers have been moved to drivers/usb/phy and renamed to a common naming scheme . Fix PHY layer so it never returns a NULL pointer, also fix all callers to avoid using IS_ERR_OR_NULL() . Sparse fixes all over the place . drivers/usb/otg/ has been deleted . Marvel drivers (mv_udc, ehci-mv, mv_otg and mv_u3d) improved clock usage - new features: . UDC core now provides a generic way for tracking and reporting UDC's state (not attached, resuming, suspended, addressed, default, etc) . twl4030-usb learned that it shouldn't be enabled during init . Full DT support for DWC3 has been implemented . ab8500-usb learned about pinctrl framework . nop PHY learned about DeviceTree and regulators . DWC3 learned about suspend/resume . DWC3 can now be compiled in host-only and gadget-only (as well as DRD) configurations . UVC now enables streaming endpoint based on negotiated speed . isp1301 now implements the PHY API properly . configfs-based interface for gadget drivers which will lead to the removal of all code which just combines functions together to build functional gadget drivers. . f_serial and f_obex were converted to new configfs interface while maintaining old interface around. - non-critical fixes: . UVC gadget driver got fixes for Endpoint usage and stream calculation . ab8500-usb fixed unbalanced clock and regulator API usage . twl4030-usb got a fix for when OMAP3 is booted with cable connected . fusb300_udc got a fix for DMA usage . UVC got fixes for two assertions of the USB Video Class Compliance specification revision 1.1 . build warning issues caused by recent addition of __must_check to regulator API These are all changes which deserve a mention, all other changes are related to these one or minor spelling fixes and other similar tasks. Signed-of-by: Felipe Balbi <balbi@ti.com>
This commit is contained in:
@@ -1,29 +1,61 @@
|
||||
#
|
||||
# Physical Layer USB driver configuration
|
||||
#
|
||||
comment "USB Physical Layer drivers"
|
||||
depends on USB || USB_GADGET
|
||||
|
||||
config OMAP_USB2
|
||||
tristate "OMAP USB2 PHY Driver"
|
||||
depends on ARCH_OMAP2PLUS
|
||||
select USB_OTG_UTILS
|
||||
select OMAP_CONTROL_USB
|
||||
menuconfig USB_PHY
|
||||
tristate "USB Physical Layer drivers"
|
||||
help
|
||||
Enable this to support the transceiver that is part of SOC. This
|
||||
driver takes care of all the PHY functionality apart from comparator.
|
||||
The USB OTG controller communicates with the comparator using this
|
||||
driver.
|
||||
USB controllers (those which are host, device or DRD) need a
|
||||
device to handle the physical layer signalling, commonly called
|
||||
a PHY.
|
||||
|
||||
config OMAP_USB3
|
||||
tristate "OMAP USB3 PHY Driver"
|
||||
select USB_OTG_UTILS
|
||||
select OMAP_CONTROL_USB
|
||||
The following drivers add support for such PHY devices.
|
||||
|
||||
if USB_PHY
|
||||
|
||||
#
|
||||
# USB Transceiver Drivers
|
||||
#
|
||||
config AB8500_USB
|
||||
tristate "AB8500 USB Transceiver Driver"
|
||||
depends on AB8500_CORE
|
||||
help
|
||||
Enable this to support the USB3 PHY that is part of SOC. This
|
||||
driver takes care of all the PHY functionality apart from comparator.
|
||||
This driver interacts with the "OMAP Control USB Driver" to power
|
||||
on/off the PHY.
|
||||
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_SUSPEND
|
||||
select USB_OTG
|
||||
help
|
||||
Enable this to support Freescale USB OTG transceiver.
|
||||
|
||||
config ISP1301_OMAP
|
||||
tristate "Philips ISP1301 with OMAP OTG"
|
||||
depends on I2C && ARCH_OMAP_OTG
|
||||
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 isp1301_omap.
|
||||
|
||||
config MV_U3D_PHY
|
||||
bool "Marvell USB 3.0 PHY controller Driver"
|
||||
depends on CPU_MMP3
|
||||
help
|
||||
Enable this to support Marvell USB 3.0 phy controller for Marvell
|
||||
SoC.
|
||||
|
||||
config NOP_USB_XCEIV
|
||||
tristate "NOP USB Transceiver Driver"
|
||||
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 OMAP_CONTROL_USB
|
||||
tristate "OMAP CONTROL USB Driver"
|
||||
@@ -34,6 +66,75 @@ config OMAP_CONTROL_USB
|
||||
power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an
|
||||
additional register to power on USB3 PHY.
|
||||
|
||||
config OMAP_USB2
|
||||
tristate "OMAP USB2 PHY Driver"
|
||||
depends on ARCH_OMAP2PLUS
|
||||
select OMAP_CONTROL_USB
|
||||
help
|
||||
Enable this to support the transceiver that is part of SOC. This
|
||||
driver takes care of all the PHY functionality apart from comparator.
|
||||
The USB OTG controller communicates with the comparator using this
|
||||
driver.
|
||||
|
||||
config OMAP_USB3
|
||||
tristate "OMAP USB3 PHY Driver"
|
||||
select OMAP_CONTROL_USB
|
||||
help
|
||||
Enable this to support the USB3 PHY that is part of SOC. This
|
||||
driver takes care of all the PHY functionality apart from comparator.
|
||||
This driver interacts with the "OMAP Control USB Driver" to power
|
||||
on/off the PHY.
|
||||
|
||||
config SAMSUNG_USBPHY
|
||||
tristate "Samsung USB PHY Driver"
|
||||
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 SAMSUNG_USB2PHY
|
||||
tristate "Samsung USB 2.0 PHY controller Driver"
|
||||
select SAMSUNG_USBPHY
|
||||
help
|
||||
Enable this to support Samsung USB 2.0 (High Speed) PHY controller
|
||||
driver for Samsung SoCs.
|
||||
|
||||
config SAMSUNG_USB3PHY
|
||||
tristate "Samsung USB 3.0 PHY controller Driver"
|
||||
select SAMSUNG_USBPHY
|
||||
help
|
||||
Enable this to support Samsung USB 3.0 (Super Speed) phy controller
|
||||
for samsung SoCs.
|
||||
|
||||
config TWL4030_USB
|
||||
tristate "TWL4030 USB Transceiver Driver"
|
||||
depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS
|
||||
help
|
||||
Enable this to support the USB OTG transceiver on TWL4030
|
||||
family chips (including the TWL5030 and TPS659x0 devices).
|
||||
This transceiver supports high and full speed devices plus,
|
||||
in host mode, low speed.
|
||||
|
||||
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 GENERIC_GPIO
|
||||
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 USB_ISP1301
|
||||
tristate "NXP ISP1301 USB transceiver support"
|
||||
depends on USB || USB_GADGET
|
||||
@@ -47,18 +148,41 @@ config USB_ISP1301
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called isp1301.
|
||||
|
||||
config MV_U3D_PHY
|
||||
bool "Marvell USB 3.0 PHY controller Driver"
|
||||
depends on USB_MV_U3D
|
||||
select USB_OTG_UTILS
|
||||
config USB_MSM_OTG
|
||||
tristate "OTG support for Qualcomm on-chip USB controller"
|
||||
depends on (USB || USB_GADGET) && ARCH_MSM
|
||||
help
|
||||
Enable this to support Marvell USB 3.0 phy controller for Marvell
|
||||
SoC.
|
||||
Enable this to support the USB OTG transceiver on MSM 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 && USB_SUSPEND
|
||||
select USB_OTG
|
||||
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
|
||||
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
|
||||
select USB_OTG_UTILS
|
||||
help
|
||||
Say Y here to add support for the Renesas R-Car USB phy driver.
|
||||
This chip is typically used as USB phy for USB host, gadget.
|
||||
@@ -67,10 +191,18 @@ config USB_RCAR_PHY
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rcar-phy.
|
||||
|
||||
config SAMSUNG_USBPHY
|
||||
bool "Samsung USB PHY controller Driver"
|
||||
depends on USB_S3C_HSOTG || USB_EHCI_S5P || USB_OHCI_EXYNOS
|
||||
select USB_OTG_UTILS
|
||||
config USB_ULPI
|
||||
bool "Generic ULPI Transceiver Driver"
|
||||
depends on ARM
|
||||
help
|
||||
Enable this to support Samsung USB phy controller for samsung
|
||||
SoCs.
|
||||
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).
|
||||
|
||||
endif # USB_PHY
|
||||
|
@@ -4,11 +4,30 @@
|
||||
|
||||
ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG
|
||||
|
||||
obj-$(CONFIG_OMAP_USB2) += omap-usb2.o
|
||||
obj-$(CONFIG_OMAP_USB3) += omap-usb3.o
|
||||
obj-$(CONFIG_OMAP_CONTROL_USB) += omap-control-usb.o
|
||||
obj-$(CONFIG_USB_ISP1301) += isp1301.o
|
||||
obj-$(CONFIG_MV_U3D_PHY) += mv_u3d_phy.o
|
||||
obj-$(CONFIG_USB_EHCI_TEGRA) += tegra_usb_phy.o
|
||||
obj-$(CONFIG_USB_RCAR_PHY) += rcar-phy.o
|
||||
obj-$(CONFIG_SAMSUNG_USBPHY) += samsung-usbphy.o
|
||||
obj-$(CONFIG_USB_PHY) += phy.o
|
||||
|
||||
# transceiver drivers, keep the list sorted
|
||||
|
||||
obj-$(CONFIG_AB8500_USB) += phy-ab8500-usb.o
|
||||
phy-fsl-usb2-objs := phy-fsl-usb.o phy-fsm-usb.o
|
||||
obj-$(CONFIG_FSL_USB2_OTG) += phy-fsl-usb2.o
|
||||
obj-$(CONFIG_ISP1301_OMAP) += phy-isp1301.omap.o
|
||||
obj-$(CONFIG_MV_U3D_PHY) += phy-mv-u3d-usb.o
|
||||
obj-$(CONFIG_NOP_USB_XCEIV) += phy-nop.o
|
||||
obj-$(CONFIG_OMAP_CONTROL_USB) += phy-omap-control.o
|
||||
obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o
|
||||
obj-$(CONFIG_OMAP_USB3) += phy-omap-usb3.o
|
||||
obj-$(CONFIG_SAMSUNG_USBPHY) += phy-samsung-usb.o
|
||||
obj-$(CONFIG_SAMSUNG_USB2PHY) += phy-samsung-usb2.o
|
||||
obj-$(CONFIG_SAMSUNG_USB3PHY) += phy-samsung-usb3.o
|
||||
obj-$(CONFIG_TWL4030_USB) += phy-twl4030-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_ULPI) += phy-ulpi.o
|
||||
obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o
|
||||
|
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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/i2c.h>
|
||||
|
||||
#define DRV_NAME "isp1301"
|
||||
|
||||
static const struct i2c_device_id isp1301_id[] = {
|
||||
{ "isp1301", 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct i2c_client *isp1301_i2c_client;
|
||||
|
||||
static int isp1301_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *i2c_id)
|
||||
{
|
||||
isp1301_i2c_client = client;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isp1301_remove(struct i2c_client *client)
|
||||
{
|
||||
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");
|
924
drivers/usb/phy/phy-ab8500-usb.c
Normal file
924
drivers/usb/phy/phy-ab8500-usb.c
Normal file
@@ -0,0 +1,924 @@
|
||||
/*
|
||||
* drivers/usb/otg/ab8500_usb.c
|
||||
*
|
||||
* USB transceiver driver for AB8500 chip
|
||||
*
|
||||
* Copyright (C) 2010 ST-Ericsson AB
|
||||
* Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mfd/abx500.h>
|
||||
#include <linux/mfd/abx500/ab8500.h>
|
||||
#include <linux/usb/musb-ux500.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
|
||||
/* Bank AB8500_SYS_CTRL2_BLOCK */
|
||||
#define AB8500_MAIN_WD_CTRL_REG 0x01
|
||||
|
||||
/* Bank AB8500_USB */
|
||||
#define AB8500_USB_LINE_STAT_REG 0x80
|
||||
#define AB8505_USB_LINE_STAT_REG 0x94
|
||||
#define AB8500_USB_PHY_CTRL_REG 0x8A
|
||||
|
||||
/* Bank AB8500_DEVELOPMENT */
|
||||
#define AB8500_BANK12_ACCESS 0x00
|
||||
|
||||
/* Bank AB8500_DEBUG */
|
||||
#define AB8500_USB_PHY_TUNE1 0x05
|
||||
#define AB8500_USB_PHY_TUNE2 0x06
|
||||
#define AB8500_USB_PHY_TUNE3 0x07
|
||||
|
||||
#define AB8500_BIT_OTG_STAT_ID (1 << 0)
|
||||
#define AB8500_BIT_PHY_CTRL_HOST_EN (1 << 0)
|
||||
#define AB8500_BIT_PHY_CTRL_DEVICE_EN (1 << 1)
|
||||
#define AB8500_BIT_WD_CTRL_ENABLE (1 << 0)
|
||||
#define AB8500_BIT_WD_CTRL_KICK (1 << 1)
|
||||
|
||||
#define AB8500_WD_KICK_DELAY_US 100 /* usec */
|
||||
#define AB8500_WD_V11_DISABLE_DELAY_US 100 /* usec */
|
||||
#define AB8500_V20_31952_DISABLE_DELAY_US 100 /* usec */
|
||||
|
||||
/* Usb line status register */
|
||||
enum ab8500_usb_link_status {
|
||||
USB_LINK_NOT_CONFIGURED_8500 = 0,
|
||||
USB_LINK_STD_HOST_NC_8500,
|
||||
USB_LINK_STD_HOST_C_NS_8500,
|
||||
USB_LINK_STD_HOST_C_S_8500,
|
||||
USB_LINK_HOST_CHG_NM_8500,
|
||||
USB_LINK_HOST_CHG_HS_8500,
|
||||
USB_LINK_HOST_CHG_HS_CHIRP_8500,
|
||||
USB_LINK_DEDICATED_CHG_8500,
|
||||
USB_LINK_ACA_RID_A_8500,
|
||||
USB_LINK_ACA_RID_B_8500,
|
||||
USB_LINK_ACA_RID_C_NM_8500,
|
||||
USB_LINK_ACA_RID_C_HS_8500,
|
||||
USB_LINK_ACA_RID_C_HS_CHIRP_8500,
|
||||
USB_LINK_HM_IDGND_8500,
|
||||
USB_LINK_RESERVED_8500,
|
||||
USB_LINK_NOT_VALID_LINK_8500,
|
||||
};
|
||||
|
||||
enum ab8505_usb_link_status {
|
||||
USB_LINK_NOT_CONFIGURED_8505 = 0,
|
||||
USB_LINK_STD_HOST_NC_8505,
|
||||
USB_LINK_STD_HOST_C_NS_8505,
|
||||
USB_LINK_STD_HOST_C_S_8505,
|
||||
USB_LINK_CDP_8505,
|
||||
USB_LINK_RESERVED0_8505,
|
||||
USB_LINK_RESERVED1_8505,
|
||||
USB_LINK_DEDICATED_CHG_8505,
|
||||
USB_LINK_ACA_RID_A_8505,
|
||||
USB_LINK_ACA_RID_B_8505,
|
||||
USB_LINK_ACA_RID_C_NM_8505,
|
||||
USB_LINK_RESERVED2_8505,
|
||||
USB_LINK_RESERVED3_8505,
|
||||
USB_LINK_HM_IDGND_8505,
|
||||
USB_LINK_CHARGERPORT_NOT_OK_8505,
|
||||
USB_LINK_CHARGER_DM_HIGH_8505,
|
||||
USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8505,
|
||||
USB_LINK_STD_UPSTREAM_NO_IDGNG_NO_VBUS_8505,
|
||||
USB_LINK_STD_UPSTREAM_8505,
|
||||
USB_LINK_CHARGER_SE1_8505,
|
||||
USB_LINK_CARKIT_CHGR_1_8505,
|
||||
USB_LINK_CARKIT_CHGR_2_8505,
|
||||
USB_LINK_ACA_DOCK_CHGR_8505,
|
||||
USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_8505,
|
||||
USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_8505,
|
||||
USB_LINK_SAMSUNG_UART_CBL_PHY_EN_8505,
|
||||
USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_8505,
|
||||
USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8505,
|
||||
};
|
||||
|
||||
enum ab8500_usb_mode {
|
||||
USB_IDLE = 0,
|
||||
USB_PERIPHERAL,
|
||||
USB_HOST,
|
||||
USB_DEDICATED_CHG
|
||||
};
|
||||
|
||||
struct ab8500_usb {
|
||||
struct usb_phy phy;
|
||||
struct device *dev;
|
||||
struct ab8500 *ab8500;
|
||||
unsigned vbus_draw;
|
||||
struct work_struct phy_dis_work;
|
||||
enum ab8500_usb_mode mode;
|
||||
struct regulator *v_ape;
|
||||
struct regulator *v_musb;
|
||||
struct regulator *v_ulpi;
|
||||
int saved_v_ulpi;
|
||||
int previous_link_status_state;
|
||||
struct pinctrl *pinctrl;
|
||||
struct pinctrl_state *pins_sleep;
|
||||
};
|
||||
|
||||
static inline struct ab8500_usb *phy_to_ab(struct usb_phy *x)
|
||||
{
|
||||
return container_of(x, struct ab8500_usb, phy);
|
||||
}
|
||||
|
||||
static void ab8500_usb_wd_workaround(struct ab8500_usb *ab)
|
||||
{
|
||||
abx500_set_register_interruptible(ab->dev,
|
||||
AB8500_SYS_CTRL2_BLOCK,
|
||||
AB8500_MAIN_WD_CTRL_REG,
|
||||
AB8500_BIT_WD_CTRL_ENABLE);
|
||||
|
||||
udelay(AB8500_WD_KICK_DELAY_US);
|
||||
|
||||
abx500_set_register_interruptible(ab->dev,
|
||||
AB8500_SYS_CTRL2_BLOCK,
|
||||
AB8500_MAIN_WD_CTRL_REG,
|
||||
(AB8500_BIT_WD_CTRL_ENABLE
|
||||
| AB8500_BIT_WD_CTRL_KICK));
|
||||
|
||||
udelay(AB8500_WD_V11_DISABLE_DELAY_US);
|
||||
|
||||
abx500_set_register_interruptible(ab->dev,
|
||||
AB8500_SYS_CTRL2_BLOCK,
|
||||
AB8500_MAIN_WD_CTRL_REG,
|
||||
0);
|
||||
}
|
||||
|
||||
static void ab8500_usb_regulator_enable(struct ab8500_usb *ab)
|
||||
{
|
||||
int ret, volt;
|
||||
|
||||
ret = regulator_enable(ab->v_ape);
|
||||
if (ret)
|
||||
dev_err(ab->dev, "Failed to enable v-ape\n");
|
||||
|
||||
if (!is_ab8500_2p0_or_earlier(ab->ab8500)) {
|
||||
ab->saved_v_ulpi = regulator_get_voltage(ab->v_ulpi);
|
||||
if (ab->saved_v_ulpi < 0)
|
||||
dev_err(ab->dev, "Failed to get v_ulpi voltage\n");
|
||||
|
||||
ret = regulator_set_voltage(ab->v_ulpi, 1300000, 1350000);
|
||||
if (ret < 0)
|
||||
dev_err(ab->dev, "Failed to set the Vintcore to 1.3V, ret=%d\n",
|
||||
ret);
|
||||
|
||||
ret = regulator_set_optimum_mode(ab->v_ulpi, 28000);
|
||||
if (ret < 0)
|
||||
dev_err(ab->dev, "Failed to set optimum mode (ret=%d)\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
ret = regulator_enable(ab->v_ulpi);
|
||||
if (ret)
|
||||
dev_err(ab->dev, "Failed to enable vddulpivio18\n");
|
||||
|
||||
if (!is_ab8500_2p0_or_earlier(ab->ab8500)) {
|
||||
volt = regulator_get_voltage(ab->v_ulpi);
|
||||
if ((volt != 1300000) && (volt != 1350000))
|
||||
dev_err(ab->dev, "Vintcore is not set to 1.3V volt=%d\n",
|
||||
volt);
|
||||
}
|
||||
|
||||
ret = regulator_enable(ab->v_musb);
|
||||
if (ret)
|
||||
dev_err(ab->dev, "Failed to enable musb_1v8\n");
|
||||
}
|
||||
|
||||
static void ab8500_usb_regulator_disable(struct ab8500_usb *ab)
|
||||
{
|
||||
int ret;
|
||||
|
||||
regulator_disable(ab->v_musb);
|
||||
|
||||
regulator_disable(ab->v_ulpi);
|
||||
|
||||
/* USB is not the only consumer of Vintcore, restore old settings */
|
||||
if (!is_ab8500_2p0_or_earlier(ab->ab8500)) {
|
||||
if (ab->saved_v_ulpi > 0) {
|
||||
ret = regulator_set_voltage(ab->v_ulpi,
|
||||
ab->saved_v_ulpi, ab->saved_v_ulpi);
|
||||
if (ret < 0)
|
||||
dev_err(ab->dev, "Failed to set the Vintcore to %duV, ret=%d\n",
|
||||
ab->saved_v_ulpi, ret);
|
||||
}
|
||||
|
||||
ret = regulator_set_optimum_mode(ab->v_ulpi, 0);
|
||||
if (ret < 0)
|
||||
dev_err(ab->dev, "Failed to set optimum mode (ret=%d)\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
regulator_disable(ab->v_ape);
|
||||
}
|
||||
|
||||
static void ab8500_usb_wd_linkstatus(struct ab8500_usb *ab, u8 bit)
|
||||
{
|
||||
/* Workaround for v2.0 bug # 31952 */
|
||||
if (is_ab8500_2p0(ab->ab8500)) {
|
||||
abx500_mask_and_set_register_interruptible(ab->dev,
|
||||
AB8500_USB, AB8500_USB_PHY_CTRL_REG,
|
||||
bit, bit);
|
||||
udelay(AB8500_V20_31952_DISABLE_DELAY_US);
|
||||
}
|
||||
}
|
||||
|
||||
static void ab8500_usb_phy_enable(struct ab8500_usb *ab, bool sel_host)
|
||||
{
|
||||
u8 bit;
|
||||
bit = sel_host ? AB8500_BIT_PHY_CTRL_HOST_EN :
|
||||
AB8500_BIT_PHY_CTRL_DEVICE_EN;
|
||||
|
||||
/* mux and configure USB pins to DEFAULT state */
|
||||
ab->pinctrl = pinctrl_get_select(ab->dev, PINCTRL_STATE_DEFAULT);
|
||||
if (IS_ERR(ab->pinctrl))
|
||||
dev_err(ab->dev, "could not get/set default pinstate\n");
|
||||
|
||||
ab8500_usb_regulator_enable(ab);
|
||||
|
||||
abx500_mask_and_set_register_interruptible(ab->dev,
|
||||
AB8500_USB, AB8500_USB_PHY_CTRL_REG,
|
||||
bit, bit);
|
||||
}
|
||||
|
||||
static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host)
|
||||
{
|
||||
u8 bit;
|
||||
bit = sel_host ? AB8500_BIT_PHY_CTRL_HOST_EN :
|
||||
AB8500_BIT_PHY_CTRL_DEVICE_EN;
|
||||
|
||||
ab8500_usb_wd_linkstatus(ab, bit);
|
||||
|
||||
abx500_mask_and_set_register_interruptible(ab->dev,
|
||||
AB8500_USB, AB8500_USB_PHY_CTRL_REG,
|
||||
bit, 0);
|
||||
|
||||
/* Needed to disable the phy.*/
|
||||
ab8500_usb_wd_workaround(ab);
|
||||
|
||||
ab8500_usb_regulator_disable(ab);
|
||||
|
||||
if (!IS_ERR(ab->pinctrl)) {
|
||||
/* configure USB pins to SLEEP state */
|
||||
ab->pins_sleep = pinctrl_lookup_state(ab->pinctrl,
|
||||
PINCTRL_STATE_SLEEP);
|
||||
|
||||
if (IS_ERR(ab->pins_sleep))
|
||||
dev_dbg(ab->dev, "could not get sleep pinstate\n");
|
||||
else if (pinctrl_select_state(ab->pinctrl, ab->pins_sleep))
|
||||
dev_err(ab->dev, "could not set pins to sleep state\n");
|
||||
|
||||
/* as USB pins are shared with idddet, release them to allow
|
||||
* iddet to request them
|
||||
*/
|
||||
pinctrl_put(ab->pinctrl);
|
||||
}
|
||||
}
|
||||
|
||||
#define ab8500_usb_host_phy_en(ab) ab8500_usb_phy_enable(ab, true)
|
||||
#define ab8500_usb_host_phy_dis(ab) ab8500_usb_phy_disable(ab, true)
|
||||
#define ab8500_usb_peri_phy_en(ab) ab8500_usb_phy_enable(ab, false)
|
||||
#define ab8500_usb_peri_phy_dis(ab) ab8500_usb_phy_disable(ab, false)
|
||||
|
||||
static int ab8505_usb_link_status_update(struct ab8500_usb *ab,
|
||||
enum ab8505_usb_link_status lsts)
|
||||
{
|
||||
enum ux500_musb_vbus_id_status event = 0;
|
||||
|
||||
dev_dbg(ab->dev, "ab8505_usb_link_status_update %d\n", lsts);
|
||||
|
||||
/*
|
||||
* Spurious link_status interrupts are seen at the time of
|
||||
* disconnection of a device in RIDA state
|
||||
*/
|
||||
if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8505 &&
|
||||
(lsts == USB_LINK_STD_HOST_NC_8505))
|
||||
return 0;
|
||||
|
||||
ab->previous_link_status_state = lsts;
|
||||
|
||||
switch (lsts) {
|
||||
case USB_LINK_ACA_RID_B_8505:
|
||||
event = UX500_MUSB_RIDB;
|
||||
case USB_LINK_NOT_CONFIGURED_8505:
|
||||
case USB_LINK_RESERVED0_8505:
|
||||
case USB_LINK_RESERVED1_8505:
|
||||
case USB_LINK_RESERVED2_8505:
|
||||
case USB_LINK_RESERVED3_8505:
|
||||
ab->mode = USB_IDLE;
|
||||
ab->phy.otg->default_a = false;
|
||||
ab->vbus_draw = 0;
|
||||
if (event != UX500_MUSB_RIDB)
|
||||
event = UX500_MUSB_NONE;
|
||||
/*
|
||||
* Fallback to default B_IDLE as nothing
|
||||
* is connected
|
||||
*/
|
||||
ab->phy.state = OTG_STATE_B_IDLE;
|
||||
break;
|
||||
|
||||
case USB_LINK_ACA_RID_C_NM_8505:
|
||||
event = UX500_MUSB_RIDC;
|
||||
case USB_LINK_STD_HOST_NC_8505:
|
||||
case USB_LINK_STD_HOST_C_NS_8505:
|
||||
case USB_LINK_STD_HOST_C_S_8505:
|
||||
case USB_LINK_CDP_8505:
|
||||
if (ab->mode == USB_IDLE) {
|
||||
ab->mode = USB_PERIPHERAL;
|
||||
ab8500_usb_peri_phy_en(ab);
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
UX500_MUSB_PREPARE, &ab->vbus_draw);
|
||||
}
|
||||
if (event != UX500_MUSB_RIDC)
|
||||
event = UX500_MUSB_VBUS;
|
||||
break;
|
||||
|
||||
case USB_LINK_ACA_RID_A_8505:
|
||||
case USB_LINK_ACA_DOCK_CHGR_8505:
|
||||
event = UX500_MUSB_RIDA;
|
||||
case USB_LINK_HM_IDGND_8505:
|
||||
if (ab->mode == USB_IDLE) {
|
||||
ab->mode = USB_HOST;
|
||||
ab8500_usb_host_phy_en(ab);
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
UX500_MUSB_PREPARE, &ab->vbus_draw);
|
||||
}
|
||||
ab->phy.otg->default_a = true;
|
||||
if (event != UX500_MUSB_RIDA)
|
||||
event = UX500_MUSB_ID;
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
event, &ab->vbus_draw);
|
||||
break;
|
||||
|
||||
case USB_LINK_DEDICATED_CHG_8505:
|
||||
ab->mode = USB_DEDICATED_CHG;
|
||||
event = UX500_MUSB_CHARGER;
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
event, &ab->vbus_draw);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ab8500_usb_link_status_update(struct ab8500_usb *ab,
|
||||
enum ab8500_usb_link_status lsts)
|
||||
{
|
||||
enum ux500_musb_vbus_id_status event = 0;
|
||||
|
||||
dev_dbg(ab->dev, "ab8500_usb_link_status_update %d\n", lsts);
|
||||
|
||||
/*
|
||||
* Spurious link_status interrupts are seen in case of a
|
||||
* disconnection of a device in IDGND and RIDA stage
|
||||
*/
|
||||
if (ab->previous_link_status_state == USB_LINK_HM_IDGND_8500 &&
|
||||
(lsts == USB_LINK_STD_HOST_C_NS_8500 ||
|
||||
lsts == USB_LINK_STD_HOST_NC_8500))
|
||||
return 0;
|
||||
|
||||
if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8500 &&
|
||||
lsts == USB_LINK_STD_HOST_NC_8500)
|
||||
return 0;
|
||||
|
||||
ab->previous_link_status_state = lsts;
|
||||
|
||||
switch (lsts) {
|
||||
case USB_LINK_ACA_RID_B_8500:
|
||||
event = UX500_MUSB_RIDB;
|
||||
case USB_LINK_NOT_CONFIGURED_8500:
|
||||
case USB_LINK_NOT_VALID_LINK_8500:
|
||||
ab->mode = USB_IDLE;
|
||||
ab->phy.otg->default_a = false;
|
||||
ab->vbus_draw = 0;
|
||||
if (event != UX500_MUSB_RIDB)
|
||||
event = UX500_MUSB_NONE;
|
||||
/* Fallback to default B_IDLE as nothing is connected */
|
||||
ab->phy.state = OTG_STATE_B_IDLE;
|
||||
break;
|
||||
|
||||
case USB_LINK_ACA_RID_C_NM_8500:
|
||||
case USB_LINK_ACA_RID_C_HS_8500:
|
||||
case USB_LINK_ACA_RID_C_HS_CHIRP_8500:
|
||||
event = UX500_MUSB_RIDC;
|
||||
case USB_LINK_STD_HOST_NC_8500:
|
||||
case USB_LINK_STD_HOST_C_NS_8500:
|
||||
case USB_LINK_STD_HOST_C_S_8500:
|
||||
case USB_LINK_HOST_CHG_NM_8500:
|
||||
case USB_LINK_HOST_CHG_HS_8500:
|
||||
case USB_LINK_HOST_CHG_HS_CHIRP_8500:
|
||||
if (ab->mode == USB_IDLE) {
|
||||
ab->mode = USB_PERIPHERAL;
|
||||
ab8500_usb_peri_phy_en(ab);
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
UX500_MUSB_PREPARE, &ab->vbus_draw);
|
||||
}
|
||||
if (event != UX500_MUSB_RIDC)
|
||||
event = UX500_MUSB_VBUS;
|
||||
break;
|
||||
|
||||
case USB_LINK_ACA_RID_A_8500:
|
||||
event = UX500_MUSB_RIDA;
|
||||
case USB_LINK_HM_IDGND_8500:
|
||||
if (ab->mode == USB_IDLE) {
|
||||
ab->mode = USB_HOST;
|
||||
ab8500_usb_host_phy_en(ab);
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
UX500_MUSB_PREPARE, &ab->vbus_draw);
|
||||
}
|
||||
ab->phy.otg->default_a = true;
|
||||
if (event != UX500_MUSB_RIDA)
|
||||
event = UX500_MUSB_ID;
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
event, &ab->vbus_draw);
|
||||
break;
|
||||
|
||||
case USB_LINK_DEDICATED_CHG_8500:
|
||||
ab->mode = USB_DEDICATED_CHG;
|
||||
event = UX500_MUSB_CHARGER;
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
event, &ab->vbus_draw);
|
||||
break;
|
||||
|
||||
case USB_LINK_RESERVED_8500:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connection Sequence:
|
||||
* 1. Link Status Interrupt
|
||||
* 2. Enable AB clock
|
||||
* 3. Enable AB regulators
|
||||
* 4. Enable USB phy
|
||||
* 5. Reset the musb controller
|
||||
* 6. Switch the ULPI GPIO pins to fucntion mode
|
||||
* 7. Enable the musb Peripheral5 clock
|
||||
* 8. Restore MUSB context
|
||||
*/
|
||||
static int abx500_usb_link_status_update(struct ab8500_usb *ab)
|
||||
{
|
||||
u8 reg;
|
||||
int ret = 0;
|
||||
|
||||
if (is_ab8500(ab->ab8500)) {
|
||||
enum ab8500_usb_link_status lsts;
|
||||
|
||||
abx500_get_register_interruptible(ab->dev,
|
||||
AB8500_USB, AB8500_USB_LINE_STAT_REG, ®);
|
||||
lsts = (reg >> 3) & 0x0F;
|
||||
ret = ab8500_usb_link_status_update(ab, lsts);
|
||||
} else if (is_ab8505(ab->ab8500)) {
|
||||
enum ab8505_usb_link_status lsts;
|
||||
|
||||
abx500_get_register_interruptible(ab->dev,
|
||||
AB8500_USB, AB8505_USB_LINE_STAT_REG, ®);
|
||||
lsts = (reg >> 3) & 0x1F;
|
||||
ret = ab8505_usb_link_status_update(ab, lsts);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disconnection Sequence:
|
||||
* 1. Disconect Interrupt
|
||||
* 2. Disable regulators
|
||||
* 3. Disable AB clock
|
||||
* 4. Disable the Phy
|
||||
* 5. Link Status Interrupt
|
||||
* 6. Disable Musb Clock
|
||||
*/
|
||||
static irqreturn_t ab8500_usb_disconnect_irq(int irq, void *data)
|
||||
{
|
||||
struct ab8500_usb *ab = (struct ab8500_usb *) data;
|
||||
enum usb_phy_events event = UX500_MUSB_NONE;
|
||||
|
||||
/* Link status will not be updated till phy is disabled. */
|
||||
if (ab->mode == USB_HOST) {
|
||||
ab->phy.otg->default_a = false;
|
||||
ab->vbus_draw = 0;
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
event, &ab->vbus_draw);
|
||||
ab8500_usb_host_phy_dis(ab);
|
||||
ab->mode = USB_IDLE;
|
||||
}
|
||||
|
||||
if (ab->mode == USB_PERIPHERAL) {
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
event, &ab->vbus_draw);
|
||||
ab8500_usb_peri_phy_dis(ab);
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
UX500_MUSB_CLEAN, &ab->vbus_draw);
|
||||
ab->mode = USB_IDLE;
|
||||
ab->phy.otg->default_a = false;
|
||||
ab->vbus_draw = 0;
|
||||
}
|
||||
|
||||
if (is_ab8500_2p0(ab->ab8500)) {
|
||||
if (ab->mode == USB_DEDICATED_CHG) {
|
||||
ab8500_usb_wd_linkstatus(ab,
|
||||
AB8500_BIT_PHY_CTRL_DEVICE_EN);
|
||||
abx500_mask_and_set_register_interruptible(ab->dev,
|
||||
AB8500_USB, AB8500_USB_PHY_CTRL_REG,
|
||||
AB8500_BIT_PHY_CTRL_DEVICE_EN, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t ab8500_usb_link_status_irq(int irq, void *data)
|
||||
{
|
||||
struct ab8500_usb *ab = (struct ab8500_usb *) data;
|
||||
|
||||
abx500_usb_link_status_update(ab);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void ab8500_usb_phy_disable_work(struct work_struct *work)
|
||||
{
|
||||
struct ab8500_usb *ab = container_of(work, struct ab8500_usb,
|
||||
phy_dis_work);
|
||||
|
||||
if (!ab->phy.otg->host)
|
||||
ab8500_usb_host_phy_dis(ab);
|
||||
|
||||
if (!ab->phy.otg->gadget)
|
||||
ab8500_usb_peri_phy_dis(ab);
|
||||
}
|
||||
|
||||
static unsigned ab8500_eyediagram_workaroud(struct ab8500_usb *ab, unsigned mA)
|
||||
{
|
||||
/*
|
||||
* AB8500 V2 has eye diagram issues when drawing more than 100mA from
|
||||
* VBUS. Set charging current to 100mA in case of standard host
|
||||
*/
|
||||
if (is_ab8500_2p0_or_earlier(ab->ab8500))
|
||||
if (mA > 100)
|
||||
mA = 100;
|
||||
|
||||
return mA;
|
||||
}
|
||||
|
||||
static int ab8500_usb_set_power(struct usb_phy *phy, unsigned mA)
|
||||
{
|
||||
struct ab8500_usb *ab;
|
||||
|
||||
if (!phy)
|
||||
return -ENODEV;
|
||||
|
||||
ab = phy_to_ab(phy);
|
||||
|
||||
mA = ab8500_eyediagram_workaroud(ab, mA);
|
||||
|
||||
ab->vbus_draw = mA;
|
||||
|
||||
atomic_notifier_call_chain(&ab->phy.notifier,
|
||||
UX500_MUSB_VBUS, &ab->vbus_draw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ab8500_usb_set_suspend(struct usb_phy *x, int suspend)
|
||||
{
|
||||
/* TODO */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ab8500_usb_set_peripheral(struct usb_otg *otg,
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
struct ab8500_usb *ab;
|
||||
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
ab = phy_to_ab(otg->phy);
|
||||
|
||||
ab->phy.otg->gadget = gadget;
|
||||
|
||||
/* Some drivers call this function in atomic context.
|
||||
* Do not update ab8500 registers directly till this
|
||||
* is fixed.
|
||||
*/
|
||||
|
||||
if ((ab->mode != USB_IDLE) && (!gadget)) {
|
||||
ab->mode = USB_IDLE;
|
||||
schedule_work(&ab->phy_dis_work);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ab8500_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
struct ab8500_usb *ab;
|
||||
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
ab = phy_to_ab(otg->phy);
|
||||
|
||||
ab->phy.otg->host = host;
|
||||
|
||||
/* Some drivers call this function in atomic context.
|
||||
* Do not update ab8500 registers directly till this
|
||||
* is fixed.
|
||||
*/
|
||||
|
||||
if ((ab->mode != USB_IDLE) && (!host)) {
|
||||
ab->mode = USB_IDLE;
|
||||
schedule_work(&ab->phy_dis_work);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ab8500_usb_regulator_get(struct ab8500_usb *ab)
|
||||
{
|
||||
int err;
|
||||
|
||||
ab->v_ape = devm_regulator_get(ab->dev, "v-ape");
|
||||
if (IS_ERR(ab->v_ape)) {
|
||||
dev_err(ab->dev, "Could not get v-ape supply\n");
|
||||
err = PTR_ERR(ab->v_ape);
|
||||
return err;
|
||||
}
|
||||
|
||||
ab->v_ulpi = devm_regulator_get(ab->dev, "vddulpivio18");
|
||||
if (IS_ERR(ab->v_ulpi)) {
|
||||
dev_err(ab->dev, "Could not get vddulpivio18 supply\n");
|
||||
err = PTR_ERR(ab->v_ulpi);
|
||||
return err;
|
||||
}
|
||||
|
||||
ab->v_musb = devm_regulator_get(ab->dev, "musb_1v8");
|
||||
if (IS_ERR(ab->v_musb)) {
|
||||
dev_err(ab->dev, "Could not get musb_1v8 supply\n");
|
||||
err = PTR_ERR(ab->v_musb);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ab8500_usb_irq_setup(struct platform_device *pdev,
|
||||
struct ab8500_usb *ab)
|
||||
{
|
||||
int err;
|
||||
int irq;
|
||||
|
||||
irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS");
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "Link status irq not found\n");
|
||||
return irq;
|
||||
}
|
||||
err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
||||
ab8500_usb_link_status_irq,
|
||||
IRQF_NO_SUSPEND | IRQF_SHARED, "usb-link-status", ab);
|
||||
if (err < 0) {
|
||||
dev_err(ab->dev, "request_irq failed for link status irq\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F");
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "ID fall irq not found\n");
|
||||
return irq;
|
||||
}
|
||||
err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
||||
ab8500_usb_disconnect_irq,
|
||||
IRQF_NO_SUSPEND | IRQF_SHARED, "usb-id-fall", ab);
|
||||
if (err < 0) {
|
||||
dev_err(ab->dev, "request_irq failed for ID fall irq\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
irq = platform_get_irq_byname(pdev, "VBUS_DET_F");
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "VBUS fall irq not found\n");
|
||||
return irq;
|
||||
}
|
||||
err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
||||
ab8500_usb_disconnect_irq,
|
||||
IRQF_NO_SUSPEND | IRQF_SHARED, "usb-vbus-fall", ab);
|
||||
if (err < 0) {
|
||||
dev_err(ab->dev, "request_irq failed for Vbus fall irq\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ab8500_usb_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ab8500_usb *ab;
|
||||
struct ab8500 *ab8500;
|
||||
struct usb_otg *otg;
|
||||
int err;
|
||||
int rev;
|
||||
|
||||
ab8500 = dev_get_drvdata(pdev->dev.parent);
|
||||
rev = abx500_get_chip_id(&pdev->dev);
|
||||
|
||||
if (is_ab8500_1p1_or_earlier(ab8500)) {
|
||||
dev_err(&pdev->dev, "Unsupported AB8500 chip rev=%d\n", rev);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ab = devm_kzalloc(&pdev->dev, sizeof(*ab), GFP_KERNEL);
|
||||
if (!ab)
|
||||
return -ENOMEM;
|
||||
|
||||
otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
|
||||
if (!otg)
|
||||
return -ENOMEM;
|
||||
|
||||
ab->dev = &pdev->dev;
|
||||
ab->ab8500 = ab8500;
|
||||
ab->phy.dev = ab->dev;
|
||||
ab->phy.otg = otg;
|
||||
ab->phy.label = "ab8500";
|
||||
ab->phy.set_suspend = ab8500_usb_set_suspend;
|
||||
ab->phy.set_power = ab8500_usb_set_power;
|
||||
ab->phy.state = OTG_STATE_UNDEFINED;
|
||||
|
||||
otg->phy = &ab->phy;
|
||||
otg->set_host = ab8500_usb_set_host;
|
||||
otg->set_peripheral = ab8500_usb_set_peripheral;
|
||||
|
||||
platform_set_drvdata(pdev, ab);
|
||||
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&ab->phy.notifier);
|
||||
|
||||
/* all: Disable phy when called from set_host and set_peripheral */
|
||||
INIT_WORK(&ab->phy_dis_work, ab8500_usb_phy_disable_work);
|
||||
|
||||
err = ab8500_usb_regulator_get(ab);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = ab8500_usb_irq_setup(pdev, ab);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = usb_add_phy(&ab->phy, USB_PHY_TYPE_USB2);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Can't register transceiver\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Phy tuning values for AB8500 */
|
||||
if (!is_ab8500_2p0_or_earlier(ab->ab8500)) {
|
||||
/* Enable the PBT/Bank 0x12 access */
|
||||
err = abx500_set_register_interruptible(ab->dev,
|
||||
AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01);
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to enable bank12 access err=%d\n",
|
||||
err);
|
||||
|
||||
err = abx500_set_register_interruptible(ab->dev,
|
||||
AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8);
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n",
|
||||
err);
|
||||
|
||||
err = abx500_set_register_interruptible(ab->dev,
|
||||
AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x00);
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n",
|
||||
err);
|
||||
|
||||
err = abx500_set_register_interruptible(ab->dev,
|
||||
AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x78);
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n",
|
||||
err);
|
||||
|
||||
/* Switch to normal mode/disable Bank 0x12 access */
|
||||
err = abx500_set_register_interruptible(ab->dev,
|
||||
AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00);
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to switch bank12 access err=%d\n",
|
||||
err);
|
||||
}
|
||||
|
||||
/* Phy tuning values for AB8505 */
|
||||
if (is_ab8505(ab->ab8500)) {
|
||||
/* Enable the PBT/Bank 0x12 access */
|
||||
err = abx500_mask_and_set_register_interruptible(ab->dev,
|
||||
AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS,
|
||||
0x01, 0x01);
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to enable bank12 access err=%d\n",
|
||||
err);
|
||||
|
||||
err = abx500_mask_and_set_register_interruptible(ab->dev,
|
||||
AB8500_DEBUG, AB8500_USB_PHY_TUNE1,
|
||||
0xC8, 0xC8);
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n",
|
||||
err);
|
||||
|
||||
err = abx500_mask_and_set_register_interruptible(ab->dev,
|
||||
AB8500_DEBUG, AB8500_USB_PHY_TUNE2,
|
||||
0x60, 0x60);
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n",
|
||||
err);
|
||||
|
||||
err = abx500_mask_and_set_register_interruptible(ab->dev,
|
||||
AB8500_DEBUG, AB8500_USB_PHY_TUNE3,
|
||||
0xFC, 0x80);
|
||||
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n",
|
||||
err);
|
||||
|
||||
/* Switch to normal mode/disable Bank 0x12 access */
|
||||
err = abx500_mask_and_set_register_interruptible(ab->dev,
|
||||
AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS,
|
||||
0x00, 0x00);
|
||||
if (err < 0)
|
||||
dev_err(ab->dev, "Failed to switch bank12 access err=%d\n",
|
||||
err);
|
||||
}
|
||||
|
||||
/* Needed to enable ID detection. */
|
||||
ab8500_usb_wd_workaround(ab);
|
||||
|
||||
abx500_usb_link_status_update(ab);
|
||||
|
||||
dev_info(&pdev->dev, "revision 0x%2x driver initialized\n", rev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ab8500_usb_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ab8500_usb *ab = platform_get_drvdata(pdev);
|
||||
|
||||
cancel_work_sync(&ab->phy_dis_work);
|
||||
|
||||
usb_remove_phy(&ab->phy);
|
||||
|
||||
if (ab->mode == USB_HOST)
|
||||
ab8500_usb_host_phy_dis(ab);
|
||||
else if (ab->mode == USB_PERIPHERAL)
|
||||
ab8500_usb_peri_phy_dis(ab);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ab8500_usb_driver = {
|
||||
.probe = ab8500_usb_probe,
|
||||
.remove = ab8500_usb_remove,
|
||||
.driver = {
|
||||
.name = "ab8500-usb",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init ab8500_usb_init(void)
|
||||
{
|
||||
return platform_driver_register(&ab8500_usb_driver);
|
||||
}
|
||||
subsys_initcall(ab8500_usb_init);
|
||||
|
||||
static void __exit ab8500_usb_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ab8500_usb_driver);
|
||||
}
|
||||
module_exit(ab8500_usb_exit);
|
||||
|
||||
MODULE_ALIAS("platform:ab8500_usb");
|
||||
MODULE_AUTHOR("ST-Ericsson AB");
|
||||
MODULE_DESCRIPTION("AB8500 usb transceiver driver");
|
||||
MODULE_LICENSE("GPL");
|
1173
drivers/usb/phy/phy-fsl-usb.c
Normal file
1173
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 "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(void *timer);
|
||||
void fsl_otg_del_timer(void *timer);
|
||||
void fsl_otg_pulse_vbus(void);
|
348
drivers/usb/phy/phy-fsm-usb.c
Normal file
348
drivers/usb/phy/phy-fsm-usb.c
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* OTG Finite State Machine from OTG spec
|
||||
*
|
||||
* Copyright (C) 2007,2008 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* Author: Li Yang <LeoLi@freescale.com>
|
||||
* Jerry Huang <Chang-Ming.Huang@freescale.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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/usb/otg.h>
|
||||
|
||||
#include "phy-otg-fsm.h"
|
||||
|
||||
/* Change USB protocol when there is a protocol change */
|
||||
static int otg_set_protocol(struct otg_fsm *fsm, int protocol)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (fsm->protocol != protocol) {
|
||||
VDBG("Changing role fsm->protocol= %d; new protocol= %d\n",
|
||||
fsm->protocol, protocol);
|
||||
/* stop old protocol */
|
||||
if (fsm->protocol == PROTO_HOST)
|
||||
ret = fsm->ops->start_host(fsm, 0);
|
||||
else if (fsm->protocol == PROTO_GADGET)
|
||||
ret = fsm->ops->start_gadget(fsm, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* start new protocol */
|
||||
if (protocol == PROTO_HOST)
|
||||
ret = fsm->ops->start_host(fsm, 1);
|
||||
else if (protocol == PROTO_GADGET)
|
||||
ret = fsm->ops->start_gadget(fsm, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
fsm->protocol = protocol;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int state_changed;
|
||||
|
||||
/* Called when leaving a state. Do state clean up jobs here */
|
||||
void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state)
|
||||
{
|
||||
switch (old_state) {
|
||||
case OTG_STATE_B_IDLE:
|
||||
otg_del_timer(fsm, b_se0_srp_tmr);
|
||||
fsm->b_se0_srp = 0;
|
||||
break;
|
||||
case OTG_STATE_B_SRP_INIT:
|
||||
fsm->b_srp_done = 0;
|
||||
break;
|
||||
case OTG_STATE_B_PERIPHERAL:
|
||||
break;
|
||||
case OTG_STATE_B_WAIT_ACON:
|
||||
otg_del_timer(fsm, b_ase0_brst_tmr);
|
||||
fsm->b_ase0_brst_tmout = 0;
|
||||
break;
|
||||
case OTG_STATE_B_HOST:
|
||||
break;
|
||||
case OTG_STATE_A_IDLE:
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VRISE:
|
||||
otg_del_timer(fsm, a_wait_vrise_tmr);
|
||||
fsm->a_wait_vrise_tmout = 0;
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
otg_del_timer(fsm, a_wait_bcon_tmr);
|
||||
fsm->a_wait_bcon_tmout = 0;
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
otg_del_timer(fsm, a_wait_enum_tmr);
|
||||
break;
|
||||
case OTG_STATE_A_SUSPEND:
|
||||
otg_del_timer(fsm, a_aidl_bdis_tmr);
|
||||
fsm->a_aidl_bdis_tmout = 0;
|
||||
fsm->a_suspend_req = 0;
|
||||
break;
|
||||
case OTG_STATE_A_PERIPHERAL:
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
otg_del_timer(fsm, a_wait_vrise_tmr);
|
||||
break;
|
||||
case OTG_STATE_A_VBUS_ERR:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Called when entering a state */
|
||||
int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state)
|
||||
{
|
||||
state_changed = 1;
|
||||
if (fsm->otg->phy->state == new_state)
|
||||
return 0;
|
||||
VDBG("Set state: %s\n", usb_otg_state_string(new_state));
|
||||
otg_leave_state(fsm, fsm->otg->phy->state);
|
||||
switch (new_state) {
|
||||
case OTG_STATE_B_IDLE:
|
||||
otg_drv_vbus(fsm, 0);
|
||||
otg_chrg_vbus(fsm, 0);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_UNDEF);
|
||||
otg_add_timer(fsm, b_se0_srp_tmr);
|
||||
break;
|
||||
case OTG_STATE_B_SRP_INIT:
|
||||
otg_start_pulse(fsm);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_UNDEF);
|
||||
otg_add_timer(fsm, b_srp_fail_tmr);
|
||||
break;
|
||||
case OTG_STATE_B_PERIPHERAL:
|
||||
otg_chrg_vbus(fsm, 0);
|
||||
otg_loc_conn(fsm, 1);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_GADGET);
|
||||
break;
|
||||
case OTG_STATE_B_WAIT_ACON:
|
||||
otg_chrg_vbus(fsm, 0);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_HOST);
|
||||
otg_add_timer(fsm, b_ase0_brst_tmr);
|
||||
fsm->a_bus_suspend = 0;
|
||||
break;
|
||||
case OTG_STATE_B_HOST:
|
||||
otg_chrg_vbus(fsm, 0);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 1);
|
||||
otg_set_protocol(fsm, PROTO_HOST);
|
||||
usb_bus_start_enum(fsm->otg->host,
|
||||
fsm->otg->host->otg_port);
|
||||
break;
|
||||
case OTG_STATE_A_IDLE:
|
||||
otg_drv_vbus(fsm, 0);
|
||||
otg_chrg_vbus(fsm, 0);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_HOST);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VRISE:
|
||||
otg_drv_vbus(fsm, 1);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_HOST);
|
||||
otg_add_timer(fsm, a_wait_vrise_tmr);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
otg_drv_vbus(fsm, 1);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_HOST);
|
||||
otg_add_timer(fsm, a_wait_bcon_tmr);
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
otg_drv_vbus(fsm, 1);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 1);
|
||||
otg_set_protocol(fsm, PROTO_HOST);
|
||||
/*
|
||||
* When HNP is triggered while a_bus_req = 0, a_host will
|
||||
* suspend too fast to complete a_set_b_hnp_en
|
||||
*/
|
||||
if (!fsm->a_bus_req || fsm->a_suspend_req)
|
||||
otg_add_timer(fsm, a_wait_enum_tmr);
|
||||
break;
|
||||
case OTG_STATE_A_SUSPEND:
|
||||
otg_drv_vbus(fsm, 1);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_HOST);
|
||||
otg_add_timer(fsm, a_aidl_bdis_tmr);
|
||||
|
||||
break;
|
||||
case OTG_STATE_A_PERIPHERAL:
|
||||
otg_loc_conn(fsm, 1);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_GADGET);
|
||||
otg_drv_vbus(fsm, 1);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
otg_drv_vbus(fsm, 0);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_HOST);
|
||||
break;
|
||||
case OTG_STATE_A_VBUS_ERR:
|
||||
otg_drv_vbus(fsm, 0);
|
||||
otg_loc_conn(fsm, 0);
|
||||
otg_loc_sof(fsm, 0);
|
||||
otg_set_protocol(fsm, PROTO_UNDEF);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
fsm->otg->phy->state = new_state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* State change judgement */
|
||||
int otg_statemachine(struct otg_fsm *fsm)
|
||||
{
|
||||
enum usb_otg_state state;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&fsm->lock, flags);
|
||||
|
||||
state = fsm->otg->phy->state;
|
||||
state_changed = 0;
|
||||
/* State machine state change judgement */
|
||||
|
||||
switch (state) {
|
||||
case OTG_STATE_UNDEFINED:
|
||||
VDBG("fsm->id = %d\n", fsm->id);
|
||||
if (fsm->id)
|
||||
otg_set_state(fsm, OTG_STATE_B_IDLE);
|
||||
else
|
||||
otg_set_state(fsm, OTG_STATE_A_IDLE);
|
||||
break;
|
||||
case OTG_STATE_B_IDLE:
|
||||
if (!fsm->id)
|
||||
otg_set_state(fsm, OTG_STATE_A_IDLE);
|
||||
else if (fsm->b_sess_vld && fsm->otg->gadget)
|
||||
otg_set_state(fsm, OTG_STATE_B_PERIPHERAL);
|
||||
else if (fsm->b_bus_req && fsm->b_sess_end && fsm->b_se0_srp)
|
||||
otg_set_state(fsm, OTG_STATE_B_SRP_INIT);
|
||||
break;
|
||||
case OTG_STATE_B_SRP_INIT:
|
||||
if (!fsm->id || fsm->b_srp_done)
|
||||
otg_set_state(fsm, OTG_STATE_B_IDLE);
|
||||
break;
|
||||
case OTG_STATE_B_PERIPHERAL:
|
||||
if (!fsm->id || !fsm->b_sess_vld)
|
||||
otg_set_state(fsm, OTG_STATE_B_IDLE);
|
||||
else if (fsm->b_bus_req && fsm->otg->
|
||||
gadget->b_hnp_enable && fsm->a_bus_suspend)
|
||||
otg_set_state(fsm, OTG_STATE_B_WAIT_ACON);
|
||||
break;
|
||||
case OTG_STATE_B_WAIT_ACON:
|
||||
if (fsm->a_conn)
|
||||
otg_set_state(fsm, OTG_STATE_B_HOST);
|
||||
else if (!fsm->id || !fsm->b_sess_vld)
|
||||
otg_set_state(fsm, OTG_STATE_B_IDLE);
|
||||
else if (fsm->a_bus_resume || fsm->b_ase0_brst_tmout) {
|
||||
fsm->b_ase0_brst_tmout = 0;
|
||||
otg_set_state(fsm, OTG_STATE_B_PERIPHERAL);
|
||||
}
|
||||
break;
|
||||
case OTG_STATE_B_HOST:
|
||||
if (!fsm->id || !fsm->b_sess_vld)
|
||||
otg_set_state(fsm, OTG_STATE_B_IDLE);
|
||||
else if (!fsm->b_bus_req || !fsm->a_conn)
|
||||
otg_set_state(fsm, OTG_STATE_B_PERIPHERAL);
|
||||
break;
|
||||
case OTG_STATE_A_IDLE:
|
||||
if (fsm->id)
|
||||
otg_set_state(fsm, OTG_STATE_B_IDLE);
|
||||
else if (!fsm->a_bus_drop && (fsm->a_bus_req || fsm->a_srp_det))
|
||||
otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VRISE:
|
||||
if (fsm->id || fsm->a_bus_drop || fsm->a_vbus_vld ||
|
||||
fsm->a_wait_vrise_tmout) {
|
||||
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
|
||||
}
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_BCON:
|
||||
if (!fsm->a_vbus_vld)
|
||||
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
|
||||
else if (fsm->b_conn)
|
||||
otg_set_state(fsm, OTG_STATE_A_HOST);
|
||||
else if (fsm->id | fsm->a_bus_drop | fsm->a_wait_bcon_tmout)
|
||||
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
|
||||
break;
|
||||
case OTG_STATE_A_HOST:
|
||||
if ((!fsm->a_bus_req || fsm->a_suspend_req) &&
|
||||
fsm->otg->host->b_hnp_enable)
|
||||
otg_set_state(fsm, OTG_STATE_A_SUSPEND);
|
||||
else if (fsm->id || !fsm->b_conn || fsm->a_bus_drop)
|
||||
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
|
||||
else if (!fsm->a_vbus_vld)
|
||||
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
|
||||
break;
|
||||
case OTG_STATE_A_SUSPEND:
|
||||
if (!fsm->b_conn && fsm->otg->host->b_hnp_enable)
|
||||
otg_set_state(fsm, OTG_STATE_A_PERIPHERAL);
|
||||
else if (!fsm->b_conn && !fsm->otg->host->b_hnp_enable)
|
||||
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
|
||||
else if (fsm->a_bus_req || fsm->b_bus_resume)
|
||||
otg_set_state(fsm, OTG_STATE_A_HOST);
|
||||
else if (fsm->id || fsm->a_bus_drop || fsm->a_aidl_bdis_tmout)
|
||||
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
|
||||
else if (!fsm->a_vbus_vld)
|
||||
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
|
||||
break;
|
||||
case OTG_STATE_A_PERIPHERAL:
|
||||
if (fsm->id || fsm->a_bus_drop)
|
||||
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
|
||||
else if (fsm->b_bus_suspend)
|
||||
otg_set_state(fsm, OTG_STATE_A_WAIT_BCON);
|
||||
else if (!fsm->a_vbus_vld)
|
||||
otg_set_state(fsm, OTG_STATE_A_VBUS_ERR);
|
||||
break;
|
||||
case OTG_STATE_A_WAIT_VFALL:
|
||||
if (fsm->id || fsm->a_bus_req || (!fsm->a_sess_vld &&
|
||||
!fsm->b_conn))
|
||||
otg_set_state(fsm, OTG_STATE_A_IDLE);
|
||||
break;
|
||||
case OTG_STATE_A_VBUS_ERR:
|
||||
if (fsm->id || fsm->a_bus_drop || fsm->a_clr_err)
|
||||
otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&fsm->lock, flags);
|
||||
|
||||
VDBG("quit statemachine, changed = %d\n", state_changed);
|
||||
return state_changed;
|
||||
}
|
154
drivers/usb/phy/phy-fsm-usb.h
Normal file
154
drivers/usb/phy/phy-fsm-usb.h
Normal file
@@ -0,0 +1,154 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#undef DEBUG
|
||||
#undef VERBOSE
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt , \
|
||||
__func__, ## args)
|
||||
#else
|
||||
#define DBG(fmt, args...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#ifdef VERBOSE
|
||||
#define VDBG DBG
|
||||
#else
|
||||
#define VDBG(stuff...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#ifdef VERBOSE
|
||||
#define MPC_LOC printk("Current Location [%s]:[%d]\n", __FILE__, __LINE__)
|
||||
#else
|
||||
#define MPC_LOC do {} while (0)
|
||||
#endif
|
||||
|
||||
#define PROTO_UNDEF (0)
|
||||
#define PROTO_HOST (1)
|
||||
#define PROTO_GADGET (2)
|
||||
|
||||
/* OTG state machine according to the OTG spec */
|
||||
struct otg_fsm {
|
||||
/* Input */
|
||||
int a_bus_resume;
|
||||
int a_bus_suspend;
|
||||
int a_conn;
|
||||
int a_sess_vld;
|
||||
int a_srp_det;
|
||||
int a_vbus_vld;
|
||||
int b_bus_resume;
|
||||
int b_bus_suspend;
|
||||
int b_conn;
|
||||
int b_se0_srp;
|
||||
int b_sess_end;
|
||||
int b_sess_vld;
|
||||
int id;
|
||||
|
||||
/* Internal variables */
|
||||
int a_set_b_hnp_en;
|
||||
int b_srp_done;
|
||||
int b_hnp_enable;
|
||||
|
||||
/* Timeout indicator for timers */
|
||||
int a_wait_vrise_tmout;
|
||||
int a_wait_bcon_tmout;
|
||||
int a_aidl_bdis_tmout;
|
||||
int b_ase0_brst_tmout;
|
||||
|
||||
/* Informative variables */
|
||||
int a_bus_drop;
|
||||
int a_bus_req;
|
||||
int a_clr_err;
|
||||
int a_suspend_req;
|
||||
int b_bus_req;
|
||||
|
||||
/* Output */
|
||||
int drv_vbus;
|
||||
int loc_conn;
|
||||
int loc_sof;
|
||||
|
||||
struct otg_fsm_ops *ops;
|
||||
struct usb_otg *otg;
|
||||
|
||||
/* Current usb protocol used: 0:undefine; 1:host; 2:client */
|
||||
int protocol;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
struct otg_fsm_ops {
|
||||
void (*chrg_vbus)(int on);
|
||||
void (*drv_vbus)(int on);
|
||||
void (*loc_conn)(int on);
|
||||
void (*loc_sof)(int on);
|
||||
void (*start_pulse)(void);
|
||||
void (*add_timer)(void *timer);
|
||||
void (*del_timer)(void *timer);
|
||||
int (*start_host)(struct otg_fsm *fsm, int on);
|
||||
int (*start_gadget)(struct otg_fsm *fsm, int on);
|
||||
};
|
||||
|
||||
|
||||
static inline void otg_chrg_vbus(struct otg_fsm *fsm, int on)
|
||||
{
|
||||
fsm->ops->chrg_vbus(on);
|
||||
}
|
||||
|
||||
static inline void otg_drv_vbus(struct otg_fsm *fsm, int on)
|
||||
{
|
||||
if (fsm->drv_vbus != on) {
|
||||
fsm->drv_vbus = on;
|
||||
fsm->ops->drv_vbus(on);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void otg_loc_conn(struct otg_fsm *fsm, int on)
|
||||
{
|
||||
if (fsm->loc_conn != on) {
|
||||
fsm->loc_conn = on;
|
||||
fsm->ops->loc_conn(on);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void otg_loc_sof(struct otg_fsm *fsm, int on)
|
||||
{
|
||||
if (fsm->loc_sof != on) {
|
||||
fsm->loc_sof = on;
|
||||
fsm->ops->loc_sof(on);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void otg_start_pulse(struct otg_fsm *fsm)
|
||||
{
|
||||
fsm->ops->start_pulse();
|
||||
}
|
||||
|
||||
static inline void otg_add_timer(struct otg_fsm *fsm, void *timer)
|
||||
{
|
||||
fsm->ops->add_timer(timer);
|
||||
}
|
||||
|
||||
static inline void otg_del_timer(struct otg_fsm *fsm, void *timer)
|
||||
{
|
||||
fsm->ops->del_timer(timer);
|
||||
}
|
||||
|
||||
int otg_statemachine(struct otg_fsm *fsm);
|
||||
|
||||
/* Defined by device specific driver, for different timer implementation */
|
||||
extern struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr,
|
||||
*a_aidl_bdis_tmr, *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_fail_tmr,
|
||||
*a_wait_enum_tmr;
|
421
drivers/usb/phy/phy-gpio-vbus-usb.c
Normal file
421
drivers/usb/phy/phy-gpio-vbus-usb.c
Normal file
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
* 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 = gpio_vbus->dev->platform_data;
|
||||
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 = pdev->dev.platform_data;
|
||||
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 = gpio_vbus->dev->platform_data;
|
||||
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 __init gpio_vbus_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data;
|
||||
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 = kzalloc(sizeof(struct gpio_vbus_data), GFP_KERNEL);
|
||||
if (!gpio_vbus)
|
||||
return -ENOMEM;
|
||||
|
||||
gpio_vbus->phy.otg = kzalloc(sizeof(struct usb_otg), GFP_KERNEL);
|
||||
if (!gpio_vbus->phy.otg) {
|
||||
kfree(gpio_vbus);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, gpio_vbus);
|
||||
gpio_vbus->dev = &pdev->dev;
|
||||
gpio_vbus->phy.label = "gpio-vbus";
|
||||
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 = gpio_request(gpio, "vbus_detect");
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "can't request vbus gpio %d, err: %d\n",
|
||||
gpio, err);
|
||||
goto err_gpio;
|
||||
}
|
||||
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 = gpio_request(gpio, "udc_pullup");
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"can't request pullup gpio %d, err: %d\n",
|
||||
gpio, err);
|
||||
gpio_free(pdata->gpio_vbus);
|
||||
goto err_gpio;
|
||||
}
|
||||
gpio_direction_output(gpio, pdata->gpio_pullup_inverted);
|
||||
}
|
||||
|
||||
err = request_irq(irq, gpio_vbus_irq, irqflags, "vbus_detect", pdev);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "can't request irq %i, err: %d\n",
|
||||
irq, err);
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&gpio_vbus->phy.notifier);
|
||||
|
||||
INIT_DELAYED_WORK(&gpio_vbus->work, gpio_vbus_work);
|
||||
|
||||
gpio_vbus->vbus_draw = 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);
|
||||
goto err_otg;
|
||||
}
|
||||
|
||||
device_init_wakeup(&pdev->dev, pdata->wakeup);
|
||||
|
||||
return 0;
|
||||
err_otg:
|
||||
regulator_put(gpio_vbus->vbus_draw);
|
||||
free_irq(irq, pdev);
|
||||
err_irq:
|
||||
if (gpio_is_valid(pdata->gpio_pullup))
|
||||
gpio_free(pdata->gpio_pullup);
|
||||
gpio_free(pdata->gpio_vbus);
|
||||
err_gpio:
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(gpio_vbus->phy.otg);
|
||||
kfree(gpio_vbus);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __exit gpio_vbus_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev);
|
||||
struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data;
|
||||
int gpio = pdata->gpio_vbus;
|
||||
|
||||
device_init_wakeup(&pdev->dev, 0);
|
||||
cancel_delayed_work_sync(&gpio_vbus->work);
|
||||
regulator_put(gpio_vbus->vbus_draw);
|
||||
|
||||
usb_remove_phy(&gpio_vbus->phy);
|
||||
|
||||
free_irq(gpio_vbus->irq, pdev);
|
||||
if (gpio_is_valid(pdata->gpio_pullup))
|
||||
gpio_free(pdata->gpio_pullup);
|
||||
gpio_free(gpio);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(gpio_vbus->phy.otg);
|
||||
kfree(gpio_vbus);
|
||||
|
||||
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
|
||||
|
||||
/* NOTE: the gpio-vbus device may *NOT* be hotplugged */
|
||||
|
||||
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
|
||||
},
|
||||
.remove = __exit_p(gpio_vbus_remove),
|
||||
};
|
||||
|
||||
module_platform_driver_probe(gpio_vbus_driver, gpio_vbus_probe);
|
||||
|
||||
MODULE_DESCRIPTION("simple GPIO controlled OTG transceiver driver");
|
||||
MODULE_AUTHOR("Philipp Zabel");
|
||||
MODULE_LICENSE("GPL");
|
1656
drivers/usb/phy/phy-isp1301-omap.c
Normal file
1656
drivers/usb/phy/phy-isp1301-omap.c
Normal file
File diff suppressed because it is too large
Load Diff
162
drivers/usb/phy/phy-isp1301.c
Normal file
162
drivers/usb/phy/phy-isp1301.c
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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->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");
|
1762
drivers/usb/phy/phy-msm-usb.c
Normal file
1762
drivers/usb/phy/phy-msm-usb.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/platform_data/mv_usb.h>
|
||||
|
||||
#include "mv_u3d_phy.h"
|
||||
#include "phy-mv-u3d-usb.h"
|
||||
|
||||
/*
|
||||
* struct mv_u3d_phy - transceiver driver state
|
909
drivers/usb/phy/phy-mv-usb.c
Normal file
909
drivers/usb/phy/phy-mv-usb.c
Normal file
@@ -0,0 +1,909 @@
|
||||
/*
|
||||
* 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/init.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);
|
||||
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,
|
||||
};
|
||||
|
||||
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);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv_otg_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mv_usb_platform_data *pdata = pdev->dev.platform_data;
|
||||
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);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
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 = __exit_p(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
|
220
drivers/usb/phy/phy-mxs-usb.c
Normal file
220
drivers/usb/phy/phy-mxs-usb.c
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2012 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>
|
||||
|
||||
#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 BM_USBPHY_CTRL_SFTRST BIT(31)
|
||||
#define BM_USBPHY_CTRL_CLKGATE BIT(30)
|
||||
#define BM_USBPHY_CTRL_ENUTMILEVEL3 BIT(15)
|
||||
#define BM_USBPHY_CTRL_ENUTMILEVEL2 BIT(14)
|
||||
#define BM_USBPHY_CTRL_ENHOSTDISCONDETECT BIT(1)
|
||||
|
||||
struct mxs_phy {
|
||||
struct usb_phy phy;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy)
|
||||
|
||||
static void mxs_phy_hw_init(struct mxs_phy *mxs_phy)
|
||||
{
|
||||
void __iomem *base = mxs_phy->phy.io_priv;
|
||||
|
||||
stmp_reset_block(base + HW_USBPHY_CTRL);
|
||||
|
||||
/* Power up the PHY */
|
||||
writel(0, base + HW_USBPHY_PWD);
|
||||
|
||||
/* enable FS/LS device */
|
||||
writel(BM_USBPHY_CTRL_ENUTMILEVEL2 |
|
||||
BM_USBPHY_CTRL_ENUTMILEVEL3,
|
||||
base + HW_USBPHY_CTRL_SET);
|
||||
}
|
||||
|
||||
static int mxs_phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct mxs_phy *mxs_phy = to_mxs_phy(phy);
|
||||
|
||||
clk_prepare_enable(mxs_phy->clk);
|
||||
mxs_phy_hw_init(mxs_phy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 {
|
||||
clk_prepare_enable(mxs_phy->clk);
|
||||
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_on_connect(struct usb_phy *phy,
|
||||
enum usb_device_speed speed)
|
||||
{
|
||||
dev_dbg(phy->dev, "%s speed device has connected\n",
|
||||
(speed == USB_SPEED_HIGH) ? "high" : "non-high");
|
||||
|
||||
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 speed device has disconnected\n",
|
||||
(speed == USB_SPEED_HIGH) ? "high" : "non-high");
|
||||
|
||||
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;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "can't get device resources\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&mxs_phy->phy.notifier);
|
||||
|
||||
mxs_phy->clk = clk;
|
||||
|
||||
platform_set_drvdata(pdev, &mxs_phy->phy);
|
||||
|
||||
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);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mxs_phy_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx23-usbphy", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids);
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
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");
|
294
drivers/usb/phy/phy-nop.c
Normal file
294
drivers/usb/phy/phy-nop.c
Normal file
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* 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/nop-usb-xceiv.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
struct nop_usb_xceiv {
|
||||
struct usb_phy phy;
|
||||
struct device *dev;
|
||||
struct clk *clk;
|
||||
struct regulator *vcc;
|
||||
struct regulator *reset;
|
||||
};
|
||||
|
||||
static struct platform_device *pd;
|
||||
|
||||
void usb_nop_xceiv_register(void)
|
||||
{
|
||||
if (pd)
|
||||
return;
|
||||
pd = platform_device_register_simple("nop_usb_xceiv", -1, NULL, 0);
|
||||
if (!pd) {
|
||||
printk(KERN_ERR "Unable to register usb nop transceiver\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(usb_nop_xceiv_register);
|
||||
|
||||
void usb_nop_xceiv_unregister(void)
|
||||
{
|
||||
platform_device_unregister(pd);
|
||||
pd = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(usb_nop_xceiv_unregister);
|
||||
|
||||
static int nop_set_suspend(struct usb_phy *x, int suspend)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nop_init(struct usb_phy *phy)
|
||||
{
|
||||
struct nop_usb_xceiv *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_enable(nop->clk);
|
||||
|
||||
if (!IS_ERR(nop->reset)) {
|
||||
/* De-assert RESET */
|
||||
if (regulator_enable(nop->reset))
|
||||
dev_err(phy->dev, "Failed to de-assert reset\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nop_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct nop_usb_xceiv *nop = dev_get_drvdata(phy->dev);
|
||||
|
||||
if (!IS_ERR(nop->reset)) {
|
||||
/* Assert RESET */
|
||||
if (regulator_disable(nop->reset))
|
||||
dev_err(phy->dev, "Failed to assert reset\n");
|
||||
}
|
||||
|
||||
if (!IS_ERR(nop->clk))
|
||||
clk_disable(nop->clk);
|
||||
|
||||
if (!IS_ERR(nop->vcc)) {
|
||||
if (regulator_disable(nop->vcc))
|
||||
dev_err(phy->dev, "Failed to disable power\n");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int nop_usb_xceiv_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct nop_usb_xceiv_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct nop_usb_xceiv *nop;
|
||||
enum usb_phy_type type = USB_PHY_TYPE_USB2;
|
||||
int err;
|
||||
u32 clk_rate = 0;
|
||||
bool needs_vcc = false;
|
||||
bool needs_reset = false;
|
||||
|
||||
nop = devm_kzalloc(&pdev->dev, sizeof(*nop), GFP_KERNEL);
|
||||
if (!nop)
|
||||
return -ENOMEM;
|
||||
|
||||
nop->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*nop->phy.otg),
|
||||
GFP_KERNEL);
|
||||
if (!nop->phy.otg)
|
||||
return -ENOMEM;
|
||||
|
||||
if (dev->of_node) {
|
||||
struct device_node *node = dev->of_node;
|
||||
|
||||
if (of_property_read_u32(node, "clock-frequency", &clk_rate))
|
||||
clk_rate = 0;
|
||||
|
||||
needs_vcc = of_property_read_bool(node, "vcc-supply");
|
||||
needs_reset = of_property_read_bool(node, "reset-supply");
|
||||
|
||||
} else if (pdata) {
|
||||
type = pdata->type;
|
||||
clk_rate = pdata->clk_rate;
|
||||
needs_vcc = pdata->needs_vcc;
|
||||
needs_reset = pdata->needs_reset;
|
||||
}
|
||||
|
||||
nop->clk = devm_clk_get(&pdev->dev, "main_clk");
|
||||
if (IS_ERR(nop->clk)) {
|
||||
dev_dbg(&pdev->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(&pdev->dev, "Error setting clock rate\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IS_ERR(nop->clk)) {
|
||||
err = clk_prepare(nop->clk);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Error preparing clock\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
nop->vcc = devm_regulator_get(&pdev->dev, "vcc");
|
||||
if (IS_ERR(nop->vcc)) {
|
||||
dev_dbg(&pdev->dev, "Error getting vcc regulator: %ld\n",
|
||||
PTR_ERR(nop->vcc));
|
||||
if (needs_vcc)
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
nop->reset = devm_regulator_get(&pdev->dev, "reset");
|
||||
if (IS_ERR(nop->reset)) {
|
||||
dev_dbg(&pdev->dev, "Error getting reset regulator: %ld\n",
|
||||
PTR_ERR(nop->reset));
|
||||
if (needs_reset)
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
nop->dev = &pdev->dev;
|
||||
nop->phy.dev = nop->dev;
|
||||
nop->phy.label = "nop-xceiv";
|
||||
nop->phy.set_suspend = nop_set_suspend;
|
||||
nop->phy.init = nop_init;
|
||||
nop->phy.shutdown = nop_shutdown;
|
||||
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;
|
||||
|
||||
err = usb_add_phy_dev(&nop->phy);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "can't register transceiver, err: %d\n",
|
||||
err);
|
||||
goto err_add;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, nop);
|
||||
|
||||
ATOMIC_INIT_NOTIFIER_HEAD(&nop->phy.notifier);
|
||||
|
||||
return 0;
|
||||
|
||||
err_add:
|
||||
if (!IS_ERR(nop->clk))
|
||||
clk_unprepare(nop->clk);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int nop_usb_xceiv_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct nop_usb_xceiv *nop = platform_get_drvdata(pdev);
|
||||
|
||||
if (!IS_ERR(nop->clk))
|
||||
clk_unprepare(nop->clk);
|
||||
|
||||
usb_remove_phy(&nop->phy);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
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 nop_usb_xceiv_driver = {
|
||||
.probe = nop_usb_xceiv_probe,
|
||||
.remove = nop_usb_xceiv_remove,
|
||||
.driver = {
|
||||
.name = "nop_usb_xceiv",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(nop_xceiv_dt_ids),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init nop_usb_xceiv_init(void)
|
||||
{
|
||||
return platform_driver_register(&nop_usb_xceiv_driver);
|
||||
}
|
||||
subsys_initcall(nop_usb_xceiv_init);
|
||||
|
||||
static void __exit nop_usb_xceiv_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&nop_usb_xceiv_driver);
|
||||
}
|
||||
module_exit(nop_usb_xceiv_exit);
|
||||
|
||||
MODULE_ALIAS("platform:nop_usb_xceiv");
|
||||
MODULE_AUTHOR("Texas Instruments Inc");
|
||||
MODULE_DESCRIPTION("NOP USB Transceiver driver");
|
||||
MODULE_LICENSE("GPL");
|
236
drivers/usb/phy/phy-samsung-usb.c
Normal file
236
drivers/usb/phy/phy-samsung-usb.c
Normal file
@@ -0,0 +1,236 @@
|
||||
/* linux/drivers/usb/phy/phy-samsung-usb.c
|
||||
*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Praveen Paneri <p.paneri@samsung.com>
|
||||
*
|
||||
* Samsung USB-PHY helper driver with common function calls;
|
||||
* interacts with Samsung USB 2.0 PHY controller driver and later
|
||||
* with Samsung USB 3.0 PHY driver.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/usb/samsung_usb_phy.h>
|
||||
|
||||
#include "phy-samsung-usb.h"
|
||||
|
||||
int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy)
|
||||
{
|
||||
struct device_node *usbphy_sys;
|
||||
|
||||
/* Getting node for system controller interface for usb-phy */
|
||||
usbphy_sys = of_get_child_by_name(sphy->dev->of_node, "usbphy-sys");
|
||||
if (!usbphy_sys) {
|
||||
dev_err(sphy->dev, "No sys-controller interface for usb-phy\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
sphy->pmuregs = of_iomap(usbphy_sys, 0);
|
||||
|
||||
if (sphy->pmuregs == NULL) {
|
||||
dev_err(sphy->dev, "Can't get usb-phy pmu control register\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
sphy->sysreg = of_iomap(usbphy_sys, 1);
|
||||
|
||||
/*
|
||||
* Not returning error code here, since this situation is not fatal.
|
||||
* Few SoCs may not have this switch available
|
||||
*/
|
||||
if (sphy->sysreg == NULL)
|
||||
dev_warn(sphy->dev, "Can't get usb-phy sysreg cfg register\n");
|
||||
|
||||
of_node_put(usbphy_sys);
|
||||
|
||||
return 0;
|
||||
|
||||
err0:
|
||||
of_node_put(usbphy_sys);
|
||||
return -ENXIO;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(samsung_usbphy_parse_dt);
|
||||
|
||||
/*
|
||||
* Set isolation here for phy.
|
||||
* Here 'on = true' would mean USB PHY block is isolated, hence
|
||||
* de-activated and vice-versa.
|
||||
*/
|
||||
void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on)
|
||||
{
|
||||
void __iomem *reg = NULL;
|
||||
u32 reg_val;
|
||||
u32 en_mask = 0;
|
||||
|
||||
if (!sphy->pmuregs) {
|
||||
dev_warn(sphy->dev, "Can't set pmu isolation\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (sphy->drv_data->cpu_type) {
|
||||
case TYPE_S3C64XX:
|
||||
/*
|
||||
* Do nothing: We will add here once S3C64xx goes for DT support
|
||||
*/
|
||||
break;
|
||||
case TYPE_EXYNOS4210:
|
||||
/*
|
||||
* Fall through since exynos4210 and exynos5250 have similar
|
||||
* register architecture: two separate registers for host and
|
||||
* device phy control with enable bit at position 0.
|
||||
*/
|
||||
case TYPE_EXYNOS5250:
|
||||
if (sphy->phy_type == USB_PHY_TYPE_DEVICE) {
|
||||
reg = sphy->pmuregs +
|
||||
sphy->drv_data->devphy_reg_offset;
|
||||
en_mask = sphy->drv_data->devphy_en_mask;
|
||||
} else if (sphy->phy_type == USB_PHY_TYPE_HOST) {
|
||||
reg = sphy->pmuregs +
|
||||
sphy->drv_data->hostphy_reg_offset;
|
||||
en_mask = sphy->drv_data->hostphy_en_mask;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dev_err(sphy->dev, "Invalid SoC type\n");
|
||||
return;
|
||||
}
|
||||
|
||||
reg_val = readl(reg);
|
||||
|
||||
if (on)
|
||||
reg_val &= ~en_mask;
|
||||
else
|
||||
reg_val |= en_mask;
|
||||
|
||||
writel(reg_val, reg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation);
|
||||
|
||||
/*
|
||||
* Configure the mode of working of usb-phy here: HOST/DEVICE.
|
||||
*/
|
||||
void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (!sphy->sysreg) {
|
||||
dev_warn(sphy->dev, "Can't configure specified phy mode\n");
|
||||
return;
|
||||
}
|
||||
|
||||
reg = readl(sphy->sysreg);
|
||||
|
||||
if (sphy->phy_type == USB_PHY_TYPE_DEVICE)
|
||||
reg &= ~EXYNOS_USB20PHY_CFG_HOST_LINK;
|
||||
else if (sphy->phy_type == USB_PHY_TYPE_HOST)
|
||||
reg |= EXYNOS_USB20PHY_CFG_HOST_LINK;
|
||||
|
||||
writel(reg, sphy->sysreg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(samsung_usbphy_cfg_sel);
|
||||
|
||||
/*
|
||||
* PHYs are different for USB Device and USB Host.
|
||||
* This make sure that correct PHY type is selected before
|
||||
* any operation on PHY.
|
||||
*/
|
||||
int samsung_usbphy_set_type(struct usb_phy *phy,
|
||||
enum samsung_usb_phy_type phy_type)
|
||||
{
|
||||
struct samsung_usbphy *sphy = phy_to_sphy(phy);
|
||||
|
||||
sphy->phy_type = phy_type;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(samsung_usbphy_set_type);
|
||||
|
||||
/*
|
||||
* Returns reference clock frequency selection value
|
||||
*/
|
||||
int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy)
|
||||
{
|
||||
struct clk *ref_clk;
|
||||
int refclk_freq = 0;
|
||||
|
||||
/*
|
||||
* In exynos5250 USB host and device PHY use
|
||||
* external crystal clock XXTI
|
||||
*/
|
||||
if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250)
|
||||
ref_clk = devm_clk_get(sphy->dev, "ext_xtal");
|
||||
else
|
||||
ref_clk = devm_clk_get(sphy->dev, "xusbxti");
|
||||
if (IS_ERR(ref_clk)) {
|
||||
dev_err(sphy->dev, "Failed to get reference clock\n");
|
||||
return PTR_ERR(ref_clk);
|
||||
}
|
||||
|
||||
if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) {
|
||||
/* set clock frequency for PLL */
|
||||
switch (clk_get_rate(ref_clk)) {
|
||||
case 9600 * KHZ:
|
||||
refclk_freq = FSEL_CLKSEL_9600K;
|
||||
break;
|
||||
case 10 * MHZ:
|
||||
refclk_freq = FSEL_CLKSEL_10M;
|
||||
break;
|
||||
case 12 * MHZ:
|
||||
refclk_freq = FSEL_CLKSEL_12M;
|
||||
break;
|
||||
case 19200 * KHZ:
|
||||
refclk_freq = FSEL_CLKSEL_19200K;
|
||||
break;
|
||||
case 20 * MHZ:
|
||||
refclk_freq = FSEL_CLKSEL_20M;
|
||||
break;
|
||||
case 50 * MHZ:
|
||||
refclk_freq = FSEL_CLKSEL_50M;
|
||||
break;
|
||||
case 24 * MHZ:
|
||||
default:
|
||||
/* default reference clock */
|
||||
refclk_freq = FSEL_CLKSEL_24M;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (clk_get_rate(ref_clk)) {
|
||||
case 12 * MHZ:
|
||||
refclk_freq = PHYCLK_CLKSEL_12M;
|
||||
break;
|
||||
case 24 * MHZ:
|
||||
refclk_freq = PHYCLK_CLKSEL_24M;
|
||||
break;
|
||||
case 48 * MHZ:
|
||||
refclk_freq = PHYCLK_CLKSEL_48M;
|
||||
break;
|
||||
default:
|
||||
if (sphy->drv_data->cpu_type == TYPE_S3C64XX)
|
||||
refclk_freq = PHYCLK_CLKSEL_48M;
|
||||
else
|
||||
refclk_freq = PHYCLK_CLKSEL_24M;
|
||||
break;
|
||||
}
|
||||
}
|
||||
clk_put(ref_clk);
|
||||
|
||||
return refclk_freq;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(samsung_usbphy_get_refclk_freq);
|
327
drivers/usb/phy/phy-samsung-usb.h
Normal file
327
drivers/usb/phy/phy-samsung-usb.h
Normal file
@@ -0,0 +1,327 @@
|
||||
/* linux/drivers/usb/phy/phy-samsung-usb.h
|
||||
*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Samsung USB-PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and
|
||||
* OHCI-EXYNOS controllers.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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/usb/phy.h>
|
||||
|
||||
/* Register definitions */
|
||||
|
||||
#define SAMSUNG_PHYPWR (0x00)
|
||||
|
||||
#define PHYPWR_NORMAL_MASK (0x19 << 0)
|
||||
#define PHYPWR_OTG_DISABLE (0x1 << 4)
|
||||
#define PHYPWR_ANALOG_POWERDOWN (0x1 << 3)
|
||||
#define PHYPWR_FORCE_SUSPEND (0x1 << 1)
|
||||
/* For Exynos4 */
|
||||
#define PHYPWR_NORMAL_MASK_PHY0 (0x39 << 0)
|
||||
#define PHYPWR_SLEEP_PHY0 (0x1 << 5)
|
||||
|
||||
#define SAMSUNG_PHYCLK (0x04)
|
||||
|
||||
#define PHYCLK_MODE_USB11 (0x1 << 6)
|
||||
#define PHYCLK_EXT_OSC (0x1 << 5)
|
||||
#define PHYCLK_COMMON_ON_N (0x1 << 4)
|
||||
#define PHYCLK_ID_PULL (0x1 << 2)
|
||||
#define PHYCLK_CLKSEL_MASK (0x3 << 0)
|
||||
#define PHYCLK_CLKSEL_48M (0x0 << 0)
|
||||
#define PHYCLK_CLKSEL_12M (0x2 << 0)
|
||||
#define PHYCLK_CLKSEL_24M (0x3 << 0)
|
||||
|
||||
#define SAMSUNG_RSTCON (0x08)
|
||||
|
||||
#define RSTCON_PHYLINK_SWRST (0x1 << 2)
|
||||
#define RSTCON_HLINK_SWRST (0x1 << 1)
|
||||
#define RSTCON_SWRST (0x1 << 0)
|
||||
|
||||
/* EXYNOS5 */
|
||||
#define EXYNOS5_PHY_HOST_CTRL0 (0x00)
|
||||
|
||||
#define HOST_CTRL0_PHYSWRSTALL (0x1 << 31)
|
||||
|
||||
#define HOST_CTRL0_REFCLKSEL_MASK (0x3 << 19)
|
||||
#define HOST_CTRL0_REFCLKSEL_XTAL (0x0 << 19)
|
||||
#define HOST_CTRL0_REFCLKSEL_EXTL (0x1 << 19)
|
||||
#define HOST_CTRL0_REFCLKSEL_CLKCORE (0x2 << 19)
|
||||
|
||||
#define HOST_CTRL0_FSEL_MASK (0x7 << 16)
|
||||
#define HOST_CTRL0_FSEL(_x) ((_x) << 16)
|
||||
|
||||
#define FSEL_CLKSEL_50M (0x7)
|
||||
#define FSEL_CLKSEL_24M (0x5)
|
||||
#define FSEL_CLKSEL_20M (0x4)
|
||||
#define FSEL_CLKSEL_19200K (0x3)
|
||||
#define FSEL_CLKSEL_12M (0x2)
|
||||
#define FSEL_CLKSEL_10M (0x1)
|
||||
#define FSEL_CLKSEL_9600K (0x0)
|
||||
|
||||
#define HOST_CTRL0_TESTBURNIN (0x1 << 11)
|
||||
#define HOST_CTRL0_RETENABLE (0x1 << 10)
|
||||
#define HOST_CTRL0_COMMONON_N (0x1 << 9)
|
||||
#define HOST_CTRL0_SIDDQ (0x1 << 6)
|
||||
#define HOST_CTRL0_FORCESLEEP (0x1 << 5)
|
||||
#define HOST_CTRL0_FORCESUSPEND (0x1 << 4)
|
||||
#define HOST_CTRL0_WORDINTERFACE (0x1 << 3)
|
||||
#define HOST_CTRL0_UTMISWRST (0x1 << 2)
|
||||
#define HOST_CTRL0_LINKSWRST (0x1 << 1)
|
||||
#define HOST_CTRL0_PHYSWRST (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_PHY_HOST_TUNE0 (0x04)
|
||||
|
||||
#define EXYNOS5_PHY_HSIC_CTRL1 (0x10)
|
||||
|
||||
#define EXYNOS5_PHY_HSIC_TUNE1 (0x14)
|
||||
|
||||
#define EXYNOS5_PHY_HSIC_CTRL2 (0x20)
|
||||
|
||||
#define EXYNOS5_PHY_HSIC_TUNE2 (0x24)
|
||||
|
||||
#define HSIC_CTRL_REFCLKSEL_MASK (0x3 << 23)
|
||||
#define HSIC_CTRL_REFCLKSEL (0x2 << 23)
|
||||
|
||||
#define HSIC_CTRL_REFCLKDIV_MASK (0x7f << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV(_x) ((_x) << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_12 (0x24 << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_15 (0x1c << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_16 (0x1a << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_19_2 (0x15 << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_20 (0x14 << 16)
|
||||
|
||||
#define HSIC_CTRL_SIDDQ (0x1 << 6)
|
||||
#define HSIC_CTRL_FORCESLEEP (0x1 << 5)
|
||||
#define HSIC_CTRL_FORCESUSPEND (0x1 << 4)
|
||||
#define HSIC_CTRL_WORDINTERFACE (0x1 << 3)
|
||||
#define HSIC_CTRL_UTMISWRST (0x1 << 2)
|
||||
#define HSIC_CTRL_PHYSWRST (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_PHY_HOST_EHCICTRL (0x30)
|
||||
|
||||
#define HOST_EHCICTRL_ENAINCRXALIGN (0x1 << 29)
|
||||
#define HOST_EHCICTRL_ENAINCR4 (0x1 << 28)
|
||||
#define HOST_EHCICTRL_ENAINCR8 (0x1 << 27)
|
||||
#define HOST_EHCICTRL_ENAINCR16 (0x1 << 26)
|
||||
|
||||
#define EXYNOS5_PHY_HOST_OHCICTRL (0x34)
|
||||
|
||||
#define HOST_OHCICTRL_SUSPLGCY (0x1 << 3)
|
||||
#define HOST_OHCICTRL_APPSTARTCLK (0x1 << 2)
|
||||
#define HOST_OHCICTRL_CNTSEL (0x1 << 1)
|
||||
#define HOST_OHCICTRL_CLKCKTRST (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_PHY_OTG_SYS (0x38)
|
||||
|
||||
#define OTG_SYS_PHYLINK_SWRESET (0x1 << 14)
|
||||
#define OTG_SYS_LINKSWRST_UOTG (0x1 << 13)
|
||||
#define OTG_SYS_PHY0_SWRST (0x1 << 12)
|
||||
|
||||
#define OTG_SYS_REFCLKSEL_MASK (0x3 << 9)
|
||||
#define OTG_SYS_REFCLKSEL_XTAL (0x0 << 9)
|
||||
#define OTG_SYS_REFCLKSEL_EXTL (0x1 << 9)
|
||||
#define OTG_SYS_REFCLKSEL_CLKCORE (0x2 << 9)
|
||||
|
||||
#define OTG_SYS_IDPULLUP_UOTG (0x1 << 8)
|
||||
#define OTG_SYS_COMMON_ON (0x1 << 7)
|
||||
|
||||
#define OTG_SYS_FSEL_MASK (0x7 << 4)
|
||||
#define OTG_SYS_FSEL(_x) ((_x) << 4)
|
||||
|
||||
#define OTG_SYS_FORCESLEEP (0x1 << 3)
|
||||
#define OTG_SYS_OTGDISABLE (0x1 << 2)
|
||||
#define OTG_SYS_SIDDQ_UOTG (0x1 << 1)
|
||||
#define OTG_SYS_FORCESUSPEND (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_PHY_OTG_TUNE (0x40)
|
||||
|
||||
/* EXYNOS5: USB 3.0 DRD */
|
||||
#define EXYNOS5_DRD_LINKSYSTEM (0x04)
|
||||
|
||||
#define LINKSYSTEM_FLADJ_MASK (0x3f << 1)
|
||||
#define LINKSYSTEM_FLADJ(_x) ((_x) << 1)
|
||||
#define LINKSYSTEM_XHCI_VERSION_CONTROL (0x1 << 27)
|
||||
|
||||
#define EXYNOS5_DRD_PHYUTMI (0x08)
|
||||
|
||||
#define PHYUTMI_OTGDISABLE (0x1 << 6)
|
||||
#define PHYUTMI_FORCESUSPEND (0x1 << 1)
|
||||
#define PHYUTMI_FORCESLEEP (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_DRD_PHYPIPE (0x0c)
|
||||
|
||||
#define EXYNOS5_DRD_PHYCLKRST (0x10)
|
||||
|
||||
#define PHYCLKRST_SSC_REFCLKSEL_MASK (0xff << 23)
|
||||
#define PHYCLKRST_SSC_REFCLKSEL(_x) ((_x) << 23)
|
||||
|
||||
#define PHYCLKRST_SSC_RANGE_MASK (0x03 << 21)
|
||||
#define PHYCLKRST_SSC_RANGE(_x) ((_x) << 21)
|
||||
|
||||
#define PHYCLKRST_SSC_EN (0x1 << 20)
|
||||
#define PHYCLKRST_REF_SSP_EN (0x1 << 19)
|
||||
#define PHYCLKRST_REF_CLKDIV2 (0x1 << 18)
|
||||
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_MASK (0x7f << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF (0x19 << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF (0x02 << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF (0x68 << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF (0x7d << 11)
|
||||
#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF (0x02 << 11)
|
||||
|
||||
#define PHYCLKRST_FSEL_MASK (0x3f << 5)
|
||||
#define PHYCLKRST_FSEL(_x) ((_x) << 5)
|
||||
#define PHYCLKRST_FSEL_PAD_100MHZ (0x27 << 5)
|
||||
#define PHYCLKRST_FSEL_PAD_24MHZ (0x2a << 5)
|
||||
#define PHYCLKRST_FSEL_PAD_20MHZ (0x31 << 5)
|
||||
#define PHYCLKRST_FSEL_PAD_19_2MHZ (0x38 << 5)
|
||||
|
||||
#define PHYCLKRST_RETENABLEN (0x1 << 4)
|
||||
|
||||
#define PHYCLKRST_REFCLKSEL_MASK (0x03 << 2)
|
||||
#define PHYCLKRST_REFCLKSEL_PAD_REFCLK (0x2 << 2)
|
||||
#define PHYCLKRST_REFCLKSEL_EXT_REFCLK (0x3 << 2)
|
||||
|
||||
#define PHYCLKRST_PORTRESET (0x1 << 1)
|
||||
#define PHYCLKRST_COMMONONN (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_DRD_PHYREG0 (0x14)
|
||||
#define EXYNOS5_DRD_PHYREG1 (0x18)
|
||||
|
||||
#define EXYNOS5_DRD_PHYPARAM0 (0x1c)
|
||||
|
||||
#define PHYPARAM0_REF_USE_PAD (0x1 << 31)
|
||||
#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26)
|
||||
#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26)
|
||||
|
||||
#define EXYNOS5_DRD_PHYPARAM1 (0x20)
|
||||
|
||||
#define PHYPARAM1_PCS_TXDEEMPH_MASK (0x1f << 0)
|
||||
#define PHYPARAM1_PCS_TXDEEMPH (0x1c)
|
||||
|
||||
#define EXYNOS5_DRD_PHYTERM (0x24)
|
||||
|
||||
#define EXYNOS5_DRD_PHYTEST (0x28)
|
||||
|
||||
#define PHYTEST_POWERDOWN_SSP (0x1 << 3)
|
||||
#define PHYTEST_POWERDOWN_HSP (0x1 << 2)
|
||||
|
||||
#define EXYNOS5_DRD_PHYADP (0x2c)
|
||||
|
||||
#define EXYNOS5_DRD_PHYBATCHG (0x30)
|
||||
|
||||
#define PHYBATCHG_UTMI_CLKSEL (0x1 << 2)
|
||||
|
||||
#define EXYNOS5_DRD_PHYRESUME (0x34)
|
||||
#define EXYNOS5_DRD_LINKPORT (0x44)
|
||||
|
||||
#ifndef MHZ
|
||||
#define MHZ (1000*1000)
|
||||
#endif
|
||||
|
||||
#ifndef KHZ
|
||||
#define KHZ (1000)
|
||||
#endif
|
||||
|
||||
#define EXYNOS_USBHOST_PHY_CTRL_OFFSET (0x4)
|
||||
#define S3C64XX_USBPHY_ENABLE (0x1 << 16)
|
||||
#define EXYNOS_USBPHY_ENABLE (0x1 << 0)
|
||||
#define EXYNOS_USB20PHY_CFG_HOST_LINK (0x1 << 0)
|
||||
|
||||
enum samsung_cpu_type {
|
||||
TYPE_S3C64XX,
|
||||
TYPE_EXYNOS4210,
|
||||
TYPE_EXYNOS5250,
|
||||
};
|
||||
|
||||
/*
|
||||
* struct samsung_usbphy_drvdata - driver data for various SoC variants
|
||||
* @cpu_type: machine identifier
|
||||
* @devphy_en_mask: device phy enable mask for PHY CONTROL register
|
||||
* @hostphy_en_mask: host phy enable mask for PHY CONTROL register
|
||||
* @devphy_reg_offset: offset to DEVICE PHY CONTROL register from
|
||||
* mapped address of system controller.
|
||||
* @hostphy_reg_offset: offset to HOST PHY CONTROL register from
|
||||
* mapped address of system controller.
|
||||
*
|
||||
* Here we have a separate mask for device type phy.
|
||||
* Having different masks for host and device type phy helps
|
||||
* in setting independent masks in case of SoCs like S5PV210,
|
||||
* in which PHY0 and PHY1 enable bits belong to same register
|
||||
* placed at position 0 and 1 respectively.
|
||||
* Although for newer SoCs like exynos these bits belong to
|
||||
* different registers altogether placed at position 0.
|
||||
*/
|
||||
struct samsung_usbphy_drvdata {
|
||||
int cpu_type;
|
||||
int devphy_en_mask;
|
||||
int hostphy_en_mask;
|
||||
u32 devphy_reg_offset;
|
||||
u32 hostphy_reg_offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct samsung_usbphy - transceiver driver state
|
||||
* @phy: transceiver structure
|
||||
* @plat: platform data
|
||||
* @dev: The parent device supplied to the probe function
|
||||
* @clk: usb phy clock
|
||||
* @regs: usb phy controller registers memory base
|
||||
* @pmuregs: USB device PHY_CONTROL register memory base
|
||||
* @sysreg: USB2.0 PHY_CFG register memory base
|
||||
* @ref_clk_freq: reference clock frequency selection
|
||||
* @drv_data: driver data available for different SoCs
|
||||
* @phy_type: Samsung SoCs specific phy types: #HOST
|
||||
* #DEVICE
|
||||
* @phy_usage: usage count for phy
|
||||
* @lock: lock for phy operations
|
||||
*/
|
||||
struct samsung_usbphy {
|
||||
struct usb_phy phy;
|
||||
struct samsung_usbphy_data *plat;
|
||||
struct device *dev;
|
||||
struct clk *clk;
|
||||
void __iomem *regs;
|
||||
void __iomem *pmuregs;
|
||||
void __iomem *sysreg;
|
||||
int ref_clk_freq;
|
||||
const struct samsung_usbphy_drvdata *drv_data;
|
||||
enum samsung_usb_phy_type phy_type;
|
||||
atomic_t phy_usage;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
#define phy_to_sphy(x) container_of((x), struct samsung_usbphy, phy)
|
||||
|
||||
static const struct of_device_id samsung_usbphy_dt_match[];
|
||||
|
||||
static inline const struct samsung_usbphy_drvdata
|
||||
*samsung_usbphy_get_driver_data(struct platform_device *pdev)
|
||||
{
|
||||
if (pdev->dev.of_node) {
|
||||
const struct of_device_id *match;
|
||||
match = of_match_node(samsung_usbphy_dt_match,
|
||||
pdev->dev.of_node);
|
||||
return match->data;
|
||||
}
|
||||
|
||||
return (struct samsung_usbphy_drvdata *)
|
||||
platform_get_device_id(pdev)->driver_data;
|
||||
}
|
||||
|
||||
extern int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy);
|
||||
extern void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on);
|
||||
extern void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy);
|
||||
extern int samsung_usbphy_set_type(struct usb_phy *phy,
|
||||
enum samsung_usb_phy_type phy_type);
|
||||
extern int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy);
|
509
drivers/usb/phy/phy-samsung-usb2.c
Normal file
509
drivers/usb/phy/phy-samsung-usb2.c
Normal file
@@ -0,0 +1,509 @@
|
||||
/* linux/drivers/usb/phy/phy-samsung-usb2.c
|
||||
*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Praveen Paneri <p.paneri@samsung.com>
|
||||
*
|
||||
* Samsung USB2.0 PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and
|
||||
* OHCI-EXYNOS controllers.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/samsung_usb_phy.h>
|
||||
#include <linux/platform_data/samsung-usbphy.h>
|
||||
|
||||
#include "phy-samsung-usb.h"
|
||||
|
||||
static int samsung_usbphy_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
if (!otg->host)
|
||||
otg->host = host;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool exynos5_phyhost_is_on(void __iomem *regs)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = readl(regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
|
||||
return !(reg & HOST_CTRL0_SIDDQ);
|
||||
}
|
||||
|
||||
static void samsung_exynos5_usb2phy_enable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
void __iomem *regs = sphy->regs;
|
||||
u32 phyclk = sphy->ref_clk_freq;
|
||||
u32 phyhost;
|
||||
u32 phyotg;
|
||||
u32 phyhsic;
|
||||
u32 ehcictrl;
|
||||
u32 ohcictrl;
|
||||
|
||||
/*
|
||||
* phy_usage helps in keeping usage count for phy
|
||||
* so that the first consumer enabling the phy is also
|
||||
* the last consumer to disable it.
|
||||
*/
|
||||
|
||||
atomic_inc(&sphy->phy_usage);
|
||||
|
||||
if (exynos5_phyhost_is_on(regs)) {
|
||||
dev_info(sphy->dev, "Already power on PHY\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Host configuration */
|
||||
phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
|
||||
/* phy reference clock configuration */
|
||||
phyhost &= ~HOST_CTRL0_FSEL_MASK;
|
||||
phyhost |= HOST_CTRL0_FSEL(phyclk);
|
||||
|
||||
/* host phy reset */
|
||||
phyhost &= ~(HOST_CTRL0_PHYSWRST |
|
||||
HOST_CTRL0_PHYSWRSTALL |
|
||||
HOST_CTRL0_SIDDQ |
|
||||
/* Enable normal mode of operation */
|
||||
HOST_CTRL0_FORCESUSPEND |
|
||||
HOST_CTRL0_FORCESLEEP);
|
||||
|
||||
/* Link reset */
|
||||
phyhost |= (HOST_CTRL0_LINKSWRST |
|
||||
HOST_CTRL0_UTMISWRST |
|
||||
/* COMMON Block configuration during suspend */
|
||||
HOST_CTRL0_COMMONON_N);
|
||||
writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
udelay(10);
|
||||
phyhost &= ~(HOST_CTRL0_LINKSWRST |
|
||||
HOST_CTRL0_UTMISWRST);
|
||||
writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
|
||||
/* OTG configuration */
|
||||
phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS);
|
||||
|
||||
/* phy reference clock configuration */
|
||||
phyotg &= ~OTG_SYS_FSEL_MASK;
|
||||
phyotg |= OTG_SYS_FSEL(phyclk);
|
||||
|
||||
/* Enable normal mode of operation */
|
||||
phyotg &= ~(OTG_SYS_FORCESUSPEND |
|
||||
OTG_SYS_SIDDQ_UOTG |
|
||||
OTG_SYS_FORCESLEEP |
|
||||
OTG_SYS_REFCLKSEL_MASK |
|
||||
/* COMMON Block configuration during suspend */
|
||||
OTG_SYS_COMMON_ON);
|
||||
|
||||
/* OTG phy & link reset */
|
||||
phyotg |= (OTG_SYS_PHY0_SWRST |
|
||||
OTG_SYS_LINKSWRST_UOTG |
|
||||
OTG_SYS_PHYLINK_SWRESET |
|
||||
OTG_SYS_OTGDISABLE |
|
||||
/* Set phy refclk */
|
||||
OTG_SYS_REFCLKSEL_CLKCORE);
|
||||
|
||||
writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS);
|
||||
udelay(10);
|
||||
phyotg &= ~(OTG_SYS_PHY0_SWRST |
|
||||
OTG_SYS_LINKSWRST_UOTG |
|
||||
OTG_SYS_PHYLINK_SWRESET);
|
||||
writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS);
|
||||
|
||||
/* HSIC phy configuration */
|
||||
phyhsic = (HSIC_CTRL_REFCLKDIV_12 |
|
||||
HSIC_CTRL_REFCLKSEL |
|
||||
HSIC_CTRL_PHYSWRST);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2);
|
||||
udelay(10);
|
||||
phyhsic &= ~HSIC_CTRL_PHYSWRST;
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2);
|
||||
|
||||
udelay(80);
|
||||
|
||||
/* enable EHCI DMA burst */
|
||||
ehcictrl = readl(regs + EXYNOS5_PHY_HOST_EHCICTRL);
|
||||
ehcictrl |= (HOST_EHCICTRL_ENAINCRXALIGN |
|
||||
HOST_EHCICTRL_ENAINCR4 |
|
||||
HOST_EHCICTRL_ENAINCR8 |
|
||||
HOST_EHCICTRL_ENAINCR16);
|
||||
writel(ehcictrl, regs + EXYNOS5_PHY_HOST_EHCICTRL);
|
||||
|
||||
/* set ohci_suspend_on_n */
|
||||
ohcictrl = readl(regs + EXYNOS5_PHY_HOST_OHCICTRL);
|
||||
ohcictrl |= HOST_OHCICTRL_SUSPLGCY;
|
||||
writel(ohcictrl, regs + EXYNOS5_PHY_HOST_OHCICTRL);
|
||||
}
|
||||
|
||||
static void samsung_usb2phy_enable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
void __iomem *regs = sphy->regs;
|
||||
u32 phypwr;
|
||||
u32 phyclk;
|
||||
u32 rstcon;
|
||||
|
||||
/* set clock frequency for PLL */
|
||||
phyclk = sphy->ref_clk_freq;
|
||||
phypwr = readl(regs + SAMSUNG_PHYPWR);
|
||||
rstcon = readl(regs + SAMSUNG_RSTCON);
|
||||
|
||||
switch (sphy->drv_data->cpu_type) {
|
||||
case TYPE_S3C64XX:
|
||||
phyclk &= ~PHYCLK_COMMON_ON_N;
|
||||
phypwr &= ~PHYPWR_NORMAL_MASK;
|
||||
rstcon |= RSTCON_SWRST;
|
||||
break;
|
||||
case TYPE_EXYNOS4210:
|
||||
phypwr &= ~PHYPWR_NORMAL_MASK_PHY0;
|
||||
rstcon |= RSTCON_SWRST;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
writel(phyclk, regs + SAMSUNG_PHYCLK);
|
||||
/* Configure PHY0 for normal operation*/
|
||||
writel(phypwr, regs + SAMSUNG_PHYPWR);
|
||||
/* reset all ports of PHY and Link */
|
||||
writel(rstcon, regs + SAMSUNG_RSTCON);
|
||||
udelay(10);
|
||||
rstcon &= ~RSTCON_SWRST;
|
||||
writel(rstcon, regs + SAMSUNG_RSTCON);
|
||||
}
|
||||
|
||||
static void samsung_exynos5_usb2phy_disable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
void __iomem *regs = sphy->regs;
|
||||
u32 phyhost;
|
||||
u32 phyotg;
|
||||
u32 phyhsic;
|
||||
|
||||
if (atomic_dec_return(&sphy->phy_usage) > 0) {
|
||||
dev_info(sphy->dev, "still being used\n");
|
||||
return;
|
||||
}
|
||||
|
||||
phyhsic = (HSIC_CTRL_REFCLKDIV_12 |
|
||||
HSIC_CTRL_REFCLKSEL |
|
||||
HSIC_CTRL_SIDDQ |
|
||||
HSIC_CTRL_FORCESLEEP |
|
||||
HSIC_CTRL_FORCESUSPEND);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2);
|
||||
|
||||
phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
phyhost |= (HOST_CTRL0_SIDDQ |
|
||||
HOST_CTRL0_FORCESUSPEND |
|
||||
HOST_CTRL0_FORCESLEEP |
|
||||
HOST_CTRL0_PHYSWRST |
|
||||
HOST_CTRL0_PHYSWRSTALL);
|
||||
writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
|
||||
phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS);
|
||||
phyotg |= (OTG_SYS_FORCESUSPEND |
|
||||
OTG_SYS_SIDDQ_UOTG |
|
||||
OTG_SYS_FORCESLEEP);
|
||||
writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS);
|
||||
}
|
||||
|
||||
static void samsung_usb2phy_disable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
void __iomem *regs = sphy->regs;
|
||||
u32 phypwr;
|
||||
|
||||
phypwr = readl(regs + SAMSUNG_PHYPWR);
|
||||
|
||||
switch (sphy->drv_data->cpu_type) {
|
||||
case TYPE_S3C64XX:
|
||||
phypwr |= PHYPWR_NORMAL_MASK;
|
||||
break;
|
||||
case TYPE_EXYNOS4210:
|
||||
phypwr |= PHYPWR_NORMAL_MASK_PHY0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Disable analog and otg block power */
|
||||
writel(phypwr, regs + SAMSUNG_PHYPWR);
|
||||
}
|
||||
|
||||
/*
|
||||
* The function passed to the usb driver for phy initialization
|
||||
*/
|
||||
static int samsung_usb2phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct samsung_usbphy *sphy;
|
||||
struct usb_bus *host = NULL;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
sphy = phy_to_sphy(phy);
|
||||
|
||||
host = phy->otg->host;
|
||||
|
||||
/* Enable the phy clock */
|
||||
ret = clk_prepare_enable(sphy->clk);
|
||||
if (ret) {
|
||||
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sphy->lock, flags);
|
||||
|
||||
if (host) {
|
||||
/* setting default phy-type for USB 2.0 */
|
||||
if (!strstr(dev_name(host->controller), "ehci") ||
|
||||
!strstr(dev_name(host->controller), "ohci"))
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST);
|
||||
} else {
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
|
||||
}
|
||||
|
||||
/* Disable phy isolation */
|
||||
if (sphy->plat && sphy->plat->pmu_isolation)
|
||||
sphy->plat->pmu_isolation(false);
|
||||
else
|
||||
samsung_usbphy_set_isolation(sphy, false);
|
||||
|
||||
/* Selecting Host/OTG mode; After reset USB2.0PHY_CFG: HOST */
|
||||
samsung_usbphy_cfg_sel(sphy);
|
||||
|
||||
/* Initialize usb phy registers */
|
||||
if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250)
|
||||
samsung_exynos5_usb2phy_enable(sphy);
|
||||
else
|
||||
samsung_usb2phy_enable(sphy);
|
||||
|
||||
spin_unlock_irqrestore(&sphy->lock, flags);
|
||||
|
||||
/* Disable the phy clock */
|
||||
clk_disable_unprepare(sphy->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The function passed to the usb driver for phy shutdown
|
||||
*/
|
||||
static void samsung_usb2phy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct samsung_usbphy *sphy;
|
||||
struct usb_bus *host = NULL;
|
||||
unsigned long flags;
|
||||
|
||||
sphy = phy_to_sphy(phy);
|
||||
|
||||
host = phy->otg->host;
|
||||
|
||||
if (clk_prepare_enable(sphy->clk)) {
|
||||
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sphy->lock, flags);
|
||||
|
||||
if (host) {
|
||||
/* setting default phy-type for USB 2.0 */
|
||||
if (!strstr(dev_name(host->controller), "ehci") ||
|
||||
!strstr(dev_name(host->controller), "ohci"))
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST);
|
||||
} else {
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
|
||||
}
|
||||
|
||||
/* De-initialize usb phy registers */
|
||||
if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250)
|
||||
samsung_exynos5_usb2phy_disable(sphy);
|
||||
else
|
||||
samsung_usb2phy_disable(sphy);
|
||||
|
||||
/* Enable phy isolation */
|
||||
if (sphy->plat && sphy->plat->pmu_isolation)
|
||||
sphy->plat->pmu_isolation(true);
|
||||
else
|
||||
samsung_usbphy_set_isolation(sphy, true);
|
||||
|
||||
spin_unlock_irqrestore(&sphy->lock, flags);
|
||||
|
||||
clk_disable_unprepare(sphy->clk);
|
||||
}
|
||||
|
||||
static int samsung_usb2phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct samsung_usbphy *sphy;
|
||||
struct usb_otg *otg;
|
||||
struct samsung_usbphy_data *pdata = pdev->dev.platform_data;
|
||||
const struct samsung_usbphy_drvdata *drv_data;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *phy_mem;
|
||||
void __iomem *phy_base;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!phy_mem) {
|
||||
dev_err(dev, "%s: missing mem resource\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
phy_base = devm_ioremap_resource(dev, phy_mem);
|
||||
if (IS_ERR(phy_base))
|
||||
return PTR_ERR(phy_base);
|
||||
|
||||
sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL);
|
||||
if (!sphy)
|
||||
return -ENOMEM;
|
||||
|
||||
otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL);
|
||||
if (!otg)
|
||||
return -ENOMEM;
|
||||
|
||||
drv_data = samsung_usbphy_get_driver_data(pdev);
|
||||
|
||||
if (drv_data->cpu_type == TYPE_EXYNOS5250)
|
||||
clk = devm_clk_get(dev, "usbhost");
|
||||
else
|
||||
clk = devm_clk_get(dev, "otg");
|
||||
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(dev, "Failed to get otg clock\n");
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
sphy->dev = dev;
|
||||
|
||||
if (dev->of_node) {
|
||||
ret = samsung_usbphy_parse_dt(sphy);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
if (!pdata) {
|
||||
dev_err(dev, "no platform data specified\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
sphy->plat = pdata;
|
||||
sphy->regs = phy_base;
|
||||
sphy->clk = clk;
|
||||
sphy->drv_data = drv_data;
|
||||
sphy->phy.dev = sphy->dev;
|
||||
sphy->phy.label = "samsung-usb2phy";
|
||||
sphy->phy.init = samsung_usb2phy_init;
|
||||
sphy->phy.shutdown = samsung_usb2phy_shutdown;
|
||||
sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy);
|
||||
|
||||
sphy->phy.otg = otg;
|
||||
sphy->phy.otg->phy = &sphy->phy;
|
||||
sphy->phy.otg->set_host = samsung_usbphy_set_host;
|
||||
|
||||
spin_lock_init(&sphy->lock);
|
||||
|
||||
platform_set_drvdata(pdev, sphy);
|
||||
|
||||
return usb_add_phy(&sphy->phy, USB_PHY_TYPE_USB2);
|
||||
}
|
||||
|
||||
static int samsung_usb2phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct samsung_usbphy *sphy = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&sphy->phy);
|
||||
|
||||
if (sphy->pmuregs)
|
||||
iounmap(sphy->pmuregs);
|
||||
if (sphy->sysreg)
|
||||
iounmap(sphy->sysreg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct samsung_usbphy_drvdata usb2phy_s3c64xx = {
|
||||
.cpu_type = TYPE_S3C64XX,
|
||||
.devphy_en_mask = S3C64XX_USBPHY_ENABLE,
|
||||
};
|
||||
|
||||
static const struct samsung_usbphy_drvdata usb2phy_exynos4 = {
|
||||
.cpu_type = TYPE_EXYNOS4210,
|
||||
.devphy_en_mask = EXYNOS_USBPHY_ENABLE,
|
||||
.hostphy_en_mask = EXYNOS_USBPHY_ENABLE,
|
||||
};
|
||||
|
||||
static struct samsung_usbphy_drvdata usb2phy_exynos5 = {
|
||||
.cpu_type = TYPE_EXYNOS5250,
|
||||
.hostphy_en_mask = EXYNOS_USBPHY_ENABLE,
|
||||
.hostphy_reg_offset = EXYNOS_USBHOST_PHY_CTRL_OFFSET,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id samsung_usbphy_dt_match[] = {
|
||||
{
|
||||
.compatible = "samsung,s3c64xx-usb2phy",
|
||||
.data = &usb2phy_s3c64xx,
|
||||
}, {
|
||||
.compatible = "samsung,exynos4210-usb2phy",
|
||||
.data = &usb2phy_exynos4,
|
||||
}, {
|
||||
.compatible = "samsung,exynos5250-usb2phy",
|
||||
.data = &usb2phy_exynos5
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match);
|
||||
#endif
|
||||
|
||||
static struct platform_device_id samsung_usbphy_driver_ids[] = {
|
||||
{
|
||||
.name = "s3c64xx-usb2phy",
|
||||
.driver_data = (unsigned long)&usb2phy_s3c64xx,
|
||||
}, {
|
||||
.name = "exynos4210-usb2phy",
|
||||
.driver_data = (unsigned long)&usb2phy_exynos4,
|
||||
}, {
|
||||
.name = "exynos5250-usb2phy",
|
||||
.driver_data = (unsigned long)&usb2phy_exynos5,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids);
|
||||
|
||||
static struct platform_driver samsung_usb2phy_driver = {
|
||||
.probe = samsung_usb2phy_probe,
|
||||
.remove = samsung_usb2phy_remove,
|
||||
.id_table = samsung_usbphy_driver_ids,
|
||||
.driver = {
|
||||
.name = "samsung-usb2phy",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(samsung_usbphy_dt_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(samsung_usb2phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Samsung USB 2.0 phy controller");
|
||||
MODULE_AUTHOR("Praveen Paneri <p.paneri@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:samsung-usb2phy");
|
347
drivers/usb/phy/phy-samsung-usb3.c
Normal file
347
drivers/usb/phy/phy-samsung-usb3.c
Normal file
@@ -0,0 +1,347 @@
|
||||
/* linux/drivers/usb/phy/phy-samsung-usb3.c
|
||||
*
|
||||
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Vivek Gautam <gautam.vivek@samsung.com>
|
||||
*
|
||||
* Samsung USB 3.0 PHY transceiver; talks to DWC3 controller.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/usb/samsung_usb_phy.h>
|
||||
#include <linux/platform_data/samsung-usbphy.h>
|
||||
|
||||
#include "phy-samsung-usb.h"
|
||||
|
||||
/*
|
||||
* Sets the phy clk as EXTREFCLK (XXTI) which is internal clock from clock core.
|
||||
*/
|
||||
static u32 samsung_usb3phy_set_refclk(struct samsung_usbphy *sphy)
|
||||
{
|
||||
u32 reg;
|
||||
u32 refclk;
|
||||
|
||||
refclk = sphy->ref_clk_freq;
|
||||
|
||||
reg = PHYCLKRST_REFCLKSEL_EXT_REFCLK |
|
||||
PHYCLKRST_FSEL(refclk);
|
||||
|
||||
switch (refclk) {
|
||||
case FSEL_CLKSEL_50M:
|
||||
reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF |
|
||||
PHYCLKRST_SSC_REFCLKSEL(0x00));
|
||||
break;
|
||||
case FSEL_CLKSEL_20M:
|
||||
reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF |
|
||||
PHYCLKRST_SSC_REFCLKSEL(0x00));
|
||||
break;
|
||||
case FSEL_CLKSEL_19200K:
|
||||
reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF |
|
||||
PHYCLKRST_SSC_REFCLKSEL(0x88));
|
||||
break;
|
||||
case FSEL_CLKSEL_24M:
|
||||
default:
|
||||
reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF |
|
||||
PHYCLKRST_SSC_REFCLKSEL(0x88));
|
||||
break;
|
||||
}
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
static int samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
void __iomem *regs = sphy->regs;
|
||||
u32 phyparam0;
|
||||
u32 phyparam1;
|
||||
u32 linksystem;
|
||||
u32 phybatchg;
|
||||
u32 phytest;
|
||||
u32 phyclkrst;
|
||||
|
||||
/* Reset USB 3.0 PHY */
|
||||
writel(0x0, regs + EXYNOS5_DRD_PHYREG0);
|
||||
|
||||
phyparam0 = readl(regs + EXYNOS5_DRD_PHYPARAM0);
|
||||
/* Select PHY CLK source */
|
||||
phyparam0 &= ~PHYPARAM0_REF_USE_PAD;
|
||||
/* Set Loss-of-Signal Detector sensitivity */
|
||||
phyparam0 &= ~PHYPARAM0_REF_LOSLEVEL_MASK;
|
||||
phyparam0 |= PHYPARAM0_REF_LOSLEVEL;
|
||||
writel(phyparam0, regs + EXYNOS5_DRD_PHYPARAM0);
|
||||
|
||||
writel(0x0, regs + EXYNOS5_DRD_PHYRESUME);
|
||||
|
||||
/*
|
||||
* Setting the Frame length Adj value[6:1] to default 0x20
|
||||
* See xHCI 1.0 spec, 5.2.4
|
||||
*/
|
||||
linksystem = LINKSYSTEM_XHCI_VERSION_CONTROL |
|
||||
LINKSYSTEM_FLADJ(0x20);
|
||||
writel(linksystem, regs + EXYNOS5_DRD_LINKSYSTEM);
|
||||
|
||||
phyparam1 = readl(regs + EXYNOS5_DRD_PHYPARAM1);
|
||||
/* Set Tx De-Emphasis level */
|
||||
phyparam1 &= ~PHYPARAM1_PCS_TXDEEMPH_MASK;
|
||||
phyparam1 |= PHYPARAM1_PCS_TXDEEMPH;
|
||||
writel(phyparam1, regs + EXYNOS5_DRD_PHYPARAM1);
|
||||
|
||||
phybatchg = readl(regs + EXYNOS5_DRD_PHYBATCHG);
|
||||
phybatchg |= PHYBATCHG_UTMI_CLKSEL;
|
||||
writel(phybatchg, regs + EXYNOS5_DRD_PHYBATCHG);
|
||||
|
||||
/* PHYTEST POWERDOWN Control */
|
||||
phytest = readl(regs + EXYNOS5_DRD_PHYTEST);
|
||||
phytest &= ~(PHYTEST_POWERDOWN_SSP |
|
||||
PHYTEST_POWERDOWN_HSP);
|
||||
writel(phytest, regs + EXYNOS5_DRD_PHYTEST);
|
||||
|
||||
/* UTMI Power Control */
|
||||
writel(PHYUTMI_OTGDISABLE, regs + EXYNOS5_DRD_PHYUTMI);
|
||||
|
||||
phyclkrst = samsung_usb3phy_set_refclk(sphy);
|
||||
|
||||
phyclkrst |= PHYCLKRST_PORTRESET |
|
||||
/* Digital power supply in normal operating mode */
|
||||
PHYCLKRST_RETENABLEN |
|
||||
/* Enable ref clock for SS function */
|
||||
PHYCLKRST_REF_SSP_EN |
|
||||
/* Enable spread spectrum */
|
||||
PHYCLKRST_SSC_EN |
|
||||
/* Power down HS Bias and PLL blocks in suspend mode */
|
||||
PHYCLKRST_COMMONONN;
|
||||
|
||||
writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST);
|
||||
|
||||
udelay(10);
|
||||
|
||||
phyclkrst &= ~(PHYCLKRST_PORTRESET);
|
||||
writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void samsung_exynos5_usb3phy_disable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
u32 phyutmi;
|
||||
u32 phyclkrst;
|
||||
u32 phytest;
|
||||
void __iomem *regs = sphy->regs;
|
||||
|
||||
phyutmi = PHYUTMI_OTGDISABLE |
|
||||
PHYUTMI_FORCESUSPEND |
|
||||
PHYUTMI_FORCESLEEP;
|
||||
writel(phyutmi, regs + EXYNOS5_DRD_PHYUTMI);
|
||||
|
||||
/* Resetting the PHYCLKRST enable bits to reduce leakage current */
|
||||
phyclkrst = readl(regs + EXYNOS5_DRD_PHYCLKRST);
|
||||
phyclkrst &= ~(PHYCLKRST_REF_SSP_EN |
|
||||
PHYCLKRST_SSC_EN |
|
||||
PHYCLKRST_COMMONONN);
|
||||
writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST);
|
||||
|
||||
/* Control PHYTEST to remove leakage current */
|
||||
phytest = readl(regs + EXYNOS5_DRD_PHYTEST);
|
||||
phytest |= (PHYTEST_POWERDOWN_SSP |
|
||||
PHYTEST_POWERDOWN_HSP);
|
||||
writel(phytest, regs + EXYNOS5_DRD_PHYTEST);
|
||||
}
|
||||
|
||||
static int samsung_usb3phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct samsung_usbphy *sphy;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
sphy = phy_to_sphy(phy);
|
||||
|
||||
/* Enable the phy clock */
|
||||
ret = clk_prepare_enable(sphy->clk);
|
||||
if (ret) {
|
||||
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sphy->lock, flags);
|
||||
|
||||
/* setting default phy-type for USB 3.0 */
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
|
||||
|
||||
/* Disable phy isolation */
|
||||
samsung_usbphy_set_isolation(sphy, false);
|
||||
|
||||
/* Initialize usb phy registers */
|
||||
samsung_exynos5_usb3phy_enable(sphy);
|
||||
|
||||
spin_unlock_irqrestore(&sphy->lock, flags);
|
||||
|
||||
/* Disable the phy clock */
|
||||
clk_disable_unprepare(sphy->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The function passed to the usb driver for phy shutdown
|
||||
*/
|
||||
static void samsung_usb3phy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct samsung_usbphy *sphy;
|
||||
unsigned long flags;
|
||||
|
||||
sphy = phy_to_sphy(phy);
|
||||
|
||||
if (clk_prepare_enable(sphy->clk)) {
|
||||
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sphy->lock, flags);
|
||||
|
||||
/* setting default phy-type for USB 3.0 */
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
|
||||
|
||||
/* De-initialize usb phy registers */
|
||||
samsung_exynos5_usb3phy_disable(sphy);
|
||||
|
||||
/* Enable phy isolation */
|
||||
samsung_usbphy_set_isolation(sphy, true);
|
||||
|
||||
spin_unlock_irqrestore(&sphy->lock, flags);
|
||||
|
||||
clk_disable_unprepare(sphy->clk);
|
||||
}
|
||||
|
||||
static int samsung_usb3phy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct samsung_usbphy *sphy;
|
||||
struct samsung_usbphy_data *pdata = pdev->dev.platform_data;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *phy_mem;
|
||||
void __iomem *phy_base;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!phy_mem) {
|
||||
dev_err(dev, "%s: missing mem resource\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
phy_base = devm_ioremap_resource(dev, phy_mem);
|
||||
if (IS_ERR(phy_base))
|
||||
return PTR_ERR(phy_base);
|
||||
|
||||
sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL);
|
||||
if (!sphy)
|
||||
return -ENOMEM;
|
||||
|
||||
clk = devm_clk_get(dev, "usbdrd30");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(dev, "Failed to get device clock\n");
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
sphy->dev = dev;
|
||||
|
||||
if (dev->of_node) {
|
||||
ret = samsung_usbphy_parse_dt(sphy);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
if (!pdata) {
|
||||
dev_err(dev, "no platform data specified\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
sphy->plat = pdata;
|
||||
sphy->regs = phy_base;
|
||||
sphy->clk = clk;
|
||||
sphy->phy.dev = sphy->dev;
|
||||
sphy->phy.label = "samsung-usb3phy";
|
||||
sphy->phy.init = samsung_usb3phy_init;
|
||||
sphy->phy.shutdown = samsung_usb3phy_shutdown;
|
||||
sphy->drv_data = samsung_usbphy_get_driver_data(pdev);
|
||||
sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy);
|
||||
|
||||
spin_lock_init(&sphy->lock);
|
||||
|
||||
platform_set_drvdata(pdev, sphy);
|
||||
|
||||
return usb_add_phy(&sphy->phy, USB_PHY_TYPE_USB3);
|
||||
}
|
||||
|
||||
static int samsung_usb3phy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct samsung_usbphy *sphy = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&sphy->phy);
|
||||
|
||||
if (sphy->pmuregs)
|
||||
iounmap(sphy->pmuregs);
|
||||
if (sphy->sysreg)
|
||||
iounmap(sphy->sysreg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct samsung_usbphy_drvdata usb3phy_exynos5 = {
|
||||
.cpu_type = TYPE_EXYNOS5250,
|
||||
.devphy_en_mask = EXYNOS_USBPHY_ENABLE,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id samsung_usbphy_dt_match[] = {
|
||||
{
|
||||
.compatible = "samsung,exynos5250-usb3phy",
|
||||
.data = &usb3phy_exynos5
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match);
|
||||
#endif
|
||||
|
||||
static struct platform_device_id samsung_usbphy_driver_ids[] = {
|
||||
{
|
||||
.name = "exynos5250-usb3phy",
|
||||
.driver_data = (unsigned long)&usb3phy_exynos5,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids);
|
||||
|
||||
static struct platform_driver samsung_usb3phy_driver = {
|
||||
.probe = samsung_usb3phy_probe,
|
||||
.remove = samsung_usb3phy_remove,
|
||||
.id_table = samsung_usbphy_driver_ids,
|
||||
.driver = {
|
||||
.name = "samsung-usb3phy",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(samsung_usbphy_dt_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(samsung_usb3phy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Samsung USB 3.0 phy controller");
|
||||
MODULE_AUTHOR("Vivek Gautam <gautam.vivek@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:samsung-usb3phy");
|
794
drivers/usb/phy/phy-twl4030-usb.c
Normal file
794
drivers/usb/phy/phy-twl4030-usb.c
Normal file
@@ -0,0 +1,794 @@
|
||||
/*
|
||||
* twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller
|
||||
*
|
||||
* Copyright (C) 2004-2007 Texas Instruments
|
||||
* Copyright (C) 2008 Nokia Corporation
|
||||
* Contact: Felipe Balbi <felipe.balbi@nokia.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:
|
||||
* - HS USB ULPI mode works.
|
||||
* - 3-pin mode support may be added in future.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/musb-omap.h>
|
||||
#include <linux/usb/ulpi.h>
|
||||
#include <linux/i2c/twl.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* Register defines */
|
||||
|
||||
#define MCPC_CTRL 0x30
|
||||
#define MCPC_CTRL_RTSOL (1 << 7)
|
||||
#define MCPC_CTRL_EXTSWR (1 << 6)
|
||||
#define MCPC_CTRL_EXTSWC (1 << 5)
|
||||
#define MCPC_CTRL_VOICESW (1 << 4)
|
||||
#define MCPC_CTRL_OUT64K (1 << 3)
|
||||
#define MCPC_CTRL_RTSCTSSW (1 << 2)
|
||||
#define MCPC_CTRL_HS_UART (1 << 0)
|
||||
|
||||
#define MCPC_IO_CTRL 0x33
|
||||
#define MCPC_IO_CTRL_MICBIASEN (1 << 5)
|
||||
#define MCPC_IO_CTRL_CTS_NPU (1 << 4)
|
||||
#define MCPC_IO_CTRL_RXD_PU (1 << 3)
|
||||
#define MCPC_IO_CTRL_TXDTYP (1 << 2)
|
||||
#define MCPC_IO_CTRL_CTSTYP (1 << 1)
|
||||
#define MCPC_IO_CTRL_RTSTYP (1 << 0)
|
||||
|
||||
#define MCPC_CTRL2 0x36
|
||||
#define MCPC_CTRL2_MCPC_CK_EN (1 << 0)
|
||||
|
||||
#define OTHER_FUNC_CTRL 0x80
|
||||
#define OTHER_FUNC_CTRL_BDIS_ACON_EN (1 << 4)
|
||||
#define OTHER_FUNC_CTRL_FIVEWIRE_MODE (1 << 2)
|
||||
|
||||
#define OTHER_IFC_CTRL 0x83
|
||||
#define OTHER_IFC_CTRL_OE_INT_EN (1 << 6)
|
||||
#define OTHER_IFC_CTRL_CEA2011_MODE (1 << 5)
|
||||
#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN (1 << 4)
|
||||
#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT (1 << 3)
|
||||
#define OTHER_IFC_CTRL_HIZ_ULPI (1 << 2)
|
||||
#define OTHER_IFC_CTRL_ALT_INT_REROUTE (1 << 0)
|
||||
|
||||
#define OTHER_INT_EN_RISE 0x86
|
||||
#define OTHER_INT_EN_FALL 0x89
|
||||
#define OTHER_INT_STS 0x8C
|
||||
#define OTHER_INT_LATCH 0x8D
|
||||
#define OTHER_INT_VB_SESS_VLD (1 << 7)
|
||||
#define OTHER_INT_DM_HI (1 << 6) /* not valid for "latch" reg */
|
||||
#define OTHER_INT_DP_HI (1 << 5) /* not valid for "latch" reg */
|
||||
#define OTHER_INT_BDIS_ACON (1 << 3) /* not valid for "fall" regs */
|
||||
#define OTHER_INT_MANU (1 << 1)
|
||||
#define OTHER_INT_ABNORMAL_STRESS (1 << 0)
|
||||
|
||||
#define ID_STATUS 0x96
|
||||
#define ID_RES_FLOAT (1 << 4)
|
||||
#define ID_RES_440K (1 << 3)
|
||||
#define ID_RES_200K (1 << 2)
|
||||
#define ID_RES_102K (1 << 1)
|
||||
#define ID_RES_GND (1 << 0)
|
||||
|
||||
#define POWER_CTRL 0xAC
|
||||
#define POWER_CTRL_OTG_ENAB (1 << 5)
|
||||
|
||||
#define OTHER_IFC_CTRL2 0xAF
|
||||
#define OTHER_IFC_CTRL2_ULPI_STP_LOW (1 << 4)
|
||||
#define OTHER_IFC_CTRL2_ULPI_TXEN_POL (1 << 3)
|
||||
#define OTHER_IFC_CTRL2_ULPI_4PIN_2430 (1 << 2)
|
||||
#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK (3 << 0) /* bits 0 and 1 */
|
||||
#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N (0 << 0)
|
||||
#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N (1 << 0)
|
||||
|
||||
#define REG_CTRL_EN 0xB2
|
||||
#define REG_CTRL_ERROR 0xB5
|
||||
#define ULPI_I2C_CONFLICT_INTEN (1 << 0)
|
||||
|
||||
#define OTHER_FUNC_CTRL2 0xB8
|
||||
#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN (1 << 0)
|
||||
|
||||
/* following registers do not have separate _clr and _set registers */
|
||||
#define VBUS_DEBOUNCE 0xC0
|
||||
#define ID_DEBOUNCE 0xC1
|
||||
#define VBAT_TIMER 0xD3
|
||||
#define PHY_PWR_CTRL 0xFD
|
||||
#define PHY_PWR_PHYPWD (1 << 0)
|
||||
#define PHY_CLK_CTRL 0xFE
|
||||
#define PHY_CLK_CTRL_CLOCKGATING_EN (1 << 2)
|
||||
#define PHY_CLK_CTRL_CLK32K_EN (1 << 1)
|
||||
#define REQ_PHY_DPLL_CLK (1 << 0)
|
||||
#define PHY_CLK_CTRL_STS 0xFF
|
||||
#define PHY_DPLL_CLK (1 << 0)
|
||||
|
||||
/* In module TWL_MODULE_PM_MASTER */
|
||||
#define STS_HW_CONDITIONS 0x0F
|
||||
|
||||
/* In module TWL_MODULE_PM_RECEIVER */
|
||||
#define VUSB_DEDICATED1 0x7D
|
||||
#define VUSB_DEDICATED2 0x7E
|
||||
#define VUSB1V5_DEV_GRP 0x71
|
||||
#define VUSB1V5_TYPE 0x72
|
||||
#define VUSB1V5_REMAP 0x73
|
||||
#define VUSB1V8_DEV_GRP 0x74
|
||||
#define VUSB1V8_TYPE 0x75
|
||||
#define VUSB1V8_REMAP 0x76
|
||||
#define VUSB3V1_DEV_GRP 0x77
|
||||
#define VUSB3V1_TYPE 0x78
|
||||
#define VUSB3V1_REMAP 0x79
|
||||
|
||||
/* In module TWL4030_MODULE_INTBR */
|
||||
#define PMBR1 0x0D
|
||||
#define GPIO_USB_4PIN_ULPI_2430C (3 << 0)
|
||||
|
||||
struct twl4030_usb {
|
||||
struct usb_phy phy;
|
||||
struct device *dev;
|
||||
|
||||
/* TWL4030 internal USB regulator supplies */
|
||||
struct regulator *usb1v5;
|
||||
struct regulator *usb1v8;
|
||||
struct regulator *usb3v1;
|
||||
|
||||
/* for vbus reporting with irqs disabled */
|
||||
spinlock_t lock;
|
||||
|
||||
/* pin configuration */
|
||||
enum twl4030_usb_mode usb_mode;
|
||||
|
||||
int irq;
|
||||
enum omap_musb_vbus_id_status linkstat;
|
||||
bool vbus_supplied;
|
||||
u8 asleep;
|
||||
bool irq_enabled;
|
||||
|
||||
struct delayed_work id_workaround_work;
|
||||
};
|
||||
|
||||
/* internal define on top of container_of */
|
||||
#define phy_to_twl(x) container_of((x), struct twl4030_usb, phy)
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl,
|
||||
u8 module, u8 data, u8 address)
|
||||
{
|
||||
u8 check;
|
||||
|
||||
if ((twl_i2c_write_u8(module, data, address) >= 0) &&
|
||||
(twl_i2c_read_u8(module, &check, address) >= 0) &&
|
||||
(check == data))
|
||||
return 0;
|
||||
dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n",
|
||||
1, module, address, check, data);
|
||||
|
||||
/* Failed once: Try again */
|
||||
if ((twl_i2c_write_u8(module, data, address) >= 0) &&
|
||||
(twl_i2c_read_u8(module, &check, address) >= 0) &&
|
||||
(check == data))
|
||||
return 0;
|
||||
dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n",
|
||||
2, module, address, check, data);
|
||||
|
||||
/* Failed again: Return error */
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
#define twl4030_usb_write_verify(twl, address, data) \
|
||||
twl4030_i2c_write_u8_verify(twl, TWL_MODULE_USB, (data), (address))
|
||||
|
||||
static inline int twl4030_usb_write(struct twl4030_usb *twl,
|
||||
u8 address, u8 data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = twl_i2c_write_u8(TWL_MODULE_USB, data, address);
|
||||
if (ret < 0)
|
||||
dev_dbg(twl->dev,
|
||||
"TWL4030:USB:Write[0x%x] Error %d\n", address, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address)
|
||||
{
|
||||
u8 data;
|
||||
int ret = 0;
|
||||
|
||||
ret = twl_i2c_read_u8(module, &data, address);
|
||||
if (ret >= 0)
|
||||
ret = data;
|
||||
else
|
||||
dev_dbg(twl->dev,
|
||||
"TWL4030:readb[0x%x,0x%x] Error %d\n",
|
||||
module, address, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address)
|
||||
{
|
||||
return twl4030_readb(twl, TWL_MODULE_USB, address);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static inline int
|
||||
twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits)
|
||||
{
|
||||
return twl4030_usb_write(twl, ULPI_SET(reg), bits);
|
||||
}
|
||||
|
||||
static inline int
|
||||
twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits)
|
||||
{
|
||||
return twl4030_usb_write(twl, ULPI_CLR(reg), bits);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static bool twl4030_is_driving_vbus(struct twl4030_usb *twl)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS);
|
||||
if (ret < 0 || !(ret & PHY_DPLL_CLK))
|
||||
/*
|
||||
* if clocks are off, registers are not updated,
|
||||
* but we can assume we don't drive VBUS in this case
|
||||
*/
|
||||
return false;
|
||||
|
||||
ret = twl4030_usb_read(twl, ULPI_OTG_CTRL);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
|
||||
return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false;
|
||||
}
|
||||
|
||||
static enum omap_musb_vbus_id_status
|
||||
twl4030_usb_linkstat(struct twl4030_usb *twl)
|
||||
{
|
||||
int status;
|
||||
enum omap_musb_vbus_id_status linkstat = OMAP_MUSB_UNKNOWN;
|
||||
|
||||
twl->vbus_supplied = false;
|
||||
|
||||
/*
|
||||
* For ID/VBUS sensing, see manual section 15.4.8 ...
|
||||
* except when using only battery backup power, two
|
||||
* comparators produce VBUS_PRES and ID_PRES signals,
|
||||
* which don't match docs elsewhere. But ... BIT(7)
|
||||
* and BIT(2) of STS_HW_CONDITIONS, respectively, do
|
||||
* seem to match up. If either is true the USB_PRES
|
||||
* signal is active, the OTG module is activated, and
|
||||
* its interrupt may be raised (may wake the system).
|
||||
*/
|
||||
status = twl4030_readb(twl, TWL_MODULE_PM_MASTER, STS_HW_CONDITIONS);
|
||||
if (status < 0)
|
||||
dev_err(twl->dev, "USB link status err %d\n", status);
|
||||
else if (status & (BIT(7) | BIT(2))) {
|
||||
if (status & BIT(7)) {
|
||||
if (twl4030_is_driving_vbus(twl))
|
||||
status &= ~BIT(7);
|
||||
else
|
||||
twl->vbus_supplied = true;
|
||||
}
|
||||
|
||||
if (status & BIT(2))
|
||||
linkstat = OMAP_MUSB_ID_GROUND;
|
||||
else if (status & BIT(7))
|
||||
linkstat = OMAP_MUSB_VBUS_VALID;
|
||||
else
|
||||
linkstat = OMAP_MUSB_VBUS_OFF;
|
||||
} else {
|
||||
if (twl->linkstat != OMAP_MUSB_UNKNOWN)
|
||||
linkstat = OMAP_MUSB_VBUS_OFF;
|
||||
}
|
||||
|
||||
dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n",
|
||||
status, status, linkstat);
|
||||
|
||||
/* REVISIT this assumes host and peripheral controllers
|
||||
* are registered, and that both are active...
|
||||
*/
|
||||
|
||||
return linkstat;
|
||||
}
|
||||
|
||||
static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode)
|
||||
{
|
||||
twl->usb_mode = mode;
|
||||
|
||||
switch (mode) {
|
||||
case T2_USB_MODE_ULPI:
|
||||
twl4030_usb_clear_bits(twl, ULPI_IFC_CTRL,
|
||||
ULPI_IFC_CTRL_CARKITMODE);
|
||||
twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);
|
||||
twl4030_usb_clear_bits(twl, ULPI_FUNC_CTRL,
|
||||
ULPI_FUNC_CTRL_XCVRSEL_MASK |
|
||||
ULPI_FUNC_CTRL_OPMODE_MASK);
|
||||
break;
|
||||
case -1:
|
||||
/* FIXME: power on defaults */
|
||||
break;
|
||||
default:
|
||||
dev_err(twl->dev, "unsupported T2 transceiver mode %d\n",
|
||||
mode);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
static void twl4030_i2c_access(struct twl4030_usb *twl, int on)
|
||||
{
|
||||
unsigned long timeout;
|
||||
int val = twl4030_usb_read(twl, PHY_CLK_CTRL);
|
||||
|
||||
if (val >= 0) {
|
||||
if (on) {
|
||||
/* enable DPLL to access PHY registers over I2C */
|
||||
val |= REQ_PHY_DPLL_CLK;
|
||||
WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL,
|
||||
(u8)val) < 0);
|
||||
|
||||
timeout = jiffies + HZ;
|
||||
while (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) &
|
||||
PHY_DPLL_CLK)
|
||||
&& time_before(jiffies, timeout))
|
||||
udelay(10);
|
||||
if (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) &
|
||||
PHY_DPLL_CLK))
|
||||
dev_err(twl->dev, "Timeout setting T2 HSUSB "
|
||||
"PHY DPLL clock\n");
|
||||
} else {
|
||||
/* let ULPI control the DPLL clock */
|
||||
val &= ~REQ_PHY_DPLL_CLK;
|
||||
WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL,
|
||||
(u8)val) < 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __twl4030_phy_power(struct twl4030_usb *twl, int on)
|
||||
{
|
||||
u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL);
|
||||
|
||||
if (on)
|
||||
pwr &= ~PHY_PWR_PHYPWD;
|
||||
else
|
||||
pwr |= PHY_PWR_PHYPWD;
|
||||
|
||||
WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0);
|
||||
}
|
||||
|
||||
static void twl4030_phy_power(struct twl4030_usb *twl, int on)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (on) {
|
||||
ret = regulator_enable(twl->usb3v1);
|
||||
if (ret)
|
||||
dev_err(twl->dev, "Failed to enable usb3v1\n");
|
||||
|
||||
ret = regulator_enable(twl->usb1v8);
|
||||
if (ret)
|
||||
dev_err(twl->dev, "Failed to enable usb1v8\n");
|
||||
|
||||
/*
|
||||
* Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP
|
||||
* in twl4030) resets the VUSB_DEDICATED2 register. This reset
|
||||
* enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to
|
||||
* SLEEP. We work around this by clearing the bit after usv3v1
|
||||
* is re-activated. This ensures that VUSB3V1 is really active.
|
||||
*/
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);
|
||||
|
||||
ret = regulator_enable(twl->usb1v5);
|
||||
if (ret)
|
||||
dev_err(twl->dev, "Failed to enable usb1v5\n");
|
||||
|
||||
__twl4030_phy_power(twl, 1);
|
||||
twl4030_usb_write(twl, PHY_CLK_CTRL,
|
||||
twl4030_usb_read(twl, PHY_CLK_CTRL) |
|
||||
(PHY_CLK_CTRL_CLOCKGATING_EN |
|
||||
PHY_CLK_CTRL_CLK32K_EN));
|
||||
} else {
|
||||
__twl4030_phy_power(twl, 0);
|
||||
regulator_disable(twl->usb1v5);
|
||||
regulator_disable(twl->usb1v8);
|
||||
regulator_disable(twl->usb3v1);
|
||||
}
|
||||
}
|
||||
|
||||
static void twl4030_phy_suspend(struct twl4030_usb *twl, int controller_off)
|
||||
{
|
||||
if (twl->asleep)
|
||||
return;
|
||||
|
||||
twl4030_phy_power(twl, 0);
|
||||
twl->asleep = 1;
|
||||
dev_dbg(twl->dev, "%s\n", __func__);
|
||||
}
|
||||
|
||||
static void __twl4030_phy_resume(struct twl4030_usb *twl)
|
||||
{
|
||||
twl4030_phy_power(twl, 1);
|
||||
twl4030_i2c_access(twl, 1);
|
||||
twl4030_usb_set_mode(twl, twl->usb_mode);
|
||||
if (twl->usb_mode == T2_USB_MODE_ULPI)
|
||||
twl4030_i2c_access(twl, 0);
|
||||
}
|
||||
|
||||
static void twl4030_phy_resume(struct twl4030_usb *twl)
|
||||
{
|
||||
if (!twl->asleep)
|
||||
return;
|
||||
__twl4030_phy_resume(twl);
|
||||
twl->asleep = 0;
|
||||
dev_dbg(twl->dev, "%s\n", __func__);
|
||||
|
||||
/*
|
||||
* XXX When VBUS gets driven after musb goes to A mode,
|
||||
* ID_PRES related interrupts no longer arrive, why?
|
||||
* Register itself is updated fine though, so we must poll.
|
||||
*/
|
||||
if (twl->linkstat == OMAP_MUSB_ID_GROUND) {
|
||||
cancel_delayed_work(&twl->id_workaround_work);
|
||||
schedule_delayed_work(&twl->id_workaround_work, HZ);
|
||||
}
|
||||
}
|
||||
|
||||
static int twl4030_usb_ldo_init(struct twl4030_usb *twl)
|
||||
{
|
||||
/* Enable writing to power configuration registers */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1,
|
||||
TWL4030_PM_MASTER_PROTECT_KEY);
|
||||
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2,
|
||||
TWL4030_PM_MASTER_PROTECT_KEY);
|
||||
|
||||
/* Keep VUSB3V1 LDO in sleep state until VBUS/ID change detected*/
|
||||
/*twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);*/
|
||||
|
||||
/* input to VUSB3V1 LDO is from VBAT, not VBUS */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1);
|
||||
|
||||
/* Initialize 3.1V regulator */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP);
|
||||
|
||||
twl->usb3v1 = devm_regulator_get(twl->dev, "usb3v1");
|
||||
if (IS_ERR(twl->usb3v1))
|
||||
return -ENODEV;
|
||||
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE);
|
||||
|
||||
/* Initialize 1.5V regulator */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP);
|
||||
|
||||
twl->usb1v5 = devm_regulator_get(twl->dev, "usb1v5");
|
||||
if (IS_ERR(twl->usb1v5))
|
||||
return -ENODEV;
|
||||
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE);
|
||||
|
||||
/* Initialize 1.8V regulator */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP);
|
||||
|
||||
twl->usb1v8 = devm_regulator_get(twl->dev, "usb1v8");
|
||||
if (IS_ERR(twl->usb1v8))
|
||||
return -ENODEV;
|
||||
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE);
|
||||
|
||||
/* disable access to power configuration registers */
|
||||
twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0,
|
||||
TWL4030_PM_MASTER_PROTECT_KEY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t twl4030_usb_vbus_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct twl4030_usb *twl = dev_get_drvdata(dev);
|
||||
unsigned long flags;
|
||||
int ret = -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&twl->lock, flags);
|
||||
ret = sprintf(buf, "%s\n",
|
||||
twl->vbus_supplied ? "on" : "off");
|
||||
spin_unlock_irqrestore(&twl->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL);
|
||||
|
||||
static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
|
||||
{
|
||||
struct twl4030_usb *twl = _twl;
|
||||
enum omap_musb_vbus_id_status status;
|
||||
bool status_changed = false;
|
||||
|
||||
status = twl4030_usb_linkstat(twl);
|
||||
|
||||
spin_lock_irq(&twl->lock);
|
||||
if (status >= 0 && status != twl->linkstat) {
|
||||
twl->linkstat = status;
|
||||
status_changed = true;
|
||||
}
|
||||
spin_unlock_irq(&twl->lock);
|
||||
|
||||
if (status_changed) {
|
||||
/* FIXME add a set_power() method so that B-devices can
|
||||
* configure the charger appropriately. It's not always
|
||||
* correct to consume VBUS power, and how much current to
|
||||
* consume is a function of the USB configuration chosen
|
||||
* by the host.
|
||||
*
|
||||
* REVISIT usb_gadget_vbus_connect(...) as needed, ditto
|
||||
* its disconnect() sibling, when changing to/from the
|
||||
* USB_LINK_VBUS state. musb_hdrc won't care until it
|
||||
* starts to handle softconnect right.
|
||||
*/
|
||||
omap_musb_mailbox(status);
|
||||
}
|
||||
sysfs_notify(&twl->dev->kobj, NULL, "vbus");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void twl4030_id_workaround_work(struct work_struct *work)
|
||||
{
|
||||
struct twl4030_usb *twl = container_of(work, struct twl4030_usb,
|
||||
id_workaround_work.work);
|
||||
enum omap_musb_vbus_id_status status;
|
||||
bool status_changed = false;
|
||||
|
||||
status = twl4030_usb_linkstat(twl);
|
||||
|
||||
spin_lock_irq(&twl->lock);
|
||||
if (status >= 0 && status != twl->linkstat) {
|
||||
twl->linkstat = status;
|
||||
status_changed = true;
|
||||
}
|
||||
spin_unlock_irq(&twl->lock);
|
||||
|
||||
if (status_changed) {
|
||||
dev_dbg(twl->dev, "handle missing status change to %d\n",
|
||||
status);
|
||||
omap_musb_mailbox(status);
|
||||
}
|
||||
|
||||
/* don't schedule during sleep - irq works right then */
|
||||
if (status == OMAP_MUSB_ID_GROUND && !twl->asleep) {
|
||||
cancel_delayed_work(&twl->id_workaround_work);
|
||||
schedule_delayed_work(&twl->id_workaround_work, HZ);
|
||||
}
|
||||
}
|
||||
|
||||
static int twl4030_usb_phy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct twl4030_usb *twl = phy_to_twl(phy);
|
||||
enum omap_musb_vbus_id_status status;
|
||||
|
||||
/*
|
||||
* Start in sleep state, we'll get called through set_suspend()
|
||||
* callback when musb is runtime resumed and it's time to start.
|
||||
*/
|
||||
__twl4030_phy_power(twl, 0);
|
||||
twl->asleep = 1;
|
||||
|
||||
status = twl4030_usb_linkstat(twl);
|
||||
twl->linkstat = status;
|
||||
|
||||
if (status == OMAP_MUSB_ID_GROUND || status == OMAP_MUSB_VBUS_VALID)
|
||||
omap_musb_mailbox(twl->linkstat);
|
||||
|
||||
sysfs_notify(&twl->dev->kobj, NULL, "vbus");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_set_suspend(struct usb_phy *x, int suspend)
|
||||
{
|
||||
struct twl4030_usb *twl = phy_to_twl(x);
|
||||
|
||||
if (suspend)
|
||||
twl4030_phy_suspend(twl, 1);
|
||||
else
|
||||
twl4030_phy_resume(twl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_set_peripheral(struct usb_otg *otg,
|
||||
struct usb_gadget *gadget)
|
||||
{
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
otg->gadget = gadget;
|
||||
if (!gadget)
|
||||
otg->phy->state = OTG_STATE_UNDEFINED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
otg->host = host;
|
||||
if (!host)
|
||||
otg->phy->state = OTG_STATE_UNDEFINED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_usb_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct twl4030_usb_data *pdata = pdev->dev.platform_data;
|
||||
struct twl4030_usb *twl;
|
||||
int status, err;
|
||||
struct usb_otg *otg;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
|
||||
twl = devm_kzalloc(&pdev->dev, sizeof *twl, GFP_KERNEL);
|
||||
if (!twl)
|
||||
return -ENOMEM;
|
||||
|
||||
if (np)
|
||||
of_property_read_u32(np, "usb_mode",
|
||||
(enum twl4030_usb_mode *)&twl->usb_mode);
|
||||
else if (pdata)
|
||||
twl->usb_mode = pdata->usb_mode;
|
||||
else {
|
||||
dev_err(&pdev->dev, "twl4030 initialized without pdata\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
otg = devm_kzalloc(&pdev->dev, sizeof *otg, GFP_KERNEL);
|
||||
if (!otg)
|
||||
return -ENOMEM;
|
||||
|
||||
twl->dev = &pdev->dev;
|
||||
twl->irq = platform_get_irq(pdev, 0);
|
||||
twl->vbus_supplied = false;
|
||||
twl->asleep = 1;
|
||||
twl->linkstat = OMAP_MUSB_UNKNOWN;
|
||||
|
||||
twl->phy.dev = twl->dev;
|
||||
twl->phy.label = "twl4030";
|
||||
twl->phy.otg = otg;
|
||||
twl->phy.type = USB_PHY_TYPE_USB2;
|
||||
twl->phy.set_suspend = twl4030_set_suspend;
|
||||
twl->phy.init = twl4030_usb_phy_init;
|
||||
|
||||
otg->phy = &twl->phy;
|
||||
otg->set_host = twl4030_set_host;
|
||||
otg->set_peripheral = twl4030_set_peripheral;
|
||||
|
||||
/* init spinlock for workqueue */
|
||||
spin_lock_init(&twl->lock);
|
||||
|
||||
INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work);
|
||||
|
||||
err = twl4030_usb_ldo_init(twl);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "ldo init failed\n");
|
||||
return err;
|
||||
}
|
||||
usb_add_phy_dev(&twl->phy);
|
||||
|
||||
platform_set_drvdata(pdev, twl);
|
||||
if (device_create_file(&pdev->dev, &dev_attr_vbus))
|
||||
dev_warn(&pdev->dev, "could not create sysfs file\n");
|
||||
|
||||
/* Our job is to use irqs and status from the power module
|
||||
* to keep the transceiver disabled when nothing's connected.
|
||||
*
|
||||
* FIXME we actually shouldn't start enabling it until the
|
||||
* USB controller drivers have said they're ready, by calling
|
||||
* set_host() and/or set_peripheral() ... OTG_capable boards
|
||||
* need both handles, otherwise just one suffices.
|
||||
*/
|
||||
twl->irq_enabled = true;
|
||||
status = devm_request_threaded_irq(twl->dev, twl->irq, NULL,
|
||||
twl4030_usb_irq, IRQF_TRIGGER_FALLING |
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT, "twl4030_usb", twl);
|
||||
if (status < 0) {
|
||||
dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n",
|
||||
twl->irq, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "Initialized TWL4030 USB module\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int twl4030_usb_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct twl4030_usb *twl = platform_get_drvdata(pdev);
|
||||
int val;
|
||||
|
||||
cancel_delayed_work(&twl->id_workaround_work);
|
||||
device_remove_file(twl->dev, &dev_attr_vbus);
|
||||
|
||||
/* set transceiver mode to power on defaults */
|
||||
twl4030_usb_set_mode(twl, -1);
|
||||
|
||||
/* autogate 60MHz ULPI clock,
|
||||
* clear dpll clock request for i2c access,
|
||||
* disable 32KHz
|
||||
*/
|
||||
val = twl4030_usb_read(twl, PHY_CLK_CTRL);
|
||||
if (val >= 0) {
|
||||
val |= PHY_CLK_CTRL_CLOCKGATING_EN;
|
||||
val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK);
|
||||
twl4030_usb_write(twl, PHY_CLK_CTRL, (u8)val);
|
||||
}
|
||||
|
||||
/* disable complete OTG block */
|
||||
twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);
|
||||
|
||||
if (!twl->asleep)
|
||||
twl4030_phy_power(twl, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id twl4030_usb_id_table[] = {
|
||||
{ .compatible = "ti,twl4030-usb" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, twl4030_usb_id_table);
|
||||
#endif
|
||||
|
||||
static struct platform_driver twl4030_usb_driver = {
|
||||
.probe = twl4030_usb_probe,
|
||||
.remove = twl4030_usb_remove,
|
||||
.driver = {
|
||||
.name = "twl4030_usb",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(twl4030_usb_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init twl4030_usb_init(void)
|
||||
{
|
||||
return platform_driver_register(&twl4030_usb_driver);
|
||||
}
|
||||
subsys_initcall(twl4030_usb_init);
|
||||
|
||||
static void __exit twl4030_usb_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&twl4030_usb_driver);
|
||||
}
|
||||
module_exit(twl4030_usb_exit);
|
||||
|
||||
MODULE_ALIAS("platform:twl4030_usb");
|
||||
MODULE_AUTHOR("Texas Instruments, Inc, Nokia Corporation");
|
||||
MODULE_DESCRIPTION("TWL4030 USB transceiver driver");
|
||||
MODULE_LICENSE("GPL");
|
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/usb/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>
|
||||
|
||||
/* 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 irq_enabled;
|
||||
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, ret = 0;
|
||||
|
||||
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->platform_data;
|
||||
|
||||
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 & TWL6025_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);
|
||||
|
||||
twl->irq_enabled = true;
|
||||
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");
|
80
drivers/usb/phy/phy-ulpi-viewport.c
Normal file
80
drivers/usb/phy/phy-ulpi-viewport.c
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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/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,
|
||||
};
|
283
drivers/usb/phy/phy-ulpi.c
Normal file
283
drivers/usb/phy/phy-ulpi.c
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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"),
|
||||
};
|
||||
|
||||
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);
|
||||
|
438
drivers/usb/phy/phy.c
Normal file
438
drivers/usb/phy/phy.c
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
return res == 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 NULL;
|
||||
|
||||
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_err("unable to find transceiver of type %s\n",
|
||||
usb_phy_type_string(type));
|
||||
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)) {
|
||||
pr_err("unable to find transceiver\n");
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 __init 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) {
|
||||
pr_err("phy_bind(): No memory for 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);
|
@@ -1,928 +0,0 @@
|
||||
/* linux/drivers/usb/phy/samsung-usbphy.c
|
||||
*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* Author: Praveen Paneri <p.paneri@samsung.com>
|
||||
*
|
||||
* Samsung USB2.0 PHY transceiver; talks to S3C HS OTG controller, EHCI-S5P and
|
||||
* OHCI-EXYNOS controllers.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/samsung_usb_phy.h>
|
||||
#include <linux/platform_data/samsung-usbphy.h>
|
||||
|
||||
/* Register definitions */
|
||||
|
||||
#define SAMSUNG_PHYPWR (0x00)
|
||||
|
||||
#define PHYPWR_NORMAL_MASK (0x19 << 0)
|
||||
#define PHYPWR_OTG_DISABLE (0x1 << 4)
|
||||
#define PHYPWR_ANALOG_POWERDOWN (0x1 << 3)
|
||||
#define PHYPWR_FORCE_SUSPEND (0x1 << 1)
|
||||
/* For Exynos4 */
|
||||
#define PHYPWR_NORMAL_MASK_PHY0 (0x39 << 0)
|
||||
#define PHYPWR_SLEEP_PHY0 (0x1 << 5)
|
||||
|
||||
#define SAMSUNG_PHYCLK (0x04)
|
||||
|
||||
#define PHYCLK_MODE_USB11 (0x1 << 6)
|
||||
#define PHYCLK_EXT_OSC (0x1 << 5)
|
||||
#define PHYCLK_COMMON_ON_N (0x1 << 4)
|
||||
#define PHYCLK_ID_PULL (0x1 << 2)
|
||||
#define PHYCLK_CLKSEL_MASK (0x3 << 0)
|
||||
#define PHYCLK_CLKSEL_48M (0x0 << 0)
|
||||
#define PHYCLK_CLKSEL_12M (0x2 << 0)
|
||||
#define PHYCLK_CLKSEL_24M (0x3 << 0)
|
||||
|
||||
#define SAMSUNG_RSTCON (0x08)
|
||||
|
||||
#define RSTCON_PHYLINK_SWRST (0x1 << 2)
|
||||
#define RSTCON_HLINK_SWRST (0x1 << 1)
|
||||
#define RSTCON_SWRST (0x1 << 0)
|
||||
|
||||
/* EXYNOS5 */
|
||||
#define EXYNOS5_PHY_HOST_CTRL0 (0x00)
|
||||
|
||||
#define HOST_CTRL0_PHYSWRSTALL (0x1 << 31)
|
||||
|
||||
#define HOST_CTRL0_REFCLKSEL_MASK (0x3 << 19)
|
||||
#define HOST_CTRL0_REFCLKSEL_XTAL (0x0 << 19)
|
||||
#define HOST_CTRL0_REFCLKSEL_EXTL (0x1 << 19)
|
||||
#define HOST_CTRL0_REFCLKSEL_CLKCORE (0x2 << 19)
|
||||
|
||||
#define HOST_CTRL0_FSEL_MASK (0x7 << 16)
|
||||
#define HOST_CTRL0_FSEL(_x) ((_x) << 16)
|
||||
|
||||
#define FSEL_CLKSEL_50M (0x7)
|
||||
#define FSEL_CLKSEL_24M (0x5)
|
||||
#define FSEL_CLKSEL_20M (0x4)
|
||||
#define FSEL_CLKSEL_19200K (0x3)
|
||||
#define FSEL_CLKSEL_12M (0x2)
|
||||
#define FSEL_CLKSEL_10M (0x1)
|
||||
#define FSEL_CLKSEL_9600K (0x0)
|
||||
|
||||
#define HOST_CTRL0_TESTBURNIN (0x1 << 11)
|
||||
#define HOST_CTRL0_RETENABLE (0x1 << 10)
|
||||
#define HOST_CTRL0_COMMONON_N (0x1 << 9)
|
||||
#define HOST_CTRL0_SIDDQ (0x1 << 6)
|
||||
#define HOST_CTRL0_FORCESLEEP (0x1 << 5)
|
||||
#define HOST_CTRL0_FORCESUSPEND (0x1 << 4)
|
||||
#define HOST_CTRL0_WORDINTERFACE (0x1 << 3)
|
||||
#define HOST_CTRL0_UTMISWRST (0x1 << 2)
|
||||
#define HOST_CTRL0_LINKSWRST (0x1 << 1)
|
||||
#define HOST_CTRL0_PHYSWRST (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_PHY_HOST_TUNE0 (0x04)
|
||||
|
||||
#define EXYNOS5_PHY_HSIC_CTRL1 (0x10)
|
||||
|
||||
#define EXYNOS5_PHY_HSIC_TUNE1 (0x14)
|
||||
|
||||
#define EXYNOS5_PHY_HSIC_CTRL2 (0x20)
|
||||
|
||||
#define EXYNOS5_PHY_HSIC_TUNE2 (0x24)
|
||||
|
||||
#define HSIC_CTRL_REFCLKSEL_MASK (0x3 << 23)
|
||||
#define HSIC_CTRL_REFCLKSEL (0x2 << 23)
|
||||
|
||||
#define HSIC_CTRL_REFCLKDIV_MASK (0x7f << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV(_x) ((_x) << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_12 (0x24 << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_15 (0x1c << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_16 (0x1a << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_19_2 (0x15 << 16)
|
||||
#define HSIC_CTRL_REFCLKDIV_20 (0x14 << 16)
|
||||
|
||||
#define HSIC_CTRL_SIDDQ (0x1 << 6)
|
||||
#define HSIC_CTRL_FORCESLEEP (0x1 << 5)
|
||||
#define HSIC_CTRL_FORCESUSPEND (0x1 << 4)
|
||||
#define HSIC_CTRL_WORDINTERFACE (0x1 << 3)
|
||||
#define HSIC_CTRL_UTMISWRST (0x1 << 2)
|
||||
#define HSIC_CTRL_PHYSWRST (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_PHY_HOST_EHCICTRL (0x30)
|
||||
|
||||
#define HOST_EHCICTRL_ENAINCRXALIGN (0x1 << 29)
|
||||
#define HOST_EHCICTRL_ENAINCR4 (0x1 << 28)
|
||||
#define HOST_EHCICTRL_ENAINCR8 (0x1 << 27)
|
||||
#define HOST_EHCICTRL_ENAINCR16 (0x1 << 26)
|
||||
|
||||
#define EXYNOS5_PHY_HOST_OHCICTRL (0x34)
|
||||
|
||||
#define HOST_OHCICTRL_SUSPLGCY (0x1 << 3)
|
||||
#define HOST_OHCICTRL_APPSTARTCLK (0x1 << 2)
|
||||
#define HOST_OHCICTRL_CNTSEL (0x1 << 1)
|
||||
#define HOST_OHCICTRL_CLKCKTRST (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_PHY_OTG_SYS (0x38)
|
||||
|
||||
#define OTG_SYS_PHYLINK_SWRESET (0x1 << 14)
|
||||
#define OTG_SYS_LINKSWRST_UOTG (0x1 << 13)
|
||||
#define OTG_SYS_PHY0_SWRST (0x1 << 12)
|
||||
|
||||
#define OTG_SYS_REFCLKSEL_MASK (0x3 << 9)
|
||||
#define OTG_SYS_REFCLKSEL_XTAL (0x0 << 9)
|
||||
#define OTG_SYS_REFCLKSEL_EXTL (0x1 << 9)
|
||||
#define OTG_SYS_REFCLKSEL_CLKCORE (0x2 << 9)
|
||||
|
||||
#define OTG_SYS_IDPULLUP_UOTG (0x1 << 8)
|
||||
#define OTG_SYS_COMMON_ON (0x1 << 7)
|
||||
|
||||
#define OTG_SYS_FSEL_MASK (0x7 << 4)
|
||||
#define OTG_SYS_FSEL(_x) ((_x) << 4)
|
||||
|
||||
#define OTG_SYS_FORCESLEEP (0x1 << 3)
|
||||
#define OTG_SYS_OTGDISABLE (0x1 << 2)
|
||||
#define OTG_SYS_SIDDQ_UOTG (0x1 << 1)
|
||||
#define OTG_SYS_FORCESUSPEND (0x1 << 0)
|
||||
|
||||
#define EXYNOS5_PHY_OTG_TUNE (0x40)
|
||||
|
||||
#ifndef MHZ
|
||||
#define MHZ (1000*1000)
|
||||
#endif
|
||||
|
||||
#ifndef KHZ
|
||||
#define KHZ (1000)
|
||||
#endif
|
||||
|
||||
#define EXYNOS_USBHOST_PHY_CTRL_OFFSET (0x4)
|
||||
#define S3C64XX_USBPHY_ENABLE (0x1 << 16)
|
||||
#define EXYNOS_USBPHY_ENABLE (0x1 << 0)
|
||||
#define EXYNOS_USB20PHY_CFG_HOST_LINK (0x1 << 0)
|
||||
|
||||
enum samsung_cpu_type {
|
||||
TYPE_S3C64XX,
|
||||
TYPE_EXYNOS4210,
|
||||
TYPE_EXYNOS5250,
|
||||
};
|
||||
|
||||
/*
|
||||
* struct samsung_usbphy_drvdata - driver data for various SoC variants
|
||||
* @cpu_type: machine identifier
|
||||
* @devphy_en_mask: device phy enable mask for PHY CONTROL register
|
||||
* @hostphy_en_mask: host phy enable mask for PHY CONTROL register
|
||||
* @devphy_reg_offset: offset to DEVICE PHY CONTROL register from
|
||||
* mapped address of system controller.
|
||||
* @hostphy_reg_offset: offset to HOST PHY CONTROL register from
|
||||
* mapped address of system controller.
|
||||
*
|
||||
* Here we have a separate mask for device type phy.
|
||||
* Having different masks for host and device type phy helps
|
||||
* in setting independent masks in case of SoCs like S5PV210,
|
||||
* in which PHY0 and PHY1 enable bits belong to same register
|
||||
* placed at position 0 and 1 respectively.
|
||||
* Although for newer SoCs like exynos these bits belong to
|
||||
* different registers altogether placed at position 0.
|
||||
*/
|
||||
struct samsung_usbphy_drvdata {
|
||||
int cpu_type;
|
||||
int devphy_en_mask;
|
||||
int hostphy_en_mask;
|
||||
u32 devphy_reg_offset;
|
||||
u32 hostphy_reg_offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct samsung_usbphy - transceiver driver state
|
||||
* @phy: transceiver structure
|
||||
* @plat: platform data
|
||||
* @dev: The parent device supplied to the probe function
|
||||
* @clk: usb phy clock
|
||||
* @regs: usb phy controller registers memory base
|
||||
* @pmuregs: USB device PHY_CONTROL register memory base
|
||||
* @sysreg: USB2.0 PHY_CFG register memory base
|
||||
* @ref_clk_freq: reference clock frequency selection
|
||||
* @drv_data: driver data available for different SoCs
|
||||
* @phy_type: Samsung SoCs specific phy types: #HOST
|
||||
* #DEVICE
|
||||
* @phy_usage: usage count for phy
|
||||
* @lock: lock for phy operations
|
||||
*/
|
||||
struct samsung_usbphy {
|
||||
struct usb_phy phy;
|
||||
struct samsung_usbphy_data *plat;
|
||||
struct device *dev;
|
||||
struct clk *clk;
|
||||
void __iomem *regs;
|
||||
void __iomem *pmuregs;
|
||||
void __iomem *sysreg;
|
||||
int ref_clk_freq;
|
||||
const struct samsung_usbphy_drvdata *drv_data;
|
||||
enum samsung_usb_phy_type phy_type;
|
||||
atomic_t phy_usage;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
#define phy_to_sphy(x) container_of((x), struct samsung_usbphy, phy)
|
||||
|
||||
int samsung_usbphy_set_host(struct usb_otg *otg, struct usb_bus *host)
|
||||
{
|
||||
if (!otg)
|
||||
return -ENODEV;
|
||||
|
||||
if (!otg->host)
|
||||
otg->host = host;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy)
|
||||
{
|
||||
struct device_node *usbphy_sys;
|
||||
|
||||
/* Getting node for system controller interface for usb-phy */
|
||||
usbphy_sys = of_get_child_by_name(sphy->dev->of_node, "usbphy-sys");
|
||||
if (!usbphy_sys) {
|
||||
dev_err(sphy->dev, "No sys-controller interface for usb-phy\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
sphy->pmuregs = of_iomap(usbphy_sys, 0);
|
||||
|
||||
if (sphy->pmuregs == NULL) {
|
||||
dev_err(sphy->dev, "Can't get usb-phy pmu control register\n");
|
||||
goto err0;
|
||||
}
|
||||
|
||||
sphy->sysreg = of_iomap(usbphy_sys, 1);
|
||||
|
||||
/*
|
||||
* Not returning error code here, since this situation is not fatal.
|
||||
* Few SoCs may not have this switch available
|
||||
*/
|
||||
if (sphy->sysreg == NULL)
|
||||
dev_warn(sphy->dev, "Can't get usb-phy sysreg cfg register\n");
|
||||
|
||||
of_node_put(usbphy_sys);
|
||||
|
||||
return 0;
|
||||
|
||||
err0:
|
||||
of_node_put(usbphy_sys);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set isolation here for phy.
|
||||
* Here 'on = true' would mean USB PHY block is isolated, hence
|
||||
* de-activated and vice-versa.
|
||||
*/
|
||||
static void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on)
|
||||
{
|
||||
void __iomem *reg = NULL;
|
||||
u32 reg_val;
|
||||
u32 en_mask = 0;
|
||||
|
||||
if (!sphy->pmuregs) {
|
||||
dev_warn(sphy->dev, "Can't set pmu isolation\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (sphy->drv_data->cpu_type) {
|
||||
case TYPE_S3C64XX:
|
||||
/*
|
||||
* Do nothing: We will add here once S3C64xx goes for DT support
|
||||
*/
|
||||
break;
|
||||
case TYPE_EXYNOS4210:
|
||||
/*
|
||||
* Fall through since exynos4210 and exynos5250 have similar
|
||||
* register architecture: two separate registers for host and
|
||||
* device phy control with enable bit at position 0.
|
||||
*/
|
||||
case TYPE_EXYNOS5250:
|
||||
if (sphy->phy_type == USB_PHY_TYPE_DEVICE) {
|
||||
reg = sphy->pmuregs +
|
||||
sphy->drv_data->devphy_reg_offset;
|
||||
en_mask = sphy->drv_data->devphy_en_mask;
|
||||
} else if (sphy->phy_type == USB_PHY_TYPE_HOST) {
|
||||
reg = sphy->pmuregs +
|
||||
sphy->drv_data->hostphy_reg_offset;
|
||||
en_mask = sphy->drv_data->hostphy_en_mask;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dev_err(sphy->dev, "Invalid SoC type\n");
|
||||
return;
|
||||
}
|
||||
|
||||
reg_val = readl(reg);
|
||||
|
||||
if (on)
|
||||
reg_val &= ~en_mask;
|
||||
else
|
||||
reg_val |= en_mask;
|
||||
|
||||
writel(reg_val, reg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure the mode of working of usb-phy here: HOST/DEVICE.
|
||||
*/
|
||||
static void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (!sphy->sysreg) {
|
||||
dev_warn(sphy->dev, "Can't configure specified phy mode\n");
|
||||
return;
|
||||
}
|
||||
|
||||
reg = readl(sphy->sysreg);
|
||||
|
||||
if (sphy->phy_type == USB_PHY_TYPE_DEVICE)
|
||||
reg &= ~EXYNOS_USB20PHY_CFG_HOST_LINK;
|
||||
else if (sphy->phy_type == USB_PHY_TYPE_HOST)
|
||||
reg |= EXYNOS_USB20PHY_CFG_HOST_LINK;
|
||||
|
||||
writel(reg, sphy->sysreg);
|
||||
}
|
||||
|
||||
/*
|
||||
* PHYs are different for USB Device and USB Host.
|
||||
* This make sure that correct PHY type is selected before
|
||||
* any operation on PHY.
|
||||
*/
|
||||
static int samsung_usbphy_set_type(struct usb_phy *phy,
|
||||
enum samsung_usb_phy_type phy_type)
|
||||
{
|
||||
struct samsung_usbphy *sphy = phy_to_sphy(phy);
|
||||
|
||||
sphy->phy_type = phy_type;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns reference clock frequency selection value
|
||||
*/
|
||||
static int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy)
|
||||
{
|
||||
struct clk *ref_clk;
|
||||
int refclk_freq = 0;
|
||||
|
||||
/*
|
||||
* In exynos5250 USB host and device PHY use
|
||||
* external crystal clock XXTI
|
||||
*/
|
||||
if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250)
|
||||
ref_clk = clk_get(sphy->dev, "ext_xtal");
|
||||
else
|
||||
ref_clk = clk_get(sphy->dev, "xusbxti");
|
||||
if (IS_ERR(ref_clk)) {
|
||||
dev_err(sphy->dev, "Failed to get reference clock\n");
|
||||
return PTR_ERR(ref_clk);
|
||||
}
|
||||
|
||||
if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) {
|
||||
/* set clock frequency for PLL */
|
||||
switch (clk_get_rate(ref_clk)) {
|
||||
case 9600 * KHZ:
|
||||
refclk_freq = FSEL_CLKSEL_9600K;
|
||||
break;
|
||||
case 10 * MHZ:
|
||||
refclk_freq = FSEL_CLKSEL_10M;
|
||||
break;
|
||||
case 12 * MHZ:
|
||||
refclk_freq = FSEL_CLKSEL_12M;
|
||||
break;
|
||||
case 19200 * KHZ:
|
||||
refclk_freq = FSEL_CLKSEL_19200K;
|
||||
break;
|
||||
case 20 * MHZ:
|
||||
refclk_freq = FSEL_CLKSEL_20M;
|
||||
break;
|
||||
case 50 * MHZ:
|
||||
refclk_freq = FSEL_CLKSEL_50M;
|
||||
break;
|
||||
case 24 * MHZ:
|
||||
default:
|
||||
/* default reference clock */
|
||||
refclk_freq = FSEL_CLKSEL_24M;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (clk_get_rate(ref_clk)) {
|
||||
case 12 * MHZ:
|
||||
refclk_freq = PHYCLK_CLKSEL_12M;
|
||||
break;
|
||||
case 24 * MHZ:
|
||||
refclk_freq = PHYCLK_CLKSEL_24M;
|
||||
break;
|
||||
case 48 * MHZ:
|
||||
refclk_freq = PHYCLK_CLKSEL_48M;
|
||||
break;
|
||||
default:
|
||||
if (sphy->drv_data->cpu_type == TYPE_S3C64XX)
|
||||
refclk_freq = PHYCLK_CLKSEL_48M;
|
||||
else
|
||||
refclk_freq = PHYCLK_CLKSEL_24M;
|
||||
break;
|
||||
}
|
||||
}
|
||||
clk_put(ref_clk);
|
||||
|
||||
return refclk_freq;
|
||||
}
|
||||
|
||||
static bool exynos5_phyhost_is_on(void *regs)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
reg = readl(regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
|
||||
return !(reg & HOST_CTRL0_SIDDQ);
|
||||
}
|
||||
|
||||
static void samsung_exynos5_usbphy_enable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
void __iomem *regs = sphy->regs;
|
||||
u32 phyclk = sphy->ref_clk_freq;
|
||||
u32 phyhost;
|
||||
u32 phyotg;
|
||||
u32 phyhsic;
|
||||
u32 ehcictrl;
|
||||
u32 ohcictrl;
|
||||
|
||||
/*
|
||||
* phy_usage helps in keeping usage count for phy
|
||||
* so that the first consumer enabling the phy is also
|
||||
* the last consumer to disable it.
|
||||
*/
|
||||
|
||||
atomic_inc(&sphy->phy_usage);
|
||||
|
||||
if (exynos5_phyhost_is_on(regs)) {
|
||||
dev_info(sphy->dev, "Already power on PHY\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Host configuration */
|
||||
phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
|
||||
/* phy reference clock configuration */
|
||||
phyhost &= ~HOST_CTRL0_FSEL_MASK;
|
||||
phyhost |= HOST_CTRL0_FSEL(phyclk);
|
||||
|
||||
/* host phy reset */
|
||||
phyhost &= ~(HOST_CTRL0_PHYSWRST |
|
||||
HOST_CTRL0_PHYSWRSTALL |
|
||||
HOST_CTRL0_SIDDQ |
|
||||
/* Enable normal mode of operation */
|
||||
HOST_CTRL0_FORCESUSPEND |
|
||||
HOST_CTRL0_FORCESLEEP);
|
||||
|
||||
/* Link reset */
|
||||
phyhost |= (HOST_CTRL0_LINKSWRST |
|
||||
HOST_CTRL0_UTMISWRST |
|
||||
/* COMMON Block configuration during suspend */
|
||||
HOST_CTRL0_COMMONON_N);
|
||||
writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
udelay(10);
|
||||
phyhost &= ~(HOST_CTRL0_LINKSWRST |
|
||||
HOST_CTRL0_UTMISWRST);
|
||||
writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
|
||||
/* OTG configuration */
|
||||
phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS);
|
||||
|
||||
/* phy reference clock configuration */
|
||||
phyotg &= ~OTG_SYS_FSEL_MASK;
|
||||
phyotg |= OTG_SYS_FSEL(phyclk);
|
||||
|
||||
/* Enable normal mode of operation */
|
||||
phyotg &= ~(OTG_SYS_FORCESUSPEND |
|
||||
OTG_SYS_SIDDQ_UOTG |
|
||||
OTG_SYS_FORCESLEEP |
|
||||
OTG_SYS_REFCLKSEL_MASK |
|
||||
/* COMMON Block configuration during suspend */
|
||||
OTG_SYS_COMMON_ON);
|
||||
|
||||
/* OTG phy & link reset */
|
||||
phyotg |= (OTG_SYS_PHY0_SWRST |
|
||||
OTG_SYS_LINKSWRST_UOTG |
|
||||
OTG_SYS_PHYLINK_SWRESET |
|
||||
OTG_SYS_OTGDISABLE |
|
||||
/* Set phy refclk */
|
||||
OTG_SYS_REFCLKSEL_CLKCORE);
|
||||
|
||||
writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS);
|
||||
udelay(10);
|
||||
phyotg &= ~(OTG_SYS_PHY0_SWRST |
|
||||
OTG_SYS_LINKSWRST_UOTG |
|
||||
OTG_SYS_PHYLINK_SWRESET);
|
||||
writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS);
|
||||
|
||||
/* HSIC phy configuration */
|
||||
phyhsic = (HSIC_CTRL_REFCLKDIV_12 |
|
||||
HSIC_CTRL_REFCLKSEL |
|
||||
HSIC_CTRL_PHYSWRST);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2);
|
||||
udelay(10);
|
||||
phyhsic &= ~HSIC_CTRL_PHYSWRST;
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2);
|
||||
|
||||
udelay(80);
|
||||
|
||||
/* enable EHCI DMA burst */
|
||||
ehcictrl = readl(regs + EXYNOS5_PHY_HOST_EHCICTRL);
|
||||
ehcictrl |= (HOST_EHCICTRL_ENAINCRXALIGN |
|
||||
HOST_EHCICTRL_ENAINCR4 |
|
||||
HOST_EHCICTRL_ENAINCR8 |
|
||||
HOST_EHCICTRL_ENAINCR16);
|
||||
writel(ehcictrl, regs + EXYNOS5_PHY_HOST_EHCICTRL);
|
||||
|
||||
/* set ohci_suspend_on_n */
|
||||
ohcictrl = readl(regs + EXYNOS5_PHY_HOST_OHCICTRL);
|
||||
ohcictrl |= HOST_OHCICTRL_SUSPLGCY;
|
||||
writel(ohcictrl, regs + EXYNOS5_PHY_HOST_OHCICTRL);
|
||||
}
|
||||
|
||||
static void samsung_usbphy_enable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
void __iomem *regs = sphy->regs;
|
||||
u32 phypwr;
|
||||
u32 phyclk;
|
||||
u32 rstcon;
|
||||
|
||||
/* set clock frequency for PLL */
|
||||
phyclk = sphy->ref_clk_freq;
|
||||
phypwr = readl(regs + SAMSUNG_PHYPWR);
|
||||
rstcon = readl(regs + SAMSUNG_RSTCON);
|
||||
|
||||
switch (sphy->drv_data->cpu_type) {
|
||||
case TYPE_S3C64XX:
|
||||
phyclk &= ~PHYCLK_COMMON_ON_N;
|
||||
phypwr &= ~PHYPWR_NORMAL_MASK;
|
||||
rstcon |= RSTCON_SWRST;
|
||||
break;
|
||||
case TYPE_EXYNOS4210:
|
||||
phypwr &= ~PHYPWR_NORMAL_MASK_PHY0;
|
||||
rstcon |= RSTCON_SWRST;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
writel(phyclk, regs + SAMSUNG_PHYCLK);
|
||||
/* Configure PHY0 for normal operation*/
|
||||
writel(phypwr, regs + SAMSUNG_PHYPWR);
|
||||
/* reset all ports of PHY and Link */
|
||||
writel(rstcon, regs + SAMSUNG_RSTCON);
|
||||
udelay(10);
|
||||
rstcon &= ~RSTCON_SWRST;
|
||||
writel(rstcon, regs + SAMSUNG_RSTCON);
|
||||
}
|
||||
|
||||
static void samsung_exynos5_usbphy_disable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
void __iomem *regs = sphy->regs;
|
||||
u32 phyhost;
|
||||
u32 phyotg;
|
||||
u32 phyhsic;
|
||||
|
||||
if (atomic_dec_return(&sphy->phy_usage) > 0) {
|
||||
dev_info(sphy->dev, "still being used\n");
|
||||
return;
|
||||
}
|
||||
|
||||
phyhsic = (HSIC_CTRL_REFCLKDIV_12 |
|
||||
HSIC_CTRL_REFCLKSEL |
|
||||
HSIC_CTRL_SIDDQ |
|
||||
HSIC_CTRL_FORCESLEEP |
|
||||
HSIC_CTRL_FORCESUSPEND);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL1);
|
||||
writel(phyhsic, regs + EXYNOS5_PHY_HSIC_CTRL2);
|
||||
|
||||
phyhost = readl(regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
phyhost |= (HOST_CTRL0_SIDDQ |
|
||||
HOST_CTRL0_FORCESUSPEND |
|
||||
HOST_CTRL0_FORCESLEEP |
|
||||
HOST_CTRL0_PHYSWRST |
|
||||
HOST_CTRL0_PHYSWRSTALL);
|
||||
writel(phyhost, regs + EXYNOS5_PHY_HOST_CTRL0);
|
||||
|
||||
phyotg = readl(regs + EXYNOS5_PHY_OTG_SYS);
|
||||
phyotg |= (OTG_SYS_FORCESUSPEND |
|
||||
OTG_SYS_SIDDQ_UOTG |
|
||||
OTG_SYS_FORCESLEEP);
|
||||
writel(phyotg, regs + EXYNOS5_PHY_OTG_SYS);
|
||||
}
|
||||
|
||||
static void samsung_usbphy_disable(struct samsung_usbphy *sphy)
|
||||
{
|
||||
void __iomem *regs = sphy->regs;
|
||||
u32 phypwr;
|
||||
|
||||
phypwr = readl(regs + SAMSUNG_PHYPWR);
|
||||
|
||||
switch (sphy->drv_data->cpu_type) {
|
||||
case TYPE_S3C64XX:
|
||||
phypwr |= PHYPWR_NORMAL_MASK;
|
||||
break;
|
||||
case TYPE_EXYNOS4210:
|
||||
phypwr |= PHYPWR_NORMAL_MASK_PHY0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Disable analog and otg block power */
|
||||
writel(phypwr, regs + SAMSUNG_PHYPWR);
|
||||
}
|
||||
|
||||
/*
|
||||
* The function passed to the usb driver for phy initialization
|
||||
*/
|
||||
static int samsung_usbphy_init(struct usb_phy *phy)
|
||||
{
|
||||
struct samsung_usbphy *sphy;
|
||||
struct usb_bus *host = NULL;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
sphy = phy_to_sphy(phy);
|
||||
|
||||
host = phy->otg->host;
|
||||
|
||||
/* Enable the phy clock */
|
||||
ret = clk_prepare_enable(sphy->clk);
|
||||
if (ret) {
|
||||
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sphy->lock, flags);
|
||||
|
||||
if (host) {
|
||||
/* setting default phy-type for USB 2.0 */
|
||||
if (!strstr(dev_name(host->controller), "ehci") ||
|
||||
!strstr(dev_name(host->controller), "ohci"))
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST);
|
||||
} else {
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
|
||||
}
|
||||
|
||||
/* Disable phy isolation */
|
||||
if (sphy->plat && sphy->plat->pmu_isolation)
|
||||
sphy->plat->pmu_isolation(false);
|
||||
else
|
||||
samsung_usbphy_set_isolation(sphy, false);
|
||||
|
||||
/* Selecting Host/OTG mode; After reset USB2.0PHY_CFG: HOST */
|
||||
samsung_usbphy_cfg_sel(sphy);
|
||||
|
||||
/* Initialize usb phy registers */
|
||||
if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250)
|
||||
samsung_exynos5_usbphy_enable(sphy);
|
||||
else
|
||||
samsung_usbphy_enable(sphy);
|
||||
|
||||
spin_unlock_irqrestore(&sphy->lock, flags);
|
||||
|
||||
/* Disable the phy clock */
|
||||
clk_disable_unprepare(sphy->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The function passed to the usb driver for phy shutdown
|
||||
*/
|
||||
static void samsung_usbphy_shutdown(struct usb_phy *phy)
|
||||
{
|
||||
struct samsung_usbphy *sphy;
|
||||
struct usb_bus *host = NULL;
|
||||
unsigned long flags;
|
||||
|
||||
sphy = phy_to_sphy(phy);
|
||||
|
||||
host = phy->otg->host;
|
||||
|
||||
if (clk_prepare_enable(sphy->clk)) {
|
||||
dev_err(sphy->dev, "%s: clk_prepare_enable failed\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&sphy->lock, flags);
|
||||
|
||||
if (host) {
|
||||
/* setting default phy-type for USB 2.0 */
|
||||
if (!strstr(dev_name(host->controller), "ehci") ||
|
||||
!strstr(dev_name(host->controller), "ohci"))
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_HOST);
|
||||
} else {
|
||||
samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE);
|
||||
}
|
||||
|
||||
/* De-initialize usb phy registers */
|
||||
if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250)
|
||||
samsung_exynos5_usbphy_disable(sphy);
|
||||
else
|
||||
samsung_usbphy_disable(sphy);
|
||||
|
||||
/* Enable phy isolation */
|
||||
if (sphy->plat && sphy->plat->pmu_isolation)
|
||||
sphy->plat->pmu_isolation(true);
|
||||
else
|
||||
samsung_usbphy_set_isolation(sphy, true);
|
||||
|
||||
spin_unlock_irqrestore(&sphy->lock, flags);
|
||||
|
||||
clk_disable_unprepare(sphy->clk);
|
||||
}
|
||||
|
||||
static const struct of_device_id samsung_usbphy_dt_match[];
|
||||
|
||||
static inline const struct samsung_usbphy_drvdata
|
||||
*samsung_usbphy_get_driver_data(struct platform_device *pdev)
|
||||
{
|
||||
if (pdev->dev.of_node) {
|
||||
const struct of_device_id *match;
|
||||
match = of_match_node(samsung_usbphy_dt_match,
|
||||
pdev->dev.of_node);
|
||||
return match->data;
|
||||
}
|
||||
|
||||
return (struct samsung_usbphy_drvdata *)
|
||||
platform_get_device_id(pdev)->driver_data;
|
||||
}
|
||||
|
||||
static int samsung_usbphy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct samsung_usbphy *sphy;
|
||||
struct usb_otg *otg;
|
||||
struct samsung_usbphy_data *pdata = pdev->dev.platform_data;
|
||||
const struct samsung_usbphy_drvdata *drv_data;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *phy_mem;
|
||||
void __iomem *phy_base;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
phy_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!phy_mem) {
|
||||
dev_err(dev, "%s: missing mem resource\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
phy_base = devm_ioremap_resource(dev, phy_mem);
|
||||
if (IS_ERR(phy_base))
|
||||
return PTR_ERR(phy_base);
|
||||
|
||||
sphy = devm_kzalloc(dev, sizeof(*sphy), GFP_KERNEL);
|
||||
if (!sphy)
|
||||
return -ENOMEM;
|
||||
|
||||
otg = devm_kzalloc(dev, sizeof(*otg), GFP_KERNEL);
|
||||
if (!otg)
|
||||
return -ENOMEM;
|
||||
|
||||
drv_data = samsung_usbphy_get_driver_data(pdev);
|
||||
|
||||
if (drv_data->cpu_type == TYPE_EXYNOS5250)
|
||||
clk = devm_clk_get(dev, "usbhost");
|
||||
else
|
||||
clk = devm_clk_get(dev, "otg");
|
||||
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(dev, "Failed to get otg clock\n");
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
sphy->dev = dev;
|
||||
|
||||
if (dev->of_node) {
|
||||
ret = samsung_usbphy_parse_dt(sphy);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
if (!pdata) {
|
||||
dev_err(dev, "no platform data specified\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
sphy->plat = pdata;
|
||||
sphy->regs = phy_base;
|
||||
sphy->clk = clk;
|
||||
sphy->drv_data = drv_data;
|
||||
sphy->phy.dev = sphy->dev;
|
||||
sphy->phy.label = "samsung-usbphy";
|
||||
sphy->phy.init = samsung_usbphy_init;
|
||||
sphy->phy.shutdown = samsung_usbphy_shutdown;
|
||||
sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy);
|
||||
|
||||
sphy->phy.otg = otg;
|
||||
sphy->phy.otg->phy = &sphy->phy;
|
||||
sphy->phy.otg->set_host = samsung_usbphy_set_host;
|
||||
|
||||
spin_lock_init(&sphy->lock);
|
||||
|
||||
platform_set_drvdata(pdev, sphy);
|
||||
|
||||
return usb_add_phy(&sphy->phy, USB_PHY_TYPE_USB2);
|
||||
}
|
||||
|
||||
static int samsung_usbphy_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct samsung_usbphy *sphy = platform_get_drvdata(pdev);
|
||||
|
||||
usb_remove_phy(&sphy->phy);
|
||||
|
||||
if (sphy->pmuregs)
|
||||
iounmap(sphy->pmuregs);
|
||||
if (sphy->sysreg)
|
||||
iounmap(sphy->sysreg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct samsung_usbphy_drvdata usbphy_s3c64xx = {
|
||||
.cpu_type = TYPE_S3C64XX,
|
||||
.devphy_en_mask = S3C64XX_USBPHY_ENABLE,
|
||||
};
|
||||
|
||||
static const struct samsung_usbphy_drvdata usbphy_exynos4 = {
|
||||
.cpu_type = TYPE_EXYNOS4210,
|
||||
.devphy_en_mask = EXYNOS_USBPHY_ENABLE,
|
||||
.hostphy_en_mask = EXYNOS_USBPHY_ENABLE,
|
||||
};
|
||||
|
||||
static struct samsung_usbphy_drvdata usbphy_exynos5 = {
|
||||
.cpu_type = TYPE_EXYNOS5250,
|
||||
.hostphy_en_mask = EXYNOS_USBPHY_ENABLE,
|
||||
.hostphy_reg_offset = EXYNOS_USBHOST_PHY_CTRL_OFFSET,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id samsung_usbphy_dt_match[] = {
|
||||
{
|
||||
.compatible = "samsung,s3c64xx-usbphy",
|
||||
.data = &usbphy_s3c64xx,
|
||||
}, {
|
||||
.compatible = "samsung,exynos4210-usbphy",
|
||||
.data = &usbphy_exynos4,
|
||||
}, {
|
||||
.compatible = "samsung,exynos5250-usbphy",
|
||||
.data = &usbphy_exynos5
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, samsung_usbphy_dt_match);
|
||||
#endif
|
||||
|
||||
static struct platform_device_id samsung_usbphy_driver_ids[] = {
|
||||
{
|
||||
.name = "s3c64xx-usbphy",
|
||||
.driver_data = (unsigned long)&usbphy_s3c64xx,
|
||||
}, {
|
||||
.name = "exynos4210-usbphy",
|
||||
.driver_data = (unsigned long)&usbphy_exynos4,
|
||||
}, {
|
||||
.name = "exynos5250-usbphy",
|
||||
.driver_data = (unsigned long)&usbphy_exynos5,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(platform, samsung_usbphy_driver_ids);
|
||||
|
||||
static struct platform_driver samsung_usbphy_driver = {
|
||||
.probe = samsung_usbphy_probe,
|
||||
.remove = samsung_usbphy_remove,
|
||||
.id_table = samsung_usbphy_driver_ids,
|
||||
.driver = {
|
||||
.name = "samsung-usbphy",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(samsung_usbphy_dt_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(samsung_usbphy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Samsung USB phy controller");
|
||||
MODULE_AUTHOR("Praveen Paneri <p.paneri@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:samsung-usbphy");
|
Reference in New Issue
Block a user