Merge tag 'rxrpc-20140126' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowells...
[firefly-linux-kernel-4.4.55.git] / drivers / power / isp1704_charger.c
index fc04d191579bd987ba0ad94ddd6b298c329c2b18..80edb7d8cb547702df7b364f087f25aade3729d8 100644 (file)
@@ -2,6 +2,7 @@
  * ISP1704 USB Charger Detection driver
  *
  * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2012 - 2013 Pali Rohár <pali.rohar@gmail.com>
  *
  * 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
@@ -28,6 +29,8 @@
 #include <linux/platform_device.h>
 #include <linux/power_supply.h>
 #include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
 
 #include <linux/usb/otg.h>
 #include <linux/usb/ulpi.h>
@@ -65,10 +68,6 @@ struct isp1704_charger {
        unsigned                present:1;
        unsigned                online:1;
        unsigned                current_max;
-
-       /* temp storage variables */
-       unsigned long           event;
-       unsigned                max_power;
 };
 
 static inline int isp1704_read(struct isp1704_charger *isp, u32 reg)
@@ -91,6 +90,8 @@ static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on)
 
        if (board && board->set_power)
                board->set_power(on);
+       else if (board)
+               gpio_set_value(board->enable_gpio, on);
 }
 
 /*
@@ -231,56 +232,59 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp)
        return ret;
 }
 
+static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp)
+{
+       if (isp1704_charger_detect(isp) &&
+                       isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP)
+               return true;
+       else
+               return false;
+}
+
 static void isp1704_charger_work(struct work_struct *data)
 {
-       int                     detect;
-       unsigned long           event;
-       unsigned                power;
        struct isp1704_charger  *isp =
                container_of(data, struct isp1704_charger, work);
        static DEFINE_MUTEX(lock);
 
-       event = isp->event;
-       power = isp->max_power;
-
        mutex_lock(&lock);
 
-       if (event != USB_EVENT_NONE)
-               isp1704_charger_set_power(isp, 1);
-
-       switch (event) {
+       switch (isp->phy->last_event) {
        case USB_EVENT_VBUS:
-               isp->online = true;
-
-               /* detect charger */
-               detect = isp1704_charger_detect(isp);
+               /* do not call wall charger detection more times */
+               if (!isp->present) {
+                       isp->online = true;
+                       isp->present = 1;
+                       isp1704_charger_set_power(isp, 1);
+
+                       /* detect wall charger */
+                       if (isp1704_charger_detect_dcp(isp)) {
+                               isp->psy.type = POWER_SUPPLY_TYPE_USB_DCP;
+                               isp->current_max = 1800;
+                       } else {
+                               isp->psy.type = POWER_SUPPLY_TYPE_USB;
+                               isp->current_max = 500;
+                       }
 
-               if (detect) {
-                       isp->present = detect;
-                       isp->psy.type = isp1704_charger_type(isp);
+                       /* enable data pullups */
+                       if (isp->phy->otg->gadget)
+                               usb_gadget_connect(isp->phy->otg->gadget);
                }
 
-               switch (isp->psy.type) {
-               case POWER_SUPPLY_TYPE_USB_DCP:
-                       isp->current_max = 1800;
-                       break;
-               case POWER_SUPPLY_TYPE_USB_CDP:
+               if (isp->psy.type != POWER_SUPPLY_TYPE_USB_DCP) {
                        /*
                         * Only 500mA here or high speed chirp
                         * handshaking may break
                         */
-                       isp->current_max = 500;
-                       /* FALLTHROUGH */
-               case POWER_SUPPLY_TYPE_USB:
-               default:
-                       /* enable data pullups */
-                       if (isp->phy->otg->gadget)
-                               usb_gadget_connect(isp->phy->otg->gadget);
+                       if (isp->current_max > 500)
+                               isp->current_max = 500;
+
+                       if (isp->current_max > 100)
+                               isp->psy.type = POWER_SUPPLY_TYPE_USB_CDP;
                }
                break;
        case USB_EVENT_NONE:
                isp->online = false;
-               isp->current_max = 0;
                isp->present = 0;
                isp->current_max = 0;
                isp->psy.type = POWER_SUPPLY_TYPE_USB;
@@ -298,12 +302,6 @@ static void isp1704_charger_work(struct work_struct *data)
 
                isp1704_charger_set_power(isp, 0);
                break;
-       case USB_EVENT_ENUMERATED:
-               if (isp->present)
-                       isp->current_max = 1800;
-               else
-                       isp->current_max = power;
-               break;
        default:
                goto out;
        }
@@ -314,16 +312,11 @@ out:
 }
 
 static int isp1704_notifier_call(struct notifier_block *nb,
-               unsigned long event, void *power)
+               unsigned long val, void *v)
 {
        struct isp1704_charger *isp =
                container_of(nb, struct isp1704_charger, nb);
 
-       isp->event = event;
-
-       if (power)
-               isp->max_power = *((unsigned *)power);
-
        schedule_work(&isp->work);
 
        return NOTIFY_OK;
@@ -411,12 +404,47 @@ static int isp1704_charger_probe(struct platform_device *pdev)
        struct isp1704_charger  *isp;
        int                     ret = -ENODEV;
 
+       struct isp1704_charger_data *pdata = dev_get_platdata(&pdev->dev);
+       struct device_node *np = pdev->dev.of_node;
+
+       if (np) {
+               int gpio = of_get_named_gpio(np, "nxp,enable-gpio", 0);
+
+               if (gpio < 0)
+                       return gpio;
+
+               pdata = devm_kzalloc(&pdev->dev,
+                       sizeof(struct isp1704_charger_data), GFP_KERNEL);
+               pdata->enable_gpio = gpio;
+
+               dev_info(&pdev->dev, "init gpio %d\n", pdata->enable_gpio);
+
+               ret = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio,
+                                       GPIOF_OUT_INIT_HIGH, "isp1704_reset");
+               if (ret)
+                       goto fail0;
+       }
+
+       if (!pdata) {
+               dev_err(&pdev->dev, "missing platform data!\n");
+               return -ENODEV;
+       }
+
+
        isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL);
        if (!isp)
                return -ENOMEM;
 
