Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

224
drivers/usb/phy/Kconfig Normal file
View file

@ -0,0 +1,224 @@
#
# Physical Layer USB driver configuration
#
menu "USB Physical Layer drivers"
config USB_PHY
def_bool n
config USB_OTG_WAKELOCK
bool "Hold a wakelock when USB connected"
depends on WAKELOCK
select USB_OTG_UTILS
help
Select this to automatically hold a wakelock when USB is
connected, preventing suspend.
#
# USB Transceiver Drivers
#
config AB8500_USB
tristate "AB8500 USB Transceiver Driver"
depends on AB8500_CORE
select USB_PHY
help
Enable this to support the USB OTG transceiver in AB8500 chip.
This transceiver supports high and full speed devices plus,
in host mode, low speed.
config FSL_USB2_OTG
bool "Freescale USB OTG Transceiver Driver"
depends on USB_EHCI_FSL && USB_FSL_USB2 && USB_OTG_FSM && PM_RUNTIME
select USB_OTG
select USB_PHY
help
Enable this to support Freescale USB OTG transceiver.
config ISP1301_OMAP
tristate "Philips ISP1301 with OMAP OTG"
depends on I2C && ARCH_OMAP_OTG
depends on USB
select USB_PHY
help
If you say yes here you get support for the Philips ISP1301
USB-On-The-Go transceiver working with the OMAP OTG controller.
The ISP1301 is a full speed USB transceiver which is used in
products including H2, H3, and H4 development boards for Texas
Instruments OMAP processors.
This driver can also be built as a module. If so, the module
will be called phy-isp1301-omap.
config KEYSTONE_USB_PHY
tristate "Keystone USB PHY Driver"
depends on ARCH_KEYSTONE || COMPILE_TEST
select NOP_USB_XCEIV
help
Enable this to support Keystone USB phy. This driver provides
interface to interact with USB 2.0 and USB 3.0 PHY that is part
of the Keystone SOC.
config NOP_USB_XCEIV
tristate "NOP USB Transceiver Driver"
select USB_PHY
help
This driver is to be used by all the usb transceiver which are either
built-in with usb ip or which are autonomous and doesn't require any
phy programming such as ISP1x04 etc.
config AM335X_CONTROL_USB
tristate
config AM335X_PHY_USB
tristate "AM335x USB PHY Driver"
depends on ARM || COMPILE_TEST
select USB_PHY
select AM335X_CONTROL_USB
select NOP_USB_XCEIV
help
This driver provides PHY support for that phy which part for the
AM335x SoC.
config SAMSUNG_USBPHY
tristate
help
Enable this to support Samsung USB phy helper driver for Samsung SoCs.
This driver provides common interface to interact, for Samsung USB 2.0 PHY
driver and later for Samsung USB 3.0 PHY driver.
config TWL6030_USB
tristate "TWL6030 USB Transceiver Driver"
depends on TWL4030_CORE && OMAP_USB2 && USB_MUSB_OMAP2PLUS
help
Enable this to support the USB OTG transceiver on TWL6030
family chips. This TWL6030 transceiver has the VBUS and ID GND
and OTG SRP events capabilities. For all other transceiver functionality
UTMI PHY is embedded in OMAP4430. The internal PHY configurations APIs
are hooked to this driver through platform_data structure.
The definition of internal PHY APIs are in the mach-omap2 layer.
config USB_GPIO_VBUS
tristate "GPIO based peripheral-only VBUS sensing 'transceiver'"
depends on GPIOLIB
select USB_PHY
help
Provides simple GPIO VBUS sensing for controllers with an
internal transceiver via the usb_phy interface, and
optionally control of a D+ pullup GPIO as well as a VBUS
current limit regulator.
config OMAP_OTG
tristate "OMAP USB OTG controller driver"
depends on ARCH_OMAP_OTG && EXTCON
help
Enable this to support some transceivers on OMAP1 platforms. OTG
controller is needed to switch between host and peripheral modes.
This driver can also be built as a module. If so, the module
will be called phy-omap-otg.
config TAHVO_USB
tristate "Tahvo USB transceiver driver"
depends on MFD_RETU && EXTCON
select USB_PHY
help
Enable this to support USB transceiver on Tahvo. This is used
at least on Nokia 770.
config TAHVO_USB_HOST_BY_DEFAULT
depends on TAHVO_USB
boolean "Device in USB host mode by default"
help
Say Y here, if you want the device to enter USB host mode
by default on bootup.
config USB_ISP1301
tristate "NXP ISP1301 USB transceiver support"
depends on USB || USB_GADGET
depends on I2C
select USB_PHY
help
Say Y here to add support for the NXP ISP1301 USB transceiver driver.
This chip is typically used as USB transceiver for USB host, gadget
and OTG drivers (to be selected separately).
To compile this driver as a module, choose M here: the
module will be called phy-isp1301.
config USB_MSM_OTG
tristate "Qualcomm on-chip USB OTG controller support"
depends on (USB || USB_GADGET) && (ARCH_MSM || ARCH_QCOM || COMPILE_TEST)
depends on RESET_CONTROLLER
select USB_PHY
help
Enable this to support the USB OTG transceiver on Qualcomm chips. It
handles PHY initialization, clock management, and workarounds
required after resetting the hardware and power management.
This driver is required even for peripheral only or host only
mode configurations.
This driver is not supported on boards like trout which
has an external PHY.
config USB_MV_OTG
tristate "Marvell USB OTG support"
depends on USB_EHCI_MV && USB_MV_UDC && PM_RUNTIME
select USB_OTG
select USB_PHY
help
Say Y here if you want to build Marvell USB OTG transciever
driver in kernel (including PXA and MMP series). This driver
implements role switch between EHCI host driver and gadget driver.
To compile this driver as a module, choose M here.
config USB_MXS_PHY
tristate "Freescale MXS USB PHY support"
depends on ARCH_MXC || ARCH_MXS
select STMP_DEVICE
select USB_PHY
help
Enable this to support the Freescale MXS USB PHY.
MXS Phy is used by some of the i.MX SoCs, for example imx23/28/6x.
config USB_RCAR_PHY
tristate "Renesas R-Car USB PHY support"
depends on USB || USB_GADGET
depends on ARCH_R8A7778 || ARCH_R8A7779 || COMPILE_TEST
select USB_PHY
help
Say Y here to add support for the Renesas R-Car USB common PHY driver.
This chip is typically used as USB PHY for USB host, gadget.
This driver supports R8A7778 and R8A7779.
To compile this driver as a module, choose M here: the
module will be called phy-rcar-usb.
config USB_RCAR_GEN2_PHY
tristate "Renesas R-Car Gen2 USB PHY support"
depends on ARCH_R8A7790 || ARCH_R8A7791 || COMPILE_TEST
select USB_PHY
help
Say Y here to add support for the Renesas R-Car Gen2 USB PHY driver.
It is typically used to control internal USB PHY for USBHS,
and to configure shared USB channels 0 and 2.
This driver supports R8A7790 and R8A7791.
To compile this driver as a module, choose M here: the
module will be called phy-rcar-gen2-usb.
config USB_ULPI
bool "Generic ULPI Transceiver Driver"
depends on ARM || ARM64
help
Enable this to support ULPI connected USB OTG transceivers which
are likely found on embedded boards.
config USB_ULPI_VIEWPORT
bool
depends on USB_ULPI
help
Provides read/write operations to the ULPI phy register set for
controllers with a viewport register (e.g. Chipidea/ARC controllers).
endmenu

29
drivers/usb/phy/Makefile Normal file
View file

@ -0,0 +1,29 @@
#
# Makefile for physical layer USB drivers
#
obj-$(CONFIG_USB_PHY) += phy.o
obj-$(CONFIG_OF) += of.o
obj-$(CONFIG_USB_OTG_WAKELOCK) += otg-wakelock.o
# transceiver drivers, keep the list sorted
obj-$(CONFIG_AB8500_USB) += phy-ab8500-usb.o
obj-$(CONFIG_FSL_USB2_OTG) += phy-fsl-usb.o
obj-$(CONFIG_ISP1301_OMAP) += phy-isp1301-omap.o
obj-$(CONFIG_NOP_USB_XCEIV) += phy-generic.o
obj-$(CONFIG_TAHVO_USB) += phy-tahvo.o
obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o
obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o
obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o
obj-$(CONFIG_SAMSUNG_USBPHY) += phy-samsung-usb.o
obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o
obj-$(CONFIG_USB_EHCI_TEGRA) += phy-tegra-usb.o
obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o
obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o
obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o
obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o
obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o
obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o
obj-$(CONFIG_USB_RCAR_GEN2_PHY) += phy-rcar-gen2-usb.o
obj-$(CONFIG_USB_ULPI) += phy-ulpi.o
obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o
obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o

View file

@ -0,0 +1,21 @@
#ifndef _AM335x_PHY_CONTROL_H_
#define _AM335x_PHY_CONTROL_H_
struct phy_control {
void (*phy_power)(struct phy_control *phy_ctrl, u32 id, bool on);
void (*phy_wkup)(struct phy_control *phy_ctrl, u32 id, bool on);
};
static inline void phy_ctrl_power(struct phy_control *phy_ctrl, u32 id, bool on)
{
phy_ctrl->phy_power(phy_ctrl, id, on);
}
static inline void phy_ctrl_wkup(struct phy_control *phy_ctrl, u32 id, bool on)
{
phy_ctrl->phy_wkup(phy_ctrl, id, on);
}
struct phy_control *am335x_get_phy_control(struct device *dev);
#endif

47
drivers/usb/phy/of.c Normal file
View file

@ -0,0 +1,47 @@
/*
* USB of helper code
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/usb/of.h>
#include <linux/usb/otg.h>
static const char *const usbphy_modes[] = {
[USBPHY_INTERFACE_MODE_UNKNOWN] = "",
[USBPHY_INTERFACE_MODE_UTMI] = "utmi",
[USBPHY_INTERFACE_MODE_UTMIW] = "utmi_wide",
[USBPHY_INTERFACE_MODE_ULPI] = "ulpi",
[USBPHY_INTERFACE_MODE_SERIAL] = "serial",
[USBPHY_INTERFACE_MODE_HSIC] = "hsic",
};
/**
* of_usb_get_phy_mode - Get phy mode for given device_node
* @np: Pointer to the given device_node
*
* The function gets phy interface string from property 'phy_type',
* and returns the correspondig enum usb_phy_interface
*/
enum usb_phy_interface of_usb_get_phy_mode(struct device_node *np)
{
const char *phy_type;
int err, i;
err = of_property_read_string(np, "phy_type", &phy_type);
if (err < 0)
return USBPHY_INTERFACE_MODE_UNKNOWN;
for (i = 0; i < ARRAY_SIZE(usbphy_modes); i++)
if (!strcmp(phy_type, usbphy_modes[i]))
return i;
return USBPHY_INTERFACE_MODE_UNKNOWN;
}
EXPORT_SYMBOL_GPL(of_usb_get_phy_mode);

View file

