From d03cccff9c768342d9576dde744ab3acb69d3ad4 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 23 Apr 2014 15:56:37 +0800 Subject: usb: chipidea: coordinate usb phy initialization for different phy type For internal PHY (like UTMI), the phy clock may from internal pll, it is on/off on the fly, the access PORTSC.PTS will hang without phy clock. So, the usb_phy_init which will open phy clock needs to be called before hw_phymode_configure. See: http://marc.info/?l=linux-arm-kernel&m=139350618732108&w=2 For external PHY (like ulpi), it needs to configure portsc.pts before visit viewport, or the viewport can't be visited. so phy_phymode_configure needs to be called before usb_phy_init. See: cd0b42c2a6d2a74244f0053f8960f5dad5842278 It may not the best solution, but it can work for all situations. Cc: Sascha Hauer Cc: Chris Ruehl Cc: shc_work@mail.ru Cc: denis@eukrea.com Cc: festevam@gmail.com Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index ca6831c5b76..1cd5d0ba587 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -276,6 +276,39 @@ static void hw_phymode_configure(struct ci_hdrc *ci) } } +/** + * ci_usb_phy_init: initialize phy according to different phy type + * @ci: the controller + * + * This function returns an error code if usb_phy_init has failed + */ +static int ci_usb_phy_init(struct ci_hdrc *ci) +{ + int ret; + + switch (ci->platdata->phy_mode) { + case USBPHY_INTERFACE_MODE_UTMI: + case USBPHY_INTERFACE_MODE_UTMIW: + case USBPHY_INTERFACE_MODE_HSIC: + ret = usb_phy_init(ci->transceiver); + if (ret) + return ret; + hw_phymode_configure(ci); + break; + case USBPHY_INTERFACE_MODE_ULPI: + case USBPHY_INTERFACE_MODE_SERIAL: + hw_phymode_configure(ci); + ret = usb_phy_init(ci->transceiver); + if (ret) + return ret; + break; + default: + ret = usb_phy_init(ci->transceiver); + } + + return ret; +} + /** * hw_device_reset: resets chip (execute without interruption) * @ci: the controller @@ -543,8 +576,6 @@ static int ci_hdrc_probe(struct platform_device *pdev) return -ENODEV; } - hw_phymode_configure(ci); - if (ci->platdata->phy) ci->transceiver = ci->platdata->phy; else @@ -564,7 +595,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) return -EPROBE_DEFER; } - ret = usb_phy_init(ci->transceiver); + ret = ci_usb_phy_init(ci); if (ret) { dev_err(dev, "unable to init phy: %d\n", ret); return ret; -- cgit v1.2.3-70-g09d2 From 0c33bf781a0da4bdab207ccc323c9afa940852af Mon Sep 17 00:00:00 2001 From: Li Jun Date: Wed, 23 Apr 2014 15:56:38 +0800 Subject: usb: chipidea: operate on otgsc register in a general way Use a more general way to read and write otgsc register. Signed-off-by: Peter Chen Signed-off-by: Li Jun Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 16 ++++++++++------ drivers/usb/chipidea/otg.c | 33 ++++++++++++++++++++++++--------- drivers/usb/chipidea/otg.h | 18 ++---------------- drivers/usb/chipidea/udc.c | 19 ++++++++++--------- 4 files changed, 46 insertions(+), 40 deletions(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 1cd5d0ba587..f0cfa5b64bf 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -393,7 +393,7 @@ static irqreturn_t ci_irq(int irq, void *data) u32 otgsc = 0; if (ci->is_otg) - otgsc = hw_read(ci, OP_OTGSC, ~0); + otgsc = hw_read_otgsc(ci, ~0); /* * Handle id change interrupt, it indicates device/host function @@ -401,7 +401,8 @@ static irqreturn_t ci_irq(int irq, void *data) */ if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) { ci->id_event = true; - ci_clear_otg_interrupt(ci, OTGSC_IDIS); + /* Clear ID change irq status */ + hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); disable_irq_nosync(ci->irq); queue_work(ci->wq, &ci->work); return IRQ_HANDLED; @@ -413,7 +414,8 @@ static irqreturn_t ci_irq(int irq, void *data) */ if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { ci->b_sess_valid_event = true; - ci_clear_otg_interrupt(ci, OTGSC_BSVIS); + /* Clear BSV irq */ + hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); disable_irq_nosync(ci->irq); queue_work(ci->wq, &ci->work); return IRQ_HANDLED; @@ -535,8 +537,9 @@ static void ci_get_otg_capable(struct ci_hdrc *ci) == (DCCPARAMS_DC | DCCPARAMS_HC)); if (ci->is_otg) { dev_dbg(ci->dev, "It is OTG capable controller\n"); - ci_disable_otg_interrupt(ci, OTGSC_INT_EN_BITS); - ci_clear_otg_interrupt(ci, OTGSC_INT_STATUS_BITS); + /* Disable and clear all OTG irq */ + hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, + OTGSC_INT_STATUS_BITS); } } @@ -648,7 +651,8 @@ static int ci_hdrc_probe(struct platform_device *pdev) */ mdelay(2); ci->role = ci_otg_role(ci); - ci_enable_otg_interrupt(ci, OTGSC_IDIE); + /* Enable ID change irq */ + hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); } else { /* * If the controller is not OTG capable, but support diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index 39bd7ec8bf7..c6943401a0b 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -23,14 +23,32 @@ #include "bits.h" #include "otg.h" +/** + * hw_read_otgsc returns otgsc register bits value. + * @mask: bitfield mask + */ +u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) +{ + return hw_read(ci, OP_OTGSC, mask); +} + +/** + * hw_write_otgsc updates target bits of OTGSC register. + * @mask: bitfield mask + * @data: to be written + */ +void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) +{ + hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data); +} + /** * ci_otg_role - pick role based on ID pin state * @ci: the controller */ enum ci_role ci_otg_role(struct ci_hdrc *ci) { - u32 sts = hw_read(ci, OP_OTGSC, ~0); - enum ci_role role = sts & OTGSC_ID + enum ci_role role = hw_read_otgsc(ci, OTGSC_ID) ? CI_ROLE_GADGET : CI_ROLE_HOST; @@ -39,14 +57,10 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci) void ci_handle_vbus_change(struct ci_hdrc *ci) { - u32 otgsc; - if (!ci->is_otg) return; - otgsc = hw_read(ci, OP_OTGSC, ~0); - - if (otgsc & OTGSC_BSV) + if (hw_read_otgsc(ci, OTGSC_BSV)) usb_gadget_vbus_connect(&ci->gadget); else usb_gadget_vbus_disconnect(&ci->gadget); @@ -115,6 +129,7 @@ void ci_hdrc_otg_destroy(struct ci_hdrc *ci) flush_workqueue(ci->wq); destroy_workqueue(ci->wq); } - ci_disable_otg_interrupt(ci, OTGSC_INT_EN_BITS); - ci_clear_otg_interrupt(ci, OTGSC_INT_STATUS_BITS); + /* Disable all OTG irq and clear status */ + hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, + OTGSC_INT_STATUS_BITS); } diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h index 449bee07f4f..73492675411 100644 --- a/drivers/usb/chipidea/otg.h +++ b/drivers/usb/chipidea/otg.h @@ -11,22 +11,8 @@ #ifndef __DRIVERS_USB_CHIPIDEA_OTG_H #define __DRIVERS_USB_CHIPIDEA_OTG_H -static inline void ci_clear_otg_interrupt(struct ci_hdrc *ci, u32 bits) -{ - /* Only clear request bits */ - hw_write(ci, OP_OTGSC, OTGSC_INT_STATUS_BITS, bits); -} - -static inline void ci_enable_otg_interrupt(struct ci_hdrc *ci, u32 bits) -{ - hw_write(ci, OP_OTGSC, bits | OTGSC_INT_STATUS_BITS, bits); -} - -static inline void ci_disable_otg_interrupt(struct ci_hdrc *ci, u32 bits) -{ - hw_write(ci, OP_OTGSC, bits | OTGSC_INT_STATUS_BITS, 0); -} - +u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask); +void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data); int ci_hdrc_otg_init(struct ci_hdrc *ci); void ci_hdrc_otg_destroy(struct ci_hdrc *ci); enum ci_role ci_otg_role(struct ci_hdrc *ci); diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 7739c64ef25..798943b6ef7 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1843,21 +1843,22 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) static int udc_id_switch_for_device(struct ci_hdrc *ci) { - if (ci->is_otg) { - ci_clear_otg_interrupt(ci, OTGSC_BSVIS); - ci_enable_otg_interrupt(ci, OTGSC_BSVIE); - } + if (ci->is_otg) + /* Clear and enable BSV irq */ + hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, + OTGSC_BSVIS | OTGSC_BSVIE); return 0; } static void udc_id_switch_for_host(struct ci_hdrc *ci) { - if (ci->is_otg) { - /* host doesn't care B_SESSION_VALID event */ - ci_clear_otg_interrupt(ci, OTGSC_BSVIS); - ci_disable_otg_interrupt(ci, OTGSC_BSVIE); - } + /* + * host doesn't care B_SESSION_VALID event + * so clear and disbale BSV irq + */ + if (ci->is_otg) + hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS); } /** -- cgit v1.2.3-70-g09d2 From 36304b0616280809a58ebdd69d74f7c61286f9b5 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Wed, 23 Apr 2014 15:56:39 +0800 Subject: usb: chipidea: export interrupt enable and status register read functions This patch moves usb interrupt enable and status register read functions from udc driver to core driver to use them in all ci drivers. Signed-off-by: Peter Chen Acked-by: Peter Chen Signed-off-by: Li Jun Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci.h | 4 ++++ drivers/usb/chipidea/core.c | 20 ++++++++++++++++++++ drivers/usb/chipidea/udc.c | 20 -------------------- 3 files changed, 24 insertions(+), 20 deletions(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index e206406ae1d..7ae8cb68032 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -319,6 +319,10 @@ static inline u32 hw_test_and_write(struct ci_hdrc *ci, enum ci_hw_regs reg, return (val & mask) >> __ffs(mask); } +u32 hw_read_intr_enable(struct ci_hdrc *ci); + +u32 hw_read_intr_status(struct ci_hdrc *ci); + int hw_device_reset(struct ci_hdrc *ci, u32 mode); int hw_port_test_set(struct ci_hdrc *ci, u8 mode); diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index f0cfa5b64bf..ff38cf36746 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -139,6 +139,26 @@ static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm) return 0; } +/** + * hw_read_intr_enable: returns interrupt enable register + * + * This function returns register data + */ +u32 hw_read_intr_enable(struct ci_hdrc *ci) +{ + return hw_read(ci, OP_USBINTR, ~0); +} + +/** + * hw_read_intr_status: returns interrupt status register + * + * This function returns register data + */ +u32 hw_read_intr_status(struct ci_hdrc *ci) +{ + return hw_read(ci, OP_USBSTS, ~0); +} + /** * hw_port_test_set: writes port test mode (execute without interruption) * @mode: new value diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 798943b6ef7..f58857d4505 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -241,26 +241,6 @@ static int hw_port_is_high_speed(struct ci_hdrc *ci) hw_read(ci, OP_PORTSC, PORTSC_HSP); } -/** - * hw_read_intr_enable: returns interrupt enable register - * - * This function returns register data - */ -static u32 hw_read_intr_enable(struct ci_hdrc *ci) -{ - return hw_read(ci, OP_USBINTR, ~0); -} - -/** - * hw_read_intr_status: returns interrupt status register - * - * This function returns register data - */ -static u32 hw_read_intr_status(struct ci_hdrc *ci) -{ - return hw_read(ci, OP_USBSTS, ~0); -} - /** * hw_test_and_clear_complete: test & clear complete status (execute without * interruption) -- cgit v1.2.3-70-g09d2 From 90893b90d372f9c721ab8cd64b66230670deded3 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 23 Apr 2014 15:56:41 +0800 Subject: usb: chipidea: add proper delay for waiting correct PHY status After the PHY has powered and initialized, it needs some delay for controller to reflect PHY's status. Some status and values for id, vbus, dp/dm are only stable after this delay. The current code tries to clear id/vbus status without enough delay, it causes the status are not cleared properly. This patch add 2ms delay after phy has initialized, and clear the unexpected status after that. Signed-off-by: Peter Chen Tested-by: Li Jun Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index ff38cf36746..d506f342d01 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -199,11 +199,10 @@ static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) hw_write(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm), 0); /* - * The controller needs at least 1ms to reflect - * PHY's status, the PHY also needs some time (less + * the PHY needs some time (less * than 1ms) to leave low power mode. */ - usleep_range(1500, 2000); + usleep_range(1000, 1100); } } @@ -555,12 +554,8 @@ static void ci_get_otg_capable(struct ci_hdrc *ci) ci->is_otg = (hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DC | DCCPARAMS_HC) == (DCCPARAMS_DC | DCCPARAMS_HC)); - if (ci->is_otg) { + if (ci->is_otg) dev_dbg(ci->dev, "It is OTG capable controller\n"); - /* Disable and clear all OTG irq */ - hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, - OTGSC_INT_STATUS_BITS); - } } static int ci_hdrc_probe(struct platform_device *pdev) @@ -622,6 +617,13 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) { dev_err(dev, "unable to init phy: %d\n", ret); return ret; + } else { + /* + * The delay to sync PHY's status, the maximum delay is + * 2ms since the otgsc uses 1ms timer to debounce the + * PHY's input + */ + usleep_range(2000, 2500); } ci->hw_bank.phys = res->start; @@ -656,6 +658,9 @@ static int ci_hdrc_probe(struct platform_device *pdev) } if (ci->is_otg) { + /* Disable and clear all OTG irq */ + hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, + OTGSC_INT_STATUS_BITS); ret = ci_hdrc_otg_init(ci); if (ret) { dev_err(dev, "init otg fails, ret = %d\n", ret); @@ -665,11 +670,6 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { if (ci->is_otg) { - /* - * ID pin needs 1ms debouce time, - * we delay 2ms for safe. - */ - mdelay(2); ci->role = ci_otg_role(ci); /* Enable ID change irq */ hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); -- cgit v1.2.3-70-g09d2 From 7cf2f86102b7d737130d8cf0f223b0ae6b8a3eac Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Wed, 23 Apr 2014 15:56:42 +0800 Subject: usb: chipidea: core: Add missing module owner field Signed-off-by: Alexander Shiyan Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index d506f342d01..6a6379a8f1f 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -735,6 +735,7 @@ static struct platform_driver ci_hdrc_driver = { .remove = ci_hdrc_remove, .driver = { .name = "ci_hdrc", + .owner = THIS_MODULE, }, }; -- cgit v1.2.3-70-g09d2 From 4dcf720c5d40b27c916e7115ad75b335c9c1e264 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Wed, 23 Apr 2014 15:56:50 +0800 Subject: usb: chipidea: OTG HNP and SRP fsm implementation USB OTG interrupt handling and fsm transitions according to USB OTG and EH 2.0. Signed-off-by: Peter Chen Signed-off-by: Li Jun Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 24 +++- drivers/usb/chipidea/otg.c | 9 +- drivers/usb/chipidea/otg_fsm.c | 243 +++++++++++++++++++++++++++++++++++++++++ drivers/usb/chipidea/otg_fsm.h | 18 +++ drivers/usb/chipidea/udc.c | 8 ++ 5 files changed, 294 insertions(+), 8 deletions(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 6a6379a8f1f..128b92ba58a 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -42,7 +42,6 @@ * - Not Supported: 15 & 16 (ISO) * * TODO List - * - OTG * - Interrupt Traffic * - GET_STATUS(device) - always reports 0 * - Gadget API (majority of optional features) @@ -74,6 +73,7 @@ #include "host.h" #include "debug.h" #include "otg.h" +#include "otg_fsm.h" /* Controller register map */ static const u8 ci_regs_nolpm[] = { @@ -411,8 +411,14 @@ static irqreturn_t ci_irq(int irq, void *data) irqreturn_t ret = IRQ_NONE; u32 otgsc = 0; - if (ci->is_otg) + if (ci->is_otg) { otgsc = hw_read_otgsc(ci, ~0); + if (ci_otg_is_fsm_mode(ci)) { + ret = ci_otg_fsm_irq(ci); + if (ret == IRQ_HANDLED) + return ret; + } + } /* * Handle id change interrupt, it indicates device/host function @@ -691,10 +697,13 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ci->role == CI_ROLE_GADGET) ci_handle_vbus_change(ci); - ret = ci_role_start(ci, ci->role); - if (ret) { - dev_err(dev, "can't start %s role\n", ci_role(ci)->name); - goto stop; + if (!ci_otg_is_fsm_mode(ci)) { + ret = ci_role_start(ci, ci->role); + if (ret) { + dev_err(dev, "can't start %s role\n", + ci_role(ci)->name); + goto stop; + } } platform_set_drvdata(pdev, ci); @@ -703,6 +712,9 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; + if (ci_otg_is_fsm_mode(ci)) + ci_hdrc_otg_fsm_start(ci); + ret = dbg_create_files(ci); if (!ret) return 0; diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index d76db51ecda..38e340cba1f 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -11,8 +11,8 @@ */ /* - * This file mainly handles otgsc register, it may include OTG operation - * in the future. + * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP + * are also included. */ #include @@ -91,6 +91,11 @@ static void ci_otg_work(struct work_struct *work) { struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); + if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) { + enable_irq(ci->irq); + return; + } + if (ci->id_event) { ci->id_event = false; ci_handle_id_switch(ci); diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index bb64fb486ee..67902a16cb7 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -13,6 +13,10 @@ /* * This file mainly handles OTG fsm, it includes OTG fsm operations * for HNP and SRP. + * + * TODO List + * - ADP + * - OTG test device */ #include @@ -93,6 +97,33 @@ static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) hw_write_otgsc(ci, OTGSC_1MSIE, 0); } +/* + * Reduce timer count by 1, and find timeout conditions. + * Called by otg 1ms timer interrupt + */ +static inline int ci_otg_tick_timer(struct ci_hdrc *ci) +{ + struct ci_otg_fsm_timer *tmp_timer, *del_tmp; + struct list_head *active_timers = &ci->fsm_timer->active_timers; + int expired = 0; + + list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) { + tmp_timer->count--; + /* check if timer expires */ + if (!tmp_timer->count) { + list_del(&tmp_timer->list); + tmp_timer->function(ci, tmp_timer->data); + expired = 1; + } + } + + /* disable 1ms irq if there is no any timer active */ + if ((expired == 1) && list_empty(active_timers)) + hw_write_otgsc(ci, OTGSC_1MSIE, 0); + + return expired; +} + /* The timeout callback function to set time out bit */ static void set_tmout(void *ptr, unsigned long indicator) { @@ -394,6 +425,218 @@ static struct otg_fsm_ops ci_otg_ops = { .start_gadget = ci_otg_start_gadget, }; +int ci_otg_fsm_work(struct ci_hdrc *ci) +{ + /* + * Don't do fsm transition for B device + * when there is no gadget class driver + */ + if (ci->fsm.id && !(ci->driver) && + ci->transceiver->state < OTG_STATE_A_IDLE) + return 0; + + if (otg_statemachine(&ci->fsm)) { + if (ci->transceiver->state == OTG_STATE_A_IDLE) { + /* + * Further state change for cases: + * a_idle to b_idle; or + * a_idle to a_wait_vrise due to ID change(1->0), so + * B-dev becomes A-dev can try to start new session + * consequently; or + * a_idle to a_wait_vrise when power up + */ + if ((ci->fsm.id) || (ci->id_event) || + (ci->fsm.power_up)) { + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + } + if (ci->id_event) + ci->id_event = false; + } else if (ci->transceiver->state == OTG_STATE_B_IDLE) { + if (ci->fsm.b_sess_vld) { + ci->fsm.power_up = 0; + /* + * Further transite to b_periphearl state + * when register gadget driver with vbus on + */ + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + } + } + } + return 0; +} + +/* + * Update fsm variables in each state if catching expected interrupts, + * called by otg fsm isr. + */ +static void ci_otg_fsm_event(struct ci_hdrc *ci) +{ + u32 intr_sts, otg_bsess_vld, port_conn; + struct otg_fsm *fsm = &ci->fsm; + + intr_sts = hw_read_intr_status(ci); + otg_bsess_vld = hw_read_otgsc(ci, OTGSC_BSV); + port_conn = hw_read(ci, OP_PORTSC, PORTSC_CCS); + + switch (ci->transceiver->state) { + case OTG_STATE_A_WAIT_BCON: + if (port_conn) { + fsm->b_conn = 1; + fsm->a_bus_req = 1; + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + } + break; + case OTG_STATE_B_IDLE: + if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) { + fsm->b_sess_vld = 1; + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + } + break; + case OTG_STATE_B_PERIPHERAL: + if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) { + fsm->a_bus_suspend = 1; + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + } else if (intr_sts & USBi_PCI) { + if (fsm->a_bus_suspend == 1) + fsm->a_bus_suspend = 0; + } + break; + case OTG_STATE_B_HOST: + if ((intr_sts & USBi_PCI) && !port_conn) { + fsm->a_conn = 0; + fsm->b_bus_req = 0; + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + ci_otg_add_timer(ci, B_SESS_VLD); + } + break; + case OTG_STATE_A_PERIPHERAL: + if (intr_sts & USBi_SLI) { + fsm->b_bus_suspend = 1; + /* + * Init a timer to know how long this suspend + * will contine, if time out, indicates B no longer + * wants to be host role + */ + ci_otg_add_timer(ci, A_BIDL_ADIS); + } + + if (intr_sts & USBi_URI) + ci_otg_del_timer(ci, A_BIDL_ADIS); + + if (intr_sts & USBi_PCI) { + if (fsm->b_bus_suspend == 1) { + ci_otg_del_timer(ci, A_BIDL_ADIS); + fsm->b_bus_suspend = 0; + } + } + break; + case OTG_STATE_A_SUSPEND: + if ((intr_sts & USBi_PCI) && !port_conn) { + fsm->b_conn = 0; + + /* if gadget driver is binded */ + if (ci->driver) { + /* A device to be peripheral mode */ + ci->gadget.is_a_peripheral = 1; + } + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + } + break; + case OTG_STATE_A_HOST: + if ((intr_sts & USBi_PCI) && !port_conn) { + fsm->b_conn = 0; + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + } + break; + case OTG_STATE_B_WAIT_ACON: + if ((intr_sts & USBi_PCI) && port_conn) { + fsm->a_conn = 1; + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + } + break; + default: + break; + } +} + +/* + * ci_otg_irq - otg fsm related irq handling + * and also update otg fsm variable by monitoring usb host and udc + * state change interrupts. + * @ci: ci_hdrc + */ +irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) +{ + irqreturn_t retval = IRQ_NONE; + u32 otgsc, otg_int_src = 0; + struct otg_fsm *fsm = &ci->fsm; + + otgsc = hw_read_otgsc(ci, ~0); + otg_int_src = otgsc & OTGSC_INT_STATUS_BITS & (otgsc >> 8); + fsm->id = (otgsc & OTGSC_ID) ? 1 : 0; + + if (otg_int_src) { + if (otg_int_src & OTGSC_1MSIS) { + hw_write_otgsc(ci, OTGSC_1MSIS, OTGSC_1MSIS); + retval = ci_otg_tick_timer(ci); + return IRQ_HANDLED; + } else if (otg_int_src & OTGSC_DPIS) { + hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); + fsm->a_srp_det = 1; + fsm->a_bus_drop = 0; + } else if (otg_int_src & OTGSC_IDIS) { + hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); + if (fsm->id == 0) { + fsm->a_bus_drop = 0; + fsm->a_bus_req = 1; + ci->id_event = true; + } + } else if (otg_int_src & OTGSC_BSVIS) { + hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); + if (otgsc & OTGSC_BSV) { + fsm->b_sess_vld = 1; + ci_otg_del_timer(ci, B_SSEND_SRP); + ci_otg_del_timer(ci, B_SRP_FAIL); + fsm->b_ssend_srp = 0; + } else { + fsm->b_sess_vld = 0; + if (fsm->id) + ci_otg_add_timer(ci, B_SSEND_SRP); + } + } else if (otg_int_src & OTGSC_AVVIS) { + hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS); + if (otgsc & OTGSC_AVV) { + fsm->a_vbus_vld = 1; + } else { + fsm->a_vbus_vld = 0; + fsm->b_conn = 0; + } + } + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); + return IRQ_HANDLED; + } + + ci_otg_fsm_event(ci); + + return retval; +} + +void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci) +{ + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); +} + int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) { int retval = 0; diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h index 420f081e7e6..6ec8247d817 100644 --- a/drivers/usb/chipidea/otg_fsm.h +++ b/drivers/usb/chipidea/otg_fsm.h @@ -92,6 +92,9 @@ struct ci_otg_fsm_timer_list { #ifdef CONFIG_USB_OTG_FSM int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci); +int ci_otg_fsm_work(struct ci_hdrc *ci); +irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci); +void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci); #else @@ -100,6 +103,21 @@ static inline int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) return 0; } +static inline int ci_otg_fsm_work(struct ci_hdrc *ci) +{ + return -ENXIO; +} + +static inline irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) +{ + return IRQ_NONE; +} + +static inline void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci) +{ + +} + #endif #endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index cba7fd63d6e..150592f10b3 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -28,6 +28,7 @@ #include "bits.h" #include "debug.h" #include "otg.h" +#include "otg_fsm.h" /* control endpoint description */ static const struct usb_endpoint_descriptor @@ -1644,6 +1645,13 @@ static int ci_udc_start(struct usb_gadget *gadget, return retval; ci->driver = driver; + + /* Start otg fsm for B-device */ + if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) { + ci_hdrc_otg_fsm_start(ci); + return retval; + } + pm_runtime_get_sync(&ci->gadget.dev); if (ci->vbus_active) { spin_lock_irqsave(&ci->lock, flags); -- cgit v1.2.3-70-g09d2 From 58ce8499d3a3690c8b547a0b21ca9304083cfb67 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 23 May 2014 08:12:47 +0800 Subject: usb: chipidea: update TODO list Update TODO list Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 128b92ba58a..95b4dd73177 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -23,7 +23,7 @@ * - BUS: bus glue code, bus abstraction layer * * Compile Options - * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities + * - CONFIG_USB_CHIPIDEA_DEBUG: enable debug facilities * - STALL_IN: non-empty bulk-in pipes cannot be halted * if defined mass storage compliance succeeds but with warnings * => case 4: Hi > Dn @@ -42,9 +42,6 @@ * - Not Supported: 15 & 16 (ISO) * * TODO List - * - Interrupt Traffic - * - GET_STATUS(device) - always reports 0 - * - Gadget API (majority of optional features) * - Suspend & Remote Wakeup */ #include -- cgit v1.2.3-70-g09d2 From be6b0c1bd0be7a4f4d75ab40965abf1bd2d9a591 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 23 May 2014 08:12:49 +0800 Subject: usb: chipidea: using one inline function to cover queue work operations The otg queue work include operations: one is disable interrupt, another one is call kernel queue work API. Many codes do this operation, using one inline function to instead of them. Signed-off-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/core.c | 6 ++-- drivers/usb/chipidea/otg.h | 5 +++ drivers/usb/chipidea/otg_fsm.c | 69 ++++++++++++++---------------------------- 3 files changed, 30 insertions(+), 50 deletions(-) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 95b4dd73177..619d13e2999 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -425,8 +425,7 @@ static irqreturn_t ci_irq(int irq, void *data) ci->id_event = true; /* Clear ID change irq status */ hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); return IRQ_HANDLED; } @@ -438,8 +437,7 @@ static irqreturn_t ci_irq(int irq, void *data) ci->b_sess_valid_event = true; /* Clear BSV irq */ hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); return IRQ_HANDLED; } diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h index 73492675411..9ecb598e48f 100644 --- a/drivers/usb/chipidea/otg.h +++ b/drivers/usb/chipidea/otg.h @@ -17,5 +17,10 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci); void ci_hdrc_otg_destroy(struct ci_hdrc *ci); enum ci_role ci_otg_role(struct ci_hdrc *ci); void ci_handle_vbus_change(struct ci_hdrc *ci); +static inline void ci_otg_queue_work(struct ci_hdrc *ci) +{ + disable_irq_nosync(ci->irq); + queue_work(ci->wq, &ci->work); +} #endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */ diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 8d4c33dea12..caaabc58021 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -84,8 +84,7 @@ set_a_bus_req(struct device *dev, struct device_attribute *attr, ci->fsm.a_bus_req = 1; } - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); mutex_unlock(&ci->fsm.lock); return count; @@ -125,8 +124,7 @@ set_a_bus_drop(struct device *dev, struct device_attribute *attr, ci->fsm.a_bus_req = 0; } - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); mutex_unlock(&ci->fsm.lock); return count; @@ -165,8 +163,7 @@ set_b_bus_req(struct device *dev, struct device_attribute *attr, else if (buf[0] == '1') ci->fsm.b_bus_req = 1; - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); mutex_unlock(&ci->fsm.lock); return count; @@ -186,8 +183,7 @@ set_a_clr_err(struct device *dev, struct device_attribute *attr, if (buf[0] == '1') ci->fsm.a_clr_err = 1; - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); mutex_unlock(&ci->fsm.lock); return count; @@ -297,8 +293,7 @@ static void set_tmout_and_fsm(void *ptr, unsigned long indicator) set_tmout(ci, indicator); - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } static void a_wait_vfall_tmout_func(void *ptr, unsigned long indicator) @@ -312,8 +307,7 @@ static void a_wait_vfall_tmout_func(void *ptr, unsigned long indicator) hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); /* Enable data pulse irq */ hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE); - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } static void b_ase0_brst_tmout_func(void *ptr, unsigned long indicator) @@ -324,8 +318,7 @@ static void b_ase0_brst_tmout_func(void *ptr, unsigned long indicator) if (!hw_read_otgsc(ci, OTGSC_BSV)) ci->fsm.b_sess_vld = 0; - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } static void b_ssend_srp_tmout_func(void *ptr, unsigned long indicator) @@ -335,10 +328,8 @@ static void b_ssend_srp_tmout_func(void *ptr, unsigned long indicator) set_tmout(ci, indicator); /* only vbus fall below B_sess_vld in b_idle state */ - if (ci->transceiver->state == OTG_STATE_B_IDLE) { - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); - } + if (ci->transceiver->state == OTG_STATE_B_IDLE) + ci_otg_queue_work(ci); } static void b_sess_vld_tmout_func(void *ptr, unsigned long indicator) @@ -349,8 +340,7 @@ static void b_sess_vld_tmout_func(void *ptr, unsigned long indicator) if (!(hw_read_otgsc(ci, OTGSC_BSV))) { ci->fsm.b_sess_vld = 0; ci_otg_add_timer(ci, B_SSEND_SRP); - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } } @@ -365,8 +355,7 @@ static void b_data_pulse_end(void *ptr, unsigned long indicator) hw_write_otgsc(ci, OTGSC_HABA, 0); - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } /* Initialize timers */ @@ -607,10 +596,8 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) * a_idle to a_wait_vrise when power up */ if ((ci->fsm.id) || (ci->id_event) || - (ci->fsm.power_up)) { - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); - } + (ci->fsm.power_up)) + ci_otg_queue_work(ci); if (ci->id_event) ci->id_event = false; } else if (ci->transceiver->state == OTG_STATE_B_IDLE) { @@ -620,8 +607,7 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) * Further transite to b_periphearl state * when register gadget driver with vbus on */ - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } } } @@ -646,22 +632,19 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci) if (port_conn) { fsm->b_conn = 1; fsm->a_bus_req = 1; - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } break; case OTG_STATE_B_IDLE: if (otg_bsess_vld && (intr_sts & USBi_PCI) && port_conn) { fsm->b_sess_vld = 1; - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } break; case OTG_STATE_B_PERIPHERAL: if ((intr_sts & USBi_SLI) && port_conn && otg_bsess_vld) { fsm->a_bus_suspend = 1; - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } else if (intr_sts & USBi_PCI) { if (fsm->a_bus_suspend == 1) fsm->a_bus_suspend = 0; @@ -671,8 +654,7 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci) if ((intr_sts & USBi_PCI) && !port_conn) { fsm->a_conn = 0; fsm->b_bus_req = 0; - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); ci_otg_add_timer(ci, B_SESS_VLD); } break; @@ -706,22 +688,19 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci) /* A device to be peripheral mode */ ci->gadget.is_a_peripheral = 1; } - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } break; case OTG_STATE_A_HOST: if ((intr_sts & USBi_PCI) && !port_conn) { fsm->b_conn = 0; - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } break; case OTG_STATE_B_WAIT_ACON: if ((intr_sts & USBi_PCI) && port_conn) { fsm->a_conn = 1; - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } break; default: @@ -782,8 +761,7 @@ irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) fsm->b_conn = 0; } } - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); return IRQ_HANDLED; } @@ -794,8 +772,7 @@ irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci) { - disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + ci_otg_queue_work(ci); } int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) -- cgit v1.2.3-70-g09d2