diff options
Diffstat (limited to 'drivers/usb/phy')
-rw-r--r-- | drivers/usb/phy/Kconfig | 26 | ||||
-rw-r--r-- | drivers/usb/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/phy/of.c | 47 | ||||
-rw-r--r-- | drivers/usb/phy/phy-ab8500-usb.c | 846 | ||||
-rw-r--r-- | drivers/usb/phy/phy-nop.c | 2 | ||||
-rw-r--r-- | drivers/usb/phy/phy-omap-usb3.c | 7 | ||||
-rw-r--r-- | drivers/usb/phy/phy-rcar-usb.c | 128 | ||||
-rw-r--r-- | drivers/usb/phy/phy-samsung-usb.c | 159 | ||||
-rw-r--r-- | drivers/usb/phy/phy-samsung-usb.h | 24 | ||||
-rw-r--r-- | drivers/usb/phy/phy-samsung-usb2.c | 62 | ||||
-rw-r--r-- | drivers/usb/phy/phy-samsung-usb3.c | 23 | ||||
-rw-r--r-- | drivers/usb/phy/phy-tegra-usb.c | 354 | ||||
-rw-r--r-- | drivers/usb/phy/phy-ulpi-viewport.c | 2 |
13 files changed, 1279 insertions, 402 deletions
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 7ef3eb8617a..3622fff8b79 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -4,11 +4,17 @@ menuconfig USB_PHY bool "USB Physical Layer drivers" help - USB controllers (those which are host, device or DRD) need a - device to handle the physical layer signalling, commonly called - a PHY. + Most USB controllers have the physical layer signalling part + (commonly called a PHY) built in. However, dual-role devices + (a.k.a. USB on-the-go) which support being USB master or slave + with the same connector often use an external PHY. - The following drivers add support for such PHY devices. + The drivers in this submenu add support for such PHY devices. + They are not needed for standard master-only (or the vast + majority of slave-only) USB interfaces. + + If you're not sure if this applies to you, it probably doesn't; + say N here. if USB_PHY @@ -86,7 +92,7 @@ config OMAP_USB3 on/off the PHY. config SAMSUNG_USBPHY - tristate "Samsung USB PHY Driver" + 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 @@ -180,15 +186,15 @@ config USB_MXS_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" + tristate "Renesas R-Car USB PHY support" depends on USB || USB_GADGET help - Say Y here to add support for the Renesas R-Car USB phy driver. - This chip is typically used as USB phy for USB host, gadget. - This driver supports: R8A7779 + 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 rcar-phy. + module will be called phy-rcar-usb. config USB_ULPI bool "Generic ULPI Transceiver Driver" diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index a9169cb1e6f..070eca3af18 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -5,6 +5,7 @@ ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG obj-$(CONFIG_USB_PHY) += phy.o +obj-$(CONFIG_OF) += of.o # transceiver drivers, keep the list sorted diff --git a/drivers/usb/phy/of.c b/drivers/usb/phy/of.c new file mode 100644 index 00000000000..7ea0154da9d --- /dev/null +++ b/drivers/usb/phy/of.c @@ -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); diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c index e5eb1b5a04e..087402350b6 100644 --- a/drivers/usb/phy/phy-ab8500-usb.c +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -1,10 +1,12 @@ /* * drivers/usb/otg/ab8500_usb.c * - * USB transceiver driver for AB8500 chip + * USB transceiver driver for AB8500 family chips * - * Copyright (C) 2010 ST-Ericsson AB + * Copyright (C) 2010-2013 ST-Ericsson AB * Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com> + * Avinash Kumar <avinash.kumar@stericsson.com> + * Thirupathi Chippakurthy <thirupathi.chippakurthy@stericsson.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +31,8 @@ #include <linux/notifier.h> #include <linux/interrupt.h> #include <linux/delay.h> +#include <linux/clk.h> +#include <linux/err.h> #include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> #include <linux/usb/musb-ux500.h> @@ -41,21 +45,34 @@ /* Bank AB8500_USB */ #define AB8500_USB_LINE_STAT_REG 0x80 #define AB8505_USB_LINE_STAT_REG 0x94 +#define AB8540_USB_LINK_STAT_REG 0x94 +#define AB9540_USB_LINK_STAT_REG 0x94 +#define AB8540_USB_OTG_CTL_REG 0x87 #define AB8500_USB_PHY_CTRL_REG 0x8A +#define AB8540_VBUS_CTRL_REG 0x82 /* Bank AB8500_DEVELOPMENT */ #define AB8500_BANK12_ACCESS 0x00 /* Bank AB8500_DEBUG */ +#define AB8540_DEBUG 0x32 #define AB8500_USB_PHY_TUNE1 0x05 #define AB8500_USB_PHY_TUNE2 0x06 #define AB8500_USB_PHY_TUNE3 0x07 +/* Bank AB8500_INTERRUPT */ +#define AB8500_IT_SOURCE2_REG 0x01 + #define AB8500_BIT_OTG_STAT_ID (1 << 0) #define AB8500_BIT_PHY_CTRL_HOST_EN (1 << 0) #define AB8500_BIT_PHY_CTRL_DEVICE_EN (1 << 1) #define AB8500_BIT_WD_CTRL_ENABLE (1 << 0) #define AB8500_BIT_WD_CTRL_KICK (1 << 1) +#define AB8500_BIT_SOURCE2_VBUSDET (1 << 7) +#define AB8540_BIT_OTG_CTL_VBUS_VALID_ENA (1 << 0) +#define AB8540_BIT_OTG_CTL_ID_HOST_ENA (1 << 1) +#define AB8540_BIT_OTG_CTL_ID_DEV_ENA (1 << 5) +#define AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA (1 << 0) #define AB8500_WD_KICK_DELAY_US 100 /* usec */ #define AB8500_WD_V11_DISABLE_DELAY_US 100 /* usec */ @@ -112,6 +129,68 @@ enum ab8505_usb_link_status { USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8505, }; +enum ab8540_usb_link_status { + USB_LINK_NOT_CONFIGURED_8540 = 0, + USB_LINK_STD_HOST_NC_8540, + USB_LINK_STD_HOST_C_NS_8540, + USB_LINK_STD_HOST_C_S_8540, + USB_LINK_CDP_8540, + USB_LINK_RESERVED0_8540, + USB_LINK_RESERVED1_8540, + USB_LINK_DEDICATED_CHG_8540, + USB_LINK_ACA_RID_A_8540, + USB_LINK_ACA_RID_B_8540, + USB_LINK_ACA_RID_C_NM_8540, + USB_LINK_RESERVED2_8540, + USB_LINK_RESERVED3_8540, + USB_LINK_HM_IDGND_8540, + USB_LINK_CHARGERPORT_NOT_OK_8540, + USB_LINK_CHARGER_DM_HIGH_8540, + USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8540, + USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_8540, + USB_LINK_STD_UPSTREAM_8540, + USB_LINK_CHARGER_SE1_8540, + USB_LINK_CARKIT_CHGR_1_8540, + USB_LINK_CARKIT_CHGR_2_8540, + USB_LINK_ACA_DOCK_CHGR_8540, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_8540, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_8540, + USB_LINK_SAMSUNG_UART_CBL_PHY_EN_8540, + USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_8540, + USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8540 +}; + +enum ab9540_usb_link_status { + USB_LINK_NOT_CONFIGURED_9540 = 0, + USB_LINK_STD_HOST_NC_9540, + USB_LINK_STD_HOST_C_NS_9540, + USB_LINK_STD_HOST_C_S_9540, + USB_LINK_CDP_9540, + USB_LINK_RESERVED0_9540, + USB_LINK_RESERVED1_9540, + USB_LINK_DEDICATED_CHG_9540, + USB_LINK_ACA_RID_A_9540, + USB_LINK_ACA_RID_B_9540, + USB_LINK_ACA_RID_C_NM_9540, + USB_LINK_RESERVED2_9540, + USB_LINK_RESERVED3_9540, + USB_LINK_HM_IDGND_9540, + USB_LINK_CHARGERPORT_NOT_OK_9540, + USB_LINK_CHARGER_DM_HIGH_9540, + USB_LINK_PHYEN_NO_VBUS_NO_IDGND_9540, + USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_9540, + USB_LINK_STD_UPSTREAM_9540, + USB_LINK_CHARGER_SE1_9540, + USB_LINK_CARKIT_CHGR_1_9540, + USB_LINK_CARKIT_CHGR_2_9540, + USB_LINK_ACA_DOCK_CHGR_9540, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_9540, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_9540, + USB_LINK_SAMSUNG_UART_CBL_PHY_EN_9540, + USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_9540, + USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_9540 +}; + enum ab8500_usb_mode { USB_IDLE = 0, USB_PERIPHERAL, @@ -119,13 +198,30 @@ enum ab8500_usb_mode { USB_DEDICATED_CHG }; +/* Register USB_LINK_STATUS interrupt */ +#define AB8500_USB_FLAG_USE_LINK_STATUS_IRQ (1 << 0) +/* Register ID_WAKEUP_F interrupt */ +#define AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ (1 << 1) +/* Register VBUS_DET_F interrupt */ +#define AB8500_USB_FLAG_USE_VBUS_DET_IRQ (1 << 2) +/* Driver is using the ab-iddet driver*/ +#define AB8500_USB_FLAG_USE_AB_IDDET (1 << 3) +/* Enable setting regulators voltage */ +#define AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE (1 << 4) +/* Enable the check_vbus_status workaround */ +#define AB8500_USB_FLAG_USE_CHECK_VBUS_STATUS (1 << 5) +/* Enable the vbus host workaround */ +#define AB8500_USB_FLAG_USE_VBUS_HOST_QUIRK (1 << 6) + struct ab8500_usb { struct usb_phy phy; struct device *dev; struct ab8500 *ab8500; unsigned vbus_draw; struct work_struct phy_dis_work; + struct work_struct vbus_event_work; enum ab8500_usb_mode mode; + struct clk *sysclk; struct regulator *v_ape; struct regulator *v_musb; struct regulator *v_ulpi; @@ -133,6 +229,8 @@ struct ab8500_usb { int previous_link_status_state; struct pinctrl *pinctrl; struct pinctrl_state *pins_sleep; + bool enabled_charging_detection; + unsigned int flags; }; static inline struct ab8500_usb *phy_to_ab(struct usb_phy *x) @@ -171,7 +269,7 @@ static void ab8500_usb_regulator_enable(struct ab8500_usb *ab) if (ret) dev_err(ab->dev, "Failed to enable v-ape\n"); - if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + if (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) { ab->saved_v_ulpi = regulator_get_voltage(ab->v_ulpi); if (ab->saved_v_ulpi < 0) dev_err(ab->dev, "Failed to get v_ulpi voltage\n"); @@ -191,7 +289,7 @@ static void ab8500_usb_regulator_enable(struct ab8500_usb *ab) if (ret) dev_err(ab->dev, "Failed to enable vddulpivio18\n"); - if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + if (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) { volt = regulator_get_voltage(ab->v_ulpi); if ((volt != 1300000) && (volt != 1350000)) dev_err(ab->dev, "Vintcore is not set to 1.3V volt=%d\n", @@ -212,7 +310,7 @@ static void ab8500_usb_regulator_disable(struct ab8500_usb *ab) regulator_disable(ab->v_ulpi); /* USB is not the only consumer of Vintcore, restore old settings */ - if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + if (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) { if (ab->saved_v_ulpi > 0) { ret = regulator_set_voltage(ab->v_ulpi, ab->saved_v_ulpi, ab->saved_v_ulpi); @@ -252,11 +350,23 @@ static void ab8500_usb_phy_enable(struct ab8500_usb *ab, bool sel_host) if (IS_ERR(ab->pinctrl)) dev_err(ab->dev, "could not get/set default pinstate\n"); + if (clk_prepare_enable(ab->sysclk)) + dev_err(ab->dev, "can't prepare/enable clock\n"); + ab8500_usb_regulator_enable(ab); abx500_mask_and_set_register_interruptible(ab->dev, AB8500_USB, AB8500_USB_PHY_CTRL_REG, bit, bit); + + if (ab->flags & AB8500_USB_FLAG_USE_VBUS_HOST_QUIRK) { + if (sel_host) + abx500_set_register_interruptible(ab->dev, + AB8500_USB, AB8540_USB_OTG_CTL_REG, + AB8540_BIT_OTG_CTL_VBUS_VALID_ENA | + AB8540_BIT_OTG_CTL_ID_HOST_ENA | + AB8540_BIT_OTG_CTL_ID_DEV_ENA); + } } static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) @@ -274,6 +384,8 @@ static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) /* Needed to disable the phy.*/ ab8500_usb_wd_workaround(ab); + clk_disable_unprepare(ab->sysclk); + ab8500_usb_regulator_disable(ab); if (!IS_ERR(ab->pinctrl)) { @@ -286,7 +398,8 @@ static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) else if (pinctrl_select_state(ab->pinctrl, ab->pins_sleep)) dev_err(ab->dev, "could not set pins to sleep state\n"); - /* as USB pins are shared with idddet, release them to allow + /* + * as USB pins are shared with iddet, release them to allow * iddet to request them */ pinctrl_put(ab->pinctrl); @@ -298,6 +411,254 @@ static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) #define ab8500_usb_peri_phy_en(ab) ab8500_usb_phy_enable(ab, false) #define ab8500_usb_peri_phy_dis(ab) ab8500_usb_phy_disable(ab, false) +static int ab9540_usb_link_status_update(struct ab8500_usb *ab, + enum ab9540_usb_link_status lsts) +{ + enum ux500_musb_vbus_id_status event = 0; + + dev_dbg(ab->dev, "ab9540_usb_link_status_update %d\n", lsts); + + if (ab->previous_link_status_state == USB_LINK_HM_IDGND_9540 && + (lsts == USB_LINK_STD_HOST_C_NS_9540 || + lsts == USB_LINK_STD_HOST_NC_9540)) + return 0; + + if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_9540 && + (lsts == USB_LINK_STD_HOST_NC_9540)) + return 0; + + ab->previous_link_status_state = lsts; + + switch (lsts) { + case USB_LINK_ACA_RID_B_9540: + event = UX500_MUSB_RIDB; + case USB_LINK_NOT_CONFIGURED_9540: + case USB_LINK_RESERVED0_9540: + case USB_LINK_RESERVED1_9540: + case USB_LINK_RESERVED2_9540: + case USB_LINK_RESERVED3_9540: + if (ab->mode == USB_PERIPHERAL) + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_CLEAN, &ab->vbus_draw); + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + if (event != UX500_MUSB_RIDB) + event = UX500_MUSB_NONE; + /* Fallback to default B_IDLE as nothing is connected. */ + ab->phy.state = OTG_STATE_B_IDLE; + break; + + case USB_LINK_ACA_RID_C_NM_9540: + event = UX500_MUSB_RIDC; + case USB_LINK_STD_HOST_NC_9540: + case USB_LINK_STD_HOST_C_NS_9540: + case USB_LINK_STD_HOST_C_S_9540: + case USB_LINK_CDP_9540: + if (ab->mode == USB_HOST) { + ab->mode = USB_PERIPHERAL; + ab8500_usb_host_phy_dis(ab); + ab8500_usb_peri_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (ab->mode == USB_IDLE) { + ab->mode = USB_PERIPHERAL; + ab8500_usb_peri_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (event != UX500_MUSB_RIDC) + event = UX500_MUSB_VBUS; + break; + + case USB_LINK_ACA_RID_A_9540: + event = UX500_MUSB_RIDA; + case USB_LINK_HM_IDGND_9540: + case USB_LINK_STD_UPSTREAM_9540: + if (ab->mode == USB_PERIPHERAL) { + ab->mode = USB_HOST; + ab8500_usb_peri_phy_dis(ab); + ab8500_usb_host_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (ab->mode == USB_IDLE) { + ab->mode = USB_HOST; + ab8500_usb_host_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + ab->phy.otg->default_a = true; + if (event != UX500_MUSB_RIDA) + event = UX500_MUSB_ID; + + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_DEDICATED_CHG_9540: + ab->mode = USB_DEDICATED_CHG; + event = UX500_MUSB_CHARGER; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_PHYEN_NO_VBUS_NO_IDGND_9540: + case USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_9540: + if (!(is_ab9540_2p0_or_earlier(ab->ab8500))) { + event = UX500_MUSB_NONE; + if (ab->mode == USB_HOST) { + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_host_phy_dis(ab); + ab->mode = USB_IDLE; + } + if (ab->mode == USB_PERIPHERAL) { + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_peri_phy_dis(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_CLEAN, + &ab->vbus_draw); + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + } + } + break; + + default: + break; + } + + return 0; +} + +static int ab8540_usb_link_status_update(struct ab8500_usb *ab, + enum ab8540_usb_link_status lsts) +{ + enum ux500_musb_vbus_id_status event = 0; + + dev_dbg(ab->dev, "ab8540_usb_link_status_update %d\n", lsts); + + if (ab->enabled_charging_detection) { + /* Disable USB Charger detection */ + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8540_VBUS_CTRL_REG, + AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA, 0x00); + ab->enabled_charging_detection = false; + } + + /* + * Spurious link_status interrupts are seen in case of a + * disconnection of a device in IDGND and RIDA stage + */ + if (ab->previous_link_status_state == USB_LINK_HM_IDGND_8540 && + (lsts == USB_LINK_STD_HOST_C_NS_8540 || + lsts == USB_LINK_STD_HOST_NC_8540)) + return 0; + + if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8540 && + (lsts == USB_LINK_STD_HOST_NC_8540)) + return 0; + + ab->previous_link_status_state = lsts; + + switch (lsts) { + case USB_LINK_ACA_RID_B_8540: + event = UX500_MUSB_RIDB; + case USB_LINK_NOT_CONFIGURED_8540: + case USB_LINK_RESERVED0_8540: + case USB_LINK_RESERVED1_8540: + case USB_LINK_RESERVED2_8540: + case USB_LINK_RESERVED3_8540: + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + if (event != UX500_MUSB_RIDB) + event = UX500_MUSB_NONE; + /* + * Fallback to default B_IDLE as nothing + * is connected + */ + ab->phy.state = OTG_STATE_B_IDLE; + break; + + case USB_LINK_ACA_RID_C_NM_8540: + event = UX500_MUSB_RIDC; + case USB_LINK_STD_HOST_NC_8540: + case USB_LINK_STD_HOST_C_NS_8540: + case USB_LINK_STD_HOST_C_S_8540: + case USB_LINK_CDP_8540: + if (ab->mode == USB_IDLE) { + ab->mode = USB_PERIPHERAL; + ab8500_usb_peri_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (event != UX500_MUSB_RIDC) + event = UX500_MUSB_VBUS; + break; + + case USB_LINK_ACA_RID_A_8540: + case USB_LINK_ACA_DOCK_CHGR_8540: + event = UX500_MUSB_RIDA; + case USB_LINK_HM_IDGND_8540: + case USB_LINK_STD_UPSTREAM_8540: + if (ab->mode == USB_IDLE) { + ab->mode = USB_HOST; + ab8500_usb_host_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + ab->phy.otg->default_a = true; + if (event != UX500_MUSB_RIDA) + event = UX500_MUSB_ID; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_DEDICATED_CHG_8540: + ab->mode = USB_DEDICATED_CHG; + event = UX500_MUSB_CHARGER; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8540: + case USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_8540: + event = UX500_MUSB_NONE; + if (ab->mode == USB_HOST) { + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_host_phy_dis(ab); + ab->mode = USB_IDLE; + } + if (ab->mode == USB_PERIPHERAL) { + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_peri_phy_dis(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_CLEAN, &ab->vbus_draw); + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + } + break; + + default: + event = UX500_MUSB_NONE; + break; + } + + return 0; +} + static int ab8505_usb_link_status_update(struct ab8500_usb *ab, enum ab8505_usb_link_status lsts) { @@ -498,6 +859,20 @@ static int abx500_usb_link_status_update(struct ab8500_usb *ab) AB8500_USB, AB8505_USB_LINE_STAT_REG, ®); lsts = (reg >> 3) & 0x1F; ret = ab8505_usb_link_status_update(ab, lsts); + } else if (is_ab8540(ab->ab8500)) { + enum ab8540_usb_link_status lsts; + + abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB8540_USB_LINK_STAT_REG, ®); + lsts = (reg >> 3) & 0xFF; + ret = ab8540_usb_link_status_update(ab, lsts); + } else if (is_ab9540(ab->ab8500)) { + enum ab9540_usb_link_status lsts; + + abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB9540_USB_LINK_STAT_REG, ®); + lsts = (reg >> 3) & 0xFF; + ret = ab9540_usb_link_status_update(ab, lsts); } return ret; @@ -553,7 +928,7 @@ static irqreturn_t ab8500_usb_disconnect_irq(int irq, void *data) static irqreturn_t ab8500_usb_link_status_irq(int irq, void *data) { - struct ab8500_usb *ab = (struct ab8500_usb *) data; + struct ab8500_usb *ab = (struct ab8500_usb *)data; abx500_usb_link_status_update(ab); @@ -572,6 +947,69 @@ static void ab8500_usb_phy_disable_work(struct work_struct *work) ab8500_usb_peri_phy_dis(ab); } +/* Check if VBUS is set and linkstatus has not detected a cable. */ +static bool ab8500_usb_check_vbus_status(struct ab8500_usb *ab) +{ + u8 isource2; + u8 reg; + enum ab8540_usb_link_status lsts; + + abx500_get_register_interruptible(ab->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE2_REG, + &isource2); + + /* If Vbus is below 3.6V abort */ + if (!(isource2 & AB8500_BIT_SOURCE2_VBUSDET)) + return false; + + abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB8540_USB_LINK_STAT_REG, + ®); + + lsts = (reg >> 3) & 0xFF; + + /* Check if linkstatus has detected a cable */ + if (lsts) + return false; + + return true; +} + +/* re-trigger charger detection again with watchdog re-kick. */ +static void ab8500_usb_vbus_turn_on_event_work(struct work_struct *work) +{ + struct ab8500_usb *ab = container_of(work, struct ab8500_usb, + vbus_event_work); + + if (ab->mode != USB_IDLE) + return; + + abx500_set_register_interruptible(ab->dev, + AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, + AB8500_BIT_WD_CTRL_ENABLE); + + udelay(100); + + abx500_set_register_interruptible(ab->dev, + AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, + AB8500_BIT_WD_CTRL_ENABLE | AB8500_BIT_WD_CTRL_KICK); + + udelay(100); + + /* Disable Main watchdog */ + abx500_set_register_interruptible(ab->dev, + AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, + 0x0); + + /* Enable USB Charger detection */ + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8540_VBUS_CTRL_REG, + AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA, + AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA); + + ab->enabled_charging_detection = true; +} + static unsigned ab8500_eyediagram_workaroud(struct ab8500_usb *ab, unsigned mA) { /* @@ -627,7 +1065,7 @@ static int ab8500_usb_set_peripheral(struct usb_otg *otg, * is fixed. */ - if ((ab->mode != USB_IDLE) && (!gadget)) { + if ((ab->mode != USB_IDLE) && !gadget) { ab->mode = USB_IDLE; schedule_work(&ab->phy_dis_work); } @@ -651,7 +1089,7 @@ static int ab8500_usb_set_host(struct usb_otg *otg, struct usb_bus *host) * is fixed. */ - if ((ab->mode != USB_IDLE) && (!host)) { + if ((ab->mode != USB_IDLE) && !host) { ab->mode = USB_IDLE; schedule_work(&ab->phy_dis_work); } @@ -659,6 +1097,33 @@ static int ab8500_usb_set_host(struct usb_otg *otg, struct usb_bus *host) return 0; } +static void ab8500_usb_restart_phy(struct ab8500_usb *ab) +{ + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_DEVICE_EN, + AB8500_BIT_PHY_CTRL_DEVICE_EN); + + udelay(100); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_DEVICE_EN, + 0); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_HOST_EN, + AB8500_BIT_PHY_CTRL_HOST_EN); + + udelay(100); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_HOST_EN, + 0); +} + static int ab8500_usb_regulator_get(struct ab8500_usb *ab) { int err; @@ -693,48 +1158,197 @@ static int ab8500_usb_irq_setup(struct platform_device *pdev, int err; int irq; - irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS"); - if (irq < 0) { - dev_err(&pdev->dev, "Link status irq not found\n"); - return irq; - } - err = devm_request_threaded_irq(&pdev->dev, irq, NULL, - ab8500_usb_link_status_irq, - IRQF_NO_SUSPEND | IRQF_SHARED, "usb-link-status", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for link status irq\n"); - return err; + if (ab->flags & AB8500_USB_FLAG_USE_LINK_STATUS_IRQ) { + irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS"); + if (irq < 0) { + dev_err(&pdev->dev, "Link status irq not found\n"); + return irq; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_link_status_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-link-status", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for link status irq\n"); + return err; + } } - irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); - if (irq < 0) { - dev_err(&pdev->dev, "ID fall irq not found\n"); - return irq; - } - err = devm_request_threaded_irq(&pdev->dev, irq, NULL, - ab8500_usb_disconnect_irq, - IRQF_NO_SUSPEND | IRQF_SHARED, "usb-id-fall", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for ID fall irq\n"); - return err; + if (ab->flags & AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ) { + irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); + if (irq < 0) { + dev_err(&pdev->dev, "ID fall irq not found\n"); + return irq; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-id-fall", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for ID fall irq\n"); + return err; + } } - irq = platform_get_irq_byname(pdev, "VBUS_DET_F"); - if (irq < 0) { - dev_err(&pdev->dev, "VBUS fall irq not found\n"); - return irq; - } - err = devm_request_threaded_irq(&pdev->dev, irq, NULL, - ab8500_usb_disconnect_irq, - IRQF_NO_SUSPEND | IRQF_SHARED, "usb-vbus-fall", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for Vbus fall irq\n"); - return err; + if (ab->flags & AB8500_USB_FLAG_USE_VBUS_DET_IRQ) { + irq = platform_get_irq_byname(pdev, "VBUS_DET_F"); + if (irq < 0) { + dev_err(&pdev->dev, "VBUS fall irq not found\n"); + return irq; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-vbus-fall", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for Vbus fall irq\n"); + return err; + } } return 0; } +static void ab8500_usb_set_ab8500_tuning_values(struct ab8500_usb *ab) +{ + int err; + + /* Enable the PBT/Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01); + if (err < 0) + dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x78); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", + err); + + /* Switch to normal mode/disable Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", + err); +} + +static void ab8500_usb_set_ab8505_tuning_values(struct ab8500_usb *ab) +{ + int err; + + /* Enable the PBT/Bank 0x12 access */ + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, + 0x01, 0x01); + if (err < 0) + dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", + err); + + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE1, + 0xC8, 0xC8); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", + err); + + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE2, + 0x60, 0x60); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", + err); + + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE3, + 0xFC, 0x80); + + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", + err); + + /* Switch to normal mode/disable Bank 0x12 access */ + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, + 0x00, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", + err); +} + +static void ab8500_usb_set_ab8540_tuning_values(struct ab8500_usb *ab) +{ + int err; + + err = abx500_set_register_interruptible(ab->dev, + AB8540_DEBUG, AB8500_USB_PHY_TUNE1, 0xCC); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register ret=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8540_DEBUG, AB8500_USB_PHY_TUNE2, 0x60); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register ret=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8540_DEBUG, AB8500_USB_PHY_TUNE3, 0x90); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester ret=%d\n", + err); +} + +static void ab8500_usb_set_ab9540_tuning_values(struct ab8500_usb *ab) +{ + int err; + + /* Enable the PBT/Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01); + if (err < 0) + dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x60); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x80); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", + err); + + /* Switch to normal mode/disable Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", + err); +} + static int ab8500_usb_probe(struct platform_device *pdev) { struct ab8500_usb *ab; @@ -772,6 +1386,33 @@ static int ab8500_usb_probe(struct platform_device *pdev) otg->set_host = ab8500_usb_set_host; otg->set_peripheral = ab8500_usb_set_peripheral; + if (is_ab8500(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | + AB8500_USB_FLAG_USE_VBUS_DET_IRQ | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + } else if (is_ab8505(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | + AB8500_USB_FLAG_USE_VBUS_DET_IRQ | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + } else if (is_ab8540(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_USE_CHECK_VBUS_STATUS | + AB8500_USB_FLAG_USE_VBUS_HOST_QUIRK | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + } else if (is_ab9540(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + if (is_ab9540_2p0_or_earlier(ab->ab8500)) + ab->flags |= AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | + AB8500_USB_FLAG_USE_VBUS_DET_IRQ; + } + + /* Disable regulator voltage setting for AB8500 <= v2.0 */ + if (is_ab8500_2p0_or_earlier(ab->ab8500)) + ab->flags &= ~AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + platform_set_drvdata(pdev, ab); ATOMIC_INIT_NOTIFIER_HEAD(&ab->phy.notifier); @@ -779,10 +1420,18 @@ static int ab8500_usb_probe(struct platform_device *pdev) /* all: Disable phy when called from set_host and set_peripheral */ INIT_WORK(&ab->phy_dis_work, ab8500_usb_phy_disable_work); + INIT_WORK(&ab->vbus_event_work, ab8500_usb_vbus_turn_on_event_work); + err = ab8500_usb_regulator_get(ab); if (err) return err; + ab->sysclk = devm_clk_get(ab->dev, "sysclk"); + if (IS_ERR(ab->sysclk)) { + dev_err(ab->dev, "Could not get sysclk.\n"); + return PTR_ERR(ab->sysclk); + } + err = ab8500_usb_irq_setup(pdev, ab); if (err < 0) return err; @@ -793,85 +1442,33 @@ static int ab8500_usb_probe(struct platform_device *pdev) return err; } - /* Phy tuning values for AB8500 */ - if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { - /* Enable the PBT/Bank 0x12 access */ - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01); - if (err < 0) - dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x00); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x78); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", - err); - - /* Switch to normal mode/disable Bank 0x12 access */ - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00); - if (err < 0) - dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", - err); - } - - /* Phy tuning values for AB8505 */ - if (is_ab8505(ab->ab8500)) { - /* Enable the PBT/Bank 0x12 access */ - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, - 0x01, 0x01); - if (err < 0) - dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", - err); - - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE1, - 0xC8, 0xC8); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", - err); - - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE2, - 0x60, 0x60); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", - err); - - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE3, - 0xFC, 0x80); - - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", - err); - - /* Switch to normal mode/disable Bank 0x12 access */ - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, - 0x00, 0x00); - if (err < 0) - dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", - err); - } + if (is_ab8500(ab->ab8500) && !is_ab8500_2p0_or_earlier(ab->ab8500)) + /* Phy tuning values for AB8500 > v2.0 */ + ab8500_usb_set_ab8500_tuning_values(ab); + else if (is_ab8505(ab->ab8500)) + /* Phy tuning values for AB8505 */ + ab8500_usb_set_ab8505_tuning_values(ab); + else if (is_ab8540(ab->ab8500)) + /* Phy tuning values for AB8540 */ + ab8500_usb_set_ab8540_tuning_values(ab); + else if (is_ab9540(ab->ab8500)) + /* Phy tuning values for AB9540 */ + ab8500_usb_set_ab9540_tuning_values(ab); /* Needed to enable ID detection. */ ab8500_usb_wd_workaround(ab); + /* + * This is required for usb-link-status to work properly when a + * cable is connected at boot time. + */ + ab8500_usb_restart_phy(ab); + + if (ab->flags & AB8500_USB_FLAG_USE_CHECK_VBUS_STATUS) { + if (ab8500_usb_check_vbus_status(ab)) + schedule_work(&ab->vbus_event_work); + } + abx500_usb_link_status_update(ab); dev_info(&pdev->dev, "revision 0x%2x driver initialized\n", rev); @@ -884,6 +1481,7 @@ static int ab8500_usb_remove(struct platform_device *pdev) struct ab8500_usb *ab = platform_get_drvdata(pdev); cancel_work_sync(&ab->phy_dis_work); + cancel_work_sync(&ab->vbus_event_work); usb_remove_phy(&ab->phy); @@ -895,11 +1493,20 @@ static int ab8500_usb_remove(struct platform_device *pdev) return 0; } +static struct platform_device_id ab8500_usb_devtype[] = { + { .name = "ab8500-usb", }, + { .name = "ab8540-usb", }, + { .name = "ab9540-usb", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, ab8500_usb_devtype); + static struct platform_driver ab8500_usb_driver = { .probe = ab8500_usb_probe, .remove = ab8500_usb_remove, + .id_table = ab8500_usb_devtype, .driver = { - .name = "ab8500-usb", + .name = "abx5x0-usb", .owner = THIS_MODULE, }, }; @@ -916,7 +1523,6 @@ static void __exit ab8500_usb_exit(void) } module_exit(ab8500_usb_exit); -MODULE_ALIAS("platform:ab8500_usb"); MODULE_AUTHOR("ST-Ericsson AB"); -MODULE_DESCRIPTION("AB8500 usb transceiver driver"); +MODULE_DESCRIPTION("AB8500 family usb transceiver driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-nop.c b/drivers/usb/phy/phy-nop.c index 638cc5dade3..55445e5d72e 100644 --- a/drivers/usb/phy/phy-nop.c +++ b/drivers/usb/phy/phy-nop.c @@ -270,7 +270,7 @@ static struct platform_driver nop_usb_xceiv_driver = { .driver = { .name = "nop_usb_xceiv", .owner = THIS_MODULE, - .of_match_table = of_match_ptr(nop_xceiv_dt_ids), + .of_match_table = nop_xceiv_dt_ids, }, }; diff --git a/drivers/usb/phy/phy-omap-usb3.c b/drivers/usb/phy/phy-omap-usb3.c index a6e60b1e102..efe6e1464f4 100644 --- a/drivers/usb/phy/phy-omap-usb3.c +++ b/drivers/usb/phy/phy-omap-usb3.c @@ -27,7 +27,7 @@ #include <linux/delay.h> #include <linux/usb/omap_control_usb.h> -#define NUM_SYS_CLKS 5 +#define NUM_SYS_CLKS 6 #define PLL_STATUS 0x00000004 #define PLL_GO 0x00000008 #define PLL_CONFIGURATION1 0x0000000C @@ -62,6 +62,7 @@ enum sys_clk_rate { CLK_RATE_12MHZ, CLK_RATE_16MHZ, CLK_RATE_19MHZ, + CLK_RATE_20MHZ, CLK_RATE_26MHZ, CLK_RATE_38MHZ }; @@ -72,6 +73,8 @@ static struct usb_dpll_params omap_usb3_dpll_params[NUM_SYS_CLKS] = { {1172, 8, 4, 20, 65537}, /* 19.2 MHz */ {1250, 12, 4, 20, 0}, /* 26 MHz */ {3125, 47, 4, 20, 92843}, /* 38.4 MHz */ + {1000, 7, 4, 10, 0}, /* 20 MHz */ + }; static int omap_usb3_suspend(struct usb_phy *x, int suspend) @@ -122,6 +125,8 @@ static inline enum sys_clk_rate __get_sys_clk_index(unsigned long rate) return CLK_RATE_16MHZ; case 19200000: return CLK_RATE_19MHZ; + case 20000000: + return CLK_RATE_20MHZ; case 26000000: return CLK_RATE_26MHZ; case 38400000: diff --git a/drivers/usb/phy/phy-rcar-usb.c b/drivers/usb/phy/phy-rcar-usb.c index a35681b0c50..ae909408958 100644 --- a/drivers/usb/phy/phy-rcar-usb.c +++ b/drivers/usb/phy/phy-rcar-usb.c @@ -1,8 +1,9 @@ /* * Renesas R-Car USB phy driver * - * Copyright (C) 2012 Renesas Solutions Corp. + * 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 @@ -15,17 +16,41 @@ #include <linux/platform_device.h> #include <linux/spinlock.h> #include <linux/module.h> - -/* USBH common register */ -#define USBPCTRL0 0x0800 -#define USBPCTRL1 0x0804 -#define USBST 0x0808 -#define USBEH0 0x080C -#define USBOH0 0x081C -#define USBCTL0 0x0858 -#define EIIBC1 0x0094 -#define EIIBC2 0x009C - +#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) @@ -58,8 +83,10 @@ 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->platform_data; 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; @@ -77,7 +104,16 @@ static int rcar_usb_phy_init(struct usb_phy *phy) /* (2) start USB-PHY internal PLL */ iowrite32(PHY_ENB | PLL_ENB, (reg0 + USBPCTRL1)); - /* (3) USB module status check */ + /* (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); @@ -90,24 +126,24 @@ static int rcar_usb_phy_init(struct usb_phy *phy) goto phy_init_end; } - /* (4) USB-PHY reset clear */ + /* (5) USB-PHY reset clear */ iowrite32(PHY_ENB | PLL_ENB | PHY_RST, (reg0 + USBPCTRL1)); - /* set platform specific port settings */ - iowrite32(0x00000000, (reg0 + USBPCTRL0)); - - /* - * EHCI IP internal buffer setting - * EHCI IP internal buffer enable - * - * These are recommended value of a datasheet - * see [USB :: EHCI internal buffer setting] - */ - iowrite32(0x00ff0040, (reg0 + EIIBC1)); - iowrite32(0x00ff0040, (reg1 + EIIBC1)); - - iowrite32(0x00000001, (reg0 + EIIBC2)); - iowrite32(0x00000001, (reg1 + EIIBC2)); + /* 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 @@ -134,10 +170,8 @@ static void rcar_usb_phy_shutdown(struct usb_phy *phy) spin_lock_irqsave(&priv->lock, flags); - if (priv->counter-- == 1) { /* last user */ - iowrite32(0x00000000, (reg0 + USBPCTRL0)); + if (priv->counter-- == 1) /* last user */ iowrite32(0x00000000, (reg0 + USBPCTRL1)); - } spin_unlock_irqrestore(&priv->lock, flags); } @@ -147,27 +181,29 @@ 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; + void __iomem *reg0, *reg1 = NULL; int ret; + if (!pdev->dev.platform_data) { + dev_err(dev, "No platform data\n"); + return -EINVAL; + } + res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); - res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (!res0 || !res1) { + if (!res0) { dev_err(dev, "Not enough platform resources\n"); return -EINVAL; } - /* - * CAUTION - * - * Because this phy address is also mapped under OHCI/EHCI address area, - * this driver can't use devm_request_and_ioremap(dev, res) here - */ - reg0 = devm_ioremap_nocache(dev, res0->start, resource_size(res0)); - reg1 = devm_ioremap_nocache(dev, res1->start, resource_size(res1)); - if (!reg0 || !reg1) { - dev_err(dev, "ioremap error\n"); - return -ENOMEM; + 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); diff --git a/drivers/usb/phy/phy-samsung-usb.c b/drivers/usb/phy/phy-samsung-usb.c index 7b118ee5f5e..ac025ca0842 100644 --- a/drivers/usb/phy/phy-samsung-usb.c +++ b/drivers/usb/phy/phy-samsung-usb.c @@ -73,7 +73,7 @@ EXPORT_SYMBOL_GPL(samsung_usbphy_parse_dt); * Here 'on = true' would mean USB PHY block is isolated, hence * de-activated and vice-versa. */ -void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on) +void samsung_usbphy_set_isolation_4210(struct samsung_usbphy *sphy, bool on) { void __iomem *reg = NULL; u32 reg_val; @@ -84,32 +84,12 @@ void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on) return; } - switch (sphy->drv_data->cpu_type) { - case TYPE_S3C64XX: - /* - * Do nothing: We will add here once S3C64xx goes for DT support - */ - break; - case TYPE_EXYNOS4210: - /* - * Fall through since exynos4210 and exynos5250 have similar - * register architecture: two separate registers for host and - * device phy control with enable bit at position 0. - */ - case TYPE_EXYNOS5250: - if (sphy->phy_type == USB_PHY_TYPE_DEVICE) { - reg = sphy->pmuregs + - sphy->drv_data->devphy_reg_offset; - en_mask = sphy->drv_data->devphy_en_mask; - } else if (sphy->phy_type == USB_PHY_TYPE_HOST) { - reg = sphy->pmuregs + - sphy->drv_data->hostphy_reg_offset; - en_mask = sphy->drv_data->hostphy_en_mask; - } - break; - default: - dev_err(sphy->dev, "Invalid SoC type\n"); - return; + if (sphy->phy_type == USB_PHY_TYPE_DEVICE) { + reg = sphy->pmuregs + sphy->drv_data->devphy_reg_offset; + en_mask = sphy->drv_data->devphy_en_mask; + } else if (sphy->phy_type == USB_PHY_TYPE_HOST) { + reg = sphy->pmuregs + sphy->drv_data->hostphy_reg_offset; + en_mask = sphy->drv_data->hostphy_en_mask; } reg_val = readl(reg); @@ -120,8 +100,13 @@ void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on) reg_val |= en_mask; writel(reg_val, reg); + + if (sphy->drv_data->cpu_type == TYPE_EXYNOS4X12) { + writel(reg_val, sphy->pmuregs + EXYNOS4X12_PHY_HSIC_CTRL0); + writel(reg_val, sphy->pmuregs + EXYNOS4X12_PHY_HSIC_CTRL1); + } } -EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation); +EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation_4210); /* * Configure the mode of working of usb-phy here: HOST/DEVICE. @@ -162,73 +147,93 @@ int samsung_usbphy_set_type(struct usb_phy *phy, } EXPORT_SYMBOL_GPL(samsung_usbphy_set_type); +int samsung_usbphy_rate_to_clksel_64xx(struct samsung_usbphy *sphy, + unsigned long rate) +{ + unsigned int clksel; + + switch (rate) { + case 12 * MHZ: + clksel = PHYCLK_CLKSEL_12M; + break; + case 24 * MHZ: + clksel = PHYCLK_CLKSEL_24M; + break; + case 48 * MHZ: + clksel = PHYCLK_CLKSEL_48M; + break; + default: + dev_err(sphy->dev, + "Invalid reference clock frequency: %lu\n", rate); + return -EINVAL; + } + + return clksel; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_rate_to_clksel_64xx); + +int samsung_usbphy_rate_to_clksel_4x12(struct samsung_usbphy *sphy, + unsigned long rate) +{ + unsigned int clksel; + + switch (rate) { + case 9600 * KHZ: + clksel = FSEL_CLKSEL_9600K; + break; + case 10 * MHZ: + clksel = FSEL_CLKSEL_10M; + break; + case 12 * MHZ: + clksel = FSEL_CLKSEL_12M; + break; + case 19200 * KHZ: + clksel = FSEL_CLKSEL_19200K; + break; + case 20 * MHZ: + clksel = FSEL_CLKSEL_20M; + break; + case 24 * MHZ: + clksel = FSEL_CLKSEL_24M; + break; + case 50 * MHZ: + clksel = FSEL_CLKSEL_50M; + break; + default: + dev_err(sphy->dev, + "Invalid reference clock frequency: %lu\n", rate); + return -EINVAL; + } + + return clksel; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_rate_to_clksel_4x12); + /* * Returns reference clock frequency selection value */ int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy) { struct clk *ref_clk; - int refclk_freq = 0; + unsigned long rate; + int refclk_freq; /* * In exynos5250 USB host and device PHY use * external crystal clock XXTI */ if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) - ref_clk = devm_clk_get(sphy->dev, "ext_xtal"); + ref_clk = clk_get(sphy->dev, "ext_xtal"); else - ref_clk = devm_clk_get(sphy->dev, "xusbxti"); + ref_clk = clk_get(sphy->dev, "xusbxti"); if (IS_ERR(ref_clk)) { dev_err(sphy->dev, "Failed to get reference clock\n"); return PTR_ERR(ref_clk); } - if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) { - /* set clock frequency for PLL */ - switch (clk_get_rate(ref_clk)) { - case 9600 * KHZ: - refclk_freq = FSEL_CLKSEL_9600K; - break; - case 10 * MHZ: - refclk_freq = FSEL_CLKSEL_10M; - break; - case 12 * MHZ: - refclk_freq = FSEL_CLKSEL_12M; - break; - case 19200 * KHZ: - refclk_freq = FSEL_CLKSEL_19200K; - break; - case 20 * MHZ: - refclk_freq = FSEL_CLKSEL_20M; - break; - case 50 * MHZ: - refclk_freq = FSEL_CLKSEL_50M; - break; - case 24 * MHZ: - default: - /* default reference clock */ - refclk_freq = FSEL_CLKSEL_24M; - break; - } - } else { - switch (clk_get_rate(ref_clk)) { - case 12 * MHZ: - refclk_freq = PHYCLK_CLKSEL_12M; - break; - case 24 * MHZ: - refclk_freq = PHYCLK_CLKSEL_24M; - break; - case 48 * MHZ: - refclk_freq = PHYCLK_CLKSEL_48M; - break; - default: - if (sphy->drv_data->cpu_type == TYPE_S3C64XX) - refclk_freq = PHYCLK_CLKSEL_48M; - else - refclk_freq = PHYCLK_CLKSEL_24M; - break; - } - } + rate = clk_get_rate(ref_clk); + refclk_freq = sphy->drv_data->rate_to_clksel(sphy, rate); + clk_put(ref_clk); return refclk_freq; diff --git a/drivers/usb/phy/phy-samsung-usb.h b/drivers/usb/phy/phy-samsung-usb.h index 70a9cae5e37..68771bfd182 100644 --- a/drivers/usb/phy/phy-samsung-usb.h +++ b/drivers/usb/phy/phy-samsung-usb.h @@ -47,6 +47,16 @@ #define RSTCON_HLINK_SWRST (0x1 << 1) #define RSTCON_SWRST (0x1 << 0) +/* EXYNOS4X12 */ +#define EXYNOS4X12_PHY_HSIC_CTRL0 (0x04) +#define EXYNOS4X12_PHY_HSIC_CTRL1 (0x08) + +#define PHYPWR_NORMAL_MASK_HSIC1 (0x7 << 12) +#define PHYPWR_NORMAL_MASK_HSIC0 (0x7 << 9) +#define PHYPWR_NORMAL_MASK_PHY1 (0x7 << 6) + +#define RSTCON_HOSTPHY_SWRST (0xf << 3) + /* EXYNOS5 */ #define EXYNOS5_PHY_HOST_CTRL0 (0x00) @@ -241,9 +251,12 @@ enum samsung_cpu_type { TYPE_S3C64XX, TYPE_EXYNOS4210, + TYPE_EXYNOS4X12, TYPE_EXYNOS5250, }; +struct samsung_usbphy; + /* * struct samsung_usbphy_drvdata - driver data for various SoC variants * @cpu_type: machine identifier @@ -268,6 +281,10 @@ struct samsung_usbphy_drvdata { int hostphy_en_mask; u32 devphy_reg_offset; u32 hostphy_reg_offset; + int (*rate_to_clksel)(struct samsung_usbphy *, unsigned long); + void (*set_isolation)(struct samsung_usbphy *, bool); + void (*phy_enable)(struct samsung_usbphy *); + void (*phy_disable)(struct samsung_usbphy *); }; /* @@ -320,8 +337,13 @@ static inline const struct samsung_usbphy_drvdata } extern int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy); -extern void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on); +extern void samsung_usbphy_set_isolation_4210(struct samsung_usbphy *sphy, + bool on); extern void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy); extern int samsung_usbphy_set_type(struct usb_phy *phy, enum samsung_usb_phy_type phy_type); extern int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy); +extern int samsung_usbphy_rate_to_clksel_64xx(struct samsung_usbphy *sphy, + unsigned long rate); +extern int samsung_usbphy_rate_to_clksel_4x12(struct samsung_usbphy *sphy, + unsigned long rate); diff --git a/drivers/usb/phy/phy-samsung-usb2.c b/drivers/usb/phy/phy-samsung-usb2.c index 9d5e273abcc..1011c16ade7 100644 --- a/drivers/usb/phy/phy-samsung-usb2.c +++ b/drivers/usb/phy/phy-samsung-usb2.c @@ -176,6 +176,11 @@ static void samsung_usb2phy_enable(struct samsung_usbphy *sphy) phypwr &= ~PHYPWR_NORMAL_MASK; rstcon |= RSTCON_SWRST; break; + case TYPE_EXYNOS4X12: + phypwr &= ~(PHYPWR_NORMAL_MASK_HSIC0 | + PHYPWR_NORMAL_MASK_HSIC1 | + PHYPWR_NORMAL_MASK_PHY1); + rstcon |= RSTCON_HOSTPHY_SWRST; case TYPE_EXYNOS4210: phypwr &= ~PHYPWR_NORMAL_MASK_PHY0; rstcon |= RSTCON_SWRST; @@ -189,6 +194,8 @@ static void samsung_usb2phy_enable(struct samsung_usbphy *sphy) /* reset all ports of PHY and Link */ writel(rstcon, regs + SAMSUNG_RSTCON); udelay(10); + if (sphy->drv_data->cpu_type == TYPE_EXYNOS4X12) + rstcon &= ~RSTCON_HOSTPHY_SWRST; rstcon &= ~RSTCON_SWRST; writel(rstcon, regs + SAMSUNG_RSTCON); } @@ -239,6 +246,10 @@ static void samsung_usb2phy_disable(struct samsung_usbphy *sphy) case TYPE_S3C64XX: phypwr |= PHYPWR_NORMAL_MASK; break; + case TYPE_EXYNOS4X12: + phypwr |= (PHYPWR_NORMAL_MASK_HSIC0 | + PHYPWR_NORMAL_MASK_HSIC1 | + PHYPWR_NORMAL_MASK_PHY1); case TYPE_EXYNOS4210: phypwr |= PHYPWR_NORMAL_MASK_PHY0; default: @@ -284,17 +295,14 @@ static int samsung_usb2phy_init(struct usb_phy *phy) /* Disable phy isolation */ if (sphy->plat && sphy->plat->pmu_isolation) sphy->plat->pmu_isolation(false); - else - samsung_usbphy_set_isolation(sphy, false); + else if (sphy->drv_data->set_isolation) + sphy->drv_data->set_isolation(sphy, false); /* Selecting Host/OTG mode; After reset USB2.0PHY_CFG: HOST */ samsung_usbphy_cfg_sel(sphy); /* Initialize usb phy registers */ - if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) - samsung_exynos5_usb2phy_enable(sphy); - else - samsung_usb2phy_enable(sphy); + sphy->drv_data->phy_enable(sphy); spin_unlock_irqrestore(&sphy->lock, flags); @@ -334,16 +342,13 @@ static void samsung_usb2phy_shutdown(struct usb_phy *phy) } /* De-initialize usb phy registers */ - if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) - samsung_exynos5_usb2phy_disable(sphy); - else - samsung_usb2phy_disable(sphy); + sphy->drv_data->phy_disable(sphy); /* Enable phy isolation */ if (sphy->plat && sphy->plat->pmu_isolation) sphy->plat->pmu_isolation(true); - else - samsung_usbphy_set_isolation(sphy, true); + else if (sphy->drv_data->set_isolation) + sphy->drv_data->set_isolation(sphy, true); spin_unlock_irqrestore(&sphy->lock, flags); @@ -408,7 +413,10 @@ static int samsung_usb2phy_probe(struct platform_device *pdev) sphy->phy.label = "samsung-usb2phy"; sphy->phy.init = samsung_usb2phy_init; sphy->phy.shutdown = samsung_usb2phy_shutdown; - sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + + sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + if (sphy->ref_clk_freq < 0) + return -EINVAL; sphy->phy.otg = otg; sphy->phy.otg->phy = &sphy->phy; @@ -438,18 +446,40 @@ static int samsung_usb2phy_remove(struct platform_device *pdev) static const struct samsung_usbphy_drvdata usb2phy_s3c64xx = { .cpu_type = TYPE_S3C64XX, .devphy_en_mask = S3C64XX_USBPHY_ENABLE, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_64xx, + .set_isolation = NULL, /* TODO */ + .phy_enable = samsung_usb2phy_enable, + .phy_disable = samsung_usb2phy_disable, }; static const struct samsung_usbphy_drvdata usb2phy_exynos4 = { .cpu_type = TYPE_EXYNOS4210, .devphy_en_mask = EXYNOS_USBPHY_ENABLE, .hostphy_en_mask = EXYNOS_USBPHY_ENABLE, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_64xx, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_usb2phy_enable, + .phy_disable = samsung_usb2phy_disable, +}; + +static const struct samsung_usbphy_drvdata usb2phy_exynos4x12 = { + .cpu_type = TYPE_EXYNOS4X12, + .devphy_en_mask = EXYNOS_USBPHY_ENABLE, + .hostphy_en_mask = EXYNOS_USBPHY_ENABLE, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_usb2phy_enable, + .phy_disable = samsung_usb2phy_disable, }; static struct samsung_usbphy_drvdata usb2phy_exynos5 = { .cpu_type = TYPE_EXYNOS5250, .hostphy_en_mask = EXYNOS_USBPHY_ENABLE, .hostphy_reg_offset = EXYNOS_USBHOST_PHY_CTRL_OFFSET, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_exynos5_usb2phy_enable, + .phy_disable = samsung_exynos5_usb2phy_disable, }; #ifdef CONFIG_OF @@ -461,6 +491,9 @@ static const struct of_device_id samsung_usbphy_dt_match[] = { .compatible = "samsung,exynos4210-usb2phy", .data = &usb2phy_exynos4, }, { + .compatible = "samsung,exynos4x12-usb2phy", + .data = &usb2phy_exynos4x12, + }, { .compatible = "samsung,exynos5250-usb2phy", .data = &usb2phy_exynos5 }, @@ -477,6 +510,9 @@ static struct platform_device_id samsung_usbphy_driver_ids[] = { .name = "exynos4210-usb2phy", .driver_data = (unsigned long)&usb2phy_exynos4, }, { + .name = "exynos4x12-usb2phy", + .driver_data = (unsigned long)&usb2phy_exynos4x12, + }, { .name = "exynos5250-usb2phy", .driver_data = (unsigned long)&usb2phy_exynos5, }, diff --git a/drivers/usb/phy/phy-samsung-usb3.c b/drivers/usb/phy/phy-samsung-usb3.c index 5a9efcbcb53..300e0cf5e31 100644 --- a/drivers/usb/phy/phy-samsung-usb3.c +++ b/drivers/usb/phy/phy-samsung-usb3.c @@ -65,7 +65,7 @@ static u32 samsung_usb3phy_set_refclk(struct samsung_usbphy *sphy) return reg; } -static int samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy) +static void samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy) { void __iomem *regs = sphy->regs; u32 phyparam0; @@ -133,8 +133,6 @@ static int samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy) phyclkrst &= ~(PHYCLKRST_PORTRESET); writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); - - return 0; } static void samsung_exynos5_usb3phy_disable(struct samsung_usbphy *sphy) @@ -184,10 +182,11 @@ static int samsung_usb3phy_init(struct usb_phy *phy) samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); /* Disable phy isolation */ - samsung_usbphy_set_isolation(sphy, false); + if (sphy->drv_data->set_isolation) + sphy->drv_data->set_isolation(sphy, false); /* Initialize usb phy registers */ - samsung_exynos5_usb3phy_enable(sphy); + sphy->drv_data->phy_enable(sphy); spin_unlock_irqrestore(&sphy->lock, flags); @@ -218,10 +217,11 @@ static void samsung_usb3phy_shutdown(struct usb_phy *phy) samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); /* De-initialize usb phy registers */ - samsung_exynos5_usb3phy_disable(sphy); + sphy->drv_data->phy_disable(sphy); /* Enable phy isolation */ - samsung_usbphy_set_isolation(sphy, true); + if (sphy->drv_data->set_isolation) + sphy->drv_data->set_isolation(sphy, true); spin_unlock_irqrestore(&sphy->lock, flags); @@ -274,7 +274,10 @@ static int samsung_usb3phy_probe(struct platform_device *pdev) sphy->phy.init = samsung_usb3phy_init; sphy->phy.shutdown = samsung_usb3phy_shutdown; sphy->drv_data = samsung_usbphy_get_driver_data(pdev); - sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + + sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + if (sphy->ref_clk_freq < 0) + return -EINVAL; spin_lock_init(&sphy->lock); @@ -300,6 +303,10 @@ static int samsung_usb3phy_remove(struct platform_device *pdev) static struct samsung_usbphy_drvdata usb3phy_exynos5 = { .cpu_type = TYPE_EXYNOS5250, .devphy_en_mask = EXYNOS_USBPHY_ENABLE, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_exynos5_usb3phy_enable, + .phy_disable = samsung_exynos5_usb3phy_disable, }; #ifdef CONFIG_OF diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index 17d811292f3..cec0855ed24 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -1,9 +1,11 @@ /* * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2013 NVIDIA Corporation * * Author: * Erik Gilling <konkers@google.com> * Benoit Goby <benoit@android.com> + * Venu Byravarasu <vbyravarasu@nvidia.com> * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -21,6 +23,7 @@ #include <linux/slab.h> #include <linux/err.h> #include <linux/export.h> +#include <linux/module.h> #include <linux/platform_device.h> #include <linux/io.h> #include <linux/gpio.h> @@ -29,13 +32,19 @@ #include <linux/usb/otg.h> #include <linux/usb/ulpi.h> #include <asm/mach-types.h> +#include <linux/usb/ehci_def.h> #include <linux/usb/tegra_usb_phy.h> -#define TEGRA_USB_BASE 0xC5000000 -#define TEGRA_USB_SIZE SZ_16K - #define ULPI_VIEWPORT 0x170 +/* PORTSC registers */ +#define TEGRA_USB_PORTSC1 0x184 +#define TEGRA_USB_PORTSC1_PTS(x) (((x) & 0x3) << 30) +#define TEGRA_USB_PORTSC1_PHCD (1 << 23) + +/* Bits of PORTSC1, which will get cleared by writing 1 into them */ +#define TEGRA_PORTSC1_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) + #define USB_SUSP_CTRL 0x400 #define USB_WAKE_ON_CNNT_EN_DEV (1 << 3) #define USB_WAKE_ON_DISCON_EN_DEV (1 << 4) @@ -196,34 +205,41 @@ static struct tegra_utmip_config utmip_default[] = { }, }; +static void set_pts(struct tegra_usb_phy *phy, u8 pts_val) +{ + void __iomem *base = phy->regs; + unsigned long val; + + val = readl(base + TEGRA_USB_PORTSC1) & ~TEGRA_PORTSC1_RWC_BITS; + val &= ~TEGRA_USB_PORTSC1_PTS(3); + val |= TEGRA_USB_PORTSC1_PTS(pts_val & 3); + writel(val, base + TEGRA_USB_PORTSC1); +} + +static void set_phcd(struct tegra_usb_phy *phy, bool enable) +{ + void __iomem *base = phy->regs; + unsigned long val; + + val = readl(base + TEGRA_USB_PORTSC1) & ~TEGRA_PORTSC1_RWC_BITS; + if (enable) + val |= TEGRA_USB_PORTSC1_PHCD; + else + val &= ~TEGRA_USB_PORTSC1_PHCD; + writel(val, base + TEGRA_USB_PORTSC1); +} + static int utmip_pad_open(struct tegra_usb_phy *phy) { - phy->pad_clk = clk_get_sys("utmip-pad", NULL); + phy->pad_clk = devm_clk_get(phy->dev, "utmi-pads"); if (IS_ERR(phy->pad_clk)) { pr_err("%s: can't get utmip pad clock\n", __func__); return PTR_ERR(phy->pad_clk); } - if (phy->is_legacy_phy) { - phy->pad_regs = phy->regs; - } else { - phy->pad_regs = ioremap(TEGRA_USB_BASE, TEGRA_USB_SIZE); - if (!phy->pad_regs) { - pr_err("%s: can't remap usb registers\n", __func__); - clk_put(phy->pad_clk); - return -ENOMEM; - } - } return 0; } -static void utmip_pad_close(struct tegra_usb_phy *phy) -{ - if (!phy->is_legacy_phy) - iounmap(phy->pad_regs); - clk_put(phy->pad_clk); -} - static void utmip_pad_power_on(struct tegra_usb_phy *phy) { unsigned long val, flags; @@ -299,7 +315,7 @@ static void utmi_phy_clk_disable(struct tegra_usb_phy *phy) val &= ~USB_SUSP_SET; writel(val, base + USB_SUSP_CTRL); } else - phy->set_phcd(&phy->u_phy, true); + set_phcd(phy, true); if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0) < 0) pr_err("%s: timeout waiting for phy to stabilize\n", __func__); @@ -321,7 +337,7 @@ static void utmi_phy_clk_enable(struct tegra_usb_phy *phy) val &= ~USB_SUSP_CLR; writel(val, base + USB_SUSP_CTRL); } else - phy->set_phcd(&phy->u_phy, false); + set_phcd(phy, false); if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, USB_PHY_CLK_VALID)) @@ -444,7 +460,7 @@ static int utmi_phy_power_on(struct tegra_usb_phy *phy) utmi_phy_clk_enable(phy); if (!phy->is_legacy_phy) - phy->set_pts(&phy->u_phy, 0); + set_pts(phy, 0); return 0; } @@ -541,11 +557,18 @@ static int ulpi_phy_power_on(struct tegra_usb_phy *phy) int ret; unsigned long val; void __iomem *base = phy->regs; - struct tegra_ulpi_config *config = phy->config; - gpio_direction_output(config->reset_gpio, 0); + ret = gpio_direction_output(phy->reset_gpio, 0); + if (ret < 0) { + dev_err(phy->dev, "gpio %d not set to 0\n", phy->reset_gpio); + return ret; + } msleep(5); - gpio_direction_output(config->reset_gpio, 1); + ret = gpio_direction_output(phy->reset_gpio, 1); + if (ret < 0) { + dev_err(phy->dev, "gpio %d not set to 1\n", phy->reset_gpio); + return ret; + } clk_prepare_enable(phy->clk); msleep(1); @@ -603,63 +626,15 @@ static int ulpi_phy_power_on(struct tegra_usb_phy *phy) static int ulpi_phy_power_off(struct tegra_usb_phy *phy) { - struct tegra_ulpi_config *config = phy->config; - clk_disable(phy->clk); - return gpio_direction_output(config->reset_gpio, 0); -} - -static int tegra_phy_init(struct usb_phy *x) -{ - struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); - struct tegra_ulpi_config *ulpi_config; - int err; - - if (phy->is_ulpi_phy) { - ulpi_config = phy->config; - phy->clk = clk_get_sys(NULL, ulpi_config->clk); - if (IS_ERR(phy->clk)) { - pr_err("%s: can't get ulpi clock\n", __func__); - err = -ENXIO; - goto err1; - } - if (!gpio_is_valid(ulpi_config->reset_gpio)) - ulpi_config->reset_gpio = - of_get_named_gpio(phy->dev->of_node, - "nvidia,phy-reset-gpio", 0); - if (!gpio_is_valid(ulpi_config->reset_gpio)) { - pr_err("%s: invalid reset gpio: %d\n", __func__, - ulpi_config->reset_gpio); - err = -EINVAL; - goto err1; - } - gpio_request(ulpi_config->reset_gpio, "ulpi_phy_reset_b"); - gpio_direction_output(ulpi_config->reset_gpio, 0); - phy->ulpi = otg_ulpi_create(&ulpi_viewport_access_ops, 0); - phy->ulpi->io_priv = phy->regs + ULPI_VIEWPORT; - } else { - err = utmip_pad_open(phy); - if (err < 0) - goto err1; - } - return 0; -err1: - clk_disable_unprepare(phy->pll_u); - clk_put(phy->pll_u); - return err; + return gpio_direction_output(phy->reset_gpio, 0); } static void tegra_usb_phy_close(struct usb_phy *x) { struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); - if (phy->is_ulpi_phy) - clk_put(phy->clk); - else - utmip_pad_close(phy); clk_disable_unprepare(phy->pll_u); - clk_put(phy->pll_u); - kfree(phy); } static int tegra_usb_phy_power_on(struct tegra_usb_phy *phy) @@ -687,54 +662,63 @@ static int tegra_usb_phy_suspend(struct usb_phy *x, int suspend) return tegra_usb_phy_power_on(phy); } -struct tegra_usb_phy *tegra_usb_phy_open(struct device *dev, int instance, - void __iomem *regs, void *config, enum tegra_usb_phy_mode phy_mode, - void (*set_pts)(struct usb_phy *x, u8 pts_val), - void (*set_phcd)(struct usb_phy *x, bool enable)) +static int ulpi_open(struct tegra_usb_phy *phy) +{ + int err; + + phy->clk = devm_clk_get(phy->dev, "ulpi-link"); + if (IS_ERR(phy->clk)) { + pr_err("%s: can't get ulpi clock\n", __func__); + return PTR_ERR(phy->clk); + } + + err = devm_gpio_request(phy->dev, phy->reset_gpio, "ulpi_phy_reset_b"); + if (err < 0) { + dev_err(phy->dev, "request failed for gpio: %d\n", + phy->reset_gpio); + return err; + } + + err = gpio_direction_output(phy->reset_gpio, 0); + if (err < 0) { + dev_err(phy->dev, "gpio %d direction not set to output\n", + phy->reset_gpio); + return err; + } + + phy->ulpi = otg_ulpi_create(&ulpi_viewport_access_ops, 0); + if (!phy->ulpi) { + dev_err(phy->dev, "otg_ulpi_create returned NULL\n"); + err = -ENOMEM; + return err; + } + + phy->ulpi->io_priv = phy->regs + ULPI_VIEWPORT; + return 0; +} +static int tegra_usb_phy_init(struct tegra_usb_phy *phy) { - struct tegra_usb_phy *phy; unsigned long parent_rate; int i; int err; - struct device_node *np = dev->of_node; - - phy = kzalloc(sizeof(struct tegra_usb_phy), GFP_KERNEL); - if (!phy) - return ERR_PTR(-ENOMEM); - - phy->instance = instance; - phy->regs = regs; - phy->config = config; - phy->mode = phy_mode; - phy->dev = dev; - phy->is_legacy_phy = - of_property_read_bool(np, "nvidia,has-legacy-mode"); - phy->set_pts = set_pts; - phy->set_phcd = set_phcd; - err = of_property_match_string(np, "phy_type", "ulpi"); - if (err < 0) - phy->is_ulpi_phy = false; - else - phy->is_ulpi_phy = true; - - if (!phy->config) { - if (phy->is_ulpi_phy) { - pr_err("%s: ulpi phy configuration missing", __func__); - err = -EINVAL; - goto err0; - } else { - phy->config = &utmip_default[instance]; - } + + if (!phy->is_ulpi_phy) { + if (phy->is_legacy_phy) + phy->config = &utmip_default[0]; + else + phy->config = &utmip_default[2]; } - phy->pll_u = clk_get_sys(NULL, "pll_u"); + phy->pll_u = devm_clk_get(phy->dev, "pll_u"); if (IS_ERR(phy->pll_u)) { pr_err("Can't get pll_u clock\n"); - err = PTR_ERR(phy->pll_u); - goto err0; + return PTR_ERR(phy->pll_u); } - clk_prepare_enable(phy->pll_u); + + err = clk_prepare_enable(phy->pll_u); + if (err) + return err; parent_rate = clk_get_rate(clk_get_parent(phy->pll_u)); for (i = 0; i < ARRAY_SIZE(tegra_freq_table); i++) { @@ -746,23 +730,22 @@ struct tegra_usb_phy *tegra_usb_phy_open(struct device *dev, int instance, if (!phy->freq) { pr_err("invalid pll_u parent rate %ld\n", parent_rate); err = -EINVAL; - goto err1; + goto fail; } - phy->u_phy.init = tegra_phy_init; - phy->u_phy.shutdown = tegra_usb_phy_close; - phy->u_phy.set_suspend = tegra_usb_phy_suspend; + if (phy->is_ulpi_phy) + err = ulpi_open(phy); + else + err = utmip_pad_open(phy); + if (err < 0) + goto fail; - return phy; + return 0; -err1: +fail: clk_disable_unprepare(phy->pll_u); - clk_put(phy->pll_u); -err0: - kfree(phy); - return ERR_PTR(err); + return err; } -EXPORT_SYMBOL_GPL(tegra_usb_phy_open); void tegra_usb_phy_preresume(struct usb_phy *x) { @@ -801,3 +784,124 @@ void tegra_ehci_phy_restore_end(struct usb_phy *x) } EXPORT_SYMBOL_GPL(tegra_ehci_phy_restore_end); +static int tegra_usb_phy_probe(struct platform_device *pdev) +{ + struct resource *res; + struct tegra_usb_phy *tegra_phy = NULL; + struct device_node *np = pdev->dev.of_node; + int err; + + tegra_phy = devm_kzalloc(&pdev->dev, sizeof(*tegra_phy), GFP_KERNEL); + if (!tegra_phy) { + dev_err(&pdev->dev, "unable to allocate memory for USB2 PHY\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get I/O memory\n"); + return -ENXIO; + } + + tegra_phy->regs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!tegra_phy->regs) { + dev_err(&pdev->dev, "Failed to remap I/O memory\n"); + return -ENOMEM; + } + + tegra_phy->is_legacy_phy = + of_property_read_bool(np, "nvidia,has-legacy-mode"); + + err = of_property_match_string(np, "phy_type", "ulpi"); + if (err < 0) { + tegra_phy->is_ulpi_phy = false; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "Failed to get UTMI Pad regs\n"); + return -ENXIO; + } + + tegra_phy->pad_regs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!tegra_phy->regs) { + dev_err(&pdev->dev, "Failed to remap UTMI Pad regs\n"); + return -ENOMEM; + } + } else { + tegra_phy->is_ulpi_phy = true; + + tegra_phy->reset_gpio = + of_get_named_gpio(np, "nvidia,phy-reset-gpio", 0); + if (!gpio_is_valid(tegra_phy->reset_gpio)) { + dev_err(&pdev->dev, "invalid gpio: %d\n", + tegra_phy->reset_gpio); + return tegra_phy->reset_gpio; + } + } + + err = of_property_match_string(np, "dr_mode", "otg"); + if (err < 0) { + err = of_property_match_string(np, "dr_mode", "peripheral"); + if (err < 0) + tegra_phy->mode = TEGRA_USB_PHY_MODE_HOST; + else + tegra_phy->mode = TEGRA_USB_PHY_MODE_DEVICE; + } else + tegra_phy->mode = TEGRA_USB_PHY_MODE_OTG; + + tegra_phy->dev = &pdev->dev; + err = tegra_usb_phy_init(tegra_phy); + if (err < 0) + return err; + + tegra_phy->u_phy.shutdown = tegra_usb_phy_close; + tegra_phy->u_phy.set_suspend = tegra_usb_phy_suspend; + + dev_set_drvdata(&pdev->dev, tegra_phy); + return 0; +} + +static struct of_device_id tegra_usb_phy_id_table[] = { + { .compatible = "nvidia,tegra20-usb-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_usb_phy_id_table); + +static struct platform_driver tegra_usb_phy_driver = { + .probe = tegra_usb_phy_probe, + .driver = { + .name = "tegra-phy", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tegra_usb_phy_id_table), + }, +}; +module_platform_driver(tegra_usb_phy_driver); + +static int tegra_usb_phy_match(struct device *dev, void *data) +{ + struct tegra_usb_phy *tegra_phy = dev_get_drvdata(dev); + struct device_node *dn = data; + + return (tegra_phy->dev->of_node == dn) ? 1 : 0; +} + +struct usb_phy *tegra_usb_get_phy(struct device_node *dn) +{ + struct device *dev; + struct tegra_usb_phy *tegra_phy; + + dev = driver_find_device(&tegra_usb_phy_driver.driver, NULL, dn, + tegra_usb_phy_match); + if (!dev) + return ERR_PTR(-EPROBE_DEFER); + + tegra_phy = dev_get_drvdata(dev); + + return &tegra_phy->u_phy; +} +EXPORT_SYMBOL_GPL(tegra_usb_get_phy); + +MODULE_DESCRIPTION("Tegra USB PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-ulpi-viewport.c b/drivers/usb/phy/phy-ulpi-viewport.c index c5ba7e5423f..7c22a5390fc 100644 --- a/drivers/usb/phy/phy-ulpi-viewport.c +++ b/drivers/usb/phy/phy-ulpi-viewport.c @@ -12,6 +12,7 @@ * */ +#include <linux/export.h> #include <linux/kernel.h> #include <linux/usb.h> #include <linux/io.h> @@ -78,3 +79,4 @@ struct usb_phy_io_ops ulpi_viewport_access_ops = { .read = ulpi_viewport_read, .write = ulpi_viewport_write, }; +EXPORT_SYMBOL_GPL(ulpi_viewport_access_ops); |