linux/drivers/extcon/extcon-axp288.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
   4 *
   5 * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
   6 * Copyright (C) 2015 Intel Corporation
   7 * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
   8 */
   9
  10#include <linux/acpi.h>
  11#include <linux/module.h>
  12#include <linux/kernel.h>
  13#include <linux/io.h>
  14#include <linux/slab.h>
  15#include <linux/interrupt.h>
  16#include <linux/platform_device.h>
  17#include <linux/property.h>
  18#include <linux/notifier.h>
  19#include <linux/extcon-provider.h>
  20#include <linux/regmap.h>
  21#include <linux/mfd/axp20x.h>
  22#include <linux/usb/role.h>
  23#include <linux/workqueue.h>
  24
  25#include <asm/cpu_device_id.h>
  26#include <asm/intel-family.h>
  27
  28/* Power source status register */
  29#define PS_STAT_VBUS_TRIGGER            BIT(0)
  30#define PS_STAT_BAT_CHRG_DIR            BIT(2)
  31#define PS_STAT_VBUS_ABOVE_VHOLD        BIT(3)
  32#define PS_STAT_VBUS_VALID              BIT(4)
  33#define PS_STAT_VBUS_PRESENT            BIT(5)
  34
  35/* BC module global register */
  36#define BC_GLOBAL_RUN                   BIT(0)
  37#define BC_GLOBAL_DET_STAT              BIT(2)
  38#define BC_GLOBAL_DBP_TOUT              BIT(3)
  39#define BC_GLOBAL_VLGC_COM_SEL          BIT(4)
  40#define BC_GLOBAL_DCD_TOUT_MASK         (BIT(6)|BIT(5))
  41#define BC_GLOBAL_DCD_TOUT_300MS        0
  42#define BC_GLOBAL_DCD_TOUT_100MS        1
  43#define BC_GLOBAL_DCD_TOUT_500MS        2
  44#define BC_GLOBAL_DCD_TOUT_900MS        3
  45#define BC_GLOBAL_DCD_DET_SEL           BIT(7)
  46
  47/* BC module vbus control and status register */
  48#define VBUS_CNTL_DPDM_PD_EN            BIT(4)
  49#define VBUS_CNTL_DPDM_FD_EN            BIT(5)
  50#define VBUS_CNTL_FIRST_PO_STAT         BIT(6)
  51
  52/* BC USB status register */
  53#define USB_STAT_BUS_STAT_MASK          (BIT(3)|BIT(2)|BIT(1)|BIT(0))
  54#define USB_STAT_BUS_STAT_SHIFT         0
  55#define USB_STAT_BUS_STAT_ATHD          0
  56#define USB_STAT_BUS_STAT_CONN          1
  57#define USB_STAT_BUS_STAT_SUSP          2
  58#define USB_STAT_BUS_STAT_CONF          3
  59#define USB_STAT_USB_SS_MODE            BIT(4)
  60#define USB_STAT_DEAD_BAT_DET           BIT(6)
  61#define USB_STAT_DBP_UNCFG              BIT(7)
  62
  63/* BC detect status register */
  64#define DET_STAT_MASK                   (BIT(7)|BIT(6)|BIT(5))
  65#define DET_STAT_SHIFT                  5
  66#define DET_STAT_SDP                    1
  67#define DET_STAT_CDP                    2
  68#define DET_STAT_DCP                    3
  69
  70enum axp288_extcon_reg {
  71        AXP288_PS_STAT_REG              = 0x00,
  72        AXP288_PS_BOOT_REASON_REG       = 0x02,
  73        AXP288_BC_GLOBAL_REG            = 0x2c,
  74        AXP288_BC_VBUS_CNTL_REG         = 0x2d,
  75        AXP288_BC_USB_STAT_REG          = 0x2e,
  76        AXP288_BC_DET_STAT_REG          = 0x2f,
  77};
  78
  79enum axp288_extcon_irq {
  80        VBUS_FALLING_IRQ = 0,
  81        VBUS_RISING_IRQ,
  82        MV_CHNG_IRQ,
  83        BC_USB_CHNG_IRQ,
  84        EXTCON_IRQ_END,
  85};
  86
  87static const unsigned int axp288_extcon_cables[] = {
  88        EXTCON_CHG_USB_SDP,
  89        EXTCON_CHG_USB_CDP,
  90        EXTCON_CHG_USB_DCP,
  91        EXTCON_USB,
  92        EXTCON_NONE,
  93};
  94
  95struct axp288_extcon_info {
  96        struct device *dev;
  97        struct regmap *regmap;
  98        struct regmap_irq_chip_data *regmap_irqc;
  99        struct usb_role_switch *role_sw;
 100        struct work_struct role_work;
 101        int irq[EXTCON_IRQ_END];
 102        struct extcon_dev *edev;
 103        struct extcon_dev *id_extcon;
 104        struct notifier_block id_nb;
 105        unsigned int previous_cable;
 106        bool vbus_attach;
 107};
 108
 109static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
 110        X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT,        NULL),
 111        {}
 112};
 113
 114/* Power up/down reason string array */
 115static const char * const axp288_pwr_up_down_info[] = {
 116        "Last wake caused by user pressing the power button",
 117        "Last wake caused by a charger insertion",
 118        "Last wake caused by a battery insertion",
 119        "Last wake caused by SOC initiated global reset",
 120        "Last wake caused by cold reset",
 121        "Last shutdown caused by PMIC UVLO threshold",
 122        "Last shutdown caused by SOC initiated cold off",
 123        "Last shutdown caused by user pressing the power button",
 124};
 125
 126/*
 127 * Decode and log the given "reset source indicator" (rsi)
 128 * register and then clear it.
 129 */
 130static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
 131{
 132        unsigned int val, i, clear_mask = 0;
 133        unsigned long bits;
 134        int ret;
 135
 136        ret = regmap_read(info->regmap, AXP288_PS_BOOT_REASON_REG, &val);
 137        if (ret < 0) {
 138                dev_err(info->dev, "failed to read reset source indicator\n");
 139                return;
 140        }
 141
 142        bits = val & GENMASK(ARRAY_SIZE(axp288_pwr_up_down_info) - 1, 0);
 143        for_each_set_bit(i, &bits, ARRAY_SIZE(axp288_pwr_up_down_info))
 144                dev_dbg(info->dev, "%s\n", axp288_pwr_up_down_info[i]);
 145        clear_mask = bits;
 146
 147        /* Clear the register value for next reboot (write 1 to clear bit) */
 148        regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
 149}
 150
 151/*
 152 * The below code to control the USB role-switch on devices with an AXP288
 153 * may seem out of place, but there are 2 reasons why this is the best place
 154 * to control the USB role-switch on such devices:
 155 * 1) On many devices the USB role is controlled by AML code, but the AML code
 156 *    only switches between the host and none roles, because of Windows not
 157 *    really using device mode. To make device mode work we need to toggle
 158 *    between the none/device roles based on Vbus presence, and this driver
 159 *    gets interrupts on Vbus insertion / removal.
 160 * 2) In order for our BC1.2 charger detection to work properly the role
 161 *    mux must be properly set to device mode before we do the detection.
 162 */
 163
 164/* Returns the id-pin value, note pulled low / false == host-mode */
 165static bool axp288_get_id_pin(struct axp288_extcon_info *info)
 166{
 167        enum usb_role role;
 168
 169        if (info->id_extcon)
 170                return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
 171
 172        /* We cannot access the id-pin, see what mode the AML code has set */
 173        role = usb_role_switch_get_role(info->role_sw);
 174        return role != USB_ROLE_HOST;
 175}
 176
 177static void axp288_usb_role_work(struct work_struct *work)
 178{
 179        struct axp288_extcon_info *info =
 180                container_of(work, struct axp288_extcon_info, role_work);
 181        enum usb_role role;
 182        bool id_pin;
 183        int ret;
 184
 185        id_pin = axp288_get_id_pin(info);
 186        if (!id_pin)
 187                role = USB_ROLE_HOST;
 188        else if (info->vbus_attach)
 189                role = USB_ROLE_DEVICE;
 190        else
 191                role = USB_ROLE_NONE;
 192
 193        ret = usb_role_switch_set_role(info->role_sw, role);
 194        if (ret)
 195                dev_err(info->dev, "failed to set role: %d\n", ret);
 196}
 197
 198static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
 199{
 200        int ret, pwr_stat;
 201
 202        ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
 203        if (ret < 0) {
 204                dev_err(info->dev, "failed to read vbus status\n");
 205                return false;
 206        }
 207
 208        return !!(pwr_stat & PS_STAT_VBUS_VALID);
 209}
 210
 211static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
 212{
 213        int ret, stat, cfg;
 214        u8 chrg_type;
 215        unsigned int cable = info->previous_cable;
 216        bool vbus_attach = false;
 217
 218        vbus_attach = axp288_get_vbus_attach(info);
 219        if (!vbus_attach)
 220                goto no_vbus;
 221
 222        /* Check charger detection completion status */
 223        ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
 224        if (ret < 0)
 225                goto dev_det_ret;
 226        if (cfg & BC_GLOBAL_DET_STAT) {
 227                dev_dbg(info->dev, "can't complete the charger detection\n");
 228                goto dev_det_ret;
 229        }
 230
 231        ret = regmap_read(info->regmap, AXP288_BC_DET_STAT_REG, &stat);
 232        if (ret < 0)
 233                goto dev_det_ret;
 234
 235        chrg_type = (stat & DET_STAT_MASK) >> DET_STAT_SHIFT;
 236
 237        switch (chrg_type) {
 238        case DET_STAT_SDP:
 239                dev_dbg(info->dev, "sdp cable is connected\n");
 240                cable = EXTCON_CHG_USB_SDP;
 241                break;
 242        case DET_STAT_CDP:
 243                dev_dbg(info->dev, "cdp cable is connected\n");
 244                cable = EXTCON_CHG_USB_CDP;
 245                break;
 246        case DET_STAT_DCP:
 247                dev_dbg(info->dev, "dcp cable is connected\n");
 248                cable = EXTCON_CHG_USB_DCP;
 249                break;
 250        default:
 251                dev_warn(info->dev, "unknown (reserved) bc detect result\n");
 252                cable = EXTCON_CHG_USB_SDP;
 253        }
 254
 255no_vbus:
 256        extcon_set_state_sync(info->edev, info->previous_cable, false);
 257        if (info->previous_cable == EXTCON_CHG_USB_SDP)
 258                extcon_set_state_sync(info->edev, EXTCON_USB, false);
 259
 260        if (vbus_attach) {
 261                extcon_set_state_sync(info->edev, cable, vbus_attach);
 262                if (cable == EXTCON_CHG_USB_SDP)
 263                        extcon_set_state_sync(info->edev, EXTCON_USB,
 264                                                vbus_attach);
 265
 266                info->previous_cable = cable;
 267        }
 268
 269        if (info->role_sw && info->vbus_attach != vbus_attach) {
 270                info->vbus_attach = vbus_attach;
 271                /* Setting the role can take a while */
 272                queue_work(system_long_wq, &info->role_work);
 273        }
 274
 275        return 0;
 276
 277dev_det_ret:
 278        if (ret < 0)
 279                dev_err(info->dev, "failed to detect BC Mod\n");
 280
 281        return ret;
 282}
 283
 284static int axp288_extcon_id_evt(struct notifier_block *nb,
 285                                unsigned long event, void *param)
 286{
 287        struct axp288_extcon_info *info =
 288                container_of(nb, struct axp288_extcon_info, id_nb);
 289
 290        /* We may not sleep and setting the role can take a while */
 291        queue_work(system_long_wq, &info->role_work);
 292
 293        return NOTIFY_OK;
 294}
 295
 296static irqreturn_t axp288_extcon_isr(int irq, void *data)
 297{
 298        struct axp288_extcon_info *info = data;
 299        int ret;
 300
 301        ret = axp288_handle_chrg_det_event(info);
 302        if (ret < 0)
 303                dev_err(info->dev, "failed to handle the interrupt\n");
 304
 305        return IRQ_HANDLED;
 306}
 307
 308static void axp288_extcon_enable(struct axp288_extcon_info *info)
 309{
 310        regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
 311                                                BC_GLOBAL_RUN, 0);
 312        /* Enable the charger detection logic */
 313        regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
 314                                        BC_GLOBAL_RUN, BC_GLOBAL_RUN);
 315}
 316
 317static void axp288_put_role_sw(void *data)
 318{
 319        struct axp288_extcon_info *info = data;
 320
 321        cancel_work_sync(&info->role_work);
 322        usb_role_switch_put(info->role_sw);
 323}
 324
 325static int axp288_extcon_find_role_sw(struct axp288_extcon_info *info)
 326{
 327        const struct software_node *swnode;
 328        struct fwnode_handle *fwnode;
 329
 330        if (!x86_match_cpu(cherry_trail_cpu_ids))
 331                return 0;
 332
 333        swnode = software_node_find_by_name(NULL, "intel-xhci-usb-sw");
 334        if (!swnode)
 335                return -EPROBE_DEFER;
 336
 337        fwnode = software_node_fwnode(swnode);
 338        info->role_sw = usb_role_switch_find_by_fwnode(fwnode);
 339        fwnode_handle_put(fwnode);
 340
 341        return info->role_sw ? 0 : -EPROBE_DEFER;
 342}
 343
 344static int axp288_extcon_probe(struct platform_device *pdev)
 345{
 346        struct axp288_extcon_info *info;
 347        struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
 348        struct device *dev = &pdev->dev;
 349        struct acpi_device *adev;
 350        int ret, i, pirq;
 351
 352        info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
 353        if (!info)
 354                return -ENOMEM;
 355
 356        info->dev = &pdev->dev;
 357        info->regmap = axp20x->regmap;
 358        info->regmap_irqc = axp20x->regmap_irqc;
 359        info->previous_cable = EXTCON_NONE;
 360        INIT_WORK(&info->role_work, axp288_usb_role_work);
 361        info->id_nb.notifier_call = axp288_extcon_id_evt;
 362
 363        platform_set_drvdata(pdev, info);
 364
 365        ret = axp288_extcon_find_role_sw(info);
 366        if (ret)
 367                return ret;
 368
 369        if (info->role_sw) {
 370                ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
 371                if (ret)
 372                        return ret;
 373
 374                adev = acpi_dev_get_first_match_dev("INT3496", NULL, -1);
 375                if (adev) {
 376                        info->id_extcon = extcon_get_extcon_dev(acpi_dev_name(adev));
 377                        put_device(&adev->dev);
 378                        if (!info->id_extcon)
 379                                return -EPROBE_DEFER;
 380
 381                        dev_info(dev, "controlling USB role\n");
 382                } else {
 383                        dev_info(dev, "controlling USB role based on Vbus presence\n");
 384                }
 385        }
 386
 387        info->vbus_attach = axp288_get_vbus_attach(info);
 388
 389        axp288_extcon_log_rsi(info);
 390
 391        /* Initialize extcon device */
 392        info->edev = devm_extcon_dev_allocate(&pdev->dev,
 393                                              axp288_extcon_cables);
 394        if (IS_ERR(info->edev)) {
 395                dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
 396                return PTR_ERR(info->edev);
 397        }
 398
 399        /* Register extcon device */
 400        ret = devm_extcon_dev_register(&pdev->dev, info->edev);
 401        if (ret) {
 402                dev_err(&pdev->dev, "failed to register extcon device\n");
 403                return ret;
 404        }
 405
 406        for (i = 0; i < EXTCON_IRQ_END; i++) {
 407                pirq = platform_get_irq(pdev, i);
 408                if (pirq < 0)
 409                        return pirq;
 410
 411                info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
 412                if (info->irq[i] < 0) {
 413                        dev_err(&pdev->dev,
 414                                "failed to get virtual interrupt=%d\n", pirq);
 415                        ret = info->irq[i];
 416                        return ret;
 417                }
 418
 419                ret = devm_request_threaded_irq(&pdev->dev, info->irq[i],
 420                                NULL, axp288_extcon_isr,
 421                                IRQF_ONESHOT | IRQF_NO_SUSPEND,
 422                                pdev->name, info);
 423                if (ret) {
 424                        dev_err(&pdev->dev, "failed to request interrupt=%d\n",
 425                                                        info->irq[i]);
 426                        return ret;
 427                }
 428        }
 429
 430        if (info->id_extcon) {
 431                ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
 432                                                        &info->id_nb);
 433                if (ret)
 434                        return ret;
 435        }
 436
 437        /* Make sure the role-sw is set correctly before doing BC detection */
 438        if (info->role_sw) {
 439                queue_work(system_long_wq, &info->role_work);
 440                flush_work(&info->role_work);
 441        }
 442
 443        /* Start charger cable type detection */
 444        axp288_extcon_enable(info);
 445
 446        device_init_wakeup(dev, true);
 447        platform_set_drvdata(pdev, info);
 448
 449        return 0;
 450}
 451
 452static int __maybe_unused axp288_extcon_suspend(struct device *dev)
 453{
 454        struct axp288_extcon_info *info = dev_get_drvdata(dev);
 455
 456        if (device_may_wakeup(dev))
 457                enable_irq_wake(info->irq[VBUS_RISING_IRQ]);
 458
 459        return 0;
 460}
 461
 462static int __maybe_unused axp288_extcon_resume(struct device *dev)
 463{
 464        struct axp288_extcon_info *info = dev_get_drvdata(dev);
 465
 466        /*
 467         * Wakeup when a charger is connected to do charger-type
 468         * connection and generate an extcon event which makes the
 469         * axp288 charger driver set the input current limit.
 470         */
 471        if (device_may_wakeup(dev))
 472                disable_irq_wake(info->irq[VBUS_RISING_IRQ]);
 473
 474        return 0;
 475}
 476
 477static SIMPLE_DEV_PM_OPS(axp288_extcon_pm_ops, axp288_extcon_suspend,
 478                         axp288_extcon_resume);
 479
 480static const struct platform_device_id axp288_extcon_table[] = {
 481        { .name = "axp288_extcon" },
 482        {},
 483};
 484MODULE_DEVICE_TABLE(platform, axp288_extcon_table);
 485
 486static struct platform_driver axp288_extcon_driver = {
 487        .probe = axp288_extcon_probe,
 488        .id_table = axp288_extcon_table,
 489        .driver = {
 490                .name = "axp288_extcon",
 491                .pm = &axp288_extcon_pm_ops,
 492        },
 493};
 494module_platform_driver(axp288_extcon_driver);
 495
 496MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
 497MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
 498MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
 499MODULE_LICENSE("GPL v2");
 500