diff options
author | Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com> | 2011-04-08 17:41:52 +0200 |
---|---|---|
committer | Philippe Langlais <philippe.langlais@linaro.org> | 2011-07-22 15:40:30 +0200 |
commit | 366a10cf28e7cf3bde3abca6568320c7d4296b12 (patch) | |
tree | 6da77be99db8cca542c48480abe1b8aa12799afa | |
parent | 5ac3ea1cfcf883831904336d660be134fe6150a1 (diff) |
usb: otg: ab8500-usb: let controller driver manage state
Changes to the state made here conflicts with the changes made within the usb
controller driver
usb: otg: ab8500-usb: split phy_control into separate enable and disable functions
Preparing to add clocks, regulators and qos control
usb: otg: ab8500-usb: update interrupt registrations and handlers
Ab8500 v1 and v2 both do not update link status unless phy is disabled. This
patch adds host disconnect and peripheral disconnect interrupts for v2 as well.
Interrupts handlers are thus renamed to reflect this change. Interrupts setup
functions are combined into one as now there are common interrupts for v1 and
v2.
usb: otg: ab8500-usb: only disable previously enabled phy
Keep track of the transceiver mode only disable the mode which was previously
enabled.
usb: otg: ab8500-usb: add transceiver clock control
usb: otg: ab8500-usb: add power supply control
usb: otg: ab8500-usb: add platform quality of service hooks
usb: otg: ab8500-usb: update log message and add chip revision
usb: otg: ab8500-usb: workaround for multiple connection detection
This patch implements fix for ab8500 chip v2 bug # 31952
usb: otg: ab8500-usb: return correct error status in irq setup
Rreturn correct error status if platform_get_irq_byname() fails
Reported-by: Philippe Langlais <philippe.langlais@stericsson.com>
usb: otg: ab8500-usb: fix unbalanced clock and regulator disable warnings
To prevent clock and regulator frameworks from complaining, only disable the
host or peripheral phy if they were enabled.
Reported-by: Sakethram Bommisetti <sakethram.bommisetti@stericsson.com>
Signed-off-by: Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>
-rw-r--r-- | drivers/usb/otg/ab8500-usb.c | 395 |
1 files changed, 271 insertions, 124 deletions
diff --git a/drivers/usb/otg/ab8500-usb.c b/drivers/usb/otg/ab8500-usb.c index 1e2adcd1f92..a3b23567b36 100644 --- a/drivers/usb/otg/ab8500-usb.c +++ b/drivers/usb/otg/ab8500-usb.c @@ -29,22 +29,29 @@ #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/ab8500.h> +#include <linux/regulator/consumer.h> +#include <mach/prcmu-fw-api.h> #define AB8500_MAIN_WD_CTRL_REG 0x01 #define AB8500_USB_LINE_STAT_REG 0x80 #define AB8500_USB_PHY_CTRL_REG 0x8A +#define AB8500_VBUS_CTRL_REG 0x82 #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_VBUS_ENABLE (1 << 0) #define AB8500_V1x_LINK_STAT_WAIT (HZ/10) #define AB8500_WD_KICK_DELAY_US 100 /* usec */ #define AB8500_WD_V11_DISABLE_DELAY_US 100 /* usec */ +#define AB8500_V20_31952_DISABLE_DELAY_US 100 /* usec */ #define AB8500_WD_V10_DISABLE_DELAY_MS 100 /* ms */ /* Usb line status register */ @@ -67,6 +74,12 @@ enum ab8500_usb_link_status { USB_LINK_NOT_VALID_LINK }; +enum ab8500_usb_mode { + USB_IDLE = 0, + USB_PERIPHERAL, + USB_HOST +}; + struct ab8500_usb { struct otg_transceiver otg; struct device *dev; @@ -80,6 +93,11 @@ struct ab8500_usb { struct work_struct phy_dis_work; unsigned long link_status_wait; int rev; + enum ab8500_usb_mode mode; + struct clk *sysclk; + struct regulator *v_ape; + struct regulator *v_musb; + struct regulator *v_ulpi; }; static inline struct ab8500_usb *xceiv_to_ab(struct otg_transceiver *x) @@ -113,40 +131,93 @@ static void ab8500_usb_wd_workaround(struct ab8500_usb *ab) 0); } -static void ab8500_usb_phy_ctrl(struct ab8500_usb *ab, bool sel_host, +static void ab8500_usb_regulator_ctrl(struct ab8500_usb *ab, bool sel_host, bool enable) { - u8 ctrl_reg; - abx500_get_register_interruptible(ab->dev, - AB8500_USB, - AB8500_USB_PHY_CTRL_REG, - &ctrl_reg); - if (sel_host) { - if (enable) - ctrl_reg |= AB8500_BIT_PHY_CTRL_HOST_EN; - else - ctrl_reg &= ~AB8500_BIT_PHY_CTRL_HOST_EN; + if (enable) { + regulator_enable(ab->v_ape); + regulator_enable(ab->v_ulpi); + regulator_enable(ab->v_musb); + + if (sel_host && (ab->rev < 0x20)) + /* Enable v-usb */ + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_REGU_CTRL1, + AB8500_VBUS_CTRL_REG, + AB8500_BIT_VBUS_ENABLE, + AB8500_BIT_VBUS_ENABLE); } else { - if (enable) - ctrl_reg |= AB8500_BIT_PHY_CTRL_DEVICE_EN; - else - ctrl_reg &= ~AB8500_BIT_PHY_CTRL_DEVICE_EN; + regulator_disable(ab->v_musb); + regulator_disable(ab->v_ulpi); + regulator_disable(ab->v_ape); + + if (sel_host && (ab->rev < 0x20)) + /* Disable v-usb */ + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_REGU_CTRL1, + AB8500_VBUS_CTRL_REG, + AB8500_BIT_VBUS_ENABLE, + 0); } +} - abx500_set_register_interruptible(ab->dev, +static void ab8500_usb_phy_enable(struct ab8500_usb *ab, bool sel_host) +{ + u8 bit; + bit = sel_host ? AB8500_BIT_PHY_CTRL_HOST_EN : + AB8500_BIT_PHY_CTRL_DEVICE_EN; + + clk_enable(ab->sysclk); + + ab8500_usb_regulator_ctrl(ab, sel_host, true); + + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, + (char *)dev_name(ab->dev), 100); + + abx500_mask_and_set_register_interruptible(ab->dev, AB8500_USB, AB8500_USB_PHY_CTRL_REG, - ctrl_reg); + bit, + bit); /* Needed to enable the phy.*/ - if (enable) - ab8500_usb_wd_workaround(ab); + ab8500_usb_wd_workaround(ab); +} + +static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) +{ + u8 bit; + bit = sel_host ? AB8500_BIT_PHY_CTRL_HOST_EN : + AB8500_BIT_PHY_CTRL_DEVICE_EN; + + /* Wrokaround for v2.0 bug # 31952 */ + if (ab->rev == 0x20) { + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, + AB8500_USB_PHY_CTRL_REG, + bit, + bit); + udelay(AB8500_V20_31952_DISABLE_DELAY_US); + } + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, + AB8500_USB_PHY_CTRL_REG, + bit, + 0); + + clk_disable(ab->sysclk); + + ab8500_usb_regulator_ctrl(ab, sel_host, false); + + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, + (char *)dev_name(ab->dev), 50); } -#define ab8500_usb_host_phy_en(ab) ab8500_usb_phy_ctrl(ab, true, true) -#define ab8500_usb_host_phy_dis(ab) ab8500_usb_phy_ctrl(ab, true, false) -#define ab8500_usb_peri_phy_en(ab) ab8500_usb_phy_ctrl(ab, false, true) -#define ab8500_usb_peri_phy_dis(ab) ab8500_usb_phy_ctrl(ab, false, false) +#define ab8500_usb_host_phy_en(ab) ab8500_usb_phy_enable(ab, true) +#define ab8500_usb_host_phy_dis(ab) ab8500_usb_phy_disable(ab, true) +#define ab8500_usb_peri_phy_en(ab) ab8500_usb_phy_enable(ab, false) +#define ab8500_usb_peri_phy_dis(ab) ab8500_usb_phy_disable(ab, false) static int ab8500_usb_link_status_update(struct ab8500_usb *ab) { @@ -166,10 +237,11 @@ static int ab8500_usb_link_status_update(struct ab8500_usb *ab) case USB_LINK_NOT_CONFIGURED: case USB_LINK_RESERVED: case USB_LINK_NOT_VALID_LINK: - /* TODO: Disable regulators. */ - ab8500_usb_host_phy_dis(ab); - ab8500_usb_peri_phy_dis(ab); - ab->otg.state = OTG_STATE_B_IDLE; + if (ab->mode == USB_HOST) + ab8500_usb_host_phy_dis(ab); + else if (ab->mode == USB_PERIPHERAL) + ab8500_usb_peri_phy_dis(ab); + ab->mode = USB_IDLE; ab->otg.default_a = false; ab->vbus_draw = 0; event = USB_EVENT_NONE; @@ -182,8 +254,8 @@ static int ab8500_usb_link_status_update(struct ab8500_usb *ab) case USB_LINK_HOST_CHG_HS: case USB_LINK_HOST_CHG_HS_CHIRP: if (ab->otg.gadget) { - /* TODO: Enable regulators. */ ab8500_usb_peri_phy_en(ab); + ab->mode = USB_PERIPHERAL; v = ab->otg.gadget; } event = USB_EVENT_VBUS; @@ -191,11 +263,10 @@ static int ab8500_usb_link_status_update(struct ab8500_usb *ab) case USB_LINK_HM_IDGND: if (ab->otg.host) { - /* TODO: Enable regulators. */ ab8500_usb_host_phy_en(ab); + ab->mode = USB_HOST; v = ab->otg.host; } - ab->otg.state = OTG_STATE_A_IDLE; ab->otg.default_a = true; event = USB_EVENT_ID; break; @@ -225,7 +296,7 @@ static void ab8500_usb_delayed_work(struct work_struct *work) ab8500_usb_link_status_update(ab); } -static irqreturn_t ab8500_usb_v1x_common_irq(int irq, void *data) +static irqreturn_t ab8500_usb_v1x_connect_irq(int irq, void *data) { struct ab8500_usb *ab = (struct ab8500_usb *) data; @@ -235,20 +306,25 @@ static irqreturn_t ab8500_usb_v1x_common_irq(int irq, void *data) return IRQ_HANDLED; } -static irqreturn_t ab8500_usb_v1x_vbus_fall_irq(int irq, void *data) +static irqreturn_t ab8500_usb_disconnect_irq(int irq, void *data) { struct ab8500_usb *ab = (struct ab8500_usb *) data; /* Link status will not be updated till phy is disabled. */ - ab8500_usb_peri_phy_dis(ab); + if (ab->mode == USB_HOST) + ab8500_usb_host_phy_dis(ab); + else if (ab->mode == USB_PERIPHERAL) + ab8500_usb_peri_phy_dis(ab); + ab->mode = USB_IDLE; - /* Wait for link status to become stable. */ - schedule_delayed_work(&ab->dwork, ab->link_status_wait); + if (ab->rev < 0x20) + /* Wait for link status to become stable. */ + schedule_delayed_work(&ab->dwork, ab->link_status_wait); return IRQ_HANDLED; } -static irqreturn_t ab8500_usb_v20_irq(int irq, void *data) +static irqreturn_t ab8500_usb_v20_link_status_irq(int irq, void *data) { struct ab8500_usb *ab = (struct ab8500_usb *) data; @@ -312,12 +388,10 @@ static int ab8500_usb_set_peripheral(struct otg_transceiver *otg, */ if (!gadget) { - /* TODO: Disable regulators. */ ab->otg.gadget = NULL; schedule_work(&ab->phy_dis_work); } else { ab->otg.gadget = gadget; - ab->otg.state = OTG_STATE_B_IDLE; /* Phy will not be enabled if cable is already * plugged-in. Schedule to enable phy. @@ -345,7 +419,6 @@ static int ab8500_usb_set_host(struct otg_transceiver *otg, */ if (!host) { - /* TODO: Disable regulators. */ ab->otg.host = NULL; schedule_work(&ab->phy_dis_work); } else { @@ -360,113 +433,166 @@ static int ab8500_usb_set_host(struct otg_transceiver *otg, return 0; } +static void ab8500_usb_regulator_put(struct ab8500_usb *ab) +{ + + if (ab->v_ape) + regulator_put(ab->v_ape); + + if (ab->v_ulpi) + regulator_put(ab->v_ulpi); + + if (ab->v_musb) + regulator_put(ab->v_musb); +} + +static int ab8500_usb_regulator_get(struct ab8500_usb *ab) +{ + int err; + + ab->v_ape = regulator_get(ab->dev, "v-ape"); + if (IS_ERR(ab->v_ape)) { + dev_err(ab->dev, "Could not get v-ape supply\n"); + err = PTR_ERR(ab->v_ape); + goto reg_error; + } + + ab->v_ulpi = regulator_get(ab->dev, "vddulpivio18"); + if (IS_ERR(ab->v_ulpi)) { + dev_err(ab->dev, "Could not get vddulpivio18 supply\n"); + err = PTR_ERR(ab->v_ulpi); + goto reg_error; + } + + ab->v_musb = regulator_get(ab->dev, "musb_1v8"); + if (IS_ERR(ab->v_musb)) { + dev_err(ab->dev, "Could not get musb_1v8 supply\n"); + err = PTR_ERR(ab->v_musb); + goto reg_error; + } + + return 0; + +reg_error: + ab8500_usb_regulator_put(ab); + return err; +} + static void ab8500_usb_irq_free(struct ab8500_usb *ab) { - if (ab->rev < 0x20) { + 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); - } else { + + if (ab->irq_num_link_status) free_irq(ab->irq_num_link_status, ab); - } } -static int ab8500_usb_v1x_res_setup(struct platform_device *pdev, +static int ab8500_usb_irq_setup(struct platform_device *pdev, struct ab8500_usb *ab) { int err; + int irq; - ab->irq_num_id_rise = platform_get_irq_byname(pdev, "ID_WAKEUP_R"); - if (ab->irq_num_id_rise < 0) { - dev_err(&pdev->dev, "ID rise irq not found\n"); - return ab->irq_num_id_rise; - } - err = request_threaded_irq(ab->irq_num_id_rise, NULL, - ab8500_usb_v1x_common_irq, - IRQF_NO_SUSPEND | IRQF_SHARED, - "usb-id-rise", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for ID rise irq\n"); - goto fail0; + if (ab->rev < 0x20) { + irq = platform_get_irq_byname(pdev, "ID_WAKEUP_R"); + if (irq < 0) { + err = irq; + dev_err(&pdev->dev, "ID rise irq not found\n"); + goto irq_fail; + } + err = request_threaded_irq(irq, NULL, + ab8500_usb_v1x_connect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-id-rise", ab); + if (err < 0) { + dev_err(ab->dev, + "request_irq failed for ID rise irq\n"); + goto irq_fail; + } + ab->irq_num_id_rise = irq; + + irq = platform_get_irq_byname(pdev, "VBUS_DET_R"); + if (irq < 0) { + err = irq; + dev_err(&pdev->dev, "VBUS rise irq not found\n"); + goto irq_fail; + } + err = request_threaded_irq(irq, NULL, + ab8500_usb_v1x_connect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-vbus-rise", ab); + if (err < 0) { + dev_err(ab->dev, + "request_irq failed for Vbus rise irq\n"); + goto irq_fail; + } + ab->irq_num_vbus_rise = irq; + }else { /* 0x20 */ + irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS"); + if (irq < 0) { + err = irq; + dev_err(&pdev->dev, "Link status irq not found\n"); + goto irq_fail; + } + + err = request_threaded_irq(irq, NULL, + ab8500_usb_v20_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; + } + ab->irq_num_link_status = irq; } - ab->irq_num_id_fall = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); - if (ab->irq_num_id_fall < 0) { + irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); + if (irq < 0) { + err = irq; dev_err(&pdev->dev, "ID fall irq not found\n"); return ab->irq_num_id_fall; } - err = request_threaded_irq(ab->irq_num_id_fall, NULL, - ab8500_usb_v1x_common_irq, + err = request_threaded_irq(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"); - goto fail1; - } - - ab->irq_num_vbus_rise = platform_get_irq_byname(pdev, "VBUS_DET_R"); - if (ab->irq_num_vbus_rise < 0) { - dev_err(&pdev->dev, "VBUS rise irq not found\n"); - return ab->irq_num_vbus_rise; - } - err = request_threaded_irq(ab->irq_num_vbus_rise, NULL, - ab8500_usb_v1x_common_irq, - IRQF_NO_SUSPEND | IRQF_SHARED, - "usb-vbus-rise", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for Vbus rise irq\n"); - goto fail2; + goto irq_fail; } + ab->irq_num_id_fall = irq; - ab->irq_num_vbus_fall = platform_get_irq_byname(pdev, "VBUS_DET_F"); - if (ab->irq_num_vbus_fall < 0) { + irq = platform_get_irq_byname(pdev, "VBUS_DET_F"); + if (irq < 0) { + err = irq; dev_err(&pdev->dev, "VBUS fall irq not found\n"); - return ab->irq_num_vbus_fall; + goto irq_fail; } - err = request_threaded_irq(ab->irq_num_vbus_fall, NULL, - ab8500_usb_v1x_vbus_fall_irq, + err = request_threaded_irq(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"); - goto fail3; + goto irq_fail; } + ab->irq_num_vbus_fall = irq; return 0; -fail3: - free_irq(ab->irq_num_vbus_rise, ab); -fail2: - free_irq(ab->irq_num_id_fall, ab); -fail1: - free_irq(ab->irq_num_id_rise, ab); -fail0: - return err; -} - -static int ab8500_usb_v2_res_setup(struct platform_device *pdev, - struct ab8500_usb *ab) -{ - int err; - - ab->irq_num_link_status = platform_get_irq_byname(pdev, - "USB_LINK_STATUS"); - if (ab->irq_num_link_status < 0) { - dev_err(&pdev->dev, "Link status irq not found\n"); - return ab->irq_num_link_status; - } - err = request_threaded_irq(ab->irq_num_link_status, NULL, - ab8500_usb_v20_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; - } - - return 0; +irq_fail: + ab8500_usb_irq_free(ab); + return err; } static int __devinit ab8500_usb_probe(struct platform_device *pdev) @@ -492,7 +618,7 @@ static int __devinit ab8500_usb_probe(struct platform_device *pdev) ab->rev = rev; ab->otg.dev = ab->dev; ab->otg.label = "ab8500"; - ab->otg.state = OTG_STATE_UNDEFINED; + ab->otg.state = OTG_STATE_B_IDLE; ab->otg.set_host = ab8500_usb_set_host; ab->otg.set_peripheral = ab8500_usb_set_peripheral; ab->otg.set_suspend = ab8500_usb_set_suspend; @@ -510,29 +636,44 @@ static int __devinit 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); - if (ab->rev < 0x20) { - err = ab8500_usb_v1x_res_setup(pdev, ab); - ab->link_status_wait = AB8500_V1x_LINK_STAT_WAIT; - } else { - err = ab8500_usb_v2_res_setup(pdev, ab); + err = ab8500_usb_regulator_get(ab); + if (err) + goto fail0; + + ab->sysclk = clk_get(ab->dev, "sysclk"); + if (IS_ERR(ab->sysclk)) { + err = PTR_ERR(ab->sysclk); + goto fail1; } + if (ab->rev < 0x20) + ab->link_status_wait = AB8500_V1x_LINK_STAT_WAIT; + + err = ab8500_usb_irq_setup(pdev, ab); if (err < 0) - goto fail0; + goto fail2; err = otg_set_transceiver(&ab->otg); if (err) { dev_err(&pdev->dev, "Can't register transceiver\n"); - goto fail1; + goto fail3; } /* Needed to enable ID detection. */ ab8500_usb_wd_workaround(ab); - dev_info(&pdev->dev, "AB8500 usb driver initialized\n"); + + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, + (char *)dev_name(ab->dev), 50); + + dev_info(&pdev->dev, "revision 0x%2x driver initialized\n", ab->rev); return 0; -fail1: +fail3: ab8500_usb_irq_free(ab); +fail2: + clk_put(ab->sysclk); +fail1: + ab8500_usb_regulator_put(ab); fail0: kfree(ab); return err; @@ -550,8 +691,14 @@ static int __devexit ab8500_usb_remove(struct platform_device *pdev) otg_set_transceiver(NULL); - ab8500_usb_host_phy_dis(ab); - ab8500_usb_peri_phy_dis(ab); + if (ab->mode == USB_HOST) + ab8500_usb_host_phy_dis(ab); + else if (ab->mode == USB_PERIPHERAL) + ab8500_usb_peri_phy_dis(ab); + + clk_put(ab->sysclk); + + ab8500_usb_regulator_put(ab); platform_set_drvdata(pdev, NULL); |