]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/power/isp1704_charger.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / drivers / power / isp1704_charger.c
index 72512185f3e281febf049f23dde76464782ed92c..2ad9b14a5ce37c4ec15f8cb8b319c7845e982c4f 100644 (file)
@@ -59,10 +59,60 @@ struct isp1704_charger {
        struct notifier_block   nb;
        struct work_struct      work;
 
-       char                    model[7];
+       /* properties */
+       char                    model[8];
        unsigned                present:1;
+       unsigned                online:1;
+       unsigned                current_max;
+
+       /* temp storage variables */
+       unsigned long           event;
+       unsigned                max_power;
 };
 
+/*
+ * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB
+ * chargers).
+ *
+ * REVISIT: The method is defined in Battery Charging Specification and is
+ * applicable to any ULPI transceiver. Nothing isp170x specific here.
+ */
+static inline int isp1704_charger_type(struct isp1704_charger *isp)
+{
+       u8 reg;
+       u8 func_ctrl;
+       u8 otg_ctrl;
+       int type = POWER_SUPPLY_TYPE_USB_DCP;
+
+       func_ctrl = otg_io_read(isp->otg, ULPI_FUNC_CTRL);
+       otg_ctrl = otg_io_read(isp->otg, ULPI_OTG_CTRL);
+
+       /* disable pulldowns */
+       reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN;
+       otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), reg);
+
+       /* full speed */
+       otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL),
+                       ULPI_FUNC_CTRL_XCVRSEL_MASK);
+       otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL),
+                       ULPI_FUNC_CTRL_FULL_SPEED);
+
+       /* Enable strong pull-up on DP (1.5K) and reset */
+       reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
+       otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), reg);
+       usleep_range(1000, 2000);
+
+       reg = otg_io_read(isp->otg, ULPI_DEBUG);
+       if ((reg & 3) != 3)
+               type = POWER_SUPPLY_TYPE_USB_CDP;
+
+       /* recover original state */
+       otg_io_write(isp->otg, ULPI_FUNC_CTRL, func_ctrl);
+       otg_io_write(isp->otg, ULPI_OTG_CTRL, otg_ctrl);
+
+       return type;
+}
+
 /*
  * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger
  * is actually a dedicated charger, the following steps need to be taken.
@@ -127,16 +177,19 @@ static inline int isp1704_charger_verify(struct isp1704_charger *isp)
 static inline int isp1704_charger_detect(struct isp1704_charger *isp)
 {
        unsigned long   timeout;
-       u8              r;
+       u8              pwr_ctrl;
        int             ret = 0;
 
+       pwr_ctrl = otg_io_read(isp->otg, ISP1704_PWR_CTRL);
+
        /* set SW control bit in PWR_CTRL register */
        otg_io_write(isp->otg, ISP1704_PWR_CTRL,
                        ISP1704_PWR_CTRL_SWCTRL);
 
        /* enable manual charger detection */
-       r = (ISP1704_PWR_CTRL_SWCTRL | ISP1704_PWR_CTRL_DPVSRC_EN);
-       otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), r);
+       otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL),
+                       ISP1704_PWR_CTRL_SWCTRL
+                       | ISP1704_PWR_CTRL_DPVSRC_EN);
        usleep_range(1000, 2000);
 
        timeout = jiffies + msecs_to_jiffies(300);
@@ -147,7 +200,10 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp)
                        ret = isp1704_charger_verify(isp);
                        break;
                }
-       } while (!time_after(jiffies, timeout));
+       } while (!time_after(jiffies, timeout) && isp->online);
+
+       /* recover original state */
+       otg_io_write(isp->otg, ISP1704_PWR_CTRL, pwr_ctrl);
 
        return ret;
 }
@@ -155,52 +211,92 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp)
 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);
 
-       /*
-        * FIXME Only supporting dedicated chargers even though isp1704 can
-        * detect HUB and HOST chargers. If the device has already been
-        * enumerated, the detection will break the connection.
-        */
-       if (isp->otg->state != OTG_STATE_B_IDLE)
-               return;
+       event = isp->event;
+       power = isp->max_power;
 