@ -0,0 +1,173 @@
/*
* otg-wakelock.c
*
* Copyright (C) 2011 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/wakelock.h>
#include <linux/spinlock.h>
#include <linux/usb/otg.h>
#define TEMPORARY_HOLD_TIME 2000
static bool enabled = true;
static struct usb_phy *otgwl_xceiv;
static struct notifier_block otgwl_nb;
/*
* otgwl_spinlock is held while the VBUS lock is grabbed or dropped and the
* held field is updated to match.
*/
static DEFINE_SPINLOCK(otgwl_spinlock);
/*
* Only one lock, but since these 3 fields are associated with each other...
*/
struct otgwl_lock {
char name[40];
struct wake_lock wakelock;
bool held;
};
/*
* VBUS present lock. Also used as a timed lock on charger
* connect/disconnect and USB host disconnect, to allow the system
* to react to the change in power.
*/
static struct otgwl_lock vbus_lock;
static void otgwl_hold(struct otgwl_lock *lock)
{
if (!lock->held) {
wake_lock(&lock->wakelock);
lock->held = true;
}
}
static void otgwl_temporary_hold(struct otgwl_lock *lock)
{
wake_lock_timeout(&lock->wakelock,
msecs_to_jiffies(TEMPORARY_HOLD_TIME));
lock->held = false;
}
static void otgwl_drop(struct otgwl_lock *lock)
{
if (lock->held) {
wake_unlock(&lock->wakelock);
lock->held = false;
}
}
static void otgwl_handle_event(unsigned long event)
{
unsigned long irqflags;
spin_lock_irqsave(&otgwl_spinlock, irqflags);
if (!enabled) {
otgwl_drop(&vbus_lock);
spin_unlock_irqrestore(&otgwl_spinlock, irqflags);
return;
}
switch (event) {
case USB_EVENT_VBUS:
case USB_EVENT_ENUMERATED:
otgwl_hold(&vbus_lock);
break;
case USB_EVENT_NONE:
case USB_EVENT_ID:
case USB_EVENT_CHARGER:
otgwl_temporary_hold(&vbus_lock);
break;
default:
break;
}
spin_unlock_irqrestore(&otgwl_spinlock, irqflags);
}
static int otgwl_otg_notifications(struct notifier_block *nb,
unsigned long event, void *unused)
{
otgwl_handle_event(event);
return NOTIFY_OK;
}
static int set_enabled(const char *val, const struct kernel_param *kp)
{
int rv = param_set_bool(val, kp);
if (rv)
return rv;
if (otgwl_xceiv)
otgwl_handle_event(otgwl_xceiv->last_event);
return 0;
}
static struct kernel_param_ops enabled_param_ops = {
.set = set_enabled,
.get = param_get_bool,
};
module_param_cb(enabled, &enabled_param_ops, &enabled, 0644);
MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present");
static int __init otg_wakelock_init(void)
{
int ret;
struct usb_phy *phy;
phy = usb_get_phy(USB_PHY_TYPE_USB2);
if (IS_ERR(phy)) {
pr_err("%s: No USB transceiver found\n", __func__);
return PTR_ERR(phy);
}
otgwl_xceiv = phy;
snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s",
dev_name(otgwl_xceiv->dev));
wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND,
vbus_lock.name);
otgwl_nb.notifier_call = otgwl_otg_notifications;
ret = usb_register_notifier(otgwl_xceiv, &otgwl_nb);
if (ret) {
pr_err("%s: usb_register_notifier on transceiver %s"
" failed\n", __func__,
dev_name(otgwl_xceiv->dev));
otgwl_xceiv = NULL;
wake_lock_destroy(&vbus_lock.wakelock);
return ret;
}
otgwl_handle_event(otgwl_xceiv->last_event);
return ret;
}
late_initcall(otg_wakelock_init);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,187 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/delay.h>
#include "am35x-phy-control.h"
struct am335x_control_usb {
struct device *dev;
void __iomem *phy_reg;
void __iomem *wkup;
spinlock_t lock;
struct phy_control phy_ctrl;
};
#define AM335X_USB0_CTRL 0x0
#define AM335X_USB1_CTRL 0x8
#define AM335x_USB_WKUP 0x0
#define USBPHY_CM_PWRDN (1 << 0)
#define USBPHY_OTG_PWRDN (1 << 1)
#define USBPHY_OTGVDET_EN (1 << 19)
#define USBPHY_OTGSESSEND_EN (1 << 20)
#define AM335X_PHY0_WK_EN (1 << 0)
#define AM335X_PHY1_WK_EN (1 << 8)
static void am335x_phy_wkup(struct phy_control *phy_ctrl, u32 id, bool on)
{
struct am335x_control_usb *usb_ctrl;
u32 val;
u32 reg;
usb_ctrl = container_of(phy_ctrl, struct am335x_control_usb, phy_ctrl);
switch (id) {
case 0:
reg = AM335X_PHY0_WK_EN;
break;
case 1:
reg = AM335X_PHY1_WK_EN;
break;
default:
WARN_ON(1);
return;
}
spin_lock(&usb_ctrl->lock);
val = readl(usb_ctrl->wkup);
if (on)
val |= reg;
else
val &= ~reg;
writel(val, usb_ctrl->wkup);
spin_unlock(&usb_ctrl->lock);
}
static void am335x_phy_power(struct phy_control *phy_ctrl, u32 id, bool on)
{
struct am335x_control_usb *usb_ctrl;
u32 val;
u32 reg;
usb_ctrl = container_of(phy_ctrl, struct am335x_control_usb, phy_ctrl);
switch (id) {
case 0:
reg = AM335X_USB0_CTRL;
break;
case 1:
reg = AM335X_USB1_CTRL;
break;
default:
WARN_ON(1);
return;
}
val = readl(usb_ctrl->phy_reg + reg);
if (on) {
val &= ~(USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN);
val |= USBPHY_OTGVDET_EN | USBPHY_OTGSESSEND_EN;
} else {
val |= USBPHY_CM_PWRDN | USBPHY_OTG_PWRDN;
}
writel(val, usb_ctrl->phy_reg + reg);
/*
* Give the PHY ~1ms to complete the power up operation.
* Tests have shown unstable behaviour if other USB PHY related
* registers are written too shortly after such a transition.
*/
if (on)
mdelay(1);
}
static const struct phy_control ctrl_am335x = {
.phy_power = am335x_phy_power,
.phy_wkup = am335x_phy_wkup,
};
static const struct of_device_id omap_control_usb_id_table[] = {
{ .compatible = "ti,am335x-usb-ctrl-module", .data = &ctrl_am335x },
{}
};
MODULE_DEVICE_TABLE(of, omap_control_usb_id_table);
static struct platform_driver am335x_control_driver;
static int match(struct device *dev, void *data)
{
struct device_node *node = (struct device_node *)data;
return dev->of_node == node &&
dev->driver == &am335x_control_driver.driver;
}
struct phy_control *am335x_get_phy_control(struct device *dev)
{
struct device_node *node;
struct am335x_control_usb *ctrl_usb;
node = of_parse_phandle(dev->of_node, "ti,ctrl_mod", 0);
if (!node)
return NULL;
dev = bus_find_device(&platform_bus_type, NULL, node, match);
if (!dev)
return NULL;
ctrl_usb = dev_get_drvdata(dev);
if (!ctrl_usb)
return NULL;
return &ctrl_usb->phy_ctrl;
}
EXPORT_SYMBOL_GPL(am335x_get_phy_control);
static int am335x_control_usb_probe(struct platform_device *pdev)
{
struct resource *res;
struct am335x_control_usb *ctrl_usb;
const struct of_device_id *of_id;
const struct phy_control *phy_ctrl;
of_id = of_match_node(omap_control_usb_id_table, pdev->dev.of_node);
if (!of_id)
return -EINVAL;
phy_ctrl = of_id->data;
ctrl_usb = devm_kzalloc(&pdev->dev, sizeof(*ctrl_usb), GFP_KERNEL);
if (!ctrl_usb) {
dev_err(&pdev->dev, "unable to alloc memory for control usb\n");
return -ENOMEM;
}
ctrl_usb->dev = &pdev->dev;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl");
ctrl_usb->phy_reg = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ctrl_usb->phy_reg))
return PTR_ERR(ctrl_usb->phy_reg);
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wakeup");
ctrl_usb->wkup = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ctrl_usb->wkup))
return PTR_ERR(ctrl_usb->wkup);
spin_lock_init(&ctrl_usb->lock);
ctrl_usb->phy_ctrl = *phy_ctrl;
dev_set_drvdata(ctrl_usb->dev, ctrl_usb);
return 0;
}
static struct platform_driver am335x_control_driver = {
.probe = am335x_control_usb_probe,
.driver = {
.name = "am335x-control-usb",
.owner = THIS_MODULE,
.of_match_table = omap_control_usb_id_table,
},
};
module_platform_driver(am335x_control_driver);
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,147 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/usb/otg.h>
#include <linux/usb/usb_phy_generic.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include "am35x-phy-control.h"
#include "phy-generic.h"
struct am335x_phy {
struct usb_phy_generic usb_phy_gen;
struct phy_control *phy_ctrl;
int id;
};
static int am335x_init(struct usb_phy *phy)
{
struct am335x_phy *am_phy = dev_get_drvdata(phy->dev);
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, true);
return 0;
}
static void am335x_shutdown(struct usb_phy *phy)
{
struct am335x_phy *am_phy = dev_get_drvdata(phy->dev);
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false);
}
static int am335x_phy_probe(struct platform_device *pdev)
{
struct am335x_phy *am_phy;
struct device *dev = &pdev->dev;
int ret;
am_phy = devm_kzalloc(dev, sizeof(*am_phy), GFP_KERNEL);
if (!am_phy)
return -ENOMEM;
am_phy->phy_ctrl = am335x_get_phy_control(dev);
if (!am_phy->phy_ctrl)
return -EPROBE_DEFER;
am_phy->id = of_alias_get_id(pdev->dev.of_node, "phy");
if (am_phy->id < 0) {
dev_err(&pdev->dev, "Missing PHY id: %d\n", am_phy->id);
return am_phy->id;
}
ret = usb_phy_gen_create_phy(dev, &am_phy->usb_phy_gen, NULL);
if (ret)
return ret;
ret = usb_add_phy_dev(&am_phy->usb_phy_gen.phy);
if (ret)
return ret;
am_phy->usb_phy_gen.phy.init = am335x_init;
am_phy->usb_phy_gen.phy.shutdown = am335x_shutdown;
platform_set_drvdata(pdev, am_phy);
device_init_wakeup(dev, true);
/*
* If we leave PHY wakeup enabled then AM33XX wakes up
* immediately from DS0. To avoid this we mark dev->power.can_wakeup
* to false. The same is checked in suspend routine to decide
* on whether to enable PHY wakeup or not.
* PHY wakeup works fine in standby mode, there by allowing us to
* handle remote wakeup, wakeup on disconnect and connect.
*/
device_set_wakeup_enable(dev, false);
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false);
return 0;
}
static int am335x_phy_remove(struct platform_device *pdev)
{
struct am335x_phy *am_phy = platform_get_drvdata(pdev);
usb_remove_phy(&am_phy->usb_phy_gen.phy);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int am335x_phy_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct am335x_phy *am_phy = platform_get_drvdata(pdev);
/*
* Enable phy wakeup only if dev->power.can_wakeup is true.
* Make sure to enable wakeup to support remote wakeup in
* standby mode ( same is not supported in OFF(DS0) mode).
* Enable it by doing
* echo enabled > /sys/bus/platform/devices/<usb-phy-id>/power/wakeup
*/
if (device_may_wakeup(dev))
phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, true);
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, false);
return 0;
}
static int am335x_phy_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct am335x_phy *am_phy = platform_get_drvdata(pdev);
phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, true);
if (device_may_wakeup(dev))
phy_ctrl_wkup(am_phy->phy_ctrl, am_phy->id, false);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(am335x_pm_ops, am335x_phy_suspend, am335x_phy_resume);
static const struct of_device_id am335x_phy_ids[] = {
{ .compatible = "ti,am335x-usb-phy" },
{ }
};
MODULE_DEVICE_TABLE(of, am335x_phy_ids);
static struct platform_driver am335x_phy_driver = {
.probe = am335x_phy_probe,
.remove = am335x_phy_remove,
.driver = {
.name = "am335x-phy-driver",
.owner = THIS_MODULE,
.pm = &am335x_pm_ops,
.of_match_table = am335x_phy_ids,
},
};
module_platform_driver(am335x_phy_driver);
MODULE_LICENSE("GPL v2");

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 <linux/usb/otg-fsm.h>
#include <linux/usb/otg.h>
#include <linux/ioctl.h>
/* USB Command Register Bit Masks */
#define USB_CMD_RUN_STOP (0x1<<0)
#define USB_CMD_CTRL_RESET (0x1<<1)
#define USB_CMD_PERIODIC_SCHEDULE_EN (0x1<<4)
#define USB_CMD_ASYNC_SCHEDULE_EN (0x1<<5)
#define USB_CMD_INT_AA_DOORBELL (0x1<<6)
#define USB_CMD_ASP (0x3<<8)
#define USB_CMD_ASYNC_SCH_PARK_EN (0x1<<11)
#define USB_CMD_SUTW (0x1<<13)
#define USB_CMD_ATDTW (0x1<<14)
#define USB_CMD_ITC (0xFF<<16)
/* bit 15,3,2 are frame list size */
#define USB_CMD_FRAME_SIZE_1024 (0x0<<15 | 0x0<<2)
#define USB_CMD_FRAME_SIZE_512 (0x0<<15 | 0x1<<2)
#define USB_CMD_FRAME_SIZE_256 (0x0<<15 | 0x2<<2)
#define USB_CMD_FRAME_SIZE_128 (0x0<<15 | 0x3<<2)
#define USB_CMD_FRAME_SIZE_64 (0x1<<15 | 0x0<<2)
#define USB_CMD_FRAME_SIZE_32 (0x1<<15 | 0x1<<2)
#define USB_CMD_FRAME_SIZE_16 (0x1<<15 | 0x2<<2)
#define USB_CMD_FRAME_SIZE_8 (0x1<<15 | 0x3<<2)
/* bit 9-8 are async schedule park mode count */
#define USB_CMD_ASP_00 (0x0<<8)
#define USB_CMD_ASP_01 (0x1<<8)
#define USB_CMD_ASP_10 (0x2<<8)
#define USB_CMD_ASP_11 (0x3<<8)
#define USB_CMD_ASP_BIT_POS (8)
/* bit 23-16 are interrupt threshold control */
#define USB_CMD_ITC_NO_THRESHOLD (0x00<<16)
#define USB_CMD_ITC_1_MICRO_FRM (0x01<<16)
#define USB_CMD_ITC_2_MICRO_FRM (0x02<<16)
#define USB_CMD_ITC_4_MICRO_FRM (0x04<<16)
#define USB_CMD_ITC_8_MICRO_FRM (0x08<<16)
#define USB_CMD_ITC_16_MICRO_FRM (0x10<<16)
#define USB_CMD_ITC_32_MICRO_FRM (0x20<<16)
#define USB_CMD_ITC_64_MICRO_FRM (0x40<<16)
#define USB_CMD_ITC_BIT_POS (16)
/* USB Status Register Bit Masks */
#define USB_STS_INT (0x1<<0)
#define USB_STS_ERR (0x1<<1)
#define USB_STS_PORT_CHANGE (0x1<<2)
#define USB_STS_FRM_LST_ROLL (0x1<<3)
#define USB_STS_SYS_ERR (0x1<<4)
#define USB_STS_IAA (0x1<<5)
#define USB_STS_RESET_RECEIVED (0x1<<6)
#define USB_STS_SOF (0x1<<7)
#define USB_STS_DCSUSPEND (0x1<<8)
#define USB_STS_HC_HALTED (0x1<<12)
#define USB_STS_RCL (0x1<<13)
#define USB_STS_PERIODIC_SCHEDULE (0x1<<14)
#define USB_STS_ASYNC_SCHEDULE (0x1<<15)
/* USB Interrupt Enable Register Bit Masks */
#define USB_INTR_INT_EN (0x1<<0)
#define USB_INTR_ERR_INT_EN (0x1<<1)
#define USB_INTR_PC_DETECT_EN (0x1<<2)
#define USB_INTR_FRM_LST_ROLL_EN (0x1<<3)
#define USB_INTR_SYS_ERR_EN (0x1<<4)
#define USB_INTR_ASYN_ADV_EN (0x1<<5)
#define USB_INTR_RESET_EN (0x1<<6)
#define USB_INTR_SOF_EN (0x1<<7)
#define USB_INTR_DEVICE_SUSPEND (0x1<<8)
/* Device Address bit masks */
#define USB_DEVICE_ADDRESS_MASK (0x7F<<25)
#define USB_DEVICE_ADDRESS_BIT_POS (25)
/* PORTSC Register Bit Masks,Only one PORT in OTG mode*/
#define PORTSC_CURRENT_CONNECT_STATUS (0x1<<0)
#define PORTSC_CONNECT_STATUS_CHANGE (0x1<<1)
#define PORTSC_PORT_ENABLE (0x1<<2)
#define PORTSC_PORT_EN_DIS_CHANGE (0x1<<3)
#define PORTSC_OVER_CURRENT_ACT (0x1<<4)
#define PORTSC_OVER_CUURENT_CHG (0x1<<5)
#define PORTSC_PORT_FORCE_RESUME (0x1<<6)
#define PORTSC_PORT_SUSPEND (0x1<<7)
#define PORTSC_PORT_RESET (0x1<<8)
#define PORTSC_LINE_STATUS_BITS (0x3<<10)
#define PORTSC_PORT_POWER (0x1<<12)
#define PORTSC_PORT_INDICTOR_CTRL (0x3<<14)
#define PORTSC_PORT_TEST_CTRL (0xF<<16)
#define PORTSC_WAKE_ON_CONNECT_EN (0x1<<20)
#define PORTSC_WAKE_ON_CONNECT_DIS (0x1<<21)
#define PORTSC_WAKE_ON_OVER_CURRENT (0x1<<22)
#define PORTSC_PHY_LOW_POWER_SPD (0x1<<23)
#define PORTSC_PORT_FORCE_FULL_SPEED (0x1<<24)
#define PORTSC_PORT_SPEED_MASK (0x3<<26)
#define PORTSC_TRANSCEIVER_WIDTH (0x1<<28)
#define PORTSC_PHY_TYPE_SEL (0x3<<30)
/* bit 11-10 are line status */
#define PORTSC_LINE_STATUS_SE0 (0x0<<10)
#define PORTSC_LINE_STATUS_JSTATE (0x1<<10)
#define PORTSC_LINE_STATUS_KSTATE (0x2<<10)
#define PORTSC_LINE_STATUS_UNDEF (0x3<<10)
#define PORTSC_LINE_STATUS_BIT_POS (10)
/* bit 15-14 are port indicator control */
#define PORTSC_PIC_OFF (0x0<<14)
#define PORTSC_PIC_AMBER (0x1<<14)
#define PORTSC_PIC_GREEN (0x2<<14)
#define PORTSC_PIC_UNDEF (0x3<<14)
#define PORTSC_PIC_BIT_POS (14)
/* bit 19-16 are port test control */
#define PORTSC_PTC_DISABLE (0x0<<16)
#define PORTSC_PTC_JSTATE (0x1<<16)
#define PORTSC_PTC_KSTATE (0x2<<16)
#define PORTSC_PTC_SEQNAK (0x3<<16)
#define PORTSC_PTC_PACKET (0x4<<16)
#define PORTSC_PTC_FORCE_EN (0x5<<16)
#define PORTSC_PTC_BIT_POS (16)
/* bit 27-26 are port speed */
#define PORTSC_PORT_SPEED_FULL (0x0<<26)
#define PORTSC_PORT_SPEED_LOW (0x1<<26)
#define PORTSC_PORT_SPEED_HIGH (0x2<<26)
#define PORTSC_PORT_SPEED_UNDEF (0x3<<26)
#define PORTSC_SPEED_BIT_POS (26)
/* bit 28 is parallel transceiver width for UTMI interface */
#define PORTSC_PTW (0x1<<28)
#define PORTSC_PTW_8BIT (0x0<<28)
#define PORTSC_PTW_16BIT (0x1<<28)
/* bit 31-30 are port transceiver select */
#define PORTSC_PTS_UTMI (0x0<<30)
#define PORTSC_PTS_ULPI (0x2<<30)
#define PORTSC_PTS_FSLS_SERIAL (0x3<<30)
#define PORTSC_PTS_BIT_POS (30)
#define PORTSC_W1C_BITS \
(PORTSC_CONNECT_STATUS_CHANGE | \
PORTSC_PORT_EN_DIS_CHANGE | \
PORTSC_OVER_CUURENT_CHG)
/* OTG Status Control Register Bit Masks */
#define OTGSC_CTRL_VBUS_DISCHARGE (0x1<<0)
#define OTGSC_CTRL_VBUS_CHARGE (0x1<<1)
#define OTGSC_CTRL_OTG_TERMINATION (0x1<<3)
#define OTGSC_CTRL_DATA_PULSING (0x1<<4)
#define OTGSC_CTRL_ID_PULL_EN (0x1<<5)
#define OTGSC_HA_DATA_PULSE (0x1<<6)
#define OTGSC_HA_BA (0x1<<7)
#define OTGSC_STS_USB_ID (0x1<<8)
#define OTGSC_STS_A_VBUS_VALID (0x1<<9)
#define OTGSC_STS_A_SESSION_VALID (0x1<<10)
#define OTGSC_STS_B_SESSION_VALID (0x1<<11)
#define OTGSC_STS_B_SESSION_END (0x1<<12)
#define OTGSC_STS_1MS_TOGGLE (0x1<<13)
#define OTGSC_STS_DATA_PULSING (0x1<<14)
#define OTGSC_INTSTS_USB_ID (0x1<<16)
#define OTGSC_INTSTS_A_VBUS_VALID (0x1<<17)
#define OTGSC_INTSTS_A_SESSION_VALID (0x1<<18)
#define OTGSC_INTSTS_B_SESSION_VALID (0x1<<19)
#define OTGSC_INTSTS_B_SESSION_END (0x1<<20)
#define OTGSC_INTSTS_1MS (0x1<<21)
#define OTGSC_INTSTS_DATA_PULSING (0x1<<22)
#define OTGSC_INTR_USB_ID_EN (0x1<<24)
#define OTGSC_INTR_A_VBUS_VALID_EN (0x1<<25)
#define OTGSC_INTR_A_SESSION_VALID_EN (0x1<<26)
#define OTGSC_INTR_B_SESSION_VALID_EN (0x1<<27)
#define OTGSC_INTR_B_SESSION_END_EN (0x1<<28)
#define OTGSC_INTR_1MS_TIMER_EN (0x1<<29)
#define OTGSC_INTR_DATA_PULSING_EN (0x1<<30)
#define OTGSC_INTSTS_MASK (0x00ff0000)
/* USB MODE Register Bit Masks */
#define USB_MODE_CTRL_MODE_IDLE (0x0<<0)
#define USB_MODE_CTRL_MODE_DEVICE (0x2<<0)
#define USB_MODE_CTRL_MODE_HOST (0x3<<0)
#define USB_MODE_CTRL_MODE_RSV (0x1<<0)
#define USB_MODE_SETUP_LOCK_OFF (0x1<<3)
#define USB_MODE_STREAM_DISABLE (0x1<<4)
#define USB_MODE_ES (0x1<<2) /* Endian Select */
/* control Register Bit Masks */
#define USB_CTRL_IOENB (0x1<<2)
#define USB_CTRL_ULPI_INT0EN (0x1<<0)
/* BCSR5 */
#define BCSR5_INT_USB (0x02)
/* USB module clk cfg */
#define SCCR_OFFS (0xA08)
#define SCCR_USB_CLK_DISABLE (0x00000000) /* USB clk disable */
#define SCCR_USB_MPHCM_11 (0x00c00000)
#define SCCR_USB_MPHCM_01 (0x00400000)
#define SCCR_USB_MPHCM_10 (0x00800000)
#define SCCR_USB_DRCM_11 (0x00300000)
#define SCCR_USB_DRCM_01 (0x00100000)
#define SCCR_USB_DRCM_10 (0x00200000)
#define SICRL_OFFS (0x114)
#define SICRL_USB0 (0x40000000)
#define SICRL_USB1 (0x20000000)
#define SICRH_OFFS (0x118)
#define SICRH_USB_UTMI (0x00020000)
/* OTG interrupt enable bit masks */
#define OTGSC_INTERRUPT_ENABLE_BITS_MASK \
(OTGSC_INTR_USB_ID_EN | \
OTGSC_INTR_1MS_TIMER_EN | \
OTGSC_INTR_A_VBUS_VALID_EN | \
OTGSC_INTR_A_SESSION_VALID_EN | \
OTGSC_INTR_B_SESSION_VALID_EN | \
OTGSC_INTR_B_SESSION_END_EN | \
OTGSC_INTR_DATA_PULSING_EN)
/* OTG interrupt status bit masks */
#define OTGSC_INTERRUPT_STATUS_BITS_MASK \
(OTGSC_INTSTS_USB_ID | \
OTGSC_INTR_1MS_TIMER_EN | \
OTGSC_INTSTS_A_VBUS_VALID | \
OTGSC_INTSTS_A_SESSION_VALID | \
OTGSC_INTSTS_B_SESSION_VALID | \
OTGSC_INTSTS_B_SESSION_END | \
OTGSC_INTSTS_DATA_PULSING)
/*
* A-DEVICE timing constants
*/
/* Wait for VBUS Rise */
#define TA_WAIT_VRISE (100) /* a_wait_vrise 100 ms, section: 6.6.5.1 */
/* Wait for B-Connect */
#define TA_WAIT_BCON (10000) /* a_wait_bcon > 1 sec, section: 6.6.5.2
* This is only used to get out of
* OTG_STATE_A_WAIT_BCON state if there was
* no connection for these many milliseconds
*/
/* A-Idle to B-Disconnect */
/* It is necessary for this timer to be more than 750 ms because of a bug in OPT
* test 5.4 in which B OPT disconnects after 750 ms instead of 75ms as stated
* in the test description
*/
#define TA_AIDL_BDIS (5000) /* a_suspend minimum 200 ms, section: 6.6.5.3 */
/* B-Idle to A-Disconnect */
#define TA_BIDL_ADIS (12) /* 3 to 200 ms */
/* B-device timing constants */
/* Data-Line Pulse Time*/
#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms, section:5.3.3 */
#define TB_DATA_PLS_MIN (5) /* minimum 5 ms */
#define TB_DATA_PLS_MAX (10) /* maximum 10 ms */
/* SRP Initiate Time */
#define TB_SRP_INIT (100) /* b_srp_init,maximum 100 ms, section:5.3.8 */
/* SRP Fail Time */
#define TB_SRP_FAIL (7000) /* b_srp_init,Fail time 5~30s, section:6.8.2.2*/
/* SRP result wait time */
#define TB_SRP_WAIT (60)
/* VBus time */
#define TB_VBUS_PLS (30) /* time to keep vbus pulsing asserted */
/* Discharge time */
/* This time should be less than 10ms. It varies from system to system. */
#define TB_VBUS_DSCHRG (8)
/* A-SE0 to B-Reset */
#define TB_ASE0_BRST (20) /* b_wait_acon, mini 3.125 ms,section:6.8.2.4 */
/* A bus suspend timer before we can switch to b_wait_aconn */
#define TB_A_SUSPEND (7)
#define TB_BUS_RESUME (12)
/* SE0 Time Before SRP */
#define TB_SE0_SRP (2) /* b_idle,minimum 2 ms, section:5.3.2 */
#define SET_OTG_STATE(otg_ptr, newstate) ((otg_ptr)->state = newstate)
struct usb_dr_mmap {
/* Capability register */
u8 res1[256];
u16 caplength; /* Capability Register Length */
u16 hciversion; /* Host Controller Interface Version */
u32 hcsparams; /* Host Controller Structual Parameters */
u32 hccparams; /* Host Controller Capability Parameters */
u8 res2[20];
u32 dciversion; /* Device Controller Interface Version */
u32 dccparams; /* Device Controller Capability Parameters */
u8 res3[24];
/* Operation register */
u32 usbcmd; /* USB Command Register */
u32 usbsts; /* USB Status Register */
u32 usbintr; /* USB Interrupt Enable Register */
u32 frindex; /* Frame Index Register */
u8 res4[4];
u32 deviceaddr; /* Device Address */
u32 endpointlistaddr; /* Endpoint List Address Register */
u8 res5[4];
u32 burstsize; /* Master Interface Data Burst Size Register */
u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */
u8 res6[8];
u32 ulpiview; /* ULPI register access */
u8 res7[12];
u32 configflag; /* Configure Flag Register */
u32 portsc; /* Port 1 Status and Control Register */
u8 res8[28];
u32 otgsc; /* On-The-Go Status and Control */
u32 usbmode; /* USB Mode Register */
u32 endptsetupstat; /* Endpoint Setup Status Register */
u32 endpointprime; /* Endpoint Initialization Register */
u32 endptflush; /* Endpoint Flush Register */
u32 endptstatus; /* Endpoint Status Register */
u32 endptcomplete; /* Endpoint Complete Register */
u32 endptctrl[6]; /* Endpoint Control Registers */
u8 res9[552];
u32 snoop1;
u32 snoop2;
u32 age_cnt_thresh; /* Age Count Threshold Register */
u32 pri_ctrl; /* Priority Control Register */
u32 si_ctrl; /* System Interface Control Register */
u8 res10[236];
u32 control; /* General Purpose Control Register */
};
struct fsl_otg_timer {
unsigned long expires; /* Number of count increase to timeout */
unsigned long count; /* Tick counter */
void (*function)(unsigned long); /* Timeout function */
unsigned long data; /* Data passed to function */
struct list_head list;
};
inline struct fsl_otg_timer *otg_timer_initializer
(void (*function)(unsigned long), unsigned long expires, unsigned long data)
{
struct fsl_otg_timer *timer;
timer = kmalloc(sizeof(struct fsl_otg_timer), GFP_KERNEL);
if (!timer)
return NULL;
timer->function = function;
timer->expires = expires;
timer->data = data;
return timer;
}
struct fsl_otg {
struct usb_phy phy;
struct otg_fsm fsm;
struct usb_dr_mmap *dr_mem_map;
struct delayed_work otg_event;
/* used for usb host */
struct work_struct work_wq;
u8 host_working;
int irq;
};
struct fsl_otg_config {
u8 otg_port;
};
/* For SRP and HNP handle */
#define FSL_OTG_MAJOR 240
#define FSL_OTG_NAME "fsl-usb2-otg"
/* Command to OTG driver ioctl */
#define OTG_IOCTL_MAGIC FSL_OTG_MAJOR
/* if otg work as host, it should return 1, otherwise return 0 */
#define GET_OTG_STATUS _IOR(OTG_IOCTL_MAGIC, 1, int)
#define SET_A_SUSPEND_REQ _IOW(OTG_IOCTL_MAGIC, 2, int)
#define SET_A_BUS_DROP _IOW(OTG_IOCTL_MAGIC, 3, int)
#define SET_A_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 4, int)
#define SET_B_BUS_REQ _IOW(OTG_IOCTL_MAGIC, 5, int)
#define GET_A_SUSPEND_REQ _IOR(OTG_IOCTL_MAGIC, 6, int)
#define GET_A_BUS_DROP _IOR(OTG_IOCTL_MAGIC, 7, int)
#define GET_A_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 8, int)
#define GET_B_BUS_REQ _IOR(OTG_IOCTL_MAGIC, 9, int)
void fsl_otg_add_timer(struct otg_fsm *fsm, void *timer);
void fsl_otg_del_timer(struct otg_fsm *fsm, void *timer);
void fsl_otg_pulse_vbus(void);

View file

