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:
Greg Kroah-Hartman
2013-04-05 15:18:00 -07:00
159 changed files with 6868 additions and 4499 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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");

View 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, &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, &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");

File diff suppressed because it is too large Load Diff

View 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);

View 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;
}

View 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;

View 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");

File diff suppressed because it is too large Load Diff

View 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");

File diff suppressed because it is too large Load Diff

View File

@@ -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

View 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);

View 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

View 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
View 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");

View 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);

View 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);

View 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");

View 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");

View 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");

View 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");

View 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
View 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
View 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);

View File

@@ -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");