-       isp->phy = usb_get_phy(USB_PHY_TYPE_USB2);
-       if (IS_ERR_OR_NULL(isp->phy))
+       if (np)
+               isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);
+       else
+               isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
+
+       if (IS_ERR(isp->phy)) {
+               ret = PTR_ERR(isp->phy);
+               goto fail0;
+       }
+       if (!isp->phy)
                goto fail0;
 
        isp->dev = &pdev->dev;
@@ -462,20 +490,19 @@ static int isp1704_charger_probe(struct platform_device *pdev)
        if (isp->phy->otg->gadget)
                usb_gadget_disconnect(isp->phy->otg->gadget);
 
+       if (isp->phy->last_event == USB_EVENT_NONE)
+               isp1704_charger_set_power(isp, 0);
+
        /* Detect charger if VBUS is valid (the cable was already plugged). */
-       ret = isp1704_read(isp, ULPI_USB_INT_STS);
-       isp1704_charger_set_power(isp, 0);
-       if ((ret & ULPI_INT_VBUS_VALID) && !isp->phy->otg->default_a) {
-               isp->event = USB_EVENT_VBUS;
+       if (isp->phy->last_event == USB_EVENT_VBUS &&
+                       !isp->phy->otg->default_a)
                schedule_work(&isp->work);
-       }
 
        return 0;
 fail2:
        power_supply_unregister(&isp->psy);
 fail1:
        isp1704_charger_set_power(isp, 0);
-       usb_put_phy(isp->phy);
 fail0:
        dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
 
@@ -488,15 +515,23 @@ static int isp1704_charger_remove(struct platform_device *pdev)
 
        usb_unregister_notifier(isp->phy, &isp->nb);
        power_supply_unregister(&isp->psy);
-       usb_put_phy(isp->phy);
        isp1704_charger_set_power(isp, 0);
 
        return 0;
 }
 
+#ifdef CONFIG_OF
+static const struct of_device_id omap_isp1704_of_match[] = {
+       { .compatible = "nxp,isp1704", },
+       {},
+};
+MODULE_DEVICE_TABLE(of, omap_isp1704_of_match);
+#endif
+
 static struct platform_driver isp1704_charger_driver = {
        .driver = {
                .name = "isp1704_charger",
+               .of_match_table = of_match_ptr(omap_isp1704_of_match),
        },
        .probe = isp1704_charger_probe,
        .remove = isp1704_charger_remove,