@ -0,0 +1,309 @@
/*
* drivers/usb/otg/nop-usb-xceiv.c
*
* NOP USB transceiver for all USB transceiver which are either built-in
* into USB IP or which are mostly autonomous.
*
* Copyright (C) 2009 Texas Instruments Inc
* Author: Ajay Kumar Gupta <ajay.gupta@ti.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Current status:
* This provides a "nop" transceiver for PHYs which are
* autonomous such as isp1504, isp1707, etc.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/usb/otg.h>
#include <linux/usb/usb_phy_generic.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include "phy-generic.h"
struct platform_device *usb_phy_generic_register(void)
{
return platform_device_register_simple("usb_phy_generic",
PLATFORM_DEVID_AUTO, NULL, 0);
}
EXPORT_SYMBOL_GPL(usb_phy_generic_register);
void usb_phy_generic_unregister(struct platform_device *pdev)
{
platform_device_unregister(pdev);
}
EXPORT_SYMBOL_GPL(usb_phy_generic_unregister);
static int nop_set_suspend(struct usb_phy *x, int suspend)
{
return 0;
}
static void nop_reset_set(struct usb_phy_generic *nop, int asserted)
{
int value;
if (!gpio_is_valid(nop->gpio_reset))
return;
value = asserted;
if (nop->reset_active_low)
value = !value;
gpio_set_value_cansleep(nop->gpio_reset, value);
if (!asserted)
usleep_range(10000, 20000);
}
int usb_gen_phy_init(struct usb_phy *phy)
{
struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
if (!IS_ERR(nop->vcc)) {
if (regulator_enable(nop->vcc))
dev_err(phy->dev, "Failed to enable power\n");
}
if (!IS_ERR(nop->clk))
clk_prepare_enable(nop->clk);
/* De-assert RESET */
nop_reset_set(nop, 0);
return 0;
}
EXPORT_SYMBOL_GPL(usb_gen_phy_init);
void usb_gen_phy_shutdown(struct usb_phy *phy)
{
struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
/* Assert RESET */
nop_reset_set(nop, 1);
if (!IS_ERR(nop->clk))
clk_disable_unprepare(nop->clk);
if (!IS_ERR(nop->vcc)) {
if (regulator_disable(nop->vcc))
dev_err(phy->dev, "Failed to disable power\n");
}
}
EXPORT_SYMBOL_GPL(usb_gen_phy_shutdown);
static int nop_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget)
{
if (!otg)
return -ENODEV;
if (!gadget) {
otg->gadget = NULL;
return -ENODEV;
}
otg->gadget = gadget;
otg->phy->state = OTG_STATE_B_IDLE;
return 0;
}
static int nop_set_host(struct usb_otg *otg, struct usb_bus *host)
{
if (!otg)
return -ENODEV;
if (!host) {
otg->host = NULL;
return -ENODEV;
}
otg->host = host;
return 0;
}
int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop,
struct usb_phy_generic_platform_data *pdata)
{
enum usb_phy_type type = USB_PHY_TYPE_USB2;
int err;
u32 clk_rate = 0;
bool needs_vcc = false;
nop->reset_active_low = true; /* default behaviour */
if (dev->of_node) {
struct device_node *node = dev->of_node;
enum of_gpio_flags flags = 0;
if (of_property_read_u32(node, "clock-frequency", &clk_rate))
clk_rate = 0;
needs_vcc = of_property_read_bool(node, "vcc-supply");
nop->gpio_reset = of_get_named_gpio_flags(node, "reset-gpios",
0, &flags);
if (nop->gpio_reset == -EPROBE_DEFER)
return -EPROBE_DEFER;
nop->reset_active_low = flags & OF_GPIO_ACTIVE_LOW;
} else if (pdata) {
type = pdata->type;
clk_rate = pdata->clk_rate;
needs_vcc = pdata->needs_vcc;
nop->gpio_reset = pdata->gpio_reset;
} else {
nop->gpio_reset = -1;
}
nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg),
GFP_KERNEL);
if (!nop->phy.otg)
return -ENOMEM;
nop->clk = devm_clk_get(dev, "main_clk");
if (IS_ERR(nop->clk)) {
dev_dbg(dev, "Can't get phy clock: %ld\n",
PTR_ERR(nop->clk));
}
if (!IS_ERR(nop->clk) && clk_rate) {
err = clk_set_rate(nop->clk, clk_rate);
if (err) {
dev_err(dev, "Error setting clock rate\n");
return err;
}
}
nop->vcc = devm_regulator_get(dev, "vcc");
if (IS_ERR(nop->vcc)) {
dev_dbg(dev, "Error getting vcc regulator: %ld\n",
PTR_ERR(nop->vcc));
if (needs_vcc)
return -EPROBE_DEFER;
}
if (gpio_is_valid(nop->gpio_reset)) {
unsigned long gpio_flags;
/* Assert RESET */
if (nop->reset_active_low)
gpio_flags = GPIOF_OUT_INIT_LOW;
else
gpio_flags = GPIOF_OUT_INIT_HIGH;
err = devm_gpio_request_one(dev, nop->gpio_reset,
gpio_flags, dev_name(dev));
if (err) {
dev_err(dev, "Error requesting RESET GPIO %d\n",
nop->gpio_reset);
return err;
}
}
nop->dev = dev;
nop->phy.dev = nop->dev;
nop->phy.label = "nop-xceiv";
nop->phy.set_suspend = nop_set_suspend;
nop->phy.state = OTG_STATE_UNDEFINED;
nop->phy.type = type;
nop->phy.otg->phy = &nop->phy;
nop->phy.otg->set_host = nop_set_host;
nop->phy.otg->set_peripheral = nop_set_peripheral;
return 0;
}
EXPORT_SYMBOL_GPL(usb_phy_gen_create_phy);
static int usb_phy_generic_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct usb_phy_generic *nop;
int err;
nop = devm_kzalloc(dev, sizeof(*nop), GFP_KERNEL);
if (!nop)
return -ENOMEM;
err = usb_phy_gen_create_phy(dev, nop, dev_get_platdata(&pdev->dev));
if (err)
return err;
nop->phy.init = usb_gen_phy_init;
nop->phy.shutdown = usb_gen_phy_shutdown;
err = usb_add_phy_dev(&nop->phy);
if (err) {
dev_err(&pdev->dev, "can't register transceiver, err: %d\n",
err);
return err;
}
platform_set_drvdata(pdev, nop);
return 0;
}
static int usb_phy_generic_remove(struct platform_device *pdev)
{
struct usb_phy_generic *nop = platform_get_drvdata(pdev);
usb_remove_phy(&nop->phy);
return 0;
}
static const struct of_device_id nop_xceiv_dt_ids[] = {
{ .compatible = "usb-nop-xceiv" },
{ }
};
MODULE_DEVICE_TABLE(of, nop_xceiv_dt_ids);
static struct platform_driver usb_phy_generic_driver = {
.probe = usb_phy_generic_probe,
.remove = usb_phy_generic_remove,
.driver = {
.name = "usb_phy_generic",
.owner = THIS_MODULE,
.of_match_table = nop_xceiv_dt_ids,
},
};
static int __init usb_phy_generic_init(void)
{
return platform_driver_register(&usb_phy_generic_driver);
}
subsys_initcall(usb_phy_generic_init);
static void __exit usb_phy_generic_exit(void)
{
platform_driver_unregister(&usb_phy_generic_driver);
}
module_exit(usb_phy_generic_exit);
MODULE_ALIAS("platform:usb_phy_generic");
MODULE_AUTHOR("Texas Instruments Inc");
MODULE_DESCRIPTION("NOP USB Transceiver driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,21 @@
#ifndef _PHY_GENERIC_H_
#define _PHY_GENERIC_H_
#include <linux/usb/usb_phy_generic.h>
struct usb_phy_generic {
struct usb_phy phy;
struct device *dev;
struct clk *clk;
struct regulator *vcc;
int gpio_reset;
bool reset_active_low;
};
int usb_gen_phy_init(struct usb_phy *phy);
void usb_gen_phy_shutdown(struct usb_phy *phy);
int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop,
struct usb_phy_generic_platform_data *pdata);
#endif

View file

