/* * drivers/usb/otg/ab8500_usb.c * * USB transceiver driver for AB8500 chip * * Copyright (C) 2010 ST-Ericsson AB * Mian Yousaf Kaukab * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include #include #include #include #include #include #include #define AB8500_MAIN_WD_CTRL_REG 0x01 #define AB8500_USB_LINE_STAT_REG 0x80 #define AB8500_USB_PHY_CTRL_REG 0x8A #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_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_WD_V10_DISABLE_DELAY_MS 100 /* ms */ /* Usb line status register */ enum ab8500_usb_link_status { USB_LINK_NOT_CONFIGURED = 0, 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_RESERVED, USB_LINK_NOT_VALID_LINK }; struct ab8500_usb { struct usb_phy phy; 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; }; static inline struct ab8500_usb *phy_to_ab(struct usb_phy *x) { return container_of(x, struct ab8500_usb, phy); } static void ab8500_usb_wd_workaround(struct ab8500_usb *ab) { abx500_set_register_interruptible(ab->dev, AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, AB8500_BIT_WD_CTRL_ENABLE); udelay(AB8500_WD_KICK_DELAY_US); 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)); if (ab->rev > 0x10) /* v1.1 v2.0 */ udelay(AB8500_WD_V11_DISABLE_DELAY_US); else /* v1.0 */ msleep(AB8500_WD_V10_DISABLE_DELAY_MS); abx500_set_register_interruptible(ab->dev, AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, 0); } static void ab8500_usb_phy_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; } else { if (enable) ctrl_reg |= AB8500_BIT_PHY_CTRL_DEVICE_EN; else ctrl_reg &= ~AB8500_BIT_PHY_CTRL_DEVICE_EN; } abx500_set_register_interruptible(ab->dev, AB8500_USB, AB8500_USB_PHY_CTRL_REG, ctrl_reg); /* Needed to enable the phy.*/ if (enable) ab8500_usb_wd_workaround(ab); } #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) static int ab8500_usb_link_status_update(struct ab8500_usb *ab) { u8 reg; enum ab8500_usb_link_status lsts; void *v = NULL; enum usb_phy_events event; abx500_get_register_interruptible(ab->dev, AB8500_USB, AB8500_USB_LINE_STAT_REG, ®); lsts = (reg >> 3) & 0x0F; switch (lsts) { 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->phy.state = OTG_STATE_B_IDLE; ab->phy.otg->default_a = false; ab->vbus_draw = 0; event = USB_EVENT_NONE; break; 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->phy.otg->gadget) { /* TODO: Enable regulators. */ ab8500_usb_peri_phy_en(ab); v = ab->phy.otg->gadget; } event = USB_EVENT_VBUS; break; case USB_LINK_HM_IDGND: if (ab->phy.otg->host) { /* TODO: Enable regulators. */ ab8500_usb_host_phy_en(ab); v = ab->phy.otg->host; } ab->phy.state = OTG_STATE_A_IDLE; ab->phy.otg->default_a = true; event = USB_EVENT_ID; break; case USB_LINK_ACA_RID_A: case USB_LINK_ACA_RID_B: /* TODO */ case USB_LINK_ACA_RID_C_NM: case USB_LINK_ACA_RID_C_HS: case USB_LINK_ACA_RID_C_HS_CHIRP: case USB_LINK_DEDICATED_CHG: /* TODO: vbus_draw */ event = USB_EVENT_CHARGER; break; } atomic_notifier_call_chain(&ab->phy.notifier, event, v); return 0; } static void ab8500_usb_delayed_work(struct work_struct *work) { struct ab8500_usb *ab = container_of(work, struct ab8500_usb, dwork.work); ab8500_usb_link_status_update(ab); } static irqreturn_t ab8500_usb_v1x_common_irq(int irq, void *data) { struct ab8500_usb *ab = (struct ab8500_usb *) data; /* Wait for link status to become stable. */ schedule_delayed_work(&ab->dwork, ab->link_status_wait); return IRQ_HANDLED; } static irqreturn_t ab8500_usb_v1x_vbus_fall_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); /* 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) { struct ab8500_usb *ab = (struct ab8500_usb *) data; ab8500_usb_link_status_update(ab); return IRQ_HANDLED; } static void ab8500_usb_phy_disable_work(struct work_struct *work) { struct ab8500_usb *ab = container_of(work, struct ab8500_usb, phy_dis_work); if (!ab->phy.otg->host) ab8500_usb_host_phy_dis(ab); if (!ab->phy.otg->gadget) ab8500_usb_peri_phy_dis(ab); } static int ab8500_usb_set_power(struct usb_phy *phy, unsigned mA) { struct ab8500_usb *ab; if (!phy) return -ENODEV; ab = phy_to_ab(phy); ab->vbus_draw = mA; if (mA) atomic_notifier_call_chain(&ab->phy.notifier, USB_EVENT_ENUMERATED, ab->phy.otg->gadget); return 0; } /* TODO: Implement some way for charging or other drivers to read * ab->vbus_draw. */ static int ab8500_usb_set_suspend(struct usb_phy *x, int suspend) { /* TODO */ return 0; } static int ab8500_usb_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget) { struct ab8500_usb *ab; if (!otg) return -ENODEV; ab = phy_to_ab(otg->phy); /* Some drivers call this function in atomic context. * Do not update ab8500 registers directly till this * is fixed. */ if (!gadget) { /* TODO: Disable regulators. */ otg->gadget = NULL; schedule_work(&ab->phy_dis_work); } else { otg->gadget = gadget; otg->phy->state = OTG_STATE_B_IDLE; /* 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 ab8500_usb_set_host(struct usb_otg *otg, struct usb_bus *host) { struct ab8500_usb *ab; if (!otg) return -ENODEV; ab = phy_to_ab(otg->phy); /* Some drivers call this function in atomic context. * Do not update ab8500 registers directly till this * is fixed. */ if (!host) { /* TODO: Disable regulators. */ otg->host = NULL; schedule_work(&ab->phy_dis_work); } else { 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 void ab8500_usb_irq_free(struct ab8500_usb *ab) { if (ab->rev < 0x20) { free_irq(ab->irq_num_id_rise, ab); free_irq(ab->irq_num_id_fall, ab); free_irq(ab->irq_num_vbus_rise, ab); free_irq(ab->irq_num_vbus_fall, ab); } else { free_irq(ab->irq_num_link_status, ab); } } static int ab8500_usb_v1x_res_setup(struct platform_device *pdev, struct ab8500_usb *ab) { int err; 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; } ab->irq_num_id_fall = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); if (ab->irq_num_id_fall < 0) { 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, 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; } ab->irq_num_vbus_fall = platform_get_irq_byname(pdev, "VBUS_DET_F"); if (ab->irq_num_vbus_fall < 0) { dev_err(&pdev->dev, "VBUS fall irq not found\n"); return ab->irq_num_vbus_fall; } err = request_threaded_irq(ab->irq_num_vbus_fall, NULL, ab8500_usb_v1x_vbus_fall_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; } 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; } static int ab8500_usb_probe(struct platform_device *pdev) { struct ab8500_usb *ab; struct usb_otg *otg; int err; int rev; rev = abx500_get_chip_id(&pdev->dev); if (rev < 0) { dev_err(&pdev->dev, "Chip id read failed\n"); return rev; } else if (rev < 0x10) { dev_err(&pdev->dev, "Unsupported AB8500 chip\n"); return -ENODEV; } ab = kzalloc(sizeof *ab, GFP_KERNEL); if (!ab) return -ENOMEM; otg = kzalloc(sizeof *otg, GFP_KERNEL); if (!otg) { kfree(ab); return -ENOMEM; } ab->dev = &pdev->dev; ab->rev = rev; ab->phy.dev = ab->dev; ab->phy.otg = otg; ab->phy.label = "ab8500"; ab->phy.set_suspend = ab8500_usb_set_suspend; ab->phy.set_power = ab8500_usb_set_power; ab->phy.state = OTG_STATE_UNDEFINED; otg->phy = &ab->phy; otg->set_host = ab8500_usb_set_host; otg->set_peripheral = ab8500_usb_set_peripheral; platform_set_drvdata(pdev, ab); ATOMIC_INIT_NOTIFIER_HEAD(&ab->phy.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, ab8500_usb_delayed_work); /* 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); } if (err < 0) goto fail0; err = usb_add_phy(&ab->phy, USB_PHY_TYPE_USB2); if (err) { dev_err(&pdev->dev, "Can't register transceiver\n"); goto fail1; } dev_info(&pdev->dev, "AB8500 usb driver initialized\n"); return 0; fail1: ab8500_usb_irq_free(ab); fail0: kfree(otg); kfree(ab); return err; } static int __devexit ab8500_usb_remove(struct platform_device *pdev) { struct ab8500_usb *ab = platform_get_drvdata(pdev); ab8500_usb_irq_free(ab); cancel_delayed_work_sync(&ab->dwork); cancel_work_sync(&ab->phy_dis_work); usb_remove_phy(&ab->phy); ab8500_usb_host_phy_dis(ab); ab8500_usb_peri_phy_dis(ab); platform_set_drvdata(pdev, NULL); kfree(ab->phy.otg); kfree(ab); return 0; } static struct platform_driver ab8500_usb_driver = { .probe = ab8500_usb_probe, .remove = ab8500_usb_remove, .driver = { .name = "ab8500-usb", .owner = THIS_MODULE, }, }; static int __init ab8500_usb_init(void) { return platform_driver_register(&ab8500_usb_driver); } subsys_initcall(ab8500_usb_init); static void __exit ab8500_usb_exit(void) { platform_driver_unregister(&ab8500_usb_driver); } module_exit(ab8500_usb_exit); MODULE_ALIAS("platform:ab8500_usb"); MODULE_AUTHOR("ST-Ericsson AB"); MODULE_DESCRIPTION("AB8500 usb transceiver driver"); MODULE_LICENSE("GPL");