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