@ -0,0 +1,396 @@
/*
* gpio-vbus.c - simple GPIO VBUS sensing driver for B peripheral devices
*
* Copyright (c) 2008 Philipp Zabel <philipp.zabel@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/usb.h>
#include <linux/workqueue.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/gadget.h>
#include <linux/usb/gpio_vbus.h>
#include <linux/usb/otg.h>
/*
* A simple GPIO VBUS sensing driver for B peripheral only devices
* with internal transceivers. It can control a D+ pullup GPIO and
* a regulator to limit the current drawn from VBUS.
*
* Needs to be loaded before the UDC driver that will use it.
*/
struct gpio_vbus_data {
struct usb_phy phy;
struct device *dev;
struct regulator *vbus_draw;
int vbus_draw_enabled;
unsigned mA;
struct delayed_work work;
int vbus;
int irq;
};
/*
* This driver relies on "both edges" triggering. VBUS has 100 msec to
* stabilize, so the peripheral controller driver may need to cope with
* some bouncing due to current surges (e.g. charging local capacitance)
* and contact chatter.
*
* REVISIT in desperate straits, toggling between rising and falling
* edges might be workable.
*/
#define VBUS_IRQ_FLAGS \
(IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
/* interface to regulator framework */
static void set_vbus_draw(struct gpio_vbus_data *gpio_vbus, unsigned mA)
{
struct regulator *vbus_draw = gpio_vbus->vbus_draw;
int enabled;
int ret;
if (!vbus_draw)
return;
enabled = gpio_vbus->vbus_draw_enabled;
if (mA) {
regulator_set_current_limit(vbus_draw, 0, 1000 * mA);
if (!enabled) {
ret = regulator_enable(vbus_draw);
if (ret < 0)
return;
gpio_vbus->vbus_draw_enabled = 1;
}
} else {
if (enabled) {
ret = regulator_disable(vbus_draw);
if (ret < 0)
return;
gpio_vbus->vbus_draw_enabled = 0;
}
}
gpio_vbus->mA = mA;
}
static int is_vbus_powered(struct gpio_vbus_mach_info *pdata)
{
int vbus;
vbus = gpio_get_value(pdata->gpio_vbus);
if (pdata->gpio_vbus_inverted)
vbus = !vbus;
return vbus;
}
static void gpio_vbus_work(struct work_struct *work)
{
struct gpio_vbus_data *gpio_vbus =
container_of(work, struct gpio_vbus_data, work.work);
struct gpio_vbus_mach_info *pdata = dev_get_platdata(gpio_vbus->dev);
int gpio, status, vbus;
if (!gpio_vbus->phy.otg->gadget)
return;
vbus = is_vbus_powered(pdata);
if ((vbus ^ gpio_vbus->vbus) == 0)
return;
gpio_vbus->vbus = vbus;
/* Peripheral controllers which manage the pullup themselves won't have
* gpio_pullup configured here. If it's configured here, we'll do what
* isp1301_omap::b_peripheral() does and enable the pullup here... although
* that may complicate usb_gadget_{,dis}connect() support.
*/
gpio = pdata->gpio_pullup;
if (vbus) {
status = USB_EVENT_VBUS;
gpio_vbus->phy.state = OTG_STATE_B_PERIPHERAL;
gpio_vbus->phy.last_event = status;
usb_gadget_vbus_connect(gpio_vbus->phy.otg->gadget);
/* drawing a "unit load" is *always* OK, except for OTG */
set_vbus_draw(gpio_vbus, 100);
/* optionally enable D+ pullup */
if (gpio_is_valid(gpio))
gpio_set_value(gpio, !pdata->gpio_pullup_inverted);
atomic_notifier_call_chain(&gpio_vbus->phy.notifier,
status, gpio_vbus->phy.otg->gadget);
} else {
/* optionally disable D+ pullup */
if (gpio_is_valid(gpio))
gpio_set_value(gpio, pdata->gpio_pullup_inverted);
set_vbus_draw(gpio_vbus, 0);
usb_gadget_vbus_disconnect(gpio_vbus->phy.otg->gadget);
status = USB_EVENT_NONE;
gpio_vbus->phy.state = OTG_STATE_B_IDLE;
gpio_vbus->phy.last_event = status;
atomic_notifier_call_chain(&gpio_vbus->phy.notifier,
status, gpio_vbus->phy.otg->gadget);
}
}
/* VBUS change IRQ handler */
static irqreturn_t gpio_vbus_irq(int irq, void *data)
{
struct platform_device *pdev = data;
struct gpio_vbus_mach_info *pdata = dev_get_platdata(&pdev->dev);
struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev);
struct usb_otg *otg = gpio_vbus->phy.otg;
dev_dbg(&pdev->dev, "VBUS %s (gadget: %s)\n",
is_vbus_powered(pdata) ? "supplied" : "inactive",
otg->gadget ? otg->gadget->name : "none");
if (otg->gadget)
schedule_delayed_work(&gpio_vbus->work, msecs_to_jiffies(100));
return IRQ_HANDLED;
}
/* OTG transceiver interface */
/* bind/unbind the peripheral controller */
static int gpio_vbus_set_peripheral(struct usb_otg *otg,
struct usb_gadget *gadget)
{
struct gpio_vbus_data *gpio_vbus;
struct gpio_vbus_mach_info *pdata;
struct platform_device *pdev;
int gpio;
gpio_vbus = container_of(otg->phy, struct gpio_vbus_data, phy);
pdev = to_platform_device(gpio_vbus->dev);
pdata = dev_get_platdata(gpio_vbus->dev);
gpio = pdata->gpio_pullup;
if (!gadget) {
dev_dbg(&pdev->dev, "unregistering gadget '%s'\n",
otg->gadget->name);
/* optionally disable D+ pullup */
if (gpio_is_valid(gpio))
gpio_set_value(gpio, pdata->gpio_pullup_inverted);
set_vbus_draw(gpio_vbus, 0);
usb_gadget_vbus_disconnect(otg->gadget);
otg->phy->state = OTG_STATE_UNDEFINED;
otg->gadget = NULL;
return 0;
}
otg->gadget = gadget;
dev_dbg(&pdev->dev, "registered gadget '%s'\n", gadget->name);
/* initialize connection state */
gpio_vbus->vbus = 0; /* start with disconnected */
gpio_vbus_irq(gpio_vbus->irq, pdev);
return 0;
}
/* effective for B devices, ignored for A-peripheral */
static int gpio_vbus_set_power(struct usb_phy *phy, unsigned mA)
{
struct gpio_vbus_data *gpio_vbus;
gpio_vbus = container_of(phy, struct gpio_vbus_data, phy);
if (phy->state == OTG_STATE_B_PERIPHERAL)
set_vbus_draw(gpio_vbus, mA);
return 0;
}
/* for non-OTG B devices: set/clear transceiver suspend mode */
static int gpio_vbus_set_suspend(struct usb_phy *phy, int suspend)
{
struct gpio_vbus_data *gpio_vbus;
gpio_vbus = container_of(phy, struct gpio_vbus_data, phy);
/* draw max 0 mA from vbus in suspend mode; or the previously
* recorded amount of current if not suspended
*
* NOTE: high powered configs (mA > 100) may draw up to 2.5 mA
* if they're wake-enabled ... we don't handle that yet.
*/
return gpio_vbus_set_power(phy, suspend ? 0 : gpio_vbus->mA);
}
/* platform driver interface */
static int gpio_vbus_probe(struct platform_device *pdev)
{
struct gpio_vbus_mach_info *pdata = dev_get_platdata(&pdev->dev);
struct gpio_vbus_data *gpio_vbus;
struct resource *res;
int err, gpio, irq;
unsigned long irqflags;
if (!pdata || !gpio_is_valid(pdata->gpio_vbus))
return -EINVAL;
gpio = pdata->gpio_vbus;
gpio_vbus = devm_kzalloc(&pdev->dev, sizeof(struct gpio_vbus_data),
GFP_KERNEL);
if (!gpio_vbus)
return -ENOMEM;
gpio_vbus->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg),
GFP_KERNEL);
if (!gpio_vbus->phy.otg)
return -ENOMEM;
platform_set_drvdata(pdev, gpio_vbus);
gpio_vbus->dev = &pdev->dev;
gpio_vbus->phy.label = "gpio-vbus";
gpio_vbus->phy.dev = gpio_vbus->dev;
gpio_vbus->phy.set_power = gpio_vbus_set_power;
gpio_vbus->phy.set_suspend = gpio_vbus_set_suspend;
gpio_vbus->phy.state = OTG_STATE_UNDEFINED;
gpio_vbus->phy.otg->phy = &gpio_vbus->phy;
gpio_vbus->phy.otg->set_peripheral = gpio_vbus_set_peripheral;
err = devm_gpio_request(&pdev->dev, gpio, "vbus_detect");
if (err) {
dev_err(&pdev->dev, "can't request vbus gpio %d, err: %d\n",
gpio, err);
return err;
}
gpio_direction_input(gpio);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res) {
irq = res->start;
irqflags = (res->flags & IRQF_TRIGGER_MASK) | IRQF_SHARED;
} else {
irq = gpio_to_irq(gpio);
irqflags = VBUS_IRQ_FLAGS;
}
gpio_vbus->irq = irq;
/* if data line pullup is in use, initialize it to "not pulling up" */
gpio = pdata->gpio_pullup;
if (gpio_is_valid(gpio)) {
err = devm_gpio_request(&pdev->dev, gpio, "udc_pullup");
if (err) {
dev_err(&pdev->dev,
"can't request pullup gpio %d, err: %d\n",
gpio, err);
return err;
}
gpio_direction_output(gpio, pdata->gpio_pullup_inverted);
}
err = devm_request_irq(&pdev->dev, irq, gpio_vbus_irq, irqflags,
"vbus_detect", pdev);
if (err) {
dev_err(&pdev->dev, "can't request irq %i, err: %d\n",
irq, err);
return err;
}
INIT_DELAYED_WORK(&gpio_vbus->work, gpio_vbus_work);
gpio_vbus->vbus_draw = devm_regulator_get(&pdev->dev, "vbus_draw");
if (IS_ERR(gpio_vbus->vbus_draw)) {
dev_dbg(&pdev->dev, "can't get vbus_draw regulator, err: %ld\n",
PTR_ERR(gpio_vbus->vbus_draw));
gpio_vbus->vbus_draw = NULL;
}
/* only active when a gadget is registered */
err = usb_add_phy(&gpio_vbus->phy, USB_PHY_TYPE_USB2);
if (err) {
dev_err(&pdev->dev, "can't register transceiver, err: %d\n",
err);
return err;
}
device_init_wakeup(&pdev->dev, pdata->wakeup);
return 0;
}
static int gpio_vbus_remove(struct platform_device *pdev)
{
struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev);
device_init_wakeup(&pdev->dev, 0);
cancel_delayed_work_sync(&gpio_vbus->work);
usb_remove_phy(&gpio_vbus->phy);
return 0;
}
#ifdef CONFIG_PM
static int gpio_vbus_pm_suspend(struct device *dev)
{
struct gpio_vbus_data *gpio_vbus = dev_get_drvdata(dev);
if (device_may_wakeup(dev))
enable_irq_wake(gpio_vbus->irq);
return 0;
}
static int gpio_vbus_pm_resume(struct device *dev)
{
struct gpio_vbus_data *gpio_vbus = dev_get_drvdata(dev);
if (device_may_wakeup(dev))
disable_irq_wake(gpio_vbus->irq);
return 0;
}
static const struct dev_pm_ops gpio_vbus_dev_pm_ops = {
.suspend = gpio_vbus_pm_suspend,
.resume = gpio_vbus_pm_resume,
};
#endif
MODULE_ALIAS("platform:gpio-vbus");
static struct platform_driver gpio_vbus_driver = {
.driver = {
.name = "gpio-vbus",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &gpio_vbus_dev_pm_ops,
#endif
},
.probe = gpio_vbus_probe,
.remove = gpio_vbus_remove,
};
module_platform_driver(gpio_vbus_driver);
MODULE_DESCRIPTION("simple GPIO controlled OTG transceiver driver");
MODULE_AUTHOR("Philipp Zabel");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,163 @@
/*
* NXP ISP1301 USB transceiver driver
*
* Copyright (C) 2012 Roland Stigge
*
* Author: Roland Stigge <stigge@antcom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/i2c.h>
#include <linux/usb/phy.h>
#include <linux/usb/isp1301.h>
#define DRV_NAME "isp1301"
struct isp1301 {
struct usb_phy phy;
struct mutex mutex;
struct i2c_client *client;
};
#define phy_to_isp(p) (container_of((p), struct isp1301, phy))
static const struct i2c_device_id isp1301_id[] = {
{ "isp1301", 0 },
{ }
};
static struct i2c_client *isp1301_i2c_client;
static int __isp1301_write(struct isp1301 *isp, u8 reg, u8 value, u8 clear)
{
return i2c_smbus_write_byte_data(isp->client, reg | clear, value);
}
static int isp1301_write(struct isp1301 *isp, u8 reg, u8 value)
{
return __isp1301_write(isp, reg, value, 0);
}
static int isp1301_clear(struct isp1301 *isp, u8 reg, u8 value)
{
return __isp1301_write(isp, reg, value, ISP1301_I2C_REG_CLEAR_ADDR);
}
static int isp1301_phy_init(struct usb_phy *phy)
{
struct isp1301 *isp = phy_to_isp(phy);
/* Disable transparent UART mode first */
isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_UART_EN);
isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_1, ~MC1_SPEED_REG);
isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_SPEED_REG);
isp1301_clear(isp, ISP1301_I2C_MODE_CONTROL_2, ~0);
isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_2, (MC2_BI_DI | MC2_PSW_EN
| MC2_SPD_SUSP_CTRL));
isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, ~0);
isp1301_write(isp, ISP1301_I2C_MODE_CONTROL_1, MC1_DAT_SE0);
isp1301_write(isp, ISP1301_I2C_OTG_CONTROL_1, (OTG1_DM_PULLDOWN
| OTG1_DP_PULLDOWN));
isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, (OTG1_DM_PULLUP
| OTG1_DP_PULLUP));
/* mask all interrupts */
isp1301_clear(isp, ISP1301_I2C_INTERRUPT_LATCH, ~0);
isp1301_clear(isp, ISP1301_I2C_INTERRUPT_FALLING, ~0);
isp1301_clear(isp, ISP1301_I2C_INTERRUPT_RISING, ~0);
return 0;
}
static int isp1301_phy_set_vbus(struct usb_phy *phy, int on)
{
struct isp1301 *isp = phy_to_isp(phy);
if (on)
isp1301_write(isp, ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV);
else
isp1301_clear(isp, ISP1301_I2C_OTG_CONTROL_1, OTG1_VBUS_DRV);
return 0;
}
static int isp1301_probe(struct i2c_client *client,
const struct i2c_device_id *i2c_id)
{
struct isp1301 *isp;
struct usb_phy *phy;
isp = devm_kzalloc(&client->dev, sizeof(*isp), GFP_KERNEL);
if (!isp)
return -ENOMEM;
isp->client = client;
mutex_init(&isp->mutex);
phy = &isp->phy;
phy->dev = &client->dev;
phy->label = DRV_NAME;
phy->init = isp1301_phy_init;
phy->set_vbus = isp1301_phy_set_vbus;
phy->type = USB_PHY_TYPE_USB2;
i2c_set_clientdata(client, isp);
usb_add_phy_dev(phy);
isp1301_i2c_client = client;
return 0;
}
static int isp1301_remove(struct i2c_client *client)
{
struct isp1301 *isp = i2c_get_clientdata(client);
usb_remove_phy(&isp->phy);
isp1301_i2c_client = NULL;
return 0;
}
static struct i2c_driver isp1301_driver = {
.driver = {
.name = DRV_NAME,
},
.probe = isp1301_probe,
.remove = isp1301_remove,
.id_table = isp1301_id,
};
module_i2c_driver(isp1301_driver);
static int match(struct device *dev, void *data)
{
struct device_node *node = (struct device_node *)data;
return (dev->of_node == node) &&
(dev->driver == &isp1301_driver.driver);
}
struct i2c_client *isp1301_get_client(struct device_node *node)
{
if (node) { /* reference of ISP1301 I2C node via DT */
struct device *dev = bus_find_device(&i2c_bus_type, NULL,
node, match);
if (!dev)
return NULL;
return to_i2c_client(dev);
} else { /* non-DT: only one ISP1301 chip supported */
return isp1301_i2c_client;
}
}
EXPORT_SYMBOL_GPL(isp1301_get_client);
MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>");
MODULE_DESCRIPTION("NXP ISP1301 USB transceiver driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,136 @@
/*
* phy-keystone - USB PHY, talking to dwc3 controller in Keystone.
*
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Author: WingMan Kwok <w-kwok2@ti.com>
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/usb/usb_phy_generic.h>
#include <linux/io.h>
#include <linux/of.h>
#include "phy-generic.h"
/* USB PHY control register offsets */
#define USB_PHY_CTL_UTMI 0x0000
#define USB_PHY_CTL_PIPE 0x0004
#define USB_PHY_CTL_PARAM_1 0x0008
#define USB_PHY_CTL_PARAM_2 0x000c
#define USB_PHY_CTL_CLOCK 0x0010
#define USB_PHY_CTL_PLL 0x0014
#define PHY_REF_SSP_EN BIT(29)
struct keystone_usbphy {
struct usb_phy_generic usb_phy_gen;
void __iomem *phy_ctrl;
};
static inline u32 keystone_usbphy_readl(void __iomem *base, u32 offset)
{
return readl(base + offset);
}
static inline void keystone_usbphy_writel(void __iomem *base,
u32 offset, u32 value)
{
writel(value, base + offset);
}
static int keystone_usbphy_init(struct usb_phy *phy)
{
struct keystone_usbphy *k_phy = dev_get_drvdata(phy->dev);
u32 val;
val = keystone_usbphy_readl(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK);
keystone_usbphy_writel(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK,
val | PHY_REF_SSP_EN);
return 0;
}
static void keystone_usbphy_shutdown(struct usb_phy *phy)
{
struct keystone_usbphy *k_phy = dev_get_drvdata(phy->dev);
u32 val;
val = keystone_usbphy_readl(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK);
keystone_usbphy_writel(k_phy->phy_ctrl, USB_PHY_CTL_CLOCK,
val &= ~PHY_REF_SSP_EN);
}
static int keystone_usbphy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct keystone_usbphy *k_phy;
struct resource *res;
int ret;
k_phy = devm_kzalloc(dev, sizeof(*k_phy), GFP_KERNEL);
if (!k_phy)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
k_phy->phy_ctrl = devm_ioremap_resource(dev, res);
if (IS_ERR(k_phy->phy_ctrl))
return PTR_ERR(k_phy->phy_ctrl);
ret = usb_phy_gen_create_phy(dev, &k_phy->usb_phy_gen, NULL);
if (ret)
return ret;
k_phy->usb_phy_gen.phy.init = keystone_usbphy_init;
k_phy->usb_phy_gen.phy.shutdown = keystone_usbphy_shutdown;
platform_set_drvdata(pdev, k_phy);
ret = usb_add_phy_dev(&k_phy->usb_phy_gen.phy);
if (ret)
return ret;
return 0;
}
static int keystone_usbphy_remove(struct platform_device *pdev)
{
struct keystone_usbphy *k_phy = platform_get_drvdata(pdev);
usb_remove_phy(&k_phy->usb_phy_gen.phy);
return 0;
}
static const struct of_device_id keystone_usbphy_ids[] = {
{ .compatible = "ti,keystone-usbphy" },
{ }
};
MODULE_DEVICE_TABLE(of, keystone_usbphy_ids);
static struct platform_driver keystone_usbphy_driver = {
.probe = keystone_usbphy_probe,
.remove = keystone_usbphy_remove,
.driver = {
.name = "keystone-usbphy",
.owner = THIS_MODULE,
.of_match_table = keystone_usbphy_ids,
},
};
module_platform_driver(keystone_usbphy_driver);
MODULE_ALIAS("platform:keystone-usbphy");
MODULE_AUTHOR("Texas Instruments Inc.");
MODULE_DESCRIPTION("Keystone USB phy driver");
MODULE_LICENSE("GPL v2");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,907 @@
/*
* Copyright (C) 2011 Marvell International Ltd. All rights reserved.
* Author: Chao Xie <chao.xie@marvell.com>
* Neil Zhang <zhangwm@marvell.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/proc_fs.h>
#include <linux/clk.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/usb.h>
#include <linux/usb/ch9.h>
#include <linux/usb/otg.h>
#include <linux/usb/gadget.h>
#include <linux/usb/hcd.h>
#include <linux/platform_data/mv_usb.h>
#include "phy-mv-usb.h"
#define DRIVER_DESC "Marvell USB OTG transceiver driver"
#define DRIVER_VERSION "Jan 20, 2010"
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");
static const char driver_name[] = "mv-otg";
static char *state_string[] = {
"undefined",
"b_idle",
"b_srp_init",
"b_peripheral",
"b_wait_acon",
"b_host",
"a_idle",
"a_wait_vrise",
"a_wait_bcon",
"a_host",
"a_suspend",
"a_peripheral",
"a_wait_vfall",
"a_vbus_err"
};
static int mv_otg_set_vbus(struct usb_otg *otg, bool on)
{
struct mv_otg *mvotg = container_of(otg->phy, struct mv_otg, phy);
if (mvotg->pdata->set_vbus == NULL)
return -ENODEV;
return mvotg->pdata->set_vbus(on);
}
static int mv_otg_set_host(struct usb_otg *otg,
struct usb_bus *host)
{
otg->host = host;
return 0;
}
static int mv_otg_set_peripheral(struct usb_otg *otg,
struct usb_gadget *gadget)
{
otg->gadget = gadget;
return 0;
}
static void mv_otg_run_state_machine(struct mv_otg *mvotg,
unsigned long delay)
{
dev_dbg(&mvotg->pdev->dev, "transceiver is updated\n");
if (!mvotg->qwork)
return;
queue_delayed_work(mvotg->qwork, &mvotg->work, delay);
}
static void mv_otg_timer_await_bcon(unsigned long data)
{
struct mv_otg *mvotg = (struct mv_otg *) data;
mvotg->otg_ctrl.a_wait_bcon_timeout = 1;
dev_info(&mvotg->pdev->dev, "B Device No Response!\n");
if (spin_trylock(&mvotg->wq_lock)) {
mv_otg_run_state_machine(mvotg, 0);
spin_unlock(&mvotg->wq_lock);
}
}
static int mv_otg_cancel_timer(struct mv_otg *mvotg, unsigned int id)
{
struct timer_list *timer;
if (id >= OTG_TIMER_NUM)
return -EINVAL;
timer = &mvotg->otg_ctrl.timer[id];
if (timer_pending(timer))
del_timer(timer);
return 0;
}
static int mv_otg_set_timer(struct mv_otg *mvotg, unsigned int id,
unsigned long interval,
void (*callback) (unsigned long))
{
struct timer_list *timer;
if (id >= OTG_TIMER_NUM)
return -EINVAL;
timer = &mvotg->otg_ctrl.timer[id];
if (timer_pending(timer)) {
dev_err(&mvotg->pdev->dev, "Timer%d is already running\n", id);
return -EBUSY;
}
init_timer(timer);
timer->data = (unsigned long) mvotg;
timer->function = callback;
timer->expires = jiffies + interval;
add_timer(timer);
return 0;
}
static int mv_otg_reset(struct mv_otg *mvotg)
{
unsigned int loops;
u32 tmp;
/* Stop the controller */
tmp = readl(&mvotg->op_regs->usbcmd);
tmp &= ~USBCMD_RUN_STOP;
writel(tmp, &mvotg->op_regs->usbcmd);
/* Reset the controller to get default values */
writel(USBCMD_CTRL_RESET, &mvotg->op_regs->usbcmd);
loops = 500;
while (readl(&mvotg->op_regs->usbcmd) & USBCMD_CTRL_RESET) {
if (loops == 0) {
dev_err(&mvotg->pdev->dev,
"Wait for RESET completed TIMEOUT\n");
return -ETIMEDOUT;
}
loops--;
udelay(20);
}
writel(0x0, &mvotg->op_regs->usbintr);
tmp = readl(&mvotg->op_regs->usbsts);
writel(tmp, &mvotg->op_regs->usbsts);
return 0;
}
static void mv_otg_init_irq(struct mv_otg *mvotg)
{
u32 otgsc;
mvotg->irq_en = OTGSC_INTR_A_SESSION_VALID
| OTGSC_INTR_A_VBUS_VALID;
mvotg->irq_status = OTGSC_INTSTS_A_SESSION_VALID
| OTGSC_INTSTS_A_VBUS_VALID;
if (mvotg->pdata->vbus == NULL) {
mvotg->irq_en |= OTGSC_INTR_B_SESSION_VALID
| OTGSC_INTR_B_SESSION_END;
mvotg->irq_status |= OTGSC_INTSTS_B_SESSION_VALID
| OTGSC_INTSTS_B_SESSION_END;
}
if (mvotg->pdata->id == NULL) {
mvotg->irq_en |= OTGSC_INTR_USB_ID;
mvotg->irq_status |= OTGSC_INTSTS_USB_ID;
}
otgsc = readl(&mvotg->op_regs->otgsc);
otgsc |= mvotg->irq_en;
writel(otgsc, &mvotg->op_regs->otgsc);
}
static void mv_otg_start_host(struct mv_otg *mvotg, int on)
{
#ifdef CONFIG_USB
struct usb_otg *otg = mvotg->phy.otg;
struct usb_hcd *hcd;
if (!otg->host)
return;
dev_info(&mvotg->pdev->dev, "%s host\n", on ? "start" : "stop");
hcd = bus_to_hcd(otg->host);
if (on) {
usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
device_wakeup_enable(hcd->self.controller);
} else {
usb_remove_hcd(hcd);
}
#endif /* CONFIG_USB */
}
static void mv_otg_start_periphrals(struct mv_otg *mvotg, int on)
{
struct usb_otg *otg = mvotg->phy.otg;
if (!otg->gadget)
return;
dev_info(mvotg->phy.dev, "gadget %s\n", on ? "on" : "off");
if (on)
usb_gadget_vbus_connect(otg->gadget);
else
usb_gadget_vbus_disconnect(otg->gadget);
}
static void otg_clock_enable(struct mv_otg *mvotg)
{
clk_prepare_enable(mvotg->clk);
}
static void otg_clock_disable(struct mv_otg *mvotg)
{
clk_disable_unprepare(mvotg->clk);
}
static int mv_otg_enable_internal(struct mv_otg *mvotg)
{
int retval = 0;
if (mvotg->active)
return 0;
dev_dbg(&mvotg->pdev->dev, "otg enabled\n");
otg_clock_enable(mvotg);
if (mvotg->pdata->phy_init) {
retval = mvotg->pdata->phy_init(mvotg->phy_regs);
if (retval) {
dev_err(&mvotg->pdev->dev,
"init phy error %d\n", retval);
otg_clock_disable(mvotg);
return retval;
}
}
mvotg->active = 1;
return 0;
}
static int mv_otg_enable(struct mv_otg *mvotg)
{
if (mvotg->clock_gating)
return mv_otg_enable_internal(mvotg);
return 0;
}
static void mv_otg_disable_internal(struct mv_otg *mvotg)
{
if (mvotg->active) {
dev_dbg(&mvotg->pdev->dev, "otg disabled\n");
if (mvotg->pdata->phy_deinit)
mvotg->pdata->phy_deinit(mvotg->phy_regs);
otg_clock_disable(mvotg);
mvotg->active = 0;
}
}
static void mv_otg_disable(struct mv_otg *mvotg)
{
if (mvotg->clock_gating)
mv_otg_disable_internal(mvotg);
}
static void mv_otg_update_inputs(struct mv_otg *mvotg)
{
struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl;
u32 otgsc;
otgsc = readl(&mvotg->op_regs->otgsc);
if (mvotg->pdata->vbus) {
if (mvotg->pdata->vbus->poll() == VBUS_HIGH) {
otg_ctrl->b_sess_vld = 1;
otg_ctrl->b_sess_end = 0;
} else {
otg_ctrl->b_sess_vld = 0;
otg_ctrl->b_sess_end = 1;
}
} else {
otg_ctrl->b_sess_vld = !!(otgsc & OTGSC_STS_B_SESSION_VALID);
otg_ctrl->b_sess_end = !!(otgsc & OTGSC_STS_B_SESSION_END);
}
if (mvotg->pdata->id)
otg_ctrl->id = !!mvotg->pdata->id->poll();
else
otg_ctrl->id = !!(otgsc & OTGSC_STS_USB_ID);
if (mvotg->pdata->otg_force_a_bus_req && !otg_ctrl->id)
otg_ctrl->a_bus_req = 1;
otg_ctrl->a_sess_vld = !!(otgsc & OTGSC_STS_A_SESSION_VALID);
otg_ctrl->a_vbus_vld = !!(otgsc & OTGSC_STS_A_VBUS_VALID);
dev_dbg(&mvotg->pdev->dev, "%s: ", __func__);
dev_dbg(&mvotg->pdev->dev, "id %d\n", otg_ctrl->id);
dev_dbg(&mvotg->pdev->dev, "b_sess_vld %d\n", otg_ctrl->b_sess_vld);
dev_dbg(&mvotg->pdev->dev, "b_sess_end %d\n", otg_ctrl->b_sess_end);
dev_dbg(&mvotg->pdev->dev, "a_vbus_vld %d\n", otg_ctrl->a_vbus_vld);
dev_dbg(&mvotg->pdev->dev, "a_sess_vld %d\n", otg_ctrl->a_sess_vld);
}
static void mv_otg_update_state(struct mv_otg *mvotg)
{
struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl;
struct usb_phy *phy = &mvotg->phy;
int old_state = phy->state;
switch (old_state) {
case OTG_STATE_UNDEFINED:
phy->state = OTG_STATE_B_IDLE;
/* FALL THROUGH */
case OTG_STATE_B_IDLE:
if (otg_ctrl->id == 0)
phy->state = OTG_STATE_A_IDLE;
else if (otg_ctrl->b_sess_vld)
phy->state = OTG_STATE_B_PERIPHERAL;
break;
case OTG_STATE_B_PERIPHERAL:
if (!otg_ctrl->b_sess_vld || otg_ctrl->id == 0)
phy->state = OTG_STATE_B_IDLE;
break;
case OTG_STATE_A_IDLE:
if (otg_ctrl->id)
phy->state = OTG_STATE_B_IDLE;
else if (!(otg_ctrl->a_bus_drop) &&
(otg_ctrl->a_bus_req || otg_ctrl->a_srp_det))
phy->state = OTG_STATE_A_WAIT_VRISE;
break;
case OTG_STATE_A_WAIT_VRISE:
if (otg_ctrl->a_vbus_vld)
phy->state = OTG_STATE_A_WAIT_BCON;
break;
case OTG_STATE_A_WAIT_BCON:
if (otg_ctrl->id || otg_ctrl->a_bus_drop
|| otg_ctrl->a_wait_bcon_timeout) {
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
phy->state = OTG_STATE_A_WAIT_VFALL;
otg_ctrl->a_bus_req = 0;
} else if (!otg_ctrl->a_vbus_vld) {
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
phy->state = OTG_STATE_A_VBUS_ERR;
} else if (otg_ctrl->b_conn) {
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
phy->state = OTG_STATE_A_HOST;
}
break;
case OTG_STATE_A_HOST:
if (otg_ctrl->id || !otg_ctrl->b_conn
|| otg_ctrl->a_bus_drop)
phy->state = OTG_STATE_A_WAIT_BCON;
else if (!otg_ctrl->a_vbus_vld)
phy->state = OTG_STATE_A_VBUS_ERR;
break;
case OTG_STATE_A_WAIT_VFALL:
if (otg_ctrl->id
|| (!otg_ctrl->b_conn && otg_ctrl->a_sess_vld)
|| otg_ctrl->a_bus_req)
phy->state = OTG_STATE_A_IDLE;
break;
case OTG_STATE_A_VBUS_ERR:
if (otg_ctrl->id || otg_ctrl->a_clr_err
|| otg_ctrl->a_bus_drop) {
otg_ctrl->a_clr_err = 0;
phy->state = OTG_STATE_A_WAIT_VFALL;
}
break;
default:
break;
}
}
static void mv_otg_work(struct work_struct *work)
{
struct mv_otg *mvotg;
struct usb_phy *phy;
struct usb_otg *otg;
int old_state;
mvotg = container_of(to_delayed_work(work), struct mv_otg, work);
run:
/* work queue is single thread, or we need spin_lock to protect */
phy = &mvotg->phy;
otg = phy->otg;
old_state = phy->state;
if (!mvotg->active)
return;
mv_otg_update_inputs(mvotg);
mv_otg_update_state(mvotg);
if (old_state != phy->state) {
dev_info(&mvotg->pdev->dev, "change from state %s to %s\n",
state_string[old_state],
state_string[phy->state]);
switch (phy->state) {
case OTG_STATE_B_IDLE:
otg->default_a = 0;
if (old_state == OTG_STATE_B_PERIPHERAL)
mv_otg_start_periphrals(mvotg, 0);
mv_otg_reset(mvotg);
mv_otg_disable(mvotg);
break;
case OTG_STATE_B_PERIPHERAL:
mv_otg_enable(mvotg);
mv_otg_start_periphrals(mvotg, 1);
break;
case OTG_STATE_A_IDLE:
otg->default_a = 1;
mv_otg_enable(mvotg);
if (old_state == OTG_STATE_A_WAIT_VFALL)
mv_otg_start_host(mvotg, 0);
mv_otg_reset(mvotg);
break;
case OTG_STATE_A_WAIT_VRISE:
mv_otg_set_vbus(otg, 1);
break;
case OTG_STATE_A_WAIT_BCON:
if (old_state != OTG_STATE_A_HOST)
mv_otg_start_host(mvotg, 1);
mv_otg_set_timer(mvotg, A_WAIT_BCON_TIMER,
T_A_WAIT_BCON,
mv_otg_timer_await_bcon);
/*
* Now, we directly enter A_HOST. So set b_conn = 1
* here. In fact, it need host driver to notify us.
*/
mvotg->otg_ctrl.b_conn = 1;
break;
case OTG_STATE_A_HOST:
break;
case OTG_STATE_A_WAIT_VFALL:
/*
* Now, we has exited A_HOST. So set b_conn = 0
* here. In fact, it need host driver to notify us.
*/
mvotg->otg_ctrl.b_conn = 0;
mv_otg_set_vbus(otg, 0);
break;
case OTG_STATE_A_VBUS_ERR:
break;
default:
break;
}
goto run;
}
}
static irqreturn_t mv_otg_irq(int irq, void *dev)
{
struct mv_otg *mvotg = dev;
u32 otgsc;
otgsc = readl(&mvotg->op_regs->otgsc);
writel(otgsc, &mvotg->op_regs->otgsc);
/*
* if we have vbus, then the vbus detection for B-device
* will be done by mv_otg_inputs_irq().
*/
if (mvotg->pdata->vbus)
if ((otgsc & OTGSC_STS_USB_ID) &&
!(otgsc & OTGSC_INTSTS_USB_ID))
return IRQ_NONE;
if ((otgsc & mvotg->irq_status) == 0)
return IRQ_NONE;
mv_otg_run_state_machine(mvotg, 0);
return IRQ_HANDLED;
}
static irqreturn_t mv_otg_inputs_irq(int irq, void *dev)
{
struct mv_otg *mvotg = dev;
/* The clock may disabled at this time */
if (!mvotg->active) {
mv_otg_enable(mvotg);
mv_otg_init_irq(mvotg);
}
mv_otg_run_state_machine(mvotg, 0);
return IRQ_HANDLED;
}
static ssize_t
get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
{
struct mv_otg *mvotg = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n",
mvotg->otg_ctrl.a_bus_req);
}
static ssize_t
set_a_bus_req(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct mv_otg *mvotg = dev_get_drvdata(dev);
if (count > 2)
return -1;
/* We will use this interface to change to A device */
if (mvotg->phy.state != OTG_STATE_B_IDLE
&& mvotg->phy.state != OTG_STATE_A_IDLE)
return -1;
/* The clock may disabled and we need to set irq for ID detected */
mv_otg_enable(mvotg);
mv_otg_init_irq(mvotg);
if (buf[0] == '1') {
mvotg->otg_ctrl.a_bus_req = 1;
mvotg->otg_ctrl.a_bus_drop = 0;
dev_dbg(&mvotg->pdev->dev,
"User request: a_bus_req = 1\n");
if (spin_trylock(&mvotg->wq_lock)) {
mv_otg_run_state_machine(mvotg, 0);
spin_unlock(&mvotg->wq_lock);
}
}
return count;
}
static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req,
set_a_bus_req);
static ssize_t
set_a_clr_err(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct mv_otg *mvotg = dev_get_drvdata(dev);
if (!mvotg->phy.otg->default_a)
return -1;
if (count > 2)
return -1;
if (buf[0] == '1') {
mvotg->otg_ctrl.a_clr_err = 1;
dev_dbg(&mvotg->pdev->dev,
"User request: a_clr_err = 1\n");
}
if (spin_trylock(&mvotg->wq_lock)) {
mv_otg_run_state_machine(mvotg, 0);
spin_unlock(&mvotg->wq_lock);
}
return count;
}
static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err);
static ssize_t
get_a_bus_drop(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct mv_otg *mvotg = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n",
mvotg->otg_ctrl.a_bus_drop);
}
static ssize_t
set_a_bus_drop(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct mv_otg *mvotg = dev_get_drvdata(dev);
if (!mvotg->phy.otg->default_a)
return -1;
if (count > 2)
return -1;
if (buf[0] == '0') {
mvotg->otg_ctrl.a_bus_drop = 0;
dev_dbg(&mvotg->pdev->dev,
"User request: a_bus_drop = 0\n");
} else if (buf[0] == '1') {
mvotg->otg_ctrl.a_bus_drop = 1;
mvotg->otg_ctrl.a_bus_req = 0;
dev_dbg(&mvotg->pdev->dev,
"User request: a_bus_drop = 1\n");
dev_dbg(&mvotg->pdev->dev,
"User request: and a_bus_req = 0\n");
}
if (spin_trylock(&mvotg->wq_lock)) {
mv_otg_run_state_machine(mvotg, 0);
spin_unlock(&mvotg->wq_lock);
}
return count;
}
static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR,
get_a_bus_drop, set_a_bus_drop);
static struct attribute *inputs_attrs[] = {
&dev_attr_a_bus_req.attr,
&dev_attr_a_clr_err.attr,
&dev_attr_a_bus_drop.attr,
NULL,
};
static struct attribute_group inputs_attr_group = {
.name = "inputs",
.attrs = inputs_attrs,
};
static int mv_otg_remove(struct platform_device *pdev)
{
struct mv_otg *mvotg = platform_get_drvdata(pdev);
sysfs_remove_group(&mvotg->pdev->dev.kobj, &inputs_attr_group);
if (mvotg->qwork) {
flush_workqueue(mvotg->qwork);
destroy_workqueue(mvotg->qwork);
}
mv_otg_disable(mvotg);
usb_remove_phy(&mvotg->phy);
return 0;
}
static int mv_otg_probe(struct platform_device *pdev)
{
struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct mv_otg *mvotg;
struct usb_otg *otg;
struct resource *r;
int retval = 0, i;
if (pdata == NULL) {
dev_err(&pdev->dev, "failed to get platform data\n");
return -ENODEV;
}
mvotg = devm_kzalloc(&pdev->dev, sizeof(*mvotg), GFP_KERNEL);
if (!mvotg) {
dev_err(&pdev->dev, "failed to allocate memory!\n");
return -ENOMEM;
}
otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL);
if (!otg)
return -ENOMEM;
platform_set_drvdata(pdev, mvotg);
mvotg->pdev = pdev;
mvotg->pdata = pdata;
mvotg->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(mvotg->clk))
return PTR_ERR(mvotg->clk);
mvotg->qwork = create_singlethread_workqueue("mv_otg_queue");
if (!mvotg->qwork) {
dev_dbg(&pdev->dev, "cannot create workqueue for OTG\n");
return -ENOMEM;
}
INIT_DELAYED_WORK(&mvotg->work, mv_otg_work);
/* OTG common part */
mvotg->pdev = pdev;
mvotg->phy.dev = &pdev->dev;
mvotg->phy.otg = otg;
mvotg->phy.label = driver_name;
mvotg->phy.state = OTG_STATE_UNDEFINED;
otg->phy = &mvotg->phy;
otg->set_host = mv_otg_set_host;
otg->set_peripheral = mv_otg_set_peripheral;
otg->set_vbus = mv_otg_set_vbus;
for (i = 0; i < OTG_TIMER_NUM; i++)
init_timer(&mvotg->otg_ctrl.timer[i]);
r = platform_get_resource_byname(mvotg->pdev,
IORESOURCE_MEM, "phyregs");
if (r == NULL) {
dev_err(&pdev->dev, "no phy I/O memory resource defined\n");
retval = -ENODEV;
goto err_destroy_workqueue;
}
mvotg->phy_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
if (mvotg->phy_regs == NULL) {
dev_err(&pdev->dev, "failed to map phy I/O memory\n");
retval = -EFAULT;
goto err_destroy_workqueue;
}
r = platform_get_resource_byname(mvotg->pdev,
IORESOURCE_MEM, "capregs");
if (r == NULL) {
dev_err(&pdev->dev, "no I/O memory resource defined\n");
retval = -ENODEV;
goto err_destroy_workqueue;
}
mvotg->cap_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
if (mvotg->cap_regs == NULL) {
dev_err(&pdev->dev, "failed to map I/O memory\n");
retval = -EFAULT;
goto err_destroy_workqueue;
}
/* we will acces controller register, so enable the udc controller */
retval = mv_otg_enable_internal(mvotg);
if (retval) {
dev_err(&pdev->dev, "mv otg enable error %d\n", retval);
goto err_destroy_workqueue;
}
mvotg->op_regs =
(struct mv_otg_regs __iomem *) ((unsigned long) mvotg->cap_regs
+ (readl(mvotg->cap_regs) & CAPLENGTH_MASK));
if (pdata->id) {
retval = devm_request_threaded_irq(&pdev->dev, pdata->id->irq,
NULL, mv_otg_inputs_irq,
IRQF_ONESHOT, "id", mvotg);
if (retval) {
dev_info(&pdev->dev,
"Failed to request irq for ID\n");
pdata->id = NULL;
}
}
if (pdata->vbus) {
mvotg->clock_gating = 1;
retval = devm_request_threaded_irq(&pdev->dev, pdata->vbus->irq,
NULL, mv_otg_inputs_irq,
IRQF_ONESHOT, "vbus", mvotg);
if (retval) {
dev_info(&pdev->dev,
"Failed to request irq for VBUS, "
"disable clock gating\n");
mvotg->clock_gating = 0;
pdata->vbus = NULL;
}
}
if (pdata->disable_otg_clock_gating)
mvotg->clock_gating = 0;
mv_otg_reset(mvotg);
mv_otg_init_irq(mvotg);
r = platform_get_resource(mvotg->pdev, IORESOURCE_IRQ, 0);
if (r == NULL) {
dev_err(&pdev->dev, "no IRQ resource defined\n");
retval = -ENODEV;
goto err_disable_clk;
}
mvotg->irq = r->start;
if (devm_request_irq(&pdev->dev, mvotg->irq, mv_otg_irq, IRQF_SHARED,
driver_name, mvotg)) {
dev_err(&pdev->dev, "Request irq %d for OTG failed\n",
mvotg->irq);
mvotg->irq = 0;
retval = -ENODEV;
goto err_disable_clk;
}
retval = usb_add_phy(&mvotg->phy, USB_PHY_TYPE_USB2);
if (retval < 0) {
dev_err(&pdev->dev, "can't register transceiver, %d\n",
retval);
goto err_disable_clk;
}
retval = sysfs_create_group(&pdev->dev.kobj, &inputs_attr_group);
if (retval < 0) {
dev_dbg(&pdev->dev,
"Can't register sysfs attr group: %d\n", retval);
goto err_remove_phy;
}
spin_lock_init(&mvotg->wq_lock);
if (spin_trylock(&mvotg->wq_lock)) {
mv_otg_run_state_machine(mvotg, 2 * HZ);
spin_unlock(&mvotg->wq_lock);
}
dev_info(&pdev->dev,
"successful probe OTG device %s clock gating.\n",
mvotg->clock_gating ? "with" : "without");
return 0;
err_remove_phy:
usb_remove_phy(&mvotg->phy);
err_disable_clk:
mv_otg_disable_internal(mvotg);
err_destroy_workqueue:
flush_workqueue(mvotg->qwork);
destroy_workqueue(mvotg->qwork);
return retval;
}
#ifdef CONFIG_PM
static int mv_otg_suspend(struct platform_device *pdev, pm_message_t state)
{
struct mv_otg *mvotg = platform_get_drvdata(pdev);
if (mvotg->phy.state != OTG_STATE_B_IDLE) {
dev_info(&pdev->dev,
"OTG state is not B_IDLE, it is %d!\n",
mvotg->phy.state);
return -EAGAIN;
}
if (!mvotg->clock_gating)
mv_otg_disable_internal(mvotg);
return 0;
}
static int mv_otg_resume(struct platform_device *pdev)
{
struct mv_otg *mvotg = platform_get_drvdata(pdev);
u32 otgsc;
if (!mvotg->clock_gating) {
mv_otg_enable_internal(mvotg);
otgsc = readl(&mvotg->op_regs->otgsc);
otgsc |= mvotg->irq_en;
writel(otgsc, &mvotg->op_regs->otgsc);
if (spin_trylock(&mvotg->wq_lock)) {
mv_otg_run_state_machine(mvotg, 0);
spin_unlock(&mvotg->wq_lock);
}
}
return 0;
}
#endif
static struct platform_driver mv_otg_driver = {
.probe = mv_otg_probe,
.remove = mv_otg_remove,
.driver = {
.owner = THIS_MODULE,
.name = driver_name,
},
#ifdef CONFIG_PM
.suspend = mv_otg_suspend,
.resume = mv_otg_resume,
#endif
};
module_platform_driver(mv_otg_driver);

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,516 @@
/*
* Copyright 2012-2014 Freescale Semiconductor, Inc.
* Copyright (C) 2012 Marek Vasut <marex@denx.de>
* on behalf of DENX Software Engineering GmbH
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/usb/otg.h>
#include <linux/stmp_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#define DRIVER_NAME "mxs_phy"
#define HW_USBPHY_PWD 0x00
#define HW_USBPHY_CTRL 0x30
#define HW_USBPHY_CTRL_SET 0x34
#define HW_USBPHY_CTRL_CLR 0x38
#define HW_USBPHY_DEBUG_SET 0x54
#define HW_USBPHY_DEBUG_CLR 0x58
#define HW_USBPHY_IP 0x90
#define HW_USBPHY_IP_SET 0x94
#define HW_USBPHY_IP_CLR 0x98
#define BM_USBPHY_CTRL_SFTRST BIT(31)
#define BM_USBPHY_CTRL_CLKGATE BIT(30)
#define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26)
#define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE BIT(25)
#define BM_USBPHY_CTRL_ENVBUSCHG_WKUP BIT(23)
#define BM_USBPHY_CTRL_ENIDCHG_WKUP BIT(22)
#define BM_USBPHY_CTRL_ENDPDMCHG_WKUP BIT(21)
#define BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD BIT(20)
#define BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE BIT(19)
#define BM_USBPHY_CTRL_ENAUTO_PWRON_PLL BIT(18)
#define BM_USBPHY_CTRL_ENUTMILEVEL3 BIT(15)
#define BM_USBPHY_CTRL_ENUTMILEVEL2 BIT(14)
#define BM_USBPHY_CTRL_ENHOSTDISCONDETECT BIT(1)
#define BM_USBPHY_IP_FIX (BIT(17) | BIT(18))
#define BM_USBPHY_DEBUG_CLKGATE BIT(30)
/* Anatop Registers */
#define ANADIG_ANA_MISC0 0x150
#define ANADIG_ANA_MISC0_SET 0x154
#define ANADIG_ANA_MISC0_CLR 0x158
#define ANADIG_USB1_VBUS_DET_STAT 0x1c0
#define ANADIG_USB2_VBUS_DET_STAT 0x220
#define ANADIG_USB1_LOOPBACK_SET 0x1e4
#define ANADIG_USB1_LOOPBACK_CLR 0x1e8
#define ANADIG_USB2_LOOPBACK_SET 0x244
#define ANADIG_USB2_LOOPBACK_CLR 0x248
#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG BIT(12)
#define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL BIT(11)
#define BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3)
#define BM_ANADIG_USB2_VBUS_DET_STAT_VBUS_VALID BIT(3)
#define BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 BIT(2)
#define BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN BIT(5)
#define BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 BIT(2)
#define BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN BIT(5)
#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy)
/* Do disconnection between PHY and controller without vbus */
#define MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS BIT(0)
/*
* The PHY will be in messy if there is a wakeup after putting
* bus to suspend (set portsc.suspendM) but before setting PHY to low
* power mode (set portsc.phcd).
*/
#define MXS_PHY_ABNORMAL_IN_SUSPEND BIT(1)
/*
* The SOF sends too fast after resuming, it will cause disconnection
* between host and high speed device.
*/
#define MXS_PHY_SENDING_SOF_TOO_FAST BIT(2)
/*
* IC has bug fixes logic, they include
* MXS_PHY_ABNORMAL_IN_SUSPEND and MXS_PHY_SENDING_SOF_TOO_FAST
* which are described at above flags, the RTL will handle it
* according to different versions.
*/
#define MXS_PHY_NEED_IP_FIX BIT(3)
struct mxs_phy_data {
unsigned int flags;
};
static const struct mxs_phy_data imx23_phy_data = {
.flags = MXS_PHY_ABNORMAL_IN_SUSPEND | MXS_PHY_SENDING_SOF_TOO_FAST,
};
static const struct mxs_phy_data imx6q_phy_data = {
.flags = MXS_PHY_SENDING_SOF_TOO_FAST |
MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
MXS_PHY_NEED_IP_FIX,
};
static const struct mxs_phy_data imx6sl_phy_data = {
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
MXS_PHY_NEED_IP_FIX,
};
static const struct mxs_phy_data vf610_phy_data = {
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
MXS_PHY_NEED_IP_FIX,
};
static const struct mxs_phy_data imx6sx_phy_data = {
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
MXS_PHY_NEED_IP_FIX,
};
static const struct of_device_id mxs_phy_dt_ids[] = {
{ .compatible = "fsl,imx6sx-usbphy", .data = &imx6sx_phy_data, },
{ .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, },
{ .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, },
{ .compatible = "fsl,imx23-usbphy", .data = &imx23_phy_data, },
{ .compatible = "fsl,vf610-usbphy", .data = &vf610_phy_data, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids);
struct mxs_phy {
struct usb_phy phy;
struct clk *clk;
const struct mxs_phy_data *data;
struct regmap *regmap_anatop;
int port_id;
};
static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy)
{
return mxs_phy->data == &imx6q_phy_data;
}
static inline bool is_imx6sl_phy(struct mxs_phy *mxs_phy)
{
return mxs_phy->data == &imx6sl_phy_data;
}
/*
* PHY needs some 32K cycles to switch from 32K clock to
* bus (such as AHB/AXI, etc) clock.
*/
static void mxs_phy_clock_switch_delay(void)
{
usleep_range(300, 400);
}
static int mxs_phy_hw_init(struct mxs_phy *mxs_phy)
{
int ret;
void __iomem *base = mxs_phy->phy.io_priv;
ret = stmp_reset_block(base + HW_USBPHY_CTRL);
if (ret)
return ret;
/* Power up the PHY */
writel(0, base + HW_USBPHY_PWD);
/*
* USB PHY Ctrl Setting
* - Auto clock/power on
* - Enable full/low speed support
*/
writel(BM_USBPHY_CTRL_ENAUTOSET_USBCLKS |
BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE |
BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD |
BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE |
BM_USBPHY_CTRL_ENAUTO_PWRON_PLL |
BM_USBPHY_CTRL_ENUTMILEVEL2 |
BM_USBPHY_CTRL_ENUTMILEVEL3,
base + HW_USBPHY_CTRL_SET);
if (mxs_phy->data->flags & MXS_PHY_NEED_IP_FIX)
writel(BM_USBPHY_IP_FIX, base + HW_USBPHY_IP_SET);
return 0;
}
/* Return true if the vbus is there */
static bool mxs_phy_get_vbus_status(struct mxs_phy *mxs_phy)
{
unsigned int vbus_value;
if (mxs_phy->port_id == 0)
regmap_read(mxs_phy->regmap_anatop,
ANADIG_USB1_VBUS_DET_STAT,
&vbus_value);
else if (mxs_phy->port_id == 1)
regmap_read(mxs_phy->regmap_anatop,
ANADIG_USB2_VBUS_DET_STAT,
&vbus_value);
if (vbus_value & BM_ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)
return true;
else
return false;
}
static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect)
{
void __iomem *base = mxs_phy->phy.io_priv;
u32 reg;
if (disconnect)
writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
base + HW_USBPHY_DEBUG_CLR);
if (mxs_phy->port_id == 0) {
reg = disconnect ? ANADIG_USB1_LOOPBACK_SET
: ANADIG_USB1_LOOPBACK_CLR;
regmap_write(mxs_phy->regmap_anatop, reg,
BM_ANADIG_USB1_LOOPBACK_UTMI_DIG_TST1 |
BM_ANADIG_USB1_LOOPBACK_TSTI_TX_EN);
} else if (mxs_phy->port_id == 1) {
reg = disconnect ? ANADIG_USB2_LOOPBACK_SET
: ANADIG_USB2_LOOPBACK_CLR;
regmap_write(mxs_phy->regmap_anatop, reg,
BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 |
BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN);
}
if (!disconnect)
writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
base + HW_USBPHY_DEBUG_SET);
/* Delay some time, and let Linestate be SE0 for controller */
if (disconnect)
usleep_range(500, 1000);
}
static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on)
{
bool vbus_is_on = false;
/* If the SoCs don't need to disconnect line without vbus, quit */
if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS))
return;
/* If the SoCs don't have anatop, quit */
if (!mxs_phy->regmap_anatop)
return;
vbus_is_on = mxs_phy_get_vbus_status(mxs_phy);
if (on && !vbus_is_on)
__mxs_phy_disconnect_line(mxs_phy, true);
else
__mxs_phy_disconnect_line(mxs_phy, false);
}
static int mxs_phy_init(struct usb_phy *phy)
{
int ret;
struct mxs_phy *mxs_phy = to_mxs_phy(phy);
mxs_phy_clock_switch_delay();
ret = clk_prepare_enable(mxs_phy->clk);
if (ret)
return ret;
return mxs_phy_hw_init(mxs_phy);
}
static void mxs_phy_shutdown(struct usb_phy *phy)
{
struct mxs_phy *mxs_phy = to_mxs_phy(phy);
writel(BM_USBPHY_CTRL_CLKGATE,
phy->io_priv + HW_USBPHY_CTRL_SET);
clk_disable_unprepare(mxs_phy->clk);
}
static int mxs_phy_suspend(struct usb_phy *x, int suspend)
{
int ret;
struct mxs_phy *mxs_phy = to_mxs_phy(x);
if (suspend) {
writel(0xffffffff, x->io_priv + HW_USBPHY_PWD);
writel(BM_USBPHY_CTRL_CLKGATE,
x->io_priv + HW_USBPHY_CTRL_SET);
clk_disable_unprepare(mxs_phy->clk);
} else {
mxs_phy_clock_switch_delay();
ret = clk_prepare_enable(mxs_phy->clk);
if (ret)
return ret;
writel(BM_USBPHY_CTRL_CLKGATE,
x->io_priv + HW_USBPHY_CTRL_CLR);
writel(0, x->io_priv + HW_USBPHY_PWD);
}
return 0;
}
static int mxs_phy_set_wakeup(struct usb_phy *x, bool enabled)
{
struct mxs_phy *mxs_phy = to_mxs_phy(x);
u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP |
BM_USBPHY_CTRL_ENDPDMCHG_WKUP |
BM_USBPHY_CTRL_ENIDCHG_WKUP;
if (enabled) {
mxs_phy_disconnect_line(mxs_phy, true);
writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_SET);
} else {
writel_relaxed(value, x->io_priv + HW_USBPHY_CTRL_CLR);
mxs_phy_disconnect_line(mxs_phy, false);
}
return 0;
}
static int mxs_phy_on_connect(struct usb_phy *phy,
enum usb_device_speed speed)
{
dev_dbg(phy->dev, "%s device has connected\n",
(speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
if (speed == USB_SPEED_HIGH)
writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
phy->io_priv + HW_USBPHY_CTRL_SET);
return 0;
}
static int mxs_phy_on_disconnect(struct usb_phy *phy,
enum usb_device_speed speed)
{
dev_dbg(phy->dev, "%s device has disconnected\n",
(speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
if (speed == USB_SPEED_HIGH)
writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
phy->io_priv + HW_USBPHY_CTRL_CLR);
return 0;
}
static int mxs_phy_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
struct clk *clk;
struct mxs_phy *mxs_phy;
int ret;
const struct of_device_id *of_id =
of_match_device(mxs_phy_dt_ids, &pdev->dev);
struct device_node *np = pdev->dev.of_node;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(clk)) {
dev_err(&pdev->dev,
"can't get the clock, err=%ld", PTR_ERR(clk));
return PTR_ERR(clk);
}
mxs_phy = devm_kzalloc(&pdev->dev, sizeof(*mxs_phy), GFP_KERNEL);
if (!mxs_phy) {
dev_err(&pdev->dev, "Failed to allocate USB PHY structure!\n");
return -ENOMEM;
}
/* Some SoCs don't have anatop registers */
if (of_get_property(np, "fsl,anatop", NULL)) {
mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle
(np, "fsl,anatop");
if (IS_ERR(mxs_phy->regmap_anatop)) {
dev_dbg(&pdev->dev,
"failed to find regmap for anatop\n");
return PTR_ERR(mxs_phy->regmap_anatop);
}
}
ret = of_alias_get_id(np, "usbphy");
if (ret < 0)
dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret);
mxs_phy->port_id = ret;
mxs_phy->phy.io_priv = base;
mxs_phy->phy.dev = &pdev->dev;
mxs_phy->phy.label = DRIVER_NAME;
mxs_phy->phy.init = mxs_phy_init;
mxs_phy->phy.shutdown = mxs_phy_shutdown;
mxs_phy->phy.set_suspend = mxs_phy_suspend;
mxs_phy->phy.notify_connect = mxs_phy_on_connect;
mxs_phy->phy.notify_disconnect = mxs_phy_on_disconnect;
mxs_phy->phy.type = USB_PHY_TYPE_USB2;
mxs_phy->phy.set_wakeup = mxs_phy_set_wakeup;
mxs_phy->clk = clk;
mxs_phy->data = of_id->data;
platform_set_drvdata(pdev, mxs_phy);
device_set_wakeup_capable(&pdev->dev, true);
ret = usb_add_phy_dev(&mxs_phy->phy);
if (ret)
return ret;
return 0;
}
static int mxs_phy_remove(struct platform_device *pdev)
{
struct mxs_phy *mxs_phy = platform_get_drvdata(pdev);
usb_remove_phy(&mxs_phy->phy);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on)
{
unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
/* If the SoCs don't have anatop, quit */
if (!mxs_phy->regmap_anatop)
return;
if (is_imx6q_phy(mxs_phy))
regmap_write(mxs_phy->regmap_anatop, reg,
BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG);
else if (is_imx6sl_phy(mxs_phy))
regmap_write(mxs_phy->regmap_anatop,
reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL);
}
static int mxs_phy_system_suspend(struct device *dev)
{
struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
if (device_may_wakeup(dev))
mxs_phy_enable_ldo_in_suspend(mxs_phy, true);
return 0;
}
static int mxs_phy_system_resume(struct device *dev)
{
struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
if (device_may_wakeup(dev))
mxs_phy_enable_ldo_in_suspend(mxs_phy, false);
return 0;
}
#endif /* CONFIG_PM_SLEEP */
static SIMPLE_DEV_PM_OPS(mxs_phy_pm, mxs_phy_system_suspend,
mxs_phy_system_resume);
static struct platform_driver mxs_phy_driver = {
.probe = mxs_phy_probe,
.remove = mxs_phy_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = mxs_phy_dt_ids,
.pm = &mxs_phy_pm,
},
};
static int __init mxs_phy_module_init(void)
{
return platform_driver_register(&mxs_phy_driver);
}
postcore_initcall(mxs_phy_module_init);
static void __exit mxs_phy_module_exit(void)
{
platform_driver_unregister(&mxs_phy_driver);
}
module_exit(mxs_phy_module_exit);
MODULE_ALIAS("platform:mxs-usb-phy");
MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>");
MODULE_DESCRIPTION("Freescale MXS USB PHY driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,169 @@
/*
* OMAP OTG controller driver
*
* Based on code from tahvo-usb.c and isp1301_omap.c drivers.
*
* Copyright (C) 2005-2006 Nokia Corporation
* Copyright (C) 2004 Texas Instruments
* Copyright (C) 2004 David Brownell
*
* This file is subject to the terms and conditions of the GNU General
* Public License. See the file "COPYING" in the main directory of this
* archive for more details.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/io.h>
#include <linux/err.h>
#include <linux/extcon.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/platform_data/usb-omap1.h>
struct otg_device {
void __iomem *base;
bool id;
bool vbus;
struct extcon_specific_cable_nb vbus_dev;
struct extcon_specific_cable_nb id_dev;
struct notifier_block vbus_nb;
struct notifier_block id_nb;
};
#define OMAP_OTG_CTRL 0x0c
#define OMAP_OTG_ASESSVLD (1 << 20)
#define OMAP_OTG_BSESSEND (1 << 19)
#define OMAP_OTG_BSESSVLD (1 << 18)
#define OMAP_OTG_VBUSVLD (1 << 17)
#define OMAP_OTG_ID (1 << 16)
#define OMAP_OTG_XCEIV_OUTPUTS \
(OMAP_OTG_ASESSVLD | OMAP_OTG_BSESSEND | OMAP_OTG_BSESSVLD | \
OMAP_OTG_VBUSVLD | OMAP_OTG_ID)
static void omap_otg_ctrl(struct otg_device *otg_dev, u32 outputs)
{
u32 l;
l = readl(otg_dev->base + OMAP_OTG_CTRL);
l &= ~OMAP_OTG_XCEIV_OUTPUTS;
l |= outputs;
writel(l, otg_dev->base + OMAP_OTG_CTRL);
}
static void omap_otg_set_mode(struct otg_device *otg_dev)
{
if (!otg_dev->id && otg_dev->vbus)
/* Set B-session valid. */
omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSVLD);
else if (otg_dev->vbus)
/* Set A-session valid. */
omap_otg_ctrl(otg_dev, OMAP_OTG_ASESSVLD);
else if (!otg_dev->id)
/* Set B-session end to indicate no VBUS. */
omap_otg_ctrl(otg_dev, OMAP_OTG_ID | OMAP_OTG_BSESSEND);
}
static int omap_otg_id_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct otg_device *otg_dev = container_of(nb, struct otg_device, id_nb);
otg_dev->id = event;
omap_otg_set_mode(otg_dev);
return NOTIFY_DONE;
}
static int omap_otg_vbus_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct otg_device *otg_dev = container_of(nb, struct otg_device,
vbus_nb);
otg_dev->vbus = event;
omap_otg_set_mode(otg_dev);
return NOTIFY_DONE;
}
static int omap_otg_probe(struct platform_device *pdev)
{
const struct omap_usb_config *config = pdev->dev.platform_data;
struct otg_device *otg_dev;
struct extcon_dev *extcon;
int ret;
u32 rev;
if (!config || !config->extcon)
return -ENODEV;
extcon = extcon_get_extcon_dev(config->extcon);
if (!extcon)
return -EPROBE_DEFER;
otg_dev = devm_kzalloc(&pdev->dev, sizeof(*otg_dev), GFP_KERNEL);
if (!otg_dev)
return -ENOMEM;
otg_dev->base = devm_ioremap_resource(&pdev->dev, &pdev->resource[0]);
if (IS_ERR(otg_dev->base))
return PTR_ERR(otg_dev->base);
otg_dev->id_nb.notifier_call = omap_otg_id_notifier;
otg_dev->vbus_nb.notifier_call = omap_otg_vbus_notifier;
ret = extcon_register_interest(&otg_dev->id_dev, config->extcon,
"USB-HOST", &otg_dev->id_nb);
if (ret)
return ret;
ret = extcon_register_interest(&otg_dev->vbus_dev, config->extcon,
"USB", &otg_dev->vbus_nb);
if (ret) {
extcon_unregister_interest(&otg_dev->id_dev);
return ret;
}
otg_dev->id = extcon_get_cable_state(extcon, "USB-HOST");
otg_dev->vbus = extcon_get_cable_state(extcon, "USB");
omap_otg_set_mode(otg_dev);
rev = readl(otg_dev->base);
dev_info(&pdev->dev,
"OMAP USB OTG controller rev %d.%d (%s, id=%d, vbus=%d)\n",
(rev >> 4) & 0xf, rev & 0xf, config->extcon, otg_dev->id,
otg_dev->vbus);
return 0;
}
static int omap_otg_remove(struct platform_device *pdev)
{
struct otg_device *otg_dev = platform_get_drvdata(pdev);
extcon_unregister_interest(&otg_dev->id_dev);
extcon_unregister_interest(&otg_dev->vbus_dev);
return 0;
}
static struct platform_driver omap_otg_driver = {
.probe = omap_otg_probe,
.remove = omap_otg_remove,
.driver = {
.owner = THIS_MODULE,
.name = "omap_otg",
},
};
module_platform_driver(omap_otg_driver);
MODULE_DESCRIPTION("OMAP USB OTG controller driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");

