root/drivers/extcon/extcon-intel-cht-wc.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. cht_wc_extcon_get_id
  2. cht_wc_extcon_get_charger
  3. cht_wc_extcon_set_phymux
  4. cht_wc_extcon_set_5v_boost
  5. cht_wc_extcon_set_otgmode
  6. cht_wc_extcon_enable_charging
  7. cht_wc_extcon_set_state
  8. cht_wc_extcon_pwrsrc_event
  9. cht_wc_extcon_isr
  10. cht_wc_extcon_sw_control
  11. cht_wc_extcon_probe
  12. cht_wc_extcon_remove

   1 // SPDX-License-Identifier: GPL-2.0
   2 /*
   3  * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC
   4  * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
   5  *
   6  * Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
   7  * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
   8  */
   9 
  10 #include <linux/extcon-provider.h>
  11 #include <linux/interrupt.h>
  12 #include <linux/kernel.h>
  13 #include <linux/mfd/intel_soc_pmic.h>
  14 #include <linux/module.h>
  15 #include <linux/mod_devicetable.h>
  16 #include <linux/platform_device.h>
  17 #include <linux/regmap.h>
  18 #include <linux/slab.h>
  19 
  20 #include "extcon-intel.h"
  21 
  22 #define CHT_WC_PHYCTRL                  0x5e07
  23 
  24 #define CHT_WC_CHGRCTRL0                0x5e16
  25 #define CHT_WC_CHGRCTRL0_CHGRRESET      BIT(0)
  26 #define CHT_WC_CHGRCTRL0_EMRGCHREN      BIT(1)
  27 #define CHT_WC_CHGRCTRL0_EXTCHRDIS      BIT(2)
  28 #define CHT_WC_CHGRCTRL0_SWCONTROL      BIT(3)
  29 #define CHT_WC_CHGRCTRL0_TTLCK          BIT(4)
  30 #define CHT_WC_CHGRCTRL0_CCSM_OFF       BIT(5)
  31 #define CHT_WC_CHGRCTRL0_DBPOFF         BIT(6)
  32 #define CHT_WC_CHGRCTRL0_CHR_WDT_NOKICK BIT(7)
  33 
  34 #define CHT_WC_CHGRCTRL1                        0x5e17
  35 #define CHT_WC_CHGRCTRL1_FUSB_INLMT_100         BIT(0)
  36 #define CHT_WC_CHGRCTRL1_FUSB_INLMT_150         BIT(1)
  37 #define CHT_WC_CHGRCTRL1_FUSB_INLMT_500         BIT(2)
  38 #define CHT_WC_CHGRCTRL1_FUSB_INLMT_900         BIT(3)
  39 #define CHT_WC_CHGRCTRL1_FUSB_INLMT_1500        BIT(4)
  40 #define CHT_WC_CHGRCTRL1_FTEMP_EVENT            BIT(5)
  41 #define CHT_WC_CHGRCTRL1_OTGMODE                BIT(6)
  42 #define CHT_WC_CHGRCTRL1_DBPEN                  BIT(7)
  43 
  44 #define CHT_WC_USBSRC                   0x5e29
  45 #define CHT_WC_USBSRC_STS_MASK          GENMASK(1, 0)
  46 #define CHT_WC_USBSRC_STS_SUCCESS       2
  47 #define CHT_WC_USBSRC_STS_FAIL          3
  48 #define CHT_WC_USBSRC_TYPE_SHIFT        2
  49 #define CHT_WC_USBSRC_TYPE_MASK         GENMASK(5, 2)
  50 #define CHT_WC_USBSRC_TYPE_NONE         0
  51 #define CHT_WC_USBSRC_TYPE_SDP          1
  52 #define CHT_WC_USBSRC_TYPE_DCP          2
  53 #define CHT_WC_USBSRC_TYPE_CDP          3
  54 #define CHT_WC_USBSRC_TYPE_ACA          4
  55 #define CHT_WC_USBSRC_TYPE_SE1          5
  56 #define CHT_WC_USBSRC_TYPE_MHL          6
  57 #define CHT_WC_USBSRC_TYPE_FLOATING     7
  58 #define CHT_WC_USBSRC_TYPE_OTHER        8
  59 #define CHT_WC_USBSRC_TYPE_DCP_EXTPHY   9
  60 
  61 #define CHT_WC_CHGDISCTRL               0x5e2f
  62 #define CHT_WC_CHGDISCTRL_OUT           BIT(0)
  63 /* 0 - open drain, 1 - regular push-pull output */
  64 #define CHT_WC_CHGDISCTRL_DRV           BIT(4)
  65 /* 0 - pin is controlled by SW, 1 - by HW */
  66 #define CHT_WC_CHGDISCTRL_FN            BIT(6)
  67 
  68 #define CHT_WC_PWRSRC_IRQ               0x6e03
  69 #define CHT_WC_PWRSRC_IRQ_MASK          0x6e0f
  70 #define CHT_WC_PWRSRC_STS               0x6e1e
  71 #define CHT_WC_PWRSRC_VBUS              BIT(0)
  72 #define CHT_WC_PWRSRC_DC                BIT(1)
  73 #define CHT_WC_PWRSRC_BATT              BIT(2)
  74 #define CHT_WC_PWRSRC_USBID_MASK        GENMASK(4, 3)
  75 #define CHT_WC_PWRSRC_USBID_SHIFT       3
  76 #define CHT_WC_PWRSRC_RID_ACA           0
  77 #define CHT_WC_PWRSRC_RID_GND           1
  78 #define CHT_WC_PWRSRC_RID_FLOAT         2
  79 
  80 #define CHT_WC_VBUS_GPIO_CTLO           0x6e2d
  81 #define CHT_WC_VBUS_GPIO_CTLO_OUTPUT    BIT(0)
  82 #define CHT_WC_VBUS_GPIO_CTLO_DRV_OD    BIT(4)
  83 #define CHT_WC_VBUS_GPIO_CTLO_DIR_OUT   BIT(5)
  84 
  85 enum cht_wc_mux_select {
  86         MUX_SEL_PMIC = 0,
  87         MUX_SEL_SOC,
  88 };
  89 
  90 static const unsigned int cht_wc_extcon_cables[] = {
  91         EXTCON_USB,
  92         EXTCON_USB_HOST,
  93         EXTCON_CHG_USB_SDP,
  94         EXTCON_CHG_USB_CDP,
  95         EXTCON_CHG_USB_DCP,
  96         EXTCON_CHG_USB_ACA,
  97         EXTCON_NONE,
  98 };
  99 
 100 struct cht_wc_extcon_data {
 101         struct device *dev;
 102         struct regmap *regmap;
 103         struct extcon_dev *edev;
 104         unsigned int previous_cable;
 105         bool usb_host;
 106 };
 107 
 108 static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
 109 {
 110         switch ((pwrsrc_sts & CHT_WC_PWRSRC_USBID_MASK) >> CHT_WC_PWRSRC_USBID_SHIFT) {
 111         case CHT_WC_PWRSRC_RID_GND:
 112                 return INTEL_USB_ID_GND;
 113         case CHT_WC_PWRSRC_RID_FLOAT:
 114                 return INTEL_USB_ID_FLOAT;
 115         case CHT_WC_PWRSRC_RID_ACA:
 116         default:
 117                 /*
 118                  * Once we have IIO support for the GPADC we should read
 119                  * the USBID GPADC channel here and determine ACA role
 120                  * based on that.
 121                  */
 122                 return INTEL_USB_ID_FLOAT;
 123         }
 124 }
 125 
 126 static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext,
 127                                      bool ignore_errors)
 128 {
 129         int ret, usbsrc, status;
 130         unsigned long timeout;
 131 
 132         /* Charger detection can take upto 600ms, wait 800ms max. */
 133         timeout = jiffies + msecs_to_jiffies(800);
 134         do {
 135                 ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc);
 136                 if (ret) {
 137                         dev_err(ext->dev, "Error reading usbsrc: %d\n", ret);
 138                         return ret;
 139                 }
 140 
 141                 status = usbsrc & CHT_WC_USBSRC_STS_MASK;
 142                 if (status == CHT_WC_USBSRC_STS_SUCCESS ||
 143                     status == CHT_WC_USBSRC_STS_FAIL)
 144                         break;
 145 
 146                 msleep(50); /* Wait a bit before retrying */
 147         } while (time_before(jiffies, timeout));
 148 
 149         if (status != CHT_WC_USBSRC_STS_SUCCESS) {
 150                 if (ignore_errors)
 151                         return EXTCON_CHG_USB_SDP; /* Save fallback */
 152 
 153                 if (status == CHT_WC_USBSRC_STS_FAIL)
 154                         dev_warn(ext->dev, "Could not detect charger type\n");
 155                 else
 156                         dev_warn(ext->dev, "Timeout detecting charger type\n");
 157                 return EXTCON_CHG_USB_SDP; /* Save fallback */
 158         }
 159 
 160         usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT;
 161         switch (usbsrc) {
 162         default:
 163                 dev_warn(ext->dev,
 164                         "Unhandled charger type %d, defaulting to SDP\n",
 165                          ret);
 166                 return EXTCON_CHG_USB_SDP;
 167         case CHT_WC_USBSRC_TYPE_SDP:
 168         case CHT_WC_USBSRC_TYPE_FLOATING:
 169         case CHT_WC_USBSRC_TYPE_OTHER:
 170                 return EXTCON_CHG_USB_SDP;
 171         case CHT_WC_USBSRC_TYPE_CDP:
 172                 return EXTCON_CHG_USB_CDP;
 173         case CHT_WC_USBSRC_TYPE_DCP:
 174         case CHT_WC_USBSRC_TYPE_DCP_EXTPHY:
 175         case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */
 176                 return EXTCON_CHG_USB_DCP;
 177         case CHT_WC_USBSRC_TYPE_ACA:
 178                 return EXTCON_CHG_USB_ACA;
 179         }
 180 }
 181 
 182 static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state)
 183 {
 184         int ret;
 185 
 186         ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state);
 187         if (ret)
 188                 dev_err(ext->dev, "Error writing phyctrl: %d\n", ret);
 189 }
 190 
 191 static void cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext,
 192                                        bool enable)
 193 {
 194         int ret, val;
 195 
 196         /*
 197          * The 5V boost converter is enabled through a gpio on the PMIC, since
 198          * there currently is no gpio driver we access the gpio reg directly.
 199          */
 200         val = CHT_WC_VBUS_GPIO_CTLO_DRV_OD | CHT_WC_VBUS_GPIO_CTLO_DIR_OUT;
 201         if (enable)
 202                 val |= CHT_WC_VBUS_GPIO_CTLO_OUTPUT;
 203 
 204         ret = regmap_write(ext->regmap, CHT_WC_VBUS_GPIO_CTLO, val);
 205         if (ret)
 206                 dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n", ret);
 207 }
 208 
 209 static void cht_wc_extcon_set_otgmode(struct cht_wc_extcon_data *ext,
 210                                       bool enable)
 211 {
 212         unsigned int val = enable ? CHT_WC_CHGRCTRL1_OTGMODE : 0;
 213         int ret;
 214 
 215         ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL1,
 216                                  CHT_WC_CHGRCTRL1_OTGMODE, val);
 217         if (ret)
 218                 dev_err(ext->dev, "Error updating CHGRCTRL1 reg: %d\n", ret);
 219 }
 220 
 221 static void cht_wc_extcon_enable_charging(struct cht_wc_extcon_data *ext,
 222                                           bool enable)
 223 {
 224         unsigned int val = enable ? 0 : CHT_WC_CHGDISCTRL_OUT;
 225         int ret;
 226 
 227         ret = regmap_update_bits(ext->regmap, CHT_WC_CHGDISCTRL,
 228                                  CHT_WC_CHGDISCTRL_OUT, val);
 229         if (ret)
 230                 dev_err(ext->dev, "Error updating CHGDISCTRL reg: %d\n", ret);
 231 }
 232 
 233 /* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
 234 static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext,
 235                                     unsigned int cable, bool state)
 236 {
 237         extcon_set_state_sync(ext->edev, cable, state);
 238         if (cable == EXTCON_CHG_USB_SDP)
 239                 extcon_set_state_sync(ext->edev, EXTCON_USB, state);
 240 }
 241 
 242 static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)
 243 {
 244         int ret, pwrsrc_sts, id;
 245         unsigned int cable = EXTCON_NONE;
 246         /* Ignore errors in host mode, as the 5v boost converter is on then */
 247         bool ignore_get_charger_errors = ext->usb_host;
 248 
 249         ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts);
 250         if (ret) {
 251                 dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret);
 252                 return;
 253         }
 254 
 255         id = cht_wc_extcon_get_id(ext, pwrsrc_sts);
 256         if (id == INTEL_USB_ID_GND) {
 257                 cht_wc_extcon_enable_charging(ext, false);
 258                 cht_wc_extcon_set_otgmode(ext, true);
 259 
 260                 /* The 5v boost causes a false VBUS / SDP detect, skip */
 261                 goto charger_det_done;
 262         }
 263 
 264         cht_wc_extcon_set_otgmode(ext, false);
 265         cht_wc_extcon_enable_charging(ext, true);
 266 
 267         /* Plugged into a host/charger or not connected? */
 268         if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) {
 269                 /* Route D+ and D- to PMIC for future charger detection */
 270                 cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
 271                 goto set_state;
 272         }
 273 
 274         ret = cht_wc_extcon_get_charger(ext, ignore_get_charger_errors);
 275         if (ret >= 0)
 276                 cable = ret;
 277 
 278 charger_det_done:
 279         /* Route D+ and D- to SoC for the host or gadget controller */
 280         cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC);
 281 
 282 set_state:
 283         if (cable != ext->previous_cable) {
 284                 cht_wc_extcon_set_state(ext, cable, true);
 285                 cht_wc_extcon_set_state(ext, ext->previous_cable, false);
 286                 ext->previous_cable = cable;
 287         }
 288 
 289         ext->usb_host = ((id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A));
 290         extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host);
 291 }
 292 
 293 static irqreturn_t cht_wc_extcon_isr(int irq, void *data)
 294 {
 295         struct cht_wc_extcon_data *ext = data;
 296         int ret, irqs;
 297 
 298         ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs);
 299         if (ret) {
 300                 dev_err(ext->dev, "Error reading irqs: %d\n", ret);
 301                 return IRQ_NONE;
 302         }
 303 
 304         cht_wc_extcon_pwrsrc_event(ext);
 305 
 306         ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs);
 307         if (ret) {
 308                 dev_err(ext->dev, "Error writing irqs: %d\n", ret);
 309                 return IRQ_NONE;
 310         }
 311 
 312         return IRQ_HANDLED;
 313 }
 314 
 315 static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable)
 316 {
 317         int ret, mask, val;
 318 
 319         val = enable ? 0 : CHT_WC_CHGDISCTRL_FN;
 320         ret = regmap_update_bits(ext->regmap, CHT_WC_CHGDISCTRL,
 321                                  CHT_WC_CHGDISCTRL_FN, val);
 322         if (ret)
 323                 dev_err(ext->dev,
 324                         "Error setting sw control for CHGDIS pin: %d\n",
 325                         ret);
 326 
 327         mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF;
 328         val = enable ? mask : 0;
 329         ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val);
 330         if (ret)
 331                 dev_err(ext->dev, "Error setting sw control: %d\n", ret);
 332 
 333         return ret;
 334 }
 335 
 336 static int cht_wc_extcon_probe(struct platform_device *pdev)
 337 {
 338         struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
 339         struct cht_wc_extcon_data *ext;
 340         unsigned long mask = ~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_USBID_MASK);
 341         int pwrsrc_sts, id;
 342         int irq, ret;
 343 
 344         irq = platform_get_irq(pdev, 0);
 345         if (irq < 0)
 346                 return irq;
 347 
 348         ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL);
 349         if (!ext)
 350                 return -ENOMEM;
 351 
 352         ext->dev = &pdev->dev;
 353         ext->regmap = pmic->regmap;
 354         ext->previous_cable = EXTCON_NONE;
 355 
 356         /* Initialize extcon device */
 357         ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables);
 358         if (IS_ERR(ext->edev))
 359                 return PTR_ERR(ext->edev);
 360 
 361         /*
 362          * When a host-cable is detected the BIOS enables an external 5v boost
 363          * converter to power connected devices there are 2 problems with this:
 364          * 1) This gets seen by the external battery charger as a valid Vbus
 365          *    supply and it then tries to feed Vsys from this creating a
 366          *    feedback loop which causes aprox. 300 mA extra battery drain
 367          *    (and unless we drive the external-charger-disable pin high it
 368          *    also tries to charge the battery causing even more feedback).
 369          * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply
 370          * Since the external battery charger has its own 5v boost converter
 371          * which does not have these issues, we simply turn the separate
 372          * external 5v boost converter off and leave it off entirely.
 373          */
 374         cht_wc_extcon_set_5v_boost(ext, false);
 375 
 376         /* Enable sw control */
 377         ret = cht_wc_extcon_sw_control(ext, true);
 378         if (ret)
 379                 goto disable_sw_control;
 380 
 381         /* Disable charging by external battery charger */
 382         cht_wc_extcon_enable_charging(ext, false);
 383 
 384         /* Register extcon device */
 385         ret = devm_extcon_dev_register(ext->dev, ext->edev);
 386         if (ret) {
 387                 dev_err(ext->dev, "Error registering extcon device: %d\n", ret);
 388                 goto disable_sw_control;
 389         }
 390 
 391         ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts);
 392         if (ret) {
 393                 dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret);
 394                 goto disable_sw_control;
 395         }
 396 
 397         /*
 398          * If no USB host or device connected, route D+ and D- to PMIC for
 399          * initial charger detection
 400          */
 401         id = cht_wc_extcon_get_id(ext, pwrsrc_sts);
 402         if (id != INTEL_USB_ID_GND)
 403                 cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
 404 
 405         /* Get initial state */
 406         cht_wc_extcon_pwrsrc_event(ext);
 407 
 408         ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr,
 409                                         IRQF_ONESHOT, pdev->name, ext);
 410         if (ret) {
 411                 dev_err(ext->dev, "Error requesting interrupt: %d\n", ret);
 412                 goto disable_sw_control;
 413         }
 414 
 415         /* Unmask irqs */
 416         ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK, mask);
 417         if (ret) {
 418                 dev_err(ext->dev, "Error writing irq-mask: %d\n", ret);
 419                 goto disable_sw_control;
 420         }
 421 
 422         platform_set_drvdata(pdev, ext);
 423 
 424         return 0;
 425 
 426 disable_sw_control:
 427         cht_wc_extcon_sw_control(ext, false);
 428         return ret;
 429 }
 430 
 431 static int cht_wc_extcon_remove(struct platform_device *pdev)
 432 {
 433         struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev);
 434 
 435         cht_wc_extcon_sw_control(ext, false);
 436 
 437         return 0;
 438 }
 439 
 440 static const struct platform_device_id cht_wc_extcon_table[] = {
 441         { .name = "cht_wcove_pwrsrc" },
 442         {},
 443 };
 444 MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table);
 445 
 446 static struct platform_driver cht_wc_extcon_driver = {
 447         .probe = cht_wc_extcon_probe,
 448         .remove = cht_wc_extcon_remove,
 449         .id_table = cht_wc_extcon_table,
 450         .driver = {
 451                 .name = "cht_wcove_pwrsrc",
 452         },
 453 };
 454 module_platform_driver(cht_wc_extcon_driver);
 455 
 456 MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver");
 457 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
 458 MODULE_LICENSE("GPL v2");

/* [<][>][^][v][top][bottom][index][help] */