#include <linux/delay.h>
#include <linux/mfd/abx500.h>
#include <linux/mfd/abx500/ab8500.h>
+#include <linux/usb/musb-ux500.h>
#define AB8500_MAIN_WD_CTRL_REG 0x01
#define AB8500_USB_LINE_STAT_REG 0x80
+#define AB8505_USB_LINE_STAT_REG 0x94
#define AB8500_USB_PHY_CTRL_REG 0x8A
#define AB8500_BIT_OTG_STAT_ID (1 << 0)
#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 */
/* 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
+ USB_LINK_NOT_CONFIGURED_8500 = 0,
+ USB_LINK_STD_HOST_NC_8500,
+ USB_LINK_STD_HOST_C_NS_8500,
+ USB_LINK_STD_HOST_C_S_8500,
+ USB_LINK_HOST_CHG_NM_8500,
+ USB_LINK_HOST_CHG_HS_8500,
+ USB_LINK_HOST_CHG_HS_CHIRP_8500,
+ USB_LINK_DEDICATED_CHG_8500,
+ USB_LINK_ACA_RID_A_8500,
+ USB_LINK_ACA_RID_B_8500,
+ USB_LINK_ACA_RID_C_NM_8500,
+ USB_LINK_ACA_RID_C_HS_8500,
+ USB_LINK_ACA_RID_C_HS_CHIRP_8500,
+ USB_LINK_HM_IDGND_8500,
+ USB_LINK_RESERVED_8500,
+ USB_LINK_NOT_VALID_LINK_8500,
+};
+
+enum ab8505_usb_link_status {
+ USB_LINK_NOT_CONFIGURED_8505 = 0,
+ USB_LINK_STD_HOST_NC_8505,
+ USB_LINK_STD_HOST_C_NS_8505,
+ USB_LINK_STD_HOST_C_S_8505,
+ USB_LINK_CDP_8505,
+ USB_LINK_RESERVED0_8505,
+ USB_LINK_RESERVED1_8505,
+ USB_LINK_DEDICATED_CHG_8505,
+ USB_LINK_ACA_RID_A_8505,
+ USB_LINK_ACA_RID_B_8505,
+ USB_LINK_ACA_RID_C_NM_8505,
+ USB_LINK_RESERVED2_8505,
+ USB_LINK_RESERVED3_8505,
+ USB_LINK_HM_IDGND_8505,
+ USB_LINK_CHARGERPORT_NOT_OK_8505,
+ USB_LINK_CHARGER_DM_HIGH_8505,
+ USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8505,
+ USB_LINK_STD_UPSTREAM_NO_IDGNG_NO_VBUS_8505,
+ USB_LINK_STD_UPSTREAM_8505,
+ USB_LINK_CHARGER_SE1_8505,
+ USB_LINK_CARKIT_CHGR_1_8505,
+ USB_LINK_CARKIT_CHGR_2_8505,
+ USB_LINK_ACA_DOCK_CHGR_8505,
+ USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_8505,
+ USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_8505,
+ USB_LINK_SAMSUNG_UART_CBL_PHY_EN_8505,
+ USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_8505,
+ USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8505,
+};
+
+enum ab8500_usb_mode {
+ USB_IDLE = 0,
+ USB_PERIPHERAL,
+ USB_HOST,
+ USB_DEDICATED_CHG
};
struct ab8500_usb {
struct usb_phy phy;
struct device *dev;
struct ab8500 *ab8500;
- int irq_num_link_status;
unsigned vbus_draw;
struct delayed_work dwork;
struct work_struct phy_dis_work;
unsigned long link_status_wait;
+ enum ab8500_usb_mode mode;
+ int previous_link_status_state;
};
static inline struct ab8500_usb *phy_to_ab(struct usb_phy *x)
0);
}
+static void ab8500_usb_wd_linkstatus(struct ab8500_usb *ab, u8 bit)
+{
+ /* Workaround for v2.0 bug # 31952 */
+ if (is_ab8500_2p0(ab->ab8500)) {
+ abx500_mask_and_set_register_interruptible(ab->dev,
+ AB8500_USB, AB8500_USB_PHY_CTRL_REG,
+ bit, bit);
+ udelay(AB8500_V20_31952_DISABLE_DELAY_US);
+ }
+}
+
static void ab8500_usb_phy_ctrl(struct ab8500_usb *ab, bool sel_host,
bool enable)
{
#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)
+static int ab8505_usb_link_status_update(struct ab8500_usb *ab,
+ enum ab8505_usb_link_status lsts)
{
- u8 reg;
- enum ab8500_usb_link_status lsts;
- void *v = NULL;
- enum usb_phy_events event;
+ enum ux500_musb_vbus_id_status event = 0;
- abx500_get_register_interruptible(ab->dev,
- AB8500_USB,
- AB8500_USB_LINE_STAT_REG,
- ®);
+ dev_dbg(ab->dev, "ab8505_usb_link_status_update %d\n", lsts);
- lsts = (reg >> 3) & 0x0F;
+ /*
+ * Spurious link_status interrupts are seen at the time of
+ * disconnection of a device in RIDA state
+ */
+ if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8505 &&
+ (lsts == USB_LINK_STD_HOST_NC_8505))
+ return 0;
+
+ ab->previous_link_status_state = lsts;
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;
+ case USB_LINK_ACA_RID_B_8505:
+ event = UX500_MUSB_RIDB;
+ case USB_LINK_NOT_CONFIGURED_8505:
+ case USB_LINK_RESERVED0_8505:
+ case USB_LINK_RESERVED1_8505:
+ case USB_LINK_RESERVED2_8505:
+ case USB_LINK_RESERVED3_8505:
+ ab->mode = USB_IDLE;
ab->phy.otg->default_a = false;
ab->vbus_draw = 0;
- event = USB_EVENT_NONE;
+ 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_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. */
+ case USB_LINK_ACA_RID_C_NM_8505:
+ event = UX500_MUSB_RIDC;
+ case USB_LINK_STD_HOST_NC_8505:
+ case USB_LINK_STD_HOST_C_NS_8505:
+ case USB_LINK_STD_HOST_C_S_8505:
+ case USB_LINK_CDP_8505:
+ if (ab->mode == USB_IDLE) {
+ ab->mode = USB_PERIPHERAL;
ab8500_usb_peri_phy_en(ab);
- v = ab->phy.otg->gadget;
+ atomic_notifier_call_chain(&ab->phy.notifier,
+ UX500_MUSB_PREPARE, &ab->vbus_draw);
}
- event = USB_EVENT_VBUS;
+ if (event != UX500_MUSB_RIDC)
+ event = UX500_MUSB_VBUS;
break;
- case USB_LINK_HM_IDGND:
- if (ab->phy.otg->host) {
- /* TODO: Enable regulators. */
+ case USB_LINK_ACA_RID_A_8505:
+ case USB_LINK_ACA_DOCK_CHGR_8505:
+ event = UX500_MUSB_RIDA;
+ case USB_LINK_HM_IDGND_8505:
+ if (ab->mode == USB_IDLE) {
+ ab->mode = USB_HOST;
ab8500_usb_host_phy_en(ab);
- v = ab->phy.otg->host;
+ atomic_notifier_call_chain(&ab->phy.notifier,
+ UX500_MUSB_PREPARE, &ab->vbus_draw);
}
- ab->phy.state = OTG_STATE_A_IDLE;
ab->phy.otg->default_a = true;
- event = USB_EVENT_ID;
+ if (event != UX500_MUSB_RIDA)
+ event = UX500_MUSB_ID;
+ atomic_notifier_call_chain(&ab->phy.notifier,
+ event, &ab->vbus_draw);
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;
+ case USB_LINK_DEDICATED_CHG_8505:
+ ab->mode = USB_DEDICATED_CHG;
+ event = UX500_MUSB_CHARGER;
+ atomic_notifier_call_chain(&ab->phy.notifier,
+ event, &ab->vbus_draw);
+ break;
+
+ default:
break;
}
- atomic_notifier_call_chain(&ab->phy.notifier, event, v);
+ return 0;
+}
+
+static int ab8500_usb_link_status_update(struct ab8500_usb *ab,
+ enum ab8500_usb_link_status lsts)
+{
+ enum ux500_musb_vbus_id_status event = 0;
+
+ dev_dbg(ab->dev, "ab8500_usb_link_status_update %d\n", lsts);
+
+ /*
+ * 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_8500 &&
+ (lsts == USB_LINK_STD_HOST_C_NS_8500 ||
+ lsts == USB_LINK_STD_HOST_NC_8500))
+ return 0;
+
+ if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8500 &&
+ lsts == USB_LINK_STD_HOST_NC_8500)
+ return 0;
+
+ ab->previous_link_status_state = lsts;
+
+ switch (lsts) {
+ case USB_LINK_ACA_RID_B_8500:
+ event = UX500_MUSB_RIDB;
+ case USB_LINK_NOT_CONFIGURED_8500:
+ case USB_LINK_NOT_VALID_LINK_8500:
+ 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_8500:
+ case USB_LINK_ACA_RID_C_HS_8500:
+ case USB_LINK_ACA_RID_C_HS_CHIRP_8500:
+ event = UX500_MUSB_RIDC;
+ case USB_LINK_STD_HOST_NC_8500:
+ case USB_LINK_STD_HOST_C_NS_8500:
+ case USB_LINK_STD_HOST_C_S_8500:
+ case USB_LINK_HOST_CHG_NM_8500:
+ case USB_LINK_HOST_CHG_HS_8500:
+ case USB_LINK_HOST_CHG_HS_CHIRP_8500:
+ 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_8500:
+ event = UX500_MUSB_RIDA;
+ case USB_LINK_HM_IDGND_8500:
+ 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_8500:
+ ab->mode = USB_DEDICATED_CHG;
+ event = UX500_MUSB_CHARGER;
+ atomic_notifier_call_chain(&ab->phy.notifier,
+ event, &ab->vbus_draw);
+ break;
+
+ case USB_LINK_RESERVED_8500:
+ break;
+ }
return 0;
}
-static void ab8500_usb_delayed_work(struct work_struct *work)
+/*
+ * Connection Sequence:
+ * 1. Link Status Interrupt
+ * 2. Enable AB clock
+ * 3. Enable AB regulators
+ * 4. Enable USB phy
+ * 5. Reset the musb controller
+ * 6. Switch the ULPI GPIO pins to fucntion mode
+ * 7. Enable the musb Peripheral5 clock
+ * 8. Restore MUSB context
+ */
+static int abx500_usb_link_status_update(struct ab8500_usb *ab)
{
- struct ab8500_usb *ab = container_of(work, struct ab8500_usb,
- dwork.work);
+ u8 reg;
+ int ret = 0;
+
+ if (is_ab8500(ab->ab8500)) {
+ enum ab8500_usb_link_status lsts;
+
+ abx500_get_register_interruptible(ab->dev,
+ AB8500_USB, AB8500_USB_LINE_STAT_REG, ®);
+ lsts = (reg >> 3) & 0x0F;
+ ret = ab8500_usb_link_status_update(ab, lsts);
+ } else if (is_ab8505(ab->ab8500)) {
+ enum ab8505_usb_link_status lsts;
+
+ abx500_get_register_interruptible(ab->dev,
+ AB8500_USB, AB8505_USB_LINE_STAT_REG, ®);
+ lsts = (reg >> 3) & 0x1F;
+ ret = ab8505_usb_link_status_update(ab, lsts);
+ }
+
+ return ret;
+}
+
+/*
+ * Disconnection Sequence:
+ * 1. Disconect Interrupt
+ * 2. Disable regulators
+ * 3. Disable AB clock
+ * 4. Disable the Phy
+ * 5. Link Status Interrupt
+ * 6. Disable Musb Clock
+ */
+static irqreturn_t ab8500_usb_disconnect_irq(int irq, void *data)
+{
+ struct ab8500_usb *ab = (struct ab8500_usb *) data;
+ enum usb_phy_events event = UX500_MUSB_NONE;
+
+ /* Link status will not be updated till phy is disabled. */
+ 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;
+ }
+
+ if (is_ab8500_2p0(ab->ab8500)) {
+ if (ab->mode == USB_DEDICATED_CHG) {
+ ab8500_usb_wd_linkstatus(ab,
+ AB8500_BIT_PHY_CTRL_DEVICE_EN);
+ abx500_mask_and_set_register_interruptible(ab->dev,
+ AB8500_USB, AB8500_USB_PHY_CTRL_REG,
+ AB8500_BIT_PHY_CTRL_DEVICE_EN, 0);
+ }
+ }
- ab8500_usb_link_status_update(ab);
+ return IRQ_HANDLED;
}
-static irqreturn_t ab8500_usb_v20_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;
- ab8500_usb_link_status_update(ab);
+ abx500_usb_link_status_update(ab);
return IRQ_HANDLED;
}
+static void ab8500_usb_delayed_work(struct work_struct *work)
+{
+ struct ab8500_usb *ab = container_of(work, struct ab8500_usb,
+ dwork.work);
+
+ abx500_usb_link_status_update(ab);
+}
+
static void ab8500_usb_phy_disable_work(struct work_struct *work)
{
struct ab8500_usb *ab = container_of(work, struct ab8500_usb,
if (mA)
atomic_notifier_call_chain(&ab->phy.notifier,
- USB_EVENT_ENUMERATED, ab->phy.otg->gadget);
+ UX500_MUSB_ENUMERATED, ab->phy.otg->gadget);
return 0;
}
return 0;
}
-static void ab8500_usb_irq_free(struct ab8500_usb *ab)
-{
- free_irq(ab->irq_num_link_status, ab);
-}
-
-static int ab8500_usb_v2_res_setup(struct platform_device *pdev,
- struct ab8500_usb *ab)
+static int ab8500_usb_irq_setup(struct platform_device *pdev,
+ struct ab8500_usb *ab)
{
int err;
+ int irq;
- ab->irq_num_link_status = platform_get_irq_byname(pdev,
- "USB_LINK_STATUS");
- if (ab->irq_num_link_status < 0) {
+ irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS");
+ if (irq < 0) {
dev_err(&pdev->dev, "Link status irq not found\n");
- return ab->irq_num_link_status;
+ 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;
}
- err = request_threaded_irq(ab->irq_num_link_status, NULL,
- ab8500_usb_v20_irq,
- IRQF_NO_SUSPEND | IRQF_SHARED,
- "usb-link-status", ab);
+ 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 link status irq\n");
+ 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;
}
/* all: Disable phy when called from set_host and set_peripheral */
INIT_WORK(&ab->phy_dis_work, ab8500_usb_phy_disable_work);
- err = ab8500_usb_v2_res_setup(pdev, ab);
+ err = ab8500_usb_irq_setup(pdev, ab);
if (err < 0)
- goto fail0;
+ goto fail;
err = usb_add_phy(&ab->phy, USB_PHY_TYPE_USB2);
if (err) {
dev_err(&pdev->dev, "Can't register transceiver\n");
- goto fail1;
+ goto fail;
}
+ /* Needed to enable ID detection. */
+ ab8500_usb_wd_workaround(ab);
+
dev_info(&pdev->dev, "revision 0x%2x driver initialized\n", rev);
return 0;
-fail1:
- ab8500_usb_irq_free(ab);
-fail0:
+fail:
kfree(otg);
kfree(ab);
return err;
{
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);