View file

@ -0,0 +1,248 @@
/*
* Renesas R-Car Gen2 USB phy driver
*
* Copyright (C) 2013 Renesas Solutions Corp.
* Copyright (C) 2013 Cogent Embedded, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_data/usb-rcar-gen2-phy.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/usb/otg.h>
struct rcar_gen2_usb_phy_priv {
struct usb_phy phy;
void __iomem *base;
struct clk *clk;
spinlock_t lock;
int usecount;
u32 ugctrl2;
};
#define usb_phy_to_priv(p) container_of(p, struct rcar_gen2_usb_phy_priv, phy)
/* Low Power Status register */
#define USBHS_LPSTS_REG 0x02
#define USBHS_LPSTS_SUSPM (1 << 14)
/* USB General control register */
#define USBHS_UGCTRL_REG 0x80
#define USBHS_UGCTRL_CONNECT (1 << 2)
#define USBHS_UGCTRL_PLLRESET (1 << 0)
/* USB General control register 2 */
#define USBHS_UGCTRL2_REG 0x84
#define USBHS_UGCTRL2_USB0_PCI (1 << 4)
#define USBHS_UGCTRL2_USB0_HS (3 << 4)
#define USBHS_UGCTRL2_USB2_PCI (0 << 31)
#define USBHS_UGCTRL2_USB2_SS (1 << 31)
/* USB General status register */
#define USBHS_UGSTS_REG 0x88
#define USBHS_UGSTS_LOCK (3 << 8)
/* Enable USBHS internal phy */
static int __rcar_gen2_usbhs_phy_enable(void __iomem *base)
{
u32 val;
int i;
/* USBHS PHY power on */
val = ioread32(base + USBHS_UGCTRL_REG);
val &= ~USBHS_UGCTRL_PLLRESET;
iowrite32(val, base + USBHS_UGCTRL_REG);
val = ioread16(base + USBHS_LPSTS_REG);
val |= USBHS_LPSTS_SUSPM;
iowrite16(val, base + USBHS_LPSTS_REG);
for (i = 0; i < 20; i++) {
val = ioread32(base + USBHS_UGSTS_REG);
if ((val & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) {
val = ioread32(base + USBHS_UGCTRL_REG);
val |= USBHS_UGCTRL_CONNECT;
iowrite32(val, base + USBHS_UGCTRL_REG);
return 0;
}
udelay(1);
}
/* Timed out waiting for the PLL lock */
return -ETIMEDOUT;
}
/* Disable USBHS internal phy */
static int __rcar_gen2_usbhs_phy_disable(void __iomem *base)
{
u32 val;
/* USBHS PHY power off */
val = ioread32(base + USBHS_UGCTRL_REG);
val &= ~USBHS_UGCTRL_CONNECT;
iowrite32(val, base + USBHS_UGCTRL_REG);
val = ioread16(base + USBHS_LPSTS_REG);
val &= ~USBHS_LPSTS_SUSPM;
iowrite16(val, base + USBHS_LPSTS_REG);
val = ioread32(base + USBHS_UGCTRL_REG);
val |= USBHS_UGCTRL_PLLRESET;
iowrite32(val, base + USBHS_UGCTRL_REG);
return 0;
}
/* Setup USB channels */
static void __rcar_gen2_usb_phy_init(struct rcar_gen2_usb_phy_priv *priv)
{
u32 val;
clk_prepare_enable(priv->clk);
/* Set USB channels in the USBHS UGCTRL2 register */
val = ioread32(priv->base + USBHS_UGCTRL2_REG);
val &= ~(USBHS_UGCTRL2_USB0_HS | USBHS_UGCTRL2_USB2_SS);
val |= priv->ugctrl2;
iowrite32(val, priv->base + USBHS_UGCTRL2_REG);
}
/* Shutdown USB channels */
static void __rcar_gen2_usb_phy_shutdown(struct rcar_gen2_usb_phy_priv *priv)
{
__rcar_gen2_usbhs_phy_disable(priv->base);
clk_disable_unprepare(priv->clk);
}
static int rcar_gen2_usb_phy_set_suspend(struct usb_phy *phy, int suspend)
{
struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy);
unsigned long flags;
int retval;
spin_lock_irqsave(&priv->lock, flags);
retval = suspend ? __rcar_gen2_usbhs_phy_disable(priv->base) :
__rcar_gen2_usbhs_phy_enable(priv->base);
spin_unlock_irqrestore(&priv->lock, flags);
return retval;
}
static int rcar_gen2_usb_phy_init(struct usb_phy *phy)
{
struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy);
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
/*
* Enable the clock and setup USB channels
* if it's the first user
*/
if (!priv->usecount++)
__rcar_gen2_usb_phy_init(priv);
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static void rcar_gen2_usb_phy_shutdown(struct usb_phy *phy)
{
struct rcar_gen2_usb_phy_priv *priv = usb_phy_to_priv(phy);
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
if (!priv->usecount) {
dev_warn(phy->dev, "Trying to disable phy with 0 usecount\n");
goto out;
}
/* Disable everything if it's the last user */
if (!--priv->usecount)
__rcar_gen2_usb_phy_shutdown(priv);
out:
spin_unlock_irqrestore(&priv->lock, flags);
}
static int rcar_gen2_usb_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rcar_gen2_phy_platform_data *pdata;
struct rcar_gen2_usb_phy_priv *priv;
struct resource *res;
void __iomem *base;
struct clk *clk;
int retval;
pdata = dev_get_platdata(dev);
if (!pdata) {
dev_err(dev, "No platform data\n");
return -EINVAL;
}
clk = devm_clk_get(dev, "usbhs");
if (IS_ERR(clk)) {
dev_err(dev, "Can't get the clock\n");
return PTR_ERR(clk);
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(dev, "Memory allocation failed\n");
return -ENOMEM;
}
spin_lock_init(&priv->lock);
priv->clk = clk;
priv->base = base;
priv->ugctrl2 = pdata->chan0_pci ?
USBHS_UGCTRL2_USB0_PCI : USBHS_UGCTRL2_USB0_HS;
priv->ugctrl2 |= pdata->chan2_pci ?
USBHS_UGCTRL2_USB2_PCI : USBHS_UGCTRL2_USB2_SS;
priv->phy.dev = dev;
priv->phy.label = dev_name(dev);
priv->phy.init = rcar_gen2_usb_phy_init;
priv->phy.shutdown = rcar_gen2_usb_phy_shutdown;
priv->phy.set_suspend = rcar_gen2_usb_phy_set_suspend;
retval = usb_add_phy_dev(&priv->phy);
if (retval < 0) {
dev_err(dev, "Failed to add USB phy\n");
return retval;
}
platform_set_drvdata(pdev, priv);
return retval;
}
static int rcar_gen2_usb_phy_remove(struct platform_device *pdev)
{
struct rcar_gen2_usb_phy_priv *priv = platform_get_drvdata(pdev);
usb_remove_phy(&priv->phy);
return 0;
}
static struct platform_driver rcar_gen2_usb_phy_driver = {
.driver = {
.name = "usb_phy_rcar_gen2",
},
.probe = rcar_gen2_usb_phy_probe,
.remove = rcar_gen2_usb_phy_remove,
};
module_platform_driver(rcar_gen2_usb_phy_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Renesas R-Car Gen2 USB phy");
MODULE_AUTHOR("Valentine Barshak <valentine.barshak@cogentembedded.com>");

View file

@ -0,0 +1,251 @@
/*
* Renesas R-Car USB phy driver
*
* Copyright (C) 2012-2013 Renesas Solutions Corp.
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
* Copyright (C) 2013 Cogent Embedded, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/usb/otg.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/platform_data/usb-rcar-phy.h>
/* REGS block */
#define USBPCTRL0 0x00
#define USBPCTRL1 0x04
#define USBST 0x08
#define USBEH0 0x0C
#define USBOH0 0x1C
#define USBCTL0 0x58
/* High-speed signal quality characteristic control registers (R8A7778 only) */
#define HSQCTL1 0x24
#define HSQCTL2 0x28
/* USBPCTRL0 */
#define OVC2 (1 << 10) /* (R8A7779 only) */
/* Switches the OVC input pin for port 2: */
/* 1: USB_OVC2, 0: OVC2 */
#define OVC1_VBUS1 (1 << 9) /* Switches the OVC input pin for port 1: */
/* 1: USB_OVC1, 0: OVC1/VBUS1 */
/* Function mode: set to 0 */
#define OVC0 (1 << 8) /* Switches the OVC input pin for port 0: */
/* 1: USB_OVC0 pin, 0: OVC0 */
#define OVC2_ACT (1 << 6) /* (R8A7779 only) */
/* Host mode: OVC2 polarity: */
/* 1: active-high, 0: active-low */
#define PENC (1 << 4) /* Function mode: output level of PENC1 pin: */
/* 1: high, 0: low */
#define OVC0_ACT (1 << 3) /* Host mode: OVC0 polarity: */
/* 1: active-high, 0: active-low */
#define OVC1_ACT (1 << 1) /* Host mode: OVC1 polarity: */
/* 1: active-high, 0: active-low */
/* Function mode: be sure to set to 1 */
#define PORT1 (1 << 0) /* Selects port 1 mode: */
/* 1: function, 0: host */
/* USBPCTRL1 */
#define PHY_RST (1 << 2)
#define PLL_ENB (1 << 1)
#define PHY_ENB (1 << 0)
/* USBST */
#define ST_ACT (1 << 31)
#define ST_PLL (1 << 30)
struct rcar_usb_phy_priv {
struct usb_phy phy;
spinlock_t lock;
void __iomem *reg0;
void __iomem *reg1;
int counter;
};
#define usb_phy_to_priv(p) container_of(p, struct rcar_usb_phy_priv, phy)
/*
* USB initial/install operation.
*
* This function setup USB phy.
* The used value and setting order came from
* [USB :: Initial setting] on datasheet.
*/
static int rcar_usb_phy_init(struct usb_phy *phy)
{
struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy);
struct device *dev = phy->dev;
struct rcar_phy_platform_data *pdata = dev_get_platdata(dev);
void __iomem *reg0 = priv->reg0;
void __iomem *reg1 = priv->reg1;
static const u8 ovcn_act[] = { OVC0_ACT, OVC1_ACT, OVC2_ACT };
int i;
u32 val;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
if (priv->counter++ == 0) {
/*
* USB phy start-up
*/
/* (1) USB-PHY standby release */
iowrite32(PHY_ENB, (reg0 + USBPCTRL1));
/* (2) start USB-PHY internal PLL */
iowrite32(PHY_ENB | PLL_ENB, (reg0 + USBPCTRL1));
/* (3) set USB-PHY in accord with the conditions of usage */
if (reg1) {
u32 hsqctl1 = pdata->ferrite_bead ? 0x41 : 0;
u32 hsqctl2 = pdata->ferrite_bead ? 0x0d : 7;
iowrite32(hsqctl1, reg1 + HSQCTL1);
iowrite32(hsqctl2, reg1 + HSQCTL2);
}
/* (4) USB module status check */
for (i = 0; i < 1024; i++) {
udelay(10);
val = ioread32(reg0 + USBST);
if (val == (ST_ACT | ST_PLL))
break;
}
if (val != (ST_ACT | ST_PLL)) {
dev_err(dev, "USB phy not ready\n");
goto phy_init_end;
}
/* (5) USB-PHY reset clear */
iowrite32(PHY_ENB | PLL_ENB | PHY_RST, (reg0 + USBPCTRL1));
/* Board specific port settings */
val = 0;
if (pdata->port1_func)
val |= PORT1;
if (pdata->penc1)
val |= PENC;
for (i = 0; i < 3; i++) {
/* OVCn bits follow each other in the right order */
if (pdata->ovc_pin[i].select_3_3v)
val |= OVC0 << i;
/* OVCn_ACT bits are spaced by irregular intervals */
if (pdata->ovc_pin[i].active_high)
val |= ovcn_act[i];
}
iowrite32(val, (reg0 + USBPCTRL0));
/*
* Bus alignment settings
*/
/* (1) EHCI bus alignment (little endian) */
iowrite32(0x00000000, (reg0 + USBEH0));
/* (1) OHCI bus alignment (little endian) */
iowrite32(0x00000000, (reg0 + USBOH0));
}
phy_init_end:
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static void rcar_usb_phy_shutdown(struct usb_phy *phy)
{
struct rcar_usb_phy_priv *priv = usb_phy_to_priv(phy);
void __iomem *reg0 = priv->reg0;
unsigned long flags;
spin_lock_irqsave(&priv->lock, flags);
if (priv->counter-- == 1) /* last user */
iowrite32(0x00000000, (reg0 + USBPCTRL1));
spin_unlock_irqrestore(&priv->lock, flags);
}
static int rcar_usb_phy_probe(struct platform_device *pdev)
{
struct rcar_usb_phy_priv *priv;
struct resource *res0, *res1;
struct device *dev = &pdev->dev;
void __iomem *reg0, *reg1 = NULL;
int ret;
if (!dev_get_platdata(&pdev->dev)) {
dev_err(dev, "No platform data\n");
return -EINVAL;
}
res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
reg0 = devm_ioremap_resource(dev, res0);
if (IS_ERR(reg0))
return PTR_ERR(reg0);
res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res1) {
reg1 = devm_ioremap_resource(dev, res1);
if (IS_ERR(reg1))
return PTR_ERR(reg1);
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(dev, "priv data allocation error\n");
return -ENOMEM;
}
priv->reg0 = reg0;
priv->reg1 = reg1;
priv->counter = 0;
priv->phy.dev = dev;
priv->phy.label = dev_name(dev);
priv->phy.init = rcar_usb_phy_init;
priv->phy.shutdown = rcar_usb_phy_shutdown;
spin_lock_init(&priv->lock);
ret = usb_add_phy(&priv->phy, USB_PHY_TYPE_USB2);
if (ret < 0) {
dev_err(dev, "usb phy addition error\n");
return ret;
}
platform_set_drvdata(pdev, priv);
return ret;
}
static int rcar_usb_phy_remove(struct platform_device *pdev)
{
struct rcar_usb_phy_priv *priv = platform_get_drvdata(pdev);
usb_remove_phy(&priv->phy);
return 0;
}
static struct platform_driver rcar_usb_phy_driver = {
.driver = {
.name = "rcar_usb_phy",
},
.probe = rcar_usb_phy_probe,
.remove = rcar_usb_phy_remove,
};
module_platform_driver(rcar_usb_phy_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Renesas R-Car USB phy");
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");

457
drivers/usb/phy/phy-tahvo.c Normal file
View file

@ -0,0 +1,457 @@
/*
* Tahvo USB transceiver driver
*
* Copyright (C) 2005-2006 Nokia Corporation
*
* Parts copied from isp1301_omap.c.
* Copyright (C) 2004 Texas Instruments
* Copyright (C) 2004 David Brownell
*
* Original driver written by Juha Yrjölä, Tony Lindgren and Timo Teräs.
* Modified for Retu/Tahvo MFD by Aaro Koskinen.
*
* This file is subject to the terms and conditions of the GNU General
* Public License. See the file "COPYING" in the main directory of this
* archive for more details.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/usb.h>
#include <linux/extcon.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/usb/otg.h>
#include <linux/mfd/retu.h>
#include <linux/usb/gadget.h>
#include <linux/platform_device.h>
#define DRIVER_NAME "tahvo-usb"
#define TAHVO_REG_IDSR 0x02
#define TAHVO_REG_USBR 0x06
#define USBR_SLAVE_CONTROL (1 << 8)
#define USBR_VPPVIO_SW (1 << 7)
#define USBR_SPEED (1 << 6)
#define USBR_REGOUT (1 << 5)
#define USBR_MASTER_SW2 (1 << 4)
#define USBR_MASTER_SW1 (1 << 3)
#define USBR_SLAVE_SW (1 << 2)
#define USBR_NSUSPEND (1 << 1)
#define USBR_SEMODE (1 << 0)
#define TAHVO_MODE_HOST 0
#define TAHVO_MODE_PERIPHERAL 1
struct tahvo_usb {
struct platform_device *pt_dev;
struct usb_phy phy;
int vbus_state;
struct mutex serialize;
struct clk *ick;
int irq;
int tahvo_mode;
struct extcon_dev extcon;
};
static const char *tahvo_cable[] = {
"USB-HOST",
"USB",
NULL,
};
static ssize_t vbus_state_show(struct device *device,
struct device_attribute *attr, char *buf)
{
struct tahvo_usb *tu = dev_get_drvdata(device);
return sprintf(buf, "%s\n", tu->vbus_state ? "on" : "off");
}
static DEVICE_ATTR(vbus, 0444, vbus_state_show, NULL);
static void check_vbus_state(struct tahvo_usb *tu)
{
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
int reg, prev_state;
reg = retu_read(rdev, TAHVO_REG_IDSR);
if (reg & TAHVO_STAT_VBUS) {
switch (tu->phy.state) {
case OTG_STATE_B_IDLE:
/* Enable the gadget driver */
if (tu->phy.otg->gadget)
usb_gadget_vbus_connect(tu->phy.otg->gadget);
tu->phy.state = OTG_STATE_B_PERIPHERAL;
break;
case OTG_STATE_A_IDLE:
/*
* Session is now valid assuming the USB hub is driving
* Vbus.
*/
tu->phy.state = OTG_STATE_A_HOST;
break;
default:
break;
}
dev_info(&tu->pt_dev->dev, "USB cable connected\n");
} else {
switch (tu->phy.state) {
case OTG_STATE_B_PERIPHERAL:
if (tu->phy.otg->gadget)
usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
tu->phy.state = OTG_STATE_B_IDLE;
break;
case OTG_STATE_A_HOST:
tu->phy.state = OTG_STATE_A_IDLE;
break;
default:
break;
}
dev_info(&tu->pt_dev->dev, "USB cable disconnected\n");
}
prev_state = tu->vbus_state;
tu->vbus_state = reg & TAHVO_STAT_VBUS;
if (prev_state != tu->vbus_state) {
extcon_set_cable_state(&tu->extcon, "USB", tu->vbus_state);
sysfs_notify(&tu->pt_dev->dev.kobj, NULL, "vbus_state");
}
}
static void tahvo_usb_become_host(struct tahvo_usb *tu)
{
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
extcon_set_cable_state(&tu->extcon, "USB-HOST", true);
/* Power up the transceiver in USB host mode */
retu_write(rdev, TAHVO_REG_USBR, USBR_REGOUT | USBR_NSUSPEND |
USBR_MASTER_SW2 | USBR_MASTER_SW1);
tu->phy.state = OTG_STATE_A_IDLE;
check_vbus_state(tu);
}
static void tahvo_usb_stop_host(struct tahvo_usb *tu)
{
tu->phy.state = OTG_STATE_A_IDLE;
}
static void tahvo_usb_become_peripheral(struct tahvo_usb *tu)
{
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
extcon_set_cable_state(&tu->extcon, "USB-HOST", false);
/* Power up transceiver and set it in USB peripheral mode */
retu_write(rdev, TAHVO_REG_USBR, USBR_SLAVE_CONTROL | USBR_REGOUT |
USBR_NSUSPEND | USBR_SLAVE_SW);
tu->phy.state = OTG_STATE_B_IDLE;
check_vbus_state(tu);
}
static void tahvo_usb_stop_peripheral(struct tahvo_usb *tu)
{
if (tu->phy.otg->gadget)
usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
tu->phy.state = OTG_STATE_B_IDLE;
}
static void tahvo_usb_power_off(struct tahvo_usb *tu)
{
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
/* Disable gadget controller if any */
if (tu->phy.otg->gadget)
usb_gadget_vbus_disconnect(tu->phy.otg->gadget);
/* Power off transceiver */
retu_write(rdev, TAHVO_REG_USBR, 0);
tu->phy.state = OTG_STATE_UNDEFINED;
}
static int tahvo_usb_set_suspend(struct usb_phy *dev, int suspend)
{
struct tahvo_usb *tu = container_of(dev, struct tahvo_usb, phy);
struct retu_dev *rdev = dev_get_drvdata(tu->pt_dev->dev.parent);
u16 w;
dev_dbg(&tu->pt_dev->dev, "%s\n", __func__);
w = retu_read(rdev, TAHVO_REG_USBR);
if (suspend)
w &= ~USBR_NSUSPEND;
else
w |= USBR_NSUSPEND;
retu_write(rdev, TAHVO_REG_USBR, w);
return 0;
}
static int tahvo_usb_set_host(struct usb_otg *otg, struct usb_bus *host)
{
struct tahvo_usb *tu = container_of(otg->phy, struct tahvo_usb, phy);
dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, host);
mutex_lock(&tu->serialize);
if (host == NULL) {
if (tu->tahvo_mode == TAHVO_MODE_HOST)
tahvo_usb_power_off(tu);
otg->host = NULL;
mutex_unlock(&tu->serialize);
return 0;
}
if (tu->tahvo_mode == TAHVO_MODE_HOST) {
otg->host = NULL;
tahvo_usb_become_host(tu);
}
otg->host = host;
mutex_unlock(&tu->serialize);
return 0;
}
static int tahvo_usb_set_peripheral(struct usb_otg *otg,
struct usb_gadget *gadget)
{
struct tahvo_usb *tu = container_of(otg->phy, struct tahvo_usb, phy);
dev_dbg(&tu->pt_dev->dev, "%s %p\n", __func__, gadget);
mutex_lock(&tu->serialize);
if (!gadget) {
if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
tahvo_usb_power_off(tu);
tu->phy.otg->gadget = NULL;
mutex_unlock(&tu->serialize);
return 0;
}
tu->phy.otg->gadget = gadget;
if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
tahvo_usb_become_peripheral(tu);
mutex_unlock(&tu->serialize);
return 0;
}
static irqreturn_t tahvo_usb_vbus_interrupt(int irq, void *_tu)
{
struct tahvo_usb *tu = _tu;
mutex_lock(&tu->serialize);
check_vbus_state(tu);
mutex_unlock(&tu->serialize);
return IRQ_HANDLED;
}
static ssize_t otg_mode_show(struct device *device,
struct device_attribute *attr, char *buf)
{
struct tahvo_usb *tu = dev_get_drvdata(device);
switch (tu->tahvo_mode) {
case TAHVO_MODE_HOST:
return sprintf(buf, "host\n");
case TAHVO_MODE_PERIPHERAL:
return sprintf(buf, "peripheral\n");
}
return -EINVAL;
}
static ssize_t otg_mode_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct tahvo_usb *tu = dev_get_drvdata(device);
int r;
mutex_lock(&tu->serialize);
if (count >= 4 && strncmp(buf, "host", 4) == 0) {
if (tu->tahvo_mode == TAHVO_MODE_PERIPHERAL)
tahvo_usb_stop_peripheral(tu);
tu->tahvo_mode = TAHVO_MODE_HOST;
if (tu->phy.otg->host) {
dev_info(device, "HOST mode: host controller present\n");
tahvo_usb_become_host(tu);
} else {
dev_info(device, "HOST mode: no host controller, powering off\n");
tahvo_usb_power_off(tu);
}
r = strlen(buf);
} else if (count >= 10 && strncmp(buf, "peripheral", 10) == 0) {
if (tu->tahvo_mode == TAHVO_MODE_HOST)
tahvo_usb_stop_host(tu);
tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
if (tu->phy.otg->gadget) {
dev_info(device, "PERIPHERAL mode: gadget driver present\n");
tahvo_usb_become_peripheral(tu);
} else {
dev_info(device, "PERIPHERAL mode: no gadget driver, powering off\n");
tahvo_usb_power_off(tu);
}
r = strlen(buf);
} else {
r = -EINVAL;
}
mutex_unlock(&tu->serialize);
return r;
}
static DEVICE_ATTR(otg_mode, 0644, otg_mode_show, otg_mode_store);
static struct attribute *tahvo_attributes[] = {
&dev_attr_vbus.attr,
&dev_attr_otg_mode.attr,
NULL
};
static struct attribute_group tahvo_attr_group = {
.attrs = tahvo_attributes,
};
static int tahvo_usb_probe(struct platform_device *pdev)
{
struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
struct tahvo_usb *tu;
int ret;
tu = devm_kzalloc(&pdev->dev, sizeof(*tu), GFP_KERNEL);
if (!tu)
return -ENOMEM;
tu->phy.otg = devm_kzalloc(&pdev->dev, sizeof(*tu->phy.otg),
GFP_KERNEL);
if (!tu->phy.otg)
return -ENOMEM;
tu->pt_dev = pdev;
/* Default mode */
#ifdef CONFIG_TAHVO_USB_HOST_BY_DEFAULT
tu->tahvo_mode = TAHVO_MODE_HOST;
#else
tu->tahvo_mode = TAHVO_MODE_PERIPHERAL;
#endif
mutex_init(&tu->serialize);
tu->ick = devm_clk_get(&pdev->dev, "usb_l4_ick");
if (!IS_ERR(tu->ick))
clk_enable(tu->ick);
/*
* Set initial state, so that we generate kevents only on state changes.
*/
tu->vbus_state = retu_read(rdev, TAHVO_REG_IDSR) & TAHVO_STAT_VBUS;
tu->extcon.name = DRIVER_NAME;
tu->extcon.supported_cable = tahvo_cable;
tu->extcon.dev.parent = &pdev->dev;
ret = extcon_dev_register(&tu->extcon);
if (ret) {
dev_err(&pdev->dev, "could not register extcon device: %d\n",
ret);
goto err_disable_clk;
}
/* Set the initial cable state. */
extcon_set_cable_state(&tu->extcon, "USB-HOST",
tu->tahvo_mode == TAHVO_MODE_HOST);
extcon_set_cable_state(&tu->extcon, "USB", tu->vbus_state);
/* Create OTG interface */
tahvo_usb_power_off(tu);
tu->phy.dev = &pdev->dev;
tu->phy.state = OTG_STATE_UNDEFINED;
tu->phy.label = DRIVER_NAME;
tu->phy.set_suspend = tahvo_usb_set_suspend;
tu->phy.otg->phy = &tu->phy;
tu->phy.otg->set_host = tahvo_usb_set_host;
tu->phy.otg->set_peripheral = tahvo_usb_set_peripheral;
ret = usb_add_phy(&tu->phy, USB_PHY_TYPE_USB2);
if (ret < 0) {
dev_err(&pdev->dev, "cannot register USB transceiver: %d\n",
ret);
goto err_extcon_unreg;
}
dev_set_drvdata(&pdev->dev, tu);
tu->irq = platform_get_irq(pdev, 0);
ret = request_threaded_irq(tu->irq, NULL, tahvo_usb_vbus_interrupt, 0,
"tahvo-vbus", tu);
if (ret) {
dev_err(&pdev->dev, "could not register tahvo-vbus irq: %d\n",
ret);
goto err_remove_phy;
}
/* Attributes */
ret = sysfs_create_group(&pdev->dev.kobj, &tahvo_attr_group);
if (ret) {
dev_err(&pdev->dev, "cannot create sysfs group: %d\n", ret);
goto err_free_irq;
}
return 0;
err_free_irq:
free_irq(tu->irq, tu);
err_remove_phy:
usb_remove_phy(&tu->phy);
err_extcon_unreg:
extcon_dev_unregister(&tu->extcon);
err_disable_clk:
if (!IS_ERR(tu->ick))
clk_disable(tu->ick);
return ret;
}
static int tahvo_usb_remove(struct platform_device *pdev)
{
struct tahvo_usb *tu = platform_get_drvdata(pdev);
sysfs_remove_group(&pdev->dev.kobj, &tahvo_attr_group);
free_irq(tu->irq, tu);
usb_remove_phy(&tu->phy);
extcon_dev_unregister(&tu->extcon);
if (!IS_ERR(tu->ick))
clk_disable(tu->ick);
return 0;
}
static struct platform_driver tahvo_usb_driver = {
.probe = tahvo_usb_probe,
.remove = tahvo_usb_remove,
.driver = {
.name = "tahvo-usb",
.owner = THIS_MODULE,
},
};
module_platform_driver(tahvo_usb_driver);
MODULE_DESCRIPTION("Tahvo USB transceiver driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Juha Yrjölä, Tony Lindgren, and Timo Teräs");
MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");

