diff options
author | Avinash Kumar <avinash.kumar@stericsson.com> | 2011-09-19 15:36:01 +0530 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@stericsson.com> | 2012-05-22 11:03:09 +0200 |
commit | 9c31e476c753f7c628a39fcb0e28c3c6a602cf43 (patch) | |
tree | 25eeb3404f29eeeac10353ca2aaa668c8a1077ab | |
parent | 112b4b841e6c902870daccbde4080b2ed3a12888 (diff) |
usb:Migrate the patches to kernel 3.0
Following patches for usb driver are manually merged to kernel 3.0
d11e52a ux500: usb: add usb device mode support on u5500
1fecc55 ux500: usb: enabling adb and ACM functionality in u5500_defconfig
2847bf4 ux500: usb: error handling in musb for u5500
37f22da ux500: usb: U5500 v2 update
e1b079d ux500: usb: Fix for enumeration when ON with cable
181701a ux500: usb: Fix for V2 emmc2 boot USB device issue
3a60721 mach-ux500: Handle the LinkStatus register.
086a83e usb: ux500: usb gpio enable/disable in pair
ST-Ericsson ID: 352334
ST-Ericsson Linux next: NA
ST-Ericsson FOSS-OUT ID: NA
Change-Id: Id918b9a55d3b85faf7c003547748f17086af3ec7
Signed-off-by: Avinash Kumar <avinash.kumar@stericsson.com>
Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30860
-rw-r--r-- | arch/arm/mach-ux500/board-u5500.c | 4 | ||||
-rw-r--r-- | arch/arm/mach-ux500/include/mach/usb.h | 1 | ||||
-rw-r--r-- | arch/arm/mach-ux500/usb.c | 3 | ||||
-rw-r--r-- | drivers/usb/musb/Kconfig | 2 | ||||
-rw-r--r-- | drivers/usb/otg/Kconfig | 9 | ||||
-rw-r--r-- | drivers/usb/otg/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/otg/ab5500-usb.c | 734 |
7 files changed, 753 insertions, 1 deletions
diff --git a/arch/arm/mach-ux500/board-u5500.c b/arch/arm/mach-ux500/board-u5500.c index 0ff4be72a80..f040ab0ba7c 100644 --- a/arch/arm/mach-ux500/board-u5500.c +++ b/arch/arm/mach-ux500/board-u5500.c @@ -23,10 +23,12 @@ #include <mach/hardware.h> #include <mach/devices.h> #include <mach/setup.h> +#include <mach/usb.h> #include "pins-db5500.h" #include "devices-db5500.h" #include <linux/led-lm3530.h> +#include "board-ux500-usb.h" /* * GPIO @@ -111,6 +113,8 @@ static struct ab5500_platform_data ab5500_plf_data = { .init_settings = NULL, .init_settings_sz = 0, .pm_power_off = false, + .dev_data[AB5500_DEVID_USB] = &abx500_usbgpio_plat_data, + .dev_data_sz[AB5500_DEVID_USB] = sizeof(abx500_usbgpio_plat_data), }; static struct platform_device ab5500_device = { diff --git a/arch/arm/mach-ux500/include/mach/usb.h b/arch/arm/mach-ux500/include/mach/usb.h index 145823a4d37..a9aa06190eb 100644 --- a/arch/arm/mach-ux500/include/mach/usb.h +++ b/arch/arm/mach-ux500/include/mach/usb.h @@ -28,6 +28,7 @@ struct abx500_usbgpio_platform_data { void (*enable)(void); void (*disable)(void); void (*put)(void); + int usb_cs; }; #endif diff --git a/arch/arm/mach-ux500/usb.c b/arch/arm/mach-ux500/usb.c index 170b8934200..37f9427307f 100644 --- a/arch/arm/mach-ux500/usb.c +++ b/arch/arm/mach-ux500/usb.c @@ -35,6 +35,8 @@ .dst_info.psize = STEDMA40_PSIZE_LOG_16, \ } +#define USB_OTG_GPIO_CS 76 + static struct stedma40_chan_cfg musb_dma_rx_ch[UX500_MUSB_DMA_NUM_RX_CHANNELS] = { MUSB_DMA40_RX_CH, @@ -158,6 +160,7 @@ struct abx500_usbgpio_platform_data abx500_usbgpio_plat_data = { .enable = &enable_gpio, .disable = &disable_gpio, .put = &put_gpio, + .usb_cs = USB_OTG_GPIO_CS, }; static inline void ux500_usb_dma_update_rx_ch_config(int *src_dev_type) diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 1169c42b56e..ac26a4f8545 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -60,7 +60,7 @@ config USB_MUSB_BLACKFIN config USB_MUSB_UX500 tristate "U8500 and U5500" - depends on (ARCH_U8500 && AB8500_USB) + depends on (ARCH_U8500) || (ARCH_U5500) endchoice diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index 5c87db06b59..168f5b0364f 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -108,6 +108,15 @@ config AB8500_USB This transceiver supports high and full speed devices plus, in host mode, low speed. +config AB5500_USB + tristate "AB5500 USB Transceiver Driver" + depends on AB5500_CORE + select USB_OTG_UTILS + help + Enable this to support the USB OTG transceiver in AB5500 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_GADGET_FSL_USB2 && USB_SUSPEND diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index 41aa5098b13..e227d9add96 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_USB_ULPI) += ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += ulpi_viewport.o obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o obj-$(CONFIG_AB8500_USB) += ab8500-usb.o +obj-$(CONFIG_AB5500_USB) += ab5500-usb.o fsl_usb2_otg-objs := fsl_otg.o otg_fsm.o obj-$(CONFIG_FSL_USB2_OTG) += fsl_usb2_otg.o obj-$(CONFIG_USB_MV_OTG) += mv_otg.o diff --git a/drivers/usb/otg/ab5500-usb.c b/drivers/usb/otg/ab5500-usb.c new file mode 100644 index 00000000000..a4f4f58f847 --- /dev/null +++ b/drivers/usb/otg/ab5500-usb.c @@ -0,0 +1,734 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Avinash Kumar <avinash.kumar@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/platform_device.h> +#include <linux/usb/otg.h> +#include <linux/slab.h> +#include <linux/notifier.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/mfd/abx500.h> +#include <linux/regulator/consumer.h> +#include <mach/prcmu.h> +#include <mach/usb.h> +#include <linux/kernel_stat.h> +#include <mach/gpio.h> + +/* AB5500 USB macros + */ +#define AB5500_USB_HOST_ENABLE 0x1 +#define AB5500_USB_HOST_DISABLE 0x0 +#define AB5500_USB_DEVICE_ENABLE 0x2 +#define AB5500_USB_DEVICE_DISABLE 0x0 +#define AB5500_MAIN_WATCHDOG_ENABLE 0x1 +#define AB5500_MAIN_WATCHDOG_KICK 0x2 +#define AB5500_MAIN_WATCHDOG_DISABLE 0x0 +#define AB5500_USB_ADP_ENABLE 0x1 +#define AB5500_WATCHDOG_DELAY 10 +#define AB5500_WATCHDOG_DELAY_US 100 +#define AB5500_PHY_DELAY_US 100 +#define AB5500_USB_DEVICE_DISABLE 0x0 +#define AB5500_MAIN_WDOG_CTRL_REG 0x01 +#define AB5500_USB_LINE_STAT_REG 0x80 +#define AB5500_USB_PHY_CTRL_REG 0x8A +#define AB5500_MAIN_WATCHDOG_ENABLE 0x1 +#define AB5500_MAIN_WATCHDOG_KICK 0x2 +#define AB5500_MAIN_WATCHDOG_DISABLE 0x0 +#define AB5500_SYS_CTRL2_BLOCK 0x2 + +#define USB_PROBE_DELAY 1000 /* 1 seconds */ + +/* UsbLineStatus register - usb types */ +enum ab8500_usb_link_status { + USB_LINK_NOT_CONFIGURED, + USB_LINK_STD_HOST_NC, + USB_LINK_STD_HOST_C_NS, + USB_LINK_STD_HOST_C_S, + USB_LINK_HOST_CHG_NM, + USB_LINK_HOST_CHG_HS, + USB_LINK_HOST_CHG_HS_CHIRP, + USB_LINK_DEDICATED_CHG, + USB_LINK_ACA_RID_A, + USB_LINK_ACA_RID_B, + USB_LINK_ACA_RID_C_NM, + USB_LINK_ACA_RID_C_HS, + USB_LINK_ACA_RID_C_HS_CHIRP, + USB_LINK_HM_IDGND, + USB_LINK_OTG_HOST_NO_CURRENT, + USB_LINK_NOT_VALID_LINK, + USB_LINK_HM_IDGND_V2 = 18, +}; + +/** + * ab5500_usb_mode - Different states of ab usb_chip + * + * Used for USB cable plug-in state machine + */ +enum ab5500_usb_mode { + USB_IDLE, + USB_DEVICE, + USB_HOST, + USB_DEDICATED_CHG, +}; +struct ab5500_usb { + struct otg_transceiver otg; + struct device *dev; + int irq_num_id_rise; + int irq_num_id_fall; + int irq_num_vbus_rise; + int irq_num_vbus_fall; + int irq_num_link_status; + unsigned vbus_draw; + struct delayed_work dwork; + struct work_struct phy_dis_work; + unsigned long link_status_wait; + int rev; + int usb_cs_gpio; + enum ab5500_usb_mode mode; + struct clk *sysclk; + struct regulator *v_ape; + struct abx500_usbgpio_platform_data *usb_gpio; + struct delayed_work work_usb_workaround; +}; + +static int ab5500_usb_irq_setup(struct platform_device *pdev, + struct ab5500_usb *ab); +static int ab5500_usb_boot_detect(struct ab5500_usb *ab); +static int ab5500_usb_link_status_update(struct ab5500_usb *ab); + +static void ab5500_usb_phy_enable(struct ab5500_usb *ab, bool sel_host); + +static inline struct ab5500_usb *xceiv_to_ab(struct otg_transceiver *x) +{ + return container_of(x, struct ab5500_usb, otg); +} + +/** + * ab5500_usb_wd_workaround() - Kick the watch dog timer + * + * This function used to Kick the watch dog timer + */ +static void ab5500_usb_wd_workaround(struct ab5500_usb *ab) +{ + abx500_set_register_interruptible(ab->dev, + AB5500_SYS_CTRL2_BLOCK, + AB5500_MAIN_WDOG_CTRL_REG, + AB5500_MAIN_WATCHDOG_ENABLE); + + udelay(AB5500_WATCHDOG_DELAY_US); + + abx500_set_register_interruptible(ab->dev, + AB5500_SYS_CTRL2_BLOCK, + AB5500_MAIN_WDOG_CTRL_REG, + (AB5500_MAIN_WATCHDOG_ENABLE + | AB5500_MAIN_WATCHDOG_KICK)); + + udelay(AB5500_WATCHDOG_DELAY_US); + + abx500_set_register_interruptible(ab->dev, + AB5500_SYS_CTRL2_BLOCK, + AB5500_MAIN_WDOG_CTRL_REG, + AB5500_MAIN_WATCHDOG_DISABLE); + + udelay(AB5500_WATCHDOG_DELAY_US); +} + +static void ab5500_usb_phy_enable(struct ab5500_usb *ab, bool sel_host) +{ + u8 bit; + bit = sel_host ? AB5500_USB_HOST_ENABLE : + AB5500_USB_DEVICE_ENABLE; + + ab->usb_gpio->enable(); + clk_enable(ab->sysclk); + regulator_enable(ab->v_ape); + + if (!sel_host) { + schedule_delayed_work_on(0, + &ab->work_usb_workaround, + msecs_to_jiffies(USB_PROBE_DELAY)); + } + + abx500_set_register_interruptible(ab->dev, + AB5500_BANK_USB, + AB5500_USB_PHY_CTRL_REG, + bit); +} + +static void ab5500_usb_phy_disable(struct ab5500_usb *ab, bool sel_host) +{ + u8 bit; + bit = sel_host ? AB5500_USB_HOST_ENABLE : + AB5500_USB_DEVICE_ENABLE; + + abx500_set_register_interruptible(ab->dev, + AB5500_BANK_USB, + AB5500_USB_PHY_CTRL_REG, + bit); + + /* Needed to disable the phy.*/ + ab5500_usb_wd_workaround(ab); + + regulator_disable(ab->v_ape); + clk_disable(ab->sysclk); + ab->usb_gpio->disable(); + if (!sel_host) { + + cancel_delayed_work_sync(&ab->work_usb_workaround); + prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "usb", 25); + } + +} + +#define ab5500_usb_peri_phy_en(ab) ab5500_usb_phy_enable(ab, false) +#define ab5500_usb_peri_phy_dis(ab) ab5500_usb_phy_disable(ab, false) +#define ab5500_usb_host_phy_en(ab) ab5500_usb_phy_enable(ab, true) +#define ab5500_usb_host_phy_dis(ab) ab5500_usb_phy_disable(ab, true) + +/* Work created after an link status update handler*/ +static int ab5500_usb_link_status_update(struct ab5500_usb *ab) +{ + u8 val = 0; + int ret = 0; + int gpioval = 0; + enum ab8500_usb_link_status lsts; + enum usb_xceiv_events event; + + (void)abx500_get_register_interruptible(ab->dev, + AB5500_BANK_USB, AB5500_USB_LINE_STAT_REG, &val); + + lsts = (val >> 3) & 0x0F; + + switch (lsts) { + + case USB_LINK_STD_HOST_NC: + case USB_LINK_STD_HOST_C_NS: + case USB_LINK_STD_HOST_C_S: + case USB_LINK_HOST_CHG_NM: + case USB_LINK_HOST_CHG_HS: + case USB_LINK_HOST_CHG_HS_CHIRP: + if (ab->otg.gadget) { + ab5500_usb_peri_phy_en(ab); + ab->mode = USB_DEVICE; + } + gpio_set_value(ab->usb_cs_gpio, 1); + event = USB_EVENT_VBUS; + break; + + case USB_LINK_HM_IDGND: + if (ab->rev == AB5500_2_0) + break; + + /* enable usb chip Select */ + ret = gpio_direction_output(ab->usb_cs_gpio, gpioval); + if (ret < 0) { + dev_err(ab->dev, "usb_cs_gpio: gpio direction failed\n"); + gpio_free(ab->usb_cs_gpio); + return ret; + } + gpio_set_value(ab->usb_cs_gpio, 1); + + ab5500_usb_host_phy_en(ab); + + break; + + case USB_LINK_HM_IDGND_V2: + if (!(ab->rev == AB5500_2_0)) + break; + + /* enable usb chip Select */ + ret = gpio_direction_output(ab->usb_cs_gpio, gpioval); + if (ret < 0) { + dev_err(ab->dev, "usb_cs_gpio: gpio direction failed\n"); + gpio_free(ab->usb_cs_gpio); + return ret; + } + gpio_set_value(ab->usb_cs_gpio, 1); + + ab5500_usb_host_phy_en(ab); + + break; + default: + break; + } + + atomic_notifier_call_chain(&ab->otg.notifier, event, &ab->vbus_draw); + + return 0; +} + +static void ab5500_usb_delayed_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct ab5500_usb *ab = container_of(dwork, struct ab5500_usb, dwork); + + ab5500_usb_link_status_update(ab); +} + +/** + * This function is used to signal the completion of + * USB Link status register update + */ +static irqreturn_t ab5500_usb_link_status_irq(int irq, void *data) +{ + struct ab5500_usb *ab = (struct ab5500_usb *) data; + ab5500_usb_link_status_update(ab); + + return IRQ_HANDLED; +} + +static irqreturn_t ab5500_usb_device_insert_irq(int irq, void *data) +{ + int ret = 0, val = 1; + struct ab5500_usb *ab = (struct ab5500_usb *) data; + ab->mode = USB_DEVICE; + + ab5500_usb_peri_phy_en(ab); + /* enable usb chip Select */ + ret = gpio_direction_output(ab->usb_cs_gpio, val); + if (ret < 0) { + dev_err(ab->dev, "usb_cs_gpio: gpio direction failed\n"); + gpio_free(ab->usb_cs_gpio); + return ret; + } + gpio_set_value(ab->usb_cs_gpio, 1); + + return IRQ_HANDLED; +} + +/** + * This function used to remove the voltage for USB ab->dev mode. + */ +static irqreturn_t ab5500_usb_device_disconnect_irq(int irq, void *data) +{ + struct ab5500_usb *ab = (struct ab5500_usb *) data; + /* disable usb chip Select */ + gpio_set_value(ab->usb_cs_gpio, 0); + ab5500_usb_peri_phy_dis(ab); + return IRQ_HANDLED; +} + +/** + * ab5500_usb_host_disconnect_irq : work handler for host cable insert. + * @work: work structure + * + * This function is used to handle the host cable insert work. + */ +static void ab5500_usb_host_disconnect_irq(int irq, void *data) +{ + struct ab5500_usb *ab = (struct ab5500_usb *) data; + /* disable usb chip Select */ + gpio_set_value(ab->usb_cs_gpio, 0); + ab5500_usb_host_phy_dis(ab); + return IRQ_HANDLED; +} + +static void ab5500_usb_irq_free(struct ab5500_usb *ab) +{ + if (ab->irq_num_id_rise) + free_irq(ab->irq_num_id_rise, ab); + + if (ab->irq_num_id_fall) + free_irq(ab->irq_num_id_fall, ab); + + if (ab->irq_num_vbus_rise) + free_irq(ab->irq_num_vbus_rise, ab); + + if (ab->irq_num_vbus_fall) + free_irq(ab->irq_num_vbus_fall, ab); + + if (ab->irq_num_link_status) + free_irq(ab->irq_num_link_status, ab); +} + +/** + * ab5500_usb_irq_setup : register USB callback handlers for ab5500 + * @mode: value for mode. + * + * This function is used to register USB callback handlers for ab5500. + */ +static int ab5500_usb_irq_setup(struct platform_device *pdev, + struct ab5500_usb *ab) +{ + int ret = 0; + int irq, err; + + if (!ab->dev) + return -EINVAL; + + irq = platform_get_irq_byname(pdev, "usb_idgnd_f"); + if (irq < 0) { + dev_err(&pdev->dev, "ID fall irq not found\n"); + err = irq; + goto irq_fail; + } + ab->irq_num_id_fall = irq; + + irq = platform_get_irq_byname(pdev, "VBUS_F"); + if (irq < 0) { + dev_err(&pdev->dev, "VBUS fall irq not found\n"); + err = irq; + goto irq_fail; + + } + ab->irq_num_vbus_fall = irq; + + irq = platform_get_irq_byname(pdev, "VBUS_R"); + if (irq < 0) { + dev_err(&pdev->dev, "VBUS raise irq not found\n"); + err = irq; + goto irq_fail; + + } + ab->irq_num_vbus_rise = irq; + + irq = platform_get_irq_byname(pdev, "Link_Update"); + if (irq < 0) { + dev_err(&pdev->dev, "Link Update irq not found\n"); + err = irq; + goto irq_fail; + } + ab->irq_num_link_status = irq; + + + ret = request_threaded_irq(ab->irq_num_link_status, + NULL, ab5500_usb_link_status_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-link-status-update", ab); + if (ret < 0) { + printk(KERN_ERR "failed to set the callback" + " handler for usb charge" + " detect done\n"); + err = ret; + goto irq_fail; + } + + ret = request_threaded_irq(ab->irq_num_vbus_rise, NULL, + ab5500_usb_device_insert_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-vbus-rise", ab); + if (ret < 0) { + printk(KERN_ERR "failed to set the callback" + " handler for usb ab->dev" + " insertion\n"); + err = ret; + goto irq_fail; + } + + ret = request_threaded_irq(ab->irq_num_vbus_fall, NULL, + ab5500_usb_device_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-vbus-fall", ab); + if (ret < 0) { + printk(KERN_ERR "failed to set the callback" + " handler for usb ab->dev" + " removal\n"); + err = ret; + goto irq_fail; + } + + ret = request_threaded_irq((ab->irq_num_id_fall), NULL, + ab5500_usb_host_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-id-fall", ab); + if (ret < 0) { + printk(KERN_ERR "failed to set the callback" + " handler for usb host" + " removal\n"); + err = ret; + goto irq_fail; + } + + ab5500_usb_wd_workaround(ab); + return 0; + +irq_fail: + ab5500_usb_irq_free(ab); + return err; +} + +/** + * ab5500_usb_boot_detect : detect the USB cable during boot time. + * @mode: value for mode. + * + * This function is used to detect the USB cable during boot time. + */ +static int ab5500_usb_boot_detect(struct ab5500_usb *ab) +{ + int ret; + int val = 1; + int usb_status = 0; + int gpioval = 0; + enum ab8500_usb_link_status lsts; + if (!ab->dev) + return -EINVAL; + + abx500_set_register_interruptible(ab->dev, + AB5500_BANK_USB, + AB5500_USB_PHY_CTRL_REG, + AB5500_USB_DEVICE_ENABLE); + + udelay(AB5500_PHY_DELAY_US); + + abx500_set_register_interruptible(ab->dev, + AB5500_BANK_USB, + AB5500_USB_PHY_CTRL_REG, + AB5500_USB_DEVICE_DISABLE); + + abx500_set_register_interruptible(ab->dev, + AB5500_BANK_USB, + AB5500_USB_PHY_CTRL_REG, + AB5500_USB_HOST_ENABLE); + + udelay(AB5500_PHY_DELAY_US); + + abx500_set_register_interruptible(ab->dev, + AB5500_BANK_USB, + AB5500_USB_PHY_CTRL_REG, + AB5500_USB_HOST_DISABLE); + + ab5500_usb_wd_workaround(ab); + + (void)abx500_get_register_interruptible(ab->dev, + AB5500_BANK_USB, AB5500_USB_LINE_STAT_REG, &usb_status); + + lsts = (usb_status >> 3) & 0x0F; + + switch (lsts) { + + case USB_LINK_STD_HOST_NC: + case USB_LINK_STD_HOST_C_NS: + case USB_LINK_STD_HOST_C_S: + case USB_LINK_HOST_CHG_NM: + case USB_LINK_HOST_CHG_HS: + case USB_LINK_HOST_CHG_HS_CHIRP: + + ab5500_usb_peri_phy_en(ab); + + /* enable usb chip Select */ + ret = gpio_direction_output(ab->usb_cs_gpio, val); + if (ret < 0) { + dev_err(ab->dev, "usb_cs_gpio: gpio direction failed\n"); + gpio_free(ab->usb_cs_gpio); + return ret; + } + gpio_set_value(ab->usb_cs_gpio, 1); + + break; + + case USB_LINK_HM_IDGND: + case USB_LINK_HM_IDGND_V2: + /* enable usb chip Select */ + ret = gpio_direction_output(ab->usb_cs_gpio, gpioval); + if (ret < 0) { + dev_err(ab->dev, "usb_cs_gpio: gpio direction failed\n"); + gpio_free(ab->usb_cs_gpio); + return ret; + } + gpio_set_value(ab->usb_cs_gpio, 1); + ab5500_usb_host_phy_en(ab); + + break; + default: + break; + } + + return 0; +} + +static int ab5500_usb_set_power(struct otg_transceiver *otg, unsigned mA) +{ + struct ab5500_usb *ab; + + if (!otg) + return -ENODEV; + + ab = xceiv_to_ab(otg); + + ab->vbus_draw = mA; + + atomic_notifier_call_chain(&ab->otg.notifier, + USB_EVENT_VBUS, &ab->vbus_draw); + return 0; +} + +static int ab5500_usb_set_suspend(struct otg_transceiver *x, int suspend) +{ + /* TODO */ + return 0; +} + +static int ab5500_usb_set_host(struct otg_transceiver *otg, + struct usb_bus *host) +{ + struct ab5500_usb *ab; + + if (!otg) + return -ENODEV; + + ab = xceiv_to_ab(otg); + + /* Some drivers call this function in atomic context. + * Do not update ab5500 registers directly till this + * is fixed. + */ + + if (!host) { + ab->otg.host = NULL; + schedule_work(&ab->phy_dis_work); + } else { + ab->otg.host = host; + /* Phy will not be enabled if cable is already + * plugged-in. Schedule to enable phy. + * Use same delay to avoid any race condition. + */ + schedule_delayed_work(&ab->dwork, ab->link_status_wait); + } + + return 0; +} + +static int ab5500_usb_set_peripheral(struct otg_transceiver *otg, + struct usb_gadget *gadget) +{ + struct ab5500_usb *ab; + + if (!otg) + return -ENODEV; + + ab = xceiv_to_ab(otg); + + /* Some drivers call this function in atomic context. + * Do not update ab5500 registers directly till this + * is fixed. + */ + + if (!gadget) { + ab->otg.gadget = NULL; + schedule_work(&ab->phy_dis_work); + } else { + ab->otg.gadget = gadget; + /* Phy will not be enabled if cable is already + * plugged-in. Schedule to enable phy. + * Use same delay to avoid any race condition. + */ + schedule_delayed_work(&ab->dwork, ab->link_status_wait); + } + + return 0; +} + +static int __devinit ab5500_usb_probe(struct platform_device *pdev) +{ + struct ab5500_usb *ab; + struct abx500_usbgpio_platform_data *usb_pdata = + pdev->dev.platform_data; + int err; + int rev; + int ret = -1; + int irq; + ab = kzalloc(sizeof *ab, GFP_KERNEL); + if (!ab) + return -ENOMEM; + + ab->dev = &pdev->dev; + ab->rev = rev; + ab->otg.dev = ab->dev; + ab->otg.label = "ab5500"; + ab->otg.state = OTG_STATE_B_IDLE; + ab->otg.set_host = ab5500_usb_set_host; + ab->otg.set_peripheral = ab5500_usb_set_peripheral; + ab->otg.set_suspend = ab5500_usb_set_suspend; + ab->otg.set_power = ab5500_usb_set_power; + ab->usb_gpio = usb_pdata; + ab->mode = USB_IDLE; + + platform_set_drvdata(pdev, ab); + + ATOMIC_INIT_NOTIFIER_HEAD(&ab->otg.notifier); + + /* v1: Wait for link status to become stable. + * all: Updates form set_host and set_peripheral as they are atomic. + */ + INIT_DELAYED_WORK(&ab->dwork, ab5500_usb_delayed_work); + + err = otg_set_transceiver(&ab->otg); + if (err) + dev_err(&pdev->dev, "Can't register transceiver\n"); + + ab->usb_cs_gpio = ab->usb_gpio->usb_cs; + + ab->rev = abx500_get_chip_id(ab->dev); + + ab->sysclk = clk_get(ab->dev, "sysclk"); + if (IS_ERR(ab->sysclk)) { + ret = PTR_ERR(ab->sysclk); + ab->sysclk = NULL; + return ret; + } + + ab->v_ape = regulator_get(ab->dev, "v-ape"); + if (!ab->v_ape) { + dev_err(ab->dev, "Could not get v-ape supply\n"); + + return -EINVAL; + } + + ab5500_usb_irq_setup(pdev, ab); + + ret = gpio_request(ab->usb_cs_gpio, "usb-cs"); + if (ret < 0) + dev_err(&pdev->dev, "usb gpio request fail\n"); + + /* Aquire GPIO alternate config struct for USB */ + err = ab->usb_gpio->get(ab->dev); + if (err < 0) + goto fail1; + + err = ab5500_usb_boot_detect(ab); + if (err < 0) + goto fail1; + + return 0; + +fail1: + ab5500_usb_irq_free(ab); + kfree(ab); + return err; +} + +static int __devexit ab5500_usb_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver ab5500_usb_driver = { + .driver = { + .name = "ab5500-usb", + .owner = THIS_MODULE, + }, + .probe = ab5500_usb_probe, + .remove = __devexit_p(ab5500_usb_remove), +}; + +static int __init ab5500_usb_init(void) +{ + return platform_driver_register(&ab5500_usb_driver); +} +subsys_initcall(ab5500_usb_init); + +static void __exit ab5500_usb_exit(void) +{ + platform_driver_unregister(&ab5500_usb_driver); +} +module_exit(ab5500_usb_exit); + +MODULE_LICENSE("GPL v2"); |