-       /* disable data pullups */
-       if (isp->otg->gadget)
-               usb_gadget_disconnect(isp->otg->gadget);
+       mutex_lock(&lock);
+
+       switch (event) {
+       case USB_EVENT_VBUS:
+               isp->online = true;
+
+               /* detect charger */
+               detect = isp1704_charger_detect(isp);
+
+               if (detect) {
+                       isp->present = detect;
+                       isp->psy.type = isp1704_charger_type(isp);
+               }
 
-       /* detect charger */
-       detect = isp1704_charger_detect(isp);
-       if (detect) {
-               isp->present = detect;
-               power_supply_changed(&isp->psy);
+               switch (isp->psy.type) {
+               case POWER_SUPPLY_TYPE_USB_DCP:
+                       isp->current_max = 1800;
+                       break;
+               case POWER_SUPPLY_TYPE_USB_CDP:
+                       /*
+                        * 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->otg->gadget)
+                               usb_gadget_connect(isp->otg->gadget);
+               }
+               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;
+
+               /*
+                * Disable data pullups. We need to prevent the controller from
+                * enumerating.
+                *
+                * FIXME: This is here to allow charger detection with Host/HUB
+                * chargers. The pullups may be enabled elsewhere, so this can
+                * not be the final solution.
+                */
+               if (isp->otg->gadget)
+                       usb_gadget_disconnect(isp->otg->gadget);
+               break;
+       case USB_EVENT_ENUMERATED:
+               if (isp->present)
+                       isp->current_max = 1800;
+               else
+                       isp->current_max = power;
+               break;
+       default:
+               goto out;
        }
 
-       /* enable data pullups */
-       if (isp->otg->gadget)
-               usb_gadget_connect(isp->otg->gadget);
+       power_supply_changed(&isp->psy);
+out:
+       mutex_unlock(&lock);
 }
 
 static int isp1704_notifier_call(struct notifier_block *nb,
-               unsigned long event, void *unused)
+               unsigned long event, void *power)
 {
        struct isp1704_charger *isp =
                container_of(nb, struct isp1704_charger, nb);
 
-       switch (event) {
-       case USB_EVENT_VBUS:
-               schedule_work(&isp->work);
-               break;
-       case USB_EVENT_NONE:
-               if (isp->present) {
-                       isp->present = 0;
-                       power_supply_changed(&isp->psy);
-               }
-               break;
-       default:
-               return NOTIFY_DONE;
-       }
+       isp->event = event;
+
+       if (power)
+               isp->max_power = *((unsigned *)power);
+
+       schedule_work(&isp->work);
 
        return NOTIFY_OK;
 }
@@ -216,6 +312,12 @@ static int isp1704_charger_get_property(struct power_supply *psy,
        case POWER_SUPPLY_PROP_PRESENT:
                val->intval = isp->present;
                break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = isp->online;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_MAX:
+               val->intval = isp->current_max;
+               break;
        case POWER_SUPPLY_PROP_MODEL_NAME:
                val->strval = isp->model;
                break;
@@ -230,6 +332,8 @@ static int isp1704_charger_get_property(struct power_supply *psy,
 
 static enum power_supply_property power_props[] = {
        POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_CURRENT_MAX,
        POWER_SUPPLY_PROP_MODEL_NAME,
        POWER_SUPPLY_PROP_MANUFACTURER,
 };
@@ -287,13 +391,13 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev)
        if (!isp->otg)
                goto fail0;
 
+       isp->dev = &pdev->dev;
+       platform_set_drvdata(pdev, isp);
+
        ret = isp1704_test_ulpi(isp);
        if (ret < 0)
                goto fail1;
 
-       isp->dev = &pdev->dev;
-       platform_set_drvdata(pdev, isp);
-
        isp->psy.name           = "isp1704";
        isp->psy.type           = POWER_SUPPLY_TYPE_USB;
        isp->psy.properties     = power_props;
@@ -318,6 +422,23 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev)
 
        dev_info(isp->dev, "registered with product id %s\n", isp->model);
 
+       /*
+        * Taking over the D+ pullup.
+        *
+        * FIXME: The device will be disconnected if it was already
+        * enumerated. The charger driver should be always loaded before any
+        * gadget is loaded.
+        */
+       if (isp->otg->gadget)
+               usb_gadget_disconnect(isp->otg->gadget);
+
+       /* Detect charger if VBUS is valid (the cable was already plugged). */
+       ret = otg_io_read(isp->otg, ULPI_USB_INT_STS);
+       if ((ret & ULPI_INT_VBUS_VALID) && !isp->otg->default_a) {
+               isp->event = USB_EVENT_VBUS;
+               schedule_work(&isp->work);
+       }
+
        return 0;
 fail2:
        power_supply_unregister(&isp->psy);