File diff suppressed because it is too large Load diff

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/phy/omap_usb.h>
#include <linux/i2c/twl.h>
#include <linux/regulator/consumer.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/of.h>
/* usb register definitions */
#define USB_VENDOR_ID_LSB 0x00
#define USB_VENDOR_ID_MSB 0x01
#define USB_PRODUCT_ID_LSB 0x02
#define USB_PRODUCT_ID_MSB 0x03
#define USB_VBUS_CTRL_SET 0x04
#define USB_VBUS_CTRL_CLR 0x05
#define USB_ID_CTRL_SET 0x06
#define USB_ID_CTRL_CLR 0x07
#define USB_VBUS_INT_SRC 0x08
#define USB_VBUS_INT_LATCH_SET 0x09
#define USB_VBUS_INT_LATCH_CLR 0x0A
#define USB_VBUS_INT_EN_LO_SET 0x0B
#define USB_VBUS_INT_EN_LO_CLR 0x0C
#define USB_VBUS_INT_EN_HI_SET 0x0D
#define USB_VBUS_INT_EN_HI_CLR 0x0E
#define USB_ID_INT_SRC 0x0F
#define USB_ID_INT_LATCH_SET 0x10
#define USB_ID_INT_LATCH_CLR 0x11
#define USB_ID_INT_EN_LO_SET 0x12
#define USB_ID_INT_EN_LO_CLR 0x13
#define USB_ID_INT_EN_HI_SET 0x14
#define USB_ID_INT_EN_HI_CLR 0x15
#define USB_OTG_ADP_CTRL 0x16
#define USB_OTG_ADP_HIGH 0x17
#define USB_OTG_ADP_LOW 0x18
#define USB_OTG_ADP_RISE 0x19
#define USB_OTG_REVISION 0x1A
/* to be moved to LDO */
#define TWL6030_MISC2 0xE5
#define TWL6030_CFG_LDO_PD2 0xF5
#define TWL6030_BACKUP_REG 0xFA
#define STS_HW_CONDITIONS 0x21
/* In module TWL6030_MODULE_PM_MASTER */
#define STS_HW_CONDITIONS 0x21
#define STS_USB_ID BIT(2)
/* In module TWL6030_MODULE_PM_RECEIVER */
#define VUSB_CFG_TRANS 0x71
#define VUSB_CFG_STATE 0x72
#define VUSB_CFG_VOLTAGE 0x73
/* in module TWL6030_MODULE_MAIN_CHARGE */
#define CHARGERUSB_CTRL1 0x8
#define CONTROLLER_STAT1 0x03
#define VBUS_DET BIT(2)
struct twl6030_usb {
struct phy_companion comparator;
struct device *dev;
/* for vbus reporting with irqs disabled */
spinlock_t lock;
struct regulator *usb3v3;
/* used to set vbus, in atomic path */
struct work_struct set_vbus_work;
int irq1;
int irq2;
enum omap_musb_vbus_id_status linkstat;
u8 asleep;
bool vbus_enable;
const char *regulator;
};
#define comparator_to_twl(x) container_of((x), struct twl6030_usb, comparator)
/*-------------------------------------------------------------------------*/
static inline int twl6030_writeb(struct twl6030_usb *twl, u8 module,
u8 data, u8 address)
{
int ret = 0;
ret = twl_i2c_write_u8(module, data, address);
if (ret < 0)
dev_err(twl->dev,
"Write[0x%x] Error %d\n", address, ret);
return ret;
}
static inline u8 twl6030_readb(struct twl6030_usb *twl, u8 module, u8 address)
{
u8 data;
int ret;
ret = twl_i2c_read_u8(module, &data, address);
if (ret >= 0)
ret = data;
else
dev_err(twl->dev,
"readb[0x%x,0x%x] Error %d\n",
module, address, ret);
return ret;
}
static int twl6030_start_srp(struct phy_companion *comparator)
{
struct twl6030_usb *twl = comparator_to_twl(comparator);
twl6030_writeb(twl, TWL_MODULE_USB, 0x24, USB_VBUS_CTRL_SET);
twl6030_writeb(twl, TWL_MODULE_USB, 0x84, USB_VBUS_CTRL_SET);
mdelay(100);
twl6030_writeb(twl, TWL_MODULE_USB, 0xa0, USB_VBUS_CTRL_CLR);
return 0;
}
static int twl6030_usb_ldo_init(struct twl6030_usb *twl)
{
/* Set to OTG_REV 1.3 and turn on the ID_WAKEUP_COMP */
twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_BACKUP_REG);
/* Program CFG_LDO_PD2 register and set VUSB bit */
twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_CFG_LDO_PD2);
/* Program MISC2 register and set bit VUSB_IN_VBAT */
twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x10, TWL6030_MISC2);
twl->usb3v3 = regulator_get(twl->dev, twl->regulator);
if (IS_ERR(twl->usb3v3))
return -ENODEV;
/* Program the USB_VBUS_CTRL_SET and set VBUS_ACT_COMP bit */
twl6030_writeb(twl, TWL_MODULE_USB, 0x4, USB_VBUS_CTRL_SET);
/*
* Program the USB_ID_CTRL_SET register to enable GND drive
* and the ID comparators
*/
twl6030_writeb(twl, TWL_MODULE_USB, 0x14, USB_ID_CTRL_SET);
return 0;
}
static ssize_t twl6030_usb_vbus_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct twl6030_usb *twl = dev_get_drvdata(dev);
unsigned long flags;
int ret = -EINVAL;
spin_lock_irqsave(&twl->lock, flags);
switch (twl->linkstat) {
case OMAP_MUSB_VBUS_VALID:
ret = snprintf(buf, PAGE_SIZE, "vbus\n");
break;
case OMAP_MUSB_ID_GROUND:
ret = snprintf(buf, PAGE_SIZE, "id\n");
break;
case OMAP_MUSB_VBUS_OFF:
ret = snprintf(buf, PAGE_SIZE, "none\n");
break;
default:
ret = snprintf(buf, PAGE_SIZE, "UNKNOWN\n");
}
spin_unlock_irqrestore(&twl->lock, flags);
return ret;
}
static DEVICE_ATTR(vbus, 0444, twl6030_usb_vbus_show, NULL);
static irqreturn_t twl6030_usb_irq(int irq, void *_twl)
{
struct twl6030_usb *twl = _twl;
enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN;
u8 vbus_state, hw_state;
int ret;
hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS);
vbus_state = twl6030_readb(twl, TWL_MODULE_MAIN_CHARGE,
CONTROLLER_STAT1);
if (!(hw_state & STS_USB_ID)) {
if (vbus_state & VBUS_DET) {
ret = regulator_enable(twl->usb3v3);
if (ret)
dev_err(twl->dev, "Failed to enable usb3v3\n");
twl->asleep = 1;
status = OMAP_MUSB_VBUS_VALID;
twl->linkstat = status;
omap_musb_mailbox(status);
} else {
if (twl->linkstat != OMAP_MUSB_UNKNOWN) {
status = OMAP_MUSB_VBUS_OFF;
twl->linkstat = status;
omap_musb_mailbox(status);
if (twl->asleep) {
regulator_disable(twl->usb3v3);
twl->asleep = 0;
}
}
}
}
sysfs_notify(&twl->dev->kobj, NULL, "vbus");
return IRQ_HANDLED;
}
static irqreturn_t twl6030_usbotg_irq(int irq, void *_twl)
{
struct twl6030_usb *twl = _twl;
enum omap_musb_vbus_id_status status = OMAP_MUSB_UNKNOWN;
u8 hw_state;
int ret;
hw_state = twl6030_readb(twl, TWL6030_MODULE_ID0, STS_HW_CONDITIONS);
if (hw_state & STS_USB_ID) {
ret = regulator_enable(twl->usb3v3);
if (ret)
dev_err(twl->dev, "Failed to enable usb3v3\n");
twl->asleep = 1;
twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_CLR);
twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_SET);
status = OMAP_MUSB_ID_GROUND;
twl->linkstat = status;
omap_musb_mailbox(status);
} else {
twl6030_writeb(twl, TWL_MODULE_USB, 0x10, USB_ID_INT_EN_HI_CLR);
twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET);
}
twl6030_writeb(twl, TWL_MODULE_USB, status, USB_ID_INT_LATCH_CLR);
return IRQ_HANDLED;
}
static int twl6030_enable_irq(struct twl6030_usb *twl)
{
twl6030_writeb(twl, TWL_MODULE_USB, 0x1, USB_ID_INT_EN_HI_SET);
twl6030_interrupt_unmask(0x05, REG_INT_MSK_LINE_C);
twl6030_interrupt_unmask(0x05, REG_INT_MSK_STS_C);
twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK,
REG_INT_MSK_LINE_C);
twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK,
REG_INT_MSK_STS_C);
twl6030_usb_irq(twl->irq2, twl);
twl6030_usbotg_irq(twl->irq1, twl);
return 0;
}
static void otg_set_vbus_work(struct work_struct *data)
{
struct twl6030_usb *twl = container_of(data, struct twl6030_usb,
set_vbus_work);
/*
* Start driving VBUS. Set OPA_MODE bit in CHARGERUSB_CTRL1
* register. This enables boost mode.
*/
if (twl->vbus_enable)
twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x40,
CHARGERUSB_CTRL1);
else
twl6030_writeb(twl, TWL_MODULE_MAIN_CHARGE , 0x00,
CHARGERUSB_CTRL1);
}
static int twl6030_set_vbus(struct phy_companion *comparator, bool enabled)
{
struct twl6030_usb *twl = comparator_to_twl(comparator);
twl->vbus_enable = enabled;
schedule_work(&twl->set_vbus_work);
return 0;
}
static int twl6030_usb_probe(struct platform_device *pdev)
{
u32 ret;
struct twl6030_usb *twl;
int status, err;
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct twl4030_usb_data *pdata = dev_get_platdata(dev);
twl = devm_kzalloc(dev, sizeof(*twl), GFP_KERNEL);
if (!twl)
return -ENOMEM;
twl->dev = &pdev->dev;
twl->irq1 = platform_get_irq(pdev, 0);
twl->irq2 = platform_get_irq(pdev, 1);
twl->linkstat = OMAP_MUSB_UNKNOWN;
twl->comparator.set_vbus = twl6030_set_vbus;
twl->comparator.start_srp = twl6030_start_srp;
ret = omap_usb2_set_comparator(&twl->comparator);
if (ret == -ENODEV) {
dev_info(&pdev->dev, "phy not ready, deferring probe");
return -EPROBE_DEFER;
}
if (np) {
twl->regulator = "usb";
} else if (pdata) {
if (pdata->features & TWL6032_SUBCLASS)
twl->regulator = "ldousb";
else
twl->regulator = "vusb";
} else {
dev_err(&pdev->dev, "twl6030 initialized without pdata\n");
return -EINVAL;
}
/* init spinlock for workqueue */
spin_lock_init(&twl->lock);
err = twl6030_usb_ldo_init(twl);
if (err) {
dev_err(&pdev->dev, "ldo init failed\n");
return err;
}
platform_set_drvdata(pdev, twl);
if (device_create_file(&pdev->dev, &dev_attr_vbus))
dev_warn(&pdev->dev, "could not create sysfs file\n");
INIT_WORK(&twl->set_vbus_work, otg_set_vbus_work);
status = request_threaded_irq(twl->irq1, NULL, twl6030_usbotg_irq,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"twl6030_usb", twl);
if (status < 0) {
dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
twl->irq1, status);
device_remove_file(twl->dev, &dev_attr_vbus);
return status;
}
status = request_threaded_irq(twl->irq2, NULL, twl6030_usb_irq,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"twl6030_usb", twl);
if (status < 0) {
dev_err(&pdev->dev, "can't get IRQ %d, err %d\n",
twl->irq2, status);
free_irq(twl->irq1, twl);
device_remove_file(twl->dev, &dev_attr_vbus);
return status;
}
twl->asleep = 0;
twl6030_enable_irq(twl);
dev_info(&pdev->dev, "Initialized TWL6030 USB module\n");
return 0;
}
static int twl6030_usb_remove(struct platform_device *pdev)
{
struct twl6030_usb *twl = platform_get_drvdata(pdev);
twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK,
REG_INT_MSK_LINE_C);
twl6030_interrupt_mask(TWL6030_USBOTG_INT_MASK,
REG_INT_MSK_STS_C);
free_irq(twl->irq1, twl);
free_irq(twl->irq2, twl);
regulator_put(twl->usb3v3);
device_remove_file(twl->dev, &dev_attr_vbus);
cancel_work_sync(&twl->set_vbus_work);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id twl6030_usb_id_table[] = {
{ .compatible = "ti,twl6030-usb" },
{}
};
MODULE_DEVICE_TABLE(of, twl6030_usb_id_table);
#endif
static struct platform_driver twl6030_usb_driver = {
.probe = twl6030_usb_probe,
.remove = twl6030_usb_remove,
.driver = {
.name = "twl6030_usb",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(twl6030_usb_id_table),
},
};
static int __init twl6030_usb_init(void)
{
return platform_driver_register(&twl6030_usb_driver);
}
subsys_initcall(twl6030_usb_init);
static void __exit twl6030_usb_exit(void)
{
platform_driver_unregister(&twl6030_usb_driver);
}
module_exit(twl6030_usb_exit);
MODULE_ALIAS("platform:twl6030_usb");
MODULE_AUTHOR("Hema HK <hemahk@ti.com>");
MODULE_DESCRIPTION("TWL6030 USB transceiver driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,82 @@
/*
* Copyright (C) 2011 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/usb.h>
#include <linux/io.h>
#include <linux/usb/otg.h>
#include <linux/usb/ulpi.h>
#define ULPI_VIEW_WAKEUP (1 << 31)
#define ULPI_VIEW_RUN (1 << 30)
#define ULPI_VIEW_WRITE (1 << 29)
#define ULPI_VIEW_READ (0 << 29)
#define ULPI_VIEW_ADDR(x) (((x) & 0xff) << 16)
#define ULPI_VIEW_DATA_READ(x) (((x) >> 8) & 0xff)
#define ULPI_VIEW_DATA_WRITE(x) ((x) & 0xff)
static int ulpi_viewport_wait(void __iomem *view, u32 mask)
{
unsigned long usec = 2000;
while (usec--) {
if (!(readl(view) & mask))
return 0;
udelay(1);
}
return -ETIMEDOUT;
}
static int ulpi_viewport_read(struct usb_phy *otg, u32 reg)
{
int ret;
void __iomem *view = otg->io_priv;
writel(ULPI_VIEW_WAKEUP | ULPI_VIEW_WRITE, view);
ret = ulpi_viewport_wait(view, ULPI_VIEW_WAKEUP);
if (ret)
return ret;
writel(ULPI_VIEW_RUN | ULPI_VIEW_READ | ULPI_VIEW_ADDR(reg), view);
ret = ulpi_viewport_wait(view, ULPI_VIEW_RUN);
if (ret)
return ret;
return ULPI_VIEW_DATA_READ(readl(view));
}
static int ulpi_viewport_write(struct usb_phy *otg, u32 val, u32 reg)
{
int ret;
void __iomem *view = otg->io_priv;
writel(ULPI_VIEW_WAKEUP | ULPI_VIEW_WRITE, view);
ret = ulpi_viewport_wait(view, ULPI_VIEW_WAKEUP);
if (ret)
return ret;
writel(ULPI_VIEW_RUN | ULPI_VIEW_WRITE | ULPI_VIEW_DATA_WRITE(val) |
ULPI_VIEW_ADDR(reg), view);
return ulpi_viewport_wait(view, ULPI_VIEW_RUN);
}
struct usb_phy_io_ops ulpi_viewport_access_ops = {
.read = ulpi_viewport_read,
.write = ulpi_viewport_write,
};
EXPORT_SYMBOL_GPL(ulpi_viewport_access_ops);

286
drivers/usb/phy/phy-ulpi.c Normal file
View file

@ -0,0 +1,286 @@
/*
* Generic ULPI USB transceiver support
*
* Copyright (C) 2009 Daniel Mack <daniel@caiaq.de>
*
* Based on sources from
*
* Sascha Hauer <s.hauer@pengutronix.de>
* Freescale Semiconductors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/usb.h>
#include <linux/usb/otg.h>
#include <linux/usb/ulpi.h>
struct ulpi_info {
unsigned int id;
char *name;
};
#define ULPI_ID(vendor, product) (((vendor) << 16) | (product))
#define ULPI_INFO(_id, _name) \
{ \
.id = (_id), \
.name = (_name), \
}
/* ULPI hardcoded IDs, used for probing */
static struct ulpi_info ulpi_ids[] = {
ULPI_INFO(ULPI_ID(0x04cc, 0x1504), "NXP ISP1504"),
ULPI_INFO(ULPI_ID(0x0424, 0x0006), "SMSC USB331x"),
ULPI_INFO(ULPI_ID(0x0424, 0x0007), "SMSC USB3320"),
ULPI_INFO(ULPI_ID(0x0424, 0x0009), "SMSC USB334x"),
ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"),
};
static int ulpi_set_otg_flags(struct usb_phy *phy)
{
unsigned int flags = ULPI_OTG_CTRL_DP_PULLDOWN |
ULPI_OTG_CTRL_DM_PULLDOWN;
if (phy->flags & ULPI_OTG_ID_PULLUP)
flags |= ULPI_OTG_CTRL_ID_PULLUP;
/*
* ULPI Specification rev.1.1 default
* for Dp/DmPulldown is enabled.
*/
if (phy->flags & ULPI_OTG_DP_PULLDOWN_DIS)
flags &= ~ULPI_OTG_CTRL_DP_PULLDOWN;
if (phy->flags & ULPI_OTG_DM_PULLDOWN_DIS)
flags &= ~ULPI_OTG_CTRL_DM_PULLDOWN;
if (phy->flags & ULPI_OTG_EXTVBUSIND)
flags |= ULPI_OTG_CTRL_EXTVBUSIND;
return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
}
static int ulpi_set_fc_flags(struct usb_phy *phy)
{
unsigned int flags = 0;
/*
* ULPI Specification rev.1.1 default
* for XcvrSelect is Full Speed.
*/
if (phy->flags & ULPI_FC_HS)
flags |= ULPI_FUNC_CTRL_HIGH_SPEED;
else if (phy->flags & ULPI_FC_LS)
flags |= ULPI_FUNC_CTRL_LOW_SPEED;
else if (phy->flags & ULPI_FC_FS4LS)
flags |= ULPI_FUNC_CTRL_FS4LS;
else
flags |= ULPI_FUNC_CTRL_FULL_SPEED;
if (phy->flags & ULPI_FC_TERMSEL)
flags |= ULPI_FUNC_CTRL_TERMSELECT;
/*
* ULPI Specification rev.1.1 default
* for OpMode is Normal Operation.
*/
if (phy->flags & ULPI_FC_OP_NODRV)
flags |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING;
else if (phy->flags & ULPI_FC_OP_DIS_NRZI)
flags |= ULPI_FUNC_CTRL_OPMODE_DISABLE_NRZI;
else if (phy->flags & ULPI_FC_OP_NSYNC_NEOP)
flags |= ULPI_FUNC_CTRL_OPMODE_NOSYNC_NOEOP;
else
flags |= ULPI_FUNC_CTRL_OPMODE_NORMAL;
/*
* ULPI Specification rev.1.1 default
* for SuspendM is Powered.
*/
flags |= ULPI_FUNC_CTRL_SUSPENDM;
return usb_phy_io_write(phy, flags, ULPI_FUNC_CTRL);
}
static int ulpi_set_ic_flags(struct usb_phy *phy)
{
unsigned int flags = 0;
if (phy->flags & ULPI_IC_AUTORESUME)
flags |= ULPI_IFC_CTRL_AUTORESUME;
if (phy->flags & ULPI_IC_EXTVBUS_INDINV)
flags |= ULPI_IFC_CTRL_EXTERNAL_VBUS;
if (phy->flags & ULPI_IC_IND_PASSTHRU)
flags |= ULPI_IFC_CTRL_PASSTHRU;
if (phy->flags & ULPI_IC_PROTECT_DIS)
flags |= ULPI_IFC_CTRL_PROTECT_IFC_DISABLE;
return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL);
}
static int ulpi_set_flags(struct usb_phy *phy)
{
int ret;
ret = ulpi_set_otg_flags(phy);
if (ret)
return ret;
ret = ulpi_set_ic_flags(phy);
if (ret)
return ret;
return ulpi_set_fc_flags(phy);
}
static int ulpi_check_integrity(struct usb_phy *phy)
{
int ret, i;
unsigned int val = 0x55;
for (i = 0; i < 2; i++) {
ret = usb_phy_io_write(phy, val, ULPI_SCRATCH);
if (ret < 0)
return ret;
ret = usb_phy_io_read(phy, ULPI_SCRATCH);
if (ret < 0)
return ret;
if (ret != val) {
pr_err("ULPI integrity check: failed!");
return -ENODEV;
}
val = val << 1;
}
pr_info("ULPI integrity check: passed.\n");
return 0;
}
static int ulpi_init(struct usb_phy *phy)
{
int i, vid, pid, ret;
u32 ulpi_id = 0;
for (i = 0; i < 4; i++) {
ret = usb_phy_io_read(phy, ULPI_PRODUCT_ID_HIGH - i);
if (ret < 0)
return ret;
ulpi_id = (ulpi_id << 8) | ret;
}
vid = ulpi_id & 0xffff;
pid = ulpi_id >> 16;
pr_info("ULPI transceiver vendor/product ID 0x%04x/0x%04x\n", vid, pid);
for (i = 0; i < ARRAY_SIZE(ulpi_ids); i++) {
if (ulpi_ids[i].id == ULPI_ID(vid, pid)) {
pr_info("Found %s ULPI transceiver.\n",
ulpi_ids[i].name);
break;
}
}
ret = ulpi_check_integrity(phy);
if (ret)
return ret;
return ulpi_set_flags(phy);
}
static int ulpi_set_host(struct usb_otg *otg, struct usb_bus *host)
{
struct usb_phy *phy = otg->phy;
unsigned int flags = usb_phy_io_read(phy, ULPI_IFC_CTRL);
if (!host) {
otg->host = NULL;
return 0;
}
otg->host = host;
flags &= ~(ULPI_IFC_CTRL_6_PIN_SERIAL_MODE |
ULPI_IFC_CTRL_3_PIN_SERIAL_MODE |
ULPI_IFC_CTRL_CARKITMODE);
if (phy->flags & ULPI_IC_6PIN_SERIAL)
flags |= ULPI_IFC_CTRL_6_PIN_SERIAL_MODE;
else if (phy->flags & ULPI_IC_3PIN_SERIAL)
flags |= ULPI_IFC_CTRL_3_PIN_SERIAL_MODE;
else if (phy->flags & ULPI_IC_CARKIT)
flags |= ULPI_IFC_CTRL_CARKITMODE;
return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL);
}
static int ulpi_set_vbus(struct usb_otg *otg, bool on)
{
struct usb_phy *phy = otg->phy;
unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL);
flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT);
if (on) {
if (phy->flags & ULPI_OTG_DRVVBUS)
flags |= ULPI_OTG_CTRL_DRVVBUS;
if (phy->flags & ULPI_OTG_DRVVBUS_EXT)
flags |= ULPI_OTG_CTRL_DRVVBUS_EXT;
}
return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
}
struct usb_phy *
otg_ulpi_create(struct usb_phy_io_ops *ops,
unsigned int flags)
{
struct usb_phy *phy;
struct usb_otg *otg;
phy = kzalloc(sizeof(*phy), GFP_KERNEL);
if (!phy)
return NULL;
otg = kzalloc(sizeof(*otg), GFP_KERNEL);
if (!otg) {
kfree(phy);
return NULL;
}
phy->label = "ULPI";
phy->flags = flags;
phy->io_ops = ops;
phy->otg = otg;
phy->init = ulpi_init;
otg->phy = phy;
otg->set_host = ulpi_set_host;
otg->set_vbus = ulpi_set_vbus;
return phy;
}
EXPORT_SYMBOL_GPL(otg_ulpi_create);

448
drivers/usb/phy/phy.c Normal file
View file

@ -0,0 +1,448 @@
/*
* phy.c -- USB phy handling
*
* Copyright (C) 2004-2013 Texas Instruments
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/usb/phy.h>
static LIST_HEAD(phy_list);
static LIST_HEAD(phy_bind_list);
static DEFINE_SPINLOCK(phy_lock);
static struct usb_phy *__usb_find_phy(struct list_head *list,
enum usb_phy_type type)
{
struct usb_phy *phy = NULL;
list_for_each_entry(phy, list, head) {
if (phy->type != type)
continue;
return phy;
}
return ERR_PTR(-ENODEV);
}
static struct usb_phy *__usb_find_phy_dev(struct device *dev,
struct list_head *list, u8 index)
{
struct usb_phy_bind *phy_bind = NULL;
list_for_each_entry(phy_bind, list, list) {
if (!(strcmp(phy_bind->dev_name, dev_name(dev))) &&
phy_bind->index == index) {
if (phy_bind->phy)
return phy_bind->phy;
else
return ERR_PTR(-EPROBE_DEFER);
}
}
return ERR_PTR(-ENODEV);
}
static struct usb_phy *__of_usb_find_phy(struct device_node *node)
{
struct usb_phy *phy;
list_for_each_entry(phy, &phy_list, head) {
if (node != phy->dev->of_node)
continue;
return phy;
}
return ERR_PTR(-ENODEV);
}
static void devm_usb_phy_release(struct device *dev, void *res)
{
struct usb_phy *phy = *(struct usb_phy **)res;
usb_put_phy(phy);
}
static int devm_usb_phy_match(struct device *dev, void *res, void *match_data)
{
struct usb_phy **phy = res;
return *phy == match_data;
}
/**
* devm_usb_get_phy - find the USB PHY
* @dev - device that requests this phy
* @type - the type of the phy the controller requires
*
* Gets the phy using usb_get_phy(), and associates a device with it using
* devres. On driver detach, release function is invoked on the devres data,
* then, devres data is freed.
*
* For use by USB host and peripheral drivers.
*/
struct usb_phy *devm_usb_get_phy(struct device *dev, enum usb_phy_type type)
{
struct usb_phy **ptr, *phy;
ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
phy = usb_get_phy(type);
if (!IS_ERR(phy)) {
*ptr = phy;
devres_add(dev, ptr);
} else
devres_free(ptr);
return phy;
}
EXPORT_SYMBOL_GPL(devm_usb_get_phy);
/**
* usb_get_phy - find the USB PHY
* @type - the type of the phy the controller requires
*
* Returns the phy driver, after getting a refcount to it; or
* -ENODEV if there is no such phy. The caller is responsible for
* calling usb_put_phy() to release that count.
*
* For use by USB host and peripheral drivers.
*/
struct usb_phy *usb_get_phy(enum usb_phy_type type)
{
struct usb_phy *phy = NULL;
unsigned long flags;
spin_lock_irqsave(&phy_lock, flags);
phy = __usb_find_phy(&phy_list, type);
if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) {
pr_debug("PHY: unable to find transceiver of type %s\n",
usb_phy_type_string(type));
if (!IS_ERR(phy))
phy = ERR_PTR(-ENODEV);
goto err0;
}
get_device(phy->dev);
err0:
spin_unlock_irqrestore(&phy_lock, flags);
return phy;
}
EXPORT_SYMBOL_GPL(usb_get_phy);
/**
* devm_usb_get_phy_by_phandle - find the USB PHY by phandle
* @dev - device that requests this phy
* @phandle - name of the property holding the phy phandle value
* @index - the index of the phy
*
* Returns the phy driver associated with the given phandle value,
* after getting a refcount to it, -ENODEV if there is no such phy or
* -EPROBE_DEFER if there is a phandle to the phy, but the device is
* not yet loaded. While at that, it also associates the device with
* the phy using devres. On driver detach, release function is invoked
* on the devres data, then, devres data is freed.
*
* For use by USB host and peripheral drivers.
*/
struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev,
const char *phandle, u8 index)
{
struct usb_phy *phy = ERR_PTR(-ENOMEM), **ptr;
unsigned long flags;
struct device_node *node;
if (!dev->of_node) {
dev_dbg(dev, "device does not have a device node entry\n");
return ERR_PTR(-EINVAL);
}
node = of_parse_phandle(dev->of_node, phandle, index);
if (!node) {
dev_dbg(dev, "failed to get %s phandle in %s node\n", phandle,
dev->of_node->full_name);
return ERR_PTR(-ENODEV);
}
ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr) {
dev_dbg(dev, "failed to allocate memory for devres\n");
goto err0;
}
spin_lock_irqsave(&phy_lock, flags);
phy = __of_usb_find_phy(node);
if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) {
phy = ERR_PTR(-EPROBE_DEFER);
devres_free(ptr);
goto err1;
}
*ptr = phy;
devres_add(dev, ptr);
get_device(phy->dev);
err1:
spin_unlock_irqrestore(&phy_lock, flags);
err0:
of_node_put(node);
return phy;
}
EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_phandle);
/**
* usb_get_phy_dev - find the USB PHY
* @dev - device that requests this phy
* @index - the index of the phy
*
* Returns the phy driver, after getting a refcount to it; or
* -ENODEV if there is no such phy. The caller is responsible for
* calling usb_put_phy() to release that count.
*
* For use by USB host and peripheral drivers.
*/
struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index)
{
struct usb_phy *phy = NULL;
unsigned long flags;
spin_lock_irqsave(&phy_lock, flags);
phy = __usb_find_phy_dev(dev, &phy_bind_list, index);
if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) {
dev_dbg(dev, "unable to find transceiver\n");
if (!IS_ERR(phy))
phy = ERR_PTR(-ENODEV);
goto err0;
}
get_device(phy->dev);
err0:
spin_unlock_irqrestore(&phy_lock, flags);
return phy;
}
EXPORT_SYMBOL_GPL(usb_get_phy_dev);
/**
* devm_usb_get_phy_dev - find the USB PHY using device ptr and index
* @dev - device that requests this phy
* @index - the index of the phy
*
* Gets the phy using usb_get_phy_dev(), and associates a device with it using
* devres. On driver detach, release function is invoked on the devres data,
* then, devres data is freed.
*
* For use by USB host and peripheral drivers.
*/
struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index)
{
struct usb_phy **ptr, *phy;
ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return NULL;
phy = usb_get_phy_dev(dev, index);
if (!IS_ERR(phy)) {
*ptr = phy;
devres_add(dev, ptr);
} else
devres_free(ptr);
return phy;
}
EXPORT_SYMBOL_GPL(devm_usb_get_phy_dev);
/**
* devm_usb_put_phy - release the USB PHY
* @dev - device that wants to release this phy
* @phy - the phy returned by devm_usb_get_phy()
*
* destroys the devres associated with this phy and invokes usb_put_phy
* to release the phy.
*
* For use by USB host and peripheral drivers.
*/
void devm_usb_put_phy(struct device *dev, struct usb_phy *phy)
{
int r;
r = devres_destroy(dev, devm_usb_phy_release, devm_usb_phy_match, phy);
dev_WARN_ONCE(dev, r, "couldn't find PHY resource\n");
}
EXPORT_SYMBOL_GPL(devm_usb_put_phy);
/**
* usb_put_phy - release the USB PHY
* @x: the phy returned by usb_get_phy()
*
* Releases a refcount the caller received from usb_get_phy().
*
* For use by USB host and peripheral drivers.
*/
void usb_put_phy(struct usb_phy *x)
{
if (x) {
struct module *owner = x->dev->driver->owner;
put_device(x->dev);
module_put(owner);
}
}
EXPORT_SYMBOL_GPL(usb_put_phy);
/**
* usb_add_phy - declare the USB PHY
* @x: the USB phy to be used; or NULL
* @type - the type of this PHY
*
* This call is exclusively for use by phy drivers, which
* coordinate the activities of drivers for host and peripheral
* controllers, and in some cases for VBUS current regulation.
*/
int usb_add_phy(struct usb_phy *x, enum usb_phy_type type)
{
int ret = 0;
unsigned long flags;
struct usb_phy *phy;
if (x->type != USB_PHY_TYPE_UNDEFINED) {
dev_err(x->dev, "not accepting initialized PHY %s\n", x->label);
return -EINVAL;
}
ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier);
spin_lock_irqsave(&phy_lock, flags);
list_for_each_entry(phy, &phy_list, head) {
if (phy->type == type) {
ret = -EBUSY;
dev_err(x->dev, "transceiver type %s already exists\n",
usb_phy_type_string(type));
goto out;
}
}
x->type = type;
list_add_tail(&x->head, &phy_list);
out:
spin_unlock_irqrestore(&phy_lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(usb_add_phy);
/**
* usb_add_phy_dev - declare the USB PHY
* @x: the USB phy to be used; or NULL
*
* This call is exclusively for use by phy drivers, which
* coordinate the activities of drivers for host and peripheral
* controllers, and in some cases for VBUS current regulation.
*/
int usb_add_phy_dev(struct usb_phy *x)
{
struct usb_phy_bind *phy_bind;
unsigned long flags;
if (!x->dev) {
dev_err(x->dev, "no device provided for PHY\n");
return -EINVAL;
}
ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier);
spin_lock_irqsave(&phy_lock, flags);
list_for_each_entry(phy_bind, &phy_bind_list, list)
if (!(strcmp(phy_bind->phy_dev_name, dev_name(x->dev))))
phy_bind->phy = x;
list_add_tail(&x->head, &phy_list);
spin_unlock_irqrestore(&phy_lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(usb_add_phy_dev);
/**
* usb_remove_phy - remove the OTG PHY
* @x: the USB OTG PHY to be removed;
*
* This reverts the effects of usb_add_phy
*/
void usb_remove_phy(struct usb_phy *x)
{
unsigned long flags;
struct usb_phy_bind *phy_bind;
spin_lock_irqsave(&phy_lock, flags);
if (x) {
list_for_each_entry(phy_bind, &phy_bind_list, list)
if (phy_bind->phy == x)
phy_bind->phy = NULL;
list_del(&x->head);
}
spin_unlock_irqrestore(&phy_lock, flags);
}
EXPORT_SYMBOL_GPL(usb_remove_phy);
/**
* usb_bind_phy - bind the phy and the controller that uses the phy
* @dev_name: the device name of the device that will bind to the phy
* @index: index to specify the port number
* @phy_dev_name: the device name of the phy
*
* Fills the phy_bind structure with the dev_name and phy_dev_name. This will
* be used when the phy driver registers the phy and when the controller
* requests this phy.
*
* To be used by platform specific initialization code.
*/
int usb_bind_phy(const char *dev_name, u8 index,
const char *phy_dev_name)
{
struct usb_phy_bind *phy_bind;
unsigned long flags;
phy_bind = kzalloc(sizeof(*phy_bind), GFP_KERNEL);
if (!phy_bind)
return -ENOMEM;
phy_bind->dev_name = dev_name;
phy_bind->phy_dev_name = phy_dev_name;
phy_bind->index = index;
spin_lock_irqsave(&phy_lock, flags);
list_add_tail(&phy_bind->list, &phy_bind_list);
spin_unlock_irqrestore(&phy_lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(usb_bind_phy);